@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,282 @@
1
+ <script setup lang="ts">
2
+ import { computed } from 'vue'
3
+ import type { CSSProperties } from 'vue'
4
+ import { Document, ArrowRight } from '@element-plus/icons-vue'
5
+ import { ElScrollbar, ElEmpty, ElIcon } from 'element-plus'
6
+
7
+ export interface ListItem {
8
+ title?: string
9
+ description?: string
10
+ extra?: string
11
+ [key: string]: unknown
12
+ }
13
+
14
+ const props = withDefaults(
15
+ defineProps<{
16
+ // 数据
17
+ data?: ListItem[]
18
+ // 字段映射
19
+ titleField?: string
20
+ descriptionField?: string
21
+ extraField?: string
22
+ // 配置
23
+ showIcon?: boolean
24
+ showTitle?: boolean
25
+ showDescription?: boolean
26
+ showExtra?: boolean
27
+ showAction?: boolean
28
+ showBorder?: boolean
29
+ showSplit?: boolean
30
+ emptyText?: string
31
+ iconSize?: number
32
+ scrollHeight?: string
33
+ // 样式
34
+ opacity?: number
35
+ visible?: boolean
36
+ backgroundColor?: string
37
+ borderRadius?: number
38
+ itemPadding?: number
39
+ itemPaddingX?: number
40
+ itemBackgroundColor?: string
41
+ splitColor?: string
42
+ borderColor?: string
43
+ iconColor?: string
44
+ titleFontSize?: number
45
+ titleColor?: string
46
+ titleFontWeight?: string
47
+ descriptionFontSize?: number
48
+ descriptionColor?: string
49
+ extraFontSize?: number
50
+ extraColor?: string
51
+ }>(),
52
+ {
53
+ data: () => [],
54
+ titleField: 'title',
55
+ descriptionField: 'description',
56
+ extraField: 'extra',
57
+ showIcon: false,
58
+ showTitle: true,
59
+ showDescription: true,
60
+ showExtra: false,
61
+ showAction: true,
62
+ showBorder: true,
63
+ showSplit: true,
64
+ emptyText: '暂无数据',
65
+ iconSize: 20,
66
+ scrollHeight: '100%',
67
+ opacity: 100,
68
+ visible: true,
69
+ backgroundColor: '#ffffff',
70
+ borderRadius: 4,
71
+ itemPadding: 12,
72
+ itemPaddingX: 16,
73
+ itemBackgroundColor: '#ffffff',
74
+ splitColor: '#e4e7ed',
75
+ borderColor: 'transparent',
76
+ iconColor: '#909399',
77
+ titleFontSize: 15,
78
+ titleColor: '#303133',
79
+ titleFontWeight: '500',
80
+ descriptionFontSize: 13,
81
+ descriptionColor: '#909399',
82
+ extraFontSize: 12,
83
+ extraColor: '#409eff',
84
+ },
85
+ )
86
+
87
+ const emit = defineEmits<{
88
+ (e: 'item-click', item: ListItem, index: number): void
89
+ }>()
90
+
91
+ // 默认数据
92
+ const defaultData: ListItem[] = [
93
+ { title: '列表项 1', description: '这是列表项的描述信息', extra: '详情' },
94
+ { title: '列表项 2', description: '这是列表项的描述信息', extra: '详情' },
95
+ { title: '列表项 3', description: '这是列表项的描述信息', extra: '详情' },
96
+ ]
97
+
98
+ const listData = computed(() => (props.data.length > 0 ? props.data : defaultData))
99
+
100
+ const computedScrollHeight = computed(() => {
101
+ const height = props.scrollHeight
102
+ if (typeof height === 'number') return `${height}px`
103
+ if (typeof height === 'string' && (height.endsWith('px') || height.endsWith('%'))) return height
104
+ return '100%'
105
+ })
106
+
107
+ function getItemTitle(item: ListItem): string {
108
+ return String(item[props.titleField] ?? '')
109
+ }
110
+
111
+ function getItemDescription(item: ListItem): string {
112
+ return String(item[props.descriptionField] ?? '')
113
+ }
114
+
115
+ function getItemExtra(item: ListItem): string {
116
+ return String(item[props.extraField] ?? '')
117
+ }
118
+
119
+ function getIcon() {
120
+ return Document
121
+ }
122
+
123
+ // 样式
124
+ const containerStyle = computed<CSSProperties>(() => ({
125
+ opacity: props.opacity / 100,
126
+ display: props.visible === false ? 'none' : 'block',
127
+ width: '100%',
128
+ height: '100%',
129
+ backgroundColor: props.backgroundColor,
130
+ borderRadius: `${props.borderRadius}px`,
131
+ overflow: 'hidden',
132
+ }))
133
+
134
+ const getItemStyle = (index: number): CSSProperties => ({
135
+ padding: `${props.itemPadding}px ${props.itemPaddingX}px`,
136
+ backgroundColor: props.itemBackgroundColor,
137
+ borderBottom:
138
+ props.showSplit && index < listData.value.length - 1 ? `1px solid ${props.splitColor}` : 'none',
139
+ borderLeft: props.showBorder ? `3px solid ${props.borderColor}` : 'none',
140
+ cursor: 'pointer',
141
+ transition: 'all 0.2s',
142
+ })
143
+
144
+ const iconStyle = computed<CSSProperties>(() => ({
145
+ color: props.iconColor,
146
+ }))
147
+
148
+ const titleStyle = computed<CSSProperties>(() => ({
149
+ fontSize: `${props.titleFontSize}px`,
150
+ color: props.titleColor,
151
+ fontWeight: props.titleFontWeight,
152
+ marginBottom: props.showDescription ? '4px' : '0',
153
+ }))
154
+
155
+ const descriptionStyle = computed<CSSProperties>(() => ({
156
+ fontSize: `${props.descriptionFontSize}px`,
157
+ color: props.descriptionColor,
158
+ lineHeight: 1.5,
159
+ }))
160
+
161
+ const extraStyle = computed<CSSProperties>(() => ({
162
+ fontSize: `${props.extraFontSize}px`,
163
+ color: props.extraColor,
164
+ marginTop: '4px',
165
+ }))
166
+
167
+ function handleItemClick(item: ListItem, index: number) {
168
+ emit('item-click', item, index)
169
+ }
170
+ </script>
171
+
172
+ <template>
173
+ <div class="list-container" :style="containerStyle">
174
+ <el-scrollbar :height="computedScrollHeight">
175
+ <div v-if="listData.length === 0" class="empty-state">
176
+ <el-empty :description="emptyText" :image-size="80" />
177
+ </div>
178
+ <div v-else class="list-wrapper">
179
+ <div
180
+ v-for="(item, index) in listData"
181
+ :key="index"
182
+ class="list-item"
183
+ :style="getItemStyle(index)"
184
+ @click="handleItemClick(item, index)"
185
+ >
186
+ <!-- 带图标的列表项 -->
187
+ <div v-if="showIcon" class="item-icon" :style="iconStyle">
188
+ <el-icon :size="iconSize">
189
+ <component :is="getIcon()" />
190
+ </el-icon>
191
+ </div>
192
+
193
+ <!-- 内容区 -->
194
+ <div class="item-content">
195
+ <div v-if="showTitle" class="item-title" :style="titleStyle">
196
+ {{ getItemTitle(item) }}
197
+ </div>
198
+ <div v-if="showDescription" class="item-description" :style="descriptionStyle">
199
+ {{ getItemDescription(item) }}
200
+ </div>
201
+ <div v-if="showExtra" class="item-extra" :style="extraStyle">
202
+ {{ getItemExtra(item) }}
203
+ </div>
204
+ </div>
205
+
206
+ <!-- 右侧操作区 -->
207
+ <div v-if="showAction" class="item-action">
208
+ <el-icon>
209
+ <ArrowRight />
210
+ </el-icon>
211
+ </div>
212
+ </div>
213
+ </div>
214
+ </el-scrollbar>
215
+ </div>
216
+ </template>
217
+
218
+ <style scoped>
219
+ .list-container {
220
+ box-sizing: border-box;
221
+ }
222
+
223
+ .empty-state {
224
+ display: flex;
225
+ align-items: center;
226
+ justify-content: center;
227
+ min-height: 200px;
228
+ }
229
+
230
+ .list-wrapper {
231
+ width: 100%;
232
+ }
233
+
234
+ .list-item {
235
+ display: flex;
236
+ align-items: center;
237
+ gap: 12px;
238
+ box-sizing: border-box;
239
+ }
240
+
241
+ .list-item:hover {
242
+ background-color: #f5f7fa !important;
243
+ }
244
+
245
+ .item-icon {
246
+ flex-shrink: 0;
247
+ display: flex;
248
+ align-items: center;
249
+ justify-content: center;
250
+ }
251
+
252
+ .item-content {
253
+ flex: 1;
254
+ min-width: 0;
255
+ }
256
+
257
+ .item-title {
258
+ overflow: hidden;
259
+ text-overflow: ellipsis;
260
+ white-space: nowrap;
261
+ }
262
+
263
+ .item-description {
264
+ overflow: hidden;
265
+ text-overflow: ellipsis;
266
+ display: -webkit-box;
267
+ -webkit-line-clamp: 2;
268
+ line-clamp: 2;
269
+ -webkit-box-orient: vertical;
270
+ }
271
+
272
+ .item-extra {
273
+ margin-top: 4px;
274
+ }
275
+
276
+ .item-action {
277
+ flex-shrink: 0;
278
+ color: #c0c4cc;
279
+ display: flex;
280
+ align-items: center;
281
+ }
282
+ </style>
@@ -0,0 +1,270 @@
1
+ <script setup lang="ts">
2
+ import { computed } from 'vue'
3
+ import type { CSSProperties } from 'vue'
4
+ import type { TableColumnCtx } from 'element-plus'
5
+ import { ElTable, ElTableColumn, ElEmpty } from 'element-plus'
6
+
7
+ export interface PivotColumn {
8
+ prop: string
9
+ label: string
10
+ width?: number
11
+ align?: string
12
+ sortable?: boolean
13
+ }
14
+
15
+ const props = withDefaults(
16
+ defineProps<{
17
+ // 数据
18
+ data?: Record<string, unknown>[]
19
+ // 配置
20
+ rowHeaders?: string[] | string
21
+ dataColumns?: PivotColumn[]
22
+ columnLabels?: Record<string, string>
23
+ stripe?: boolean
24
+ border?: boolean
25
+ size?: 'large' | 'default' | 'small'
26
+ showSummary?: boolean
27
+ emptyText?: string
28
+ height?: string
29
+ maxHeight?: string
30
+ rowHeaderWidth?: number
31
+ fixedRowHeaders?: boolean
32
+ rowHeaderAlign?: string
33
+ valueFormat?: 'number' | 'percent' | 'currency'
34
+ highlightThreshold?: number
35
+ // 样式
36
+ opacity?: number
37
+ visible?: boolean
38
+ backgroundColor?: string
39
+ borderRadius?: number
40
+ padding?: number
41
+ headerBackgroundColor?: string
42
+ headerColor?: string
43
+ hoverBackgroundColor?: string
44
+ borderColor?: string
45
+ highlightColor?: string
46
+ }>(),
47
+ {
48
+ data: () => [],
49
+ rowHeaders: () => ['category', 'region'],
50
+ dataColumns: () => [],
51
+ columnLabels: () => ({}),
52
+ stripe: true,
53
+ border: true,
54
+ size: 'default',
55
+ showSummary: true,
56
+ emptyText: '暂无数据',
57
+ height: 'auto',
58
+ maxHeight: '',
59
+ rowHeaderWidth: 120,
60
+ fixedRowHeaders: true,
61
+ rowHeaderAlign: 'left',
62
+ valueFormat: 'number',
63
+ highlightThreshold: 0,
64
+ opacity: 100,
65
+ visible: true,
66
+ backgroundColor: '#ffffff',
67
+ borderRadius: 4,
68
+ padding: 0,
69
+ headerBackgroundColor: '#f5f7fa',
70
+ headerColor: '#909399',
71
+ hoverBackgroundColor: '#f5f7fa',
72
+ borderColor: '#ebeef5',
73
+ highlightColor: '#67c23a',
74
+ },
75
+ )
76
+
77
+ // 默认数据
78
+ const defaultData = [
79
+ { category: '产品A', region: '华东', q1: 1200, q2: 1500, q3: 1800, q4: 2100 },
80
+ { category: '产品A', region: '华南', q1: 1100, q2: 1300, q3: 1600, q4: 1900 },
81
+ { category: '产品B', region: '华东', q1: 900, q2: 1100, q3: 1300, q4: 1500 },
82
+ { category: '产品B', region: '华南', q1: 800, q2: 1000, q3: 1200, q4: 1400 },
83
+ ]
84
+
85
+ const pivotData = computed(() => (props.data.length > 0 ? props.data : defaultData))
86
+
87
+ const rowHeadersList = computed(() => {
88
+ if (Array.isArray(props.rowHeaders)) return props.rowHeaders
89
+ if (typeof props.rowHeaders === 'string') return props.rowHeaders.split(',').map((s) => s.trim())
90
+ return ['category', 'region']
91
+ })
92
+
93
+ const computedDataColumns = computed<PivotColumn[]>(() => {
94
+ if (props.dataColumns.length > 0) return props.dataColumns
95
+
96
+ // 从第一行数据推断列
97
+ if (pivotData.value.length > 0) {
98
+ const firstRow = pivotData.value[0]
99
+ if (!firstRow) return []
100
+ const cols: PivotColumn[] = []
101
+
102
+ Object.keys(firstRow).forEach((key) => {
103
+ if (!rowHeadersList.value.includes(key)) {
104
+ cols.push({
105
+ prop: key,
106
+ label: key.toUpperCase(),
107
+ width: 100,
108
+ align: 'center',
109
+ sortable: true,
110
+ })
111
+ }
112
+ })
113
+
114
+ return cols
115
+ }
116
+
117
+ return []
118
+ })
119
+
120
+ function getColumnLabel(prop: string): string {
121
+ return props.columnLabels[prop] || prop
122
+ }
123
+
124
+ function formatValue(value: unknown): string {
125
+ if (value === null || value === undefined) return '-'
126
+
127
+ const numValue = Number(value)
128
+ if (isNaN(numValue)) return String(value)
129
+
130
+ switch (props.valueFormat) {
131
+ case 'number':
132
+ return numValue.toLocaleString()
133
+ case 'percent':
134
+ return `${(numValue * 100).toFixed(2)}%`
135
+ case 'currency':
136
+ return `¥${numValue.toLocaleString()}`
137
+ default:
138
+ return String(value)
139
+ }
140
+ }
141
+
142
+ function getCellStyle(value: unknown): CSSProperties {
143
+ const numValue = Number(value)
144
+
145
+ if (!isNaN(numValue) && props.highlightThreshold > 0) {
146
+ if (numValue >= props.highlightThreshold) {
147
+ return {
148
+ color: props.highlightColor,
149
+ fontWeight: 'bold',
150
+ }
151
+ }
152
+ }
153
+
154
+ return {}
155
+ }
156
+
157
+ function getSummaries(param: {
158
+ columns: TableColumnCtx<Record<string, unknown>>[]
159
+ data: Record<string, unknown>[]
160
+ }) {
161
+ const { columns, data } = param
162
+ const sums: string[] = []
163
+
164
+ columns.forEach((column, index) => {
165
+ if (index === 0) {
166
+ sums[index] = '合计'
167
+ return
168
+ }
169
+
170
+ if (rowHeadersList.value.includes(column.property)) {
171
+ sums[index] = ''
172
+ return
173
+ }
174
+
175
+ const values = data.map((item) => Number(item[column.property]))
176
+ if (values.every((value) => !isNaN(value))) {
177
+ const sum = values.reduce((prev, curr) => prev + curr, 0)
178
+ sums[index] = formatValue(sum)
179
+ } else {
180
+ sums[index] = '-'
181
+ }
182
+ })
183
+
184
+ return sums
185
+ }
186
+
187
+ // 样式
188
+ const containerStyle = computed<CSSProperties>(() => ({
189
+ opacity: props.opacity / 100,
190
+ display: props.visible === false ? 'none' : 'block',
191
+ width: '100%',
192
+ height: '100%',
193
+ backgroundColor: props.backgroundColor,
194
+ borderRadius: `${props.borderRadius}px`,
195
+ padding: `${props.padding}px`,
196
+ overflow: 'hidden',
197
+ boxSizing: 'border-box',
198
+ }))
199
+
200
+ const tableStyle = computed<CSSProperties>(
201
+ () =>
202
+ ({
203
+ '--el-table-header-bg-color': props.headerBackgroundColor,
204
+ '--el-table-header-text-color': props.headerColor,
205
+ '--el-table-row-hover-bg-color': props.hoverBackgroundColor,
206
+ '--el-table-border-color': props.borderColor,
207
+ }) as CSSProperties,
208
+ )
209
+ </script>
210
+
211
+ <template>
212
+ <div class="pivot-container" :style="containerStyle">
213
+ <div v-if="pivotData.length === 0" class="empty-state">
214
+ <el-empty :description="emptyText" :image-size="80" />
215
+ </div>
216
+ <el-table
217
+ v-else
218
+ :data="pivotData"
219
+ :stripe="stripe"
220
+ :border="border"
221
+ :size="size"
222
+ :show-summary="showSummary"
223
+ :summary-method="getSummaries"
224
+ :height="height === 'auto' ? undefined : height"
225
+ :max-height="maxHeight || undefined"
226
+ :style="tableStyle"
227
+ >
228
+ <!-- 行表头列 -->
229
+ <el-table-column
230
+ v-for="rowHeader in rowHeadersList"
231
+ :key="rowHeader"
232
+ :prop="rowHeader"
233
+ :label="getColumnLabel(rowHeader)"
234
+ :width="rowHeaderWidth"
235
+ :fixed="fixedRowHeaders"
236
+ :align="rowHeaderAlign"
237
+ />
238
+
239
+ <!-- 数据列 -->
240
+ <el-table-column
241
+ v-for="col in computedDataColumns"
242
+ :key="col.prop"
243
+ :prop="col.prop"
244
+ :label="col.label"
245
+ :width="col.width"
246
+ :align="col.align || 'center'"
247
+ :sortable="col.sortable"
248
+ >
249
+ <template #default="{ row }">
250
+ <span :style="getCellStyle(row[col.prop])">
251
+ {{ formatValue(row[col.prop]) }}
252
+ </span>
253
+ </template>
254
+ </el-table-column>
255
+ </el-table>
256
+ </div>
257
+ </template>
258
+
259
+ <style scoped>
260
+ .pivot-container {
261
+ box-sizing: border-box;
262
+ }
263
+
264
+ .empty-state {
265
+ display: flex;
266
+ align-items: center;
267
+ justify-content: center;
268
+ min-height: 200px;
269
+ }
270
+ </style>
@@ -0,0 +1,150 @@
1
+ <script setup lang="ts">
2
+ import { computed } from 'vue'
3
+ import type { CSSProperties } from 'vue'
4
+ import { ElTable, ElTableColumn } from 'element-plus'
5
+ import type { TableColumn } from '@vela/core/types'
6
+
7
+ const props = withDefaults(
8
+ defineProps<{
9
+ // 数据
10
+ data?: Record<string, unknown>[]
11
+ columns?: TableColumn[]
12
+ // 配置
13
+ stripe?: boolean
14
+ border?: boolean
15
+ size?: 'large' | 'default' | 'small'
16
+ autoHeight?: boolean
17
+ maxHeight?: number
18
+ highlightCurrentRow?: boolean
19
+ showHeader?: boolean
20
+ emptyText?: string
21
+ // 样式
22
+ opacity?: number
23
+ visible?: boolean
24
+ headerBackgroundColor?: string
25
+ headerTextColor?: string
26
+ headerFontSize?: number
27
+ headerFontWeight?: string
28
+ fontSize?: number
29
+ textColor?: string
30
+ rowBackgroundColor?: string
31
+ }>(),
32
+ {
33
+ data: () => [],
34
+ columns: () => [],
35
+ stripe: true,
36
+ border: true,
37
+ size: 'default',
38
+ autoHeight: false,
39
+ maxHeight: undefined,
40
+ highlightCurrentRow: true,
41
+ showHeader: true,
42
+ emptyText: '暂无数据',
43
+ opacity: 100,
44
+ visible: true,
45
+ headerBackgroundColor: '#f5f7fa',
46
+ headerTextColor: '#606266',
47
+ headerFontSize: 14,
48
+ headerFontWeight: 'bold',
49
+ fontSize: 14,
50
+ textColor: '#606266',
51
+ rowBackgroundColor: '#ffffff',
52
+ },
53
+ )
54
+
55
+ const emit = defineEmits<{
56
+ (e: 'row-click', row: Record<string, unknown>): void
57
+ }>()
58
+
59
+ // 默认数据
60
+ const defaultData = [
61
+ { name: '数据1', value: 100, status: '正常' },
62
+ { name: '数据2', value: 200, status: '正常' },
63
+ { name: '数据3', value: 150, status: '警告' },
64
+ ]
65
+
66
+ const defaultColumns: TableColumn[] = [
67
+ { prop: 'name', label: '名称', align: 'left' },
68
+ { prop: 'value', label: '数值', align: 'right' },
69
+ { prop: 'status', label: '状态', align: 'center' },
70
+ ]
71
+
72
+ const tableData = computed(() => (props.data.length > 0 ? props.data : defaultData))
73
+ const tableColumns = computed(() => (props.columns.length > 0 ? props.columns : defaultColumns))
74
+
75
+ // 样式
76
+ const containerStyle = computed<CSSProperties>(() => ({
77
+ opacity: props.opacity / 100,
78
+ display: props.visible === false ? 'none' : 'block',
79
+ width: '100%',
80
+ height: '100%',
81
+ }))
82
+
83
+ const headerCellStyle = computed(() => ({
84
+ backgroundColor: props.headerBackgroundColor,
85
+ color: props.headerTextColor,
86
+ fontSize: `${props.headerFontSize}px`,
87
+ fontWeight: props.headerFontWeight,
88
+ }))
89
+
90
+ const cellStyle = computed(() => ({
91
+ fontSize: `${props.fontSize}px`,
92
+ color: props.textColor,
93
+ }))
94
+
95
+ const rowStyle = computed(() => ({
96
+ backgroundColor: props.rowBackgroundColor,
97
+ }))
98
+
99
+ function handleRowClick(row: Record<string, unknown>) {
100
+ emit('row-click', row)
101
+ }
102
+ </script>
103
+
104
+ <template>
105
+ <div class="table-container" :style="containerStyle">
106
+ <el-table
107
+ :data="tableData"
108
+ :stripe="stripe"
109
+ :border="border"
110
+ :size="size"
111
+ :height="autoHeight ? undefined : '100%'"
112
+ :max-height="maxHeight"
113
+ :highlight-current-row="highlightCurrentRow"
114
+ :show-header="showHeader"
115
+ :empty-text="emptyText"
116
+ :header-cell-style="headerCellStyle"
117
+ :cell-style="cellStyle"
118
+ :row-style="rowStyle"
119
+ @row-click="handleRowClick"
120
+ >
121
+ <el-table-column
122
+ v-for="(col, index) in tableColumns"
123
+ :key="index"
124
+ :prop="col.prop"
125
+ :label="col.label"
126
+ :width="col.width"
127
+ :min-width="col.minWidth"
128
+ :align="col.align"
129
+ :sortable="col.sortable"
130
+ :fixed="col.fixed || undefined"
131
+ >
132
+ <template v-if="col.type === 'index'" #default="scope">
133
+ {{ scope.$index + 1 }}
134
+ </template>
135
+ </el-table-column>
136
+ </el-table>
137
+ </div>
138
+ </template>
139
+
140
+ <style scoped>
141
+ .table-container {
142
+ box-sizing: border-box;
143
+ padding: 0;
144
+ }
145
+
146
+ :deep(.el-table) {
147
+ width: 100%;
148
+ height: 100%;
149
+ }
150
+ </style>