Commit 0e80dddad43088342c30c8b064aa4b33228f8980

Authored by xp.Huang
2 parents 478db852 1a9f821d

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 +}
... ...
  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>
... ...
  1 +import CameraItem from './CameraItem.vue'
  2 +
  3 +export { CameraItem }
... ...
... ... @@ -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 /**
... ...