@idooel/components 0.0.2 → 0.0.3-beta.2

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 (108) hide show
  1. package/README.md +98 -98
  2. package/dist/@idooel/components.esm.js +8527 -7070
  3. package/dist/@idooel/components.umd.js +8569 -7114
  4. package/jsconfig.json +7 -7
  5. package/package.json +65 -50
  6. package/packages/alert/index.js +4 -4
  7. package/packages/alert/src/index.vue +45 -45
  8. package/packages/batch-export/index.js +4 -4
  9. package/packages/batch-export/src/index.vue +104 -104
  10. package/packages/business-components/modal-fsm/index.js +4 -4
  11. package/packages/business-components/modal-fsm/src/index.vue +163 -163
  12. package/packages/business-components/modal-import/index.js +4 -4
  13. package/packages/business-components/modal-import/src/index.vue +222 -139
  14. package/packages/business-components/modal-timeline/index.js +4 -4
  15. package/packages/business-components/modal-timeline/src/index.vue +77 -77
  16. package/packages/business-components/tabs-sub-center/index.js +4 -4
  17. package/packages/business-components/tabs-sub-center/src/index.vue +116 -116
  18. package/packages/button/index.js +4 -4
  19. package/packages/button/src/index.vue +65 -65
  20. package/packages/checkbox/index.js +4 -4
  21. package/packages/checkbox/src/index.vue +52 -52
  22. package/packages/composite-components/button-group/index.js +4 -4
  23. package/packages/composite-components/button-group/src/index.vue +151 -151
  24. package/packages/composite-components/form-attachment/src/index.vue +14 -14
  25. package/packages/composite-components/form-img-crop/index.js +4 -4
  26. package/packages/composite-components/form-img-crop/src/index.vue +131 -120
  27. package/packages/composite-components/modal-confirm/index.js +4 -4
  28. package/packages/composite-components/modal-confirm/src/index.vue +103 -103
  29. package/packages/composite-components/modal-form/index.js +4 -4
  30. package/packages/composite-components/modal-form/src/index.vue +230 -230
  31. package/packages/composite-components/modal-img-crop/index.js +4 -4
  32. package/packages/composite-components/modal-img-crop/src/index.vue +298 -298
  33. package/packages/composite-components/modal-table/index.js +4 -4
  34. package/packages/composite-components/modal-table/src/index.vue +150 -155
  35. package/packages/composite-components/modal-tree/index.js +4 -4
  36. package/packages/composite-components/modal-tree/src/index.vue +75 -75
  37. package/packages/composite-components/search-area/index.js +4 -4
  38. package/packages/composite-components/search-area/src/index.vue +239 -237
  39. package/packages/composite-components/search-area/src/label.vue +35 -35
  40. package/packages/composite-components/select-entity-modal-table/index.js +4 -4
  41. package/packages/composite-components/select-entity-modal-table/src/index.vue +171 -171
  42. package/packages/date/index.js +4 -4
  43. package/packages/date/src/index.vue +112 -112
  44. package/packages/date-range/index.js +4 -4
  45. package/packages/date-range/src/index.vue +47 -47
  46. package/packages/form/index.js +4 -4
  47. package/packages/form/src/index.vue +393 -318
  48. package/packages/icon/index.js +4 -4
  49. package/packages/icon/src/index.vue +31 -31
  50. package/packages/index.js +162 -153
  51. package/packages/input/index.js +4 -4
  52. package/packages/input/src/index.vue +35 -35
  53. package/packages/input-number/index.js +4 -4
  54. package/packages/input-number/src/index.vue +23 -23
  55. package/packages/loading/index.js +4 -4
  56. package/packages/loading/src/index.vue +36 -36
  57. package/packages/meta/provider.js +4 -0
  58. package/packages/modal/index.js +4 -4
  59. package/packages/modal/src/index.vue +184 -184
  60. package/packages/models/form-group-model/index.js +4 -4
  61. package/packages/models/form-group-model/src/index.vue +271 -273
  62. package/packages/models/form-model/index.js +4 -4
  63. package/packages/models/form-model/src/index.vue +353 -232
  64. package/packages/models/step-model/index.js +4 -4
  65. package/packages/models/step-model/src/index.vue +224 -224
  66. package/packages/models/tree-table-model/index.js +4 -4
  67. package/packages/models/tree-table-model/src/index.vue +1376 -689
  68. package/packages/pagination/index.js +5 -0
  69. package/packages/pagination/src/index.vue +372 -0
  70. package/packages/radio/index.js +4 -4
  71. package/packages/radio/src/index.vue +56 -56
  72. package/packages/select/index.js +4 -4
  73. package/packages/select/src/index.vue +113 -105
  74. package/packages/select-entity/index.js +4 -4
  75. package/packages/select-entity/src/index.vue +119 -119
  76. package/packages/table/index.js +4 -4
  77. package/packages/table/src/action.vue +176 -172
  78. package/packages/table/src/index.vue +604 -289
  79. package/packages/tabs/index.js +4 -4
  80. package/packages/tabs/src/index.vue +55 -55
  81. package/packages/text/index.js +4 -4
  82. package/packages/text/src/index.vue +47 -47
  83. package/packages/text-editor/index.js +4 -4
  84. package/packages/text-editor/src/index.vue +72 -72
  85. package/packages/textarea/index.js +4 -4
  86. package/packages/textarea/src/index.vue +57 -57
  87. package/packages/theme/form.scss +21 -21
  88. package/packages/theme/index.scss +43 -42
  89. package/packages/theme/overrid.scss +7 -7
  90. package/packages/theme/styleClass.scss +2 -2
  91. package/packages/theme/variables.scss +55 -55
  92. package/packages/timeline/index.js +4 -4
  93. package/packages/timeline/src/index.vue +257 -257
  94. package/packages/tpl/index.js +4 -4
  95. package/packages/tpl/src/index.vue +55 -55
  96. package/packages/tree/index.js +4 -4
  97. package/packages/tree/src/TreeNode.vue +29 -29
  98. package/packages/tree/src/index.vue +101 -101
  99. package/packages/tree-select/index.js +4 -4
  100. package/packages/tree-select/src/index.vue +142 -142
  101. package/packages/upload/index.js +4 -4
  102. package/packages/upload/src/index.vue +998 -444
  103. package/packages/utils/index.js +67 -63
  104. package/packages/utils/vue2-expr.js +72 -0
  105. package/scripts/rollup.config.js +46 -42
  106. package/scripts/rollup.esm.config.js +11 -11
  107. package/scripts/rollup.umd.config.js +17 -14
  108. package/vitest.config.js +17 -0
