arms-app 1.0.66 → 1.0.67
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/view/555.vue +196 -0
package/package.json
CHANGED
package/view/555.vue
ADDED
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<!-- 列拖拽示例页面 -->
|
|
3
|
+
<section class="draggable-columns-table-page">
|
|
4
|
+
<!-- 标题与说明 -->
|
|
5
|
+
<h1>列拖拽表格</h1>
|
|
6
|
+
<p class="desc">通过拖拽表头,使用 sortablejs 实现列换位。</p>
|
|
7
|
+
|
|
8
|
+
<!-- 表格:通过 columns 数组驱动列配置 -->
|
|
9
|
+
<el-table
|
|
10
|
+
ref="draggableTable"
|
|
11
|
+
:key="tableKey"
|
|
12
|
+
:data="rows"
|
|
13
|
+
border
|
|
14
|
+
stripe
|
|
15
|
+
style="width: 100%"
|
|
16
|
+
height="420"
|
|
17
|
+
>
|
|
18
|
+
<!-- 固定的序号列,不参与拖拽换位 -->
|
|
19
|
+
<el-table-column type="index" label="#" width="60" />
|
|
20
|
+
|
|
21
|
+
<!-- 动态业务列:所有可拖拽的列 -->
|
|
22
|
+
<el-table-column
|
|
23
|
+
v-for="col in columns"
|
|
24
|
+
:key="col.prop"
|
|
25
|
+
:prop="col.prop"
|
|
26
|
+
:label="col.label"
|
|
27
|
+
:width="col.width"
|
|
28
|
+
:min-width="col.minWidth"
|
|
29
|
+
:align="col.align || 'left'"
|
|
30
|
+
:header-align="col.headerAlign || col.align || 'left'"
|
|
31
|
+
>
|
|
32
|
+
<!-- 表头插槽:添加拖拽手柄,作为 sortablejs 的 handle -->
|
|
33
|
+
<template slot="header">
|
|
34
|
+
<div class="header-draggable">
|
|
35
|
+
<span class="header-label">{{ col.label }}</span>
|
|
36
|
+
</div>
|
|
37
|
+
</template>
|
|
38
|
+
<!-- 单元格内容:根据列配置动态渲染字段 -->
|
|
39
|
+
<template slot-scope="scope">
|
|
40
|
+
<span>{{ scope.row[col.prop] }}</span>
|
|
41
|
+
</template>
|
|
42
|
+
</el-table-column>
|
|
43
|
+
</el-table>
|
|
44
|
+
</section>
|
|
45
|
+
</template>
|
|
46
|
+
|
|
47
|
+
<script>
|
|
48
|
+
import Sortable from "sortablejs";
|
|
49
|
+
|
|
50
|
+
export default {
|
|
51
|
+
name: "DraggableColumnsTableView",
|
|
52
|
+
data() {
|
|
53
|
+
return {
|
|
54
|
+
// 表格行数据:示例客户信息
|
|
55
|
+
rows: [
|
|
56
|
+
{
|
|
57
|
+
id: 1,
|
|
58
|
+
name: "王小虎",
|
|
59
|
+
customerTag: "重点客户",
|
|
60
|
+
manager: "张三",
|
|
61
|
+
contribution: 12000,
|
|
62
|
+
pass1: 98,
|
|
63
|
+
pass2: 87,
|
|
64
|
+
pass3: 93,
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
id: 2,
|
|
68
|
+
name: "李小虎",
|
|
69
|
+
customerTag: "潜在客户",
|
|
70
|
+
manager: "李四",
|
|
71
|
+
contribution: 8600,
|
|
72
|
+
pass1: 88,
|
|
73
|
+
pass2: 90,
|
|
74
|
+
pass3: 85,
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
id: 3,
|
|
78
|
+
name: "赵六",
|
|
79
|
+
customerTag: "普通客户",
|
|
80
|
+
manager: "王五",
|
|
81
|
+
contribution: 5400,
|
|
82
|
+
pass1: 76,
|
|
83
|
+
pass2: 80,
|
|
84
|
+
pass3: 79,
|
|
85
|
+
},
|
|
86
|
+
],
|
|
87
|
+
// 列配置:用于驱动 el-table-column 渲染,同时作为拖拽排序的数据源
|
|
88
|
+
columns: [
|
|
89
|
+
{ prop: "name", label: "姓名", width: 160, align: "center" },
|
|
90
|
+
{ prop: "customerTag", label: "客户标签", width: 120, align: "center" },
|
|
91
|
+
{ prop: "manager", label: "客户人", width: 160, align: "center" },
|
|
92
|
+
{ prop: "contribution", label: "贡献积分", width: 120, align: "right", headerAlign: "center" },
|
|
93
|
+
{ prop: "pass1", label: "通关1", width: 100, align: "center" },
|
|
94
|
+
{ prop: "pass2", label: "通关2", width: 100, align: "center" },
|
|
95
|
+
{ prop: "pass3", label: "通关3", width: 100, align: "center" },
|
|
96
|
+
],
|
|
97
|
+
// sortablejs 实例,用于在组件销毁时释放
|
|
98
|
+
headerSortable: null,
|
|
99
|
+
// 用于强制重建 el-table,确保列顺序与数据完全同步
|
|
100
|
+
tableKey: 0,
|
|
101
|
+
isDestroyed: false,
|
|
102
|
+
};
|
|
103
|
+
},
|
|
104
|
+
mounted() {
|
|
105
|
+
// 表格渲染完成后,初始化表头拖拽能力
|
|
106
|
+
this.$nextTick(() => {
|
|
107
|
+
this.initColumnDrag();
|
|
108
|
+
});
|
|
109
|
+
},
|
|
110
|
+
beforeDestroy() {
|
|
111
|
+
// 组件销毁前销毁 sortable 实例,避免内存泄露
|
|
112
|
+
this.isDestroyed = true;
|
|
113
|
+
if (this.headerSortable && this.headerSortable.destroy) {
|
|
114
|
+
this.headerSortable.destroy();
|
|
115
|
+
this.headerSortable = null;
|
|
116
|
+
}
|
|
117
|
+
},
|
|
118
|
+
methods: {
|
|
119
|
+
// 初始化列拖拽:绑定在 ElementUI 表格头部行上
|
|
120
|
+
initColumnDrag() {
|
|
121
|
+
if (this.isDestroyed) return;
|
|
122
|
+
// 已存在实例时先销毁,避免重复绑定和内存泄漏
|
|
123
|
+
if (this.headerSortable && this.headerSortable.destroy) {
|
|
124
|
+
this.headerSortable.destroy();
|
|
125
|
+
this.headerSortable = null;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const table = this.$refs.draggableTable;
|
|
129
|
+
if (!table || !table.$el) return;
|
|
130
|
+
|
|
131
|
+
// ElementUI 表头 DOM:thead 里的第一行 tr
|
|
132
|
+
const headerRow = table.$el.querySelector(".el-table__header-wrapper thead tr");
|
|
133
|
+
if (!headerRow) return;
|
|
134
|
+
|
|
135
|
+
this.headerSortable = Sortable.create(headerRow, {
|
|
136
|
+
// 拖拽动画时长(毫秒)
|
|
137
|
+
animation: 150,
|
|
138
|
+
// 只允许拖拽 header-draggable 区域作为手柄
|
|
139
|
+
handle: ".header-draggable",
|
|
140
|
+
// 可被拖动的元素类型:表头单元格 th
|
|
141
|
+
draggable: "th",
|
|
142
|
+
// 拖拽结束时回调:根据新旧索引更新 columns 顺序
|
|
143
|
+
onEnd: (evt) => {
|
|
144
|
+
const { oldIndex, newIndex } = evt;
|
|
145
|
+
if (oldIndex == null || newIndex == null) return;
|
|
146
|
+
|
|
147
|
+
// 第 0 列是 index 序号列,从第 1 列开始才对应 columns[0]
|
|
148
|
+
const from = oldIndex - 1;
|
|
149
|
+
const to = newIndex - 1;
|
|
150
|
+
if (from < 0 || to < 0 || from >= this.columns.length || to >= this.columns.length) return;
|
|
151
|
+
|
|
152
|
+
// 交换 columns 中两列的位置,实现“换列”
|
|
153
|
+
const list = this.columns.slice();
|
|
154
|
+
[list[from], list[to]] = [list[to], list[from]];
|
|
155
|
+
this.columns = list;
|
|
156
|
+
|
|
157
|
+
// 强制重建表格组件,避免 ElementUI 内部列状态与 DOM 拖拽产生偏差,
|
|
158
|
+
// 重建后在下一个 tick 重新绑定 sortable
|
|
159
|
+
this.tableKey += 1;
|
|
160
|
+
this.$nextTick(() => {
|
|
161
|
+
this.initColumnDrag();
|
|
162
|
+
});
|
|
163
|
+
},
|
|
164
|
+
});
|
|
165
|
+
},
|
|
166
|
+
},
|
|
167
|
+
};
|
|
168
|
+
</script>
|
|
169
|
+
|
|
170
|
+
<style scoped>
|
|
171
|
+
.draggable-columns-table-page {
|
|
172
|
+
padding: 24px;
|
|
173
|
+
}
|
|
174
|
+
h1 {
|
|
175
|
+
margin: 0 0 12px;
|
|
176
|
+
font-size: 20px;
|
|
177
|
+
}
|
|
178
|
+
.desc {
|
|
179
|
+
margin: 0 0 16px;
|
|
180
|
+
color: #666;
|
|
181
|
+
}
|
|
182
|
+
.header-draggable {
|
|
183
|
+
display: inline-flex;
|
|
184
|
+
align-items: center;
|
|
185
|
+
cursor: move;
|
|
186
|
+
user-select: none;
|
|
187
|
+
}
|
|
188
|
+
.drag-icon {
|
|
189
|
+
margin-right: 4px;
|
|
190
|
+
font-size: 14px;
|
|
191
|
+
color: #999;
|
|
192
|
+
}
|
|
193
|
+
.header-label {
|
|
194
|
+
white-space: nowrap;
|
|
195
|
+
}
|
|
196
|
+
</style>
|