Commit 1b53c9bc50713a140993da1d8f6a4adb0a11a0bd

Authored by fengwotao
1 parent 478db852

perf(external/Composes): 小组件下新增天气

... ... @@ -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 +<script lang="ts" setup>
  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>
  2 +import { toRefs } from 'vue'
  3 +import SelectCity from './SelectCity.vue'
  4 +import { weatherTextMapImg } from '../config'
  5 +
  6 +const props = defineProps({
  7 + data: {
  8 + type: Array as any
  9 + }
  10 +})
  11 +
  12 +const emits = defineEmits(['submit'])
  13 +
  14 +const { casts } = toRefs(props.data[0])
  15 +
  16 +const onHandleSelectValues = (values: any) => {
  17 + emits('submit', values)
  18 +}
  19 +
  20 +//根据返回的文字加载不同天气图片
  21 +const loadWeatherImg = (text: string) => {
  22 + return (
  23 + weatherTextMapImg.find((item: any) => item.text === text)?.img ||
  24 + '/large-designer/src/assets/external/weather/clearDay.png'
  25 + )
  26 +}
  27 +</script>
  28 +
  29 +<template>
  30 + <div>
  31 + <n-card
  32 + :segmented="{
  33 + content: true,
  34 + footer: 'soft'
  35 + }"
  36 + :style="{ backgroundImage: `url('src/assets/external/weather/bg.png')` }"
  37 + class="n-card"
  38 + >
  39 + <template #header>
  40 + <div class="card-header">
  41 + <div class="city-text">{{ data[0]?.city }}</div>
  42 + <n-popover placement="bottom" trigger="hover">
  43 + <template #trigger>
  44 + <div class="city-text">切换城市</div>
  45 + </template>
  46 + <div :style="`width:${300}px;height:${300}px;`">
  47 + <SelectCity @submit="onHandleSelectValues" />
  48 + </div>
  49 + </n-popover>
  50 + </div>
  51 + </template>
  52 + <div class="card-content">
  53 + <div class="content-weather">
  54 + <div>{{ casts[0]?.daytemp }}°</div>
  55 + <div>
  56 + <img :src="loadWeatherImg(casts[0]?.dayweather)" />
  57 + </div>
  58 + </div>
  59 + <div class="content-weather-air">
  60 + <div>{{ casts[0]?.dayweather }}</div>
  61 + <div>{{ casts[0]?.daywind }}</div>
  62 + </div>
  63 + </div>
  64 + <template #footer>
  65 + <div v-for="(item, index) in casts?.slice(0, 3)" :key="index" class="footer-content">
  66 + <div>{{ index === 0 ? '今天' : index === 1 ? '明天' : '后天' }}</div>
  67 + <div>
  68 + <img :src="loadWeatherImg(item?.dayweather)" />
  69 + </div>
  70 + <div>{{ item?.daytemp }}/{{ item?.nighttemp }}</div>
  71 + <div>{{ item?.daywind }}</div>
  72 + </div>
  73 + </template>
  74 + </n-card>
  75 + </div>
  76 +</template>
  77 +
  78 +<style lang="scss" scoped>
  79 +%flex-style {
  80 + display: flex;
  81 + justify-content: space-between;
  82 + color: white;
  83 +}
  84 +.n-card {
  85 + background-repeat: no-repeat;
  86 + background-size: cover;
  87 + .card-header {
  88 + @extend %flex-style;
  89 + .city-text {
  90 + color: white;
  91 + cursor: pointer;
  92 + }
  93 + }
  94 + .card-content {
  95 + width: 100%;
  96 + @extend %flex-style;
  97 + .content-weather {
  98 + @extend %flex-style;
  99 + font-size: 45px;
  100 + }
  101 + .content-weather-air {
  102 + display: flex;
  103 + flex-direction: column;
  104 + }
  105 + img {
  106 + width: 24px;
  107 + height: 24px;
  108 + }
  109 + }
  110 + .footer-content {
  111 + width: 100%;
  112 + @extend %flex-style;
  113 + gap: 30px;
  114 + margin-left: 5px;
  115 + div {
  116 + flex: 1;
  117 + img {
  118 + width: 24px;
  119 + height: 24px;
  120 + }
  121 + }
  122 + }
  123 +}
  124 +</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 const thirdPartyWeatherKey = `0551a87b45e0363ae6c7a2242e8ac944`
  9 +export const thirdPartyWeatherApi = `https://restapi.amap.com/v3/weather/weatherInfo`
  10 +
  11 +//省市区枚举
  12 +export const enum areaEnum {
  13 + PROVINCE = 'PROVINCE',
  14 + CITY = 'CITY',
  15 + COUNTY = 'COUNTY'
  16 +}
  17 +
  18 +//天气文字映射图片
  19 +export const weatherTextMapImg = [
  20 + {
  21 + text: '晴',
  22 + img: '/large-designer/src/assets/external/weather/clearDay.png'
  23 + },
  24 + {
  25 + text: '多云',
  26 + img: '/large-designer/src/assets/external/weather/cloudy.png'
  27 + },
  28 + {
  29 + text: '阴',
  30 + img: '/large-designer/src/assets/external/weather/cloudyDay.png'
  31 + },
  32 + {
  33 + text: '小雨',
  34 + img: '/large-designer/src/assets/external/weather/lightRain.png'
  35 + }
  36 +]
  37 +
  38 +export const option = {
  39 + dataset: {
  40 + provinceValue: null,
  41 + cityValue: '510100',
  42 + countyValue: null
  43 + },
  44 + weatherCss: {
  45 + temperatureTextSize: '16',
  46 + temperatureTextColor: '#000000',
  47 + cityTextSize: '16',
  48 + cityTextColor: '#000000',
  49 + weatherIconSizeWidth: '24',
  50 + weatherIconSizeHeight: '24',
  51 + airSpeedTextSize: '16',
  52 + airSpeedTextColor: '#000000',
  53 + backgroundColor: 'transparent',
  54 + borderRadius: '5'
  55 + }
  56 +}
  57 +
  58 +export default class Config extends PublicConfigClass implements CreateComponentType {
  59 + public key = WeatherConfig.key
  60 + public attr = { ...chartInitConfig, zIndex: 1, w: 181, h: 44 }
  61 + public chartConfig = cloneDeep(WeatherConfig)
  62 + public option = cloneDeep(option)
  63 +}
