vue前端自适应布局,一步到位所有自适应 - lgx211 - 博客园

mikel阅读(166)

来源: vue前端自适应布局,一步到位所有自适应 – lgx211 – 博客园

vue前端自适应布局,一步到位所有自适应

页面展示


实现内容

1,左右布局

  • 左侧固定宽带,右侧自适应剩余的宽度。
  • 中间一条分割线,可以拖拉,自适应调整左右侧的宽度。
  • 左侧的高度超长自动出现横向滚动条,左侧宽度超长,自动出现竖向滚动条。

2,上中下布局

  • 最上面的 搜索条件 div 固定占用 100 px 高度,下面的 查询条件 div 固定占用 30 px 高度,最下面的分页固定占用高度,页面剩下的高度自动分配给中间的表格内容。
  • 表格内容高度超过后自动出现竖向滚动条,宽度超出后自动出现横向滚动条。
  • 点击按钮,可以 隐藏/显示 搜索条件 div 里面的内容。
  • 当隐藏 搜索条件 div 里面的内容时,中间表格的高度为:整个页面的高度—操作按钮div的高度—分页div的高度。
  • 当搜索条件 div 里面的内容时,中间表格的高度为:整个页面的高度—搜索条件div的高度—操作按钮div的高度—分页div的高度。

3,分辨率自适应

  • 加载即动态实时计算高度,宽度

实现代码

<template>
  <div class="app-container">
    <div class="left" :style="{ width: leftWidth + 'px' }">
      <div class="right-center-left">
        左边的内容,可以很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长<br />
        1<br />1<br />1<br />1<br />1<br />1<br />1<br />1<br />1<br />1<br />1<br />1<br />1<br />1<br />1<br />1<br />1<br />
        1<br />1<br />1<br />1<br />1<br />1<br />1<br />1<br />1<br />1<br />1<br />1<br />1<br />1<br />1<br />1<br />1<br />
        1<br />1<br />1<br />1<br />1<br />1<br />1<br />1<br />1<br />1<br />1<br />1<br />1<br />1<br />1<br />1<br />1<br />
        1<br />1<br />1<br />1<br />1<br />1<br />1<br />1<br />1<br />1<br />1<br />1<br />1<br />1<br />1<br />1<br />1<br />
        1<br />1<br />1<br />1<br />1<br />1<br />1<br />1<br />1<br />1<br />1<br />1<br />1<br />1<br />1<br />1<br />1<br />
        1<br />1<br />1<br />1<br />1<br />1<br />1<br />1<br />1<br />1<br />1<br />1<br />1<br />1<br />1<br />1<br />1<br />
      </div>
    </div>
    <div class="divider" @mousedown="startDragging"></div>
    <div class="right">
      <div v-if="showDiv1" class="div1">查询条件</div>
      <div class="div2">
        <button @click="toggleDiv1">操作按钮 div1</button>
      </div>
      <div class="div3" :style="{ height: div3Height + 'px' }">
        1<br />1<br />1<br />1<br />1<br />1<br />1<br />1<br />1<br />1<br />1<br />1<br />1<br />1<br />1<br />1<br />1<br />
        1<br />1<br />1<br />1<br />1<br />1<br />1<br />1<br />1<br />1<br />1<br />1<br />1<br />1<br />1<br />1<br />1<br />
        1<br />1<br />1<br />1<br />1<br />1<br />1<br />1<br />1<br />1<br />1<br />1<br />1<br />1<br />1<br />1<br />1<br />
        1<br />1<br />1<br />1<br />1<br />1<br />1<br />1<br />1<br />1<br />1<br />1<br />1<br />1<br />1<br />1<br />1<br />
        1<br />1<br />1<br />1<br />1<br />1<br />1<br />1<br />1<br />1<br />1<br />1<br />1<br />1<br />1<br />1<br />1<br />
        1<br />1<br />1<br />1<br />1<br />1<br />1<br />1<br />1<br />1<br />1<br />1<br />1<br />1<br />1<br />1<br />1<br />
      </div>
      <div class="div4">分页</div>
    </div>
  </div>
</template>

<script>
export default {
  name: "AppContainer",
  data() {
    return {
      isDragging: false,
      leftWidth: 200,
      showDiv1: true
    };
  },
  computed: {
    div3Height() {
      const totalHeight = window.innerHeight;
      const div2Height = 30;
      const div4Height = 30;
      const div1Height = this.showDiv1 ? 100 : 0;

      // 计算 div3 的高度
      return totalHeight - div2Height - div4Height - div1Height;
    }
  },
  methods: {
    startDragging(e) {
      this.isDragging = true;
      document.addEventListener("mousemove", this.onDrag);
      document.addEventListener("mouseup", this.stopDragging);
    },
    onDrag(e) {
      if (this.isDragging) {
        const minWidth = 50;
        const maxWidth = window.innerWidth - 50;
        const newLeftWidth = e.clientX;

        if (newLeftWidth > minWidth && newLeftWidth < maxWidth) {
          this.leftWidth = newLeftWidth;
        }
      }
    },
    stopDragging() {
      this.isDragging = false;
      document.removeEventListener("mousemove", this.onDrag);
      document.removeEventListener("mouseup", this.stopDragging);
    },
    toggleDiv1() {
      this.showDiv1 = !this.showDiv1;
    }
  }
};
</script>

<style scoped>
.app-container {
  display: flex;
  height: 100vh;
  overflow: hidden;
}

.left {
  overflow-x: auto;
  overflow-y: auto;
  white-space: nowrap;
  min-width: 90px;
}

.divider {
  width: 5px;
  cursor: ew-resize;
  background-color: #ccc;
}

.right {
  display: flex;
  flex-direction: column;
  height: 100%;
  flex: 1; /* 自动填满剩余宽度 */
}

.div1 {
  height: 100px;
  background-color: #f0f0f0;
}

.div2 {
  height: 30px;
  background-color: #ddd;
}

.div3 {
  overflow-x: auto; /* 添加横向滚动条 */
  overflow-y: auto; /* 添加纵向滚动条 */
  background-color: #f5f5f5;
}

.div4 {
  height: 200px;
  background-color: #ccc;
}
</style>

实现感想

这个功能,从毕业就开始思索,直到八年后的今天成熟完善,真是艰辛也是很不容易。目前市面上没有见过有人实现,很多人都是只言片语的,基本复制下来,无法达到效果。我这个一键复制到自己的项目,就能实现了,中间的坎坷不平,到了完全实现的这一刻,才觉得激动不已。

无任何坑,也没有任何额外的引入,一个普普通通,最简单的vue页面,布局建好,里面的内容就可以自己随意发挥了。

未觉池塘春草梦,阶前梧叶已秋声。记录激动时刻,也造福后来人。

记录Layui-select中的搜索下拉框lay-search相关的使用(对于初次使用是真的难顶)-CSDN博客

mikel阅读(179)

来源: 记录Layui-select中的搜索下拉框lay-search相关的使用(对于初次使用是真的难顶)-CSDN博客

对于一个后端开发人员来说,常用的easyUI,miniUI,bootstrap,layUI等前端整合式的UI框架能很好的提高页面美观程度以及开发效率

我个人在开发中,使用过miniUI,bootstrap,layUI,其中miniUI是在公司开发时公司要求学习使用的,个人写东西还是偏重于layUI和bootstrap,毕竟看着漂亮点.

本次记录的内容是lay-search这个属性在使用时的问题(可能我能力不够,卡了我两个多小时),因为浪费了过多时间,所以记录一下,在我之前有很多类似的文章,但是都很零碎,没有一个比较全的(搜索水平有限)

首先,lay-search这个属性的作用是:将一个<select>标签初始化为可以输入和选择内容,你输入的文字可以自动匹配,类似百度的搜索.

下面首先贴代码:

<form class=”layui-form”>
<div class=”layui-form-item”>
<div class=”layui-inline col-sm-12″>
<select id=”TemplateOptions” name=”effectsType” class=”layui-select” lay-verify=”required” lay-filter=”selectTemplate” lay-search>
<option value=””>实体要素内容选择</option>
</select>
</div>
</div>
</form>
这里,有个问题就出来,很多人写了select标签,也写了lay-search,但是就是不能选择输入,什么原因?

这里就是第一个问题,select中的lay-search这个属性,必须在layui-form标签中才可以生效

如果说没有layui-form包裹,那么你的select仅仅只是一个正常的<select>下拉选择

然后我们option里面的内容,我选择的方式是页面加载就进行查询渲染,不和数据库做实时交互,毕竟数据量不大

我的思路是页面加载–>初始化Layui–>调用Ajax方法请求数据–>在success方法中组装option标签参数–>使用JQuery进行渲染

是不是上面这个方法看着没啥毛病?对啊,确实是没毛病啊,然后我就卡在这里了,<select>标签里面永远没有我的数据,不管我是用id渲染还是用name获取元素去渲染,都没有数据,不管我用append还是add都一样,最后在layui的文档中发现了,让你给这个lay-search以后重新给了数据以后,需要调用layui.form这个对象进行一次调用,进行重新渲染.否则不会生效.

所以我就这样写了:

