@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,144 @@
1
+ <template>
2
+ <div class="v-sankey-chart" :style="{ width: '100%', height: '100%' }">
3
+ <v-chart :option="finalOption" autoresize class="echart" />
4
+ </div>
5
+ </template>
6
+
7
+ <script setup lang="ts">
8
+ import { computed } from 'vue'
9
+ import { use } from 'echarts/core'
10
+ import { CanvasRenderer } from 'echarts/renderers'
11
+ import { SankeyChart } from 'echarts/charts'
12
+ import { TitleComponent, TooltipComponent } from 'echarts/components'
13
+ import VChart from 'vue-echarts'
14
+ import type { EChartsOption } from 'echarts'
15
+
16
+ // 注册 ECharts 组件
17
+ use([TitleComponent, TooltipComponent, SankeyChart, CanvasRenderer])
18
+
19
+ // Sankey 节点类型
20
+ interface SankeyNode {
21
+ name: string
22
+ value?: number
23
+ depth?: number
24
+ itemStyle?: Record<string, unknown>
25
+ }
26
+
27
+ // Sankey 连接类型
28
+ interface SankeyLink {
29
+ source: string
30
+ target: string
31
+ value: number
32
+ }
33
+
34
+ // 定义标准的 Props,不含任何业务逻辑 ID
35
+ const props = defineProps<{
36
+ // 基础数据 Props
37
+ data?: SankeyNode[]
38
+ links?: SankeyLink[]
39
+
40
+ // 样式配置 Props
41
+ title?: string
42
+ orient?: 'horizontal' | 'vertical'
43
+ left?: string
44
+ top?: string
45
+ right?: string
46
+ bottom?: string
47
+ nodeWidth?: number
48
+ nodeGap?: number
49
+ layoutIterations?: number
50
+ nodeAlign?: 'left' | 'right' | 'justify'
51
+ showLabel?: boolean
52
+ labelPosition?: 'left' | 'right' | 'top' | 'bottom'
53
+ labelFontSize?: number
54
+ labelColor?: string
55
+ lineColor?: string
56
+ lineOpacity?: number
57
+ lineCurveness?: number
58
+
59
+ // 高级覆盖
60
+ option?: EChartsOption
61
+ }>()
62
+
63
+ // 默认值配置
64
+ const defaultNodes: SankeyNode[] = [
65
+ { name: 'a' },
66
+ { name: 'b' },
67
+ { name: 'c' },
68
+ { name: 'd' },
69
+ { name: 'e' },
70
+ { name: 'f' },
71
+ ]
72
+
73
+ const defaultLinks: SankeyLink[] = [
74
+ { source: 'a', target: 'b', value: 5 },
75
+ { source: 'a', target: 'c', value: 3 },
76
+ { source: 'b', target: 'd', value: 8 },
77
+ { source: 'b', target: 'e', value: 3 },
78
+ { source: 'c', target: 'e', value: 4 },
79
+ { source: 'd', target: 'f', value: 6 },
80
+ { source: 'e', target: 'f', value: 5 },
81
+ ]
82
+
83
+ // 计算最终 Option
84
+ const finalOption = computed<EChartsOption>(() => {
85
+ // 如果有高级配置 option,优先使用
86
+ if (props.option && Object.keys(props.option).length > 0) return props.option
87
+
88
+ const data = props.data && props.data.length ? props.data : defaultNodes
89
+ const links = props.links && props.links.length ? props.links : defaultLinks
90
+
91
+ return {
92
+ title: {
93
+ text: props.title || '',
94
+ left: 'center',
95
+ },
96
+ tooltip: {
97
+ trigger: 'item',
98
+ triggerOn: 'mousemove',
99
+ },
100
+ series: [
101
+ {
102
+ type: 'sankey',
103
+ data: data,
104
+ links: links,
105
+ emphasis: {
106
+ focus: 'adjacency',
107
+ },
108
+ orient: props.orient || 'horizontal',
109
+ left: props.left || '5%',
110
+ top: props.top || '10%',
111
+ right: props.right || '20%',
112
+ bottom: props.bottom || '10%',
113
+ nodeWidth: props.nodeWidth || 20,
114
+ nodeGap: props.nodeGap || 8,
115
+ layoutIterations: props.layoutIterations || 32,
116
+ nodeAlign: props.nodeAlign || 'justify',
117
+ label: {
118
+ show: props.showLabel !== false,
119
+ position: props.labelPosition || 'right',
120
+ fontSize: props.labelFontSize || 12,
121
+ color: props.labelColor || '#000',
122
+ },
123
+ lineStyle: {
124
+ color: props.lineColor || 'source',
125
+ opacity: props.lineOpacity || 0.2,
126
+ curveness: props.lineCurveness || 0.5,
127
+ },
128
+ },
129
+ ],
130
+ }
131
+ })
132
+ </script>
133
+
134
+ <style scoped>
135
+ .v-sankey-chart {
136
+ width: 100%;
137
+ height: 100%;
138
+ }
139
+
140
+ .echart {
141
+ width: 100%;
142
+ height: 100%;
143
+ }
144
+ </style>
@@ -0,0 +1,162 @@
1
+ <template>
2
+ <div class="v-scatter-chart" :style="{ width: '100%', height: '100%' }">
3
+ <v-chart :option="finalOption" autoresize class="echart" />
4
+ </div>
5
+ </template>
6
+
7
+ <script setup lang="ts">
8
+ import { computed } from 'vue'
9
+ import { use } from 'echarts/core'
10
+ import { CanvasRenderer } from 'echarts/renderers'
11
+ import { ScatterChart } from 'echarts/charts'
12
+ import {
13
+ TitleComponent,
14
+ TooltipComponent,
15
+ GridComponent,
16
+ LegendComponent,
17
+ } from 'echarts/components'
18
+ import VChart from 'vue-echarts'
19
+ import type { EChartsOption } from 'echarts'
20
+
21
+ // 注册 ECharts 组件
22
+ use([
23
+ TitleComponent,
24
+ TooltipComponent,
25
+ GridComponent,
26
+ LegendComponent,
27
+ ScatterChart,
28
+ CanvasRenderer,
29
+ ])
30
+
31
+ // 定义标准的 Props,不含任何业务逻辑 ID
32
+ const props = defineProps<{
33
+ // 基础数据 Props
34
+ data?: Array<[number, number]>
35
+
36
+ // 样式配置 Props
37
+ title?: string
38
+ titleAlign?: 'left' | 'center' | 'right'
39
+ titleSize?: number
40
+ titleColor?: string
41
+ seriesName?: string
42
+ symbolSize?: number
43
+ color?: string
44
+ opacity?: number
45
+ showLegend?: boolean
46
+ legendLeft?: string
47
+ legendTop?: string
48
+ gridLeft?: string
49
+ gridRight?: string
50
+ gridBottom?: string
51
+ gridTop?: string
52
+ xAxisName?: string
53
+ yAxisName?: string
54
+ showXAxisSplitLine?: boolean
55
+ showYAxisSplitLine?: boolean
56
+
57
+ // 高级覆盖
58
+ option?: EChartsOption
59
+ }>()
60
+
61
+ // 默认值配置
62
+ const defaultData: Array<[number, number]> = [
63
+ [10.0, 8.04],
64
+ [8.07, 6.95],
65
+ [13.0, 7.58],
66
+ [9.05, 8.81],
67
+ [11.0, 8.33],
68
+ [14.0, 7.66],
69
+ [13.4, 6.81],
70
+ [10.0, 6.33],
71
+ [14.0, 8.96],
72
+ [12.5, 6.82],
73
+ ]
74
+
75
+ // 计算最终 Option
76
+ const finalOption = computed<EChartsOption>(() => {
77
+ // 如果有高级配置 option,优先使用
78
+ if (props.option && Object.keys(props.option).length > 0) return props.option
79
+
80
+ const data = props.data && props.data.length ? props.data : defaultData
81
+ const seriesName = props.seriesName || 'Data'
82
+
83
+ return {
84
+ title: {
85
+ text: props.title || '',
86
+ left: props.titleAlign || 'center',
87
+ textStyle: {
88
+ fontSize: props.titleSize || 16,
89
+ color: props.titleColor || '#333',
90
+ },
91
+ },
92
+ tooltip: {
93
+ trigger: 'item',
94
+ formatter: (params: unknown) => {
95
+ const p = params as { seriesName: string; value: [number, number] }
96
+ return `${p.seriesName}<br/>${p.value[0]}, ${p.value[1]}`
97
+ },
98
+ },
99
+ legend: {
100
+ show: props.showLegend !== false,
101
+ left: props.legendLeft || 'center',
102
+ top: props.legendTop || 'bottom',
103
+ },
104
+ grid: {
105
+ left: props.gridLeft || '10%',
106
+ right: props.gridRight || '10%',
107
+ bottom: props.gridBottom || '15%',
108
+ top: props.gridTop || '15%',
109
+ containLabel: true,
110
+ },
111
+ xAxis: {
112
+ type: 'value',
113
+ name: props.xAxisName || '',
114
+ nameLocation: 'middle',
115
+ nameGap: 30,
116
+ splitLine: {
117
+ show: props.showXAxisSplitLine !== false,
118
+ },
119
+ },
120
+ yAxis: {
121
+ type: 'value',
122
+ name: props.yAxisName || '',
123
+ nameLocation: 'middle',
124
+ nameGap: 40,
125
+ splitLine: {
126
+ show: props.showYAxisSplitLine !== false,
127
+ },
128
+ },
129
+ series: [
130
+ {
131
+ name: seriesName,
132
+ type: 'scatter',
133
+ symbolSize: props.symbolSize || 10,
134
+ data: data,
135
+ itemStyle: {
136
+ color: props.color || '#5470c6',
137
+ opacity: props.opacity || 0.8,
138
+ },
139
+ emphasis: {
140
+ itemStyle: {
141
+ shadowBlur: 10,
142
+ shadowOffsetX: 0,
143
+ shadowColor: 'rgba(0, 0, 0, 0.5)',
144
+ },
145
+ },
146
+ },
147
+ ],
148
+ }
149
+ })
150
+ </script>
151
+
152
+ <style scoped>
153
+ .v-scatter-chart {
154
+ width: 100%;
155
+ height: 100%;
156
+ }
157
+
158
+ .echart {
159
+ width: 100%;
160
+ height: 100%;
161
+ }
162
+ </style>
@@ -0,0 +1,184 @@
1
+ <template>
2
+ <div class="v-stacked-bar-chart" :style="{ width: '100%', height: '100%' }">
3
+ <v-chart :option="finalOption" autoresize class="echart" />
4
+ </div>
5
+ </template>
6
+
7
+ <script setup lang="ts">
8
+ import { computed } from 'vue'
9
+ import { use } from 'echarts/core'
10
+ import { CanvasRenderer } from 'echarts/renderers'
11
+ import { BarChart } from 'echarts/charts'
12
+ import {
13
+ TitleComponent,
14
+ TooltipComponent,
15
+ GridComponent,
16
+ LegendComponent,
17
+ } from 'echarts/components'
18
+ import VChart from 'vue-echarts'
19
+ import type { EChartsOption } from 'echarts'
20
+
21
+ // 注册 ECharts 组件
22
+ use([TitleComponent, TooltipComponent, GridComponent, LegendComponent, BarChart, CanvasRenderer])
23
+
24
+ // 定义标准的 Props,不含任何业务逻辑 ID
25
+ const props = defineProps<{
26
+ // 基础数据 Props
27
+ xAxisData?: string[]
28
+ seriesNames?: string[]
29
+ seriesData?: number[][]
30
+
31
+ // 样式配置 Props
32
+ title?: string
33
+ colors?: string[]
34
+ barWidth?: string
35
+ borderRadius?: number
36
+ showTooltip?: boolean
37
+ showLegend?: boolean
38
+ legendPosition?: 'top' | 'bottom' | 'left' | 'right'
39
+ showGrid?: boolean
40
+ xAxisName?: string
41
+ yAxisName?: string
42
+ showXAxisLine?: boolean
43
+ showXAxisLabel?: boolean
44
+ showYAxisLine?: boolean
45
+ showYAxisLabel?: boolean
46
+ showLabel?: boolean
47
+
48
+ // 高级覆盖
49
+ option?: EChartsOption
50
+ }>()
51
+
52
+ // 默认值配置
53
+ const defaultXAxis = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
54
+ const defaultSeriesNames = ['Series 1', 'Series 2', 'Series 3']
55
+ const defaultSeriesData = [
56
+ [120, 132, 101, 134, 90, 230, 210],
57
+ [220, 182, 191, 234, 290, 330, 310],
58
+ [150, 232, 201, 154, 190, 330, 410],
59
+ ]
60
+ const defaultColors = ['#5470c6', '#91cc75', '#fac858']
61
+
62
+ // 计算最终 Option
63
+ const finalOption = computed<EChartsOption>(() => {
64
+ // 如果有高级配置 option,优先使用
65
+ if (props.option && Object.keys(props.option).length > 0) return props.option
66
+
67
+ const xAxisData = props.xAxisData && props.xAxisData.length ? props.xAxisData : defaultXAxis
68
+ const seriesNames =
69
+ props.seriesNames && props.seriesNames.length ? props.seriesNames : defaultSeriesNames
70
+ const seriesData =
71
+ props.seriesData && props.seriesData.length ? props.seriesData : defaultSeriesData
72
+ const colors = props.colors && props.colors.length ? props.colors : defaultColors
73
+
74
+ return {
75
+ color: colors,
76
+ // 标题
77
+ title: props.title
78
+ ? {
79
+ text: props.title,
80
+ left: 'center',
81
+ textStyle: {
82
+ fontSize: 16,
83
+ },
84
+ }
85
+ : undefined,
86
+
87
+ // 提示框
88
+ tooltip:
89
+ props.showTooltip !== false
90
+ ? {
91
+ trigger: 'axis',
92
+ axisPointer: {
93
+ type: 'shadow',
94
+ },
95
+ }
96
+ : undefined,
97
+
98
+ // 图例
99
+ legend:
100
+ props.showLegend !== false
101
+ ? {
102
+ [(props.legendPosition as string) || 'top']: 10,
103
+ data: seriesNames,
104
+ }
105
+ : undefined,
106
+
107
+ // 网格
108
+ grid: {
109
+ left: '6%',
110
+ right: '6%',
111
+ bottom: '8%',
112
+ top: props.title ? '15%' : '10%',
113
+ containLabel: true,
114
+ show: props.showGrid !== false,
115
+ },
116
+
117
+ // X 轴
118
+ xAxis: {
119
+ type: 'category',
120
+ data: xAxisData,
121
+ name: props.xAxisName || '',
122
+ nameLocation: 'middle',
123
+ nameGap: 30,
124
+ axisLine: {
125
+ show: props.showXAxisLine !== false,
126
+ },
127
+ axisLabel: {
128
+ show: props.showXAxisLabel !== false,
129
+ },
130
+ splitLine: {
131
+ show: false,
132
+ },
133
+ },
134
+
135
+ // Y 轴
136
+ yAxis: {
137
+ type: 'value',
138
+ name: props.yAxisName || '',
139
+ nameLocation: 'middle',
140
+ nameGap: 50,
141
+ axisLine: {
142
+ show: props.showYAxisLine !== false,
143
+ },
144
+ axisLabel: {
145
+ show: props.showYAxisLabel !== false,
146
+ },
147
+ splitLine: {
148
+ show: props.showGrid !== false,
149
+ },
150
+ },
151
+
152
+ // 系列
153
+ series: seriesData.map((data, index) => ({
154
+ name: seriesNames[index] || `Series ${index + 1}`,
155
+ type: 'bar',
156
+ stack: 'total',
157
+ data: data,
158
+ barWidth: props.barWidth || '60%',
159
+ itemStyle: {
160
+ borderRadius: index === seriesData.length - 1 ? props.borderRadius || 0 : 0,
161
+ },
162
+ label: props.showLabel
163
+ ? {
164
+ show: true,
165
+ position: 'inside',
166
+ fontSize: 12,
167
+ }
168
+ : undefined,
169
+ })),
170
+ }
171
+ })
172
+ </script>
173
+
174
+ <style scoped>
175
+ .v-stacked-bar-chart {
176
+ width: 100%;
177
+ height: 100%;
178
+ }
179
+
180
+ .echart {
181
+ width: 100%;
182
+ height: 100%;
183
+ }
184
+ </style>
@@ -0,0 +1,104 @@
1
+ <template>
2
+ <div :style="containerStyle" class="html-container" v-html="sanitizedHtml"></div>
3
+ </template>
4
+
5
+ <script setup lang="ts">
6
+ import { computed } from 'vue'
7
+ import type { CSSProperties } from 'vue'
8
+ import DOMPurify from 'dompurify'
9
+
10
+ const props = withDefaults(
11
+ defineProps<{
12
+ content?: string
13
+ sanitize?: boolean
14
+ allowedTags?: string
15
+ allowedAttributes?: string
16
+ padding?: number
17
+ backgroundColor?: string
18
+ textColor?: string
19
+ fontSize?: number
20
+ lineHeight?: number | string
21
+ borderRadius?: number
22
+ border?: string
23
+ overflow?: string
24
+ fontFamily?: string
25
+ }>(),
26
+ {
27
+ content: '<p>请输入 HTML 内容...</p>',
28
+ sanitize: true,
29
+ allowedTags: undefined,
30
+ allowedAttributes: undefined,
31
+ padding: 16,
32
+ backgroundColor: '#ffffff',
33
+ textColor: '#333333',
34
+ fontSize: 14,
35
+ lineHeight: 1.6,
36
+ borderRadius: 0,
37
+ border: 'none',
38
+ overflow: 'auto',
39
+ fontFamily: 'inherit',
40
+ },
41
+ )
42
+
43
+ // 清理后的 HTML
44
+ const sanitizedHtml = computed(() => {
45
+ if (props.sanitize) {
46
+ return DOMPurify.sanitize(props.content, {
47
+ ALLOWED_TAGS: props.allowedTags ? props.allowedTags.split(',') : undefined,
48
+ ALLOWED_ATTR: props.allowedAttributes ? props.allowedAttributes.split(',') : undefined,
49
+ })
50
+ }
51
+ return props.content
52
+ })
53
+
54
+ // 样式
55
+ const containerStyle = computed<CSSProperties>(() => ({
56
+ width: '100%',
57
+ height: '100%',
58
+ padding: `${props.padding}px`,
59
+ backgroundColor: props.backgroundColor,
60
+ color: props.textColor,
61
+ fontSize: `${props.fontSize}px`,
62
+ lineHeight: String(props.lineHeight),
63
+ borderRadius: `${props.borderRadius}px`,
64
+ border: props.border,
65
+ overflow: props.overflow,
66
+ fontFamily: props.fontFamily,
67
+ }))
68
+ </script>
69
+
70
+ <style scoped>
71
+ .html-container {
72
+ box-sizing: border-box;
73
+ }
74
+
75
+ /* 基础样式重置 */
76
+ .html-container :deep(*) {
77
+ box-sizing: border-box;
78
+ }
79
+
80
+ .html-container :deep(img) {
81
+ max-width: 100%;
82
+ height: auto;
83
+ }
84
+
85
+ .html-container :deep(table) {
86
+ border-collapse: collapse;
87
+ width: 100%;
88
+ }
89
+
90
+ .html-container :deep(table th),
91
+ .html-container :deep(table td) {
92
+ padding: 8px;
93
+ border: 1px solid #ddd;
94
+ }
95
+
96
+ .html-container :deep(a) {
97
+ color: #409eff;
98
+ text-decoration: none;
99
+ }
100
+
101
+ .html-container :deep(a:hover) {
102
+ text-decoration: underline;
103
+ }
104
+ </style>
@@ -0,0 +1,111 @@
1
+ <template>
2
+ <div :style="containerStyle" class="iframe-container">
3
+ <iframe
4
+ v-if="url"
5
+ :src="url"
6
+ :style="iframeStyle"
7
+ :title="title"
8
+ :sandbox="sandbox"
9
+ :allow="allow"
10
+ loading="lazy"
11
+ referrerpolicy="no-referrer-when-downgrade"
12
+ ></iframe>
13
+ <!-- 遮罩层:防止 iframe 拦截鼠标事件(编辑模式) -->
14
+ <div v-if="url && showMask" class="iframe-mask"></div>
15
+ <div v-else-if="!url" class="iframe-placeholder">
16
+ <el-icon><Link /></el-icon>
17
+ <span>{{ placeholder }}</span>
18
+ </div>
19
+ </div>
20
+ </template>
21
+
22
+ <script setup lang="ts">
23
+ import { computed } from 'vue'
24
+ import type { CSSProperties } from 'vue'
25
+ import { Link } from '@element-plus/icons-vue'
26
+ import { ElIcon } from 'element-plus'
27
+
28
+ const props = withDefaults(
29
+ defineProps<{
30
+ url?: string
31
+ title?: string
32
+ sandbox?: string
33
+ allow?: string
34
+ placeholder?: string
35
+ showMask?: boolean
36
+ backgroundColor?: string
37
+ borderRadius?: number
38
+ border?: string
39
+ opacity?: number
40
+ }>(),
41
+ {
42
+ url: '',
43
+ title: 'iframe',
44
+ sandbox: undefined,
45
+ allow: undefined,
46
+ placeholder: '请设置 iframe 地址',
47
+ showMask: true,
48
+ backgroundColor: '#ffffff',
49
+ borderRadius: 0,
50
+ border: '1px solid #dcdfe6',
51
+ opacity: 100,
52
+ },
53
+ )
54
+
55
+ // 样式
56
+ const containerStyle = computed<CSSProperties>(() => ({
57
+ width: '100%',
58
+ height: '100%',
59
+ backgroundColor: props.backgroundColor,
60
+ borderRadius: `${props.borderRadius}px`,
61
+ overflow: 'hidden',
62
+ border: props.border,
63
+ }))
64
+
65
+ const iframeStyle = computed<CSSProperties>(() => ({
66
+ width: '100%',
67
+ height: '100%',
68
+ border: 'none',
69
+ opacity: props.opacity / 100,
70
+ }))
71
+ </script>
72
+
73
+ <style scoped>
74
+ .iframe-container {
75
+ position: relative;
76
+ }
77
+
78
+ .iframe-mask {
79
+ position: absolute;
80
+ top: 0;
81
+ left: 0;
82
+ width: 100%;
83
+ height: 100%;
84
+ background: transparent;
85
+ z-index: 1;
86
+ pointer-events: auto;
87
+ }
88
+
89
+ /* 运行时模式下移除遮罩 */
90
+ :global(.runtime-mode) .iframe-mask {
91
+ display: none;
92
+ }
93
+
94
+ .iframe-placeholder {
95
+ display: flex;
96
+ flex-direction: column;
97
+ justify-content: center;
98
+ align-items: center;
99
+ width: 100%;
100
+ height: 100%;
101
+ color: #909399;
102
+ font-size: 14px;
103
+ gap: 8px;
104
+ background-color: #f5f7fa;
105
+ }
106
+
107
+ .iframe-placeholder .el-icon {
108
+ font-size: 48px;
109
+ color: #c0c4cc;
110
+ }
111
+ </style>