简单实现一个列配置
发表于:2024-05-16 |

前言

简单记录一下自己写的列配置的功能,实现列的显示与隐藏,固定列,列顺序的功能。

简单写个 table

我先简单写个 table 列表,代码大概如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
<template>
<t-table :data="data" :columns="columns"></t-table>
</template>
<script setup lang="ts">
const columns = [
{
colKey: "name",
title: "姓名",
},
{
colKey: "age",
title: "年龄",
},
{
colKey: "gender",
title: "性别",
},
{
colKey: "address",
title: "地址",
},
{
colKey: "phone",
title: "电话",
},
];

const data = [
{
name: "张三",
age: 18,
address: "北京",
gender: "男",
phone: "123456789",
},
{
name: "李四",
age: 20,
address: "上海",
gender: "女",
phone: "1234567890",
},
{
name: "王五",
age: 22,
address: "广州",
gender: "女",
phone: "1234567891",
},
{
name: "赵六",
age: 24,
address: "深圳",
gender: "男",
phone: "1234567892",
},
{
name: "孙七",
age: 26,
address: "杭州",
gender: "男",
phone: "1234567894",
},
];
</script>

简易table

前置安装

这里我们先安装一下vue用来拖动的组件vue-draggable-next

绘制样式

这里我就不多阐述了,直接贴代码了

列配置的button

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
<template>
<t-popup
placement="bottom"
destroy-on-close
trigger="click"
:visible="popupVisible"
>
<slot>
<t-button @click="onToggle" theme="default" v-bind="buttonOptions">
<template #icon>
<SettingIcon />
</template>
列配置
</t-button>
</slot>
<template #content>
<Draggable
:columns-list="displayColumns"
@changeCloumnsConfig="changeCloumnsConfig"
@resetColumns="resetColumns"
@submitColumns="submitColumns"
></Draggable>
</template>
</t-popup>
</template>
<script setup lang="ts">
import { ref, onMounted, reactive, nextTick, computed } from "vue";
import Draggable from "./column-set.vue";
import { SettingIcon } from "tdesign-icons-vue-next";
import { COLUMNS_SHOW_FORMAT } from "./type";
import { debounce, isEmpty } from "lodash";


const props = defineProps<{
columnsList: any[];
buttonOptions?: any;
name: string;
}>();

const displayColumns = ref<COLUMNS_SHOW_FORMAT[]>([]);

// 获取localStorage中的列配置
const localKey = computed(() => {
return `userColumnsSet_${props.name}`;
});

const emit = defineEmits(["changeColumns"]);

// 初始化存储列数据
const saveColumnsData = () => {

};

const initColumns = () => {
displayColumns.value = props.columnsList.map((item)=>{
return {
name:item.title,
check:item.check??true,
isFixed:item.isFixed??false
}
})
};

onMounted(() => {
initColumns();
});

const curColumnData = reactive({
fixedData: [], // 固定列数据
unCheckData: [], // 隐藏列数据
order: [], // 列顺序
});

const changeCloumnsConfig = (list: COLUMNS_SHOW_FORMAT[]) => {

};

const popupVisible = ref(false);

const onToggle = debounce(() => {
popupVisible.value = !popupVisible.value;
}, 100);

// 子组件提交确认
const submitColumns = () => {
popupVisible.value = false;
saveUserColumns();
};

// 重置列
const resetColumns = async () => {

};
// 保存列
const saveUserColumns = async () => {

};

defineExpose({
onToggle
})
</script>

