文章目录

一、前端

1、前端字段的label中文说明短时,this.boxOptions.labelWidth = 300

OnInit中增加

this.boxOptions.labelWidth = 300   

2、this.searchAfter(data, result);

注意查询后的方法有两个参数,返回所有后台的所有数据;动态多选格式

3、查询提交查询之前,客户端可以增加查询条件

    searchBefore(params){
        params.where.push(
                    {
                        name:"luxs_project_id",
                        value:"82756554-646e-482f-a4c4-6a044f64018f,79ce2d2e-c07c-4707-8eeb-2925c8bf5175",
                        displayType:"selectList"
                    });  

        params.Value=1;
}

4、生成主从表时,要先生成从表的model和编辑行,然后再生成主表;

5、最彻底的删除数组中的对象

while(list.length>0){//删除数据中所有的对象
            console.log(list.pop());
}

6、刷新viewgrid用this.refresh();手工刷新voltable用this.Search();

7、刷新前端的下拉表内容 刷新数据字典

    modelOpenAfter(row) {
      this.initDicKeys()//使用场景如:新建编辑级联下拉框保存后,调用此方法刷新级联的数据源
    }

8、想让子表添加的行始终在第一行;

addRow(){
    this.$refs.detail.rowData.unshift({});//添加空行
}
this.$refs.detail.rowData.unshift(row);//添加有数据的行

9、如何给跳转路由加参数

//存:
 router.push({path:item.url,hash:"dd"});
//取:
let router = useRouter();//huangxs==2021年12月14日 15:33:24
console.log(router.currentRoute._rawValue.hash);//huangxs==2021年12月14日 15:33:24

10、viewgrid如何设置默认全部选中行?

this.$refs.table.$refs.table.toggleRowSelection(this.$refs.table.rowData)

11、如何获取viewgrid控件中自定义列的合计数据

onInited() {        
            setTimeout(() => {
                this.$nextTick(() => {

                    this.$refs.table.summaryData[5]=123;
                    console.log(this.$refs.table.summaryData);

                })
            }, 1000)
            //this.extend.text="";
        }

12、viewgrid控件中下拉框的值发生变化,其他的下拉框的值也响应的变化

this.deatil.columns.find(x=>{return x.field=xx}).bind.data=[];

13、对主表和明细表按字段进行排序

onInit(){//
 this.pagination.sortName = "排序字字段";  //设置排序字段
 this.pagination.order = "desc" ; //排序方式desc或者asc
  this.detailOptions.pagination.sortName="OrderId";
 this.detailOptions.pagination.order = "asc";//排序方式desc或者asc
}

14、在OnInited中增加,viewgrid字段浮动提示,tip,超长字段,showOverflowTooltip,弹出窗体移动

  this.columns.forEach(x => {
                x.showOverflowTooltip=true;//内容浮动显示
});

this.textInline=false;//vol表头名称超长换行
this.boxOptions.draggable=true;//弹出窗体移动

15、新增VUE的事件方法时一定要先查看一下之前是否有了,否则新增的也无效,事件重复无效

16、判断{}对象是否为undefined,必须要用===,严格等于号

if (_tt === undefined) {} else {}

17、一个控件上的属性对应多个变量的语法

<el-steps :space="200" :active="_active" finish-status="success">
                <el-step :title="item.stepname" :description="`${item.bdatetime}`+`${item.UserTrueName}`" v-for="item in step_items"></el-step>
</el-steps>

18、插槽slot的语法,如:description属性slot,使用slot之后,原有的:description属性失效,只有认可template中的

    <el-tab-pane label="流程图" name="first">
            <el-steps :space="200" :active="_active" finish-status="success">
                <el-step :title="item.stepname" :description="item.description" v-for="(item) in step_items">
                    <template #description="scope">
                        {{item.UserTrueName}}<br/>{{item.bdatetime}}
                    </template>
                </el-step>
            </el-steps>
        </el-tab-pane>

19、系统默认从数据库下载都是静态下载,vue代码如下

   downfile(){
                let url=this.http.ipAddress +
                      "/Upload/Tables/Fk_reim/202202121527114556/部门管理 (2).xlsx";
                window.open(url)
            },

20、在网页加载js文件时有乱码,可以加上charset="UTF-8"试试

<script src="static/js/custom_ai.js" charset="UTF-8"></script>

21、vue前端formatter注意一定要回写才能生效,才能存入数据库

//例子1
onInited() {
      //框架初始化配置后
      //如果要配置明细表,在此方法操作
      //this.detailOptions.columns.forEach(column=>{ });
	  
	   this.columns.forEach(x=>{
	   	if(x.field == "q_decribe"){
	   	   x.formatter = (row)=>{
	   	     return row["q_decribe"];//直接过滤html标识
	   	   }
	   	}
	   });
    },
//例子2
this.detailOptions.columns.forEach(x => {
if (x.field == "t_num"){
    x.formatter = (row)=>{
        let b=Date.parse(new Date(row.cc_bdate))
        let e=Date.parse(new Date(row.cc_edate))
        row.t_num=Math.round((e-b)/(86400*1000))//把两日期换算成天日期,注意一定要回写才能生效 2022年3月11日 16:53:59 huangxs
        //console.log(row)
        return Math.round((e-b)/(86400*1000));
       }
      }
});