layui.use([‘element’,’form’], function(){
var $ = layui.JQuery
,element = layui.element; //Tab的切换功能,切换事件监听等,需要依赖element模块
var form = layui.form;
$.ajax({
type: “POST”,
url: prefix + “/*****”,
data: null,
dataType: ‘json’,
success: function(result) {
var datas = “”;
var data = result.data;
for(var i = 0;i<data.length;i++){
var text = data[i].rtContent.replace(/<\/?.+?>/g,””).replace(/ /g,””);
datas += “<option value='”+text+”‘>” + text +”</option>”;
}
$(‘#TemplateOptions’).append(datas);
form.render(‘select’);
},
error: function(error) {
$.modal.alertWarning(“获取模版数据失败”);
}
});
});
在layui这个js模块初始化的时候顺便获取了下数据这里就是调用重新渲染,括号里面写select就是只重新渲染了当前页面的所有select,如果什么都不写就是默认全页面重新渲染

最后,我希望在我选择以后,这个数据要放在其他的地方,或者弹出来等,然后我就很耿直的去和以前纯js开发那样,直接给option里面去拼接了onchange事件,这样的话,选择完不就可以直接拿值了吗?

嘿嘿……怎么可能满足你.事实是,拼接了onchange事件以后,并没有啥卵用,不会其任何作用,你打开他的页面结构你会发现这鬼地方哪来的所谓的option????所以你绑定的东西.并没有啥卵用,那怎么办呢?

 

layUI,bootstrap等UI框架都需要做的一件事,就是事件监听.你需要自己去初始化一个监听事件,这里我并没有了解过为什么这样,听前端朋友说这样写会好一点,安全一点

form.on(‘select(selectTemplate)’,function(data){
console.info(data.value);
})
在刚刚的ajax获取数据的代码下面,layui初始化方法的里面,我加上了这么一行,这样以后,你就可以在你下拉框中的数据确定变动以后,获取到这里的数据.

 

这次的记录就这些,希望以后有人使用这个东西,能花费很少的时间完工
————————————————

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

原文链接:https://blog.csdn.net/Zachariahs/article/details/99823417

使用layui 表格 下拉框 的实现_layui 下拉框-CSDN博客

mikel阅读(155)

1: 想要实现的 效果 :

2:使用 templet 模版 (代码如下 前提 :接口正常 能获取到数据并展示)

table.render({
elem: ‘#munu-table’
, url: ‘xxx.com/xxxx’
,id:”table”

,page:{
limit: 15
, limits: [15, 20, 30, 40, 50, 100, 150, 200, 250, 300, 400, 500, 800, 1000]
// ,curr:localStorage.getItem(‘spgl_curr’) ? localStorage.getItem(‘spgl_curr’) : 1
}
,totalRow: true //开启合计行
, cols: [[
{type: ‘checkbox’, width: 40, sort: true},
{field: ‘id’, width: 140, title: ‘id’,hide:true},
{field: ‘image’, width: 70, title: ‘图片’,templet:function (d) {
return ‘<img class=”productimages” width=”16px” src=’+d.image+’>’;
}},
{field: ‘list_number’, width: 100, title: ‘清单编号’},
{field: ‘storage_id’, width: 100, title: ‘入库编号’},
{field: ‘batch_number’, width: 80, title: ‘批次号’},
{field: ‘sku’, width: 80, title: ‘sku’},
{field: ‘storage_quantity’, width: 90, title: ‘入库数量’},
{field: ‘sample_quantity’, width: 90, title: ‘留样数量’},
{field: ‘actual_packing_quantity’, width: 115, title: ‘实际装箱数量’},
{field: ‘purchaser’, width: 80, title: ‘采购人’},

{field: ‘outbound_date’, width: 100, title: ‘出库日期’},

{field: ‘pending_days’, width: 100, title: ‘待处理天数’},
{field: ‘processing_status’, width: 100, title: ‘处理进度’},
{field: ‘processor’, width: 80, title: ‘处理人’},
{field: ‘created_time’, width: 100, title: ‘创建时间’},
{field: ‘modified_time’, width: 100, title: ‘修改时间’},

{
field: ‘processing_method’,
title: ‘处理方式’,
align: ‘center’,
width: 200,
templet: function (d) {
var selectHtml = ‘<select name=”paid” class=”sel_xlk” lay-filter=”stateSelect” lay-verify=”required” data-state=”‘ + d.paid + ‘” data-value=”‘ + d.id + ‘” >’ +
‘ <option value=”签补协议”>签补协议</option>’ +
‘ <option value=”等待供应商补发数量”>等待供应商补发数量</option>’ +
‘ <option value=”报损”>报损</option>’ +
‘ <option value=”解除协议”>解除协议</option>’ +
‘ </select>’;

return d.processing_method + selectHtml;
}
},

]]
, done: function(res, curr, count) {
console.log(res); // 在控制台打印获取到的数据

$(“.layui-table-body”).css(‘overflow’,’visible’);
$(“.layui-table-box”).css(‘overflow’,’visible’);
$(“.layui-table-view”).css(‘overflow’,’visible’);

var tableElem = this.elem.next(‘.layui-table-view’);
count || tableElem.find(‘.layui-table-header’).css(‘overflow’, ‘auto’);
layui.each(tableElem.find(‘select[name=”paid”]’), function (index, item) {
var elem = $(item);
elem.val(elem.data(‘state’)).parents(‘div.layui-table-cell’).css(‘overflow’, ‘visible’);
});
form.render();//刷新表单

}

});
注意:templet 的用法 在手册中 只是简单的举了几个例子 。

文档:参考链接

3:注意done 里面的回调 :

done: function(res, curr, count) {
console.log(res); // 在控制台打印获取到的数据

$(“.layui-table-body”).css(‘overflow’,’visible’);
$(“.layui-table-box”).css(‘overflow’,’visible’);
$(“.layui-table-view”).css(‘overflow’,’visible’);

var tableElem = this.elem.next(‘.layui-table-view’);
count || tableElem.find(‘.layui-table-header’).css(‘overflow’, ‘auto’);
layui.each(tableElem.find(‘select[name=”paid”]’), function (index, item) {
var elem = $(item);
elem.val(elem.data(‘state’)).parents(‘div.layui-table-cell’).css(‘overflow’, ‘visible’);
});
form.render();//刷新表单

}
layui.each(tableElem.find(‘select[name=”paid”]’) pid 是 field: ‘pid’, 的值。

上面的代码 加上后才能正常的展示 下拉 数据 如果没有 这段代码 点击 下拉图标 不会起作用。

4:修改数据:

form.on(‘select(stateSelect)’, function (data) {//修改类型
let id = data.elem.dataset.value; //当前数据的id
let processing_method = data.elem.value; //当前字段变化的值
// 传值:表单变化后的值传递到后台数据库进行实时修改,例如,根据id修改这条数据的状态。

console.log(id);
console.log(processing_method);

$.ajax({
type: ‘post’,
url:’xxx.com/xxx’, // ajax请求路径
data: {
id: id,
processing_method: processing_method
},
success: function(data){

var data = JSON.parse(data)
layer.msg(data.msg);

// layer.msg(‘修改成功’);

// console.log(data);
//执行重载
//table.reload(‘bizInvoiceTable’);
//window.location.href = Feng.ctxPath + ‘/bizInvoice’
}
});
});

总结: 我遇到最多的问题就是 选择 下拉图标的时候 不能展示下拉的数据 。 所以一定要注意 红色字段。

未解决的问题 : 下拉框会被覆盖 f12 能高亮度找到 但是被表格下面的滑块 覆盖了 。

有滑块的时候 会出现 每列的线格子 会与 tittle 对不齐
————————————————

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

原文链接:https://blog.csdn.net/qq_51110402/article/details/132511932

layui实现table添加行功能 table里有select可选择可编辑 并且与form表单一起提交数据保存_layuitable 可编辑,有下拉框和输入框-CSDN博客

mikel阅读(182)

来源: layui实现table添加行功能 table里有select可选择可编辑 并且与form表单一起提交数据保存_layuitable 可编辑,有下拉框和输入框-CSDN博客

这个例子中的下拉框是 可选择 并且 可输入的 还有一个下拉框的点击事件 选择一个值的时候 带出后面几列的值
<%@ page language=”java” import=”java.util.*” pageEncoding=”UTF-8″%>
<%@ include file=”/WEB-INF/page/public/tag.jsp”%>
<!DOCTYPE HTML PUBLIC “-//W3C//DTD HTML 4.01 Transitional//EN”>
<html>
<head>
<meta charset=”utf-8″>
<title><jsp:include page=”/WEB-INF/page/public/top_title.jsp”/></title>
<meta name=”renderer” content=”webkit”>
<meta http-equiv=”X-UA-Compatible” content=”IE=edge,chrome=1″>
<meta name=”viewport” content=”width=device-width, initial-scale=1, maximum-scale=1″>
<meta name=”apple-mobile-web-app-status-bar-style” content=”black”>
<meta name=”apple-mobile-web-app-capable” content=”yes”>
<meta name=”format-detection” content=”telephone=no”>
<link rel=”stylesheet” href=”${contextPath}/statics/css/layui.css” media=”all”/>
<style>
        /* 防止下拉框的下拉列表被隐藏—必须设置— 此样式和表格的样式有冲突 如果表格列数太多 会出现错乱的情况 目前我的解决方法是忽略下拉框的美化渲染 <select lay-ignore> */
        .layui-table-cell {
            overflow: visible;
        }
        .layui-table-box {
            overflow: visible;
        }
        .layui-table-body {
            overflow: visible;
        }
        /* 设置下拉框的高度与表格单元相同 */
        td .layui-form-select{
margin-top: -10px;
margin-left: -15px;
margin-right: -15px;
}
    </style>
</head>
<body class=”childrenBody”>
<div  style=”padding:15px;”>
<blockquote class=”layui-elem-quote”>
<span class=”layui-breadcrumb” style=”visibility: visible;”>
<a href=”main.html”>首页</a>
<span lay-separator=””>/</span>
<a href=”${contextPath}/storage/toList”>物料入库</a>
<span lay-separator=””>/</span>
<a><cite>物料入库新增</cite></a> </span>
</blockquote>
<form class=”layui-form” id=”fromId” action=”#”>
  <fieldset class=”layui-elem-field”>
<div style=”padding-top:25px;” class=”layui-field-box”>
  <div class=”layui-form-item”>
<label class=”layui-form-label”>入库单编号</label>
<div class=”layui-input-inline” style=”width:13%”>
  <input type=”text” name=”storagecode” placeholder=”请输入” class=”layui-input” lay-verify=”required”>
</div>
<label class=”layui-form-label”>入库人</label>
<div class=”layui-input-inline” style=”width:13%”>
<input type=”text” name=”storageuser” placeholder=”请输入” class=”layui-input”>
</div>
<label class=”layui-form-label”>仓库</label>
<div class=”layui-input-inline” style=”width:13%”>
<input type=”text” name=”warehouseid” placeholder=”请输入” class=”layui-input”>
</div>
<label class=”layui-form-label”>金额</label>
<div class=”layui-input-inline” style=”width:13%”>
<input type=”text” name=”money” placeholder=”请输入” class=”layui-input”>
</div>
  </div>
  <div class=”layui-form-item”>
    <label class=”layui-form-label”>入库日期</label>
    <div class=”layui-input-inline” style=”width:13%”>
    <input type=”text” name=”storagetime” placeholder=”请选择” class=”layui-input” id=”date”>
    </div>
    <label class=”layui-form-label”>制单人</label>
<div class=”layui-input-inline” style=”width:13%”>
<input type=”text” name=”documentmaker” placeholder=”请输入” class=”layui-input”>
</div>
<label class=”layui-form-label”>供应商</label>
<div class=”layui-input-inline” style=”width:13%”>
<input type=”text” name=”supplier” placeholder=”请输入” class=”layui-input”>
</div>
</div>
</div>
  </fieldset>
  <script type=”text/html” id=”selectTool”>
 <select name=”selectDemo” lay-filter=”selectDemo” lay-search>
<option value=””>请选择或输入</option>
        {{# layui.each(${selectByExample}, function(index, item){ }}
        <option>{{ item.materialcode }}</option>
        {{# }); }}
    </select>
</script>
  <script type=”text/html” id=”toolbarDemo”>
  <div align=”right” class=”layui-btn-container”>
  <button id=”addTable” class=”layui-btn layui-btn-sm layui-btn-normal” lay-event=”add”>添加行</button>
  </div>
</script>
  <script type=”text/html” id=”bar”>
<a class=”layui-btn layui-btn-danger layui-btn-xs” lay-event=”del”>删除</a>
    </script>
<table id=”demo” lay-filter=”tableFilter”></table>
<div class=”layui-form-item”  style=”margin-top: 30px;text-align: center;”>
    <button class=”layui-btn” lay-submit=”” lay-filter=”*”>保存</button>
    <a href=”${contextPath}/storage/toList” class=”layui-btn layui-btn-primary”>返回</a>
</div>
</form>
</div>
<script type=”text/JavaScript” src=”${contextPath}/statics/layui.js”></script>
<script>
layui.use([‘laydate’,’table’,’form’,’JQuery’], function(){
  var table = layui.table,
      form = layui.form,
    laydate = layui.laydate,
    $ = layui.JQuery;
  //时间控件
  laydate.render({
  elem: ‘#date’ //指定元素
  });
  //下拉框监听事件
  form.on(‘select(selectDemo)’, function(data){
//这里是当选择一个下拉选项的时候 把选择的值赋值给表格的当前行的缓存数据 否则提交到后台的时候下拉框的值是空的
var elem = data.othis.parents(‘tr’);
  var dataindex = elem.attr(“data-index”);
  $.each(tabledata,function(index,value){
        if(value.LAY_TABLE_INDEX==dataindex){
        value.materialcode = data.value;
        }
      });
      //这个是根据下拉框选的值 查询后台 带出后面几列的数据并赋值给页面 没有需要的同学忽略掉即可
     if(data.value){ $.ajax({
url:”${contextPath}/storage/toSelect”,
async:true,
type:”post”,
data:{“materialcode”:data.value},
success:function(data){
if(typeof(data) == ‘string’){
data = JSON.parse(data)
}
//给页面赋值
elem.find(“td[data-field=’materialname’]”).children().html(data.data.materialname);
elem.find(“td[data-field=’specifications’]”).children().html(data.data.specifications);
elem.find(“td[data-field=’warehouseid’]”).children().html(data.data.warehouseid);
elem.find(“td[data-field=’warningnumber’]”).children().html(data.data.warningnumber);
elem.find(“td[data-field=’topwarning’]”).children().html(data.data.topwarning);
elem.find(“td[data-field=’unitprice’]”).children().html(data.data.unitprice);
//给表格缓存赋值
$.each(tabledata,function(index,value){
      if(value.LAY_TABLE_INDEX==dataindex){
      value.materialname = data.data.materialname;
      value.specifications = data.data.specifications;
      value.warehouseid = data.data.warehouseid
      value.warningnumber = data.data.warningnumber;
      value.topwarning = data.data.topwarning
      value.unitprice = data.data.unitprice;
      }
     });
}
    });
     }
});
//第一个实例 加载表格
var tableIns = table.render({
 elem: ‘#demo’
 ,toolbar: ‘#toolbarDemo’
 ,defaultToolbar:[]
 ,limit:100
 ,cols: [[ //表头
    {field: ‘materialcode’, title: ‘物料编号’,templet: ‘#selectTool’}
   ,{field: ‘materialname’, title: ‘物料名称’,edit: ‘text’}
   ,{field: ‘number’, title: ‘数量’,edit: ‘text’}
   ,{field: ‘specifications’, title: ‘规格’,edit: ‘text’}
   ,{field: ‘warehouseid’, title: ‘仓库’,edit: ‘text’}
   ,{field: ‘warningnumber’, title: ‘最低库存’,edit: ‘text’}
   ,{field: ‘topwarning’, title: ‘最高库存’,edit: ‘text’}
   ,{field: ‘unitprice’, title: ‘单价’,edit: ‘text’}
   ,{field: ‘subtotal’, title: ‘小计’}
   ,{title: ‘操作’,align:’center’, toolbar: ‘#bar’}
 ]]
,data:[{  “materialcode”: “”
      ,”materialname”: “”
      ,”number”: “”
      ,”specifications”: “”
      ,”warehouseid”: “”
      ,”warningnumber”: “”
      ,”topwarning”: “”
      ,”unitprice”: “”
      ,”subtotal”: “”
    }]
,done: function(res, curr, count){
    //如果是异步请求数据方式,res即为你接口返回的信息。
    //如果是直接赋值的方式,res即为:{data: [], count: 99} data为当前页数据、count为数据总长度
    tabledata = res.data;
    //去掉下拉框的失焦事件 否则在下拉框里输入值 失焦后变回下拉选项里的值了 没有需要的同学忽略掉即可
    $(‘.layui-form-select’).find(‘input’).unbind(“blur”);
    //这里是表格重载的时候 回显下拉框的数据
    $(‘tr’).each(function(e){
    var $cr=$(this);
    var dataindex = $cr.attr(“data-index”);
        $.each(tabledata,function(index,value){
        if(value.LAY_TABLE_INDEX==dataindex){
        $cr.find(‘input’).val(value.materialcode);
        }
       });
      });
        //这里是下拉框输入值改变时触发 给表格缓存赋值 没有需要的同学忽略掉即可
        $(‘.layui-form-select’).find(‘input’).on(“change”,function(e){
        var $cr=$(e.target);
        console.log($cr);
        var dataindex = $cr.parents(‘tr’).attr(“data-index”);
        var selectdata = $cr.val();
            $.each(tabledata,function(index,value){
            if(value.LAY_TABLE_INDEX==dataindex){
            value.materialcode = selectdata;
            }
           });
        });
        //这里是数量的输入事件 计算小计用的 小计 = 数量 X 单价
        var numberelem = $(‘.layui-table-main’).find(“td[data-field=’number’]”);
        var unitpriceelem = $(‘.layui-table-main’).find(“td[data-field=’unitprice’]”);
        numberelem.on(“input”,function(e){
        var $cr=$(e.target);
        var dataindex = $cr.parents(‘tr’).attr(“data-index”);
        var unitprice = $cr.parents(‘tr’).find(“td[data-field=’unitprice’]”).children().html();
        var sub = unitprice*e.target.value;
        $cr.parents(‘tr’).find(“td[data-field=’subtotal’]”).children().html(sub);
        $.each(tabledata,function(index,value){
            if(value.LAY_TABLE_INDEX==dataindex){
            value.subtotal = sub;
            }
           });
        });
        //这里是单价的输入事件 计算小计用的 小计 = 数量 X 单价
        unitpriceelem.on(“input”,function(e){
        var $cr=$(e.target);
        var dataindex = $cr.parents(‘tr’).attr(“data-index”);
        var number = $cr.parents(‘tr’).find(“td[data-field=’number’]”).children().html();
        var sub = number*e.target.value;
        $cr.parents(‘tr’).find(“td[data-field=’subtotal’]”).children().html(sub);
        $.each(tabledata,function(index,value){
            if(value.LAY_TABLE_INDEX==dataindex){
            value.subtotal = sub;
            }
           });
        });
  }
});
var tabledata;
//监听工具条删除按钮
   table.on(‘tool(tableFilter)’, function(obj){
    if(obj.event === ‘del’){
    obj.del();
      };
     }
  );
 //头工具栏添加按钮事件
  table.on(‘toolbar(tableFilter)’, function(obj){
if(obj.event === ‘add’){
tabledata.push({
  “materialcode”: “”
        ,”materialname”: “”
        ,”number”: “”
        ,”specifications”: “”
        ,”warehouseid”: “”
        ,”warningnumber”: “”
    ,”topwarning”: “”
        ,”unitprice”: “”
        ,”subtotal”: “”
          })
      table.reload(‘demo’, {
    data: tabledata
  });
    };
  });
 //提交数据到后台保存
  form.on(‘submit(*)’, function(data){
// console.log(data.elem) //被执行事件的元素DOM对象,一般为button对象
// console.log(data.form) //被执行提交的form对象,一般在存在form标签时才会返回
//  console.log(data.field) //当前容器的全部表单字段,名值对形式:{name: value}
//  console.log(tabledata) //当前容器的全部表单字段,名值对形式:{name: value}
  $.ajax({
url:”${contextPath}/storage/toSave”,
async:true,
type:”post”,
data:$(data.form).serialize()+’&tabledata=’+JSON.stringify(tabledata),
success:function(data){
if(typeof(data) == ‘string’){
data = JSON.parse(data)
}
if(data.code == 0){
layer.msg(data.msg);
window.location.href=”${contextPath}/storage/toList”;
}else{
layer.msg(data.msg);
}
}
      });
  return false; //阻止表单跳转。如果需要表单跳转,去掉这段即可。
});
});
</script>
</body>
</html>
————————————————
                            版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/qq_26814945/article/details/83275765

.NET 权限工作流框架 TOP 榜 - 小码编匠 - 博客园

mikel阅读(142)

来源: .NET 权限工作流框架 TOP 榜 – 小码编匠 – 博客园

前言

.NET权限管理及快速开发框架、最好用的权限工作流系统。

基于经典领域驱动设计的权限管理及快速开发框架,源于Martin Fowler企业级应用开发思想及最新技术组合(SQLSugar、EF、Quartz、AutoFac、WebAPI、Swagger、Mock、NUnit、Vue2/3、Element-ui/plus、IdentityServer等)。已成功在docker/jenkins中实施。

核心模块包括:组织机构、角色用户、权限授权、表单设计、工作流等。

它的架构精良易于扩展,是中小企业的首选。

版本说明

1、主分支main运行环境默认为.NET SDK 6.0,支持.NET未来版本,需要.NET SDK 4.0/4.5开发环境的同学请查看本项目4.0分支,已停止维护。

2、目前OpenAuth.Net以全部开源的方式向大众开放,对于有经验的开发者,官方文档足以满足日常开发。为了能让项目走的更远,特推出基于vue2 + element-ui /vue3 + element-plus的单页面应用程序,即企业版/高级版OpenAuth.Pro

开源地址:http://demo.openauth.net.cn:1802

3、该版本是一套后端基于OpenAuth.WebAPI接口,前端基于vue-element-admin,采用VUE全家桶(VUE+VUEX+VUE-ROUTER)单页面SPA开发的管理后台。

预览地址:http://demo.openauth.net.cn:1803

另外 企业版包含一套基于有赞Vant+Vue3的移动端界面。

预览地址:http://demo.openauth.net.cn:1804

核心看点

  • 同时支持EntityFramework、SQLSugar两款最流行的ORM框架
  • 符合国情的RBAC权限体系。超强的自定义权限控制功能,可灵活配置用户、角色可访问的数据权限。
  • 完整的字段权限控制,可以控制字段可见及API是否返回字段值
  • 可拖拽的表单设计。详情:可拖拽表单
  • 可视化流程设计
  • 全网最好用的打印解决方案。详情:智能打印
  • 基于Quartz.Net的定时任务控制,可随时启/停,可视化配置Cron表达式功能
  • 基于CodeSmith的代码生成功能,可快速生成带有头/明细结构的页面
  • 支持SQLServer、mysql、Oracle、PostgreSql数据库,理论上支持所有数据库
  • 集成IdentityServer4,实现基于OAuth2的登录体系
  • 建立三方对接规范,已有系统可以无缝对接流程引擎
  • 前端采用 vue + layui + element-ui + ztree + gooflow + leipiformdesign
  • 后端采用 .NET Core +EF core+ autofac + quartz +IdentityServer4 + nunit + swagger
  • 设计工具 PowerDesigner + Enterprise Architect

项目截图

流程中心

表单设计

数据权限

仓储中心

项目经验

教科书级的分层思想,哪怕苛刻的你阅读的是大神级精典大作(如:《企业应用架构模式》《重构与模式》《ASP.NET设计模式》等),你也可以参考本项目。不信?有图为证,Resharper自动生成的项目引用关系,毫无PS痕迹!

官方地址

  • 网站:http://www.openauth.net.cn
  • 文档:http://doc.openauth.net.cn
  • 项目:https://gitee.com/dotnetchina/OpenAuth.Net

如果觉得这篇文章对你有用,欢迎加入微信公众号 [DotNet技术匠] 社区,与其他热爱技术的同行交流心得,共同成长。

.NET 窗口/屏幕截图 - 唐宋元明清2188 - 博客园

mikel阅读(161)

来源: .NET 窗口/屏幕截图 – 唐宋元明清2188 – 博客园

图像采集源除了显示控件(上一篇《.NET 控件转图片》有介绍从界面控件转图片),更多的是窗口以及屏幕。

窗口截图最常用的方法是GDI,直接上Demo吧:

复制代码
 1         private void GdiCaptureButton_OnClick(object sender, RoutedEventArgs e)
 2         {
 3             var bitmap = CaptureScreen();
 4             CaptureImage.Source = ConvertBitmapToBitmapSource(bitmap);
 5         }
 6         /// <summary>
 7         /// 截图屏幕
 8         /// </summary>
 9         /// <returns></returns>
10         public static Bitmap CaptureScreen()
11         {
12             IntPtr desktopWindow = GetDesktopWindow();
13             //获取窗口位置大小
14             GetWindowRect(desktopWindow, out var lpRect);
15             return CaptureByGdi(desktopWindow, 0d, 0d, lpRect.Width, lpRect.Height);
16         }
17         private BitmapSource ConvertBitmapToBitmapSource(Bitmap bitmap)
18         {
19             using MemoryStream memoryStream = new MemoryStream();
20             // 将 System.Drawing.Bitmap 保存到内存流中
21             bitmap.Save(memoryStream, System.Drawing.Imaging.ImageFormat.Png);
22             // 重置内存流的指针到开头
23             memoryStream.Seek(0, SeekOrigin.Begin);
24 
25             // 创建 BitmapImage 对象并从内存流中加载图像
26             BitmapImage bitmapImage = new BitmapImage();
27             bitmapImage.BeginInit();
28             bitmapImage.StreamSource = memoryStream;
29             bitmapImage.CacheOption = BitmapCacheOption.OnLoad;
30             bitmapImage.EndInit();
31             // 确保内存流不会被回收
32             bitmapImage.Freeze();
33             return bitmapImage;
34         }
35         /// <summary>
36         /// 截图窗口/屏幕
37         /// </summary>
38         /// <param name="windowIntPtr">窗口句柄(窗口或者桌面)</param>
39         /// <param name="left">水平坐标</param>
40         /// <param name="top">竖直坐标</param>
41         /// <param name="width">宽度</param>
42         /// <param name="height">高度</param>
43         /// <returns></returns>
44         private static Bitmap CaptureByGdi(IntPtr windowIntPtr, double left, double top, double width, double height)
45         {
46             IntPtr windowDc = GetWindowDC(windowIntPtr);
47             IntPtr compatibleDc = CreateCompatibleDC(windowDc);
48             IntPtr compatibleBitmap = CreateCompatibleBitmap(windowDc, (int)width, (int)height);
49             IntPtr bitmapObj = SelectObject(compatibleDc, compatibleBitmap);
50             BitBlt(compatibleDc, 0, 0, (int)width, (int)height, windowDc, (int)left, (int)top, CopyPixelOperation.SourceCopy);
51             Bitmap bitmap = System.Drawing.Image.FromHbitmap(compatibleBitmap);
52             //释放
53             SelectObject(compatibleDc, bitmapObj);
54             DeleteObject(compatibleBitmap);
55             DeleteDC(compatibleDc);
56             ReleaseDC(windowIntPtr, windowDc);
57             return bitmap;
58         }
复制代码

根据user32.dll下拿到的桌面信息-句柄获取桌面窗口的设备上下文,再以设备上下文分别创建内存设备上下文、设备位图句柄

复制代码
 1 BOOL BitBlt(
 2     HDC   hdcDest,  // 目标设备上下文
 3     int   nXDest,   // 目标起始x坐标
 4     int   nYDest,   // 目标起始y坐标
 5     int   nWidth,   // 宽度(像素)
 6     int   nHeight,  // 高度(像素)
 7     HDC   hdcSrc,   // 源设备上下文
 8     int   nXSrc,    // 源起始x坐标
 9     int   nYSrc,    // 源起始y坐标
10     DWORD dwRop    // 操作码(如CopyPixelOperation.SourceCopy)
11 );
复制代码

图像位块传输BitBlt是最关键的函数,Gdi提供用于在设备上下文之间进行位图块的传输,从原设备上下文复现位图到创建的设备上下文

另外,与Bitblt差不多的还有StretchBlt,StretchBlt也是复制图像,但可以同时对图像进行拉伸或者缩小,需要缩略图可以用这个方法

然后以设备位图句柄输出一个位图System.Drawing.Bitmap,使用到的User32、Gdi32函数:

 View Code

还有一种比较简单的方法Graphics.CopyFromScreen,看看调用DEMO:

复制代码
 1         private void GraphicsCaptureButton_OnClick(object sender, RoutedEventArgs e)
 2         {
 3             var image = CaptureScreen1();
 4             CaptureImage.Source = ConvertBitmapToBitmapSource(image);
 5         }
 6         /// <summary>
 7         /// 截图屏幕
 8         /// </summary>
 9         /// <returns></returns>
10         public static Bitmap CaptureScreen1()
11         {
12             IntPtr desktopWindow = GetDesktopWindow();
13             //获取窗口位置大小
14             GetWindowRect(desktopWindow, out var lpRect);
15             return CaptureScreenByGraphics(0, 0, lpRect.Width, lpRect.Height);
16         }
17         /// <summary>
18         /// 截图屏幕
19         /// </summary>
20         /// <param name="x">x坐标</param>
21         /// <param name="y">y坐标</param>
22         /// <param name="width">截取的宽度</param>
23         /// <param name="height">截取的高度</param>
24         /// <returns></returns>
25         public static Bitmap CaptureScreenByGraphics(int x, int y, int width, int height)
26         {
27             var bitmap = new Bitmap(width, height);
28             using var graphics = Graphics.FromImage(bitmap);
29             graphics.CopyFromScreen(x, y, 0, 0, new System.Drawing.Size(width, height), CopyPixelOperation.SourceCopy);
30             return bitmap;
31         }
复制代码

Graphics.CopyFromScreen调用简单了很多,与GDI有什么区别?

Graphics.CopyFromScreen内部也是通过GDI.BitBlt来完成屏幕捕获的,封装了下提供更高级别、易胜的API。

测试了下,第一种方法Gdi32性能比Graphics.CopyFromScreen性能略微好一点,冷启动时更明显点,试了2次耗时大概少个10多ms。

所以对于一般应用场景,使用 Graphics.CopyFromScreen 就足够了,但如果你需要更高的控制权和性能优化,建议使用 Gdi32.BitBlt

kybs00/CaptureImageDemo (github.com)

C# 网络编程:.NET 开发者的核心技能 - 小码编匠 - 博客园

mikel阅读(146)

来源: C# 网络编程:.NET 开发者的核心技能 – 小码编匠 – 博客园

前言

数字化时代,网络编程已成为软件开发中不可或缺的一环,尤其对于 .NET 开发者而言,掌握 C# 中的网络编程技巧是迈向更高层次的必经之路。无论是构建高性能的 Web 应用,还是实现复杂的分布式系统,网络编程都是支撑这一切的基石。

本篇主要为 .NET 开发者提供一份全面而精炼的 C# 网络编程入门,从基础知识到高级话题,逐一剖析,帮助你建立起扎实的网络编程功底,让你在网络世界的编码之旅中游刃有余。

一、HTTP 请求

HTTP(Hypertext Transfer Protocol)是互联网上应用最为广泛的一种网络协议,主要用于从万维网服务器传输超文本到本地浏览器的传输协议。

C#中,处理HTTP请求有多种方式,从传统的System.Net命名空间到现代的HttpClient类,每种方法都有其适用场景。

1、使用 HttpClient 发送HTTP请求

HttpClient是C#中推荐用于发送HTTP请求的类,它提供了异步的API,可以更好地处理长时间运行的操作,避免阻塞UI线程。

以下是一个简单的GET请求示例:

复制代码
using System;
using System.Net.Http;
using System.Threading.Tasks;

public class HttpClientExample
{
    public static async Task Main()
    {
        using var client = new HttpClient();
        var response = await client.GetAsync("https://api.example.com/data");
        
        if (response.IsSuccessStatusCode)
        {
            var content = await response.Content.ReadAsStringAsync();
            Console.WriteLine(content);
        }
        else
        {
            Console.WriteLine($"Failed to retrieve data: {response.StatusCode}");
        }
    }
}
复制代码

 2、使用 WebClient 发送HTTP请求

尽管WebClient类仍然存在于.NET Framework中,但在.NET Core和后续版本中,它已被标记为过时,推荐使用HttpClient

不过,对于简单的同步请求,WebClient仍然可以使用:

复制代码
using System;
using System.IO;
using System.Net;

class WebClientExample
{
    public static void Main()
    {
        using (var client = new WebClient())
        {
            try
            {
                string result = client.DownloadString("https://api.example.com/info");
                Console.WriteLine(result);
            }
            catch (Exception ex)
            {
                Console.WriteLine($"Error: {ex.Message}");
            }
        }
    }
}
复制代码

3、使用 HttpRequestMessage 和 HttpMessageHandler

对于更复杂的HTTP请求,如需要自定义请求头或处理认证,可以使用HttpRequestMessageHttpMessageHandler

这种方式提供了更多的灵活性和控制:

复制代码
using System;
using System.Net.Http;
using System.Threading.Tasks;

class HttpRequestMessageExample
{
    public static async Task Main()
    {
        using var client = new HttpClient();
        var request = new HttpRequestMessage(HttpMethod.Get, "https://api.example.com/info");
        request.Headers.Add("Authorization", "Bearer your-access-token");

        var response = await client.SendAsync(request);

        if (response.IsSuccessStatusCode)
        {
            var content = await response.Content.ReadAsStringAsync();
            Console.WriteLine(content);
        }
        else
        {
            Console.WriteLine($"Failed to retrieve data: {response.StatusCode}");
        }
    }
}
复制代码

4、注意事项

  • 安全性和性能: 使用HttpClient时,确保在一个应用程序的生命周期内重用同一个实例,而不是每次请求都创建新的实例。
  • 错误处理: 总是对HTTP请求的结果进行检查,处理可能发生的异常和非成功的HTTP状态码。
  • 超时和取消: 使用HttpClient时,可以通过CancellationToken来控制请求的超时和取消。

通过掌握这些知识点,能够在C#中有效地处理各种HTTP请求,从简单的GET请求到复杂的POST请求,包括身份验证和错误处理。

二、WebSocket 通信

WebSocket是一种在单个TCP连接上进行全双工通信的协议,它提供了比传统HTTP请求/响应模型更低的延迟和更高的效率,非常适合实时数据流、聊天应用、在线游戏等场景。在C#中,无论是服务器端还是客户端,都可以使用WebSocket进行通信。

1、客户端使用 WebSocket

在C#中,你可以使用System.Net.WebSockets命名空间下的ClientWebSocket类来创建WebSocket客户端。下面是一个简单的示例,展示了如何连接到WebSocket服务器并发送和接收消息:

复制代码
using System;
using System.IO.Pipelines;
using System.Net.WebSockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

/// <summary>
/// WebSocket客户端类,用于与WebSocket服务器建立连接和通信。
/// </summary>
public class WebSocketClient
{
    /// <summary>
    /// 客户端WebSocket实例。
    /// </summary>
    private readonly ClientWebSocket _webSocket = new ClientWebSocket();
    
    /// <summary>
    /// 用于取消操作的CancellationTokenSource。
    /// </summary>
    private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource();

    /// <summary>
    /// 连接到指定的WebSocket服务器。
    /// </summary>
    /// <param name="uri">WebSocket服务器的URI。</param>
    public async Task Connect(string uri)
    {
        // 使用提供的URI连接到WebSocket服务器
        await _webSocket.ConnectAsync(new Uri(uri), _cancellationTokenSource.Token);
    }

    /// <summary>
    /// 向WebSocket服务器发送消息。
    /// </summary>
    /// <param name="message">要发送的消息字符串。</param>
    public async Task SendMessage(string message)
    {
        // 将消息转换为UTF8编码的字节
        byte[] buffer = Encoding.UTF8.GetBytes(message);
        
        // 创建ArraySegment,封装要发送的字节缓冲区
        ArraySegment<byte> segment = new ArraySegment<byte>(buffer);
        
        // 发送消息到WebSocket服务器
        await _webSocket.SendAsync(segment, WebSocketMessageType.Text, true, _cancellationTokenSource.Token);
    }

    /// <summary>
    /// 接收WebSocket服务器发送的消息。
    /// </summary>
    /// <param name="onMessageReceived">接收到消息时调用的回调函数。</param>
    public async Task ReceiveMessage(Action<string> onMessageReceived)
    {
        // 当WebSocket连接处于打开状态时,持续接收消息
        while (_webSocket.State == WebSocketState.Open)
        {
            var buffer = new byte[1024];
            
            // 接收来自WebSocket服务器的数据
            var result = await _webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), _cancellationTokenSource.Token);
            
            // 如果接收到的类型为关闭,则关闭连接
            if (result.MessageType == WebSocketMessageType.Close)
            {
                await _webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Closing", CancellationToken.None);
                break;
            }
            
            // 将接收到的字节转换为字符串,并通过回调函数处理
            var receivedMessage = Encoding.UTF8.GetString(buffer, 0, result.Count);
            onMessageReceived(receivedMessage);
        }
    }

    /// <summary>
    /// 断开与WebSocket服务器的连接。
    /// </summary>
    public async Task Disconnect()
    {
        // 取消接收和发送操作
        _cancellationTokenSource.Cancel();
        
        // 关闭WebSocket连接
        await _webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Closing", CancellationToken.None);
    }
}
复制代码

2、服务器端使用 WebSocket

在服务器端,可以使用ASP.NET Core中的Microsoft.AspNetCore.WebSockets来支持WebSocket。

下面是一个简单的WebSocket服务端点配置示例:

复制代码
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Threading.Tasks;

public class Startup
{
    /// <summary>
    /// 配置服务容器。
    /// </summary>
    /// <param name="services">服务集合。</param>
    public void ConfigureServices(IServiceCollection services)
    {
        // 添加控制器服务
        services.AddControllers();
    }

    /// <summary>
    /// 配置应用管道。
    /// </summary>
    /// <param name="app">应用构建器。</param>
    /// <param name="env">主机环境。</param>
    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        // 在开发环境中启用异常页面
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }

        // 启用路由
        app.UseRouting();

        // 启用WebSocket中间件
        app.UseWebSockets();

        // 配置端点处理器
        app.UseEndpoints(endpoints =>
        {
            // 映射默认的GET请求处理器
            endpoints.MapGet("/", async context =>
            {
                await context.Response.WriteAsync("Hello World!");
            });

            // 映射WebSocket请求处理器
            endpoints.Map("/ws", async context =>
            {
                // 检查当前请求是否为WebSocket请求
                if (context.WebSockets.IsWebSocketRequest)
                {
                    // 接受WebSocket连接
                    using var webSocket = await context.WebSockets.AcceptWebSocketAsync();

                    // 持续监听WebSocket消息
                    while (true)
                    {
                        // 准备接收缓冲区
                        var buffer = new byte[1024 * 4];
                        
                        // 接收WebSocket消息
                        var result = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);

                        // 如果收到的类型为关闭消息,则关闭连接
                        if (result.MessageType == WebSocketMessageType.Close)
                        {
                            await webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Closing", CancellationToken.None);
                            break;
                        }

                        // 解码接收到的消息
                        var message = Encoding.UTF8.GetString(buffer, 0, result.Count);
                        Console.WriteLine($"Received: {message}");

                        // 回复消息给客户端
                        await webSocket.SendAsync(
                            new ArraySegment<byte>(Encoding.UTF8.GetBytes($"Echo: {message}")),
                            result.MessageType,
                            result.EndOfMessage,
                            CancellationToken.None);
                    }
                }
                else
                {
                    // 如果不是WebSocket请求,则返回400错误
                    context.Response.StatusCode = 400;
                }
            });
        });
    }
}
复制代码

在上面的服务器端代码中,首先启用了WebSocket中间件,然后映射了一个/ws端点来处理WebSocket连接。

当收到连接请求时,我们接受连接并进入循环,监听客户端发送的消息,然后简单地回传一个回显消息。

3、说明

WebSocket为C#开发者提供了强大的实时通信能力,无论是构建复杂的实时数据流应用还是简单的聊天室,WebSocket都是一个值得考虑的选择。通过掌握客户端和服务器端的实现细节,可以充分利用WebSocket的优势,创建高性能和低延迟的实时应用。

三、 Socket 编程

Socket编程是计算机网络通信中的基础概念,它提供了在不同计算机之间发送和接收数据的能力。

在C#中,Socket编程主要通过System.Net.Sockets命名空间下的Socket类来实现。Socket可以用于创建TCP/IP和UDP两种主要类型的网络连接,分别对应于流式套接字(Stream Sockets)和数据报套接字(Datagram Sockets)。

1、Socket 基础

Socket地址族:指定网络协议的类型,如AddressFamily.InterNetwork用于IPv4。

Socket类型:SocketType.Stream用于TCP,SocketType.Dgram用于UDP。

Socket协议:ProtocolType.TcpProtocolType.Udp,分别用于TCP和UDP。

2、TCP Socket 客户端

TCP Socket客户端通常用于建立持久的连接,并通过流的方式发送和接收数据。

以下是一个简单的TCP客户端示例:

复制代码
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;

public class TcpClientExample
{
    public static void Main()
    {
        try
        {
            // 创建一个新的Socket实例
            using (Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))
            {
                // 连接到服务器
                IPAddress ipAddress = IPAddress.Parse("127.0.0.1");
                IPEndPoint remoteEP = new IPEndPoint(ipAddress, 11000);
                socket.Connect(remoteEP);

                // 发送数据
                string message = "Hello Server!";
                byte[] data = Encoding.ASCII.GetBytes(message);
                socket.Send(data);

                // 接收服务器响应
                data = new byte[1024];
                int bytes = socket.Receive(data);
                Console.WriteLine("Received: {0}", Encoding.ASCII.GetString(data, 0, bytes));
            }
        }
        catch (Exception e)
        {
            Console.WriteLine("Error: {0}", e.ToString());
        }
    }
}
复制代码

3、TCP Socket 服务器

TCP Socket服务器负责监听客户端的连接请求,并处理来自客户端的数据。

以下是一个简单的TCP服务器示例:

复制代码
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;

public class TcpServerExample
{
    public static void Main()
    {
        try
        {
            // 创建一个新的Socket实例
            using (Socket listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))
            {
                // 绑定到本地端口
                IPAddress ipAddress = IPAddress.Any;
                IPEndPoint localEP = new IPEndPoint(ipAddress, 11000);
                listener.Bind(localEP);

                // 监听连接
                listener.Listen(10);

                // 接受客户端连接
                Console.WriteLine("Waiting for a connection...");
                Socket handler = listener.Accept();

                // 接收数据
                byte[] data = new byte[1024];
                int bytes = handler.Receive(data);
                Console.WriteLine("Text received: {0}", Encoding.ASCII.GetString(data, 0, bytes));

                // 发送响应
                string response = "Hello Client!";
                byte[] responseData = Encoding.ASCII.GetBytes(response);
                handler.Send(responseData);
            }
        }
        catch (Exception e)
        {
            Console.WriteLine("Error: {0}", e.ToString());
        }
    }
}
复制代码

4、UDP Socket

UDP Socket用于无连接的、不可靠的网络通信,通常用于实时数据传输,如视频流或游戏。

以下是一个简单的UDP客户端和服务器示例:

UDP客户端

复制代码
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;

public class UdpClientExample
{
    public static void Main()
    {
        try
        {
            // 创建一个新的Socket实例
            using (UdpClient client = new UdpClient())
            {
                // 发送数据
                string message = "Hello UDP Server!";
                byte[] data = Encoding.ASCII.GetBytes(message);
                IPEndPoint server = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 11000);
                client.Send(data, data.Length, server);

                // 接收服务器响应
                data = client.Receive(ref server);
                Console.WriteLine("Received: {0}", Encoding.ASCII.GetString(data));
            }
        }
        catch (Exception e)
        {
            Console.WriteLine("Error: {0}", e.ToString());
        }
    }
}
复制代码

UDP服务器

复制代码
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;

public class UdpServerExample
{
    public static void Main()
    {
        try
        {
            // 创建一个新的Socket实例
            using (UdpClient listener = new UdpClient(11000))
            {
                // 接收数据
                IPEndPoint client = new IPEndPoint(IPAddress.Any, 0);
                byte[] data = listener.Receive(ref client);
                Console.WriteLine("Text received: {0}", Encoding.ASCII.GetString(data));

                // 发送响应
                string response = "Hello UDP Client!";
                byte[] responseData = Encoding.ASCII.GetBytes(response);
                listener.Send(responseData, responseData.Length, client);
            }
        }
        catch (Exception e)
        {
            Console.WriteLine("Error: {0}", e.ToString());
        }
    }
}
复制代码

以上示例展示了如何使用C#中的Socket类来实现TCP和UDP的客户端与服务器通信。

在实际应用中,可能还需要处理并发连接、错误处理和资源管理等问题。

此外,对于TCP通信,考虑到性能和资源使用,通常建议使用异步编程模型。

四、C# 网络安全

C# 中进行网络编程时,网络安全是一个至关重要的方面,涉及数据传输的保密性、完整性和可用性。以下是一些关键的网络安全知识点,它们对于构建安全的网络应用程序至关重要:

1、SSL/TLS 加密

在C#中使用HttpClient时,可以通过HttpClientHandler类来配置SSL/TLS相关的选项,确保HTTPS请求的安全性。

下面是一个示例,演示了如何使用HttpClientHandler来配置SSL/TLS设置:

复制代码
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Security.Cryptography.X509Certificates;
using System.Threading.Tasks;

class Program
{
    static async Task Main()
    {
        // 创建 HttpClientHandler 实例
        var handler = new HttpClientHandler();

        // 配置 SSL/TLS 设置
        // 设置检查服务器证书的委托
        handler.ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator;

        // 设置是否自动重定向
        handler.AllowAutoRedirect = true;

        // 设置代理
        // handler.UseProxy = true;
        // handler.Proxy = new WebProxy("http://proxy.example.com:8080");

        // 创建 HttpClient 实例
        using var httpClient = new HttpClient(handler);

        // 设置请求头部
        httpClient.DefaultRequestHeaders.Accept.Clear();
        httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));

        // 发送 HTTPS 请求
        var response = await httpClient.GetAsync("https://api.example.com/data");

        // 检查响应状态
        if (response.IsSuccessStatusCode)
        {
            var content = await response.Content.ReadAsStringAsync();
            Console.WriteLine(content);
        }
        else
        {
            Console.WriteLine($"Failed to retrieve data: {response.StatusCode}");
        }
    }
}
复制代码