列配置button点击展开的详情

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
<template>
<div>
<div class="tip-div" style="justify-content: space-around; margin: 5px 0">
<t-button size="small" @click="resetColumns"> 重置 </t-button>
<t-tooltip theme="danger">
<template #content>
<div>1.鼠标长按可以拖动列的位置</div>
<div>2.点击左侧框可控制列是否显示</div>
<div>3.点击右侧开关可控制列是否固定</div>
</template>
<div class="tip-div">
<li>说明&nbsp;</li>
<InfoCircleIcon />
</div>
</t-tooltip>
<t-button size="small" @click="submitColumns"> 确定 </t-button>
</div>
<VueDraggableNext
v-model="list"
class="list-group"
tag="ul"
v-bind="{
animation: 200,
group: 'description',
disabled: false,
ghostClass: 'ghost',
}"
@start="onStart"
@end="onEnd"
>
<li v-for="element in list" :key="element.order" class="list-group-item">
<t-checkbox
v-model="element.check"
:value="element.name"
@change="changeCheckStatus"
>
{{ element.name }}
</t-checkbox>
<span class="t_switch"
><t-switch
v-model="element.isFixed"
@change="changeSwitch($event, element.name)"
/></span>
</li>
</VueDraggableNext>
</div>
</template>
<script setup lang="ts">
import { VueDraggableNext } from "vue-draggable-next";
import { ref, PropType, watch } from "vue";
import { COLUMNS_SHOW_FORMAT } from "../type";
import { InfoCircleIcon } from "tdesign-icons-vue-next";
const props = defineProps({
columnsList: {
type: Array as unknown as PropType<COLUMNS_SHOW_FORMAT[]>,
required: true,
},
});
const emits = defineEmits([
"changeCloumnsConfig",
"resetColumns",
"submitColumns",
]);
const list = ref<COLUMNS_SHOW_FORMAT[]>([]);
watch(
() => {
return props.columnsList;
},
(newVal, oldVal) => {
list.value = newVal.map((item, index) => ({
name: item.name,
check: item.check,
isFixed: item.isFixed,
}));
},
{ immediate: true, deep: true }
);
const resetColumns = () => {
emits("resetColumns");
};
const submitColumns = () => {
emits("submitColumns");
};
// 更改列显示隐藏
const changeCheckStatus = (val: boolean, { e }: { e: any }) => {
list.value.forEach((item) => {
if (item.name === e.target._value) {
item.check = val;
}
});

emits("changeCloumnsConfig", list.value);
};
// 更改列是否固定
const changeSwitch = (val: any, name: string) => {
list.value.forEach((item) => {
if (item.name === name) {
item.isFixed = val;
}
});
emits("changeCloumnsConfig", list.value);
};
// 拖拽开始的事件
const onStart = (e: Event) => {
console.log("开始拖拽", e);
};

// 拖拽结束的事件
const onEnd = () => {
console.log("结束拖拽");
emits("changeCloumnsConfig", list.value);
};
</script>
<style lang="less" scoped>
ul,
dl,
li,
dd,
dt {
margin: 0;
padding: 0;
list-style: none;
}
.tip-div {
display: flex;
align-items: center;
justify-content: center;
}

.ghost {
opacity: 0.5;
background: #c8ebfb;
}

.list-group {
min-height: 20px;
max-height: 400px;
overflow-y: auto;
list-style: none;

&::-webkit-scrollbar {
width: 4px;
}
}

.list-group-item {
cursor: pointer;
height: 30px;
line-height: 30px;
border: 1px solid #ccc;
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 8px;

.t_switch {
margin-left: 10px;
}
}
</style>

调用

这时候就在外面调用一下button的组件就行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
<template>
<t-row style="width: 100%" justify="end">
<ColumnSetButton :columns-list="columns" name="codesigner-column" />
</t-row>
<t-table :data="data" :columns="columns"></t-table>
</template>
<script setup lang="ts">
import ColumnSetButton from "./components/column-set-button.vue";
const columns = [
{
colKey: "name",
title: "姓名",
},
{
colKey: "age",
title: "年龄",
},
{
colKey: "gender",
title: "性别",
},
{
colKey: "address",
title: "地址",
},
{
colKey: "phone",
title: "电话",
},
]

const data = [
{
name: "张三",
age: 18,
address: "北京",
gender: "男",
phone: "123456789",
},
{
name: "李四",
age: 20,
address: "上海",
gender: "女",
phone: "1234567890",
},
{
name: "王五",
age: 22,
address: "广州",
gender: "女",
phone: "1234567891",
},
{
name: "赵六",
age: 24,
address: "深圳",
gender: "男",
phone: "1234567892",
},
{
name: "孙七",
age: 26,
address: "杭州",
gender: "男",
phone: "1234567894",
},
];
</script>

此时我们的效果如下
列配置

写方法

无本地数据处理

接下来就是我们比较复杂的逻辑了,就是处理那些处理方法了,首先来简单处理一下初始化列

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// 初始化存储列数据
const saveColumnsData = () => {
const fixedData: string[] = [];
const unCheckData: string[] = [];
displayColumns.value.forEach((item) => {
if (item.isFixed) {
fixedData.push(item.name);
} else if (!item.check) {
unCheckData.push(item.name);
}
});
const userColumnsSetData = {
fixedData,
unCheckData,
order: displayColumns.value.map((item) => item.name),
};
localStorage.setItem(localKey.value, JSON.stringify(userColumnsSetData));
};