//附件是图片就显示图片,如果不是显示路径,使用formatter
this.columns.forEach(x=>{
	if(x.field == "pic_path"){
	   x.formatter = (row)=>{
	   const suffix =`(bmp|jpg|png|tif|gif|pcx/tga|exif|fpx|svg|psd|cdr|pcd|dxf|ufo|eps|ai|raw|WMF|webp|jpeg)`;
	   const regular = new RegExp(`.*\${suffix}`);
	   if (!regular.test(row.pic_path)){
		let str=`<div>${row.pic_path}</div>`
		return str;
	   }else{
		let  str=`<div><img src="${row.pic_path}" style="width:50px;"/></div>`
		return str;
	   }
	}
});
//调用this.base里面的查看放大图片方法,在common
this.base.previewImg(url);

22、viewgrid不显示设置按钮

this.showCustom=false;

23、ts6语法,some\findIndex\filter\find\findIndex\map

\\some方法:
array_a.some(s=>s>6);//逐个数据去判断
roles_array.some(s=>{return (s==yx_array[i])})//返回bool
let _index=Erp_mara_row.some((x=>{
        return x.isPublish=="否"
      }))
//返回true/false
\\findIndex()方法的用法,返回第一个符合条件的数组成员的位置,如果所有成员都不符合条件,则返回-1。
let _index=Erp_mara_row.findIndex((x)=>{
        return x.isPublish=="否"
      });
\\findIndex()返回的是满足条件的第一个对象的位置
findIndex查询不到返回-1,find返回undefined;
\\filter()方法使用指定的函数测试所有元素的数组[](一定要注意是数组),
\\并创建一个包含所有通过测试的元素的新数组。不改变原有数组
let gridButtons = buttons.filter(item => {
            return !item.value || permissions.indexOf(item.value) != -1
        });
\\find()跟filter()、findIndex()用法一致,注意返回的是满足条件的第一个对象,
\\findIndex()返回的是满足条件的第一个对象的位置
\\findIndex查询不到返回-1,find返回undefined;
\\map()方法:类似SQL中的select,返回的是对象数组
 let mapy=s.racct_list.map(s=>{
 return {
     racct:s.racct,
     goodsid:s.goodsid,
 }});
 this.$emit('parentCall',($parent)=>{//voltable之间复制数据
     $parent.refs.detail.rowDate.push(...mapy) ;
 })

24、注意图片的VUE端

 <div  align="left"><el-image style="width: 100%; height: 50%" :src="logo" fit="fit" /></div>

var imgurl:require("@/assets/imgs/comlog.png"),//注意图片的VUE端
data(){
  logo : ref(imgUrl),
}

25、明细录单回车换下一行录入数量

onInit(){
    Object.assign(element,{edti:null,type:'file',title:'附件'})
	if (element.field=="productName"") {
	      element.onKeyPress = $event=>{//注意大小写
        if ($event.keyCode == 13){
            this.$refs.detail.edti.rowIndex=1;
            this.$refs.detail.$refs.PrductDesc[0].focus();
        }	
  }
}
//或者
//在onInited中,注意是双重循环
this.editFormOptions.forEach(e => {
    e.forEach(ee=>{
      if (ee.field === 'title') {
          ee.onKeyPress = $event => { if ($event.keyCode == 13) {//注意onKeyPress大小写,回车触发
          console.log("23");
        } }
      }
    })
});

26、获取volfrom字段获取输入焦点

this.$refs.form.$refs.PrductDesc[0].focus();//成功
//组件还没渲染完,setTimeout(()=>{},300)这个里设置focus//成功

//给volform输入框获取输入焦点,this.$refs.form.$refs.字段名,使用场景:新建/编辑时设置input标签设置焦点:
this.$refs.form.$refs.字段名.foucs();

27、VOL平台的同步执行,异步

async up_tijiao(ls_type) {
await this.http.post("/api/Fk_reim/getPageData", {}, true).then((result) => {
	this.ls_alldata=result;
	})
}

21、javascript正确判断为空的方式:

if (row===undefined){
...}
if (result.fk_reimfloat_id == undefined || result.fk_reimfloat_id == null) {
                                ls_fk_reimfloat_id = "";
}

22、判断前端json对象属性为数组

let savedata={//判断前端对象属性为数组 
    mainData:this.editFormFields,
    delKeys: null,
    detailData: null
  }
    var keys=Object.keys(savedata.mainData);//需要把selectList字段类型转换为逗号分割的字符串
          for (let index = 0; index < keys.length; index++) {
            const e = keys[index];
        console.log(savedata.mainData[e]);
            if(Array.isArray(savedata.mainData[e])){//判断savedata.mainData对象中哪些属性为数组的,如果是转为字符串
            console.log(savedata.mainData[e]+"array..........");
                savedata.mainData[e]=savedata.mainData[e].join(',');//,转换为,分割的字符串,用于volform提交数据
            }
          }

23、定义VUE页面时,data数据中的数组最好给空值,如,[{}],否则父页面访问就报undefined错误

data() {
      return{
         ys_data:[{}], 
         sj_data:[{}],
      }
  },

24、vol-table表头对齐

this.columns.forEach((column) => {  
      if (column.field == 'xx') {     
           column.align = 'right';   
              }   
     });

25、git强制覆盖本地代码:

Git fetch --all
Git reset --hard origin/master
Git pull

强制除locl
del .git\index.lock

26、viewgrid控件中查询事件后,把数据中的字段通过字典转化为中文字,如,字典为steplist,在数据中增加字段stepname,并且通过字典翻译为中文

searchAfter(result) {
      var dic = this.dicKeys.filter((x) => {
        return x.dicNo == 'steplist';
      });
      result.forEach(e => {
        var v=dic[0].data.filter((x) => {
          return x.key == e.stepid;
        })[0].value
        e["stepname"]=v
      });
	}
//或者

	var dic = this.dicKeys.filter((x) => {
            return x.dicNo == 'cntr';
        });
        var v=dic[0].data.filter((x) => {
        return x.key == data[0].rcntr;
        })[0].value
        rcntr=v.split(" ")[0];

27、问题,在前端的ViewGrid控件中弹出界面中使用EFCore(DBServerProvider.DbContext),操作数据库时要及时手动关闭前端的的弹出窗体,不让用户点击保存按钮,否则会回滚数据的事务,API(save_shenqi)导致保存不成功。

场景,在“费用报销”弹出窗体中,点击“申请解除额度限制”,后要及时关闭”弹出窗体,否则用户点击保存按钮后,VOL框架后台EFCore会自动回滚,保存失败;

28、javascript复制数组对象:concat

var array1 = ["1", "2", "3"];
var array2 = [ ].concat(array1);
//修改array2,不会影响array1

29、父组件与子组件初始化传值,完美解决方案使用如下,总是能解决问题

//(当this.$nextTick(() => {}),使用不起作用时)
this.$store.getters.data().curr_yjbm

30、git解决.gitignore不生效:

git rm -r --cached .
git add .
git commit -m "update .gitignore"  

31、对于VOL框架中的viewgird想要取里面的data数据,一般是通过控件事件参数来取;

32、查询用户日志访问SQL:

select* from sys_log where userip not like '%16.22%' and userip not like '%150.18%' and userip not like '%4.111%' order by begindate desc

select BeginDate,userip,username,url from sys_log where userip not like '%16.22%' and userip not like '%150.18%' and userip not like '%4.111%' order by begindate desc

33、代码生产器中的编辑行字段,如果填0,表示可以编辑(实体中增加[Editable(true)]),但是界面上不显示,这点非常重要!

VOL代码生成器中的FORM编辑字段,允许写入和编辑,但是不想显示,编辑行设为0;

34、对象属性是否存在判断 json

//javascript判断json对象是否有error_code属性,如果==-1没有属性,
var jsonObj=JSON.stringify(tt);  
if (jsonObj.indexOf("error_code")>0){  
    this.$message.error("OCR失败,请重新上传附件"); } 
if (jsonObj.indexOf("error_code")==-1) 
   this.$message.error("没有error_code属性");

35、对象数组按字段大小进行排序 数组排序,按对象属性排序

	data.rows=data.rows.sort(function(a, b){return a.year - b.year})  
	var cars = [  
	  {type:"BMW", year:2017},  
	  {type:"Audi", year:2019},  
	  {type:"porsche", year:2018}  
	];  
	  
	displayCars();  
	  
	function myFunction() {  
	  cars.sort(function(a, b){return b.year - a.year});  
	  displayCars();  
	} 

36、本地项目同步到gitee步骤如下,4个步骤

1、先在gitee网站上创建空projectname;
2、本地运行 初始换脚本,如下:
	创建 git 仓库:

	mkdir sapwebapi
	cd sapwebapi
	git init
	touch README.md
	git add README.md
	git commit -m "first commit"
	git remote add origin https://gitee.com/hxshxs2011/sapwebapi.git
	git push -u origin "master"
	
	已有仓库?
	cd existing_git_repo
	git remote add origin https://gitee.com/hxshxs2011/sapwebapi.git
	git push -u origin "master"
3、本地导入gitee DeskTop工具,导入 Add existing respository;
4、在gitee DeskTop中完成同步网站;

37、this.singleSearch = null;//屏蔽快捷查询

38、实现手工模拟viewgrid控件的增加数据功能,详细见立讯零件-项目视图

//D:\DotNetProjects\JGJ.NetCore\Vol.Vue3版本\src\extension\myerp\other\pop_select_project_for_part.vue
//D:\DotNetProjects\JGJ.NetCore\Vol.Vue3版本\src\extension\myerp\base_data\Luxs_view_project_for_mara.js
let mainData1={
          mara_id: this.$store.getters.data().mara_id,
          luxs_project_id: row[0].luxs_project_id,
          customer_id: ""
      };
 let newdata={
          delKeys:null,
          detailData: null,
          mainData:mainData1
      }
      let turl = "/api/Luxs_view_project_for_mara/Add";
      this.http.post(turl, newdata, true).then((s) => {
        this.$emit("parentCall", ($vue) => {
          console.log($vue);
          $vue.refresh();
        });
        this.$message.info(s.message);
      });

39、一对多界面扩展,比较完善的前端例子:主表与子表都用代码生成器独立生成

立讯项目:
供应商主表(D:\DotNetProjects\JGJ.NetCore\Vol.Vue3版本\src\extension\myerp\base_data\Erp_supper.js)
供应商要求子表 (D:\DotNetProjects\JGJ.NetCore\Vol.Vue3版本\src\extension\myerp\base_data\Luxs_supp_zliao.js),
连接界面(D:\DotNetProjects\JGJ.NetCore\Vol.Vue3版本\src\extension\myerp\other\supperGridFooter.vue)
供应商主表:Erp_supper.js
/*****************************************************************************************
**  Author:jxx 2022
**  QQ:283591387
**完整文档见:http://v2.volcore.xyz/document/api 【代码生成页面ViewGrid】
**常用示例见:http://v2.volcore.xyz/document/vueDev
**后台操作见:http://v2.volcore.xyz/document/netCoreDev
*****************************************************************************************/
//此js文件是用来自定义扩展业务代码,可以扩展一些自定义页面或者重新配置生成的代码
import ziliao_upload from '../other/ziliao_upload.vue';
import gridFooter from '../other/supperGridFooter.vue';
let extension = {
  components: {
    //查询界面扩展组件
    gridHeader: '',
    gridBody: '',
    gridFooter: gridFooter,
    //新建、编辑弹出框扩展组件
    modelHeader: '',
    modelBody: '',
    modelFooter: ''
  },
  tableAction: '', //指定某张表的权限(这里填写表名,默认不用填写)
  buttons: { view: [], box: [], detail: [] }, //扩展的按钮
  methods: {
     //下面这些方法可以保留也可以删除
    onInit() {  //框架初始化配置前,
        //示例:在按钮的最前面添加一个按钮
       /*     this.buttons.unshift({  //也可以用push或者splice方法来修改buttons数组
            name: '开通登录账户', //按钮名称
             icon: 'el-icon-document', //按钮图标vue2版本见iview文档icon,vue3版本见element ui文档icon(注意不是element puls文档)
             type: 'primary', //按钮样式vue2版本见iview文档button,vue3版本见element ui文档button
             onClick: function () {
                let rows=this.getSelectRows();
                if (rows.length<=0) {
                  this.$message.error("请选择行!");
                  return;
                }
             }
        }); */
        this.editFormOptions.forEach(x => {
          x.forEach(item => {
            if (item.field == 'lifnr') {
              //editFormOptions更多配置见volform组件api
              if(this.$route.path=="/Erp_supper"){
                item.extra = {
                  icon: "el-icon-setting", //显示图标
                  text: "自动生成编码",//显示文本
                  style: 'color:red;cursor: pointer;',
                  click: item => {//触发事件
                    var turl = "/api/Luxs_project/get_new_project_no?serial_name=supp_manage";
                    this.http.post(turl, {}, true).then((s) => {
                      this.editFormFields.lifnr=s;
                    });
                  }
                }
              }
              
            }
          })
        })
        //示例:设置修改新建、编辑弹出框字段标签的长度
        this.boxOptions.labelWidth = 150;
        this.tableHeight = window.outerHeight*0.25;//只有放在onInit事件才有效果,放在onInited无效
        if (this.$route.path=="/Luxs_rfq"){//为了让rowChange事件启用
          //启动RFQ是需要多选,其他的都是单选
        }
        else{
          this.single=true;
        }
      //this.boxOptions.height=900//弹出窗体的宽度
      //this.boxOptions.width=1200//弹出窗体的宽度
      this.boxOptions.draggable=true;
    },
    searchAfter(result) {
      //查询后,清空原来记录选中行的id
       this.$store.getters.data().supper_id = null;
      return true;
    },
    rowChange(row){//单选不能少,配合this.single=true;只有单选才触发
      //console.log(row);
      this.$store.getters.data().supper_id = row.supper_id;
      this.$store.getters.data().select_lif_row = row;
      this.$refs.gridFooter.rowClick(row);
    },
    //操作步骤1:主表点击事件加载明细数据
    rowClick({ row, column, event }) {
      this.$store.getters.data().supper_id = row.supper_id;
      this.$store.getters.data().select_lif_row = row;
      this.$refs.table.$refs.table.clearSelection();
      this.$refs.table.$refs.table.toggleRowSelection(row, true); //单击行时选中当前行;
      this.$refs.gridFooter.rowClick(row);
    },
    onInited() {
      this.columns.forEach(x => {
        x.showOverflowTooltip = true;//浮动提示
      });
      //框架初始化配置后
      //如果要配置明细表,在此方法操作
      //this.detailOptions.columns.forEach(column=>{ });
      let _index = this.detailOptions.columns.findIndex(x => { return x.field == 'fujian' });
			//从表动态添加一列(上传图片列),生成上传图片、与删除图片操作
			this.detailOptions.columns.splice(_index+1, 0, {
				field: "随便写",
				title: "上传资料",
				width: 150,
				align: "center",
				render: (h, { row, column, index }) => {
				  //下面所有需要显示的信息都从row里面取出来
				  return h(
					"div", { style: { color: '#0c83ff', 'font-size': '13px', cursor: 'pointer' } },
					[
					  h(
						"i", {
						style: { 'margin-right': '10px' },
						class:['el-icon-upload'],
						onClick: (e) => {
						  e.stopPropagation();
						  //记住当前操作的明细表行数据
						  //如果原来有图片,上传图片界面把原来的图片也显示出来
						  let fileInfo = (row.fujian || '').split(",").filter(x => { return x }).map(img => {
							//(生成文件格式) fileInfo格式参数,见volupload组件
							return { path: img, name: "" };
						  })
						  this.$refs.modelHeader.open(fileInfo, row)
						}
					  }, [], '上传'
					  ),
					  h('i', {
						style: { 'margin-right': '10px' },
						class: ['el-icon-delete'], onClick: (e) => {
						  e.stopPropagation(); row.fujian = ''
						}
					  }, '删除'),
					])
				},
			  });
        if (this.$route.path != '/Erp_supper') {
          this.set_all_readonly();
        }
        if(this.$route.path=='/Erp_supper'){
          this.set_login();
        }
        this.columns.forEach(x => {//要放在inited的最后,前面可能会增加column
          if (x.field === 'lifnr') {//需要改成颜色的字段
            x.cellStyle = (row, rowIndex, columnIndex) => {
              console.log(row);
              if (row.enable === 1) {
                return { background: "#00FF00" }//红色#FF0000 ,绿色#00FF00        
              } else {
                return { background: "#FF0000" }//红色#FF0000 ,绿色#00FF00     
              }
            }
          }
        });
        
    },
    searchBefore(param) {
      //界面查询前,可以给param.wheres添加查询参数
      //返回false,则不会执行查询
      return true;
    },
    /* searchAfter(result) {
      //查询后,result返回的查询数据,可以在显示到表格前处理表格的值
      return true;
    }, */
    addBefore(formData) {
      //新建保存前formData为对象,包括明细表,可以给给表单设置值,自己输出看formData的值
      return true;
    },
    updateBefore(formData) {
      //编辑保存前formData为对象,包括明细表、删除行的Id
      return true;
    },
   /*  rowClick({ row, column, event }) {
      //查询界面点击行事件
      // this.$refs.table.$refs.table.toggleRowSelection(row); //单击行时选中当前行;
    }, */
    modelOpenAfter(row) {
      //点击编辑、新建按钮弹出框后,可以在此处写逻辑,如,从后台获取数据
      //(1)判断是编辑还是新建操作: this.currentAction=='Add';
      //(2)给弹出框设置默认值
      //(3)this.editFormFields.字段='xxx';
      //如果需要给下拉框设置默认值,请遍历this.editFormOptions找到字段配置对应data属性的key值
      //看不懂就把输出看:console.log(this.editFormOptions)
      if(this.currentAction!='Add'){

        this.$store.getters.data().supper_id = row.supper_id;
        this.$refs.gridFooter.rowClick(row);
      
        if (this.$route.path != '/Erp_supper') {
          this.set_all_readonly_for_model();
        }
      }
      
    },
    set_all_readonly(){//viewgrid只读 huangxs 2022年8月10日 11:25:23
      this.editFormOptions.forEach((x)=>{
        x.forEach((itme)=>{
          itme.readonly=true;
        })
      })
      this.buttons.forEach((x)=>{
        if (x.name!='查 询'  && x.name!='高级查询'){
             x.hidden=true;
        }
        // console.log(x);
       })
 
       this.boxButtons.forEach((x)=>{
         x.hidden=true;
        })
     
    },
    set_all_readonly_for_model(){//viewgrid弹出窗体中的modelFooter组件只读 huangxs 2022年8月10日 11:25:23
     
      this.$refs.gridFooter.$refs.supp_zliao_detail.$refs.grid.buttons.forEach((x)=>{
        x.hidden=true;
      })
      /* var list=toRaw(this.$refs.modelFooter.$refs.Luxs_view_project_for_mara.$refs.grid.buttons);//获取最原始的traget数据
			while(list.length>0){//删除数据中所有的对象,最彻底的删除数组中的对象	
				list.pop();//最彻底的删除数组中的对象,包括循环不到的数组元素
			} */
    },
    set_login(){
      this.columns.unshift({
        field: "caozuo",
        title: "操作",
        width: 150,
        align: "center",
        render: (h, { row, column, index }) => {
          //下面所有需要显示的信息都从row里面取出来
          return h(
            "div", { style: { color: '#0c83ff', 'font-size': '10px', cursor: 'pointer' } },
            [
              h(
                "i", {
                style: { 'margin-right': '10px' },
                class: ['el-icon-check'],
                onClick: (e) => {
                  e.stopPropagation();
                  let turl="/api/Erp_supper/kaitong?lifnr="+row["lifnr"] +"&ls_type=1";//接受
                  this.http.post(turl, {}, true).then((r) => {
                  this.refresh();
                  }) 
                }
              }, [], '允许登录'
              ),
              h(
                "i", {
                style: { 'margin-right': '10px' },
                class: ['el-icon-close'],
                onClick: (e) => {
                  e.stopPropagation();
                  let turl="/api/Erp_supper/kaitong?lifnr="+row["lifnr"]+"&ls_type=0"//拒收
                  this.http.post(turl, {}, true).then((r) => {
                 
                  this.refresh();
                  }) 
                }
              }, [], '关闭登录'
              ),
            ])
        },
      })
    },
  }
};
export default extension;
供应商要求子表:Luxs_supp_zliao.js
/*****************************************************************************************
**  Author:jxx 2022
**  QQ:283591387
**完整文档见:http://v2.volcore.xyz/document/api 【代码生成页面ViewGrid】
**常用示例见:http://v2.volcore.xyz/document/vueDev
**后台操作见:http://v2.volcore.xyz/document/netCoreDev
*****************************************************************************************/
//此js文件是用来自定义扩展业务代码,可以扩展一些自定义页面或者重新配置生成的代码
 import abc from "@/uitils/h_common.js";//这是默认的写法
let extension = {
  components: {
    //查询界面扩展组件
    gridHeader: '',
    gridBody: '',
    gridFooter: '',
    //新建、编辑弹出框扩展组件
    modelHeader: '',
    modelBody: '',
    modelFooter: ''
  },
  tableAction: '', //指定某张表的权限(这里填写表名,默认不用填写)
  buttons: { view: [], box: [], detail: [] }, //扩展的按钮
  methods: {
    //下面这些方法可以保留也可以删除
    onInit() {  //框架初始化配置前,
      //示例:在按钮的最前面添加一个按钮
      //   this.buttons.unshift({  //也可以用push或者splice方法来修改buttons数组
      //     name: '按钮', //按钮名称
      //     icon: 'el-icon-document', //按钮图标vue2版本见iview文档icon,vue3版本见element ui文档icon(注意不是element puls文档)
      //     type: 'primary', //按钮样式vue2版本见iview文档button,vue3版本见element ui文档button
      //     onClick: function () {
      //       this.$Message.success('点击了按钮');
      //     }
      //   });

      //示例:设置修改新建、编辑弹出框字段标签的长度
      // this.boxOptions.labelWidth = 150;
      //隐藏查询界面按钮
			this.tableHeight = window.innerHeight*0.35
    },
    onInited() {
      //框架初始化配置后
      //如果要配置明细表,在此方法操作
      //this.detailOptions.columns.forEach(column=>{ });
      //框架初始化配置后
      //如果要配置明细表,在此方法操作
      //this.detailOptions.columns.forEach(column=>{ });
      this.singleSearch = null;//屏蔽快捷查询
      this.load = false;//初始化不加载数据
      this.tableHeight = 270;
      this.buttons.forEach(x => {
        //console.log(x)
       if (x.value == "Import" || x.value == "Export" || x.value=="Search" ||x.name=='高级查询') {
         //console.log("ddd");
         x.hidden = true;
         //this.$set(x, "hidden", true);
       }
     })

      this.editFormOptions.forEach(x => {
        x.forEach(item => {
          if (item.field == 'fujian') {
            //多文件上传,两个属性要同时用
            item.multiple = true;
            item.maxFile = 1;
          }
        })
      });
      this.tableHeight = 270;
      if (this.$route.path == '/Erp_supper') {
        this.load = false;
      }
    },
    searchBefore(param) {
       //操作步骤3:主表点击行时,设置查询条件
       if (this.$route.path == '/Erp_supper') {
        //产品管理界面必须选中行数据后才能查询
        if (!this.$store.getters.data().supper_id) {
          this.$message.error('请选中供应商行数据');
          return false;
        }
        //查询选中行的数据
        param.wheres.push({
          name: 'supper_id', //查询字段
          value: this.$store.getters.data().supper_id //查询值为主表的主键id值
        });
      }
      return true;
    },
    searchAfter(result) {
      //查询后,result返回的查询数据,可以在显示到表格前处理表格的值
      if (this.$route.path!="/Erp_supper"){
                abc.read_only(this);
              }
      return true;
    },
    addBefore(formData) {
     //新建保存前
      //操作步骤5:将主表选行的值添加到明细表中(注意代码生成器,明细表的ProductId字段必须设置编辑为0,并生成下model)

      formData.mainData.supper_id = this.$store.getters.data().supper_id;
      //查询后,result返回的查询数据,可以在显示到表格前处理表格的值
      return true;
    },
    updateBefore(formData) {
      
      formData.mainData.supper_id = this.$store.getters.data().supper_id;
      //查询后,result返回的查询数据,可以在显示到表格前处理表格的值
      return true;
    },
    rowClick({ row, column, event }) {
      //查询界面点击行事件
      this.$refs.table.$refs.table.clearSelection();
       this.$refs.table.$refs.table.toggleRowSelection(row); //单击行时选中当前行;
    },
    modelOpenAfter(row) {
      //点击编辑、新建按钮弹出框后,可以在此处写逻辑,如,从后台获取数据
      //(1)判断是编辑还是新建操作: this.currentAction=='Add';
      //(2)给弹出框设置默认值
      //(3)this.editFormFields.字段='xxx';
      //如果需要给下拉框设置默认值,请遍历this.editFormOptions找到字段配置对应data属性的key值
      //看不懂就把输出看:console.log(this.editFormOptions)
    },
    modelOpenBeforeAsync() {
      //操作步骤4:打开弹出框时判断是否为新建操作,新建时必须让选中行数据

      //新建时产品管理界面必须选中行数据后才能弹出框
     // if (this.$route.path == '/Erp_supper') {
        if (this.currentAction == 'Add') {
          if (!this.$store.getters.data().supper_id) {
            this.$message.error('请选中供应商行数据!');
            return false;
          }
        }
      //}
      return true;
    }
  }
};
export default extension;
连接页:supperGridFooter.vue
/* 供应商对应的多个明细表页面组合 */
<template>
  <div class="tabs">
    <el-tabs v-model="activeName" type="border-card" @tab-click="tabClick">
      <el-tab-pane label="供应商资料" name="supp_zliao_detail">
        <supp_zliao_detail ref="supp_zliao_detail"></supp_zliao_detail>
      </el-tab-pane>
          <el-tab-pane label="供应商联系人" name="erp_supper_contacts">
        <erp_supper_contacts ref="erp_supper_contacts"></erp_supper_contacts>
      </el-tab-pane>
   <!--    <el-tab-pane label="供应商定点物料" name="size">
       
         <Luxs_supp_mara_detail ref="Luxs_supp_mara_detail"></Luxs_supp_mara_detail>
      </el-tab-pane> -->
    </el-tabs>
  </div>
</template>
<script>
import supp_zliao_detail from '@/views/myerp/base_data/Luxs_supp_zliao.vue';//明细表1供应商资料上传
import erp_supper_contacts from '@/views/myerp/base_data/erp_supper_contacts.vue';//供应商联系人

//import Luxs_supp_mara_detail from '@/views/myerp/base_data/Luxs_supp_mara_detail.vue';//明细表1供应商资料上传
//import ProductSize from '@/views/demo/product/Demo_ProductSize';
export default {
  components: {
    supp_zliao_detail,
    erp_supper_contacts,
   // Luxs_supp_mara_detail
  },
  data() {
    return {
      activeName: 'supp_zliao_detail'
    };
  },
  methods: {
    tabClick(params) {
      console.log('选项卡点击事件');
    },
    rowClick() {
      //主表(产品管理)点击行事件
     
      //操作步骤2:调用两张表明细表的查询方法
      
      //浮脉查询条件配置,见Demo_ProductColor.js、Demo_ProductSize.js文件
      this.$refs.supp_zliao_detail.$refs.grid.search();
      this.$refs.erp_supper_contacts.$refs.grid.search();
     // this.$refs.Luxs_supp_mara_detail.$refs.grid.search();
      
      //加载尺寸明细表数据
      //this.$refs.size.$refs.grid.search();
    }
  }
};
</script>
<style scoped>
.tabs {
  padding: 15px;
}
.tabs >>> .el-tabs {
  box-shadow: none;
}
.tabs >>> .el-tabs__content {
  padding: 0 !important;
  padding-bottom: 15px;
}
</style>

40、记住forEach方法中途不能return ,否则会返回错误

get_zd_type(column_name) {//voltable获取column_name类型的type
      var rtn = "";
      this.editFormOptions.forEach((s) => {
        s.forEach((i) => {
          if (i.field == column_name) {
            //console.log(i.type);
            rtn = i.field;
            // retrun i.field  //记住forEach方法中途不能return ,否则会返回错误
          }
        });
      });
      return rtn;
},

41、基于VUE和dotnet独立文件上传

<template>
 <view>
  <vol-alert>
   <view></view>
  </vol-alert>
  <u-upload
    :fileList="fileList1"
    @afterRead="afterRead"
    @delete="deletePic"
    name="1"
    multiple
    :maxCount="10"
   ></u-upload>
  <view class="">
  </view>
 </view>
</template>

<script>
 export default {
  data() {
   return {
    fileList1: []
   }
  },
  onLoad() {
  },
  methods: {
   deletePic(event) {
    this[`fileList${event.name}`].splice(event.index, 1)
   },
   // 新增图片
   async afterRead(event) {
       // 当设置 mutiple 为 true 时, file 为数组格式,否则为对象格式
       let lists = [].concat(event.file)
       let fileListLen = this[`fileList${event.name}`].length
       lists.map((item) => {
        this[`fileList${event.name}`].push({
         ...item,
         status: 'uploading',
         message: '上传中'
        })
       })
       for (let i = 0; i < lists.length; i++) {
        const result = await this.uploadFilePromise(lists[i].url)
        let item = this[`fileList${event.name}`][fileListLen]
        this[`fileList${event.name}`].splice(fileListLen, 1, Object.assign(item, {
         status: 'success',
         message: '',
         url: result
        }))
        fileListLen++
       }
      },
   uploadFilePromise(url) {
    //console.log(url)
    return new Promise((resolve, reject) => {
     let a = uni.uploadFile({
      url: this.http.ipAddress + "api/Fk_reim/upload",
      filePath: url,
      name: 'fileInput',//vol框架上传固定名称
      formData: {
      },
      header: {
       "Authorization": this.$store.getters.getToken()//vol框架授权
      },
      success: (res) => {
       setTimeout(() => {
        resolve(res.data.data)
       }, 1000)
      }
     });
    })
   },

  }
 }
</script>
<style>
</style>

42、this.textInline=false;//voltable表内容换行

43、隐藏VOL弹出窗体字段,注意editFormOptions主要两层循环

	this.editFormOptions.forEach(x => {//隐藏弹出窗体字段
	        x.forEach(xx => {
	          console.log(xx);
	          if (xx.field === "customer_id") {
	           xx.hidden=true;
	          }
	        })
	      })

44、“基于数据库视图生成的viewgrid,字段编辑类型为selectList,下拉表如果有多条数据显示字典编码,下拉表一条数据显示正常”,问题的解决方法,在代码生成器中需要设置该字段为编辑行1,下来表才能正常显示;

45、修改VOL平台voltable整体样式在App.vue中修改和修改voltable表头样式,目录src\components\basic\VolTable.vue

.v-table ::v-deep(.el-table__header th) {
  /* padding: 0px !important; */
  background-color: #f8f8f9 !important;
  font-size: 10px;
  height: 25px;
  padding: 1px;
  color: #616161;
}

46、VOLForm只读属性disabled=“true”

47、窗体高度、浏览器高度

window.outerHeight 浏览器高度 单位px
window.outerWidth 浏览器宽度 单位px
window.innerHeight 浏览器内可用高度 单位px

48、在OnInit时间中增加this.tableHeight = window.innerHeight*0.25

//注意:需要放在放onInit中在onInited无效25%的屏幕高度 ,自适应

49、浮动显示 voltable.vue ,浮动提示第64行 :show-overflow-tooltip=“true”

50、高级查询的labelwitdh设置this.labelWidth=this.labelWidth+20;

51、设置固定显示所有查询条件,onInit(){ this.setFiexdSearchForm(true)};

52、ViewGrid中excel导入前事件处理

importExcelBefore (formData) { //导入excel导入前
    //往formData写一些其他参数提交到后台,
    // formData.append("val2", "xxx");
    //后台按下面方法获取请求的参数
    // Core.Utilities.HttpContext.Current.Request("val2");
    return true;
  },

53、前端提示确认、确认提示

import { ElMessage, ElMessageBox } from 'element-plus';
 ElMessageBox.confirm(
      '你确定要重新获取吗?',
      '警告',
      {
        confirmButtonText: 'OK',
        cancelButtonText: '取消',
        type: 'warning',
      }
    )
      .then(() => {
        let turl = "/api/Luxs_rfq/get_last_part_js?rfq_no=" + this.$store.getters.data().curr_rfq_row["rfq_no"]
        this.http.post(turl, {}, true).then((r) => {
          this.refresh();
          this.$Message.success('获取成功');
        })
      })
      .catch(() => {
        ElMessage({
          type: 'info',
          message: '取消!',
        })
      })

54、VUE端更新数据后需要刷新父页面的中其他子页面的viewgrid控件,可以不断尝试增加.$parent,例如,

//因为Ls_pro_npqr.$refs.grid.refresh()是固定的
this.$parent.$parent.$parent.$parent.$refs.Ls_pro_npqr.$refs.grid.refresh();
this.$emit("parentCall", ($vue) => {//viewgrid弹出的窗体
                  $vue.refresh();
    });

55、SQLServer建库是字符字段尽量使用varchar(N),因为Linq中trim()使用可能会报引用空值错;

56、VUE页面跳转this.$router.push

this.$router.push({ path: "/Ls_pro_npqr_head", text: "生成成功" });
setTimeout(() => {//等一下再刷新,否则无效
             window.location.reload();
            }, 20);

57、gridview弹出窗体移动可以移动,拖动,this.boxOptions.draggable=true;//gridview弹出窗体移动,在onInit中添加

this.boxOptions.draggable=true;//gridview弹出窗体移动,在onInit中添加

58、gridview显示序号this.columnIndex=true;//显示序号

this.columnIndex=true;//显示序号

59、前端searchBefore中可以传给后端特殊参数:

//前端:
param.value =this.$store.getters.data().ls_pro_npqr_head_id;//前端特殊参数给后端
//后端:在PageGridData中接收 
Guid ls_pro_npqr_head_id = options.Value.ToGuid();
var rtn = base.GetPageData(options);//一定要放在最后

60、C# 自动补0

Int a=6;
a.ToString().PadLeft(5,'0');//输出00005

61、前端判断不为空:

if(this.$store.getters.data().banb){//不为空时
        param.wheres.push({
          name: 'npqr_bbh', //查询字段
          value: this.$store.getters.data().banb //版本号
 	   displayType:"selectList"
        });
 }

62、前端自定义导出文件名称,导出文件名

onInit() {
	this.downloadFileName='文件.xlsx'
}

63、前端冻结列:fixed

this.columns.forEach(e=>{
        if (e.field=='receive'){
          e.fixed="right"
        }
       });

64、VUE端全局变量,设置、获取,js语法

设置,localStorage.setItem('lrcenter', "5654");
获取,localStorage.getItem('lrcenter')

65、当前VUE访问页面的地址路径,路由判断

if (this.$route.path=="/tree_bom"){
        this.load=false;
 }

66、el-button按钮icon加载方式

<el-button><el-icon style="vertical-align: middle"><Search /></el-icon> </el-button>
<scritp>
import {
  Check,
  Delete,
  Edit,
  Message,
  Search,
  Star,
} from "@element-plus/icons-vue";
components: {
    Search,
  },
</scritp>

67、Viewgird单行选

this.single=true;//单选
this.detailOptions.single=true;//明细表单选

68、viewgird 选中行checkbox行事件,只有单选才触发rowChange事件

rowChange(row) {//单选不能少,否则前台操作有错误,配合this.single=true by hxs; //选中行checkbox行事件
      //console.log(row);
      this.$store.getters.data().ls_pro_npqr_head_id = row.ls_pro_npqr_head_id;
      //this.$refs.gridFooter.rowClick(row);
    },

69、前端,按钮最大数量,this.maxBtnLength=10

70、Viewgrid在线编辑

//前端
add(){ this.$refs.table.addRow({})}
add(){ this.$refs.table.unshift({})}

71、框架前端更新数据手工保存,模拟框架提交update,前端保存数据

let sdata={
  mainData:this.row,//row为
}
let turl ="/api/luxs_gongf_doc/update"
this.http.post(turl, sdata, true).then((r) => {
    });

72、this.ck=false;//viewgrid check 勾选去掉

73、VOL平台弹出窗体的适宜、最优宽度、高度、适宜宽 合适窗体高,height=”500”,width=”1200”,适宜笔记本和台式机,高度<=500,宽度<=1300 弹出框模板volbox

<template>
  <div>
    <vol-box
      :lazy="true"
      v-model="dialogVisible"
      title="发放资料"
      :height="500"
      :width="1200"
      :padding="1"
      :draggable="true">
      <div>

      </div>
 
      <template #footer>

        <div>
<el-button
            type="primary"
            size="mini"
            v-show="isshow1"
            @click="get_all_req_list"
            >重新获取全部签收清单</el-button
          >
<el-button
            type="primary"
            size="mini"
            v-show="isshow1"
            @click="confirm_req_list"
            >供应商提交文件签收单</el-button
          >
 
          <el-button type="default" size="mini" @click="dialogVisible = false"
            >关闭</el-button
          >
   
        </div></template
      >
    </vol-box>
  </div>
</template>
<script>
import VolBox from "@/components/basic/VolBox.vue";
import { ElMessage, ElMessageBox } from 'element-plus';
import view_fs_rfq_form from '@/extension/myerp/other/view_fs_rfq_form.vue';
import abc from "@/uitils/h_common.js";//这是默认的写法

export default {
components: {VolBox,abc },
data() {
 return {
 dialogVisible: false,
 isshow1:true,
 };
 },
 setup() {},
 destroyed() {},
 mounted() {
    this.$nextTick(() => { 
    //this.$refs.Erp_mara.$refs.grid.clearSelection();
    });
},
methods: {}
};
</script>

74、this.$tabs.open打开新的页面

this.$tabs.open({
            text: "RFQ",
            path: '/Luxs_rfq',
            query: { rfq_no: "RFQ-221205-1" }
          });
//参数接受方式 This.$route.query.rfq_no
//防止参数二次传入丢失,注意如下
//所有页面默认开启了缓存,即所有页面只会执行一次created与onInit

解决方法1:在router文件夹下找到路由配置里加上:  (具体见router->form.js中的/kindEditor配置)
   meta: {
      keepAlive: false
   } 

解决方法2:如果需要每次进入页面查询数据,在[表名.js]文件中添加:
   onActivated(){
   }

75、http头部携带信息

this.http.post(url,{},true,{'Content-Type':'application/json'}).then

76、前端行颜色、列颜色

 this.columns.forEach(x => {//要放在inited的最后,前面可能会增加column
  if (x.field == 'detail') {//需要改成颜色的字段
    x.cellStyle = (row, rowIndex, columnIndex) => {
      //console.log(row);
      if(row.isback==1){
        return {background: "#FF0000"}//红色#FF0000 ,绿色#00FF00  黄色 #FFFF00       
        //return { background: "#2196F3", color: "#ffff" } // 蓝色 #2196F3  白色  #ffff  (带背景)
      }   
    }
  }
});

77、VOL平台用户对用菜单URL的SQL语句

--单角色
select b.username,e.url from Sys_User b,Sys_RoleAuth d,Sys_Menu e where b.role_id=d.role_id and  d.menu_id=e.menu_id
--多角色(待验证)
select b.*,e.* from 
Sys_User b,Sys_UserRole c,Sys_RoleAuth d,Sys_Menu e where b.user_id=c.userid and c.roleid=d.role_id and d.menu_id=e.menu_id

78、前端load参数查询方法 --voltable在线载入数据方法 2023年1月4日 16:58:56

<vol-table
          ref="tableList"
          :loadKey="true"
          :columns="columns"
          :pagination-hide="false"
          :height="230"
          :defaultLoadPage="true"
          @loadBefore="loadBefore"
          @loadAfter="loadTableAfter"
          :endEditAfter="endEditAfter"
          url="/api/fk_reimtask/getPageData"
          :row-index="true"
          :columnIndex="true"
          :index="true"
          click-edit="false"
></vol-table>

import VolTable from "@/components/basic/VolTable.vue";
components: {VolTable,}
columns: [
        {
          field: "to_username",
          title: "接受人1",
          type: "string",
          width: 220,
          hidden: true,
          align: "left", 
        },
        {
          field: "to_userrealname",
          title: "任务接受人",
          type: "string",
          width: 180,
          align: "left",
           cellStyle: (row, rowIndex, columnIndex) => {
              if (row.ishandled != 1) {
                return { background: "#2196F3", color: "#ffff" } // 蓝色 #2196F3 白色  #ffff  
              }
            },
        },
        {
          field: "ishandled",
          title: "是否处理",
          type: "int",
          bind: { key: "cq", data: [] },
          width: 110,
          align: "left",
        },
        
      ],

 let params = {
            page: 1, //分页页数(可不填)
            rows: 30, //分页大小(可不填)
            sort: "Title", //可不填
            order: "desc", //可不填desc/asc
            wheres:[{ name: "Title", value: this.formInline.title,displayType:"like" }],
          };
 this.$refs.tableList.load(params, true); //查询审核页的业务数据

79、前端空模版文档、空文档 模版文档 空模板

<template>
 <VolHeader style="margin: 30px 0" :text="text">
      <template #content>VolHeader这里可以定义显示内容</template>
      <div style="text-align: right; padding-top: 4px">
        <el-button type="primary" plain size="mini" @click="click"
          ><i class="el-icon-search"></i>按钮</el-button
        >
      </div>
</VolHeader>
<look_all_rfq_ppap_view ref="look_all_rfq_ppap_view"></look_all_rfq_ppap_view>
</template>

<script>
import look_all_rfq_ppap_view from "@/views/myerp/base_data/look_all_rfq_ppap_view.vue";
import VolHeader from "@/components/basic/VolHeader.vue";
import http from "@/api/http.js";
export default {
components:{
    look_all_rfq_ppap_view,
},
data(){
  return{
    
  }
},
created(){//在其他页面加载之前运行
this.$store.getters.data().look_his="my";
//console.log("dd");
},
mounted() {
  this.$refs.look_all_rfq_ppap_view.$refs.grid.table.cnName="我的历史流程"
 /*  setTimeout(() => {
      this.$nextTick(()=>{    
      //this.$refs.look_all_rfq_ppap_view.$refs.grid.refresh();
  })
  }, 500); */

},
methods:{
}
}
</script>

<style>

</style>

80、VUE监听全局变量的是否变化,如果有变化就运行watch,监听变量

computed:{
    data_mara(){
      return this.$store.getters.data().mara_id;this.$store.getters.data().mara_id全局变量的变化
    },
  },
  watch:{//用于监听,如果有变化就运行watch
    data_mara(n,m){
      if(this.$store.getters.data().select_mara_row){
            let tt={
            matnr:this.$store.getters.data().select_mara_row.matnr,
            maktx:this.$store.getters.data().select_mara_row.maktx
          }
        this.todata_mara.push(tt);
      }
      //console.log(m+"变化前");
      //console.log(n+"变化后");
    },
  },

81、前端 行转列 row to form 行转form row转form

part_editFormFields: {"matnr":"","maktx":"",},
part_editFormOptions: [[{"title":"物料编码","required":true,"field":"matnr","type":"text"},
                               {"title":"物料描述","required":true,"field":"maktx","type":"text"},
                              ]],
  
 let part_row = this.$store.getters.data().curr_part_row;//从后端传入的
 for (var colname in this.part_editFormFields) {
      let colname_type = this.get_zd_type(colname,this.part_editFormOptions);
      if (colname_type == "selectList" ||colname_type =="checkbox") {
        this.$refs.part_form.formFields[colname] = part_row[colname].split(","); 
    //selectList,checkbox类型的字段只能接受数组
      } else {
        this.$refs.part_form.formFields[colname] = part_row[colname];
      }
}

//判断字段的类型,注意selectList checkbox
 get_zd_type(column_name,editFormOptions) {
      //voltable获取column_name类型的type
      var rtn = "";
      editFormOptions.forEach((s) => {
        s.forEach((i) => {
          if (i.field == column_name) {
            //console.log(i.type);
            rtn = i.type;
            // retrun i.field  //记住forEach方法中途不能return ,否则会返回错误
          }
        });
      });
      return rtn;
    },

82、编辑表单select默认值,select动态赋值 动态下拉

modelOpenAfter() {
  //新建弹出框时,设置设置默认订单类型
  if (this.currentAction == "Add") {
    this.editFormOptions.forEach(item => {
      item.forEach(x => {
        //如果是编辑帐号设置为只读
        if (x.field == "OrderType") {
          //新建时默认选择中第一个下拉框的值,如果要选中其他的值,请遍历x.data获取key
          /*注意:如果下拉框的数据源是自定义sql,并且key是数字,请将(x.data[0].key*1)转换成数字*/
          this.editFormFields.OrderType = x.data[0].key;
          //可以指定其他input标签的默认值
          this.editFormFields.TranNo="8888"
        }
      })

    })
  }
    }
//select动态下拉赋值
 modelOpenAfter(row) {
      console.log(this.zid_table);
      this.editFormOptions.forEach(x=>{
          x.forEach(item=>{
            if(item.type=="select"){
              var tt=this.zid_table.filter(x=>{return item.title==x.shuom});//查询满足条件的数据
              let t1=tt.map(s=>{//组合select清单数据
                return{
                  key:s.shuoM_1,
                  value:s.shuoM_1,
                  label:s.shuoM_1
                }
              });
              item.data=t1;//赋值给下拉清单
            }
          })
         
      })
    }


//注意在后台定义下拉SQL时要防止有空数据,否则前台报错,如添加,where xxx is not null
select SHUOM_1 as 'key' , SHUOM_1 as 'value' from yg_xx where SHUOM_1 is not null

83、前端viewgrid添加列,在onInited的最后添加,插入列,注意,如果图表无效,字体就会变小不会斜体

 this.columns.unshift({
        field: "随便写",
        title: "详细",
        width: 130,
        align: "center",
        render: (h, { row, column, index }) => {
          //下面所有需要显示的信息都从row里面取出来
          return h(//这个模式是带图标的模式
            "div", { style: { color: '#0c83ff', 'font-size': '10px', cursor: 'pointer' } },
            [
              h(
                "i", {
                  style: { 'margin-right': '10px' },
                  class: ['el-icon-edit'],// class: ['el-icon-'],表示无效
                  onClick: (e) => {
                  e.stopPropagation();
                  this.$nextTick(() => {   
                    let turl="/api/change_ppap_view/get_ziliao_all?luxs_rfq_id="+row["luxs_rfq_id"]
                    this.http.post(turl, {}, true).then((r) => {
                    this.$store.getters.data().curr_part_row=r.mara;
                    this.$store.getters.data().curr_supper_row=r.supper;
                    this.$store.getters.data().curr_rfq_row=row
                    //console.log(r);
                    if (row["stepid"]==30){//只有30步骤供应商签收
                      this.$refs.gridHeader.open_w();//可以写入
                    }else{
                      this.$refs.gridHeader.open_w("readonly");
                    }                   
                  });
                  })
                  
                }
              }, [], '发放资料'
              ), 
              h(
                "i", {
                  style: { 'margin-right': '10px' },
                  class: ['el-icon-printer'],
                onClick: (e) => {
                  e.stopPropagation();
                  console.log(row);
                  let turl="/api/fk_reimfloat/get_curr_reimfloat?reim_id="+row["luxs_rfq_id"]
                  this.http.post(turl, {}, true).then((r) => {
                    //console.log(r);
                    this.$refs.gridBody.open_pop_look_status();
                    this.$store.getters.data().reimid=row["luxs_rfq_id"];
                    //console.log(this.$store.getters.data().reimid+"----------------");
                    this.$nextTick(()=>{
                      
                      this.$refs.gridBody.$refs.pop_look_status.$refs.el_step.step_items.splice(0,this.$refs.gridBody.$refs.pop_look_status.$refs.el_step.step_items.length)
                      this.$refs.gridBody.$refs.pop_look_status.$refs.el_step.step_items.push(...r);
                      let ii=r.findIndex((x)=>{return x.stepid==row["stepid"]})//返回第一次出现位置
                      this.$refs.gridBody.$refs.pop_look_status.$refs.el_step._active=ii;  
                    })
                   
                  })
                  
                }
              }, [], '进度'
              ),
            ])
        },
      })

84、前端viewgrid动态增加列,这种方式比较简短

 this.columns.unshift({
        field: '操作',
        title: '操作',
        width: 80,
        align: 'center',
        formatter: (row) => {
          return '<a style="font-size: 14px;border-bottom: 1px solid #3a8ee6;color: #3a8ee6; padding-bottom: 2px;  cursor: pointer;">创建变更</a>';
        },
        click: (row) => {
          this.$tabs.open({//创建新的tab页面
            text: "创建变更",
            path: '/change_ppap_view',
            query: { part_no: row.part_no,part_name:row.part_name}
          });
        }
      });

85、前端图片放大预览 this.base.previewImg(url)

this.base.previewImg(url)

86、前端给volform字段增加extra额外功能

this.editFormOptions.forEach(x => {
          x.forEach(item => {
            if (item.field == 'pno') {
              //editFormOptions更多配置见volform组件api
              item.extra = {
                icon: "el-icon-setting", //显示图标
                text: "自动生成编码",//显示文本
                style: 'color:red;cursor: pointer;',
                click: item => {//触发事件
                  var turl = "/api/Luxs_project/get_new_project_no?serial_name=projectmanage";
                  this.http.post(turl, {}, true).then((s) => {
                    this.editFormFields.pno=s;
                  });
                }
              }
            }
          })
        })

87、volform使用 可以使用可视化编辑 volbox volform 数据的获取再调用他的父页面上,然后给本页的data赋值

<template>
  <div>
    <vol-box
      :lazy="true"
      v-model="dialogVisible"
      title="发放资料"
      :height="500"
      :width="1200"
      :padding="1"
      :draggable="true"
    >
      <div>
        <vol-form ref="form"
            :labelWidth="80"
            :load-key="true"
            :formFields="fields"
            :formRules="formOptions">
  </vol-form>
      </div>

      <template #footer>
        <div>
          <el-button
            type="primary"
            size="mini"
            v-show="isshow1"
            @click="get_all_req_list"
            >重新获取全部签收清单</el-button
          >
          <el-button
            type="primary"
            size="mini"
            v-show="isshow1"
            @click="confirm_req_list"
            >供应商提交文件签收单</el-button
          >

          <el-button type="default" size="mini" @click="dialogVisible = false"
            >关闭</el-button
          >
        </div></template
      >
    </vol-box>
  </div>
</template>
<script>
import VolBox from "@/components/basic/VolBox.vue";
import VolForm from "@/components/basic/VolForm.vue";
import { ElMessage, ElMessageBox } from "element-plus";
import view_fs_rfq_form from "@/extension/myerp/other/view_fs_rfq_form.vue";
import abc from "@/uitils/h_common.js"; //这是默认的写法

export default {
  components: { VolBox, abc,VolForm },
  data() {
    return {
      dialogVisible: false,
      isshow1: true,
       fields: {"title":null,"context":null,"fujian":[],"Creator":null,"CreateDate":null}, 
       formOptions: [[{"field":"title","title":"标题","type":"text","required":false,"readonly":true,"colSize":null},
                {"field":"Creator","title":"创建人","type":"text","required":false,"readonly":true,"colSize":null},
                {"field":"Creator","title":"创建人","type":"text","required":false,"readonly":true,"colSize":null}],
                [{"field":"context","title":"编辑器","type":"editor","required":false,"readonly":true,"height":200,"colSize":12,"url":""}],
                [{"field":"fujian","title":"附件","type":"file","required":false,"readonly":true,"colSize":12,"maxSize":3,"fileInfo":[],"multiple":false,"autoUpload":false,"maxFile":5,"url":""}]],
       
    };
  },
  setup() {},
  destroyed() {},
  mounted() {
    this.$nextTick(() => {
       
    });
  },
  methods: {

  },
};
</script>
//父页面获取数据
open_info(item){
      this.$nextTick(()=>{
        this.http.post("/api/infom_notice/get_one_inform_notice?infom_notice_id=D539CAD2-A51D-48A0-B810-85E7D3686508",{},true).then(result=>{
                    console.log("url");
                    this.$refs.look_inform.fields=result
                    console.log(this.$refs.look_inform.fields);
                    this.$refs.look_inform.dialogVisible=true;
        })
      })
    },

88、判断前端对象属性为数组

Let psw={
A:’’,
B:[],
C:[]
}
var keys=Object.keys(psw);
for (let index = 0; index < keys.length; index++) {
  const e = keys[index];
  if(psw[e].constructor === Array){//判断psw对象中哪些属性为数组的,如果是转为字符串
      psw[e]=psw[e].join();//,分割的字符串
  } 
}


var keys=Object.keys(savedata.mainData);//需要把selectList字段类型转换为逗号分割的字符串
      for (let index = 0; index < keys.length; index++) {
        const e = keys[index];
    console.log(savedata.mainData[e]);
        if(Array.isArray(savedata.mainData[e])){//判断savedata.mainData对象中哪些属性为数组的,如果是转为字符串
        console.log(savedata.mainData[e]+"array..........");
            savedata.mainData[e]=savedata.mainData[e].join(',');//,转换为,分割的字符串,用于volform提交数据
        }
      }

89、前端日期格式化

Var riqi=(new Date).getFullYear()+'-'+(new Date).getMonth()+1+"-"+(new Date).getDate()//

90、通过iso文件安装.net安装程序(需要修改iso的盘符)

DISM /Online /Enable-Feature /FeatureName:NetFx3 /All /LimitAccess /Source:d:\sources\sxs

91、前端viewgrid的tooltip的使用,在OnInited()中

//表格显示Tooltip提示
  this.columns.forEach(col => {
    if (col.field == 'to_userrealname') {
      col.title = "单元格Tooltip(鼠标放上来看效果)";
      col.render = (h, { row, column, index }) => {
        return <el-popover
          placement="top-start"
          title="提示信息"
          width={350}
          trigger="hover"
          content={row.to_userrealname+",这里是用render渲染的表格提示,示例见:App_Appointment.js"}
        >
          {{ reference: <span>{row.to_userrealname}</span> }}
        </el-popover>
      }
    }
  })

92、voltable新增selectionChange事件,选择行事件,只有单选才触发,volform中字段onChange事件

<vol-form ref="form"
    :labelWidth="80"
    :load-key="true"
    :formFields="fields"
    :formRules="formOptions">
 </vol-form>
 <script>
 import VolForm from "@/components/basic/VolForm.vue";
 export default{
  components: {
			VolForm 
		},
  data(){
    fields: {"progress":""}, 
    formOptions: [[{"dataKey":"progress_step","data":[],"title":"进度(%)","field":"progress","type":"select",
        "colSize":2,onChange:(val)=>{
          let params = {
					page: 1, //分页页数(可不填)
					rows: 30, //分页大小(可不填)
					sort: "", //可不填
					order: "", //可不填
					wheres: [{
						name: "progress",
						value: val,
						displayType: "select"
					}, ],
				};
				// params.value="out";//说明从系统外登录
				this.$refs.tableList.load(params, true); //查询审核页的业务数据
        }}]],
  }
 }
</scritp>

93、把弹出框父组件传值给vol的子组件(viewgrid) ,父传子 父组件 子组件 需求,根据子组件的数据searchAfter结果确定父组件的显示内容

原理,在子组件中编写函数(函数功能,把父组件对象传给子组件)让父组件调用,在组件中的触发位置修改父组件的数据
父组件中isshow_back变量需要在子组件中进行控制luxs_gongf_doc为值控件,在子组件中定义函数获取父件的this

//父组件如下:
<template>
  <vol-box
    :lazy="true"
    v-model="model"
    title="供方文件清单"
    :height="500"
    :width="1200"
    :draggable="true"
    :padding="5"
  >
    <div>
      <luxs_gongf_doc ref="luxs_gongf_doc" ></luxs_gongf_doc>
    </div>
  </vol-box>
</template>
<script>
import VolBox from "@/components/basic/VolBox.vue";
import luxs_gongf_doc from "@/views/myerp/base_data/luxs_gongf_doc.vue";
//这里使用的vue2语法,也可以写成vue3语法
export default {
  components: { "vol-box": VolBox, luxs_gongf_doc },
  methods: {},
  data() {
    return {
      isshow_back:false,
         };
  },
  methods: {
    open(table) {
      this.$nextTick(() => {
           this.$refs.luxs_gongf_doc.$refs.grid.search();
 	      this.$refs.luxs_gongf_doc.$refs.grid.get_p_object(this)//把父对象传给vol的子组件(viewgrid)

      });
    },
  },
};
</script>
//子组件如下:(viewgrid控件vol框架生成代码)
Methods:{
p_object:null,//父页面对象
onInited() {
this.fun1();//根据登录人角色判断是否增加doc审核按钮
},
fun1(){//根据登录人角色判断是否增加doc审核按钮
      let url = "/api/fk_reimfloat/get_user_info"//
      this.http.post(url, {}, true).then((r) => {
    
        if(this.$store.getters.data().rfqrow.stepid == 90 && this.isgys==0) {
         this.p_object.isshow_back=1;//调用父对象的属性,控制父界面的显示
}
      });
    },
 	get_p_object(parent){//获取父对象
      this.p_object=parent;
    },

}

94、刷新前端的下拉表内容 刷新字典,使用场景如:新建编辑级联下拉框保存后,调用此方法刷新级联的数据

modelOpenAfter(row) {
      this.initDicKeys()//使用场景如:新建编辑级联下拉框保存后,调用此方法刷新级联的数据源
    }

95、viewgrid代码生成器生产的代码,必须要登录才能使用,注意这是坑

96、桌面端无需登录就能使用vol-table,系统外登录,需要修改3个地方,无需登录 无须登录,免登陆

//首页打开的路由和api的路由都要修改:
{
        path: '/Ok3w_Article',
        name: 'Ok3w_Article',
        component: () => import('@/views/mytable/jrtable/Ok3w_Article.vue'),
        meta: {
          anonymous: true//无需登录
        }   
}

//后台控制器中增加
[HttpPost, Route("GetPageData"),AllowAnonymous]
[ApiActionPermission()]

97、前端viewgrid不显示设置按钮 this.showCustom=false;

98、多附件上传,修改附件数量 上传文件数量

this.editFormOptions.forEach(x => {
		 		           x.forEach(item => {
		 		             if (item.field == 'fujian') {
								 //多文件上传,两个属性要同时用
								 item.multiple = true;
		 						 item.maxFile = 3;
		 						 }
		 					})
});

99、进度条, 表格上添加自定义控件

//在onInited中增加
 this.columns.unshift({
  title: "进度",
  field: "进度",
  width: 250,
  align: "center",
  render: (h, { row, column, index }) => {
  if (row.progress==100){
  return <div> <el-progress text-inside="false" status="success" stroke-width="26" percentage={row.progress}></el-progress>
        </div>
  }
  else
  {
    return <div> <el-progress text-inside="false" status="exception" stroke-width="26" percentage={row.progress}></el-progress>
          </div>
  }
  }
})

100、去掉表格html标识 html标记

this.columns.forEach(x=>{
	   	if(x.field == "a_decribe"){
	   	   x.formatter = (row)=>{
	   	     return row["a_decribe"];//直接过滤html标识
	   	   }
	   	}
	   });

101、定义数组,循环数组取数

let toData_username=new Array();
for(var i=0;i<=this.$refs.jroadeparttree.toData.length-1;i++){
  toData_username.push(this.$refs.jroadeparttree.toData[i].username);
}

102、

103、

104、

105、

106、

107、

108、

109、

二、后端

1、动态SQL语句,要有dynameic类型来取数比较方便,执行SQL参数化查询 直接执行sql

 //SqlDapper中的in操作如果用参数
string tsql = "select * from bai_event where eventId in(@_eventid)";
List<dynamic> all_bai_event = DBServerProvider.SqlDapper.QueryList<dynamic>(tsql, new { _eventid = eventid }).ToList();

2、linq的多字段select的动态接受,select别名的使用,not in的使用

IQueryable<Fk_yusuan> iq_yuan=DBServerProvider.DbContext.Set<Fk_yusuan>();
string[] arry_rcntr = iq_yuan.Select(s => s.rcntr).Distinct().ToArray();
IQueryable<Fk_cntr> Fk_cntr_queryeable = Fk_cntrRepository.
                Instance.FindAsIQueryable(t => arry_rcntr.Contains(t.rcntr_no));//linq的in 的使用
dynamic array_cntr = Fk_cntr_queryeable.Select(s =>new {value=s.rcntr_no, label=s.rcntr }).Distinct().ToList();//linq的select的别名使用,多字段select动态接受

3、部分更新,需要Repository,只更新部分字段

IFk_reimfloatRepository repository = Fk_reimfloatRepository.Instance;
string loginid = UserContext.Current.GetUserInfo(UserContext.Current.UserId).UserName;
ff.username = loginid;
ff.bdatetime = DateTime.Now;
repository.Update(ff, x => new { x.fk_reimfloat_id, x.remark,x.bdatetime,x.username },true);//只更新相关字段

4、EFCore批量update,先查询出来再更新相关中字段 ★★★★★改

List<Ls_pro_bom> ls_Pro_Boms = DBServerProvider.DbContext.Set<Ls_pro_bom>().ToList();
List<Ls_pdb> Ls_pdbs = DBServerProvider.DbContext.Set<Ls_pdb>().ToList();
for (int i = 0; i < ls_Pro_Boms.Count; i++)
{
    ls_Pro_Boms[i].isinpdb = Convert.ToInt16(Ls_pdbs.Exists(x => x.matnr == ls_Pro_Boms[i].mara_for_ls));
    DBServerProvider.DbContext.Set<Ls_pro_bom>().Update(ls_Pro_Boms[i]);//注意这里只更新了isinpdb 字段,其他字段值没有动
}
DBServerProvider.DbContext.SaveChanges();

//或者
  using (var db = DBServerProvider.DbContext)
    {
       DbSet<Fei_typelist> _rep = db.Set<Fei_typelist>();
       foreach (dynamic item in array_sap)
        {
            Fei_typelist _ls = new Fei_typelist();
           _ls.fei_typename = item.TXT50;
            _ls.company_oa_id = Convert.ToInt32(item.BUKRS);
            _tt = _ls;
           _rep.Update(_ls);
      }
      db.SaveChanges();注意不能放在foreach循环里面
 }

5、EFCore批量删除模版,先查询再删除 ★★★★★删、查

Luxs_rfq_receipt[] keys=DBServerProvider.DbContext.Set<Luxs_rfq_receipt>().Where(w => w.rfq_no ==rfq_no).ToArray();
DBServerProvider.DbContext.Set<Luxs_rfq_receipt>().RemoveRange(keys);

//查询时需注意,字段可能为空情况,增加!string.IsNullOrEmpty(w.SHUOM_1) 判断,或者ToList方法不先使用
 var yg_xx_list = DBServerProvider.DbContext.Set<yg_xx>().ToList();//亿格配置变量
 string xuanx_z = yg_xx_list.Where(w => !string.IsNullOrEmpty(w.SHUOM_1) && w.SHUOM_1.Contains("固定式(U+)")&& w.SHUOM.Contains("导流罩")).Select(s => s.XUANX).FirstOrDefault();//查询字符中有特殊字符,如  旗舰+滑动

6、EFCore新增数据,无VOL,并且获取新增数据的默认值信息 ★★★★★增

  using (var db = DBServerProvider.DbContext)
    {
        Wxgzh_login_map rf = new Wxgzh_login_map();
        rf.openid = loginInfo.wx_openid.Trim();
        rf.uname = loginInfo.UserName;
        rf.pwd = loginInfo.Password;
        db.Add(rf);
        db.SaveChanges();//注意不能放在foreach循环里面                      
    }

7、Linq的左关联,q全部取

IQueryable<dynamic> mingx = from q in all_minx
    join q2 in mstep on q.stepid equals q2.stepid into q2_temp
    from q2_out in q2_temp.DefaultIfEmpty()
    join q3 in muser on q.username equals q3.UserName into q3_temp
    from q3_out in q3_temp.DefaultIfEmpty()
    orderby  q.fk_reimfloat_id ascending
    select new
    {
        q2_out.stepname,
        q3_out.UserTrueName,
        ok_remark= CustomHelper.checkStr(q.remark),
        q
    };

8、linq 左外部关联就是a的全部数据,b没有就是NULL,要使用是IQueryable数据类型,注意在使用linq查询时,要使用是IQueryable数据类型,否则会报莫名其妙的问题,如ys_analyse,预算执行分析,不要使用IEnumerable

from a in A
join b in B on a.BId equals b.Id
into re
from r in re.DefaultIfEmpty()
select new {a.Id, r.Id}
//这里B表的数据已经放进re这个IEnumerable中了,所以select的时候从re集合去取
//可以看到和直接内连接的join差距在多了into,把可能为空的那个集合(表)放到一个集合,然后再对接进行DefaultIfEmpty(),再从这个结果中去取
//重点就是into到集合,再DefaultIfEmpty()
//注意在使用linq查询时,要使用是IQueryable数据类型,否则会报莫名其妙的问题,如//ys_analyse,预算执行分析,不要使用IEnumerable

9、数据隔离,在Service中编写,只能看到自己的数据,管理员可以看到所有人的数据

public override PageGridData<Ok3w_Article> GetPageData(PageDataOptions options)
        {
            QueryRelativeExpression = (IQueryable<Ok3w_Article> queryable) =>
            {
                var user = UserContext.Current.GetUserInfo(UserContext.Current.UserId);
                if (user != null) queryable = queryable.Where(x => user.Role_Id == 1 || user.Role_Id == 2 || user.UserTrueName == x.Creator);//数据隔离

                return queryable;

            };
            return base.GetPageData(options);
        }

10、EFCore打开生成linq生成的SQL语句,VOLContext.cs中的89行到91行,查看linq生成的sql语句

  var loggerFactory = new LoggerFactory();
  loggerFactory.AddProvider(new EFLoggerProvider());
  optionsBuilder.UseLoggerFactory(loggerFactory);

11、让错误信息直接显示到前台 ,ExceptionHandlerMiddleWare.cs 打开45行

12、在SQLSERVER中SQL语句运算中,a+null=null,一个数字加个空结果为空

一定要注意了。
所以在数值运算的时候要加使用空合并符??,
sqlserver默认值,在设计数据库的时候,把数字字段空设为0默认值,必须为非空,默认值才有效;
数据库字段中,涉及到数值计算的字段默认值改成0,并且设为必填字段,
否则C#程序中(特别Linq中)空值计算有错误,因为空+任何值都为空;
还有一种解决方案就是空合并符号的使用,如; s_ys_month = g.Sum(s => s.tsl02??0),//未空就返回0,一定要放在扩号里面,否则会把整体解析错误。
用??0时一定要用括号,否则linq会有解析错误

13、对于linq语句select动态产生的结构,要用dynamic去循环,才能动态取到对象的属性名称,不能用Object,否则取不到;

14、系统默认为小驼峰方式,使用JsonNormal,按原值返回

15、VOL框架的重载是利用Func委托实现的,如:ServiceBase.cs中的GetPageData方法中的267行QueryRelativeExpression函数,SummaryExpress284行,GetPageDataOnExecuted,查询后对结果的数据进行操作,只要有Invoke(参数),就是给重载时重写条件的机会;

16、C#三元表达式的写法

  IQueryable<dynamic> mingx = from q in all_minx
    join q2 in mstep on q.stepid equals q2.stepid into q2_temp
    from q2_out in q2_temp.DefaultIfEmpty()
    join q3 in muser on q.username equals q3.UserName into q3_temp
    from q3_out in q3_temp.DefaultIfEmpty()
    orderby q.fk_reimfloat_id ascending
    select new
    {
        description = q3_out.UserTrueName + (q.bdatetime==null?"": q.bdatetime.ToString("yyyy-MM-dd HH:mm:ss")),//三元表达式
        q2_out.stepname,
        q3_out.UserTrueName,
        ok_remark= CustomHelper.checkStr(q.remark),
        q
    };                 ok_remark= CustomHelper.checkStr(q.remark),
        q
    };

17、添加单元测试项目,单元测需要注意

1)、单元测试运行结果只能在中查看;
2)、单元测试运行时的目录不是项目目录,所以测试的需要注意;

