我用Vue.js与ElementUI搭建了一个无限级联层级表格组件
前言
今天,回老家了。第一件事就是回家把大屏安排上,写作的感觉太爽了,终于可以专心地写文章了。我们今天要做的项目是怎么样搭建一个无限级联层级表格组件,好了,多了不多说,赶快行动起来吧!
项目一览
到底是啥样子来?我们来看下。
正如你所看到的那样,这个组件涉及添加、删除、编辑功能,并且可以无限级嵌套。那么怎样实现的?我们来看下。
源码
直接给出源码,就是这么直接。
<template>
<div class="container">
<el-button
type="primary"
size="small"
@click="handleCreate"
icon="el-icon-circle-plus-outline"
style="margin: 10px 0"
>添加el-button
>
<el-table
:data="tableData"
style="width: 100%; margin-bottom: 20px"
border
row-key="value"
stripe
size="medium"
:tree-props="{ children: 'children' }"
>
<el-table-column prop="label" label="标签名称"> el-table-column>
<el-table-column prop="location" label="层级"> el-table-column>
<el-table-column label="操作" :align="alignDir" width="180">
<template slot-scope="scope">
<el-button
type="text"
size="small"
@click="handleUpdate(scope.row)"
>编辑el-button
>
<el-button
type="text"
size="small"
@click="deleteClick(scope.row)"
>删除el-button
>
template>
el-table-column>
el-table>
<el-dialog
:title="textMap[dialogStatus]"
:visible.sync="dialogFormVisible"
width="30%"
>
<el-form
ref="dataForm"
:rules="rules"
:model="temp"
label-position="left"
label-width="120px"
style="margin-left: 50px"
>
<el-form-item
label="层级:"
prop="location"
v-if="dialogStatus !== 'update'"
>
<el-select
v-model="temp.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 && dialogStatus !== 'update'"
label="子位置:"
prop="children"
>
<el-cascader
size="small"
:key="isResouceShow"
v-model="temp.children"
placeholder="请选择子位置"
:label="'label'"
:value="'value'"
:options="tableData"
:props="{ checkStrictly: true }"
clearable
@change="getCasVal"
>el-cascader>
el-form-item>
<el-form-item label="标签名称:" prop="label">
<el-input
v-model="temp.label"
size="small"
autocomplete="off"
placeholder="请输入标签名称"
>el-input>
el-form-item>
el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="dialogFormVisible = false" size="small">
取消
el-button>
<el-button
type="primary"
size="small"
@click="
dialogStatus === 'create' ? createData() : updateData()
"
>
确认
el-button>
div>
el-dialog>
div>
template>
<script>
export default {
name: 'Tag',
data() {
return {
alignDir: 'center',
textMap: {
update: '编辑',
create: '添加',
},
dialogStatus: '',
dialogFormVisible: false,
temp: {},
isResouceShow: 1,
sonStatus: false,
casArr: [],
idx: '',
childKey: [],
rules: {
location: [
{
required: true,
message: '请选择层级',
trigger: 'blur',
},
],
label: [
{ required: true, message: '请输入名称', trigger: 'blur' },
],
children: [
{
required: true,
message: '请选择子位置',
trigger: 'blur',
},
],
},
locationData: [
{
id: '1',
name: '顶',
},
{
id: '2',
name: '子',
},
],
tableData: [
{
tagId: '1', // 标签id
label: '第0', // 标签名称
parent: '', // 父级名称
location: '1', // 层级
value: '0', // 标识位
children: [
{
tagId: '1', // 子标签id
childKey: ['0', '0'], // 子标识位
label: '第0-0',
parent: '第0',
location: '2',
value: '0-0',
children: [],
},
{
tagId: '2', // 子标签id
childKey: ['0', '1'],
label: '第0-1',
parent: '第0',
location: '2',
value: '0-1',
children: [],
},
],
},
]
};
},
methods: {
// 递归寻找同级
findSameTable(arr, i, casArr) {
if (i == casArr.length - 1) {
return arr;
} else {
return this.findTable(
arr[casArr[i].substr(casArr[i].length - 1, 1)].children,
(i += 1),
casArr
);
}
},
// 寻找父级
findTable(arr, i, casArr) {
if (i == casArr.length - 1) {
let index = casArr[i].substr(casArr[i].length - 1, 1);
return arr[index];
} else {
return this.findTable(
arr[casArr[i].substr(casArr[i].length - 1, 1)].children,
(i += 1),
casArr
);
}
},
// 递归表格数据(添加)
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)
);
}
},
// 递归表格数据(编辑)
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.temp);
} else {
return this.findSd(
arr[casArr[i].substr(casArr[i].length - 1, 1)].children,
(i += 1),
casArr
);
}
},
// 递归寻找同步名称
findLable(arr, i, casArr) {
if (i == casArr.length - 1) {
let index = casArr[i].substr(casArr[i].length - 1, 1);
return arr[index];
} else {
return this.findLable(
arr[casArr[i].substr(casArr[i].length - 1, 1)].children,
(i += 1),
casArr
);
}
},
// 同步子名称
useChildLable(arr) {
if (arr !== []) {
arr.forEach((item) => {
item.parent = this.temp.label;
});
}
},
// 递归表格数据(删除)
findDel(arr, i, item) {
let casArr = item.childKey;
if (i == casArr.length - 2) {
let index = casArr[i].substr(casArr[i].length - 1, 1);
arr[index].children.forEach((it, ix, arrs) => {
if (it == item) {
return arrs.splice(ix, 1);
}
});
} else {
return this.findDel(
arr[casArr[i].substr(casArr[i].length - 1, 1)].children,
(i += 1),
item
);
}
},
// 置空
resetTemp() {
this.temp = {};
},
// 打开添加
handleCreate() {
this.resetTemp();
this.dialogFormVisible = true;
this.dialogStatus = 'create';
this.$nextTick(() => {
this.$refs['dataForm'].clearValidate();
});
},
// 添加
createData() {
this.$refs['dataForm'].validate((valid) => {
if (valid) {
if (this.sonStatus == false) {
this.temp.value = String(this.tableData.length);
const obj = Object.assign({}, this.temp);
obj.children = [];
obj.parent = '';
this.tableData.push(obj);
this.$message({
type: 'success',
message: '添加成功',
});
this.dialogFormVisible = false;
} else {
let arr = this.find(this.tableData, 0);
this.temp.value =
String(this.casArr[this.casArr.length - 1]) +
'-' +
String(arr.length);
delete this.temp.children;
const obj = Object.assign({}, this.temp);
obj.children = [];
obj.childKey = [...this.casArr, String(arr.length)];
obj.parent = this.findTable(
this.tableData,
0,
this.casArr
).label;
if (this.temp.location === '2') {
obj.location = String(
[...this.casArr, String(arr.length)].length
);
}
arr.push(obj);
this.$message({
type: 'success',
message: '添加成功',
});
this.dialogFormVisible = false;
}
} else {
return false;
}
});
},
// 打开更新
handleUpdate(row) {
console.log(row);
row.value.length != 1
? (this.sonStatus = true)
: (this.sonStatus = false);
this.temp = Object.assign({}, row); // copy obj
if (row.childKey) {
this.childKey = row.childKey;
this.idx = row.childKey[row.childKey.length - 1];
} else {
this.idx = row.value;
}
console.log(this.idx);
this.dialogStatus = 'update';
this.dialogFormVisible = true;
this.$nextTick(() => {
this.$refs['dataForm'].clearValidate();
});
},
// 更新
updateData() {
this.$refs['dataForm'].validate((valid) => {
if (valid) {
if (this.temp.location === '1') {
console.log(this.temp);
this.tableData.splice(this.idx, 1, this.temp);
this.useChildLable(this.tableData[this.idx].children);
this.$message({
type: 'success',
message: '编辑成功',
});
this.dialogFormVisible = false;
} else {
this.findSd(this.tableData, 0, this.childKey);
this.useChildLable(
this.findLable(this.tableData, 0, this.childKey)
.children
);
this.$message({
type: 'success',
message: '编辑成功',
});
this.dialogFormVisible = false;
}
} else {
return false;
}
});
},
// 删除父级节点
deleteParent(item) {
this.tableData.forEach((it, ix, arrs) => {
if (it == item) {
return arrs.splice(ix, 1);
}
});
},
// 删除
deleteClick(item) {
this.$confirm(`此操作将删除该标签, 是否继续?`, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
})
.then(() => {
if (item.children.length != 0) {
this.$message.warning({
message: '请删除子节点',
duration: 1000,
});
} else {
++this.isResouceShow;
if (item.value.length == 1) {
this.deleteParent(item);
this.$message({
type: 'success',
message: '删除成功',
});
} else {
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;
},
},
};
script>
代码可以直接拿来用,但是要注意事先要安装下ElementUI框架。无限层级的核心算法是递归算法,掌握了这一点,任何难题都可以解决。
下面,我们就这个项目来回顾下前端中的递归算法。
递归简而言之就是函数调用自己。递归算法中有两个条件:基线条件和递归条件。基线条件用于控制递归啥时候暂停,而递归条件是控制调用自己的方式。
最简单的一个例子是5的阶乘。
var func = function(i){
if(i === 1){
return 1;
}else{
return i*func(i-1);
}
}
func(5);
这样就很简单的实现了一个递归算法,我们将上述例子拆解下。
// 递
5*func(4);
5*4*func(3);
5*4*3*func(2);
5*4*3*2*func(1);
// 归
5*4*3*2*1;
5*4*3*2;
5*4*6;
5*24;
120
递归其实可以理解成两个操作递与归。可以这样比喻,比如你在做一道数学题时,有一个知识点你不懂,你需要查资料。但是,通过查资料你发现这个知识点中你又有另一个不明白的知识点,你又开始继续查,直到你没有不懂的知识点,这样递的操作已经完成。然后,你把已经查过的这些知识点又从尾到头复习了一遍,这样归的操作已经完成。最后,你明白了最初那个知识点。
结语
最后,在知乎上看到一篇文章觉得比较好,分享下。
对于刚工作的年轻人,我觉得技术重要:你以后很可能会跳槽到不同业务方向的公司,这时原先公司的业务知识没用了,而技术是不分公司、国界的~ 可能中国国情特殊,还没形成德国或美国硅谷所谓的工程师文化环境吧;我一向认为:技术人优先锻炼技术,比业务熟悉程度我们能比得过产品、运营、领导吗?招你的是技术岗位,就先把技术弄好,技术才是我们技术人的安身立命之本~ 当然,业务也很重要,不了解业务瞎钻技术,就像个无头苍蝇,无法根据业务场景选择合适够用、省力的技术,做事就会事倍功半~
摘自--知乎大牛(刀剑红叶)
欢迎关注我的公众号 前端历劫之路
回复关键词 电子书
,即可获取12本前端热门电子书。回复关键词 红宝书第4版
,即可获取最新《JavaScript高级程序设计》(第四版)电子书。我创建了一个技术交流、文章分享群,群里有很多大厂的前端大佬,关注公众号后,点击下方菜单了解更多即可加我微信,期待你的加入。