arms-app 1.0.67 → 1.0.69

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "arms-app",
3
- "version": "1.0.67",
3
+ "version": "1.0.69",
4
4
  "description": "一个基于 Express 的 Web 应用1",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -1,11 +1,11 @@
1
1
  <template>
2
- <!-- 列拖拽示例页面 -->
2
+ <!-- 列拖拽示例页面:支持通过拖拽表头调整列顺序 -->
3
3
  <section class="draggable-columns-table-page">
4
- <!-- 标题与说明 -->
4
+ <!-- 标题与说明文案 -->
5
5
  <h1>列拖拽表格</h1>
6
6
  <p class="desc">通过拖拽表头,使用 sortablejs 实现列换位。</p>
7
7
 
8
- <!-- 表格:通过 columns 数组驱动列配置 -->
8
+ <!-- 表格:通过 columns 数组驱动所有可拖拽列(含序号/操作列),rows 负责行数据 -->
9
9
  <el-table
10
10
  ref="draggableTable"
11
11
  :key="tableKey"
@@ -15,13 +15,10 @@
15
15
  style="width: 100%"
16
16
  height="420"
17
17
  >
18
- <!-- 固定的序号列,不参与拖拽换位 -->
19
- <el-table-column type="index" label="#" width="60" />
20
-
21
- <!-- 动态业务列:所有可拖拽的列 -->
18
+ <!-- 所有列(包含序号列/业务列/操作列)均由 columns 数组统一驱动,可参与拖拽 -->
22
19
  <el-table-column
23
20
  v-for="col in columns"
24
- :key="col.prop"
21
+ :key="col.key || col.prop"
25
22
  :prop="col.prop"
26
23
  :label="col.label"
27
24
  :width="col.width"
@@ -29,15 +26,25 @@
29
26
  :align="col.align || 'left'"
30
27
  :header-align="col.headerAlign || col.align || 'left'"
31
28
  >
32
- <!-- 表头插槽:添加拖拽手柄,作为 sortablejs handle -->
29
+ <!-- 表头插槽:渲染拖拽手柄(handle),供 sortablejs 使用 -->
33
30
  <template slot="header">
34
31
  <div class="header-draggable">
35
32
  <span class="header-label">{{ col.label }}</span>
36
33
  </div>
37
34
  </template>
38
- <!-- 单元格内容:根据列配置动态渲染字段 -->
35
+ <!-- 单元格插槽:根据是否为操作列渲染不同内容 -->
39
36
  <template slot-scope="scope">
40
- <span>{{ scope.row[col.prop] }}</span>
37
+ <template v-if="col.isOperation">
38
+ <el-button type="text" size="mini" @click="onView(scope.row)">查看</el-button>
39
+ <el-button type="text" size="mini" @click="onEdit(scope.row)">编辑</el-button>
40
+ <el-button type="text" size="mini" @click="onDelete(scope.row)">删除</el-button>
41
+ </template>
42
+ <template v-else-if="col.isIndex">
43
+ <span>{{ scope.$index + 1 }}</span>
44
+ </template>
45
+ <template v-else>
46
+ <span>{{ scope.row[col.prop] }}</span>
47
+ </template>
41
48
  </template>
42
49
  </el-table-column>
43
50
  </el-table>