18、vol框架连接第三方数据库的写法

List<dynamic> dditem=Other_Oracle_Helper.query<dynamic>(sql, "dms");//订单明细,通过第三方连接取

19、dynamic对象中属性c#对自动转为大写,也可以使用lambda表达式,要注意类型转换, 连接服务器 链接服务器

 List<dynamic> m_plan = DBServerProvider.SqlDapper.QueryList<dynamic>("select * from openquery([MES],'" + sql + "')", "").ToList(); ;//本月生产计划和每日生产计划
  int m_total_plan = m_plan.Sum(t =>int.Parse(t.SUM));//注意要大写

20、限定一个帐号不能在多处登陆,ApiAuthorizeFilter.cs

21、MapPath直接取的是开发环境下的文件,不是Debug的目录

22、Union方法只能对简单的List去重,复杂的List对象去重复要引入第三方List, 见Sys.MenyService.cs;

  List<dynamic> pp=new List<dynamic>();//pp和p_menu联合
  for(int i=0;i<= p_menu.Count()-1; i++)//不存在的加入union
  {
      if( pp.Exists(x=>x.id == p_menu.ElementAt(i).id)==false)  pp.Add(p_menu.ElementAt(i));
  }

23、只有自己有的权限才能分配给别人,所有要先检查自己的权限 by huangxs Sys_RoleServeice.cs SavePermission()