解释

  • ServerCertificateCustomValidationCallback:此属性允许你指定一个委托,用来验证服务器的SSL证书。在这个示例中,我们使用了HttpClientHandler.DangerousAcceptAnyServerCertificateValidator,它会接受任何证书,这在测试环境中可能有用,但强烈建议在生产环境中使用更严格的证书验证逻辑。
  • AllowAutoRedirect:此属性控制是否允许HttpClient自动处理重定向。默认情况下,它是开启的。
  • UseProxy 和 Proxy:如果需要通过代理服务器发送请求,可以配置这两个属性。
  • DefaultRequestHeaders:用于设置请求的默认头部,如Accept,以指定期望的响应格式。

注意事项:

  • 实际应用中,不建议使用DangerousAcceptAnyServerCertificateValidator,因为它绕过了正常的证书验证,可能使应用程序暴露于中间人攻击。在生产环境中,应该实现自己的证书验证逻辑,确保只接受有效和可信的证书。
  • 此外,如果应用程序需要处理特定的SSL/TLS协议版本或加密套件,也可以通过SslProtocols属性进一步定制HttpClientHandler的SSL/TLS设置。
  • 例如,可以将其设置为SslProtocols.Tls12SslProtocols.Tls13,以限制使用的协议版本。

2、密码安全存储

