手把手教你實(shí)現(xiàn)一個(gè)Vue無限級(jí)聯(lián)樹形表格(增刪改)
前言
平時(shí)我們可能在做項(xiàng)目時(shí),會(huì)遇到一個(gè)業(yè)務(wù)邏輯。實(shí)現(xiàn)一個(gè)無限級(jí)聯(lián)樹形表格,什么叫做無限級(jí)聯(lián)樹形表格呢?就是下圖所展示的內(nèi)容,有一個(gè)祖元素,然后下面可能有很多子孫元素,你可以實(shí)現(xiàn)添加、編輯、刪除這樣幾個(gè)功能。
資源
JavaScript框架:vue.js
UI框架:Element UI
源碼
這里需要重點(diǎn)說明的是,主要使用了遞歸的算法以及給數(shù)據(jù)標(biāo)識(shí)的重要性。詳細(xì)說明可以在源碼中查看注釋,也可以通過刪改代碼融會(huì)貫通。
<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="請(qǐng)選擇位置"
@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="請(qǐng)選擇子位置"
: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="請(qǐng)輸入名稱"
></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="請(qǐng)選擇位置"
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="請(qǐng)選擇子位置"
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="請(qǐng)輸入名稱"
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: '請(qǐng)輸入名稱', trigger: 'blur' }
]
},
locationData: [
{
id: 1,
name: '頂'
},
{
id: 2,
name: '子'
}
],
tableData: []
};
},
methods: {
// 監(jiān)聽關(guān)閉窗口
closeView() {
this.$refs['form'].resetFields(); // 關(guān)閉窗口,清空填寫的內(nèi)容
},
// 打開編輯
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;
},
// 遞歸表格數(shù)據(jù)(編輯)
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;
}
});
},
// 遞歸表格數(shù)據(jù)(刪除)
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(`此操作將刪除該項(xiàng), 是否繼續(xù)?`, '提示', {
confirmButtonText: '確定',
cancelButtonText: '取消',
type: 'warning'
})
.then(() => {
if (item.children.length != 0) {
this.$message.warning({
message: '請(qǐng)刪除子節(jié)點(diǎn)',
duration: 1000
});
} else {
this.casArr = item.childArr;
++this.isResouceShow; // 給級(jí)聯(lián)控件綁定一個(gè)key,防止報(bào)錯(cuò)。
if (item.value.length == 1) { // 刪除的是頂節(jié)點(diǎn)
console.log(1);
this.tableData.splice(item.value, 1);
this.$message({
type: 'success',
message: '刪除成功'
});
} else { // 刪除的是子節(jié)點(diǎn)
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;
},
// 遞歸表格數(shù)據(jù)(添加)
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>
作者:Vam的金豆之路
主要領(lǐng)域:前端開發(fā)
我的微信:maomin9761
微信公眾號(hào):前端歷劫之路