24、vol系统是用中间件的加载来记录每次API调用日志的,如,在Startup.cs中app.UserMiddleware();

写入Logger.Info(LoggerType.System),整个vol系统就一个system级日志记录

25、int[]不包含“Contains”的定义 需要转换一下类型_reimds.Contains((int)(t.reim_id))

 List<Fk_reimlist> _reimitemlist = DBServerProvider.DbContext.Set<Fk_reimlist>()
          .Where(t => _reimds.Contains((int)(t.reim_id))).ToList();

26、继承IDependency类的作用是为了注入时使用,判断哪些类可以注入,在startup.cs中初始化注入程序AddModule

27、决定用户登录有效时长的位置在 JwtHelper.cs,手机端是43200分钟合计30天,电脑端120分钟

//登录时长
string exp = $"{new DateTimeOffset(DateTime.Now.AddMinutes(ManageUser.UserContext.MenuType == 1? 43200: AppSetting.ExpMinutes)).ToUnixTimeSeconds()}";

28、做大型报表时一定要先建立对外对象;如ys_analyse,预算执行分析,注意在使用linq查询时,要使用是IQueryable数据类型,否则会报莫名其妙的问题,如ys_analyse,预算执行分析,不要使用IEnumerable

29、异步写入日志

using VOL.Core.Services;
try{
}
catch (Exception e)
   {
    Logger.Info("发生异常" + e.Message.ToString());
    return _webResponse.Error("获取失败,转到登录页" + e.Message.ToString()+ex.StackTrace);
    }

30、VS编辑器中TEST模块设断点无效解决方式:

项目属性中“生成”——>“代码优化” 勾去掉;

31、C#中判断字符串不为空string.isNullOrEmpty(str)

if(string.IsNullOrEmpty(tousername))
 hq_username = hq_username + "," + tousername;

32、c#端防止重复录入,在xxxxService.cs端 vol平台防止重复录入数据

//如果数据库中有就更新,没有就插入,需要修改3个地方:
//1、Import中over;2、Add中over;3、Update中over

        private WebResponseContent webResponse = new WebResponseContent();
        public override WebResponseContent Update(SaveModel saveModel)
        {
            UpdateOnExecuted = (Erp_supper curr, object addList, object updateList, List<object> delKeys) =>
            {
                //需在UpdateOnExecuted事件中,判断总数不能>1
                var TTT = DBServerProvider.DbContext.Set<Erp_supper>().Where(w => w.lifnr.Trim() == curr.lifnr.Trim()).ToList();
                if (TTT.Count > 1)
                {
                    return webResponse.Error(curr.lifnr + ",该号已经存在!");
                }
                return webResponse.OK();
            };
            return base.Update(saveModel);
        }
        public override WebResponseContent Add(SaveModel saveDataModel)
        {
            AddOnExecuting = (Erp_supper curr, object list) =>
            {
                var TTT = DBServerProvider.DbContext.Set<Erp_supper>().Where(w => w.lifnr.Trim() == curr.lifnr.Trim()).FirstOrDefault();
                if (TTT != null)
                {
                    return webResponse.Error(curr.lifnr + ",该号已经存在!");
                }
                return webResponse.OK();
            };
            return base.Add(saveDataModel);
        }
        public override WebResponseContent Import(List<IFormFile> files)
        {
            ImportOnExecuting = (List<Erp_supper> list) =>
            {
                if (list != null)//根据供应商号,如果数据库中有就更新,没有就插入
                {
                    List<Erp_supper> addlist = new List<Erp_supper>();//新增List
                    List<Erp_supper> uplist = new List<Erp_supper>();//更新List
                    foreach (var item in list)
                    {
                        var TTT = DBServerProvider.DbContext.Set<Erp_supper>().Where(w => w.lifnr.Trim() == item.lifnr.Trim()).FirstOrDefault();//
                        if (TTT != null)
                        {
                            item.supper_id = TTT.supper_id;
                            uplist.Add(item);
                        }
                        else
                        {
                            addlist.Add(item);
                        }
                    }
                    //过滤重复数据
                    if (addlist.Count() > 0)
                    {
                        addlist = addlist.GroupBy(x => x.lifnr).Select(y => y.First()).ToList();
                        repository.AddRange(addlist, true);
                    }
                    foreach (var item in uplist)
                    {
                        repository.Detached(item);//efcore取消跟踪,需要放在数据库查询tolist()前面
                    }
                    if (uplist.Count() > 0)
                    {
                        uplist = uplist.GroupBy(x => x.lifnr).Select(y => y.First()).ToList();

                        repository.UpdateRange(uplist, true);
                    }
                }
                webResponse.Code = "-1";//-1表示不执行后面的update,测试强制返回
                return webResponse.OK("导入成功!");
            };

            return base.Import(files);
        }

33、vol平台对表增删改记录,在xxxxxService.cs

  public override WebResponseContent Add(SaveModel saveDataModel)
        {
            AddOnExecuting = (Erp_mara curr, object list) =>
            {
                var TTT = DBServerProvider.DbContext.Set<Erp_mara>().Where(w => w.matnr == curr.matnr).FirstOrDefault();
                if (TTT != null)
                {
                    return webResponse.Error(curr.matnr + ",该物料号已经存在!");
                }
                return webResponse.OK();
            };
            AddOnExecuted = (Erp_mara currList, object list) =>
            {
                string xgren = UserContext.Current.GetUserInfo(UserContext.Current.UserId).UserTrueName;
                Console.WriteLine(xgren + "新增物料名称为:" + currList.maktx + ",新增时间为:" + DateTime.Now);

                return webResponse.OK("保存成功!");//必须要加,vol的bug
            };
            return base.Add(saveDataModel);
        }
  public override WebResponseContent Del(object[] keys, bool delList = true)
        {

            string ?delstr = _repository.Find(t => t.mara_id == keys[0].ToString().ToGuid()).Select(s => s.maktx).FirstOrDefault();
            string xgren = UserContext.Current.GetUserInfo(UserContext.Current.UserId).UserTrueName;
            Console.WriteLine(xgren + " 删除物料名称为:" + delstr + " 删除时间为:" + DateTime.Now);
            return base.Del(keys, delList);
        }
   public override WebResponseContent Update(SaveModel saveModel)
        {

            //编辑方法保存数据库前处理,只能在修改之前取数,因为需要取原始值
            UpdateOnExecuting = (Erp_mara currList, object addList, object updateList, List<object> delKeys) =>
            {
                //工艺数据字段更改记录,原值!=现值才记录
                string t = (_repository.Find(t => t.mara_id == currList.mara_id).Select(s => s.gongyi).FirstOrDefault()??"").ToString();//注意原值为空的情况??""
                string gongyi_old = t;
                string gongyi_new = currList.gongyi;
                if (gongyi_old != gongyi_new)
                {
                    string xgren = UserContext.Current.GetUserInfo(UserContext.Current.UserId).UserTrueName;
                    Console.WriteLine(xgren + " 修改了物料名称为:" + currList.maktx + "的工艺字段,原值为:" + gongyi_old + " 现值为:" + gongyi_new + " 修改时间为:" + DateTime.Now);
                }
                return webResponse.OK();
            };
            UpdateOnExecuted = (Erp_mara curr, object addList, object updateList, List<object> delKeys) =>
            {
                //需在UpdateOnExecuted事件中,判断总数不能>1
                var TTT = DBServerProvider.DbContext.Set<Erp_mara>().Where(w => w.matnr == curr.matnr).ToList();
                if (TTT.Count > 1)
                {
                    return webResponse.Error(curr.matnr + ",该物料号已经存在!");
                }
                return webResponse.OK();
            };
            return base.Update(saveModel);
        }

34、VOL框架执行dblink的更新操作,如下,具体例子查询:Bak_partout_117Service.cs ,SQLServer中使用的连接数据库OPENQUERY

//在xxxService.cs中增加
 public override WebResponseContent Update(SaveModel saveModel)
        {
            //sqlerver dblink到oracle执行update时需要用到openquery否则效率会非常慢,修改查询都是用openquery,可以提示运行效率
            //下面的实例绕开SQLServer事务,直接执行openquery的update的SQL语句
            UpdateOnExecuting = (Bak_partout_117 order, object addList, object updateList, List<object> delKeys) =>
            {
                Console.WriteLine(order.BAK_SCM_SAP_PARTOUT_ID);
                decimal ls_id= order.BAK_SCM_SAP_PARTOUT_ID;
                string pflag = order.PFLAG;
                string TPLANT_NO = order.TPLANT_NO;
                string OUTBOUND_TYPE=order.OUTBOUND_TYPE;
                DBServerProvider.SqlDapper.ExcuteNonQuery("UPDATE OPENQUERY([117],'SELECT pflag,TPLANT_NO,OUTBOUND_TYPE FROM BAK_SCM_SAP_PARTOUT WHERE bak_scm_sap_partout_id=" + ls_id + "') SET pflag=@pflag, TPLANT_NO=@TPLANT_NO,OUTBOUND_TYPE=@OUTBOUND_TYPE", new { pflag, TPLANT_NO, OUTBOUND_TYPE });
                //_repository.Update(order,true);
                webResponse.Code = "-1";//-1表示不执行后面的update,测试强制返回
                //webResponse.Message = "";
                return webResponse.OK("修改成功!");
            };
            return base.Update(saveModel);
        }

35、VOL对视图增删改

 public override WebResponseContent Add(SaveModel saveDataModel)//增加
        {            //return webResponse.OK();
            return Luxs_project_for_maraService.Instance.Add(saveDataModel);//Luxs_project_for_maraService可以直接使用
        }
        public override WebResponseContent Del(object[] keys, bool delList = true)//删除
        {
            return Luxs_project_for_maraService.Instance.Del(keys, delList);
            //return base.Del(keys, delList);
        }
public override WebResponseContent Update(SaveModel saveModel)//修改
        {
   UpdateOnExecuting = (Bak_partout_117 order, object addList, object updateList, List<object> delKeys) =>
            {
                Console.WriteLine(order.BAK_SCM_SAP_PARTOUT_ID);
                decimal ls_id= order.BAK_SCM_SAP_PARTOUT_ID;
                string pflag = order.PFLAG;
                string TPLANT_NO = order.TPLANT_NO;
                string OUTBOUND_TYPE=order.OUTBOUND_TYPE;
	                DBServerProvider.SqlDapper.ExcuteNonQuery("UPDATE OPENQUERY([117],'SELECT pflag,TPLANT_NO,OUTBOUND_TYPE FROM BAK_SCM_SAP_PARTOUT WHERE bak_scm_sap_partout_id=" + ls_id + "') SET pflag=@pflag, TPLANT_NO=@TPLANT_NO,OUTBOUND_TYPE=@OUTBOUND_TYPE", new { pflag, TPLANT_NO, OUTBOUND_TYPE });
	                //_repository.Update(order,true);
	                webResponse.Code = "-1";//-1表示不执行后面的update,测试强制返回
	                //webResponse.Message = "";
	                return webResponse.OK("修改成功!");
	            };
	            return base.Update(saveModel);
	        }10.	

36、Guid guid = new Guid(xx);//string转为Guid

涉及到Guid的操作尽量用List,因为List也有Contains
如List<Guid> all_customer_id_list = new List<Guid>();

37、配置VOL流程步骤,AuditStatus

1、建表要求:表里面必须包括AuditStatus审核状态字段,int类型,审核字段数据源配置要求 select audit
2、后台注入审批流程表: startup类ConfigureContainer方法添加WorkFlowContainer.Instance.Use<表名>()
3、设计表的流程
4、添加数据进入流程
5、菜单与权限控制

38、C#异常出现继续运行

try
{
      Xxxx            
}
catch (Exception)
{  
      co = 0;
}

39、Linq中any相当于两个内表之间交集, 如 AsEnumerable(),Exist相当于返回一个判断布尔

 var steps = _stepRepository.FindAsIQueryable(x => x.WorkFlow_Id == workFlow.WorkFlow_Id) .Select(s => new { s.WorkStepFlow_Id, s.StepId }).ToList();
 var updateSteps = stepsClone.Where(x => steps.Any(c => c.StepId == x.StepId)).ToList();
 var items = DBServerProvider.SqlDapper.QueryList<rtn_detail_table>(sql, new { _username = username });
 var luxs = DBServerProvider.DbContext.Set<Luxs_rfq>().AsEnumerable().Where(w => items.Any(s => s.reimid == w.luxs_rfq_id)).ToList();


//any相当于使用SQL中的exist语句,注意使用any时不能toList();
 var fl_template_ls = DBServerProvider.DbContext.Set<fl_template>().AsQueryable();//此处不能使用toList(),否则Linq报错
 List<fl_template_list> fl_template_list_list = DBServerProvider.DbContext.Set<fl_template_list>()
                .Where(w => fl_template_ls.Any(c => c.ft_id == w.ft_id && c.ft_table== reimtable)).ToList();

40、Exist相当于返回一个判断布尔,如

List<Ls_pdb> _pdb = new List<Ls_pdb>();
  _pdb = Ls_pdbRepository.Instance.FindAsIQueryable(w => (1 == 1)).ToList();
 if (_pdb.Exists(e => e.matnr.Trim() == list[i][1]))//先判断立讯料号
{
  ...
}

41、相当于对add深度拷贝,不是地址的指向

 List<Sys_WorkFlowStep> add = addList as List<Sys_WorkFlowStep>;
 var stepsClone = add.Serialize().DeserializeObject<List<Sys_WorkFlowStep>>();
 add.Clear();

42、c#日期格式化:string ddtime = DateTime.Now.ToString(“yyyyMMddHHmmss”);//时间格式化

 bai_Events1.requestTime = DateTime.ParseExact(arr[1], "yyyyMMddHHmmss",CultureInfo.CurrentCulture);

43、c#加入cache

//加入内存
	 ICacheService cacheService = AutofacContainerModule.GetService<ICacheService>();
	 cacheService.AddObject(UserContext.Current.UserId.ToString(), ys_analues_query.ToList());
	/从内存中取出导出数据
	ICacheService cacheService = AutofacContainerModule.GetService<ICacheService>();
	List<Yus_Analyse> dataList = cacheService.Get<List<Yus_Analyse>>(UserContext.Current.UserId.ToString());/

44、c# list 移除查询条件 gridview控件移除查询条件,在xxxService.cs中

public override PageGridData<Ls_ctb_view> GetPageData(PageDataOptions options)
        {
            //此处是从前台提交的原生的查询条件,这里可以自己过滤
            QueryRelativeList = (List<SearchParameters> parameters) =>
            {
                /*for(int i=0;i<= parameters.Count - 1; i++)
                 {//不能用for因为下标在不断变化
                     SearchParameters param = parameters[i];
	                     if (param.Name == "req_date")
	                     {
	                         parameters.Remove(param);
	                     }
	                 } */
	                parameters.RemoveAll(s => s.Name == "req_date");//删除list的正确操作
	            };
	            return base.GetPageData(options);
	        }

45、c#按日期生成今天流水号最大编号,生成序号、如rfq编号生成:

  string now_d = DateTime.Now.ToString("yyMMdd").ToString();
  string yy = now_d.ToString().Substring(0, 2);
  string mm = now_d.ToString().Substring(2, 2);
  string dd = now_d.ToString().Substring(4, 2);
  //用到了Convert.ToDateTime,先转换为list表后再,where中使用Convert.ToDateTime,否则报错
  int co = 0;
  try//报错后继续执行,因为数据库中可能没有数据,所以报错,
  {
    //注意Substring(11)跟RFQ-的长度有关
      co = DBServerProvider.DbContext.Set<Luxs_rfq>().ToList().Where(w => Convert.ToDateTime(w.CreateDate).ToString("yyMMdd") == now_d).Select(s => s.rfq_no.Substring(N).ToInt()).Max();//从当日的最大值开始
  }
  catch (Exception)
  {  
      co = 0;
  }
  co = co + 1;
  string rfq_no = "RFQ-" + yy + mm + dd + "-" + co;

46、后端/前端当前登录人,登录用户:UserContext.Current.GetUserInfo(UserContext.Current.UserId).UserTrueName;

//后端
UserContext.Current.GetUserInfo(UserContext.Current.UserId).UserTrueName;
//前端
//前端登录用户:
//let _userInfo = store.getters.getUserInfo();
this.$store.getters.getUserInfo();//这个在任何地方都能引用

47、excel导入时,指定导入字段,待验证

DownLoadTemplateColumns = null;  
 if(1==1){  
    DownLoadTemplateColumns = x=>new {};  
   }    

48、c#相同对象直接每个字段之间赋值,对象反射,对象复制数据

fk_reimfloat ff = new fk_reimfloat();//源对象,有数据
fk_reimfloat_his ff_his = new fk_reimfloat_his();//目的对象
            var s_type = ff.GetType();//原对象
            var to_type = typeof(fk_reimfloat_his);//目的对象
            foreach (var s in s_type.GetProperties())
            {
                foreach (var to in to_type.GetProperties())
                {
                    if (s.Name == to.Name)
                    {
                        to.SetValue(ff_his, s.GetValue(ff));//前面是目的对象,后面的原对象的值
                    }
                }
            }
DBServerProvider.DbContext.Add(ff_his);
DBServerProvider.DbContext.SaveChanges();

//利用反射获取对象属性(colname)的值,属性可以是字符串(方便动态替换)
//jhz_pzbm_js为模型名称
jhz_pzbm_js js=new jhz_pzbm_js();
Type type = typeof(jhz_pzbm_js);
PropertyInfo property = type.GetProperty("colname");//colname可以动态替换的
object value = property.GetValue(js);//value就是js.colname的值

49、PropertyInfo 获取实体类指定属性值

 public static string GetPropertyValue<T>(T t, string field)
  {
      string value = "9";
      if (t == null)
      {
          return value;
      }
      PropertyInfo[] properties = t.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public);

      if (properties.Length <= 0)
      {
          return value;
      }
      var property = properties.Where(x => x.Name == field).FirstOrDefault();
      value = property.GetValue(t, null).ToString();

      return value;
  }

50、c#判断字符串不为空,!string.IsNullOrEmpty(row[j])要放在if最前面

if ((!string.IsNullOrEmpty(row[j])) && row[j].Substring(0, 1)== "=")//如果内容为公式的话
{
   worksheet.Cells[i + start_row + 1, j + 1].Formula = row[j];
}

51、c# Predicate函数参数的使用,用于List中查询字符串使用,Predicate参数就是有参数函数体

  public int loc_index()//
  {
      List<string> Demand = new List<string>();//行
      Demand.Add("test");//给行添加1列
      Demand.Add("Demand");//给行添加2列
      Demand.Add("Demand1");//给行添加2列
      int loc = Demand.FindIndex(0, (string x) => {
          if (x == "Demand1")
              return true;
          else
              return false;
      });
      return loc;
  }

52、VOL平台excel合并单元格

worksheet.Cells[1, 1, 15, 1].Merge = true;//合并单元格
worksheet.Cells[1, 1, 15, 1].Value = "8989";
worksheet.Cells[1, 1, 15, 1].Style.HorizontalAlignment = ExcelHorizontalAlignment.Center;
worksheet.Cells[1, 1, 15, 1].Style.VerticalAlignment = ExcelVerticalAlignment.Center;

53、VOL后端中,UpdateOnExecuting中修改不想显示的字段,不想前端提交的值,想手工给值用UpdateOnExecuting,想联动修改其它的ID行数据,就用UpdateOnExecuted

在UpdateOnExecuting如果要手动设置某些字段的值,值不是前端提交的(代码生成器里面编辑行必须设置为0并生成model),
如Remark字段:注意必须设置上面saveModel.MainData.TryAdd("Remark", "888")
order.Remark = "888";

54、后端重载Service中PageGridData时,var rtn = base.GetPageData(options);//注意一点要放在最后调用,否则前的自定义条件无效,

public override PageGridData<Ls_pdb> GetPageData(PageDataOptions options)
        {
    Hashtable rtn_hst = new Hashtable();
    //
    Sys_User ?sys_User = DBServerProvider.DbContext.Set<Sys_User>().Where(w => w.User_Id == UserContext.Current.UserId).FirstOrDefault();
    if (sys_User.UserName.Contains("ls_admin") || UserContext.Current.IsSuperAdmin)
    {
        rtn_hst.Add("pdb_auth", "write");
    }
    else
    {
        rtn_hst.Add("pdb_auth", sys_User?.pdb_auth);
    }

    //查询前可以自已设定查询表达式的条件
    /* QueryRelativeExpression = (IQueryable<Ls_pdb> queryable) =>
      {//这样写不可行
          Sys_User? sys_User1 = DBServerProvider.DbContext.Set<Sys_User>().Where(w => w.User_Id == UserContext.Current.UserId).FirstOrDefault();
          List<string> lscenter_auth_list = sys_User1.lrcenter.Split(',').ToList();
          queryable = queryable.Where(w => lscenter_auth_list.Any(a => w.lrcenter.Contains(a.ToString())));
          return queryable;

      };*/
    /* string where_str = "";
      List<string> lscenter_auth_list = sys_User.lrcenter.Split(',').ToList();
      for (int i = 0; i < lscenter_auth_list.Count; i++)
      {
          where_str= where_str+ " and Ls_pdb.lrcenter like '%" +
      }*/
    QuerySql = $@"select * from ls_pdb where 1=1 and lrcenter like '%center_001%' or lrcenter like '%center_002%'";
    
    var rtn = base.GetPageData(options);//注意一点要放在最后调用,否则前的自定义条件无效
    rtn.extra = rtn_hst;
    return rtn;
}

55、SQLServer中日期格式字段格式化:时间格式化

select  CONVERT(VARCHAR(10),getdate(),120) AS  t1  from bai_event --'yyyy-mm-dd'

delete from fk_reimtask where CONVERT(VARCHAR(10),createtime,120)<='2023-03-26'

56、Vol框架c#中ftp用法

string today = DateTime.Now.ToString("yyyMMdd");
string bd_path = "c:/output/"+ today;//本地文件路径
string ftp_path = "/output/";//ftp服务器上的路径
FtpClient ftpClient = new FtpClient(@"ftp://10.100.16.15"+ ftp_path + today, "ctruckftp", "123456");     
ftpClient.get(today + ".txt", bd_path + "\\"+today+ ".txt");

57、作者的后台扩展Service