... ...
  1 +<template>
  2 + <CollapseItem name="天气配置" :expanded="true">
  3 + <SettingItemBox name="区域选择">
  4 + <SelectCity @submit="onHandleSelectValues" />
  5 + </SettingItemBox>
  6 + <SettingItemBox name="背景">
  7 + <SettingItem name="颜色">
  8 + <n-color-picker v-model:value="optionData.weatherCss.backgroundColor" />
  9 + </SettingItem>
  10 + <SettingItem>
  11 + <n-button size="small" @click="optionData.weatherCss.backgroundColor = 'transparent'"> 恢复默认颜色 </n-button>
  12 + </SettingItem>
  13 + <SettingItem name="圆角">
  14 + <n-input v-model:value="optionData.weatherCss.borderRadius" />
  15 + </SettingItem>
  16 + </SettingItemBox>
  17 + <SettingItemBox name="温度文字">
  18 + <SettingItem name="大小">
  19 + <n-input v-model:value="optionData.weatherCss.temperatureTextSize" />
  20 + </SettingItem>
  21 + <SettingItem name="颜色">
  22 + <n-color-picker v-model:value="optionData.weatherCss.temperatureTextColor" />
  23 + </SettingItem>
  24 + <SettingItem>
  25 + <n-button size="small" @click="optionData.weatherCss.temperatureTextColor = '#000000'"> 恢复默认颜色 </n-button>
  26 + </SettingItem>
  27 + </SettingItemBox>
  28 + <SettingItemBox name="城市名">
  29 + <SettingItem name="大小">
  30 + <n-input v-model:value="optionData.weatherCss.cityTextSize" />
  31 + </SettingItem>
  32 + <SettingItem name="颜色">
  33 + <n-color-picker v-model:value="optionData.weatherCss.cityTextColor" />
  34 + </SettingItem>
  35 + <SettingItem>
  36 + <n-button size="small" @click="optionData.weatherCss.cityTextColor = '#000000'"> 恢复默认颜色 </n-button>
  37 + </SettingItem>
  38 + </SettingItemBox>
  39 + <SettingItemBox name="天气图标">
  40 + <SettingItem name="宽度">
  41 + <n-input v-model:value="optionData.weatherCss.weatherIconSizeWidth" />
  42 + </SettingItem>
  43 + <SettingItem name="高度">
  44 + <n-input v-model:value="optionData.weatherCss.weatherIconSizeHeight" />
  45 + </SettingItem>
  46 + </SettingItemBox>
  47 + <SettingItemBox name="风速文字">
  48 + <SettingItem name="大小">
  49 + <n-input v-model:value="optionData.weatherCss.airSpeedTextSize" />
  50 + </SettingItem>
  51 + <SettingItem name="颜色">
  52 + <n-color-picker v-model:value="optionData.weatherCss.airSpeedTextColor" />
  53 + </SettingItem>
  54 + <SettingItem>
  55 + <n-button size="small" @click="optionData.weatherCss.airSpeedTextColor = '#000000'"> 恢复默认颜色 </n-button>
  56 + </SettingItem>
  57 + </SettingItemBox>
  58 + </CollapseItem>
  59 +</template>
  60 +
  61 +<script setup lang="ts">
  62 +import { PropType } from 'vue'
  63 +import { CollapseItem, SettingItemBox, SettingItem } from '@/components/Pages/ChartItemSetting'
  64 +import { option } from './config'
  65 +import SelectCity from './componnets/SelectCity.vue'
  66 +
  67 +const props = defineProps({
  68 + optionData: {
  69 + type: Object as PropType<typeof option>,
  70 + required: true
  71 + }
  72 +})
  73 +
  74 +const onHandleSelectValues = (values: any) => {
  75 + props.optionData.dataset = values
  76 +}
  77 +</script>
