后台管理项目,需要很多列表,比如公司信息列表、员工信息列表等。一般情况列表做好之后,列的先后顺序以及隐藏等就固定不变了,如果客户想改,那么需要改代码,即使是低代码,一般也需要改JSON。
这样不够灵活嘛,应该可以为客户提供一种“个性化方案”,让客户自己在权限允许的情况下,设置列的(部分)属性,以满足客户灵活多变的需求!
工欲善其事,必先利其器,我们先设计一下需要的接口
对于用户来说,最方便的当然是拖拽的方式来调整列表,所以我们先来定义一个记录拖拽信息的接口。
/**
* 拖拽时记录相关信息
*/
export interface IDragInfo {/** * 拖拽移除的延迟 */timeout: NodeJS.Timeout,/** * 状态 */state: string,/** * 拖拽时X坐标 */offsetX: number,/** * 是否“容器”左面释放鼠标 */isLeft: boolean,/** * 是否按下 ctrl */ctrl: boolean,/** * 开始的“容器”ID */targetId: string,/** * 开始的“容器”标题,判断是否拖拽出去 */targetLabel: string,/** * 开始的 target */target: any,/** * 结束的“容器”ID */sourceId: string,/** * 开始的序号 */targetIndex: number,/** * 结束的序号 */sourceIndex: number
}
拖拽的时候记录相关的消息,拖拽结束后,作为相关的操作数据依据。
低代码的列表是依赖 JSON 渲染的,所以我们先来看看 JSON 结构:
{"gridMeta": { "moduleId": 142,"idName": "ID","colOrder": [ 【列的显示依据】90,101, 102, 105,110, 111, 114, 112, 113, 115, 116,120, 121, 100, 150, 151, 152, 153,160, 161, 162, 163, 164]},"height": 400,"stripe": true,"itemMeta": {"90": {"id": 90,"colName": "kind","label": "分类","width": 140, 【负责列宽】"title": "分类","align": "center", 【内容对齐方式】"header-align": "center" 【标题对齐方式】},"100": {"id": 100,"colName": "area","label": "多行文本","width": 140,"title": "多行文本","align": "center","header-align": "center"}【其他列】}
}
观察JSON结构我们可以找到几个关键元素:
我们依据上面的属性特点,做一个结构来保存变化后的数据:
/**
* 个性化方案
*/
type ICaseList = {caseId: string | number, // 方案编号moduleId: string | number, // 模块IDlabel: string, // 方案名称meta: ICase // 个性化方案 的 meta
}
/**
* 个性化方案 的 meta
*/
type ICase = {gridMeta: {colOrder: Array // 排序用},itemMeta: {[index: string | number]: {'header-align': string, // 标题的对齐'align': string, // 内容的对齐width: number | string // 宽度}}
}
准备工作完毕,开始编码。
那么如何实现拖拽呢?我们可以使用 Vue 的自定义指令实现。
const _gridDrag = {// 指令的定义mounted (el, binding) {const className = 'el-table__header'// 控件的metaconst meta = binding.value// table 并不会被立即渲染出来nextTick(() => {setTimeout(() => {// 根据 class 找到 tableconst table = el.getElementsByClassName(className)[0]// 获取 metaconst { gridMeta, itemMeta, modCol } = meta// 调用拖拽功能gridDrag(gridMeta, itemMeta, table, deleteDom, modCol).girdSetup()}, 600)})}
}
分析 el-table 渲染出来的 table,我们可以发现 class="el-table__header",这样就找到 table,然后用 gridDrag 实现拖拽即可。
自定义指令注册之后,我们就可以在列表控件上面使用了。
v-bind="gridMeta":dataList="dataList":selection="selection"size="small"/>
拖拽结束后,依据拖拽信息修改 colOrder 。
/** * 交换两个th的位置 */const _swapPlaces = () => {// 交换colOrder[dragInfo.sourceIndex] = dragInfo.targetIdcolOrder[dragInfo.targetIndex] = dragInfo.sourceId}/** * 拖拽 th 后调整顺序 */const _order = () => {// 判断前插、后插。后插:偏移 0;前插:偏移 1const offsetTarget = dragInfo.isLeft ? 0 : 1// 判断前后顺序。const offsetSource = dragInfo.sourceIndex < dragInfo.targetIndex ? 0 : 1// 插入源colOrder.splice(dragInfo.targetIndex + offsetTarget, 0, dragInfo.sourceId)// 删除源colOrder.splice(dragInfo.sourceIndex + offsetSource, 1)}
隐藏也比较方便,只需要删除 colOrder 里对应的元素即可。我们可以定义一个“手势指令”,向上拖拽移出到 table 外面就表示隐藏这个列。
/** * 移除选择的字段 * @param dragInfo 拖拽信息 */const _setRemove = (dragInfo: IDragInfo) => {const col = itemMeta[dragInfo.sourceId]// 调用外部的确认对话框deleteDom(col, () => {// 确认移除,才会执行回调gridMeta.colOrder.splice(dragInfo.sourceIndex, 1)}) }
对齐方式分为标题的对齐和内容的对齐。那么如何让客户方便操作呢,我们还是可以定义一个“手势指令”,向左拖动就是左对齐,向右拖动就是右对齐,是不是很自然?
那么如何区分是对齐标题还是内容呢?只好加上 ctrl 作为区分了。
标题一般都是居中的不需要改,所以按住 ctrl 不放,表示对齐标题,直接拖拽是对齐内容。
/** * 设置th、td的对齐方式 */const _setThAlgin = (dragInfo: IDragInfo) => {// 判断 th 还是 tdconst alignKind = (dragInfo.ctrl) ? 'header-align' : 'align'// 获取列的 meta const col = itemMeta[dragInfo.sourceId]// 判断当前对齐方式:左、中、右switch (col[alignKind]) {case 'left': // -> 变成居中col[alignKind] = (dragInfo.isLeft) ? 'left' : 'center'breakcase 'center': // -> 变成右对齐; <- 变成左对齐col[alignKind] = (dragInfo.isLeft) ? 'left' : 'right'breakcase 'right': // <- 变成居中col[alignKind] = (dragInfo.isLeft) ? 'center' : 'right'break}}
el-table 自带调整列宽的功能,所以我们只需要记录下来调整后的 td 的宽度即可,那么什么时候调整完毕呢,是不是需要用户自己按个按钮?
当然不需要,要求用户做的操作,能少一下就少一下。
那么怎么做呢?我们可以监听事件。拖拽结束会触发什么事件呢?对,mouseup!我们监听一下就可以获知用户是否调整了宽度。
table.removeEventListener("mouseup", _setThWidth)table.addEventListener("mouseup", _setThWidth)// 调整 th 的宽度后,记录新的宽度const _setThWidth = (e) => {// 等待刷新setTimeout(() => {// 监听事件,获取调整后的 th 的宽度const arr = Array.from(table.rows[0].cells)// 遍历 table 的第一行(标题)的 tharr.forEach((element, index) => {if (index === 0) { // 跳过第一列 (check)} else {itemMeta[gridMeta.colOrder[index - 1]].width = element.offsetWidth}})}, 600)}
话说,el-table 提供了调整宽度的功能,然后有没有考虑过刷新的问题?或者,是我没有认真看文档漏掉了什么。
用户设置好个性化方案之后,还需要保存一下,否则一刷新就没了,容易被打。
那么保存到哪里呢?有多个容器可以选择:
综合考虑,前端采用 indexedDB 保存个性化方案。
// 创建help
import { dbCreateHelp } from '@naturefw/storage'
// 设置数据库名称和版本
const db = {dbName: 'nf-customer-setting',ver: 1
}
/**
* 用户的个性化方案
*/
export default async function createDBHelp (callback) {const help = dbCreateHelp({dbFlag: 'nf-customer-setting',dbConfig: db,stores: {/** * * caseId:'方案编号', * * moduleId: '模块ID', * * label: '方案名称', * * meta: { // meta内容 * * * header-align: '标题对齐方式' * * * align: '内容对齐方式' * * * width: '宽度' * * } */cus_grid: { // 列表的个性化方案id: 'caseId',index: {moduleId: false},isClear: false}},// 设置初始数据async init (help) {}})return help
}
维护 json 的工具清空数据 重置
import { defineComponent, reactive } from 'vue'import { nfGrid, nfGridSlot, createDataList, _gridDrag } from '@naturefw/ui-elp'import _gridMeta from './grid.json'import _formMeta from '../form/form.json'import cusGrid from './cus-grid.vue'// helpimport { helpGridCard } from '../../../lib/main'export default defineComponent({name: 'nf-helps-grid-card',directives: {gridDrag: _gridDrag},components: {nfGrid: nfGrid,helpGridCard,cusGrid},props: {moduleID: { // 模块IDtype: [Number, String],default: 1 }},setup(props) {const gridMeta = reactive(_gridMeta)// 设置抽屉const drawerInfo = reactive({isShow: false})const selection = reactive({dataId: '', // 单选ID number 、stringrow: {}, // 单选的数据对象 {}dataIds: [], // 多选ID []rows: [] // 多选的数据对象 []})// 根据 meta 创建表单的 modelconst _dataList = createDataList(_formMeta.itemMeta, 10).reverse()const dataList = reactive>(_dataList)// 清空数据,演示 “没有数据”const myclear = () => {dataList.length = 0}// 重新设置数据const reset = () => {dataList.length = 0dataList.push(...createDataList(_formMeta.itemMeta, 30).reverse())}return {// 重置reset,myclear,// 数据dataList,selection,gridMeta}}})
最近还整理一份JavaScript与ES的笔记,一共25个重要的知识点,对每个知识点都进行了讲解和分析。能帮你快速掌握JavaScript与ES的相关知识,提升工作效率。




有需要的小伙伴,可以点击下方卡片领取,无偿分享