public partial class SellOrderService
{
    public string GetServiceDate()
    {
        return DateTime.Now.ToString("yyyy-MM-dd HH:mm:sss");
    }
    //此SellOrderService.cs类由代码生成器生成,默认是没有任何代码,如果需要写业务代码,请在此类中实现
    //如果默认的增、删、改、查、导入、导出、审核满足不了业务,请参考下面的方法进行业务代码扩展(扩展代码是对ServiceFunFilter.cs的实现)
    WebResponseContent webResponse = new WebResponseContent();
    private IHttpContextAccessor _httpContextAccessor;
    private ISellOrderRepository _repository;
    [ActivatorUtilitiesConstructor]
    public SellOrderService(IHttpContextAccessor httpContextAccessor, ISellOrderRepository repository)
        : base(repository)
    {
        _httpContextAccessor = httpContextAccessor;
        _repository = repository;
        //2020.08.15
        //开启数据隔离功能,开启后会对查询、导出、删除、编辑功能同时生效
        //如果只需要对某个功能生效,如编辑,则在重写编辑方法中设置 IsMultiTenancy = true;
        // IsMultiTenancy = true;
    }
    //查询
    public override PageGridData<SellOrder> GetPageData(PageDataOptions options)
    {
        //options.Value可以从前台查询的方法提交一些其他参数放到value里面
        //前端提交方式,见文档:组件api->viewgrid组件里面的searchBefore方法
        object extraValue = options.Value;

        //此处是从前台提交的原生的查询条件,这里可以自己过滤
        QueryRelativeList = (List<SearchParameters> parameters) =>
        {

        };

        //2020.08.15
        //设置原生查询的sql语句,这里必须返回select * 表所有字段
        //(先内部过滤数据,内部调用EF方法FromSqlRaw,自己写的sql注意sql注入的问题),不会影响界面上提交的查询
        /*  
         *  string date = DateTime.Now.AddYears(-10).ToString("yyyy-MM-dd");
            QuerySql = $@"select * from SellOrder  
                                   where createdate>'{date}'
                                       and  Order_Id in (select Order_Id from SellOrderList)
                                       and CreateID={UserContext.Current.UserId}";
        */

        //2020.08.15
        //此处与上面QuerySql只需要实现其中一个就可以了
        //查询前可以自已设定查询表达式的条件
        QueryRelativeExpression = (IQueryable<SellOrder> queryable) =>
        {
            //当前用户只能操作自己(与下级角色)创建的数据,如:查询、删除、修改等操作
            //IQueryable<int> userQuery = RoleContext.GetCurrentAllChildUser();
            //queryable = queryable.Where(x => x.CreateID == UserContext.Current.UserId || userQuery.Contains(x.CreateID ?? 0));
            return queryable;
        };

        //指定多个字段进行排序
        OrderByExpression = x => new Dictionary<object, QueryOrderBy>() {
            { x.CreateDate,QueryOrderBy.Desc },
            { x.SellNo,QueryOrderBy.Asc}
        };

        //int a = 1;
        指定多个字段按条件进行排序(需要2021.07.04更新LambdaExtensions类后才能使用)
        //OrderByExpression = x => new Dictionary<object, QueryOrderBy>() {
        //    { x.CreateDate,QueryOrderBy.Desc },
        //    { x.SellNo,a==1?QueryOrderBy.Desc:QueryOrderBy.Asc}
        //};

        //查询完成后,在返回页面前可对查询的数据进行操作
        GetPageDataOnExecuted = (PageGridData<SellOrder> grid) =>
        {
            //可对查询的结果的数据操作
            List<SellOrder> sellOrders = grid.rows;
        };
        //查询table界面显示求和
        SummaryExpress = (IQueryable<SellOrder> queryable) =>
        {
            return queryable.GroupBy(x => 1).Select(x => new
            {
                //AvgPrice注意大小写和数据库字段大小写一样
                Qty = x.Sum(o => o.Qty).ToString("f2")
            })
            .FirstOrDefault();
        };

        return base.GetPageData(options);
    }
        /// <summary>
        /// 2023.02.03增加or查询条件示例 or条件
        /// 注意:如果有导出功能,GetPageData方法内的代码在下面的export方法里需要同样的复制一份
        /// </summary>
        /// <param name="options"></param>
        /// <returns></returns>
        public override PageGridData<SellOrder> GetPageData(PageDataOptions options)
        {
            System.Linq.Expressions.Expression<Func<SellOrder, bool>> orFilter = null;
            QueryRelativeList = (List<SearchParameters> parameters) =>
            {
                //方式1:动态生成or查询条件
                foreach (var item in parameters)
                {
                    if (!string.IsNullOrEmpty(item.Value))
                    {
                        //注意:这里只需要判断or查询的字段,其他的字段不需要处理
                        //这里必须拷贝value值
                        if (orFilter==null){ orFilter = x => false; }
                        string value = item.Value;
                        if (item.Name == "TranNo")
                        {
                            //进行or模糊查询
                            orFilter = orFilter.Or(x => x.TranNo.Contains(value));
                            //清空原来的数据
                            item.Value = null;
                        }
                        else if (item.Name == "SellNo")
                        {
                            //进行or等于查询
                            orFilter = orFilter.Or(x => x.SellNo == value);
                            //清空原来的数据
                            item.Value = null;
                        }
                    }
                }
                ///方式2:原生sql查询,需要自己处理sql注入问题(不建议使用此方法)
                //string sql = null;
                //foreach (var item in parameters)
                //{
                //    if (!string.IsNullOrEmpty(item.Value))
                //    {
                //        if (sql == null)
                //        {
                //            sql = "where 1=2";
                //        }
                //        string value = item.Value;
                //        //清空原来的数据
                //        item.Value = null;
                //        if (item.Name == "TranNo")
                //        {
                //            sql += $" or TranNo='{value}'";
                //            //清空原来的数据
                //            item.Value = null;
                //        }
                //        else if (item.Name == "SellNo")
                //        {
                //            sql += $" or SellNo='{value}'";
                //        }
                //    }
                //}
                //QuerySql = "select * from sellorder " + sql;
            };

            QueryRelativeExpression = (IQueryable<SellOrder> queryable) =>
            {
                if (orFilter != null)
                {
                    queryable = queryable.Where(orFilter);
                }
                return queryable;
            };
            return base.GetPageData(options);
        }
    /// <summary>
    /// 设置弹出框明细表的合计信息
    /// </summary>
    /// <typeparam name="detail"></typeparam>
    /// <param name="queryeable"></param>
    /// <returns></returns>
    protected override object GetDetailSummary<detail>(IQueryable<detail> queryeable)
    {
        return (queryeable as IQueryable<SellOrderList>).GroupBy(x => 1).Select(x => new
        {
            //Weight/Qty注意大小写和数据库字段大小写一样
            Weight = x.Sum(o => o.Weight),
            Qty = x.Sum(o => o.Qty)
        }).FirstOrDefault();
    }

    /// <summary>
    /// 查询业务代码编写(从表(明细表查询))
    /// </summary>
    /// <param name="pageData"></param>
    /// <returns></returns>
    public override object GetDetailPage(PageDataOptions pageData)
    {
        //自定义查询胆细表

        明细表自定义查询方式一:EF
        //var query = SellOrderListRepository.Instance.IQueryablePage<SellOrderList>(
        //     pageData.Page,
        //     pageData.Rows,
        //     out int count,
        //     x => x.Order_Id == pageData.Value.GetGuid(),
        //      orderBy: x => new Dictionary<object, QueryOrderBy>() { { x.CreateDate, QueryOrderBy.Desc } }
        //    );
        //PageGridData<SellOrderList> detailGrid = new PageGridData<SellOrderList>();
        //detailGrid.rows = query.ToList();
        //detailGrid.total = count;

        明细表自定义查询方式二:dapper
        //string sql = "select count(1) from SellOrderList where Order_Id=@orderId";
        //detailGrid.total = repository.DapperContext.ExecuteScalar(sql, new { orderId = pageData.Value }).GetInt();

        //sql = @$"select * from (
        //              select *,ROW_NUMBER()over(order by createdate desc) as rowId
        //           from SellOrderList where Order_Id=@orderId
        //        ) as s where s.rowId between {((pageData.Page - 1) * pageData.Rows + 1)} and {pageData.Page * pageData.Rows} ";
        //detailGrid.rows = repository.DapperContext.QueryList<SellOrderList>(sql, new { orderId = pageData.Value });

        //return detailGrid;

        return base.GetDetailPage(pageData);
    }

    /// <summary>
    /// 新建
    /// </summary>
    /// <param name="saveDataModel"></param>
    /// <returns></returns>
    public override WebResponseContent Add(SaveModel saveDataModel)
    {
        //此处saveModel是从前台提交的原生数据,可对数据进修改过滤
        AddOnExecute = (SaveModel saveModel) =>
        {
            //如果返回false,后面代码不会再执行
            return webResponse.OK();
        };
        // 在保存数据库前的操作,所有数据都验证通过了,这一步执行完就执行数据库保存
        AddOnExecuting = (SellOrder order, object list) =>
        {
            //明细表对象
            List<SellOrderList> orderLists = list as List<SellOrderList>;

            //自定义逻辑
            if (orderLists == null || orderLists.Count == 0)
            {//如果没有界面上没有填写明细,则中断执行
                return webResponse.Error("必须填写明细数据");
            }
            if (orderLists.Exists(x => x.Qty <= 20))
                return webResponse.Error("明细数量必须大于20");

            //设置webResponse.Code = "-1"会中止后面代码执行,与返回 webResponse.Error()一样,区别在于前端提示的是成功或失败
            //webResponse.Code = "-1";
            // webResponse.Message = "测试强制返回";
            //return webResponse.OK("ok");

            return webResponse.OK();
        };

        //此方法中已开启了事务,如果在此方法中做其他数据库操作,请不要再开启事务
        // 在保存数据库后的操作,此时已进行数据提交,但未提交事务,如果返回false,则会回滚提交
        AddOnExecuted = (SellOrder order, object list) =>
        {
            //明细表对象
            // List<SellOrderList> orderLists = list as List<SellOrderList>;

            if (order.Qty < 10)
            {  //如果输入的销售数量<10,会回滚数据库
                return webResponse.Error("销售数量必须大于1000");
            }

            return webResponse.OK("已新建成功,台AddOnExecuted方法返回的消息");
        };

        //新建的数据进入审批流程前处理,
        AddWorkFlowExecuting = (SellOrder order) =>
        {
            //返回false,当前数据不会进入审批流程
            return true;
        };

        //新建的数据写入审批流程后,第二个参数为审批人的用户id
        AddWorkFlowExecuted = (SellOrder order, List<int> userIds) =>
        {
            //这里可以做发邮件通知
            //var userInfo = repository.DbContext.Set<Sys_User>()
            //                .Where(x => userIds.Contains(x.User_Id))
            //                .Select(s => new { s.User_Id, s.UserTrueName, s.Email, s.PhoneNo }).ToList();

            //发送邮件方法
            //MailHelper.Send()
        };

        return base.Add(saveDataModel);
    }
    /// <summary>
    /// 编辑操作
    /// </summary>
    /// <param name="saveModel"></param>
    /// <returns></returns>
    public override WebResponseContent Update(SaveModel saveModel)
    {
        //注意:如果要给其他字段设置值,请在此处设置,如:(代码生成器上将字段编辑行设置为0,然后点生成model)
        //saveModel.MainData["字段"] = "值";

        //此处saveModel是从前台提交的原生数据,可对数据进修改过滤
        UpdateOnExecute = (SaveModel model) =>
        {
            这里的设置配合下面order.Remark = "888"代码位置使用
            // saveModel.MainData.TryAdd("Remark", "1231");

            //如果不想前端提交某些可以编辑的字段的值,直接移除字段
            // saveModel.MainData.Remove("字段");

            //如果返回false,后面代码不会再执行
            return webResponse.OK();

        };

        //编辑方法保存数据库前处理
        UpdateOnExecuting = (SellOrder order, object addList, object updateList, List<object> delKeys) =>
          {
              if (order.TranNo == "2019000001810001")
              {
                  //如果设置code=-1会强制返回,不再继续后面的操作,2021.07.04更新LambdaExtensions文件后才可以使用此属性
                  //webResponse.Code = "-1";
                  // webResponse.Message = "测试强制返回";
                  //return webResponse.OK();
                  return webResponse.Error("不能更新此[" + order.TranNo + "]单号");
              }

              如果要手动设置某些字段的值,值不是前端提交的(代码生成器里面编辑行必须设置为0并生成model),如Remark字段:
              注意必须设置上面saveModel.MainData.TryAdd("Remark", "1231")
              //order.Remark = "888";

              //新增的明细表
              List<SellOrderList> add = addList as List<SellOrderList>;
              //修改的明细表
              List<SellOrderList> update = updateList as List<SellOrderList>;
              //删除明细表Id
              var guids = delKeys?.Select(x => (Guid)x);

              //设置webResponse.Code = "-1"会中止后面代码执行,与返回 webResponse.Error()一样,区别在于前端提示的是成功或失败
              //webResponse.Code = "-1";
              // webResponse.Message = "测试强制返回";
              //return webResponse.OK("ok");

              return webResponse.OK();
          };

        //编辑方法保存数据库后处理
        //此方法中已开启了事务,如果在此方法中做其他数据库操作,请不要再开启事务
        // 在保存数据库后的操作,此时已进行数据提交,但未提交事务,如果返回false,则会回滚提交
        UpdateOnExecuted = (SellOrder order, object addList, object updateList, List<object> delKeys) =>
          {
              //新增的明细
              List<SellOrderList> add = addList as List<SellOrderList>;
              //修改的明细
              List<SellOrderList> update = updateList as List<SellOrderList>;
              //删除的行的主键
              var guids = delKeys?.Select(x => (Guid)x);
              return webResponse.OK();
          };

        return base.Update(saveModel);
    }

    /// <summary>
    /// 删除
    /// </summary>
    /// <param name="keys">删除的行的主键</param>
    /// <param name="delList">删除时是否将明细也删除</param>
    /// <returns></returns>
    public override WebResponseContent Del(object[] keys, bool delList = true)
    {
        //删除前处理
        //删除的行的主键
        DelOnExecuting = (object[] _keys) =>
        {
            return webResponse.OK();
        };
        //删除后处理
        //删除的行的主键
        DelOnExecuted = (object[] _keys) =>
         {
             return webResponse.OK();
         };
        return base.Del(keys, delList);
    }
    public override WebResponseContent Audit(object[] keys, int? auditStatus, string auditReason)
    {
        //status当前审批状态,lastAudit是否最后一个审批节点
        AuditWorkFlowExecuting = (SellOrder order, AuditStatus status, bool lastAudit) =>
        {
            return webResponse.OK();
        };
        //status当前审批状态,nextUserIds下一个节点审批人的帐号id(可以从sys_user表中查询用户具体信息),lastAudit是否最后一个审批节点
        AuditWorkFlowExecuted = (SellOrder order, AuditStatus status, List<int> nextUserIds, bool lastAudit) =>
        {
            //lastAudit=true时,流程已经结束
            if (!lastAudit)
            {
                //这里可以给下一批审批发送邮件通知
                //var userInfo = repository.DbContext.Set<Sys_User>()
                //             .Where(x => nextUserIds.Contains(x.User_Id))
                //             .Select(s => new { s.User_Id, s.UserTrueName, s.Email, s.PhoneNo }).ToList();
            }

            //审批流程回退功能,回到第一个审批人重新审批(重新生成审批流程)
            //if (status==AuditStatus.审核未通过||status==AuditStatus.驳回)
            //{
            //    base.RewriteFlow(order);
            //}

            return webResponse.OK();
        };

        //审核保存前处理(不是审批流程)
        AuditOnExecuting = (List<SellOrder> order) =>
        {
            return webResponse.OK();
        };
        //审核后处理(不是审批流程)
        AuditOnExecuted = (List<SellOrder> order) =>
        {
            return webResponse.OK();
        };


        return base.Audit(keys, auditStatus, auditReason);
    }

    /// <summary>
    /// 导出
    /// </summary>
    /// <param name="pageData"></param>
    /// <returns></returns>
    public override WebResponseContent Export(PageDataOptions pageData)
    {
        //设置最大导出的数量
        Limit = 1000;
        //指定导出的字段(2020.05.07)
        ExportColumns = x => new { x.SellNo, x.TranNo, x.CreateDate };

        //查询要导出的数据后,在生成excel文件前处理
        //list导出的实体,ignore过滤不导出的字段
        ExportOnExecuting = (List<SellOrder> list, List<string> ignore) =>
        {
            return webResponse.OK();
        };

        return base.Export(pageData);
    }

    /// <summary>
    /// 下载模板(导入时弹出框中的下载模板)(2020.05.07)
    /// </summary>
    /// <returns></returns>
    public override WebResponseContent DownLoadTemplate()
    {
        //指定导出模板的字段,如果不设置DownLoadTemplateColumns,默认导出查所有页面上能看到的列(2020.05.07)
        DownLoadTemplateColumns = x => new { x.SellNo, x.TranNo, x.Remark, x.CreateDate };
        return base.DownLoadTemplate();
    }

    /// <summary>
    /// 导入
    /// </summary>
    /// <param name="files"></param>
    /// <returns></returns>
    public override WebResponseContent Import(List<IFormFile> files)
    {
        //(2020.05.07)
        //设置导入的字段(如果指定了上面导出模板的字段,这里配置应该与上面DownLoadTemplate方法里配置一样)
        //如果不设置导入的字段DownLoadTemplateColumns,默认显示所有界面上所有可以看到的字段
        DownLoadTemplateColumns = x => new { x.SellNo, x.TranNo, x.Remark, x.CreateDate };

        /// <summary>
        /// 2022.06.20增加原生excel读取方法(导入时可以自定义读取excel内容)
        /// string=当前读取的excel单元格的值
        /// ExcelWorksheet=excel对象
        /// ExcelRange当前excel单元格对象
        /// int=当前读取的第几行
        /// int=当前读取的第几列
        /// string=返回的值
        /// </summary>
        ImportOnReadCellValue = (string value, ExcelWorksheet worksheet, ExcelRange excelRange, int rowIndex, int columnIndex) =>
        {
            string 表头列名 = worksheet.Cells[1, columnIndex].Value?.ToString();
            //这里可以返回处理后的值,值最终写入到model字段上
            return value;
        };

        //导入保存前处理(可以对list设置新的值)
        ImportOnExecuting = (List<SellOrder> list) =>
        {
            //设置webResponse.Code = "-1"会中止后面代码执行,与返回 webResponse.Error()一样,区别在于前端提示的是成功或失败
            //webResponse.Code = "-1";
            //webResponse.Message = "测试强制返回";
            //return webResponse.OK("ok");

            return webResponse.OK();
        };

        //导入后处理(已经写入到数据库了)
        ImportOnExecuted = (List<SellOrder> list) =>
        {
            return webResponse.OK();
        };
        return base.Import(files);
    }

    public override WebResponseContent Upload(List<IFormFile> files)
    {
        //自定义上传文件路径(目前只支持配置相对路径,默认上传到wwwwroot下)
        //2022.10.07更新ServiceBase.cs、ServiceFunFilter.cs后才能使用
        UploadFolder = $"test/{DateTime.Now.ToString("yyyyMMdd")}";
        return base.Upload(files);
    }

}

58、C#判断Guid为空方法,if (luxs_project_id==Guid.Empty)

if (luxs_project_id==Guid.Empty)

59、后台权限重写,在某些情况下需要手动调用框架的方法,但又没有给用户分配权限,或者不想分配权限,可以直接重写框架方法的权限,在Control中重写权限

 [HttpPost, Route("GetPageData"), AllowAnonymous]
[ApiActionPermission()]//权限重写,在控制器中增加
public override ActionResult GetPageData([FromBody] PageDataOptions loadData)
{
    return base.GetPageData(loadData);
}
[HttpPost, Route("Update"), AllowAnonymous]
[ApiActionPermission()]//权限重写,在控制器中增加
public override ActionResult Update([FromBody] SaveModel saveModel)
{
    return base.Update(saveModel);
}
[HttpPost, Route("Add"), AllowAnonymous]
[ApiActionPermission()]//权限重写,在控制器中增加
public override ActionResult Add([FromBody] SaveModel saveModel)
{
    return base.Add(saveModel);
}

60、VOL流程使用步骤:

1、建表要求:表中必须包括AuditStatus字段 int类型;
2、后台注入:startup类ConfigureContainer方法中增加WorkFlowContainer.Instance.User<表名1>().User<表2>();
3、设计流程图;
4、菜单控制;

61、VOL平台生成父子表的时候一定要先生成子表的模型,再生成父表的模型,否则要走弯路 2022年11月20日 19:09:51

62、VOL平台自带的开启流程

 WorkFlowManager.AddProcese(luxs_Rfq);//启动一个新审批任务

63、C#修改数据库,报跟踪错误时,注意Detached一定要放在第一个SaveChanges之后(最好前后都有Detached),否则报跟踪错误,already being tracked

 dbContext.Entry(ff).State = EntityState.Detached;
  ff = dbContext.Set<fk_reimfloat>().Where(w => w.stepid == curr_stepid && w.reimid == key.ToGuid()).FirstOrDefault();
  ff.remark = remark;
  ff.reyijian = status;//同意or不同意
  ff.bdatetime=DateTime.Now;
  ff.username = userInfo.UserName;
  ff.userrealname = userInfo.UserTrueName;  
  dbContext.Update(ff);
  dbContext.SaveChanges();
  dbContext.Entry(ff).State = EntityState.Detached;//注意Detached一定要放在第一个SaveChanges之后,否则报跟踪错误

64、redis的使用

 public string get_redis()
{
    ICacheService cacheService = AutofacContainerModule.GetService<ICacheService>();
    string tt = cacheService.Get<string>("name1");
    return tt;
}

  [HttpPost, Route("set_redis"), AllowAnonymous]//匿名登录 免登录
  public void set_redis()
  {
      List<Sys_Log> sys_Logs = DBServerProvider.DbContext.Set<Sys_Log>().ToList();    
      ICacheService cacheService = AutofacContainerModule.GetService<ICacheService>();
      for(int i = 0; i <= sys_Logs.Count-1; i++)
      {
          cacheService.AddObject("log"+i.ToString(), sys_Logs[i]);
      }
  }

65、自定义上传文件路径(目前只支持配置相对路径,默认上传到wwwwroot下)

 public override WebResponseContent Upload(List<IFormFile> files)
  {
      //自定义上传文件路径(目前只支持配置相对路径,默认上传到wwwwroot下)
      //2022.10.07更新ServiceBase.cs、ServiceFunFilter.cs后才能使用
      UploadFolder = $"test/{DateTime.Now.ToString("yyyyMMdd")}";
      return base.Upload(files);
  }
//2022年12月5日 16:08:50

66、C#中字符串任意换行,在字符串前面加上@,然后在字符串中间就可以随意用回车换行了,sql语句太长

 string sql = @"select * 
from tab"

67、c#端把对象转为字符串

[HttpPost, Route("save_data_for_ddx")]
 public dynamic save_data_for_ddx([FromBody] ddx ddxxx)
  {
      Hashtable rtn_hst = new Hashtable();
        Console.WriteLine(JsonConvert.SerializeObject(ddxxx));         
      // Console.WriteLine(ddxxx);
      return rtn_hst;
  }

68、数据隔离

//后台在XXXService中override
public override PageGridData<Ok3w_Article> GetPageData(PageDataOptions options)
{
    QueryRelativeExpression = (IQueryable<Ok3w_Article> queryable) =>
    {
       
       if (options.Value==null)//从大屏过来的全部数据
            return queryable;
        else {//从系统中访问
            var user = UserContext.Current.GetUserInfo(UserContext.Current.UserId);
            if (user.UserTrueName != null) queryable = queryable.Where(x => user.Role_Id == 1 || user.Role_Id == 2 || user.UserTrueName == x.Creator);//数据隔离
            return queryable;
        }
    };
    return base.GetPageData(options);
}
//前台,系统内登录
searchBefore(param) {
      //界面查询前,可以给param.wheres添加查询参数
      //返回false,则不会执行查询
      param.value="vol";//说明从系统内登录
      return true;
    },

69、后台获取数据字典

DictionaryManager.GetDictionary("字典编号 ").Sys_DictionaryList

70、后台发送邮件,邮件附件发送,异步发送

EmailHelper.cs,放在core=>Utilities文件夹中

 EmailHelper emailHelper = new EmailHelper(dddx.y_email, "立讯精密供应商定点信", "详情见附件");
 emailHelper.AddAttachments(("wwwroot/" + dddx.fujian).MapPath());
 emailHelper.Send();
 //
EmailHelper emailHelper = new EmailHelper(emailaddress, title, content);
emailHelper.SendMailAsync();//异步发送邮件

71、版本号用日期来表示

  <PropertyGroup>
    <TargetFrameworks>net461;netstandard2.0;netstandard2.1;netcoreapp3.1;net5.0;net6.0;net5.0-windows;net6.0-windows;net7.0;net7.0-windows</TargetFrameworks>
    <AssemblyName>DaIot.Core</AssemblyName>
    <RootNamespace>DaIot</RootNamespace>
    <AssemblyTitle>DaIot.Core</AssemblyTitle>
    <Description>基础类库</Description>
    <Company>DaIot</Company>
    <Copyright>©2002-2023 DaIot</Copyright>
    <VersionPrefix>1.1</VersionPrefix>
    <VersionSuffix>$([System.DateTime]::Now.ToString(`yyyy.MMdd`))</VersionSuffix>
    <Version>$(VersionPrefix).$(VersionSuffix)</Version>
    <FileVersion>$(Version)</FileVersion>
    <AssemblyVersion>$(VersionPrefix).$([System.DateTime]::Now.ToString(`yyyy.MMdd`))</AssemblyVersion>
    <OutputPath>..\Bin</OutputPath>
    <GenerateDocumentationFile>True</GenerateDocumentationFile>
    <LangVersion>latest</LangVersion>
    <!--<Nullable>enable</Nullable>-->
    <ImplicitUsings>enable</ImplicitUsings>
    <SignAssembly>False</SignAssembly>
    <AssemblyOriginatorKeyFile></AssemblyOriginatorKeyFile>
  </PropertyGroup>

72、生成二维码 QRCode.cs

73、创建PDF文件和水印

