@vela-studio/ui 1.0.1

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 (68) hide show
  1. package/README.md +152 -0
  2. package/dist/index.d.ts +696 -0
  3. package/dist/index.js +10 -0
  4. package/dist/index.js.map +1 -0
  5. package/dist/index.mjs +11786 -0
  6. package/dist/index.mjs.map +1 -0
  7. package/dist/index.umd.js +10 -0
  8. package/dist/index.umd.js.map +1 -0
  9. package/dist/style.css +1 -0
  10. package/index.ts +150 -0
  11. package/package.json +73 -0
  12. package/src/components/advanced/scripting/Scripting.vue +189 -0
  13. package/src/components/advanced/state/State.vue +231 -0
  14. package/src/components/advanced/trigger/Trigger.vue +256 -0
  15. package/src/components/basic/button/Button.vue +120 -0
  16. package/src/components/basic/container/Container.vue +22 -0
  17. package/src/components/chart/barChart/barChart.vue +176 -0
  18. package/src/components/chart/doughnutChart/doughnutChart.vue +128 -0
  19. package/src/components/chart/funnelChart/funnelChart.vue +128 -0
  20. package/src/components/chart/gaugeChart/gaugeChart.vue +144 -0
  21. package/src/components/chart/lineChart/lineChart.vue +188 -0
  22. package/src/components/chart/pieChart/pieChart.vue +114 -0
  23. package/src/components/chart/radarChart/radarChart.vue +115 -0
  24. package/src/components/chart/sankeyChart/sankeyChart.vue +144 -0
  25. package/src/components/chart/scatterChart/scatterChart.vue +162 -0
  26. package/src/components/chart/stackedBarChart/stackedBarChart.vue +184 -0
  27. package/src/components/content/html/Html.vue +104 -0
  28. package/src/components/content/iframe/Iframe.vue +111 -0
  29. package/src/components/content/markdown/Markdown.vue +174 -0
  30. package/src/components/controls/breadcrumb/Breadcrumb.vue +79 -0
  31. package/src/components/controls/buttonGroup/ButtonGroup.vue +93 -0
  32. package/src/components/controls/checkboxGroup/CheckboxGroup.vue +147 -0
  33. package/src/components/controls/dateRange/DateRange.vue +174 -0
  34. package/src/components/controls/multiSelect/MultiSelect.vue +155 -0
  35. package/src/components/controls/navButton/NavButton.vue +97 -0
  36. package/src/components/controls/pagination/Pagination.vue +94 -0
  37. package/src/components/controls/searchBox/SearchBox.vue +170 -0
  38. package/src/components/controls/select/Select.vue +134 -0
  39. package/src/components/controls/slider/Slider.vue +167 -0
  40. package/src/components/controls/switch/Switch.vue +107 -0
  41. package/src/components/data/cardGrid/CardGrid.vue +318 -0
  42. package/src/components/data/list/List.vue +282 -0
  43. package/src/components/data/pivot/Pivot.vue +270 -0
  44. package/src/components/data/table/Table.vue +150 -0
  45. package/src/components/data/timeline/Timeline.vue +315 -0
  46. package/src/components/group/Group.vue +75 -0
  47. package/src/components/kpi/box/Box.vue +98 -0
  48. package/src/components/kpi/countUp/CountUp.vue +193 -0
  49. package/src/components/kpi/progress/Progress.vue +159 -0
  50. package/src/components/kpi/stat/Stat.vue +205 -0
  51. package/src/components/kpi/text/Text.vue +74 -0
  52. package/src/components/layout/badge/Badge.vue +105 -0
  53. package/src/components/layout/col/Col.vue +114 -0
  54. package/src/components/layout/flex/Flex.vue +105 -0
  55. package/src/components/layout/grid/Grid.vue +89 -0
  56. package/src/components/layout/modal/Modal.vue +118 -0
  57. package/src/components/layout/panel/Panel.vue +162 -0
  58. package/src/components/layout/row/Row.vue +99 -0
  59. package/src/components/layout/tabs/Tabs.vue +117 -0
  60. package/src/components/media/image/Image.vue +132 -0
  61. package/src/components/media/video/Video.vue +115 -0
  62. package/src/components/v2/basic/BaseButton.vue +179 -0
  63. package/src/components/v2/kpi/KpiCard.vue +215 -0
  64. package/src/components/v2/layout/GridBox.vue +55 -0
  65. package/src/hooks/useDataSource.ts +123 -0
  66. package/src/types/gis.ts +251 -0
  67. package/src/utils/chartUtils.ts +349 -0
  68. package/src/utils/dataUtils.ts +403 -0
