本文转载自微信公众号「 前端历劫之路」,作者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>