在C#中安全地存储密码是一个至关重要的实践,尤其是当涉及到用户账户和敏感信息时。为了保护密码不被泄露或破解,应避免以明文形式存储密码,而是采用加密或哈希的方式。

以下是一些推荐的实践:

  • 使用哈希函数

使用安全的哈希函数,如SHA-256或SHA-512,可以将密码转换为一个固定长度的摘要。但是,简单的哈希容易受到彩虹表攻击,因此需要加入盐值(salt)。

示例代码:

复制代码
using System;
using System.Security.Cryptography;
using System.Text;

public static class PasswordHasher
{
    public static string HashPassword(string password, byte[] salt)
    {
        using (var sha256 = SHA256.Create())
        {
            var passwordSalted = Encoding.UTF8.GetBytes(password + Encoding.UTF8.GetString(salt));
            var hash = sha256.ComputeHash(passwordSalted);
            return Convert.ToBase64String(hash);
        }
    }

    public static byte[] GenerateSalt()
    {
        using (var rng = new RNGCryptoServiceProvider())
        {
            var salt = new byte[32];
            rng.GetBytes(salt);
            return salt;
        }
    }
}

// 使用示例
byte[] salt = PasswordHasher.GenerateSalt();
string hashedPassword = PasswordHasher.HashPassword("password123", salt);
复制代码
  • 使用加盐哈希