@@ -1,689 +1,1376 @@
1
- <template>
2
- <section class="ele model__tree-table">
3
- <section class="model__tree-table--container" v-if="showTree">
4
- <div class="model__tree--title"></div>
5
- <section :ref="modelTreeWrapper" class="model__tree--wrapper" :style="{height: `${treeWrapperHeight}px`}">
6
- <ele-tree
7
- :tree-data="treeData"
8
- :defaultExpandedKeys="defaultExpandedKeys"
9
- :defaultSelectedKeys="defaultSelectedKeys"
10
- @select="selectTreeNode"
11
- :replace-fields="mapFields">
12
- </ele-tree>
13
- </section>
14
- </section>
15
- <section class="model__table--container" :ref="modelTableContainerRef">
16
- <div class="model__table--title" v-if="title">
17
- <template v-if="titleMode">
18
- <div :class="[`model__table-title--${titleMode}`]"></div>
19
- </template>
20
- <template v-else>
21
- <div class="model__table-title--text">{{ title }}</div>
22
- </template>
23
- </div>
24
- <section :ref="modelTableWrapper" class="model__table--wrapper">
25
- <ele-search-area :ref="searchArea" @search="onSearch" :data-source="searchMeta.elements"></ele-search-area>
26
- <div class="button-row__area">
27
- <ele-button-group class="model-table__button-group" v-on="overrideButtonGroupEvent" :ref="buttonGroup" @click="handleClickButtonGroup" :data-source="getButtonGroupElements"></ele-button-group>
28
- <slot name="tags"></slot>
29
- <slot v-if="$slots['sub-center']" name="sub-center"></slot>
30
- </div>
31
- <ele-table
32
- v-on="overrideTableEvent"
33
- :ref="tableRef"
34
- :row-selection="rowSelection"
35
- :loading="loading"
36
- :columns="columns"
37
- :total="total"
38
- :x="x"
39
- :y="y"
40
- :bordered="setBorder"
41
- :height="tableHeight"
42
- :width="tableWidth"
43
- :actions="actions"
44
- :pageSize="pageSize"
45
- :pageSizeOptions="pageSizeOptions"
46
- :data-source="tableData"
47
- @change-page="onChangePage"
48
- ></ele-table>
49
- </section>
50
- </section>
51
- <ele-modal-form v-model="modalFormValue" v-on="overrideModalFormEvent" :meta="modalFormMeta"></ele-modal-form>
52
- <ele-modal-fsm v-model="showFsmModal" :contextProp="fsmContextProp" :meta="fsmMeta" @cancel="handleCloseFsmModal"></ele-modal-fsm>
53
- <ele-modal-table
54
- :meta="modalTableMeta"
55
- v-model="modalTableValue"
56
- v-on="overrideModalTableEvent"
57
- ></ele-modal-table>
58
- </section>
59
- </template>
60
-
61
- <script>
62
- import { type, net, util } from '@idooel/shared'
63
- import { v4 as uuidv4 } from 'uuid'
64
- import { BUILT_IN_EVENT_NAMES, RESERVE_EVENT_NAMES, parseFieldMap, BUILT_IN_TRIGGER, CONTEXT } from '../../../utils'
65
- export default {
66
- name: 'ele-tree-table-model',
67
- props: {
68
- title: {
69
- type: [Object, String]
70
- },
71
- overHeight: {
72
- type: Number,
73
- default: 0
74
- },
75
- treeMeta: {
76
- type: Object,
77
- default: () => ({})
78
- },
79
- searchMeta: {
80
- type: Object,
81
- default: () => ({})
82
- },
83
- buttonGroupMeta: {
84
- typeof: Object,
85
- default: () => ({})
86
- },
87
- tableMeta: {
88
- type: Object,
89
- default: () => ({})
90
- },
91
- createMeta: {
92
- type: Object
93
- },
94
- editMeta: {
95
- type: Object
96
- }
97
- },
98
- provide () {
99
- return {
100
- requestTreeData: this.requestTreeData,
101
- requestTableData: this.requestTableData,
102
- [CONTEXT]: () => {
103
- return {
104
- exposed: this.exposed
105
- }
106
- }
107
- }
108
- },
109
- data () {
110
- return {
111
- tableHeight: 0,
112
- tableWidth: 0,
113
- modalFormMeta: {},
114
- modalFormValue: false,
115
- treeData: [],
116
- tableData: [],
117
- defaultExpandedKeys: [],
118
- defaultSelectedKeys: [],
119
- replaceFields: {
120
- title: 'title',
121
- children: 'children',
122
- key: 'id'
123
- },
124
- loading: false,
125
- total: 0,
126
- tableQuerys: {},
127
- resizeObserverModelTableWrapper: null,
128
- modelTableWrapperHeight: 0,
129
- currentTreeNodeData: {},
130
- currentRowData: {},
131
- treeWrapperHeight: 0,
132
- currentTableSelection: this.currentTableMode == 'radio' ? {} : [],
133
- showFsmModal: false,
134
- fsmMeta: {},
135
- fsmContextProp: {},
136
- modalTableValue: false,
137
- modalTableMeta: {}
138
- }
139
- },
140
- computed: {
141
- setBorder () {
142
- return this.tableMeta.bordered === false ? false : true
143
- },
144
- rowSelection () {
145
- if (!this.currentTableMode) return void 0
146
- return {
147
- columnTitle: this.currentSelectionColumn.columnTitle,
148
- fixed: true,
149
- type: this.currentTableMode,
150
- onChange: this.onChangeTableSelection
151
- }
152
- },
153
- currentSelectionColumn () {
154
- const { multiple } = this.tableMeta
155
- const target = this.columns.find(item => Object.keys(item).includes('multiple'))
156
- const isGlobalExistMultiple = Object.keys(this.tableMeta).includes('multiple')
157
- if (target) {
158
- return target
159
- } else if (isGlobalExistMultiple) {
160
- return { multiple }
161
- }
162
- return void 0
163
- },
164
- x () {
165
- const { x } = this.tableMeta
166
- return x
167
- },
168
- y () {
169
- const { y } = this.tableMeta
170
- return y
171
- },
172
- currentTableMode () {
173
- if (!this.currentSelectionColumn) return void 0
174
- const { multiple } = this.currentSelectionColumn
175
- if (type.isBool(multiple)) {
176
- if (multiple) {
177
- return 'checkbox'
178
- } else {
179
- return 'radio'
180
- }
181
- } else {
182
- return void 0
183
- }
184
- },
185
- modelTableContainerRef () {
186
- return uuidv4()
187
- },
188
- titleMode () {
189
- if (type.isObject(this.title)) {
190
- const { mode = '' } = this.title
191
- return mode
192
- }
193
- return void 0
194
- },
195
- tableRef () {
196
- return uuidv4()
197
- },
198
- exposed () {
199
- return {
200
- showModalForm: this.showModalForm,
201
- closeModalForm: this.closeModalForm,
202
- showModalTable: this.showModalTable,
203
- closeModalTable: this.closeModalTable,
204
- currentTableSelection: this.currentTableSelection,
205
- currentTreeNode: this.currentTreeNodeData,
206
- requestTableData: this.requestTableData,
207
- refreshTreeData: this.refreshTreeData,
208
- querys: this.tableQuerys,
209
- currentRowData: this.currentRowData,
210
- getCurrentRowData: this.getCurrentRowData,
211
- setCurrentRowData: this.setCurrentRowData,
212
- setCurrentTableSelection: this.setCurrentTableSelection,
213
- getCurrentTableSelection: this.getCurrentTableSelection,
214
- cleanCurrentModelEffect: this.cleanCurrentModelEffect,
215
- route: this.$route,
216
- _route: this.$route.query,
217
- _routeMeta: this.$route.meta
218
- }
219
- },
220
- overrideTableEvent () {
221
- const events = this.actions.reduce((ret, action) => {
222
- ret[action.eventName || action.key] = (e) => {
223
- this.setCurrentRowData(e.exposed.currentRowData)
224
- const { target } = action
225
- const targetMeta = this.findMetaByKey(target)
226
- const { mode } = targetMeta
227
- mode && this.dispatchTrigger({ mode, record: e.exposed.currentRowData, modeMeta: targetMeta })
228
- this.$emit(action.eventName || action.key, { ...e, currentTreeNode: this.currentTreeNodeData, exposed: { ...this.exposed, ...e.exposed } })
229
- }
230
- return ret
231
- }, {})
232
- return {
233
- ...this.$listeners,
234
- ...events,
235
- [BUILT_IN_EVENT_NAMES.EDIT]: this[BUILT_IN_EVENT_NAMES.EDIT],
236
- [BUILT_IN_EVENT_NAMES.SUBMIT]: this[BUILT_IN_EVENT_NAMES.SUBMIT]
237
- }
238
- },
239
- overrideModalFormEvent () {
240
- const { footerMeta } = this.modalFormMeta
241
- const { elements = [] } = footerMeta || {}
242
- const eles = type.isFunction(elements) ? elements.call(this) : elements
243
- const events = eles.reduce((ret, ele) => {
244
- ret[ele.eventName] = (e = {}) => {
245
- if (ele.eventName === 'cancel') {
246
- this.closeModalForm()
247
- } else {
248
- const { exposed = {} } = e
249
- this.$emit(`${ele.eventName || ele.key}`, { ...e, currentTreeNode: this.currentTreeNodeData, exposed: Object.assign({}, exposed )})
250
- }
251
- }
252
- return ret
253
- }, {})
254
- return {
255
- ...events
256
- }
257
- },
258
- overrideModalTableEvent () {
259
- const { footerMeta } = this.modalTableMeta
260
- const { elements = [] } = footerMeta || {}
261
- const eles = type.isFunction(elements) ? elements.call(this) : elements
262
- const events = eles.reduce((ret, ele) => {
263
- ret[ele.eventName] = (e = {}) => {
264
- if (ele.eventName === 'cancel') {
265
- this.closeModalTable()
266
- } else {
267
- const { exposed = {} } = e
268
- this.$emit(`${ele.eventName || ele.key}`, { ...e, currentTreeNode: this.currentTreeNodeData, exposed: Object.assign({}, exposed )})
269
- }
270
- }
271
- return ret
272
- }, {})
273
- return {
274
- ...events,
275
- exposed: this.exposed
276
- }
277
- },
278
- overrideButtonGroupEvent () {
279
- const events = this.getButtonGroupElements.reduce((ret, ele) => {
280
- ret[ele.eventName] = (e) => {
281
- this.$emit(ele.eventName || 'click', { ...e, currentTreeNode: this.currentTreeNodeData, exposed: Object.assign({}, e.exposed || {}, this.exposed)})
282
- }
283
- return ret
284
- }, {})
285
- return {
286
- ...this.$listeners,
287
- ...events,
288
- [BUILT_IN_EVENT_NAMES.CREATE]: this[BUILT_IN_EVENT_NAMES.CREATE],
289
- exposed: this.exposed
290
- }
291
- },
292
- showTree () {
293
- return !!Object.keys(this.treeMeta).length
294
- },
295
- buttonGroup () {
296
- return uuidv4()
297
- },
298
- searchArea () {
299
- return uuidv4()
300
- },
301
- modelTreeWrapper () {
302
- return uuidv4()
303
- },
304
- modelTableWrapper () {
305
- return uuidv4()
306
- },
307
- actions () {
308
- const { operations } = this.tableMeta
309
- if (operations) {
310
- return operations.elements
311
- } else {
312
- return []
313
- }
314
- },
315
- pageSize () {
316
- const { page = {} } = this.tableMeta
317
- return page.pageSize || 10
318
- },
319
- pageSizeOptions () {
320
- const { page = {} } = this.tableMeta
321
- return page.pageSizeOptions || ['10', '20', '30', '40']
322
- },
323
- columns () {
324
- const { columns, operations } = this.tableMeta
325
- if (type.get(columns) === 'array') {
326
- const columnsOptions = columns.map(item => {
327
- const { mode = 'text' } = item
328
- if (item.render) {
329
- return {
330
- ...item,
331
- customRender: (text, record, index) => {
332
- const { $createElement } = this
333
- return item.render.call(this,
334
- { h: $createElement, ctx: this },
335
- text ? typeof text == 'object' ? text[item.dataIndex] : text : '',
336
- record, index)
337
- }
338
- }
339
- } else if (mode !== BUILT_IN_TRIGGER.TEXT) {
340
- const { [`${mode}Meta`]: modeMeta } = item
341
- return {
342
- ...item,
343
- customRender: (text, record, index) => {
344
- return <span onClick={() => this.dispatchTrigger({ mode, record, modeMeta, index })} class={ mode }>{ text }</span>
345
- }
346
- }
347
- }
348
- return {
349
- ...item
350
- }
351
- })
352
- if (operations) {
353
- return [
354
- ...columnsOptions,
355
- {
356
- title: '操作',
357
- width: operations.width,
358
- key: 'action',
359
- fixed: 'right',
360
- scopedSlots: { customRender: 'action' }
361
- }
362
- ]
363
- }
364
- return columnsOptions
365
- } else {
366
- console.error('Error: columns is invalid, please check it')
367
- return []
368
- }
369
- },
370
- getButtonGroupElements () {
371
- const { elements } = this.buttonGroupMeta
372
- if (type.get(elements) === 'function') {
373
- return elements.call(this)
374
- } else if (type.get(elements) === 'array') {
375
- return elements
376
- } else {
377
- return []
378
- }
379
- },
380
- mapFields () {
381
- const { replaceFields = {} } = this.treeMeta
382
- const mapFields = type.isEmpty(replaceFields) ? this.replaceFields : replaceFields
383
- return mapFields
384
- }
385
- },
386
- async created () {
387
- if (this.showTree) {
388
- this.treeData = await this.requestTreeData()
389
- const [defaultTreeNode = {}] = this.treeData
390
- this.defaultExpandedKeys = [defaultTreeNode[this.mapFields.key]]
391
- this.defaultSelectedKeys = [defaultTreeNode[this.mapFields.key]]
392
- this.currentTreeNodeData = defaultTreeNode
393
- }
394
- const { params = {}, fieldMap = {}, overrideInit = false } = this.tableMeta
395
- const ctx = { ...this.currentTreeNodeData, _route: this.$route.query }
396
- const initQuerys = Object.assign({}, params, parseFieldMap(fieldMap, ctx))
397
- if (overrideInit) {
398
- this.$emit(RESERVE_EVENT_NAMES.INIT, { ...this.exposed })
399
- } else {
400
- this.tableData = await this.requestTableData(initQuerys)
401
- }
402
- },
403
- methods: {
404
- async refreshTreeData () {
405
- this.treeData = await this.requestTreeData()
406
- const [defaultTreeNode = {}] = this.treeData
407
- this.defaultExpandedKeys = [defaultTreeNode[this.mapFields.key]]
408
- this.defaultSelectedKeys = [defaultTreeNode[this.mapFields.key]]
409
- this.currentTreeNodeData = defaultTreeNode
410
- },
411
- dispatchTrigger ({ mode, record = {}, modeMeta = { } }) {
412
- switch (mode) {
413
- case BUILT_IN_TRIGGER.FSM:
414
- this[`${BUILT_IN_TRIGGER.FSM}Trigger`](record, modeMeta = type.isEmpty(modeMeta) ? {
415
- url: 'api-fsm/workbench/fsm/auditFlow',
416
- requestType: 'GET',
417
- fieldMap: {
418
- modelCode: 'modelCode',
419
- businessId: 'businessId'
420
- }
421
- } : modeMeta)
422
- break
423
- case BUILT_IN_TRIGGER.ELE_MODAL_FORM:
424
- this.modalFormMeta = modeMeta
425
- this.showModalForm(modeMeta)
426
- break
427
- case BUILT_IN_TRIGGER.ELE_MODAL_TABLE:
428
- this.modalTableMeta = modeMeta
429
- this.showModalTable(modeMeta)
430
- break
431
- default:
432
- break
433
- }
434
- },
435
- handleCloseFsmModal () {
436
- this.showFsmModal = false
437
- },
438
- [`${BUILT_IN_TRIGGER.FSM}Trigger`] (record, meta) {
439
- this.fsmMeta = meta
440
- this.fsmContextProp = record
441
- this.showFsmModal = true
442
- },
443
- onChangeTableSelection (_, selectedRows = []) {
444
- if (this.currentTableMode === 'radio') {
445
- this.setCurrentTableSelection(selectedRows)
446
- this.$emit('on-change-table-selection', this.currentTableSelection)
447
- this.$emit('x:refresh-exposed', { exposed: this.exposed })
448
- } else {
449
- this.setCurrentTableSelection(selectedRows)
450
- this.$emit('on-change-table-selection', this.currentTableSelection)
451
- this.$emit('x:refresh-exposed', { exposed: this.exposed })
452
- }
453
- },
454
- setCurrentTableSelection (props = {}) {
455
- if (this.currentTableMode === 'radio') {
456
- this.$set(this, 'currentTableSelection', (type.isArray(props) && props.length > 0) ? props[0] : type.isObject(props) ? props : {})
457
- } else {
458
- this.$set(this, 'currentTableSelection', type.isArray(props) ? props : [])
459
- }
460
- },
461
- getCurrentTableSelection () {
462
- return this.currentTableSelection
463
- },
464
- setCurrentRowData (props = {}) {
465
- this.currentRowData = props
466
- },
467
- getCurrentRowData () {
468
- return this.currentRowData
469
- },
470
- cleanCurrentModelEffect () {
471
- this.setCurrentTableSelection()
472
- this.setCurrentRowData({})
473
- },
474
- [BUILT_IN_EVENT_NAMES.SUBMIT] (props = {}) {
475
- this.cleanCurrentModelEffect()
476
- this.requestTableData()
477
- },
478
- [BUILT_IN_EVENT_NAMES.EDIT] (props = {}) {
479
- const { record = {} } = props
480
- this.currentRowData = record
481
- this.setCurrentRowData(record)
482
- this.modalFormMeta = this.editMeta
483
- this.modalFormValue = true
484
- },
485
- [BUILT_IN_EVENT_NAMES.CREATE] () {
486
- this.modalFormMeta = this.createMeta
487
- this.modalFormValue = true
488
- },
489
- showModalForm (modeMeta = {}) {
490
- if (type.isStr(modeMeta)) {
491
- const targetMeta = this.findMetaByKey(modeMeta)
492
- this.modalFormMeta = targetMeta
493
- } else {
494
- this.modalFormMeta = modeMeta
495
- }
496
- this.modalFormValue = true
497
- },
498
- showModalTable (modeMeta = {}) {
499
- console.log('showModalTable', modeMeta)
500
- if (type.isStr(modeMeta)) {
501
- const targetMeta = this.findMetaByKey(modeMeta)
502
- this.modalTableMeta = targetMeta
503
- } else {
504
- this.modalTableMeta = modeMeta
505
- }
506
- this.modalTableValue = true
507
- },
508
- closeModalForm () {
509
- this.modalFormValue = false
510
- },
511
- closeModalTable () {
512
- console.log('closeModalTable')
513
- this.modalTableValue = false
514
- },
515
- findMetaByKey (key) {
516
- return this.$attrs[key] || {}
517
- },
518
- handleClickButtonGroup (props) {
519
- const { eventName, target } = props
520
- const targetMeta = this.findMetaByKey(target)
521
- const { mode } = targetMeta
522
- mode && this.dispatchTrigger({ mode, modeMeta: targetMeta })
523
- this.$emit(eventName || 'click', { currentTreeNode: this.currentTreeNodeData })
524
- },
525
- watchViewPort () {
526
- const modelTableWrapper = this.$refs[this.modelTableWrapper]
527
- const { top } = modelTableWrapper.getBoundingClientRect()
528
- this.$refs[this.modelTreeWrapper].style.height = `calc(100vh - ${top}px)`
529
- },
530
- async onSearch (props) {
531
- const { overrideInit = false } = this.tableMeta
532
- this.tableQuerys = Object.assign(this.tableQuerys, props)
533
- if (overrideInit) {
534
- this.$emit(RESERVE_EVENT_NAMES.TREE_CHANGE, { ...this.exposed })
535
- } else {
536
- this.tableData = await this.requestTableData()
537
- }
538
- },
539
- async selectTreeNode (selectedKeys, e) {
540
- const { fieldMap } = this.tableMeta
541
- this.currentTreeNodeData = e.node.$vnode.data.props.dataRef || {}
542
- //@deprecated '_' namespace is deprecated, please use 'exposed' instead
543
- const execFieldMapRet = parseFieldMap(fieldMap, { ...this.currentTreeNodeData, exposed: this.exposed, _route: this.exposed._route })
544
- const { overrideInit = false } = this.tableMeta
545
- if (overrideInit) {
546
- this.$emit(RESERVE_EVENT_NAMES.TREE_CHANGE, { ...this.exposed })
547
- } else {
548
- this.tableData = await this.requestTableData(execFieldMapRet)
549
- }
550
- },
551
- async requestTreeData () {
552
- const { url, requestType = 'GET', params = {}, fieldMap = {} } = this.treeMeta
553
- const fieldMapRet = parseFieldMap(fieldMap, { ...this.currentTreeNodeData, _route: this.$route.query })
554
- const ret = await net[requestType.toLowerCase()](
555
- url,
556
- { ...params, ...fieldMapRet }
557
- ).then(resp => {
558
- const { data } = resp || {}
559
- return data
560
- })
561
- return ret
562
- },
563
- async onChangePage (page, pageSize) {
564
- this.tableData = await this.requestTableData({ currentPage: page, pageSize })
565
- },
566
- async requestTableData (props = {}) {
567
- const { url, requestType = 'GET', page = {} } = this.tableMeta
568
- const { pageSize = 10 } = page
569
- this.tableQuerys = Object.assign(this.tableQuerys, { currentPage: 1, pageSize }, props)
570
- this.$emit(RESERVE_EVENT_NAMES.WATCH, { ...this.exposed })
571
- this.loading = true
572
- const ret = await net[requestType.toLowerCase()](
573
- url,
574
- this.tableQuerys
575
- ).then(resp => {
576
- const { data = [], count } = resp || {}
577
- this.total = count
578
- this.loading = false
579
- return (data || []).map(item => {
580
- delete item.children
581
- return {
582
- key: uuidv4(),
583
- ...item
584
- }
585
- })
586
- })
587
- this.cleanCurrentModelEffect()
588
- this.tableData = ret
589
- return ret
590
- },
591
- refreshTreeStatus (props = {}) {},
592
- refreshTableStatus (props = {}) {},
593
- calculateTableHeight () {
594
- const currentViewportHeight = window.innerHeight
595
- const tableRef = this.$refs[this.tableRef]
596
- const { top: tableToTop, width } = tableRef.$el.getBoundingClientRect()
597
- this.tableWidth = width
598
- this.tableHeight = currentViewportHeight - tableToTop - this.overHeight
599
- },
600
- calculateTreeHeight () {
601
- if (!this.showTree) return
602
- const modelTableContainerRef = this.$refs[this.modelTableContainerRef]
603
- const { height } = modelTableContainerRef.getBoundingClientRect()
604
- this.treeWrapperHeight = height
605
- }
606
- },
607
- mounted () {
608
- this.calculateTableHeight()
609
- this.$nextTick(() => {
610
- this.calculateTreeHeight()
611
- })
612
- this.resizeObserverModelTableWrapper = new ResizeObserver(entries => {
613
- for (const _ of entries) {
614
- requestAnimationFrame(() => {
615
- this.calculateTableHeight()
616
- })
617
- }
618
- })
619
- this.resizeObserverModelTableWrapper.observe(this.$refs[this.modelTableWrapper])
620
- },
621
- destroyed () {
622
- this.resizeObserverModelTableWrapper.disconnect()
623
- }
624
- }
625
- </script>
626
-
627
- <style lang="scss" scoped>
628
- .ele {
629
- &.model__tree-table {
630
- background: transparent;
631
- display: flex;
632
- flex-direction: row;
633
- width: 100%;
634
- .model__tree-table--container {
635
- .model__tree--wrapper {
636
- width: 240px;
637
- background: #fff;
638
- flex-shrink: 0;
639
- padding: 16px;
640
- box-sizing: border-box;
641
- margin-right: 16px;
642
- overflow-y: auto;
643
- }
644
- }
645
- .model__table--container {
646
- width: 100%;
647
- min-width: 0;
648
- background: #fff;
649
- .model__table--title {
650
- .model__table-title--bar {
651
- width: 100%;
652
- height: 8px;
653
- background: var(--idooel-primary-color);
654
- border-top-left-radius: 4px;
655
- border-top-right-radius: 4px;
656
- }
657
- .model__table-title--text {
658
- text-align: left;
659
- padding: 16px;
660
- font-size: 16px;
661
- font-weight: bold;
662
- background: #fff;
663
- border-bottom: 1px solid;
664
- border-color: var(--idoole-black-016);
665
- }
666
- }
667
- .model__table--wrapper {
668
- background: #fff;
669
- .button-row__area {
670
- width: 100%;
671
- display: flex;
672
- flex-direction: row;
673
- align-items: center;
674
- justify-content: space-between;
675
- padding-top: 16px;
676
- padding-bottom: 8px;
677
- padding-right: 16px;
678
- }
679
- .g-table__wrapper {
680
- .fsm {
681
- cursor: pointer;
682
- color: var(--idooel-primary-color);
683
- }
684
- }
685
- }
686
- }
687
- }
688
- }
689
- </style>
1
+ <template>
2
+ <section class="ele model__tree-table">
3
+ <section class="model__tree-table--container" v-if="showTree">
4
+ <div class="model__tree--title"></div>
5
+ <section :ref="modelTreeWrapper" class="model__tree--wrapper" :style="{height: `${treeWrapperHeight}px`}">
6
+ <ele-tree
7
+ :tree-data="treeData"
8
+ :defaultExpandedKeys="defaultExpandedKeys"
9
+ :defaultSelectedKeys="defaultSelectedKeys"
10
+ @select="selectTreeNode"
11
+ :replace-fields="mapFields">
12
+ </ele-tree>
13
+ </section>
14
+ </section>
15
+ <section class="model__table--container" :ref="modelTableContainerRef">
16
+ <div class="model__table--title" v-if="title">
17
+ <template v-if="titleMode">
18
+ <div :class="[`model__table-title--${titleMode}`]"></div>
19
+ </template>
20
+ <template v-else>
21
+ <div class="model__table-title--text">{{ title }}</div>
22
+ </template>
23
+ </div>
24
+ <section :ref="modelTableWrapper" class="model__table--wrapper">
25
+ <ele-search-area :ref="searchArea" @search="onSearch" :data-source="searchMeta.elements"></ele-search-area>
26
+ <div class="button-row__area">
27
+ <ele-button-group class="model-table__button-group" v-on="overrideButtonGroupEvent" :ref="buttonGroup" @click="handleClickButtonGroup" :data-source="getButtonGroupElements"></ele-button-group>
28
+ <slot name="tags"></slot>
29
+ <slot v-if="$slots['sub-center']" name="sub-center"></slot>
30
+ </div>
31
+ <ele-table
32
+ v-on="overrideTableEvent"
33
+ :ref="tableRef"
34
+ :row-selection="rowSelection"
35
+ :loading="loading"
36
+ :columns="columns"
37
+ :total="total"
38
+ :x="x"
39
+ :y="y"
40
+ :bordered="setBorder"
41
+ :height="tableHeight"
42
+ :width="tableWidth"
43
+ :actions="actions"
44
+ :pageSize="pageSize"
45
+ :pageSizeOptions="pageSizeOptions"
46
+ :data-source="tableData"
47
+ :mode="mode"
48
+ @change-page="onChangePage"
49
+ ></ele-table>
50
+ </section>
51
+ </section>
52
+ <ele-modal-form v-model="modalFormValue" v-on="overrideModalFormEvent" :meta="modalFormMeta"></ele-modal-form>
53
+ <ele-modal-fsm v-model="showFsmModal" :contextProp="fsmContextProp" :meta="fsmMeta" @cancel="handleCloseFsmModal"></ele-modal-fsm>
54
+ <ele-modal-table
55
+ :meta="modalTableMeta"
56
+ v-model="modalTableValue"
57
+ v-on="overrideModalTableEvent"
58
+ ></ele-modal-table>
59
+ </section>
60
+ </template>
61
+
62
+ <script>
63
+ import { type, net } from '@idooel/shared'
64
+ import { v4 as uuidv4 } from 'uuid'
65
+ import { BUILT_IN_EVENT_NAMES, RESERVE_EVENT_NAMES, BUILT_IN_TRIGGER, CONTEXT } from '../../../utils'
66
+ import { createTreeTableModel } from '@idooel/runtime-context'
67
+ export default {
68
+ name: 'ele-tree-table-model',
69
+ props: {
70
+ title: {
71
+ type: [Object, String]
72
+ },
73
+ overHeight: {
74
+ type: Number,
75
+ default: 0
76
+ },
77
+ treeMeta: {
78
+ type: Object,
79
+ default: () => ({})
80
+ },
81
+ searchMeta: {
82
+ type: Object,
83
+ default: () => ({})
84
+ },
85
+ buttonGroupMeta: {
86
+ typeof: Object,
87
+ default: () => ({})
88
+ },
89
+ tableMeta: {
90
+ type: Object,
91
+ default: () => ({})
92
+ },
93
+ createMeta: {
94
+ type: Object
95
+ },
96
+ editMeta: {
97
+ type: Object
98
+ }
99
+ },
100
+ inject: {
101
+ parentScopeId: {
102
+ default: () => (() => null)
103
+ },
104
+ parentEvalExpression: {
105
+ from: 'evalExpression',
106
+ default: () => (() => undefined)
107
+ }
108
+ },
109
+ provide () {
110
+ return {
111
+ evalExpression: this.parseExpression,
112
+ requestTreeData: this.requestTreeData,
113
+ requestTableData: this.requestTableData,
114
+ keepAliveRefresh: this.keepAliveRefresh,
115
+ parentScopeId: () => this.model?.scopeId,
116
+ [CONTEXT]: () => {
117
+ return {
118
+ exposed: this.exposed
119
+ }
120
+ }
121
+ }
122
+ },
123
+ data () {
124
+ return {
125
+ tableHeight: 0,
126
+ tableWidth: 0,
127
+ modalFormMeta: {},
128
+ modalFormValue: false,
129
+ treeData: [],
130
+ tableData: [],
131
+ defaultExpandedKeys: [],
132
+ defaultSelectedKeys: [],
133
+ replaceFields: {
134
+ title: 'title',
135
+ children: 'children',
136
+ key: 'id'
137
+ },
138
+ loading: false,
139
+ total: 0,
140
+ tableQuerys: {},
141
+ resizeObserverModelTableWrapper: null,
142
+ resizeObserverModelTableContainer: null,
143
+ modelTableWrapperHeight: 0,
144
+ currentTreeNodeData: {},
145
+ currentRowData: {},
146
+ treeWrapperHeight: 0,
147
+ currentTableSelection: this.currentTableMode == 'radio' ? {} : [],
148
+ showFsmModal: false,
149
+ fsmMeta: {},
150
+ fsmContextProp: {},
151
+ modalTableValue: false,
152
+ modalTableMeta: {}
153
+ }
154
+ },
155
+ computed: {
156
+ setBorder () {
157
+ return this.tableMeta.bordered === false ? false : true
158
+ },
159
+ rowSelection () {
160
+ if (!this.currentTableMode) return void 0
161
+ return {
162
+ columnTitle: this.currentSelectionColumn.columnTitle,
163
+ fixed: true,
164
+ type: this.currentTableMode,
165
+ onChange: this.onChangeTableSelection
166
+ }
167
+ },
168
+ currentSelectionColumn () {
169
+ const { multiple } = this.tableMeta
170
+ const target = this.columns.find(item => Object.keys(item).includes('multiple'))
171
+ const isGlobalExistMultiple = Object.keys(this.tableMeta).includes('multiple')
172
+ if (target) {
173
+ return target
174
+ } else if (isGlobalExistMultiple) {
175
+ return { multiple }
176
+ }
177
+ return void 0
178
+ },
179
+ x () {
180
+ const { x } = this.tableMeta
181
+ return x
182
+ },
183
+ y () {
184
+ const { y } = this.tableMeta
185
+ return y
186
+ },
187
+ currentTableMode () {
188
+ if (!this.currentSelectionColumn) return void 0
189
+ const { multiple } = this.currentSelectionColumn
190
+ if (type.isBool(multiple)) {
191
+ if (multiple) {
192
+ return 'checkbox'
193
+ } else {
194
+ return 'radio'
195
+ }
196
+ } else {
197
+ return void 0
198
+ }
199
+ },
200
+ modelTableContainerRef () {
201
+ return uuidv4()
202
+ },
203
+ titleMode () {
204
+ if (type.isObject(this.title)) {
205
+ const { mode = '' } = this.title
206
+ return mode
207
+ }
208
+ return void 0
209
+ },
210
+ tableRef () {
211
+ return uuidv4()
212
+ },
213
+ exposed () {
214
+ return {
215
+ showModalForm: this.showModalForm,
216
+ closeModalForm: this.closeModalForm,
217
+ showModalTable: this.showModalTable,
218
+ closeModalTable: this.closeModalTable,
219
+ currentTableSelection: this.currentTableSelection,
220
+ currentTreeNode: this.currentTreeNodeData,
221
+ requestTableData: this.requestTableData,
222
+ keepAliveRefresh: this.keepAliveRefresh,
223
+ refreshTreeData: this.refreshTreeData,
224
+ querys: this.tableQuerys,
225
+ currentRowData: this.getCurrentRowData(),
226
+ getCurrentRowData: this.getCurrentRowData,
227
+ setCurrentRowData: this.setCurrentRowData,
228
+ setCurrentTableSelection: this.setCurrentTableSelection,
229
+ getCurrentTableSelection: this.getCurrentTableSelection,
230
+ cleanCurrentModelEffect: this.cleanCurrentModelEffect,
231
+ route: this.$route,
232
+ _route: this.$route.query,
233
+ _routeMeta: this.$route.meta
234
+ }
235
+ },
236
+ overrideTableEvent () {
237
+ const events = this.actions.reduce((ret, action) => {
238
+ ret[action.eventName || action.key] = (e) => {
239
+ this.setCurrentRowData(e.exposed.currentRowData)
240
+ const { target } = action
241
+ const targetMeta = this.findMetaByKey(target)
242
+ const { mode } = targetMeta
243
+ mode && this.dispatchTrigger({ mode, record: e.exposed.currentRowData, modeMeta: targetMeta })
244
+ this.$emit(action.eventName || action.key, { ...e, currentTreeNode: this.currentTreeNodeData, exposed: { ...this.exposed, ...e.exposed } })
245
+ }
246
+ return ret
247
+ }, {})
248
+ return {
249
+ ...this.$listeners,
250
+ ...events,
251
+ [BUILT_IN_EVENT_NAMES.EDIT]: this[BUILT_IN_EVENT_NAMES.EDIT],
252
+ [BUILT_IN_EVENT_NAMES.SUBMIT]: this[BUILT_IN_EVENT_NAMES.SUBMIT]
253
+ }
254
+ },
255
+ overrideModalFormEvent () {
256
+ const { footerMeta } = this.modalFormMeta
257
+ const { elements = [] } = footerMeta || {}
258
+ const eles = type.isFunction(elements) ? elements.call(this) : elements
259
+ const events = eles.reduce((ret, ele) => {
260
+ ret[ele.eventName] = (e = {}) => {
261
+ if (ele.eventName === 'cancel') {
262
+ this.closeModalForm()
263
+ } else {
264
+ const { exposed = {} } = e
265
+ this.$emit(`${ele.eventName || ele.key}`, { ...e, currentTreeNode: this.currentTreeNodeData, exposed: Object.assign({}, exposed )})
266
+ }
267
+ }
268
+ return ret
269
+ }, {})
270
+ return {
271
+ ...events
272
+ }
273
+ },
274
+ overrideModalTableEvent () {
275
+ const { footerMeta } = this.modalTableMeta
276
+ const { elements = [] } = footerMeta || {}
277
+ const eles = type.isFunction(elements) ? elements.call(this) : elements
278
+ const events = eles.reduce((ret, ele) => {
279
+ ret[ele.eventName] = (e = {}) => {
280
+ if (ele.eventName === 'cancel') {
281
+ this.closeModalTable()
282
+ } else {
283
+ const { exposed = {} } = e
284
+ this.$emit(`${ele.eventName || ele.key}`, { ...e, currentTreeNode: this.currentTreeNodeData, exposed: Object.assign({}, exposed )})
285
+ }
286
+ }
287
+ return ret
288
+ }, {})
289
+ return {
290
+ ...events,
291
+ exposed: this.exposed
292
+ }
293
+ },
294
+ overrideButtonGroupEvent () {
295
+ const events = this.getButtonGroupElements.reduce((ret, ele) => {
296
+ ret[ele.eventName] = (e) => {
297
+ this.$emit(ele.eventName || 'click', { ...e, currentTreeNode: this.currentTreeNodeData, exposed: Object.assign({}, e.exposed || {}, this.exposed)})
298
+ }
299
+ return ret
300
+ }, {})
301
+ return {
302
+ ...this.$listeners,
303
+ ...events,
304
+ [BUILT_IN_EVENT_NAMES.CREATE]: this[BUILT_IN_EVENT_NAMES.CREATE],
305
+ exposed: this.exposed
306
+ }
307
+ },
308
+ showTree () {
309
+ return !!Object.keys(this.treeMeta).length
310
+ },
311
+ buttonGroup () {
312
+ return uuidv4()
313
+ },
314
+ searchArea () {
315
+ return uuidv4()
316
+ },
317
+ modelTreeWrapper () {
318
+ return uuidv4()
319
+ },
320
+ modelTableWrapper () {
321
+ return uuidv4()
322
+ },
323
+ actions () {
324
+ const { operations } = this.tableMeta
325
+ if (operations) {
326
+ return operations.elements
327
+ } else {
328
+ return []
329
+ }
330
+ },
331
+ pageSize () {
332
+ const { page = {} } = this.tableMeta
333
+ return page.pageSize || 10
334
+ },
335
+ mode () {
336
+ const { page = {} } = this.tableMeta
337
+ return page.mode
338
+ },
339
+ pageSizeOptions () {
340
+ const { page = {} } = this.tableMeta
341
+ return page.pageSizeOptions || ['10', '20', '30', '40']
342
+ },
343
+ columns () {
344
+ const { columns, operations } = this.tableMeta
345
+ if (type.get(columns) === 'array') {
346
+ const columnsOptions = columns.map(item => {
347
+ const { mode = 'text' } = item
348
+ if (item.render) {
349
+ return {
350
+ ...item,
351
+ customRender: (text, record, index) => {
352
+ const { $createElement } = this
353
+ return item.render.call(this,
354
+ { h: $createElement, ctx: this },
355
+ text ? typeof text == 'object' ? text[item.dataIndex] : text : '',
356
+ record, index)
357
+ }
358
+ }
359
+ } else if (mode !== BUILT_IN_TRIGGER.TEXT) {
360
+ const { [`${mode}Meta`]: modeMeta } = item
361
+ return {
362
+ ...item,
363
+ customRender: (text, record, index) => {
364
+ return <span onClick={() => this.dispatchTrigger({ mode, record, modeMeta, index })} class={ mode }>{ text }</span>
365
+ }
366
+ }
367
+ }
368
+ return {
369
+ ...item
370
+ }
371
+ })
372
+ if (operations) {
373
+ return [
374
+ ...columnsOptions,
375
+ {
376
+ title: '操作',
377
+ width: operations.width,
378
+ key: 'action',
379
+ fixed: 'right',
380
+ scopedSlots: { customRender: 'action' }
381
+ }
382
+ ]
383
+ }
384
+ return columnsOptions
385
+ } else {
386
+ console.error('Error: columns is invalid, please check it')
387
+ return []
388
+ }
389
+ },
390
+ getButtonGroupElements () {
391
+ const { elements } = this.buttonGroupMeta
392
+ if (type.get(elements) === 'function') {
393
+ return elements.call(this)
394
+ } else if (type.get(elements) === 'array') {
395
+ return elements
396
+ } else {
397
+ return []
398
+ }
399
+ },
400
+ mapFields () {
401
+ const { replaceFields = {} } = this.treeMeta
402
+ const mapFields = type.isEmpty(replaceFields) ? this.replaceFields : replaceFields
403
+ return mapFields
404
+ }
405
+ },
406
+ async created () {
407
+ // onSearch会初始化请求表格数据,所以不需要在这里请求表格数据
408
+ // 初始化数据池管理器(使用新的 Mutix-based runtime-context)
409
+ try {
410
+ // 使用组件名 + UUID 作为调试名称,方便在 DevTools 中区分多个实例
411
+ // 这里的 Symbol description 将被 mutix devtools plugin 提取显示
412
+ const debugName = `TreeTableModel_${this.tableRef || uuidv4().slice(0, 8)}`
413
+ // 获取父作用域 ID
414
+ const parentId = this.parentScopeId?.() || null
415
+ // 创建模型时传入父作用域
416
+ this.model = createTreeTableModel(Symbol(debugName), undefined, parentId)
417
+
418
+ if (!this.model) {
419
+ throw new Error('Failed to create tree table model')
420
+ }
421
+ } catch (error) {
422
+ console.error('Error creating tree table model:', error)
423
+ this.model = null
424
+ return
425
+ }
426
+
427
+ // 初始化 currentRowData
428
+ this.initializeCurrentRowData()
429
+
430
+ // 订阅数据池变化,实现精确的状态同步
431
+ this.setupModelSubscriptions()
432
+
433
+ if (this.showTree) {
434
+ this.treeData = await this.requestTreeData()
435
+ const [defaultTreeNode = {}] = this.treeData
436
+ this.defaultExpandedKeys = [defaultTreeNode[this.mapFields.key]]
437
+ this.defaultSelectedKeys = [defaultTreeNode[this.mapFields.key]]
438
+
439
+ // 1. 先更新模型(Single Source of Truth)
440
+ if (this.model) {
441
+ this.model.setSelectedNode(defaultTreeNode)
442
+ }
443
+
444
+ // 2. 组件层数据通过订阅自动更新,无需手动设置
445
+ // this.currentTreeNodeData = defaultTreeNode // 不再需要
446
+
447
+ const { params = {}, fieldMap = {}, overrideInit = false } = this.tableMeta
448
+
449
+ // 使用最新的 currentRowData 和统一的表达式上下文
450
+ const currentRowData = this.getCurrentRowData()
451
+ const extraContext = {
452
+ currentRowData: currentRowData
453
+ }
454
+
455
+ const initQuerys = Object.assign({}, params, this.parseFieldMap(fieldMap, extraContext))
456
+ if (overrideInit) {
457
+ this.$emit(RESERVE_EVENT_NAMES.INIT, { ...this.exposed })
458
+ } else {
459
+ this.tableData = await this.requestTableData(initQuerys)
460
+ }
461
+ } else {
462
+ const { params = {}, fieldMap = {} } = this.tableMeta
463
+ const currentRowData = this.getCurrentRowData()
464
+ const extraContext = {
465
+ currentRowData: currentRowData
466
+ }
467
+ this.tableQuerys = Object.assign({}, params, this.parseFieldMap(fieldMap, extraContext))
468
+ }
469
+ },
470
+ methods: {
471
+ /**
472
+ * 构建标准化的表达式数据上下文
473
+ * @returns {Object} 表达式求值上下文
474
+ */
475
+ buildExpressionContext () {
476
+ let parentSelectedNode = {}
477
+ let parentCurrentRowData = {}
478
+
479
+ if (this.parentEvalExpression) {
480
+ try {
481
+ parentSelectedNode = this.parentEvalExpression('selectedNode') || {}
482
+ parentCurrentRowData = this.parentEvalExpression('currentRowData') || {}
483
+ } catch (e) {
484
+ // ignore
485
+ }
486
+ }
487
+
488
+ const currentTreeNode = this.model ? this.model.getSelectedNode() : this.currentTreeNodeData
489
+ const currentRowData = this.getCurrentRowData()
490
+
491
+ return {
492
+ // 路由数据
493
+ _route: this.$route.query,
494
+ _routeMeta: this.$route.meta,
495
+
496
+ // 树节点数据 - 合并父级数据以支持字段级 fallback
497
+ currentTreeNode: { ...parentSelectedNode, ...currentTreeNode },
498
+ selectedNode: { ...parentSelectedNode, ...currentTreeNode },
499
+
500
+ // 表格数据 - 合并父级数据以支持字段级 fallback
501
+ currentRowData: { ...parentCurrentRowData, ...currentRowData },
502
+ currentTableSelection: this.getCurrentTableSelection(),
503
+
504
+ // 分页数据
505
+ pagination: {
506
+ currentPage: this.tableQuerys.currentPage || 1,
507
+ pageSize: this.tableQuerys.pageSize || this.pageSize,
508
+ total: this.total
509
+ },
510
+
511
+ // 查询参数
512
+ queryParams: this.tableQuerys,
513
+
514
+ // 表格元数据
515
+ tableMeta: this.tableMeta,
516
+ treeMeta: this.treeMeta,
517
+
518
+ // 暴露的方法和数据
519
+ exposed: this.exposed
520
+ }
521
+ },
522
+
523
+ /**
524
+ * 统一表达式解析方法
525
+ * @param {string} expression - 表达式字符串
526
+ * @param {Object} extraContext - 额外上下文数据
527
+ * @returns {any} 表达式求值结果
528
+ */
529
+ parseExpression (expression, extraContext = {}) {
530
+ if (!this.model) {
531
+ console.warn('[TreeTableModel] Model not initialized, cannot parse expression')
532
+ return undefined
533
+ }
534
+
535
+ try {
536
+ const context = {
537
+ ...this.buildExpressionContext(),
538
+ ...extraContext
539
+ }
540
+ const val = this.model.eval(expression, context)
541
+ if ((val === undefined || val === null) && this.parentEvalExpression) {
542
+ return this.parentEvalExpression(expression, extraContext)
543
+ }
544
+ return val
545
+ } catch (error) {
546
+ console.error(`[TreeTableModel] Expression parsing failed: "${expression}"`, error)
547
+ return undefined
548
+ }
549
+ },
550
+
551
+ /**
552
+ * 统一字段映射解析方法
553
+ * @param {Object} fieldMap - 字段映射配置
554
+ * @param {Object} extraContext - 额外上下文数据
555
+ * @returns {Object} 解析后的参数字段
556
+ */
557
+ parseFieldMap (fieldMap = {}, extraContext = {}) {
558
+ if (!fieldMap || Object.keys(fieldMap).length === 0) {
559
+ return {}
560
+ }
561
+
562
+ const ret = {}
563
+ Object.keys(fieldMap).forEach((expression) => {
564
+ const targetKey = fieldMap[expression]
565
+ // Use this.parseExpression which internally uses model.eval for better context access
566
+ ret[targetKey] = this.parseExpression(expression, extraContext)
567
+ })
568
+
569
+ return ret
570
+ },
571
+
572
+ /**
573
+ * 设置模型订阅,实现精确的状态同步
574
+ */
575
+ setupModelSubscriptions () {
576
+ if (!this.model) {
577
+ console.warn('[TreeTableModel] Model not initialized, cannot setup subscriptions')
578
+ return
579
+ }
580
+
581
+ // 订阅当前行数据变化
582
+ this.unsubscribeRowData = this.model.subscribe('currentRowData', (newValue, oldValue) => {
583
+ // 模型已经确保只有真正变化时才会调用这里
584
+ this.currentRowData = newValue || {}
585
+ // 触发视图更新,但避免过度使用$forceUpdate
586
+ this.$nextTick(() => {
587
+ this.$emit('x:current-row-changed', {
588
+ currentRowData: this.currentRowData,
589
+ previousRowData: oldValue
590
+ })
591
+ })
592
+ })
593
+
594
+ // 订阅表格数据变化
595
+ this.unsubscribeTableData = this.model.subscribe('tableData', (newValue) => {
596
+ // 模型已经确保只有真正变化时才会调用这里
597
+ this.tableData = newValue || []
598
+ this.$emit('x:table-data-changed', { tableData: this.tableData })
599
+ })
600
+
601
+ // 订阅树数据变化
602
+ this.unsubscribeTreeData = this.model.subscribe('treeData', (newValue) => {
603
+ // 模型已经确保只有真正变化时才会调用这里
604
+ this.treeData = newValue || []
605
+ this.$emit('x:tree-data-changed', { treeData: this.treeData })
606
+ })
607
+
608
+ // 订阅加载状态变化
609
+ this.unsubscribeLoading = this.model.subscribe('loading', (newValue) => {
610
+ if (this.loading !== newValue) {
611
+ this.loading = newValue
612
+ this.$emit('x:loading-changed', { loading: this.loading })
613
+ }
614
+ })
615
+
616
+ // 订阅总记录数变化
617
+ this.unsubscribeTotal = this.model.subscribe('total', (newValue) => {
618
+ if (this.total !== newValue) {
619
+ this.total = newValue
620
+ this.$emit('x:total-changed', { total: this.total })
621
+ }
622
+ })
623
+
624
+ // 订阅共享数据变化(用于弹窗表格等场景)
625
+ this.unsubscribeShared = this.model.subscribe('_sharedData', (newValue) => {
626
+ if (newValue && Object.keys(newValue).length > 0) {
627
+ this.setCurrentRowData(newValue)
628
+ this.$emit('x:shared-data-changed', { sharedData: newValue })
629
+ }
630
+ })
631
+
632
+ // 订阅选择数据变化
633
+ this.unsubscribeSelection = this.model.subscribe('currentTableSelection', (newValue, oldValue) => {
634
+ // 模型已经确保只有真正变化时才会调用这里
635
+ this.currentTableSelection = newValue || {}
636
+ // 触发视图更新和事件通知
637
+ this.$nextTick(() => {
638
+ this.$emit('x:current-table-selection-changed', {
639
+ currentTableSelection: this.currentTableSelection,
640
+ previousCurrentTableSelection: oldValue
641
+ })
642
+ })
643
+ })
644
+
645
+ // 订阅选中的树节点变化
646
+ this.unsubscribeSelectedNode = this.model.subscribe('selectedNode', (newValue, oldValue) => {
647
+ // 模型已经确保只有真正变化时才会调用这里
648
+ this.currentTreeNodeData = newValue || {}
649
+ // 触发视图更新和事件通知
650
+ this.$nextTick(() => {
651
+ this.$emit('x:selected-node-changed', {
652
+ selectedNode: this.currentTreeNodeData,
653
+ previousSelectedNode: oldValue
654
+ })
655
+ })
656
+ })
657
+
658
+ console.log('[TreeTableModel] Model subscriptions setup completed')
659
+ },
660
+
661
+ /**
662
+ * 性能监控工具方法
663
+ * @param {string} operation - 操作名称
664
+ * @param {Function} fn - 要监控的函数
665
+ * @returns {Promise<any>} 函数执行结果
666
+ */
667
+ async withPerformanceMonitoring (operation, fn) {
668
+ const startTime = performance.now()
669
+ try {
670
+ const result = await fn()
671
+ const duration = performance.now() - startTime
672
+ console.log(`[TreeTableModel] ${operation} took ${duration.toFixed(2)}ms`)
673
+ return result
674
+ } catch (error) {
675
+ const duration = performance.now() - startTime
676
+ console.error(`[TreeTableModel] ${operation} failed after ${duration.toFixed(2)}ms:`, error)
677
+ throw error
678
+ }
679
+ },
680
+
681
+ /**
682
+ * 错误处理工具方法
683
+ * @param {string} context - 错误上下文
684
+ * @param {Function} fn - 要执行的函数
685
+ * @param {any} fallbackValue - 错误时的默认值
686
+ * @returns {any} 函数执行结果或默认值
687
+ */
688
+ withErrorHandling (context, fn, fallbackValue = undefined) {
689
+ try {
690
+ return fn()
691
+ } catch (error) {
692
+ console.error(`[TreeTableModel] Error in ${context}:`, error)
693
+ return fallbackValue
694
+ }
695
+ },
696
+
697
+ /**
698
+ * 清理所有模型订阅
699
+ */
700
+ cleanupModelSubscriptions () {
701
+ const subscriptions = [
702
+ 'unsubscribeRowData',
703
+ 'unsubscribeTableData',
704
+ 'unsubscribeTreeData',
705
+ 'unsubscribeLoading',
706
+ 'unsubscribeTotal',
707
+ 'unsubscribeShared',
708
+ 'unsubscribeSelection',
709
+ 'unsubscribeSelectedNode'
710
+ ]
711
+
712
+ subscriptions.forEach(subscriptionName => {
713
+ if (this[subscriptionName] && typeof this[subscriptionName] === 'function') {
714
+ try {
715
+ this[subscriptionName]()
716
+ this[subscriptionName] = null
717
+ } catch (error) {
718
+ console.warn(`[TreeTableModel] Failed to cleanup subscription: ${subscriptionName}`, error)
719
+ }
720
+ }
721
+ })
722
+
723
+ console.log('[TreeTableModel] Model subscriptions cleanup completed')
724
+ },
725
+
726
+ initializeCurrentRowData () {
727
+ if (!this.model) return
728
+
729
+ let initialData = {}
730
+
731
+ // 1. 优先检查共享数据(来自父级)
732
+ const sharedData = this.model.getSharedData()
733
+ if (sharedData && Object.keys(sharedData).length > 0) {
734
+ initialData = sharedData
735
+ } else {
736
+ // 2. 其次检查 props 或路由参数 (示例逻辑)
737
+ // const { rowId } = this.$route.query
738
+ // if (rowId) { ... }
739
+ }
740
+
741
+ // 3. 设置到 Model,这是唯一的数据源初始化入口
742
+ this.model.setCurrentRowData(initialData)
743
+
744
+ // 4. 同步回本地(虽然 subscribe 会处理,但为了立即能用,这里也设置一下)
745
+ this.currentRowData = initialData
746
+ },
747
+ async refreshTreeData () {
748
+ return this.withPerformanceMonitoring('refreshTreeData', async () => {
749
+ this.treeData = await this.requestTreeData()
750
+ const [defaultTreeNode = {}] = this.treeData
751
+ this.defaultExpandedKeys = [defaultTreeNode[this.mapFields.key]]
752
+ this.defaultSelectedKeys = [defaultTreeNode[this.mapFields.key]]
753
+
754
+ // 1. 先更新模型(Single Source of Truth)
755
+ if (this.model) {
756
+ this.model.setSelectedNode(defaultTreeNode)
757
+ }
758
+
759
+ // 2. 组件层数据通过订阅自动更新,无需手动设置
760
+ // this.currentTreeNodeData = defaultTreeNode // 不再需要
761
+
762
+ this.$emit('x:tree-refreshed', {
763
+ treeData: this.treeData,
764
+ currentTreeNode: defaultTreeNode
765
+ })
766
+
767
+ return this.treeData
768
+ })
769
+ },
770
+ dispatchTrigger ({ mode, record = {}, modeMeta = { } }) {
771
+ switch (mode) {
772
+ case BUILT_IN_TRIGGER.FSM:
773
+ this[`${BUILT_IN_TRIGGER.FSM}Trigger`](record, modeMeta = type.isEmpty(modeMeta) ? {
774
+ url: 'api-fsm/workbench/fsm/auditFlow',
775
+ requestType: 'GET',
776
+ fieldMap: {
777
+ modelCode: 'modelCode',
778
+ businessId: 'businessId'
779
+ }
780
+ } : modeMeta)
781
+ break
782
+ case BUILT_IN_TRIGGER.ELE_MODAL_FORM:
783
+ this.modalFormMeta = modeMeta
784
+ this.showModalForm(modeMeta)
785
+ break
786
+ case BUILT_IN_TRIGGER.ELE_MODAL_TABLE:
787
+ this.modalTableMeta = modeMeta
788
+ // 将当前行的 record 数据传递给 modal table
789
+ this.showModalTable(modeMeta, record)
790
+ break
791
+ default:
792
+ break
793
+ }
794
+ },
795
+ handleCloseFsmModal () {
796
+ this.showFsmModal = false
797
+ },
798
+ [`${BUILT_IN_TRIGGER.FSM}Trigger`] (record, meta) {
799
+ this.fsmMeta = meta
800
+ this.fsmContextProp = record
801
+ this.showFsmModal = true
802
+ },
803
+ onChangeTableSelection (_, selectedRows = []) {
804
+ // 1. 先更新模型(Single Source of Truth)
805
+ if (this.model) {
806
+ this.model.setCurrentTableSelection(selectedRows)
807
+ }
808
+
809
+ // 2. 组件层数据通过订阅自动更新,无需手动设置
810
+ // this.setCurrentTableSelection(selectedRows) // 不再需要
811
+
812
+ // 3. 继续后续逻辑
813
+ this.$emit('on-change-table-selection', this.currentTableSelection)
814
+ this.$emit('x:refresh-exposed', { exposed: this.exposed })
815
+ },
816
+ setCurrentTableSelection (props = {}) {
817
+ return this.withErrorHandling('setCurrentTableSelection', () => {
818
+ // 处理数据格式
819
+ let processedData
820
+ if (this.currentTableMode === 'radio') {
821
+ processedData = (type.isArray(props) && props.length > 0) ? props[0] : type.isObject(props) ? props : {}
822
+ } else {
823
+ processedData = type.isArray(props) ? props : []
824
+ }
825
+
826
+ // 1. 始终先更新 Model (Single Source of Truth)
827
+ if (this.model) {
828
+ this.model.setCurrentTableSelection(processedData)
829
+ }
830
+
831
+ // 2. 组件层数据通过订阅自动更新,无需手动设置
832
+ // this.$set(this, 'currentTableSelection', processedData) // 不再需要
833
+
834
+ // 3. 对外广播
835
+ this.$emit('x:current-table-selection-set', { currentTableSelection: processedData })
836
+
837
+ return processedData
838
+ }, {})
839
+ },
840
+ getCurrentTableSelection () {
841
+ return this.withErrorHandling('getCurrentTableSelection', () => {
842
+ if (this.model) {
843
+ return this.model.getCurrentTableSelection()
844
+ }
845
+ console.warn('[TreeTableModel] Model not initialized, getCurrentTableSelection returning local data')
846
+ return this.currentTableSelection || {}
847
+ }, {})
848
+ },
849
+ setCurrentRowData (props = {}) {
850
+ return this.withErrorHandling('setCurrentRowData', () => {
851
+ // 关键:通过浅克隆断开与 Vue 2 Observer 的引用,保证数据纯净
852
+ const newData = props ? { ...props } : {}
853
+
854
+ // 1. 始终先更新 Model (Single Source of Truth)
855
+ if (this.model) {
856
+ this.model.setCurrentRowData(newData)
857
+ } else {
858
+ console.warn('[TreeTableModel] Model not initialized, cannot setCurrentRowData in model')
859
+ }
860
+
861
+ // 2. 更新本地视图 (双重保证,避免订阅延迟导致的闪烁)
862
+ this.currentRowData = newData
863
+
864
+ // 3. 对外广播
865
+ // 注意:不再需要手动 emit 事件给外部来同步状态,因为外部如果想监听,应该监听 model 变化
866
+ // 但为了兼容旧代码,保持一些必要的 emit 可能是好的,比如 'row-change'
867
+ this.$emit('x:current-row-data-set', { currentRowData: this.currentRowData })
868
+
869
+ return this.currentRowData
870
+ }, {})
871
+ },
872
+ getCurrentRowData () {
873
+ return this.withErrorHandling('getCurrentRowData', () => {
874
+ if (this.model) {
875
+ return this.model.getCurrentRowData()
876
+ }
877
+ console.warn('[TreeTableModel] Model not initialized, getCurrentRowData returning local data')
878
+ return this.currentRowData || {}
879
+ }, {})
880
+ },
881
+ cleanCurrentModelEffect (clearRowData = true) {
882
+ const action = () => {
883
+ this.setCurrentTableSelection()
884
+ if (clearRowData) {
885
+ this.setCurrentRowData({})
886
+ }
887
+ }
888
+
889
+ if (this.model) {
890
+ this.model.batch(action)
891
+ } else {
892
+ action()
893
+ }
894
+ },
895
+ [BUILT_IN_EVENT_NAMES.SUBMIT] (props = {}) {
896
+ this.cleanCurrentModelEffect()
897
+ this.requestTableData()
898
+ },
899
+ [BUILT_IN_EVENT_NAMES.EDIT] (props = {}) {
900
+ const { record = {} } = props
901
+ this.setCurrentRowData(record)
902
+ this.modalFormMeta = this.editMeta
903
+ this.modalFormValue = true
904
+ },
905
+ [BUILT_IN_EVENT_NAMES.CREATE] () {
906
+ this.modalFormMeta = this.createMeta
907
+ this.modalFormValue = true
908
+ },
909
+ showModalForm (modeMeta = {}) {
910
+ if (type.isStr(modeMeta)) {
911
+ const targetMeta = this.findMetaByKey(modeMeta)
912
+ this.modalFormMeta = targetMeta
913
+ } else {
914
+ this.modalFormMeta = modeMeta
915
+ }
916
+ this.modalFormValue = true
917
+ },
918
+ showModalTable (modeMeta = {}, record = null) {
919
+ // 获取当前行数据并设置到共享命名空间
920
+ const currentRowData = record || this.getCurrentRowData()
921
+
922
+ // 🔍 关键调试日志 - 开始
923
+ console.log('[DEBUG] showModalTable - currentRowData:', currentRowData)
924
+ console.log('[DEBUG] showModalTable - currentTreeNodeData:', this.currentTreeNodeData)
925
+ console.log('[DEBUG] showModalTable - 是否有model:', !!this.model)
926
+
927
+ if (this.model) {
928
+ this.model.setSharedData(currentRowData)
929
+ } else {
930
+ console.warn('Model not initialized, cannot setSharedData')
931
+ }
932
+
933
+ let targetMeta = modeMeta
934
+ if (type.isStr(modeMeta)) {
935
+ targetMeta = this.findMetaByKey(modeMeta)
936
+ }
937
+
938
+ // 🔍 关键调试日志 - fieldMap处理
939
+ console.log('[DEBUG] showModalTable - targetMeta:', targetMeta)
940
+ console.log('[DEBUG] showModalTable - 是否有fieldMap:', !!(targetMeta && targetMeta.fieldMap))
941
+
942
+ // 解析 fieldMap 参数,使用完整的上下文包括 currentRowData 和父级数据
943
+ if (targetMeta && targetMeta.tableMeta.fieldMap) {
944
+ const { fieldMap, params = {} } = targetMeta.tableMeta
945
+
946
+ console.log('[DEBUG] showModalTable - fieldMap:', fieldMap)
947
+
948
+ // 关键:构建完整的上下文,保留原有数据并添加父级节点
949
+ const extraContext = {}
950
+
951
+ console.log('[DEBUG] showModalTable - extraContext:', extraContext)
952
+
953
+ const parsedParams = this.parseFieldMap(fieldMap, extraContext)
954
+
955
+ console.log('[DEBUG] showModalTable - parsedParams:', parsedParams)
956
+
957
+ // 将解析后的参数传递给 modal table
958
+ targetMeta.tableMeta = {
959
+ ...targetMeta.tableMeta,
960
+ params: {
961
+ ...params,
962
+ ...parsedParams
963
+ }
964
+ }
965
+
966
+ console.log('[DEBUG] showModalTable - 最终targetMeta.params:', targetMeta.params)
967
+ }
968
+
969
+ this.modalTableMeta = targetMeta
970
+ this.modalTableValue = true
971
+
972
+ console.log('[DEBUG] showModalTable - 最终modalTableMeta:', this.modalTableMeta)
973
+ },
974
+ closeModalForm () {
975
+ this.modalFormValue = false
976
+ },
977
+ closeModalTable () {
978
+ this.modalTableValue = false
979
+ },
980
+ findMetaByKey (key) {
981
+ return this.$attrs[key] || {}
982
+ },
983
+ handleClickButtonGroup (props) {
984
+ const { eventName, target } = props
985
+ const targetMeta = this.findMetaByKey(target)
986
+ const { mode } = targetMeta
987
+ mode && this.dispatchTrigger({ mode, modeMeta: targetMeta })
988
+ this.$emit(eventName || 'click', { currentTreeNode: this.currentTreeNodeData })
989
+ },
990
+ async onSearch (props) {
991
+ const { overrideInit = false } = this.tableMeta
992
+ this.tableQuerys = Object.assign(this.tableQuerys, props)
993
+ if (overrideInit) {
994
+ this.$emit(RESERVE_EVENT_NAMES.TREE_CHANGE, { ...this.exposed })
995
+ } else {
996
+ const { initSearch = false } = props
997
+ if (this.showTree && initSearch) return
998
+ this.tableData = await this.requestTableData()
999
+ }
1000
+ },
1001
+ async selectTreeNode (selectedKeys, e) {
1002
+ const { fieldMap } = this.tableMeta
1003
+ const selectedNodeData = e.node.$vnode.data.props.dataRef || {}
1004
+
1005
+ // 1. 先更新模型(Single Source of Truth)
1006
+ if (this.model) {
1007
+ this.model.setSelectedNode(selectedNodeData)
1008
+ }
1009
+
1010
+ // 2. 组件层数据通过订阅自动更新,无需手动设置
1011
+ // this.currentTreeNodeData = selectedNodeData // 不再需要
1012
+
1013
+ // 3. 继续后续逻辑
1014
+ // 使用统一的字段映射解析方法,自动包含所需的上下文
1015
+ const execFieldMapRet = this.parseFieldMap(fieldMap)
1016
+ const { overrideInit = false } = this.tableMeta
1017
+ if (overrideInit) {
1018
+ this.$emit(RESERVE_EVENT_NAMES.TREE_CHANGE, { ...this.exposed })
1019
+ } else {
1020
+ this.tableData = await this.requestTableData(execFieldMapRet)
1021
+ }
1022
+ },
1023
+ async requestTreeData () {
1024
+ const { url, requestType = 'GET', params = {}, fieldMap = {} } = this.treeMeta
1025
+
1026
+ // 使用统一的字段映射解析方法
1027
+ const fieldMapRet = this.parseFieldMap(fieldMap)
1028
+
1029
+ try {
1030
+ const startTime = performance.now()
1031
+ const ret = await net[requestType.toLowerCase()](
1032
+ url,
1033
+ { ...params, ...fieldMapRet }
1034
+ ).then(resp => {
1035
+ const { data } = resp || {}
1036
+ return data
1037
+ })
1038
+
1039
+ // 性能监控
1040
+ const duration = performance.now() - startTime
1041
+ console.log(`[TreeTableModel] Tree data request took ${duration.toFixed(2)}ms`)
1042
+
1043
+ // 通过模型设置树数据
1044
+ if (this.model) {
1045
+ this.model.setTreeData(ret)
1046
+ }
1047
+
1048
+ return ret
1049
+ } catch (error) {
1050
+ console.error('[TreeTableModel] Failed to request tree data:', error)
1051
+ // 错误时设置空数据
1052
+ if (this.model) {
1053
+ this.model.setTreeData([])
1054
+ }
1055
+ return []
1056
+ }
1057
+ },
1058
+ async onChangePage (page, pageSize) {
1059
+ this.tableData = await this.requestTableData({ currentPage: page, pageSize })
1060
+ },
1061
+ async requestTableData (props = {}) {
1062
+ const { url, requestType = 'GET', page = {} } = this.tableMeta
1063
+
1064
+ // 更新查询参数
1065
+ this.tableQuerys = Object.assign(this.tableQuerys, {
1066
+ currentPage: this.tableQuerys.currentPage || 1,
1067
+ pageSize: this.tableQuerys.pageSize || page.pageSize || 10
1068
+ }, props)
1069
+
1070
+ // 通过模型设置加载状态
1071
+ if (this.model) {
1072
+ this.model.setLoading(true)
1073
+ }
1074
+
1075
+ this.$emit(RESERVE_EVENT_NAMES.WATCH, { ...this.exposed })
1076
+
1077
+ try {
1078
+ const startTime = performance.now()
1079
+ const ret = await net[requestType.toLowerCase()](
1080
+ url,
1081
+ this.tableQuerys
1082
+ ).then(resp => {
1083
+ const { data = [], count } = resp || {}
1084
+
1085
+ // 通过模型设置数据
1086
+ if (this.model) {
1087
+ this.model.batch(() => {
1088
+ this.model.setTableData(data || [])
1089
+ this.model.setTotal(count || 0)
1090
+ this.model.setPagination(
1091
+ this.tableQuerys.currentPage || 1,
1092
+ this.tableQuerys.pageSize || page.pageSize || 10
1093
+ )
1094
+ })
1095
+ }
1096
+
1097
+ // 性能监控
1098
+ const duration = performance.now() - startTime
1099
+ console.log(`[TreeTableModel] Table data request took ${duration.toFixed(2)}ms, returned ${(data || []).length} items`)
1100
+
1101
+ return (data || []).map(item => {
1102
+ delete item.children
1103
+ return {
1104
+ key: uuidv4(),
1105
+ ...item
1106
+ }
1107
+ })
1108
+ })
1109
+
1110
+ // 更新本地数据(保持兼容)
1111
+ this.tableData = ret
1112
+ this.total = this.model ? this.model.getTotal() : 0
1113
+
1114
+ return ret
1115
+ } catch (error) {
1116
+ console.error('[TreeTableModel] Failed to request table data:', error)
1117
+
1118
+ // 错误时设置空数据
1119
+ if (this.model) {
1120
+ this.model.setTableData([])
1121
+ this.model.setTotal(0)
1122
+ }
1123
+
1124
+ this.tableData = []
1125
+ this.total = 0
1126
+ return []
1127
+ } finally {
1128
+ const finalize = () => {
1129
+ if (this.model) {
1130
+ this.model.setLoading(false)
1131
+ }
1132
+ this.loading = false
1133
+ // 清理当前模型效果(不清空currentRowData)
1134
+ this.cleanCurrentModelEffect(false)
1135
+ }
1136
+
1137
+ // 使用 batch 确保 loading 状态改变和清理操作原子化
1138
+ if (this.model) {
1139
+ this.model.batch(finalize)
1140
+ } else {
1141
+ finalize()
1142
+ }
1143
+ }
1144
+ },
1145
+ calculateTableHeight () {
1146
+ const currentViewportHeight = window.innerHeight
1147
+ const tableRef = this.$refs[this.tableRef]
1148
+ if (!tableRef || !tableRef.$el) return
1149
+
1150
+ const { top: tableToTop, width } = tableRef.$el.getBoundingClientRect()
1151
+ this.tableWidth = width
1152
+
1153
+ // 获取分页组件的高度(如果存在)
1154
+ let paginationHeight = 0
1155
+ const paginationEl = tableRef.$el.querySelector('.g-table__pagination')
1156
+ if (paginationEl) {
1157
+ paginationHeight = paginationEl.getBoundingClientRect().height || 50
1158
+ } else {
1159
+ // 如果分页组件还未渲染,使用默认高度
1160
+ paginationHeight = 50
1161
+ }
1162
+
1163
+ // 计算表格高度:视口高度 - 表格顶部距离 - 分页高度 - 额外高度
1164
+ const calculatedHeight = currentViewportHeight - tableToTop - paginationHeight - this.overHeight - 20
1165
+ // 确保最小高度,避免表格过小
1166
+ this.tableHeight = Math.max(calculatedHeight, 200)
1167
+ },
1168
+ calculateTreeHeight () {
1169
+ if (!this.showTree) return
1170
+ const modelTableContainerRef = this.$refs[this.modelTableContainerRef]
1171
+ if (!modelTableContainerRef) return
1172
+
1173
+ const { height } = modelTableContainerRef.getBoundingClientRect()
1174
+ // 确保树的高度和表格容器高度一致
1175
+ this.treeWrapperHeight = height
1176
+
1177
+ // 如果表格容器有标题,需要减去标题高度
1178
+ const titleEl = modelTableContainerRef.querySelector('.model__table--title')
1179
+ if (titleEl) {
1180
+ const titleHeight = titleEl.getBoundingClientRect().height
1181
+ this.treeWrapperHeight = height - titleHeight
1182
+ }
1183
+ },
1184
+ async keepAliveRefresh () {
1185
+ return this.withPerformanceMonitoring('keepAliveRefresh', async () => {
1186
+ // 重新计算表格高度(应对窗口大小变化)
1187
+ this.$nextTick(() => {
1188
+ setTimeout(() => {
1189
+ this.calculateTableHeight()
1190
+ if (this.showTree) {
1191
+ this.calculateTreeHeight()
1192
+ }
1193
+ }, 200)
1194
+ })
1195
+
1196
+ // 刷新列表数据
1197
+ const { overrideInit = false } = this.tableMeta
1198
+ if (overrideInit) {
1199
+ // 如果使用自定义初始化模式,触发 INIT 事件
1200
+ this.$emit(RESERVE_EVENT_NAMES.INIT, { ...this.exposed })
1201
+ } else {
1202
+ // 使用当前查询参数刷新表格数据
1203
+ await this.requestTableData(this.tableQuerys)
1204
+ }
1205
+
1206
+ this.$emit('x:refresh-completed', {
1207
+ tableData: this.tableData,
1208
+ treeData: this.treeData,
1209
+ currentRowData: this.currentRowData
1210
+ })
1211
+ })
1212
+ }
1213
+ },
1214
+ mounted () {
1215
+ // 初始化时先设置一个默认高度,避免布局混乱
1216
+ this.tableHeight = 400
1217
+ if (this.showTree) {
1218
+ this.treeWrapperHeight = 400
1219
+ }
1220
+
1221
+ // 延迟计算,确保所有组件都已渲染
1222
+ this.$nextTick(() => {
1223
+ setTimeout(() => {
1224
+ this.calculateTableHeight()
1225
+ this.calculateTreeHeight()
1226
+ }, 200)
1227
+ })
1228
+
1229
+ // 使用 ResizeObserver 监听容器大小变化
1230
+ this.resizeObserverModelTableWrapper = new ResizeObserver(entries => {
1231
+ for (const _ of entries) {
1232
+ requestAnimationFrame(() => {
1233
+ // 延迟重新计算,确保分页组件高度已更新
1234
+ setTimeout(() => {
1235
+ this.calculateTableHeight()
1236
+ if (this.showTree) {
1237
+ this.calculateTreeHeight()
1238
+ }
1239
+ }, 100)
1240
+ })
1241
+ }
1242
+ })
1243
+
1244
+ if (this.$refs[this.modelTableWrapper]) {
1245
+ this.resizeObserverModelTableWrapper.observe(this.$refs[this.modelTableWrapper])
1246
+ }
1247
+
1248
+ // 监听表格容器大小变化(用于同步树高度)
1249
+ if (this.showTree && this.$refs[this.modelTableContainerRef]) {
1250
+ this.resizeObserverModelTableContainer = new ResizeObserver(entries => {
1251
+ for (const _ of entries) {
1252
+ requestAnimationFrame(() => {
1253
+ this.calculateTreeHeight()
1254
+ })
1255
+ }
1256
+ })
1257
+ this.resizeObserverModelTableContainer.observe(this.$refs[this.modelTableContainerRef])
1258
+ }
1259
+
1260
+ // 监听窗口大小变化
1261
+ this.handleResize = () => {
1262
+ this.$nextTick(() => {
1263
+ setTimeout(() => {
1264
+ this.calculateTableHeight()
1265
+ if (this.showTree) {
1266
+ this.calculateTreeHeight()
1267
+ }
1268
+ }, 100)
1269
+ })
1270
+ }
1271
+ window.addEventListener('resize', this.handleResize)
1272
+ },
1273
+ destroyed () {
1274
+ if (this.resizeObserverModelTableWrapper) {
1275
+ this.resizeObserverModelTableWrapper.disconnect()
1276
+ }
1277
+ if (this.resizeObserverModelTableContainer) {
1278
+ this.resizeObserverModelTableContainer.disconnect()
1279
+ }
1280
+ if (this.handleResize) {
1281
+ window.removeEventListener('resize', this.handleResize)
1282
+ }
1283
+ if (this.model) {
1284
+ // 清理所有订阅
1285
+ this.cleanupModelSubscriptions()
1286
+ // 清理模型数据
1287
+ this.model.cleanup()
1288
+ }
1289
+ },
1290
+ async activated () {
1291
+ await this.keepAliveRefresh()
1292
+ }
1293
+ }
1294
+ </script>
1295
+
1296
+ <style lang="scss" scoped>
1297
+ .ele {
1298
+ &.model__tree-table {
1299
+ background: transparent;
1300
+ display: flex;
1301
+ flex-direction: row;
1302
+ width: 100%;
1303
+ height: 100%;
1304
+ overflow: hidden;
1305
+ .model__tree-table--container {
1306
+ display: flex;
1307
+ flex-direction: column;
1308
+ height: 100%;
1309
+ .model__tree--wrapper {
1310
+ width: 240px;
1311
+ background: #fff;
1312
+ flex-shrink: 0;
1313
+ padding: 16px;
1314
+ box-sizing: border-box;
1315
+ margin-right: 16px;
1316
+ overflow-y: auto;
1317
+ overflow-x: hidden;
1318
+ }
1319
+ }
1320
+ .model__table--container {
1321
+ width: 100%;
1322
+ min-width: 0;
1323
+ background: #fff;
1324
+ display: flex;
1325
+ flex-direction: column;
1326
+ height: 100%;
1327
+ overflow: hidden;
1328
+ .model__table--title {
1329
+ .model__table-title--bar {
1330
+ width: 100%;
1331
+ height: 8px;
1332
+ background: var(--idooel-primary-color);
1333
+ border-top-left-radius: 4px;
1334
+ border-top-right-radius: 4px;
1335
+ }
1336
+ .model__table-title--text {
1337
+ text-align: left;
1338
+ padding: 16px;
1339
+ font-size: 16px;
1340
+ font-weight: bold;
1341
+ background: #fff;
1342
+ border-bottom: 1px solid;
1343
+ border-color: var(--idoole-black-016);
1344
+ }
1345
+ }
1346
+ .model__table--wrapper {
1347
+ background: #fff;
1348
+ display: flex;
1349
+ flex-direction: column;
1350
+ height: 100%;
1351
+ overflow: hidden;
1352
+ .button-row__area {
1353
+ width: 100%;
1354
+ display: flex;
1355
+ flex-direction: row;
1356
+ align-items: center;
1357
+ justify-content: space-between;
1358
+ padding-top: 16px;
1359
+ padding-bottom: 8px;
1360
+ padding-right: 16px;
1361
+ flex-shrink: 0;
1362
+ }
1363
+ .g-table__wrapper {
1364
+ flex: 1;
1365
+ min-height: 0;
1366
+ overflow: hidden;
1367
+ .fsm {
1368
+ cursor: pointer;
1369
+ color: var(--idooel-primary-color);
1370
+ }
1371
+ }
1372
+ }
1373
+ }
1374
+ }
1375
+ }
1376
+ </style>