@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,105 @@
1
+ <template>
2
+ <div class="v-flex-container" :style="containerStyle">
3
+ <slot>
4
+ <div class="v-flex-placeholder" :style="placeholderStyle">
5
+ {{ placeholder }}
6
+ </div>
7
+ </slot>
8
+ </div>
9
+ </template>
10
+
11
+ <script setup lang="ts">
12
+ import { computed } from 'vue'
13
+ import type { CSSProperties } from 'vue'
14
+
15
+ // 定义纯 UI Props
16
+ const props = withDefaults(
17
+ defineProps<{
18
+ // Flex 布局属性
19
+ flexDirection?: 'row' | 'column' | 'row-reverse' | 'column-reverse'
20
+ justifyContent?:
21
+ | 'flex-start'
22
+ | 'flex-end'
23
+ | 'center'
24
+ | 'space-between'
25
+ | 'space-around'
26
+ | 'space-evenly'
27
+ alignItems?: 'flex-start' | 'flex-end' | 'center' | 'stretch' | 'baseline'
28
+ flexWrap?: 'nowrap' | 'wrap' | 'wrap-reverse'
29
+ gap?: number
30
+
31
+ // 容器样式
32
+ padding?: number
33
+ backgroundColor?: string
34
+ borderRadius?: number
35
+ borderWidth?: number
36
+ borderColor?: string
37
+ minHeight?: string
38
+
39
+ // 占位文本
40
+ placeholder?: string
41
+ textColor?: string
42
+ fontSize?: number
43
+ }>(),
44
+ {
45
+ flexDirection: 'row',
46
+ justifyContent: 'flex-start',
47
+ alignItems: 'stretch',
48
+ flexWrap: 'nowrap',
49
+ gap: 0,
50
+ padding: 16,
51
+ backgroundColor: 'transparent',
52
+ borderRadius: 0,
53
+ borderWidth: 0,
54
+ borderColor: '#dcdfe6',
55
+ minHeight: '100px',
56
+ placeholder: 'Flex Container',
57
+ textColor: '#909399',
58
+ fontSize: 14,
59
+ },
60
+ )
61
+
62
+ // 容器样式
63
+ const containerStyle = computed<CSSProperties>(() => {
64
+ return {
65
+ display: 'flex',
66
+ flexDirection: props.flexDirection,
67
+ justifyContent: props.justifyContent,
68
+ alignItems: props.alignItems,
69
+ flexWrap: props.flexWrap,
70
+ gap: `${props.gap}px`,
71
+ padding: `${props.padding}px`,
72
+ backgroundColor: props.backgroundColor,
73
+ borderRadius: `${props.borderRadius}px`,
74
+ borderWidth: `${props.borderWidth}px`,
75
+ borderStyle: props.borderWidth ? 'solid' : 'none',
76
+ borderColor: props.borderColor,
77
+ minHeight: props.minHeight,
78
+ width: '100%',
79
+ boxSizing: 'border-box',
80
+ }
81
+ })
82
+
83
+ // 占位样式
84
+ const placeholderStyle = computed<CSSProperties>(() => {
85
+ return {
86
+ width: '100%',
87
+ minHeight: '50px',
88
+ display: 'flex',
89
+ alignItems: 'center',
90
+ justifyContent: 'center',
91
+ color: props.textColor,
92
+ fontSize: `${props.fontSize}px`,
93
+ }
94
+ })
95
+ </script>
96
+
97
+ <style scoped>
98
+ .v-flex-container {
99
+ box-sizing: border-box;
100
+ }
101
+
102
+ .v-flex-placeholder {
103
+ width: 100%;
104
+ }
105
+ </style>
@@ -0,0 +1,89 @@
1
+ <template>
2
+ <div class="v-grid-container" :style="containerStyle">
3
+ <slot>
4
+ <template v-if="placeholderItems.length">
5
+ <div v-for="(item, idx) in placeholderItems" :key="idx" class="v-grid-placeholder-item">
6
+ {{ item }}
7
+ </div>
8
+ </template>
9
+ </slot>
10
+ </div>
11
+ </template>
12
+
13
+ <script setup lang="ts">
14
+ import { computed } from 'vue'
15
+ import type { CSSProperties } from 'vue'
16
+
17
+ // 定义纯 UI Props
18
+ const props = withDefaults(
19
+ defineProps<{
20
+ // Grid 布局属性
21
+ gridTemplateColumns?: string
22
+ gridTemplateRows?: string
23
+ gridGap?: number
24
+ gridAutoFlow?: 'row' | 'column' | 'dense' | 'row dense' | 'column dense'
25
+
26
+ // 容器样式
27
+ padding?: number
28
+ backgroundColor?: string
29
+ border?: string
30
+ borderRadius?: number
31
+ minHeight?: number
32
+ textColor?: string
33
+
34
+ // 占位内容
35
+ content?: string
36
+ placeholderItems?: string[]
37
+ }>(),
38
+ {
39
+ gridTemplateColumns: 'repeat(3, 1fr)',
40
+ gridTemplateRows: 'auto',
41
+ gridGap: 16,
42
+ gridAutoFlow: 'row',
43
+ padding: 16,
44
+ backgroundColor: '#ffffff',
45
+ border: '1px solid #e5e7eb',
46
+ borderRadius: 4,
47
+ minHeight: 200,
48
+ textColor: '#333333',
49
+ placeholderItems: () => ['Grid 1', 'Grid 2', 'Grid 3'],
50
+ },
51
+ )
52
+
53
+ // 容器样式
54
+ const containerStyle = computed<CSSProperties>(() => {
55
+ return {
56
+ display: 'grid',
57
+ gridTemplateColumns: props.gridTemplateColumns,
58
+ gridTemplateRows: props.gridTemplateRows,
59
+ gap: `${props.gridGap}px`,
60
+ gridAutoFlow: props.gridAutoFlow,
61
+ padding: `${props.padding}px`,
62
+ backgroundColor: props.backgroundColor,
63
+ border: props.border,
64
+ borderRadius: `${props.borderRadius}px`,
65
+ minHeight: `${props.minHeight}px`,
66
+ color: props.textColor,
67
+ boxSizing: 'border-box',
68
+ }
69
+ })
70
+
71
+ // 占位项
72
+ const placeholderItems = computed(() => {
73
+ return props.placeholderItems
74
+ })
75
+ </script>
76
+
77
+ <style scoped>
78
+ .v-grid-container {
79
+ box-sizing: border-box;
80
+ width: 100%;
81
+ }
82
+
83
+ .v-grid-placeholder-item {
84
+ padding: 12px;
85
+ background-color: #f3f4f6;
86
+ border-radius: 4px;
87
+ text-align: center;
88
+ }
89
+ </style>
@@ -0,0 +1,118 @@
1
+ <template>
2
+ <el-dialog
3
+ v-model="dialogVisible"
4
+ :title="title"
5
+ :width="width"
6
+ :fullscreen="fullscreen"
7
+ :close-on-click-modal="closeOnClickModal"
8
+ :show-close="showClose"
9
+ :append-to-body="true"
10
+ @close="handleClose"
11
+ >
12
+ <div class="v-modal-body" :style="bodyStyle">
13
+ <slot>
14
+ <span v-if="content">{{ content }}</span>
15
+ </slot>
16
+ </div>
17
+ <template #footer v-if="showFooter">
18
+ <slot name="footer">
19
+ <span class="v-modal-footer">
20
+ <el-button @click="handleCancel">{{ cancelText }}</el-button>
21
+ <el-button type="primary" @click="handleConfirm">{{ confirmText }}</el-button>
22
+ </span>
23
+ </slot>
24
+ </template>
25
+ </el-dialog>
26
+ </template>
27
+
28
+ <script setup lang="ts">
29
+ import { computed, ref, watch } from 'vue'
30
+ import type { CSSProperties } from 'vue'
31
+ import { ElDialog, ElButton } from 'element-plus'
32
+
33
+ // 定义纯 UI Props
34
+ const props = defineProps<{
35
+ // Dialog 配置
36
+ visible?: boolean
37
+ title?: string
38
+ width?: string | number
39
+ fullscreen?: boolean
40
+ closeOnClickModal?: boolean
41
+ showClose?: boolean
42
+ showFooter?: boolean
43
+
44
+ // 内容
45
+ content?: string
46
+
47
+ // 按钮文本
48
+ cancelText?: string
49
+ confirmText?: string
50
+
51
+ // 样式
52
+ backgroundColor?: string
53
+ textColor?: string
54
+ }>()
55
+
56
+ const emit = defineEmits<{
57
+ (e: 'update:visible', value: boolean): void
58
+ (e: 'close'): void
59
+ (e: 'cancel'): void
60
+ (e: 'confirm'): void
61
+ }>()
62
+
63
+ // 对话框可见性
64
+ const dialogVisible = ref(props.visible ?? false)
65
+
66
+ // 监听外部 visible 变化
67
+ watch(
68
+ () => props.visible,
69
+ (newVal) => {
70
+ dialogVisible.value = newVal ?? false
71
+ },
72
+ )
73
+
74
+ // 同步内部状态到外部
75
+ watch(
76
+ () => dialogVisible.value,
77
+ (newVal) => {
78
+ emit('update:visible', newVal)
79
+ },
80
+ )
81
+
82
+ // 关闭处理
83
+ const handleClose = () => {
84
+ dialogVisible.value = false
85
+ emit('close')
86
+ }
87
+
88
+ // 取消处理
89
+ const handleCancel = () => {
90
+ dialogVisible.value = false
91
+ emit('cancel')
92
+ }
93
+
94
+ // 确认处理
95
+ const handleConfirm = () => {
96
+ emit('confirm')
97
+ }
98
+
99
+ // 内容样式
100
+ const bodyStyle = computed<CSSProperties>(() => {
101
+ return {
102
+ backgroundColor: props.backgroundColor ?? '#ffffff',
103
+ color: props.textColor ?? '#333333',
104
+ }
105
+ })
106
+ </script>
107
+
108
+ <style scoped>
109
+ .v-modal-footer {
110
+ display: flex;
111
+ justify-content: flex-end;
112
+ gap: 8px;
113
+ }
114
+
115
+ .v-modal-body {
116
+ box-sizing: border-box;
117
+ }
118
+ </style>
@@ -0,0 +1,162 @@
1
+ <template>
2
+ <div class="v-panel" :style="containerStyle">
3
+ <!-- 头部 -->
4
+ <div v-if="showHeader" class="v-panel-header" :style="headerStyle" @click="handleHeaderClick">
5
+ <span>{{ title }}</span>
6
+ <span v-if="collapsible" class="v-panel-collapse-icon">
7
+ {{ isCollapsed ? '▼' : '▲' }}
8
+ </span>
9
+ </div>
10
+
11
+ <!-- 内容 -->
12
+ <div class="v-panel-body" :style="bodyStyle">
13
+ <slot>
14
+ <span v-if="content">{{ content }}</span>
15
+ </slot>
16
+ </div>
17
+
18
+ <!-- 底部 -->
19
+ <div v-if="showFooter && footerContent" class="v-panel-footer" :style="footerStyle">
20
+ {{ footerContent }}
21
+ </div>
22
+ </div>
23
+ </template>
24
+
25
+ <script setup lang="ts">
26
+ import { computed, ref, watch } from 'vue'
27
+ import type { CSSProperties } from 'vue'
28
+
29
+ // 定义纯 UI Props
30
+ const props = defineProps<{
31
+ // 内容
32
+ title?: string
33
+ content?: string
34
+ footerContent?: string
35
+
36
+ // 功能配置
37
+ showHeader?: boolean
38
+ showFooter?: boolean
39
+ collapsible?: boolean
40
+ collapsed?: boolean
41
+
42
+ // 容器样式
43
+ backgroundColor?: string
44
+ border?: string
45
+ borderRadius?: number
46
+ boxShadow?: string
47
+
48
+ // 头部样式
49
+ headerPadding?: number
50
+ headerBg?: string
51
+ headerFontSize?: number
52
+ headerColor?: string
53
+
54
+ // 内容样式
55
+ bodyPadding?: number
56
+ textColor?: string
57
+ fontSize?: number
58
+
59
+ // 底部样式
60
+ footerPadding?: number
61
+ footerBg?: string
62
+ footerFontSize?: number
63
+ footerColor?: string
64
+ }>()
65
+
66
+ const emit = defineEmits<{
67
+ (e: 'update:collapsed', value: boolean): void
68
+ (e: 'toggle', value: boolean): void
69
+ }>()
70
+
71
+ // 折叠状态
72
+ const isCollapsed = ref(props.collapsed ?? false)
73
+
74
+ // 监听外部 collapsed 变化
75
+ watch(
76
+ () => props.collapsed,
77
+ (newVal) => {
78
+ isCollapsed.value = newVal ?? false
79
+ },
80
+ )
81
+
82
+ // 切换折叠
83
+ const handleHeaderClick = () => {
84
+ if (props.collapsible) {
85
+ isCollapsed.value = !isCollapsed.value
86
+ emit('update:collapsed', isCollapsed.value)
87
+ emit('toggle', isCollapsed.value)
88
+ }
89
+ }
90
+
91
+ // 容器样式
92
+ const containerStyle = computed<CSSProperties>(() => {
93
+ return {
94
+ backgroundColor: props.backgroundColor ?? '#ffffff',
95
+ border: props.border ?? '1px solid #e5e7eb',
96
+ borderRadius: `${props.borderRadius ?? 4}px`,
97
+ overflow: 'hidden',
98
+ boxShadow: props.boxShadow ?? '0 1px 3px rgba(0, 0, 0, 0.1)',
99
+ boxSizing: 'border-box',
100
+ }
101
+ })
102
+
103
+ // 头部样式
104
+ const headerStyle = computed<CSSProperties>(() => {
105
+ return {
106
+ padding: `${props.headerPadding ?? 16}px`,
107
+ backgroundColor: props.headerBg ?? '#f9fafb',
108
+ borderBottom: '1px solid #e5e7eb',
109
+ display: 'flex',
110
+ justifyContent: 'space-between',
111
+ alignItems: 'center',
112
+ cursor: props.collapsible ? 'pointer' : 'default',
113
+ fontWeight: 600,
114
+ fontSize: `${props.headerFontSize ?? 14}px`,
115
+ color: props.headerColor ?? '#111827',
116
+ }
117
+ })
118
+
119
+ // 内容样式
120
+ const bodyStyle = computed<CSSProperties>(() => {
121
+ return {
122
+ padding: `${props.bodyPadding ?? 16}px`,
123
+ color: props.textColor ?? '#333333',
124
+ fontSize: `${props.fontSize ?? 14}px`,
125
+ display: isCollapsed.value ? 'none' : 'block',
126
+ }
127
+ })
128
+
129
+ // 底部样式
130
+ const footerStyle = computed<CSSProperties>(() => {
131
+ return {
132
+ padding: `${props.footerPadding ?? 16}px`,
133
+ backgroundColor: props.footerBg ?? '#f9fafb',
134
+ borderTop: '1px solid #e5e7eb',
135
+ fontSize: `${props.footerFontSize ?? 12}px`,
136
+ color: props.footerColor ?? '#6b7280',
137
+ }
138
+ })
139
+ </script>
140
+
141
+ <style scoped>
142
+ .v-panel {
143
+ box-sizing: border-box;
144
+ width: 100%;
145
+ }
146
+
147
+ .v-panel-header {
148
+ user-select: none;
149
+ }
150
+
151
+ .v-panel-collapse-icon {
152
+ font-size: 12px;
153
+ }
154
+
155
+ .v-panel-body {
156
+ box-sizing: border-box;
157
+ }
158
+
159
+ .v-panel-footer {
160
+ box-sizing: border-box;
161
+ }
162
+ </style>
@@ -0,0 +1,99 @@
1
+ <template>
2
+ <el-row
3
+ :gutter="gutter"
4
+ :justify="justify"
5
+ :align="align"
6
+ :tag="tag"
7
+ :style="containerStyle"
8
+ class="v-row"
9
+ >
10
+ <slot>
11
+ <div class="v-row-placeholder" :style="placeholderStyle">
12
+ {{ placeholder }}
13
+ </div>
14
+ </slot>
15
+ </el-row>
16
+ </template>
17
+
18
+ <script setup lang="ts">
19
+ import { computed } from 'vue'
20
+ import type { CSSProperties } from 'vue'
21
+ import { ElRow } from 'element-plus'
22
+
23
+ // 定义纯 UI Props
24
+ const props = withDefaults(
25
+ defineProps<{
26
+ // Row 布局属性
27
+ gutter?: number
28
+ justify?: 'start' | 'end' | 'center' | 'space-around' | 'space-between' | 'space-evenly'
29
+ align?: 'top' | 'middle' | 'bottom'
30
+ tag?: string
31
+
32
+ // 容器样式
33
+ padding?: number
34
+ backgroundColor?: string
35
+ borderRadius?: number
36
+ borderWidth?: number
37
+ borderColor?: string
38
+ minHeight?: string
39
+
40
+ // 占位文本
41
+ placeholder?: string
42
+ textColor?: string
43
+ fontSize?: number
44
+ }>(),
45
+ {
46
+ gutter: 0,
47
+ justify: 'start',
48
+ align: 'top',
49
+ tag: 'div',
50
+ padding: 0,
51
+ backgroundColor: 'transparent',
52
+ borderRadius: 0,
53
+ borderWidth: 0,
54
+ borderColor: '#dcdfe6',
55
+ minHeight: 'auto',
56
+ placeholder: '行布局容器 - 可拖入其他组件',
57
+ textColor: '#909399',
58
+ fontSize: 14,
59
+ },
60
+ )
61
+
62
+ // 容器样式
63
+ const containerStyle = computed<CSSProperties>(() => {
64
+ return {
65
+ padding: `${props.padding}px`,
66
+ backgroundColor: props.backgroundColor,
67
+ borderRadius: `${props.borderRadius}px`,
68
+ borderWidth: `${props.borderWidth}px`,
69
+ borderStyle: props.borderWidth ? 'solid' : 'none',
70
+ borderColor: props.borderColor,
71
+ minHeight: props.minHeight,
72
+ boxSizing: 'border-box',
73
+ }
74
+ })
75
+
76
+ // 占位样式
77
+ const placeholderStyle = computed<CSSProperties>(() => {
78
+ return {
79
+ width: '100%',
80
+ minHeight: '50px',
81
+ display: 'flex',
82
+ alignItems: 'center',
83
+ justifyContent: 'center',
84
+ color: props.textColor,
85
+ fontSize: `${props.fontSize}px`,
86
+ }
87
+ })
88
+ </script>
89
+
90
+ <style scoped>
91
+ .v-row {
92
+ width: 100%;
93
+ box-sizing: border-box;
94
+ }
95
+
96
+ .v-row-placeholder {
97
+ width: 100%;
98
+ }
99
+ </style>
@@ -0,0 +1,117 @@
1
+ <template>
2
+ <div class="v-tabs-container" :style="containerStyle">
3
+ <el-tabs
4
+ v-model="activeTabValue"
5
+ :type="type"
6
+ :tab-position="tabPosition"
7
+ :closable="closable"
8
+ :addable="addable"
9
+ @tab-change="handleTabChange"
10
+ >
11
+ <el-tab-pane
12
+ v-for="(tab, idx) in tabs"
13
+ :key="tab.name"
14
+ :label="tab.label"
15
+ :name="String(tab.name)"
16
+ >
17
+ <!-- 使用具名插槽,允许外部自定义每个 tab 的内容 -->
18
+ <slot :name="`tab-${idx}`" :tab="tab">
19
+ {{ tab.content }}
20
+ </slot>
21
+ </el-tab-pane>
22
+ </el-tabs>
23
+ </div>
24
+ </template>
25
+
26
+ <script setup lang="ts">
27
+ import { computed, ref, watch } from 'vue'
28
+ import type { CSSProperties } from 'vue'
29
+ import { ElTabs, ElTabPane } from 'element-plus'
30
+
31
+ // Tab 项类型
32
+ interface TabItem {
33
+ label: string
34
+ name: string
35
+ content?: string
36
+ }
37
+
38
+ // 定义纯 UI Props
39
+ const props = defineProps<{
40
+ // Tabs 数据
41
+ tabs?: TabItem[]
42
+ activeTab?: string
43
+
44
+ // Tabs 配置
45
+ type?: '' | 'card' | 'border-card'
46
+ tabPosition?: 'top' | 'right' | 'bottom' | 'left'
47
+ closable?: boolean
48
+ addable?: boolean
49
+
50
+ // 容器样式
51
+ backgroundColor?: string
52
+ padding?: number
53
+ textColor?: string
54
+ }>()
55
+
56
+ const emit = defineEmits<{
57
+ (e: 'update:activeTab', value: string): void
58
+ (e: 'tab-change', value: string): void
59
+ }>()
60
+
61
+ // 当前激活的 tab
62
+ const activeTabValue = ref(props.activeTab ?? '')
63
+
64
+ // 监听外部 activeTab 变化
65
+ watch(
66
+ () => props.activeTab,
67
+ (newVal) => {
68
+ if (newVal) activeTabValue.value = newVal
69
+ },
70
+ )
71
+
72
+ // 初始化 activeTab
73
+ watch(
74
+ () => props.tabs,
75
+ (newTabs) => {
76
+ if (newTabs?.length && !activeTabValue.value) {
77
+ activeTabValue.value = String(newTabs[0]?.name ?? '')
78
+ }
79
+ },
80
+ { immediate: true },
81
+ )
82
+
83
+ // Tab 切换处理
84
+ const handleTabChange = (tabName: string) => {
85
+ emit('update:activeTab', tabName)
86
+ emit('tab-change', tabName)
87
+ }
88
+
89
+ // 容器样式
90
+ const containerStyle = computed<CSSProperties>(() => {
91
+ return {
92
+ backgroundColor: props.backgroundColor ?? '#ffffff',
93
+ padding: `${props.padding ?? 0}px`,
94
+ color: props.textColor ?? '#333333',
95
+ boxSizing: 'border-box',
96
+ width: '100%',
97
+ }
98
+ })
99
+
100
+ // 默认 tabs
101
+ const tabs = computed<TabItem[]>(() => {
102
+ return (
103
+ props.tabs ?? [
104
+ { label: 'Tab 1', name: 'tab1', content: 'Content of Tab 1' },
105
+ { label: 'Tab 2', name: 'tab2', content: 'Content of Tab 2' },
106
+ { label: 'Tab 3', name: 'tab3', content: 'Content of Tab 3' },
107
+ ]
108
+ )
109
+ })
110
+ </script>
111
+
112
+ <style scoped>
113
+ .v-tabs-container {
114
+ box-sizing: border-box;
115
+ width: 100%;
116
+ }
117
+ </style>