//原理:先生成html文件模板,再把html文件转换为PDF
//添加水印 安装2个组件 FreeSpire.PDF 版本8.6.0 、 Magicodes.IE.Pdf 版本 2.7.4.2 导出
//参考ls-projects\sqms项目的中save_data_for_ddx控制方法(生成定点信数据)
/// <summary>
        /// 存储定点信数据
        /// </summary>
        /// <param name="_reimds_str"></param>
        /// <returns></returns>
        [HttpPost, Route("save_data_for_ddx")]
        public WebResponseContent save_data_for_ddx([FromBody] dynamic qian)
        {
            WebResponseContent ws = new WebResponseContent();
            Hashtable rtn_hst = new Hashtable();
            ddx ddxxx = new ddx();          
            ddxxx=JsonConvert.DeserializeObject<ddx>(JsonConvert.SerializeObject(qian.ddx));
            var rfq = DBServerProvider.DbContext.Set<Luxs_rfq>().Where(w => w.luxs_rfq_id == ddxxx.luxs_rfq_id).FirstOrDefault();

            int Role_Id = UserContext.Current.GetUserInfo(UserContext.Current.UserId).Role_Id;
            if (Role_Id != 5)
            {
                return ws.Error("只有采购员角色才能发定点信!");
            }

            List<Luxs_user_for_project> luxs_User_For_Project_list= JsonConvert.DeserializeObject<List<Luxs_user_for_project>>(JsonConvert.SerializeObject(qian.project_usage_array));
            if (luxs_User_For_Project_list.Count > 0)
            {
                var del_Luxs_user_for_project = DBServerProvider.DbContext.Set<Luxs_user_for_project>().Where(w => w.luxs_project_id == luxs_User_For_Project_list.First().luxs_project_id).ToList();//删除不在清单中的数据
                DBServerProvider.DbContext.RemoveRange(del_Luxs_user_for_project);
                DBServerProvider.DbContext.SaveChanges();
            }
            
            foreach (var item in luxs_User_For_Project_list) //回写项目用量数据
            {
                DBServerProvider.DbContext.Add(item);
            }
            DBServerProvider.DbContext.SaveChanges();


            List<Luxs_plan_for_project> luxs_plan_for_project_list_list = JsonConvert.DeserializeObject<List<Luxs_plan_for_project>>(JsonConvert.SerializeObject(qian.luxs_plan_for_project_list));
            if (luxs_plan_for_project_list_list.Count > 0)
            {
                var del_Luxs_plan_for_project = DBServerProvider.DbContext.Set<Luxs_plan_for_project>().Where(w => w.luxs_project_id == luxs_plan_for_project_list_list.First().luxs_project_id).ToList();//删除不在清单中的数据
                DBServerProvider.DbContext.RemoveRange(del_Luxs_plan_for_project);
                DBServerProvider.DbContext.SaveChanges();
            }
              
            foreach (var item in luxs_plan_for_project_list_list)//回写项目计划数据
            {
                DBServerProvider.DbContext.Add(item);
            }
            DBServerProvider.DbContext.SaveChanges();
            //开始生成定点信PDF文件
            string s_filepath = @"Template/cutomer_print_template/ddx_print/ddx_template_ok.html";
            string save_path= "Upload/Tables/ddx/" + DateTime.Now.ToString("yyyyMMddHHmmss") + "/";
            string d_filepath = @"wwwroot/" + save_path;
            string temp = s_filepath.MapPath().ReplacePath();
            d_filepath = d_filepath.MapPath().ReplacePath();
            string pdf_filename = ddxxx.y_fang + "定点信.pdf";

            //开始生成定点信数据库数据
            //luxs_rfq_id与luxs_project_id获取唯一定点信,因为一个RFQ对应可能多个项目问题
            var del_ddx = DBServerProvider.DbContext.Set<ddx>().Where(w => w.luxs_rfq_id == ddxxx.luxs_rfq_id && w.luxs_project_id == ddxxx.luxs_project_id);
            DBServerProvider.DbContext.RemoveRange(del_ddx);
            ddxxx.fujian = save_path + pdf_filename;
            DBServerProvider.DbContext.Add(ddxxx);
            DBServerProvider.DbContext.SaveChanges();
            // Console.WriteLine(ddxxx);
          
            string htmlstr = CustomHelper.read_local_file(s_filepath);
            Type type = ddxxx.GetType();
            MemberInfo[] members = type.GetProperties();
            foreach (PropertyInfo item in members)
            {
                htmlstr = htmlstr.Replace("@" + item.Name + "@", (item.GetValue(ddxxx)??"").ToString());
            }
            string yongliang = "";
            for (int i = 0; i < 2; i++)
            {
                if (i == 0)
                {
                    yongliang = yongliang+"<tr>";
                    foreach (var item in luxs_User_For_Project_list)
                    {
                        yongliang = yongliang + "<td>" + item.year + "</td>";
                    }
                    yongliang = yongliang + "</tr>";
                }
                if (i == 1)
                {
                    yongliang = yongliang + "<tr>";
                    foreach (var item in luxs_User_For_Project_list)
                    {
                        yongliang = yongliang + "<td>" + item.dosage.ToString() + "</td>";
                    }
                    yongliang = yongliang + "</tr>";
                }
            }
            
            htmlstr = htmlstr.Replace("@yonliang@", yongliang).ToString();
            string proplan = "";

            foreach (var item in luxs_plan_for_project_list_list)
            {
                proplan = proplan + "<tr><td>" + item.plannode + "</td><td>"+item.edate+"</td></tr>";
            }
            htmlstr = htmlstr.Replace("@proplan@", proplan).ToString();

            // Console.WriteLine(qian.ddx_str.ToString());
            //html文件转PDF文件
            PDFHelper.GenPDFFromHtml_str(htmlstr, d_filepath, pdf_filename);
            rfq = DBServerProvider.DbContext.Set<Luxs_rfq>().Where(w => w.luxs_rfq_id == ddxxx.luxs_rfq_id).FirstOrDefault();
            PDFHelper.PDFAddMark(d_filepath+ pdf_filename, d_filepath+ pdf_filename, rfq.ddfren, 1, 6);
            return ws.OK("定点信生成成功!");
        }

74、附件copy ,复制附件 附件复制

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using VOL.Core.Extensions;

namespace VOL.Core.Utilities.my_huang_tools
{
    public static class copy_vol_fujian
    {

        /// <summary>
        /// 应用场景用于附件文件的多个版本备份
        /// 本功能可以copy多个VOL平台的附件,每个附件用,号隔开,
        /// 返回值是目标文件的路径,多个文件也是用,号隔开,
        /// 返回值可以直接存viewgrid控件的file类型字段可以给前端下载
        /// 实例如下:
        ///  public dynamic testVolCopy()
       ///             {
       ///                 string rtn = "";
       ///             rtn=copy_vol_fujian.copy_fujian("Upload/Tables/Luxs_js_for_mara/202212081118359593/重卡商用车.xlsx,Upload/Tables/Luxs_js_for_mara/202212081131089575/SQMS模块.xlsx");
      ///                  if (string.IsNullOrEmpty(rtn))
      ///                  {
      ///                      return "";
       ///                 }
        ///                else
         ///               {
         ///                   return rtn;
         ///               }
         ///   }
/// </summary>
/// <param name="files_path"></param>
/// <returns></returns>
public static string copy_fujian(string files_path)
        {
            string rtn_files_path = "";
            if (string.IsNullOrWhiteSpace(files_path)) return "";
            string[] file_path = files_path.Split(",");
            List<string> rtn_files = new List<string>();
            foreach (string item in file_path)
            {
                string s_str = ("wwwroot/" + item).MapPath();
                //Console.WriteLine(luxs_gongf_doc.fujian);
                string his_path = DateTime.Now.ToString("yyyyMMddHHmmss") + "/his/";
                string tt = his_path;
                string d_path = Path.GetDirectoryName(s_str) + "/" + tt;
                //string d_full = d_path + Path.GetFileName(s_str);
                string save_path = item.Replace(Path.GetFileName(s_str), "") + his_path + Path.GetFileName(s_str);//保存到数据库中的历史文件路径
                rtn_files.Add(save_path);
                path_create(d_path);
                try {
                    File.Copy(s_str, d_path + Path.GetFileName(s_str), true);
                }
                catch (Exception)//文件删除后,可以继续
                {
                    continue;
                }
                
            }
            rtn_files_path=string.Join(",", rtn_files);
            return rtn_files_path;
        }

        static void  path_create(string path_str)
        {
            if (!Directory.Exists(path_str))
            {
                Directory.CreateDirectory(path_str);
            }
        }
    }
}

75、获取web端提交的IP地址 这个方法只能获取nginx的IP,想获取真实IP需要修改nginx的配置

 public static string GetClientUserIp()
  {
      var ip = HttpContext.Current.Request.Headers["X-Real-IP"].ToString();   
      Logger.Info("X-Real-IPoooo" + ip);
      Logger.Info("X-Forwarded-Foroooo" + HttpContext.Current.Request.Headers["X-Forwarded-For"].ToString());
      if (string.IsNullOrEmpty(ip))
      {
          ip = HttpContext.Current.Request.Headers["X-Forwarded-For"].ToString();
          //Logger.Info("X-Forwarded-Foroooo" + ip);
      }
      if (string.IsNullOrEmpty(ip))
      {
          ip = HttpContext.Current.Connection.RemoteIpAddress.ToString();
      }
      return ip.Replace("::ffff:", ""); ;
  }

ngnix配置如下

server {
		listen 80;
		server_name api.volcore.xyz;
		location / {
    #配置跨域信息
    add_header 'Access-Control-Allow-Credentials' 'true';
    # 为预检请求加的header
    add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE';
    #为预检请求加的header
    add_header 'Access-Control-Allow-Headers' '*';
    add_header Access-Control-Allow-Credentials 'true';
		#proxy_set_header X-Real-IP $remote_addr;
		proxy_set_header Host $http_host;
	  proxy_set_header X-Real-IP  $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
		proxy_pass http://localhost:9991; 
	     }

        }

76、后台动态控制器修改saveModel中字典数据 vol框架

 [HttpPost, Route("Add"), AllowAnonymous]
        [ApiActionPermission()]//权限重写,在控制器中增加
        public override ActionResult Add([FromBody] SaveModel saveModel)
        {
            for(int i=0;i<= saveModel.MainData.Keys.Count - 1; i++)//轮询修改字典的key/value
            {
                if (saveModel.MainData.ContainsKey("ipaddress"))
                {
                    saveModel.MainData["ipaddress"] = CustomHelper.GetClientUserIp();
                }
            }
           // Console.WriteLine(CustomHelper.GetClientUserIp());
            return base.Add(saveModel);
        }

77、后台权限调试 ActionPermissionFilter.cs这里调试看下就知道了

ActionPermissionFilter.cs这里调试看下就知道了

78、

79、

80、

81、

82、

83、

84、

85、

86、

87、

88、

89、

90、

91、

92、

93、

94、

95、

96、

三、移动端

1、uniapp项目在Hbuild中是可视化运行,一定要注意了,不是用npm

2、修改了vol-uni的viewgrid控件:为了增自定义审核按钮,记得以后再改回来

<view style="padding: 0 20rpx;width: 100%;">
  <u-button @click="otherclick" :customStyle="{'border-radius': '6px'}" type="primary"
    text="审批/提交"></u-button>
</view>

3、uni中页面跳转如果是tabBar,只能用switchTab,且url一定要以/开头


4、uniapp中实现单独页面volform收集数据,基于vol代码生成器自动生成,重用字段js

//single_form.vue 用于手机端收集数据
<template>
	<view >
		<vol-form @extraClick="extraClick" :load-key="true" ref="form" :form-options.sync="editFormOptions"
			:formFields.sync="editFormFields">
		</vol-form>

		<view class="btns">
			<view class="btn">
				<u-button type="primary" @click="reset" shape="circle" text="重置表单"></u-button>
			</view>
			<view class="btn">
				<u-button type="success" @click="save" shape="circle" text="保存"></u-button>
			</view>
		</view>
	</view>
</template>

<script>
	import options from './renshiOptions.js';//引入同目录下的代码生成器生成的js文件
	let _options = options();
	export default {
		data() {
			return {
				opt:_options,
				editFormFields:_options.editFormFields,
				editFormOptions:_options.editFormOptions,
			}
		},
		onLoad() {
			console.log("load..");
		},
		onUnload() {
			console.log("onUnload..");
			uni.navigateTo({
				url: "/pages/mytable/renshi/end_back"//返回按钮跳转,
			})
		},
		onShow() {
			this.$nextTick(()=>{
				this.$refs.form.reset();
				this.$toast("表单已重置")
			})
			
		},
		onBackPress(options) {//返回return 阻止按钮起效
			console.log("kkkk");
		   /* if (options.from === 'navigateBack') {
		        return false
		    }
		    uni.navigateBack({
		        delta: 100
		    })*/
		    return true;//返回return 阻止按钮起效
		},
		methods: {
			save() {
				if (this.$refs.form.validate()) {
					let savedata={//判断前端对象属性为数组 
						mainData:this.editFormFields,
						delKeys: null,
						detailData: null
					}
					var keys=Object.keys(savedata.mainData);//需要把selectList字段类型转换为逗号分割的字符串,这边不要,否则保存数据库格式不对
					      for (let index = 0; index < keys.length; index++) {
					        const e = keys[index];
							console.log(savedata.mainData[e]);
					        if(Array.isArray(savedata.mainData[e])){//判断savedata.mainData对象中哪些属性为数组的,如果是转为字符串
									
					          savedata.mainData[e]=savedata.mainData[e].join(',');//,转换为,分割的字符串,用于volform提交数据
					        }
					      }

					this.http.post("/api/renshi/Add", savedata, true).then((s) => {
						this.$toast("保存成功");
						uni.navigateTo({
							url: "/pages/mytable/renshi/end_back"
						})
						})
				}
			},
			reset() {
				this.$refs.form.reset();
				this.$toast("表单已重置")
			}
		},
	}
</script>

<style lang="less" scoped>
	.form-test {
		margin-top: -20rpx;
		background: #fbfbfb;
		padding-top: 20rpx;
	}

	.btns {
		display: flex;
		padding: 0rpx 20rpx;

		.btn {
			flex: 1;
			padding: 20rpx;
		}
	}
</style>
//end_back.vue  用于保存完跳转页面
<template>
	<view class="uni-common-mt" style="background:#FFF; padding:20rpx;">
		<rich-text :nodes="nodes"></rich-text>
	</view>
</template>

<script>

	export default {
		data() {
			return {
				nodes: [{
					name: 'div',
					attrs: {
						class: 'div-class',
						style: 'line-height: 60px; color: red; text-align:center;'
					},
					children: [{
						type: 'text',
						text: '谢谢提交,立讯精密欢迎你!'
					}]
				}],
			}
		},
		onUnload() {
			console.log("onUnload..");
			uni.navigateTo({
				url: "/pages/mytable/renshi/single_form"
			})
		},
		methods: {
			
		},

	}
</script>

<style lang="less" scoped>
	.form-test {
		margin-top: -20rpx;
		background: #fbfbfb;
		padding-top: 20rpx;
	}

	.btns {
		display: flex;
		padding: 0rpx 20rpx;

		.btn {
			flex: 1;
			padding: 20rpx;
		}
	}
</style>

5、注意移动端,授权后测试需要退出重新登录才有效

6、两个按钮在一行显示

<view v-if="item.field =='department' && item.field !='xname'" style="flex-flow: row;justify-content: flex-start;display: flex;">	
				<u-picker :show="show" :columns="depart" @confirm="confirm"></u-picker>
				<u-button @click="show = true">{{item.title}}:{{editFormFields.department}}</u-button>
				<u-button  @click="save" size=normal text="提交"></u-button>
			</view>

7、

8、

9、

10、

11、

四、前后端综合样例

1、手工分页模版实例:先用通用SQL查询出数据包括条件、排序,然后再转成List类型,进行分页给客户端,参考jgj.vol系统

//服务器端:/api/Fk_reimlist/my_reim_list

        public object my_reim_list(string options, string menu, string tj_volform)//多参数查询,pdata是对象{},直接访问属性
        {

            dynamic queryjson = JsonConvert.DeserializeObject(options);
            dynamic tj_form = JsonConvert.DeserializeObject(tj_volform);
            Hashtable rtn_hst = new Hashtable();
            dynamic orderby = queryjson.orderby;
            int currentpage = queryjson.currentpage;//当前页
            int pagesize = queryjson.pagesize;//每页条数
            string where_str = "";
            string order_str = "";
            string ordername = "CreateDate";//默认排序字段
            string orderfx = "desc";

            // Console.WriteLine(tj_form);
            string loginid = UserContext.Current.GetUserInfo(UserContext.Current.UserId).UserName;//用户登录账户
            //Console.WriteLine(menu);
            if (menu == "reim_all_lookup") loginid = "";//loginid空表示所有的案件都能看到

            int?[] reimid_array = DBServerProvider.DbContext.Set<Fk_reimfloat>()//登录用户处理过的所有数据,传入前端避免重复查询后台
              .Where(t => t.username.Contains(loginid)).Select(s => s.reimid).ToArray();



            IQueryable<Fk_reimfloat> all_minx = DBServerProvider.DbContext.Set<Fk_reimfloat>()//流程明细数据
               .Where(t => (reimid_array.Contains(t.reimid)));

            IQueryable<Fk_reimlist> _all_reimlist = DBServerProvider.DbContext.Set<Fk_reimlist>()//报销行项目数据
              .Where(t => (reimid_array.Contains(t.reim_id)));


            /*IQueryable<Fk_yusuan> _all_ys = DBServerProvider.DbContext.Set<Fk_yusuan>()//预算数据
             .Where(t =>t.gjahr==DateTime.Now.Year);*/

            /*foreach (var item in reimid_array)
            {
                Console.WriteLine(item);
                *//*foreach (dynamic item1 in item.q)
                {
                    Console.WriteLine(item1.reimid);
                }*//*

            }*/


            //
            IQueryable<Sys_Step> mstep = DBServerProvider.DbContext.Set<Sys_Step>()//,步骤表
              .Where(t => (true));

            IQueryable<Sys_User> muser = DBServerProvider.DbContext.Set<Sys_User>()//,人员表
              .Where(t => (true));

            IQueryable<Fei_typelist> _km_query = Fei_typelistRepository.//科目
             Instance.FindAsIQueryable(t => (t.company_oa_id == 9000));//9000和8000的科目是一样的

            IQueryable<Fk_cntr> _cntr_query = Fk_cntrRepository.//成本中心
              Instance.FindAsIQueryable(t => (true));


            IQueryable<dynamic> all_reimlist = from q in _all_reimlist

              join q2 in _km_query on q.fei_type equals q2.racct into q2_temp
              from q2_out in q2_temp.DefaultIfEmpty()

              join q3 in _cntr_query on q.rcntr equals q3.rcntr_no into q3_temp
              from q3_out in q3_temp.DefaultIfEmpty()

              orderby q.reimlist_id ascending
              select new
              {
                  q2_out.fei_typename,
                  q3_out.rcntr,
                  q
              };

            IQueryable<dynamic> mingx = from q in all_minx

            join q2 in mstep on q.stepid equals q2.stepid into q2_temp
            from q2_out in q2_temp.DefaultIfEmpty()

            join q3 in muser on q.username equals q3.UserName into q3_temp
            from q3_out in q3_temp.DefaultIfEmpty()


            orderby q.pxm descending
            select new
            {
                description = q3_out.UserTrueName + (q.bdatetime == null ? "" : q.bdatetime.ToString("yyyy-MM-dd HH:mm:ss")),
                q2_out.stepname,
                q3_out.UserTrueName,
                ok_remark = CustomHelper.checkStr(q.remark),
                q.reimid,
                q
            };


            if (((JObject)tj_form).Count != 0)//判断查询条件不为空
            {
                tj_form = JsonConvert.SerializeObject(tj_form);//把json对象转为字符串,序列化
                JObject obj = JObject.Parse(tj_form);
                //JObject where_obj = (JObject)obj["wheres"];
                foreach (var item in obj)
                {
                    Console.WriteLine(item.Key);
                    Console.WriteLine(item.Value);

                    if (item.Key == "depid_oa" && !string.IsNullOrEmpty(item.Value.ToString()))//
                    {
                        string tt=item.Value.ToString().Substring(1, item.Value.ToString().Length - 2);   
                        where_str = where_str + " and a." + item.Key + " in(" + tt + ")";
                    }
                    if (item.Key == "username" && !string.IsNullOrEmpty(item.Value.ToString()))//判读字符串数组,前端为selectList类型多选
                    {

                        string tt = item.Value.ToString().Substring(1, item.Value.ToString().Length - 2);//去掉前后[]
                        if (!string.IsNullOrEmpty(tt) && tt.Length > 0 && tt != "null")
                        {

                            tt = tt.Replace("\"", "'");
                            where_str = where_str + " and username in(" + tt+")";
                        }

                    }
                    if (item.Key == "bdatetime" && !string.IsNullOrEmpty(item.Value.ToString()))//判读日期区间类型,其他的
                    {

                        string tt = item.Value.ToString().Substring(1, item.Value.ToString().Length - 2);//去掉前后[]
                        string[] dd = tt.Split(",");

                        dd[0] = dd[0].Replace("\"", "'").Replace("'", "").Trim();//去掉支付串中的双引号和单引号
                        if (!string.IsNullOrEmpty(dd[0])&& dd[0].Length>0 && dd[0]!="null")
                        {

                            where_str = where_str + " and convert(varchar,"+ item.Key + ",120)" + " >='" + dd[0]+"'";
                        }
                        dd[1] = dd[1].Replace("\"", "'").Replace("'", "").Trim();
                        if (!string.IsNullOrEmpty(dd[1]) && dd[1].Length > 0 && dd[1] != "null")
                        {

                            where_str = where_str + " and convert(varchar," + item.Key + ",120)" + " <='" + dd[1]+"'";
                        }
                    }
                    if (item.Key == "reim_id" && !string.IsNullOrEmpty(item.Value.ToString()))//
                    {
                        where_str = where_str + " and " + item.Key + " in(" + item.Value + ")";
                    }
                    if (item.Key == "typeid" && !string.IsNullOrEmpty(item.Value.ToString()))//
                    {
                        where_str = where_str + " and " + item.Key + " in(" + item.Value + ")";
                    }
                }
            }

            if (((JObject)orderby).Count != 0)//判断排序不为空
            {
                //判断字符串是否为空,把转换为长度判断
                if (Convert.ToString(orderby.ordername).Length > 0) ordername = orderby.ordername;
                if (Convert.ToString(orderby.orderfx).Length > 0) orderfx = orderby.orderfx;

            }

            order_str = " order by " + ordername + " " + orderfx;
            //为了跨数据库的通用性,多表关联时SQL语句要最好使用嵌套,单表模式;
            string sqlstr = "select a.*,c.*,(select typename from Sys_Typelist where typeid=a.typeid) typename,(select stepname from sys_step e where e.stepid=a.stepid) stepname from Fk_reim a,fk_depart c " +
                            " where a.depid_oa=c.depid_oa" +
                            " and a.reim_id in(select d.reimid from fk_reimfloat d where d.username like '%" + loginid + "%')" +
                              where_str +
                            " and 1=1" + order_str;
            Console.WriteLine(sqlstr);
            List<dynamic> rnt_list = DBServerProvider.SqlDapper.QueryList<dynamic>(sqlstr, "").ToList();

            rtn_hst.Add("rows", rnt_list.Count());

            rnt_list = rnt_list.Skip(pagesize * (currentpage - 1)).Take(pagesize).ToList();
            rtn_hst.Add("_query", rnt_list);
            rtn_hst.Add("q_minx", mingx);//流程数据明细
            rtn_hst.Add("all_reimlist", all_reimlist);//报销行项目明细
                                                      //  rtn_hst.Add("_all_ys", _all_ys);//本年度预算

            JsonSerializerSettings js = new JsonSerializerSettings();
            js.DateFormatString = "yyyy-MM-dd HH:mm:ss";
            return JsonNormal(rtn_hst, js);//原样返回数据,系统默认为小驼峰方式

        }

vue端:mytable\reim_loopup.vue

<template>
  <VolHeader
    class="el-icon-s-grid"
    text="已办流程"
    style="padding-left: 10px; margin-bottom: 26px"
  >
  </VolHeader>
