vue2-client 1.17.45 → 1.17.46

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. package/package.json +1 -1
  2. package/src/assets/svg/female.svg +1 -1
  3. package/src/assets/svg/male.svg +1 -1
  4. package/src/base-client/components/common/HIS/HButtons/HButtons.vue +491 -491
  5. package/src/base-client/components/common/HIS/HFormGroup/index.js +3 -3
  6. package/src/base-client/components/common/HIS/HTab/HTab.vue +443 -443
  7. package/src/base-client/components/common/XCollapse/XCollapse.vue +830 -830
  8. package/src/base-client/components/common/XTimeline/XTimeline.vue +477 -477
  9. package/src/base-client/components/his/HChart/HChart.vue +493 -493
  10. package/src/base-client/components/his/HChart/demo.vue +88 -88
  11. package/src/base-client/components/his/HChart/index.md +798 -798
  12. package/src/base-client/components/his/XHisEditor/XHisEditor.vue +705 -705
  13. package/src/base-client/components/his/XIcon/XIcon.vue +73 -73
  14. package/src/base-client/components/his/XIcon/index.js +3 -3
  15. package/src/base-client/components/his/XIcon/index.md +177 -177
  16. package/src/base-client/components/his/XList/XList.vue +938 -938
  17. package/src/base-client/components/his/XSidebar/XSidebar.vue +1 -1
  18. package/src/base-client/components/his/XTimeSelect/XTimeSelect.vue +354 -354
  19. package/src/base-client/components/his/XTitle/XTitle.vue +314 -314
  20. package/src/base-client/components/his/XTreeRows/XTreeRows.vue +341 -341
  21. package/src/base-client/components/his/threeTestOrders/editor.vue +113 -113
  22. package/src/pages/userInfoDetailManage/ExceptionRecordQuery/index.vue +45 -45
  23. package/src-base-client/components/common/HIS/HForm/HForm.vue +347 -0
  24. package/src/assets/img/paymentMethod/icon1.png +0 -0
  25. package/src/assets/img/paymentMethod/icon2.png +0 -0
  26. package/src/assets/img/paymentMethod/icon3.png +0 -0
  27. package/src/assets/img/paymentMethod/icon4.png +0 -0
  28. package/src/assets/img/paymentMethod/icon5.png +0 -0
  29. package/src/assets/img/paymentMethod/icon6.png +0 -0
  30. package/src/base-client/components/common/XReport/XReportHospitalizationDemo.vue +0 -45
  31. package/src-base-client/components/his/XCharge/XCharge.vue +0 -0
  32. /package/src-base-client/components/{his/XCharge/README.md → common/XCollapse/XCollapse.vue} +0 -0
