@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,315 @@
1
+ <script setup lang="ts">
2
+ import { computed } from 'vue'
3
+ import type { CSSProperties } from 'vue'
4
+ import { Clock } from '@element-plus/icons-vue'
5
+ import { ElScrollbar, ElEmpty, ElTimeline, ElTimelineItem, ElCard } from 'element-plus'
6
+
7
+ export interface TimelineItem {
8
+ title?: string
9
+ content?: string
10
+ timestamp?: string
11
+ type?: 'primary' | 'success' | 'warning' | 'danger' | 'info'
12
+ color?: string
13
+ extra?: string
14
+ icon?: unknown
15
+ }
16
+
17
+ const props = withDefaults(
18
+ defineProps<{
19
+ // 数据
20
+ data?: TimelineItem[]
21
+ // 配置
22
+ showCard?: boolean
23
+ showTitle?: boolean
24
+ showTimestamp?: boolean
25
+ showExtra?: boolean
26
+ timestampPlacement?: 'top' | 'bottom'
27
+ itemSize?: 'normal' | 'large'
28
+ hollow?: boolean
29
+ cardShadow?: 'always' | 'hover' | 'never'
30
+ emptyText?: string
31
+ scrollHeight?: string
32
+ // 样式
33
+ backgroundColor?: string
34
+ borderRadius?: number
35
+ padding?: number
36
+ opacity?: number
37
+ visible?: boolean
38
+ // 时间轴样式
39
+ timelinePadding?: number
40
+ // 卡片样式
41
+ cardMargin?: number
42
+ cardBorderRadius?: number
43
+ // 标题样式
44
+ headerFontSize?: number
45
+ headerFontWeight?: string
46
+ headerColor?: string
47
+ titleFontSize?: number
48
+ titleFontWeight?: string
49
+ titleColor?: string
50
+ // 内容样式
51
+ contentFontSize?: number
52
+ contentColor?: string
53
+ textFontSize?: number
54
+ textColor?: string
55
+ // 额外信息样式
56
+ extraFontSize?: number
57
+ extraColor?: string
58
+ }>(),
59
+ {
60
+ data: () => [],
61
+ showCard: true,
62
+ showTitle: true,
63
+ showTimestamp: true,
64
+ showExtra: false,
65
+ timestampPlacement: 'top',
66
+ itemSize: 'normal',
67
+ hollow: false,
68
+ cardShadow: 'hover',
69
+ emptyText: '暂无数据',
70
+ scrollHeight: '100%',
71
+ backgroundColor: '#ffffff',
72
+ borderRadius: 4,
73
+ padding: 16,
74
+ opacity: 100,
75
+ visible: true,
76
+ timelinePadding: 0,
77
+ cardMargin: 12,
78
+ cardBorderRadius: 4,
79
+ headerFontSize: 15,
80
+ headerFontWeight: '600',
81
+ headerColor: '#303133',
82
+ titleFontSize: 15,
83
+ titleFontWeight: '600',
84
+ titleColor: '#303133',
85
+ contentFontSize: 14,
86
+ contentColor: '#606266',
87
+ textFontSize: 14,
88
+ textColor: '#606266',
89
+ extraFontSize: 12,
90
+ extraColor: '#909399',
91
+ },
92
+ )
93
+
94
+ // 默认示例数据
95
+ const defaultData: TimelineItem[] = [
96
+ {
97
+ title: '活动开始',
98
+ content: '这是活动的开始阶段',
99
+ timestamp: '2024-01-01 10:00',
100
+ type: 'primary',
101
+ },
102
+ { title: '进行中', content: '活动正在进行中', timestamp: '2024-01-02 14:30', type: 'success' },
103
+ { title: '活动结束', content: '活动已经顺利结束', timestamp: '2024-01-03 18:00', type: 'info' },
104
+ ]
105
+
106
+ const timelineData = computed(() => (props.data.length > 0 ? props.data : defaultData))
107
+
108
+ function getTitle(item: TimelineItem): string {
109
+ return item.title ?? ''
110
+ }
111
+
112
+ function getContent(item: TimelineItem): string {
113
+ return item.content ?? ''
114
+ }
115
+
116
+ function getTimestamp(item: TimelineItem): string {
117
+ return item.timestamp ?? ''
118
+ }
119
+
120
+ function getExtra(item: TimelineItem): string {
121
+ return item.extra ?? ''
122
+ }
123
+
124
+ function getItemType(
125
+ item: TimelineItem,
126
+ index: number,
127
+ ): 'primary' | 'success' | 'warning' | 'danger' | 'info' {
128
+ if (item.type && ['primary', 'success', 'warning', 'danger', 'info'].includes(item.type)) {
129
+ return item.type
130
+ }
131
+ const types: ('primary' | 'success' | 'warning' | 'danger' | 'info')[] = [
132
+ 'primary',
133
+ 'success',
134
+ 'info',
135
+ 'warning',
136
+ 'danger',
137
+ ]
138
+ return types[index % types.length]
139
+ }
140
+
141
+ function getItemColor(item: TimelineItem): string | undefined {
142
+ return item.color
143
+ }
144
+
145
+ function getItemIcon(item: TimelineItem) {
146
+ return item.icon || Clock
147
+ }
148
+
149
+ // 样式
150
+ const containerStyle = computed<CSSProperties>(() => ({
151
+ opacity: props.opacity / 100,
152
+ display: props.visible === false ? 'none' : 'block',
153
+ width: '100%',
154
+ height: '100%',
155
+ backgroundColor: props.backgroundColor,
156
+ borderRadius: `${props.borderRadius}px`,
157
+ padding: `${props.padding}px`,
158
+ overflow: 'hidden',
159
+ boxSizing: 'border-box',
160
+ }))
161
+
162
+ const timelineStyle = computed<CSSProperties>(() => ({
163
+ paddingLeft: `${props.timelinePadding}px`,
164
+ }))
165
+
166
+ const cardStyle = computed<CSSProperties>(() => ({
167
+ marginBottom: `${props.cardMargin}px`,
168
+ borderRadius: `${props.cardBorderRadius}px`,
169
+ }))
170
+
171
+ const headerStyle = computed<CSSProperties>(() => ({
172
+ display: 'flex',
173
+ justifyContent: 'space-between',
174
+ alignItems: 'center',
175
+ fontSize: `${props.headerFontSize}px`,
176
+ fontWeight: props.headerFontWeight,
177
+ color: props.headerColor,
178
+ }))
179
+
180
+ const contentStyle = computed<CSSProperties>(() => ({
181
+ fontSize: `${props.contentFontSize}px`,
182
+ color: props.contentColor,
183
+ lineHeight: 1.6,
184
+ }))
185
+
186
+ const extraStyle = computed<CSSProperties>(() => ({
187
+ marginTop: '8px',
188
+ fontSize: `${props.extraFontSize}px`,
189
+ color: props.extraColor,
190
+ }))
191
+
192
+ const titleStyle = computed<CSSProperties>(() => ({
193
+ fontSize: `${props.titleFontSize}px`,
194
+ fontWeight: props.titleFontWeight,
195
+ color: props.titleColor,
196
+ marginBottom: '4px',
197
+ }))
198
+
199
+ const textStyle = computed<CSSProperties>(() => ({
200
+ fontSize: `${props.textFontSize}px`,
201
+ color: props.textColor,
202
+ lineHeight: 1.6,
203
+ }))
204
+
205
+ const extraTextStyle = computed<CSSProperties>(() => ({
206
+ marginTop: '6px',
207
+ fontSize: `${props.extraFontSize}px`,
208
+ color: props.extraColor,
209
+ }))
210
+ </script>
211
+
212
+ <template>
213
+ <div class="timeline-container" :style="containerStyle">
214
+ <el-scrollbar :height="scrollHeight">
215
+ <div v-if="timelineData.length === 0" class="empty-state">
216
+ <el-empty :description="emptyText" :image-size="80" />
217
+ </div>
218
+ <el-timeline v-else :style="timelineStyle">
219
+ <el-timeline-item
220
+ v-for="(item, index) in timelineData"
221
+ :key="index"
222
+ :timestamp="getTimestamp(item)"
223
+ :placement="timestampPlacement"
224
+ :type="getItemType(item, index)"
225
+ :color="getItemColor(item)"
226
+ :size="itemSize"
227
+ :hollow="hollow"
228
+ :icon="getItemIcon(item)"
229
+ >
230
+ <el-card v-if="showCard" :shadow="cardShadow" :style="cardStyle">
231
+ <template #header>
232
+ <div class="card-header" :style="headerStyle">
233
+ <span class="card-title">{{ getTitle(item) }}</span>
234
+ <span v-if="showTimestamp" class="card-time">{{ getTimestamp(item) }}</span>
235
+ </div>
236
+ </template>
237
+ <div class="card-content" :style="contentStyle">
238
+ {{ getContent(item) }}
239
+ </div>
240
+ <div v-if="showExtra && getExtra(item)" class="card-extra" :style="extraStyle">
241
+ {{ getExtra(item) }}
242
+ </div>
243
+ </el-card>
244
+
245
+ <div v-else class="timeline-content">
246
+ <div v-if="showTitle" class="content-title" :style="titleStyle">
247
+ {{ getTitle(item) }}
248
+ </div>
249
+ <div class="content-text" :style="textStyle">
250
+ {{ getContent(item) }}
251
+ </div>
252
+ <div v-if="showExtra && getExtra(item)" class="content-extra" :style="extraTextStyle">
253
+ {{ getExtra(item) }}
254
+ </div>
255
+ </div>
256
+ </el-timeline-item>
257
+ </el-timeline>
258
+ </el-scrollbar>
259
+ </div>
260
+ </template>
261
+
262
+ <style scoped>
263
+ .timeline-container {
264
+ box-sizing: border-box;
265
+ }
266
+
267
+ .empty-state {
268
+ display: flex;
269
+ align-items: center;
270
+ justify-content: center;
271
+ min-height: 200px;
272
+ }
273
+
274
+ .card-header {
275
+ display: flex;
276
+ justify-content: space-between;
277
+ align-items: center;
278
+ }
279
+
280
+ .card-title {
281
+ flex: 1;
282
+ }
283
+
284
+ .card-time {
285
+ font-size: 13px;
286
+ color: #909399;
287
+ font-weight: normal;
288
+ margin-left: 12px;
289
+ }
290
+
291
+ .card-content {
292
+ margin: 0;
293
+ }
294
+
295
+ .card-extra {
296
+ padding-top: 8px;
297
+ border-top: 1px solid #ebeef5;
298
+ }
299
+
300
+ .timeline-content {
301
+ padding: 8px 0;
302
+ }
303
+
304
+ .content-title {
305
+ margin-bottom: 4px;
306
+ }
307
+
308
+ .content-text {
309
+ margin: 0;
310
+ }
311
+
312
+ .content-extra {
313
+ padding-top: 6px;
314
+ }
315
+ </style>
@@ -0,0 +1,75 @@
1
+ <template>
2
+ <div class="group-container" :style="containerStyle">
3
+ <slot>
4
+ <div v-if="showPlaceholder" class="group-placeholder">
5
+ {{ placeholder }}
6
+ </div>
7
+ </slot>
8
+ </div>
9
+ </template>
10
+
11
+ <script setup lang="ts">
12
+ import { computed, type CSSProperties } from 'vue'
13
+
14
+ export interface GroupProps {
15
+ /** 容器透明度 0-1 */
16
+ opacity?: number
17
+ /** 是否可见 */
18
+ visible?: boolean
19
+ /** 旋转角度 */
20
+ rotation?: number
21
+ /** 边框圆角 */
22
+ borderRadius?: number
23
+ /** 背景色 */
24
+ backgroundColor?: string
25
+ /** 边框样式 */
26
+ border?: string
27
+ /** 是否显示占位符 */
28
+ showPlaceholder?: boolean
29
+ /** 占位符文本 */
30
+ placeholder?: string
31
+ }
32
+
33
+ const props = withDefaults(defineProps<GroupProps>(), {
34
+ opacity: 1,
35
+ visible: true,
36
+ rotation: 0,
37
+ borderRadius: 0,
38
+ backgroundColor: 'transparent',
39
+ border: 'none',
40
+ showPlaceholder: true,
41
+ placeholder: '组合',
42
+ })
43
+
44
+ const containerStyle = computed<CSSProperties>(() => ({
45
+ position: 'relative',
46
+ width: '100%',
47
+ height: '100%',
48
+ boxSizing: 'border-box',
49
+ opacity: props.opacity,
50
+ display: props.visible ? 'block' : 'none',
51
+ transform: props.rotation ? `rotate(${props.rotation}deg)` : undefined,
52
+ borderRadius: props.borderRadius ? `${props.borderRadius}px` : undefined,
53
+ backgroundColor: props.backgroundColor,
54
+ border: props.border,
55
+ }))
56
+ </script>
57
+
58
+ <style scoped>
59
+ .group-container {
60
+ position: relative;
61
+ width: 100%;
62
+ height: 100%;
63
+ }
64
+
65
+ .group-placeholder {
66
+ position: absolute;
67
+ top: 50%;
68
+ left: 50%;
69
+ transform: translate(-50%, -50%);
70
+ color: #909399;
71
+ font-size: 12px;
72
+ opacity: 0.5;
73
+ pointer-events: none;
74
+ }
75
+ </style>
@@ -0,0 +1,98 @@
1
+ <template>
2
+ <div class="v-box-container" :style="containerStyle">
3
+ <div class="v-box-content" :style="contentStyle">
4
+ {{ content }}
5
+ </div>
6
+ </div>
7
+ </template>
8
+
9
+ <script setup lang="ts">
10
+ import { computed } from 'vue'
11
+ import type { CSSProperties } from 'vue'
12
+
13
+ const props = withDefaults(
14
+ defineProps<{
15
+ // 内容
16
+ content?: string
17
+
18
+ // 容器样式
19
+ opacity?: number
20
+ visible?: boolean
21
+ backgroundColor?: string
22
+ borderColor?: string
23
+ borderWidth?: number
24
+ borderStyle?: 'solid' | 'dashed' | 'dotted' | 'none'
25
+ borderRadius?: number
26
+ padding?: number
27
+ boxShadow?: string
28
+
29
+ // 文字样式
30
+ fontSize?: number
31
+ textColor?: string
32
+ fontWeight?: 'normal' | 'bold' | 'lighter' | number
33
+ textAlign?: 'left' | 'center' | 'right' | 'justify'
34
+ lineHeight?: number
35
+ }>(),
36
+ {
37
+ content: 'Box Content',
38
+ opacity: 100,
39
+ visible: true,
40
+ backgroundColor: '#ffffff',
41
+ borderColor: '#dcdfe6',
42
+ borderWidth: 1,
43
+ borderStyle: 'solid',
44
+ borderRadius: 4,
45
+ padding: 16,
46
+ boxShadow: 'none',
47
+ fontSize: 14,
48
+ textColor: '#606266',
49
+ fontWeight: 'normal',
50
+ textAlign: 'center',
51
+ lineHeight: 1.5,
52
+ },
53
+ )
54
+
55
+ // 计算容器样式
56
+ const containerStyle = computed<CSSProperties>(() => {
57
+ return {
58
+ opacity: ((props.opacity ?? 100) as number) / 100,
59
+ display: props.visible === false ? 'none' : 'flex',
60
+ backgroundColor: props.backgroundColor ?? '#ffffff',
61
+ borderColor: props.borderColor ?? '#dcdfe6',
62
+ borderWidth: `${props.borderWidth ?? 1}px`,
63
+ borderStyle: props.borderStyle ?? 'solid',
64
+ borderRadius: `${props.borderRadius ?? 4}px`,
65
+ padding: `${props.padding ?? 16}px`,
66
+ boxShadow: props.boxShadow ?? 'none',
67
+ width: '100%',
68
+ height: '100%',
69
+ boxSizing: 'border-box',
70
+ alignItems: 'center',
71
+ justifyContent: 'center',
72
+ }
73
+ })
74
+
75
+ // 计算内容样式
76
+ const contentStyle = computed<CSSProperties>(() => {
77
+ return {
78
+ fontSize: `${props.fontSize ?? 14}px`,
79
+ color: props.textColor ?? '#606266',
80
+ fontWeight: props.fontWeight ?? 'normal',
81
+ textAlign: props.textAlign ?? 'center',
82
+ lineHeight: props.lineHeight ?? 1.5,
83
+ wordBreak: 'break-word',
84
+ whiteSpace: 'pre-wrap',
85
+ }
86
+ })
87
+ </script>
88
+
89
+ <style scoped>
90
+ .v-box-container {
91
+ box-sizing: border-box;
92
+ }
93
+
94
+ .v-box-content {
95
+ word-break: break-word;
96
+ white-space: pre-wrap;
97
+ }
98
+ </style>
@@ -0,0 +1,193 @@
1
+ <template>
2
+ <div class="v-countup-container" :style="containerStyle">
3
+ <el-statistic
4
+ :value="animatedValue"
5
+ :precision="decimals"
6
+ :prefix="showPrefix ? prefix : ''"
7
+ :suffix="showSuffix ? suffix : ''"
8
+ :value-style="valueStyleComputed"
9
+ :title="title"
10
+ :title-style="titleStyleComputed"
11
+ >
12
+ <!-- 自定义前缀插槽 -->
13
+ <template v-if="showPrefix && prefix && customPrefix" #prefix>
14
+ <span :style="prefixStyleComputed">{{ prefix }}</span>
15
+ </template>
16
+ <!-- 自定义后缀插槽 -->
17
+ <template v-if="showSuffix && suffix && customSuffix" #suffix>
18
+ <span :style="suffixStyleComputed">{{ suffix }}</span>
19
+ </template>
20
+ </el-statistic>
21
+ </div>
22
+ </template>
23
+
24
+ <script setup lang="ts">
25
+ import { computed, watch, ref } from 'vue'
26
+ import type { CSSProperties } from 'vue'
27
+ import { ElStatistic } from 'element-plus'
28
+
29
+ // 定义纯 UI Props,无业务逻辑
30
+ const props = defineProps<{
31
+ // 核心数据
32
+ value?: number
33
+ title?: string
34
+ startValue?: number
35
+ duration?: number
36
+ decimals?: number
37
+ prefix?: string
38
+ suffix?: string
39
+ showPrefix?: boolean
40
+ showSuffix?: boolean
41
+ useEasing?: boolean
42
+ customPrefix?: boolean
43
+ customSuffix?: boolean
44
+
45
+ // 容器样式
46
+ opacity?: number
47
+ visible?: boolean
48
+ backgroundColor?: string
49
+ borderColor?: string
50
+ borderWidth?: number
51
+ borderRadius?: number
52
+ padding?: number
53
+ align?: 'flex-start' | 'center' | 'flex-end'
54
+
55
+ // 数值样式
56
+ valueColor?: string
57
+ valueFontSize?: number
58
+ valueFontWeight?: 'normal' | 'bold' | 'lighter' | number
59
+ fontFamily?: string
60
+
61
+ // 前缀样式
62
+ prefixColor?: string
63
+ prefixFontSize?: number
64
+ prefixFontWeight?: 'normal' | 'bold' | 'lighter' | number
65
+
66
+ // 后缀样式
67
+ suffixColor?: string
68
+ suffixFontSize?: number
69
+ suffixFontWeight?: 'normal' | 'bold' | 'lighter' | number
70
+
71
+ // 标题样式
72
+ titleColor?: string
73
+ titleFontSize?: number
74
+ titleFontWeight?: 'normal' | 'bold' | 'lighter' | number
75
+ }>()
76
+
77
+ // 当前显示值(用于动画)
78
+ const animatedValue = ref(0)
79
+
80
+ // 缓动函数
81
+ function easeOutExpo(t: number, b: number, c: number, d: number): number {
82
+ return c * (-Math.pow(2, (-10 * t) / d) + 1) + b
83
+ }
84
+
85
+ // 数字动画
86
+ function animateValue(start: number, end: number) {
87
+ const startTime = Date.now()
88
+ const dur = (props.duration ?? 2) * 1000 // Convert seconds to ms
89
+ const useEase = props.useEasing !== false
90
+
91
+ function update() {
92
+ const currentTime = Date.now()
93
+ const elapsed = currentTime - startTime
94
+
95
+ if (elapsed < dur) {
96
+ const progress = elapsed / dur
97
+ if (useEase) {
98
+ animatedValue.value = easeOutExpo(elapsed, start, end - start, dur)
99
+ } else {
100
+ animatedValue.value = start + (end - start) * progress
101
+ }
102
+ requestAnimationFrame(update)
103
+ } else {
104
+ animatedValue.value = end
105
+ }
106
+ }
107
+
108
+ update()
109
+ }
110
+
111
+ // 监听目标值变化
112
+ watch(
113
+ () => props.value,
114
+ (newVal, oldVal) => {
115
+ const start = oldVal ?? props.startValue ?? 0
116
+ const end = newVal ?? 0
117
+ animateValue(start, end)
118
+ },
119
+ { immediate: true },
120
+ )
121
+
122
+ // 容器样式
123
+ const containerStyle = computed<CSSProperties>(() => {
124
+ return {
125
+ opacity: ((props.opacity ?? 100) as number) / 100,
126
+ display: props.visible === false ? 'none' : 'flex',
127
+ backgroundColor: props.backgroundColor ?? 'transparent',
128
+ borderColor: props.borderColor ?? 'transparent',
129
+ borderWidth: `${props.borderWidth ?? 0}px`,
130
+ borderStyle: 'solid',
131
+ borderRadius: `${props.borderRadius ?? 0}px`,
132
+ padding: `${props.padding ?? 10}px`,
133
+ justifyContent: props.align ?? 'center',
134
+ alignItems: 'center',
135
+ width: '100%',
136
+ height: '100%',
137
+ boxSizing: 'border-box',
138
+ }
139
+ })
140
+
141
+ // 数值样式
142
+ const valueStyleComputed = computed<CSSProperties>(() => {
143
+ return {
144
+ color: props.valueColor ?? '#303133',
145
+ fontSize: `${props.valueFontSize ?? 32}px`,
146
+ fontWeight: props.valueFontWeight ?? 'bold',
147
+ fontFamily: props.fontFamily ?? 'inherit',
148
+ }
149
+ })
150
+
151
+ // 前缀样式
152
+ const prefixStyleComputed = computed<CSSProperties>(() => {
153
+ return {
154
+ color: props.prefixColor ?? '#909399',
155
+ fontSize: `${props.prefixFontSize ?? 16}px`,
156
+ fontWeight: props.prefixFontWeight ?? 'normal',
157
+ marginRight: '8px',
158
+ }
159
+ })
160
+
161
+ // 后缀样式
162
+ const suffixStyleComputed = computed<CSSProperties>(() => {
163
+ return {
164
+ color: props.suffixColor ?? '#909399',
165
+ fontSize: `${props.suffixFontSize ?? 16}px`,
166
+ fontWeight: props.suffixFontWeight ?? 'normal',
167
+ marginLeft: '8px',
168
+ }
169
+ })
170
+
171
+ // 标题样式
172
+ const titleStyleComputed = computed<CSSProperties>(() => {
173
+ return {
174
+ color: props.titleColor ?? '#909399',
175
+ fontSize: `${props.titleFontSize ?? 14}px`,
176
+ fontWeight: props.titleFontWeight ?? 'normal',
177
+ }
178
+ })
179
+ </script>
180
+
181
+ <style scoped>
182
+ .v-countup-container {
183
+ box-sizing: border-box;
184
+ }
185
+
186
+ :deep(.el-statistic) {
187
+ text-align: center;
188
+ display: flex;
189
+ align-items: center;
190
+ justify-content: center;
191
+ box-sizing: border-box;
192
+ }
193
+ </style>