<VolForm
      ref="tj_myform"
      :loadKey="tj_loadKey"
      :formFields="tj_formFields"
      :formRules="tj_formRules"
    >
    </VolForm>
  <el-row>
    <el-col :span="6"><div class="grid-content bg-purple-light"></div></el-col>
    <el-col :span="6"><div class="grid-content bg-purple-light"></div></el-col>
    <el-col :span="6"><div class="grid-content bg-purple-light"></div></el-col>
    <el-col :span="6"
      ><div class="grid-content bg-purple-light">
        <el-button type="primary" @click="tj_search">查询</el-button>
        <el-divider direction="vertical" border-style="dashed" />
        <el-dropdown split-button type="primary" @click="tt">
          详细表单
          <template #dropdown>
            <el-dropdown-menu>
              <el-dropdown-item @click="f_print('hktzs')"
                >汇款通知书</el-dropdown-item
              >
              <el-dropdown-item @click="f_print('cnbxd')"
                >差旅费报销单</el-dropdown-item
              >
              <el-dropdown-item @click="f_print('fybxd')"
                >费用报销单</el-dropdown-item
              >
              <!-- <el-dropdown-item>审批流程</el-dropdown-item> -->
            </el-dropdown-menu>
          </template>
        </el-dropdown>
      </div>
    </el-col>
  </el-row>

  <el-table
    :data="tabledata"
    height="380"
    border
    style="width: 100%"
    highlight-current-row
    @sort-change="sortChange"
    @current-change="on_click_handleCurrentChange"
    :cell-style="cellstyle"
    ref="multitable"
    @selection-change="handleSelectionChange"
  >
    <el-table-column label="查看" width="120">
      <template #default="scope">
        <el-button
          type="text"
          size="small"
          @click="look_reimitem(scope.$index, scope.row)"
          >详细</el-button
        >
        <el-button
          v-show="scope.row.fujian != null"
          type="text"
          @click="downfile(scope.row.fujian)"
          size="small"
          >附件</el-button
        >
        <el-button
          v-show="is_shen(scope.row.reim_id)"
          type="text"
          @click="shenh(scope.row.reim_id)"
          size="small"
          >解除可用额度限制</el-button
        >
      </template>
    </el-table-column>
    <el-table-column type="selection" width="55" />
    <el-table-column prop="reim_id" label="编号" width="100" sortable />
    <el-table-column prop="typename" label="类别" width="180" sortable />
    <el-table-column prop="depname_oa" label="提报部门" width="180" sortable />
    <el-table-column prop="username" label="提报人" width="180" sortable />
    <el-table-column prop="stepname" label="当前步骤" width="180" sortable />
    <el-table-column
      prop="step_username"
      label="当前待处理人"
      width="180"
      sortable
    />
    <el-table-column
      prop="hq_username"
      label="当前汇签处理人"
      width="180"
      sortable
    />
    <el-table-column prop="total_j" label="总金额" width="100" sortable />
    <el-table-column prop="CreateDate" label="创建时间" width="180" sortable />
  </el-table>
  <div style="text-align: center; margin-top: 30px">
    <el-pagination
      background
      layout="total,sizes,prev, pager, next"
      :total="rows"
      :page-sizes="[10, 60, 100, 120, 200]"
      :page-size="tj_options.pagesize"
      @current-change="handleCurrentChange"
      @size-change="handleSizeChange"
    >
    </el-pagination>
  </div>

  <el-tabs
    type="border-card"
    v-model="_tabName"
    class="demo-tabs"
    @tab-click="handleClick"
  >
    <el-tab-pane label="流程明细" name="first">
      <el-table :data="mxdata" style="width: 100%">
        <el-table-column prop="fk_reimfloat_id" label="序号" width="110" />
        <el-table-column prop="bdatetime" label="处理时间" width="180" />
        <el-table-column prop="UserTrueName" label="处理人" width="180" />
        <el-table-column prop="reyijian" label="提交意见" width="180" />
        <el-table-column prop="ok_remark" label="意见详情" width="180" />
        <el-table-column prop="stepname" label="步骤" width="180" />
        <el-table-column prop="node_type" label="类型" width="180" />
      </el-table>
    </el-tab-pane>
    <el-tab-pane label="流程图" name="second">
      <el-steps :space="200" :active="_active" finish-status="success">
        <el-step
          :title="item.stepname"
          :description="item.description"
          v-for="item in step_items" 
        >
          <template #description="scope">
            {{ item.UserTrueName }}<br />{{ item.bdatetime }}
          </template>
        </el-step>
      </el-steps>
    </el-tab-pane>
  </el-tabs>

  <el-dialog v-model="_dialogV" title="费用明细" width="88%" draggable>
    <div v-show="feiy">
      <view_reim_form ref="view_reim_form"></view_reim_form>
    </div>
    <div v-show="cnf">
      <view_reim_form_cc ref="view_reim_form_cc"></view_reim_form_cc>
    </div>
    <span>
      <el-table :data="reimlist" @row-click="rowclick">
        <el-table-column
          property="fei_typename"
          label="科目"
          width="200"
        ></el-table-column>
        <el-table-column
          property="fei_type"
          label="科目编码"
          width="200"
        ></el-table-column>
        <el-table-column
          property="rcntr_no"
          label="成本中心"
          width="200"
        ></el-table-column>
        <el-table-column
          property="rcntr"
          label="成本中心编码"
          width="200"
        ></el-table-column>
        <el-table-column property="je" label="金额"></el-table-column>
        <el-table-column property="tax" label="税额"></el-table-column>
        <el-table-column property="tickno" label="票号"></el-table-column>
        <el-table-column property="fzdate" label="时间"></el-table-column>
        <el-table-column property="remark" label="备注"></el-table-column>
      </el-table>
      <ys_analyse_detail ref="ys_analyse_detail"></ys_analyse_detail>
    </span>
    <template #footer>
      <span>
        <el-button type="primary" @click="_dialogV = false">关闭</el-button>
      </span>
    </template>
  </el-dialog>

  <el-dialog v-model="_vshen" title="填写解除意见" width="80%" center>
    <reimfloat_form ref="reimfloat_form1"></reimfloat_form>
    <!-- <span>确定审核通过吗?</span> -->
    <template #footer>
      <span class="dialog-footer">
        <el-button @click="_vshen = false">取消</el-button>
        <el-button type="primary" @click="s_shen_sever">确定</el-button>
      </span>
    </template>
  </el-dialog>

  <el-dialog v-model="_v_print" title="">
    <div style="">
      <pre_print ref="pre_print1"></pre_print>
      <!-- 窗体 -->
    </div>

    <template #footer>
      <span class="dialog-footer">
        <el-button type="primary" @click="_v_print = false">关闭</el-button>
      </span>
    </template>
  </el-dialog>
</template>

<script>
import VolHeader from "@/components/basic/VolHeader.vue";
import VolForm from '@/components/basic/VolForm.vue';
import http from "@/api/http.js";
import { ArrowDown } from "@element-plus/icons"; //注意引入包,后需要在components中export
import pre_print from "@/extension/mytable/mytable/other/pre_print.vue";
import ys_analyse_detail from "@/views/mytable/mytable/ys_analyse_detail.vue";
import reimfloat_form from "@/extension/mytable/mytable/other/reimfloat_form.vue";
import view_reim_form from "@/extension/mytable/mytable/other/view_reim_form.vue";
import view_reim_form_cc from "@/extension/mytable/mytable/other/view_reim_form_cc.vue";
export default {
  components: {
    ys_analyse_detail,
    reimfloat_form,
    VolHeader,
    pre_print,
    ArrowDown, //注意引入包,后需要在components中export
    view_reim_form,
    view_reim_form_cc,
    VolForm
  },
  data() {
    return {
      tj_formFields:{"reim_id":"","typeid":"","bdatetime":"","depid_oa":"","username":""},
      tj_formRules:[[{"title":"编号","field":"reim_id","type":"input"},
      {"dataKey":"tree_department","data":[],"title":"部门名称","field":"depid_oa","type":"cascader"},
      {"dataKey":"typelist","data":[],"title":"业务类型","field":"typeid","type":"select"},],
      [{"title":"填报时间","field":"bdatetime","type":"date","range": true,onChange: (val) => {
              console.log('日期:' + val);
            }},
            {"dataKey":"uerlist","data":[],"title":"姓名","field":"username","type":"selectList"},]],
      tj_loadKey:true,
      feiy: false,
      cnf: false,
      tabledata: [], //{通用}
      rows: 0, //总行数{通用}
      tj_options: {
        //提交给后台查询条件{通用}
        currentpage: 1, //当前页
        pagesize: 10, //每页大小
        wheres: {
          //用于提交到后端的数据结构,如果没有查询条件就不要定义变量,否则后台会当作查询条件
          year: "",
          month: "",
          racct: "all",
          cntr: "all",
          reim_id: null,
        },
        orderby: {
          ordername: "CreateDate", //table排序字段
          orderfx: "desc", //asc,desc
        },
      },
      mxdata: [], //明细数据用于显示
      _mxdata: [], //用于过程处理
      step_items: [],
      _active: 1,
      _dialogV: false, //
      reimlist: [],
      _reimlist: [],
      _tabName: "first",
      _v_fujian: 0,
      _sqj: [], //待审核reimid[]
      _vshen: false,
      _cur_shen_id: null, //当前审核reim_id
      _curr_muti_select: [], //当前多选的行
      _v_print: false,
    };
  },
  setup() {},
  destroyed() {},
  mounted() {
    this.tj_search();
  },
  methods: {
    rowclick(row, column, event) {
      var ndate = new Date();
      let tijian = {
        racct: row.fei_type,
        rcntr: row.rcntr,
        racct_name: row.fei_typename,
        rcntr_name: row.rcntr_no,
        month: ndate.getMonth(),
        year: ndate.getFullYear(),
      };
      let _tijiao = encodeURIComponent(JSON.stringify(tijian));

      //console.log(row);

      let url = "/api/Fk_yusuan/ys_analyse_detail?options=" + _tijiao;
      http.post(url, {}, true).then((s) => {
        //console.log(s);
        //this.$refs.ys_analyse_detail.ys_data.splice(0,this.$refs.ys_analyse_detail.ys_data.length);
        //this.$refs.ys_analyse_detail.sj_data.splice(0,this.$refs.ys_analyse_detail.sj_data.length)
        s.ys.forEach((x) => {
          x.racct_name = s.racct_name;
          x.rcntr_name = s.rcntr_name;
        }); //把中文名称赋值给ys,相当于给s.ys增加属性
        s.sj.forEach((x) => {
          x.racct_name = s.racct_name;
          x.rcntr_name = s.rcntr_name;
        });
        this.$refs.ys_analyse_detail.ys_data = s.ys;
        this.$refs.ys_analyse_detail.sj_data = s.sj;
        //console.log(this.$refs.ys_analyse_detail.ys_data);
        //return;
        //this.rows = s.rows;
        //this.tabledata.splice(0, this.tabledata.length);
        //this.tabledata.push(...s._query);
      });
    },
    tj_search() {
      //{通用}
      //console.log(this.$route.name);

      let tj_volform=encodeURIComponent(JSON.stringify(this.tj_formFields))
      let _tjoptions = encodeURIComponent(JSON.stringify(this.tj_options)); //防止中文
      let url =
        "/api/Fk_reimlist/my_reim_list?options=" +
        _tjoptions +
        "&menu=" +
        this.$route.name+
        "&tj_volform="+tj_volform;
      let _s = [];

      http.post(url, {}, true).then((s) => {
        //有访问后台的http时初始化一定要放在http里面
        this._mxdata.splice(0, this._mxdata.length);
        this.mxdata.splice(0, this.mxdata.length); //清空表
        this._reimlist.splice(0, this._reimlist.length);
        this.reimlist.splice(0, this.reimlist.length); //清空表

        s.q_minx.forEach((x) => {
          //流程明细表把服务器端的多个对象合并,方便前端取数
          let _t = {
            //合并新的对象
            ...x.q,
            UserTrueName: x.UserTrueName,
            stepname: x.stepname,
            ok_remark: x.ok_remark,
            description: x.description,
          };
          this._mxdata.push(_t); //流程明细
          //console.log(_t);
        });

        s.all_reimlist.forEach((x) => {
          //报销明细表 服务器端的多个对象合并
          let _t = {
            //合并新的对象
            ...x.q,
            fei_typename: x.fei_typename,
            rcntr_no: x.rcntr,
          };
          this._reimlist.push(_t); //流程明细
        });

        this.rows = s.rows; //总行数
        this.tabledata.splice(0, this.tabledata.length); //清空表
        this.tabledata.push(...s._query);
        //console.log(s._query);
        //this.tabledata.push(...s);
        //console.log(this._mxdata);
        this._sqj = this._mxdata.filter((s) => s.node_type == "sqj"); //申请预算不限制
        // console.log(this._reimlist);
        // console.log(s._all_ys);
      });
    },

    handleCurrentChange(val) {
      //点击下一页,{通用}
      this.tj_options.currentpage = val;
      this.tj_search();
    },
    handleSizeChange(val) {
      //改变页面大小{通用}
      this.tj_options.pagesize = val;
      this.tj_search();
    },

    sortChange(column, prop, order) {
      //排序{通用}
      this.tj_options.orderby.ordername = column.prop;
      let fx = "";
      if (column.order == "descending") {
        //主要与服务器的SQL语句兼容
        fx = "desc";
      } else {
        fx = "asc";
      }
      this.tj_options.orderby.orderfx = fx;
      this.tj_search();
      //console.log(column.prop); //prop标签 => nickname
      //console.log(column.order);//descending降序、ascending升序
    },
    on_click_handleCurrentChange(currentrow, oldrow) {
      this.$refs.multitable.toggleRowSelection(currentrow); //这行加上直接实现勾选
      //单击一行后
      //console.log(currentrow)
      if (currentrow == null) return;
      this.mxdata.splice(0, this.mxdata.length); //清空表
      let _show = this._mxdata.filter(
        (s) => s.reimid == currentrow.reim_id && s.node_type != "key"
      );
      let _noshow = this._mxdata.filter(
        (s) => s.reimid == currentrow.reim_id && s.node_type == "key"
      ); //key为关键步骤,流程初始化生成的
      this.mxdata.push(..._show);
      this.step_items = _noshow;
      this._active = _noshow.findIndex(
        (t) => t.stepname == currentrow.stepname
      ); //返回查询项所在的位置
      //console.log(_noshow)
    },

    look_reimitem(index, row) {
      console.log(row);

      //console.log(this.$refs.ys_analyse_detail.ys_data)
      //this.$refs.ys_analyse_detail.sj_data.splice(0,this.$refs.ys_analyse_detail.sj_data.length);
      this.reimlist.splice(0, this.reimlist.length); //清空表
      this.reimlist = this._reimlist.filter((t) => t.reim_id == row.reim_id);
      this._dialogV = true;
      // /console.log(this.$refs.ys_analyse_detail.ys_data)

      //if (this.$refs.ys_analyse_detail.hasOwnProperty('ys_data')){
      this.$nextTick(() => {
        this.$refs.ys_analyse_detail.ys_data.splice(
          0,
          this.$refs.ys_analyse_detail.ys_data.length
        );
        this.$refs.ys_analyse_detail.sj_data.splice(
          0,
          this.$refs.ys_analyse_detail.sj_data.length
        );

        if (row.typeid == 1) {
          this.feiy = true;
          this.cnf=false;
          let params = {
            page: 1, //分页页数(可不填)
            rows: 30, //分页大小(可不填)
            sort: "排序字段", //可不填
            order: "desc/asc", //可不填
            wheres: [{ name: "reim_id", value: row.reim_id }], // 查询条件(可不填)
          };
          this.$store.getters.data().curr_reimid = row.reim_id;
          this.$refs.view_reim_form.$refs.tableList.load(params, true); //查询审核页的业务数据
          for (var item in this.$refs.view_reim_form.$refs.myform.formFields) {
            this.$refs.view_reim_form.$refs.myform.formFields[item] =
              row[item]; //给界面赋值
            //console.log(item)
          }
        } else {
          this.feiy = false;
          this.cnf = true;
          let params = {
            page: 1, //分页页数(可不填)
            rows: 30, //分页大小(可不填)
            sort: "排序字段", //可不填
            order: "desc/asc", //可不填
            wheres: [{ name: "reim_id", value: row.reim_id }], // 查询条件(可不填)
          };
          this.$store.getters.data().curr_reimid = row.reim_id;
          this.$refs.view_reim_form_cc.$refs.tableList.load(params, true); //查询审核页的业务数据
          for (var item in this.$refs.view_reim_form_cc.$refs.myform
            .formFields) {
            this.$refs.view_reim_form_cc.$refs.myform.formFields[item] =
              row[item]; //给界面赋值
            //console.log(item)
          }
        }
      });
      //}
    },
    downfile(dbfilename) {
      let url = this.http.ipAddress + dbfilename;
      window.open(url);
    },
    cellstyle({ row, column, rowIndex, columnIndex }) {
      if (columnIndex == 0) {
        //待审核
        if (this._sqj.findIndex((s) => s.reimid == row.reim_id) != -1) {
          return "background:orange";
        }
      }

      //console.log(column)
      //
    },
    is_shen(_id) {
      //判断是否显示审核按钮
      return (
        this._sqj.findIndex((s) => s.reimid == _id) != -1 &&
        this.$route.name == "reim_all_lookup"
      );
    },
    shenh(_id) {
      this.$nextTick(() => {
        this._cur_shen_id = _id;

        this.$store.getters.data().curr_yjbm = "同意解除超预算额度提报限制。";
        this._vshen = true;
        //this.$refs.reimfloat_form1.$refs.myform.formFields.reyijian="同意解除超预算额度提报限制。"
        //console.log(this.$refs.reimfloat_form1)
      });
    },
    s_shen_sever() {
      //console.log(this._sqj);

      var postdata = this.$refs.reimfloat_form1.$refs.myform.formFields;
      //console.log(postdata);
      let fk_reimfloat_id = this._sqj.find(
        (s) => s.reimid == this._cur_shen_id
      ).fk_reimfloat_id;
      let url = "/api/Fk_reimlist/shenh?reimfloat_id=" + fk_reimfloat_id;
      http.post(url, postdata, true).then((s) => {
        this._vshen = false;
        this.tj_search();
      });
    },
    f_print(ls_type) {
      if (this._curr_muti_select.length == 0) {
        this.$message.error("需要打印的行请勾选");
        return;
      }
      this.$store.getters.data()._curr_muti_select = {
        muti_select: this._curr_muti_select,
        ls_type: ls_type,
      };
      this._v_print = true;
      this.$nextTick(() => {
        //子组件调用是需要$nextTick
        //console.log()
        this.$refs.pre_print1.tj_search();
      });
      //console.log(this.$refs);
      //console.log(this.$store.getters.data()._curr_muti_select);
    },
    handleSelectionChange(selection) {
      this._curr_muti_select = selection;
      //console.log(selection);
    },
  },
};
</script>
<style scoped>
.dialog-footer button:first-child {
  margin-right: 10px;
}
</style>
<style>
.el-row {
  margin-bottom: 20px;
}
.el-row:last-child {
  margin-bottom: 0;
}
.el-col {
  border-radius: 0px;
}
.bg-purple-dark {
  background: #99a9bf;
}
.bg-purple {
  background: #d3dce6;
}
.bg-purple-light {
  background: #e5e9f2;
}
.grid-content {
  border-radius: 0px;
  min-height: 36px;
}
.row-bg {
  padding: 10px 0;
  background-color: #f9fafc;
}
.example-showcase .el-dropdown + .el-dropdown {
  margin-left: 15px;
}
.example-showcase .el-dropdown-link {
  cursor: pointer;
  color: var(--el-color-primary);
  display: flex;
  align-items: center;
}
</style>

2、上传文件大小修改 改3处地方

后台的  
1、Program.cs中serverOptions.Limits.MaxRequestBodySize = int.MaxValue;  
2、Startup.cs中函数ConfigureServices  
 services.Configure<FormOptions>(options =>
            {
                options.MultipartBodyLengthLimit = long.MaxValue;//上次文件大小
            });

3、前端的  基础组件VolUpload.vue
 maxSize: {
      //文件限制大小3M
      type: Number,
      default: 1000,
    },

3、WebResponseContent、 async、await的使用,调用百度api详见查询: vatInvoice

//后端
public async Task<IActionResult> vatInvoice(string fileNamePath)    
      { 
        WebResponseContent _webResponse = new WebResponseContent();         
          if (fileNamePath.IsNullOrEmpty() || fileNamePath=="null") 
             {_webResponse.Status = false; 
               return Json(_webResponse.Error("请先上传增值税发票")); 
              } 
          _webResponse.Data = new { fpstr=(await baiduAi_tools.vatInvoice_async(fileNamePath)).ToString() };//注意data是个结构 
      _webResponse.Status = true;//默认是false
      _webResponse.Message = "OCR成功"; 
       Console.WriteLine(_webResponse.Data); 
       return Json(_webResponse);          }
//前端
 await  this.http.get("/api/customer/vatInvoice?fileNamePath="+row.fujian).then(x => {
      console.log(x)
      if (x.status){//
          this.$message.success(x.message);              
      }else{//失败
          this.$message.error(x.message);
          //this.openid=x.message;
      }
  });

4、文件流下载方式 前后端

//后端
/// <summary>
        /// 导出Excel,使用文件流下载文件
        /// </summary>
        /// <returns></returns>
        [HttpPost, Route("customer_export"), AllowAnonymous]
        public ActionResult customer_export()
        {
            string path = null;
            string fileName = null;

            ICacheService cacheService = AutofacContainerModule.GetService<ICacheService>();
            List<Yus_Analyse> dataList = cacheService.Get<List<Yus_Analyse>>(UserContext.Current.UserId.ToString());//从内存中取出导出数据
    
            fileName = "temp.xlsx";//随便给
            path = CustomHelper.ExportGeneralExcel(dataList, fileName);

            return File(sysio.File.ReadAllBytes(path), sysnet.Mime.MediaTypeNames.Application.Octet, fileName);
        }

//前端,注意移动端uniapp里面没有文件流的参数,BLOB
 downfile(){//使用文件流下载文件
      let url=this.http.ipAddress +
            "api/Fk_yusuan/customer_export";
      http.post(url, {}, "正在导出数据....", { responseType: "blob" }).then((content) => {
		  const blob = new Blob([content]);
          if ("download" in document.createElement("a")) {
            // 非IE下载
            const elink = document.createElement("a");
            elink.download = "导出文件.xlsx";
            elink.style.display = "none";
            elink.href = URL.createObjectURL(blob);
            document.body.appendChild(elink);
            elink.click();
            URL.revokeObjectURL(elink.href);
            document.body.removeChild(elink);
          }else{
            navigator.msSaveBlob(blob, "导出文件非IE.xlsx");
          }
      })
    },

5、Controller后端模板API,前端模板(包括状态返回),控制器模板 、api模板,标准api、api模版 api标准

//后台
  [HttpPost, Route("insert_from_gf_template")]
        public WebResponseContent insert_from_gf_template(Guid ft_id,Guid luxs_rfq_id)
        {
             WebResponseContent _webResponse = new WebResponseContent();
            Luxs_view_project_for_mara luxs_View_Project_For_Mara= new Luxs_view_project_for_mara();
            var pro_co = DBServerProvider.DbContext.Set<Luxs_view_project_for_mara>().ToList().Where(w => w.mara_id == part_id).Count();
            var js_co = DBServerProvider.DbContext.Set<Luxs_js_for_mara>().ToList().Where(w => w.mara_id == part_id).Count();
            if (pro_co <= 0)
            {
                return webResponse.Error("请给物料绑定项目!");
            }
            if (js_co <= 0)
            {
                return webResponse.Error("请给物料绑定技术图纸!");
            }

            List<gf_document_template_detail> _Fl_template_list = DBServerProvider.DbContext.Set<gf_document_template_detail>().Where(w => w.gf_document_template_id == ft_id).ToList();
            for (int i = 0; i <= _Fl_template_list.Count - 1; i++)
            {
                luxs_gongf_doc luxs_Gongf_Doc = new luxs_gongf_doc();
                luxs_Gongf_Doc.luxs_rfq_id = luxs_rfq_id;
                luxs_Gongf_Doc.docname = _Fl_template_list[i].doc_name;
                luxs_Gongf_Doc.roles = _Fl_template_list[i].roles;
                DBServerProvider.DbContext.Update(luxs_Gongf_Doc);

            }
            DBServerProvider.DbContext.SaveChanges();
                return _webResponse.OK("导入成功!");
        }
//前台
 var turl = "/api/Ls_pro_npqr_head/import_npqr?ls_pro_npqr_head_id=" + this.$store.getters.data().ls_pro_npqr_head_id+"&fujian="+fujian;
      this.http.post(turl, {}, true).then((s) => {
        if (s.status){
          this.$message.success(s.message)
        }else{
          this.$message.error(s.message);
        }
      });
import { ElMessage, ElMessageBox } from 'element-plus';
this.http.post("/api/Erp_mara/PublishPart?part_id=" + row[0].mara_id).then(x => {
                if (x.status) {
                  ElMessage({
                    type: 'success',
                    message: x.message,
                  });
                  this.refresh();
                } else {
                  ElMessage({
                    type: 'error',
                    message: x.message,
                  });
                }
              })
            })

//传对象给API
//后台
private WebResponseContent webResponse = new WebResponseContent();
        [HttpPost, Route("New_rfq")]//,AllowAnonymous
        public WebResponseContent New_rfq([FromBody] Luxs_rfq formFields, string part_nos, string supper_nos)
        {
  			return webResponse.OK("成功生成"+ b_co + "个RFQ!");
        }
//前端:
formFields: {
        ishiddencust:1,
        ishiddenpname:0,
        sqe:"",
        engineer:""
      },

 let turl = "/api/Luxs_rfq/New_rfq?part_nos="+mara_nos+"&supper_nos="+supper_nos;
      
      for (const key in this.formFields) {//传对象给API服务器端,数据类型必须与服务器端的对象完全一致,对于selectList数组要转换为字符串
          if (this.formFields[key] instanceof Array) {
              this.formFields[key] = this.formFields[key].join(',');
          }
      }
      
      this.http.post(turl, this.formFields, true).then((s) => {
        this.$emit("parentCall", ($vue) => {
          //console.log($vue);
          $vue.refresh();
        });
        this.$message.success(s.message);
      });

