Commit 0e80dddad43088342c30c8b064aa4b33228f8980
Merge branch 'ft' into 'main_dev'
feat: 大屏新增自定义天气 See merge request yunteng/thingskit-view!65
Showing
31 changed files
with
1070 additions
and
16 deletions
| ... | ... | @@ -4,7 +4,8 @@ import { DictItem, UploadResponse } from './model' |
| 4 | 4 | enum Api { |
| 5 | 5 | GET_DICT = '/dict_item', |
| 6 | 6 | UPLOAD = '/oss/upload', |
| 7 | - DOWNLOAD = '/oss/download_file/' | |
| 7 | + DOWNLOAD = '/oss/download_file/', | |
| 8 | + AREALIST = '/area/list' | |
| 8 | 9 | } |
| 9 | 10 | |
| 10 | 11 | export const getDictItemByCode = (value: string) => { |
| ... | ... | @@ -26,3 +27,10 @@ export const upload = (file: FormData) => { |
| 26 | 27 | export const downloadFile = (fileName: string) => { |
| 27 | 28 | return defHttp.get({ url: `${Api.DOWNLOAD}${fileName}` }) |
| 28 | 29 | } |
| 30 | + | |
| 31 | +export const getAreaList = (data: object) => { | |
| 32 | + return defHttp.post<any>({ | |
| 33 | + url: Api.AREALIST, | |
| 34 | + data | |
| 35 | + }) | |
| 36 | +} | ... | ... |
src/assets/external/weather/bg.png
0 → 100644
222 KB
src/assets/external/weather/clearDay.png
0 → 100644
3.45 KB
src/assets/external/weather/cloudy.png
0 → 100644
4.32 KB
src/assets/external/weather/cloudyDay.png
0 → 100644
3.97 KB
src/assets/external/weather/lightRain.png
0 → 100644
5.45 KB
4.32 KB
src/hooks/external/useChartInteract.hook.ts
0 → 100644
| 1 | +/** | |
| 2 | + * 覆盖原作者默认hooks | |
| 3 | + * hooks/useChartInteract.hook.ts | |
| 4 | + * 目的 修改tab切换显示不同内容 | |
| 5 | + * 新增代码是写在注释之间,其他原作者代码逻辑未做修改 | |
| 6 | + */ | |
| 7 | +import { toRefs } from 'vue' | |
| 8 | +import { CreateComponentType } from '@/packages/index.d' | |
| 9 | +import { useChartEditStore } from '@/store/modules/chartEditStore/chartEditStore' | |
| 10 | + | |
| 11 | +// 获取类型 | |
| 12 | +type ChartEditStoreType = typeof useChartEditStore | |
| 13 | + | |
| 14 | +// Params 参数修改触发 api 更新图表请求 | |
| 15 | +export const useChartInteract = ( | |
| 16 | + chartConfig: CreateComponentType, | |
| 17 | + useChartEditStore: ChartEditStoreType, | |
| 18 | + param: { [T: string]: any }, | |
| 19 | + interactEventOn: string | |
| 20 | +) => { | |
| 21 | + /**新增代码 */ | |
| 22 | + let combineData: any = null | |
| 23 | + const { | |
| 24 | + option: { dataset } | |
| 25 | + } = chartConfig | |
| 26 | + combineData = dataset | |
| 27 | + const { data } = param | |
| 28 | + /**新增代码 */ | |
| 29 | + const chartEditStore = useChartEditStore() | |
| 30 | + const { interactEvents } = chartConfig.events | |
| 31 | + const fnOnEvent = interactEvents.filter(item => { | |
| 32 | + return item.interactOn === interactEventOn | |
| 33 | + }) | |
| 34 | + | |
| 35 | + if (fnOnEvent.length === 0) return | |
| 36 | + fnOnEvent.forEach((item, itemIndex) => { | |
| 37 | + const index = chartEditStore.fetchTargetIndex(item.interactComponentId) | |
| 38 | + if (index === -1) return | |
| 39 | + const { Params, Header } = toRefs(chartEditStore.componentList[index].request.requestParams) | |
| 40 | + Object.keys(item.interactFn).forEach(key => { | |
| 41 | + if (Params.value[key]) { | |
| 42 | + Params.value[key] = param[item.interactFn[key]] | |
| 43 | + } | |
| 44 | + if (Header.value[key]) { | |
| 45 | + Header.value[key] = param[item.interactFn[key]] | |
| 46 | + } | |
| 47 | + }) | |
| 48 | + /**新增代码 */ | |
| 49 | + combineData.forEach((combinItem: any, combinIndex: number) => { | |
| 50 | + if (itemIndex === combinIndex) { | |
| 51 | + combinItem.targetItem = chartEditStore.componentList[index] | |
| 52 | + } | |
| 53 | + }) | |
| 54 | + /**新增代码 */ | |
| 55 | + }) | |
| 56 | + /**新增代码 */ | |
| 57 | + combineData.forEach((item: any) => { | |
| 58 | + try { | |
| 59 | + if (item.value === data) item.targetItem.status.hide = false | |
| 60 | + else { | |
| 61 | + item.targetItem.status.hide = true | |
| 62 | + } | |
| 63 | + } catch (e) { | |
| 64 | + console.log(`useChartInteract.hook.ts:${e}`) | |
| 65 | + } | |
| 66 | + }) | |
| 67 | + /**新增代码 */ | |
| 68 | +} | |
| 69 | + | |
| 70 | +// 联动事件触发的 type 变更时,清除当前绑定内容 | |
| 71 | + | |
| 72 | +/**新增代码 */ | |
| 73 | +// eslint-disable-next-line @typescript-eslint/no-empty-function | |
| 74 | +export const clearInteractEvent = (chartConfig: CreateComponentType) => {} | |
| 75 | +/**新增代码 */ | ... | ... |
src/packages/components/external/Composes/Mores/Camera/components/CameraItem.vue
renamed from
src/packages/components/external/Composes/Mores/Camera/cameraItem.vue
| 1 | +<template> | |
| 2 | + <video | |
| 3 | + crossOrigin="anonymous" | |
| 4 | + :id="`my-player${index}`" | |
| 5 | + ref="videoRef" | |
| 6 | + class="video-js my-video vjs-theme-city vjs-big-play-centered" | |
| 7 | + > | |
| 8 | + <source :src="sourceSrc" /> | |
| 9 | + </video> | |
| 10 | +</template> | |
| 11 | +<script setup lang="ts"> | |
| 12 | +import { onMounted, ref, onUnmounted, watch } from 'vue' | |
| 13 | +import videojs from 'video.js' | |
| 14 | +import type { VideoJsPlayerOptions } from 'video.js' | |
| 15 | +import 'video.js/dist/video-js.min.css' | |
| 16 | + | |
| 17 | +const props = defineProps({ | |
| 18 | + sourceSrc: { | |
| 19 | + type: String | |
| 20 | + }, | |
| 21 | + name: { | |
| 22 | + type: String | |
| 23 | + }, | |
| 24 | + avatar: { | |
| 25 | + type: String | |
| 26 | + }, | |
| 27 | + w: { | |
| 28 | + type: Number, | |
| 29 | + default: 300 | |
| 30 | + }, | |
| 31 | + h: { | |
| 32 | + type: Number, | |
| 33 | + default: 300 | |
| 34 | + }, | |
| 35 | + index: { | |
| 36 | + type: Number | |
| 37 | + } | |
| 38 | +}) | |
| 39 | + | |
| 40 | +// video标签 | |
| 41 | +const videoRef = ref<HTMLElement | null>(null) | |
| 42 | + | |
| 43 | +// video实例对象 | |
| 44 | +let videoPlayer: videojs.Player | null = null | |
| 45 | + | |
| 46 | +//options配置 | |
| 47 | +const options: VideoJsPlayerOptions = { | |
| 48 | + language: 'zh-CN', // 设置语言 | |
| 49 | + controls: true, // 是否显示控制条 | |
| 50 | + preload: 'auto', // 预加载 | |
| 51 | + autoplay: true, // 是否自动播放 | |
| 52 | + fluid: false, // 自适应宽高 | |
| 53 | + poster: props?.avatar || '', | |
| 54 | + src: props?.sourceSrc || '', // 要嵌入的视频源的源 URL | |
| 55 | + muted: true, | |
| 56 | + userActions: { | |
| 57 | + hotkeys: true | |
| 58 | + } | |
| 59 | +} | |
| 60 | + | |
| 61 | +// 初始化videojs | |
| 62 | +const initVideo = () => { | |
| 63 | + if (videoRef.value) { | |
| 64 | + // 创建 video 实例 | |
| 65 | + videoPlayer = videojs(videoRef.value, options) | |
| 66 | + } | |
| 67 | +} | |
| 68 | + | |
| 69 | +watch( | |
| 70 | + () => props.sourceSrc, | |
| 71 | + (newData: any) => { | |
| 72 | + // props.sourceSrc = newData | |
| 73 | + videoPlayer?.src(newData) as any | |
| 74 | + videoPlayer?.play() | |
| 75 | + }, | |
| 76 | + { | |
| 77 | + immediate: true | |
| 78 | + } | |
| 79 | +) | |
| 80 | + | |
| 81 | +onMounted(() => { | |
| 82 | + initVideo() | |
| 83 | +}) | |
| 84 | + | |
| 85 | +onUnmounted(() => { | |
| 86 | + handleVideoDispose() | |
| 87 | +}) | |
| 88 | + | |
| 89 | +//播放 | |
| 90 | +const handleVideoPlay = () => videoPlayer?.play() | |
| 91 | + | |
| 92 | +const handleVideoDispose = () => videoPlayer?.dispose() && videoPlayer?.pause() | |
| 93 | +//暂停 | |
| 94 | +defineExpose({ | |
| 95 | + handleVideoPlay, | |
| 96 | + handleVideoDispose | |
| 97 | +}) | |
| 98 | +</script> | |
| 99 | + | |
| 100 | +<style lang="scss" scoped> | |
| 101 | +.my-video { | |
| 102 | + width: 100%; | |
| 103 | + height: 100%; | |
| 104 | +} | |
| 105 | +</style> | ... | ... |
| ... | ... | @@ -7,12 +7,13 @@ export const option = { |
| 7 | 7 | dataset: [ |
| 8 | 8 | { |
| 9 | 9 | url: '' |
| 10 | - }, | |
| 10 | + } | |
| 11 | 11 | ] as any, |
| 12 | 12 | // 自动播放的间隔(ms) |
| 13 | 13 | interval: 5000, |
| 14 | 14 | autoplay: true, |
| 15 | - effect: 'slide' | |
| 15 | + effect: 'slide', | |
| 16 | + displayMode: 'singleGrid' | |
| 16 | 17 | } |
| 17 | 18 | |
| 18 | 19 | export default class Config extends PublicConfigClass implements CreateComponentType { | ... | ... |
| ... | ... | @@ -8,11 +8,16 @@ |
| 8 | 8 | </n-input-group> |
| 9 | 9 | </setting-item> |
| 10 | 10 | <setting-item> |
| 11 | - <n-button v-if="optionData.dataset.length < 5" size="small" @click="optionData.dataset.push({ url: '' })"> | |
| 11 | + <n-button v-if="optionData.dataset.length < 9" size="small" @click="optionData.dataset.push({ url: '' })"> | |
| 12 | 12 | + |
| 13 | 13 | </n-button> |
| 14 | 14 | </setting-item> |
| 15 | 15 | </setting-item-box> |
| 16 | + <setting-item-box name="排布"> | |
| 17 | + <setting-item> | |
| 18 | + <n-select v-model:value="optionData.displayMode" size="small" :options="displayModeOptions"></n-select> | |
| 19 | + </setting-item> | |
| 20 | + </setting-item-box> | |
| 16 | 21 | </CollapseItem> |
| 17 | 22 | </template> |
| 18 | 23 | |
| ... | ... | @@ -27,4 +32,20 @@ defineProps({ |
| 27 | 32 | required: true |
| 28 | 33 | } |
| 29 | 34 | }) |
| 35 | + | |
| 36 | +// 旋转方式 | |
| 37 | +const displayModeOptions = [ | |
| 38 | + { | |
| 39 | + value: 'singleGrid', | |
| 40 | + label: '默认' | |
| 41 | + }, | |
| 42 | + { | |
| 43 | + value: 'fourGrid', | |
| 44 | + label: '四宫格' | |
| 45 | + }, | |
| 46 | + { | |
| 47 | + value: 'nineGrid', | |
| 48 | + label: '九宫格' | |
| 49 | + } | |
| 50 | +] | |
| 30 | 51 | </script> | ... | ... |
| 1 | 1 | <template> |
| 2 | 2 | <div class="banner-box" ref="root"> |
| 3 | - <div class="wrapper"> | |
| 4 | - <div v-for="(item, index) in option.dataset" :key="index + item" :class="item.className" :style="item.sty"> | |
| 3 | + <div class="wrapper"> | |
| 4 | + <div | |
| 5 | + v-for="(item, index) in option.dataset" | |
| 6 | + :key="index + item" | |
| 7 | + :class="item.className" | |
| 8 | + :style="item.sty" | |
| 9 | + > | |
| 5 | 10 | <CameraItem |
| 6 | 11 | ref="cameraRef" |
| 7 | 12 | :name="item.name" |
| ... | ... | @@ -24,7 +29,7 @@ import { PropType, watch, toRefs, shallowReactive, onMounted, ref } from 'vue' |
| 24 | 29 | import { CreateComponentType } from '@/packages/index.d' |
| 25 | 30 | import 'video.js/dist/video-js.min.css' |
| 26 | 31 | import { option as configOption } from './config' |
| 27 | -import CameraItem from './cameraItem.vue' | |
| 32 | +import { CameraItem } from './components' | |
| 28 | 33 | |
| 29 | 34 | const props = defineProps({ |
| 30 | 35 | chartConfig: { |
| ... | ... | @@ -83,7 +88,7 @@ const computedFunc = (initial: number, source: any) => { |
| 83 | 88 | } |
| 84 | 89 | item.sty = { |
| 85 | 90 | transform, |
| 86 | - zIndex, | |
| 91 | + zIndex | |
| 87 | 92 | } |
| 88 | 93 | item.className = className |
| 89 | 94 | return item |
| ... | ... | @@ -101,6 +106,7 @@ watch( |
| 101 | 106 | deep: true |
| 102 | 107 | } |
| 103 | 108 | ) |
| 109 | + | |
| 104 | 110 | option.dataset = computedFunc(initial.value, option.dataset) |
| 105 | 111 | |
| 106 | 112 | watch( |
| ... | ... | @@ -109,6 +115,7 @@ watch( |
| 109 | 115 | option.dataset = computedFunc(newV, option.dataset) |
| 110 | 116 | } |
| 111 | 117 | ) |
| 118 | + | |
| 112 | 119 | // 处理自动轮播 |
| 113 | 120 | let timer: any = null |
| 114 | 121 | |
| ... | ... | @@ -135,13 +142,11 @@ onMounted(() => { |
| 135 | 142 | // 点击左右按钮切换图片 |
| 136 | 143 | function changeVideo(dir: string) { |
| 137 | 144 | if (dir === 'left') { |
| 138 | - // cameraRef.value?.handleVideoPlay() | |
| 139 | 145 | clearInterval(timer) |
| 140 | 146 | initial.value++ |
| 141 | 147 | initial.value >= option.dataset.length ? (initial.value = 0) : false |
| 142 | 148 | return |
| 143 | 149 | } |
| 144 | - // cameraRef.value?.handleVideoDispose() | |
| 145 | 150 | initial.value-- |
| 146 | 151 | initial.value < 0 ? (initial.value = option.dataset.length - 1) : false |
| 147 | 152 | } |
| ... | ... | @@ -155,16 +160,13 @@ function changeSlide(dir: string) { |
| 155 | 160 | <style lang="scss" scoped> |
| 156 | 161 | .banner-box { |
| 157 | 162 | .wrapper { |
| 158 | - // width: 100%; | |
| 159 | 163 | height: 100%; |
| 160 | - // position: relative; | |
| 161 | 164 | display: flex; |
| 162 | 165 | overflow: hidden; |
| 163 | 166 | .slide { |
| 164 | 167 | width: 20%; |
| 165 | 168 | height: 100%; |
| 166 | 169 | position: absolute; |
| 167 | - // top: 50%; | |
| 168 | 170 | left: 10%; |
| 169 | 171 | transform: translateX(-50%); |
| 170 | 172 | transition: 0.5s; |
| ... | ... | @@ -193,12 +195,12 @@ function changeSlide(dir: string) { |
| 193 | 195 | } |
| 194 | 196 | a.left { |
| 195 | 197 | @extend .arrow; |
| 196 | - background-image: url(./left.svg); | |
| 198 | + background-image: url(./static/left.svg); | |
| 197 | 199 | left: 0px; |
| 198 | 200 | } |
| 199 | 201 | a.right { |
| 200 | 202 | @extend .arrow; |
| 201 | - background-image: url(./right.svg); | |
| 203 | + background-image: url(./static/right.svg); | |
| 202 | 204 | right: 0px; |
| 203 | 205 | } |
| 204 | 206 | } | ... | ... |
| 1 | +<template> | |
| 2 | + <div class="banner-box" ref="root"> | |
| 3 | + <n-grid x-gap="12" :y-gap="12" :cols="computedCols"> | |
| 4 | + <n-gi v-for="(item, index) in option.dataset" :key="index + item"> | |
| 5 | + <div class="camera-container"> | |
| 6 | + <CameraItem | |
| 7 | + ref="cameraRef" | |
| 8 | + :name="item.name" | |
| 9 | + :avatar="item.avatar" | |
| 10 | + :key="item + index" | |
| 11 | + :sourceSrc="item.url" | |
| 12 | + :index="index" | |
| 13 | + /> | |
| 14 | + </div> | |
| 15 | + </n-gi> | |
| 16 | + </n-grid> | |
| 17 | + </div> | |
| 18 | +</template> | |
| 19 | +<script setup lang="ts"> | |
| 20 | +import { PropType, toRefs, watch, shallowReactive, ref, computed } from 'vue' | |
| 21 | +import { CreateComponentType } from '@/packages/index.d' | |
| 22 | +import 'video.js/dist/video-js.min.css' | |
| 23 | +import { option as configOption } from './config' | |
| 24 | +import { CameraItem } from './components' | |
| 25 | + | |
| 26 | +const props = defineProps({ | |
| 27 | + chartConfig: { | |
| 28 | + type: Object as PropType<CreateComponentType>, | |
| 29 | + required: true | |
| 30 | + } | |
| 31 | +}) | |
| 32 | + | |
| 33 | +const { h } = toRefs(props.chartConfig.attr) | |
| 34 | + | |
| 35 | +const responsiveComputeValue = ref(0) | |
| 36 | + | |
| 37 | +const option = shallowReactive({ | |
| 38 | + dataset: configOption.dataset | |
| 39 | +}) | |
| 40 | + | |
| 41 | +const computedCols = computed(() => { | |
| 42 | + if (option.dataset.length <= 1) return 1 | |
| 43 | + if (option.dataset.length <= 4) return 2 | |
| 44 | + return 3 | |
| 45 | +}) | |
| 46 | + | |
| 47 | +const cameraRef = ref<InstanceType<typeof CameraItem>>() | |
| 48 | + | |
| 49 | +const responsive = (value: number) => { | |
| 50 | + responsiveComputeValue.value = value | |
| 51 | + if (option.dataset.length <= 2) responsiveComputeValue.value = value | |
| 52 | + if (option.dataset.length > 2 && option.dataset.length <= 4) responsiveComputeValue.value = value / 2.03 | |
| 53 | + if (option.dataset.length > 4 && option.dataset.length <= 9) responsiveComputeValue.value = value / 3.1 | |
| 54 | +} | |
| 55 | + | |
| 56 | +watch( | |
| 57 | + () => props.chartConfig.option.dataset, | |
| 58 | + newData => { | |
| 59 | + option.dataset = newData | |
| 60 | + responsive(h.value) | |
| 61 | + }, | |
| 62 | + { | |
| 63 | + immediate: true, | |
| 64 | + deep: true | |
| 65 | + } | |
| 66 | +) | |
| 67 | + | |
| 68 | +watch( | |
| 69 | + () => h.value, | |
| 70 | + newData => responsive(newData), | |
| 71 | + { | |
| 72 | + immediate: true | |
| 73 | + } | |
| 74 | +) | |
| 75 | +</script> | |
| 76 | + | |
| 77 | +<style lang="scss" scoped> | |
| 78 | +.banner-box { | |
| 79 | + .camera-container { | |
| 80 | + height: v-bind('`${responsiveComputeValue}px`'); | |
| 81 | + } | |
| 82 | +} | |
| 83 | +</style> | ... | ... |
src/packages/components/external/Composes/Mores/Camera/static/left.svg
renamed from
src/packages/components/external/Composes/Mores/Camera/left.svg
src/packages/components/external/Composes/Mores/Camera/static/right.svg
renamed from
src/packages/components/external/Composes/Mores/Camera/right.svg
| 1 | +<script lang="ts" setup name="SelectCity"> | |
| 2 | +import { onMounted, reactive } from 'vue' | |
| 3 | +import { getAreaList } from '@/api/external/common/index' | |
| 4 | +import { areaEnum } from '../config' | |
| 5 | + | |
| 6 | +const emits = defineEmits(['submit']) | |
| 7 | + | |
| 8 | +const selectOptions = reactive({ | |
| 9 | + provinceOptions: [], | |
| 10 | + cityOptions: [], | |
| 11 | + countryOptions: [] | |
| 12 | +}) | |
| 13 | + | |
| 14 | +const selectValues = reactive({ | |
| 15 | + provinceValue: null, | |
| 16 | + cityValue: null, | |
| 17 | + countyValue: null | |
| 18 | +}) | |
| 19 | + | |
| 20 | +const getAreaLists = async (level = areaEnum.PROVINCE, parentId = 1) => { | |
| 21 | + const resp = await getAreaList({ | |
| 22 | + level, | |
| 23 | + parentId | |
| 24 | + }) | |
| 25 | + if (!resp) return [] | |
| 26 | + return resp.map((item: any) => ({ label: item.name, value: item.code })) | |
| 27 | +} | |
| 28 | + | |
| 29 | +onMounted(async () => { | |
| 30 | + selectOptions.provinceOptions = await getAreaLists() | |
| 31 | +}) | |
| 32 | + | |
| 33 | +const onHandleSelectProvince = async (value: number) => { | |
| 34 | + selectValues.cityValue = null | |
| 35 | + selectValues.countyValue = null | |
| 36 | + selectOptions.cityOptions = await getAreaLists(areaEnum.CITY, value) | |
| 37 | +} | |
| 38 | + | |
| 39 | +const onHandleSelectCity = async (value: number) => { | |
| 40 | + selectValues.countyValue = null | |
| 41 | + selectOptions.countryOptions = await getAreaLists(areaEnum.COUNTY, value) | |
| 42 | +} | |
| 43 | + | |
| 44 | +const onHandleSubmit = () => { | |
| 45 | + emits('submit', selectValues) | |
| 46 | +} | |
| 47 | +</script> | |
| 48 | + | |
| 49 | +<template> | |
| 50 | + <div class="select-city-content"> | |
| 51 | + <n-select | |
| 52 | + @change="onHandleSelectProvince" | |
| 53 | + placeholder="请选择省份" | |
| 54 | + v-model:value="selectValues.provinceValue" | |
| 55 | + :options="selectOptions.provinceOptions" | |
| 56 | + /> | |
| 57 | + <n-select | |
| 58 | + @change="onHandleSelectCity" | |
| 59 | + placeholder="请选择城市" | |
| 60 | + v-model:value="selectValues.cityValue" | |
| 61 | + :options="selectOptions.cityOptions" | |
| 62 | + /> | |
| 63 | + <n-select | |
| 64 | + placeholder="请选择区域" | |
| 65 | + v-model:value="selectValues.countyValue" | |
| 66 | + :options="selectOptions.countryOptions" | |
| 67 | + /> | |
| 68 | + <n-button type="primary" @click="onHandleSubmit">确定</n-button> | |
| 69 | + </div> | |
| 70 | +</template> | |
| 71 | + | |
| 72 | +<style lang="scss" scoped> | |
| 73 | +.select-city-content { | |
| 74 | + display: flex; | |
| 75 | + flex-direction: column; | |
| 76 | + justify-content: space-between; | |
| 77 | + align-items: center; | |
| 78 | + gap: 30px; | |
| 79 | +} | |
| 80 | +</style> | ... | ... |
| 1 | +<script lang="ts" setup name="WeatherContent"> | |
| 2 | +import { toRefs } from 'vue' | |
| 3 | +import SelectCity from './SelectCity.vue' | |
| 4 | +import { useUtils } from '../hooks/useUtils' | |
| 5 | + | |
| 6 | +const props = defineProps({ | |
| 7 | + data: { | |
| 8 | + type: Array as any | |
| 9 | + } | |
| 10 | +}) | |
| 11 | + | |
| 12 | +const emits = defineEmits(['submit']) | |
| 13 | + | |
| 14 | +const { loadWeatherImg, loadWeatherLevel } = useUtils() | |
| 15 | + | |
| 16 | +const { casts } = toRefs(props.data[0]) | |
| 17 | + | |
| 18 | +const onHandleSelectValues = (values: any) => { | |
| 19 | + emits('submit', values) | |
| 20 | +} | |
| 21 | +</script> | |
| 22 | + | |
| 23 | +<template> | |
| 24 | + <div> | |
| 25 | + <n-card | |
| 26 | + :segmented="{ | |
| 27 | + content: true, | |
| 28 | + footer: 'soft' | |
| 29 | + }" | |
| 30 | + :style="{ backgroundImage: `url('src/assets/external/weather/bg.png')` }" | |
| 31 | + class="n-card" | |
| 32 | + > | |
| 33 | + <template #header> | |
| 34 | + <div class="card-header"> | |
| 35 | + <div class="city-text">{{ data[0]?.city }}</div> | |
| 36 | + <n-popover placement="bottom" trigger="hover"> | |
| 37 | + <template #trigger> | |
| 38 | + <div class="city-text">切换城市</div> | |
| 39 | + </template> | |
| 40 | + <div :style="`width:${300}px;height:${300}px;`"> | |
| 41 | + <SelectCity @submit="onHandleSelectValues" /> | |
| 42 | + </div> | |
| 43 | + </n-popover> | |
| 44 | + </div> | |
| 45 | + </template> | |
| 46 | + <div class="card-content"> | |
| 47 | + <div class="content-weather"> | |
| 48 | + <div>{{ casts[0]?.daytemp }}°</div> | |
| 49 | + <div> | |
| 50 | + <img :src="loadWeatherImg(casts[0]?.dayweather)" /> | |
| 51 | + </div> | |
| 52 | + </div> | |
| 53 | + <div class="content-weather-air"> | |
| 54 | + <div>{{ casts[0]?.dayweather }}</div> | |
| 55 | + <div>{{ casts[0]?.daywind }}</div> | |
| 56 | + <div>{{ loadWeatherLevel(casts[0]?.daypower) }}</div> | |
| 57 | + </div> | |
| 58 | + </div> | |
| 59 | + <template #footer> | |
| 60 | + <div v-for="(item, index) in casts" :key="index" class="footer-content"> | |
| 61 | + <div>{{ index === 0 ? '今天' : index === 1 ? '明天' : index === 2 ? '后天' : '外天' }}</div> | |
| 62 | + <div> | |
| 63 | + <img :src="loadWeatherImg(item?.dayweather)" /> | |
| 64 | + </div> | |
| 65 | + <div>{{ item?.daytemp }}/{{ item?.nighttemp }}</div> | |
| 66 | + <div>{{ item?.daywind }}</div> | |
| 67 | + <div>{{ loadWeatherLevel(item?.daypower) }}</div> | |
| 68 | + </div> | |
| 69 | + </template> | |
| 70 | + </n-card> | |
| 71 | + </div> | |
| 72 | +</template> | |
| 73 | + | |
| 74 | +<style lang="scss" scoped> | |
| 75 | +%flex-style { | |
| 76 | + display: flex; | |
| 77 | + justify-content: space-between; | |
| 78 | + color: white; | |
| 79 | +} | |
| 80 | +.n-card { | |
| 81 | + background-repeat: no-repeat; | |
| 82 | + background-size: cover; | |
| 83 | + .card-header { | |
| 84 | + @extend %flex-style; | |
| 85 | + .city-text { | |
| 86 | + color: white; | |
| 87 | + cursor: pointer; | |
| 88 | + } | |
| 89 | + } | |
| 90 | + .card-content { | |
| 91 | + @extend %flex-style; | |
| 92 | + .content-weather { | |
| 93 | + @extend %flex-style; | |
| 94 | + font-size: 45px; | |
| 95 | + } | |
| 96 | + .content-weather-air { | |
| 97 | + display: flex; | |
| 98 | + flex-direction: column; | |
| 99 | + } | |
| 100 | + img { | |
| 101 | + width: 24px; | |
| 102 | + height: 24px; | |
| 103 | + } | |
| 104 | + } | |
| 105 | + .footer-content { | |
| 106 | + @extend %flex-style; | |
| 107 | + gap: 10px; | |
| 108 | + margin-left: 10px; | |
| 109 | + div { | |
| 110 | + flex: 1; | |
| 111 | + text-align: left; | |
| 112 | + img { | |
| 113 | + width: 24px; | |
| 114 | + height: 24px; | |
| 115 | + } | |
| 116 | + } | |
| 117 | + } | |
| 118 | +} | |
| 119 | +</style> | ... | ... |
| 1 | +import { PublicConfigClass } from '@/packages/public' | |
| 2 | +import { CreateComponentType } from '@/packages/index.d' | |
| 3 | +import { WeatherConfig } from './index' | |
| 4 | +import cloneDeep from 'lodash/cloneDeep' | |
| 5 | +import { chartInitConfig } from '@/settings/designSetting' | |
| 6 | + | |
| 7 | +//第三方 天气接口key值和api配置(高德) | |
| 8 | +export class ThirdPartyWeatherConnfig { | |
| 9 | + static ak = '0551a87b45e0363ae6c7a2242e8ac944' | |
| 10 | + static api = 'https://restapi.amap.com/v3/weather/weatherInfo' | |
| 11 | + // ((all,预报天气),(base,实况天气)) | |
| 12 | + static extensions = 'all' | |
| 13 | +} | |
| 14 | + | |
| 15 | +//省市区枚举 | |
| 16 | +export const enum areaEnum { | |
| 17 | + PROVINCE = 'PROVINCE', | |
| 18 | + CITY = 'CITY', | |
| 19 | + COUNTY = 'COUNTY' | |
| 20 | +} | |
| 21 | + | |
| 22 | +//天气文字映射图片 | |
| 23 | +export const weatherTextMapImg = [ | |
| 24 | + { | |
| 25 | + text: '晴', | |
| 26 | + img: '/large-designer/src/assets/external/weather/clearDay.png' | |
| 27 | + }, | |
| 28 | + { | |
| 29 | + text: '多云', | |
| 30 | + img: '/large-designer/src/assets/external/weather/cloudy.png' | |
| 31 | + }, | |
| 32 | + { | |
| 33 | + text: '阴', | |
| 34 | + img: '/large-designer/src/assets/external/weather/cloudyDay.png' | |
| 35 | + }, | |
| 36 | + { | |
| 37 | + text: '小雨', | |
| 38 | + img: '/large-designer/src/assets/external/weather/lightRain.png' | |
| 39 | + } | |
| 40 | +] | |
| 41 | + | |
| 42 | +//风力等级文字映射 | |
| 43 | +export const weatherSpeedMapText = [ | |
| 44 | + { | |
| 45 | + level: (value: number) => value >= 0.0 && value <= 0.2, | |
| 46 | + text: '无风' | |
| 47 | + }, | |
| 48 | + { | |
| 49 | + level: (value: number) => value >= 0.3 && value <= 1.5, | |
| 50 | + text: '软风' | |
| 51 | + }, | |
| 52 | + { | |
| 53 | + level: (value: number) => value >= 1.6 && value <= 3.3, | |
| 54 | + text: '轻风' | |
| 55 | + }, | |
| 56 | + { | |
| 57 | + level: (value: number) => value >= 3.4 && value <= 5.4, | |
| 58 | + text: '微风' | |
| 59 | + }, | |
| 60 | + { | |
| 61 | + level: (value: number) => value >= 5.5 && value <= 7.9, | |
| 62 | + text: '和风' | |
| 63 | + }, | |
| 64 | + { | |
| 65 | + level: (value: number) => value >= 8.0 && value <= 10.7, | |
| 66 | + text: '劲风' | |
| 67 | + }, | |
| 68 | + { | |
| 69 | + level: (value: number) => value >= 10.8 && value <= 13.8, | |
| 70 | + text: '强风' | |
| 71 | + }, | |
| 72 | + { | |
| 73 | + level: (value: number) => value >= 13.9 || value <= 17.1, | |
| 74 | + text: '疾风' | |
| 75 | + }, | |
| 76 | + { | |
| 77 | + level: (value: number) => value >= 17.2 && value <= 20.7, | |
| 78 | + text: '大风' | |
| 79 | + }, | |
| 80 | + { | |
| 81 | + level: (value: number) => value >= 20.8 && value <= 24.4, | |
| 82 | + text: '烈风' | |
| 83 | + }, | |
| 84 | + { | |
| 85 | + level: (value: number) => value >= 24.5 && value <= 28.4, | |
| 86 | + text: '狂风' | |
| 87 | + }, | |
| 88 | + { | |
| 89 | + level: (value: number) => value >= 28.5 && value <= 32.6, | |
| 90 | + text: '暴风' | |
| 91 | + }, | |
| 92 | + { | |
| 93 | + level: (value: number) => value >= 32.7 && value <= 36.9, | |
| 94 | + text: '飓风' | |
| 95 | + } | |
| 96 | +] | |
| 97 | + | |
| 98 | +export const option = { | |
| 99 | + dataset: { | |
| 100 | + provinceValue: null, | |
| 101 | + cityValue: '510100', | |
| 102 | + countyValue: null | |
| 103 | + }, | |
| 104 | + weatherCss: { | |
| 105 | + temperatureTextSize: '16', | |
| 106 | + temperatureTextColor: '#000000', | |
| 107 | + cityTextSize: '16', | |
| 108 | + cityTextColor: '#000000', | |
| 109 | + weatherIconSizeWidth: '24', | |
| 110 | + weatherIconSizeHeight: '24', | |
| 111 | + airSpeedTextSize: '16', | |
| 112 | + airSpeedTextColor: '#000000', | |
| 113 | + airSpeedLevelTextSize: '16', | |
| 114 | + airSpeedLevelTextColor: '#000000', | |
| 115 | + backgroundColor: 'transparent', | |
| 116 | + borderRadius: '5' | |
| 117 | + } | |
| 118 | +} | |
| 119 | + | |
| 120 | +export default class Config extends PublicConfigClass implements CreateComponentType { | |
| 121 | + public key = WeatherConfig.key | |
| 122 | + public attr = { ...chartInitConfig, zIndex: 1, w: 300, h: 60 } | |
| 123 | + public chartConfig = cloneDeep(WeatherConfig) | |
| 124 | + public option = cloneDeep(option) | |
| 125 | +} | ... | ... |
| 1 | +<template> | |
| 2 | + <CollapseItem name="天气配置" :expanded="true"> | |
| 3 | + <SettingItemBox name="区域选择"> </SettingItemBox> | |
| 4 | + <SelectCity @submit="onHandleSelectValues" /> | |
| 5 | + <SettingItemBox name="背景"> | |
| 6 | + <SettingItem name="颜色"> | |
| 7 | + <n-color-picker v-model:value="optionData.weatherCss.backgroundColor" /> | |
| 8 | + </SettingItem> | |
| 9 | + <SettingItem> | |
| 10 | + <n-button size="small" @click="optionData.weatherCss.backgroundColor = 'transparent'"> 恢复默认颜色 </n-button> | |
| 11 | + </SettingItem> | |
| 12 | + <SettingItem name="圆角"> | |
| 13 | + <n-input v-model:value="optionData.weatherCss.borderRadius" /> | |
| 14 | + </SettingItem> | |
| 15 | + </SettingItemBox> | |
| 16 | + <SettingItemBox name="温度文字"> | |
| 17 | + <SettingItem name="大小"> | |
| 18 | + <n-input v-model:value="optionData.weatherCss.temperatureTextSize" /> | |
| 19 | + </SettingItem> | |
| 20 | + <SettingItem name="颜色"> | |
| 21 | + <n-color-picker v-model:value="optionData.weatherCss.temperatureTextColor" /> | |
| 22 | + </SettingItem> | |
| 23 | + <SettingItem> | |
| 24 | + <n-button size="small" @click="optionData.weatherCss.temperatureTextColor = '#000000'"> 恢复默认颜色 </n-button> | |
| 25 | + </SettingItem> | |
| 26 | + </SettingItemBox> | |
| 27 | + <SettingItemBox name="城市名"> | |
| 28 | + <SettingItem name="大小"> | |
| 29 | + <n-input v-model:value="optionData.weatherCss.cityTextSize" /> | |
| 30 | + </SettingItem> | |
| 31 | + <SettingItem name="颜色"> | |
| 32 | + <n-color-picker v-model:value="optionData.weatherCss.cityTextColor" /> | |
| 33 | + </SettingItem> | |
| 34 | + <SettingItem> | |
| 35 | + <n-button size="small" @click="optionData.weatherCss.cityTextColor = '#000000'"> 恢复默认颜色 </n-button> | |
| 36 | + </SettingItem> | |
| 37 | + </SettingItemBox> | |
| 38 | + <SettingItemBox name="天气图标"> | |
| 39 | + <SettingItem name="宽度"> | |
| 40 | + <n-input v-model:value="optionData.weatherCss.weatherIconSizeWidth" /> | |
| 41 | + </SettingItem> | |
| 42 | + <SettingItem name="高度"> | |
| 43 | + <n-input v-model:value="optionData.weatherCss.weatherIconSizeHeight" /> | |
| 44 | + </SettingItem> | |
| 45 | + </SettingItemBox> | |
| 46 | + <SettingItemBox name="风速文字"> | |
| 47 | + <SettingItem name="大小"> | |
| 48 | + <n-input v-model:value="optionData.weatherCss.airSpeedTextSize" /> | |
| 49 | + </SettingItem> | |
| 50 | + <SettingItem name="颜色"> | |
| 51 | + <n-color-picker v-model:value="optionData.weatherCss.airSpeedTextColor" /> | |
| 52 | + </SettingItem> | |
| 53 | + <SettingItem> | |
| 54 | + <n-button size="small" @click="optionData.weatherCss.airSpeedTextColor = '#000000'"> 恢复默认颜色 </n-button> | |
| 55 | + </SettingItem> | |
| 56 | + </SettingItemBox> | |
| 57 | + <SettingItemBox name="风速等级"> | |
| 58 | + <SettingItem name="大小"> | |
| 59 | + <n-input v-model:value="optionData.weatherCss.airSpeedLevelTextSize" /> | |
| 60 | + </SettingItem> | |
| 61 | + <SettingItem name="颜色"> | |
| 62 | + <n-color-picker v-model:value="optionData.weatherCss.airSpeedLevelTextColor" /> | |
| 63 | + </SettingItem> | |
| 64 | + <SettingItem> | |
| 65 | + <n-button size="small" @click="optionData.weatherCss.airSpeedLevelTextColor = '#000000'"> | |
| 66 | + 恢复默认颜色 | |
| 67 | + </n-button> | |
| 68 | + </SettingItem> | |
| 69 | + </SettingItemBox> | |
| 70 | + </CollapseItem> | |
| 71 | +</template> | |
| 72 | + | |
| 73 | +<script setup lang="ts"> | |
| 74 | +import { PropType } from 'vue' | |
| 75 | +import { CollapseItem, SettingItemBox, SettingItem } from '@/components/Pages/ChartItemSetting' | |
| 76 | +import { option } from './config' | |
| 77 | +import SelectCity from './componnets/SelectCity.vue' | |
| 78 | + | |
| 79 | +const props = defineProps({ | |
| 80 | + optionData: { | |
| 81 | + type: Object as PropType<typeof option>, | |
| 82 | + required: true | |
| 83 | + } | |
| 84 | +}) | |
| 85 | + | |
| 86 | +const onHandleSelectValues = (values: any) => { | |
| 87 | + props.optionData.dataset = values | |
| 88 | +} | |
| 89 | +</script> | ... | ... |
| 1 | +import { weatherSpeedMapText, weatherTextMapImg } from '../config' | |
| 2 | + | |
| 3 | +export const useUtils = () => { | |
| 4 | + const loadWeatherImg = (text: string) => { | |
| 5 | + return ( | |
| 6 | + weatherTextMapImg.find((item: any) => item.text === text)?.img || | |
| 7 | + '/large-designer/src/assets/external/weather/clearDay.png' | |
| 8 | + ) | |
| 9 | + } | |
| 10 | + | |
| 11 | + //风力等级 ≤3 3 | |
| 12 | + const loadWeatherLevel = (speed: string) => { | |
| 13 | + let handleSpeed = '' | |
| 14 | + if (speed?.startsWith('≤')) { | |
| 15 | + handleSpeed = speed.slice(1) | |
| 16 | + } else { | |
| 17 | + handleSpeed = speed | |
| 18 | + } | |
| 19 | + return weatherSpeedMapText.find((item: any) => item.level(Number(handleSpeed)))?.text | |
| 20 | + } | |
| 21 | + return { | |
| 22 | + loadWeatherImg, | |
| 23 | + loadWeatherLevel | |
| 24 | + } | |
| 25 | +} | ... | ... |
| 1 | +import { ConfigType, PackagesCategoryEnum, ChartFrameEnum } from '@/packages/index.d' | |
| 2 | +import { ChatCategoryEnum, ChatCategoryEnumName } from '@/packages/components/Decorates/index.d' | |
| 3 | +import { useWidgetKey } from '@/packages/external/useWidgetKey' | |
| 4 | + | |
| 5 | +const { key, conKey, chartKey } = useWidgetKey('Weather', true) | |
| 6 | + | |
| 7 | +export const WeatherConfig: ConfigType = { | |
| 8 | + key, | |
| 9 | + chartKey, | |
| 10 | + conKey, | |
| 11 | + title: '天气', | |
| 12 | + category: ChatCategoryEnum.MORE, | |
| 13 | + categoryName: ChatCategoryEnumName.MORE, | |
| 14 | + package: PackagesCategoryEnum.DECORATES, | |
| 15 | + chartFrame: ChartFrameEnum.STATIC, | |
| 16 | + image: 'weather.png' | |
| 17 | +} | ... | ... |
| 1 | +<script lang="ts" setup name="Weather"> | |
| 2 | +import { PropType, toRefs, onMounted, reactive, watch } from 'vue' | |
| 3 | +import { CreateComponentType } from '@/packages/index.d' | |
| 4 | +import { option, ThirdPartyWeatherConnfig } from './config' | |
| 5 | +import axios from 'axios' | |
| 6 | +import WeatherContent from './componnets/WeatherContent.vue' | |
| 7 | +import { useUtils } from './hooks/useUtils' | |
| 8 | + | |
| 9 | +const props = defineProps({ | |
| 10 | + chartConfig: { | |
| 11 | + type: Object as PropType<CreateComponentType & { option: typeof option }>, | |
| 12 | + required: true | |
| 13 | + } | |
| 14 | +}) | |
| 15 | + | |
| 16 | +const { loadWeatherImg, loadWeatherLevel } = useUtils() | |
| 17 | + | |
| 18 | +const { w, h } = toRefs(props.chartConfig.attr) | |
| 19 | + | |
| 20 | +const { weatherCss } = toRefs(props.chartConfig.option) | |
| 21 | + | |
| 22 | +const weatherInfoValues = reactive<any>({ | |
| 23 | + weatherInfo: [] | |
| 24 | +}) | |
| 25 | + | |
| 26 | +const getWeatherInfos = async (area: any) => { | |
| 27 | + const { cityValue, countyValue } = area || props.chartConfig.option.dataset | |
| 28 | + const params = { | |
| 29 | + key: ThirdPartyWeatherConnfig.ak, | |
| 30 | + city: !countyValue ? cityValue : countyValue, | |
| 31 | + extensions: ThirdPartyWeatherConnfig.extensions | |
| 32 | + } | |
| 33 | + const { | |
| 34 | + data: { forecasts } | |
| 35 | + } = await axios.get(ThirdPartyWeatherConnfig.api, { params }) | |
| 36 | + if (!forecasts) return | |
| 37 | + weatherInfoValues.weatherInfo = forecasts | |
| 38 | +} | |
| 39 | + | |
| 40 | +onMounted(() => { | |
| 41 | + getWeatherInfos(props.chartConfig.option.dataset) | |
| 42 | +}) | |
| 43 | + | |
| 44 | +watch( | |
| 45 | + () => props.chartConfig.option.dataset, | |
| 46 | + newValue => { | |
| 47 | + getWeatherInfos(newValue) | |
| 48 | + }, | |
| 49 | + { | |
| 50 | + deep: true, | |
| 51 | + immediate: true | |
| 52 | + } | |
| 53 | +) | |
| 54 | + | |
| 55 | +const onHandleSelectValues = (values: any) => { | |
| 56 | + getWeatherInfos(values) | |
| 57 | +} | |
| 58 | +</script> | |
| 59 | + | |
| 60 | +<template> | |
| 61 | + <div> | |
| 62 | + <n-popover placement="bottom" trigger="hover" raw :show-arrow="false"> | |
| 63 | + <template #trigger> | |
| 64 | + <n-card class="card" :bordered="false"> | |
| 65 | + <div class="card-content" v-for="(item, index) in weatherInfoValues.weatherInfo" :key="index"> | |
| 66 | + <div class="weather-temp">{{ item?.casts[0]?.daytemp }}°C</div> | |
| 67 | + <div class="weather-city">{{ item?.city }}</div> | |
| 68 | + <div><img class="weather-img" :src="loadWeatherImg(item?.casts[0]?.dayweather)" /></div> | |
| 69 | + <div class="weather-speed">{{ item?.casts[0]?.daywind }}</div> | |
| 70 | + <div class="weather-speed-level">{{ loadWeatherLevel(item?.casts[0]?.daypower) }}</div> | |
| 71 | + </div> | |
| 72 | + </n-card> | |
| 73 | + </template> | |
| 74 | + <div class="popup-content"> | |
| 75 | + <WeatherContent :data="weatherInfoValues.weatherInfo" @submit="onHandleSelectValues" /> | |
| 76 | + </div> | |
| 77 | + </n-popover> | |
| 78 | + </div> | |
| 79 | +</template> | |
| 80 | + | |
| 81 | +<style lang="scss" scoped> | |
| 82 | +.popup-content { | |
| 83 | + width: 300px; | |
| 84 | + height: 300px; | |
| 85 | +} | |
| 86 | +.card { | |
| 87 | + width: v-bind('w+"px"'); | |
| 88 | + height: v-bind('h+"px"'); | |
| 89 | + background-color: v-bind('weatherCss.backgroundColor'); | |
| 90 | + border-radius: v-bind('`${weatherCss.borderRadius}px`'); | |
| 91 | + .card-content { | |
| 92 | + display: flex; | |
| 93 | + justify-content: space-between; | |
| 94 | + .weather-temp { | |
| 95 | + font-size: v-bind('`${weatherCss.temperatureTextSize}px`'); | |
| 96 | + color: v-bind('weatherCss.temperatureTextColor'); | |
| 97 | + } | |
| 98 | + .weather-city { | |
| 99 | + text-overflow: ellipsis; | |
| 100 | + overflow: hidden; | |
| 101 | + word-break: break-all; | |
| 102 | + white-space: nowrap; | |
| 103 | + font-size: v-bind('`${weatherCss.cityTextSize}px`'); | |
| 104 | + color: v-bind('weatherCss.cityTextColor'); | |
| 105 | + } | |
| 106 | + .weather-img { | |
| 107 | + width: v-bind('`${weatherCss.weatherIconSizeWidth}px`'); | |
| 108 | + height: v-bind('`${weatherCss.weatherIconSizeHeight}px`'); | |
| 109 | + } | |
| 110 | + .weather-speed { | |
| 111 | + font-size: v-bind('`${weatherCss.airSpeedTextSize}px`'); | |
| 112 | + color: v-bind('weatherCss.airSpeedTextColor'); | |
| 113 | + } | |
| 114 | + .weather-speed-level { | |
| 115 | + font-size: v-bind('`${weatherCss.airSpeedLevelTextSize}px`'); | |
| 116 | + color: v-bind('weatherCss.airSpeedLevelTextColor'); | |
| 117 | + } | |
| 118 | + img { | |
| 119 | + width: 24px; | |
| 120 | + height: 24px; | |
| 121 | + } | |
| 122 | + } | |
| 123 | +} | |
| 124 | +</style> | ... | ... |
| 1 | +import cloneDeep from 'lodash/cloneDeep' | |
| 2 | +import { PublicConfigClass } from '@/packages/public' | |
| 3 | +import { CreateComponentType } from '@/packages/index.d' | |
| 4 | +import { chartInitConfig } from '@/settings/designSetting' | |
| 5 | +import { COMPONENT_INTERACT_EVENT_KET } from '@/enums/eventEnum' | |
| 6 | +import { interactActions, ComponentInteractEventEnum } from './interact' | |
| 7 | +import { OverrideInputsTabConfig } from './index' | |
| 8 | + | |
| 9 | +export const option = { | |
| 10 | + // 时间组件展示类型,必须和 interactActions 中定义的数据一致 | |
| 11 | + [COMPONENT_INTERACT_EVENT_KET]: ComponentInteractEventEnum.DATA, | |
| 12 | + // 默认值 | |
| 13 | + tabLabel: '选项1', | |
| 14 | + // 样式 | |
| 15 | + tabType: 'segment', | |
| 16 | + // 暴露配置内容给用户 | |
| 17 | + dataset: [ | |
| 18 | + { | |
| 19 | + label: '选项1', | |
| 20 | + value: '1' | |
| 21 | + }, | |
| 22 | + { | |
| 23 | + label: '选项2', | |
| 24 | + value: '2' | |
| 25 | + }, | |
| 26 | + { | |
| 27 | + label: '选项3', | |
| 28 | + value: '3' | |
| 29 | + } | |
| 30 | + ] | |
| 31 | +} | |
| 32 | + | |
| 33 | +export default class Config extends PublicConfigClass implements CreateComponentType { | |
| 34 | + public key = OverrideInputsTabConfig.key | |
| 35 | + public attr = { ...chartInitConfig, w: 460, h: 32, zIndex: -1 } | |
| 36 | + public chartConfig = cloneDeep(OverrideInputsTabConfig) | |
| 37 | + public interactActions = interactActions | |
| 38 | + public option = cloneDeep(option) | |
| 39 | +} | ... | ... |
| 1 | +<template> | |
| 2 | + <collapse-item name="标签页配置" :expanded="true"> | |
| 3 | + <setting-item-box name="默认值" :alone="true"> | |
| 4 | + <n-select size="small" v-model:value="optionData.tabType" :options="tabTypeOptions" /> | |
| 5 | + </setting-item-box> | |
| 6 | + </collapse-item> | |
| 7 | +</template> | |
| 8 | + | |
| 9 | +<script lang="ts" setup> | |
| 10 | +import { PropType } from 'vue' | |
| 11 | +import { CollapseItem, SettingItemBox } from '@/components/Pages/ChartItemSetting' | |
| 12 | +import { option } from './config' | |
| 13 | + | |
| 14 | +defineProps({ | |
| 15 | + optionData: { | |
| 16 | + type: Object as PropType<typeof option>, | |
| 17 | + required: true | |
| 18 | + } | |
| 19 | +}) | |
| 20 | + | |
| 21 | +const tabTypeOptions = [ | |
| 22 | + { | |
| 23 | + label: '线条', | |
| 24 | + value: 'bar' | |
| 25 | + }, | |
| 26 | + { | |
| 27 | + label: '分段', | |
| 28 | + value: 'segment' | |
| 29 | + } | |
| 30 | +] | |
| 31 | +</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('OverrideInputsTab', true) | |
| 6 | + | |
| 7 | +export const OverrideInputsTabConfig: ConfigType = { | |
| 8 | + key, | |
| 9 | + chartKey, | |
| 10 | + conKey, | |
| 11 | + title: '自定义标签选择器', | |
| 12 | + category: ChatCategoryEnum.MORE, | |
| 13 | + categoryName: ChatCategoryEnumName.MORE, | |
| 14 | + package: PackagesCategoryEnum.INFORMATIONS, | |
| 15 | + chartFrame: ChartFrameEnum.STATIC, | |
| 16 | + image: 'inputs_tab.png' | |
| 17 | +} | ... | ... |
| 1 | +<template> | |
| 2 | + <n-tabs :type="option.value.tabType" @update:value="onChange"> | |
| 3 | + <n-tab v-for="(item, index) in option.value.dataset" :name="item.label" :key="index"> {{ item.label }} </n-tab> | |
| 4 | + </n-tabs> | |
| 5 | +</template> | |
| 6 | + | |
| 7 | +<script setup lang="ts"> | |
| 8 | +import { PropType, toRefs, shallowReactive, watch, onMounted } from 'vue' | |
| 9 | +import cloneDeep from 'lodash/cloneDeep' | |
| 10 | +import { CreateComponentType } from '@/packages/index.d' | |
| 11 | +import { useChartEditStore } from '@/store/modules/chartEditStore/chartEditStore' | |
| 12 | +import { useChartInteract } from '@/hooks/external/useChartInteract.hook' | |
| 13 | +import { InteractEventOn } from '@/enums/eventEnum' | |
| 14 | +import { ComponentInteractParamsEnum } from './interact' | |
| 15 | + | |
| 16 | +const props = defineProps({ | |
| 17 | + chartConfig: { | |
| 18 | + type: Object as PropType<CreateComponentType>, | |
| 19 | + required: true | |
| 20 | + } | |
| 21 | +}) | |
| 22 | + | |
| 23 | +const { w, h } = toRefs(props.chartConfig.attr) | |
| 24 | +const option = shallowReactive({ | |
| 25 | + value: cloneDeep(props.chartConfig.option) | |
| 26 | +}) | |
| 27 | + | |
| 28 | +// 监听事件改变 | |
| 29 | +const onChange = (v: string) => { | |
| 30 | + if (v === undefined) return | |
| 31 | + const selectItem = option.value.dataset.find((item: { label: string; value: any }) => item.label === v) | |
| 32 | + // 存储到联动数据 | |
| 33 | + useChartInteract( | |
| 34 | + props.chartConfig, | |
| 35 | + useChartEditStore, | |
| 36 | + { [ComponentInteractParamsEnum.DATA]: selectItem.value }, | |
| 37 | + InteractEventOn.CHANGE | |
| 38 | + ) | |
| 39 | +} | |
| 40 | + | |
| 41 | +/**新增代码 */ | |
| 42 | +onMounted(() => { | |
| 43 | + onChange(option.value.dataset[0].label) | |
| 44 | +}) | |
| 45 | +/**新增代码 */ | |
| 46 | + | |
| 47 | +// 手动更新 | |
| 48 | +watch( | |
| 49 | + () => props.chartConfig.option, | |
| 50 | + (newData: any) => { | |
| 51 | + option.value = newData | |
| 52 | + onChange(newData.tabValue) | |
| 53 | + }, | |
| 54 | + { | |
| 55 | + immediate: true, | |
| 56 | + deep: true | |
| 57 | + } | |
| 58 | +) | |
| 59 | +</script> | ... | ... |
| 1 | +import { InteractEventOn, InteractActionsType } from '@/enums/eventEnum' | |
| 2 | + | |
| 3 | +// 时间组件类型 | |
| 4 | +export enum ComponentInteractEventEnum { | |
| 5 | + DATA = 'data' | |
| 6 | +} | |
| 7 | + | |
| 8 | +// 联动参数 | |
| 9 | +export enum ComponentInteractParamsEnum { | |
| 10 | + DATA = 'data' | |
| 11 | +} | |
| 12 | + | |
| 13 | +// 定义组件触发回调事件 | |
| 14 | +export const interactActions: InteractActionsType[] = [ | |
| 15 | + { | |
| 16 | + interactType: InteractEventOn.CHANGE, | |
| 17 | + interactName: '选择完成', | |
| 18 | + componentEmitEvents: { | |
| 19 | + [ComponentInteractEventEnum.DATA]: [ | |
| 20 | + { | |
| 21 | + value: ComponentInteractParamsEnum.DATA, | |
| 22 | + label: '选择项' | |
| 23 | + } | |
| 24 | + ] | |
| 25 | + } | |
| 26 | + } | |
| 27 | +] | ... | ... |
| ... | ... | @@ -86,7 +86,7 @@ |
| 86 | 86 | |
| 87 | 87 | <script lang="ts" setup> |
| 88 | 88 | import { PropType } from 'vue' |
| 89 | -import { CollapseItem, SettingItemBox } from '@/components/Pages/ChartItemSetting' | |
| 89 | +import { CollapseItem, SettingItemBox, SettingItem } from '@/components/Pages/ChartItemSetting' | |
| 90 | 90 | import { option } from './config' |
| 91 | 91 | |
| 92 | 92 | defineProps({ | ... | ... |
| ... | ... | @@ -2,20 +2,24 @@ import { EPackagesCategoryEnum, EPackagesType } from '@/packages/components/exte |
| 2 | 2 | import { ComposesList } from '@/packages/components/external/Composes' |
| 3 | 3 | import { ConfigType, PackagesCategoryEnum } from '@/packages/index.d' |
| 4 | 4 | import { ClockConfig } from '@/packages/components/external/Decorates/Mores/Icon' |
| 5 | +import { WeatherConfig } from '@/packages/components/external/Decorates/Mores/Weather' | |
| 5 | 6 | import { OverrideImageConfig } from '@/packages/components/external/Informations/Mores/OverrideImage' |
| 6 | 7 | import { OverrideCarouselConfig } from '@/packages/components/external/Informations/Mores/OverrideCarousel' |
| 7 | 8 | import { OverrideSelectConfig } from '@/packages/components/external/Informations/Mores/OverrideSelect' |
| 8 | 9 | import { OverrideInputsDateConfig } from '@/packages/components/external/Informations/Mores/OverrideInputsDate' |
| 10 | +import { OverrideInputsTabConfig } from '@/packages/components/external/Informations/Mores/OverrideInputsTab' | |
| 9 | 11 | |
| 10 | 12 | export function useInjectLib(packagesList: EPackagesType) { |
| 11 | 13 | |
| 12 | 14 | packagesList[EPackagesCategoryEnum.COMPOSES] = ComposesList |
| 13 | 15 | |
| 14 | 16 | addWidgetToCategoryByCategoryName(packagesList, PackagesCategoryEnum.DECORATES, ClockConfig) |
| 17 | + addWidgetToCategoryByCategoryName(packagesList, PackagesCategoryEnum.DECORATES, WeatherConfig) | |
| 15 | 18 | addWidgetToCategoryByCategoryName(packagesList, PackagesCategoryEnum.INFORMATIONS, OverrideImageConfig) |
| 16 | 19 | addWidgetToCategoryByCategoryName(packagesList, PackagesCategoryEnum.INFORMATIONS, OverrideCarouselConfig) |
| 17 | 20 | addWidgetToCategoryByCategoryName(packagesList, PackagesCategoryEnum.INFORMATIONS, OverrideSelectConfig) |
| 18 | 21 | addWidgetToCategoryByCategoryName(packagesList, PackagesCategoryEnum.INFORMATIONS, OverrideInputsDateConfig) |
| 22 | + addWidgetToCategoryByCategoryName(packagesList, PackagesCategoryEnum.INFORMATIONS, OverrideInputsTabConfig) | |
| 19 | 23 | } |
| 20 | 24 | |
| 21 | 25 | /** | ... | ... |