arms-app 1.0.79 → 1.0.81

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.
Files changed (3) hide show
  1. package/package.json +1 -1
  2. package/public/1.d +43 -0
  3. package/public/222.d +0 -548
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "arms-app",
3
- "version": "1.0.79",
3
+ "version": "1.0.81",
4
4
  "description": "一个基于 Express 的 Web 应用1",
5
5
  "main": "index.js",
6
6
  "scripts": {
package/public/1.d ADDED
@@ -0,0 +1,43 @@
1
+ function listToTree(list) {
2
+ const map = new Map();
3
+ const tree = [];
4
+
5
+ // 1. 初始化 map
6
+ list.forEach(item => {
7
+ map.set(item.id, { ...item, children: [] });
8
+ });
9
+
10
+ // 2. 组装父子关系
11
+ list.forEach(item => {
12
+ const node = map.get(item.id);
13
+ const parentId = item.parentId;
14
+
15
+ // parentId 为 null / undefined / 不存在于 map → 根节点
16
+ if (parentId == null || !map.has(parentId)) {
17
+ tree.push(node);
18
+ } else {
19
+ map.get(parentId).children.push(node);
20
+ }
21
+ });
22
+
23
+ return tree;
24
+ }
25
+ function treeToList(tree) {
26
+ const result = [];
27
+
28
+ function traverse(node, parentId = null) {
29
+ const { children, ...rest } = node;
30
+
31
+ result.push({
32
+ ...rest,
33
+ parentId
34
+ });
35
+
36
+ if (children && children.length) {
37
+ children.forEach(child => traverse(child, node.id));
38
+ }
39
+ }
40
+
41
+ tree.forEach(root => traverse(root, null));
42
+ return result;
43
+ }
package/public/222.d DELETED
@@ -1,548 +0,0 @@
1
- <template>
2
- <el-dialog
3
- title="发放"
4
- :visible.sync="dialogVisible"
5
- width="980px"
6
- :close-on-click-modal="false"
7
- @close="onClose"
8
- >
9
- <div class="issue-body">
10
- <div class="issue-col">
11
- <div class="issue-col-head">
12
- <div class="issue-col-title">待选列表</div>
13
- <div class="issue-col-count">
14
- {{ issueLeftSelected.length }}/{{ issueLeftTotal }}
15
- </div>
16
- </div>
17
- <el-input
18
- v-model="issueLeftKeyword"
19
- size="small"
20
- placeholder="请输入"
21
- clearable
22
- prefix-icon="el-icon-search"
23
- class="issue-search"
24
- />
25
- <el-table
26
- ref="issueLeftTable"
27
- class="issue-left-table"
28
- :data="issueLeftPageRows"
29
- border
30
- stripe
31
- size="small"
32
- height="380"
33
- :show-header="false"
34
- @selection-change="onIssueLeftSelectionChange"
35
- >
36
- <el-table-column
37
- type="selection"
38
- width="46"
39
- :selectable="isIssueLeftRowSelectable"
40
- />
41
- <el-table-column label="人员">
42
- <template slot-scope="scope">
43
- <span
44
- class="issue-name"
45
- @click="onIssueLeftNameClick(scope.row)"
46
- >
47
- {{ formatUserLabel(scope.row) }}
48
- </span>
49
- </template>
50
- </el-table-column>
51
- </el-table>
52
- <div class="issue-pagination">
53
- <el-pagination
54
- small
55
- :current-page="issueLeftPage"
56
- :page-size="issueLeftPageSize"
57
- layout="total, prev, pager, next"
58
- :total="issueLeftTotal"
59
- @current-change="onIssueLeftPageChange"
60
- />
61
- </div>
62
- </div>
63
-
64
- <div class="issue-transfer">
65
- <el-button
66
- size="mini"
67
- type="primary"
68
- :disabled="issueLeftSelected.length === 0"
69
- @click="onIssueMoveToRight"
70
- >
71
- <i class="el-icon-arrow-right" />
72
- </el-button>
73
- <el-button
74
- size="mini"
75
- type="primary"
76
- :disabled="issueRightSelected.length === 0"
77
- @click="onIssueMoveToLeft"
78
- >
79
- <i class="el-icon-arrow-left" />
80
- </el-button>
81
- </div>
82
-
83
- <div class="issue-col">
84
- <div class="issue-col-head">
85
- <div class="issue-col-title">
86
- <el-checkbox
87
- class="issue-right-select-all"
88
- :value="issueRightAllSelected"
89
- :indeterminate="issueRightIndeterminate"
90
- @change="onToggleRightSelectAll"
91
- >
92
- 全选
93
- </el-checkbox>
94
- <span>当前佩戴</span>
95
- </div>
96
- <div class="issue-col-count">
97
- {{ issueSelectedUsers.length }}/{{ limitCount === -1 ? '不限' : limitCount }}
98
- </div>
99
- </div>
100
- <el-input
101
- v-model="issueRightKeyword"
102
- size="small"
103
- placeholder="请输入"
104
- clearable
105
- prefix-icon="el-icon-search"
106
- class="issue-search"
107
- />
108
- <el-table
109
- ref="issueRightTable"
110
- :data="issueRightPageRows"
111
- border
112
- stripe
113
- size="small"
114
- height="380"
115
- :show-header="false"
116
- @selection-change="onIssueRightSelectionChange"
117
- >
118
- <el-table-column type="selection" width="46" />
119
- <el-table-column label="人员">
120
- <template slot-scope="scope">
121
- <span
122
- class="issue-name"
123
- @click="onIssueRightNameClick(scope.row)"
124
- >
125
- {{ formatUserLabel(scope.row) }}
126
- </span>
127
- </template>
128
- </el-table-column>
129
- </el-table>
130
- <div class="issue-pagination">
131
- <el-pagination
132
- small
133
- :current-page="issueRightPage"
134
- :page-size="issueRightPageSize"
135
- layout="total, prev, pager, next"
136
- :total="issueRightTotal"
137
- @current-change="onIssueRightPageChange"
138
- />
139
- </div>
140
- </div>
141
- </div>
142
- <span slot="footer" class="dialog-footer">
143
- <el-button size="small" @click="onCancel">取消</el-button>
144
- <el-button size="small" type="primary" @click="onConfirm">
145
- 发放
146
- </el-button>
147
- </span>
148
- </el-dialog>
149
- </template>
150
-
151
- <script>
152
- import { get } from "../utils/request";
153
-
154
- /**
155
- * 勋章发放弹窗组件
156
- * 用于管理勋章的发放对象,支持左右穿梭框选择用户
157
- */
158
- export default {
159
- name: "MedalIssueDialog",
160
- props: {
161
- // 控制弹窗显示
162
- visible: {
163
- type: Boolean,
164
- default: false
165
- },
166
- limitCount: {
167
- type: Number,
168
- default: -1
169
- },
170
- // 当前操作的勋章对象
171
- medal: {
172
- type: Object,
173
- default: null
174
- }
175
- },
176
- data() {
177
- return {
178
- issueSelectedUsers: [], // 当前勋章已选中的用户列表
179
- issueLeftKeyword: "", // 左侧(待选)搜索关键字
180
- issueRightKeyword: "", // 右侧(已选)搜索关键字
181
- issueLeftPage: 1, // 左侧当前页码
182
- issueRightPage: 1, // 右侧当前页码
183
- issueLeftPageSize: 10, // 左侧每页显示数量
184
- issueRightPageSize: 10, // 右侧每页显示数量
185
- issueLeftRows: [], // 左侧当前页数据(服务端分页)
186
- issueLeftTotalCount: 0, // 左侧总数(服务端返回)
187
- issueLeftSelected: [], // 左侧表格当前勾选的行(准备移入右侧)
188
- issueRightSelected: [], // 右侧表格当前勾选的行(准备移出右侧)
189
- issueLeftSelectedMap: {},
190
- issueLeftRestoringSelection: false,
191
- issueLeftKeywordTimer: null,
192
- issueRightAllSelected: false,
193
- issueRightIndeterminate: false
194
- };
195
- },
196
- computed: {
197
- dialogVisible: {
198
- get() {
199
- return this.visible;
200
- },
201
- set(val) {
202
- this.$emit("update:visible", val);
203
- }
204
- },
205
- // 左侧待选列表总数
206
- issueLeftTotal() {
207
- return this.issueLeftTotalCount;
208
- },
209
- // 右侧已选列表总数
210
- issueRightTotal() {
211
- return this.issueRightFiltered.length;
212
- },
213
- issueLeftFiltered() {
214
- return Array.isArray(this.issueLeftRows) ? this.issueLeftRows : [];
215
- },
216
- // 右侧过滤后的列表(根据关键字过滤)
217
- issueRightFiltered() {
218
- const keyword = (this.issueRightKeyword || "").trim();
219
- const list = Array.isArray(this.issueSelectedUsers) ? this.issueSelectedUsers : [];
220
- if (!keyword) return list;
221
- return list.filter(u => this.formatUserLabel(u).includes(keyword));
222
- },
223
- // 左侧当前页显示的数据
224
- issueLeftPageRows() {
225
- return this.issueLeftFiltered;
226
- },
227
- // 右侧当前页显示的数据
228
- issueRightPageRows() {
229
- const start = (this.issueRightPage - 1) * this.issueRightPageSize;
230
- return this.issueRightFiltered.slice(start, start + this.issueRightPageSize);
231
- }
232
- },
233
- watch: {
234
- // 监听 visible 变化,为 true 时初始化数据
235
- visible: {
236
- handler(val) {
237
- if (val) this.init();
238
- },
239
- immediate: true
240
- },
241
- issueLeftKeyword() {
242
- if (this.issueLeftKeywordTimer) clearTimeout(this.issueLeftKeywordTimer);
243
- this.issueLeftKeywordTimer = setTimeout(() => {
244
- this.issueLeftPage = 1;
245
- this.fetchIssueLeftPage();
246
- }, 250);
247
- },
248
- issueLeftPageRows() {
249
- this.$nextTick(() => {
250
- this.restoreIssueLeftPageSelection();
251
- });
252
- }
253
- },
254
- methods: {
255
- /**
256
- * 初始化数据
257
- * 1. 确保用户池数据已加载
258
- * 2. 根据服务端返回的已发放用户初始化选中状态
259
- */
260
- async init() {
261
- const serverIssuedUsers = await this.fetchIssuedUsers();
262
- const row = this.medal;
263
- if (!row) return;
264
-
265
- this.issueSelectedUsers = (serverIssuedUsers || []).map(u => ({ ...u }));
266
-
267
- // 重置搜索和分页状态
268
- this.issueLeftKeyword = "";
269
- this.issueRightKeyword = "";
270
- this.issueLeftPage = 1;
271
- this.issueRightPage = 1;
272
- this.issueLeftRows = [];
273
- this.issueLeftTotalCount = 0;
274
- this.issueLeftSelected = [];
275
- this.issueRightSelected = [];
276
- this.issueLeftSelectedMap = {};
277
- // 清空表格选中状态
278
- this.$nextTick(() => {
279
- this.$refs.issueLeftTable && this.$refs.issueLeftTable.clearSelection && this.$refs.issueLeftTable.clearSelection();
280
- this.$refs.issueRightTable && this.$refs.issueRightTable.clearSelection && this.$refs.issueRightTable.clearSelection();
281
- });
282
- this.fetchIssueLeftPage();
283
- },
284
- // 格式化用户显示名称 (姓名/工号)
285
- formatUserLabel(user) {
286
- if (!user) return "";
287
- const name = user.displayName || "";
288
- const no = user.userNo || "";
289
- if (name && no) return `${name}/${no}`;
290
- return name || no || "";
291
- },
292
- async fetchIssuedUsers() {
293
- try {
294
- const medalNo = this.medal ? (this.medal.medalNo || this.medal.id || "") : "";
295
- const res = await get('/medal/users', { medalNo: String(medalNo) });
296
- if (res) {
297
- // 兼容新旧接口结构
298
- if (res.allUsers) {
299
- return res.issuedUsers || [];
300
- } else if (Array.isArray(res)) {
301
- return [];
302
- }
303
- }
304
- } catch (err) {
305
- console.error(err);
306
- this.$message.error('获取用户列表失败');
307
- }
308
- return [];
309
- },
310
- async fetchIssueLeftPage(allowRetry = true) {
311
- try {
312
- const res = await get('/medal/candidates', {
313
- page: this.issueLeftPage,
314
- pageSize: this.issueLeftPageSize,
315
- keyword: this.issueLeftKeyword
316
- });
317
- if (res && res.list) {
318
- this.issueLeftRows = Array.isArray(res.list) ? res.list : [];
319
- this.issueLeftTotalCount = Number.isFinite(res.total) ? res.total : 0;
320
- } else if (Array.isArray(res)) {
321
- this.issueLeftRows = res;
322
- this.issueLeftTotalCount = res.length;
323
- }
324
- const maxPage = Math.max(1, Math.ceil(this.issueLeftTotalCount / this.issueLeftPageSize));
325
- if (allowRetry && this.issueLeftPage > maxPage) {
326
- this.issueLeftPage = maxPage;
327
- await this.fetchIssueLeftPage(false);
328
- return;
329
- }
330
- } catch (err) {
331
- console.error(err);
332
- this.$message.error('获取待选用户失败');
333
- } finally {
334
- this.$nextTick(() => {
335
- this.restoreIssueLeftPageSelection();
336
- });
337
- }
338
- },
339
- // 关闭弹窗时的处理
340
- onClose() {
341
- this.issueLeftSelected = [];
342
- this.issueRightSelected = [];
343
- this.issueLeftSelectedMap = {};
344
- this.$nextTick(() => {
345
- this.$refs.issueLeftTable && this.$refs.issueLeftTable.clearSelection && this.$refs.issueLeftTable.clearSelection();
346
- this.$refs.issueRightTable && this.$refs.issueRightTable.clearSelection && this.$refs.issueRightTable.clearSelection();
347
- });
348
- this.$emit('update:visible', false);
349
- },
350
- // 点击取消
351
- onCancel() {
352
- this.$emit('update:visible', false);
353
- },
354
- // 点击确定,校验并发射 confirm 事件
355
- onConfirm() {
356
- const medal = this.medal;
357
- if (!medal) return;
358
- const total = this.issueSelectedUsers.length;
359
- // 校验限量勋章的数量限制
360
- if (this.limitCount !== -1 && total > this.limitCount) {
361
- this.$message({
362
- type: "warning",
363
- message: `超出发放数量上限(上限:${this.limitCount})`
364
- });
365
- return;
366
- }
367
-
368
- this.$emit('confirm', {
369
- medal,
370
- users: this.issueSelectedUsers
371
- });
372
- },
373
- // 左侧表格选中变化
374
- onIssueLeftSelectionChange(list) {
375
- if (this.issueLeftRestoringSelection) return;
376
- const pageRows = Array.isArray(this.issueLeftPageRows) ? this.issueLeftPageRows : [];
377
- const selected = Array.isArray(list) ? list : [];
378
- const selectedSet = new Set(selected.map(r => r && r.userNo).filter(Boolean));
379
-
380
- pageRows.forEach(r => {
381
- if (!r || !r.userNo) return;
382
- if (selectedSet.has(r.userNo)) this.$set(this.issueLeftSelectedMap, r.userNo, r);
383
- else this.$delete(this.issueLeftSelectedMap, r.userNo);
384
- });
385
-
386
- this.issueLeftSelected = Object.values(this.issueLeftSelectedMap);
387
- },
388
- isIssueLeftRowSelectable(row) {
389
- if (!row || !row.userNo) return false;
390
- const selected = Array.isArray(this.issueSelectedUsers) ? this.issueSelectedUsers : [];
391
- return !selected.some(u => u && u.userNo === row.userNo);
392
- },
393
- onIssueLeftNameClick(row) {
394
- if (!this.isIssueLeftRowSelectable(row)) return;
395
- const table = this.$refs.issueLeftTable;
396
- if (!table || !table.toggleRowSelection) return;
397
- table.toggleRowSelection(row);
398
- },
399
- // 右侧表格选中变化
400
- onIssueRightSelectionChange(list) {
401
- this.issueRightSelected = Array.isArray(list) ? list.slice() : [];
402
- const pageRows = Array.isArray(this.issueRightPageRows) ? this.issueRightPageRows : [];
403
- const total = pageRows.length;
404
- const selectedCount = Array.isArray(list) ? list.length : 0;
405
- this.issueRightAllSelected = total > 0 && selectedCount === total;
406
- this.issueRightIndeterminate = selectedCount > 0 && selectedCount < total;
407
- },
408
- // 将左侧选中用户移动到右侧(添加)
409
- onIssueMoveToRight() {
410
- const selected = Array.isArray(this.issueLeftSelected) ? this.issueLeftSelected : [];
411
- if (selected.length === 0) return;
412
-
413
- // 预先计算添加后的总数
414
- const currentCount = this.issueSelectedUsers.length;
415
- // 过滤掉已经在右侧的用户,计算实际新增数量
416
- const existingIds = new Set(this.issueSelectedUsers.map(u => u.userNo));
417
- const newUsers = selected.filter(u => !existingIds.has(u.userNo));
418
-
419
- if (this.limitCount !== -1 && (currentCount + newUsers.length) > this.limitCount) {
420
- this.$message({
421
- type: "warning",
422
- message: `选择的用户数量超出限制(上限:${this.limitCount},当前已选:${currentCount},尝试添加:${newUsers.length})`
423
- });
424
- return;
425
- }
426
-
427
- const map = new Map((this.issueSelectedUsers || []).map(u => [u.userNo, u]));
428
- selected.forEach(u => {
429
- if (!u || !u.userNo) return;
430
- if (!map.has(u.userNo)) map.set(u.userNo, { ...u });
431
- });
432
- this.issueSelectedUsers = Array.from(map.values());
433
- this.issueLeftSelected = [];
434
- this.issueLeftSelectedMap = {};
435
- this.$nextTick(() => {
436
- this.$refs.issueLeftTable && this.$refs.issueLeftTable.clearSelection && this.$refs.issueLeftTable.clearSelection();
437
- });
438
- this.fetchIssueLeftPage();
439
- },
440
- // 将右侧选中用户移动到左侧(移除)
441
- onIssueMoveToLeft() {
442
- const selected = Array.isArray(this.issueRightSelected) ? this.issueRightSelected : [];
443
- if (selected.length === 0) return;
444
- const removeSet = new Set(selected.map(u => u.userNo));
445
- this.issueSelectedUsers = (this.issueSelectedUsers || []).filter(u => !removeSet.has(u.userNo));
446
- this.issueRightSelected = [];
447
- this.$nextTick(() => {
448
- this.$refs.issueRightTable && this.$refs.issueRightTable.clearSelection && this.$refs.issueRightTable.clearSelection();
449
- });
450
- this.fetchIssueLeftPage();
451
- // 调整分页
452
- const maxPage = Math.max(1, Math.ceil(this.issueRightTotal / this.issueRightPageSize));
453
- if (this.issueRightPage > maxPage) this.issueRightPage = maxPage;
454
- },
455
- // 左侧分页变化
456
- onIssueLeftPageChange(page) {
457
- this.issueLeftPage = page;
458
- this.fetchIssueLeftPage();
459
- },
460
- restoreIssueLeftPageSelection() {
461
- const table = this.$refs.issueLeftTable;
462
- if (!table || !table.clearSelection || !table.toggleRowSelection) return;
463
- const rows = Array.isArray(this.issueLeftPageRows) ? this.issueLeftPageRows : [];
464
-
465
- this.issueLeftRestoringSelection = true;
466
- table.clearSelection();
467
- rows.forEach(r => {
468
- if (!r || !r.userNo) return;
469
- if (this.issueLeftSelectedMap[r.userNo]) table.toggleRowSelection(r, true);
470
- });
471
- this.issueLeftRestoringSelection = false;
472
- },
473
- // 右侧分页变化
474
- onIssueRightPageChange(page) {
475
- this.issueRightPage = page;
476
- this.issueRightSelected = [];
477
- this.$nextTick(() => {
478
- this.$refs.issueRightTable && this.$refs.issueRightTable.clearSelection && this.$refs.issueRightTable.clearSelection();
479
- });
480
- this.issueRightAllSelected = false;
481
- this.issueRightIndeterminate = false;
482
- },
483
- onIssueRightNameClick(row) {
484
- const table = this.$refs.issueRightTable;
485
- if (!table || !table.toggleRowSelection) return;
486
- table.toggleRowSelection(row);
487
- },
488
- onToggleRightSelectAll(checked) {
489
- const table = this.$refs.issueRightTable;
490
- if (!table || !table.clearSelection || !table.toggleRowSelection) return;
491
- const rows = Array.isArray(this.issueRightPageRows) ? this.issueRightPageRows : [];
492
- table.clearSelection();
493
- if (checked) {
494
- rows.forEach(r => {
495
- if (!r) return;
496
- table.toggleRowSelection(r, true);
497
- });
498
- }
499
- }
500
- }
501
- };
502
- </script>
503
-
504
- <style scoped>
505
- .issue-body {
506
- display: flex;
507
- align-items: flex-start;
508
- gap: 12px;
509
- }
510
- .issue-col {
511
- flex: 1;
512
- min-width: 0;
513
- }
514
- .issue-col-head {
515
- display: flex;
516
- align-items: center;
517
- justify-content: space-between;
518
- margin-bottom: 8px;
519
- }
520
- .issue-col-title {
521
- font-size: 14px;
522
- color: #303133;
523
- }
524
- .issue-col-count {
525
- font-size: 13px;
526
- color: #909399;
527
- }
528
- .issue-search {
529
- margin-bottom: 10px;
530
- }
531
- .issue-transfer {
532
- width: 72px;
533
- flex-shrink: 0;
534
- display: flex;
535
- flex-direction: column;
536
- align-items: center;
537
- justify-content: center;
538
- gap: 10px;
539
- }
540
- .issue-pagination {
541
- margin-top: 10px;
542
- display: flex;
543
- justify-content: center;
544
- }
545
- ::v-deep .issue-left-table .el-table__header-wrapper .el-checkbox {
546
- display: none;
547
- }
548
- </style>