// 初始化列
const initColumns = () => {
// 列配置展开详情的部分
displayColumns.value = props.columnsList.map((item)=>{
return {
name:item.title,
check:item.check??true,
isFixed:item.isFixed??false
}
})
// 看本地有没有配置
const userColumnsSet = localStorage.getItem(localKey.value);
// 如果没有列配置或者是重置
if (!userColumnsSet) {
saveColumnsData();
}
};

这里为了简单,我就在本地进行配置了,大家如果用的话最好和后端有沟通,存数据库里面,用接口获取数据,先来说一下逻辑,我这里的逻辑就很简单,如果本地没有配置过,那就存一次就结束,相当于初始化,这里我进行存储的是三个东西,一个是固定列的数据,一个是隐藏的数据,还有一个是列顺序

有本地数据处理

这里进行了最简单的处理,根据order进行判断,因为这个是顺序嘛,然后根据顺序去进行配置

1
2
3
4
5
6
7
8
9
10
11
12
13
//如果有列配置
else {
const { fixedData, unCheckData, order } = JSON.parse(userColumnsSet);
displayColumns.value = order.map((item) => {
return {
...item,
check: !unCheckData.includes(item),
isFixed: fixedData.includes(item),
};
});
}
// 然后根据displayColumns.value的数据去进行设置最新的列
changeCloumnsConfig(displayColumns.value)

displayColumns变化重新设置列

这里的调用包括了初始化调用和子组件里列配置变化的全部代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
const changeCloumnsConfig = (list: COLUMNS_SHOW_FORMAT[]) => {
// 重置数据
curColumnData.fixedData = [];
curColumnData.unCheckData = [];
curColumnData.order = [];
// 当前变化的list
const changeList = [...list];
// 原先的table列
const originColumns = [...props.columnsList];
// 新的table列
const newColumns: PrimaryTableCol<TableRowData>[] = [];
// 重新设置列
changeList.forEach((item) => {
const findItemIndex = originColumns.findIndex((i) => i.title === item.name);
if (~findItemIndex) {
const curData = originColumns[findItemIndex];
// 删除当前index的数据,减少下一次循环查找次数
originColumns.splice(findItemIndex, 1);
curColumnData.order.push(item.name);
const isFixed = item.isFixed ? 'left' : undefined;
if (isFixed) {
curColumnData.fixedData.push(item.name);
}
if (item.check) {
newColumns.push({
...curData,
fixed: isFixed,
});
} else {
// 隐藏的列
curColumnData.unCheckData.push(item.name);
}
}
});
displayColumns.value = changeList;
emit('changeColumns', isEmpty(newColumns) ? props.columnsList : newColumns);
};

然后在table的地方进行列属性重新赋值,这里注意哈,给列配置的列属性是固定的,不是给table的那个列,是变化的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
<template>
<t-row style="width: 100%" justify="end">
<ColumnSetButton :columns-list="TABLE_COLUMNS" name="codesigner-column" @changeColumns="handleChangeColumns" />
</t-row>
<t-table :data="data" :columns="columns"></t-table>
</template>
<script setup lang="ts">
import {ref} from "vue"
import ColumnSetButton from "./components/column-set-button.vue";
import { PrimaryTableCol, TableRowData } from 'tdesign-vue-next';

const TABLE_COLUMNS=[
{
colKey: "name",
title: "姓名",
width:400
},
{
colKey: "age",
title: "年龄",
width:400
},
{
colKey: "gender",
title: "性别",
width:400
},
{
colKey: "address",
title: "地址",
width:400
},
{
colKey: "phone",
title: "电话",
width:300
},
]
const columns = ref<PrimaryTableCol<TableRowData>[]>([...TABLE_COLUMNS])

const data = [
{
name: "张三",
age: 18,
address: "北京",
gender: "男",
phone: "123456789",
},
{
name: "李四",
age: 20,
address: "上海",
gender: "女",
phone: "1234567890",
},
{
name: "王五",
age: 22,
address: "广州",
gender: "女",
phone: "1234567891",
},
{
name: "赵六",
age: 24,
address: "深圳",
gender: "男",
phone: "1234567892",
},
{
name: "孙七",
age: 26,
address: "杭州",
gender: "男",
phone: "1234567894",
},
];

