@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.
- package/README.md +152 -0
- package/dist/index.d.ts +696 -0
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +11786 -0
- package/dist/index.mjs.map +1 -0
- package/dist/index.umd.js +10 -0
- package/dist/index.umd.js.map +1 -0
- package/dist/style.css +1 -0
- package/index.ts +150 -0
- package/package.json +73 -0
- package/src/components/advanced/scripting/Scripting.vue +189 -0
- package/src/components/advanced/state/State.vue +231 -0
- package/src/components/advanced/trigger/Trigger.vue +256 -0
- package/src/components/basic/button/Button.vue +120 -0
- package/src/components/basic/container/Container.vue +22 -0
- package/src/components/chart/barChart/barChart.vue +176 -0
- package/src/components/chart/doughnutChart/doughnutChart.vue +128 -0
- package/src/components/chart/funnelChart/funnelChart.vue +128 -0
- package/src/components/chart/gaugeChart/gaugeChart.vue +144 -0
- package/src/components/chart/lineChart/lineChart.vue +188 -0
- package/src/components/chart/pieChart/pieChart.vue +114 -0
- package/src/components/chart/radarChart/radarChart.vue +115 -0
- package/src/components/chart/sankeyChart/sankeyChart.vue +144 -0
- package/src/components/chart/scatterChart/scatterChart.vue +162 -0
- package/src/components/chart/stackedBarChart/stackedBarChart.vue +184 -0
- package/src/components/content/html/Html.vue +104 -0
- package/src/components/content/iframe/Iframe.vue +111 -0
- package/src/components/content/markdown/Markdown.vue +174 -0
- package/src/components/controls/breadcrumb/Breadcrumb.vue +79 -0
- package/src/components/controls/buttonGroup/ButtonGroup.vue +93 -0
- package/src/components/controls/checkboxGroup/CheckboxGroup.vue +147 -0
- package/src/components/controls/dateRange/DateRange.vue +174 -0
- package/src/components/controls/multiSelect/MultiSelect.vue +155 -0
- package/src/components/controls/navButton/NavButton.vue +97 -0
- package/src/components/controls/pagination/Pagination.vue +94 -0
- package/src/components/controls/searchBox/SearchBox.vue +170 -0
- package/src/components/controls/select/Select.vue +134 -0
- package/src/components/controls/slider/Slider.vue +167 -0
- package/src/components/controls/switch/Switch.vue +107 -0
- package/src/components/data/cardGrid/CardGrid.vue +318 -0
- package/src/components/data/list/List.vue +282 -0
- package/src/components/data/pivot/Pivot.vue +270 -0
- package/src/components/data/table/Table.vue +150 -0
- package/src/components/data/timeline/Timeline.vue +315 -0
- package/src/components/group/Group.vue +75 -0
- package/src/components/kpi/box/Box.vue +98 -0
- package/src/components/kpi/countUp/CountUp.vue +193 -0
- package/src/components/kpi/progress/Progress.vue +159 -0
- package/src/components/kpi/stat/Stat.vue +205 -0
- package/src/components/kpi/text/Text.vue +74 -0
- package/src/components/layout/badge/Badge.vue +105 -0
- package/src/components/layout/col/Col.vue +114 -0
- package/src/components/layout/flex/Flex.vue +105 -0
- package/src/components/layout/grid/Grid.vue +89 -0
- package/src/components/layout/modal/Modal.vue +118 -0
- package/src/components/layout/panel/Panel.vue +162 -0
- package/src/components/layout/row/Row.vue +99 -0
- package/src/components/layout/tabs/Tabs.vue +117 -0
- package/src/components/media/image/Image.vue +132 -0
- package/src/components/media/video/Video.vue +115 -0
- package/src/components/v2/basic/BaseButton.vue +179 -0
- package/src/components/v2/kpi/KpiCard.vue +215 -0
- package/src/components/v2/layout/GridBox.vue +55 -0
- package/src/hooks/useDataSource.ts +123 -0
- package/src/types/gis.ts +251 -0
- package/src/utils/chartUtils.ts +349 -0
- 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>
|