先看效果
要求:
1、能够动态添加多个参数 因为不是每个设备都有对应的参数
2、同一个设备参数名不能重复 这个在后端很好整 前端需要限制用户的输入操作
3、保存之后能够随时修改
环境:
基于若依4.6.1开发的项目
前言:
因为没有现成的东西,值能把旧的代码翻出来找 找到了个类似的
在还不知道若伊这类管理平台的时候 生成表格、表单的代码都是自己写的
尤其是刚毕业后工作的两个单位 对前端框架仅仅是bootstrap这种单纯的前端框架
其实造轮子挺好玩的 就是速度慢一些
数据库部分:
按照一对多的模型创建的参数表
| uuid | 设备型号id | 参数键名 | 参数值 |
参数键存在数据字典中方便管理
参数键名为0x01 0x02这样的编号
参数键标签名为执行标准、公称直径这类业务数据 存在数据字典中
参数值为业务数据 GB/T 12345这类
后端略过:数据的增查删改会的都会,不会的学去,最基本的玩意
前端:
最麻烦的就是前端 要考虑用户的使用 要考虑到各种zz操作导致的各种bug
我也不会讲 代码写的不咋样 就是按ruoyi里面有个$.Table这个玩意 仿照着感觉写的
先是一个小工具 方便创建HTML元素
/** * 创建HTML元素 * @param tagName html标签名 * @param parent 父对象 * @param callback 回调函数 参数为该元素 * @returns {Element} 返回新建的HTML元素 */ function createElement(tagName, parent, callback) { let element = document.createElement(tagName); if (parent) { $(parent).append(element); } if (callback) { callback(element); } return element; }
能用代码整得就不要拼接字符串 除非我不会
这两大坨代码仍在工具类的js文件中
那个$.extend只是为了能够通过$.activeTable调用 没其他意思
/** * 用于管理活动表格的配置信息 * @type {{config: {}, options: {}, set: activeTable.set, get: activeTable.get}} */ var activeTable = { // 配置信息的集合 config: {}, // 活动的配置信息 options: {}, // 选择活动的配置信息 set: function (id) { if ($.common.getLength(activeTable.config) > 1) { let tableId = $.common.isEmpty(id) ? $(event.currentTarget).parents(".active-table").find("table.table").attr("id") : id; if ($.common.isNotEmpty(tableId)) { activeTable.options = activeTable.get(tableId); } } }, // 获取配置信息 get: function (id) { if ($.common.isNotEmpty(id)) { return activeTable.config[id]; } else { return activeTable.options; } } }; $.extend({ /** * 活动表格 */ activeTable: { /** * 初始化活动表格 调用后会调用loadData方法填充数据 * 回调函数接受三个参数{cell:单元格, * @param _options 配置信息 {id:唯一识别符,titleList:标题列,fieldList:字段列,dataList:数据列,callback:每个单元格的回调函数} * * option可以自行添加额外配置 以上内容为最小配置 * 配置id目的是在同一页面使用多个active-table * 我也没试过 谁有兴趣试试看中不中 */ init: function (_options) { let defaults = { id: "active-table", // 标识符 titleList: [], // 标题列 fieldList: [], // 字段列 dataList: [], // 数据列 callback: null // 处理每个单元格的回调函数 }; let options = $.extend(defaults, _options); // 合并配置项 覆盖默认配置 activeTable.options = options; activeTable.config[options.id] = options; }, /** * 活动表格填充数据 * @param data 要填充的数据 为空则使用options中的数据 */ load: function (data) { let table = $('#' + activeTable.options['id']); let options = activeTable.options; if (data) { options.dataList = data; } let titleList = options.titleList; let dataList = options.dataList; let callback = options.callback; // 清空表格内容 $(table).empty(); // 创建表格结构 let thead = createElement('thead', table); let tr_head = createElement('tr', thead); for (let title of titleList) { createElement('th', tr_head, function (e) { $(e).text(title); $(e).css('text-align', 'center'); }); } createElement('tbody', table); if (dataList) { // 追加数据 $.activeTable.appendData(dataList, callback); } // 追加空行 做添加行的按钮 $.activeTable.appendRow(null, callback); }, /** * 创建一行 * @param data 要插入的数据(该行的) * @param callback(options, cell, data, field) 回调函数处理每个单元格的数据 * @returns {Element} 返回创建的行 */ createRow: function (data, callback) { let fieldList = activeTable.options.fieldList; let row = createElement('tr', null); for (let field of fieldList) { let cell = createElement('td', row); if (data) { $(cell).text(data[field]); } if (callback) { callback(activeTable.options, cell, data, field); } } return row; }, /** * 追加行 * @param data 该行的数据 * @param callback(cell,data,field) 回调函数处理每个单元格的数据 */ appendRow: function (data, callback) { let tbody = $('#' + activeTable.options['id'] + ' tbody'); let row = $.activeTable.createRow(data, callback); $(tbody).append(row); }, /** * 插入行 在最后一行前插入一行数据 * @param data 该行的数据 * @param callback(cell,data,field) 回调函数处理每个单元格的数据 */ insertRow: function (data, callback) { let tbody = $('#' + activeTable.options['id'] + ' tbody'); let row = $.activeTable.createRow(data, callback); $(tbody).find('tr:last').before(row); }, /** * 追加数据 * @param data 数据列 * @param callback(cell,data,field) 回调函数处理每个单元格的数据 */ appendData: function (data, callback) { for (let item of data) { $.activeTable.appendRow(item, callback); } }, /** * 插入数据 在最后一行前插入一行数据 * @param data 该行的数据 * @param callback(cell,data,field) 回调函数处理每个单元格的数据 */ insertData: function (data, callback) { for (let item of data) { $.activeTable.insertRow(item, callback); } } } });
这只是个基本的框架 之前的动态表格就是这么整得 只不过那个只是数据展示 现在要做成表单填写提交数据
按照业务需要 在业务的公共js文件中编写如下配置 配置比框架代码都多 看着好头大
主要是处理联动部分写的太多了
var titleList = ['参数名', '参数值', '操作']; var fieldList = ['paramKey', 'paramValue', 'options']; // 自定义的配置选项 业务是配置设备参数 参数不一定都有 但是唯一 只能出现一次 // dictKeys 表示可能使用的参数键 // usedKeys 表示已经使用的参数键 // addParamKey 是添加行的时候在paramKey的时候调用的方法 // addParamValue 是添加行的时候在paramValue的时候调用的方法 var defaultOptions = { id: 'device_params', titleList: titleList, fieldList: fieldList, dictKeys: [], usedKeys: [], addParamKey: function (options, data) { // js的this好麻烦 不注意就成window了 let dictKeys = this.dictKeys; let usedKeys = this.usedKeys; // 按照业务需求创建下拉框 let paramKey = document.createElement('select'); $(paramKey).attr('name', 'paramKeys'); // 加上ruoyi提供的样式 $(paramKey).addClass('form-control'); // ruoyi框架提供了dict转下拉框的option的方法 直接调用了 自己实现也不难 遍历一遍键值创建一堆option填充数据就行 paramKey.innerHTML = $.common.dictToSelect(dictKeys); // 先隐藏已经使用过的键 for (let usedKey of usedKeys) { $(paramKey).find('option[value="' + usedKey + '"]').attr('hidden', 'true'); } // 如果这行数据不空 将这行数据的key置成选中状态 并去掉隐藏属性 // 建议直接移除hidden属性 而不是置为false if (data) { $(paramKey).find('option[value="' + data['paramKey'] + '"]').attr('selected', 'true'); $(paramKey).find('option[value="' + data['paramKey'] + '"]').removeAttr('hidden'); } else { // 如果这是个空行 将第一个未使用的key置为选中状态 let optionList = $(paramKey).find('option'); for (let usedKey of usedKeys) { optionList = $(optionList).not('[value="' + usedKey + '"]'); } $(optionList).first().attr('selected', 'true'); let initKey = $(paramKey).val(); // 将该表格内其他的该键设置为隐藏 因为这个时候该select还没有添加进表格 所以该行不受影响 $('#' + options.id).find('option[value=' + initKey + ']').each(function (idx, e) { $(e).attr('hidden', 'true'); }); } // 将该键放到使用的键集合中 let selectKey = $(paramKey).val(); if (usedKeys.indexOf(selectKey) == -1) { usedKeys.push(selectKey); } // 如果所有的键都使用完成 则隐藏添加按钮 if (dictKeys.length == usedKeys.length) { $('#' + options.id).find('tr:last').attr('hidden', 'true'); } // 以下为当某个下拉框发生变动 则联动其他下拉框隐藏和显示键 let oldKey = null; // 当下拉框聚焦的时候获取值作为旧值 $(paramKey).focus(function (e) { oldKey = $(e.target).val(); }).change(function (e) { // 当发生改变的时候 获取新的值 let newKey = $(e.target).val(); let oldIdx = usedKeys.indexOf(oldKey); usedKeys.splice(oldIdx, 1); usedKeys.push(newKey); // 显示旧的值 $('#' + options.id).find('option[value=' + oldKey + ']').each(function (idx, e) { $(e).removeAttr('hidden'); }); // 隐藏新的值 $('#' + options.id).find('option[value=' + newKey + ']').each(function (idx, e) { $(e).attr('hidden', 'true'); }); // 旧的值取消选中状态 $(paramKey).find('option[value=' + oldKey + ']').removeAttr('selected'); // 该下拉框新的值取消隐藏状态 $(paramKey).find('option[value=' + newKey + ']').removeAttr('hidden'); // 该下拉框新的值设置选中状态 $(paramKey).find('option[value=' + newKey + ']').attr('selected', 'true'); oldKey = newKey; }); return paramKey; }, addParamValue: function (data) { // 参数值就是个input框 没啥说的 let paramValue = document.createElement('input'); $(paramValue).attr('name', 'paramValues'); // 加上ruoyi提供的样式 $(paramValue).addClass('form-control'); if (data) { $(paramValue).val(data['paramValue']); } $(paramValue).attr('name', 'paramValues'); return paramValue; }, callback: function (options, element, data, field) { // 根据字段分别设置行为 if (field == 'paramKey') { $(element).empty(); if (data) { let paramKey = options.addParamKey(options, data); element.append(paramKey); } } if (field == 'paramValue') { $(element).empty(); if (data) { let paramValue = options.addParamValue(data); element.append(paramValue); } } if (field == 'options') { // 如果有数据 则添加删除按钮 if (data) { $(element).empty(); $(element).css('text-align', 'center'); let btn = createElement('a', element, function (e) { $(e).addClass('btn'); $(e).addClass('btn-warning'); $(e).text('删除'); }); $(btn).click(function () { let tr = $(element).parent(); tr.remove(); // 删除后需要联动其他下拉框 更新可用的键 let key = $(element).parent().find('select').val(); let index = options.usedKeys.indexOf(key); options.usedKeys.splice(index, 1); $('#' + options.id).find('option[value=' + key + ']').each(function (idx, e) { $(e).removeAttr('hidden'); }); // 如果有可用的键 则显示添加按钮 if (options.dictKeys.length > options.usedKeys.length) { $('#' + options.id).find('tr:last').attr('hidden', true); } }); } else { // 如果没有数据 则增加个添加参数的按钮 这一段只在初始化表格的时候使用 let blankLine = $(element).parent(); $(blankLine).empty(); $(blankLine).append(element); $(element).attr('colspan', '3'); let btn = createElement('a', element, function (e) { $(e).addClass('btn'); $(e).addClass('btn-success'); $(e).css('width', '100%'); $(e).text('添加'); }); // 该按钮的行为和上面的回调函数应该相同 $(btn).click(function () { // 以下逻辑与上面大致相同 但要注意点击按钮新增的行数据肯定为空 而且只有删除功能 $.activeTable.insertRow(null, function (options, cellElement, data, field) { let rowData = data == null ? null : data[field]; if (field == 'paramKey') { $(cellElement).empty(); let paramKey = options.addParamKey(options, rowData); cellElement.append(paramKey); } if (field == 'paramValue') { $(cellElement).empty(); let paramValue = options.addParamValue(rowData); cellElement.append(paramValue); } if (field == 'options') { $(cellElement).empty(); $(cellElement).css('text-align', 'center'); let btn = createElement('a', cellElement, function (e) { $(e).addClass('btn'); $(e).addClass('btn-warning'); $(e).text('删除'); }); $(btn).click(function () { let tr = $(cellElement).parent(); tr.remove(); let key = $(cellElement).parent().find('select').val(); let index = options.usedKeys.indexOf(key); options.usedKeys.splice(index, 1); $('#' + options.id).find('option[value=' + key + ']').each(function (idx, e) { $(e).removeAttr('hidden'); }); if (options.dictKeys.length > options.usedKeys.length) { $('#' + options.id).find('tr:last').removeAttr('hidden'); } }); } }); }); } } } };
里面一些class样式是直接用的bootstrap的 大概吧。。。 我对前端不是很熟 不对 是很不熟
代码也许很烂 但是功能实现了
用的时候html部分扔一个<table id='active-table'></table>
id的名称随便整 但是要和options中的一样 因为需要找到这个表格 然后
let options = objClone(defaultOptions); options.id = 'device_params'; // 这个就是表格的id options.dataList = deviceParams; // 从后台获取 options.dictKeys = paramKeys; // 从后台获取 for(let item of deviceParams) { options.usedKeys.push(item['paramKey']); } $.activeTable.init(options); $.activeTable.load();
objClone是个深拷贝对象的方法 去百度能找到不少 反正就是最好不要修改业务默认配置 免得引起什么问题
到这就结束了