在哈希密码之前,先将随机生成的盐值与密码结合。这可以防止彩虹表攻击和暴力破解。

  • 使用慢速哈希函数

使用像PBKDF2、bcrypt、scrypt或Argon2这样的慢速哈希函数,可以显著增加破解难度,因为它们设计时考虑了防止暴力破解。

示例代码:

复制代码
using System;
using System.Linq;
using System.Security.Cryptography;
using System.Text;

public static class PasswordHasher
{
    public static string HashPasswordUsingBcrypt(string password)
    {
        using (var bcrypt = new Rfc2898DeriveBytes(password, 16, 10000)) // 16 bytes of salt, 10000 iterations
        {
            return Convert.ToBase64String(bcrypt.GetBytes(24)); // 24 bytes of hash
        }
    }
}

// 使用示例
string hashedPassword = PasswordHasher.HashPasswordUsingBcrypt("password123");
复制代码
  • 存储哈希和盐值

在数据库中,除了存储哈希后的密码,还应存储用于该密码的盐值,以便在验证时使用相同的盐值重新计算哈希。

  • 验证密码

在用户登录时,从数据库中检索哈希和盐值,使用相同的哈希函数和盐值对输入的密码进行哈希,然后与存储的哈希值进行比较。

示例代码:

public static bool VerifyPassword(string inputPassword, string storedHash, byte[] storedSalt)
{
    string hashOfInput = PasswordHasher.HashPassword(inputPassword, storedSalt);
    return hashOfInput == storedHash;
}
  • 不要存储密码重置问题的答案