//后台 传数据给前端 map
[HttpPost, Route("get_data_for_ddx")]//传数据给前端 map 
        public dynamic get_data_for_ddx(string rfq_id)
        {
            Hashtable rtn_hst = new Hashtable(); 
            var all_project = DBServerProvider.DbContext.Set<Luxs_view_project_for_mara>().Where(w => w.mara_id == mara.mara_id).AsEnumerable();//.Select((pname, luxs_project_id)=>new {pname,luxs_project_id});
            var Luxs_user_for_project_list = DBServerProvider.DbContext.Set<Luxs_user_for_project>().Where(w => (all_project.First().luxs_project_id.ToString().ToGuid() == w.luxs_project_id));//随便取一个项目的用量
            
            rtn_hst.Add("all_project", all_project);
            rtn_hst.Add("project_usage", Luxs_user_for_project_list);
            return rtn_hst;
        }
//前台
  var turl = "/api/Erp_supper/get_data_for_ddx?rfq_id=" + this.$store.getters.data().ddrow.luxs_rfq_id;
        this.http.post(turl, {}, true).then((s) => {
          //console.log(s.all_project);
          //console.log(s.luxs_project);
          this.$refs.ddx_form.luxs_plan_for_project_list = s.all_project;//项目计划
          this.$refs.ddx_form.project_usage_array = s.project_usage;//项目用量
          this.$refs.ddx.$refs.grid.refresh()
        });

6、后端传extra信息给前端 , 额外信息,

//后端代码:在控制器中
public override PageGridData<Ls_pdb> GetPageData(PageDataOptions options)
        {
            Hashtable rtn_hst = new Hashtable();
            var rtn = base.GetPageData(options);
            Sys_User ?sys_User = DBServerProvider.DbContext.Set<Sys_User>().Where(w => w.User_Id == UserContext.Current.UserId).FirstOrDefault();
            rtn_hst.Add("pdb_auth", sys_User?.pdb_auth);
            rtn.extra = rtn_hst;
            return rtn;
        }
//前端代码:在searchAfter中接收
searchAfter(result,all) {
      //查询后,result返回的查询数据,可以在显示到表格前处理表格的
      console.log(all);
      return true;
    },

7、新增viewgrid权限只读权限控制,从后端读取权限给前端做只读控制

//前端:在modelOpenAfter、searchAfter中都需要增加,
import abc from "@/uitils/h_common.js";//这是默认的写法
searchAfter(result) {
      //查询后,result返回的查询数据,可以在显示到表格前处理表格的值
     if (this.$route.path!="/Luxs_project"){
        abc.read_only(this);
      }
      return true;
},
modelOpenAfter(row) {
     if (this.$route.path!="/Luxs_project"){
        abc.read_only(this);
      }
}
onInit() {
	  if (this.$route.path=="/Luxs_rfq"){
          abc.read_only(this);
        }
}

//h_common.js中内容
this.buttons.forEach(x => {//隐藏编辑按钮
             if (x.value == "Update" || x.value == 'Add' || x.value == "Import"|| x.value == "Export" || x.value == "Delete" || x.value == "Update"|| x.value == "Audit"||x.name=="高级查询"||x.value =="Search") {
        x.hidden = true;
      }});

this.singleSearch = null;//快捷查询
this.boxButtons.forEach(x => {//弹出窗体按钮隐藏
      if (x.value == "save") {
        x.hidden = true;
      }
    });

 this.editFormOptions.forEach((x) => {//编辑字段只读
      x.forEach((item) => {
        item.readonly = true;  
      })});

  this.columns.forEach((x) => {//表格隐藏字段     
          if (x.field.indexOf("price")>=0){
            table.editFormFields[x.field]="***";
            x.formatter=(row)=>{
              return "***";
            }
          }
    });


//后端:xxxService.cs  注意参数为options都是xxxService.cs
public override PageGridData<Ls_pdb> GetPageData(PageDataOptions options)
  {
      Hashtable rtn_hst = new Hashtable();
      var rtn = base.GetPageData(options);
      Sys_User ?sys_User = DBServerProvider.DbContext.Set<Sys_User>().Where(w => w.User_Id == UserContext.Current.UserId).FirstOrDefault();
      if (sys_User.UserName.Contains("admin"))
      {
          rtn_hst.Add("pdb_auth", "write");
      }
      else
      {
          rtn_hst.Add("pdb_auth", sys_User?.pdb_auth);
      }
      
      rtn.extra = rtn_hst;
      return rtn;
  }

8、viewgrid动态增加显示字段 动态列

//1、服务器端service.cs需要在over GetPageData方法,把数据放在extra中传到前端;
//2、前端在searchAfter重新组合,rows,在onInited中增加需要关联的字段;
//后台
public override PageGridData<Luxs_js_for_mara> GetPageData(PageDataOptions options)
  {
      var list = repository.DapperContext.QueryList<dynamic>("select *,(select count(*) from luxs_js_for_mara_his b where a.luxs_js_for_mara_id=b.luxs_js_for_mara_id and a.js_detail=b.js_detail) his_co from luxs_js_for_mara a", null);
      var rtn = base.GetPageData(options);
      List<Guid> idnrk_array = rtn.rows.Select(s => s.luxs_js_for_mara_id).ToList();
      var extra = list.Where(w => idnrk_array.Contains(w.luxs_js_for_mara_id));//后台过滤一次,减少前端的压力
      rtn.extra = extra;
      rtn.extra = list;
      return rtn;
  }
//前端
searchAfter(rows,result) {
      //查询后,result返回的查询数据,可以在显示到表格前处理表格的值
      let _rows = rows.map(a => {//组合新的数组(相当于SQL中的select)
        return {
          ...result.extra.find(item => {//...相当于增加select显示字段
            return (item.luxs_js_for_mara_id === a.luxs_js_for_mara_id)//相当于where条件
          })
        }
      });
      rows.splice(0, rows.length);//数组清空
      rows.push(..._rows);//数组重新赋值
      return true;
    },
// 前端增加列
onInited() {
      //框架初始化配置后
      //如果要配置明细表,在此方法操作
      //this.detailOptions.columns.forEach(column=>{ });
      this.columns.splice(2, 0, { field: "his_co", title: '个数',width: 200,});
    },

9、c# 自定义抛出异常问题,throw 项上一层调用的程序抛出异常,错误抛出

//1、正常抛出异常
try{
      Ls_zongcen_npqr _Ls_zongcen_npqr = new Ls_zongcen_npqr();
      head++;
  }
  catch (Exception e)
  {
      throw new Exception("请检查导入模板第" + (i + 2).ToString() + "行" + (j + 1).ToString() + "列数据的格式,数据值为【" + list[i][j] + "】的正确性!");
  }


//2、try中主动抛出异常
  try{
      Ls_zongcen_npqr _Ls_zongcen_npqr = new Ls_zongcen_npqr();
      head++;
      if(1==0){//主动抛出异常
         throw new Exception("1!=0");
      }
  }
  catch (Exception e)
  {
        if (ex.Message =="1!=0"){//主动捕获异常到前端
          throw new Exception(ex.Message);
        }
        else {
          throw new Exception("请检查导入模板第" + (i + 2).ToString() + "行" + (j + 1).ToString() + "列数据的格式,数据值为【" + list[i][j] + "】的正确性!");
        }
  }

10、多个对象数组上传到服务器 FromBody 是数组对象

//后端
[HttpPost, Route("save_data_for_ddx")]
        public dynamic save_data_for_ddx([FromBody] dynamic qian)
        {
            Hashtable rtn_hst = new Hashtable();
            ddx ddxxx = new ddx();          
            ddxxx=JsonConvert.DeserializeObject<ddx>(JsonConvert.SerializeObject(qian.ddx));//先转为字符串再转为对象
            List<Luxs_user_for_project> luxs_User_For_Project_list= JsonConvert.DeserializeObject<List<Luxs_user_for_project>>(JsonConvert.SerializeObject(qian.project_usage_array));//对象数组
            var del_Luxs_user_for_project= DBServerProvider.DbContext.Set<Luxs_user_for_project>().Where(w => w.luxs_project_id == luxs_User_For_Project_list.First().luxs_project_id).ToList();//删除不在清单中的数据
            DBServerProvider.DbContext.RemoveRange(del_Luxs_user_for_project);
            DBServerProvider.DbContext.SaveChanges();
            foreach (var item in luxs_User_For_Project_list)
            {
                DBServerProvider.DbContext.Add(item);
            }
            DBServerProvider.DbContext.SaveChanges();
                        var del_ddx = DBServerProvider.DbContext.Set<ddx>().Where(w => w.luxs_rfq_id == ddxxx.luxs_rfq_id && w.luxs_project_id == ddxxx.luxs_project_id);
            DBServerProvider.DbContext.RemoveRange(del_ddx);
            DBServerProvider.DbContext.Add(ddxxx);
            DBServerProvider.DbContext.SaveChanges();
            // Console.WriteLine(ddxxx);
            return rtn_hst;
        }

//前端
var turl = "/api/Erp_supper/save_data_for_ddx?rfq_id="+this.$store.getters.data().ddrow.luxs_rfq_id;
  let qian={//多个对象数组
          ddx:this.$refs.ddx_form.ddx,
          project_usage_array:this.$refs.ddx_form.project_usage_array,
        }
this.http.post(turl, qian, true).then((s) => {
    });

11、通用审批流程增加步骤

流程通用性同步服用过程:
通用流程相关表fk_reimfloat、fk_reimfloat_his、fk_reimhandle、fk_reimtask、fl_template、fl_template_list
0、在业务表中增加字段stepid(int)步骤状态
1、在float中界面中编辑流程;
	主要是维护各个流程节点的path_router
2、复制D:\DotNetProjects\ls-projects\SQMS\.Net6版本\VOL.Core\Float\FloatManager.cs中的专用流程管理
	(根据不同的业务流程,需要编写不同流程newtask逻辑);
3、controll提交/回退逻辑;提交后端逻辑:Audit_tj(需要针对不同的业务重写)
//提交回退后台
	 [HttpPost, Route("Audit_tj")]//,AllowAnonymous
        public WebResponseContent Audit_tj(string reimids, string pdata)
        {
            WebResponseContent webResponse = new WebResponseContent();
            string[] ls_id = reimids.Split(',');
            string[] hq_tousername_array = hq_tousername.Split(',');//汇签人员名单
            List<Guid> ls_list = new List<Guid>();
            foreach (var item in ls_id)
            {
                ls_list.Add((Guid)item.ToGuid());
            }
            
            var dynamic_option_list = DBServerProvider.DbContext.Set<dynamic_option>().Where(s => ls_list.Contains(s.dynamic_option_id)).ToList();
            dynamic queryJson = JsonConvert.DeserializeObject(pdata);//接受前端提交的表单
            var user = UserContext.Current.GetUserInfo(UserContext.Current.UserId);
            foreach (var item in dynamic_option_list)
            {
               if (hq_tousername_array.Length > 0)
                {
                    for (int i = 0; i < hq_tousername_array.Length; i++)
                    {
                        FloatManager.add_task(item, (int)item.stepid, hq_tousername_array[i], "汇签");
                    }
                    
                }
                if (queryJson.reyijian.ToString() == "回退" )//回退到上一步骤
                {
                    int pre_stepid = FloatManager.get_pre_step((int)item.stepid, item.dynamic_option_id);
                    FloatManager.Audit_To_Stepid(item, queryJson.remark.ToString(), pre_stepid);
                }else if(queryJson.reyijian.ToString() == "不同意")//回退到起始步骤
                {
                    FloatManager.Audit_To_Stepid(item, queryJson.remark.ToString(), FloatManager.GetFirstStepid(item));
                }
                else
                {
                    FloatManager.Audit(item, queryJson.reyijian.ToString(), queryJson.remark.ToString());//正常审核提交
                }
            };
            return webResponse.OK("操作成功!");
        }
  
   注意:提交就是把自己的任务结束,是否到下一环节需要判断本节点的所有任务全部结束会自动流转到下一步骤
  //提交回退前台,js端,调用通用功能
  import pop_common from '@/extension/system/float/pop_common.vue'//
	this.buttons.push({  //也可以用push或者splice方法来修改buttons数组
          name: '审核提交', //按钮名称
          value:"submit",
          icon: 'el-icon-check', //按钮图标vue2版本见iview文档icon,vue3版本见element ui文档icon(注意不是element puls文档)
          type: 'primary', //按钮样式vue2版本见iview文档button,vue3版本见element ui文档button
          onClick: function () {
              let rows=this.getSelectRows();
              if (rows.length==0){
                this.$message.error("请选择行!");
                return;
              }
              let ss=[];
               rows.forEach(e=>{
                        ss.push(e.dynamic_option_id);//
                      })
            //console.log(this);
            this.$store.getters.data().ss_string=ss.join(",")//业务数据id
            this.$store.getters.data().refresh_table=this;
            this.$refs.gridBody.open_pop_submit_show();
          }
        });

 提交/回退前端逻辑:src\extension\system\float\pop_submit.vue
4、业务表中service中增加数据显示逻辑;启动流程、显示流程数据
      //启动流程
	     public override WebResponseContent Add(SaveModel saveDataModel)
        {
            //dynamic_option mainEntity = saveDataModel.MainData.DicToEntity<dynamic_option>();
            WebResponseContent webResponse = new WebResponseContent();
            AddOnExecuted = (dynamic_option currList, object list)=>
            {
                FloatManager.AddProcese(currList);//启动一个新审批流程
                return webResponse.OK();
            };
            return base.Add(saveDataModel);
        }
        //显示流程数据
        public override PageGridData<dynamic_option> GetPageData(PageDataOptions options)
        {
            QueryRelativeExpression = (IQueryable<dynamic_option> queryable) =>
            {
                //1、自己创建或者超级管理都能全程看到
                //2、有没有处理过的task任务

                var user = UserContext.Current.GetUserInfo(UserContext.Current.UserId);

                var doctask = DBServerProvider.DbContext.Set<fk_reimtask>();
                queryable = queryable.Where(x => //user.Role_Id == 1 || user.Role_Id == 2 || 
                                              (
                                            (doctask.Any(t => t.to_username == user.UserName && t.stepid == x.stepid && t.ishandled != 1 && t.reimid == x.dynamic_option_id)//在task中任务存在(判断唯一提交任务的条件)
                                            )));

                return queryable;
            };
            return base.GetPageData(options);
        }

5、Home中增加待处理流程(基本不用修改)
注意:fk_reimfloat_his跟fk_reimtask功能基本相同,做是用于前台的我的历史查看作用

12、前后台发布在一起,屏蔽swagger

将前端发布后的dist文件里面的内容(只要复制dist里面的内容)直接放到wwwroot文件下即可(前端http.js里面的ipaddress值改为/)
//axios.defaults.baseURL = '/';//前后端发布在一个目录中
注意:如果是vue3版本请将Startup.cs中的app.UseDefaultFiles();注释掉
屏蔽swagger页面

13、vscode加载 Web视图时出错: Error: Could not register service workers: InvalidStateError: Failed to register a ServiceWorker: The document is in an invalid state

解决:
1、关闭所有的vscode
2、按WIN + R,输入cmd,打开终端,然后输入命令
code --no-sandbox
3、会重启vscode,就可以正常使用了。

14、树形表格配置、树形table、treetable配置 增删查

需要三个步骤:1、后台controller、2、后台Service、3、前端vue
1、后台controller
public partial class Sys_DepartmentController
    {
        private readonly ISys_DepartmentService _service;//访问业务代码
        private readonly ISys_DepartmentRepository _repository;
        private readonly IHttpContextAccessor _httpContextAccessor;

        [ActivatorUtilitiesConstructor]
        public Sys_DepartmentController(
            ISys_DepartmentService service,
             ISys_DepartmentRepository repository,
            IHttpContextAccessor httpContextAccessor
        )
        : base(service)
        {
            _service = service;
            _repository = repository;
            _httpContextAccessor = httpContextAccessor;
        }


        /// <summary>
        /// treetable 获取子节点数据(2021.05.02)
        /// </summary>
        /// <param name="loadData"></param>
        /// <returns></returns>
        [ApiActionPermission(ActionPermissionOptions.Search)]
        [HttpPost, Route("GetPageData")]
        public override ActionResult GetPageData([FromBody] PageDataOptions loadData)
        {

            if (loadData.Value.GetInt() == 1)
            {
                return GetTreeTableRootData(loadData).Result;
            }
            return base.GetPageData(loadData);
        }

        /// <summary>
        /// treetable 获取子节点数据
        /// </summary>
        /// <returns></returns>
        [HttpPost, Route("getTreeTableRootData")]
        [ApiActionPermission(ActionPermissionOptions.Search)]
        public async Task<ActionResult> GetTreeTableRootData([FromBody] PageDataOptions options)
        {
            //页面加载根节点数据条件x => x.ParentId == 0,自己根据需要设置
            var query = _repository.FindAsIQueryable(x => true);
            if (UserContext.Current.IsSuperAdmin)
            {
                query = query.Where(x => x.ParentId == null);
            }
            else
            {
                var deptIds = UserContext.Current.DeptIds;
                query = query.Where(x => deptIds.Contains(x.DepartmentId));
            }
            var queryChild = _repository.FindAsIQueryable(x => true);
            var rows = await query.TakeOrderByPage(options.Page, options.Rows)
                .OrderBy(x => x.DepartmentName).Select(s => new
                {
                    s.DepartmentId,
                    s.ParentId,
                    s.DepartmentName,
                    s.DepartmentCode,
                    s.Enable,
                    s.Remark,
                    s.CreateDate,
                    s.Creator,
                    s.Modifier,
                    s.ModifyDate,
                    hasChildren = queryChild.Any(x => x.ParentId == s.DepartmentId)
                }).ToListAsync();
            return JsonNormal(new { total = await query.CountAsync(), rows });
        }

        /// <summary>
        ///treetable 获取子节点数据
        /// </summary>
        /// <returns></returns>
        [HttpPost, Route("getTreeTableChildrenData")]
        [ApiActionPermission(ActionPermissionOptions.Search)]
        public async Task<ActionResult> GetTreeTableChildrenData(Guid departmentId)
        {
            //点击节点时,加载子节点数据
            var query = _repository.FindAsIQueryable(x => true);
            var rows = await query.Where(x => x.ParentId == departmentId)
                .Select(s => new
                {
                    s.DepartmentId,
                    s.ParentId,
                    s.DepartmentName,
                    s.DepartmentCode,
                    s.Enable,
                    s.Remark,
                    s.CreateDate,
                    s.Creator,
                    s.Modifier,
                    s.ModifyDate,
                    hasChildren = query.Any(x => x.ParentId == s.DepartmentId)
                }).ToListAsync();
            return JsonNormal(new { rows });
        }
    }
2、后台Service
public partial class Sys_DepartmentService
    {
        private readonly IHttpContextAccessor _httpContextAccessor;
        private readonly ISys_DepartmentRepository _repository;//访问数据库

        [ActivatorUtilitiesConstructor]
        public Sys_DepartmentService(
            ISys_DepartmentRepository dbRepository,
            IHttpContextAccessor httpContextAccessor
            )
        : base(dbRepository)
        {
            _httpContextAccessor = httpContextAccessor;
            _repository = dbRepository;
            //多租户会用到这init代码,其他情况可以不用
            //base.Init(dbRepository);
        }

        public override PageGridData<Sys_Department> GetPageData(PageDataOptions options)
        {
            FilterData();
            return base.GetPageData(options);
        }

        private void FilterData()
        {
            //限制 只能看自己部门及下级组织的数据
            QueryRelativeExpression = (IQueryable<Sys_Department> queryable) =>
            {
                if (UserContext.Current.IsSuperAdmin)
                {
                    return queryable;
                }
                var deptIds = UserContext.Current.GetAllChildrenDeptIds();
                return queryable.Where(x => deptIds.Contains(x.DepartmentId));
            };
        }
        public override WebResponseContent Export(PageDataOptions pageData)
        {
            FilterData();
            return base.Export(pageData);
        }

        WebResponseContent webResponse = new WebResponseContent();
        public override WebResponseContent Add(SaveModel saveDataModel)
        {
            AddOnExecuting = (Sys_Department dept, object list) =>
            {
                return webResponse.OK();
            };
            return base.Add(saveDataModel);
        }
        public override WebResponseContent Update(SaveModel saveModel)
        {
            UpdateOnExecuting = (Sys_Department dept, object addList, object updateList, List<object> delKeys) =>
            {
                if (_repository.Exists(x => x.DepartmentId == dept.ParentId && x.DepartmentId == dept.DepartmentId))
                {
                    return webResponse.Error("上级组织不能选择自己");
                }
                if (_repository.Exists(x => x.ParentId == dept.DepartmentId) && _repository.Exists(x => x.DepartmentId == dept.ParentId))
                {
                    return webResponse.Error("不能选择此上级组织");
                }
                return webResponse.OK();
            };
            return base.Update(saveModel);
        }
    }
3、前台vue
/*****************************************************************************************
**  Author:jxx 2022
**  QQ:283591387
**完整文档见:http://v2.volcore.xyz/document/api 【代码生成页面ViewGrid】
**常用示例见:http://v2.volcore.xyz/document/vueDev
**后台操作见:http://v2.volcore.xyz/document/netCoreDev
*****************************************************************************************/
//此js文件是用来自定义扩展业务代码,可以扩展一些自定义页面或者重新配置生成的代码

let extension = {
  components: {
    //查询界面扩展组件
    gridHeader: '',
    gridBody: '',
    gridFooter: '',
    //新建、编辑弹出框扩展组件
    modelHeader: '',
    modelBody: '',
    modelFooter: ''
  },
  tableAction: '', //指定某张表的权限(这里填写表名,默认不用填写)
  buttons: { view: [], box: [], detail: [] }, //扩展的按钮
  methods: {
    //下面这些方法可以保留也可以删除
    onInit() {  //框架初始化配置前,
      this.rowKey = "DepartmentId";
    },
    loadTreeChildren(tree, treeNode, resolve) { //加载子节点
      let url = `api/Sys_Department/getTreeTableChildrenData?departmentId=${tree.DepartmentId}`;
      this.http.post(url, {}).then(result => {
        resolve(result.rows)
      })
    },
    /***加载后台数据见Sys_RoleController.cs文件***/
    searchBefore(params) {//判断加载根节点或子节点
      //没有查询条件,默认查询返回所有根节点数据
      if (!params.wheres.length) {
        params.value = 1;
      }
      return true;
    },
    onInited() {
      let hasUpdate, hasDel, hasAdd;
      this.buttons.forEach((x) => {
        if (x.value == 'Update') {
          x.hidden = true;
          hasUpdate = true;
        } else if (x.value == 'Delete') {
          hasDel = true;
          x.hidden = true;//隐藏按钮
        }
        else if (x.value == 'Add') {
          x.type="primary";
          hasAdd = true;
        }
      });
      if (!(hasUpdate || hasDel || hasAdd)) {
        return;
      }
      this.columns.push({
        title: '操作',
        field: '操作',
        width: 80,
        fixed: 'right',
        align: 'center',
        render: (h, { row, column, index }) => {
          return (
            <div>
              <el-button
                onClick={($e) => {
                  this.addBtnClick(row)
                }}
                type="primary"
                link
                v-show={hasAdd}
                icon="Plus"
              >
              </el-button>
              <el-button
                onClick={($e) => {
                  this.edit(row);
                }}
                type="success"
                link
                v-show={hasUpdate}
                icon="Edit"
              >
              </el-button>
              <el-tooltip
                class="box-item"
                effect="dark"
                content="删除"
                placement="top"
              >
                <el-button
                  link
                  onClick={($e) => {
                    this.del(row);
                  }}
                  v-show={hasDel}
                  type="danger"
                  icon="Delete"
                >
                </el-button>
              </el-tooltip>
            </div>
          );
        }
      });
    },
    addBtnClick(row) {
      //这里是动态addCurrnetRow属性记录当前点击的行数据,下面modelOpenAfter设置默认值
      this.addCurrnetRow = row;
      this.add();
    },
    addAfter() {//添加后刷新字典
      this.initDicKeys();
      return true;
    },
    updateAfter() {
      this.initDicKeys();
      return true;
    },
    delAfter(result) {//查询界面的表删除后
      this.initDicKeys();
      return true;
    },
    modelOpenAfter(row) {
      //点击行上的添加按钮事件
      if (this.addCurrnetRow) {

        //获取当前组织构架的所有父级id,用于设置新建时父级id的默认值

        //获取数据数据源
        let data = [];
        this.editFormOptions.forEach(options => {
          options.forEach(option => {
            if (option.field == 'ParentId') {
              data = option.orginData;
            }
          })
        })
        let parentIds = this.base.getTreeAllParent(this.addCurrnetRow.DepartmentId, data).map(x => { return x.id });
        //设置编辑表单上级组织的默认值
        this.editFormFields.ParentId = parentIds;
        this.addCurrnetRow = null;

      }
    }
  }
};
export default extension;

15、

16、

Logo

低代码爱好者的网上家园

更多推荐