【VUE】Vue中实现树状表格结构编辑与版本对比的详细技术实现

Vue中实现树状表格结构编辑与版本对比的详细技术实现

在Vue中,创建一个可编辑的树状表格并实施版本对比功能是一种需求较为常见的场景。在本教程中,我们将使用Vue结合Element UI的el-table组件,来构建一个树状表格,其中包含添加、编辑功能,并通过特定的方法展示数据变更。本文会详继解析每一步的代码实现。

图中,黄色为修改的数据,绿色为新增,红色是被删除的数据。
在这里插入图片描述

初始设置与组件

首先,我们使用el-table组件创建基础的表格,并通过tree-props属性指定了如何展示树形数据的结构。

<template><div class="h-station"><el-card class="h-card"><el-button type="primary" @click="addScheme">添加一级分类</el-button><el-button type="primary">批量导入</el-button><el-table:data="tableData":row-class-name="getRowClass"style="width: 100%; margin-top: 15px"borderheight="calc(100vh - 260px)"row-key="id":tree-props="{ children: 'children' }"><el-table-column align="center" prop="name" label="维修方案名称" min-width="100px"><template slot-scope="scope"><el-inputv-if="scope.row.type !== 'Button'"style="width: calc(100% - 50px)"v-model="scope.row.name":disabled="scope.row.type === 'delete'"></el-input></template></el-table-column></el-table></el-card></div>
</template>

数据模型和方法定义

data函数中定义的tableData数组,包含了表格数据和结构信息。此外,我们会备份原始数据以供版本对比之用。

