通过动态列表学习数据思维
刀刀
4/7/2025
0 字
0 分钟
需求效果
如上图所示,可以同时勾选、取消勾选展示对应列的表格,有一部分的列允许显示隐藏,一部分的列不允许显示隐藏。同时能本地存储上次的配置、表格内容支持通过 slot
自定义的需求。
实现思路
实现该效果有两个方案,方案一是创建一个数组对象,每一项是一个列的配置对象,如下方代码所示:
const columnsConfig = [
{
label: "姓名",
prop: "name",
canHidden: true,
},
{
label: "年龄",
prop: "age",
canHidden: true,
},
{
label: "地址",
prop: "address",
canHidden: true,
},
];
通过 v-for
循环数组,渲染每一列。但是这么做有一个缺点,如果到时候不实现某些东西,比如不需要某个配置,则需要直接修改数组对象,后面如果又需要了就找不回来了,会随着操作丢失一部分。因此该方案不适用。
方案二通过两个配置共同决定,columnConfig
是一个不会改变的对象(与方案一直接修改数组配置不同),只用来决定每一列的配置项情况,再新增一个 state
对象,用于定义每一列的显隐。代码如下:
const columnsConfig = {
name: {
label: "姓名",
canHidden: true,
},
age: {
label: "年龄",
canHidden: true,
},
address: {
label: "地址",
canHidden: true,
},
};
const state = {
name: true,
age: true,
address: false,
};
实现效果
初步效果
根据上方思路的方案二,通过 v-for
循环 state
对象, v-if
判断 state
对象的值来决定是否显示该列。这样即使不显示该列,该列的配置信息也不会丢失,可以随时恢复显示。
const columnsConfig = {
id: {
label: "id",
prop: "id",
canHidden: false, // 不可取消勾选
},
name: {
label: "姓名",
prop: "name",
canHidden: false, // 不可取消勾选
},
age: {
label: "年纪",
prop: "age",
canHidden: true,
},
mingzu: {
label: "名族",
prop: "mingzu",
canHidden: true,
},
};
const state = reactive({}); // 是否显示该列
Object.keys(columnsConfig).forEach((key) => {
state[key] = true;
});
const data = [
{
id: 1,
name: "张三",
age: 18,
mingzu: "汉族",
},
{
id: 2,
name: "李四",
age: 19,
mingzu: "回族",
},
];
<template>
<!-- 表格列的显隐 -->
<div v-for="item in Object.keys(state)" :key="item">
<span>{{ columnsConfig[item].label }}</span>
<el-checkbox
:disabled="!columnsConfig[item].canHidden"
v-model="state[item]"
></el-checkbox>
</div>
<!-- 表格 -->
<el-table :data="data" width="100%">
<template v-for="item in Object.keys(state)" :key="item">
<el-table-column v-if="state[item]" v-bind="columnsConfig[item]">
<!-- 表头,自定义label和复选框 -->
<template #header>
<div>
<span>{{ columnsConfig[item].label }}</span>
<el-checkbox
:disabled="!columnsConfig[item].canHidden"
v-model="state[item]"
/>
</div>
</template>
</el-table-column>
</template>
</el-table>
</template>
现在初步效果已经实现了。
列动态内容
动态列内容效果比如年龄,小于 18 岁显示红色,反之显示绿色。实现方案有三种,方案一是通过 v-if
判断,是否为年龄,为年龄则设置对应的效果。
<template #default="{ row }">
<div
v-if="columnsConfig[item].prop === 'age'"
:style="{ color: row.age < 18 ? 'red' : 'green' }"
></div>
<div v-if="columnsConfig[item].prop === 'name'"></div>
</template>
这个方法前期还好,后续维护如果需要很多种效果,则会有很多 v-if
判断,代码会显得很冗余。
方案二是把每一个需要自定义内容的 DOM 单独抽离出来封装为一个子组件,修改 columnConfig
对象,新增一个 render
属性,绑定对应的子组件。这样只需要 v-if
判断当前对象有没有 render
属性,有则渲染对应的子组件。
import AgeComponent from "./AgeComponent.vue";
const columnsConfig = {
id: {
label: "id",
prop: "id",
canHidden: false, // 不可取消勾选
},
name: {
label: "姓名",
prop: "name",
canHidden: false, // 不可取消勾选
},
age: {
label: "年纪",
prop: "age",
canHidden: true,
render: AgeComponent, // 自定义渲染组件
},
mingzu: {
label: "名族",
prop: "mingzu",
canHidden: true,
},
};
<template>
<!-- 表格列的显隐 -->
<div v-for="item in Object.keys(state)" :key="item">
<span>{{ columnsConfig[item].label }}</span>
<el-checkbox :disabled="!columnsConfig[item].canHidden" v-model="state[item]"></el-checkbox>
</div>
<!-- 表格 -->
<el-table :data="data" width="100%">
<template v-for="item in Object.keys(state)" :key="item">
<el-table-column v-if="state[item]" v-bind="columnsConfig[item]">
<!-- 表头,自定义label和复选框 -->
<template #header>
<div>
<span>{{ columnsConfig[item].label }}</span>
<el-checkbox :disabled="!columnsConfig[item].canHidden" v-model="state[item]" />
</div>
</template>
<template v-if="columnsConfig[item].render"" #default="{row}">
<component :is="columnsConfig[item].render" :row="row"></component>
</template>
</el-table-column>
</template>
</el-table>
</template>
这个方案比起第一个方案,虽然需要新增很多子组件维护,但是父组件不再有过多的 v-if
判断,减少代码的冗余量。
第三种方法是通过 render
或 JSX
的方法来实现,这里采取 render
的方法。首先与方法二一样,需要创建一个子组件,接收父组件的参数和 render
函数,返回的是 render
方法和 h
方法创建的 DOM。父组件的 columnConfig
对象新增 render
属性,值是一个函数,传递三个参数,参数一是该 DOM 的标签,参数二是一个对象,该 DOM 的元素(如类名、id 等),参数三是标签的文本。父组件的 el-table-column
组件的 #default
插槽引入子组件后,传递 row
和对应属性的 render
参数。
import TableSlot from "./TableSlot.vue";
const columnsConfig = {
id: {
label: "id",
prop: "id",
canHidden: false, // 不可取消勾选
},
name: {
label: "姓名",
prop: "name",
canHidden: false, // 不可取消勾选
},
age: {
label: "年纪",
prop: "age",
canHidden: true,
render: (h, row) => {
return h(
"div",
{
style: {
color: row.age < 18 ? "red" : "green",
},
},
row.age
);
},
},
mingzu: {
label: "名族",
prop: "mingzu",
canHidden: true,
},
};
<template>
<!-- 表格列的显隐 -->
<div v-for="item in Object.keys(state)" :key="item">
<span>{{ columnsConfig[item].label }}</span>
<el-checkbox
:disabled="!columnsConfig[item].canHidden"
v-model="state[item]"
></el-checkbox>
</div>
<!-- 表格 -->
<el-table :data="data" width="100%">
<template v-for="item in Object.keys(state)" :key="item">
<el-table-column v-if="state[item]" v-bind="columnsConfig[item]">
<!-- 表头,自定义label和复选框 -->
<template #header>
<div>
<span>{{ columnsConfig[item].label }}</span>
<el-checkbox
:disabled="!columnsConfig[item].canHidden"
v-model="state[item]"
/>
</div>
</template>
<template v-if="columnsConfig[item].render" #default="{ row }">
<TableSlot
:row="row"
:render="columnsConfig[item].render"
></TableSlot>
</template>
</el-table-column>
</template>
</el-table>
</template>
<script>
import { ref, h } from "vue";
export default {
props: ["render", "row"],
setup(props) {
return () => {
return props.render(h, props.row);
};
},
};
</script>
题外话
这里的 render
返回的是一个方法,查看官方文档关于 h
方法的代码是 return h('div', 123)
,实际上相当于以下代码的缩写:
return () => {
const vnode = h("div", 123);
return vnode;
};
本地存储
本地存储效果实现可以在最开始获取本地存储的数据。后续 state
每次更新时,都要调用方法更新本地存储。
function setStateStroe(state) {
localStorage.setItem("table_state", JSON.stringify(state));
}
const state = reactive({}); // 是否显示该列
if (localStorage.getItem("table_state")) {
state = reactive(JSON.parse(localStorage.getItem("table_state")));
}
else {
Object.keys(columnsConfig).forEach((key) => {
state[key] = true;
});
}
watchEffect(() => {
setStateStroe(state);
});
完整代码
<script setup>
import TableSlot from "./TableSlot.vue";
const columnsConfig = {
id: {
label: "id",
prop: "id",
canHidden: false, // 不可取消勾选
},
name: {
label: "姓名",
prop: "name",
canHidden: false, // 不可取消勾选
},
age: {
label: "年纪",
prop: "age",
canHidden: true,
render: (h, row) => {
return h(
"div",
{
style: {
color: row.age < 18 ? "red" : "green",
},
},
row.age
);
},
},
mingzu: {
label: "名族",
prop: "mingzu",
canHidden: true,
},
};
function setStateStroe(state) {
localStorage.setItem("table_state", JSON.stringify(state));
}
const state = reactive({}); // 是否显示该列
if (localStorage.getItem("table_state")) {
state = reactive(JSON.parse(localStorage.getItem("table_state")));
} else {
Object.keys(columnsConfig).forEach((key) => {
state[key] = true;
});
}
watchEffect(() => {
setStateStroe(state);
});
const data = [
{
id: 1,
name: "张三",
age: 18,
mingzu: "汉族",
},
{
id: 2,
name: "李四",
age: 19,
mingzu: "回族",
},
];
</script>
<template>
<!-- 表格列的显隐 -->
<div v-for="item in Object.keys(state)" :key="item">
<span>{{ columnsConfig[item].label }}</span>
<el-checkbox
:disabled="!columnsConfig[item].canHidden"
v-model="state[item]"
></el-checkbox>
</div>
<!-- 表格 -->
<el-table :data="data" width="100%">
<template v-for="item in Object.keys(state)" :key="item">
<el-table-column v-if="state[item]" v-bind="columnsConfig[item]">
<!-- 表头,自定义label和复选框 -->
<template #header>
<div>
<span>{{ columnsConfig[item].label }}</span>
<el-checkbox
:disabled="!columnsConfig[item].canHidden"
v-model="state[item]"
/>
</div>
</template>
<template v-if="columnsConfig[item].render" #default="{ row }">
<TableSlot
:row="row"
:render="columnsConfig[item].render"
></TableSlot>
</template>
</el-table-column>
</template>
</el-table>
</template>
<script>
import { ref, h } from "vue";
export default {
props: ["render", "row"],
setup(props) {
return () => {
return props.render(h, props.row);
};
},
};
</script>