密码重置问题的答案应该像密码一样被安全地处理,避免以明文形式存储。

ASP.NET Core提供了内置的密码哈希和验证方法,使用这些框架通常比手动实现更安全。总之,安全地存储密码涉及到使用强哈希算法、加盐、适当的迭代次数和存储机制。同时,保持对最新安全实践的关注,并定期更新代码以应对新的威胁。

3、防止SQL注入

使用参数化查询或ORM工具等,防止SQL注入攻击。

string query = "SELECT * FROM SystemUser WHERE Username = @username";
SqlCommand command = new SqlCommand(query, connection);
command.Parameters.AddWithValue("@username", inputUsername);

4、防止跨站脚本攻击(XSS)

对用户输入进行合适的编码和验证,防止恶意脚本注入。

string userContent = "<script>alert('XSS');</script>";
string encodedContent = HttpUtility.HtmlEncode(userContent);

5、防止跨站请求伪造(CSRF)

ASP.NET MVC可以使用Anti-Forgery Token等机制来防止CSRF攻击。

@Html.AntiForgeryToken()

6、身份验证和授权

使用更高级的身份验证机制,如JWT(JSON Web Token),并在应用中实施合适的授权策略。

[Authorize]
public ActionResult SecureAction()
{
    // 安全操作
}

