vue2-client 1.22.39 → 1.22.46

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 (39) hide show
  1. package/.env.iot +1 -0
  2. package/.idea/af-vue2-client.iml +2 -3
  3. package/.idea/inspectionProfiles/Project_Default.xml +17 -0
  4. package/1yarn.lock +11850 -0
  5. package/logs/afgit.config.log +12 -0
  6. package/logs/afgit.config.log.2026-02-27 +7 -0
  7. package/logs/afgit.config_error.log +6 -0
  8. package/logs/afgit.config_error.log.2026-02-27 +3 -0
  9. package/package.json +1 -1
  10. package/pnpm-workspace.yaml +4 -0
  11. package/public/his/editor/mock/bind_data_with_images.html +78 -0
  12. package/public/his/editor/mock/blank.html +645 -0
  13. package/src/assets/img/80359c35a5465167cb25ff87bab49035d041a65558b35-YJOr3x.png +0 -0
  14. package/src/assets/img/hisLogo.png +0 -0
  15. package/src/assets/svg/unknown-icon.svg +3 -3
  16. package/src/base-client/components/common/AfMap/InfoWindowComponent.vue +141 -141
  17. package/src/base-client/components/common/AfMap/demo.vue +492 -492
  18. package/src/base-client/components/common/HIS/HButtons/HButtons.vue +89 -1
  19. package/src/base-client/components/common/HIS/HButtons/HButtonsSuffixDemo.vue +141 -0
  20. package/src/base-client/components/common/Upload/Upload.vue +2 -2
  21. package/src/base-client/components/common/XInspectionDetailDrawer/components/DeviceStatus.vue +1 -1
  22. package/src/base-client/components/his/XHisEditor/ImageReportDemo.vue +156 -0
  23. package/src/base-client/components/his/XHisEditor/XDocTree.vue +529 -529
  24. package/src/base-client/plugins/GetLoginInfoService.js +26 -1
  25. package/src/components/ImagePreview/ImagePreview.vue +323 -323
  26. package/src/constants/crypto.js +9 -1
  27. package/src/pages/login/Login.vue +107 -3
  28. package/src/router/async/router.map.js +6 -1
  29. package/src/services/user.js +7 -3
  30. package/src/utils/EncryptUtil.js +7 -2
  31. package/src/utils/login.js +29 -14
  32. package/src/utils/request.js +14 -3
  33. package/src/utils/util.js +27 -4
  34. package/vue.config.js +10 -0
  35. package/.idea/MarsCodeWorkspaceAppSettings.xml +0 -7
  36. package/.idea/deployment.xml +0 -14
  37. package/.idea/encodings.xml +0 -6
  38. package/.idea/gradle.xml +0 -7
  39. package/.idea/misc.xml +0 -6