@@ -0,0 +1,403 @@
1
+ /**
2
+ * 数据提取工具函数(通用)
3
+ * 用于所有组件的数据源、路径解析、数据格式化等
4
+ * 图表、KPI、Text 等组件都使用这些工具函数提取数据
5
+ */
6
+
7
+ /**
8
+ * 从对象中根据路径提取值
9
+ * 支持点号路径和数组索引,如: "data.chart.values" 或 "items[0].name"
10
+ *
11
+ * @param obj - 源数据对象
12
+ * @param path - 提取路径,可选(为空则返回 undefined)
13
+ * @returns 提取的值,或 undefined
14
+ */
15
+ export function getValueByPath(obj: unknown, path: string | undefined): unknown {
16
+ if (!path || !obj) return undefined
17
+ try {
18
+ const keys = path.replace(/\[(\d+)\]/g, '.$1').split('.')
19
+ let result: unknown = obj
20
+ for (const key of keys) {
21
+ if (result === null || result === undefined) return undefined
22
+ result = (result as Record<string, unknown>)[key]
23
+ }
24
+ return result
25
+ } catch {
26
+ return undefined
27
+ }
28
+ }
29
+
30
+ /**
31
+ * 设置对象路径上的值
32
+ * 支持点号路径和数组索引,如: "data.chart.values" 或 "items[0].name"
33
+ * 用于数据联动(DataBinding)等需要动态写入路径的场景
34
+ *
35
+ * @param obj - 目标对象
36
+ * @param path - 设置路径
37
+ * @param value - 要设置的值
38
+ * @returns 是否设置成功
39
+ */
40
+ export function setValueByPath(obj: unknown, path: string, value: unknown): boolean {
41
+ if (!obj || typeof obj !== 'object') return false
42
+ if (!path) return false
43
+
44
+ try {
45
+ // 将路径分割为 tokens(支持数组索引和对象属性)
46
+ const tokens: Array<string | number> = []
47
+ const normalized = path
48
+ .replace(/\[(\d+)\]/g, '.$1')
49
+ .split('.')
50
+ .map((s) => s.trim())
51
+ .filter(Boolean)
52
+
53
+ for (const seg of normalized) {
54
+ if (/^\d+$/.test(seg)) tokens.push(Number(seg))
55
+ else tokens.push(seg)
56
+ }
57
+
58
+ if (tokens.length === 0) return false
59
+
60
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
61
+ let cur: any = obj
62
+
63
+ // 遍历路径创建中间容器
64
+ for (let i = 0; i < tokens.length - 1; i++) {
65
+ const key = tokens[i] as string | number
66
+ const nextKey = tokens[i + 1]
67
+
68
+ if (cur[key] == null || typeof cur[key] !== 'object') {
69
+ // 下一个键是数字则创建数组,否则创建对象
70
+ cur[key] = typeof nextKey === 'number' ? [] : {}
71
+ }
72
+ cur = cur[key]
73
+ }
74
+
75
+ // 设置最后一个键的值
76
+ const lastKey = tokens[tokens.length - 1] as string | number
77
+ cur[lastKey] = value
78
+ return true
79
+ } catch {
80
+ return false
81
+ }
82
+ }
83
+
84
+ /**
85
+ * 从数据源提取单个数值
86
+ * 用于 KPI 组件(countUp, progress, badge 等)
87
+ *
88
+ * @param remoteData - 远程数据对象
89
+ * @param valuePath - 数值路径,可选
90
+ * @param defaultValue - 默认值
91
+ * @returns 提取的数值
92
+ */
93
+ export function extractNumber(
94
+ remoteData: unknown,
95
+ valuePath: string | undefined,
96
+ defaultValue: number = 0,
97
+ ): number {
98
+ if (!valuePath) return defaultValue
99
+
100
+ const value = getValueByPath(remoteData, valuePath)
101
+ if (typeof value === 'number') return value
102
+ if (value !== undefined && value !== null) {
103
+ const parsed = parseFloat(String(value))
104
+ return isNaN(parsed) ? defaultValue : parsed
105
+ }
106
+
107
+ return defaultValue
108
+ }
109
+
110
+ /**
111
+ * 解析逗号分隔的数字输入
112
+ * 支持 JSON 数组格式和逗号分隔格式
113
+ * @param input - 输入字符串,如 "150, 230, 224" 或 "[150, 230, 224]"
114
+ * @param defaultValue - 默认值数组
115
+ */
116
+ export function parseNumberInput(input: string | undefined, defaultValue: number[] = []): number[] {
117
+ if (!input) return defaultValue
118
+
119
+ // 尝试 JSON 解析
120
+ try {
121
+ const parsed = JSON.parse(input)
122
+ if (Array.isArray(parsed)) {
123
+ return parsed
124
+ .map((v) => (typeof v === 'number' ? v : parseFloat(String(v))))
125
+ .filter((v) => !isNaN(v))
126
+ }
127
+ } catch {
128
+ // JSON 解析失败,尝试逗号分隔
129
+ }
130
+
131
+ // 逗号分隔解析
132
+ return input
133
+ .split(',')
134
+ .map((v) => parseFloat(v.trim()))
135
+ .filter((v) => !isNaN(v))
136
+ }
137
+
138
+ /**
139
+ * 解析逗号分隔的字符串输入
140
+ * 支持 JSON 数组格式和逗号分隔格式
141
+ * @param input - 输入字符串,如 "Mon, Tue, Wed" 或 '["Mon", "Tue", "Wed"]'
142
+ * @param defaultValue - 默认值数组
143
+ */
144
+ export function parseStringInput(input: string | undefined, defaultValue: string[] = []): string[] {
145
+ if (!input) return defaultValue
146
+
147
+ // 尝试 JSON 解析
148
+ try {
149
+ const parsed = JSON.parse(input)
150
+ if (Array.isArray(parsed)) {
151
+ return parsed.map((v) => String(v))
152
+ }
153
+ } catch {
154
+ // JSON 解析失败,尝试逗号分隔
155
+ }
156
+
157
+ // 逗号分隔解析
158
+ return input
159
+ .split(',')
160
+ .map((v) => v.trim())
161
+ .filter((v) => v.length > 0)
162
+ }
163
+
164
+ /**
165
+ * 解析二维数组输入(用于散点图等)
166
+ * @param input - 输入字符串,如 "[[10, 8], [8, 7]]"
167
+ * @param defaultValue - 默认值数组
168
+ */
169
+ export function parse2DArrayInput(
170
+ input: string | undefined,
171
+ defaultValue: Array<[number, number]> = [],
172
+ ): Array<[number, number]> {
173
+ if (!input) return defaultValue
174
+
175
+ try {
176
+ const parsed = JSON.parse(input)
177
+ if (Array.isArray(parsed) && parsed.length > 0 && Array.isArray(parsed[0])) {
178
+ return parsed.map((item) => {
179
+ if (Array.isArray(item) && item.length >= 2) {
180
+ return [
181
+ typeof item[0] === 'number' ? item[0] : parseFloat(String(item[0])),
182
+ typeof item[1] === 'number' ? item[1] : parseFloat(String(item[1])),
183
+ ] as [number, number]
184
+ }
185
+ return [0, 0] as [number, number]
186
+ })
187
+ }
188
+ } catch {
189
+ // JSON 解析失败
190
+ }
191
+
192
+ return defaultValue
193
+ }
194
+
195
+ /**
196
+ * 从数据源提取数字数组
197
+ * @param remoteData - 远程数据对象
198
+ * @param dataPath - 数据路径
199
+ */
200
+ export function extractNumberArray(
201
+ remoteData: unknown,
202
+ dataPath: string | undefined,
203
+ ): number[] | undefined {
204
+ if (!dataPath) return undefined
205
+
206
+ const extractedData = getValueByPath(remoteData, dataPath)
207
+ if (Array.isArray(extractedData)) {
208
+ return extractedData.map((v) => (typeof v === 'number' ? v : parseFloat(String(v))))
209
+ }
210
+
211
+ return undefined
212
+ }
213
+
214
+ /**
215
+ * 从数据源提取字符串数组
216
+ * @param remoteData - 远程数据对象
217
+ * @param dataPath - 数据路径
218
+ */
219
+ export function extractStringArray(
220
+ remoteData: unknown,
221
+ dataPath: string | undefined,
222
+ ): string[] | undefined {
223
+ if (!dataPath) return undefined
224
+
225
+ const extractedData = getValueByPath(remoteData, dataPath)
226
+ if (Array.isArray(extractedData)) {
227
+ return extractedData.map((v) => String(v))
228
+ }
229
+
230
+ return undefined
231
+ }
232
+
233
+ /**
234
+ * 从数据源提取二维数组(用于散点图等)
235
+ * @param remoteData - 远程数据对象
236
+ * @param dataPath - 数据路径
237
+ */
238
+ export function extract2DArray(
239
+ remoteData: unknown,
240
+ dataPath: string | undefined,
241
+ ): Array<[number, number]> | undefined {
242
+ if (!dataPath) return undefined
243
+
244
+ const extractedData = getValueByPath(remoteData, dataPath)
245
+ if (Array.isArray(extractedData) && extractedData.length > 0 && Array.isArray(extractedData[0])) {
246
+ return extractedData.map((item) => {
247
+ if (Array.isArray(item) && item.length >= 2) {
248
+ return [
249
+ typeof item[0] === 'number' ? item[0] : parseFloat(String(item[0])),
250
+ typeof item[1] === 'number' ? item[1] : parseFloat(String(item[1])),
251
+ ] as [number, number]
252
+ }
253
+ return [0, 0] as [number, number]
254
+ })
255
+ }
256
+
257
+ return undefined
258
+ }
259
+
260
+ /**
261
+ * 从数据源提取字符串值
262
+ * @param remoteData - 远程数据对象
263
+ * @param dataPath - 数据路径
264
+ */
265
+ export function extractString(
266
+ remoteData: unknown,
267
+ dataPath: string | undefined,
268
+ ): string | undefined {
269
+ if (!dataPath) return undefined
270
+
271
+ const extractedData = getValueByPath(remoteData, dataPath)
272
+ return extractedData ? String(extractedData) : undefined
273
+ }
274
+
275
+ /**
276
+ * 解析 JSON 数组输入(通用)
277
+ * @param input - JSON 字符串输入
278
+ * @param defaultValue - 默认值
279
+ */
280
+ export function parseJSONInput<T = unknown>(input: string | undefined, defaultValue: T): T {
281
+ if (!input) return defaultValue
282
+
283
+ try {
284
+ const parsed = JSON.parse(input)
285
+ return parsed as T
286
+ } catch (e) {
287
+ console.error('Failed to parse JSON input:', e)
288
+ return defaultValue
289
+ }
290
+ }
291
+
292
+ /**
293
+ * 提取并规范化 Sankey 图节点数据
294
+ * @param remoteData - 远程数据对象
295
+ * @param dataPath - 数据路径
296
+ */
297
+ export function extractSankeyNodes(
298
+ remoteData: unknown,
299
+ dataPath: string | undefined,
300
+ ): Array<{ name: string; value?: number; depth?: number; itemStyle?: unknown }> | undefined {
301
+ if (!dataPath) return undefined
302
+
303
+ const nodesData = getValueByPath(remoteData, dataPath)
304
+ if (!Array.isArray(nodesData) || nodesData.length === 0) return undefined
305
+
306
+ return nodesData.map((node: unknown) => {
307
+ const obj =
308
+ typeof node === 'object' && node !== null ? (node as Record<string, unknown>) : undefined
309
+ const name = typeof node === 'string' ? node : String(obj?.name ?? obj?.id ?? '')
310
+
311
+ return {
312
+ name,
313
+ value:
314
+ obj && typeof obj.value === 'number'
315
+ ? (obj.value as number)
316
+ : obj && obj.value != null
317
+ ? parseFloat(String(obj.value))
318
+ : undefined,
319
+ depth:
320
+ obj && typeof obj.depth === 'number'
321
+ ? (obj.depth as number)
322
+ : obj && obj.depth != null
323
+ ? parseFloat(String(obj.depth))
324
+ : undefined,
325
+ itemStyle: obj ? obj.itemStyle : undefined,
326
+ }
327
+ })
328
+ }
329
+
330
+ /**
331
+ * 提取并规范化 Sankey 图连接数据
332
+ * @param remoteData - 远程数据对象
333
+ * @param dataPath - 数据路径
334
+ */
335
+ export function extractSankeyLinks(
336
+ remoteData: unknown,
337
+ dataPath: string | undefined,
338
+ ): Array<{ source: string; target: string; value: number }> | undefined {
339
+ if (!dataPath) return undefined
340
+
341
+ const linksData = getValueByPath(remoteData, dataPath)
342
+ if (!Array.isArray(linksData) || linksData.length === 0) return undefined
343
+
344
+ return linksData.map((link: unknown) => {
345
+ const l = link as Record<string, unknown>
346
+ return {
347
+ source: (l.source || l.from || '') as string,
348
+ target: (l.target || l.to || '') as string,
349
+ value: (l.value || 0) as number,
350
+ }
351
+ })
352
+ }
353
+
354
+ /**
355
+ * KPI 组件专用:提取多个字段的数据
356
+ * 用于 stat 组件等需要同时提取多个字段的场景
357
+ *
358
+ * @param remoteData - 远程数据对象
359
+ * @param paths - 字段路径映射 { title: 'data.title', value: 'data.value', ... }
360
+ * @returns 提取的数据对象
361
+ *
362
+ * @example
363
+ * const data = extractMultipleFields(remoteData, {
364
+ * title: 'data.kpi.title',
365
+ * value: 'data.kpi.value',
366
+ * change: 'data.kpi.change'
367
+ * })
368
+ * // 返回: { title: '销售额', value: 12345, change: 5.2 }
369
+ */
370
+ export function extractMultipleFields<T extends Record<string, string | undefined>>(
371
+ remoteData: unknown,
372
+ paths: T,
373
+ ): Record<keyof T, unknown> {
374
+ const result: Record<string, unknown> = {}
375
+
376
+ for (const [key, path] of Object.entries(paths)) {
377
+ if (path && typeof path === 'string') {
378
+ result[key] = getValueByPath(remoteData, path)
379
+ }
380
+ }
381
+
382
+ return result as Record<keyof T, unknown>
383
+ }
384
+
385
+ /**
386
+ * 智能提取数据:优先使用路径提取,否则使用默认值
387
+ * 用于所有简单组件(text, countUp, progress, badge 等)
388
+ *
389
+ * @param remoteData - 远程数据对象
390
+ * @param path - 数据路径
391
+ * @param fallbackValue - 回退值(当路径未配置或提取失败时使用)
392
+ * @returns 提取的数据或回退值
393
+ */
394
+ export function extractWithFallback<T>(
395
+ remoteData: unknown,
396
+ path: string | undefined,
397
+ fallbackValue: T,
398
+ ): T {
399
+ if (!path) return fallbackValue
400
+
401
+ const extracted = getValueByPath(remoteData, path)
402
+ return (extracted ?? fallbackValue) as T
403
+ }