@@ -51,7 +58,7 @@ export default {
51
58
  name: "DraggableColumnsTableView",
52
59
  data() {
53
60
  return {
54
- // 表格行数据:示例客户信息
61
+ // 表格行数据:示例客户信息(实际项目中可从接口或 Vuex 获取)
55
62
  rows: [
56
63
  {
57
64
  id: 1,
@@ -86,29 +93,32 @@ export default {
86
93
  ],
87
94
  // 列配置:用于驱动 el-table-column 渲染,同时作为拖拽排序的数据源
88
95
  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
+ { key: "index", label: "#", width: 60, align: "center", isIndex: true },
97
+ { key: "name", prop: "name", label: "姓名", width: 160, align: "center" },
98
+ { key: "customerTag", prop: "customerTag", label: "客户标签", width: 120, align: "center" },
99
+ { key: "manager", prop: "manager", label: "客户人", width: 160, align: "center" },
100
+ { key: "contribution", prop: "contribution", label: "贡献积分", width: 120, align: "right", headerAlign: "center" },
101
+ { key: "pass1", prop: "pass1", label: "通关1", width: 100, align: "center" },
102
+ { key: "pass2", prop: "pass2", label: "通关2", width: 100, align: "center" },
103
+ { key: "pass3", prop: "pass3", label: "通关3", width: 100, align: "center" },
104
+ { key: "operation", prop: "__operation__", label: "操作", width: 180, align: "center", headerAlign: "center", isOperation: true },
96
105
  ],
97
- // sortablejs 实例,用于在组件销毁时释放
106
+ // sortablejs 实例引用,用于在组件销毁或重建时手动销毁,避免内存泄露
98
107
  headerSortable: null,
99
- // 用于强制重建 el-table,确保列顺序与数据完全同步
108
+ // 用于强制重建 el-table(修改 key),确保列顺序变更后 ElementUI 内部状态与 DOM 同步
100
109
  tableKey: 0,
110
+ // 标识组件是否已经进入销毁流程,避免销毁后仍然去访问 DOM 或创建 sortable 实例
101
111
  isDestroyed: false,
102
112
  };
103
113
  },
104
114
  mounted() {
105
- // 表格渲染完成后,初始化表头拖拽能力
115
+ // 组件挂载后,在下一个渲染周期中初始化表头拖拽能力
106
116
  this.$nextTick(() => {
107
117
  this.initColumnDrag();
108
118
  });
109
119
  },
110
120
  beforeDestroy() {
111
- // 组件销毁前销毁 sortable 实例,避免内存泄露
121
+ // 组件销毁前:设置销毁标记并清理 sortable 实例,避免内存泄露
112
122
  this.isDestroyed = true;
113
123
  if (this.headerSortable && this.headerSortable.destroy) {
114
124
  this.headerSortable.destroy();
@@ -116,37 +126,38 @@ export default {
116
126
  }
117
127
  },
118
128
  methods: {
119
- // 初始化列拖拽:绑定在 ElementUI 表格头部行上
129
+ // 初始化列拖拽:在 ElementUI 表格头部行(thead > tr)上绑定 sortable 实例
120
130
  initColumnDrag() {
131
+ // 若组件已处于销毁阶段,直接返回,避免无意义的 DOM 访问
121
132
  if (this.isDestroyed) return;
122
- // 已存在实例时先销毁,避免重复绑定和内存泄漏
133
+ // 若已存在 sortable 实例,先销毁,避免重复绑定导致的事件堆积与内存泄露
123
134
  if (this.headerSortable && this.headerSortable.destroy) {
124
135
  this.headerSortable.destroy();
125
136
  this.headerSortable = null;
126
137
  }
127
138
 
139
+ // 通过 ref 获取 ElementUI 表格实例,再访问内部根元素
128
140
  const table = this.$refs.draggableTable;
129
141
  if (!table || !table.$el) return;
130
142
 
131
- // ElementUI 表头 DOM:thead 里的第一行 tr
143
+ // ElementUI 表头 DOM:thead 里的第一行 tr,作为 sortable 容器
132
144
  const headerRow = table.$el.querySelector(".el-table__header-wrapper thead tr");
133
145
  if (!headerRow) return;
134
146
 
135
147
  this.headerSortable = Sortable.create(headerRow, {
136
148
  // 拖拽动画时长(毫秒)
137
149
  animation: 150,
138
- // 只允许拖拽 header-draggable 区域作为手柄
150
+ // 只允许拖拽 header-draggable 区域作为手柄,避免误拖整个表头
139
151
  handle: ".header-draggable",
140
- // 可被拖动的元素类型:表头单元格 th
152
+ // 可被拖动的元素:表头单元格 th(索引列因没有 handle 实际不可拖拽)
141
153
  draggable: "th",
142
154
  // 拖拽结束时回调:根据新旧索引更新 columns 顺序
143
155
  onEnd: (evt) => {
144
156
  const { oldIndex, newIndex } = evt;
145
157
  if (oldIndex == null || newIndex == null) return;
146
-
147
- // 0 列是 index 序号列,从第 1 列开始才对应 columns[0]
148
- const from = oldIndex - 1;
149
- const to = newIndex - 1;
158
+ if (oldIndex === newIndex) return;
159
+ const from = oldIndex;
160
+ const to = newIndex;
150
161
  if (from < 0 || to < 0 || from >= this.columns.length || to >= this.columns.length) return;
151
162
 
152
163
  // 交换 columns 中两列的位置,实现“换列”
@@ -167,30 +178,3 @@ export default {
167
178
  };
168
179
  </script>
169
180
 
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>
package/view/1.js DELETED
@@ -1,23 +0,0 @@
1
- // vue.config.js
2
- const { merge } = require('webpack-merge');
3
- const baseConfig = require('./build/webpack.base.config');
4
- const baseProxy = require('./build/base.proxy');
5
- const persionProxy = require('./build/persio n.proxy');
6
-
7
- module.exports = merge(baseConfig, {
8
- devServer: {
9
- historyApiFallback: true,
10
- port: 8081,
11
- proxy: {
12
- ...baseProxy,
13
- ...persionProxy,
14
- },
15
- },
16
- // 新增:sourcemap 配置
17
- configureWebpack: {
18
- // 根据环境自动切换 sourcemap
19
- devtool: process.env.NODE_ENV === 'development'
20
- ? 'cheap-module-source-map' // 开发环境
21
- : false // 生产环境
22
- }
23
- });
package/view/1.vue DELETED
@@ -1,117 +0,0 @@
1
- <template>
2
- <div class="page">
3
- <h3>悬停停自动,手动仍可滚(无缝)</h3>
4
-
5
- <vue-seamless-scroll
6
- ref="ss"
7
- class="warp"
8
- :data="list"
9
- :class-option="option"
10
- @mouseenter.native="hovering = true"
11
- @mouseleave.native="hovering = false"
12
- @mousewheel.native.prevent="onWheel"
13
- @touchstart.native.passive="onTouchStart"
14
- @touchmove.native.prevent="onTouchMove"
15
- >
16
- <div class="list">
17
- <div class="item" v-for="(item, index) in list" :key="index">
18
- {{ item }}
19
- </div>
20
- </div>
21
- </vue-seamless-scroll>
22
- </div>
23
- </template>
24
-
25
- <script>
26
- import VueSeamlessScroll from "vue-seamless-scroll";
27
-
28
- export default {
29
- name: "SeamlessHoverManualView",
30
- components: {
31
- VueSeamlessScroll,
32
- },
33
- data() {
34
- return {
35
- list: [],
36
- lastTouchY: 0,
37
- hovering: false,
38
- autoStep: 0.3,
39
- };
40
- },
41
- computed: {
42
- option() {
43
- return {
44
- step: this.hovering ? 0 : this.autoStep,
45
- direction: 1,
46
- hoverStop: false,
47
- limitMoveNum: 1,
48
- openWatch: true,
49
- };
50
- },
51
- },
52
- mounted() {
53
- this.fetchData();
54
- },
55
- methods: {
56
- async fetchData() {
57
- // 模拟接口请求
58
- console.log('开始获取数据...');
59
- await new Promise((resolve) => setTimeout(resolve, 1000));
60
- const data = Array.from({ length: 5000 }, (_, i) => `列表项 ${i + 1}`);
61
- // 使用 Object.freeze 冻结数据,避免 Vue 深度监听,显著提升大数据量下的渲染性能
62
- this.list = Object.freeze(data);
63
- console.log('数据获取完成,共', this.list.length, '条');
64
- this.$nextTick(() => {
65
- if (this.$refs.ss && this.$refs.ss.reset) this.$refs.ss.reset();
66
- });
67
- },
68
- onWheel(e) {
69
- const ss = this.$refs.ss;
70
- if (!ss) return;
71
-
72
- ss.yPos -= e.deltaY;
73
-
74
- if (ss.yPos > 0) ss.yPos = 0;
75
-
76
- if (ss.realBoxHeight && Math.abs(ss.yPos) > ss.realBoxHeight / 2) {
77
- ss.yPos = 0;
78
- }
79
- },
80
- onTouchStart(e) {
81
- this.lastTouchY = e.touches[0].clientY;
82
- },
83
- onTouchMove(e) {
84
- const y = e.touches[0].clientY;
85
- const delta = this.lastTouchY - y;
86
- this.lastTouchY = y;
87
- this.onWheel({ deltaY: delta });
88
- },
89
- },
90
- };
91
- </script>
92
-
93
- <style scoped>
94
- .page {
95
- padding: 20px;
96
- font-family: Arial, Helvetica, sans-serif;
97
- }
98
-
99
- .warp {
100
- height: 260px;
101
- overflow: hidden;
102
- border: 1px solid #ccc;
103
- border-radius: 4px;
104
- }
105
-
106
- .list {
107
- padding: 0 12px;
108
- }
109
-
110
- .item {
111
- height: 40px;
112
- line-height: 40px;
113
- border-bottom: 1px dashed #ddd;
114
- box-sizing: border-box;
115
- }
116
- </style>
117
-
package/view/111.js DELETED
@@ -1,35 +0,0 @@
1
- function deepEqual(a, b, seen = new WeakMap()) {
2
- if (Object.is(a, b)) return true
3
-
4
- if (
5
- typeof a !== 'object' ||
6
- typeof b !== 'object' ||
7
- a === null ||
8
- b === null
9
- ) return false
10
-
11
- if (seen.get(a) === b) return true
12
- seen.set(a, b)
13
-
14
- if (a.constructor !== b.constructor) return false
15
-
16
- if (a instanceof Date) {
17
- return a.getTime() === b.getTime()
18
- }
19
-
20
- if (a instanceof RegExp) {
21
- return a.source === b.source && a.flags === b.flags
22
- }
23
-
24
- const keysA = Reflect.ownKeys(a)
25
- const keysB = Reflect.ownKeys(b)
26
-
27
- if (keysA.length !== keysB.length) return false
28
-
29
- for (const key of keysA) {
30
- if (!keysB.includes(key)) return false
31
- if (!deepEqual(a[key], b[key], seen)) return false
32
- }
33
-
34
- return true
35
- }
package/view/2.js DELETED
@@ -1,90 +0,0 @@
1
- function traverseChildren(arr) {
2
- if (!Array.isArray(arr)) return;
3
-
4
- for (let i = 0; i < arr.length; i++) {
5
- const item = arr[i];
6
-
7
- // 处理当前节点
8
- console.log('当前节点:', item.name || item.id);
9
-
10
- // 如果有children,递归遍历
11
- if (item.children && Array.isArray(item.children) && item.children.length > 0) {
12
- console.log(`进入 ${item.name} 的子节点:`);
13
- traverseChildren(item.children);
14
- console.log(`离开 ${item.name} 的子节点`);
15
- }
16
- }
17
- }
18
- function getAllNodes(arr) {
19
- let result = [];
20
-
21
- function traverse(nodes) {
22
- if (!Array.isArray(nodes)) return;
23
-
24
- for (let i = 0; i < nodes.length; i++) {
25
- const node = nodes[i];
26
-
27
- // 将当前节点添加到结果数组
28
- result.push(node);
29
-
30
- // 递归处理子节点
31
- if (node.children && Array.isArray(node.children)) {
32
- traverse(node.children);
33
- }
34
- }
35
- }
36
-
37
- traverse(arr);
38
- return result;
39
- }
40
-
41
- function flattenIterative(arr, childrenKey = 'children') {
42
- const result = [];
43
- const stack = [...arr]; // 使用栈来模拟递归
44
-
45
- while (stack.length > 0) {
46
- const node = stack.pop();
47
-
48
- if (!node) continue;
49
-
50
- // 添加当前节点
51
- result.push(node);
52
-
53
- // 将子节点逆序压入栈中(保持原顺序)
54
- const children = node[childrenKey];
55
- if (Array.isArray(children) && children.length > 0) {
56
- // 从后往前压栈,这样前面出来的顺序是正确的
57
- for (let i = children.length - 1; i >= 0; i--) {
58
- stack.push(children[i]);
59
- }
60
- }
61
- }
62
-
63
- return result;
64
- }
65
-
66
-
67
-
68
- //
69
- function getAllOrgIds(arr) {
70
- let result = [];
71
-
72
- function traverse(nodes) {
73
- if (!Array.isArray(nodes)) return;
74
-
75
- for (let node of nodes) {
76
- // 如果当前节点有 orgid,添加到结果中
77
- if (node.orgid !== undefined && node.orgid !== null) {
78
- result.push(node.orgid);
79
- }
80
-
81
- // 如果当前节点有 children,递归遍历
82
- if (node.children && Array.isArray(node.children) && node.children.length > 0) {
83
- traverse(node.children);
84
- }
85
- }
86
- }
87
-
88
- traverse(arr);
89
- return result;
90
- }
package/view/2.vue DELETED
@@ -1,129 +0,0 @@
1
- <template>
2
- <el-dialog
3
- :visible.sync="visibleInner"
4
- title="列表"
5
- width="800px"
6
- :close-on-click-modal="false"
7
- @open="onOpen"
8
- @closed="onClosed"
9
- >
10
- <!-- 表格只渲染当前页 rows(10条) -->
11
- <el-table
12
- :data="rows"
13
- height="400"
14
- border
15
- row-key="id"
16
- >
17
- <el-table-column prop="id" label="ID" width="120" />
18
- <el-table-column prop="name" label="名称" />
19
- <el-table-column prop="status" label="状态" width="120" />
20
- </el-table>
21
-
22
- <div style="margin-top: 12px; text-align: right;">
23
- <el-pagination
24
- background
25
- layout="total, prev, pager, next, sizes"
26
- :total="total"
27
- :current-page.sync="page"
28
- :page-size.sync="pageSize"
29
- :page-sizes="[10, 20, 50, 100]"
30
- @current-change="refreshPage"
31
- @size-change="onSizeChange"
32
- />
33
- </div>
34
-
35
- <span slot="footer" class="dialog-footer">
36
- <el-button @click="close">关闭</el-button>
37
- </span>
38
- </el-dialog>
39
- </template>
40
-
41
- <script>
42
- export default {
43
- name: "ChildDialog",
44
- props: {
45
- // 只控制显隐,不要用 props 传 5000 条
46
- visible: { type: Boolean, default: false }
47
- },
48
- data() {
49
- return {
50
- // 内部 visible(避免直接改 props)
51
- visibleInner: false,
52
-
53
- // 只存 UI 需要的(轻量)
54
- page: 1,
55
- pageSize: 10,
56
- total: 0,
57
- rows: []
58
- };
59
- },
60
-
61
- watch: {
62
- // 父组件控制打开/关闭
63
- visible: {
64
- immediate: true,
65
- handler(v) {
66
- this.visibleInner = v;
67
- }
68
- },
69
- // 同步回父组件(:visible.sync)
70
- visibleInner(v) {
71
- this.$emit("update:visible", v);
72
- }
73
- },
74
-
75
- created() {
76
- // 非响应式大数据容器(不要放 data 里)
77
- this._rawList = [];
78
- this._hasData = false;
79
- },
80
-
81
- methods: {
82
- /** 父组件 open 后调用:this.$refs.dlg.setData(bigList) */
83
- setData(list) {
84
- // 关键:不放 data,不做深拷贝,不 deep watch
85
- this._rawList = Array.isArray(list) ? list : [];
86
- this._hasData = true;
87
-
88
- // 初始化分页
89
- this.page = 1;
90
- this.total = this._rawList.length;
91
-
92
- // 如果 dialog 已经打开,立刻刷新;否则等 onOpen 再刷
93
- if (this.visibleInner) this.refreshPage();
94
- },
95
-
96
- onOpen() {
97
- // dialog 打开后,如果数据已设置,刷新一次(渲染10条)
98
- if (this._hasData) this.refreshPage();
99
- },
100
-
101
- onClosed() {
102
- // 关闭时只清理轻量状态,避免销毁/重建造成卡顿
103
- this.rows = [];
104
- this.page = 1;
105
- // 如果你希望下次打开仍复用数据,就别清 _rawList
106
- // this._rawList = [];
107
- // this._hasData = false;
108
- },
109
-
110
- refreshPage() {
111
- const start = (this.page - 1) * this.pageSize;
112
- const end = start + this.pageSize;
113
-
114
- // 只 slice 当前页,rows 才是响应式的
115
- this.rows = this._rawList.slice(start, end);
116
- this.total = this._rawList.length;
117
- },
118
-
119
- onSizeChange() {
120
- this.page = 1;
121
- this.refreshPage();
122
- },
123
-
124
- close() {
125
- this.visibleInner = false;
126
- }
127
- }
128
- };
129
- </script>