Commit ed3194e378e5d8016e7f5eb62195daac85a57b6b
Merge branch 'ft' into 'main_dev'
feat(src/packages): 重写信息里的视频组件,支持自定义上传等 See merge request yunteng/thingskit-view!78
Showing
24 changed files
with
511 additions
and
79 deletions
src/assets/external/weather/shower.png
0 → 100644
848 Bytes
src/assets/external/weather/thunderstorm.png
0 → 100644
5.51 KB
... | ... | @@ -14,7 +14,7 @@ export const enum areaEnum { |
14 | 14 | export const includes = [] |
15 | 15 | |
16 | 16 | export const option = { |
17 | - drillingIn:false, | |
17 | + drillingIn: false, | |
18 | 18 | dataset: dataJson, |
19 | 19 | mapRegion: { |
20 | 20 | adcode: 'china', |
... | ... | @@ -152,7 +152,7 @@ export const option = { |
152 | 152 | shadowOffsetY: 2, |
153 | 153 | shadowBlur: 10 |
154 | 154 | } |
155 | - } | |
155 | + }, | |
156 | 156 | ] |
157 | 157 | } |
158 | 158 | export const MapDefaultConfig = { ...option } | ... | ... |
... | ... | @@ -27,6 +27,8 @@ import mapJsonWithoutHainanIsLands from './mapWithoutHainanIsLands.json' |
27 | 27 | import { DatasetComponent, GridComponent, TooltipComponent, GeoComponent, VisualMapComponent } from 'echarts/components' |
28 | 28 | import cityMap from './mapGeojson/china-main-city-map.json' |
29 | 29 | |
30 | +type historyDataType = { name: string; code: string } | |
31 | + | |
30 | 32 | const props = defineProps({ |
31 | 33 | themeSetting: { |
32 | 34 | type: Object, |
... | ... | @@ -57,6 +59,12 @@ use([ |
57 | 59 | |
58 | 60 | const saveSelectValue = ref('') |
59 | 61 | |
62 | +const iconStr = ref( | |
63 | + 'path://M853.333333 245.333333H245.333333l93.866667-93.866666c12.8-12.8 12.8-34.133333 0-46.933334-12.8-12.8-34.133333-12.8-46.933333 0l-145.066667 145.066667c-12.8 12.8-12.8 34.133333 0 46.933333l145.066667 145.066667c6.4 6.4 14.933333 10.666667 23.466666 10.666667s17.066667-4.266667 23.466667-10.666667c12.8-12.8 12.8-34.133333 0-46.933333L256 311.466667h597.333333c6.4 0 10.666667 4.266667 10.666667 10.666666v426.666667c0 6.4-4.266667 10.666667-10.666667 10.666667H170.666667c-17.066667 0-32 14.933333-32 32s14.933333 32 32 32h682.666666c40.533333 0 74.666667-34.133333 74.666667-74.666667V320c0-40.533333-34.133333-74.666667-74.666667-74.666667z' | |
64 | +) | |
65 | + | |
66 | +const historyData = ref<historyDataType[]>([]) | |
67 | + | |
60 | 68 | const option = reactive({ |
61 | 69 | value: mergeTheme(props.chartConfig.option, props.themeSetting, includes) |
62 | 70 | }) |
... | ... | @@ -68,7 +76,7 @@ const toolBoxOption = { |
68 | 76 | myFullButton: { |
69 | 77 | show: true, |
70 | 78 | title: '返回', |
71 | - icon: 'path://M853.333333 245.333333H245.333333l93.866667-93.866666c12.8-12.8 12.8-34.133333 0-46.933334-12.8-12.8-34.133333-12.8-46.933333 0l-145.066667 145.066667c-12.8 12.8-12.8 34.133333 0 46.933333l145.066667 145.066667c6.4 6.4 14.933333 10.666667 23.466666 10.666667s17.066667-4.266667 23.466667-10.666667c12.8-12.8 12.8-34.133333 0-46.933333L256 311.466667h597.333333c6.4 0 10.666667 4.266667 10.666667 10.666666v426.666667c0 6.4-4.266667 10.666667-10.666667 10.666667H170.666667c-17.066667 0-32 14.933333-32 32s14.933333 32 32 32h682.666666c40.533333 0 74.666667-34.133333 74.666667-74.666667V320c0-40.533333-34.133333-74.666667-74.666667-74.666667z', | |
79 | + icon: iconStr.value, | |
72 | 80 | onclick: () => watchAdcode() |
73 | 81 | } |
74 | 82 | } |
... | ... | @@ -79,10 +87,12 @@ props.chartConfig.option = { |
79 | 87 | ...{ toolbox: toolBoxOption } |
80 | 88 | } |
81 | 89 | |
90 | +//地图点击返回 | |
82 | 91 | const watchAdcode = () => { |
83 | 92 | if (props.chartConfig.option.drillingIn) { |
84 | - const findCity = (cityMap as any)[saveSelectValue.value] | |
85 | - props.chartConfig.option.mapRegion.adcode = 'china' | |
93 | + const code = historyData.value.at(-2)?.code | |
94 | + props.chartConfig.option.mapRegion.adcode = code ? code : 'china' | |
95 | + historyData.value.pop() | |
86 | 96 | } |
87 | 97 | } |
88 | 98 | |
... | ... | @@ -90,12 +100,16 @@ const vChartRef = ref<typeof VChart>() |
90 | 100 | |
91 | 101 | //动态获取json注册地图 |
92 | 102 | const getGeojson = (regionId: string) => { |
93 | - return new Promise<boolean>(resolve => { | |
94 | - import(`./mapGeojson/${regionId}.json`).then(data => { | |
95 | - registerMap(regionId, { geoJSON: data.default as any, specialAreas: {} }) | |
96 | - resolve(true) | |
103 | + try { | |
104 | + return new Promise<boolean>(resolve => { | |
105 | + import(`./mapGeojson/${regionId}.json`).then(data => { | |
106 | + registerMap(regionId, { geoJSON: data.default as any, specialAreas: {} }) | |
107 | + resolve(true) | |
108 | + }) | |
97 | 109 | }) |
98 | - }) | |
110 | + } finally { | |
111 | + console.log | |
112 | + } | |
99 | 113 | } |
100 | 114 | |
101 | 115 | //异步时先注册空的 保证初始化不报错 |
... | ... | @@ -199,7 +213,12 @@ const handleVChartClick = async (params: any) => { |
199 | 213 | const { name } = params |
200 | 214 | saveSelectValue.value = name |
201 | 215 | const findAdcode = (cityMap as any)[name] |
216 | + if (!findAdcode) return | |
202 | 217 | props.chartConfig.option.mapRegion.adcode = findAdcode |
218 | + historyData.value.push({ | |
219 | + name, | |
220 | + code: findAdcode | |
221 | + }) | |
203 | 222 | } |
204 | 223 | } |
205 | 224 | </script> | ... | ... |
1 | 1 | <template> |
2 | 2 | <div class="videoPlay"> |
3 | - <video crossOrigin="anonymous" ref="videoRef" class="video-js vjs-default-skin vjs-big-play-centered" controls> | |
3 | + <video | |
4 | + style="object-fit: cover" | |
5 | + :poster="poster" | |
6 | + crossOrigin="anonymous" | |
7 | + ref="videoRef" | |
8 | + class="video-js vjs-default-skin vjs-big-play-centered" | |
9 | + controls | |
10 | + > | |
4 | 11 | <source :src="path" /> |
5 | 12 | </video> |
6 | 13 | </div> |
7 | 14 | </template> |
8 | 15 | <script lang="ts" setup> |
9 | -import { nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue' | |
16 | +import { nextTick, onBeforeUnmount, onMounted, ref, watch, toRefs } from 'vue' | |
10 | 17 | import videojs, { VideoJsPlayer } from 'video.js' |
11 | 18 | import type { VideoJsPlayerOptions } from 'video.js' |
12 | 19 | import 'video.js/dist/video-js.css' |
... | ... | @@ -17,6 +24,7 @@ const props = withDefaults( |
17 | 24 | path: string |
18 | 25 | autoPlay?: boolean |
19 | 26 | h?: number |
27 | + poster?: string | |
20 | 28 | }>(), |
21 | 29 | { autoPlay: false } |
22 | 30 | ) |
... | ... | @@ -33,19 +41,22 @@ const initPlay = async () => { |
33 | 41 | controls: true, |
34 | 42 | autoplay: props.autoPlay, |
35 | 43 | src: props?.path, |
44 | + poster: props?.poster, | |
36 | 45 | language: 'zh-CN', |
37 | - techOrder: ['html5'] | |
46 | + techOrder: ['html5'], | |
47 | + preload: 'none' | |
38 | 48 | } |
39 | 49 | player = videojs(videoRef.value, options, () => { |
40 | - videojs.log('播放器已经准备好了!') | |
50 | + videojs.log('Video is reading') | |
41 | 51 | if (props.autoPlay) { |
42 | 52 | player.play() |
43 | 53 | } |
44 | 54 | player.on('ended', () => { |
45 | - videojs.log('播放结束了!') | |
55 | + videojs.log('Play end') | |
46 | 56 | }) |
47 | 57 | player.on('error', () => { |
48 | - videojs.log('播放器解析出错!') | |
58 | + player.errorDisplay.close() | |
59 | + videojs.log('Play parse error') | |
49 | 60 | }) |
50 | 61 | }) |
51 | 62 | } |
... | ... | @@ -58,10 +69,10 @@ onMounted(() => { |
58 | 69 | watch( |
59 | 70 | () => props.path, |
60 | 71 | () => { |
61 | - player?.pause() | |
62 | - player?.src(props.path) | |
63 | - player?.load() | |
64 | - if (props.path) { | |
72 | + if (props.path && props.autoPlay) { | |
73 | + player?.pause() | |
74 | + player?.load() | |
75 | + player?.src(props.path) | |
65 | 76 | player?.play() |
66 | 77 | } |
67 | 78 | }, |
... | ... | @@ -74,11 +85,15 @@ onBeforeUnmount(() => { |
74 | 85 | player?.dispose() |
75 | 86 | }) |
76 | 87 | </script> |
88 | +<style> | |
89 | +.vjs-poster { | |
90 | + background-size: cover !important; | |
91 | +} | |
92 | +</style> | |
77 | 93 | <style lang="scss" scoped> |
78 | 94 | .videoPlay { |
79 | 95 | flex: 1; |
80 | 96 | height: v-bind('`${h}px`'); |
81 | - | |
82 | 97 | .video-js { |
83 | 98 | height: 100%; |
84 | 99 | width: 100%; | ... | ... |
1 | 1 | <template> |
2 | 2 | <CollapseItem name="播放器配置" :expanded="true"> |
3 | + <setting-item-box name="上传图片" :alone="true"> | |
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> | |
23 | + </setting-item> | |
24 | + </setting-item-box> | |
3 | 25 | <setting-item-box name="源地址" :alone="true"> |
4 | 26 | <setting-item> |
5 | 27 | <n-input-group> |
... | ... | @@ -16,14 +38,94 @@ |
16 | 38 | </template> |
17 | 39 | |
18 | 40 | <script setup lang="ts"> |
19 | -import { PropType } from 'vue' | |
41 | +import { PropType, ref, nextTick } from 'vue' | |
20 | 42 | import { option } from './config' |
21 | 43 | import { CollapseItem, SettingItemBox, SettingItem } from '@/components/Pages/ChartItemSetting' |
44 | +import { FileTypeEnum } from '@/enums/fileTypeEnum' | |
45 | +import { uploadFile } from '@/api/external/contentSave/content' | |
46 | +import { UploadCustomRequestOptions } from 'naive-ui' | |
47 | +import { backgroundImageSize } from '@/settings/designSetting' | |
48 | +import { fetchRouteParamsLocation } from '@/utils' | |
22 | 49 | |
23 | -defineProps({ | |
50 | +const props = defineProps({ | |
24 | 51 | optionData: { |
25 | 52 | type: Object as PropType<typeof option>, |
26 | 53 | required: true |
27 | 54 | } |
28 | 55 | }) |
56 | + | |
57 | +const uploadFileListRef = ref() | |
58 | + | |
59 | +// 上传图片前置处理 | |
60 | +// eslint-disable-next-line @typescript-eslint/ban-ts-comment | |
61 | +//@ts-ignore | |
62 | +const beforeUploadHandle = async ({ file }) => { | |
63 | + uploadFileListRef.value = [] | |
64 | + const type = file.file.type | |
65 | + const size = file.file.size | |
66 | + | |
67 | + if (size > 1024 * 1024 * backgroundImageSize) { | |
68 | + window['$message'].warning(`图片超出 ${backgroundImageSize}M 限制,请重新上传!`) | |
69 | + return false | |
70 | + } | |
71 | + if (type !== FileTypeEnum.PNG && type !== FileTypeEnum.JPEG && type !== FileTypeEnum.GIF) { | |
72 | + window['$message'].warning('文件格式不符合,请重新上传!') | |
73 | + return false | |
74 | + } | |
75 | + return true | |
76 | +} | |
77 | + | |
78 | +// 自定义上传操作 | |
79 | +const customRequest = (options: UploadCustomRequestOptions) => { | |
80 | + const { file } = options | |
81 | + nextTick(async () => { | |
82 | + if (file.file) { | |
83 | + // 修改名称 | |
84 | + const newNameFile = new File([file.file], `${fetchRouteParamsLocation()}_index_background.png`, { | |
85 | + type: file.file.type | |
86 | + }) | |
87 | + let uploadParams = new FormData() | |
88 | + uploadParams.append('file', newNameFile) | |
89 | + const uploadRes = await uploadFile(uploadParams) | |
90 | + if (uploadRes) { | |
91 | + props.optionData.poster = uploadRes?.fileStaticUri | |
92 | + window['$message'].success('添加图片成功!') | |
93 | + } | |
94 | + } else { | |
95 | + window['$message'].error('添加图片失败,请稍后重试!') | |
96 | + } | |
97 | + }) | |
98 | +} | |
29 | 99 | </script> |
100 | +<style lang="scss" scoped> | |
101 | +$uploadHeight: 193px; | |
102 | +.upload-box { | |
103 | + cursor: pointer; | |
104 | + margin-bottom: 20px; | |
105 | + @include deep() { | |
106 | + .n-card__content { | |
107 | + padding: 0; | |
108 | + overflow: hidden; | |
109 | + } | |
110 | + .n-upload-dragger { | |
111 | + padding: 5px; | |
112 | + } | |
113 | + } | |
114 | + .upload-show { | |
115 | + width: -webkit-fill-available; | |
116 | + height: $uploadHeight; | |
117 | + border-radius: 5px; | |
118 | + } | |
119 | + .upload-img { | |
120 | + display: flex; | |
121 | + flex-direction: column; | |
122 | + align-items: center; | |
123 | + img { | |
124 | + height: 150px; | |
125 | + } | |
126 | + .upload-desc { | |
127 | + padding: 10px 0; | |
128 | + } | |
129 | + } | |
130 | +} | |
131 | +</style> | ... | ... |
1 | 1 | <template> |
2 | 2 | <div> |
3 | - <VideoPlay :h="h" :path="option.dataset" :autoPlay="autoplay" /> | |
3 | + <VideoPlay :h="h" :path="option.dataset" :autoPlay="autoplay" :poster="option.poster" /> | |
4 | 4 | </div> |
5 | 5 | </template> |
6 | 6 | <script setup lang="ts"> |
... | ... | @@ -18,10 +18,11 @@ const props = defineProps({ |
18 | 18 | |
19 | 19 | const { h } = toRefs(props.chartConfig.attr) |
20 | 20 | |
21 | -const { autoplay, dataset } = toRefs(props.chartConfig.option) | |
21 | +const { autoplay, dataset, poster } = toRefs(props.chartConfig.option) | |
22 | 22 | |
23 | 23 | const option = shallowReactive({ |
24 | - dataset: configOption.dataset | |
24 | + dataset: configOption.dataset, | |
25 | + poster: configOption.poster | |
25 | 26 | }) |
26 | 27 | |
27 | 28 | watch( |
... | ... | @@ -30,7 +31,17 @@ watch( |
30 | 31 | option.dataset = newData |
31 | 32 | }, |
32 | 33 | { |
33 | - immediate: true, | |
34 | + immediate: true | |
35 | + } | |
36 | +) | |
37 | + | |
38 | +watch( | |
39 | + () => poster?.value, | |
40 | + (newData: string) => { | |
41 | + option.poster = newData | |
42 | + }, | |
43 | + { | |
44 | + immediate: true | |
34 | 45 | } |
35 | 46 | ) |
36 | 47 | </script> | ... | ... |
1 | 1 | <script lang="ts" setup name="WeatherContent"> |
2 | -import { toRefs } from 'vue' | |
2 | +import { toRefs, PropType, computed } from 'vue' | |
3 | 3 | import SelectCity from './SelectCity.vue' |
4 | 4 | import { useUtils } from '../hooks/useUtils' |
5 | 5 | import weatherBg from '@/assets/external/weather/bg.png' |
6 | +import { weatherInfoInterface, selectValueType } from '../config' | |
6 | 7 | |
7 | 8 | const props = defineProps({ |
8 | 9 | data: { |
9 | - type: Array as any | |
10 | + type: Array as PropType<weatherInfoInterface[]>, | |
11 | + required: true | |
10 | 12 | } |
11 | 13 | }) |
12 | 14 | |
... | ... | @@ -16,21 +18,31 @@ const { loadWeatherImg, loadWeatherLevel } = useUtils() |
16 | 18 | |
17 | 19 | const { casts } = toRefs(props.data[0]) |
18 | 20 | |
19 | -const onHandleSelectValues = (values: any) => { | |
21 | +const onHandleSelectValues = (values: selectValueType) => { | |
20 | 22 | emits('submit', values) |
21 | 23 | } |
24 | + | |
25 | +const nCardComputed = computed(() => { | |
26 | + return { | |
27 | + backgroundImage: `url('${weatherBg}')` | |
28 | + } | |
29 | +}) | |
30 | + | |
31 | +const segmentedComputed = computed(() => { | |
32 | + return { | |
33 | + content: true, | |
34 | + footer: 'soft' | |
35 | + } | |
36 | +}) | |
37 | + | |
38 | +const indexMapDayFunc = (index: number) => { | |
39 | + return index === 0 ? '今天' : index === 1 ? '明天' : index === 2 ? '后天' : '外天' | |
40 | +} | |
22 | 41 | </script> |
23 | 42 | |
24 | 43 | <template> |
25 | 44 | <div> |
26 | - <n-card | |
27 | - :segmented="{ | |
28 | - content: true, | |
29 | - footer: 'soft' | |
30 | - }" | |
31 | - :style="{ backgroundImage: `url('${weatherBg}')` }" | |
32 | - class="n-card" | |
33 | - > | |
45 | + <n-card :segmented="segmentedComputed" :style="nCardComputed" class="n-card"> | |
34 | 46 | <template #header> |
35 | 47 | <div class="card-header"> |
36 | 48 | <div class="city-text">{{ data[0]?.city }}</div> |
... | ... | @@ -59,7 +71,7 @@ const onHandleSelectValues = (values: any) => { |
59 | 71 | </div> |
60 | 72 | <template #footer> |
61 | 73 | <div v-for="(item, index) in casts" :key="index" class="footer-content"> |
62 | - <div>{{ index === 0 ? '今天' : index === 1 ? '明天' : index === 2 ? '后天' : '外天' }}</div> | |
74 | + <div>{{ indexMapDayFunc(index) }}</div> | |
63 | 75 | <div> |
64 | 76 | <img :src="loadWeatherImg(item?.dayweather)" /> |
65 | 77 | </div> | ... | ... |
... | ... | @@ -7,6 +7,8 @@ import clearDay from '@/assets/external/weather/clearDay.png' |
7 | 7 | import cloudy from '@/assets/external/weather/cloudy.png' |
8 | 8 | import cloudyDay from '@/assets/external/weather/cloudyDay.png' |
9 | 9 | import lightRain from '@/assets/external/weather/lightRain.png' |
10 | +import thunderstorm from '@/assets/external/weather/thunderstorm.png' | |
11 | +import shower from '@/assets/external/weather/shower.png' | |
10 | 12 | |
11 | 13 | //第三方 天气接口key值和api配置(高德) |
12 | 14 | export class ThirdPartyWeatherConnfig { |
... | ... | @@ -23,7 +25,7 @@ export const enum areaEnum { |
23 | 25 | COUNTY = 'COUNTY' |
24 | 26 | } |
25 | 27 | |
26 | -//天气文字映射图片 | |
28 | +//天气文字图片映射 | |
27 | 29 | export const weatherTextMapImg = [ |
28 | 30 | { |
29 | 31 | text: '晴', |
... | ... | @@ -40,10 +42,17 @@ export const weatherTextMapImg = [ |
40 | 42 | { |
41 | 43 | text: '小雨', |
42 | 44 | img: lightRain |
45 | + }, | |
46 | + { | |
47 | + text: '雷阵雨', | |
48 | + img: thunderstorm | |
49 | + }, | |
50 | + { | |
51 | + text: '阵雨', | |
52 | + img: shower | |
43 | 53 | } |
44 | 54 | ] |
45 | 55 | |
46 | -console.log(clearDay, cloudy) | |
47 | 56 | //风力等级文字映射 |
48 | 57 | export const weatherSpeedMapText = [ |
49 | 58 | { |
... | ... | @@ -100,6 +109,36 @@ export const weatherSpeedMapText = [ |
100 | 109 | } |
101 | 110 | ] |
102 | 111 | |
112 | +export type selectValueType = { | |
113 | + provinceValue: null | |
114 | + cityValue: string | |
115 | + countyValue: null | |
116 | +} | |
117 | + | |
118 | +export interface castsInterface { | |
119 | + date: string | |
120 | + week: string | |
121 | + dayweather: string | |
122 | + nightweather: string | |
123 | + daytemp: string | |
124 | + nighttemp: string | |
125 | + daywind: string | |
126 | + nightwind: string | |
127 | + daypower: string | |
128 | + nightpower: string | |
129 | + daytemp_float: string | |
130 | + nighttemp_float: string | |
131 | +} | |
132 | +export interface weatherInfoInterface { | |
133 | + city: string | |
134 | + adcode: string | |
135 | + province: string | |
136 | + reporttime: string | |
137 | + casts: castsInterface[] | |
138 | +} | |
139 | + | |
140 | +export type weatherTextMapType = { text: string; img: string; level: Fn } | |
141 | + | |
103 | 142 | export const option = { |
104 | 143 | dataset: { |
105 | 144 | provinceValue: null, | ... | ... |
... | ... | @@ -6,6 +6,8 @@ |
6 | 6 | <SettingItem name="颜色"> |
7 | 7 | <n-color-picker v-model:value="optionData.weatherCss.backgroundColor" /> |
8 | 8 | </SettingItem> |
9 | + </SettingItemBox> | |
10 | + <SettingItemBox name="背景"> | |
9 | 11 | <SettingItem> |
10 | 12 | <n-button size="small" @click="optionData.weatherCss.backgroundColor = 'transparent'"> 恢复默认颜色 </n-button> |
11 | 13 | </SettingItem> |
... | ... | @@ -73,7 +75,7 @@ |
73 | 75 | <script setup lang="ts"> |
74 | 76 | import { PropType } from 'vue' |
75 | 77 | import { CollapseItem, SettingItemBox, SettingItem } from '@/components/Pages/ChartItemSetting' |
76 | -import { option } from './config' | |
78 | +import { option, selectValueType } from './config' | |
77 | 79 | import SelectCity from './componnets/SelectCity.vue' |
78 | 80 | |
79 | 81 | const props = defineProps({ |
... | ... | @@ -83,7 +85,7 @@ const props = defineProps({ |
83 | 85 | } |
84 | 86 | }) |
85 | 87 | |
86 | -const onHandleSelectValues = (values: any) => { | |
88 | +const onHandleSelectValues = (values: selectValueType) => { | |
87 | 89 | props.optionData.dataset = values |
88 | 90 | } |
89 | 91 | </script> | ... | ... |
1 | -import { weatherSpeedMapText, weatherTextMapImg } from '../config' | |
1 | +import { weatherSpeedMapText, weatherTextMapImg, weatherTextMapType } from '../config' | |
2 | 2 | import clearDay from '@/assets/external/weather/clearDay.png' |
3 | 3 | |
4 | 4 | export const useUtils = () => { |
5 | 5 | const loadWeatherImg = (text: string) => { |
6 | - return weatherTextMapImg.find((item: any) => item.text === text)?.img || clearDay | |
6 | + return weatherTextMapImg.find((item: Omit<weatherTextMapType, 'level'>) => item.text === text)?.img || clearDay | |
7 | 7 | } |
8 | 8 | |
9 | 9 | //风力等级 ≤3 3 |
... | ... | @@ -14,7 +14,7 @@ export const useUtils = () => { |
14 | 14 | } else { |
15 | 15 | handleSpeed = speed |
16 | 16 | } |
17 | - return weatherSpeedMapText.find((item: any) => item.level(Number(handleSpeed)))?.text | |
17 | + return weatherSpeedMapText.find((item: { text: string; level: Fn }) => item.level(Number(handleSpeed)))?.text | |
18 | 18 | } |
19 | 19 | return { |
20 | 20 | loadWeatherImg, | ... | ... |
1 | 1 | <script lang="ts" setup name="Weather"> |
2 | 2 | import { PropType, toRefs, onMounted, reactive, watch } from 'vue' |
3 | 3 | import { CreateComponentType } from '@/packages/index.d' |
4 | -import { option, ThirdPartyWeatherConnfig } from './config' | |
4 | +import { option, ThirdPartyWeatherConnfig, selectValueType, weatherInfoInterface } from './config' | |
5 | 5 | import axios from 'axios' |
6 | 6 | import WeatherContent from './componnets/WeatherContent.vue' |
7 | 7 | import { useUtils } from './hooks/useUtils' |
... | ... | @@ -19,11 +19,15 @@ const { w, h } = toRefs(props.chartConfig.attr) |
19 | 19 | |
20 | 20 | const { weatherCss } = toRefs(props.chartConfig.option) |
21 | 21 | |
22 | -const weatherInfoValues = reactive<any>({ | |
22 | +type weatherInfoValuesType = { | |
23 | + weatherInfo: weatherInfoInterface[] | |
24 | +} | |
25 | + | |
26 | +const weatherInfoValues = reactive<weatherInfoValuesType>({ | |
23 | 27 | weatherInfo: [] |
24 | 28 | }) |
25 | 29 | |
26 | -const getWeatherInfos = async (area: any) => { | |
30 | +const getWeatherInfos = async (area: selectValueType) => { | |
27 | 31 | const { cityValue, countyValue } = area || props.chartConfig.option.dataset |
28 | 32 | const params = { |
29 | 33 | key: ThirdPartyWeatherConnfig.ak, |
... | ... | @@ -52,7 +56,7 @@ watch( |
52 | 56 | } |
53 | 57 | ) |
54 | 58 | |
55 | -const onHandleSelectValues = (values: any) => { | |
59 | +const onHandleSelectValues = (values: selectValueType) => { | |
56 | 60 | getWeatherInfos(values) |
57 | 61 | } |
58 | 62 | </script> | ... | ... |
... | ... | @@ -6,13 +6,17 @@ import cloneDeep from 'lodash/cloneDeep' |
6 | 6 | |
7 | 7 | export const option = { |
8 | 8 | // 网站路径 |
9 | - dataset: "", | |
9 | + dataset: '', | |
10 | 10 | // 圆角 |
11 | - borderRadius: 10 | |
11 | + borderRadius: 10, | |
12 | + pages: { | |
13 | + page: 1, | |
14 | + pageSize: 10 | |
15 | + }, | |
16 | + color: 'black' | |
12 | 17 | } |
13 | 18 | |
14 | -export default class Config extends PublicConfigClass implements CreateComponentType | |
15 | -{ | |
19 | +export default class Config extends PublicConfigClass implements CreateComponentType { | |
16 | 20 | public key = OverrideILoadConfigurationframeConfig.key |
17 | 21 | public attr = { ...chartInitConfig, w: 1200, h: 800, zIndex: -1 } |
18 | 22 | public chartConfig = cloneDeep(OverrideILoadConfigurationframeConfig) | ... | ... |
... | ... | @@ -15,16 +15,38 @@ |
15 | 15 | ></n-input-number> |
16 | 16 | </setting-item> |
17 | 17 | </setting-item-box> |
18 | + <setting-item-box name="分页"> | |
19 | + <setting-item name="页码"> | |
20 | + <n-input-number v-model:value="optionData.pages.page" size="small" :min="1" placeholder="页码"></n-input-number> | |
21 | + </setting-item> | |
22 | + <setting-item name="页数"> | |
23 | + <n-input-number | |
24 | + disabled | |
25 | + v-model:value="optionData.pages.pageSize" | |
26 | + size="small" | |
27 | + :min="10" | |
28 | + placeholder="页数" | |
29 | + ></n-input-number> | |
30 | + </setting-item> | |
31 | + </setting-item-box> | |
32 | + <setting-item-box name="颜色" :alone="true"> | |
33 | + <SettingItem name="颜色"> | |
34 | + <n-color-picker size="small" :modes="['hex']" v-model:value="optionData.color"></n-color-picker> | |
35 | + </SettingItem> | |
36 | + <SettingItem> | |
37 | + <n-button size="small" @click="optionData.color = 'black'"> 恢复默认 </n-button> | |
38 | + </SettingItem> | |
39 | + </setting-item-box> | |
18 | 40 | </collapse-item> |
19 | 41 | </template> |
20 | 42 | |
21 | 43 | <script setup lang="ts"> |
22 | -import { PropType, onMounted, ref } from 'vue' | |
44 | +import { PropType, onMounted, ref, watch } from 'vue' | |
23 | 45 | import { option } from './config' |
24 | 46 | import { CollapseItem, SettingItemBox, SettingItem } from '@/components/Pages/ChartItemSetting' |
25 | 47 | import { getConfigurationList } from '@/api/external/common/index' |
26 | 48 | |
27 | -defineProps({ | |
49 | +const props = defineProps({ | |
28 | 50 | optionData: { |
29 | 51 | type: Object as PropType<typeof option>, |
30 | 52 | required: true |
... | ... | @@ -40,7 +62,18 @@ const getConfigurationOptions = async (params: object) => { |
40 | 62 | } |
41 | 63 | } |
42 | 64 | |
65 | +watch( | |
66 | + () => props.optionData.pages, | |
67 | + (newData: any) => { | |
68 | + getConfigurationOptions({ page: newData.page, pageSize: newData.pageSize }) | |
69 | + }, | |
70 | + { | |
71 | + deep: true, | |
72 | + immediate: true | |
73 | + } | |
74 | +) | |
75 | + | |
43 | 76 | onMounted(() => { |
44 | - getConfigurationOptions({ page: 1, pageSize: 10 }) | |
77 | + getConfigurationOptions({ page: props.optionData.pages.page, pageSize: props.optionData.pages.pageSize }) | |
45 | 78 | }) |
46 | 79 | </script> | ... | ... |
... | ... | @@ -3,9 +3,9 @@ |
3 | 3 | <div v-show="isShowSvg" @click="handleFullScreen" id="fullscreenButton"> |
4 | 4 | <svg |
5 | 5 | focusable="false" |
6 | - class="" | |
7 | 6 | data-icon="fullscreen" |
8 | 7 | width="4vw" |
8 | + :style="`color:${color}`" | |
9 | 9 | height="4vh" |
10 | 10 | fill="currentColor" |
11 | 11 | aria-hidden="true" |
... | ... | @@ -35,8 +35,7 @@ import { useChartDataFetch } from '@/hooks' |
35 | 35 | import { CreateComponentType } from '@/packages/index.d' |
36 | 36 | import { useChartEditStore } from '@/store/modules/chartEditStore/chartEditStore' |
37 | 37 | import { useFullScreen } from '@/packages/components/external/Charts/utls/fullScreen' |
38 | -import { isDevMode } from '@/utils/external/env'; | |
39 | - | |
38 | +import { isDevMode } from '@/utils/external/env' | |
40 | 39 | |
41 | 40 | const props = defineProps({ |
42 | 41 | chartConfig: { |
... | ... | @@ -50,7 +49,7 @@ const isShowSvg = ref(false) |
50 | 49 | const allowfullscreen = ref(false) |
51 | 50 | |
52 | 51 | const { w, h } = toRefs(props.chartConfig.attr) |
53 | -const { borderRadius } = toRefs(props.chartConfig.option) | |
52 | +const { borderRadius, color } = toRefs(props.chartConfig.option) | |
54 | 53 | |
55 | 54 | const option = shallowReactive({ |
56 | 55 | dataset: '' |
... | ... | @@ -63,7 +62,7 @@ const getStyle = (radius: number) => { |
63 | 62 | } |
64 | 63 | } |
65 | 64 | |
66 | -const isDev = isDevMode(); | |
65 | +const isDev = isDevMode() | |
67 | 66 | |
68 | 67 | // 编辑更新 |
69 | 68 | watch( |
... | ... | @@ -72,7 +71,9 @@ watch( |
72 | 71 | const currentHost = window.location.host |
73 | 72 | const currentProtocol = window.location.protocol |
74 | 73 | //预览 |
75 | - option.dataset = `${currentProtocol}//${currentHost}/thingskit-scada/${isDev ? '?dev=1&' : '?'}configurationId=${newData}&lightbox=1` | |
74 | + option.dataset = `${currentProtocol}//${currentHost}/thingskit-scada/${ | |
75 | + isDev ? '?dev=1&' : '?' | |
76 | + }configurationId=${newData}&lightbox=1` | |
76 | 77 | }, |
77 | 78 | { |
78 | 79 | immediate: true | ... | ... |
... | ... | @@ -6,13 +6,13 @@ import cloneDeep from 'lodash/cloneDeep' |
6 | 6 | |
7 | 7 | export const option = { |
8 | 8 | // 网站路径 |
9 | - dataset: "https://www.thingskit.com/", | |
9 | + dataset: 'https://www.thingskit.com/', | |
10 | 10 | // 圆角 |
11 | - borderRadius: 10 | |
11 | + borderRadius: 10, | |
12 | + color: 'black' | |
12 | 13 | } |
13 | 14 | |
14 | -export default class Config extends PublicConfigClass implements CreateComponentType | |
15 | -{ | |
15 | +export default class Config extends PublicConfigClass implements CreateComponentType { | |
16 | 16 | public key = OverrideIframeConfig.key |
17 | 17 | public attr = { ...chartInitConfig, w: 1200, h: 800, zIndex: -1 } |
18 | 18 | public chartConfig = cloneDeep(OverrideIframeConfig) | ... | ... |
... | ... | @@ -15,22 +15,26 @@ |
15 | 15 | ></n-input-number> |
16 | 16 | </setting-item> |
17 | 17 | </setting-item-box> |
18 | + <setting-item-box name="颜色" :alone="true"> | |
19 | + <SettingItem name="颜色"> | |
20 | + <n-color-picker size="small" :modes="['hex']" v-model:value="optionData.color"></n-color-picker> | |
21 | + </SettingItem> | |
22 | + <SettingItem> | |
23 | + <n-button size="small" @click="optionData.color = 'black'"> 恢复默认 </n-button> | |
24 | + </SettingItem> | |
25 | + </setting-item-box> | |
18 | 26 | </collapse-item> |
19 | 27 | </template> |
20 | 28 | |
21 | 29 | <script setup lang="ts"> |
22 | -import { PropType } from "vue"; | |
23 | -import { option } from "./config"; | |
24 | -import { | |
25 | - CollapseItem, | |
26 | - SettingItemBox, | |
27 | - SettingItem, | |
28 | -} from "@/components/Pages/ChartItemSetting"; | |
30 | +import { PropType } from 'vue' | |
31 | +import { option } from './config' | |
32 | +import { CollapseItem, SettingItemBox, SettingItem } from '@/components/Pages/ChartItemSetting' | |
29 | 33 | |
30 | 34 | const props = defineProps({ |
31 | 35 | optionData: { |
32 | 36 | type: Object as PropType<typeof option>, |
33 | - required: true, | |
34 | - }, | |
35 | -}); | |
37 | + required: true | |
38 | + } | |
39 | +}) | |
36 | 40 | </script> | ... | ... |
... | ... | @@ -3,9 +3,9 @@ |
3 | 3 | <div v-show="isShowSvg" @click="handleFullScreen" id="fullscreenButton"> |
4 | 4 | <svg |
5 | 5 | focusable="false" |
6 | - class="" | |
7 | 6 | data-icon="fullscreen" |
8 | 7 | width="4vw" |
8 | + :style="`color:${color}`" | |
9 | 9 | height="4vh" |
10 | 10 | fill="currentColor" |
11 | 11 | aria-hidden="true" |
... | ... | @@ -48,7 +48,7 @@ const isShowSvg = ref(false) |
48 | 48 | const allowfullscreen = ref(false) |
49 | 49 | |
50 | 50 | const { w, h } = toRefs(props.chartConfig.attr) |
51 | -const { borderRadius } = toRefs(props.chartConfig.option) | |
51 | +const { borderRadius, color } = toRefs(props.chartConfig.option) | |
52 | 52 | |
53 | 53 | const option = shallowReactive({ |
54 | 54 | dataset: '' | ... | ... |
1 | +import { PublicConfigClass } from '@/packages/public' | |
2 | +import { CreateComponentType } from '@/packages/index.d' | |
3 | +import { OverrideVideoConfig } from './index' | |
4 | +import cloneDeep from 'lodash/cloneDeep' | |
5 | +import video from '@/assets/videos/earth.mp4' | |
6 | + | |
7 | +export const option = { | |
8 | + // 视频路径 | |
9 | + dataset: video, | |
10 | + // 循环播放 | |
11 | + loop: true, | |
12 | + // 静音 | |
13 | + muted: true, | |
14 | + // 适应方式 | |
15 | + fit: 'contain', | |
16 | + // 圆角 | |
17 | + borderRadius: 10 | |
18 | +} | |
19 | + | |
20 | +export default class Config extends PublicConfigClass implements CreateComponentType { | |
21 | + public key = OverrideVideoConfig.key | |
22 | + public chartConfig = cloneDeep(OverrideVideoConfig) | |
23 | + public option = cloneDeep(option) | |
24 | +} | ... | ... |
1 | +<!-- eslint-disable vue/multi-word-component-names --> | |
2 | +<!-- eslint-disable vue/no-mutating-props --> | |
3 | +<template> | |
4 | + <collapse-item name="视频" expanded> | |
5 | + <setting-item-box name="源" alone> | |
6 | + <FileUpload | |
7 | + :fileSizeConst="fileSizeConst" | |
8 | + :max="1" | |
9 | + :threeSupportFileFormat="supportVideoType" | |
10 | + @fileStaticUri="handleFileStaticUri" | |
11 | + /> | |
12 | + </setting-item-box> | |
13 | + <setting-item-box name="控制"> | |
14 | + <setting-item> | |
15 | + <n-checkbox v-model:checked="optionData.loop" size="small">循环播放</n-checkbox> | |
16 | + </setting-item> | |
17 | + <setting-item> | |
18 | + <n-checkbox v-model:checked="optionData.muted" size="small">静音</n-checkbox> | |
19 | + </setting-item> | |
20 | + </setting-item-box> | |
21 | + | |
22 | + <setting-item-box name="样式"> | |
23 | + <setting-item name="类型"> | |
24 | + <n-select v-model:value="optionData.fit" size="small" :options="fitList"></n-select> | |
25 | + </setting-item> | |
26 | + </setting-item-box> | |
27 | + </collapse-item> | |
28 | +</template> | |
29 | + | |
30 | +<script setup lang="ts"> | |
31 | +import { PropType, ref } from 'vue' | |
32 | +import { option } from './config' | |
33 | +import { CollapseItem, SettingItemBox, SettingItem } from '@/components/Pages/ChartItemSetting' | |
34 | +import FileUpload from '../../../Composes/Mores/ThreeDimensional/components/FileUpload.vue' | |
35 | +import type { UploadFileInfo } from 'naive-ui' | |
36 | + | |
37 | +//视频类型 | |
38 | +const supportVideoType = ['mp4'] | |
39 | + | |
40 | +const fileSizeConst = ref(100) | |
41 | + | |
42 | +// 适应类型 | |
43 | +const fitList = [ | |
44 | + { | |
45 | + value: 'fill', | |
46 | + label: 'fill' | |
47 | + }, | |
48 | + { | |
49 | + value: 'contain', | |
50 | + label: 'contain' | |
51 | + }, | |
52 | + { | |
53 | + value: 'cover', | |
54 | + label: 'cover' | |
55 | + }, | |
56 | + { | |
57 | + value: 'scale-down', | |
58 | + label: 'scale-down' | |
59 | + }, | |
60 | + { | |
61 | + value: 'none', | |
62 | + label: 'none' | |
63 | + } | |
64 | +] | |
65 | + | |
66 | +const props = defineProps({ | |
67 | + optionData: { | |
68 | + type: Object as PropType<typeof option>, | |
69 | + required: true | |
70 | + } | |
71 | +}) | |
72 | + | |
73 | +const handleFileStaticUri = (value: UploadFileInfo[]) => { | |
74 | + props.optionData.dataset = value[0]?.url as string | |
75 | +} | |
76 | +</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('OverrideVideo', true) | |
6 | + | |
7 | +export const OverrideVideoConfig: 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: 'video.png' | |
17 | +} | ... | ... |
1 | +<!-- eslint-disable vue/multi-word-component-names --> | |
2 | +<template> | |
3 | + <!-- 重要:需要设置 crossOrigin="anonymous",否则保存画板缩略图会失败 --> | |
4 | + <video | |
5 | + ref="vVideoRef" | |
6 | + class="go-video" | |
7 | + preload="auto" | |
8 | + crossOrigin="anonymous" | |
9 | + playsinline | |
10 | + autoplay | |
11 | + :loop="option.loop" | |
12 | + :muted="option.muted" | |
13 | + :width="w" | |
14 | + :height="h" | |
15 | + :src="option.dataset" | |
16 | + controls | |
17 | + ></video> | |
18 | +</template> | |
19 | + | |
20 | +<script setup lang="ts"> | |
21 | +import { PropType, toRefs, shallowReactive, watch, ref } from 'vue' | |
22 | +import { useChartDataFetch } from '@/hooks' | |
23 | +import { CreateComponentType } from '@/packages/index.d' | |
24 | +import { useChartEditStore } from '@/store/modules/chartEditStore/chartEditStore' | |
25 | +import { option as configOption } from './config' | |
26 | + | |
27 | +const props = defineProps({ | |
28 | + chartConfig: { | |
29 | + type: Object as PropType<CreateComponentType>, | |
30 | + required: true | |
31 | + } | |
32 | +}) | |
33 | + | |
34 | +const { w, h } = toRefs(props.chartConfig.attr) | |
35 | +let option = shallowReactive({ ...configOption }) | |
36 | + | |
37 | +// 预览更新 | |
38 | +const vVideoRef = ref(null) | |
39 | +useChartDataFetch(props.chartConfig, useChartEditStore, (newData: any) => { | |
40 | + option = newData | |
41 | +}) | |
42 | + | |
43 | +// 编辑更新 | |
44 | +watch( | |
45 | + () => props.chartConfig.option, | |
46 | + (newData: any) => { | |
47 | + option = newData | |
48 | + if (!vVideoRef.value) return | |
49 | + const video: any = vVideoRef.value | |
50 | + video.loop = option.loop | |
51 | + video.muted = option.muted | |
52 | + video.play() | |
53 | + }, | |
54 | + { | |
55 | + immediate: true, | |
56 | + deep: true | |
57 | + } | |
58 | +) | |
59 | +</script> | |
60 | + | |
61 | +<style lang="scss" scoped> | |
62 | +@include go('video') { | |
63 | + display: block; | |
64 | + object-fit: v-bind('option.fit'); | |
65 | +} | |
66 | +</style> | ... | ... |
... | ... | @@ -20,6 +20,7 @@ import { OverrideProcessConfig } from '@/packages/components/external/Charts/Mor |
20 | 20 | import { OverridePieCircleConfig } from '@/packages/components/external/Charts/Pies/OverridePieCircle' |
21 | 21 | import { OverrideMapBaseConfig } from '@/packages/components/external/Charts/Maps/OverrideMapBase' |
22 | 22 | import { OverrideILoadConfigurationframeConfig } from '@/packages/components/external/Informations/Mores/OverrideILoadConfigurationframe' |
23 | +import { OverrideVideoConfig } from '@/packages/components/external/Informations/Mores/OverrideVideo' | |
23 | 24 | |
24 | 25 | export function useInjectLib(packagesList: EPackagesType) { |
25 | 26 | packagesList[EPackagesCategoryEnum.COMPOSES] = ComposesList |
... | ... | @@ -43,6 +44,7 @@ export function useInjectLib(packagesList: EPackagesType) { |
43 | 44 | addWidgetToCategoryByCategoryName(packagesList, PackagesCategoryEnum.CHARTS, OverridePieCircleConfig) |
44 | 45 | addWidgetToCategoryByCategoryName(packagesList, PackagesCategoryEnum.CHARTS, OverrideMapBaseConfig) |
45 | 46 | addWidgetToCategoryByCategoryName(packagesList, PackagesCategoryEnum.INFORMATIONS, OverrideILoadConfigurationframeConfig) |
47 | + addWidgetToCategoryByCategoryName(packagesList, PackagesCategoryEnum.INFORMATIONS, OverrideVideoConfig) | |
46 | 48 | } |
47 | 49 | |
48 | 50 | /** | ... | ... |