vue2-client 1.22.50 → 1.22.52

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 (111) hide show
  1. package/.env.his +19 -19
  2. package/.history/.eslintrc_20260521171150.js +74 -0
  3. package/.history/.eslintrc_20260521171213.js +74 -0
  4. package/.history/src/base-client/components/common/HIS/HAddNativeForm/HAddNativeForm_20260601154443.vue +726 -0
  5. package/.history/src/base-client/components/common/HIS/HAddNativeForm/HAddNativeForm_20260601154700.vue +478 -0
  6. package/.history/src/base-client/components/common/HIS/HButtons/HButtons_20260611152602.vue +755 -0
  7. package/.history/src/base-client/components/common/HIS/HButtons/HButtons_20260702161223.vue +748 -0
  8. package/.history/src/base-client/components/common/HIS/HButtons/HButtons_20260702163310.vue +832 -0
  9. package/.history/src/base-client/components/common/HIS/HButtons/HButtons_20260702163324.vue +748 -0
  10. package/.history/src/base-client/components/common/HIS/HButtons/HButtons_20260702165416.vue +748 -0
  11. package/.history/src/base-client/components/common/HIS/HButtons/HButtons_20260702174939.vue +793 -0
  12. package/.history/src/base-client/components/common/HIS/HForm/HForm_20260702115430.vue +1463 -0
  13. package/.history/src/base-client/components/common/HIS/HForm/HForm_20260723174534.vue +1708 -0
  14. package/.history/src/base-client/components/common/HIS/HForm/HForm_20260724104007.vue +1708 -0
  15. package/.history/src/base-client/components/common/HIS/HForm/HForm_20260724133409.vue +2003 -0
  16. package/.history/src/base-client/components/common/HIS/HForm/HForm_20260724133723.vue +2003 -0
  17. package/.history/src/base-client/components/common/HIS/HForm/HForm_20260724135500.vue +1982 -0
  18. package/.history/src/base-client/components/common/HIS/HForm/HForm_20260724135609.vue +1972 -0
  19. package/.history/src/base-client/components/common/HIS/HFormTable/HFormTable_20260310142713.vue +512 -0
  20. package/.history/src/base-client/components/common/HIS/HFormTable/HFormTable_20260310145118.vue +511 -0
  21. package/.history/src/base-client/components/common/HIS/HFormTable/HFormTable_20260311094834.vue +696 -0
  22. package/.history/src/base-client/components/common/HIS/HFormTable/HFormTable_20260320143028.vue +693 -0
  23. package/.history/src/base-client/components/common/HIS/HFormTable/HFormTable_20260409101450.vue +677 -0
  24. package/.history/src/base-client/components/common/HIS/HTab/HTab_20260625173703.vue +767 -0
  25. package/.history/src/base-client/components/common/HIS/HTab/HTab_20260626112425.vue +803 -0
  26. package/.history/src/base-client/components/common/XAddNativeForm/XAddNativeForm_20260626173854.vue +1522 -0
  27. package/.history/src/base-client/components/common/XCollapse/XCollapse_20260703180349.vue +862 -0
  28. package/.history/src/base-client/components/common/XCollapse/XCollapse_20260703180623.vue +862 -0
  29. package/.history/src/base-client/components/common/XCollapse/XCollapse_20260703180702.vue +842 -0
  30. package/.history/src/base-client/components/common/XDatePicker/index_20260724135453.vue +438 -0
  31. package/.history/src/base-client/components/common/XDatePicker/index_20260724135500.vue +429 -0
  32. package/.history/src/base-client/components/common/XForm/XFormItem_20260703101840.vue +1621 -0
  33. package/.history/src/base-client/components/common/XMarkdownViewer/XMarkdownViewer_20260519140403.vue +643 -0
  34. package/.history/src/base-client/components/common/XMarkdownViewer/XMarkdownViewer_20260519140829.vue +628 -0
  35. package/.history/src/base-client/components/common/XMarkdownViewer/demo_20260519142824.vue +104 -0
  36. package/.history/src/base-client/components/common/XMarkdownViewer/demo_20260519143155.vue +102 -0
  37. package/.history/src/base-client/components/common/XReportGrid/XReportTrGroup_20260703180412.vue +1102 -0
  38. package/.history/src/base-client/components/common/XReportGrid/XReportTrGroup_20260703180623.vue +1093 -0
  39. package/.history/src/base-client/components/common/XReportGrid/XReport_20260309171231.vue +1241 -0
  40. package/.history/src/base-client/components/common/XReportGrid/XReport_20260309171441.vue +1223 -0
  41. package/.history/src/base-client/components/his/HAi/HAi_20260612174826.vue +472 -0
  42. package/.history/src/base-client/components/his/HAi/HAi_20260612175839.vue +538 -0
  43. package/.history/src/base-client/components/his/HAi/HAi_20260615103331.vue +650 -0
  44. package/.history/src/base-client/components/his/HAi/HAi_20260618140214.vue +1396 -0
  45. package/.history/src/base-client/components/his/HAi/HAi_20260618140556.vue +1396 -0
  46. package/.history/src/base-client/components/his/XHDescriptions/XHDescriptions_20260424134504.vue +1469 -0
  47. package/.history/src/base-client/components/his/XRadio/XRadio_20260702140351.vue +774 -0
  48. package/.history/src/base-client/components/his/XRadio/XRadio_20260702140441.vue +774 -0
  49. package/.history/src/base-client/components/his/XSidebar/XSidebar_20260610171133.vue +788 -0
  50. package/.history/src/base-client/components/his/XSidebar/XSidebar_20260610171151.vue +780 -0
  51. package/.history/src/base-client/components/his/XTransfer/XTransfer_20260511170841.vue +585 -0
  52. package/.history/src/base-client/components/his/XTransfer/XTransfer_20260511171138.vue +787 -0
  53. package/.history/src/base-client/components/his/XTransfer/XTransfer_20260512141830.vue +739 -0
  54. package/.history/src/components/STable/index_20260409155138.js +806 -0
  55. package/.history/src/components/STable/index_20260409155218.js +814 -0
  56. package/.history/src/expression/core/Expression_20260305164427.js +1371 -0
  57. package/.history/src/expression/core/Expression_20260305170258.js +1358 -0
  58. package/.history/src/expression/core/Program_20260305111830.js +944 -0
  59. package/.history/src/expression/core/Program_20260305112041.js +931 -0
  60. package/.history/src/logic/LogicRunner_20260304154306.js +170 -0
  61. package/.history/src/logic/LogicRunner_20260304155553.js +112 -0
  62. package/.history/src/logic/LogicRunner_20260305105834.js +112 -0
  63. package/.history/src/logic/LogicRunner_20260305112718.js +129 -0
  64. package/.history/src/logic/LogicRunner_20260305182436.js +133 -0
  65. package/.history/src/logic/LogicRunner_20260306151301.js +213 -0
  66. package/.history/src/logic/LogicRunner_20260306152419.js +213 -0
  67. package/.history/src/logic/plugins/common/DateTools_20260305154159.js +61 -0
  68. package/.history/src/logic/plugins/common/DateTools_20260305154217.js +44 -0
  69. package/.history/src/logic/plugins/common/DateTools_20260305161014.js +44 -0
  70. package/.history/src/logic/plugins/common/HttpTools_20260305164352.js +80 -0
  71. package/.history/src/logic/plugins/common/HttpTools_20260305170258.js +75 -0
  72. package/.history/src/logic/plugins/common/HttpTools_20260305171634.js +75 -0
  73. package/.history/src/logic/plugins/common/HttpTools_20260306152419.js +72 -0
  74. package/.history/src/services/api/restTools_20260427142149.js +245 -0
  75. package/.history/src/services/api/restTools_20260427142853.js +230 -0
  76. package/.history/src/services/api/restTools_20260519135558.js +230 -0
  77. package/.history/src/services/api/restTools_20260519140825.js +230 -0
  78. package/.history/src/services/api/restTools_20260519151223.js +230 -0
  79. package/.history/src/theme/global_20260703101634.less +370 -0
  80. package/.history/src/theme/global_20260703101840.less +337 -0
  81. package/.history/src/theme/global_20260703104511.less +337 -0
  82. package/.history/src/theme/global_20260724113706.less +337 -0
  83. package/.history/src/theme/global_20260724133409.less +337 -0
  84. package/.history/src/utils/indexedDB_20260306150918.js +593 -0
  85. package/.history/src/utils/indexedDB_20260306151301.js +586 -0
  86. package/.idea/af-vue2-client.iml +3 -2
  87. package/.idea/codeStyles/Project.xml +62 -0
  88. package/.idea/codeStyles/codeStyleConfig.xml +5 -0
  89. package/.idea/misc.xml +6 -0
  90. package/docs/XCollapse/345/261/225/345/274/200/347/251/272/347/231/275/346/216/222/346/237/245/350/256/260/345/275/225.md +153 -0
  91. package/package.json +1 -1
  92. package/preview-input-box.html +180 -0
  93. package/src/base-client/components/common/HIS/HForm/HForm.vue +280 -0
  94. package/src/base-client/components/common/HIS/HTab/HTab.vue +790 -790
  95. package/src/base-client/components/common/XMarkdownViewer/demo.vue +102 -102
  96. package/src/base-client/components/his/XHisEditor/XDocTree.vue +529 -529
  97. package/src/base-client/components/his/XTitle/XTitle.vue +1 -1
  98. package/src/base-client/components/his/XTransfer/index.md +327 -327
  99. package/.idea/inspectionProfiles/Project_Default.xml +0 -23
  100. package/1yarn.lock +0 -11850
  101. package/logs/afgit.config.log +0 -12
  102. package/logs/afgit.config.log.2026-02-27 +0 -7
  103. package/logs/afgit.config_error.log +0 -6
  104. package/logs/afgit.config_error.log.2026-02-27 +0 -3
  105. package/pnpm-workspace.yaml +0 -4
  106. package/public/his/editor/mock/bind_data_with_images.html +0 -78
  107. package/public/his/editor/mock/blank.html +0 -645
  108. package/src/assets/img/80359c35a5465167cb25ff87bab49035d041a65558b35-YJOr3x.png +0 -0
  109. package/src/assets/img/hisLogo.png +0 -0
  110. package/src/base-client/components/common/HIS/HButtons/HButtonsSuffixDemo.vue +0 -141
  111. package/src/base-client/components/his/XHisEditor/ImageReportDemo.vue +0 -156
@@ -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>