在前面随笔《基于SqlSugar的开发框架循序渐进介绍(12)– 拆分页面模块内容为组件,实现分而治之的处理》中我们已经介绍过,对于相关的业务表的界面代码,我们已经尽可能把不同的业务逻辑封装在不同的页面组件中,隔离变化的差异,因此界面组件化后,就可以利用代码生成工具进行统一的界面代码的生成了,而且由于变化的隔离处理,我们实际上维护的代码变得更加方便维护了。本篇随笔介绍在整合代码生成工具进行前端界面的生成的一些思路和实际的界面代码的生成。
1、页面的模块化处理
在前面随笔《基于SqlSugar的开发框架循序渐进介绍(12)– 拆分页面模块内容为组件,实现分而治之的处理》中我们已经介绍过,常规页面包含有列表界面,新增、编辑、查看、导入等界面,除了列表页面,其他内容以弹出层对话框的方式进行处理,如下界面示意图所示。
根据以上的页面划分,我们把一个页面分为search.vue、edit.vue、import.vue、view.vue、index.vue,其中index.vue为整合各个组件的主页面,在视图中如下所示。我们每个业务模块都是如此统一划分,因此比较统一,同时也是为后续的代码生成工具批量生成做好准备。
因此在index.vue页面中,我们整合了几个组件页面即可,如下所示。
<template> <div class="main"> <!--条件及列表展示--> <Search ref="searchRef" @show-import="showImport" @show-add="showAdd" @show-view="showView" @show-edit="showEdit" /> <!--查看详细组件界面--> <view-data ref="viewRef" /> <!--新增、编辑组件界面--> <edit-data ref="editRef" @submit="refreshData" /> <!--模板导入信息--> <import-data ref="importRef" @finish="finishImport" /> </div></template><script setup lang="ts">import { reactive, ref, onMounted } from 'vue';import Search from './search.vue';import ViewData from './view.vue';import EditData from './edit.vue';import ImportData from './import.vue';
1)查看视图页面
我们先以view.vue查看页面为例进行介绍,它是一个查看明细的界面,因此也是一个弹出对话框页面,我们把它的代码处理如下所示。
<template> <el-dialog v-if="isVisible" v-model="isVisible" title="查看信息" append-to-body @close="closeDialog"> <el-form ref="viewRef" :model="viewForm" label-width="100px"> <el-tabs type="border-card"> <el-tab-pane label="基本信息"> <el-descriptions title="" :column="2" border> <el-descriptions-item label="显示名称"> {{ viewForm.name }} </el-descriptions-item> <el-descriptions-item label="Web地址"> {{ viewForm.url }} </el-descriptions-item> <el-descriptions-item label="Web图标"> <!-- {{ viewForm.webIcon }} --> <icon :icon="viewForm.webIcon" /> </el-descriptions-item> <el-descriptions-item label="排序"> {{ viewForm.seq }} </el-descriptions-item> <el-descriptions-item label="可见"> <el-tag v-if="viewForm.visible" type="success" effect="dark">可见</el-tag> <el-tag v-else type="danger" effect="dark">隐藏</el-tag> </el-descriptions-item> <el-descriptions-item label="展开"> <el-tag v-if="viewForm.expand" type="success" effect="dark">展开</el-tag> <el-tag v-else type="" effect="dark">收缩</el-tag> </el-descriptions-item> <el-descriptions-item label="创建时间"> <el-date-picker v-model="viewForm.createTime" align="right" type="datetime" placeholder="选择日期" value-format="YYYY-MM-DD HH:mm" disabled /> </el-descriptions-item> <el-descriptions-item label="特殊标签"> {{ viewForm.tag }} </el-descriptions-item> </el-descriptions> </el-tab-pane> </el-tabs> </el-form> <template #footer> <span class="dialog-footer"> <el-button @click="closeDialog">关闭</el-button> </span> </template> </el-dialog></template>
其他的js代码采用tyepscript语法,我们把它放在
<script setup lang="ts">//逻辑代码</script>
然后我们在js代码中抛出show的方法,便于父组件的调用。
//显示窗口const show = (id: string | number) => { if (!$u.test.isNullOrUnDef(id)) { menu.Get(id).then((data) => { Object.assign(viewForm, data); isVisible.value = true; //显示对话框 }); }};//暴露组件属性和方法defineExpose({ show,});
同时在页面里面,也定义一个表单的对象引用,便于上面模板组件的显示。
const viewRef = ref(); //表单引用// 表单属性定义let viewForm = reactive({ iD: '', pid: '', name: '', icon: '', seq: '', functionId: '', visible: 0, expand: 0, winformType: '', url: '', webIcon: '', creator: '', createTime: '', tag: '',});
如果是查看详细的视图中有树形列表,我们还可以在onMounted的处理中,添加获取树列表的数据即可,如下代码所示。
//挂载的时候初始化数据onMounted(() => { // 设置默认值 getTree();});// 初始化树列表let treedata = ref([]);const getTree = () => { // 树列表数据获取 menu.GetAll().then((data) => { treedata.value = []; // 树列表清空 var list = data?.items; if (list) { var newTreedata = $u.util.getJsonTree(list, { id: 'id', pid: 'pid', label: 'name', children: 'children', }); treedata.value = newTreedata; } });};
而查看视图的触发,往往在列表的操作按钮中,或者双击表格行进行触发,界面如下所示。
界面如下代码所示。
而调用查看详细页面的事件,传递对应的id并调用组件实例抛出的方法即可。如下代码所示。
// 显示查看对话框处理const viewRef = ref<InstanceType<typeof ViewData>>();function showView(id) { viewRef.value.show(id);}
至此,一个独立的视图页面组件,以及如何触发调用就完成了,视图页面单独维护,便于代码的管理,同时也隔离了复杂的页面逻辑。
视图页面效果如下所示。
2)新增编辑视图页面
在常规的处理中,往往编辑和新增的界面是差不多的,差异不同的地方,我们可以通过条件 if 的方式进行处理即可。因此可以把两者放在一个组件中实现对话框内容和逻辑处理。
刚才我们提到了Index.vue页面,是对几个组件的统筹处理,如下代码所示。
<template> <div class="main"> <!--条件及列表展示--> <Search ref="searchRef" @show-import="showImport" @show-add="showAdd" @show-view="showView" @show-edit="showEdit" /> <!--查看详细组件界面--> <view-data ref="viewRef" /> <!--新增、编辑组件界面--> <edit-data ref="editRef" @submit="refreshData" /> <!--模板导入信息--> <import-data ref="importRef" @finish="finishImport" /> </div></template>
从上面代码我们看到,在HTML代码中,我们引入对应的组件,并在主查询页面中触发事件即可,如下所示。
其中showAdd和ShowEdit类似,都是调用编辑/新增的对话框,不同的是,通过传递id来辨别是否为新增,如果需要传入pid的父节点信息,那么我们也可以创建一个showAdd的方法。
//新增、编辑表单引用const editRef = ref<InstanceType<typeof EditData>>();//显示新增对话框function showAdd(pid?: string | number) { editRef.value.showAdd(pid);}// 显示编辑对话框function showEdit(id) { editRef.value.show(id);}//新增/更新后刷新function refreshData() { searchRef.value.getlist();}
编辑业务数据的对话框和查看详细的类似,不过这里是需要使用输入控件进行内容编辑修改处理的。
对于一些复杂控件,我们可以自定义组件来简化在界面上的使用,尽可能的快捷、简单。
我们在组件中定义showAdd和show的方法,便于父组件的调用即可,如果传递了id值,我们根据业务对象的get方法获取详细的数据,赋值到表单对象上就可以正常显示了。
//默认标题为[编辑信息],当show传入id为空的时候,为[新建信息]let title = ref('编辑信息');let isAdd = ref(false); //是否新增状态//显示窗口(编辑/新建)const showAdd = async (pid?: string | number) => { title.value = '新建信息'; isAdd.value = true; resetFields(); //清空表单 editForm.pid = pid + ''; isVisible.value = true; //显示对话框};const show = async (id?: string | number) => { if (!$u.test.isNullOrUnDef(id)) { title.value = '编辑信息'; isAdd.value = false; await menu.Get(id).then((data) => { Object.assign(editForm, data); }); } else { title.value = '新建信息'; isAdd.value = true; resetFields(); //清空表单 } isVisible.value = true; //显示对话框};
抛出这两个实例的方法,供外面调用。
//暴露组件属性和方法defineExpose({ show, showAdd,});
为了承载表单的数据模型,我们创建相关的业务对象
const editRef = ref(); //表单引用// 表单属性定义(初始化)let editForm = reactive({ id: '', pid: '', name: '', icon: '', functionId: '', winformType: '', url: '', seq: '001', isTop: false, expand: 1, visible: 1, webIcon: '', tag: 'web', // Web专用});
保存数据的时候,我们判断表单的rules规则,如果符合通过,那么提交数据并提示用户即可。
// 保存数据处理async function submitData() { var formEl = editRef.value; if (!formEl) return; await formEl.validate(async (valid) => { if (valid) { //验证成功,执行下面方法 var result = false; if (isAdd.value) { result = await menu.Create(editForm); //新增保存 } else { result = await menu.Update(editForm); //编辑保存 } if (result) { $u.success('操作成功!'); // 提示信息 emit('submit'); // 提示刷新数据 closeDialog(); // 重置窗口状态 } else { $u.error('操作失败'); } } });}
编辑界面的效果如下所示。
3)导入界面的处理
导入界面,一般我们分为几个步骤,一个是提供导入模板下载,然后上传文件并显示数据,然后确认提交即可。
由于导入数据的逻辑上大致类似,不同的是他们的业务数据和验证规则,因此我们通过自定义组件的方式,来简化相关的处理。
我通过改造ele-import 的第三方组件,让它支持Vue3+Typescript语法,实现对业务数据的上传操作。
<template> <div class="main"> <!--条件及列表展示--> <Search ref="searchRef" @show-import="showImport" @show-add="showAdd" @show-view="showView" @show-edit="showEdit" /> <import-data ref="importRef" @finish="finishImport" /> </div></template>
而在import.vue页面里面,我们的代码是使用ele-import来处理即可。
<template> <div> <!-- 模板导入信息 --> <ele-import :fields="importForm.fields" :filepath="importForm.filepath" :append="importForm.append" :formatter="importForm.formatter" :rules="importForm.rules" :tips="importForm.tips" :title="importForm.title" :visible="isVisible" :request-fn="saveImport" @close="close" @finish="finishImport" /> </div></template>
通过传入对应的数据,如导入字段,以及规则,以及下载文件等相关参数,就可以实现了文件的上传处理了。
// 请求服务端处理上传数据,返回一个Promise对象const saveImport = async (data) => { // console.log(data); const result = await menu.SaveImport(data); // console.log(result); if (result) { return Promise.resolve(); } else { return Promise.reject(); }};
同时,这也是一个弹出窗口,因此也需要暴露show方法给外部调用。
//显示窗口const show = () => { isVisible.value = true; //显示对话框};//暴露组件属性和方法defineExpose({ show,});
而这个组件的相关数据信息,定义在importForm中,我们来看看对应的数据格式。
const importForm = reactive({ // Excel 模板导入数据 // 弹出层标题 title: '功能菜单导入', // 提示信息 tips: [], // ['商品编号 必填', '产品类型 必填', '商品名称 必填'], // 字段名称参照表 fields: { // 字段根据需要裁剪 pid: '父ID', name: '显示名称', icon: '图标', seq: '排序', url: 'Url地址', webIcon: '菜单图标', systemType_ID: '系统编号', tag: '特殊标签', },// 附加数据, 在每条记录上都会加这两个字段和值 append: { // company: '广州爱奇迪', // leader: '伍华聪' }, // 参数校检, 和 element-ui 中 form表单中传递的rules一样, 都是使用的 async-validator 库 // https://element.eleme.cn/#/zh-CN/component/form#biao-dan-yan-zheng rules: { pid: { type: 'string', required: true, message: '父ID必填' }, name: { type: 'string', required: true, message: '显示名称必填' }, url: { type: 'string', required: true, message: 'Url地址必填' }, webIcon: { type: 'string', required: true, message: '菜单图标必填' }, systemType_ID: { type: 'string', required: true, message: '系统编号必填' }, tag: { type: 'string', required: true, message: '特殊标签必填' }, }, // Excel模板下载地址。注意, 只能是.xlsx的文件, .xls或者.cvs都会报错 filepath: 'http://localhost:5043/UploadFiles/template/功能菜单.xlsx',});
整个导入模块,会通过这个对象的数据格式,进行不同的显示和校验处理等操作。界面效果如下所示。
其中第一步提示信息,并提供模板文件下载
第二步提供文件上传处理。
第三步确认数据并完成处理。
这种统一的操作,我们通过封装,隔离了逻辑步骤的协调处理,只需要在业务组件中生成相关的数据即可,便于使用。
2、利用代码生成工具快速生成
通过上面的介绍,我们了解到整个页面的组件代码结构,因此可以利用它们和数据表之间的关系,生成对应的页面组件,利用代码生成工具Database2Sharp强大的数据库元数据和模板引擎,我们构建了对应的框架代码生成规则,因此统一生成即可,提高了代码开发的效能,同时也统一了代码的结构,便于大项目的维护。
对于SQLSugar的项目框架,我们为了方便,分别单独提供后端代码和Web API代码的生成、Winform界面代码的生成,以及前面介绍到的Vue3+TypeScript+ElementPlus的代码生成操作。
代码生成工具的界面效果如下所示,通过入口菜单,可以实现不同部分的代码快速生成。
通过隔离页面组件的内容变化,实现变化不同通过数据库表关系生成,固定部分采用规定模板预置内容,实现了代码的快速生成操作。
系列文章:
《基于SqlSugar的开发框架的循序渐进介绍(1)–框架基础类的设计和使用》
《基于SqlSugar的开发框架循序渐进介绍(2)– 基于中间表的查询处理》
《基于SqlSugar的开发框架循序渐进介绍(3)– 实现代码生成工具Database2Sharp的整合开发》
《基于SqlSugar的开发框架循序渐进介绍(4)– 在数据访问基类中对GUID主键进行自动赋值处理》
《基于SqlSugar的开发框架循序渐进介绍(5)– 在服务层使用接口注入方式实现IOC控制反转》
《基于SqlSugar的开发框架循序渐进介绍(6)– 在基类接口中注入用户身份信息接口》
《基于SqlSugar的开发框架循序渐进介绍(7)– 在文件上传模块中采用选项模式【Options】处理常规上传和FTP文件上传》
《基于SqlSugar的开发框架循序渐进介绍(8)– 在基类函数封装实现用户操作日志记录》
《基于SqlSugar的开发框架循序渐进介绍(9)– 结合Winform控件实现字段的权限控制》
《基于SqlSugar的开发框架循序渐进介绍(10)– 利用axios组件的封装,实现对后端API数据的访问和基类的统一封装处理》
《基于SqlSugar的开发框架循序渐进介绍(11)– 使用TypeScript和Vue3的Setup语法糖编写页面和组件的总结》
《基于SqlSugar的开发框架循序渐进介绍(12)– 拆分页面模块内容为组件,实现分而治之的处理》
《基于SqlSugar的开发框架循序渐进介绍(13)– 基于ElementPlus的上传组件进行封装,便于项目使用》
《基于SqlSugar的开发框架循序渐进介绍(14)– 基于Vue3+TypeScript的全局对象的注入和使用》
专注于代码生成工具、.Net/.NetCore 框架架构及软件开发,以及各种Vue.js的前端技术应用。著有Winform开发框架/混合式开发框架、微信开发框架、Bootstrap开发框架、ABP开发框架、SqlSugar开发框架等框架产品。
转载请注明出处:撰写人:伍华聪 http://www.iqidi.com