... ...
  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>
  2 +import { PropType, toRefs, onMounted, reactive, watch } from 'vue'
  3 +import { CreateComponentType } from '@/packages/index.d'
  4 +import { option, thirdPartyWeatherKey, thirdPartyWeatherApi, weatherTextMapImg } from './config'
  5 +import axios from 'axios'
  6 +import WeatherContent from './componnets/WeatherContent.vue'
  7 +
  8 +const props = defineProps({
  9 + chartConfig: {
  10 + type: Object as PropType<CreateComponentType & { option: typeof option }>,
  11 + required: true
  12 + }
  13 +})
  14 +
  15 +const { w, h } = toRefs(props.chartConfig.attr)
  16 +
  17 +const {
  18 + temperatureTextSize,
  19 + temperatureTextColor,
  20 + cityTextSize,
  21 + cityTextColor,
  22 + weatherIconSizeWidth,
  23 + weatherIconSizeHeight,
  24 + airSpeedTextSize,
  25 + airSpeedTextColor,
  26 + backgroundColor,
  27 + borderRadius
  28 +} = toRefs(props.chartConfig.option.weatherCss)
  29 +
  30 +const weatherInfoValues: any = reactive({
  31 + weatherInfo: []
  32 +})
  33 +
  34 +//获取第三方服务(高德天气)
  35 +const getWeatherInfos = async (cityObj: any, extensions = 'all') => {
  36 + const { cityValue, countyValue } = cityObj || props.chartConfig.option.dataset
  37 + const params = {
  38 + key: thirdPartyWeatherKey,
  39 + city: !countyValue ? cityValue : countyValue,
  40 + extensions
  41 + } as any
  42 + const {
  43 + data: { forecasts }
  44 + } = await axios.get(thirdPartyWeatherApi, { params })
  45 + if (!forecasts) return
  46 + weatherInfoValues.weatherInfo = forecasts
  47 +}
  48 +
  49 +onMounted(() => {
  50 + getWeatherInfos(props.chartConfig.option.dataset)
  51 +})
  52 +
  53 +watch(
  54 + () => props.chartConfig.option.dataset,
  55 + newValue => {
  56 + getWeatherInfos(newValue)
  57 + },
  58 + {
  59 + deep: true,
  60 + immediate: true
  61 + }
  62 +)
  63 +
  64 +const onHandleSelectValues = (values: any) => {
  65 + getWeatherInfos(values)
  66 +}
  67 +
  68 +//根据返回的文字加载不同天气图片
  69 +const loadWeatherImg = (text: string) => {
  70 + return (
  71 + weatherTextMapImg.find((item: any) => item.text === text)?.img ||
  72 + '/large-designer/src/assets/external/weather/clearDay.png'
  73 + )
  74 +}
  75 +</script>
  76 +
  77 +<template>
  78 + <div>
  79 + <n-popover placement="bottom" trigger="hover" raw :show-arrow="false">
  80 + <template #trigger>
  81 + <n-card
  82 + class="card"
  83 + :bordered="false"
  84 + :style="`background-color:${backgroundColor};border-radius:${borderRadius}px;`"
  85 + >
  86 + <div class="card-content" v-for="(item, index) in weatherInfoValues.weatherInfo" :key="index">
  87 + <div :style="`font-size:${temperatureTextSize}px;color:${temperatureTextColor};`">
  88 + {{ item?.casts[0]?.daytemp }}°C
  89 + </div>
  90 + <div class="card-content-city-text" :style="`font-size:${cityTextSize}px;color:${cityTextColor};`">
  91 + {{ item?.city }}
  92 + </div>
  93 + <div>
  94 + <img
  95 + :style="`width:${weatherIconSizeWidth}px;height:${weatherIconSizeHeight}px;`"
  96 + :src="loadWeatherImg(item?.casts[0]?.dayweather)"
  97 + />
  98 + </div>
  99 + <div :style="`font-size:${airSpeedTextSize}px;color:${airSpeedTextColor};`">
  100 + {{ item?.casts[0]?.daywind }}
  101 + </div>
  102 + </div>
  103 + </n-card>
  104 + </template>
  105 + <div :style="`width:${300}px;height:${300}px;`">
  106 + <WeatherContent :data="weatherInfoValues.weatherInfo" @submit="onHandleSelectValues" />
  107 + </div>
  108 + </n-popover>
  109 + </div>
  110 +</template>
  111 +
  112 +<style lang="scss" scoped>
  113 +.card {
  114 + width: v-bind('w+"px"');
  115 + height: v-bind('h+"px"');
  116 + .card-content {
  117 + width: 100%;
  118 + display: flex;
  119 + justify-content: space-between;
  120 + img {
  121 + width: 24px;
  122 + height: 24px;
  123 + }
  124 + .card-content-city-text {
  125 + text-overflow: ellipsis;
  126 + overflow: hidden;
  127 + word-break: break-all;
  128 + white-space: nowrap;
  129 + }
  130 + }
  131 +}
  132 +</style>
... ...
... ... @@ -2,6 +2,7 @@ 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'
... ... @@ -12,6 +13,7 @@ export function useInjectLib(packagesList: EPackagesType) {
12 13 packagesList[EPackagesCategoryEnum.COMPOSES] = ComposesList
13 14
14 15 addWidgetToCategoryByCategoryName(packagesList, PackagesCategoryEnum.DECORATES, ClockConfig)
  16 + addWidgetToCategoryByCategoryName(packagesList, PackagesCategoryEnum.DECORATES, WeatherConfig)
15 17 addWidgetToCategoryByCategoryName(packagesList, PackagesCategoryEnum.INFORMATIONS, OverrideImageConfig)
16 18 addWidgetToCategoryByCategoryName(packagesList, PackagesCategoryEnum.INFORMATIONS, OverrideCarouselConfig)
17 19 addWidgetToCategoryByCategoryName(packagesList, PackagesCategoryEnum.INFORMATIONS, OverrideSelectConfig)
... ...