手把手教你實(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):前端歷劫之路