vue2-client 1.18.6 → 1.18.8
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/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/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/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/XFormTable/XFormTable.vue +0 -1
- 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/CustomFuncCel.vue +6 -1
- package/src/base-client/components/common/XTable/index.md +255 -255
- package/src/base-client/components/his/HChart/HChart.vue +504 -53
- 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/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//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,124 @@ 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
|
+
label: point.label,
|
|
382
|
+
symbolSize: point.symbolSize,
|
|
383
|
+
itemStyle: point.itemStyle,
|
|
384
|
+
label: {
|
|
385
|
+
show: true,
|
|
386
|
+
fontWeight: idx === activeIndex ? 600 : 500,
|
|
387
|
+
color: idx === activeIndex ? highlightColor : lineColor,
|
|
388
|
+
formatter: `${point.value}${valueSuffix}`,
|
|
389
|
+
position: 'top'
|
|
390
|
+
}
|
|
391
|
+
})),
|
|
392
|
+
lineStyle: {
|
|
393
|
+
width: 3,
|
|
394
|
+
color: lineColor
|
|
395
|
+
},
|
|
396
|
+
emphasis: {
|
|
397
|
+
focus: 'series',
|
|
398
|
+
scale: true
|
|
399
|
+
},
|
|
400
|
+
areaStyle: {
|
|
401
|
+
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
|
402
|
+
{ offset: 0, color: areaStart },
|
|
403
|
+
{ offset: 1, color: areaEnd }
|
|
404
|
+
])
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
]
|
|
408
|
+
}
|
|
409
|
+
},
|
|
163
410
|
pie: ({ dataset }) => ({
|
|
164
411
|
animationDuration: 420,
|
|
165
412
|
animationEasing: 'cubicOut',
|
|
@@ -171,6 +418,7 @@ const presetResolvers = {
|
|
|
171
418
|
{
|
|
172
419
|
type: 'pie',
|
|
173
420
|
radius: '55%',
|
|
421
|
+
startAngle: 180,
|
|
174
422
|
data: dataset.map(item => ({ name: item.label, value: item.value })),
|
|
175
423
|
itemStyle: {
|
|
176
424
|
borderColor: '#fff',
|
|
@@ -193,6 +441,72 @@ const presetResolvers = {
|
|
|
193
441
|
})
|
|
194
442
|
}
|
|
195
443
|
|
|
444
|
+
// 颜色工具函数:将十六进制颜色转换为 RGB
|
|
445
|
+
const hexToRgb = (hex) => {
|
|
446
|
+
// 移除 # 号
|
|
447
|
+
const cleanHex = hex.replace('#', '')
|
|
448
|
+
// 处理 3 位和 6 位十六进制
|
|
449
|
+
const fullHex = cleanHex.length === 3
|
|
450
|
+
? cleanHex.split('').map(char => char + char).join('')
|
|
451
|
+
: cleanHex
|
|
452
|
+
const r = parseInt(fullHex.substring(0, 2), 16)
|
|
453
|
+
const g = parseInt(fullHex.substring(2, 4), 16)
|
|
454
|
+
const b = parseInt(fullHex.substring(4, 6), 16)
|
|
455
|
+
return { r, g, b }
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
// 颜色工具函数:将 RGB 转换为十六进制颜色
|
|
459
|
+
const rgbToHex = (r, g, b) => {
|
|
460
|
+
const toHex = (n) => {
|
|
461
|
+
const hex = Math.round(n).toString(16)
|
|
462
|
+
return hex.length === 1 ? '0' + hex : hex
|
|
463
|
+
}
|
|
464
|
+
return `#${toHex(r)}${toHex(g)}${toHex(b)}`
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
// 生成颜色渐变数组:基于单个颜色生成变淡的颜色数组
|
|
468
|
+
const generateColorGradient = (baseColor, count, fadeRatio = 0.15) => {
|
|
469
|
+
if (!baseColor) return []
|
|
470
|
+
if (count <= 0) return []
|
|
471
|
+
if (count === 1) {
|
|
472
|
+
return Array.isArray(baseColor) ? baseColor : [baseColor]
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
// 如果已经是数组且长度大于1,直接返回
|
|
476
|
+
if (Array.isArray(baseColor) && baseColor.length > 1) {
|
|
477
|
+
return baseColor
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
// 获取基础颜色(如果是数组,取第一个)
|
|
481
|
+
const color = Array.isArray(baseColor) ? baseColor[0] : baseColor
|
|
482
|
+
|
|
483
|
+
// 转换为 RGB
|
|
484
|
+
const baseRgb = hexToRgb(color)
|
|
485
|
+
const white = { r: 255, g: 255, b: 255 }
|
|
486
|
+
|
|
487
|
+
// 生成渐变数组
|
|
488
|
+
const colors = []
|
|
489
|
+
for (let i = 0; i < count; i++) {
|
|
490
|
+
// 第一个颜色保持原色不变,后续逐渐向白色混合
|
|
491
|
+
if (i === 0) {
|
|
492
|
+
colors.push(color)
|
|
493
|
+
continue
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
// 计算混合比例:fadeRatio 控制每次变淡的比例(0-1之间,值越大变淡越快)
|
|
497
|
+
const ratio = i * fadeRatio
|
|
498
|
+
const mixRatio = Math.min(ratio, 0.9) // 最多混合到90%,避免完全变白
|
|
499
|
+
|
|
500
|
+
const r = baseRgb.r + (white.r - baseRgb.r) * mixRatio
|
|
501
|
+
const g = baseRgb.g + (white.g - baseRgb.g) * mixRatio
|
|
502
|
+
const b = baseRgb.b + (white.b - baseRgb.b) * mixRatio
|
|
503
|
+
|
|
504
|
+
colors.push(rgbToHex(r, g, b))
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
return colors
|
|
508
|
+
}
|
|
509
|
+
|
|
196
510
|
// 数据转换:将后端返回的数据转换为图表需要的 dataset 格式
|
|
197
511
|
const transformData = (rawData, dataMapping) => {
|
|
198
512
|
// 增强数据提取:支持从包装对象中提取数组
|
|
@@ -252,7 +566,6 @@ const fetchConfigAndData = async () => {
|
|
|
252
566
|
// 如果直接提供了 config,直接使用
|
|
253
567
|
if (props.config) {
|
|
254
568
|
chartConfig.value = props.config
|
|
255
|
-
console.log('44444444444444444444444444444444')
|
|
256
569
|
await loadChartData(props.config)
|
|
257
570
|
return
|
|
258
571
|
}
|
|
@@ -266,7 +579,6 @@ const fetchConfigAndData = async () => {
|
|
|
266
579
|
}
|
|
267
580
|
|
|
268
581
|
chartConfig.value = res
|
|
269
|
-
console.log('3333333333333333333333333')
|
|
270
582
|
await loadChartData(res)
|
|
271
583
|
})
|
|
272
584
|
} catch (error) {
|
|
@@ -280,20 +592,24 @@ const fetchConfigAndData = async () => {
|
|
|
280
592
|
// 加载图表数据
|
|
281
593
|
const loadChartData = async (config) => {
|
|
282
594
|
if (!config) return
|
|
595
|
+
ensureListSelection()
|
|
596
|
+
ensureFilterSelection()
|
|
283
597
|
|
|
284
598
|
try {
|
|
285
|
-
// 如果配置中直接提供了 dataset
|
|
286
|
-
|
|
287
|
-
|
|
599
|
+
// 如果配置中直接提供了 dataset(数组或映射),直接使用
|
|
600
|
+
const staticDataset = resolveStaticDataset(config)
|
|
601
|
+
if (staticDataset) {
|
|
602
|
+
chartData.value = staticDataset
|
|
603
|
+
activePointIndex.value = -1
|
|
288
604
|
renderChart()
|
|
289
|
-
console.log('1111111111111111111111111')
|
|
290
605
|
emit('dataLoaded', chartData.value)
|
|
291
606
|
return
|
|
292
607
|
}
|
|
293
608
|
|
|
294
609
|
// 如果配置中提供了 data(logicName),通过 runLogic 获取数据
|
|
295
610
|
if (config.data) {
|
|
296
|
-
const
|
|
611
|
+
const queryPayload = buildQueryPayload()
|
|
612
|
+
const result = await runLogic(config.data, queryPayload, props.serviceName)
|
|
297
613
|
|
|
298
614
|
// 转换数据格式
|
|
299
615
|
const transformedData = transformData(result, config.dataMapping)
|
|
@@ -301,12 +617,13 @@ const loadChartData = async (config) => {
|
|
|
301
617
|
console.warn('HChart: 数据转换后为空数组,原始数据:', result)
|
|
302
618
|
}
|
|
303
619
|
chartData.value = transformedData
|
|
620
|
+
activePointIndex.value = -1
|
|
304
621
|
renderChart()
|
|
305
|
-
console.log('22222222222222222222222222')
|
|
306
622
|
emit('dataLoaded', transformedData)
|
|
307
623
|
} else {
|
|
308
624
|
// 没有数据源,使用空数据
|
|
309
625
|
chartData.value = []
|
|
626
|
+
activePointIndex.value = -1
|
|
310
627
|
renderChart()
|
|
311
628
|
}
|
|
312
629
|
} catch (error) {
|
|
@@ -316,6 +633,17 @@ const loadChartData = async (config) => {
|
|
|
316
633
|
}
|
|
317
634
|
|
|
318
635
|
// 渲染图表
|
|
636
|
+
const handleChartClick = (params) => {
|
|
637
|
+
const config = chartConfig.value || props.config
|
|
638
|
+
if (config?.type !== 'line') return
|
|
639
|
+
if (params?.componentType !== 'series') return
|
|
640
|
+
if (params.seriesType !== 'line') return
|
|
641
|
+
if (typeof params.dataIndex !== 'number') return
|
|
642
|
+
if (activePointIndex.value === params.dataIndex) return
|
|
643
|
+
activePointIndex.value = params.dataIndex
|
|
644
|
+
renderChart()
|
|
645
|
+
}
|
|
646
|
+
|
|
319
647
|
const renderChart = () => {
|
|
320
648
|
if (!chartRef.value) return
|
|
321
649
|
nextTick(() => {
|
|
@@ -326,6 +654,7 @@ const renderChart = () => {
|
|
|
326
654
|
chartInstance?.resize()
|
|
327
655
|
})
|
|
328
656
|
resizeObserver.observe(chartRef.value)
|
|
657
|
+
chartInstance.on('click', handleChartClick)
|
|
329
658
|
}
|
|
330
659
|
|
|
331
660
|
const config = chartConfig.value || props.config
|
|
@@ -337,7 +666,22 @@ const renderChart = () => {
|
|
|
337
666
|
return
|
|
338
667
|
}
|
|
339
668
|
|
|
340
|
-
const preset = resolver({
|
|
669
|
+
const preset = resolver({
|
|
670
|
+
dataset: chartData.value,
|
|
671
|
+
config,
|
|
672
|
+
state: { activePointIndex: activePointIndex.value }
|
|
673
|
+
})
|
|
674
|
+
|
|
675
|
+
// 处理颜色配置:如果只有一个颜色,生成渐变数组
|
|
676
|
+
let processedColor = config.options?.color
|
|
677
|
+
if (processedColor) {
|
|
678
|
+
const dataCount = chartData.value.length || 12 // 默认生成12个颜色
|
|
679
|
+
// 如果只有一个颜色(字符串或长度为1的数组),生成渐变
|
|
680
|
+
if (typeof processedColor === 'string' || (Array.isArray(processedColor) && processedColor.length === 1)) {
|
|
681
|
+
processedColor = generateColorGradient(processedColor, dataCount)
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
|
|
341
685
|
// 深度合并 series 配置
|
|
342
686
|
let finalOptions = {
|
|
343
687
|
legend: config.legend || {},
|
|
@@ -348,11 +692,13 @@ const renderChart = () => {
|
|
|
348
692
|
// 如果 options 中有 series,进行深度合并
|
|
349
693
|
if (config.options?.series && preset.series) {
|
|
350
694
|
// 分离需要特殊处理的配置项
|
|
351
|
-
const { xAxis: customXAxis, yAxis: customYAxis, series: customSeries, ...restOptions } = config.options
|
|
695
|
+
const { xAxis: customXAxis, yAxis: customYAxis, series: customSeries, color: _, ...restOptions } = config.options
|
|
352
696
|
|
|
353
697
|
finalOptions = {
|
|
354
698
|
...finalOptions,
|
|
355
699
|
...restOptions,
|
|
700
|
+
// 应用处理后的颜色
|
|
701
|
+
...(processedColor && { color: processedColor }),
|
|
356
702
|
// 对于有 xAxis 的图表类型(bar, line),智能合并 xAxis
|
|
357
703
|
...(preset.xAxis && {
|
|
358
704
|
xAxis: {
|
|
@@ -382,11 +728,13 @@ const renderChart = () => {
|
|
|
382
728
|
}
|
|
383
729
|
} else {
|
|
384
730
|
// 没有自定义 series 的情况
|
|
385
|
-
const { xAxis: customXAxis, yAxis: customYAxis, ...restOptions } = config.options || {}
|
|
731
|
+
const { xAxis: customXAxis, yAxis: customYAxis, color: _, ...restOptions } = config.options || {}
|
|
386
732
|
|
|
387
733
|
finalOptions = {
|
|
388
734
|
...finalOptions,
|
|
389
735
|
...restOptions,
|
|
736
|
+
// 应用处理后的颜色
|
|
737
|
+
...(processedColor && { color: processedColor }),
|
|
390
738
|
// 对于有 xAxis 的图表类型,智能合并
|
|
391
739
|
...(preset.xAxis && {
|
|
392
740
|
xAxis: {
|
|
@@ -416,7 +764,6 @@ watch(
|
|
|
416
764
|
() => props.queryParamsName,
|
|
417
765
|
(newName, oldName) => {
|
|
418
766
|
if (newName !== oldName) {
|
|
419
|
-
console.log('99999999999999999999999999')
|
|
420
767
|
fetchConfigAndData()
|
|
421
768
|
}
|
|
422
769
|
}
|
|
@@ -424,7 +771,6 @@ watch(
|
|
|
424
771
|
|
|
425
772
|
// 暴露给外部的方法
|
|
426
773
|
const refresh = () => {
|
|
427
|
-
console.log('88888888888888888888888888')
|
|
428
774
|
fetchConfigAndData()
|
|
429
775
|
}
|
|
430
776
|
|
|
@@ -432,7 +778,6 @@ const reload = (newQueryForm) => {
|
|
|
432
778
|
if (newQueryForm) {
|
|
433
779
|
emit('update:fixedQueryForm', { ...props.fixedQueryForm, ...newQueryForm })
|
|
434
780
|
} else {
|
|
435
|
-
console.log('7777777777777777777777777')
|
|
436
781
|
fetchConfigAndData()
|
|
437
782
|
}
|
|
438
783
|
}
|
|
@@ -446,13 +791,13 @@ defineExpose({
|
|
|
446
791
|
onMounted(() => {
|
|
447
792
|
// 初始化时记录 queryParamsName
|
|
448
793
|
lastQueryParamsName.value = props.queryParamsName
|
|
449
|
-
console.log('66666666666666666666666666666')
|
|
450
794
|
fetchConfigAndData()
|
|
451
795
|
})
|
|
452
796
|
|
|
453
797
|
onBeforeUnmount(() => {
|
|
454
798
|
// 清理图表实例和观察器
|
|
455
799
|
if (chartInstance) {
|
|
800
|
+
chartInstance.off('click', handleChartClick)
|
|
456
801
|
chartInstance.dispose()
|
|
457
802
|
chartInstance = null
|
|
458
803
|
}
|
|
@@ -485,6 +830,112 @@ onBeforeUnmount(() => {
|
|
|
485
830
|
box-shadow: 0 12px 28px rgba(0, 0, 0, 0.12);
|
|
486
831
|
}
|
|
487
832
|
|
|
833
|
+
.chart-layout {
|
|
834
|
+
display: flex;
|
|
835
|
+
gap: 16px;
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
.chart-side-list {
|
|
839
|
+
width: 180px;
|
|
840
|
+
flex-shrink: 0;
|
|
841
|
+
display: flex;
|
|
842
|
+
flex-direction: column;
|
|
843
|
+
border-right: 1px solid #ECF0F7;
|
|
844
|
+
padding-right: 12px;
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
.side-list-label {
|
|
848
|
+
font-size: 14px;
|
|
849
|
+
color: #4A5875;
|
|
850
|
+
font-weight: 600;
|
|
851
|
+
margin-bottom: 8px;
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
.side-list-menu {
|
|
855
|
+
display: flex;
|
|
856
|
+
flex-direction: column;
|
|
857
|
+
gap: 6px;
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
.side-list-item {
|
|
861
|
+
width: 100%;
|
|
862
|
+
text-align: left;
|
|
863
|
+
border: 0;
|
|
864
|
+
background: transparent;
|
|
865
|
+
padding: 8px 10px;
|
|
866
|
+
border-radius: 8px;
|
|
867
|
+
font-size: 14px;
|
|
868
|
+
color: #4A5875;
|
|
869
|
+
cursor: pointer;
|
|
870
|
+
transition: all 0.15s ease;
|
|
871
|
+
display: flex;
|
|
872
|
+
justify-content: space-between;
|
|
873
|
+
align-items: center;
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
.side-list-item .item-label {
|
|
877
|
+
flex: 1;
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
.side-list-item .item-badge {
|
|
881
|
+
font-size: 12px;
|
|
882
|
+
color: #9AA7C5;
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
.side-list-item.is-active {
|
|
886
|
+
background: rgba(31, 91, 255, 0.08);
|
|
887
|
+
color: #1F5BFF;
|
|
888
|
+
font-weight: 600;
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
.chart-main {
|
|
892
|
+
flex: 1;
|
|
893
|
+
display: flex;
|
|
894
|
+
flex-direction: column;
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
.chart-toolbar {
|
|
898
|
+
display: flex;
|
|
899
|
+
align-items: center;
|
|
900
|
+
gap: 12px;
|
|
901
|
+
margin-top: 4px;
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
.toolbar-label {
|
|
905
|
+
font-size: 14px;
|
|
906
|
+
color: #5C6C8C;
|
|
907
|
+
font-weight: 500;
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
.toolbar-radio-group {
|
|
911
|
+
display: inline-flex;
|
|
912
|
+
gap: 8px;
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
.radio-pill {
|
|
916
|
+
display: inline-flex;
|
|
917
|
+
align-items: center;
|
|
918
|
+
padding: 4px 14px;
|
|
919
|
+
border-radius: 16px;
|
|
920
|
+
border: 1px solid #D5DFF2;
|
|
921
|
+
font-size: 13px;
|
|
922
|
+
color: #5C6C8C;
|
|
923
|
+
cursor: pointer;
|
|
924
|
+
transition: all 0.18s ease;
|
|
925
|
+
user-select: none;
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
.radio-pill-input {
|
|
929
|
+
display: none;
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
.radio-pill.is-active {
|
|
933
|
+
background-color: rgba(31, 91, 255, 0.08);
|
|
934
|
+
border-color: #1F5BFF;
|
|
935
|
+
color: #1F5BFF;
|
|
936
|
+
font-weight: 600;
|
|
937
|
+
}
|
|
938
|
+
|
|
488
939
|
.chart-canvas {
|
|
489
940
|
width: 100%;
|
|
490
941
|
margin-top: 10px;
|