vue2-client 1.18.7 → 1.18.9
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.
- package/.eslintrc.js +90 -90
- package/Components.md +60 -60
- package/docs/index.md +30 -30
- package/index.js +31 -31
- package/jest-transform-stub.js +8 -8
- package/jest.setup.js +7 -7
- package/package.json +1 -1
- package/src/assets/img/querySlotDemo.svg +15 -15
- package/src/assets/svg/female.svg +1 -1
- package/src/assets/svg/male.svg +1 -1
- package/src/base-client/components/common/AmapMarker/AmapPointRendering.vue +120 -120
- package/src/base-client/components/common/CitySelect/index.js +3 -3
- package/src/base-client/components/common/CitySelect/index.md +109 -109
- package/src/base-client/components/common/CreateQuery/CreateQuery.vue +669 -669
- package/src/base-client/components/common/CreateQuery/index.js +3 -3
- package/src/base-client/components/common/CreateQuery/index.md +42 -42
- package/src/base-client/components/common/CreateSimpleFormQuery/index.js +3 -3
- package/src/base-client/components/common/CreateSimpleFormQuery/index.md +42 -42
- package/src/base-client/components/common/FormGroupEdit/index.js +3 -3
- package/src/base-client/components/common/FormGroupEdit/index.md +43 -43
- package/src/base-client/components/common/FormGroupQuery/FormGroupQuery.vue +166 -166
- package/src/base-client/components/common/FormGroupQuery/index.js +3 -3
- package/src/base-client/components/common/FormGroupQuery/index.md +43 -43
- package/src/base-client/components/common/HIS/HButtons/HButtons.vue +491 -491
- package/src/base-client/components/common/HIS/HForm/HForm.vue +32 -10
- package/src/base-client/components/common/HIS/HFormGroup/index.js +3 -3
- package/src/base-client/components/common/HIS/HTab/HTab.vue +443 -443
- package/src/base-client/components/common/JSONToTree/jsontotree.vue +271 -271
- package/src/base-client/components/common/PersonSetting/PersonSetting.vue +208 -208
- package/src/base-client/components/common/PersonSetting/index.js +3 -3
- package/src/base-client/components/common/Tree/Tree.vue +149 -149
- package/src/base-client/components/common/Tree/index.js +2 -2
- package/src/base-client/components/common/Upload/index.js +3 -3
- package/src/base-client/components/common/XAddNativeForm/index.md +146 -146
- package/src/base-client/components/common/XCard/XCard.vue +64 -64
- package/src/base-client/components/common/XCollapse/XCollapse.vue +830 -830
- package/src/base-client/components/common/XDataDrawer/XDataDrawer.vue +180 -180
- package/src/base-client/components/common/XDataDrawer/index.js +3 -3
- package/src/base-client/components/common/XDataDrawer/index.md +41 -41
- package/src/base-client/components/common/XDescriptions/index.js +3 -3
- package/src/base-client/components/common/XDescriptions/index.md +83 -83
- package/src/base-client/components/common/XForm/index.md +178 -178
- package/src/base-client/components/common/XInput/XInput.vue +12 -6
- package/src/base-client/components/common/XStepView/XStepView.vue +252 -252
- package/src/base-client/components/common/XStepView/index.js +3 -3
- package/src/base-client/components/common/XStepView/index.md +31 -31
- package/src/base-client/components/common/XTable/index.md +255 -255
- package/src/base-client/components/common/XTimeline/XTimeline.vue +477 -477
- package/src/base-client/components/his/HChart/HChart.vue +500 -53
- package/src/base-client/components/his/XHDescriptions/XHDescriptions.vue +44 -78
- package/src/base-client/components/his/XHisEditor/XHisEditor.vue +705 -705
- package/src/base-client/components/his/XList/XList.vue +938 -938
- package/src/base-client/components/his/XTimeSelect/XTimeSelect.vue +354 -354
- package/src/base-client/components/his/XTitle/XTitle.vue +314 -314
- package/src/base-client/components/his/XTreeRows/XTreeRows.vue +341 -341
- package/src/base-client/components/his/threeTestOrders/editor.vue +113 -113
- package/src/base-client/components/system/DictionaryDetailsView/DictionaryDetailsView.vue +232 -232
- package/src/base-client/plugins/Config.js +19 -19
- package/src/base-client/plugins/tabs-page-plugin.js +39 -39
- package/src/components/Charts/Bar.vue +62 -62
- package/src/components/Charts/ChartCard.vue +134 -134
- package/src/components/Charts/Liquid.vue +67 -67
- package/src/components/Charts/MiniArea.vue +39 -39
- package/src/components/Charts/MiniBar.vue +39 -39
- package/src/components/Charts/MiniProgress.vue +75 -75
- package/src/components/Charts/MiniSmoothArea.vue +40 -40
- package/src/components/Charts/Radar.vue +68 -68
- package/src/components/Charts/RankList.vue +77 -77
- package/src/components/Charts/TagCloud.vue +113 -113
- package/src/components/Charts/TransferBar.vue +64 -64
- package/src/components/Charts/Trend.vue +82 -82
- package/src/components/Charts/chart.less +12 -12
- package/src/components/Charts/smooth.area.less +13 -13
- package/src/components/NumberInfo/NumberInfo.vue +54 -54
- package/src/components/NumberInfo/index.js +3 -3
- package/src/components/NumberInfo/index.less +54 -54
- package/src/components/NumberInfo/index.md +43 -43
- package/src/components/card/ChartCard.vue +79 -79
- package/src/components/chart/Bar.vue +60 -60
- package/src/components/chart/MiniArea.vue +67 -67
- package/src/components/chart/MiniBar.vue +59 -59
- package/src/components/chart/MiniProgress.vue +57 -57
- package/src/components/chart/Radar.vue +80 -80
- package/src/components/chart/RankingList.vue +60 -60
- package/src/components/chart/Trend.vue +79 -79
- package/src/components/chart/index.less +9 -9
- package/src/components/checkbox/ColorCheckbox.vue +157 -157
- package/src/components/index.js +36 -36
- package/src/components/input/IInput.vue +66 -66
- package/src/components/menu/SideMenu.vue +75 -75
- package/src/components/menu/menu.js +273 -273
- package/src/components/tool/AStepItem.vue +60 -60
- package/src/layouts/CommonLayout.vue +56 -56
- package/src/layouts/header/HeaderNotice.vue +177 -177
- package/src/lib.js +1 -1
- package/src/mock/extend/index.js +84 -84
- package/src/mock/goods/index.js +108 -108
- package/src/pages/dashboard/workplace/WorkPlace.vue +141 -141
- package/src/pages/system/dictionary/index.vue +44 -44
- package/src/pages/system/monitor/loginInfor/index.vue +37 -37
- package/src/pages/system/monitor/operLog/index.vue +37 -37
- package/src/pages/userInfoDetailManage/ExceptionRecordQuery/index.vue +45 -45
- package/src/services/api/cas.js +79 -79
- package/src/store/modules/setting.js +119 -119
- package/src/utils/authority-utils.js +85 -85
- package/src/utils/errorCode.js +6 -6
- package/src-base-client/components/common/HIS/HForm/HForm.vue +347 -0
- package/src-base-client/components/common/XCollapse/XCollapse.vue +0 -0
- package//350/277/201/347/247/273/346/227/245/345/277/227.md +15 -15
|
@@ -1,8 +1,47 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div class="h-chart-configurable" :class="wrapperClassObject">
|
|
3
3
|
<x-title v-if="chartTitle" v-bind="xTitleAttrs" :title="chartTitle" />
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
<div class="chart-layout">
|
|
5
|
+
<div v-if="showSideList" class="chart-side-list">
|
|
6
|
+
<div v-if="sideListLabel" class="side-list-label">{{ sideListLabel }}</div>
|
|
7
|
+
<div class="side-list-menu">
|
|
8
|
+
<button
|
|
9
|
+
v-for="option in sideListOptions"
|
|
10
|
+
:key="option.value"
|
|
11
|
+
type="button"
|
|
12
|
+
class="side-list-item"
|
|
13
|
+
:class="{ 'is-active': option.value === selectedSideListValue }"
|
|
14
|
+
@click="handleListChange(option.value)"
|
|
15
|
+
>
|
|
16
|
+
<span class="item-label">{{ option.label }}</span>
|
|
17
|
+
<span v-if="option.badge" class="item-badge">{{ option.badge }}</span>
|
|
18
|
+
</button>
|
|
19
|
+
</div>
|
|
20
|
+
</div>
|
|
21
|
+
<div class="chart-main">
|
|
22
|
+
<div v-if="showRadioFilter" class="chart-toolbar">
|
|
23
|
+
<span v-if="radioFilterLabel" class="toolbar-label">{{ radioFilterLabel }}</span>
|
|
24
|
+
<div class="toolbar-radio-group">
|
|
25
|
+
<label
|
|
26
|
+
v-for="option in radioFilterOptions"
|
|
27
|
+
:key="option.value"
|
|
28
|
+
class="radio-pill"
|
|
29
|
+
:class="{ 'is-active': option.value === selectedFilterValue }"
|
|
30
|
+
>
|
|
31
|
+
<input
|
|
32
|
+
type="radio"
|
|
33
|
+
class="radio-pill-input"
|
|
34
|
+
:value="option.value"
|
|
35
|
+
:checked="option.value === selectedFilterValue"
|
|
36
|
+
@change="handleFilterChange(option.value)"
|
|
37
|
+
/>
|
|
38
|
+
<span>{{ option.label }}</span>
|
|
39
|
+
</label>
|
|
40
|
+
</div>
|
|
41
|
+
</div>
|
|
42
|
+
<div ref="chartRef" class="chart-canvas" :style="chartStyle"></div>
|
|
43
|
+
</div>
|
|
44
|
+
</div>
|
|
6
45
|
</div>
|
|
7
46
|
</template>
|
|
8
47
|
|
|
@@ -47,6 +86,9 @@ const chartConfig = ref(null)
|
|
|
47
86
|
const chartData = ref([])
|
|
48
87
|
const loading = ref(false)
|
|
49
88
|
let resizeObserver = null
|
|
89
|
+
const activePointIndex = ref(-1)
|
|
90
|
+
const selectedFilterValue = ref('')
|
|
91
|
+
const selectedSideListValue = ref('')
|
|
50
92
|
|
|
51
93
|
// 记录上一次的 queryParamsName,用于比较变化
|
|
52
94
|
const lastQueryParamsName = ref('')
|
|
@@ -66,6 +108,87 @@ const chartStyle = computed(() => {
|
|
|
66
108
|
if (target.style && typeof target.style === 'object') return target.style
|
|
67
109
|
return {}
|
|
68
110
|
})
|
|
111
|
+
const radioFilterConfig = computed(() => props.config?.radioFilter || chartConfig.value?.radioFilter || null)
|
|
112
|
+
const radioFilterOptions = computed(() => {
|
|
113
|
+
const options = radioFilterConfig.value?.options
|
|
114
|
+
return Array.isArray(options) ? options : []
|
|
115
|
+
})
|
|
116
|
+
const radioFilterLabel = computed(() => radioFilterConfig.value?.label || '')
|
|
117
|
+
const showRadioFilter = computed(() => radioFilterOptions.value.length > 0)
|
|
118
|
+
const filterFieldName = computed(() => radioFilterConfig.value?.field || radioFilterConfig.value?.prop || 'dimension')
|
|
119
|
+
const filterQueryParams = computed(() => {
|
|
120
|
+
if (!showRadioFilter.value) return {}
|
|
121
|
+
const optionValue = selectedFilterValue.value
|
|
122
|
+
if (optionValue === undefined || optionValue === null || optionValue === '') return {}
|
|
123
|
+
return { [filterFieldName.value]: optionValue }
|
|
124
|
+
})
|
|
125
|
+
const sideListConfig = computed(() => props.config?.sideList || chartConfig.value?.sideList || null)
|
|
126
|
+
const sideListOptions = computed(() => {
|
|
127
|
+
const options = sideListConfig.value?.options
|
|
128
|
+
return Array.isArray(options) ? options : []
|
|
129
|
+
})
|
|
130
|
+
const sideListLabel = computed(() => sideListConfig.value?.label || sideListConfig.value?.title || '')
|
|
131
|
+
const showSideList = computed(() => sideListOptions.value.length > 0)
|
|
132
|
+
const sideListFieldName = computed(() => sideListConfig.value?.field || sideListConfig.value?.prop || 'category')
|
|
133
|
+
const listQueryParams = computed(() => {
|
|
134
|
+
if (!showSideList.value) return {}
|
|
135
|
+
const optionValue = selectedSideListValue.value
|
|
136
|
+
if (optionValue === undefined || optionValue === null || optionValue === '') return {}
|
|
137
|
+
return { [sideListFieldName.value]: optionValue }
|
|
138
|
+
})
|
|
139
|
+
const resolveStaticDataset = (config) => {
|
|
140
|
+
const dataset = config?.dataset
|
|
141
|
+
if (Array.isArray(dataset)) return dataset
|
|
142
|
+
if (dataset && typeof dataset === 'object') {
|
|
143
|
+
const pickFirstArray = (source) => {
|
|
144
|
+
if (!source || typeof source !== 'object') return null
|
|
145
|
+
for (const value of Object.values(source)) {
|
|
146
|
+
if (Array.isArray(value)) return value
|
|
147
|
+
if (value && typeof value === 'object') {
|
|
148
|
+
const nested = pickFirstArray(value)
|
|
149
|
+
if (nested) return nested
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
return null
|
|
153
|
+
}
|
|
154
|
+
const pickByKeys = (source, keys, allowObjectReturn = false) => {
|
|
155
|
+
if (!source || typeof source !== 'object') return null
|
|
156
|
+
for (const key of keys) {
|
|
157
|
+
if (!key) continue
|
|
158
|
+
const scoped = source[key]
|
|
159
|
+
if (scoped === undefined) continue
|
|
160
|
+
if (Array.isArray(scoped)) return scoped
|
|
161
|
+
if (allowObjectReturn && scoped && typeof scoped === 'object') return scoped
|
|
162
|
+
}
|
|
163
|
+
return null
|
|
164
|
+
}
|
|
165
|
+
const listKeys = [
|
|
166
|
+
selectedSideListValue.value,
|
|
167
|
+
sideListConfig.value?.defaultValue
|
|
168
|
+
].filter(Boolean)
|
|
169
|
+
let scopedDataset = dataset
|
|
170
|
+
if (showSideList.value) {
|
|
171
|
+
const listScoped = pickByKeys(dataset, listKeys, true)
|
|
172
|
+
if (Array.isArray(listScoped)) return listScoped
|
|
173
|
+
if (listScoped && typeof listScoped === 'object') scopedDataset = listScoped
|
|
174
|
+
}
|
|
175
|
+
if (Array.isArray(scopedDataset)) return scopedDataset
|
|
176
|
+
const filterKeys = [
|
|
177
|
+
selectedFilterValue.value,
|
|
178
|
+
radioFilterConfig.value?.defaultValue
|
|
179
|
+
].filter(Boolean)
|
|
180
|
+
const filterScoped = pickByKeys(scopedDataset, filterKeys, false)
|
|
181
|
+
if (Array.isArray(filterScoped)) return filterScoped
|
|
182
|
+
const firstArray = pickFirstArray(scopedDataset)
|
|
183
|
+
if (Array.isArray(firstArray)) return firstArray
|
|
184
|
+
}
|
|
185
|
+
return null
|
|
186
|
+
}
|
|
187
|
+
const buildQueryPayload = () => ({
|
|
188
|
+
...(props.fixedQueryForm || {}),
|
|
189
|
+
...listQueryParams.value,
|
|
190
|
+
...filterQueryParams.value
|
|
191
|
+
})
|
|
69
192
|
|
|
70
193
|
const attrs = useAttrs()
|
|
71
194
|
const wrapperClassObject = computed(() => {
|
|
@@ -87,6 +210,53 @@ const wrapperClassObject = computed(() => {
|
|
|
87
210
|
return classes
|
|
88
211
|
})
|
|
89
212
|
|
|
213
|
+
const ensureFilterSelection = () => {
|
|
214
|
+
const options = radioFilterOptions.value
|
|
215
|
+
if (!options.length) {
|
|
216
|
+
if (selectedFilterValue.value !== '') selectedFilterValue.value = ''
|
|
217
|
+
return
|
|
218
|
+
}
|
|
219
|
+
const exists = options.some(option => option.value === selectedFilterValue.value)
|
|
220
|
+
if (exists) return
|
|
221
|
+
const fallback = radioFilterConfig.value?.defaultValue ?? options[0]?.value ?? ''
|
|
222
|
+
selectedFilterValue.value = fallback
|
|
223
|
+
}
|
|
224
|
+
const ensureListSelection = () => {
|
|
225
|
+
const options = sideListOptions.value
|
|
226
|
+
if (!options.length) {
|
|
227
|
+
if (selectedSideListValue.value !== '') selectedSideListValue.value = ''
|
|
228
|
+
return
|
|
229
|
+
}
|
|
230
|
+
const exists = options.some(option => option.value === selectedSideListValue.value)
|
|
231
|
+
if (exists) return
|
|
232
|
+
const fallback = sideListConfig.value?.defaultValue ?? options[0]?.value ?? ''
|
|
233
|
+
selectedSideListValue.value = fallback
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
watch([radioFilterOptions, () => radioFilterConfig.value?.defaultValue], () => {
|
|
237
|
+
ensureFilterSelection()
|
|
238
|
+
}, { immediate: true })
|
|
239
|
+
watch([sideListOptions, () => sideListConfig.value?.defaultValue], () => {
|
|
240
|
+
ensureListSelection()
|
|
241
|
+
}, { immediate: true })
|
|
242
|
+
|
|
243
|
+
const handleFilterChange = (value) => {
|
|
244
|
+
if (value === selectedFilterValue.value) return
|
|
245
|
+
selectedFilterValue.value = value
|
|
246
|
+
const config = chartConfig.value || props.config
|
|
247
|
+
if (config) {
|
|
248
|
+
loadChartData(config)
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
const handleListChange = (value) => {
|
|
252
|
+
if (value === selectedSideListValue.value) return
|
|
253
|
+
selectedSideListValue.value = value
|
|
254
|
+
const config = chartConfig.value || props.config
|
|
255
|
+
if (config) {
|
|
256
|
+
loadChartData(config)
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
90
260
|
// 常用图表预设,统一处理 dataset → ECharts option 的映射
|
|
91
261
|
const presetResolvers = {
|
|
92
262
|
bar: ({ dataset }) => ({
|
|
@@ -119,47 +289,123 @@ const presetResolvers = {
|
|
|
119
289
|
}
|
|
120
290
|
]
|
|
121
291
|
}),
|
|
122
|
-
line: ({ dataset }) =>
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
axisPointer: {
|
|
129
|
-
type: 'line'
|
|
292
|
+
line: ({ dataset = [], config = {}, state = {} }) => {
|
|
293
|
+
if (!dataset.length) {
|
|
294
|
+
return {
|
|
295
|
+
xAxis: { type: 'category', data: [] },
|
|
296
|
+
yAxis: { type: 'value' },
|
|
297
|
+
series: [{ type: 'line', data: [] }]
|
|
130
298
|
}
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
const highlightColor = config.highlightColor || '#1F5BFF'
|
|
302
|
+
const lineColor = config.lineColor || '#1F66FF'
|
|
303
|
+
const areaStart = config.areaStartColor || 'rgba(31, 91, 255, 0.25)'
|
|
304
|
+
const areaEnd = config.areaEndColor || 'rgba(31, 91, 255, 0.02)'
|
|
305
|
+
const valueSuffix = config.valueSuffix || ''
|
|
306
|
+
const isSmoothLine = typeof config.smooth === 'boolean' ? config.smooth : false
|
|
307
|
+
const baseSymbolSize = Number(config.symbolSize) || 12
|
|
308
|
+
const activeSymbolSize = Number(config.activeSymbolSize) || baseSymbolSize + 4
|
|
309
|
+
const inactivePointFill = config.inactivePointFill || '#fff'
|
|
310
|
+
const pointBorderWidth = Number(config.pointBorderWidth) || 4
|
|
311
|
+
const activeIndex = typeof state.activePointIndex === 'number' ? state.activePointIndex : -1
|
|
312
|
+
|
|
313
|
+
const seriesData = dataset.map((item, idx) => ({
|
|
314
|
+
value: item.value,
|
|
315
|
+
label: item.label,
|
|
316
|
+
symbol: 'circle',
|
|
317
|
+
symbolSize: idx === activeIndex ? activeSymbolSize : baseSymbolSize,
|
|
318
|
+
itemStyle: {
|
|
319
|
+
color: idx === activeIndex ? highlightColor : inactivePointFill,
|
|
320
|
+
borderColor: idx === activeIndex ? highlightColor : lineColor,
|
|
321
|
+
borderWidth: pointBorderWidth
|
|
322
|
+
}
|
|
323
|
+
}))
|
|
324
|
+
|
|
325
|
+
return {
|
|
326
|
+
animationDuration: 420,
|
|
327
|
+
animationEasing: 'cubicOut',
|
|
328
|
+
tooltip: {
|
|
329
|
+
trigger: 'axis',
|
|
330
|
+
backgroundColor: '#1F5BFF',
|
|
331
|
+
borderWidth: 0,
|
|
332
|
+
textStyle: { color: '#fff' },
|
|
333
|
+
axisPointer: {
|
|
334
|
+
type: 'line',
|
|
152
335
|
lineStyle: {
|
|
153
|
-
|
|
336
|
+
color: highlightColor,
|
|
337
|
+
width: 2
|
|
154
338
|
}
|
|
155
339
|
},
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
340
|
+
formatter: (params = []) => {
|
|
341
|
+
if (!params.length) return ''
|
|
342
|
+
const point = params[0]
|
|
343
|
+
return `${point.axisValue}<br/>${point.data.value}${valueSuffix}`
|
|
159
344
|
}
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
|
|
345
|
+
},
|
|
346
|
+
grid: {
|
|
347
|
+
top: 40,
|
|
348
|
+
left: 60,
|
|
349
|
+
right: 40,
|
|
350
|
+
bottom: 40,
|
|
351
|
+
containLabel: true
|
|
352
|
+
},
|
|
353
|
+
xAxis: {
|
|
354
|
+
type: 'category',
|
|
355
|
+
boundaryGap: false,
|
|
356
|
+
data: dataset.map(item => item.label),
|
|
357
|
+
axisLine: { lineStyle: { color: '#C8D2E8' } },
|
|
358
|
+
axisLabel: { color: '#5C6C8C', fontWeight: 500 },
|
|
359
|
+
axisTick: { show: false },
|
|
360
|
+
splitLine: { show: false }
|
|
361
|
+
},
|
|
362
|
+
yAxis: {
|
|
363
|
+
type: 'value',
|
|
364
|
+
minInterval: 1,
|
|
365
|
+
axisLine: { show: false },
|
|
366
|
+
axisTick: { show: false },
|
|
367
|
+
axisLabel: { color: '#5C6C8C', fontWeight: 500 },
|
|
368
|
+
splitLine: {
|
|
369
|
+
show: true,
|
|
370
|
+
lineStyle: { type: 'dashed', color: '#E0E6F1' }
|
|
371
|
+
}
|
|
372
|
+
},
|
|
373
|
+
series: [
|
|
374
|
+
{
|
|
375
|
+
type: 'line',
|
|
376
|
+
smooth: isSmoothLine,
|
|
377
|
+
showSymbol: true,
|
|
378
|
+
symbol: 'circle',
|
|
379
|
+
data: seriesData.map((point, idx) => ({
|
|
380
|
+
value: point.value,
|
|
381
|
+
symbolSize: point.symbolSize,
|
|
382
|
+
itemStyle: point.itemStyle,
|
|
383
|
+
label: {
|
|
384
|
+
show: true,
|
|
385
|
+
fontWeight: idx === activeIndex ? 600 : 500,
|
|
386
|
+
color: idx === activeIndex ? highlightColor : lineColor,
|
|
387
|
+
formatter: `${point.value}${valueSuffix}`,
|
|
388
|
+
position: 'top'
|
|
389
|
+
}
|
|
390
|
+
})),
|
|
391
|
+
lineStyle: {
|
|
392
|
+
width: 3,
|
|
393
|
+
color: lineColor
|
|
394
|
+
},
|
|
395
|
+
emphasis: {
|
|
396
|
+
focus: 'series',
|
|
397
|
+
scale: true
|
|
398
|
+
},
|
|
399
|
+
areaStyle: {
|
|
400
|
+
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
|
401
|
+
{ offset: 0, color: areaStart },
|
|
402
|
+
{ offset: 1, color: areaEnd }
|
|
403
|
+
])
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
]
|
|
407
|
+
}
|
|
408
|
+
},
|
|
163
409
|
pie: ({ dataset }) => ({
|
|
164
410
|
animationDuration: 420,
|
|
165
411
|
animationEasing: 'cubicOut',
|
|
@@ -171,6 +417,7 @@ const presetResolvers = {
|
|
|
171
417
|
{
|
|
172
418
|
type: 'pie',
|
|
173
419
|
radius: '55%',
|
|
420
|
+
startAngle: 180,
|
|
174
421
|
data: dataset.map(item => ({ name: item.label, value: item.value })),
|
|
175
422
|
itemStyle: {
|
|
176
423
|
borderColor: '#fff',
|
|
@@ -193,6 +440,70 @@ const presetResolvers = {
|
|
|
193
440
|
})
|
|
194
441
|
}
|
|
195
442
|
|
|
443
|
+
// 颜色工具函数:将十六进制颜色转换为 RGB
|
|
444
|
+
const hexToRgb = (hex) => {
|
|
445
|
+
// 移除 # 号
|
|
446
|
+
const cleanHex = hex.replace('#', '')
|
|
447
|
+
// 处理 3 位和 6 位十六进制
|
|
448
|
+
const fullHex = cleanHex.length === 3
|
|
449
|
+
? cleanHex.split('').map(char => char + char).join('')
|
|
450
|
+
: cleanHex
|
|
451
|
+
const r = parseInt(fullHex.substring(0, 2), 16)
|
|
452
|
+
const g = parseInt(fullHex.substring(2, 4), 16)
|
|
453
|
+
const b = parseInt(fullHex.substring(4, 6), 16)
|
|
454
|
+
return { r, g, b }
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
// 颜色工具函数:将 RGB 转换为十六进制颜色
|
|
458
|
+
const rgbToHex = (r, g, b) => {
|
|
459
|
+
const toHex = (n) => {
|
|
460
|
+
const hex = Math.round(n).toString(16)
|
|
461
|
+
return hex.length === 1 ? '0' + hex : hex
|
|
462
|
+
}
|
|
463
|
+
return `#${toHex(r)}${toHex(g)}${toHex(b)}`
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
// 生成颜色渐变数组:基于单个颜色生成变淡的颜色数组
|
|
467
|
+
const generateColorGradient = (baseColor, count, fadeRatio = 0.15) => {
|
|
468
|
+
if (!baseColor) return []
|
|
469
|
+
if (count <= 0) return []
|
|
470
|
+
if (count === 1) {
|
|
471
|
+
return Array.isArray(baseColor) ? baseColor : [baseColor]
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
// 如果已经是数组且长度大于1,直接返回
|
|
475
|
+
if (Array.isArray(baseColor) && baseColor.length > 1) {
|
|
476
|
+
return baseColor
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
// 获取基础颜色(如果是数组,取第一个)
|
|
480
|
+
const color = Array.isArray(baseColor) ? baseColor[0] : baseColor
|
|
481
|
+
|
|
482
|
+
// 转换为 RGB
|
|
483
|
+
const baseRgb = hexToRgb(color)
|
|
484
|
+
const white = { r: 255, g: 255, b: 255 }
|
|
485
|
+
|
|
486
|
+
// 生成渐变数组
|
|
487
|
+
const colors = []
|
|
488
|
+
for (let i = 0; i < count; i++) {
|
|
489
|
+
// 第一个颜色保持原色不变,后续逐渐向白色混合
|
|
490
|
+
if (i === 0) {
|
|
491
|
+
colors.push(color)
|
|
492
|
+
continue
|
|
493
|
+
}
|
|
494
|
+
// 计算混合比例:fadeRatio 控制每次变淡的比例(0-1之间,值越大变淡越快)
|
|
495
|
+
const ratio = i * fadeRatio
|
|
496
|
+
const mixRatio = Math.min(ratio, 0.9) // 最多混合到90%,避免完全变白
|
|
497
|
+
const r = baseRgb.r + (white.r - baseRgb.r) * mixRatio
|
|
498
|
+
const g = baseRgb.g + (white.g - baseRgb.g) * mixRatio
|
|
499
|
+
const b = baseRgb.b + (white.b - baseRgb.b) * mixRatio
|
|
500
|
+
|
|
501
|
+
colors.push(rgbToHex(r, g, b))
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
return colors
|
|
505
|
+
}
|
|
506
|
+
|
|
196
507
|
// 数据转换:将后端返回的数据转换为图表需要的 dataset 格式
|
|
197
508
|
const transformData = (rawData, dataMapping) => {
|
|
198
509
|
// 增强数据提取:支持从包装对象中提取数组
|
|
@@ -252,7 +563,6 @@ const fetchConfigAndData = async () => {
|
|
|
252
563
|
// 如果直接提供了 config,直接使用
|
|
253
564
|
if (props.config) {
|
|
254
565
|
chartConfig.value = props.config
|
|
255
|
-
console.log('44444444444444444444444444444444')
|
|
256
566
|
await loadChartData(props.config)
|
|
257
567
|
return
|
|
258
568
|
}
|
|
@@ -266,7 +576,6 @@ const fetchConfigAndData = async () => {
|
|
|
266
576
|
}
|
|
267
577
|
|
|
268
578
|
chartConfig.value = res
|
|
269
|
-
console.log('3333333333333333333333333')
|
|
270
579
|
await loadChartData(res)
|
|
271
580
|
})
|
|
272
581
|
} catch (error) {
|
|
@@ -280,20 +589,24 @@ const fetchConfigAndData = async () => {
|
|
|
280
589
|
// 加载图表数据
|
|
281
590
|
const loadChartData = async (config) => {
|
|
282
591
|
if (!config) return
|
|
592
|
+
ensureListSelection()
|
|
593
|
+
ensureFilterSelection()
|
|
283
594
|
|
|
284
595
|
try {
|
|
285
|
-
// 如果配置中直接提供了 dataset
|
|
286
|
-
|
|
287
|
-
|
|
596
|
+
// 如果配置中直接提供了 dataset(数组或映射),直接使用
|
|
597
|
+
const staticDataset = resolveStaticDataset(config)
|
|
598
|
+
if (staticDataset) {
|
|
599
|
+
chartData.value = staticDataset
|
|
600
|
+
activePointIndex.value = -1
|
|
288
601
|
renderChart()
|
|
289
|
-
console.log('1111111111111111111111111')
|
|
290
602
|
emit('dataLoaded', chartData.value)
|
|
291
603
|
return
|
|
292
604
|
}
|
|
293
605
|
|
|
294
606
|
// 如果配置中提供了 data(logicName),通过 runLogic 获取数据
|
|
295
607
|
if (config.data) {
|
|
296
|
-
const
|
|
608
|
+
const queryPayload = buildQueryPayload()
|
|
609
|
+
const result = await runLogic(config.data, queryPayload, props.serviceName)
|
|
297
610
|
|
|
298
611
|
// 转换数据格式
|
|
299
612
|
const transformedData = transformData(result, config.dataMapping)
|
|
@@ -301,12 +614,13 @@ const loadChartData = async (config) => {
|
|
|
301
614
|
console.warn('HChart: 数据转换后为空数组,原始数据:', result)
|
|
302
615
|
}
|
|
303
616
|
chartData.value = transformedData
|
|
617
|
+
activePointIndex.value = -1
|
|
304
618
|
renderChart()
|
|
305
|
-
console.log('22222222222222222222222222')
|
|
306
619
|
emit('dataLoaded', transformedData)
|
|
307
620
|
} else {
|
|
308
621
|
// 没有数据源,使用空数据
|
|
309
622
|
chartData.value = []
|
|
623
|
+
activePointIndex.value = -1
|
|
310
624
|
renderChart()
|
|
311
625
|
}
|
|
312
626
|
} catch (error) {
|
|
@@ -316,6 +630,17 @@ const loadChartData = async (config) => {
|
|
|
316
630
|
}
|
|
317
631
|
|
|
318
632
|
// 渲染图表
|
|
633
|
+
const handleChartClick = (params) => {
|
|
634
|
+
const config = chartConfig.value || props.config
|
|
635
|
+
if (config?.type !== 'line') return
|
|
636
|
+
if (params?.componentType !== 'series') return
|
|
637
|
+
if (params.seriesType !== 'line') return
|
|
638
|
+
if (typeof params.dataIndex !== 'number') return
|
|
639
|
+
if (activePointIndex.value === params.dataIndex) return
|
|
640
|
+
activePointIndex.value = params.dataIndex
|
|
641
|
+
renderChart()
|
|
642
|
+
}
|
|
643
|
+
|
|
319
644
|
const renderChart = () => {
|
|
320
645
|
if (!chartRef.value) return
|
|
321
646
|
nextTick(() => {
|
|
@@ -326,6 +651,7 @@ const renderChart = () => {
|
|
|
326
651
|
chartInstance?.resize()
|
|
327
652
|
})
|
|
328
653
|
resizeObserver.observe(chartRef.value)
|
|
654
|
+
chartInstance.on('click', handleChartClick)
|
|
329
655
|
}
|
|
330
656
|
|
|
331
657
|
const config = chartConfig.value || props.config
|
|
@@ -337,7 +663,21 @@ const renderChart = () => {
|
|
|
337
663
|
return
|
|
338
664
|
}
|
|
339
665
|
|
|
340
|
-
const preset = resolver({
|
|
666
|
+
const preset = resolver({
|
|
667
|
+
dataset: chartData.value,
|
|
668
|
+
config,
|
|
669
|
+
state: { activePointIndex: activePointIndex.value }
|
|
670
|
+
})
|
|
671
|
+
|
|
672
|
+
// 处理颜色配置:如果只有一个颜色,生成渐变数组
|
|
673
|
+
let processedColor = config.options?.color
|
|
674
|
+
if (processedColor) {
|
|
675
|
+
const dataCount = chartData.value.length || 12 // 默认生成12个颜色
|
|
676
|
+
// 如果只有一个颜色(字符串或长度为1的数组),生成渐变
|
|
677
|
+
if (typeof processedColor === 'string' || (Array.isArray(processedColor) && processedColor.length === 1)) {
|
|
678
|
+
processedColor = generateColorGradient(processedColor, dataCount)
|
|
679
|
+
}
|
|
680
|
+
}
|
|
341
681
|
// 深度合并 series 配置
|
|
342
682
|
let finalOptions = {
|
|
343
683
|
legend: config.legend || {},
|
|
@@ -348,11 +688,13 @@ const renderChart = () => {
|
|
|
348
688
|
// 如果 options 中有 series,进行深度合并
|
|
349
689
|
if (config.options?.series && preset.series) {
|
|
350
690
|
// 分离需要特殊处理的配置项
|
|
351
|
-
const { xAxis: customXAxis, yAxis: customYAxis, series: customSeries, ...restOptions } = config.options
|
|
691
|
+
const { xAxis: customXAxis, yAxis: customYAxis, series: customSeries, color: _, ...restOptions } = config.options
|
|
352
692
|
|
|
353
693
|
finalOptions = {
|
|
354
694
|
...finalOptions,
|
|
355
695
|
...restOptions,
|
|
696
|
+
// 应用处理后的颜色
|
|
697
|
+
...(processedColor && { color: processedColor }),
|
|
356
698
|
// 对于有 xAxis 的图表类型(bar, line),智能合并 xAxis
|
|
357
699
|
...(preset.xAxis && {
|
|
358
700
|
xAxis: {
|
|
@@ -382,11 +724,13 @@ const renderChart = () => {
|
|
|
382
724
|
}
|
|
383
725
|
} else {
|
|
384
726
|
// 没有自定义 series 的情况
|
|
385
|
-
const { xAxis: customXAxis, yAxis: customYAxis, ...restOptions } = config.options || {}
|
|
727
|
+
const { xAxis: customXAxis, yAxis: customYAxis, color: _, ...restOptions } = config.options || {}
|
|
386
728
|
|
|
387
729
|
finalOptions = {
|
|
388
730
|
...finalOptions,
|
|
389
731
|
...restOptions,
|
|
732
|
+
// 应用处理后的颜色
|
|
733
|
+
...(processedColor && { color: processedColor }),
|
|
390
734
|
// 对于有 xAxis 的图表类型,智能合并
|
|
391
735
|
...(preset.xAxis && {
|
|
392
736
|
xAxis: {
|
|
@@ -416,7 +760,6 @@ watch(
|
|
|
416
760
|
() => props.queryParamsName,
|
|
417
761
|
(newName, oldName) => {
|
|
418
762
|
if (newName !== oldName) {
|
|
419
|
-
console.log('99999999999999999999999999')
|
|
420
763
|
fetchConfigAndData()
|
|
421
764
|
}
|
|
422
765
|
}
|
|
@@ -424,7 +767,6 @@ watch(
|
|
|
424
767
|
|
|
425
768
|
// 暴露给外部的方法
|
|
426
769
|
const refresh = () => {
|
|
427
|
-
console.log('88888888888888888888888888')
|
|
428
770
|
fetchConfigAndData()
|
|
429
771
|
}
|
|
430
772
|
|
|
@@ -432,7 +774,6 @@ const reload = (newQueryForm) => {
|
|
|
432
774
|
if (newQueryForm) {
|
|
433
775
|
emit('update:fixedQueryForm', { ...props.fixedQueryForm, ...newQueryForm })
|
|
434
776
|
} else {
|
|
435
|
-
console.log('7777777777777777777777777')
|
|
436
777
|
fetchConfigAndData()
|
|
437
778
|
}
|
|
438
779
|
}
|
|
@@ -446,13 +787,13 @@ defineExpose({
|
|
|
446
787
|
onMounted(() => {
|
|
447
788
|
// 初始化时记录 queryParamsName
|
|
448
789
|
lastQueryParamsName.value = props.queryParamsName
|
|
449
|
-
console.log('66666666666666666666666666666')
|
|
450
790
|
fetchConfigAndData()
|
|
451
791
|
})
|
|
452
792
|
|
|
453
793
|
onBeforeUnmount(() => {
|
|
454
794
|
// 清理图表实例和观察器
|
|
455
795
|
if (chartInstance) {
|
|
796
|
+
chartInstance.off('click', handleChartClick)
|
|
456
797
|
chartInstance.dispose()
|
|
457
798
|
chartInstance = null
|
|
458
799
|
}
|
|
@@ -485,6 +826,112 @@ onBeforeUnmount(() => {
|
|
|
485
826
|
box-shadow: 0 12px 28px rgba(0, 0, 0, 0.12);
|
|
486
827
|
}
|
|
487
828
|
|
|
829
|
+
.chart-layout {
|
|
830
|
+
display: flex;
|
|
831
|
+
gap: 16px;
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
.chart-side-list {
|
|
835
|
+
width: 180px;
|
|
836
|
+
flex-shrink: 0;
|
|
837
|
+
display: flex;
|
|
838
|
+
flex-direction: column;
|
|
839
|
+
border-right: 1px solid #ECF0F7;
|
|
840
|
+
padding-right: 12px;
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
.side-list-label {
|
|
844
|
+
font-size: 14px;
|
|
845
|
+
color: #4A5875;
|
|
846
|
+
font-weight: 600;
|
|
847
|
+
margin-bottom: 8px;
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
.side-list-menu {
|
|
851
|
+
display: flex;
|
|
852
|
+
flex-direction: column;
|
|
853
|
+
gap: 6px;
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
.side-list-item {
|
|
857
|
+
width: 100%;
|
|
858
|
+
text-align: left;
|
|
859
|
+
border: 0;
|
|
860
|
+
background: transparent;
|
|
861
|
+
padding: 8px 10px;
|
|
862
|
+
border-radius: 8px;
|
|
863
|
+
font-size: 14px;
|
|
864
|
+
color: #4A5875;
|
|
865
|
+
cursor: pointer;
|
|
866
|
+
transition: all 0.15s ease;
|
|
867
|
+
display: flex;
|
|
868
|
+
justify-content: space-between;
|
|
869
|
+
align-items: center;
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
.side-list-item .item-label {
|
|
873
|
+
flex: 1;
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
.side-list-item .item-badge {
|
|
877
|
+
font-size: 12px;
|
|
878
|
+
color: #9AA7C5;
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
.side-list-item.is-active {
|
|
882
|
+
background: rgba(31, 91, 255, 0.08);
|
|
883
|
+
color: #1F5BFF;
|
|
884
|
+
font-weight: 600;
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
.chart-main {
|
|
888
|
+
flex: 1;
|
|
889
|
+
display: flex;
|
|
890
|
+
flex-direction: column;
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
.chart-toolbar {
|
|
894
|
+
display: flex;
|
|
895
|
+
align-items: center;
|
|
896
|
+
gap: 12px;
|
|
897
|
+
margin-top: 4px;
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
.toolbar-label {
|
|
901
|
+
font-size: 14px;
|
|
902
|
+
color: #5C6C8C;
|
|
903
|
+
font-weight: 500;
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
.toolbar-radio-group {
|
|
907
|
+
display: inline-flex;
|
|
908
|
+
gap: 8px;
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
.radio-pill {
|
|
912
|
+
display: inline-flex;
|
|
913
|
+
align-items: center;
|
|
914
|
+
padding: 4px 14px;
|
|
915
|
+
border-radius: 16px;
|
|
916
|
+
border: 1px solid #D5DFF2;
|
|
917
|
+
font-size: 13px;
|
|
918
|
+
color: #5C6C8C;
|
|
919
|
+
cursor: pointer;
|
|
920
|
+
transition: all 0.18s ease;
|
|
921
|
+
user-select: none;
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
.radio-pill-input {
|
|
925
|
+
display: none;
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
.radio-pill.is-active {
|
|
929
|
+
background-color: rgba(31, 91, 255, 0.08);
|
|
930
|
+
border-color: #1F5BFF;
|
|
931
|
+
color: #1F5BFF;
|
|
932
|
+
font-weight: 600;
|
|
933
|
+
}
|
|
934
|
+
|
|
488
935
|
.chart-canvas {
|
|
489
936
|
width: 100%;
|
|
490
937
|
margin-top: 10px;
|