• 【低代码】为客户设计个性化方案:列表篇(客户自己调整排序对齐等)


    列表的个性化方案 for 用户

    后台管理项目,需要很多列表,比如公司信息列表、员工信息列表等。一般情况列表做好之后,列的先后顺序以及隐藏等就固定不变了,如果客户想改,那么需要改代码,即使是低代码,一般也需要改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
    } 
    
    • 1
    • 2
    • 3
    • 4
    • 5

    拖拽的时候记录相关的消息,拖拽结束后,作为相关的操作数据依据。

    分析 json 结构

    低代码的列表是依赖 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"}【其他列】}
    } 
    
    • 1
    • 2

    观察JSON结构我们可以找到几个关键元素:

    • colOrder: 列表的列是遍历(v-for)colOrder 渲染出来的,如果想改顺序和隐藏列的话,维护好 colOrder 即可。
    • width:itemMeta 的 width 存放的是列的宽度,想要调整宽度的话,需要修改这个属性。
    • align:itemMeta 的 align 存放的是列的内容的对齐方式。
    • header-align:itemMeta 的 header-align 存放的是列的标题的对齐方式。

    定义个性化方案的存储结构

    我们依据上面的属性特点,做一个结构来保存变化后的数据:

    /**
     * 个性化方案
     */
    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 // 宽度}}
    } 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    实现具体功能

    准备工作完毕,开始编码。

    自定义指令

    那么如何实现拖拽呢?我们可以使用 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)})}
    } 
    
    • 1
    • 2

    分析 el-table 渲染出来的 table,我们可以发现 class="el-table__header",这样就找到 table,然后用 gridDrag 实现拖拽即可。

    自定义指令注册之后,我们就可以在列表控件上面使用了。

     v-bind="gridMeta":dataList="dataList":selection="selection"size="small"/> 
    
    • 1

    实现设置列的顺序的功能

    拖拽结束后,依据拖拽信息修改 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)} 
    
    • 1
    • 动画演示

    实现设置列的隐藏的功能

    隐藏也比较方便,只需要删除 colOrder 里对应的元素即可。我们可以定义一个“手势指令”,向上拖拽移出到 table 外面就表示隐藏这个列。

     /** * 移除选择的字段 * @param dragInfo 拖拽信息 */const _setRemove = (dragInfo: IDragInfo) => {const col = itemMeta[dragInfo.sourceId]// 调用外部的确认对话框deleteDom(col, () => {// 确认移除,才会执行回调gridMeta.colOrder.splice(dragInfo.sourceIndex, 1)}) } 
    
    • 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}} 
    
    • 1
    • 动画演示

    实现设置设置列的宽度的功能

    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)} 
    
    • 1

    话说,el-table 提供了调整宽度的功能,然后有没有考虑过刷新的问题?或者,是我没有认真看文档漏掉了什么。

    • 动画演示

    保存、加载和共享

    用户设置好个性化方案之后,还需要保存一下,否则一刷新就没了,容易被打。

    那么保存到哪里呢?有多个容器可以选择:

    • localStorage —— 特点:同步操作,非常方便;缺点:容量有限。
    • indexedDB —— 特点:不用担心容量问题;缺点:异步操作有点麻烦。
    • 云端 —— 特点:支持多设备同步;缺点:需要后端配合,提供存放用户的个性化方案的功能。

    综合考虑,前端采用 indexedDB 保存个性化方案。

    • 动画演示

    定义 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
    } 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    使用方法

    • 引入列表控件
    • 加载 meta
    • 加载个性化方案
    • 设置指令
    • 监听 meta 的变化,保存
     维护 json 的工具清空数据 重置



    • 1
     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}}}) 
    
    • 1

    最后

    最近还整理一份JavaScript与ES的笔记,一共25个重要的知识点,对每个知识点都进行了讲解和分析。能帮你快速掌握JavaScript与ES的相关知识,提升工作效率。



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

  • 相关阅读:
    【原型模式】设计模式系列:高效克隆的艺术(深入解析)
    消息中间件解析 | 如何正确理解软件应用系统中关于系统通信的那些事?
    力扣bash
    在云上使用 OpenText 实现业务关键型应用程序的现代化
    C++面向对象程序设计 - 构造函数
    Spark、Filnk简单介绍
    LAMMPS模拟中,明明是cu晶格,为什么在生成原子命令中,注释标注的却是添加第一类Fe原子。
    15篇MyBatis-Plus系列集合篇「值得收藏学习」
    “要疯”六年,安踏与年轻人疯出“新宇宙”
    基于无人机倾斜摄影模型提取高精度地形DEM数据
  • 原文地址:https://blog.csdn.net/pfourfire/article/details/126991271