Commit 5a81643a658f33c2804e10e799f04a88dd130492
Merge branch 'ft' into 'main_dev'
chore: 升级go-view版本至v1.2.9 See merge request yunteng/thingskit-view!131
Showing
44 changed files
with
847 additions
and
385 deletions
readme/sponsors/dandian-banner.png
0 → 100644
14.9 KB
readme/sponsors/mdy-banner.png
0 → 100644
53.7 KB
1 | +<template> | |
2 | + <n-upload | |
3 | + v-model:file-list="fileList" | |
4 | + :show-file-list="true" | |
5 | + :customRequest="customRequest" | |
6 | + :onBeforeUpload="beforeUploadHandle" | |
7 | + :onRemove="remove" | |
8 | + > | |
9 | + <n-upload-dragger> | |
10 | + <img v-if="uploadImageUrl" class="upload-show" :src="uploadImageUrl" alt="上传的图片" /> | |
11 | + <div class="upload-img" v-show="!uploadImageUrl"> | |
12 | + <img src="@/assets/images/canvas/noImage.png" /> | |
13 | + <n-text class="upload-desc" depth="3"> | |
14 | + 上传文件需小于 {{ uploadSizeFormat.size }}M ,格式为 {{ uploadSizeFormat.format }} 的文件 | |
15 | + </n-text> | |
16 | + </div> | |
17 | + </n-upload-dragger> | |
18 | + </n-upload> | |
19 | +</template> | |
20 | + | |
21 | +<script lang="ts" setup name="TKUpload"> | |
22 | +import { ref, PropType, nextTick } from 'vue' | |
23 | +import type { UploadCustomRequestOptions, UploadFileInfo } from 'naive-ui' | |
24 | +import { fetchRouteParamsLocation } from '@/utils' | |
25 | +import { uploadFile } from '@/api/external/contentSave/content' | |
26 | +import { FileTypeEnum } from '@/enums/external/fileTypeEnum' | |
27 | + | |
28 | +interface uploadSizeFormatIF { | |
29 | + size: number | |
30 | + format: string | |
31 | +} | |
32 | + | |
33 | +const props = defineProps({ | |
34 | + uploadImageUrl: { | |
35 | + type: String as PropType<string>, | |
36 | + default: '' | |
37 | + }, | |
38 | + uploadSizeFormat: { | |
39 | + type: Object as PropType<uploadSizeFormatIF>, | |
40 | + default: () => ({ | |
41 | + size: 5, | |
42 | + format: 'png/jpg/jpeg/gif' | |
43 | + }) | |
44 | + } | |
45 | +}) | |
46 | + | |
47 | +const emit = defineEmits(['sendFile', 'removeFile']) | |
48 | + | |
49 | +const fileList = ref<UploadFileInfo[]>() | |
50 | + | |
51 | +// 自定义上传操作 | |
52 | +const customRequest = (options: UploadCustomRequestOptions) => { | |
53 | + const { file } = options | |
54 | + nextTick(async () => { | |
55 | + if (file.file) { | |
56 | + const newNameFile = new File([file.file], `${fetchRouteParamsLocation()}_index_upload.png`, { | |
57 | + type: file.file.type | |
58 | + }) | |
59 | + let uploadParams = new FormData() | |
60 | + uploadParams.append('file', newNameFile) | |
61 | + const uploadRes = await uploadFile(uploadParams) | |
62 | + if (!uploadRes) return | |
63 | + emit('sendFile', uploadRes?.fileStaticUri) | |
64 | + window['$message'].success('上传文件成功!') | |
65 | + } else { | |
66 | + window['$message'].error('上传文件失败,请稍后重试!') | |
67 | + } | |
68 | + }) | |
69 | +} | |
70 | + | |
71 | +// 文件上传前置处理 | |
72 | +const beforeUploadHandle = (file: UploadFileInfo) => { | |
73 | + fileList.value = [] | |
74 | + const type = file.file?.type | |
75 | + const size = file.file?.size as number | |
76 | + const typeSuffix = type?.split('/')?.at(-1)?.toUpperCase() as keyof typeof FileTypeEnum | |
77 | + if (size > 1024 * 1024 * props.uploadSizeFormat.size) { | |
78 | + window['$message'].warning(`文件超出 ${props.uploadSizeFormat.size}M限制,请重新上传!`) | |
79 | + return false | |
80 | + } | |
81 | + if (!FileTypeEnum[typeSuffix]) { | |
82 | + window['$message'].warning('文件格式不符合,请重新上传!') | |
83 | + return false | |
84 | + } | |
85 | + return true | |
86 | +} | |
87 | + | |
88 | +//单个点击删除 | |
89 | +const remove = () => { | |
90 | + fileList.value = [] | |
91 | + emit('removeFile', true) | |
92 | +} | |
93 | +</script> | |
94 | + | |
95 | +<style lang="scss" scoped> | |
96 | +$uploadHeight: 193px; | |
97 | +@include deep() { | |
98 | + .n-card__content { | |
99 | + padding: 0; | |
100 | + overflow: hidden; | |
101 | + } | |
102 | + .n-upload-dragger { | |
103 | + padding: 5px; | |
104 | + } | |
105 | +} | |
106 | +.upload-show { | |
107 | + width: -webkit-fill-available; | |
108 | + height: $uploadHeight; | |
109 | + border-radius: 5px; | |
110 | +} | |
111 | +.upload-img { | |
112 | + display: flex; | |
113 | + flex-direction: column; | |
114 | + align-items: center; | |
115 | + img { | |
116 | + height: 150px; | |
117 | + } | |
118 | + .upload-desc { | |
119 | + padding: 10px 0; | |
120 | + } | |
121 | +} | |
122 | +</style> | ... | ... |
src/enums/external/fileTypeEnum.ts
0 → 100644
... | ... | @@ -11,7 +11,10 @@ export const option = { |
11 | 11 | dataset: dataJson, |
12 | 12 | mapRegion: { |
13 | 13 | adcode: 'china', |
14 | - showHainanIsLands: true | |
14 | + showHainanIsLands: true, | |
15 | + enter: false, | |
16 | + backSize: 20, | |
17 | + backColor: '#ffffff' | |
15 | 18 | }, |
16 | 19 | tooltip: { |
17 | 20 | show: true, |
... | ... | @@ -103,19 +106,19 @@ export const option = { |
103 | 106 | borderColor: 'rgba(147, 235, 248, 0.8)', |
104 | 107 | textStyle: { |
105 | 108 | color: '#FFFFFF', |
106 | - fontSize: 12, | |
109 | + fontSize: 12 | |
107 | 110 | } |
108 | 111 | }, |
109 | 112 | label: { |
110 | 113 | show: false, |
111 | 114 | color: '#FFFFFF', |
112 | - fontSize: 12, | |
115 | + fontSize: 12 | |
113 | 116 | }, |
114 | 117 | emphasis: { |
115 | 118 | disabled: false, |
116 | 119 | label: { |
117 | 120 | color: '#FFFFFF', |
118 | - fontSize: 12, | |
121 | + fontSize: 12 | |
119 | 122 | }, |
120 | 123 | itemStyle: { |
121 | 124 | areaColor: '#389BB7', |
... | ... | @@ -148,6 +151,26 @@ export const option = { |
148 | 151 | shadowOffsetY: 2, |
149 | 152 | shadowBlur: 10 |
150 | 153 | } |
154 | + }, | |
155 | + { | |
156 | + type: 'lines', | |
157 | + zlevel: 2, | |
158 | + effect: { | |
159 | + show: true, | |
160 | + period: 4, //箭头指向速度,值越小速度越快 | |
161 | + trailLength: 0.4, //特效尾迹长度[0,1]值越大,尾迹越长重 | |
162 | + symbol: 'arrow', //箭头图标 | |
163 | + symbolSize: 7 //图标大小 | |
164 | + }, | |
165 | + lineStyle: { | |
166 | + normal: { | |
167 | + color: '#4fb6d2', | |
168 | + width: 1, //线条宽度 | |
169 | + opacity: 0.1, //尾迹线条透明度 | |
170 | + curveness: 0.3 //尾迹线条曲直度 | |
171 | + } | |
172 | + }, | |
173 | + data: [] | |
151 | 174 | } |
152 | 175 | ] |
153 | 176 | } | ... | ... |
... | ... | @@ -69,11 +69,7 @@ |
69 | 69 | </n-space> |
70 | 70 | </SettingItem> |
71 | 71 | <SettingItem name="字体颜色"> |
72 | - <n-color-picker | |
73 | - size="small" | |
74 | - :modes="['hex']" | |
75 | - v-model:value="seriesList[1].label.color" | |
76 | - ></n-color-picker> | |
72 | + <n-color-picker size="small" :modes="['hex']" v-model:value="seriesList[1].label.color"></n-color-picker> | |
77 | 73 | </SettingItem> |
78 | 74 | <SettingItem name="字体大小"> |
79 | 75 | <n-input-number |
... | ... | @@ -129,7 +125,7 @@ |
129 | 125 | ></n-color-picker> |
130 | 126 | </SettingItem> |
131 | 127 | </SettingItemBox> |
132 | - | |
128 | + | |
133 | 129 | <SettingItemBox name="悬浮弹窗"> |
134 | 130 | <SettingItem name="显示"> |
135 | 131 | <n-space> |
... | ... | @@ -180,6 +176,22 @@ |
180 | 176 | <SettingItem> |
181 | 177 | <n-checkbox v-model:checked="mapRegion.showHainanIsLands" size="small">显示南海群岛</n-checkbox> |
182 | 178 | </SettingItem> |
179 | + <SettingItem v-if="seriesList[2]"> | |
180 | + <n-checkbox v-model:checked="mapRegion.enter" size="small">点击进入下级</n-checkbox> | |
181 | + </SettingItem> | |
182 | + </SettingItemBox> | |
183 | + <SettingItemBox name="返回图标" v-if="mapRegion.enter"> | |
184 | + <SettingItem name="颜色"> | |
185 | + <n-color-picker size="small" :modes="['hex']" v-model:value="mapRegion.backColor"></n-color-picker> | |
186 | + </SettingItem> | |
187 | + <SettingItem name="大小"> | |
188 | + <n-input-number | |
189 | + v-model:value="mapRegion.backSize" | |
190 | + :min="1" | |
191 | + size="small" | |
192 | + placeholder="请输入字体大小" | |
193 | + ></n-input-number> | |
194 | + </SettingItem> | |
183 | 195 | </SettingItemBox> |
184 | 196 | </CollapseItem> |
185 | 197 | <CollapseItem name="标记" :expanded="true"> |
... | ... | @@ -191,7 +203,7 @@ |
191 | 203 | <n-color-picker size="small" :modes="['hex']" v-model:value="seriesList[0].itemStyle.color"></n-color-picker> |
192 | 204 | </SettingItem> |
193 | 205 | </SettingItemBox> |
194 | - | |
206 | + | |
195 | 207 | <SettingItemBox name="文本"> |
196 | 208 | <SettingItem name="显示"> |
197 | 209 | <n-space> |
... | ... | @@ -223,6 +235,47 @@ |
223 | 235 | </SettingItem> |
224 | 236 | </SettingItemBox> |
225 | 237 | </CollapseItem> |
238 | + | |
239 | + <CollapseItem v-if="seriesList[2]" name="飞线" :expanded="true"> | |
240 | + <SettingItemBox name="箭头"> | |
241 | + <SettingItem name="速度"> | |
242 | + <n-tooltip trigger="hover"> | |
243 | + <template #trigger> | |
244 | + <n-input-number v-model:value="seriesList[2].effect.period" size="small" :min="0"></n-input-number> | |
245 | + </template> | |
246 | + 值越小速度越快 | |
247 | + </n-tooltip> | |
248 | + </SettingItem> | |
249 | + <SettingItem name="尾迹"> | |
250 | + <n-tooltip trigger="hover"> | |
251 | + <template #trigger> | |
252 | + <n-input-number | |
253 | + v-model:value="seriesList[2].effect.trailLength" | |
254 | + size="small" | |
255 | + :min="0" | |
256 | + :max="1" | |
257 | + ></n-input-number> | |
258 | + </template> | |
259 | + 特效尾迹长度[0,1]值越大,尾迹越长重 | |
260 | + </n-tooltip> | |
261 | + </SettingItem> | |
262 | + <SettingItem name="大小"> | |
263 | + <n-input-number v-model:value="seriesList[2].effect.symbolSize" size="small" :min="0"></n-input-number> | |
264 | + </SettingItem> | |
265 | + </SettingItemBox> | |
266 | + <SettingItemBox name="配置"> | |
267 | + <SettingItem name="颜色"> | |
268 | + <n-color-picker | |
269 | + size="small" | |
270 | + :modes="['hex']" | |
271 | + v-model:value="seriesList[2].lineStyle.normal.color" | |
272 | + ></n-color-picker> | |
273 | + </SettingItem> | |
274 | + <SettingItem name="宽度"> | |
275 | + <n-input-number v-model:value="seriesList[2].lineStyle.normal.width" size="small" :min="1"></n-input-number> | |
276 | + </SettingItem> | |
277 | + </SettingItemBox> | |
278 | + </CollapseItem> | |
226 | 279 | </template> |
227 | 280 | |
228 | 281 | <script setup lang="ts"> | ... | ... |
... | ... | @@ -21,6 +21,32 @@ |
21 | 21 | "value": [126.642464, 45.756967, 101] |
22 | 22 | } |
23 | 23 | ], |
24 | + "line": [ | |
25 | + { | |
26 | + "coords": [ | |
27 | + [113.665412, 34.757975], | |
28 | + [116.405285, 39.904989] | |
29 | + ] | |
30 | + }, | |
31 | + { | |
32 | + "coords": [ | |
33 | + [101.778916, 36.623178], | |
34 | + [116.405285, 39.904989] | |
35 | + ] | |
36 | + }, | |
37 | + { | |
38 | + "coords": [ | |
39 | + [106.278179, 38.46637], | |
40 | + [116.405285, 39.904989] | |
41 | + ] | |
42 | + }, | |
43 | + { | |
44 | + "coords": [ | |
45 | + [126.642464, 45.756967], | |
46 | + [116.405285, 39.904989] | |
47 | + ] | |
48 | + } | |
49 | + ], | |
24 | 50 | "map": [ |
25 | 51 | { |
26 | 52 | "name": "北京市", | ... | ... |
1 | 1 | <template> |
2 | - <v-chart ref="vChartRef" :init-options="initOptions" :theme="themeColor" :option="option.value" :manual-update="isPreview()" autoresize> | |
3 | - </v-chart> | |
2 | + <div> | |
3 | + <div class="back-icon" v-if="(enter && levelHistory.length !== 0) || (enter && !isPreview())" @click="backLevel"> | |
4 | + <n-icon :color="backColor" :size="backSize * 1.1"> | |
5 | + <ArrowBackIcon /> | |
6 | + </n-icon> | |
7 | + <span | |
8 | + :style="{ | |
9 | + 'font-weight': 200, | |
10 | + color: backColor, | |
11 | + 'font-size': `${backSize}px` | |
12 | + }" | |
13 | + > | |
14 | + 返回上级 | |
15 | + </span> | |
16 | + </div> | |
17 | + <v-chart | |
18 | + ref="vChartRef" | |
19 | + :init-options="initOptions" | |
20 | + :theme="themeColor" | |
21 | + :option="option.value" | |
22 | + :manual-update="isPreview()" | |
23 | + autoresize | |
24 | + @click="chartPEvents" | |
25 | + > | |
26 | + </v-chart> | |
27 | + </div> | |
4 | 28 | </template> |
5 | 29 | |
6 | 30 | <script setup lang="ts"> |
7 | -import { PropType, reactive, watch, ref, nextTick } from 'vue' | |
31 | +import { PropType, reactive, watch, ref, nextTick, toRefs } from 'vue' | |
8 | 32 | import config, { includes } from './config' |
9 | 33 | import VChart from 'vue-echarts' |
34 | +import { icon } from '@/plugins' | |
10 | 35 | import { useCanvasInitOptions } from '@/hooks/useCanvasInitOptions.hook' |
11 | 36 | import { use, registerMap } from 'echarts/core' |
12 | 37 | import { EffectScatterChart, MapChart } from 'echarts/charts' |
... | ... | @@ -16,6 +41,7 @@ import { mergeTheme, setOption } from '@/packages/public/chart' |
16 | 41 | import { useChartEditStore } from '@/store/modules/chartEditStore/chartEditStore' |
17 | 42 | import { isPreview } from '@/utils' |
18 | 43 | import mapJsonWithoutHainanIsLands from './mapWithoutHainanIsLands.json' |
44 | +import mapChinaJson from './mapGeojson/china.json' | |
19 | 45 | import { DatasetComponent, GridComponent, TooltipComponent, GeoComponent, VisualMapComponent } from 'echarts/components' |
20 | 46 | |
21 | 47 | const props = defineProps({ |
... | ... | @@ -33,6 +59,10 @@ const props = defineProps({ |
33 | 59 | } |
34 | 60 | }) |
35 | 61 | |
62 | +const { ArrowBackIcon } = icon.ionicons5 | |
63 | +let levelHistory: any = ref([]) | |
64 | + | |
65 | +const { backColor, backSize, enter } = toRefs(props.chartConfig.option.mapRegion) | |
36 | 66 | const initOptions = useCanvasInitOptions(props.chartConfig.option, props.themeSetting) |
37 | 67 | |
38 | 68 | use([ |
... | ... | @@ -67,7 +97,7 @@ registerMap(`${props.chartConfig.option.mapRegion.adcode}`, { geoJSON: {} as any |
67 | 97 | // 进行更换初始化地图 如果为china 单独处理 |
68 | 98 | const registerMapInitAsync = async () => { |
69 | 99 | await nextTick() |
70 | - const adCode = `${props.chartConfig.option.mapRegion.adcode}`; | |
100 | + const adCode = `${props.chartConfig.option.mapRegion.adcode}` | |
71 | 101 | if (adCode !== 'china') { |
72 | 102 | await getGeojson(adCode) |
73 | 103 | } else { |
... | ... | @@ -87,7 +117,16 @@ const vEchartsSetOption = () => { |
87 | 117 | const dataSetHandle = async (dataset: any) => { |
88 | 118 | props.chartConfig.option.series.forEach((item: any) => { |
89 | 119 | if (item.type === 'effectScatter' && dataset.point) item.data = dataset.point |
90 | - else if (item.type === 'map' && dataset.map) item.data = dataset.map | |
120 | + else if (item.type === 'lines' && dataset.line) { | |
121 | + item.data = dataset.line.map((it: any) => { | |
122 | + return { | |
123 | + ...it, | |
124 | + lineStyle: { | |
125 | + color: props.chartConfig.option.series[2].lineStyle.normal.color | |
126 | + } | |
127 | + } | |
128 | + }) | |
129 | + } else if (item.type === 'map' && dataset.map) item.data = dataset.map | |
91 | 130 | }) |
92 | 131 | if (dataset.pieces) props.chartConfig.option.visualMap.pieces = dataset.pieces |
93 | 132 | |
... | ... | @@ -101,6 +140,45 @@ const hainanLandsHandle = async (newData: boolean) => { |
101 | 140 | registerMap('china', { geoJSON: mapJsonWithoutHainanIsLands as any, specialAreas: {} }) |
102 | 141 | } |
103 | 142 | } |
143 | + | |
144 | +// 点击区域 | |
145 | +const chartPEvents = (e: any) => { | |
146 | + if (e.seriesType !== 'map') return | |
147 | + if (!props.chartConfig.option.mapRegion.enter) { | |
148 | + return | |
149 | + } | |
150 | + mapChinaJson.features.forEach(item => { | |
151 | + var pattern = new RegExp(e.name) | |
152 | + if (pattern.test(item.properties.name)) { | |
153 | + let code = String(item.properties.adcode) | |
154 | + levelHistory.value.push(code) | |
155 | + checkOrMap(code) | |
156 | + } | |
157 | + }) | |
158 | +} | |
159 | + | |
160 | +// 返回上一级 | |
161 | +const backLevel = () => { | |
162 | + levelHistory.value = [] | |
163 | + if (levelHistory.value.length > 1) { | |
164 | + levelHistory.value.pop() | |
165 | + const code = levelHistory[levelHistory.value.length - 1] | |
166 | + checkOrMap(code) | |
167 | + } else { | |
168 | + checkOrMap('china') | |
169 | + } | |
170 | +} | |
171 | + | |
172 | +// 切换地图 | |
173 | +const checkOrMap = async (newData: string) => { | |
174 | + await getGeojson(newData) | |
175 | + props.chartConfig.option.geo.map = newData | |
176 | + props.chartConfig.option.series.forEach((item: any) => { | |
177 | + if (item.type === 'map') item.map = newData | |
178 | + }) | |
179 | + vEchartsSetOption() | |
180 | +} | |
181 | + | |
104 | 182 | //监听 dataset 数据发生变化 |
105 | 183 | watch( |
106 | 184 | () => props.chartConfig.option.dataset, |
... | ... | @@ -113,33 +191,42 @@ watch( |
113 | 191 | } |
114 | 192 | ) |
115 | 193 | |
116 | -//监听是否显示南海群岛 | |
117 | -watch( | |
118 | - () => props.chartConfig.option.mapRegion.showHainanIsLands, | |
119 | - async newData => { | |
120 | - try { | |
121 | - await hainanLandsHandle(newData) | |
122 | - vEchartsSetOption() | |
123 | - } catch (error) { | |
124 | - console.log(error) | |
194 | +// 监听线的颜色 | |
195 | +if (props.chartConfig.option.series[2] && !isPreview()) { | |
196 | + watch( | |
197 | + () => props.chartConfig.option.series[2].lineStyle.normal.color, | |
198 | + () => { | |
199 | + dataSetHandle(props.chartConfig.option.dataset) | |
200 | + }, | |
201 | + { | |
202 | + deep: false | |
125 | 203 | } |
126 | - }, | |
127 | - { | |
128 | - deep: false | |
129 | - } | |
130 | -) | |
204 | + ) | |
205 | +} | |
131 | 206 | |
207 | +//监听是否显示南海群岛 | |
208 | +if (!isPreview()) { | |
209 | + watch( | |
210 | + () => props.chartConfig.option.mapRegion.showHainanIsLands, | |
211 | + async newData => { | |
212 | + try { | |
213 | + await hainanLandsHandle(newData) | |
214 | + vEchartsSetOption() | |
215 | + } catch (error) { | |
216 | + console.log(error) | |
217 | + } | |
218 | + }, | |
219 | + { | |
220 | + deep: false | |
221 | + } | |
222 | + ) | |
223 | +} | |
132 | 224 | //监听地图展示区域发生变化 |
133 | 225 | watch( |
134 | 226 | () => `${props.chartConfig.option.mapRegion.adcode}`, |
135 | - async newData => { | |
227 | + newData => { | |
136 | 228 | try { |
137 | - await getGeojson(newData) | |
138 | - props.chartConfig.option.geo.map = newData | |
139 | - props.chartConfig.option.series.forEach((item: any) => { | |
140 | - if (item.type === 'map') item.map = newData | |
141 | - }) | |
142 | - vEchartsSetOption() | |
229 | + checkOrMap(newData) | |
143 | 230 | } catch (error) { |
144 | 231 | console.log(error) |
145 | 232 | } |
... | ... | @@ -154,3 +241,16 @@ useChartDataFetch(props.chartConfig, useChartEditStore, (newData: any) => { |
154 | 241 | dataSetHandle(newData) |
155 | 242 | }) |
156 | 243 | </script> |
244 | + | |
245 | +<style scope lang="scss"> | |
246 | +.back-icon { | |
247 | + z-index: 50; | |
248 | + cursor: pointer; | |
249 | + position: absolute; | |
250 | + display: flex; | |
251 | + align-items: center; | |
252 | + top: 0; | |
253 | + left: 0; | |
254 | + gap: 2px; | |
255 | +} | |
256 | +</style> | ... | ... |
... | ... | @@ -9,7 +9,8 @@ export const includes = [] |
9 | 9 | // 关系图布局 |
10 | 10 | export const GraphLayout = [ |
11 | 11 | { label: '无', value: 'none' }, |
12 | - { label: '环形', value: 'circular' } | |
12 | + { label: '环形', value: 'circular' }, | |
13 | + { label: '力引导', value: 'force' } | |
13 | 14 | ] |
14 | 15 | |
15 | 16 | // 标签开关 |
... | ... | @@ -24,7 +25,13 @@ export const LabelPosition = [ |
24 | 25 | { label: '右侧', value: 'right' }, |
25 | 26 | { label: '顶部', value: 'top' }, |
26 | 27 | { label: '底部', value: 'bottom' }, |
27 | - { label: '内部', value: 'inside' }, | |
28 | + { label: '内部', value: 'inside' } | |
29 | +] | |
30 | + | |
31 | +// 图-迭代动画 | |
32 | +export const LayoutAnimation = [ | |
33 | + { label: '开启', value: 1 }, | |
34 | + { label: '关闭', value: 0 } | |
28 | 35 | ] |
29 | 36 | |
30 | 37 | export const option = { |
... | ... | @@ -33,11 +40,11 @@ export const option = { |
33 | 40 | legend:{ |
34 | 41 | show:true, |
35 | 42 | textStyle:{ |
36 | - color:"#eee", | |
37 | - fontSize: 14 , | |
43 | + color: '#eee', | |
44 | + fontSize: 14 | |
38 | 45 | }, |
39 | 46 | data: dataJson.categories.map(function (a) { |
40 | - return a.name; | |
47 | + return a.name | |
41 | 48 | }) |
42 | 49 | }, |
43 | 50 | series: [ |
... | ... | @@ -47,7 +54,7 @@ export const option = { |
47 | 54 | data: dataJson.nodes, |
48 | 55 | links: dataJson.links, |
49 | 56 | categories: dataJson.categories, |
50 | - label: { // 标签 | |
57 | + label: { | |
51 | 58 | show: 1, |
52 | 59 | position: 'right', |
53 | 60 | formatter: '{b}' |
... | ... | @@ -58,10 +65,17 @@ export const option = { |
58 | 65 | lineStyle: { |
59 | 66 | color: 'source', // 线条颜色 |
60 | 67 | curveness: 0.2 // 线条卷曲程度 |
68 | + }, | |
69 | + force: { | |
70 | + repulsion: 100, | |
71 | + gravity: 0.1, | |
72 | + edgeLength: 30, | |
73 | + layoutAnimation: 1, | |
74 | + friction: 0.6 | |
61 | 75 | } |
62 | 76 | } |
63 | 77 | ] |
64 | - }; | |
78 | +} | |
65 | 79 | |
66 | 80 | export default class Config extends PublicConfigClass implements CreateComponentType { |
67 | 81 | public key = GraphConfig.key | ... | ... |
... | ... | @@ -35,7 +35,58 @@ |
35 | 35 | ></n-color-picker> |
36 | 36 | </SettingItem> |
37 | 37 | <SettingItem name="文本"> |
38 | - <n-input-number v-model:value="optionData.legend.textStyle.fontSize" :min="0" :step="1" size="small" placeholder="文字大小"> | |
38 | + <n-input-number | |
39 | + v-model:value="optionData.legend.textStyle.fontSize" | |
40 | + :min="0" | |
41 | + :step="1" | |
42 | + size="small" | |
43 | + placeholder="文字大小" | |
44 | + > | |
45 | + </n-input-number> | |
46 | + </SettingItem> | |
47 | + </SettingItemBox> | |
48 | + <SettingItemBox name="力引导" v-if="optionData.series[0].force && graphConfig.layout == 'force'"> | |
49 | + <SettingItem name="斥力因子" v-if="optionData.series[0].force.repulsion"> | |
50 | + <n-input-number | |
51 | + v-model:value="optionData.series[0].force.repulsion" | |
52 | + :min="0" | |
53 | + :step="1" | |
54 | + size="small" | |
55 | + placeholder="斥力因子大小" | |
56 | + > | |
57 | + </n-input-number> | |
58 | + </SettingItem> | |
59 | + <SettingItem name="引力因子" v-if="optionData.series[0].force.gravity"> | |
60 | + <n-input-number | |
61 | + v-model:value="optionData.series[0].force.gravity" | |
62 | + :min="0" | |
63 | + :step="0.1" | |
64 | + size="small" | |
65 | + placeholder="引力因子" | |
66 | + > | |
67 | + </n-input-number> | |
68 | + </SettingItem> | |
69 | + <SettingItem name="节点距离"> | |
70 | + <n-input-number | |
71 | + v-model:value="optionData.series[0].force.edgeLength" | |
72 | + :min="0" | |
73 | + :step="1" | |
74 | + size="small" | |
75 | + placeholder="节点距离" | |
76 | + > | |
77 | + </n-input-number> | |
78 | + </SettingItem> | |
79 | + <SettingItem name="迭代动画"> | |
80 | + <n-select v-model:value="graphConfig.force.layoutAnimation" :options="LayoutAnimation" size="small" /> | |
81 | + </SettingItem> | |
82 | + <SettingItem name="节点速度"> | |
83 | + <n-input-number | |
84 | + v-model:value="optionData.series[0].force.friction" | |
85 | + :min="0" | |
86 | + :step="0.1" | |
87 | + size="small" | |
88 | + placeholder="节点速度" | |
89 | + > | |
39 | 90 | </n-input-number> |
40 | 91 | </SettingItem> |
41 | 92 | </SettingItemBox> |
... | ... | @@ -46,7 +97,7 @@ |
46 | 97 | <script setup lang="ts"> |
47 | 98 | import { PropType, computed } from 'vue' |
48 | 99 | import { CollapseItem, SettingItemBox, SettingItem } from '@/components/Pages/ChartItemSetting' |
49 | -import { option, GraphLayout, LabelSwitch, LabelPosition } from './config' | |
100 | +import { option, GraphLayout, LabelSwitch, LabelPosition, LayoutAnimation } from './config' | |
50 | 101 | import { GlobalThemeJsonType } from '@/settings/chartThemes/index' |
51 | 102 | |
52 | 103 | const props = defineProps({ |
... | ... | @@ -56,7 +107,7 @@ const props = defineProps({ |
56 | 107 | } |
57 | 108 | }) |
58 | 109 | |
59 | -const graphConfig = computed<typeof option.series[0]>(() => { | |
110 | +const graphConfig = computed<(typeof option.series)[0]>(() => { | |
60 | 111 | return props.optionData.series[0] |
61 | 112 | }) |
62 | 113 | </script> | ... | ... |
1 | 1 | <template> |
2 | - <v-chart ref="vChartRef" :init-options="initOptions" :theme="themeColor" :option="option" :manual-update="isPreview()" autoresize></v-chart> | |
2 | + <v-chart | |
3 | + ref="vChartRef" | |
4 | + :init-options="initOptions" | |
5 | + :theme="themeColor" | |
6 | + :option="option" | |
7 | + :manual-update="isPreview()" | |
8 | + autoresize | |
9 | + ></v-chart> | |
3 | 10 | </template> |
4 | 11 | |
5 | 12 | <script setup lang="ts"> | ... | ... |
... | ... | @@ -8,4 +8,14 @@ import { DialConfig } from './Dial/index' |
8 | 8 | import { SankeyConfig } from './Sankey/index' |
9 | 9 | import { GraphConfig } from './Graph/index' |
10 | 10 | |
11 | -export default [ProcessConfig, RadarConfig, FunnelConfig, HeatmapConfig, WaterPoloConfig, TreeMapConfig, GraphConfig, SankeyConfig, DialConfig] | |
11 | +export default [ | |
12 | + ProcessConfig, | |
13 | + RadarConfig, | |
14 | + FunnelConfig, | |
15 | + HeatmapConfig, | |
16 | + WaterPoloConfig, | |
17 | + TreeMapConfig, | |
18 | + GraphConfig, | |
19 | + SankeyConfig, | |
20 | + DialConfig | |
21 | +] | ... | ... |
... | ... | @@ -33,8 +33,6 @@ const option = shallowReactive({ |
33 | 33 | dataset: '' |
34 | 34 | }) |
35 | 35 | |
36 | - | |
37 | - | |
38 | 36 | const getStyle = (radius: number) => { |
39 | 37 | return { |
40 | 38 | borderRadius: `${radius}px`, |
... | ... | @@ -78,7 +76,6 @@ watch( |
78 | 76 | return |
79 | 77 | } |
80 | 78 | option.dataset = newData |
81 | - | |
82 | 79 | }, |
83 | 80 | { |
84 | 81 | immediate: true | ... | ... |
... | ... | @@ -161,6 +161,26 @@ export const option = { |
161 | 161 | shadowOffsetY: 2, |
162 | 162 | shadowBlur: 10 |
163 | 163 | } |
164 | + }, | |
165 | + { | |
166 | + type: 'lines', | |
167 | + zlevel: 2, | |
168 | + effect: { | |
169 | + show: true, | |
170 | + period: 4, //箭头指向速度,值越小速度越快 | |
171 | + trailLength: 0.4, //特效尾迹长度[0,1]值越大,尾迹越长重 | |
172 | + symbol: 'arrow', //箭头图标 | |
173 | + symbolSize: 7 //图标大小 | |
174 | + }, | |
175 | + lineStyle: { | |
176 | + normal: { | |
177 | + color: '#4fb6d2', | |
178 | + width: 1, //线条宽度 | |
179 | + opacity: 0.1, //尾迹线条透明度 | |
180 | + curveness: 0.3 //尾迹线条曲直度 | |
181 | + } | |
182 | + }, | |
183 | + data: [] | |
164 | 184 | } |
165 | 185 | ] |
166 | 186 | } | ... | ... |
... | ... | @@ -246,6 +246,46 @@ |
246 | 246 | </SettingItem> |
247 | 247 | </SettingItemBox> |
248 | 248 | </CollapseItem> |
249 | + <CollapseItem v-if="seriesList[2]" name="飞线" :expanded="true"> | |
250 | + <SettingItemBox name="箭头"> | |
251 | + <SettingItem name="速度"> | |
252 | + <n-tooltip trigger="hover"> | |
253 | + <template #trigger> | |
254 | + <n-input-number v-model:value="seriesList[2].effect.period" size="small" :min="0"></n-input-number> | |
255 | + </template> | |
256 | + 值越小速度越快 | |
257 | + </n-tooltip> | |
258 | + </SettingItem> | |
259 | + <SettingItem name="尾迹"> | |
260 | + <n-tooltip trigger="hover"> | |
261 | + <template #trigger> | |
262 | + <n-input-number | |
263 | + v-model:value="seriesList[2].effect.trailLength" | |
264 | + size="small" | |
265 | + :min="0" | |
266 | + :max="1" | |
267 | + ></n-input-number> | |
268 | + </template> | |
269 | + 特效尾迹长度[0,1]值越大,尾迹越长重 | |
270 | + </n-tooltip> | |
271 | + </SettingItem> | |
272 | + <SettingItem name="大小"> | |
273 | + <n-input-number v-model:value="seriesList[2].effect.symbolSize" size="small" :min="0"></n-input-number> | |
274 | + </SettingItem> | |
275 | + </SettingItemBox> | |
276 | + <SettingItemBox name="配置"> | |
277 | + <SettingItem name="颜色"> | |
278 | + <n-color-picker | |
279 | + size="small" | |
280 | + :modes="['hex']" | |
281 | + v-model:value="seriesList[2].lineStyle.normal.color" | |
282 | + ></n-color-picker> | |
283 | + </SettingItem> | |
284 | + <SettingItem name="宽度"> | |
285 | + <n-input-number v-model:value="seriesList[2].lineStyle.normal.width" size="small" :min="1"></n-input-number> | |
286 | + </SettingItem> | |
287 | + </SettingItemBox> | |
288 | + </CollapseItem> | |
249 | 289 | </template> |
250 | 290 | |
251 | 291 | <script setup lang="ts"> | ... | ... |
... | ... | @@ -26,6 +26,36 @@ |
26 | 26 | "value": [126.642464, 45.756967, 101] |
27 | 27 | } |
28 | 28 | ], |
29 | + "line": [ | |
30 | + { | |
31 | + "adcode": 410000, | |
32 | + "coords": [ | |
33 | + [113.665412, 34.757975], | |
34 | + [116.405285, 39.904989] | |
35 | + ] | |
36 | + }, | |
37 | + { | |
38 | + "adcode": 630000, | |
39 | + "coords": [ | |
40 | + [101.778916, 36.623178], | |
41 | + [116.405285, 39.904989] | |
42 | + ] | |
43 | + }, | |
44 | + { | |
45 | + "adcode": 640000, | |
46 | + "coords": [ | |
47 | + [106.278179, 38.46637], | |
48 | + [116.405285, 39.904989] | |
49 | + ] | |
50 | + }, | |
51 | + { | |
52 | + "adcode": 230000, | |
53 | + "coords": [ | |
54 | + [126.642464, 45.756967], | |
55 | + [116.405285, 39.904989] | |
56 | + ] | |
57 | + } | |
58 | + ], | |
29 | 59 | "map": [ |
30 | 60 | { |
31 | 61 | "name": "北京市", | ... | ... |
... | ... | @@ -202,9 +202,19 @@ const vEchartsSetOption = async () => { |
202 | 202 | const dataSetHandle = async (dataset: any) => { |
203 | 203 | props.chartConfig.option.series.forEach((item: any) => { |
204 | 204 | if (item.type === 'effectScatter' && dataset.point) item.data = dataset.point |
205 | - else if (item.type === 'map' && dataset.map) item.data = dataset.map | |
205 | + else if (item.type === 'lines' && dataset.line) { | |
206 | + item.data = dataset.line.map((it: any) => { | |
207 | + return { | |
208 | + ...it, | |
209 | + lineStyle: { | |
210 | + color: props.chartConfig.option.series[2].lineStyle.normal.color | |
211 | + } | |
212 | + } | |
213 | + }) | |
214 | + } else if (item.type === 'map' && dataset.map) item.data = dataset.map | |
206 | 215 | }) |
207 | 216 | if (dataset.pieces) props.chartConfig.option.visualMap.pieces = dataset.pieces |
217 | + | |
208 | 218 | isPreview() && vEchartsSetOption() |
209 | 219 | } |
210 | 220 | |
... | ... | @@ -245,14 +255,16 @@ watch( |
245 | 255 | } |
246 | 256 | ) |
247 | 257 | |
248 | -//处理数据标点 | |
258 | +//处理数据标点包括飞线 | |
249 | 259 | const handleDataPoint = (newData: any) => { |
250 | 260 | if (newData === 'china') { |
251 | 261 | dataSetHandle(dataJson) |
252 | 262 | } else { |
253 | 263 | const filterPoint = dataJson.point.filter((item: any) => item.adcode === newData) |
264 | + // const filterLine = dataJson.line.filter((item: any) => item.adcode === newData) | |
254 | 265 | dataSetHandle({ |
255 | - point: filterPoint | |
266 | + point: filterPoint, | |
267 | + line: [] //由于点击单个地图模块,所以去除飞线 | |
256 | 268 | }) |
257 | 269 | } |
258 | 270 | } |
... | ... | @@ -310,7 +322,7 @@ const handleVChartClick = async (params: any) => { |
310 | 322 | const level = item.properties.level.toUpperCase() |
311 | 323 | const adcode = item.properties.adcode |
312 | 324 | if (level === 'DISTRICT') return |
313 | - if(String(adcode).startsWith('15') && level===areaEnum.CITY) return | |
325 | + if (String(adcode).startsWith('15') && level === areaEnum.CITY) return | |
314 | 326 | props.chartConfig.option.mapRegion.adcode = adcode |
315 | 327 | saveLevelStr.level = level |
316 | 328 | handleDataPoint(adcode) | ... | ... |
... | ... | @@ -2,24 +2,11 @@ |
2 | 2 | <collapse-item name="属性" :expanded="true"> |
3 | 3 | <setting-item-box name="上传图片" :alone="true"> |
4 | 4 | <setting-item> |
5 | - <n-card class="upload-box"> | |
6 | - <n-upload | |
7 | - :show-file-list="false" | |
8 | - v-model:file-list="uploadFileListRef" | |
9 | - :customRequest="customRequest" | |
10 | - :onBeforeUpload="beforeUploadHandle" | |
11 | - > | |
12 | - <n-upload-dragger> | |
13 | - <img v-if="optionData.dataset" class="upload-show" :src="optionData.dataset" alt="背景" /> | |
14 | - <div class="upload-img" v-show="!optionData.dataset"> | |
15 | - <img src="@/assets/images/canvas/noImage.png" /> | |
16 | - <n-text class="upload-desc" depth="3"> | |
17 | - 图片需小于 {{ backgroundImageSize }}M ,格式为 png/jpg/gif 的文件 | |
18 | - </n-text> | |
19 | - </div> | |
20 | - </n-upload-dragger> | |
21 | - </n-upload> | |
22 | - </n-card> | |
5 | + <TKUpload | |
6 | + :uploadImageUrl="props.optionData.dataset" | |
7 | + @sendFile="handleSendFile" | |
8 | + @removeFile="handleRemoveFile" | |
9 | + /> | |
23 | 10 | </setting-item> |
24 | 11 | </setting-item-box> |
25 | 12 | <setting-item-box name="样式"> |
... | ... | @@ -39,14 +26,10 @@ |
39 | 26 | </template> |
40 | 27 | |
41 | 28 | <script setup lang="ts"> |
42 | -import { PropType, ref, nextTick } from 'vue' | |
29 | +import { PropType } from 'vue' | |
43 | 30 | import { option } from './config' |
44 | 31 | import { CollapseItem, SettingItemBox, SettingItem } from '@/components/Pages/ChartItemSetting' |
45 | -import { FileTypeEnum } from '@/enums/fileTypeEnum' | |
46 | -import { uploadFile } from '@/api/external/contentSave/content' | |
47 | -import { UploadCustomRequestOptions } from 'naive-ui' | |
48 | -import { backgroundImageSize } from '@/settings/designSetting' | |
49 | -import { fetchRouteParamsLocation } from '@/utils' | |
32 | +import { TKUpload } from '@/components/external/Common/TKUpload' | |
50 | 33 | |
51 | 34 | const props = defineProps({ |
52 | 35 | optionData: { |
... | ... | @@ -55,48 +38,12 @@ const props = defineProps({ |
55 | 38 | } |
56 | 39 | }) |
57 | 40 | |
58 | -const uploadFileListRef = ref() | |
59 | - | |
60 | -// 上传图片前置处理 | |
61 | -// eslint-disable-next-line @typescript-eslint/ban-ts-comment | |
62 | -//@ts-ignore | |
63 | -const beforeUploadHandle = async ({ file }) => { | |
64 | - uploadFileListRef.value = [] | |
65 | - const type = file.file.type | |
66 | - const size = file.file.size | |
67 | - | |
68 | - if (size > 1024 * 1024 * backgroundImageSize) { | |
69 | - window['$message'].warning(`图片超出 ${backgroundImageSize}M 限制,请重新上传!`) | |
70 | - return false | |
71 | - } | |
72 | - if (type !== FileTypeEnum.PNG && type !== FileTypeEnum.JPEG && type !== FileTypeEnum.GIF) { | |
73 | - window['$message'].warning('文件格式不符合,请重新上传!') | |
74 | - return false | |
75 | - } | |
76 | - return true | |
41 | +const handleSendFile = (file: string) => { | |
42 | + if (!file) return | |
43 | + props.optionData.dataset = file | |
77 | 44 | } |
78 | 45 | |
79 | -// 自定义上传操作 | |
80 | -const customRequest = (options: UploadCustomRequestOptions) => { | |
81 | - const { file } = options | |
82 | - nextTick(async () => { | |
83 | - if (file.file) { | |
84 | - // 修改名称 | |
85 | - const newNameFile = new File([file.file], `${fetchRouteParamsLocation()}_index_background.png`, { | |
86 | - type: file.file.type | |
87 | - }) | |
88 | - let uploadParams = new FormData() | |
89 | - uploadParams.append('file', newNameFile) | |
90 | - const uploadRes = await uploadFile(uploadParams) | |
91 | - if (uploadRes) { | |
92 | - props.optionData.dataset = uploadRes?.fileStaticUri | |
93 | - window['$message'].success('添加图片成功!') | |
94 | - } | |
95 | - } else { | |
96 | - window['$message'].error('添加图片失败,请稍后重试!') | |
97 | - } | |
98 | - }) | |
99 | -} | |
46 | +const handleRemoveFile = (status: boolean) => (status ? (props.optionData.dataset = '') : null) | |
100 | 47 | |
101 | 48 | // 适应类型 |
102 | 49 | const fitList = [ |
... | ... | @@ -122,35 +69,3 @@ const fitList = [ |
122 | 69 | } |
123 | 70 | ] |
124 | 71 | </script> |
125 | -<style lang="scss" scoped> | |
126 | -$uploadHeight: 193px; | |
127 | -.upload-box { | |
128 | - cursor: pointer; | |
129 | - margin-bottom: 20px; | |
130 | - @include deep() { | |
131 | - .n-card__content { | |
132 | - padding: 0; | |
133 | - overflow: hidden; | |
134 | - } | |
135 | - .n-upload-dragger { | |
136 | - padding: 5px; | |
137 | - } | |
138 | - } | |
139 | - .upload-show { | |
140 | - width: -webkit-fill-available; | |
141 | - height: $uploadHeight; | |
142 | - border-radius: 5px; | |
143 | - } | |
144 | - .upload-img { | |
145 | - display: flex; | |
146 | - flex-direction: column; | |
147 | - align-items: center; | |
148 | - img { | |
149 | - height: 150px; | |
150 | - } | |
151 | - .upload-desc { | |
152 | - padding: 10px 0; | |
153 | - } | |
154 | - } | |
155 | -} | |
156 | -</style> | ... | ... |
1 | 1 | <template> |
2 | - <div class="videoPlay"> | |
2 | + <div class="go-content-box" :style="{ width: w + 'px', height: h + 'px' }"> | |
3 | 3 | <video |
4 | - style="object-fit: cover" | |
5 | - :poster="poster" | |
6 | 4 | crossOrigin="anonymous" |
5 | + :id="`my-player`" | |
7 | 6 | ref="videoRef" |
8 | - class="video-js vjs-default-skin vjs-big-play-centered" | |
9 | - controls | |
10 | - > | |
11 | - <source :src="path" /> | |
12 | - </video> | |
7 | + class="video-js my-video vjs-theme-city vjs-big-play-centered" | |
8 | + ></video> | |
13 | 9 | </div> |
14 | 10 | </template> |
15 | -<script lang="ts" setup> | |
16 | -import { nextTick, onBeforeUnmount, onMounted, ref, watch, toRefs } from 'vue' | |
17 | -import videojs, { VideoJsPlayer } from 'video.js' | |
11 | +<script setup lang="ts"> | |
12 | +import { onMounted, ref, onUnmounted, watch, unref } from 'vue' | |
13 | +import videojs from 'video.js' | |
14 | +import 'videojs-flvjs-es6' | |
18 | 15 | import type { VideoJsPlayerOptions } from 'video.js' |
19 | -import 'video.js/dist/video-js.css' | |
20 | -import zh from 'video.js/dist/lang/zh-CN.json' | |
21 | - | |
22 | -const props = withDefaults( | |
23 | - defineProps<{ | |
24 | - path: string | |
25 | - autoPlay?: boolean | |
26 | - h?: number | |
27 | - poster?: string | |
28 | - }>(), | |
29 | - { autoPlay: false } | |
30 | -) | |
16 | +import 'video.js/dist/video-js.min.css' | |
17 | +import { getJwtToken, getShareJwtToken } from '@/utils/external/auth' | |
18 | +import { isShareMode } from '@/views/share/hook' | |
19 | +import { getOpenFlvPlayUrl, closeFlvPlay } from '@/api/external/flvPlay' | |
20 | +import { useFingerprint } from '@/utils/external/useFingerprint' | |
21 | +import { GetResult } from '@fingerprintjs/fingerprintjs' | |
31 | 22 | |
32 | -const videoRef = ref() | |
33 | - | |
34 | -let player: VideoJsPlayer | |
35 | - | |
36 | -const initPlay = async () => { | |
37 | - videojs.addLanguage('zh-CN', zh) | |
38 | - await nextTick() | |
39 | - const options: VideoJsPlayerOptions = { | |
40 | - muted: true, | |
41 | - controls: true, | |
42 | - autoplay: props.autoPlay, | |
43 | - src: props?.path, | |
44 | - poster: props?.poster, | |
45 | - language: 'zh-CN', | |
46 | - techOrder: ['html5'], | |
47 | - preload: 'none' | |
23 | +const props = defineProps({ | |
24 | + sourceSrc: { | |
25 | + type: String | |
26 | + }, | |
27 | + autoplay: { | |
28 | + type: Boolean | |
29 | + }, | |
30 | + name: { | |
31 | + type: String | |
32 | + }, | |
33 | + avatar: { | |
34 | + type: String | |
35 | + }, | |
36 | + w: { | |
37 | + type: Number, | |
38 | + default: 300 | |
39 | + }, | |
40 | + h: { | |
41 | + type: Number, | |
42 | + default: 300 | |
48 | 43 | } |
49 | - player = videojs(videoRef.value, options, () => { | |
50 | - videojs.log('Video is reading') | |
51 | - if (props.autoPlay) { | |
52 | - player.play() | |
44 | +}) | |
45 | + | |
46 | +enum VideoPlayerType { | |
47 | + m3u8 = 'application/x-mpegURL', | |
48 | + mp4 = 'video/mp4', | |
49 | + webm = 'video/webm', | |
50 | + flv = 'video/x-flv' | |
51 | +} | |
52 | + | |
53 | +const isRtspProtocol = (url: string) => { | |
54 | + const reg = /^rtsp:\/\//g | |
55 | + return reg.test(url) | |
56 | +} | |
57 | + | |
58 | +const getVideoTypeByUrl = (url = '') => { | |
59 | + try { | |
60 | + const { protocol, pathname } = new URL(url) | |
61 | + if (protocol.startsWith('rtsp:')) return VideoPlayerType.flv | |
62 | + const reg = /[^.]\w*$/ | |
63 | + const mathValue = pathname.match(reg) || [] | |
64 | + const ext = (mathValue[0] as keyof typeof VideoPlayerType) || 'webm' | |
65 | + const type = VideoPlayerType[ext] | |
66 | + return type ? type : VideoPlayerType.webm | |
67 | + } catch (error) { | |
68 | + console.error(error) | |
69 | + return VideoPlayerType.webm | |
70 | + } | |
71 | +} | |
72 | + | |
73 | +// video标签 | |
74 | +const videoRef = ref<HTMLElement | null>(null) | |
75 | + | |
76 | +// video实例对象 | |
77 | +let videoPlayer: videojs.Player | null = null | |
78 | + | |
79 | +const fingerprintResult = ref<Nullable<GetResult>>(null) | |
80 | + | |
81 | +//options配置 | |
82 | +const options: VideoJsPlayerOptions & Recordable = { | |
83 | + language: 'zh-CN', // 设置语言 | |
84 | + controls: true, // 是否显示控制条 | |
85 | + preload: 'auto', // 预加载 | |
86 | + autoplay: true, // 是否自动播放 | |
87 | + fluid: false, // 自适应宽高 | |
88 | + poster: props?.avatar || '', | |
89 | + // src: getSource() || '', // 要嵌入的视频源的源 URL | |
90 | + sources: [], | |
91 | + muted: true, | |
92 | + userActions: { | |
93 | + hotkeys: true | |
94 | + }, | |
95 | + techOrder: ['html5', 'flvjs'], | |
96 | + flvjs: { | |
97 | + mediaDataSource: { | |
98 | + isLive: true, | |
99 | + cors: true, | |
100 | + withCredentials: false, | |
101 | + hasAudio: false | |
102 | + }, | |
103 | + config: { | |
104 | + autoCleanupSourceBuffer: true | |
53 | 105 | } |
54 | - player.on('ended', () => { | |
55 | - videojs.log('Play end') | |
56 | - }) | |
57 | - player.on('error', () => { | |
58 | - player.errorDisplay.close() | |
59 | - videojs.log('Play parse error') | |
60 | - }) | |
61 | - }) | |
106 | + } | |
62 | 107 | } |
63 | 108 | |
64 | -onMounted(() => { | |
65 | - initPlay() | |
66 | -}) | |
109 | +const { getResult } = useFingerprint() | |
110 | +async function getSource() { | |
111 | + fingerprintResult.value = await getResult() | |
112 | + let src = props.sourceSrc || '' | |
113 | + if (isRtspProtocol(props.sourceSrc!)) { | |
114 | + src = getOpenFlvPlayUrl(src, unref(fingerprintResult)?.visitorId || '') | |
115 | + } | |
116 | + return [ | |
117 | + { | |
118 | + type: getVideoTypeByUrl(props.sourceSrc), | |
119 | + src | |
120 | + } | |
121 | + ] | |
122 | +} | |
67 | 123 | |
68 | -//直接改变路径测试 | |
69 | -watch( | |
70 | - () => props.path, | |
71 | - () => { | |
72 | - if (props.path && props.autoPlay) { | |
73 | - try { | |
74 | - player?.pause() | |
75 | - player?.load() | |
76 | - player?.src(props.path) | |
77 | - player?.play() | |
78 | - } catch (e) { | |
79 | - console.log(e) | |
124 | +// 初始化videojs | |
125 | +const initVideo = async () => { | |
126 | + if (videoRef.value) { | |
127 | + // 创建 video 实例 | |
128 | + options.sources = await getSource() | |
129 | + if (options.sources && options.sources.length) { | |
130 | + if (isRtspProtocol(props.sourceSrc || '')) { | |
131 | + options.flvjs = { | |
132 | + ...(options.flvjs || {}), | |
133 | + config: { | |
134 | + headers: { | |
135 | + 'X-Authorization': `Bearer ${isShareMode() ? getShareJwtToken() : getJwtToken()}` | |
136 | + } | |
137 | + } | |
138 | + } | |
80 | 139 | } |
140 | + videoPlayer = videojs(videoRef.value, options) | |
81 | 141 | } |
142 | + } | |
143 | +} | |
144 | + | |
145 | +watch( | |
146 | + () => props.sourceSrc, | |
147 | + async (newData: any) => { | |
148 | + const result = await getSource() | |
149 | + // props.sourceSrc = newData | |
150 | + videoPlayer?.src(result) as any | |
151 | + videoPlayer?.play() | |
82 | 152 | }, |
83 | 153 | { |
84 | 154 | immediate: true |
85 | 155 | } |
86 | 156 | ) |
87 | 157 | |
88 | -onBeforeUnmount(() => { | |
89 | - player?.dispose() | |
158 | +onMounted(() => { | |
159 | + initVideo() | |
160 | +}) | |
161 | + | |
162 | +onUnmounted(() => { | |
163 | + if (props.sourceSrc) { | |
164 | + closeFlvPlay(props.sourceSrc, unref(fingerprintResult)!.visitorId!) | |
165 | + } | |
166 | + handleVideoDispose() | |
167 | +}) | |
168 | + | |
169 | +//播放 | |
170 | +const handleVideoPlay = () => videoPlayer?.play() | |
171 | + | |
172 | +const handleVideoDispose = () => videoPlayer?.dispose() && videoPlayer?.pause() | |
173 | +//暂停 | |
174 | +defineExpose({ | |
175 | + handleVideoPlay, | |
176 | + handleVideoDispose | |
90 | 177 | }) |
91 | 178 | </script> |
92 | -<style> | |
93 | -.vjs-poster { | |
94 | - background-size: cover !important; | |
95 | -} | |
96 | -</style> | |
179 | + | |
97 | 180 | <style lang="scss" scoped> |
98 | -.videoPlay { | |
99 | - flex: 1; | |
100 | - height: v-bind('`${h}px`'); | |
101 | - .video-js { | |
102 | - height: 100%; | |
103 | - width: 100%; | |
181 | +.go-content-box { | |
182 | + display: flex; | |
183 | + align-items: center; | |
184 | + justify-content: center; | |
185 | + | |
186 | + .my-video { | |
187 | + width: 100% !important; | |
188 | + height: 100% !important; | |
189 | + position: relative; | |
104 | 190 | } |
105 | 191 | } |
106 | 192 | </style> | ... | ... |
... | ... | @@ -2,29 +2,12 @@ |
2 | 2 | <CollapseItem name="播放器配置" :expanded="true"> |
3 | 3 | <setting-item-box name="上传图片" :alone="true"> |
4 | 4 | <setting-item> |
5 | - <n-card class="upload-box"> | |
6 | - <n-upload | |
7 | - :show-file-list="false" | |
8 | - v-model:file-list="uploadFileListRef" | |
9 | - :customRequest="customRequest" | |
10 | - :onBeforeUpload="beforeUploadHandle" | |
11 | - > | |
12 | - <n-upload-dragger> | |
13 | - <img v-if="optionData.poster" class="upload-show" :src="optionData.poster" alt="背景" /> | |
14 | - <div class="upload-img" v-show="!optionData.poster"> | |
15 | - <img src="@/assets/images/canvas/noImage.png" /> | |
16 | - <n-text class="upload-desc" depth="3"> | |
17 | - 图片需小于 {{ backgroundImageSize }}M ,格式为 png/jpg/gif 的文件 | |
18 | - </n-text> | |
19 | - </div> | |
20 | - </n-upload-dragger> | |
21 | - </n-upload> | |
22 | - </n-card> | |
5 | + <TKUpload :uploadImageUrl="optionData.poster" @sendFile="handleSendFile" @removeFile="handleRemoveFile" /> | |
23 | 6 | </setting-item> |
24 | 7 | </setting-item-box> |
25 | 8 | <setting-item-box name="源类型" :alone="true"> |
26 | 9 | <setting-item> |
27 | - <n-radio-group @change="handleChecked" v-model:value="optionData.sourceType" name="radiogroup"> | |
10 | + <n-radio-group @update:value="handleChecked" v-model:value="optionData.sourceType" name="radiogroup"> | |
28 | 11 | <n-space> |
29 | 12 | <n-radio v-for="(item, index) in sourceTypes" :key="item.value" :value="item.value"> |
30 | 13 | {{ item.label }} |
... | ... | @@ -55,7 +38,12 @@ |
55 | 38 | </setting-item-box> |
56 | 39 | <setting-item-box v-if="optionData.sourceType === sourceTypeEnum.PLATFORM" name="视频" :alone="true"> |
57 | 40 | <setting-item> |
58 | - <n-select @update:value="handleSelect" v-model:value="optionData.dataset" :options="videoOptions" /> | |
41 | + <n-select | |
42 | + @update:value="handleSelect" | |
43 | + v-model:value="optionData.dataset" | |
44 | + :options="videoOptions" | |
45 | + placeholder="请选择视频地址" | |
46 | + /> | |
59 | 47 | </setting-item> |
60 | 48 | </setting-item-box> |
61 | 49 | <setting-item-box name="自动播放"> |
... | ... | @@ -67,15 +55,21 @@ |
67 | 55 | </template> |
68 | 56 | |
69 | 57 | <script setup lang="ts"> |
70 | -import { PropType, ref, nextTick, onMounted } from 'vue' | |
58 | +import { PropType, ref, onMounted } from 'vue' | |
71 | 59 | import { option, sourceTypeEnum } from './config' |
72 | 60 | import { CollapseItem, SettingItemBox, SettingItem } from '@/components/Pages/ChartItemSetting' |
73 | -import { FileTypeEnum } from '@/enums/fileTypeEnum' | |
74 | -import { uploadFile } from '@/api/external/contentSave/content' | |
75 | -import { UploadCustomRequestOptions, NTreeSelect } from 'naive-ui' | |
76 | -import { backgroundImageSize } from '@/settings/designSetting' | |
77 | -import { fetchRouteParamsLocation } from '@/utils' | |
61 | +import { NTreeSelect } from 'naive-ui' | |
78 | 62 | import { getOrganizationList, getVideoList, getVideoUrl } from '@/api/external/common/index' |
63 | +import { TKUpload } from '@/components/external/Common/TKUpload' | |
64 | + | |
65 | +interface videoListIF { | |
66 | + name: string | |
67 | + accessMode: number | |
68 | + id: string | |
69 | + videoUrl: string | |
70 | + label: string | |
71 | + value: string | |
72 | +} | |
79 | 73 | |
80 | 74 | const props = defineProps({ |
81 | 75 | optionData: { |
... | ... | @@ -84,8 +78,6 @@ const props = defineProps({ |
84 | 78 | } |
85 | 79 | }) |
86 | 80 | |
87 | -const uploadFileListRef = ref() | |
88 | - | |
89 | 81 | const sourceTypes = [ |
90 | 82 | { |
91 | 83 | value: 'custom', |
... | ... | @@ -99,7 +91,7 @@ const sourceTypes = [ |
99 | 91 | |
100 | 92 | const originationOption = ref([]) |
101 | 93 | |
102 | -const videoOptions = ref([]) | |
94 | +const videoOptions = ref<videoListIF[]>([]) | |
103 | 95 | |
104 | 96 | const getOriginationList = async () => { |
105 | 97 | const res = await getOrganizationList() |
... | ... | @@ -113,7 +105,8 @@ const handleUpdateTreeValue = (value: string) => { |
113 | 105 | |
114 | 106 | const getVideoLists = async (organizationId: string) => { |
115 | 107 | const res = await getVideoList({ organizationId }) |
116 | - videoOptions.value = res?.data?.map((item: any) => ({ | |
108 | + if (!res) return | |
109 | + videoOptions.value = res?.data?.map((item: videoListIF) => ({ | |
117 | 110 | label: item.name, |
118 | 111 | value: item.accessMode === 1 ? item.id : item.videoUrl, |
119 | 112 | id: item.id, |
... | ... | @@ -128,16 +121,16 @@ const getVideoUrlById = async (id: string) => { |
128 | 121 | props.optionData.url = url |
129 | 122 | } |
130 | 123 | |
131 | -const handleChecked = ({ target }: any) => { | |
124 | +const handleChecked = (value: string) => { | |
132 | 125 | props.optionData.dataset = '' |
133 | - const { value } = target | |
134 | 126 | if (value === sourceTypeEnum.PLATFORM) { |
135 | 127 | getOriginationList() |
136 | 128 | } |
137 | 129 | } |
138 | 130 | |
139 | -const handleSelect = (value: string, e: any) => { | |
131 | +const handleSelect = (_: string, e: videoListIF) => { | |
140 | 132 | const { accessMode, id } = e |
133 | + //1表示,需要从服务端调取接口换取播放的地址,0则不需要 | |
141 | 134 | if (accessMode === 1) { |
142 | 135 | getVideoUrlById(id) |
143 | 136 | } else { |
... | ... | @@ -154,76 +147,10 @@ onMounted(() => { |
154 | 147 | } |
155 | 148 | }) |
156 | 149 | |
157 | -// 上传图片前置处理 | |
158 | -// eslint-disable-next-line @typescript-eslint/ban-ts-comment | |
159 | -//@ts-ignore | |
160 | -const beforeUploadHandle = async ({ file }) => { | |
161 | - uploadFileListRef.value = [] | |
162 | - const type = file.file.type | |
163 | - const size = file.file.size | |
164 | - | |
165 | - if (size > 1024 * 1024 * backgroundImageSize) { | |
166 | - window['$message'].warning(`图片超出 ${backgroundImageSize}M 限制,请重新上传!`) | |
167 | - return false | |
168 | - } | |
169 | - if (type !== FileTypeEnum.PNG && type !== FileTypeEnum.JPEG && type !== FileTypeEnum.GIF) { | |
170 | - window['$message'].warning('文件格式不符合,请重新上传!') | |
171 | - return false | |
172 | - } | |
173 | - return true | |
150 | +const handleSendFile = (file: string) => { | |
151 | + if (!file) return | |
152 | + props.optionData.poster = file | |
174 | 153 | } |
175 | 154 | |
176 | -// 自定义上传操作 | |
177 | -const customRequest = (options: UploadCustomRequestOptions) => { | |
178 | - const { file } = options | |
179 | - nextTick(async () => { | |
180 | - if (file.file) { | |
181 | - // 修改名称 | |
182 | - const newNameFile = new File([file.file], `${fetchRouteParamsLocation()}_index_background.png`, { | |
183 | - type: file.file.type | |
184 | - }) | |
185 | - let uploadParams = new FormData() | |
186 | - uploadParams.append('file', newNameFile) | |
187 | - const uploadRes = await uploadFile(uploadParams) | |
188 | - if (uploadRes) { | |
189 | - props.optionData.poster = uploadRes?.fileStaticUri | |
190 | - window['$message'].success('添加图片成功!') | |
191 | - } | |
192 | - } else { | |
193 | - window['$message'].error('添加图片失败,请稍后重试!') | |
194 | - } | |
195 | - }) | |
196 | -} | |
155 | +const handleRemoveFile = (status: boolean) => (status ? (props.optionData.poster = '') : null) | |
197 | 156 | </script> |
198 | -<style lang="scss" scoped> | |
199 | -$uploadHeight: 193px; | |
200 | -.upload-box { | |
201 | - cursor: pointer; | |
202 | - margin-bottom: 20px; | |
203 | - @include deep() { | |
204 | - .n-card__content { | |
205 | - padding: 0; | |
206 | - overflow: hidden; | |
207 | - } | |
208 | - .n-upload-dragger { | |
209 | - padding: 5px; | |
210 | - } | |
211 | - } | |
212 | - .upload-show { | |
213 | - width: -webkit-fill-available; | |
214 | - height: $uploadHeight; | |
215 | - border-radius: 5px; | |
216 | - } | |
217 | - .upload-img { | |
218 | - display: flex; | |
219 | - flex-direction: column; | |
220 | - align-items: center; | |
221 | - img { | |
222 | - height: 150px; | |
223 | - } | |
224 | - .upload-desc { | |
225 | - padding: 10px 0; | |
226 | - } | |
227 | - } | |
228 | -} | |
229 | -</style> | ... | ... |
1 | 1 | <template> |
2 | 2 | <div> |
3 | - <VideoPlay :h="h" :path="option.dataset" :autoPlay="autoplay" :poster="option.poster" /> | |
3 | + <VideoPlay :w="w" :h="h" :sourceSrc="option.dataset" :autoPlay="autoplay" :avatar="option.poster" /> | |
4 | 4 | </div> |
5 | 5 | </template> |
6 | 6 | <script setup lang="ts"> |
... | ... | @@ -16,7 +16,7 @@ const props = defineProps({ |
16 | 16 | } |
17 | 17 | }) |
18 | 18 | |
19 | -const { h } = toRefs(props.chartConfig.attr) | |
19 | +const { w, h } = toRefs(props.chartConfig.attr) | |
20 | 20 | |
21 | 21 | const { autoplay, dataset, poster, url } = toRefs(props.chartConfig.option) |
22 | 22 | ... | ... |
... | ... | @@ -2,7 +2,7 @@ |
2 | 2 | <collapse-item name="信息" :expanded="true"> |
3 | 3 | <setting-item-box name="文字" :alone="true"> |
4 | 4 | <setting-item> |
5 | - <n-input v-model:value="optionData.dataset" size="small"></n-input> | |
5 | + <n-input v-model:value="optionData.dataset" type="textarea" size="small"></n-input> | |
6 | 6 | </setting-item> |
7 | 7 | </setting-item-box> |
8 | 8 | </collapse-item> | ... | ... |
... | ... | @@ -2,7 +2,7 @@ |
2 | 2 | <collapse-item name="信息" :expanded="true"> |
3 | 3 | <setting-item-box name="文字" :alone="true"> |
4 | 4 | <setting-item> |
5 | - <n-input v-model:value="optionData.dataset" size="small"></n-input> | |
5 | + <n-input v-model:value="optionData.dataset" type="textarea" size="small"></n-input> | |
6 | 6 | </setting-item> |
7 | 7 | </setting-item-box> |
8 | 8 | <setting-item-box name="链接" :alone="true"> | ... | ... |
1 | 1 | <template> |
2 | 2 | <div class="go-text-box"> |
3 | 3 | <div class="content"> |
4 | - <span style="cursor: pointer" v-if="link" @click="click">{{ option.dataset }}</span> | |
5 | - <span v-else>{{ option.dataset }}</span> | |
4 | + <span style="cursor: pointer;white-space: pre-wrap" v-if="link" @click="click">{{ option.dataset }}</span> | |
5 | + <span style="white-space: pre-wrap" v-else>{{ option.dataset }}</span> | |
6 | 6 | </div> |
7 | 7 | </div> |
8 | 8 | </template> | ... | ... |
... | ... | @@ -2,7 +2,7 @@ |
2 | 2 | <collapse-item name="信息" :expanded="true"> |
3 | 3 | <setting-item-box name="文字" :alone="true"> |
4 | 4 | <setting-item> |
5 | - <n-input v-model:value="optionData.dataset" size="small"></n-input> | |
5 | + <n-input v-model:value="optionData.dataset" type="textarea" size="small"></n-input> | |
6 | 6 | </setting-item> |
7 | 7 | </setting-item-box> |
8 | 8 | </collapse-item> | ... | ... |
... | ... | @@ -27,6 +27,16 @@ export let packagesList: PackagesType = { |
27 | 27 | [PackagesCategoryEnum.ICONS]: IconList |
28 | 28 | } |
29 | 29 | |
30 | +// 组件缓存, 可以大幅度提升组件加载速度 | |
31 | +const componentCacheMap = new Map<string, any>() | |
32 | +const loadConfig = (packageName: string, categoryName: string, keyName: string) => { | |
33 | + const key = packageName + categoryName + keyName | |
34 | + if (!componentCacheMap.has(key)) { | |
35 | + componentCacheMap.set(key, import(`./components/${packageName}/${categoryName}/${keyName}/config.ts`)) | |
36 | + } | |
37 | + return componentCacheMap.get(key) | |
38 | +} | |
39 | + | |
30 | 40 | /** |
31 | 41 | * * 获取目标组件配置信息 |
32 | 42 | * @param targetData |
... | ... | @@ -36,10 +46,10 @@ export const createComponent = async (targetData: ConfigType) => { |
36 | 46 | // redirectComponent 是给图片组件库和图标组件库使用的 |
37 | 47 | if (redirectComponent) { |
38 | 48 | const [packageName, categoryName, keyName] = redirectComponent.split('/') |
39 | - const redirectChart = await import(`./components/${packageName}/${categoryName}/${keyName}/config.ts`) | |
49 | + const redirectChart = await loadConfig(packageName, categoryName, keyName) | |
40 | 50 | return new redirectChart.default() |
41 | 51 | } |
42 | - const chart = await import(`./components/${targetData.package}/${category}/${key}/config.ts`) | |
52 | + const chart = await loadConfig(targetData.package, category, key) | |
43 | 53 | return new chart.default() |
44 | 54 | } |
45 | 55 | |
... | ... | @@ -98,5 +108,4 @@ export const fetchImages = async (targetData?: ConfigType) => { |
98 | 108 | } |
99 | 109 | return '' |
100 | 110 | } |
101 | - | |
102 | 111 | useInjectLib(packagesList as any) // THINGS_KIT 修改注册组件 | ... | ... |
... | ... | @@ -24,7 +24,6 @@ import cloneDeep from 'lodash/cloneDeep' |
24 | 24 | * 修改后的代码在注释之间,并标注好源代码和修改后代码,方便回溯 |
25 | 25 | * 源代码 requestDataType: RequestDataTypeEnum.STATIC, |
26 | 26 | * 修改后的代码 requestDataType: RequestDataTypeEnum.Pond, |
27 | - * 修改后代码在//ft之间 | |
28 | 27 | */ |
29 | 28 | // 请求基础属性 |
30 | 29 | export const requestConfig: RequestConfigType = { |
... | ... | @@ -49,7 +48,6 @@ export const requestConfig: RequestConfigType = { |
49 | 48 | Params: {} |
50 | 49 | } |
51 | 50 | } |
52 | -//ft之间 | |
53 | 51 | |
54 | 52 | // 单实例类 |
55 | 53 | export class PublicConfigClass implements PublicConfigType { | ... | ... |
... | ... | @@ -162,17 +162,17 @@ export const useChartEditStore = defineStore({ |
162 | 162 | }, |
163 | 163 | getComponentList(): Array<CreateComponentType | CreateComponentGroupType> { |
164 | 164 | return this.componentList |
165 | - }, | |
166 | - // 获取需要存储的数据项 | |
165 | + } | |
166 | + }, | |
167 | + actions: { | |
168 | + // * 获取需要存储的数据项 | |
167 | 169 | getStorageInfo(): ChartEditStorage { |
168 | 170 | return { |
169 | 171 | [ChartEditStoreEnum.EDIT_CANVAS_CONFIG]: this.getEditCanvasConfig, |
170 | 172 | [ChartEditStoreEnum.COMPONENT_LIST]: this.getComponentList, |
171 | 173 | [ChartEditStoreEnum.REQUEST_GLOBAL_CONFIG]: this.getRequestGlobalConfig |
172 | 174 | } |
173 | - } | |
174 | - }, | |
175 | - actions: { | |
175 | + }, | |
176 | 176 | // * 设置 editCanvas 数据项 |
177 | 177 | setEditCanvas<T extends keyof EditCanvasType, K extends EditCanvasType[T]>(key: T, value: K) { |
178 | 178 | this.editCanvas[key] = value | ... | ... |
... | ... | @@ -10,13 +10,7 @@ import { useInjectAside } from './external/useInjectAside' |
10 | 10 | |
11 | 11 | // 图标 |
12 | 12 | const { AirPlaneOutlineIcon, ImageIcon, BarChartIcon } = icon.ionicons5 |
13 | -const { | |
14 | - TableSplitIcon, | |
15 | - RoadmapIcon, | |
16 | - SpellCheckIcon, | |
17 | - GraphicalDataFlowIcon, | |
18 | -} = icon.carbon | |
19 | - | |
13 | +const { TableSplitIcon, RoadmapIcon, SpellCheckIcon, GraphicalDataFlowIcon } = icon.carbon | |
20 | 14 | |
21 | 15 | // 图表 |
22 | 16 | export type MenuOptionsType = { | ... | ... |
... | ... | @@ -19,14 +19,14 @@ export const syncData = () => { |
19 | 19 | transformOrigin: 'center', |
20 | 20 | onPositiveCallback: () => { |
21 | 21 | window['$message'].success('正在同步编辑器...') |
22 | - dispatchEvent(new CustomEvent(SavePageEnum.CHART, { detail: chartEditStore.getStorageInfo })) | |
22 | + dispatchEvent(new CustomEvent(SavePageEnum.CHART, { detail: chartEditStore.getStorageInfo() })) | |
23 | 23 | } |
24 | 24 | }) |
25 | 25 | } |
26 | 26 | |
27 | 27 | // 同步数据到预览页 |
28 | 28 | export const syncDataToPreview = () => { |
29 | - dispatchEvent(new CustomEvent(SavePageEnum.CHART_TO_PREVIEW, { detail: chartEditStore.getStorageInfo })) | |
29 | + dispatchEvent(new CustomEvent(SavePageEnum.CHART_TO_PREVIEW, { detail: chartEditStore.getStorageInfo() })) | |
30 | 30 | } |
31 | 31 | |
32 | 32 | // 侦听器更新 | ... | ... |
... | ... | @@ -160,7 +160,7 @@ const editHandle = () => { |
160 | 160 | |
161 | 161 | // 把内存中的数据同步到SessionStorage 便于传递给新窗口初始化数据 |
162 | 162 | const updateToSession = (id: string) => { |
163 | - const storageInfo = chartEditStore.getStorageInfo | |
163 | + const storageInfo = chartEditStore.getStorageInfo() | |
164 | 164 | const sessionStorageInfo = getLocalStorage(StorageEnum.GO_CHART_STORAGE_LIST) || [] |
165 | 165 | |
166 | 166 | if (sessionStorageInfo?.length) { | ... | ... |
... | ... | @@ -32,7 +32,7 @@ const previewHandle = () => { |
32 | 32 | const { id } = routerParamsInfo.params |
33 | 33 | // id 标识 |
34 | 34 | const previewId = typeof id === 'string' ? id : id[0] |
35 | - const storageInfo = chartEditStore.getStorageInfo | |
35 | + const storageInfo = chartEditStore.getStorageInfo() | |
36 | 36 | const sessionStorageInfo = getLocalStorage(StorageEnum.GO_CHART_STORAGE_LIST) || [] |
37 | 37 | |
38 | 38 | if (sessionStorageInfo?.length) { | ... | ... |
... | ... | @@ -32,7 +32,7 @@ const previewHandle = () => { |
32 | 32 | const { id } = routerParamsInfo.params |
33 | 33 | // id 标识 |
34 | 34 | const previewId = typeof id === 'string' ? id : id[0] |
35 | - const storageInfo = chartEditStore.getStorageInfo | |
35 | + const storageInfo = chartEditStore.getStorageInfo() | |
36 | 36 | const sessionStorageInfo = getLocalStorage(StorageEnum.GO_CHART_STORAGE_LIST) || [] |
37 | 37 | |
38 | 38 | if (sessionStorageInfo?.length) { | ... | ... |
... | ... | @@ -76,7 +76,7 @@ const handleBlur = async () => { |
76 | 76 | dataViewId, |
77 | 77 | dataViewContent: { |
78 | 78 | id: dataViewContent.id, |
79 | - content: JSON.stringify(chartEditStore.getStorageInfo) | |
79 | + content: JSON.stringify(chartEditStore.getStorageInfo()) | |
80 | 80 | } |
81 | 81 | } as unknown as BaseUpdateContentParams |
82 | 82 | await contentUpdateApi(saveContent) | ... | ... |
... | ... | @@ -92,7 +92,7 @@ export const useSyncRemote = () => { |
92 | 92 | const saveContent = { |
93 | 93 | dataViewContent: { |
94 | 94 | id: dataViewContent.id, |
95 | - content: JSONStringify(chartEditStore.getStorageInfo || {}) | |
95 | + content: JSONStringify(chartEditStore.getStorageInfo() || {}) | |
96 | 96 | }, |
97 | 97 | dataViewName, |
98 | 98 | dataViewId | ... | ... |
1 | 1 | <template> |
2 | 2 | <div |
3 | - class="chart-item" | |
4 | - v-for="item in groupData.groupList" | |
5 | - :class="animationsClass(item.styles.animations)" | |
6 | - :key="item.id" | |
3 | + :class="animationsClass(groupData.styles.animations)" | |
7 | 4 | :style="{ |
8 | - ...getComponentAttrStyle(item.attr, groupIndex), | |
9 | - ...getFilterStyle(item.styles), | |
10 | - ...getTransformStyle(item.styles), | |
11 | - ...getStatusStyle(item.status), | |
12 | - ...getPreviewConfigStyle(item.preview), | |
13 | - ...getBlendModeStyle(item.styles) as any | |
5 | + ...getSizeStyle(groupData.attr), | |
6 | + ...getFilterStyle(groupData.styles), | |
14 | 7 | }" |
15 | 8 | > |
9 | + <div | |
10 | + class="chart-item" | |
11 | + v-for="item in groupData.groupList" | |
12 | + :class="animationsClass(item.styles.animations)" | |
13 | + :key="item.id" | |
14 | + :style="{ | |
15 | + ...getComponentAttrStyle(item.attr, groupIndex), | |
16 | + ...getStatusStyle(item.status), | |
17 | + ...getPreviewConfigStyle(item.preview), | |
18 | + ...getBlendModeStyle(item.styles) as any | |
19 | + }" | |
20 | + > | |
16 | 21 | <component |
17 | 22 | :is="item.chartConfig.chartKey" |
18 | 23 | :id="item.id" |
19 | 24 | :chartConfig="item" |
20 | 25 | :themeSetting="themeSetting" |
21 | 26 | :themeColor="themeColor" |
22 | - :style="{ ...getSizeStyle(item.attr) }" | |
23 | - v-on="useLifeHandler(item)" | |
24 | - ></component> | |
27 | + :style="{ | |
28 | + ...getSizeStyle(item.attr), | |
29 | + ...getFilterStyle(item.styles), | |
30 | + ...getTransformStyle(item.styles) | |
31 | + }" | |
32 | + v-on="useLifeHandler(item)" | |
33 | + ></component> | |
34 | + </div> | |
25 | 35 | </div> |
26 | 36 | </template> |
27 | 37 | ... | ... |
... | ... | @@ -6,7 +6,6 @@ |
6 | 6 | :key="item.id" |
7 | 7 | :style="{ |
8 | 8 | ...getComponentAttrStyle(item.attr, index), |
9 | - ...getFilterStyle(item.styles), | |
10 | 9 | ...getTransformStyle(item.styles), |
11 | 10 | ...getStatusStyle(item.status), |
12 | 11 | ...getPreviewConfigStyle(item.preview), |
... | ... | @@ -31,7 +30,10 @@ |
31 | 30 | :chartConfig="item" |
32 | 31 | :themeSetting="themeSetting" |
33 | 32 | :themeColor="themeColor" |
34 | - :style="{ ...getSizeStyle(item.attr) }" | |
33 | + :style="{ | |
34 | + ...getSizeStyle(item.attr), | |
35 | + ...getFilterStyle(item.styles) | |
36 | + }" | |
35 | 37 | v-on="useLifeHandler(item)" |
36 | 38 | ></component> |
37 | 39 | </div> | ... | ... |
... | ... | @@ -33,7 +33,7 @@ import { getFilterStyle, setTitle } from '@/utils' |
33 | 33 | |
34 | 34 | // THINGS_KIT 重写预览逻辑,调用接口 |
35 | 35 | import { dragCanvas } from './utils' |
36 | -import { getEditCanvasConfigStyle,keyRecordHandle } from './utils' | |
36 | +import { getEditCanvasConfigStyle, keyRecordHandle } from './utils' | |
37 | 37 | import { getSessionStorageInfo } from './external/usePreview' |
38 | 38 | |
39 | 39 | import { useComInstall } from './hooks/useComInstall.hook' |
... | ... | @@ -52,6 +52,7 @@ setTitle(`预览-${chartEditStore.editCanvasConfig.projectName}`) |
52 | 52 | |
53 | 53 | const previewRefStyle = computed(() => { |
54 | 54 | return { |
55 | + overflow: 'hidden', | |
55 | 56 | ...getEditCanvasConfigStyle(chartEditStore.editCanvasConfig), |
56 | 57 | ...getFilterStyle(chartEditStore.editCanvasConfig) |
57 | 58 | } | ... | ... |
... | ... | @@ -15,7 +15,7 @@ let key = ref(Date.now()) |
15 | 15 | |
16 | 16 | // 数据变更 -> 组件销毁重建 |
17 | 17 | ;[SavePageEnum.JSON, SavePageEnum.CHART_TO_PREVIEW].forEach((saveEvent: string) => { |
18 | - if (!window.opener) return | |
18 | + if (!window.opener && !window.opener.addEventListener) return | |
19 | 19 | window.opener.addEventListener(saveEvent, async (e: any) => { |
20 | 20 | const localStorageInfo: ChartEditStorageType = await getSessionStorageInfo() as unknown as ChartEditStorageType |
21 | 21 | setSessionStorage(StorageEnum.GO_CHART_STORAGE_LIST, [{ ...e.detail, id: localStorageInfo.id }]) | ... | ... |