您现在的位置是:首页 > Javascript
当前栏目
手把手教你实现一个Vue无限级联树形表格(增删改)
2023-03-14 09:39:23 时间
本文转载自微信公众号「 前端历劫之路」,作者maomin9761。转载本文请联系 前端历劫之路公众号。
前言
平时我们可能在做项目时,会遇到一个业务逻辑。实现一个无限级联树形表格,什么叫做无限级联树形表格呢?就是下图所展示的内容,有一个祖元素,然后下面可能有很多子孙元素,你可以实现添加、编辑、删除这样几个功能。
资源
- JavaScript框架:vue.js
- UI框架:Element UI
源码
这里需要重点说明的是,主要使用了递归的算法以及给数据标识的重要性。详细说明可以在源码中查看注释,也可以通过删改代码融会贯通。
- <template>
- <div class="container">
- <div class="btn-r">
- <el-button
- type="primary"
- size="small"
- @click="addView = true"
- icon="el-icon-circle-plus-outline"
- class="add"
- >添加</el-button
- >
- </div>
- <el-table
- :data="tableData"
- style="width: 100%; margin-bottom: 20px"
- row-key="value"
- border
- default-expand-all
- size="medium"
- :tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
- >
- <el-table-column prop="label" label="名称" sortable>
- </el-table-column>
- <el-table-column label="操作" align="center" width="180">
- <template slot-scope="scope">
- <el-button
- type="text"
- size="small"
- @click="handleClick(scope.row, scope.$index)"
- >编辑</el-button
- >
- <el-button
- type="text"
- size="small"
- @click="deleteClick(scope.row, scope.$index)"
- >删除</el-button
- >
- </template>
- </el-table-column>
- </el-table>
- <!-- 添加窗口 -->
- <el-dialog
- title="添加"
- :visible.sync="addView"
- :close-on-click-modal="false"
- width="30%"
- @close="closeView"
- >
- <el-form :model="form" ref="form" :rules="rules">
- <el-form-item
- label="位置"
- :label-width="formLabelWidth"
- prop="location"
- >
- <el-select
- v-model="form.location"
- placeholder="请选择位置"
- @change="locationChange"
- size="small"
- >
- <el-option
- v-for="item in locationData"
- :key="item.id"
- :label="item.name"
- :value="item.id"
- />
- </el-select>
- </el-form-item>
- <el-form-item
- v-if="sonStatus"
- label="子位置"
- :label-width="formLabelWidth"
- prop="childArr"
- >
- <el-cascader
- size="small"
- :key="isResouceShow"
- v-model="form.childArr"
- placeholder="请选择子位置"
- :label="'name'"
- :value="'id'"
- :options="tableData"
- :props="{ checkStrictly: true }"
- clearable
- @change="getCasVal"
- ></el-cascader>
- </el-form-item>
- <el-form-item
- label="名称"
- :label-width="formLabelWidth"
- prop="label"
- >
- <el-input
- v-model="form.label"
- size="small"
- autocomplete="off"
- placeholder="请输入名称"
- ></el-input>
- </el-form-item>
- </el-form>
- <span slot="footer" class="dialog-footer">
- <el-button @click="addView = false" size="small"
- >取 消</el-button
- >
- <el-button type="primary" @click="okAdd('form')" size="small"
- >确 定</el-button
- >
- </span>
- </el-dialog>
- <!-- 编辑窗口 -->
- <el-dialog
- title="编辑"
- :visible.sync="editView"
- :close-on-click-modal="false"
- width="30%"
- >
- <el-form :model="data" ref="data" :rules="rules">
- <el-form-item
- label="位置"
- :label-width="formLabelWidth"
- prop="location"
- >
- <el-select
- v-model="data.location"
- placeholder="请选择位置"
- size="small"
- @change="locationChange"
- >
- <el-option
- v-for="item in locationData"
- :key="item.id"
- :label="item.name"
- :value="item.id"
- />
- </el-select>
- </el-form-item>
- <el-form-item
- v-if="sonStatus"
- label="子位置"
- :label-width="formLabelWidth"
- prop="childArr"
- >
- <el-cascader
- :key="isResouceShow"
- v-model="data.childArr"
- placeholder="请选择子位置"
- size="small"
- :label="'name'"
- :value="'id'"
- :options="tableData"
- :props="{ checkStrictly: true }"
- clearable
- @change="getCasVal"
- ></el-cascader>
- </el-form-item>
- <el-form-item
- label="名称"
- :label-width="formLabelWidth"
- prop="label"
- >
- <el-input
- v-model="data.label"
- autocomplete="off"
- placeholder="请输入名称"
- size="small"
- ></el-input>
- </el-form-item>
- </el-form>
- <span slot="footer" class="dialog-footer">
- <el-button @click="editView = false" size="small"
- >取 消</el-button
- >
- <el-button type="primary" @click="okEdit('data')" size="small"
- >确 定</el-button
- >
- </span>
- </el-dialog>
- </div>
- </template>
- <script>
- export default {
- name: 'Tag',
- data() {
- return {
- location: '',
- isResouceShow: 1,
- addView: false,
- sonStatus: false,
- editView: false,
- casArr: [],
- childArr: [],
- form: {},
- data: {},
- idx: '',
- childkey: [],
- formLabelWidth: '80px',
- rules: {
- label: [
- { required: true, message: '请输入名称', trigger: 'blur' }
- ]
- },
- locationData: [
- {
- id: 1,
- name: '顶'
- },
- {
- id: 2,
- name: '子'
- }
- ],
- tableData: []
- };
- },
- methods: {
- // 监听关闭窗口
- closeView() {
- this.$refs['form'].resetFields(); // 关闭窗口,清空填写的内容
- },
- // 打开编辑
- handleClick(item, index) {
- item.value.length != 1
- ? (this.sonStatus = true)
- : (this.sonStatus = false);
- this.editView = true;
- const obj = Object.assign({}, item);
- this.childkey = item.childkey;
- this.casArr = item.childArr;
- this.idx = index;
- this.data = obj;
- },
- // 递归表格数据(编辑)
- findSd(arr, i, casArr) {
- if (i == casArr.length - 1) {
- let index = casArr[i].substr(casArr[i].length - 1, 1);
- return arr.splice(index, 1, this.data);
- } else {
- return this.findSd(
- arr[casArr[i].substr(casArr[i].length - 1, 1)].children,
- (i += 1),
- casArr
- );
- }
- },
- // 确定编辑
- okEdit(data) {
- this.$refs[data].validate(valid => {
- if (valid) {
- if (this.data.value.length == 1) {
- this.tableData.splice(this.idx, 1, this.data);
- this.$message({
- type: 'success',
- message: '编辑成功'
- });
- this.editView = false;
- } else {
- this.findSd(this.tableData, 0, this.childkey);
- this.$message({
- type: 'success',
- message: '编辑成功'
- });
- this.editView = false;
- }
- } else {
- return false;
- }
- });
- },
- // 递归表格数据(删除)
- findDel(arr, i, item) {
- let casArr = item.childkey;
- if (i == casArr.length - 1) {
- let index = casArr[i].substr(casArr[i].length - 1, 1);
- return arr.splice(index, 1);
- } else {
- return this.findDel(
- arr[casArr[i].substr(casArr[i].length - 1, 1)].children,
- (i += 1),
- item
- );
- }
- },
- // 删除
- deleteClick(item) {
- this.$confirm(`此操作将删除该项, 是否继续?`, '提示', {
- confirmButtonText: '确定',
- cancelButtonText: '取消',
- type: 'warning'
- })
- .then(() => {
- if (item.children.length != 0) {
- this.$message.warning({
- message: '请删除子节点',
- duration: 1000
- });
- } else {
- this.casArr = item.childArr;
- ++this.isResouceShow; // 给级联控件绑定一个key,防止报错。
- if (item.value.length == 1) { // 删除的是顶节点
- console.log(1);
- this.tableData.splice(item.value, 1);
- this.$message({
- type: 'success',
- message: '删除成功'
- });
- } else { // 删除的是子节点
- console.log(2);
- this.findDel(this.tableData, 0, item);
- this.$message({
- type: 'success',
- message: '删除成功'
- });
- }
- }
- })
- .catch(err => {
- console.log(err);
- this.$message({
- type: 'info',
- message: '已取消删除'
- });
- });
- },
- // 是否显示次位置
- locationChange(v) {
- if (v == 2) {
- this.sonStatus = true;
- } else {
- this.sonStatus = false;
- }
- },
- // 获取次位置
- getCasVal(v) {
- this.casArr = v;
- this.form.childArr = v;
- },
- // 递归表格数据(添加)
- find(arr, i) {
- if (i == this.casArr.length - 1) {
- return arr[this.casArr[i].substr(this.casArr[i].length - 1, 1)]
- .children;
- } else {
- return this.find(
- arr[this.casArr[i].substr(this.casArr[i].length - 1, 1)]
- .children,
- (i += 1)
- );
- }
- },
- // 确定添加
- okAdd(form) {
- this.$refs[form].validate(valid => {
- if (valid) {
- if (this.sonStatus == false) {
- this.form.value = String(this.tableData.length);
- const obj = Object.assign({}, this.form);
- obj.children = [];
- obj.childArr = [];
- this.tableData.push(obj);
- this.$message({
- type: 'success',
- message: '添加成功'
- });
- this.addView = false;
- } else {
- let arr = this.find(this.tableData, 0);
- this.childArr = [...this.casArr, String(arr.length)];
- this.form.value =
- String(this.casArr[this.casArr.length - 1]) +
- String(arr.length);
- delete this.form.children;
- const obj = Object.assign({}, this.form);
- obj.children = [];
- obj.childkey = [...this.casArr, String(arr.length)];
- arr.push(obj);
- this.$message({
- type: 'success',
- message: '添加成功'
- });
- this.addView = false;
- }
- } else {
- return false;
- }
- });
- }
- }
- };
- </script>
- <style lang="scss" scoped>
- ::v-deep .el-form-item__content {
- width: 203px;
- }
- </style>
相关文章
- 鲜为人知但很有用的 HTML 属性
- 翻转再翻转!有意思的水平横向溢出滚动
- 自定义计数器小技巧!CSS 实现长按点赞累加动画
- 过五关!React高频面试题指南
- 软件开发中的十个认知偏差
- 不需要 JS!仅用 CSS 也能达到监听页面滚动的效果!
- 一文读懂TypeScript类型兼容性
- Vue 的响应式原则与双向数据绑定
- 快速掌握 TypeScript 新语法:Infer Extends
- JWT教你如何证明你是我的人!
- 一篇带给你 V8 GC 的实现
- 面试官:请使用JS完成一个LRU缓存?
- 通过可视化来学习JavaScript事件循环
- 新的跨域策略:使用 COOP、COEP 为浏览器创建更安全的环境
- 为什么有人说 vite 快,有人却说 vite 慢?
- 种草 Vue3 中几个好玩的插件和配置
- 超全面的前端工程化配置指南
- Vue 状态管理未来样子
- Volatile关键字能保证原子性么?
- 面试突击:SpringBoot 有几种读取配置文件的方法?