@@ -1,493 +1,493 @@
1
- <template>
2
- <div class="h-chart-configurable" :class="wrapperClassObject">
3
- <x-title v-if="chartTitle" v-bind="xTitleAttrs" :title="chartTitle" />
4
- <!-- 图表挂载容器 -->
5
- <div ref="chartRef" class="chart-canvas" :style="chartStyle"></div>
6
- </div>
7
- </template>
8
-
9
- <script setup>
10
- import { ref, watch, onMounted, computed, useAttrs, onBeforeUnmount, nextTick } from 'vue'
11
- import * as echarts from 'echarts/core'
12
- import { BarChart, LineChart, PieChart } from 'echarts/charts'
13
- import { TooltipComponent, LegendComponent, GridComponent } from 'echarts/components'
14
- import { CanvasRenderer } from 'echarts/renderers'
15
- import { getConfigByName, runLogic } from '@vue2-client/services/api/common'
16
- import XTitle from '../XTitle/XTitle.vue'
17
- // 注册常用图表类型,避免每次重复导入
18
- echarts.use([BarChart, LineChart, PieChart, TooltipComponent, LegendComponent, GridComponent, CanvasRenderer])
19
-
20
- const props = defineProps({
21
- // 配置名称(用于查询配置)
22
- queryParamsName: {
23
- type: String,
24
- default: ''
25
- },
26
- // 服务名
27
- serviceName: {
28
- type: String,
29
- default: 'af-his'
30
- },
31
- // 固定查询参数
32
- fixedQueryForm: {
33
- type: Object,
34
- default: () => ({ condition: '1=1' })
35
- },
36
- config: {
37
- type: Object,
38
- default: null
39
- }
40
- })
41
-
42
- const emit = defineEmits(['dataLoaded', 'error', 'update:fixedQueryForm'])
43
-
44
- const chartRef = ref(null)
45
- let chartInstance = null
46
- const chartConfig = ref(null)
47
- const chartData = ref([])
48
- const loading = ref(false)
49
- let resizeObserver = null
50
-
51
- // 记录上一次的 queryParamsName,用于比较变化
52
- const lastQueryParamsName = ref('')
53
-
54
- const chartTitle = computed(() => {
55
- if (props.config?.xtitle) return props.config.xtitle
56
- if (chartConfig.value?.xtitle) return chartConfig.value.xtitle
57
- return ''
58
- })
59
- const xTitleAttrs = computed(() => {
60
- if (props.config?.xtitleAttrs) return props.config.xtitleAttrs
61
- if (chartConfig.value?.xtitleAttrs) return chartConfig.value.xtitleAttrs
62
- return {}
63
- })
64
- const chartStyle = computed(() => {
65
- const target = props.config ?? chartConfig.value ?? {}
66
- if (target.style && typeof target.style === 'object') return target.style
67
- return {}
68
- })
69
-
70
- const attrs = useAttrs()
71
- const wrapperClassObject = computed(() => {
72
- const a = attrs
73
- const classes = {}
74
-
75
- // 通用布尔样式开关(以存在/空字符串/'true' 为真)
76
- const booleanStyleKeys = [
77
- 'show-border',
78
- 'border-hide-left',
79
- 'border-hide-right'
80
- ]
81
- for (const key of booleanStyleKeys) {
82
- const val = a[key]
83
- const truthy = val === true || val === '' || val === 'true'
84
- if (truthy) classes[`h-chart-${key}`] = true
85
- }
86
-
87
- return classes
88
- })
89
-
90
- // 常用图表预设,统一处理 dataset → ECharts option 的映射
91
- const presetResolvers = {
92
- bar: ({ dataset }) => ({
93
- animationDuration: 420,
94
- animationEasing: 'cubicOut',
95
- animationDelay: (_, idx) => idx * 60,
96
- tooltip: {
97
- trigger: 'axis',
98
- axisPointer: {
99
- type: 'shadow'
100
- }
101
- },
102
- xAxis: { type: 'category', data: dataset.map(item => item.label) },
103
- yAxis: { type: 'value' },
104
- series: [
105
- {
106
- type: 'bar',
107
- data: dataset.map(item => item.value),
108
- itemStyle: {
109
- color: '#3362DA',
110
- borderRadius: [4, 4, 0, 0]
111
- },
112
- emphasis: {
113
- itemStyle: {
114
- color: '#4C7CFF',
115
- shadowBlur: 18,
116
- shadowColor: 'rgba(76, 124, 255, 0.45)'
117
- }
118
- }
119
- }
120
- ]
121
- }),
122
- line: ({ dataset }) => ({
123
- animationDuration: 420,
124
- animationEasing: 'cubicOut',
125
- animationDelay: (_, idx) => idx * 50,
126
- tooltip: {
127
- trigger: 'axis',
128
- axisPointer: {
129
- type: 'line'
130
- }
131
- },
132
- xAxis: { type: 'category', data: dataset.map(item => item.label) },
133
- yAxis: { type: 'value' },
134
- series: [
135
- {
136
- type: 'line',
137
- smooth: true,
138
- data: dataset.map(item => item.value),
139
- lineStyle: {
140
- width: 3,
141
- color: '#28C8B5'
142
- },
143
- itemStyle: {
144
- color: '#28C8B5'
145
- },
146
- emphasis: {
147
- itemStyle: {
148
- color: '#4BE1CD',
149
- borderColor: '#D8FFF6',
150
- borderWidth: 6
151
- },
152
- lineStyle: {
153
- width: 4
154
- }
155
- },
156
- areaStyle: {
157
- opacity: 0.1,
158
- color: '#28C8B5'
159
- }
160
- }
161
- ]
162
- }),
163
- pie: ({ dataset }) => ({
164
- animationDuration: 420,
165
- animationEasing: 'cubicOut',
166
- tooltip: {
167
- trigger: 'item',
168
- formatter: '{a} <br/>{b}: {c} ({d}%)'
169
- },
170
- series: [
171
- {
172
- type: 'pie',
173
- radius: '55%',
174
- data: dataset.map(item => ({ name: item.label, value: item.value })),
175
- itemStyle: {
176
- borderColor: '#fff',
177
- borderWidth: 2
178
- },
179
- emphasis: {
180
- scale: true,
181
- scaleSize: 6,
182
- itemStyle: {
183
- shadowBlur: 16,
184
- shadowColor: 'rgba(0, 0, 0, 0.25)'
185
- },
186
- label: {
187
- show: true,
188
- fontWeight: 'bold'
189
- }
190
- }
191
- }
192
- ]
193
- })
194
- }
195
-
196
- // 数据转换:将后端返回的数据转换为图表需要的 dataset 格式
197
- const transformData = (rawData, dataMapping) => {
198
- // 增强数据提取:支持从包装对象中提取数组
199
- let dataArray = rawData
200
-
201
- // 如果 rawData 是对象但不是数组,尝试提取数组数据
202
- if (rawData && typeof rawData === 'object' && !Array.isArray(rawData)) {
203
- // 尝试常见的包装字段
204
- dataArray = rawData.data || rawData.result || rawData.list || rawData.items || rawData.records
205
-
206
- // 如果还是找不到数组,检查是否所有值都是数组
207
- if (!Array.isArray(dataArray)) {
208
- const arrayValues = Object.values(rawData).filter(val => Array.isArray(val))
209
- if (arrayValues.length === 1) {
210
- dataArray = arrayValues[0]
211
- }
212
- }
213
- }
214
-
215
- // 最终检查:必须是数组
216
- if (!dataArray || !Array.isArray(dataArray)) {
217
- console.warn('HChart: transformData 接收到的数据不是数组格式:', rawData)
218
- return []
219
- }
220
-
221
- // 如果配置了数据映射规则,使用映射规则
222
- if (dataMapping) {
223
- const { labelField = 'label', valueField = 'value' } = dataMapping
224
- return dataArray.map(item => ({
225
- label: item[labelField] || item.name || item.label || '',
226
- value: Number(item[valueField] || item.value || 0)
227
- }))
228
- }
229
-
230
- // 默认处理:尝试自动识别
231
- return dataArray.map(item => {
232
- if (typeof item === 'object' && item !== null) {
233
- return {
234
- label: item.label || item.name || item.text || item.x || String(item[Object.keys(item)[0]] || ''),
235
- value: Number(item.value || item.count || item.y || item[Object.keys(item)[1]] || 0)
236
- }
237
- }
238
- return { label: String(item), value: 0 }
239
- })
240
- }
241
-
242
- // 获取配置和数据
243
- const fetchConfigAndData = async () => {
244
- if (!props.queryParamsName && !props.config) {
245
- console.warn('HChart: queryParamsName 或 config 必须提供一个')
246
- return
247
- }
248
-
249
- try {
250
- loading.value = true
251
-
252
- // 如果直接提供了 config,直接使用
253
- if (props.config) {
254
- chartConfig.value = props.config
255
- console.log('44444444444444444444444444444444')
256
- await loadChartData(props.config)
257
- return
258
- }
259
-
260
- // 否则通过 queryParamsName 查询配置
261
- getConfigByName(props.queryParamsName, props.serviceName, async (res) => {
262
- if (!res) {
263
- console.error('HChart: 未能获取到配置内容')
264
- emit('error', new Error('未能获取到配置内容'))
265
- return
266
- }
267
-
268
- chartConfig.value = res
269
- console.log('3333333333333333333333333')
270
- await loadChartData(res)
271
- })
272
- } catch (error) {
273
- console.error('HChart: 获取配置或数据时发生错误:', error)
274
- emit('error', error)
275
- } finally {
276
- loading.value = false
277
- }
278
- }
279
-
280
- // 加载图表数据
281
- const loadChartData = async (config) => {
282
- if (!config) return
283
-
284
- try {
285
- // 如果配置中直接提供了 dataset,直接使用
286
- if (config.dataset && Array.isArray(config.dataset)) {
287
- chartData.value = config.dataset
288
- renderChart()
289
- console.log('1111111111111111111111111')
290
- emit('dataLoaded', chartData.value)
291
- return
292
- }
293
-
294
- // 如果配置中提供了 data(logicName),通过 runLogic 获取数据
295
- if (config.data) {
296
- const result = await runLogic(config.data, props.fixedQueryForm, props.serviceName)
297
-
298
- // 转换数据格式
299
- const transformedData = transformData(result, config.dataMapping)
300
- if (transformedData.length === 0) {
301
- console.warn('HChart: 数据转换后为空数组,原始数据:', result)
302
- }
303
- chartData.value = transformedData
304
- renderChart()
305
- console.log('22222222222222222222222222')
306
- emit('dataLoaded', transformedData)
307
- } else {
308
- // 没有数据源,使用空数据
309
- chartData.value = []
310
- renderChart()
311
- }
312
- } catch (error) {
313
- console.error('HChart: 加载数据时发生错误:', error)
314
- emit('error', error)
315
- }
316
- }
317
-
318
- // 渲染图表
319
- const renderChart = () => {
320
- if (!chartRef.value) return
321
- nextTick(() => {
322
- if (!chartInstance) {
323
- chartInstance = echarts.init(chartRef.value)
324
- // 监听窗口大小变化
325
- resizeObserver = new ResizeObserver(() => {
326
- chartInstance?.resize()
327
- })
328
- resizeObserver.observe(chartRef.value)
329
- }
330
-
331
- const config = chartConfig.value || props.config
332
- if (!config || !config.type) return
333
-
334
- const resolver = presetResolvers[config.type]
335
- if (!resolver) {
336
- console.warn(`HChart: 不支持的图表类型 ${config.type}`)
337
- return
338
- }
339
-
340
- const preset = resolver({ dataset: chartData.value })
341
- // 深度合并 series 配置
342
- let finalOptions = {
343
- legend: config.legend || {},
344
- grid: config.grid || {},
345
- ...preset
346
- }
347
-
348
- // 如果 options 中有 series,进行深度合并
349
- if (config.options?.series && preset.series) {
350
- // 分离需要特殊处理的配置项
351
- const { xAxis: customXAxis, yAxis: customYAxis, series: customSeries, ...restOptions } = config.options
352
-
353
- finalOptions = {
354
- ...finalOptions,
355
- ...restOptions,
356
- // 对于有 xAxis 的图表类型(bar, line),智能合并 xAxis
357
- ...(preset.xAxis && {
358
- xAxis: {
359
- ...preset.xAxis,
360
- ...customXAxis,
361
- // 如果用户配置的 data 为空数组或未定义,使用预设的 data
362
- data: (customXAxis?.data && customXAxis.data.length > 0)
363
- ? customXAxis.data
364
- : preset.xAxis.data
365
- }
366
- }),
367
- // 对于有 yAxis 的图表类型,合并 yAxis
368
- ...(preset.yAxis && {
369
- yAxis: {
370
- ...preset.yAxis,
371
- ...customYAxis
372
- }
373
- }),
374
- series: [
375
- {
376
- ...preset.series[0],
377
- ...customSeries[0],
378
- // 确保 data 来自预设(从 logic 获取的数据)
379
- data: preset.series[0].data
380
- }
381
- ]
382
- }
383
- } else {
384
- // 没有自定义 series 的情况
385
- const { xAxis: customXAxis, yAxis: customYAxis, ...restOptions } = config.options || {}
386
-
387
- finalOptions = {
388
- ...finalOptions,
389
- ...restOptions,
390
- // 对于有 xAxis 的图表类型,智能合并
391
- ...(preset.xAxis && {
392
- xAxis: {
393
- ...preset.xAxis,
394
- ...customXAxis,
395
- // 如果用户配置的 data 为空数组或未定义,使用预设的 data
396
- data: (customXAxis?.data && customXAxis.data.length > 0)
397
- ? customXAxis.data
398
- : preset.xAxis.data
399
- }
400
- }),
401
- // 对于有 yAxis 的图表类型,合并 yAxis
402
- ...(preset.yAxis && {
403
- yAxis: {
404
- ...preset.yAxis,
405
- ...customYAxis
406
- }
407
- })
408
- }
409
- }
410
- chartInstance.setOption(finalOptions, true)
411
- })
412
- }
413
-
414
- // 监听 queryParamsName 变化,只有在名称变化时才重新获取配置
415
- watch(
416
- () => props.queryParamsName,
417
- (newName, oldName) => {
418
- if (newName !== oldName) {
419
- console.log('99999999999999999999999999')
420
- fetchConfigAndData()
421
- }
422
- }
423
- )
424
-
425
- // 暴露给外部的方法
426
- const refresh = () => {
427
- console.log('88888888888888888888888888')
428
- fetchConfigAndData()
429
- }
430
-
431
- const reload = (newQueryForm) => {
432
- if (newQueryForm) {
433
- emit('update:fixedQueryForm', { ...props.fixedQueryForm, ...newQueryForm })
434
- } else {
435
- console.log('7777777777777777777777777')
436
- fetchConfigAndData()
437
- }
438
- }
439
-
440
- defineExpose({
441
- refresh,
442
- reload,
443
- chartInstance
444
- })
445
-
446
- onMounted(() => {
447
- // 初始化时记录 queryParamsName
448
- lastQueryParamsName.value = props.queryParamsName
449
- console.log('66666666666666666666666666666')
450
- fetchConfigAndData()
451
- })
452
-
453
- onBeforeUnmount(() => {
454
- // 清理图表实例和观察器
455
- if (chartInstance) {
456
- chartInstance.dispose()
457
- chartInstance = null
458
- }
459
- if (resizeObserver) {
460
- resizeObserver.disconnect()
461
- resizeObserver = null
462
- }
463
- })
464
- </script>
465
-
466
- <style scoped lang="less">
467
- .h-chart-configurable {
468
- display: flex;
469
- flex-direction: column;
470
- transition: transform 0.22s ease, box-shadow 0.22s ease;
471
-
472
- &.h-chart-show-border {
473
- border: 1px solid #E5E9F0;
474
- }
475
- &.h-chart-border-hide-right {
476
- border-right: 0px;
477
- }
478
- &.h-chart-border-hide-left {
479
- border-left: 0px;
480
- }
481
- }
482
-
483
- .h-chart-configurable.hoverable:hover {
484
- transform: translateY(-4px);
485
- box-shadow: 0 12px 28px rgba(0, 0, 0, 0.12);
486
- }
487
-
488
- .chart-canvas {
489
- width: 100%;
490
- margin-top: 10px;
491
- min-height: 280px;
492
- }
493
- </style>
1
+ <template>
2
+ <div class="h-chart-configurable" :class="wrapperClassObject">
3
+ <x-title v-if="chartTitle" v-bind="xTitleAttrs" :title="chartTitle" />
4
+ <!-- 图表挂载容器 -->
5
+ <div ref="chartRef" class="chart-canvas" :style="chartStyle"></div>
6
+ </div>
7
+ </template>
8
+
9
+ <script setup>
10
+ import { ref, watch, onMounted, computed, useAttrs, onBeforeUnmount, nextTick } from 'vue'
11
+ import * as echarts from 'echarts/core'
12
+ import { BarChart, LineChart, PieChart } from 'echarts/charts'
13
+ import { TooltipComponent, LegendComponent, GridComponent } from 'echarts/components'
14
+ import { CanvasRenderer } from 'echarts/renderers'
15
+ import { getConfigByName, runLogic } from '@vue2-client/services/api/common'
16
+ import XTitle from '../XTitle/XTitle.vue'
17
+ // 注册常用图表类型,避免每次重复导入
18
+ echarts.use([BarChart, LineChart, PieChart, TooltipComponent, LegendComponent, GridComponent, CanvasRenderer])
19
+
20
+ const props = defineProps({
21
+ // 配置名称(用于查询配置)
22
+ queryParamsName: {
23
+ type: String,
24
+ default: ''
25
+ },
26
+ // 服务名
27
+ serviceName: {
28
+ type: String,
29
+ default: 'af-his'
30
+ },
31
+ // 固定查询参数
32
+ fixedQueryForm: {
33
+ type: Object,
34
+ default: () => ({ condition: '1=1' })
35
+ },
36
+ config: {
37
+ type: Object,
38
+ default: null
39
+ }
40
+ })
41
+
42
+ const emit = defineEmits(['dataLoaded', 'error', 'update:fixedQueryForm'])
43
+
44
+ const chartRef = ref(null)
45
+ let chartInstance = null
46
+ const chartConfig = ref(null)
47
+ const chartData = ref([])
48
+ const loading = ref(false)
49
+ let resizeObserver = null
50
+
51
+ // 记录上一次的 queryParamsName,用于比较变化
52
+ const lastQueryParamsName = ref('')
53
+
54
+ const chartTitle = computed(() => {
55
+ if (props.config?.xtitle) return props.config.xtitle
56
+ if (chartConfig.value?.xtitle) return chartConfig.value.xtitle
57
+ return ''
58
+ })
59
+ const xTitleAttrs = computed(() => {
60
+ if (props.config?.xtitleAttrs) return props.config.xtitleAttrs
61
+ if (chartConfig.value?.xtitleAttrs) return chartConfig.value.xtitleAttrs
62
+ return {}
63
+ })
64
+ const chartStyle = computed(() => {
65
+ const target = props.config ?? chartConfig.value ?? {}
66
+ if (target.style && typeof target.style === 'object') return target.style
67
+ return {}
68
+ })
69
+
70
+ const attrs = useAttrs()
71
+ const wrapperClassObject = computed(() => {
72
+ const a = attrs
73
+ const classes = {}
74
+
75
+ // 通用布尔样式开关(以存在/空字符串/'true' 为真)
76
+ const booleanStyleKeys = [
77
+ 'show-border',
78
+ 'border-hide-left',
79
+ 'border-hide-right'
80
+ ]
81
+ for (const key of booleanStyleKeys) {
82
+ const val = a[key]
83
+ const truthy = val === true || val === '' || val === 'true'
84
+ if (truthy) classes[`h-chart-${key}`] = true
85
+ }
86
+
87
+ return classes
88
+ })
89
+
90
+ // 常用图表预设,统一处理 dataset → ECharts option 的映射
91
+ const presetResolvers = {
92
+ bar: ({ dataset }) => ({
93
+ animationDuration: 420,
94
+ animationEasing: 'cubicOut',
95
+ animationDelay: (_, idx) => idx * 60,
96
+ tooltip: {
97
+ trigger: 'axis',
98
+ axisPointer: {
99
+ type: 'shadow'
100
+ }
101
+ },
102
+ xAxis: { type: 'category', data: dataset.map(item => item.label) },
103
+ yAxis: { type: 'value' },
104
+ series: [
105
+ {
106
+ type: 'bar',
107
+ data: dataset.map(item => item.value),
108
+ itemStyle: {
109
+ color: '#3362DA',
110
+ borderRadius: [4, 4, 0, 0]
111
+ },
112
+ emphasis: {
113
+ itemStyle: {
114
+ color: '#4C7CFF',
115
+ shadowBlur: 18,
116
+ shadowColor: 'rgba(76, 124, 255, 0.45)'
117
+ }
118
+ }
119
+ }
120
+ ]
121
+ }),
122
+ line: ({ dataset }) => ({
123
+ animationDuration: 420,
124
+ animationEasing: 'cubicOut',
125
+ animationDelay: (_, idx) => idx * 50,
126
+ tooltip: {
127
+ trigger: 'axis',
128
+ axisPointer: {
129
+ type: 'line'
130
+ }
131
+ },
132
+ xAxis: { type: 'category', data: dataset.map(item => item.label) },
133
+ yAxis: { type: 'value' },
134
+ series: [
135
+ {
136
+ type: 'line',
137
+ smooth: true,
138
+ data: dataset.map(item => item.value),
139
+ lineStyle: {
140
+ width: 3,
141
+ color: '#28C8B5'
142
+ },
143
+ itemStyle: {
144
+ color: '#28C8B5'
145
+ },
146
+ emphasis: {
147
+ itemStyle: {
148
+ color: '#4BE1CD',
149
+ borderColor: '#D8FFF6',
150
+ borderWidth: 6
151
+ },
152
+ lineStyle: {
153
+ width: 4
154
+ }
155
+ },
156
+ areaStyle: {
157
+ opacity: 0.1,
158
+ color: '#28C8B5'
159
+ }
160
+ }
161
+ ]
162
+ }),
163
+ pie: ({ dataset }) => ({
164
+ animationDuration: 420,
165
+ animationEasing: 'cubicOut',
166
+ tooltip: {
167
+ trigger: 'item',
168
+ formatter: '{a} <br/>{b}: {c} ({d}%)'
169
+ },
170
+ series: [
171
+ {
172
+ type: 'pie',
173
+ radius: '55%',
174
+ data: dataset.map(item => ({ name: item.label, value: item.value })),
175
+ itemStyle: {
176
+ borderColor: '#fff',
177
+ borderWidth: 2
178
+ },
179
+ emphasis: {
180
+ scale: true,
181
+ scaleSize: 6,
182
+ itemStyle: {
183
+ shadowBlur: 16,
184
+ shadowColor: 'rgba(0, 0, 0, 0.25)'
185
+ },
186
+ label: {
187
+ show: true,
188
+ fontWeight: 'bold'
189
+ }
190
+ }
191
+ }
192
+ ]
193
+ })
194
+ }
195
+
196
+ // 数据转换:将后端返回的数据转换为图表需要的 dataset 格式
197
+ const transformData = (rawData, dataMapping) => {
198
+ // 增强数据提取:支持从包装对象中提取数组
199
+ let dataArray = rawData
200
+
201
+ // 如果 rawData 是对象但不是数组,尝试提取数组数据
202
+ if (rawData && typeof rawData === 'object' && !Array.isArray(rawData)) {
203
+ // 尝试常见的包装字段
204
+ dataArray = rawData.data || rawData.result || rawData.list || rawData.items || rawData.records
205
+
206
+ // 如果还是找不到数组,检查是否所有值都是数组
207
+ if (!Array.isArray(dataArray)) {
208
+ const arrayValues = Object.values(rawData).filter(val => Array.isArray(val))
209
+ if (arrayValues.length === 1) {
210
+ dataArray = arrayValues[0]
211
+ }
212
+ }
213
+ }
214
+
215
+ // 最终检查:必须是数组
216
+ if (!dataArray || !Array.isArray(dataArray)) {
217
+ console.warn('HChart: transformData 接收到的数据不是数组格式:', rawData)
218
+ return []
219
+ }
220
+
221
+ // 如果配置了数据映射规则,使用映射规则
222
+ if (dataMapping) {
223
+ const { labelField = 'label', valueField = 'value' } = dataMapping
224
+ return dataArray.map(item => ({
225
+ label: item[labelField] || item.name || item.label || '',
226
+ value: Number(item[valueField] || item.value || 0)
227
+ }))
228
+ }
229
+
230
+ // 默认处理:尝试自动识别
231
+ return dataArray.map(item => {
232
+ if (typeof item === 'object' && item !== null) {
233
+ return {
234
+ label: item.label || item.name || item.text || item.x || String(item[Object.keys(item)[0]] || ''),
235
+ value: Number(item.value || item.count || item.y || item[Object.keys(item)[1]] || 0)
236
+ }
237
+ }
238
+ return { label: String(item), value: 0 }
239
+ })
240
+ }
241
+
242
+ // 获取配置和数据
243
+ const fetchConfigAndData = async () => {
244
+ if (!props.queryParamsName && !props.config) {
245
+ console.warn('HChart: queryParamsName 或 config 必须提供一个')
246
+ return
247
+ }
248
+
249
+ try {
250
+ loading.value = true
251
+
252
+ // 如果直接提供了 config,直接使用
253
+ if (props.config) {
254
+ chartConfig.value = props.config
255
+ console.log('44444444444444444444444444444444')
256
+ await loadChartData(props.config)
257
+ return
258
+ }
259
+
260
+ // 否则通过 queryParamsName 查询配置
261
+ getConfigByName(props.queryParamsName, props.serviceName, async (res) => {
262
+ if (!res) {
263
+ console.error('HChart: 未能获取到配置内容')
264
+ emit('error', new Error('未能获取到配置内容'))
265
+ return
266
+ }
267
+
268
+ chartConfig.value = res
269
+ console.log('3333333333333333333333333')
270
+ await loadChartData(res)
271
+ })
272
+ } catch (error) {
273
+ console.error('HChart: 获取配置或数据时发生错误:', error)
274
+ emit('error', error)
275
+ } finally {
276
+ loading.value = false
277
+ }
278
+ }
279
+
280
+ // 加载图表数据
281
+ const loadChartData = async (config) => {
282
+ if (!config) return
283
+
284
+ try {
285
+ // 如果配置中直接提供了 dataset,直接使用
286
+ if (config.dataset && Array.isArray(config.dataset)) {
287
+ chartData.value = config.dataset
288
+ renderChart()
289
+ console.log('1111111111111111111111111')
290
+ emit('dataLoaded', chartData.value)
291
+ return
292
+ }
293
+
294
+ // 如果配置中提供了 data(logicName),通过 runLogic 获取数据
295
+ if (config.data) {
296
+ const result = await runLogic(config.data, props.fixedQueryForm, props.serviceName)
297
+
298
+ // 转换数据格式
299
+ const transformedData = transformData(result, config.dataMapping)
300
+ if (transformedData.length === 0) {
301
+ console.warn('HChart: 数据转换后为空数组,原始数据:', result)
302
+ }
303
+ chartData.value = transformedData
304
+ renderChart()
305
+ console.log('22222222222222222222222222')
306
+ emit('dataLoaded', transformedData)
307
+ } else {
308
+ // 没有数据源,使用空数据
309
+ chartData.value = []
310
+ renderChart()
311
+ }
312
+ } catch (error) {
313
+ console.error('HChart: 加载数据时发生错误:', error)
314
+ emit('error', error)
315
+ }
316
+ }
317
+
318
+ // 渲染图表
319
+ const renderChart = () => {
320
+ if (!chartRef.value) return
321
+ nextTick(() => {
322
+ if (!chartInstance) {
323
+ chartInstance = echarts.init(chartRef.value)
324
+ // 监听窗口大小变化
325
+ resizeObserver = new ResizeObserver(() => {
326
+ chartInstance?.resize()
327
+ })
328
+ resizeObserver.observe(chartRef.value)
329
+ }
330
+
331
+ const config = chartConfig.value || props.config
332
+ if (!config || !config.type) return
333
+
334
+ const resolver = presetResolvers[config.type]
335
+ if (!resolver) {
336
+ console.warn(`HChart: 不支持的图表类型 ${config.type}`)
337
+ return
338
+ }
339
+
340
+ const preset = resolver({ dataset: chartData.value })
341
+ // 深度合并 series 配置
342
+ let finalOptions = {
343
+ legend: config.legend || {},
344
+ grid: config.grid || {},
345
+ ...preset
346
+ }
347
+
348
+ // 如果 options 中有 series,进行深度合并
349
+ if (config.options?.series && preset.series) {
350
+ // 分离需要特殊处理的配置项
351
+ const { xAxis: customXAxis, yAxis: customYAxis, series: customSeries, ...restOptions } = config.options
352
+
353
+ finalOptions = {
354
+ ...finalOptions,
355
+ ...restOptions,
356
+ // 对于有 xAxis 的图表类型(bar, line),智能合并 xAxis
357
+ ...(preset.xAxis && {
358
+ xAxis: {
359
+ ...preset.xAxis,
360
+ ...customXAxis,
361
+ // 如果用户配置的 data 为空数组或未定义,使用预设的 data
362
+ data: (customXAxis?.data && customXAxis.data.length > 0)
363
+ ? customXAxis.data
364
+ : preset.xAxis.data
365
+ }
366
+ }),
367
+ // 对于有 yAxis 的图表类型,合并 yAxis
368
+ ...(preset.yAxis && {
369
+ yAxis: {
370
+ ...preset.yAxis,
371
+ ...customYAxis
372
+ }
373
+ }),
374
+ series: [
375
+ {
376
+ ...preset.series[0],
377
+ ...customSeries[0],
378
+ // 确保 data 来自预设(从 logic 获取的数据)
379
+ data: preset.series[0].data
380
+ }
381
+ ]
382
+ }
383
+ } else {
384
+ // 没有自定义 series 的情况
385
+ const { xAxis: customXAxis, yAxis: customYAxis, ...restOptions } = config.options || {}
386
+
387
+ finalOptions = {
388
+ ...finalOptions,
389
+ ...restOptions,
390
+ // 对于有 xAxis 的图表类型,智能合并
391
+ ...(preset.xAxis && {
392
+ xAxis: {
393
+ ...preset.xAxis,
394
+ ...customXAxis,
395
+ // 如果用户配置的 data 为空数组或未定义,使用预设的 data
396
+ data: (customXAxis?.data && customXAxis.data.length > 0)
397
+ ? customXAxis.data
398
+ : preset.xAxis.data
399
+ }
400
+ }),
401
+ // 对于有 yAxis 的图表类型,合并 yAxis
402
+ ...(preset.yAxis && {
403
+ yAxis: {
404
+ ...preset.yAxis,
405
+ ...customYAxis
406
+ }
407
+ })
408
+ }
409
+ }
410
+ chartInstance.setOption(finalOptions, true)
411
+ })
412
+ }
413
+
414
+ // 监听 queryParamsName 变化,只有在名称变化时才重新获取配置
415
+ watch(
416
+ () => props.queryParamsName,
417
+ (newName, oldName) => {
418
+ if (newName !== oldName) {
419
+ console.log('99999999999999999999999999')
420
+ fetchConfigAndData()
421
+ }
422
+ }
423
+ )
424
+
425
+ // 暴露给外部的方法
426
+ const refresh = () => {
427
+ console.log('88888888888888888888888888')
428
+ fetchConfigAndData()
429
+ }
430
+
431
+ const reload = (newQueryForm) => {
432
+ if (newQueryForm) {
433
+ emit('update:fixedQueryForm', { ...props.fixedQueryForm, ...newQueryForm })
434
+ } else {
435
+ console.log('7777777777777777777777777')
436
+ fetchConfigAndData()
437
+ }
438
+ }
439
+
440
+ defineExpose({
441
+ refresh,
442
+ reload,
443
+ chartInstance
444
+ })
445
+
446
+ onMounted(() => {
447
+ // 初始化时记录 queryParamsName
448
+ lastQueryParamsName.value = props.queryParamsName
449
+ console.log('66666666666666666666666666666')
450
+ fetchConfigAndData()
451
+ })
452
+
453
+ onBeforeUnmount(() => {
454
+ // 清理图表实例和观察器
455
+ if (chartInstance) {
456
+ chartInstance.dispose()
457
+ chartInstance = null
458
+ }
459
+ if (resizeObserver) {
460
+ resizeObserver.disconnect()
461
+ resizeObserver = null
462
+ }
463
+ })
464
+ </script>
465
+
466
+ <style scoped lang="less">
467
+ .h-chart-configurable {
468
+ display: flex;
469
+ flex-direction: column;
470
+ transition: transform 0.22s ease, box-shadow 0.22s ease;
471
+
472
+ &.h-chart-show-border {
473
+ border: 1px solid #E5E9F0;
474
+ }
475
+ &.h-chart-border-hide-right {
476
+ border-right: 0px;
477
+ }
478
+ &.h-chart-border-hide-left {
479
+ border-left: 0px;
480
+ }
481
+ }
482
+
483
+ .h-chart-configurable.hoverable:hover {
484
+ transform: translateY(-4px);
485
+ box-shadow: 0 12px 28px rgba(0, 0, 0, 0.12);
486
+ }
487
+
488
+ .chart-canvas {
489
+ width: 100%;
490
+ margin-top: 10px;
491
+ min-height: 280px;
492
+ }
493
+ </style>