7、判断文件安全

在C#中,判断一个文件是否”安全”可以从多个角度考量,这通常涉及到文件的来源、内容、权限以及是否包含潜在的恶意代码等。

下面我会介绍几种可能的方法来检查文件的安全性:

  • 检查文件的来源

确保文件是从可信的源下载或获取的。在Web应用程序中,可以使用Content-Disposition响应头来检查文件是否作为附件提供,以及文件名是否符合预期。

  • 验证文件的类型和扩展名

通过检查文件的扩展名或MIME类型来确定文件类型是否符合预期,例如,如果期望图片文件,那么只接受.jpg.png等扩展名。

复制代码
private bool IsFileSafeByExtension(string filePath)
{
    string[] allowedExtensions = { ".jpg", ".png", ".gif" };
    string extension = Path.GetExtension(filePath).ToLower();
    return allowedExtensions.Contains(extension);
}
复制代码
  • 检查文件的内容

使用文件签名或魔法数字来验证文件的实际类型与声明的类型是否一致,防止扩展名欺骗。

复制代码
private bool IsFileSafeByContent(string filePath)
{
    byte[] magicNumbers = File.ReadAllBytes(filePath);
    if (magicNumbers.Length >= 2 && magicNumbers[0] == 0xFF && magicNumbers[1] == 0xD8) // JPEG
    {
        return true;
    }
    // Add checks for other formats...
    return false;
}
复制代码
  • 扫描病毒和恶意软件

使用反病毒软件或在线API来检查文件是否含有病毒或恶意软件,VirusTotal 提供了API来检查文件是否含有病毒,https://www.virustotal.com/ 具体示例如下

复制代码
using System;  
using System.Net.Http;  
using System.Threading.Tasks;  
using Newtonsoft.Json; // 需要安装Newtonsoft.Json NuGet包  
  
class Program  
{  
    static async Task Main(string[] args)  
    {  
        string apiKey = "API密钥";  
        string fileUrl = "文件ID";  
  
        string url = $"https://www.virustotal.com/vtapi/v3/files/{fileUrl}/report";  
        HttpClient client = new HttpClient();  
        client.DefaultRequestHeaders.Add("x-apikey", apiKey);  
  
        HttpResponseMessage response = await client.GetAsync(url);  
  
        if (response.IsSuccessStatusCode)  
        {  
            string responseBody = await response.Content.ReadAsStringAsync();  
            dynamic report = JsonConvert.DeserializeObject(responseBody);  
  
            if (report.positives > 0)  
            {  
                Console.WriteLine("文件含有病毒或恶意软件。");  
            }  
            else  
            {  
                Console.WriteLine("文件安全。");  
            }  
        }  
        else  
        {  
            Console.WriteLine("API请求失败。");  
        }  
    }  
}
复制代码
  • 检查文件权限

确保文件具有正确的权限,以防止未经授权的访问。

复制代码
private bool IsFileSafeByPermissions(string filePath)
{
    var fileInfo = new FileInfo(filePath);
    var security = fileInfo.GetAccessControl();
    // Check permissions here...
    return true; // Placeholder logic
}
复制代码
  • 文件大小检查

限制文件的大小,避免消耗过多的磁盘空间或内存。

private bool IsFileSafeBySize(string filePath, long maxSizeInBytes)
{
    var fileInfo = new FileInfo(filePath);
    return fileInfo.Length <= maxSizeInBytes;
}
  • 内容安全策略(CSP)

在Web应用中,使用CSP来限制加载的资源类型和来源,防止XSS等攻击。

  • 综合检查函数示例
复制代码
private bool IsFileSafe(string filePath)
{
    return IsFileSafeByExtension(filePath) &&
           IsFileSafeByContent(filePath) &&
           IsFileSafeFromVirus(filePath) &&
           IsFileSafeByPermissions(filePath) &&
           IsFileSafeBySize(filePath, 1024 * 1024); // Limit to 1MB
}
复制代码

请注意,上述代码片段仅作为示例,实际应用中可能需要调整和补充具体的实现细节,例如引入实际的病毒扫描库或API,以及更复杂的权限和内容检查逻辑。

安全检查是多层面的,需要结合具体的应用场景和需求进行综合考量。

8、安全的Cookie处理

Cookies是Web开发中用于存储用户信息的一种常用机制,它们可以在客户端浏览器中保存小量的数据,以便服务器可以跟踪用户的偏好设置、登录状态等信息。然而,如果Cookie处理不当,可能会引发严重的安全问题,如数据泄露、会话劫持(Session Hijacking)和跨站脚本攻击(XSS)。因此,确保Cookie的安全处理至关重要。

以下是处理Cookie时应当遵循的一些最佳实践:

  • 使用HTTPS:传输Cookie时,务必使用HTTPS加密连接。HTTPS可以防止中间人攻击(Man-in-the-Middle Attack),保护Cookie数据免受窃听。
  • 设置HttpOnly标志:将Cookie标记为HttpOnly可以阻止JavaScript脚本访问Cookie,从而降低跨站脚本攻击(XSS)的风险。
  • 设置Secure标志:当Cookie被标记为Secure时,它们只会在HTTPS连接下发送,确保数据在传输过程中的安全性。
  • 限制Cookie的有效路径和域:通过设置Cookie的Path和Domain属性,可以控制哪些页面可以访问特定的Cookie,减少攻击面。
  • 使用SameSite属性:SameSite属性可以控制Cookie是否随跨站点请求发送,减少跨站请求伪造(CSRF)攻击的可能性。可以选择Strict、Lax或None三种模式之一。
  • 设置合理的过期时间:为Cookie设定一个适当的过期时间,可以避免永久性Cookie带来的安全风险,同时也便于清理不再需要的用户信息。
  • 定期审查和更新Cookie策略:定期检查Cookie的使用情况,确保所有Cookie设置符合最新的安全标准和隐私法规。

通过遵循这些最佳实践,可以大大增强应用程序的安全性,保护用户数据免受恶意攻击。在Web开发中,安全的Cookie处理不仅是技术要求,也是对用户隐私和数据安全的责任体现。

复制代码
using System;
using System.Web;

public class CookieHandler : IHttpHandler
{
    public void ProcessRequest(HttpContext context)
    {
        // 创建一个新的Cookie对象
        HttpCookie cookie = new HttpCookie("UserSession");

        // 设置Cookie值
        cookie.Value = "123456"; // 假设这是用户的唯一标识符

        // 设置Cookie的过期时间
        cookie.Expires = DateTime.Now.AddDays(1); // 设置Cookie在一天后过期

        // 设置HttpOnly属性以增加安全性
        cookie.HttpOnly = true;

        // 如果你的网站支持HTTPS,设置Secure属性
        if (context.Request.IsSecureConnection)
            cookie.Secure = true;

        // 添加Cookie到响应中
        context.Response.AppendCookie(cookie);
    }

