Commit ac4f27b6ac5acf57dfa042d73c7a5130287cf478
Merge branch 'ft' into 'main_dev'
feat(packages/external): 重写部分柱状图和部分折线图,新增开启关闭动画效果和全屏效果 See merge request yunteng/thingskit-view!66
Showing
37 changed files
with
1909 additions
and
65 deletions
1 | +import { echartOptionProfixHandle, PublicConfigClass } from '@/packages/public' | |
2 | +import { OverrideBarCommonConfig } from './index' | |
3 | +import { CreateComponentType } from '@/packages/index.d' | |
4 | +import cloneDeep from 'lodash/cloneDeep' | |
5 | +import dataJson from './data.json' | |
6 | + | |
7 | +export const includes = ['legend', 'xAxis', 'yAxis', 'grid'] | |
8 | +// 其它配置项比如新增(动画) | |
9 | +const otherConfig = { | |
10 | + // 轮播动画 | |
11 | + isCarousel: false | |
12 | +} | |
13 | +export const seriesItem = { | |
14 | + type: 'bar', | |
15 | + barWidth: 15, | |
16 | + label: { | |
17 | + show: true, | |
18 | + position: 'top', | |
19 | + color: '#fff', | |
20 | + fontSize: 12 | |
21 | + }, | |
22 | + itemStyle: { | |
23 | + color: null, | |
24 | + borderRadius: 2 | |
25 | + } | |
26 | +} | |
27 | +export const option = { | |
28 | + ...otherConfig, | |
29 | + tooltip: { | |
30 | + show: true, | |
31 | + trigger: 'axis', | |
32 | + axisPointer: { | |
33 | + show: true, | |
34 | + type: 'shadow' | |
35 | + } | |
36 | + }, | |
37 | + xAxis: { | |
38 | + show: true, | |
39 | + type: 'category' | |
40 | + }, | |
41 | + yAxis: { | |
42 | + show: true, | |
43 | + type: 'value' | |
44 | + }, | |
45 | + dataset: { ...dataJson }, | |
46 | + series: [seriesItem, seriesItem] | |
47 | +} | |
48 | + | |
49 | +export default class Config extends PublicConfigClass implements CreateComponentType { | |
50 | + public key = OverrideBarCommonConfig.key | |
51 | + public chartConfig = cloneDeep(OverrideBarCommonConfig) | |
52 | + // 图表配置项 | |
53 | + public option = echartOptionProfixHandle(option, includes) | |
54 | +} | ... | ... |
1 | +<template> | |
2 | + <!-- Echarts 全局设置 --> | |
3 | + <global-setting :optionData="optionData"></global-setting> | |
4 | + <CollapseItem v-for="(item, index) in seriesList" :key="index" :name="`柱状图-${index + 1}`" :expanded="true"> | |
5 | + <SettingItemBox name="图形"> | |
6 | + <SettingItem name="宽度"> | |
7 | + <n-input-number | |
8 | + v-model:value="item.barWidth" | |
9 | + :min="1" | |
10 | + :max="100" | |
11 | + size="small" | |
12 | + placeholder="自动计算" | |
13 | + ></n-input-number> | |
14 | + </SettingItem> | |
15 | + <SettingItem name="圆角"> | |
16 | + <n-input-number v-model:value="item.itemStyle.borderRadius" :min="0" size="small"></n-input-number> | |
17 | + </SettingItem> | |
18 | + </SettingItemBox> | |
19 | + <SettingItemBox name="动画" :alone="true"> | |
20 | + <SettingItem> | |
21 | + <n-space> | |
22 | + <n-switch v-model:value="optionData.isCarousel" size="small"></n-switch> | |
23 | + <n-text>开启<n-text :depth="3">(将自动隐藏图例)</n-text></n-text> | |
24 | + </n-space> | |
25 | + </SettingItem> | |
26 | + <SettingItem> | |
27 | + <n-text :depth="3">无鼠标点击图例场景时,可强行打开图例</n-text> | |
28 | + </SettingItem> | |
29 | + </SettingItemBox> | |
30 | + <setting-item-box name="标签"> | |
31 | + <setting-item> | |
32 | + <n-space> | |
33 | + <n-switch v-model:value="item.label.show" size="small" /> | |
34 | + <n-text>展示标签</n-text> | |
35 | + </n-space> | |
36 | + </setting-item> | |
37 | + <setting-item name="大小"> | |
38 | + <n-input-number v-model:value="item.label.fontSize" size="small" :min="1"></n-input-number> | |
39 | + </setting-item> | |
40 | + <setting-item name="颜色"> | |
41 | + <n-color-picker size="small" :modes="['hex']" v-model:value="item.label.color"></n-color-picker> | |
42 | + </setting-item> | |
43 | + <setting-item name="位置"> | |
44 | + <n-select | |
45 | + v-model:value="item.label.position" | |
46 | + :options="[ | |
47 | + { label: 'top', value: 'top' }, | |
48 | + { label: 'left', value: 'left' }, | |
49 | + { label: 'right', value: 'right' }, | |
50 | + { label: 'bottom', value: 'bottom' } | |
51 | + ]" | |
52 | + /> | |
53 | + </setting-item> | |
54 | + </setting-item-box> | |
55 | + </CollapseItem> | |
56 | +</template> | |
57 | + | |
58 | +<script setup lang="ts"> | |
59 | +import { PropType, computed } from 'vue' | |
60 | +import { GlobalSetting, CollapseItem, SettingItemBox, SettingItem } from '@/components/Pages/ChartItemSetting' | |
61 | +import { GlobalThemeJsonType } from '@/settings/chartThemes/index' | |
62 | + | |
63 | +const props = defineProps({ | |
64 | + optionData: { | |
65 | + type: Object as PropType<GlobalThemeJsonType>, | |
66 | + required: true | |
67 | + } | |
68 | +}) | |
69 | + | |
70 | +const seriesList = computed(() => { | |
71 | + return props.optionData.series | |
72 | +}) | |
73 | +</script> | ... | ... |
1 | +{ | |
2 | + "dimensions": ["product", "data1", "data2"], | |
3 | + "source": [ | |
4 | + { | |
5 | + "product": "Mon", | |
6 | + "data1": 120, | |
7 | + "data2": 130 | |
8 | + }, | |
9 | + { | |
10 | + "product": "Tue", | |
11 | + "data1": 200, | |
12 | + "data2": 130 | |
13 | + }, | |
14 | + { | |
15 | + "product": "Wed", | |
16 | + "data1": 150, | |
17 | + "data2": 312 | |
18 | + }, | |
19 | + { | |
20 | + "product": "Thu", | |
21 | + "data1": 80, | |
22 | + "data2": 268 | |
23 | + }, | |
24 | + { | |
25 | + "product": "Fri", | |
26 | + "data1": 70, | |
27 | + "data2": 155 | |
28 | + }, | |
29 | + { | |
30 | + "product": "Sat", | |
31 | + "data1": 110, | |
32 | + "data2": 117 | |
33 | + }, | |
34 | + { | |
35 | + "product": "Sun", | |
36 | + "data1": 130, | |
37 | + "data2": 160 | |
38 | + } | |
39 | + ] | |
40 | +} | ... | ... |
1 | +import { ConfigType, PackagesCategoryEnum, ChartFrameEnum } from '@/packages/index.d' | |
2 | +import { ChatCategoryEnum, ChatCategoryEnumName } from '@/packages/components/Charts/index.d' | |
3 | +import { useWidgetKey } from '@/packages/external/useWidgetKey' | |
4 | + | |
5 | +const { key, conKey, chartKey } = useWidgetKey('OverrideBarCommon', true) | |
6 | + | |
7 | +export const OverrideBarCommonConfig: ConfigType = { | |
8 | + key, | |
9 | + chartKey, | |
10 | + conKey, | |
11 | + title: '柱状图动画', | |
12 | + category: ChatCategoryEnum.BAR, | |
13 | + categoryName: ChatCategoryEnumName.BAR, | |
14 | + package: PackagesCategoryEnum.CHARTS, | |
15 | + chartFrame: ChartFrameEnum.ECHARTS, | |
16 | + image: 'bar_x.png' | |
17 | +} | ... | ... |
1 | +<template> | |
2 | + <v-chart | |
3 | + ref="vChartRef" | |
4 | + :init-options="initOptions" | |
5 | + :theme="themeColor" | |
6 | + :option="option" | |
7 | + :manual-update="isPreview()" | |
8 | + :update-options="{ | |
9 | + replaceMerge: replaceMergeArr | |
10 | + }" | |
11 | + autoresize | |
12 | + @mouseover="handleHighlight" | |
13 | + @mouseout="handleDownplay" | |
14 | + > | |
15 | + </v-chart> | |
16 | +</template> | |
17 | + | |
18 | +<script setup lang="ts"> | |
19 | +import { ref, nextTick, computed, watch, PropType, onMounted } from 'vue' | |
20 | +import VChart from 'vue-echarts' | |
21 | +import { useCanvasInitOptions } from '@/hooks/useCanvasInitOptions.hook' | |
22 | +import { use } from 'echarts/core' | |
23 | +import { CanvasRenderer } from 'echarts/renderers' | |
24 | +import { BarChart } from 'echarts/charts' | |
25 | +import config, { includes, seriesItem } from './config' | |
26 | +import { mergeTheme } from '@/packages/public/chart' | |
27 | +import { useChartDataFetch } from '@/hooks' | |
28 | +import { CreateComponentType } from '@/packages/index.d' | |
29 | +import { useChartEditStore } from '@/store/modules/chartEditStore/chartEditStore' | |
30 | +import { isPreview } from '@/utils' | |
31 | +import { DatasetComponent, GridComponent, TooltipComponent, LegendComponent } from 'echarts/components' | |
32 | +import isObject from 'lodash/isObject' | |
33 | +import dataJson from './data.json' | |
34 | +import { isFullScreen } from '../../utls/isFullScreen' | |
35 | + | |
36 | +const props = defineProps({ | |
37 | + themeSetting: { | |
38 | + type: Object, | |
39 | + required: true | |
40 | + }, | |
41 | + themeColor: { | |
42 | + type: Object, | |
43 | + required: true | |
44 | + }, | |
45 | + chartConfig: { | |
46 | + type: Object as PropType<config>, | |
47 | + required: true | |
48 | + } | |
49 | +}) | |
50 | + | |
51 | +const initOptions = useCanvasInitOptions(props.chartConfig.option, props.themeSetting) | |
52 | + | |
53 | +use([DatasetComponent, CanvasRenderer, BarChart, GridComponent, TooltipComponent, LegendComponent]) | |
54 | + | |
55 | +const replaceMergeArr = ref<string[]>() | |
56 | + | |
57 | +const option = computed(() => { | |
58 | + return mergeTheme(props.chartConfig.option, props.themeSetting, includes) | |
59 | +}) | |
60 | + | |
61 | +const toolBoxOption = { | |
62 | + show: true, | |
63 | + right: 20, | |
64 | + feature: { | |
65 | + myFullButton: { | |
66 | + show: true, | |
67 | + title: '全屏查看', | |
68 | + icon: 'path://M733.549304 0l116.434359 116.23452-226.402521 226.40252 57.053835 57.068109 226.459617-226.445342 120.616689 120.41685V0H733.549304zM689.513507 619.855586l-57.068108 57.068109 224.232847 224.232847-122.64362 122.843458h293.676657V729.838022l-114.007751 114.207588-224.190025-224.190024zM338.197775 404.144414l57.068109-57.068109L171.033037 122.843458 293.676657 0H0v294.161978l114.022025-114.207588 224.17575 224.190024zM347.076305 624.294851L120.616689 850.754468 0 730.323343v293.676657h294.161978l-116.420084-116.23452 226.40252-226.40252-57.068109-57.068109z', | |
69 | + onclick: () => { | |
70 | + const getEchartDom = vChartRef.value?.getDom() | |
71 | + const domName = document.getElementById(getEchartDom.id) as any | |
72 | + const htmlName = document.querySelector('html') as any | |
73 | + isFullScreen(domName, htmlName) | |
74 | + } | |
75 | + } | |
76 | + } | |
77 | +} | |
78 | +props.chartConfig.option = { | |
79 | + ...props.chartConfig.option, | |
80 | + ...{ toolbox: toolBoxOption } | |
81 | +} | |
82 | + | |
83 | +// dataset 无法变更条数的补丁 | |
84 | +watch( | |
85 | + () => props.chartConfig.option.dataset, | |
86 | + (newData: { dimensions: any }, oldData) => { | |
87 | + try { | |
88 | + if (!isObject(newData) || !('dimensions' in newData)) return | |
89 | + if (Array.isArray(newData?.dimensions)) { | |
90 | + const seriesArr = [] | |
91 | + for (let i = 0; i < newData.dimensions.length - 1; i++) { | |
92 | + seriesArr.push(seriesItem) | |
93 | + } | |
94 | + replaceMergeArr.value = ['series'] | |
95 | + props.chartConfig.option.series = seriesArr | |
96 | + nextTick(() => { | |
97 | + replaceMergeArr.value = [] | |
98 | + }) | |
99 | + } | |
100 | + } catch (error) { | |
101 | + console.log(error) | |
102 | + } | |
103 | + }, | |
104 | + { | |
105 | + deep: false | |
106 | + } | |
107 | +) | |
108 | + | |
109 | +let seriesDataNum = -1 | |
110 | +let seriesDataMaxLength = 0 | |
111 | +let intervalInstance: any = null | |
112 | +//轮播时长 | |
113 | +const duration = 1500 | |
114 | + | |
115 | +// 会重新选择需要选中和展示的数据 | |
116 | +const handleSeriesData = () => { | |
117 | + if (seriesDataNum > -1) { | |
118 | + vChartRef.value?.dispatchAction({ | |
119 | + type: 'downplay', | |
120 | + dataIndex: seriesDataNum | |
121 | + }) | |
122 | + } | |
123 | + seriesDataNum = seriesDataNum >= seriesDataMaxLength - 1 ? 0 : seriesDataNum + 1 | |
124 | + vChartRef.value?.dispatchAction({ | |
125 | + type: 'showTip', | |
126 | + seriesIndex: 0, | |
127 | + dataIndex: seriesDataNum | |
128 | + }) | |
129 | +} | |
130 | + | |
131 | +// 新增轮播 | |
132 | +const addPieInterval = (newData?: typeof dataJson, skipPre = false) => { | |
133 | + if (!skipPre && !Array.isArray(newData?.source)) return | |
134 | + if (!skipPre) seriesDataMaxLength = newData?.source.length || 0 | |
135 | + clearInterval(intervalInstance) | |
136 | + intervalInstance = setInterval(() => { | |
137 | + handleSeriesData() | |
138 | + }, duration) | |
139 | +} | |
140 | + | |
141 | +// 取消轮播 | |
142 | +const clearPieInterval = () => { | |
143 | + vChartRef.value?.dispatchAction({ | |
144 | + type: 'hideTip', | |
145 | + seriesIndex: 0, | |
146 | + dataIndex: seriesDataNum | |
147 | + }) | |
148 | + vChartRef.value?.dispatchAction({ | |
149 | + type: 'downplay', | |
150 | + dataIndex: seriesDataNum | |
151 | + }) | |
152 | + clearInterval(intervalInstance) | |
153 | + intervalInstance = null | |
154 | +} | |
155 | + | |
156 | +// 处理鼠标聚焦高亮内容 | |
157 | +const handleHighlight = () => { | |
158 | + clearPieInterval() | |
159 | +} | |
160 | + | |
161 | +// 处理鼠标取消悬浮 | |
162 | +const handleDownplay = () => { | |
163 | + if (props.chartConfig.option.isCarousel && !intervalInstance) { | |
164 | + // 恢复轮播 | |
165 | + addPieInterval(undefined, true) | |
166 | + } | |
167 | +} | |
168 | + | |
169 | +watch( | |
170 | + () => props.chartConfig.option.isCarousel, | |
171 | + newData => { | |
172 | + if (newData) { | |
173 | + addPieInterval(undefined, true) | |
174 | + props.chartConfig.option.legend.show = false | |
175 | + } else { | |
176 | + props.chartConfig.option.legend.show = true | |
177 | + clearPieInterval() | |
178 | + } | |
179 | + } | |
180 | +) | |
181 | + | |
182 | +const { vChartRef } = useChartDataFetch(props.chartConfig, useChartEditStore, (newData: typeof dataJson) => { | |
183 | + // addPieInterval(newData) | |
184 | +}) | |
185 | + | |
186 | +onMounted(() => { | |
187 | + seriesDataMaxLength = dataJson.source.length | |
188 | + if (props.chartConfig.option.isCarousel) { | |
189 | + addPieInterval(undefined, true) | |
190 | + } | |
191 | +}) | |
192 | +</script> | ... | ... |
1 | +import { echartOptionProfixHandle, PublicConfigClass } from '@/packages/public' | |
2 | +import { OverrideLineCommonConfig } from './index' | |
3 | +import { CreateComponentType } from '@/packages/index.d' | |
4 | +import cloneDeep from 'lodash/cloneDeep' | |
5 | +import dataJson from './data.json' | |
6 | + | |
7 | +export const includes = ['legend', 'xAxis', 'yAxis', 'grid'] | |
8 | +export const seriesItem = { | |
9 | + type: 'line', | |
10 | + label: { | |
11 | + show: true, | |
12 | + position: 'top', | |
13 | + color: '#fff', | |
14 | + fontSize: 12 | |
15 | + }, | |
16 | + symbolSize: 5, //设定实心点的大小 | |
17 | + itemStyle: { | |
18 | + color: null, | |
19 | + borderRadius: 0 | |
20 | + }, | |
21 | + lineStyle: { | |
22 | + type: 'solid', | |
23 | + width: 3, | |
24 | + color: null | |
25 | + } | |
26 | +} | |
27 | +// 其它配置项比如新增(动画) | |
28 | +const otherConfig = { | |
29 | + // 轮播动画 | |
30 | + isCarousel: false, | |
31 | +} | |
32 | +export const option = { | |
33 | + ...otherConfig, | |
34 | + tooltip: { | |
35 | + show: true, | |
36 | + trigger: 'axis', | |
37 | + axisPointer: { | |
38 | + type: 'line' | |
39 | + } | |
40 | + }, | |
41 | + xAxis: { | |
42 | + show: true, | |
43 | + type: 'category' | |
44 | + }, | |
45 | + yAxis: { | |
46 | + show: true, | |
47 | + type: 'value' | |
48 | + }, | |
49 | + dataset: { ...dataJson }, | |
50 | + series: [seriesItem, seriesItem] | |
51 | +} | |
52 | + | |
53 | +export default class Config extends PublicConfigClass implements CreateComponentType { | |
54 | + public key: string = OverrideLineCommonConfig.key | |
55 | + public chartConfig = cloneDeep(OverrideLineCommonConfig) | |
56 | + // 图表配置项 | |
57 | + public option = echartOptionProfixHandle(option, includes) | |
58 | +} | ... | ... |
1 | +<template> | |
2 | + <!-- Echarts 全局设置 --> | |
3 | + <global-setting :optionData="optionData"></global-setting> | |
4 | + <CollapseItem v-for="(item, index) in seriesList" :key="index" :name="`折线图-${index + 1}`" :expanded="true"> | |
5 | + <SettingItemBox name="线条"> | |
6 | + <SettingItem name="宽度"> | |
7 | + <n-input-number | |
8 | + v-model:value="item.lineStyle.width" | |
9 | + :min="1" | |
10 | + :max="100" | |
11 | + size="small" | |
12 | + placeholder="自动计算" | |
13 | + ></n-input-number> | |
14 | + </SettingItem> | |
15 | + <SettingItem name="类型"> | |
16 | + <n-select v-model:value="item.lineStyle.type" size="small" :options="lineConf.lineStyle.type"></n-select> | |
17 | + </SettingItem> | |
18 | + </SettingItemBox> | |
19 | + <SettingItemBox name="动画" :alone="true"> | |
20 | + <SettingItem> | |
21 | + <n-space> | |
22 | + <n-switch v-model:value="optionData.isCarousel" size="small"></n-switch> | |
23 | + <n-text>开启<n-text :depth="3">(将自动隐藏图例)</n-text></n-text> | |
24 | + </n-space> | |
25 | + </SettingItem> | |
26 | + <SettingItem> | |
27 | + <n-text :depth="3">无鼠标点击图例场景时,可强行打开图例</n-text> | |
28 | + </SettingItem> | |
29 | + </SettingItemBox> | |
30 | + <SettingItemBox name="实心点"> | |
31 | + <SettingItem name="大小"> | |
32 | + <n-input-number | |
33 | + v-model:value="item.symbolSize" | |
34 | + :min="1" | |
35 | + :max="100" | |
36 | + size="small" | |
37 | + placeholder="自动计算" | |
38 | + ></n-input-number> | |
39 | + </SettingItem> | |
40 | + </SettingItemBox> | |
41 | + <setting-item-box name="标签"> | |
42 | + <setting-item> | |
43 | + <n-space> | |
44 | + <n-switch v-model:value="item.label.show" size="small" /> | |
45 | + <n-text>展示标签</n-text> | |
46 | + </n-space> | |
47 | + </setting-item> | |
48 | + <setting-item name="大小"> | |
49 | + <n-input-number v-model:value="item.label.fontSize" size="small" :min="1"></n-input-number> | |
50 | + </setting-item> | |
51 | + <setting-item name="颜色"> | |
52 | + <n-color-picker size="small" :modes="['hex']" v-model:value="item.label.color"></n-color-picker> | |
53 | + </setting-item> | |
54 | + <setting-item name="位置"> | |
55 | + <n-select | |
56 | + v-model:value="item.label.position" | |
57 | + :options="[ | |
58 | + { label: 'top', value: 'top' }, | |
59 | + { label: 'left', value: 'left' }, | |
60 | + { label: 'right', value: 'right' }, | |
61 | + { label: 'bottom', value: 'bottom' } | |
62 | + ]" | |
63 | + /> | |
64 | + </setting-item> | |
65 | + </setting-item-box> | |
66 | + </CollapseItem> | |
67 | +</template> | |
68 | + | |
69 | +<script setup lang="ts"> | |
70 | +import { PropType, computed } from 'vue' | |
71 | +import { lineConf } from '@/packages/chartConfiguration/echarts/index' | |
72 | +import { GlobalThemeJsonType } from '@/settings/chartThemes/index' | |
73 | +import { GlobalSetting, CollapseItem, SettingItemBox, SettingItem } from '@/components/Pages/ChartItemSetting' | |
74 | + | |
75 | +const props = defineProps({ | |
76 | + optionData: { | |
77 | + type: Object as PropType<GlobalThemeJsonType>, | |
78 | + required: true | |
79 | + } | |
80 | +}) | |
81 | + | |
82 | +const seriesList = computed(() => { | |
83 | + return props.optionData.series | |
84 | +}) | |
85 | +</script> | ... | ... |
1 | +{ | |
2 | + "dimensions": ["product", "data1", "data2"], | |
3 | + "source": [ | |
4 | + { | |
5 | + "product": "Mon", | |
6 | + "data1": 120, | |
7 | + "data2": 130 | |
8 | + }, | |
9 | + { | |
10 | + "product": "Tue", | |
11 | + "data1": 200, | |
12 | + "data2": 130 | |
13 | + }, | |
14 | + { | |
15 | + "product": "Wed", | |
16 | + "data1": 150, | |
17 | + "data2": 312 | |
18 | + }, | |
19 | + { | |
20 | + "product": "Thu", | |
21 | + "data1": 80, | |
22 | + "data2": 268 | |
23 | + }, | |
24 | + { | |
25 | + "product": "Fri", | |
26 | + "data1": 70, | |
27 | + "data2": 155 | |
28 | + }, | |
29 | + { | |
30 | + "product": "Sat", | |
31 | + "data1": 110, | |
32 | + "data2": 117 | |
33 | + }, | |
34 | + { | |
35 | + "product": "Sun", | |
36 | + "data1": 130, | |
37 | + "data2": 160 | |
38 | + } | |
39 | + ] | |
40 | +} | ... | ... |
1 | +import { ConfigType, PackagesCategoryEnum, ChartFrameEnum } from '@/packages/index.d' | |
2 | +import { ChatCategoryEnum, ChatCategoryEnumName } from '@/packages/components/Charts/index.d' | |
3 | +import { useWidgetKey } from '@/packages/external/useWidgetKey' | |
4 | + | |
5 | +const { key, conKey, chartKey } = useWidgetKey('OverrideLineCommon', true) | |
6 | + | |
7 | +export const OverrideLineCommonConfig: ConfigType = { | |
8 | + key, | |
9 | + chartKey, | |
10 | + conKey, | |
11 | + title: '折线图动画', | |
12 | + category: ChatCategoryEnum.LINE, | |
13 | + categoryName: ChatCategoryEnumName.LINE, | |
14 | + package: PackagesCategoryEnum.CHARTS, | |
15 | + chartFrame: ChartFrameEnum.ECHARTS, | |
16 | + image: 'line.png' | |
17 | +} | ... | ... |
1 | +<template> | |
2 | + <v-chart | |
3 | + ref="vChartRef" | |
4 | + :init-options="initOptions" | |
5 | + :theme="themeColor" | |
6 | + :option="option" | |
7 | + :manual-update="isPreview()" | |
8 | + :update-options="{ | |
9 | + replaceMerge: replaceMergeArr | |
10 | + }" | |
11 | + autoresize | |
12 | + @mouseover="handleHighlight" | |
13 | + @mouseout="handleDownplay" | |
14 | + > | |
15 | + </v-chart> | |
16 | +</template> | |
17 | + | |
18 | +<script setup lang="ts"> | |
19 | +import { PropType, computed, watch, ref, nextTick, onMounted } from 'vue' | |
20 | +import VChart from 'vue-echarts' | |
21 | +import { useCanvasInitOptions } from '@/hooks/useCanvasInitOptions.hook' | |
22 | +import { use } from 'echarts/core' | |
23 | +import { CanvasRenderer } from 'echarts/renderers' | |
24 | +import { LineChart } from 'echarts/charts' | |
25 | +import config, { includes, seriesItem } from './config' | |
26 | +import { mergeTheme } from '@/packages/public/chart' | |
27 | +import { useChartEditStore } from '@/store/modules/chartEditStore/chartEditStore' | |
28 | +import { useChartDataFetch } from '@/hooks' | |
29 | +import { isPreview } from '@/utils' | |
30 | +import { DatasetComponent, GridComponent, TooltipComponent, LegendComponent } from 'echarts/components' | |
31 | +import isObject from 'lodash/isObject' | |
32 | +import dataJson from './data.json' | |
33 | +import { isFullScreen } from '../../utls/isFullScreen' | |
34 | + | |
35 | +const props = defineProps({ | |
36 | + themeSetting: { | |
37 | + type: Object, | |
38 | + required: true | |
39 | + }, | |
40 | + themeColor: { | |
41 | + type: Object, | |
42 | + required: true | |
43 | + }, | |
44 | + chartConfig: { | |
45 | + type: Object as PropType<config>, | |
46 | + required: true | |
47 | + } | |
48 | +}) | |
49 | + | |
50 | +const initOptions = useCanvasInitOptions(props.chartConfig.option, props.themeSetting) | |
51 | + | |
52 | +use([DatasetComponent, CanvasRenderer, LineChart, GridComponent, TooltipComponent, LegendComponent]) | |
53 | + | |
54 | +const replaceMergeArr = ref<string[]>() | |
55 | + | |
56 | +const option = computed(() => { | |
57 | + return mergeTheme(props.chartConfig.option, props.themeSetting, includes) | |
58 | +}) | |
59 | + | |
60 | +const toolBoxOption = { | |
61 | + show: true, | |
62 | + right: 20, | |
63 | + feature: { | |
64 | + myFullButton: { | |
65 | + show: true, | |
66 | + title: '全屏查看', | |
67 | + icon: 'path://M733.549304 0l116.434359 116.23452-226.402521 226.40252 57.053835 57.068109 226.459617-226.445342 120.616689 120.41685V0H733.549304zM689.513507 619.855586l-57.068108 57.068109 224.232847 224.232847-122.64362 122.843458h293.676657V729.838022l-114.007751 114.207588-224.190025-224.190024zM338.197775 404.144414l57.068109-57.068109L171.033037 122.843458 293.676657 0H0v294.161978l114.022025-114.207588 224.17575 224.190024zM347.076305 624.294851L120.616689 850.754468 0 730.323343v293.676657h294.161978l-116.420084-116.23452 226.40252-226.40252-57.068109-57.068109z', | |
68 | + onclick: () => { | |
69 | + const getEchartDom = vChartRef.value?.getDom() | |
70 | + const domName = document.getElementById(getEchartDom.id) as any | |
71 | + const htmlName = document.querySelector('html') as any | |
72 | + isFullScreen(domName, htmlName) | |
73 | + } | |
74 | + } | |
75 | + } | |
76 | +} | |
77 | +props.chartConfig.option = { | |
78 | + ...props.chartConfig.option, | |
79 | + ...{ toolbox: toolBoxOption } | |
80 | +} | |
81 | + | |
82 | +// dataset 无法变更条数的补丁 | |
83 | +watch( | |
84 | + () => props.chartConfig.option.dataset, | |
85 | + (newData: { dimensions: any }, oldData) => { | |
86 | + try { | |
87 | + if (!isObject(newData) || !('dimensions' in newData)) return | |
88 | + if (Array.isArray(newData?.dimensions)) { | |
89 | + const seriesArr = [] | |
90 | + for (let i = 0; i < newData.dimensions.length - 1; i++) { | |
91 | + seriesArr.push(seriesItem) | |
92 | + } | |
93 | + replaceMergeArr.value = ['series'] | |
94 | + props.chartConfig.option.series = seriesArr | |
95 | + nextTick(() => { | |
96 | + replaceMergeArr.value = [] | |
97 | + }) | |
98 | + } | |
99 | + } catch (error) { | |
100 | + console.log(error) | |
101 | + } | |
102 | + }, | |
103 | + { | |
104 | + deep: false | |
105 | + } | |
106 | +) | |
107 | + | |
108 | +let seriesDataNum = -1 | |
109 | +let seriesDataMaxLength = 0 | |
110 | +let intervalInstance: any = null | |
111 | +const duration = 1500 | |
112 | + | |
113 | +// 会重新选择需要选中和展示的数据 | |
114 | +const handleSeriesData = () => { | |
115 | + if (seriesDataNum > -1) { | |
116 | + vChartRef.value?.dispatchAction({ | |
117 | + type: 'downplay', | |
118 | + dataIndex: seriesDataNum | |
119 | + }) | |
120 | + } | |
121 | + seriesDataNum = seriesDataNum >= seriesDataMaxLength - 1 ? 0 : seriesDataNum + 1 | |
122 | + vChartRef.value?.dispatchAction({ | |
123 | + type: 'showTip', | |
124 | + seriesIndex: 0, | |
125 | + dataIndex: seriesDataNum | |
126 | + }) | |
127 | +} | |
128 | + | |
129 | +// 新增轮播 | |
130 | +const addPieInterval = (newData?: typeof dataJson, skipPre = false) => { | |
131 | + if (!skipPre && !Array.isArray(newData?.source)) return | |
132 | + if (!skipPre) seriesDataMaxLength = newData?.source.length || 0 | |
133 | + clearInterval(intervalInstance) | |
134 | + intervalInstance = setInterval(() => { | |
135 | + handleSeriesData() | |
136 | + }, duration) | |
137 | +} | |
138 | + | |
139 | +// 取消轮播 | |
140 | +const clearPieInterval = () => { | |
141 | + vChartRef.value?.dispatchAction({ | |
142 | + type: 'hideTip', | |
143 | + seriesIndex: 0, | |
144 | + dataIndex: seriesDataNum | |
145 | + }) | |
146 | + vChartRef.value?.dispatchAction({ | |
147 | + type: 'downplay', | |
148 | + dataIndex: seriesDataNum | |
149 | + }) | |
150 | + clearInterval(intervalInstance) | |
151 | + intervalInstance = null | |
152 | +} | |
153 | + | |
154 | +// 处理鼠标聚焦高亮内容 | |
155 | +const handleHighlight = () => { | |
156 | + clearPieInterval() | |
157 | +} | |
158 | + | |
159 | +// 处理鼠标取消悬浮 | |
160 | +const handleDownplay = () => { | |
161 | + if (props.chartConfig.option.isCarousel && !intervalInstance) { | |
162 | + // 恢复轮播 | |
163 | + addPieInterval(undefined, true) | |
164 | + } | |
165 | +} | |
166 | + | |
167 | +watch( | |
168 | + () => props.chartConfig.option.isCarousel, | |
169 | + newData => { | |
170 | + if (newData) { | |
171 | + addPieInterval(undefined, true) | |
172 | + props.chartConfig.option.legend.show = false | |
173 | + } else { | |
174 | + props.chartConfig.option.legend.show = true | |
175 | + clearPieInterval() | |
176 | + } | |
177 | + } | |
178 | +) | |
179 | + | |
180 | +const { vChartRef } = useChartDataFetch(props.chartConfig, useChartEditStore, (newData: typeof dataJson) => { | |
181 | + // addPieInterval(newData) | |
182 | +}) | |
183 | + | |
184 | +onMounted(() => { | |
185 | + seriesDataMaxLength = dataJson.source.length | |
186 | + if (props.chartConfig.option.isCarousel) { | |
187 | + addPieInterval(undefined, true) | |
188 | + } | |
189 | +}) | |
190 | +</script> | ... | ... |
1 | +import { echartOptionProfixHandle, PublicConfigClass } from '@/packages/public' | |
2 | +import { OverrideLineGradientsConfig } from './index' | |
3 | +import { CreateComponentType } from '@/packages/index.d' | |
4 | +import { graphic } from 'echarts/core' | |
5 | +import { defaultTheme, chartColorsSearch } from '@/settings/chartThemes/index' | |
6 | +import cloneDeep from 'lodash/cloneDeep' | |
7 | +import dataJson from './data.json' | |
8 | + | |
9 | +export const includes = ['legend', 'xAxis', 'yAxis', 'grid'] | |
10 | + | |
11 | +// 其它配置项比如新增(动画) | |
12 | +const otherConfig = { | |
13 | + // 轮播动画 | |
14 | + isCarousel: false, | |
15 | +} | |
16 | +const option = { | |
17 | + ...otherConfig, | |
18 | + tooltip: { | |
19 | + show: true, | |
20 | + trigger: 'axis', | |
21 | + axisPointer: { | |
22 | + type: 'line' | |
23 | + } | |
24 | + }, | |
25 | + xAxis: { | |
26 | + show: true, | |
27 | + type: 'category' | |
28 | + }, | |
29 | + yAxis: { | |
30 | + show: true, | |
31 | + type: 'value' | |
32 | + }, | |
33 | + dataset: { ...dataJson }, | |
34 | + series: [ | |
35 | + { | |
36 | + type: 'line', | |
37 | + smooth: false, | |
38 | + symbolSize: 5, //设定实心点的大小 | |
39 | + label: { | |
40 | + show: true, | |
41 | + position: 'top', | |
42 | + color: '#fff', | |
43 | + fontSize: 12 | |
44 | + }, | |
45 | + lineStyle: { | |
46 | + width: 3, | |
47 | + type: 'solid' | |
48 | + }, | |
49 | + areaStyle: { | |
50 | + opacity: 0.8, | |
51 | + color: new graphic.LinearGradient(0, 0, 0, 1, [ | |
52 | + { | |
53 | + offset: 0, | |
54 | + color: chartColorsSearch[defaultTheme][3] | |
55 | + }, | |
56 | + { | |
57 | + offset: 1, | |
58 | + color: 'rgba(0,0,0,0)' | |
59 | + } | |
60 | + ]) | |
61 | + } | |
62 | + }, | |
63 | + { | |
64 | + type: 'line', | |
65 | + smooth: false, | |
66 | + label: { | |
67 | + show: true, | |
68 | + position: 'top', | |
69 | + color: '#fff', | |
70 | + fontSize: 12 | |
71 | + }, | |
72 | + lineStyle: { | |
73 | + width: 3, | |
74 | + type: 'solid' | |
75 | + }, | |
76 | + areaStyle: { | |
77 | + opacity: 0.8, | |
78 | + color: new graphic.LinearGradient(0, 0, 0, 1, [ | |
79 | + { | |
80 | + offset: 0, | |
81 | + color: chartColorsSearch[defaultTheme][4] | |
82 | + }, | |
83 | + { | |
84 | + offset: 1, | |
85 | + color: 'rgba(0,0,0,0)' | |
86 | + } | |
87 | + ]) | |
88 | + } | |
89 | + } | |
90 | + ] | |
91 | +} | |
92 | + | |
93 | +export default class Config extends PublicConfigClass implements CreateComponentType { | |
94 | + public key: string = OverrideLineGradientsConfig.key | |
95 | + public chartConfig = cloneDeep(OverrideLineGradientsConfig) | |
96 | + // 图表配置项 | |
97 | + public option = echartOptionProfixHandle(option, includes) | |
98 | +} | ... | ... |
1 | +<template> | |
2 | + <!-- Echarts 全局设置 --> | |
3 | + <global-setting :optionData="optionData"></global-setting> | |
4 | + <CollapseItem | |
5 | + v-for="(item, index) in seriesList" | |
6 | + :key="index" | |
7 | + name="单折线面积图" | |
8 | + :expanded="true" | |
9 | + > | |
10 | + <SettingItemBox name="线条"> | |
11 | + <SettingItem name="宽度"> | |
12 | + <n-input-number | |
13 | + v-model:value="item.lineStyle.width" | |
14 | + :min="1" | |
15 | + size="small" | |
16 | + placeholder="自动计算" | |
17 | + ></n-input-number> | |
18 | + </SettingItem> | |
19 | + <SettingItem name="类型"> | |
20 | + <n-select | |
21 | + v-model:value="item.lineStyle.type" | |
22 | + size="small" | |
23 | + :options="lineConf.lineStyle.type" | |
24 | + ></n-select> | |
25 | + </SettingItem> | |
26 | + </SettingItemBox> | |
27 | + <SettingItemBox name="动画" :alone="true"> | |
28 | + <SettingItem> | |
29 | + <n-space> | |
30 | + <n-switch v-model:value="optionData.isCarousel" size="small"></n-switch> | |
31 | + <n-text>开启<n-text :depth="3">(将自动隐藏图例)</n-text></n-text> | |
32 | + </n-space> | |
33 | + </SettingItem> | |
34 | + <SettingItem> | |
35 | + <n-text :depth="3">无鼠标点击图例场景时,可强行打开图例</n-text> | |
36 | + </SettingItem> | |
37 | + </SettingItemBox> | |
38 | + <SettingItemBox name="实心点"> | |
39 | + <SettingItem name="大小"> | |
40 | + <n-input-number | |
41 | + v-model:value="item.symbolSize" | |
42 | + :min="1" | |
43 | + :max="100" | |
44 | + size="small" | |
45 | + placeholder="自动计算" | |
46 | + ></n-input-number> | |
47 | + </SettingItem> | |
48 | + </SettingItemBox> | |
49 | + <setting-item-box name="标签"> | |
50 | + <setting-item> | |
51 | + <n-space> | |
52 | + <n-switch v-model:value="item.label.show" size="small" /> | |
53 | + <n-text>展示标签</n-text> | |
54 | + </n-space> | |
55 | + </setting-item> | |
56 | + <setting-item name="大小"> | |
57 | + <n-input-number | |
58 | + v-model:value="item.label.fontSize" | |
59 | + size="small" | |
60 | + :min="1" | |
61 | + ></n-input-number> | |
62 | + </setting-item> | |
63 | + <setting-item name="颜色"> | |
64 | + <n-color-picker | |
65 | + size="small" | |
66 | + :modes="['hex']" | |
67 | + v-model:value="item.label.color" | |
68 | + ></n-color-picker> | |
69 | + </setting-item> | |
70 | + <setting-item name="位置"> | |
71 | + <n-select | |
72 | + v-model:value="item.label.position" | |
73 | + :options="[ | |
74 | + { label: 'top', value: 'top' }, | |
75 | + { label: 'left', value: 'left' }, | |
76 | + { label: 'right', value: 'right' }, | |
77 | + { label: 'bottom', value: 'bottom' }, | |
78 | + ]" | |
79 | + /> | |
80 | + </setting-item> | |
81 | + </setting-item-box> | |
82 | + </CollapseItem> | |
83 | +</template> | |
84 | + | |
85 | +<script setup lang="ts"> | |
86 | +import { PropType, computed } from 'vue' | |
87 | +import { lineConf } from '@/packages/chartConfiguration/echarts/index' | |
88 | +import { GlobalThemeJsonType } from '@/settings/chartThemes/index' | |
89 | +import { | |
90 | + GlobalSetting, | |
91 | + CollapseItem, | |
92 | + SettingItemBox, | |
93 | + SettingItem | |
94 | +} from '@/components/Pages/ChartItemSetting' | |
95 | + | |
96 | +const props = defineProps({ | |
97 | + optionData: { | |
98 | + type: Object as PropType<GlobalThemeJsonType>, | |
99 | + required: true | |
100 | + }, | |
101 | +}) | |
102 | + | |
103 | +const seriesList = computed(() => { | |
104 | + return props.optionData.series | |
105 | +}) | |
106 | +</script> | ... | ... |
1 | +{ | |
2 | + "dimensions": ["product", "data1", "data2"], | |
3 | + "source": [ | |
4 | + { | |
5 | + "product": "Mon", | |
6 | + "data1": 120, | |
7 | + "data2": 130 | |
8 | + }, | |
9 | + { | |
10 | + "product": "Tue", | |
11 | + "data1": 200, | |
12 | + "data2": 130 | |
13 | + }, | |
14 | + { | |
15 | + "product": "Wed", | |
16 | + "data1": 150, | |
17 | + "data2": 312 | |
18 | + }, | |
19 | + { | |
20 | + "product": "Thu", | |
21 | + "data1": 80, | |
22 | + "data2": 268 | |
23 | + }, | |
24 | + { | |
25 | + "product": "Fri", | |
26 | + "data1": 70, | |
27 | + "data2": 155 | |
28 | + }, | |
29 | + { | |
30 | + "product": "Sat", | |
31 | + "data1": 110, | |
32 | + "data2": 117 | |
33 | + }, | |
34 | + { | |
35 | + "product": "Sun", | |
36 | + "data1": 130, | |
37 | + "data2": 160 | |
38 | + } | |
39 | + ] | |
40 | +} | ... | ... |
1 | +import { ConfigType, PackagesCategoryEnum, ChartFrameEnum } from '@/packages/index.d' | |
2 | +import { ChatCategoryEnum, ChatCategoryEnumName } from '@/packages/components/Charts/index.d' | |
3 | +import { useWidgetKey } from '@/packages/external/useWidgetKey' | |
4 | + | |
5 | +const { key, conKey, chartKey } = useWidgetKey('OverrideLineGradients', true) | |
6 | + | |
7 | +export const OverrideLineGradientsConfig: ConfigType = { | |
8 | + key, | |
9 | + chartKey, | |
10 | + conKey, | |
11 | + title: '双折线渐变面积图动画', | |
12 | + category: ChatCategoryEnum.LINE, | |
13 | + categoryName: ChatCategoryEnumName.LINE, | |
14 | + package: PackagesCategoryEnum.CHARTS, | |
15 | + chartFrame: ChartFrameEnum.ECHARTS, | |
16 | + image: 'line_gradient.png' | |
17 | +} | ... | ... |
1 | +<template> | |
2 | + <v-chart | |
3 | + ref="vChartRef" | |
4 | + :init-options="initOptions" | |
5 | + :theme="themeColor" | |
6 | + :option="option.value" | |
7 | + :manual-update="isPreview()" | |
8 | + autoresize | |
9 | + @mouseover="handleHighlight" | |
10 | + @mouseout="handleDownplay" | |
11 | + ></v-chart> | |
12 | +</template> | |
13 | + | |
14 | +<script setup lang="ts"> | |
15 | +import { reactive, watch, PropType, onMounted } from 'vue' | |
16 | +import VChart from 'vue-echarts' | |
17 | +import { useCanvasInitOptions } from '@/hooks/useCanvasInitOptions.hook' | |
18 | +import { use, graphic } from 'echarts/core' | |
19 | +import { CanvasRenderer } from 'echarts/renderers' | |
20 | +import { LineChart } from 'echarts/charts' | |
21 | +import config, { includes } from './config' | |
22 | +import { mergeTheme } from '@/packages/public/chart' | |
23 | +import { useChartEditStore } from '@/store/modules/chartEditStore/chartEditStore' | |
24 | +import { chartColorsSearch, defaultTheme } from '@/settings/chartThemes/index' | |
25 | +import { DatasetComponent, GridComponent, TooltipComponent, LegendComponent } from 'echarts/components' | |
26 | +import { useChartDataFetch } from '@/hooks' | |
27 | +import { isPreview, colorGradientCustomMerge } from '@/utils' | |
28 | +import dataJson from './data.json' | |
29 | +import { isFullScreen } from '../../utls/isFullScreen' | |
30 | + | |
31 | +const props = defineProps({ | |
32 | + themeSetting: { | |
33 | + type: Object, | |
34 | + required: true | |
35 | + }, | |
36 | + themeColor: { | |
37 | + type: Object, | |
38 | + required: true | |
39 | + }, | |
40 | + chartConfig: { | |
41 | + type: Object as PropType<config>, | |
42 | + required: true | |
43 | + } | |
44 | +}) | |
45 | + | |
46 | +const initOptions = useCanvasInitOptions(props.chartConfig.option, props.themeSetting) | |
47 | + | |
48 | +use([DatasetComponent, CanvasRenderer, LineChart, GridComponent, TooltipComponent, LegendComponent]) | |
49 | +const chartEditStore = useChartEditStore() | |
50 | + | |
51 | +const option = reactive({ | |
52 | + value: {} | |
53 | +}) | |
54 | +const toolBoxOption = { | |
55 | + show: true, | |
56 | + right: 20, | |
57 | + feature: { | |
58 | + myFullButton: { | |
59 | + show: true, | |
60 | + title: '全屏查看', | |
61 | + icon: 'path://M733.549304 0l116.434359 116.23452-226.402521 226.40252 57.053835 57.068109 226.459617-226.445342 120.616689 120.41685V0H733.549304zM689.513507 619.855586l-57.068108 57.068109 224.232847 224.232847-122.64362 122.843458h293.676657V729.838022l-114.007751 114.207588-224.190025-224.190024zM338.197775 404.144414l57.068109-57.068109L171.033037 122.843458 293.676657 0H0v294.161978l114.022025-114.207588 224.17575 224.190024zM347.076305 624.294851L120.616689 850.754468 0 730.323343v293.676657h294.161978l-116.420084-116.23452 226.40252-226.40252-57.068109-57.068109z', | |
62 | + onclick: () => { | |
63 | + const getEchartDom = vChartRef.value?.getDom() | |
64 | + const domName = document.getElementById(getEchartDom.id) as any | |
65 | + const htmlName = document.querySelector('html') as any | |
66 | + isFullScreen(domName, htmlName) | |
67 | + } | |
68 | + } | |
69 | + } | |
70 | +} | |
71 | +props.chartConfig.option = { | |
72 | + ...props.chartConfig.option, | |
73 | + ...{ toolbox: toolBoxOption } | |
74 | +} | |
75 | + | |
76 | +// 渐变色处理 | |
77 | +watch( | |
78 | + () => chartEditStore.getEditCanvasConfig.chartThemeColor, | |
79 | + (newColor: keyof typeof chartColorsSearch) => { | |
80 | + try { | |
81 | + if (!isPreview()) { | |
82 | + const themeColor = | |
83 | + colorGradientCustomMerge(chartEditStore.getEditCanvasConfig.chartCustomThemeColorInfo)[newColor] || | |
84 | + colorGradientCustomMerge(chartEditStore.getEditCanvasConfig.chartCustomThemeColorInfo)[defaultTheme] | |
85 | + props.chartConfig.option.series.forEach((value: any, index: number) => { | |
86 | + value.areaStyle.color = new graphic.LinearGradient(0, 0, 0, 1, [ | |
87 | + { | |
88 | + offset: 0, | |
89 | + color: themeColor[3 + index] | |
90 | + }, | |
91 | + { | |
92 | + offset: 1, | |
93 | + color: 'rgba(0,0,0, 0)' | |
94 | + } | |
95 | + ]) | |
96 | + }) | |
97 | + } | |
98 | + option.value = mergeTheme(props.chartConfig.option, props.themeSetting, includes) | |
99 | + props.chartConfig.option = option.value | |
100 | + } catch (error) { | |
101 | + console.log(error) | |
102 | + } | |
103 | + }, | |
104 | + { | |
105 | + immediate: true | |
106 | + } | |
107 | +) | |
108 | + | |
109 | +watch( | |
110 | + () => props.chartConfig.option.dataset, | |
111 | + () => { | |
112 | + option.value = props.chartConfig.option | |
113 | + } | |
114 | +) | |
115 | +let seriesDataNum = -1 | |
116 | +let seriesDataMaxLength = 0 | |
117 | +let intervalInstance: any = null | |
118 | +const duration = 1500 | |
119 | + | |
120 | +// 会重新选择需要选中和展示的数据 | |
121 | +const handleSeriesData = () => { | |
122 | + if (seriesDataNum > -1) { | |
123 | + vChartRef.value?.dispatchAction({ | |
124 | + type: 'downplay', | |
125 | + dataIndex: seriesDataNum | |
126 | + }) | |
127 | + } | |
128 | + seriesDataNum = seriesDataNum >= seriesDataMaxLength - 1 ? 0 : seriesDataNum + 1 | |
129 | + vChartRef.value?.dispatchAction({ | |
130 | + type: 'showTip', | |
131 | + seriesIndex: 0, | |
132 | + dataIndex: seriesDataNum | |
133 | + }) | |
134 | +} | |
135 | + | |
136 | +// 新增轮播 | |
137 | +const addPieInterval = (newData?: typeof dataJson, skipPre = false) => { | |
138 | + if (!skipPre && !Array.isArray(newData?.source)) return | |
139 | + if (!skipPre) seriesDataMaxLength = newData?.source.length || 0 | |
140 | + clearInterval(intervalInstance) | |
141 | + intervalInstance = setInterval(() => { | |
142 | + handleSeriesData() | |
143 | + }, duration) | |
144 | +} | |
145 | + | |
146 | +// 取消轮播 | |
147 | +const clearPieInterval = () => { | |
148 | + vChartRef.value?.dispatchAction({ | |
149 | + type: 'hideTip', | |
150 | + seriesIndex: 0, | |
151 | + dataIndex: seriesDataNum | |
152 | + }) | |
153 | + vChartRef.value?.dispatchAction({ | |
154 | + type: 'downplay', | |
155 | + dataIndex: seriesDataNum | |
156 | + }) | |
157 | + clearInterval(intervalInstance) | |
158 | + intervalInstance = null | |
159 | +} | |
160 | + | |
161 | +// 处理鼠标聚焦高亮内容 | |
162 | +const handleHighlight = () => { | |
163 | + clearPieInterval() | |
164 | +} | |
165 | + | |
166 | +// 处理鼠标取消悬浮 | |
167 | +const handleDownplay = () => { | |
168 | + if (props.chartConfig.option.isCarousel && !intervalInstance) { | |
169 | + // 恢复轮播 | |
170 | + addPieInterval(undefined, true) | |
171 | + } | |
172 | +} | |
173 | + | |
174 | +watch( | |
175 | + () => props.chartConfig.option.isCarousel, | |
176 | + newData => { | |
177 | + if (newData) { | |
178 | + addPieInterval(undefined, true) | |
179 | + props.chartConfig.option.legend.show = false | |
180 | + } else { | |
181 | + props.chartConfig.option.legend.show = true | |
182 | + clearPieInterval() | |
183 | + } | |
184 | + } | |
185 | +) | |
186 | + | |
187 | +const { vChartRef } = useChartDataFetch(props.chartConfig, useChartEditStore, (newData: typeof dataJson) => { | |
188 | + // addPieInterval(newData) | |
189 | +}) | |
190 | + | |
191 | +onMounted(() => { | |
192 | + seriesDataMaxLength = dataJson.source.length | |
193 | + if (props.chartConfig.option.isCarousel) { | |
194 | + addPieInterval(undefined, true) | |
195 | + } | |
196 | +}) | |
197 | +</script> | ... | ... |
1 | +export const isFullScreen = (domName: any, htmlName: any) => { | |
2 | + const isFullScreen = document.fullscreenElement | |
3 | + const currentDatkTheme = htmlName.getAttribute('data-theme') | |
4 | + | |
5 | + if (isFullScreen) { | |
6 | + console.log('退出全屏') | |
7 | + if (document.exitFullscreen) { | |
8 | + document.exitFullscreen() | |
9 | + domName.style.background = '' | |
10 | + } | |
11 | + } else { | |
12 | + console.log('进入全屏') | |
13 | + if (domName.requestFullscreen) { | |
14 | + domName.requestFullscreen() | |
15 | + domName.style.background = currentDatkTheme === 'light' ? 'white' : '#18181c' | |
16 | + } else if (domName.mozRequestFullScreen) { | |
17 | + domName.mozRequestFullScreen() | |
18 | + domName.style.background = currentDatkTheme === 'light' ? 'white' : '#18181c' | |
19 | + } else if (domName.webkitRequestFullscreen) { | |
20 | + domName.webkitRequestFullscreen() | |
21 | + domName.style.background = currentDatkTheme === 'light' ? 'white' : '#18181c' | |
22 | + } else if (domName.msRequestFullscreen) { | |
23 | + domName.msRequestFullscreen() | |
24 | + domName.style.background = currentDatkTheme === 'light' ? 'white' : '#18181c' | |
25 | + } | |
26 | + } | |
27 | +} | ... | ... |
... | ... | @@ -13,11 +13,6 @@ |
13 | 13 | </n-button> |
14 | 14 | </setting-item> |
15 | 15 | </setting-item-box> |
16 | - <setting-item-box name="排布"> | |
17 | - <setting-item> | |
18 | - <n-select v-model:value="optionData.displayMode" size="small" :options="displayModeOptions"></n-select> | |
19 | - </setting-item> | |
20 | - </setting-item-box> | |
21 | 16 | </CollapseItem> |
22 | 17 | </template> |
23 | 18 | |
... | ... | @@ -32,20 +27,4 @@ defineProps({ |
32 | 27 | required: true |
33 | 28 | } |
34 | 29 | }) |
35 | - | |
36 | -// 旋转方式 | |
37 | -const displayModeOptions = [ | |
38 | - { | |
39 | - value: 'singleGrid', | |
40 | - label: '默认' | |
41 | - }, | |
42 | - { | |
43 | - value: 'fourGrid', | |
44 | - label: '四宫格' | |
45 | - }, | |
46 | - { | |
47 | - value: 'nineGrid', | |
48 | - label: '九宫格' | |
49 | - } | |
50 | -] | |
51 | 30 | </script> | ... | ... |
1 | +<template> | |
2 | + <video | |
3 | + crossOrigin="anonymous" | |
4 | + :id="`my-player${index}`" | |
5 | + ref="videoRef" | |
6 | + class="video-js my-video vjs-theme-city vjs-big-play-centered" | |
7 | + > | |
8 | + <source :src="sourceSrc" /> | |
9 | + </video> | |
10 | +</template> | |
11 | +<script setup lang="ts"> | |
12 | +import { onMounted, ref, onUnmounted, watch } from 'vue' | |
13 | +import videojs from 'video.js' | |
14 | +import type { VideoJsPlayerOptions } from 'video.js' | |
15 | +import 'video.js/dist/video-js.min.css' | |
16 | + | |
17 | +const props = defineProps({ | |
18 | + sourceSrc: { | |
19 | + type: String | |
20 | + }, | |
21 | + name: { | |
22 | + type: String | |
23 | + }, | |
24 | + avatar: { | |
25 | + type: String | |
26 | + }, | |
27 | + index: { | |
28 | + type: Number | |
29 | + } | |
30 | +}) | |
31 | + | |
32 | +// video标签 | |
33 | +const videoRef = ref<HTMLElement | null>(null) | |
34 | + | |
35 | +// video实例对象 | |
36 | +let videoPlayer: videojs.Player | null = null | |
37 | + | |
38 | +//options配置 | |
39 | +const options: VideoJsPlayerOptions = { | |
40 | + language: 'zh-CN', // 设置语言 | |
41 | + controls: true, // 是否显示控制条 | |
42 | + preload: 'auto', // 预加载 | |
43 | + autoplay: true, // 是否自动播放 | |
44 | + fluid: false, // 自适应宽高 | |
45 | + poster: props?.avatar || '', | |
46 | + src: props?.sourceSrc || '', // 要嵌入的视频源的源 URL | |
47 | + muted: true, | |
48 | + userActions: { | |
49 | + hotkeys: true | |
50 | + } | |
51 | +} | |
52 | + | |
53 | +// 初始化videojs | |
54 | +const initVideo = () => { | |
55 | + if (videoRef.value) { | |
56 | + // 创建 video 实例 | |
57 | + videoPlayer = videojs(videoRef.value, options) | |
58 | + } | |
59 | +} | |
60 | + | |
61 | +watch( | |
62 | + () => props.sourceSrc, | |
63 | + (newData: any) => { | |
64 | + // props.sourceSrc = newData | |
65 | + videoPlayer?.src(newData) as any | |
66 | + videoPlayer?.play() | |
67 | + }, | |
68 | + { | |
69 | + immediate: true | |
70 | + } | |
71 | +) | |
72 | + | |
73 | +onMounted(() => { | |
74 | + initVideo() | |
75 | +}) | |
76 | + | |
77 | +onUnmounted(() => { | |
78 | + handleVideoDispose() | |
79 | +}) | |
80 | + | |
81 | +//播放 | |
82 | +const handleVideoPlay = () => videoPlayer?.play() | |
83 | + | |
84 | +const handleVideoDispose = () => videoPlayer?.dispose() && videoPlayer?.pause() | |
85 | +//暂停 | |
86 | +defineExpose({ | |
87 | + handleVideoPlay, | |
88 | + handleVideoDispose | |
89 | +}) | |
90 | +</script> | |
91 | + | |
92 | +<style lang="scss" scoped> | |
93 | +.my-video { | |
94 | + width: 100%; | |
95 | + height: 100%; | |
96 | +} | |
97 | +</style> | ... | ... |
1 | +import { PublicConfigClass } from '@/packages/public' | |
2 | +import { CreateComponentType } from '@/packages/index.d' | |
3 | +import { SingleCameraConfig } from './index' | |
4 | +import cloneDeep from 'lodash/cloneDeep' | |
5 | + | |
6 | +export const option = { | |
7 | + dataset: [ | |
8 | + { | |
9 | + url: '' | |
10 | + } | |
11 | + ] as any, | |
12 | + // 自动播放的间隔(ms) | |
13 | + interval: 5000, | |
14 | + autoplay: true, | |
15 | + effect: 'slide', | |
16 | + displayMode: 'singleGrid' | |
17 | +} | |
18 | + | |
19 | +export default class Config extends PublicConfigClass implements CreateComponentType { | |
20 | + public key = SingleCameraConfig.key | |
21 | + public chartConfig = cloneDeep(SingleCameraConfig) | |
22 | + public option = cloneDeep(option) | |
23 | +} | ... | ... |
1 | +<template> | |
2 | + <CollapseItem name="播放器配置" :expanded="true"> | |
3 | + <setting-item-box name="源地址" :alone="true"> | |
4 | + <setting-item v-for="(item, index) in optionData.dataset" :key="index"> | |
5 | + <n-input-group> | |
6 | + <n-input v-model:value="item.url" size="small" placeholder="请输入源地址"></n-input> | |
7 | + </n-input-group> | |
8 | + </setting-item> | |
9 | + </setting-item-box> | |
10 | + </CollapseItem> | |
11 | +</template> | |
12 | + | |
13 | +<script setup lang="ts"> | |
14 | +import { PropType } from 'vue' | |
15 | +import { option } from './config' | |
16 | +import { CollapseItem, SettingItemBox, SettingItem } from '@/components/Pages/ChartItemSetting' | |
17 | + | |
18 | +defineProps({ | |
19 | + optionData: { | |
20 | + type: Object as PropType<typeof option>, | |
21 | + required: true | |
22 | + } | |
23 | +}) | |
24 | + | |
25 | +</script> | ... | ... |
1 | +import { ChartFrameEnum, ConfigType } from '@/packages/index.d' | |
2 | +import { EPackagesCategoryEnum } from '@/packages/components/external/types' | |
3 | +import { ChatCategoryEnum, ChatCategoryEnumName } from '../../index.d' | |
4 | +import { useWidgetKey } from '@/packages/external/useWidgetKey' | |
5 | + | |
6 | +const { key, chartKey, conKey } = useWidgetKey('SingleCamera') | |
7 | +export const SingleCameraConfig: ConfigType = { | |
8 | + key, | |
9 | + chartKey, | |
10 | + conKey, | |
11 | + title: '单个摄像头', | |
12 | + category: ChatCategoryEnum.MORE, | |
13 | + categoryName: ChatCategoryEnumName.MORE, | |
14 | + package: EPackagesCategoryEnum.COMPOSES, | |
15 | + chartFrame: ChartFrameEnum.NAIVE_UI, | |
16 | + image: 'camera.png' | |
17 | +} | ... | ... |
1 | +<template> | |
2 | + <div class="banner-box" ref="root"> | |
3 | + <n-grid x-gap="12" :y-gap="12" :cols="computedCols"> | |
4 | + <n-gi v-for="(item, index) in option.dataset" :key="index + item"> | |
5 | + <div class="camera-container"> | |
6 | + <CameraItem | |
7 | + ref="cameraRef" | |
8 | + :name="item.name" | |
9 | + :avatar="item.avatar" | |
10 | + :key="item + index" | |
11 | + :sourceSrc="item.url" | |
12 | + :index="index" | |
13 | + /> | |
14 | + </div> | |
15 | + </n-gi> | |
16 | + </n-grid> | |
17 | + </div> | |
18 | +</template> | |
19 | +<script setup lang="ts"> | |
20 | +import { PropType, toRefs, watch, shallowReactive, ref, computed } from 'vue' | |
21 | +import { CreateComponentType } from '@/packages/index.d' | |
22 | +import 'video.js/dist/video-js.min.css' | |
23 | +import { option as configOption } from './config' | |
24 | +import { CameraItem } from './components' | |
25 | + | |
26 | +const props = defineProps({ | |
27 | + chartConfig: { | |
28 | + type: Object as PropType<CreateComponentType>, | |
29 | + required: true | |
30 | + } | |
31 | +}) | |
32 | + | |
33 | +const { h } = toRefs(props.chartConfig.attr) | |
34 | + | |
35 | +const responsiveComputeValue = ref(0) | |
36 | + | |
37 | +const option = shallowReactive({ | |
38 | + dataset: configOption.dataset | |
39 | +}) | |
40 | + | |
41 | +const computedCols = computed(() => { | |
42 | + if (option.dataset.length <= 1) return 1 | |
43 | + if (option.dataset.length <= 4) return 2 | |
44 | + return 3 | |
45 | +}) | |
46 | + | |
47 | +const cameraRef = ref<InstanceType<typeof CameraItem>>() | |
48 | + | |
49 | +const responsive = (value: number) => { | |
50 | + responsiveComputeValue.value = value | |
51 | + if (option.dataset.length <= 2) responsiveComputeValue.value = value | |
52 | + if (option.dataset.length > 2 && option.dataset.length <= 4) responsiveComputeValue.value = value / 2.03 | |
53 | + if (option.dataset.length > 4 && option.dataset.length <= 9) responsiveComputeValue.value = value / 3.1 | |
54 | +} | |
55 | + | |
56 | +watch( | |
57 | + () => props.chartConfig.option.dataset, | |
58 | + newData => { | |
59 | + option.dataset = newData | |
60 | + responsive(h.value) | |
61 | + }, | |
62 | + { | |
63 | + immediate: true, | |
64 | + deep: true | |
65 | + } | |
66 | +) | |
67 | + | |
68 | +watch( | |
69 | + () => h.value, | |
70 | + newData => responsive(newData), | |
71 | + { | |
72 | + immediate: true | |
73 | + } | |
74 | +) | |
75 | +</script> | |
76 | + | |
77 | +<style lang="scss" scoped> | |
78 | +.banner-box { | |
79 | + .camera-container { | |
80 | + height: v-bind('`${responsiveComputeValue}px`'); | |
81 | + } | |
82 | +} | |
83 | +</style> | ... | ... |
... | ... | @@ -2,6 +2,7 @@ import { Title1Config } from './Title1/index' |
2 | 2 | import { Title2Config } from './Title2/index' |
3 | 3 | import { Title3Config } from './Title3/index' |
4 | 4 | import { CameraConfig } from './Camera/index' |
5 | +import { SingleCameraConfig } from './SingleCamera/index' | |
5 | 6 | import { ThreeDimensionalConfig } from './ThreeDimensional/index' |
6 | 7 | |
7 | -export default [Title1Config, Title2Config, Title3Config, CameraConfig, ThreeDimensionalConfig] | |
8 | +export default [Title1Config, Title2Config, Title3Config, CameraConfig, SingleCameraConfig, ThreeDimensionalConfig] | ... | ... |
... | ... | @@ -23,19 +23,19 @@ export const enum areaEnum { |
23 | 23 | export const weatherTextMapImg = [ |
24 | 24 | { |
25 | 25 | text: '晴', |
26 | - img: '/large-designer/src/assets/external/weather/clearDay.png' | |
26 | + img: 'src/assets/external/weather/clearDay.png' | |
27 | 27 | }, |
28 | 28 | { |
29 | 29 | text: '多云', |
30 | - img: '/large-designer/src/assets/external/weather/cloudy.png' | |
30 | + img: 'src/assets/external/weather/cloudy.png' | |
31 | 31 | }, |
32 | 32 | { |
33 | 33 | text: '阴', |
34 | - img: '/large-designer/src/assets/external/weather/cloudyDay.png' | |
34 | + img: 'src/assets/external/weather/cloudyDay.png' | |
35 | 35 | }, |
36 | 36 | { |
37 | 37 | text: '小雨', |
38 | - img: '/large-designer/src/assets/external/weather/lightRain.png' | |
38 | + img: 'src/assets/external/weather/lightRain.png' | |
39 | 39 | } |
40 | 40 | ] |
41 | 41 | ... | ... |
... | ... | @@ -4,7 +4,7 @@ export const useUtils = () => { |
4 | 4 | const loadWeatherImg = (text: string) => { |
5 | 5 | return ( |
6 | 6 | weatherTextMapImg.find((item: any) => item.text === text)?.img || |
7 | - '/large-designer/src/assets/external/weather/clearDay.png' | |
7 | + '/src/assets/external/weather/clearDay.png' | |
8 | 8 | ) |
9 | 9 | } |
10 | 10 | ... | ... |
1 | +import { PublicConfigClass } from '@/packages/public' | |
2 | +import { CreateComponentType } from '@/packages/index.d' | |
3 | +import { OverrideTextCommonConfig } from './index' | |
4 | +import cloneDeep from 'lodash/cloneDeep' | |
5 | + | |
6 | +export enum WritingModeEnum { | |
7 | + HORIZONTAL = '水平', | |
8 | + VERTICAL = '垂直' | |
9 | +} | |
10 | + | |
11 | +export const WritingModeObject = { | |
12 | + [WritingModeEnum.HORIZONTAL]: 'horizontal-tb', | |
13 | + [WritingModeEnum.VERTICAL]: 'vertical-rl' | |
14 | +} | |
15 | + | |
16 | +export enum FontWeightEnum { | |
17 | + NORMAL = '常规', | |
18 | + BOLD = '加粗' | |
19 | +} | |
20 | + | |
21 | +export const FontWeightObject = { | |
22 | + [FontWeightEnum.NORMAL]: 'normal', | |
23 | + [FontWeightEnum.BOLD]: 'bold' | |
24 | +} | |
25 | + | |
26 | +export const option = { | |
27 | + link: '', | |
28 | + linkHead: 'http://', | |
29 | + dataset: '我是文本', | |
30 | + fontSize: 20, | |
31 | + fontColor: '#ffffff', | |
32 | + paddingX: 10, | |
33 | + paddingY: 10, | |
34 | + textAlign: 'center', // 水平对齐方式 | |
35 | + fontWeight: 'normal', | |
36 | + | |
37 | + // 边框 | |
38 | + borderWidth: 0, | |
39 | + borderColor: '#ffffff', | |
40 | + borderRadius: 5, | |
41 | + | |
42 | + // 字间距 | |
43 | + letterSpacing: 5, | |
44 | + writingMode: 'horizontal-tb', | |
45 | + backgroundColor: '#00000000', | |
46 | + | |
47 | + //跳转方式 | |
48 | + linkMethod: 'default' | |
49 | +} | |
50 | + | |
51 | +export default class Config extends PublicConfigClass implements CreateComponentType { | |
52 | + public key = OverrideTextCommonConfig.key | |
53 | + public chartConfig = cloneDeep(OverrideTextCommonConfig) | |
54 | + public option = cloneDeep(option) | |
55 | +} | ... | ... |
1 | +<template> | |
2 | + <collapse-item name="信息" :expanded="true"> | |
3 | + <setting-item-box name="文字" :alone="true"> | |
4 | + <setting-item> | |
5 | + <n-input v-model:value="optionData.dataset" size="small"></n-input> | |
6 | + </setting-item> | |
7 | + </setting-item-box> | |
8 | + <setting-item-box name="链接" :alone="true"> | |
9 | + <setting-item> | |
10 | + <n-input-group> | |
11 | + <n-select | |
12 | + v-model:value="optionData.linkHead" | |
13 | + size="small" | |
14 | + :style="{ width: '80%' }" | |
15 | + :options="linkHeadOptions" | |
16 | + /> | |
17 | + <n-input v-model:value="optionData.link" size="small"></n-input> | |
18 | + </n-input-group> | |
19 | + </setting-item> | |
20 | + </setting-item-box> | |
21 | + <setting-item-box name="跳转方式" :alone="true"> | |
22 | + <setting-item> | |
23 | + <n-input-group> | |
24 | + <n-select | |
25 | + v-model:value="optionData.linkMethod" | |
26 | + size="small" | |
27 | + :style="{ width: '80%' }" | |
28 | + :options="linkMethodOptions" | |
29 | + /> | |
30 | + <n-button | |
31 | + :disabled="!optionData.linkMethod" | |
32 | + secondary | |
33 | + size="small" | |
34 | + @click="handleLinkClick(optionData.linkMethod)" | |
35 | + >跳转</n-button | |
36 | + > | |
37 | + </n-input-group> | |
38 | + </setting-item> | |
39 | + </setting-item-box> | |
40 | + </collapse-item> | |
41 | + | |
42 | + <collapse-item name="样式" :expanded="true"> | |
43 | + <setting-item-box name="文字"> | |
44 | + <setting-item name="颜色"> | |
45 | + <n-color-picker size="small" :modes="['hex']" v-model:value="optionData.fontColor"></n-color-picker> | |
46 | + </setting-item> | |
47 | + <setting-item name="字体大小"> | |
48 | + <n-input-number v-model:value="optionData.fontSize" size="small" placeholder="字体大小"></n-input-number> | |
49 | + </setting-item> | |
50 | + <setting-item name="字体粗细"> | |
51 | + <n-select v-model:value="optionData.fontWeight" size="small" :options="fontWeightOptions" /> | |
52 | + </setting-item> | |
53 | + <setting-item name="X轴内边距"> | |
54 | + <n-input-number v-model:value="optionData.paddingX" size="small" placeholder="输入内边距"></n-input-number> | |
55 | + </setting-item> | |
56 | + <setting-item name="Y轴内边距"> | |
57 | + <n-input-number v-model:value="optionData.paddingY" size="small" placeholder="输入内边距"></n-input-number> | |
58 | + </setting-item> | |
59 | + | |
60 | + <setting-item name="水平对齐"> | |
61 | + <n-select v-model:value="optionData.textAlign" size="small" :options="textAlignOptions" /> | |
62 | + </setting-item> | |
63 | + <setting-item name="文本方向"> | |
64 | + <n-select v-model:value="optionData.writingMode" size="small" :options="verticalOptions" /> | |
65 | + </setting-item> | |
66 | + | |
67 | + <setting-item name="字间距"> | |
68 | + <n-input-number v-model:value="optionData.letterSpacing" size="small" placeholder="输入字间距"></n-input-number> | |
69 | + </setting-item> | |
70 | + </setting-item-box> | |
71 | + | |
72 | + <setting-item-box name="边框"> | |
73 | + <setting-item name="宽度"> | |
74 | + <n-input-number | |
75 | + v-model:value="optionData.borderWidth" | |
76 | + size="small" | |
77 | + :min="0" | |
78 | + placeholder="宽度" | |
79 | + ></n-input-number> | |
80 | + </setting-item> | |
81 | + <setting-item name="颜色"> | |
82 | + <n-color-picker size="small" :modes="['hex']" v-model:value="optionData.borderColor"></n-color-picker> | |
83 | + </setting-item> | |
84 | + <setting-item name="圆角"> | |
85 | + <n-input-number | |
86 | + v-model:value="optionData.borderRadius" | |
87 | + size="small" | |
88 | + :min="0" | |
89 | + placeholder="圆角" | |
90 | + ></n-input-number> | |
91 | + </setting-item> | |
92 | + </setting-item-box> | |
93 | + | |
94 | + <setting-item-box name="背景" :alone="true"> | |
95 | + <setting-item name="背景颜色"> | |
96 | + <n-color-picker size="small" :modes="['hex']" v-model:value="optionData.backgroundColor"></n-color-picker> | |
97 | + </setting-item> | |
98 | + </setting-item-box> | |
99 | + </collapse-item> | |
100 | +</template> | |
101 | + | |
102 | +<script setup lang="ts"> | |
103 | +import { PropType } from 'vue' | |
104 | +import { option, WritingModeEnum, WritingModeObject, FontWeightEnum, FontWeightObject } from './config' | |
105 | +import { CollapseItem, SettingItemBox, SettingItem } from '@/components/Pages/ChartItemSetting' | |
106 | +const props = defineProps({ | |
107 | + optionData: { | |
108 | + type: Object as PropType<typeof option>, | |
109 | + required: true | |
110 | + } | |
111 | +}) | |
112 | + | |
113 | +const textAlignOptions = [ | |
114 | + { label: '左对齐', value: 'start' }, | |
115 | + { label: '居中', value: 'center' }, | |
116 | + { label: '右对齐', value: 'end' } | |
117 | +] | |
118 | + | |
119 | +const linkMethodOptions = [ | |
120 | + { label: '默认', value: 'default' }, | |
121 | + { label: '新开窗口', value: 'open' } | |
122 | +] | |
123 | + | |
124 | +const verticalOptions = [ | |
125 | + { | |
126 | + label: WritingModeEnum.HORIZONTAL, | |
127 | + value: WritingModeObject[WritingModeEnum.HORIZONTAL] | |
128 | + }, | |
129 | + { | |
130 | + label: WritingModeEnum.VERTICAL, | |
131 | + value: WritingModeObject[WritingModeEnum.VERTICAL] | |
132 | + } | |
133 | +] | |
134 | +const fontWeightOptions = [ | |
135 | + { | |
136 | + label: FontWeightEnum.NORMAL, | |
137 | + value: FontWeightObject[FontWeightEnum.NORMAL] | |
138 | + }, | |
139 | + { | |
140 | + label: FontWeightEnum.BOLD, | |
141 | + value: FontWeightObject[FontWeightEnum.BOLD] | |
142 | + } | |
143 | +] | |
144 | +const handleLinkClick = (target: string) => { | |
145 | + const hrefStr = props.optionData.linkHead + props.optionData.link | |
146 | + if (target === 'open') window.open(hrefStr) | |
147 | + else window.location.href = hrefStr | |
148 | +} | |
149 | +const linkHeadOptions = [ | |
150 | + { label: 'http://', value: 'http://' }, | |
151 | + { label: 'https://', value: 'https://' } | |
152 | +] | |
153 | +</script> | ... | ... |
1 | +import { ConfigType, PackagesCategoryEnum, ChartFrameEnum } from '@/packages/index.d' | |
2 | +import { ChatCategoryEnum, ChatCategoryEnumName } from '@/packages/components/Informations/index.d' | |
3 | +import { useWidgetKey } from '@/packages/external/useWidgetKey' | |
4 | + | |
5 | +const { key, conKey, chartKey } = useWidgetKey('OverrideTextCommon', true) | |
6 | + | |
7 | +export const OverrideTextCommonConfig: ConfigType = { | |
8 | + key, | |
9 | + chartKey, | |
10 | + conKey, | |
11 | + title: '自定义文字', | |
12 | + category: ChatCategoryEnum.MORE, | |
13 | + categoryName: ChatCategoryEnumName.MORE, | |
14 | + package: PackagesCategoryEnum.INFORMATIONS, | |
15 | + chartFrame: ChartFrameEnum.COMMON, | |
16 | + image: 'text_static.png' | |
17 | +} | ... | ... |
1 | +<template> | |
2 | + <div class="go-text-box"> | |
3 | + <div class="content"> | |
4 | + <span style="cursor: pointer" v-if="link" @click="click">{{ option.dataset }}</span> | |
5 | + <span v-else>{{ option.dataset }}</span> | |
6 | + </div> | |
7 | + </div> | |
8 | +</template> | |
9 | + | |
10 | +<script setup lang="ts"> | |
11 | +import { PropType, toRefs, shallowReactive, watch } from 'vue' | |
12 | +import { CreateComponentType } from '@/packages/index.d' | |
13 | +import { useChartDataFetch } from '@/hooks' | |
14 | +import { useChartEditStore } from '@/store/modules/chartEditStore/chartEditStore' | |
15 | +import { option as configOption } from './config' | |
16 | + | |
17 | +const props = defineProps({ | |
18 | + chartConfig: { | |
19 | + type: Object as PropType<CreateComponentType & typeof option>, | |
20 | + required: true | |
21 | + } | |
22 | +}) | |
23 | + | |
24 | +const { | |
25 | + linkHead, | |
26 | + link, | |
27 | + fontColor, | |
28 | + fontSize, | |
29 | + letterSpacing, | |
30 | + paddingY, | |
31 | + paddingX, | |
32 | + textAlign, | |
33 | + borderWidth, | |
34 | + borderColor, | |
35 | + borderRadius, | |
36 | + writingMode, | |
37 | + backgroundColor, | |
38 | + fontWeight, | |
39 | + linkMethod | |
40 | +} = toRefs(props.chartConfig.option) | |
41 | + | |
42 | +const option = shallowReactive({ | |
43 | + dataset: configOption.dataset | |
44 | +}) | |
45 | + | |
46 | +// 手动更新 | |
47 | +watch( | |
48 | + () => props.chartConfig.option.dataset, | |
49 | + (newData: any) => { | |
50 | + option.dataset = newData | |
51 | + }, | |
52 | + { | |
53 | + immediate: true, | |
54 | + deep: false | |
55 | + } | |
56 | +) | |
57 | + | |
58 | +// 预览更新 | |
59 | +useChartDataFetch(props.chartConfig, useChartEditStore, (newData: string) => { | |
60 | + option.dataset = newData | |
61 | +}) | |
62 | + | |
63 | +//打开链接 | |
64 | +const click = () => { | |
65 | + const hrefStr = linkHead.value + link.value | |
66 | + if (linkMethod.value === 'open') window.open(hrefStr) | |
67 | + else window.location.href = hrefStr | |
68 | +} | |
69 | +</script> | |
70 | + | |
71 | +<style lang="scss" scoped> | |
72 | +@include go('text-box') { | |
73 | + display: flex; | |
74 | + align-items: center; | |
75 | + justify-content: v-bind('textAlign'); | |
76 | + | |
77 | + .content { | |
78 | + color: v-bind('fontColor'); | |
79 | + padding: v-bind('`${paddingY}px ${paddingX}px`'); | |
80 | + font-size: v-bind('fontSize + "px"'); | |
81 | + letter-spacing: v-bind('letterSpacing + "px"'); | |
82 | + writing-mode: v-bind('writingMode'); | |
83 | + font-weight: v-bind('fontWeight'); | |
84 | + border-style: solid; | |
85 | + border-width: v-bind('borderWidth + "px"'); | |
86 | + border-radius: v-bind('borderRadius + "px"'); | |
87 | + border-color: v-bind('borderColor'); | |
88 | + | |
89 | + background-color: v-bind('backgroundColor'); | |
90 | + } | |
91 | +} | |
92 | +</style> | ... | ... |
... | ... | @@ -8,6 +8,10 @@ import { OverrideCarouselConfig } from '@/packages/components/external/Informati |
8 | 8 | import { OverrideSelectConfig } from '@/packages/components/external/Informations/Mores/OverrideSelect' |
9 | 9 | import { OverrideInputsDateConfig } from '@/packages/components/external/Informations/Mores/OverrideInputsDate' |
10 | 10 | import { OverrideInputsTabConfig } from '@/packages/components/external/Informations/Mores/OverrideInputsTab' |
11 | +import { OverrideTextCommonConfig } from '@/packages/components/external/Informations/Mores/OverrideTextCommon' | |
12 | +import { OverrideBarCommonConfig } from '@/packages/components/external/Charts/Bars/OverrideBarCommon' | |
13 | +import { OverrideLineCommonConfig } from '@/packages/components/external/Charts/Lines/OverrideLineCommon' | |
14 | +import { OverrideLineGradientsConfig } from '@/packages/components/external/Charts/Lines/OverrideLineGradients' | |
11 | 15 | |
12 | 16 | export function useInjectLib(packagesList: EPackagesType) { |
13 | 17 | |
... | ... | @@ -20,6 +24,10 @@ export function useInjectLib(packagesList: EPackagesType) { |
20 | 24 | addWidgetToCategoryByCategoryName(packagesList, PackagesCategoryEnum.INFORMATIONS, OverrideSelectConfig) |
21 | 25 | addWidgetToCategoryByCategoryName(packagesList, PackagesCategoryEnum.INFORMATIONS, OverrideInputsDateConfig) |
22 | 26 | addWidgetToCategoryByCategoryName(packagesList, PackagesCategoryEnum.INFORMATIONS, OverrideInputsTabConfig) |
27 | + addWidgetToCategoryByCategoryName(packagesList, PackagesCategoryEnum.INFORMATIONS, OverrideTextCommonConfig) | |
28 | + addWidgetToCategoryByCategoryName(packagesList, PackagesCategoryEnum.CHARTS, OverrideBarCommonConfig) | |
29 | + addWidgetToCategoryByCategoryName(packagesList, PackagesCategoryEnum.CHARTS, OverrideLineCommonConfig) | |
30 | + addWidgetToCategoryByCategoryName(packagesList, PackagesCategoryEnum.CHARTS, OverrideLineGradientsConfig) | |
23 | 31 | } |
24 | 32 | |
25 | 33 | /** | ... | ... |
... | ... | @@ -69,10 +69,17 @@ export const useSocketStore = defineStore({ |
69 | 69 | }) |
70 | 70 | } else { |
71 | 71 | const keysRecord: Record<string, KeyBoundComponentList[]> = {} |
72 | - | |
73 | - keys.forEach(key => { | |
72 | + /**这里修改自定义tab切换传的单个属性问题 | |
73 | + * ft | |
74 | + * 源代码 keys.forEach | |
75 | + * 修改代码 const overrideKeys = typeof keys==="string"?[keys]: keys overrideKeys.forEach | |
76 | + */ | |
77 | + const overrideKeys = typeof keys==="string"?[keys]: keys | |
78 | + | |
79 | + overrideKeys.forEach(key => { | |
74 | 80 | Reflect.set(keysRecord, key, [{ componentId }]) |
75 | 81 | }) |
82 | + //ft | |
76 | 83 | Reflect.set(this.connectionPool, entityId, keysRecord) |
77 | 84 | } |
78 | 85 | return this.refreshSubscribedMessage(entityId) |
... | ... | @@ -177,15 +184,22 @@ export const useSocketStore = defineStore({ |
177 | 184 | */ |
178 | 185 | getNeedUpdateComponentsIdBySubscribeId(subscribeId: number, keys: string[]) { |
179 | 186 | const entityId = this.subscribePool.find(item => item.subscribeId === subscribeId)?.entityId |
187 | + /**这里修改自定义tab切换传的单个属性问题 | |
188 | + * ft | |
189 | + * 源代码 keys.map(key => keysRecord[key]) | |
190 | + * 修改代码 const overrideKeys = typeof keys==="string"?[keys]: keys overrideKeys.map(key => keysRecord[key]) | |
191 | + */ | |
192 | + const overrideKeys = typeof keys==="string"?[keys]: keys | |
180 | 193 | |
181 | 194 | if (entityId) { |
182 | 195 | const keysRecord = Reflect.get(this.connectionPool, entityId) |
183 | - const needUpdateComponents = keys.map(key => keysRecord[key]) | |
196 | + const needUpdateComponents = overrideKeys.map(key => keysRecord[key]) | |
184 | 197 | const ids = needUpdateComponents |
185 | 198 | .reduce((prev, next) => [...prev, ...next], []) |
186 | 199 | .map((item: KeyBoundComponentList) => item.componentId) |
187 | 200 | return [...new Set(ids)] |
188 | 201 | } |
202 | + //ft | |
189 | 203 | }, |
190 | 204 | |
191 | 205 | /** |
... | ... | @@ -198,7 +212,15 @@ export const useSocketStore = defineStore({ |
198 | 212 | const { request: { requestParams } } = targetComponent |
199 | 213 | const { Params } = requestParams |
200 | 214 | const { keys = [] } = Params |
201 | - const targetComponentBindKeys = keys as unknown as string[] | |
215 | + /**这里修改自定义tab切换传的单个属性问题 | |
216 | + * ft | |
217 | + * 源代码 keys as unknown as string[] | |
218 | + * 修改代码 const overrideKeys = typeof keys==="string"?[keys]: keys overrideKeys as unknown as string[] | |
219 | + */ | |
220 | + const overrideKeys = typeof keys==="string"?[keys]: keys | |
221 | + | |
222 | + const targetComponentBindKeys = overrideKeys as unknown as string[] | |
223 | + //ft | |
202 | 224 | |
203 | 225 | const _value = cloneDeep(value) || { data: {}, latestValues: {} } |
204 | 226 | _value.data = targetComponentBindKeys.reduce((prev, next) => { | ... | ... |
... | ... | @@ -43,7 +43,22 @@ |
43 | 43 | </n-button> |
44 | 44 | </div> |
45 | 45 | </template> |
46 | - <span>保存</span> | |
46 | + <span>保存内容</span> | |
47 | + </n-tooltip> | |
48 | + <!-- 保存缩略图 --> | |
49 | + <n-tooltip v-if="!isCustomerUser" placement="bottom" trigger="hover"> | |
50 | + <template #trigger> | |
51 | + <div class="save-btn"> | |
52 | + <n-button size="small" type="primary" ghost @click="thumbnailSyncUpdate()"> | |
53 | + <template #icon> | |
54 | + <n-icon> | |
55 | + <Awake /> | |
56 | + </n-icon> | |
57 | + </template> | |
58 | + </n-button> | |
59 | + </div> | |
60 | + </template> | |
61 | + <span>保存缩略图</span> | |
47 | 62 | </n-tooltip> |
48 | 63 | </n-space> |
49 | 64 | </n-space> |
... | ... | @@ -53,7 +68,7 @@ |
53 | 68 | import { toRefs, Ref, reactive, computed } from 'vue' |
54 | 69 | import { renderIcon, goDialog, goHome } from '@/utils' |
55 | 70 | import { icon } from '@/plugins' |
56 | -import { Save } from '@vicons/carbon' | |
71 | +import { Save, Awake } from '@vicons/carbon' | |
57 | 72 | import { useRemoveKeyboard } from '../../../hooks/useKeyboard.hook' |
58 | 73 | |
59 | 74 | import { useChartEditStore } from '@/store/modules/chartEditStore/chartEditStore' |
... | ... | @@ -71,7 +86,7 @@ const { setItem } = useChartLayoutStore() |
71 | 86 | const { getLayers, getCharts, getDetails } = toRefs(useChartLayoutStore()) |
72 | 87 | const chartEditStore = useChartEditStore() |
73 | 88 | const chartHistoryStore = useChartHistoryStore() |
74 | -const { dataSyncUpdate } = useSyncRemote() | |
89 | +const { dataSyncUpdate ,thumbnailSyncUpdate } = useSyncRemote() | |
75 | 90 | const { isCustomerUser } = useRole() |
76 | 91 | |
77 | 92 | interface ItemType<T> { | ... | ... |
... | ... | @@ -70,22 +70,47 @@ export const useSyncRemote = () => { |
70 | 70 | } |
71 | 71 | } |
72 | 72 | |
73 | - // 数据保存 | |
74 | - const dataSyncUpdate = throttle(async (updateImg = true) => { | |
73 | + /** | |
74 | + * 数据保存和缩略图保存逻辑拆分 | |
75 | + */ | |
76 | + | |
77 | + //dataSyncUpdate 数据保存 | |
78 | + const dataSyncUpdate = throttle(async () => { | |
75 | 79 | if (!fetchRouteParamsLocation()) return |
76 | 80 | |
77 | 81 | // 客户角色只有查看权限 |
78 | 82 | if (unref(isCustomerUser)) return |
79 | 83 | |
80 | - const { dataViewId, state, organizationId, dataViewName, dataViewContent } = projectInfoStore.getProjectInfo | |
84 | + const { dataViewId, dataViewName, dataViewContent } = projectInfoStore.getProjectInfo | |
81 | 85 | |
82 | 86 | if (dataViewId === null || dataViewId === '') { |
83 | 87 | window['$message'].error('数据初未始化成功,请刷新页面!') |
84 | 88 | return |
85 | 89 | } |
86 | 90 | projectInfoStore.setSaveStatus(SyncEnum.START) |
87 | - // 异常处理:缩略图上传失败不影响JSON的保存 | |
88 | - try { | |
91 | + // 保存数据 | |
92 | + const saveContent = { | |
93 | + dataViewContent: { | |
94 | + id: dataViewContent.id, | |
95 | + content: JSONStringify(chartEditStore.getStorageInfo || {}) | |
96 | + }, | |
97 | + dataViewName, | |
98 | + dataViewId | |
99 | + } | |
100 | + await contentUpdateApi(saveContent as unknown as BaseUpdateContentParams) | |
101 | + window['$message'].success('保存成功!') | |
102 | + // 成功状态 | |
103 | + setTimeout(() => { | |
104 | + projectInfoStore.setSaveStatus(SyncEnum.SUCCESS) | |
105 | + }, 1000) | |
106 | + return | |
107 | + // 失败状态 | |
108 | + // chartEditStore.setEditCanvas(EditCanvasTypeEnum.SAVE_STATUS, SyncEnum.FAILURE) | |
109 | + }, 3000) | |
110 | + | |
111 | + //thumbnailSyncUpdate 缩略图保存 | |
112 | + const thumbnailSyncUpdate = throttle(async(updateImg = true)=>{ | |
113 | + const { state, organizationId, dataViewName } = projectInfoStore.getProjectInfo | |
89 | 114 | if (updateImg) { |
90 | 115 | // 获取缩略图片 |
91 | 116 | const range = document.querySelector('.go-edit-range') as HTMLElement |
... | ... | @@ -96,7 +121,6 @@ export const useSyncRemote = () => { |
96 | 121 | useCORS: true, |
97 | 122 | logging: false |
98 | 123 | }) |
99 | - | |
100 | 124 | // 上传预览图 |
101 | 125 | const uploadParams = new FormData() |
102 | 126 | uploadParams.append( |
... | ... | @@ -104,7 +128,6 @@ export const useSyncRemote = () => { |
104 | 128 | base64toFile(canvasImage.toDataURL(), `${fetchRouteParamsLocation()}_index_preview.png`) |
105 | 129 | ) |
106 | 130 | const uploadRes = await uploadFile(uploadParams) |
107 | - | |
108 | 131 | // 保存预览图 |
109 | 132 | if (uploadRes) { |
110 | 133 | await saveDataViewList({ |
... | ... | @@ -114,30 +137,10 @@ export const useSyncRemote = () => { |
114 | 137 | id: fetchRouteParamsLocation(), |
115 | 138 | thumbnail: `${uploadRes.fileStaticUri}` |
116 | 139 | }) |
140 | + window['$message'].success('保存缩略图成功!') | |
117 | 141 | } |
118 | 142 | } |
119 | - } catch (e) { | |
120 | - console.log(e) | |
121 | - } | |
122 | - // 保存数据 | |
123 | - const saveContent = { | |
124 | - dataViewContent: { | |
125 | - id: dataViewContent.id, | |
126 | - content: JSONStringify(chartEditStore.getStorageInfo || {}) | |
127 | - }, | |
128 | - dataViewName, | |
129 | - dataViewId | |
130 | - } | |
131 | - await contentUpdateApi(saveContent as unknown as BaseUpdateContentParams) | |
132 | - window['$message'].success('保存成功!') | |
133 | - // 成功状态 | |
134 | - setTimeout(() => { | |
135 | - projectInfoStore.setSaveStatus(SyncEnum.SUCCESS) | |
136 | - }, 1000) | |
137 | - return | |
138 | - // 失败状态 | |
139 | - // chartEditStore.setEditCanvas(EditCanvasTypeEnum.SAVE_STATUS, SyncEnum.FAILURE) | |
140 | - }, 3000) | |
143 | + }) | |
141 | 144 | |
142 | 145 | // * 定时处理 |
143 | 146 | const intervalDataSyncUpdate = () => { |
... | ... | @@ -145,7 +148,6 @@ export const useSyncRemote = () => { |
145 | 148 | // const syncTiming = setInterval(() => { |
146 | 149 | // dataSyncUpdate() |
147 | 150 | // }, saveInterval * 1000) |
148 | - | |
149 | 151 | // // 销毁 |
150 | 152 | // onUnmounted(() => { |
151 | 153 | // clearInterval(syncTiming) |
... | ... | @@ -154,6 +156,7 @@ export const useSyncRemote = () => { |
154 | 156 | return { |
155 | 157 | dataSyncFetch, |
156 | 158 | dataSyncUpdate, |
159 | + thumbnailSyncUpdate, | |
157 | 160 | intervalDataSyncUpdate |
158 | 161 | } |
159 | 162 | } | ... | ... |