<script>
export default {data() {return {rawData: [],tableData: [...],curMateral: { children: [] },materials: [],materialsTypeIds: [],materialsTypeNames: [],};},created() {this.rawData = JSON.parse(JSON.stringify(this.tableData));this.loadMaterialsInfo();  // 模拟加载物料信息},methods: {enrichDataWithLevel(data, level = 1, parent = null) {return data.map(item => ({...item,level,children: item.children ? this.enrichDataWithLevel(item.children, level + 1, item) : [],parent,}));},// 示例方法:模拟加载物料信息loadMaterialsInfo() {this.materials = [{ materialsTypeId: 1, materialsTypeName: '物料1' }];this.curMateral = this.materials[0];},}
};
</script>

版本对比的展示实现

我们通过getRowClass方法为表格行动态赋予样式,标识数据的更改状态:新添加、更改或删除。

methods: {getRowClass({ row, rowIndex }) {let rawNode = this.findNodeById(this.rawData, row.id);if (row.type === 'delete') {return 'deleted-row';} else if (row.id.includes && row.id.includes('cwx-') && row.type !== 'Button') {return 'new-row';} else if (rawNode) {let flag = true;if (rawNode&&!(row.id+'').includes('cwx-')) {let keys = Object.keys(rawNode);keys.push('materialsTypeIds')for (let key of keys) {if(key==='materialsTypeIds'){if((!rawNode.materialsTypeIds||rawNode.materialsTypeIds.length===0)&&(!row.materialsTypeIds||row.materialsTypeIds.length===0)){}else{flag=false}}else if (rawNode[key] !== row[key]&&(key!=='parent')&&(key!=='children')) {flag = false;}}}if(!flag){return 'change-row';}}},
}

样式定义

使用SCSS来定义不同状态下行的样式:

<style scoped>
::v-deep .change-row {background-color: rgba(230,162,60,0.2);
}
::v-deep .el-table--enable-row-hover .el-table__body .change-row:hover > td.el-table__cell {background-color: rgba(230,162,60,0.2);
}
::v-deep .new-row {background-color: rgba(103, 194, 58, 0.2);
}
::v-deep .el-table--enable-row-hover .el-table__body .new-row:hover > td.el-table__cell {background-color: rgba(103, 194, 58, 0.2);
}
::v-deep .deleted-row {background-color: rgba(245, 108, 108, 0.2);
}
::v-deep .deleted-row::after {content: '';position: absolute;left: 0;top: 50%; /* 置于行的中间 */width: 100%; /* 线的宽度为整行 */border-top: 1px solid #000; /* 红色的线,你可以调整颜色和线的样式 */opacity: 0.7; /* 线的透明度,你可以调整它使线更清晰或更隐蔽 */
}
::v-deep .el-table .el-table__row {position: relative;
}
::v-deep .el-table--enable-row-hover .el-table__body .deleted-row:hover > td.el-table__cell {background-color: rgba(245, 108, 108, 0.2);
}
</style>

完整代码实现

<template><div class="h-station"><el-card class="h-card"><el-button type="primary" @click="addScheme">添加一级分类</el-button><el-button type="primary">批量导入</el-button><el-table:row-class-name="getRowClass":data="tableData"style="width: 100%; margin-top: 15px"borderheight="calc(100vh - 260px)"row-key="id":tree-props="{ children: 'children' }"><el-table-column align="center" prop="name" label="维修方案名称" min-width="100px"><template slot-scope="scope"><el-inputv-if="scope.row.type !== 'Button'"style="width: calc(100% - 50px)"v-model="scope.row.name":disabled="scope.row.type === 'delete'"></el-input><el-button v-else type="primary" :disabled="tableData.find((item) => item.id === scope.row.parent.id).type==='delete'" @click="addSubScheme(scope.row)">添加子方案</el-button></template></el-table-column><el-table-column align="center" prop="state" label="状态" min-width="30px"><template slot-scope="scope"><el-switchv-if="scope.row.level === 0"v-model="scope.row.state"active-color="#199f7e"inactive-color="#eee"></el-switch><span v-else-if="scope.row.type === 'Button'"></span><span v-else>--</span></template></el-table-column><el-table-column align="center" show-overflow-tooltip prop="wlz" label="物料组"><template slot-scope="scope">{{getMaterialsNameStr(getMaterialsName(scope.row.materialsTypeIds || []))}}</template></el-table-column><el-table-column align="center" prop="cjsj" label="创建时间" min-width="60px"> </el-table-column><el-table-column align="center" prop="handle" label="操作" min-width="40px"><template slot-scope="scope"><template v-if="scope.row.type !== 'Button'"><template v-if="scope.row.type === 'delete'"><el-button type="text" @click="revokeDeleteScheme(scope.row)">撤销删除</el-button></template><template v-else><el-button v-if="scope.row.level > 0" type="text" @click="relatedMaterials(scope.row)">关联物料</el-button><el-button type="text" @click="deleteScheme(scope.row)">删除</el-button></template></template></template></el-table-column></el-table><p style="text-align: center;margin-top: 20px;"><el-button type="primary">保存更改</el-button><el-button>返回</el-button></p></el-card><common-dialogtitle="关联物料组":visible="visible"width="700px"confirmText="保存":loading="btnloading":handleClose="handleClose":handleConfirm="handleConfirm"><div style="width: 100%; overflow: hidden"><el-row :gutter="20"><el-col :span="3"><p style="margin-top: 30px">物料组:</p></el-col><el-col :span="10"><p class="mb10">物料组</p><div class="select-box"><p v-for="item in materials" :key="item.materialsTypeId" :class="{'cur-materials': item.materialsTypeId===curMateral.materialsTypeId}" class="materials" @click="selectMateral(item)"><span>{{ item.materialsTypeName }}</span><i class="el-icon-arrow-right"></i></p></div></el-col><el-col :span="11"><p class="mb10">二级分类</p><div class="select-box"><el-checkbox-group v-model="materialsTypeIds" @change="changeMaterialsTypes"><p v-for="item in curMateral.children" :key="item.materialsTypeId"><el-checkbox :label="item.materialsTypeId">{{ item.materialsTypeName }}</el-checkbox></p></el-checkbox-group></div></el-col></el-row><el-row :gutter="20" style="margin-top: 15px"><el-col :span="3"><p>已关联物料组:</p></el-col><el-col :span="21"><div class="select-box h150"><p v-for="(item, index) in materialsTypeNames" :key="index">{{ item }}</p></div></el-col></el-row></div></common-dialog></div>
</template><script>
import commonDialog from '@/components/CommonDialog/index';
import { getSecondaryClassi } from '@/api/sparePartsManagement/basic/materialsInfo.js';
export default {components: { commonDialog },data() {return {rawData: [],tableData: [{id: 1,name: '123',state: true,children: [{ id: 10, name: '10' },{ id: 11, name: '11' },],},{id: 2,name: '222',state: true,children: [],},],visible: false,btnloading: false,curRow: null,curMateral: {children: [],},materials: [],materialsTypeIds: [],materialsTypeNames: [],};},created() {this.tableData = this.enrichDataWithLevel(this.tableData);this.rawData = JSON.parse(JSON.stringify(this.tableData));this.getMaterialsInfo();},methods: {findNodeById(tree, id) {for (let i = 0; i < tree.length; i++) {if (tree[i].id === id) {return tree[i];}if (tree[i].children && tree[i].children.length) {const found = this.findNodeById(tree[i].children, id);if (found) {return found;}}}return null; // 如果在树中找不到该节点},getRowClass({ row, rowIndex }) {let rawNode = this.findNodeById(this.rawData, row.id);if (row.type === 'delete') {return 'deleted-row';} else if (row.id.includes && row.id.includes('cwx-') && row.type !== 'Button') {return 'new-row';} else if (rawNode) {let flag = true;if (rawNode&&!(row.id+'').includes('cwx-')) {let keys = Object.keys(rawNode);keys.push('materialsTypeIds')for (let key of keys) {if(key==='materialsTypeIds'){if((!rawNode.materialsTypeIds||rawNode.materialsTypeIds.length===0)&&(!row.materialsTypeIds||row.materialsTypeIds.length===0)){}else{flag=false}}else if (rawNode[key] !== row[key]&&(key!=='parent')&&(key!=='children')) {flag = false;}}}if(!flag){return 'change-row';}}},changeMaterialsTypes() {this.materialsTypeNames = this.getMaterialsName(this.materialsTypeIds);},getMaterialsNameStr(arr) {return arr.join(';');},getMaterialsName(ids) {let nodes = this.findNodesById({ children: this.materials, materialsTypeId: -1 }, ids);let tree = [];for (let node of nodes) {let parentNode = this.materials.find((item) => item.materialsTypeId === node.parentId);let treeIndex = tree.findIndex((item) => item.materialsTypeId === parentNode.materialsTypeId);if (treeIndex === -1) {tree.push({ ...parentNode, children: [node] });} else {tree[treeIndex].children.push(node);}}let arr = [];for (let parent of tree) {let str = parent.materialsTypeName + ':';let childs = parent.children.map((item) => item.materialsTypeName).join('、');str += childs;arr.push(str);}return arr;},findNodesById(tree, ids) {// 定义一个结果数组来存储找到的节点let result = [];// 定义一个递归函数用于在树中找到具有特定id的节点function searchTree(node, ids) {// 如果当前节点的id在ids数组中,那么把节点按照ids的顺序加入结果数组const index = ids.indexOf(node.materialsTypeId);if (index !== -1) {result[index] = node;}// 如果当前节点有子节点,递归搜索每个子节点if (node.children && node.children.length) {node.children.forEach((child) => searchTree(child, ids));}}// 开始从树的根节点开始递归搜索searchTree(tree, ids);// 过滤掉结果数组中的未定义项,并返回结果return result.filter((node) => node !== undefined);},selectMateral(item) {this.curMateral = item;},getMaterialsInfo() {getSecondaryClassi().then((res) => {this.materials = res.data;});},relatedMaterials(row) {this.curRow = row;this.materialsTypeIds = [...(this.curRow?.materialsTypeIds || [])];this.changeMaterialsTypes();this.visible = true;},handleClose() {this.visible = false;this.curRow = null;this.curMateral = {children: [],};},handleConfirm() {this.$set(this.curRow, 'materialsTypeIds', [...this.materialsTypeIds]);this.handleClose();},deleteScheme(row) {if (row.id.includes && row.id.includes('cwx-')) {this.deleteNode(this.tableData, row.id);} else {this.$set(row, 'type', 'delete');if(row.children){for(let item of row.children){if(item.type!=='Button'){this.$set(item, 'type', 'delete');}}}}},deleteNode(data, id) {for (let i = 0; i < data.length; i++) {if (data[i].id === id) {// 直接从数组中删除data.splice(i, 1);return true; // 表示删除完成}if (data[i].children && data[i].children.length > 0) {// 递归调用删除函数if (this.deleteNode(data[i].children, id)) {if (data[i].children.length === 0) {// 如果子数组为空,则删除子数组属性delete data[i].children;}return true;}}}return false;},revokeDeleteScheme(row) {this.$set(row, 'type', '');if(row.children){for(let item of row.children){if(item.type!=='Button'){this.$set(item, 'type', '');}}}},addScheme() {let now = new Date().valueOf();let item = {id: 'cwx-' + now, //id 为 cwx-xxx这种格式的均为临时id,和后端返回的id隔离name: '',state: true,wlz: '',cjsj: '',level: 0,parent: null,children: [],};item.children.push({id: 'cwx-' + now + 1, //id 为 cwx-xxx这种格式的均为临时id,和后端返回的id隔离type: 'Button',level: 1,parent: item,});this.tableData.push(item);},addSubScheme(row) {let parent = this.tableData.find((item) => item.id === row.parent.id);let now = new Date().valueOf();parent.children.splice(-1, 0, {id: 'cwx-' + now, //id 为 cwx-xxx这种格式的均为临时id,和后端返回的id隔离name: '',state: true,wlz: '',cjsj: '',level: 1,parent: row.parent,});},enrichDataWithLevel(data, level = 0, parent = null) {let list = data.map((item) => ({...item,level: level,children: item.children ? this.enrichDataWithLevel(item.children, level + 1, item) : null,parent: parent,}));if (level === 1) {let now = new Date().valueOf();list.push(//添加按钮节点{id: 'cwx-' + now, //id 为 cwx-xxx这种格式的均为临时id,和后端返回的id隔离type: 'Button',parent: parent,});}return list;},},
};
</script><style lang="scss" scoped>
p {margin: 0;
}
::v-deep .change-row {background-color: rgba(230,162,60,0.2);
}
::v-deep .el-table--enable-row-hover .el-table__body .change-row:hover > td.el-table__cell {background-color: rgba(230,162,60,0.2);
}
::v-deep .new-row {background-color: rgba(103, 194, 58, 0.2);
}
::v-deep .el-table--enable-row-hover .el-table__body .new-row:hover > td.el-table__cell {background-color: rgba(103, 194, 58, 0.2);
}
::v-deep .deleted-row {background-color: rgba(245, 108, 108, 0.2);
}
::v-deep .deleted-row::after {content: '';position: absolute;left: 0;top: 50%; /* 置于行的中间 */width: 100%; /* 线的宽度为整行 */border-top: 1px solid #000; /* 红色的线,你可以调整颜色和线的样式 */opacity: 0.7; /* 线的透明度,你可以调整它使线更清晰或更隐蔽 */
}
::v-deep .el-table .el-table__row {position: relative;
}
::v-deep .el-table--enable-row-hover .el-table__body .deleted-row:hover > td.el-table__cell {background-color: rgba(245, 108, 108, 0.2);
}
.select-box {padding: 10px;height: 200px;border: 1px solid #eee;overflow: auto;
}.materials {line-height: 25px;padding: 0 15px;cursor: pointer;span {display: inline-block;vertical-align: middle;width: calc(100% - 16px);overflow: hidden; //超出的文本隐藏text-overflow: ellipsis; //溢出用省略号显示white-space: nowrap; //溢出不换行}
}
.cur-materials{background-color: rgba(25,159,126,0.41);
}.h150 {height: 150px;
}.mb10 {margin-bottom: 10px;
}
</style>

总结

通过这种方式,我们不仅提供了树状表格数据的编辑功能,还实现了通过颜色和样式标识不同版本之间数据变动的可视化展示。这使得数据的对比和审核变得直观和高效。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/315258.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

codeforce#933 题解

E. Rudolf and k Bridges 题意不讲了&#xff0c;不如去题干看图。 传统dp&#xff0c;每个点有两个选择&#xff0c;那么建桥要么不建。需要注意的是在状态转移的时候&#xff0c;桥是有长度的&#xff0c;如果不建需要前d格中建桥花费最少的位置作为状态转移的初态。 #incl…

与Apollo共创生态:Apollo7周年大会自动驾驶生态利剑出鞘

前言 4月22日&#xff0c;百度Apollo在北京车展前夕举办了以“破晓•拥抱智变时刻”为主题的智能汽车产品发布会&#xff0c;围绕汽车智能化&#xff0c;发布了智驾、智舱、智图等全新升级的“驾舱图”系列产品。 1、7周年大会 自2013年百度开始布局自动驾驶&#xff0c;201…

Axure RP 9中文激活版:专业原型设计工具mac/win

Axure RP 9是一款由美国Axure Software Solution公司开发的专业原型设计工具。它凭借强大的交互功能和丰富的设计素材&#xff0c;为产品经理、UI设计师、交互设计师等用户提供了高效、便捷的原型设计体验。 Axure RP 9支持快速创建线框图、流程图、原型和规格说明文档&#xf…

c++中的链表list的模拟实现

拖更了半个月&#xff0c;我终于来填c的坑啦。上次我们说的vetcor不知道小伙伴还记得多少呢&#xff1f;今天我们要讲list的模拟实现。 目录 架构结点list表的结构 构造函数尾插push_back()尾删pop_back()计算个数&#xff1a;size()判断空empty()※迭代器问题普通迭代器迭代器…

vue2[黑马笔记]

vue基础 是什么—javascript框架 构建用户界面的前端框架 1.构建用户界面用vue往html页面中填充数据 2.框架现成的解决方案&#xff0c;遵守框架的规范去实现自己的业务功能学习vue 就是学习vue框架中规定的用法vue的指令组件&#xff08;对ul结构的复用&#xff09;&#x…

windows服务启动提示‘服务没有响应控制功能’(mysql启动报错)

在安装mysql的时候&#xff0c;在windows服务项启动 或 使用命令net start mysql 时启动是报错&#xff0c;提示 服务没有响应控制功能 发生原因&#xff1a; Windows10 x64 或 更高的操作系统&#xff0c;有些系统缺少一些组件 解决办法&#xff1a; 1、下载最新的 Microsoft …

CSS详解(一)

1、css工作中使用场景 美化网页&#xff08;文字样式、背景样式、边框样式、盒子模型、定位、动画、&#xff09;&#xff0c;布局页面&#xff08;flex布局、响应式布局、媒体查询&#xff09; 2、CSS 规则 通常由两个主要部分组成选择器和样式声明 2.1选择器 选择器指定了…

制作自己的YOLOv8数据集

制作自己的YOLO8数据集 前言 该数据集的格式参照于coco数据集结构✨ 步骤一&#xff1a;收集图像数据 从互联网上下载公开的数据集&#xff0c;也可以使用摄像头或其他设备自行采集图像&#xff0c;确保你的图像数据覆盖了你感兴趣的目标和场景 步骤二&#xff1a;安装Labe…

正点原子[第二期]ARM(I.MX6U)裸机篇学习笔记-1.2

前言&#xff1a; 本文是来自哔哩哔哩网站上视频“正点原子[第二期]Linux之ARM&#xff08;MX6U&#xff09;裸机篇”的学习笔记&#xff0c;在这里会记录下正点原子Linux ARM MX6ULL 开发板根据配套的哔哩哔哩学习视频所作的实验和笔记内容。本文大量的引用了正点原子哔哔哩网…

[论文笔记] EcomGPT:COT扩充数据的电商大模型

社区供稿 | EcomGPT:基于任务链数据的电商大模型(附魔搭推理实践) - 知乎 https://arxiv.org/pdf/2312.15696.pdf EcomInstruct指令数据集构建 数据集组成 COT方式构造垂域训练数据:把原本的垂域任务分解成了原子任务,构造了基于解决原子任务的数据。这样能用类似…

网页模版如何用

现在的网页模版已经得到了许多人的喜爱和使用。随着人们对互联网的需求不断增加&#xff0c;更多的公司和组织需要拥有自己的网站&#xff0c;以推广他们的品牌和服务。而网页模版为他们提供了一个简单而高效的方法来创建自己的网站。 网页模版是预先设计好的网站模板&#xff…

人工智能技术在教育中的潜力有多大

原文&#xff1a;人工智能技术在教育中的潜力有多大&#xff1f; - 知乎 作者&#xff1a;大全Prompt 链接&#xff1a;https://www.zhihu.com/question/637034129/answer/3346272227 来源&#xff1a;知乎 谢邀&#xff1a;在技术快速发展的今天&#xff0c;人工智能&#x…

炒股自动化:券商官方,散户可用,查询订单状态API如何用?

券商官方的接口&#xff0c;个人账户可申请&#xff0c;入金门槛低&#xff0c;接入文档完善&#xff0c;技术支持好的&#xff0c;经过我们筛选后&#xff0c;只有一家符合 会编程&#xff0c;有基础&#xff0c;只是需要API接口的朋友不用看这些&#xff0c;不会写程序的朋友…

【vue2】实现微信截图(复制图片)在项目内可粘贴

需求 后台管理在上传图片地方需要将复制的图片粘贴上传 一、添加事件 在原有上传组件的基础上添加 paste事件 二、方法 onPaste(e) {const items (e.clipboardData || window.clipboardData).items;let blob null;for (let i 0; i < items.length; i) {if (items[i].ty…

设计模式:单例、原型和生成器

在这篇文章中&#xff0c;我们将重点介绍其余的创建模式&#xff1a;Singleton&#xff0c;Builder和Prototype。 在我看来&#xff0c;这些模式不如工厂重要。然而&#xff0c;了解它们仍然很有用。我将提供UML描述&#xff0c;简单的java示例&#xff08;这样即使你不了解jav…

[linux网络编程]UDP协议和TCP协议的使用

目录 看以下内容前&#xff0c;你要先了解main函数带参数有什么用、 了解socket的相关函数接口 如果不了解socket的相关函数接口请先看我这篇文章 main函数带参数有什么用 UDP udp_server 1.生成socket文件描述符 2.填充sockaddr_in信息 3.bind 4.发&#xff08;收&…

渗透之sql注入练气篇

sql注入产生的原因&#xff1a; 由于程序过滤不严谨&#xff0c;导致用户有一些异常输入&#xff0c;最终触发数据库的查询。所以会出现sql注入这个问题。有些恶意的人就会利用这些信息导致数据库泄露。 注意&#xff1a;一般我们存在注入点我们会查询管理员的账号和密码&#…

如何30天快速掌握键盘盲打

失业后在家备考公务员&#xff0c;发现了自己不正确的打字方式&#xff0c;决定每天抽出一点时间练习打字。在抖音上看到一些高手的飞速盲打键盘后&#xff0c;觉得使用正确的指法打字是很必要的。 练习打字&#xff0c;掌握正确的键盘指法十分关键。 练习打字的第一步是找到…

ElasticSearch批处理

在刚才的新增当中&#xff0c;我们是一次新增一条数据。那么如果你将来的数据库里有数千上万的数据&#xff0c;你一次新增一个&#xff0c;那得多麻烦。所以我们还要学习一下批量导入功能。 也就是说批量的把数据库的数据写入索引库。那这里的需求是&#xff0c;首先利用mybat…

C++中布隆过滤器

&#x1f436;博主主页&#xff1a;ᰔᩚ. 一怀明月ꦿ ❤️‍&#x1f525;专栏系列&#xff1a;线性代数&#xff0c;C初学者入门训练&#xff0c;题解C&#xff0c;C的使用文章&#xff0c;「初学」C&#xff0c;linux &#x1f525;座右铭&#xff1a;“不要等到什么都没有了…