    public bool IsReusable
    {
        get { return false; }
    }
}
复制代码

在.NET Core或.NET 6+中,使用不同的API来处理Cookie,例如Microsoft.AspNetCore.Http命名空间下的IResponseCookies接口。

五、总结

通过文章的全面介绍 C# 网络编程,相信对这一块内容有了了解和理解。从简单的 HTTP 请求到复杂的套接字通信,从异步编程模型到安全协议的应用,每一步都为我们构建现代网络应用奠定了坚实的基。在实际项目中,根据需求深入学习和实践这些知识点,将有助于提升.NET开发者在网络编程领域的能力。持续学习和实践是成为优秀 .NET 开发者的不二法门。

如果觉得这篇文章对你有用,欢迎加入微信公众号 [DotNet技术匠] 社区,与其他热爱技术的同行交流心得,共同成长。

我们的前端开发逆天了!1 小时搞定了新网站,还跟我说 “不要钱” - 程序员鱼皮 - 博客园

mikel阅读(145)

来源: 我们的前端开发逆天了!1 小时搞定了新网站,还跟我说 “不要钱” – 程序员鱼皮 – 博客园

大家好,我是程序员鱼皮。前段时间我们上线了一个新软件 剪切助手 ,并且针对该项目做了一个官网:

很多同学表示官网很好看,还好奇是怎么做的,其实这个网站的背后还有个有趣的小故事。。。

鱼皮:我们要做个官网,能下载应用就行,一周时间怎么样?

我们的前端开发 – 多喝热水同学:一周?太小瞧我了吧,1 小时给你搞定!

鱼皮:唔嘈,你很勇哦?

本来以为他是开玩笑的,没想到,1 小时后,他真的给我看了网站效果,而且比预期的好太多了。我的评价是:逆天

他给我解释道:其实我用了一个新框架,基本不用自己写代码,而且还可以白票平台来免费部署网站~

鱼皮:不错不错,回头给我的读者们也分享一下!

于是,就有了下面这篇文章:

对于前端同学来说,用的最多的 Web 框架无非就是 React/Vue/Angular 这三大件了,那本文将带你了解一个新的 Web 框架 Astro,并手把手带你使用 Astro 搭建一个属于自己的站点,用过的都说真香!

关于技术栈的选择

假如现在有这样一个需求,公司需要你去做一个官网落地页,没什么别的要求,界面美观且能介绍公司就行,你会怎么选技术栈?

如果是以前,我可能会挑一个自己熟悉的语言去快速开发,但是现在你问我选什么技术栈,我可能会选择 Astro,为什么?且听我娓娓道来~

首先如果是自己花时间去开发的话,我们需要搭建网站的整体布局,如导航栏、logo、页脚等等,还需要考虑移动端的适配、网站 SEO 优化等等…

我不知道你们会不会觉得有点烦?反正我是有点烦了,且自己做出来的可能还没那么好看…

image

如果布局、适配、SEO 这些都配好了,只需要改改文字的话那该多好!

image

没错,依靠 Astro 强大的主题生态就可以帮助我们快速完成这些事情!像我们公司的产品 剪切助手(https://jianqiezhushou.com) 的官网就是用 Astro 搭建的,如下:

image

image

还是很好看的有木有,且移动端的响应式适配、SEO 通通都搞定,一举多得!

接下来我们就简单了解一下 Astro 这个框架,然后使用 Astro 快速生成一个自己的站点!

Astro 框架介绍

关于 Astro 的介绍,官方文档(https://docs.astro.build/zh-cn/concepts/why-astro)给出了很明确的定位:“最适合构建像博客、营销网站、电子商务网站这样的以内容驱动的网站的 Web 框架”

image

它默认就支持服务端渲染,且支持 React、Preact、Svelte、Vue、Solid、Lit、HTMX、Web 组件,这意味着你可以用任意框架的写法来编写 Astro,这一特性在 Astro 中被称为 “群岛”。

快速拥有一个 Astro 应用

这里我们不会从零到一的去介绍 Astro 的写法,感兴趣的同学可以简单从官网过一遍入门指南(https://docs.astro.build/zh-cn/getting-started),我们要做的就是依靠 Astro 强大的主题模板,实现只需要改改文字、写一写 Markdown 就能轻松搭建一个漂亮的博客网站!

1)选择主题模板

进入 Astro 官方模板网站(https://astro.build/themes),挑选一个自己心仪的模板,如下:

image

我选择的模板是 https://astro.build/themes/details/astro-boilerplate/ ,我们进入到这个模板的详情页,可以看到有两个按钮,如下:

image

第一个是源码,第二个是在线效果的演示。

我们点击 Get Started 获取项目的源代码。

2)查看模板的 README 文档

通过 README 文档我们可以了解到如下信息

  1. 怎么去启动这个项目?
  2. 怎么构建发布?

如下图:

image

那么接下来我们就按照 README 中所描述的步骤来操作,第一步我们先把项目拉取到本地,执行如下命令:

git clone --depth=1 https://github.com/ixartz/Astro-boilerplate

image

在编辑器中打开这个项目,并安装项目依赖,如下:

image

安装依赖完成后启动项目,项目启动后我们访问 http://localhost:4321 ,如下图:

image

ok,现在我们就得到了一个最原始的模板,和之前的预览效果是一致的,如下:

image

3)更换项目中的个人信息

现在我们要做的就是把里面的文字换成自己的信息,没有的信息我们可以删掉,这里可以通过查看 index.astro 文件来了解整个网站的大致布局,从而找到我们要调整的组件,如下:

image

如果你不知道怎么调整也可以用另一种更简单的方法,直接搜索内容关键词,来找到我们要修改的内容,如下:

image

下面是我调整后的效果,如下:

image

看起来也不赖,主打一个简约风格。

4)如何发文

这是一个博客站,所以还需要找到发文入口,我们找到 posts 文件夹,在此文件夹下编写 markdown 文件即可,配置按已有的格式修改,如下:

image

ok,接下来我们就尝试一下发一篇文章,在 posts 文件夹下新建一个 md 格式的文件,往里面写入一些内容,如下:

image

可以看到,我们编写的 markdown 已经被解析为了文章~

至此网站的搭建已经结束了,剩下的就是自己在上面添加内容。

现在网站我们有了,还需要让别人能够访问你的网站,一般到这一步我们需要服务器,域名等等,如果没有的话怎么办?

白票!!将白票贯彻到底!!!

image

我们可以白票的第三方服务有:

1)GitHub Pages

2)Netlify

3)Cloudflare

4)Vercel

等等…

这里我们就以 Netlify 为例,其他的大家感兴趣可以自行去了解。

部署

1)创建仓库

首先我们需要一个能够存放代码的地方,我们去 GitHub 创建一个代码仓库,并上传代码,如下:

image

2)将仓库关联到 Netlify

进入到 Netlify登录页(https://app.netlify.com/login),这里因为我们的代码放在了 GitHub,所以我们选择使用 GitHub 进行登录,如下:

image

选择导入已有的项目,如下:

image

从 GitHub 导入,如下:

image

找到我们博客所在的代码仓库,如下:

image

点击仓库我们会进入到部署配置页,如下:

image

一些关键的配置说明都列出来了,按照要求配置即可,没有特别说明的目前不需要关注,点击部署后等待几分钟即可完成部署,如下:

image

现在我们访问 https://codereshui.netlify.app 就能看到部署的网站了,且后续有新的内容变更的时候(比如发文),网站会自动构建并发布!

妥妥的一条龙服务!!!好了,这篇文章就肝到这里,大家也可以把自己的网站搞起来了~

mysql 使用show tables表存在,但是select时却提示表不存在!_show tables 能查到表 select 查不到表-CSDN博客

mikel阅读(287)

来源: mysql 使用show tables表存在,但是select时却提示表不存在!_show tables 能查到表 select 查不到表-CSDN博客

这个问题困扰了很久,情况是某张表被损坏。

直接使用select查询时报错:

ERROR 1146 (42S02): Last_Error: Error ‘Table ‘xxxxxx’ doesn’t exist’ Error ” ERROR 1146 (42S02): Table
咦!怎么表无缘无故就不见了???

使用 show tables; 命令却发现表是存在的,瞬间懵逼了。无论发生什么情况,肯定是有原因的,哈哈哈。

最终解决方案是,不需要停mySQL服务,直接通过mySQL的my.conf查看到datadir目录,进入目录后,找到指定出问题的表关联文件:

1、tableName.frm
2、tableName.ibd
直接删除,至此才把出问题的表完全删除掉。
————————————————

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

原文链接:https://blog.csdn.net/yangjiechao945/article/details/96458466

报错 General error: 1366 Incorrect string value: ‘\xF0\x9F\x8D\x83‘ for column ‘per_name‘ at row 1-CSDN博客

mikel阅读(148)

解决办法:将数据库此字段设置为 utf8mb4_general_ci 即可。

来源: 报错 General error: 1366 Incorrect string value: ‘\xF0\x9F\x8D\x83‘ for column ‘per_name‘ at row 1-CSDN博客

插入数据报错:
SQLSTATE[HY000]: General error: 1366 Incorrect string value: ‘\xF0\x9F\x8D\x83’ for column ‘per_name’ at row 1。

产生错误原因是,入库字段设置的字节无法满足要求。一般文字字节在1-3之间,但是有些生僻字,例如产生此报错的文字是四个字节就无法入库而报错。

解决办法:将数据库此字段设置为 utf8mb4_general_ci 即可。
————————————————

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

原文链接:https://blog.csdn.net/lwaimj/article/details/110536257