vue2-client 1.12.82 → 1.12.84

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.
@@ -1,616 +1,616 @@
1
- <template>
2
- <div class="reportGridMain" ref="reportGridMain">
3
- <template v-if="isWidget">
4
- <!-- 插槽展示 -->
5
- <x-report-tr-group
6
- v-for="(row, rowIndex) in activatedConfig.columns"
7
- @updateImg="updateImg"
8
- @selectRow="selectRow"
9
- :show-img-in-cell="showImgInCell"
10
- :server-name="serverName"
11
- :env="env"
12
- :key="rowIndex"
13
- :columns="row"
14
- :no-top-border="noTopBorder"
15
- :config-data="activatedConfig.data"
16
- :config="activatedConfig"
17
- :display="true">
18
- </x-report-tr-group>
19
- </template>
20
- <template v-else>
21
- <div
22
- :class=" noPadding ? 'reportMainNoPadding' : 'reportMain'"
23
- :style="activatedConfig.width > 0 ? ('width:' + activatedConfig.width + 'px') : undefined">
24
- <!-- 主体表格 -->
25
- <table class="reportTable" v-if="render" :style="activatedConfig.style ? activatedConfig.style : undefined">
26
- <tbody class="reportTable">
27
- <!-- 插槽展示 -->
28
- <x-report-tr-group
29
- :key="rowIndex"
30
- v-for="(row, rowIndex) in activatedConfig.columns"
31
- @selectRow="selectRow"
32
- @slotRendered="slotRendered"
33
- :server-name="serverName"
34
- :env="env"
35
- :columns="row"
36
- :config-data="activatedConfig.data"
37
- :config="activatedConfig"
38
- :display="true">
39
- </x-report-tr-group>
40
- </tbody>
41
- </table>
42
- </div>
43
- </template>
44
- </div>
45
- </template>
46
-
47
- <script>
48
- import XReportTrGroup from './XReportTrGroup.vue'
49
- import { getConfigByName } from '@vue2-client/services/api/common'
50
- import XFormTable from '@vue2-client/base-client/components/common/XFormTable/XFormTable'
51
-
52
- export default {
53
- name: 'XReportDesign',
54
- props: {
55
- // 配置
56
- config: {
57
- type: Object,
58
- require: true,
59
- default: () => {
60
- }
61
- },
62
- showImgInCell: {
63
- type: Boolean,
64
- default: false
65
- },
66
- // 是否是展示用
67
- forDisplay: {
68
- type: Boolean,
69
- default: false
70
- },
71
- // 命名空间
72
- serverName: {
73
- type: String,
74
- default: 'af-system'
75
- },
76
- // 环境
77
- env: {
78
- type: String,
79
- default: 'prod'
80
- },
81
- // 是否只能展示不可编辑
82
- displayOnly: {
83
- type: Boolean,
84
- default: false
85
- },
86
- // 插槽名
87
- slotConfigName: {
88
- type: String,
89
- default: undefined
90
- },
91
- // 表格没有边距
92
- noPadding: {
93
- type: Boolean,
94
- default: true
95
- },
96
- // 表格没有上边框,与noPadding搭配可以实现连续表格
97
- noTopBorder: {
98
- type: Boolean,
99
- default: false
100
- },
101
- },
102
- components: {
103
- XFormTable,
104
- XReportTrGroup
105
- },
106
- inject: ['isWidget'],
107
- provide () {
108
- return {
109
- setColSpanByName: this.setColSpanByName
110
- }
111
- },
112
- data () {
113
- return {
114
- // 表单的数据
115
- data: this.config.data,
116
- // 插槽配置
117
- slotConfig: undefined,
118
- // 控制是否渲染
119
- render: false,
120
- // 用于展示的配置
121
- activatedConfig: {}
122
- }
123
- },
124
- methods: {
125
- slotRendered () {
126
- this.$emit('slotRendered')
127
- },
128
- updateImg (data) {
129
- this.$emit('updateImg', data)
130
- },
131
- selectRow (selectedRowKeys, selectedRows) {
132
- this.table_selectedRowKeys = selectedRowKeys
133
- this.table_selectedRows = selectedRows
134
- console.log('XReportDesign')
135
- this.$emit('selectRow', selectedRowKeys, selectedRows)
136
- },
137
- setColSpanByName (componentName, colSpan) {
138
- // 处理columns配置(二维数组结构)
139
- const processColumns = (columns) => {
140
- columns.forEach((row) => {
141
- row.forEach((cell, index) => {
142
- if (Array.isArray(cell)) {
143
- processColumns(cell) // 递归处理嵌套数组
144
- } else if (cell.slotRef === componentName) {
145
- // 使用Vue.set确保响应式更新
146
- this.$set(row[index], 'colSpan', colSpan)
147
- this.$set(row[index], '_isManualColSpan', true)
148
- }
149
- })
150
- })
151
- }
152
-
153
- // 处理table配置(二维数组结构)
154
- const processTable = (table) => {
155
- table.forEach((row) => {
156
- row.forEach((cell, index) => {
157
- // 同时检查顶层和def中的slotRef
158
- const isTargetCell =
159
- cell.slotRef === componentName ||
160
- cell.def?.slotRef === componentName
161
-
162
- if (isTargetCell) {
163
- // 同时更新顶层和def中的colSpan
164
- this.$set(row[index], 'colSpan', colSpan)
165
- this.$set(row[index], '_isManualColSpan', true)
166
- if (cell.def) {
167
- this.$set(row[index].def, 'colSpan', colSpan)
168
- this.$set(row[index].def, '_isManualColSpan', true)
169
- }
170
- }
171
- })
172
- })
173
- }
174
-
175
- // 强制刷新视图
176
- const forceUpdate = () => {
177
- this.$nextTick(() => {
178
- // 创建新对象引用以触发watch
179
- this.activatedConfig = {
180
- ...this.activatedConfig,
181
- _version: Date.now()
182
- }
183
- this.$forceUpdate()
184
- })
185
- }
186
-
187
- // 处理当前激活的配置
188
- if (this.activatedConfig?.columns) {
189
- processColumns(this.activatedConfig.columns)
190
- }
191
- if (this.activatedConfig?.table) {
192
- processTable(this.activatedConfig.table)
193
- }
194
-
195
- forceUpdate()
196
- },
197
- applyAllStyles () {
198
- // 应用组件样式
199
- const component = this.$refs.reportGridMain
200
- // 确保组件已经完全挂载
201
- this.$nextTick(() => {
202
- this.applyComponentStyles(component)
203
- })
204
- },
205
- // 获取组件样式配置
206
- async getComponentStyleConfig (componentType) {
207
- try {
208
- // 从配置中获取样式定义
209
- const styleConfig = this.$appdata.getStylesByKey(componentType)
210
- return styleConfig || {}
211
- } catch (error) {
212
- console.error('获取组件样式配置失败:', error)
213
- return {}
214
- }
215
- },
216
-
217
- // 解析组件样式配置
218
- async parseComponentStyles (cell) {
219
- if (!cell.class) return { rootStyles: {}, childStyles: {} }
220
-
221
- const styleConfig = await this.getComponentStyleConfig('reportGridMain')
222
- if (!styleConfig) return { rootStyles: {}, childStyles: {} }
223
-
224
- const rootStyles = {}
225
- const childStyles = new Map()
226
-
227
- // 处理每个class配置
228
- cell.class.split(' ').forEach(className => {
229
- const classConfig = styleConfig[className]
230
- if (!classConfig) return
231
-
232
- // 处理根节点样式
233
- Object.entries(classConfig).forEach(([key, value]) => {
234
- if (!key.startsWith('*') && typeof key !== 'object') {
235
- rootStyles[key] = value
236
- }
237
- })
238
-
239
- // 处理子节点样式
240
- this.parseNestedStyles(classConfig, childStyles)
241
- })
242
-
243
- console.warn('样式配置', rootStyles, childStyles)
244
- return {
245
- rootStyles,
246
- childStyles
247
- }
248
- },
249
-
250
- // 递归解析嵌套的样式配置
251
- parseNestedStyles (config, styleMap, parentKey = '') {
252
- Object.entries(config).forEach(([key, value]) => {
253
- if (!key.startsWith('*')) return
254
-
255
- const className = key.replace('*', '.')
256
-
257
- // 如果值是对象,检查是否包含样式和子节点
258
- if (typeof value === 'object') {
259
- const { style = {}, children = {} } = this.separateStyleAndChildren(value)
260
-
261
- // 创建或获取当前节点的样式配置
262
- if (!styleMap.has(className)) {
263
- styleMap.set(className, {
264
- styles: {},
265
- children: new Map()
266
- })
267
- }
268
-
269
- const nodeData = styleMap.get(className)
270
-
271
- // 合并样式
272
- Object.assign(nodeData.styles, style)
273
-
274
- // 递归处理子节点
275
- this.parseNestedStyles(children, nodeData.children, className)
276
- }
277
- })
278
- },
279
-
280
- // 分离样式属性和子节点配置
281
- separateStyleAndChildren (obj) {
282
- const style = {}
283
- const children = {}
284
-
285
- Object.entries(obj).forEach(([key, value]) => {
286
- if (key.startsWith('*')) {
287
- // 子节点配置
288
- children[key] = value
289
- } else {
290
- // 样式属性
291
- style[key] = value
292
- }
293
- })
294
-
295
- return { style, children }
296
- },
297
-
298
- // 应用组件样式
299
- async applyComponentStyles (component) {
300
- if (!component) return
301
-
302
- const { rootStyles, childStyles } = await this.parseComponentStyles(this.activatedConfig)
303
-
304
- // 应用根节点样式
305
- if (Object.keys(rootStyles).length > 0) {
306
- Object.entries(rootStyles).forEach(([property, value]) => {
307
- component.style.setProperty(property, value, 'important')
308
- })
309
- }
310
-
311
- // 如果没有子节点样式,直接返回
312
- if (childStyles.size === 0) return
313
-
314
- let retryCount = 0
315
- const maxRetries = 5
316
- const retryInterval = 100 // 100ms
317
-
318
- const applyStyles = () => {
319
- this.applyChildStylesOptimized(component, childStyles)
320
- }
321
-
322
- // 首次应用样式
323
- applyStyles()
324
-
325
- // 创建重试机制
326
- const retryApplyStyles = () => {
327
- if (retryCount >= maxRetries) return
328
-
329
- setTimeout(() => {
330
- applyStyles()
331
- retryCount++
332
- retryApplyStyles()
333
- }, retryInterval)
334
- }
335
-
336
- // 开始重试
337
- retryApplyStyles()
338
-
339
- // 创建 MutationObserver 用于动态内容
340
- const observer = new MutationObserver((mutations) => {
341
- // 检查是否有新增节点
342
- const hasNewNodes = mutations.some(mutation =>
343
- mutation.type === 'childList' && mutation.addedNodes.length > 0
344
- )
345
-
346
- if (hasNewNodes) {
347
- applyStyles()
348
- }
349
- })
350
-
351
- // 配置 observer
352
- observer.observe(component, {
353
- childList: true,
354
- subtree: true,
355
- attributes: false
356
- })
357
-
358
- // 3秒后停止观察
359
- // setTimeout(() => {
360
- // observer.disconnect()
361
- // }, 3000)
362
-
363
- // 组件销毁时清理
364
- this.$once('hook:beforeDestroy', () => {
365
- observer.disconnect()
366
- })
367
- },
368
-
369
- // 优化后的子节点样式应用方法
370
- applyChildStylesOptimized (rootElement, styleMap, parentSelector = '') {
371
- if (!rootElement) return
372
-
373
- // 处理样式映射
374
- for (const [selector, data] of styleMap.entries()) {
375
- const currentSelector = parentSelector ? `${parentSelector} ${selector}` : selector
376
-
377
- try {
378
- // 查找匹配的元素
379
- const elements = Array.from(rootElement.querySelectorAll(currentSelector))
380
-
381
- if (!elements.length) continue
382
-
383
- // 应用当前层级样式
384
- if (data.styles) {
385
- elements.forEach(element => {
386
- if (!element) return
387
-
388
- // 应用每个样式属性
389
- Object.entries(data.styles).forEach(([property, value]) => {
390
- try {
391
- element.style.setProperty(property, value, 'important')
392
- } catch (err) {
393
- console.warn(`设置样式失败: ${property}=${value}`, err)
394
- }
395
- })
396
- })
397
- }
398
-
399
- // 处理子层级
400
- if (data.children && data.children.size > 0) {
401
- elements.forEach(element => {
402
- if (element) {
403
- this.applyChildStylesOptimized(element, data.children, currentSelector)
404
- }
405
- })
406
- }
407
- } catch (err) {
408
- console.warn(`处理选择器失败: ${currentSelector}`, err)
409
- continue
410
- }
411
- }
412
- },
413
- },
414
- created () {
415
- console.log('>>>> xreportdesign 渲染', JSON.stringify(this.config, null, 2))
416
- if (this.slotConfigName) {
417
- getConfigByName(this.slotConfigName, undefined, res => {
418
- this.slotConfig = res
419
- res.data = { ...res.data, ...this.data }
420
- this.config.data = res.data
421
- this.activatedConfig = res
422
- this.render = true
423
- }, this.env === 'dev')
424
- } else {
425
- this.activatedConfig = this.config
426
- this.render = true
427
- }
428
-
429
- this.activatedConfig.columns.forEach(row => {
430
- if (row[0] && row[0].type && row[0].type === 'list' && row[0].listLength === 1) {
431
- row.forEach(cell => {
432
- cell.listLength = this.activatedConfig.data[cell.dataIndex].length
433
- })
434
- }
435
- })
436
- },
437
- mounted () {
438
- this.applyAllStyles()
439
- },
440
- watch: {
441
- config: {
442
- deep: true,
443
- handler (newVal) {
444
- }
445
- },
446
- activatedConfig: {
447
- deep: true,
448
- handler (val) {
449
- console.log('>>>> activatedConfig: ', val)
450
- }
451
- }
452
- }
453
- }
454
- </script>
455
-
456
- <style lang="less" scoped>
457
- .img {
458
- width: 95%;
459
- height: 180px;
460
- object-fit: cover;
461
- }
462
-
463
- .reportMain {
464
- text-align: center;
465
- margin: 0 auto;
466
- font-size: 16px;
467
- color: #000;
468
- background-color: #fff;
469
- border-radius: 8px;
470
-
471
- .reportTitle {
472
- font-weight: bold;
473
- }
474
-
475
- .subTitle {
476
- display: flex;
477
- justify-content: space-between;
478
- margin-bottom: 1%;
479
-
480
- .subTitleItems {
481
- max-width: 30%;
482
- }
483
- }
484
-
485
- .inputsDiv {
486
- display: flex;
487
- justify-content: space-between;
488
-
489
- .inputsDivItem {
490
- display: flex;
491
- align-items: center;
492
- padding: 0 4px;
493
- white-space: nowrap;
494
-
495
- .inputsDivItemLabel {
496
- padding: 0 4px;
497
- }
498
- }
499
- }
500
-
501
- .reportTable {
502
- width: 100%;
503
- border-collapse: collapse;
504
- table-layout: fixed;
505
- word-break: break-all;
506
- }
507
- }
508
-
509
- .reportMainForDisplay {
510
- text-align: center;
511
- margin: 10% auto;
512
- font-size: 16px;
513
- color: #000;
514
- background-color: #fff;
515
- border-radius: 8px;
516
-
517
- .reportTitle {
518
- font-weight: bold;
519
- }
520
-
521
- .subTitle {
522
- display: flex;
523
- justify-content: space-between;
524
-
525
- .subTitleItems {
526
- max-width: 30%;
527
- }
528
- }
529
-
530
- .inputsDiv {
531
- display: flex;
532
- justify-content: space-around;
533
-
534
- .inputsDivItem {
535
- display: flex;
536
- align-items: center;
537
- padding: 0 4px;
538
- white-space: nowrap;
539
-
540
- .inputsDivItemLabel {
541
- padding: 0 4px;
542
- }
543
- }
544
- }
545
-
546
- .reportTable {
547
- width: 100%;
548
- border-collapse: collapse;
549
- table-layout: fixed;
550
- word-break: break-all;
551
- }
552
- }
553
-
554
- .reportMainNoPadding {
555
- // text-align: center;
556
- margin: 0 auto;
557
- font-size: 16px;
558
- color: #000;
559
- // background-color: #fff;
560
- border-radius: 8px;
561
- height: auto;
562
- min-height: 20vh;
563
- overflow-y: auto;
564
- overflow-x: hidden;
565
-
566
- .reportTitle {
567
- font-weight: bold;
568
- }
569
-
570
- .subTitle {
571
- display: flex;
572
- justify-content: space-between;
573
-
574
- .subTitleItems {
575
- max-width: 30%;
576
- }
577
- }
578
-
579
- .inputsDiv {
580
- display: flex;
581
- justify-content: space-between;
582
-
583
- .inputsDivItem {
584
- display: flex;
585
- align-items: center;
586
- padding: 0 4px;
587
- white-space: nowrap;
588
-
589
- .inputsDivItemLabel {
590
- padding: 0 4px;
591
- }
592
- }
593
- }
594
-
595
- .reportTable {
596
- width: 100%;
597
- border-collapse: collapse;
598
- table-layout: fixed;
599
- word-break: break-all;
600
- }
601
- }
602
-
603
- .tools {
604
- position: fixed;
605
- right: 2%;
606
- text-align: right;
607
- width: 60%;
608
- cursor: pointer;
609
-
610
- .toolsItem {
611
- width: 15%;
612
- margin-right: 3%;
613
- display: inline-block;
614
- }
615
- }
616
- </style>
1
+ <template>
2
+ <div class="reportGridMain" ref="reportGridMain">
3
+ <template v-if="isWidget">
4
+ <!-- 插槽展示 -->
5
+ <x-report-tr-group
6
+ v-for="(row, rowIndex) in activatedConfig.columns"
7
+ @updateImg="updateImg"
8
+ @selectRow="selectRow"
9
+ :show-img-in-cell="showImgInCell"
10
+ :server-name="serverName"
11
+ :env="env"
12
+ :key="rowIndex"
13
+ :columns="row"
14
+ :no-top-border="noTopBorder"
15
+ :config-data="activatedConfig.data"
16
+ :config="activatedConfig"
17
+ :display="true">
18
+ </x-report-tr-group>
19
+ </template>
20
+ <template v-else>
21
+ <div
22
+ :class=" noPadding ? 'reportMainNoPadding' : 'reportMain'"
23
+ :style="activatedConfig.width > 0 ? ('width:' + activatedConfig.width + 'px') : undefined">
24
+ <!-- 主体表格 -->
25
+ <table class="reportTable" v-if="render" :style="activatedConfig.style ? activatedConfig.style : undefined">
26
+ <tbody class="reportTable">
27
+ <!-- 插槽展示 -->
28
+ <x-report-tr-group
29
+ :key="rowIndex"
30
+ v-for="(row, rowIndex) in activatedConfig.columns"
31
+ @selectRow="selectRow"
32
+ @slotRendered="slotRendered"
33
+ :server-name="serverName"
34
+ :env="env"
35
+ :columns="row"
36
+ :config-data="activatedConfig.data"
37
+ :config="activatedConfig"
38
+ :display="true">
39
+ </x-report-tr-group>
40
+ </tbody>
41
+ </table>
42
+ </div>
43
+ </template>
44
+ </div>
45
+ </template>
46
+
47
+ <script>
48
+ import XReportTrGroup from './XReportTrGroup.vue'
49
+ import { getConfigByName } from '@vue2-client/services/api/common'
50
+ import XFormTable from '@vue2-client/base-client/components/common/XFormTable/XFormTable'
51
+
52
+ export default {
53
+ name: 'XReportDesign',
54
+ props: {
55
+ // 配置
56
+ config: {
57
+ type: Object,
58
+ require: true,
59
+ default: () => {
60
+ }
61
+ },
62
+ showImgInCell: {
63
+ type: Boolean,
64
+ default: false
65
+ },
66
+ // 是否是展示用
67
+ forDisplay: {
68
+ type: Boolean,
69
+ default: false
70
+ },
71
+ // 命名空间
72
+ serverName: {
73
+ type: String,
74
+ default: 'af-system'
75
+ },
76
+ // 环境
77
+ env: {
78
+ type: String,
79
+ default: 'prod'
80
+ },
81
+ // 是否只能展示不可编辑
82
+ displayOnly: {
83
+ type: Boolean,
84
+ default: false
85
+ },
86
+ // 插槽名
87
+ slotConfigName: {
88
+ type: String,
89
+ default: undefined
90
+ },
91
+ // 表格没有边距
92
+ noPadding: {
93
+ type: Boolean,
94
+ default: true
95
+ },
96
+ // 表格没有上边框,与noPadding搭配可以实现连续表格
97
+ noTopBorder: {
98
+ type: Boolean,
99
+ default: false
100
+ },
101
+ },
102
+ components: {
103
+ XFormTable,
104
+ XReportTrGroup
105
+ },
106
+ inject: ['isWidget'],
107
+ provide () {
108
+ return {
109
+ setColSpanByName: this.setColSpanByName
110
+ }
111
+ },
112
+ data () {
113
+ return {
114
+ // 表单的数据
115
+ data: this.config.data,
116
+ // 插槽配置
117
+ slotConfig: undefined,
118
+ // 控制是否渲染
119
+ render: false,
120
+ // 用于展示的配置
121
+ activatedConfig: {}
122
+ }
123
+ },
124
+ methods: {
125
+ slotRendered () {
126
+ this.$emit('slotRendered')
127
+ },
128
+ updateImg (data) {
129
+ this.$emit('updateImg', data)
130
+ },
131
+ selectRow (selectedRowKeys, selectedRows) {
132
+ this.table_selectedRowKeys = selectedRowKeys
133
+ this.table_selectedRows = selectedRows
134
+ console.log('XReportDesign')
135
+ this.$emit('selectRow', selectedRowKeys, selectedRows)
136
+ },
137
+ setColSpanByName (componentName, colSpan) {
138
+ // 处理columns配置(二维数组结构)
139
+ const processColumns = (columns) => {
140
+ columns.forEach((row) => {
141
+ row.forEach((cell, index) => {
142
+ if (Array.isArray(cell)) {
143
+ processColumns(cell) // 递归处理嵌套数组
144
+ } else if (cell.slotRef === componentName) {
145
+ // 使用Vue.set确保响应式更新
146
+ this.$set(row[index], 'colSpan', colSpan)
147
+ this.$set(row[index], '_isManualColSpan', true)
148
+ }
149
+ })
150
+ })
151
+ }
152
+
153
+ // 处理table配置(二维数组结构)
154
+ const processTable = (table) => {
155
+ table.forEach((row) => {
156
+ row.forEach((cell, index) => {
157
+ // 同时检查顶层和def中的slotRef
158
+ const isTargetCell =
159
+ cell.slotRef === componentName ||
160
+ cell.def?.slotRef === componentName
161
+
162
+ if (isTargetCell) {
163
+ // 同时更新顶层和def中的colSpan
164
+ this.$set(row[index], 'colSpan', colSpan)
165
+ this.$set(row[index], '_isManualColSpan', true)
166
+ if (cell.def) {
167
+ this.$set(row[index].def, 'colSpan', colSpan)
168
+ this.$set(row[index].def, '_isManualColSpan', true)
169
+ }
170
+ }
171
+ })
172
+ })
173
+ }
174
+
175
+ // 强制刷新视图
176
+ const forceUpdate = () => {
177
+ this.$nextTick(() => {
178
+ // 创建新对象引用以触发watch
179
+ this.activatedConfig = {
180
+ ...this.activatedConfig,
181
+ _version: Date.now()
182
+ }
183
+ this.$forceUpdate()
184
+ })
185
+ }
186
+
187
+ // 处理当前激活的配置
188
+ if (this.activatedConfig?.columns) {
189
+ processColumns(this.activatedConfig.columns)
190
+ }
191
+ if (this.activatedConfig?.table) {
192
+ processTable(this.activatedConfig.table)
193
+ }
194
+
195
+ forceUpdate()
196
+ },
197
+ applyAllStyles () {
198
+ // 应用组件样式
199
+ const component = this.$refs.reportGridMain
200
+ // 确保组件已经完全挂载
201
+ this.$nextTick(() => {
202
+ this.applyComponentStyles(component)
203
+ })
204
+ },
205
+ // 获取组件样式配置
206
+ async getComponentStyleConfig (componentType) {
207
+ try {
208
+ // 从配置中获取样式定义
209
+ const styleConfig = this.$appdata.getStylesByKey(componentType)
210
+ return styleConfig || {}
211
+ } catch (error) {
212
+ console.error('获取组件样式配置失败:', error)
213
+ return {}
214
+ }
215
+ },
216
+
217
+ // 解析组件样式配置
218
+ async parseComponentStyles (cell) {
219
+ if (!cell.class) return { rootStyles: {}, childStyles: {} }
220
+
221
+ const styleConfig = await this.getComponentStyleConfig('reportGridMain')
222
+ if (!styleConfig) return { rootStyles: {}, childStyles: {} }
223
+
224
+ const rootStyles = {}
225
+ const childStyles = new Map()
226
+
227
+ // 处理每个class配置
228
+ cell.class.split(' ').forEach(className => {
229
+ const classConfig = styleConfig[className]
230
+ if (!classConfig) return
231
+
232
+ // 处理根节点样式
233
+ Object.entries(classConfig).forEach(([key, value]) => {
234
+ if (!key.startsWith('*') && typeof key !== 'object') {
235
+ rootStyles[key] = value
236
+ }
237
+ })
238
+
239
+ // 处理子节点样式
240
+ this.parseNestedStyles(classConfig, childStyles)
241
+ })
242
+
243
+ console.warn('样式配置', rootStyles, childStyles)
244
+ return {
245
+ rootStyles,
246
+ childStyles
247
+ }
248
+ },
249
+
250
+ // 递归解析嵌套的样式配置
251
+ parseNestedStyles (config, styleMap, parentKey = '') {
252
+ Object.entries(config).forEach(([key, value]) => {
253
+ if (!key.startsWith('*')) return
254
+
255
+ const className = key.replace('*', '.')
256
+
257
+ // 如果值是对象,检查是否包含样式和子节点
258
+ if (typeof value === 'object') {
259
+ const { style = {}, children = {} } = this.separateStyleAndChildren(value)
260
+
261
+ // 创建或获取当前节点的样式配置
262
+ if (!styleMap.has(className)) {
263
+ styleMap.set(className, {
264
+ styles: {},
265
+ children: new Map()
266
+ })
267
+ }
268
+
269
+ const nodeData = styleMap.get(className)
270
+
271
+ // 合并样式
272
+ Object.assign(nodeData.styles, style)
273
+
274
+ // 递归处理子节点
275
+ this.parseNestedStyles(children, nodeData.children, className)
276
+ }
277
+ })
278
+ },
279
+
280
+ // 分离样式属性和子节点配置
281
+ separateStyleAndChildren (obj) {
282
+ const style = {}
283
+ const children = {}
284
+
285
+ Object.entries(obj).forEach(([key, value]) => {
286
+ if (key.startsWith('*')) {
287
+ // 子节点配置
288
+ children[key] = value
289
+ } else {
290
+ // 样式属性
291
+ style[key] = value
292
+ }
293
+ })
294
+
295
+ return { style, children }
296
+ },
297
+
298
+ // 应用组件样式
299
+ async applyComponentStyles (component) {
300
+ if (!component) return
301
+
302
+ const { rootStyles, childStyles } = await this.parseComponentStyles(this.activatedConfig)
303
+
304
+ // 应用根节点样式
305
+ if (Object.keys(rootStyles).length > 0) {
306
+ Object.entries(rootStyles).forEach(([property, value]) => {
307
+ component.style.setProperty(property, value, 'important')
308
+ })
309
+ }
310
+
311
+ // 如果没有子节点样式,直接返回
312
+ if (childStyles.size === 0) return
313
+
314
+ let retryCount = 0
315
+ const maxRetries = 5
316
+ const retryInterval = 100 // 100ms
317
+
318
+ const applyStyles = () => {
319
+ this.applyChildStylesOptimized(component, childStyles)
320
+ }
321
+
322
+ // 首次应用样式
323
+ applyStyles()
324
+
325
+ // 创建重试机制
326
+ const retryApplyStyles = () => {
327
+ if (retryCount >= maxRetries) return
328
+
329
+ setTimeout(() => {
330
+ applyStyles()
331
+ retryCount++
332
+ retryApplyStyles()
333
+ }, retryInterval)
334
+ }
335
+
336
+ // 开始重试
337
+ retryApplyStyles()
338
+
339
+ // 创建 MutationObserver 用于动态内容
340
+ const observer = new MutationObserver((mutations) => {
341
+ // 检查是否有新增节点
342
+ const hasNewNodes = mutations.some(mutation =>
343
+ mutation.type === 'childList' && mutation.addedNodes.length > 0
344
+ )
345
+
346
+ if (hasNewNodes) {
347
+ applyStyles()
348
+ }
349
+ })
350
+
351
+ // 配置 observer
352
+ observer.observe(component, {
353
+ childList: true,
354
+ subtree: true,
355
+ attributes: false
356
+ })
357
+
358
+ // 3秒后停止观察
359
+ // setTimeout(() => {
360
+ // observer.disconnect()
361
+ // }, 3000)
362
+
363
+ // 组件销毁时清理
364
+ this.$once('hook:beforeDestroy', () => {
365
+ observer.disconnect()
366
+ })
367
+ },
368
+
369
+ // 优化后的子节点样式应用方法
370
+ applyChildStylesOptimized (rootElement, styleMap, parentSelector = '') {
371
+ if (!rootElement) return
372
+
373
+ // 处理样式映射
374
+ for (const [selector, data] of styleMap.entries()) {
375
+ const currentSelector = parentSelector ? `${parentSelector} ${selector}` : selector
376
+
377
+ try {
378
+ // 查找匹配的元素
379
+ const elements = Array.from(rootElement.querySelectorAll(currentSelector))
380
+
381
+ if (!elements.length) continue
382
+
383
+ // 应用当前层级样式
384
+ if (data.styles) {
385
+ elements.forEach(element => {
386
+ if (!element) return
387
+
388
+ // 应用每个样式属性
389
+ Object.entries(data.styles).forEach(([property, value]) => {
390
+ try {
391
+ element.style.setProperty(property, value, 'important')
392
+ } catch (err) {
393
+ console.warn(`设置样式失败: ${property}=${value}`, err)
394
+ }
395
+ })
396
+ })
397
+ }
398
+
399
+ // 处理子层级
400
+ if (data.children && data.children.size > 0) {
401
+ elements.forEach(element => {
402
+ if (element) {
403
+ this.applyChildStylesOptimized(element, data.children, currentSelector)
404
+ }
405
+ })
406
+ }
407
+ } catch (err) {
408
+ console.warn(`处理选择器失败: ${currentSelector}`, err)
409
+ continue
410
+ }
411
+ }
412
+ },
413
+ },
414
+ created () {
415
+ console.log('>>>> xreportdesign 渲染', JSON.stringify(this.config, null, 2))
416
+ if (this.slotConfigName) {
417
+ getConfigByName(this.slotConfigName, undefined, res => {
418
+ this.slotConfig = res
419
+ res.data = { ...res.data, ...this.data }
420
+ this.config.data = res.data
421
+ this.activatedConfig = res
422
+ this.render = true
423
+ }, this.env === 'dev')
424
+ } else {
425
+ this.activatedConfig = this.config
426
+ this.render = true
427
+ }
428
+
429
+ this.activatedConfig.columns.forEach(row => {
430
+ if (row[0] && row[0].type && row[0].type === 'list' && row[0].listLength === 1) {
431
+ row.forEach(cell => {
432
+ cell.listLength = this.activatedConfig.data[cell.dataIndex].length
433
+ })
434
+ }
435
+ })
436
+ },
437
+ mounted () {
438
+ this.applyAllStyles()
439
+ },
440
+ watch: {
441
+ config: {
442
+ deep: true,
443
+ handler (newVal) {
444
+ }
445
+ },
446
+ activatedConfig: {
447
+ deep: true,
448
+ handler (val) {
449
+ console.log('>>>> activatedConfig: ', val)
450
+ }
451
+ }
452
+ }
453
+ }
454
+ </script>
455
+
456
+ <style lang="less" scoped>
457
+ .img {
458
+ width: 95%;
459
+ height: 180px;
460
+ object-fit: cover;
461
+ }
462
+
463
+ .reportMain {
464
+ text-align: center;
465
+ margin: 0 auto;
466
+ font-size: 16px;
467
+ color: #000;
468
+ background-color: #fff;
469
+ border-radius: 8px;
470
+
471
+ .reportTitle {
472
+ font-weight: bold;
473
+ }
474
+
475
+ .subTitle {
476
+ display: flex;
477
+ justify-content: space-between;
478
+ margin-bottom: 1%;
479
+
480
+ .subTitleItems {
481
+ max-width: 30%;
482
+ }
483
+ }
484
+
485
+ .inputsDiv {
486
+ display: flex;
487
+ justify-content: space-between;
488
+
489
+ .inputsDivItem {
490
+ display: flex;
491
+ align-items: center;
492
+ padding: 0 4px;
493
+ white-space: nowrap;
494
+
495
+ .inputsDivItemLabel {
496
+ padding: 0 4px;
497
+ }
498
+ }
499
+ }
500
+
501
+ .reportTable {
502
+ width: 100%;
503
+ border-collapse: collapse;
504
+ table-layout: fixed;
505
+ word-break: break-all;
506
+ }
507
+ }
508
+
509
+ .reportMainForDisplay {
510
+ text-align: center;
511
+ margin: 10% auto;
512
+ font-size: 16px;
513
+ color: #000;
514
+ background-color: #fff;
515
+ border-radius: 8px;
516
+
517
+ .reportTitle {
518
+ font-weight: bold;
519
+ }
520
+
521
+ .subTitle {
522
+ display: flex;
523
+ justify-content: space-between;
524
+
525
+ .subTitleItems {
526
+ max-width: 30%;
527
+ }
528
+ }
529
+
530
+ .inputsDiv {
531
+ display: flex;
532
+ justify-content: space-around;
533
+
534
+ .inputsDivItem {
535
+ display: flex;
536
+ align-items: center;
537
+ padding: 0 4px;
538
+ white-space: nowrap;
539
+
540
+ .inputsDivItemLabel {
541
+ padding: 0 4px;
542
+ }
543
+ }
544
+ }
545
+
546
+ .reportTable {
547
+ width: 100%;
548
+ border-collapse: collapse;
549
+ table-layout: fixed;
550
+ word-break: break-all;
551
+ }
552
+ }
553
+
554
+ .reportMainNoPadding {
555
+ // text-align: center;
556
+ margin: 0 auto;
557
+ font-size: 16px;
558
+ color: #000;
559
+ // background-color: #fff;
560
+ border-radius: 8px;
561
+ height: auto;
562
+ min-height: 20vh;
563
+ overflow-y: auto;
564
+ overflow-x: hidden;
565
+
566
+ .reportTitle {
567
+ font-weight: bold;
568
+ }
569
+
570
+ .subTitle {
571
+ display: flex;
572
+ justify-content: space-between;
573
+
574
+ .subTitleItems {
575
+ max-width: 30%;
576
+ }
577
+ }
578
+
579
+ .inputsDiv {
580
+ display: flex;
581
+ justify-content: space-between;
582
+
583
+ .inputsDivItem {
584
+ display: flex;
585
+ align-items: center;
586
+ padding: 0 4px;
587
+ white-space: nowrap;
588
+
589
+ .inputsDivItemLabel {
590
+ padding: 0 4px;
591
+ }
592
+ }
593
+ }
594
+
595
+ .reportTable {
596
+ width: 100%;
597
+ border-collapse: collapse;
598
+ table-layout: fixed;
599
+ word-break: break-all;
600
+ }
601
+ }
602
+
603
+ .tools {
604
+ position: fixed;
605
+ right: 2%;
606
+ text-align: right;
607
+ width: 60%;
608
+ cursor: pointer;
609
+
610
+ .toolsItem {
611
+ width: 15%;
612
+ margin-right: 3%;
613
+ display: inline-block;
614
+ }
615
+ }
616
+ </style>