@@ -1,529 +1,529 @@
1
- <template>
2
- <div class="x-doc-tree-wrapper" ref="dropdownRef">
3
- <!-- 触发器:默认显示当前选中的文档名 -->
4
- <div class="dropdown-trigger" @click="toggleDropdown">
5
- <span class="trigger-text">{{ selectedLabel || '选择文档' }}</span>
6
- <a-icon class="trigger-arrow" :type="visible ? 'up' : 'down'" />
7
- </div>
8
-
9
- <!-- 下拉面板:左右布局 -->
10
- <transition name="dropdown-fade">
11
- <div v-if="visible" class="dropdown-panel">
12
- <!-- 左侧:文档列表 -->
13
- <div class="panel-left">
14
- <div class="panel-header">
15
- <a-input-search
16
- v-model="searchValue"
17
- placeholder="搜索文档"
18
- class="panel-search"
19
- allow-clear
20
- @search="handleSearch"
21
- @change="handleSearchChange"
22
- />
23
- </div>
24
- <div class="panel-body">
25
- <a-spin v-if="loading" size="small" />
26
- <a-empty v-else-if="displayData.length === 0" :description="searchValue ? '未找到匹配的文档' : '暂无文档'" />
27
- <div v-else class="tree-nodes">
28
- <x-doc-tree-node
29
- v-for="node in displayData"
30
- :key="node.value"
31
- :node="node"
32
- :selected-id="selectedId"
33
- :current-res-id="resId"
34
- @node-click="handleNodeClick"
35
- @add-click="handleAddClick"
36
- />
37
- </div>
38
- </div>
39
- </div>
40
-
41
- <!-- 右侧:添加表单(与左侧同高度) -->
42
- <transition name="slide-fade">
43
- <div v-if="showAddForm" class="panel-right">
44
- <div class="form-header">
45
- <span class="form-title">添加报告模板</span>
46
- <a-icon type="close" class="close-btn" @click="closeAddForm" />
47
- </div>
48
- <div class="form-body">
49
- <div class="form-row">
50
- <span class="form-label">文书类型</span>
51
- <a-input
52
- class="form-input"
53
- :value="currentParentNode ? currentParentNode.label : ''"
54
- disabled
55
- />
56
- </div>
57
- <div class="form-row">
58
- <span class="form-label">文书名称</span>
59
- <a-input
60
- class="form-input"
61
- v-model="newDocName"
62
- placeholder="请输入"
63
- />
64
- </div>
65
- </div>
66
- <div class="form-footer">
67
- <div class="footer-buttons">
68
- <a-button class="btn-action" @click="closeAddForm">取消</a-button>
69
- <a-button class="btn-action" :loading="addLoading" @click="handleAddConfirm">保存</a-button>
70
- </div>
71
- </div>
72
- </div>
73
- </transition>
74
- </div>
75
- </transition>
76
- </div>
77
- </template>
78
-
79
- <script>
80
- import XDocTreeNode from './XDocTreeNode.vue'
81
-
82
- export default {
83
- name: 'XDocTree',
84
- components: {
85
- XDocTreeNode
86
- },
87
- props: {
88
- resList: {
89
- type: Array,
90
- default: () => []
91
- },
92
- resId: {
93
- type: [Number, String],
94
- default: undefined
95
- },
96
- loading: {
97
- type: Boolean,
98
- default: false
99
- },
100
- searchParams: {
101
- type: Object,
102
- default: () => ({})
103
- }
104
- },
105
- data() {
106
- return {
107
- visible: false,
108
- showAddForm: false,
109
- addLoading: false,
110
- newDocName: '',
111
- selectedId: null,
112
- treeData: [],
113
- displayData: [],
114
- currentParentNode: null,
115
- form: {},
116
- searchValue: ''
117
- }
118
- },
119
- computed: {
120
- selectedLabel() {
121
- if (!this.selectedId) return ''
122
- const targetId = String(this.selectedId)
123
- const findNode = (nodes) => {
124
- for (const node of nodes) {
125
- if (String(node.value) === targetId) return node.label
126
- if (node.children && node.children.length > 0) {
127
- const found = findNode(node.children)
128
- if (found) return found
129
- }
130
- }
131
- return null
132
- }
133
- return findNode(this.treeData) || ''
134
- }
135
- },
136
- watch: {
137
- resList: {
138
- immediate: true,
139
- handler(newVal) {
140
- this.buildTreeData(newVal)
141
- this.updateDisplayData()
142
- }
143
- },
144
- resId: {
145
- immediate: true,
146
- handler(newVal) {
147
- this.selectedId = newVal
148
- }
149
- }
150
- },
151
- mounted() {
152
- document.addEventListener('click', this.handleClickOutside)
153
- },
154
- beforeDestroy() {
155
- document.removeEventListener('click', this.handleClickOutside)
156
- },
157
- methods: {
158
- handleClickOutside(e) {
159
- // 如果点击的是模态框内的元素,不关闭面板
160
- const modalElements = document.querySelectorAll('.ant-modal-wrap')
161
- for (const modal of modalElements) {
162
- if (modal.contains(e.target)) {
163
- return
164
- }
165
- }
166
- if (this.$refs.dropdownRef && !this.$refs.dropdownRef.contains(e.target)) {
167
- this.visible = false
168
- this.showAddForm = false
169
- this.addLoading = false // 👈 新增这一行
170
- }
171
- },
172
- toggleDropdown() {
173
- this.visible = !this.visible
174
- if (!this.visible) {
175
- this.showAddForm = false
176
- }
177
- },
178
- buildTreeData(resList) {
179
- if (!resList || resList.length === 0) {
180
- this.treeData = []
181
- return
182
- }
183
- // 适配新的数据结构:resList 是分类列表,每个分类有 children
184
- this.treeData = resList.map(category => {
185
- // 分类本身是第一层(folder 类型)
186
- const processNode = (node, level = 0) => ({
187
- ...node,
188
- expanded: level === 0, // 默认展开第一层
189
- children: (node.children || []).map(child => processNode(child, level + 1))
190
- })
191
- return processNode(category)
192
- })
193
- },
194
- updateDisplayData() {
195
- if (!this.searchValue) {
196
- this.displayData = this.treeData
197
- return
198
- }
199
- const keyword = this.searchValue.toLowerCase()
200
- this.displayData = this.filterTreeData(this.treeData, keyword)
201
- },
202
- filterTreeData(nodes, keyword) {
203
- const result = []
204
- for (const node of nodes) {
205
- const matchSelf = node.label && node.label.toLowerCase().includes(keyword)
206
- const matchedChildren = node.children && node.children.length > 0
207
- ? this.filterTreeData(node.children, keyword)
208
- : []
209
-
210
- if (matchSelf || matchedChildren.length > 0) {
211
- result.push({
212
- ...node,
213
- expanded: true,
214
- children: matchedChildren
215
- })
216
- }
217
- }
218
- return result
219
- },
220
- handleSearch(value) {
221
- // 如果 value 为空,updateDisplayData 会显示全部数据
222
- this.searchValue = value
223
- this.updateDisplayData()
224
- },
225
- handleSearchChange(e) {
226
- const value = e.target.value
227
- if (!value) {
228
- this.updateDisplayData()
229
- }
230
- },
231
- handleNodeClick(node) {
232
- this.selectedId = node.value
233
- this.$emit('change', node)
234
- this.visible = false
235
- },
236
- handleAddClick(node) {
237
- this.currentParentNode = node
238
- this.showAddForm = true
239
- this.newDocName = ''
240
- },
241
- closeAddForm() {
242
- this.showAddForm = false
243
- this.newDocName = ''
244
- this.currentParentNode = null
245
- this.addLoading = false // 👈 新增这一行
246
- },
247
- handleAddConfirm() {
248
- if (!this.newDocName || !this.newDocName.trim()) {
249
- this.$message.warning('请输入文书名称')
250
- return
251
- }
252
- // 先保存名称
253
- const docName = this.newDocName.trim()
254
- // 关闭右侧表单并重置状态(按钮恢复)
255
- this.closeAddForm()
256
- // 使用保存的名称传给父组件
257
- this.$emit('add-doc', {
258
- parentNode: this.currentParentNode,
259
- name: docName
260
- })
261
- },
262
- onAddDocSuccess() {
263
- this.addLoading = false
264
- this.closeAddForm()
265
- },
266
- onAddDocError() {
267
- this.addLoading = false
268
- },
269
- setTreeData(data) {
270
- this.buildTreeData(data)
271
- this.updateDisplayData()
272
- }
273
- }
274
- }
275
- </script>
276
-
277
- <style scoped lang="less">
278
- .x-doc-tree-wrapper {
279
- position: relative;
280
- display: inline-block;
281
- }
282
-
283
- /* 触发器样式 */
284
- .dropdown-trigger {
285
- display: inline-flex;
286
- align-items: center;
287
- gap: 4px;
288
- padding: 4px 8px;
289
- min-width: 120px;
290
- max-width: 200px;
291
- height: 32px;
292
- border: 1px solid #d9d9d9;
293
- border-radius: 4px;
294
- background: #fff;
295
- cursor: pointer;
296
- transition: border-color 0.2s;
297
-
298
- &:hover {
299
- border-color: #40a9ff;
300
- }
301
-
302
- .trigger-text {
303
- flex: 1;
304
- font-size: 13px;
305
- color: #333;
306
- overflow: hidden;
307
- text-overflow: ellipsis;
308
- white-space: nowrap;
309
- }
310
-
311
- .trigger-arrow {
312
- flex-shrink: 0;
313
- color: #999;
314
- font-size: 10px;
315
- }
316
- }
317
-
318
- /* 左侧面板 */
319
- .panel-left {
320
- display: flex;
321
- flex-direction: column;
322
- width: 180px;
323
- background: #fff;
324
- }
325
-
326
- .dropdown-panel {
327
- position: absolute;
328
- top: 100%;
329
- left: 0;
330
- z-index: 1050;
331
- display: inline-flex;
332
- max-height: 600px;
333
- background: #fff;
334
- border: 1px solid #d9d9d9;
335
- border-radius: 4px;
336
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
337
- overflow: hidden;
338
- }
339
-
340
-
341
- .panel-header {
342
- height: 50px;
343
- display: flex;
344
- align-items: center;
345
- padding: 0 8px;
346
- background: #fff;
347
- flex-shrink: 0;
348
- }
349
-
350
- .panel-search {
351
- width: 100%;
352
-
353
- :deep(.ant-input) {
354
- height: 32px;
355
- font-size: 13px;
356
- }
357
-
358
- :deep(.ant-input-search-button) {
359
- height: 32px;
360
- }
361
- }
362
-
363
- .panel-body {
364
- flex: 1;
365
- overflow-y: auto;
366
- padding: 4px 0;
367
- max-height: 500px;
368
-
369
- &::-webkit-scrollbar {
370
- width: 4px;
371
- }
372
-
373
- &::-webkit-scrollbar-thumb {
374
- background-color: #ccc;
375
- border-radius: 2px;
376
- }
377
- }
378
-
379
- .tree-nodes {
380
- padding: 0 4px;
381
- }
382
-
383
- /* 右侧表单面板 */
384
- .panel-right {
385
- width: 240px;
386
- display: flex;
387
- flex-direction: column;
388
- background: #fff;
389
- border-left: 1px solid #d9d9d9;
390
- }
391
-
392
- .form-header {
393
- height: 50px;
394
- display: flex;
395
- align-items: center;
396
- justify-content: space-between;
397
- padding: 0 12px;
398
- border-bottom: 1px solid #d9d9d9;
399
- background: #fff;
400
- flex-shrink: 0;
401
- }
402
-
403
- .form-title {
404
- font-size: 13px;
405
- font-weight: 500;
406
- color: #333;
407
- }
408
-
409
- .close-btn {
410
- cursor: pointer;
411
- color: #666;
412
- font-size: 12px;
413
-
414
- &:hover {
415
- color: #333;
416
- }
417
- }
418
-
419
- .form-body {
420
- flex: 1;
421
- padding: 12px;
422
- overflow-y: auto;
423
- max-height: 450px;
424
-
425
- &::-webkit-scrollbar {
426
- width: 4px;
427
- }
428
-
429
- &::-webkit-scrollbar-thumb {
430
- background-color: #ccc;
431
- border-radius: 2px;
432
- }
433
- }
434
-
435
- .form-row {
436
- display: flex;
437
- align-items: center;
438
- margin-bottom: 12px;
439
-
440
- &:last-child {
441
- margin-bottom: 0;
442
- }
443
- }
444
-
445
- .form-label {
446
- flex-shrink: 0;
447
- width: 70px;
448
- font-size: 13px;
449
- color: #333;
450
- }
451
-
452
- .form-input {
453
- flex: 1;
454
-
455
- :deep(.ant-input) {
456
- height: 32px;
457
- font-size: 13px;
458
- }
459
-
460
- :deep(.ant-input-disabled) {
461
- background: #f5f5f5;
462
- color: #666;
463
- }
464
- }
465
-
466
- .form-footer {
467
- height: 50px;
468
- display: flex;
469
- align-items: center;
470
- justify-content: center;
471
- padding: 0 12px;
472
- border-top: 1px solid #d9d9d9;
473
- background: #fff;
474
- flex-shrink: 0;
475
- }
476
-
477
- .footer-buttons {
478
- display: flex;
479
- justify-content: space-between;
480
- width: 100%;
481
-
482
- .btn-action {
483
- flex: 0 0 46%;
484
- display: inline-flex;
485
- align-items: center;
486
- justify-content: center;
487
- border-radius: 6px;
488
- border: 1px solid #CDCDCD;
489
- background: #fff;
490
- color: #5D5C5C;
491
- font-size: 14px;
492
- height: 32px;
493
- padding: 0 8px;
494
- transition: all 0.2s;
495
-
496
- &:hover {
497
- border-color: #1890ff;
498
- color: #1890ff;
499
- }
500
- }
501
- }
502
-
503
- /* 下拉动画 */
504
- .dropdown-fade-enter-active,
505
- .dropdown-fade-leave-active {
506
- transition: opacity 0.15s, transform 0.15s;
507
- }
508
-
509
- .dropdown-fade-enter,
510
- .dropdown-fade-leave-to {
511
- opacity: 0;
512
- transform: translateY(-4px);
513
- }
514
-
515
- /* 右侧滑入动画 */
516
- .slide-fade-enter-active {
517
- transition: all 0.2s ease;
518
- }
519
-
520
- .slide-fade-leave-active {
521
- transition: all 0.15s ease;
522
- }
523
-
524
- .slide-fade-enter,
525
- .slide-fade-leave-to {
526
- width: 0;
527
- opacity: 0;
528
- }
529
- </style>
1
+ <template>
2
+ <div class="x-doc-tree-wrapper" ref="dropdownRef">
3
+ <!-- 触发器:默认显示当前选中的文档名 -->
4
+ <div class="dropdown-trigger" @click="toggleDropdown">
5
+ <span class="trigger-text">{{ selectedLabel || '选择文档' }}</span>
6
+ <a-icon class="trigger-arrow" :type="visible ? 'up' : 'down'" />
7
+ </div>
8
+
9
+ <!-- 下拉面板:左右布局 -->
10
+ <transition name="dropdown-fade">
11
+ <div v-if="visible" class="dropdown-panel">
12
+ <!-- 左侧:文档列表 -->
13
+ <div class="panel-left">
14
+ <div class="panel-header">
15
+ <a-input-search
16
+ v-model="searchValue"
17
+ placeholder="搜索文档"
18
+ class="panel-search"
19
+ allow-clear
20
+ @search="handleSearch"
21
+ @change="handleSearchChange"
22
+ />
23
+ </div>
24
+ <div class="panel-body">
25
+ <a-spin v-if="loading" size="small" />
26
+ <a-empty v-else-if="displayData.length === 0" :description="searchValue ? '未找到匹配的文档' : '暂无文档'" />
27
+ <div v-else class="tree-nodes">
28
+ <x-doc-tree-node
29
+ v-for="node in displayData"
30
+ :key="node.value"
31
+ :node="node"
32
+ :selected-id="selectedId"
33
+ :current-res-id="resId"
34
+ @node-click="handleNodeClick"
35
+ @add-click="handleAddClick"
36
+ />
37
+ </div>
38
+ </div>
39
+ </div>
40
+
41
+ <!-- 右侧:添加表单(与左侧同高度) -->
42
+ <transition name="slide-fade">
43
+ <div v-if="showAddForm" class="panel-right">
44
+ <div class="form-header">
45
+ <span class="form-title">添加报告模板</span>
46
+ <a-icon type="close" class="close-btn" @click="closeAddForm" />
47
+ </div>
48
+ <div class="form-body">
49
+ <div class="form-row">
50
+ <span class="form-label">文书类型</span>
51
+ <a-input
52
+ class="form-input"
53
+ :value="currentParentNode ? currentParentNode.label : ''"
54
+ disabled
55
+ />
56
+ </div>
57
+ <div class="form-row">
58
+ <span class="form-label">文书名称</span>
59
+ <a-input
60
+ class="form-input"
61
+ v-model="newDocName"
62
+ placeholder="请输入"
63
+ />
64
+ </div>
65
+ </div>
66
+ <div class="form-footer">
67
+ <div class="footer-buttons">
68
+ <a-button class="btn-action" @click="closeAddForm">取消</a-button>
69
+ <a-button class="btn-action" :loading="addLoading" @click="handleAddConfirm">保存</a-button>
70
+ </div>
71
+ </div>
72
+ </div>
73
+ </transition>
74
+ </div>
75
+ </transition>
76
+ </div>
77
+ </template>
78
+
79
+ <script>
80
+ import XDocTreeNode from './XDocTreeNode.vue'
81
+
82
+ export default {
83
+ name: 'XDocTree',
84
+ components: {
85
+ XDocTreeNode
86
+ },
87
+ props: {
88
+ resList: {
89
+ type: Array,
90
+ default: () => []
91
+ },
92
+ resId: {
93
+ type: [Number, String],
94
+ default: undefined
95
+ },
96
+ loading: {
97
+ type: Boolean,
98
+ default: false
99
+ },
100
+ searchParams: {
101
+ type: Object,
102
+ default: () => ({})
103
+ }
104
+ },
105
+ data() {
106
+ return {
107
+ visible: false,
108
+ showAddForm: false,
109
+ addLoading: false,
110
+ newDocName: '',
111
+ selectedId: null,
112
+ treeData: [],
113
+ displayData: [],
114
+ currentParentNode: null,
115
+ form: {},
116
+ searchValue: ''
117
+ }
118
+ },
119
+ computed: {
120
+ selectedLabel() {
121
+ if (!this.selectedId) return ''
122
+ const targetId = String(this.selectedId)
123
+ const findNode = (nodes) => {
124
+ for (const node of nodes) {
125
+ if (String(node.value) === targetId) return node.label
126
+ if (node.children && node.children.length > 0) {
127
+ const found = findNode(node.children)
128
+ if (found) return found
129
+ }
130
+ }
131
+ return null
132
+ }
133
+ return findNode(this.treeData) || ''
134
+ }
135
+ },
136
+ watch: {
137
+ resList: {
138
+ immediate: true,
139
+ handler(newVal) {
140
+ this.buildTreeData(newVal)
141
+ this.updateDisplayData()
142
+ }
143
+ },
144
+ resId: {
145
+ immediate: true,
146
+ handler(newVal) {
147
+ this.selectedId = newVal
148
+ }
149
+ }
150
+ },
151
+ mounted() {
152
+ document.addEventListener('click', this.handleClickOutside)
153
+ },
154
+ beforeDestroy() {
155
+ document.removeEventListener('click', this.handleClickOutside)
156
+ },
157
+ methods: {
158
+ handleClickOutside(e) {
159
+ // 如果点击的是模态框内的元素,不关闭面板
160
+ const modalElements = document.querySelectorAll('.ant-modal-wrap')
161
+ for (const modal of modalElements) {
162
+ if (modal.contains(e.target)) {
163
+ return
164
+ }
165
+ }
166
+ if (this.$refs.dropdownRef && !this.$refs.dropdownRef.contains(e.target)) {
167
+ this.visible = false
168
+ this.showAddForm = false
169
+ this.addLoading = false // 👈 新增这一行
170
+ }
171
+ },
172
+ toggleDropdown() {
173
+ this.visible = !this.visible
174
+ if (!this.visible) {
175
+ this.showAddForm = false
176
+ }
177
+ },
178
+ buildTreeData(resList) {
179
+ if (!resList || resList.length === 0) {
180
+ this.treeData = []
181
+ return
182
+ }
183
+ // 适配新的数据结构:resList 是分类列表,每个分类有 children
184
+ this.treeData = resList.map(category => {
185
+ // 分类本身是第一层(folder 类型)
186
+ const processNode = (node, level = 0) => ({
187
+ ...node,
188
+ expanded: level === 0, // 默认展开第一层
189
+ children: (node.children || []).map(child => processNode(child, level + 1))
190
+ })
191
+ return processNode(category)
192
+ })
193
+ },
194
+ updateDisplayData() {
195
+ if (!this.searchValue) {
196
+ this.displayData = this.treeData
197
+ return
198
+ }
199
+ const keyword = this.searchValue.toLowerCase()
200
+ this.displayData = this.filterTreeData(this.treeData, keyword)
201
+ },
202
+ filterTreeData(nodes, keyword) {
203
+ const result = []
204
+ for (const node of nodes) {
205
+ const matchSelf = node.label && node.label.toLowerCase().includes(keyword)
206
+ const matchedChildren = node.children && node.children.length > 0
207
+ ? this.filterTreeData(node.children, keyword)
208
+ : []
209
+
210
+ if (matchSelf || matchedChildren.length > 0) {
211
+ result.push({
212
+ ...node,
213
+ expanded: true,
214
+ children: matchedChildren
215
+ })
216
+ }
217
+ }
218
+ return result
219
+ },
220
+ handleSearch(value) {
221
+ // 如果 value 为空,updateDisplayData 会显示全部数据
222
+ this.searchValue = value
223
+ this.updateDisplayData()
224
+ },
225
+ handleSearchChange(e) {
226
+ const value = e.target.value
227
+ if (!value) {
228
+ this.updateDisplayData()
229
+ }
230
+ },
231
+ handleNodeClick(node) {
232
+ this.selectedId = node.value
233
+ this.$emit('change', node)
234
+ this.visible = false
235
+ },
236
+ handleAddClick(node) {
237
+ this.currentParentNode = node
238
+ this.showAddForm = true
239
+ this.newDocName = ''
240
+ },
241
+ closeAddForm() {
242
+ this.showAddForm = false
243
+ this.newDocName = ''
244
+ this.currentParentNode = null
245
+ this.addLoading = false // 👈 新增这一行
246
+ },
247
+ handleAddConfirm() {
248
+ if (!this.newDocName || !this.newDocName.trim()) {
249
+ this.$message.warning('请输入文书名称')
250
+ return
251
+ }
252
+ // 先保存名称
253
+ const docName = this.newDocName.trim()
254
+ // 关闭右侧表单并重置状态(按钮恢复)
255
+ this.closeAddForm()
256
+ // 使用保存的名称传给父组件
257
+ this.$emit('add-doc', {
258
+ parentNode: this.currentParentNode,
259
+ name: docName
260
+ })
261
+ },
262
+ onAddDocSuccess() {
263
+ this.addLoading = false
264
+ this.closeAddForm()
265
+ },
266
+ onAddDocError() {
267
+ this.addLoading = false
268
+ },
269
+ setTreeData(data) {
270
+ this.buildTreeData(data)
271
+ this.updateDisplayData()
272
+ }
273
+ }
274
+ }
275
+ </script>
276
+
277
+ <style scoped lang="less">
278
+ .x-doc-tree-wrapper {
279
+ position: relative;
280
+ display: inline-block;
281
+ }
282
+
283
+ /* 触发器样式 */
284
+ .dropdown-trigger {
285
+ display: inline-flex;
286
+ align-items: center;
287
+ gap: 4px;
288
+ padding: 4px 8px;
289
+ min-width: 120px;
290
+ max-width: 200px;
291
+ height: 32px;
292
+ border: 1px solid #d9d9d9;
293
+ border-radius: 4px;
294
+ background: #fff;
295
+ cursor: pointer;
296
+ transition: border-color 0.2s;
297
+
298
+ &:hover {
299
+ border-color: #40a9ff;
300
+ }
301
+
302
+ .trigger-text {
303
+ flex: 1;
304
+ font-size: 13px;
305
+ color: #333;
306
+ overflow: hidden;
307
+ text-overflow: ellipsis;
308
+ white-space: nowrap;
309
+ }
310
+
311
+ .trigger-arrow {
312
+ flex-shrink: 0;
313
+ color: #999;
314
+ font-size: 10px;
315
+ }
316
+ }
317
+
318
+ /* 左侧面板 */
319
+ .panel-left {
320
+ display: flex;
321
+ flex-direction: column;
322
+ width: 180px;
323
+ background: #fff;
324
+ }
325
+
326
+ .dropdown-panel {
327
+ position: absolute;
328
+ top: 100%;
329
+ left: 0;
330
+ z-index: 1050;
331
+ display: inline-flex;
332
+ max-height: 600px;
333
+ background: #fff;
334
+ border: 1px solid #d9d9d9;
335
+ border-radius: 4px;
336
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
337
+ overflow: hidden;
338
+ }
339
+
340
+
341
+ .panel-header {
342
+ height: 50px;
343
+ display: flex;
344
+ align-items: center;
345
+ padding: 0 8px;
346
+ background: #fff;
347
+ flex-shrink: 0;
348
+ }
349
+
350
+ .panel-search {
351
+ width: 100%;
352
+
353
+ :deep(.ant-input) {
354
+ height: 32px;
355
+ font-size: 13px;
356
+ }
357
+
358
+ :deep(.ant-input-search-button) {
359
+ height: 32px;
360
+ }
361
+ }
362
+
363
+ .panel-body {
364
+ flex: 1;
365
+ overflow-y: auto;
366
+ padding: 4px 0;
367
+ max-height: 500px;
368
+
369
+ &::-webkit-scrollbar {
370
+ width: 4px;
371
+ }
372
+
373
+ &::-webkit-scrollbar-thumb {
374
+ background-color: #ccc;
375
+ border-radius: 2px;
376
+ }
377
+ }
378
+
379
+ .tree-nodes {
380
+ padding: 0 4px;
381
+ }
382
+
383
+ /* 右侧表单面板 */
384
+ .panel-right {
385
+ width: 240px;
386
+ display: flex;
387
+ flex-direction: column;
388
+ background: #fff;
389
+ border-left: 1px solid #d9d9d9;
390
+ }
391
+
392
+ .form-header {
393
+ height: 50px;
394
+ display: flex;
395
+ align-items: center;
396
+ justify-content: space-between;
397
+ padding: 0 12px;
398
+ border-bottom: 1px solid #d9d9d9;
399
+ background: #fff;
400
+ flex-shrink: 0;
401
+ }
402
+
403
+ .form-title {
404
+ font-size: 13px;
405
+ font-weight: 500;
406
+ color: #333;
407
+ }
408
+
409
+ .close-btn {
410
+ cursor: pointer;
411
+ color: #666;
412
+ font-size: 12px;
413
+
414
+ &:hover {
415
+ color: #333;
416
+ }
417
+ }
418
+
419
+ .form-body {
420
+ flex: 1;
421
+ padding: 12px;
422
+ overflow-y: auto;
423
+ max-height: 450px;
424
+
425
+ &::-webkit-scrollbar {
426
+ width: 4px;
427
+ }
428
+
429
+ &::-webkit-scrollbar-thumb {
430
+ background-color: #ccc;
431
+ border-radius: 2px;
432
+ }
433
+ }
434
+
435
+ .form-row {
436
+ display: flex;
437
+ align-items: center;
438
+ margin-bottom: 12px;
439
+
440
+ &:last-child {
441
+ margin-bottom: 0;
442
+ }
443
+ }
444
+
445
+ .form-label {
446
+ flex-shrink: 0;
447
+ width: 70px;
448
+ font-size: 13px;
449
+ color: #333;
450
+ }
451
+
452
+ .form-input {
453
+ flex: 1;
454
+
455
+ :deep(.ant-input) {
456
+ height: 32px;
457
+ font-size: 13px;
458
+ }
459
+
460
+ :deep(.ant-input-disabled) {
461
+ background: #f5f5f5;
462
+ color: #666;
463
+ }
464
+ }
465
+
466
+ .form-footer {
467
+ height: 50px;
468
+ display: flex;
469
+ align-items: center;
470
+ justify-content: center;
471
+ padding: 0 12px;
472
+ border-top: 1px solid #d9d9d9;
473
+ background: #fff;
474
+ flex-shrink: 0;
475
+ }
476
+
477
+ .footer-buttons {
478
+ display: flex;
479
+ justify-content: space-between;
480
+ width: 100%;
481
+
482
+ .btn-action {
483
+ flex: 0 0 46%;
484
+ display: inline-flex;
485
+ align-items: center;
486
+ justify-content: center;
487
+ border-radius: 6px;
488
+ border: 1px solid #CDCDCD;
489
+ background: #fff;
490
+ color: #5D5C5C;
491
+ font-size: 14px;
492
+ height: 32px;
493
+ padding: 0 8px;
494
+ transition: all 0.2s;
495
+
496
+ &:hover {
497
+ border-color: #1890ff;
498
+ color: #1890ff;
499
+ }
500
+ }
501
+ }
502
+
503
+ /* 下拉动画 */
504
+ .dropdown-fade-enter-active,
505
+ .dropdown-fade-leave-active {
506
+ transition: opacity 0.15s, transform 0.15s;
507
+ }
508
+
509
+ .dropdown-fade-enter,
510
+ .dropdown-fade-leave-to {
511
+ opacity: 0;
512
+ transform: translateY(-4px);
513
+ }
514
+
515
+ /* 右侧滑入动画 */
516
+ .slide-fade-enter-active {
517
+ transition: all 0.2s ease;
518
+ }
519
+
520
+ .slide-fade-leave-active {
521
+ transition: all 0.15s ease;
522
+ }
523
+
524
+ .slide-fade-enter,
525
+ .slide-fade-leave-to {
526
+ width: 0;
527
+ opacity: 0;
528
+ }
529
+ </style>