// 改变列
const handleChangeColumns=(list:any)=>{
columns.value=list
}
</script>


不知道大家能不能看懂我的代码,其实简单来说就是根据配置重新给列赋值。可以给大家看看效果

此时,我们的基础功能就可以算简单实现了,接下来就是先把重置和保存给加上

重置保存方法

这里就非常的简单

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 重置列
const resetColumns = async () => {
// 去除localStorage的值
localStorage.removeItem(localKey.value);
// 重新初始化
initColumns();
};
// 保存列
const saveUserColumns = async () => {
const { fixedData, unCheckData, order } = curColumnData;
// 本地保存列
localStorage.setItem(
localKey.value,
JSON.stringify({
fixedData,
unCheckData,
order,
}),
);
};

这样也许你会说就大功告成了,其实还差一些,比如当我们本地的列名称改了,本地列添加删除了,还用浏览器中的缓存,那么就会产生bug,因此,我们还需要继续完善。

本地代码列修改和存储对应不上的问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
const initColumns = () => {
// 列配置展开详情的部分
displayColumns.value = props.columnsList.map((item) => {
return {
name: item.title,
check: item.check ?? true,
isFixed: item.isFixed ?? false,
};
});
// 看本地有没有配置
const userColumnsSet = localStorage.getItem(localKey.value);
// 如果没有列配置或者是重置
if (!userColumnsSet) {
saveColumnsData();
} else {
const { fixedData, unCheckData, order } = JSON.parse(userColumnsSet);
// 有顺序,根据顺序排序
const list = new Array(
Math.max(order.length, displayColumns.value.length)
).fill(null);
// 复制一份(记录的值)
let displayColumnsCopyValue = [...displayColumns.value];
// 记录没有进行赋值的数值
const unAssignData: string[] = [];
// 上一个被赋值的值
let lastValData = "";
for (let i = 0; i < displayColumns.value.length; i++) {
const item = displayColumns.value[i];
const findIndex = order.findIndex(
(orderItem: string) => orderItem === item.name
);
// 这里排除了当次列的数量比order的情况
if (~findIndex) {
list[findIndex] = item;
// 将赋值进去的给删掉
displayColumnsCopyValue = displayColumnsCopyValue.filter(
(i) => i.name !== item.name
);
lastValData = item.name;
} else {
unAssignData.push(lastValData);
}
}
// 复制一份unAssignData
const unAssignDataCopy = [...unAssignData];
// 将list中的null去除
const filterNullList = list.filter((i) => i);
// 此时还有可能displayColumnsCopyValue中还有一些列没有在order中,按照指定顺序操作(用来避免当次列的数量比order多或者名称变化的情况)
while (unAssignData.length > 0 && displayColumnsCopyValue.length > 0) {
const item = displayColumnsCopyValue.shift();
const lastVal = unAssignData.shift();
// 如果没有lastVal,说明是第一个
if (!lastVal) {
filterNullList.unshift(item);
break;
}
// 根据lastVal找到插入filterNullList的位置
const findIndex = filterNullList.findIndex((i) => i.name === lastVal);
filterNullList.splice(findIndex + 1, 0, item);
}
displayColumns.value = filterNullList.map((item) => {
// 说明是代码里面操作过的列
if (unAssignDataCopy.includes(item.name)) {
return {
...item,
check: item.check ?? true,
isFixed: item.isFixed ?? false,
};
}
return {
...item,
check: !unCheckData.includes(item.name),
isFixed: fixedData.includes(item.name),
};
});
// 长度不相等或者数据修改了,需要重新赋值
const isLengthEqual = displayColumns.value.length === order.length;
if (!isEmpty(unAssignDataCopy) || !isLengthEqual) {
saveColumnsData();
}
}
changeCloumnsConfig(displayColumns.value);
};

我们根据一定的代码进行判断是否是有列名的修改进行后续的操作,这样一来,我们的列配置的功能就比较完整了,我们修改一个本地的代码试一下。

结语

这样,一个列配置的模块就完成了,当然这个代码还需要优化,里面用到的循环太多了,这个算法肯定是有问题的,后续我会想办法优化一下,但是思路就是这样的一个思路,当然你也可以把列宽也进行存储,这个就看你个人操作了,债见。

上一篇:
【算法学习】02-利用数据结构思维解决问题
下一篇:
使用jszip压缩文件夹