Commit ba4c57471aa327ccc19761780826f4883ed1362b

Authored by xp.Huang
2 parents 4dba979d 3b34bcf0

Merge branch 'ft' into 'main_dev'

feat(src/packages): 图表里的地图 新增3d地图,支持凸起和标点

See merge request yunteng/thingskit-view!116

Too many changes to show.

To preserve performance only 12 of 21 files are displayed.

@@ -30,6 +30,7 @@ @@ -30,6 +30,7 @@
30 "crypto-js": "^4.1.1", 30 "crypto-js": "^4.1.1",
31 "dayjs": "^1.11.7", 31 "dayjs": "^1.11.7",
32 "dom-helpers": "^5.2.1", 32 "dom-helpers": "^5.2.1",
  33 + "echarts-gl": "^2.0.9",
33 "echarts-liquidfill": "^3.1.0", 34 "echarts-liquidfill": "^3.1.0",
34 "echarts-stat": "^1.2.0", 35 "echarts-stat": "^1.2.0",
35 "echarts-wordcloud": "^2.0.0", 36 "echarts-wordcloud": "^2.0.0",
@@ -10,7 +10,10 @@ enum Api { @@ -10,7 +10,10 @@ enum Api {
10 CONFIGURATION = '/configuration/center', 10 CONFIGURATION = '/configuration/center',
11 CONFIGURATION_SHARE = '/configuration/center/share/', 11 CONFIGURATION_SHARE = '/configuration/center/share/',
12 BASEORIGINATION = '/organization/me/list/', 12 BASEORIGINATION = '/organization/me/list/',
13 - VIDEO = '/video/list' 13 + VIDEO = '/video/list',
  14 + DEVICE_PROFILE = '/device_profile/me/list',
  15 + DEVICE = '/device',
  16 + VIDEOURL = '/video/url/'
14 } 17 }
15 18
16 export const getDictItemByCode = (value: string) => { 19 export const getDictItemByCode = (value: string) => {
@@ -77,8 +80,29 @@ export const getOrganizationList = (params?: OrganizationListItem) => @@ -77,8 +80,29 @@ export const getOrganizationList = (params?: OrganizationListItem) =>
77 }) 80 })
78 81
79 //获取视频列表 82 //获取视频列表
80 -export const getVideoList = (params?:object) => 83 +export const getVideoList = (params?: object) =>
81 defHttp.get({ 84 defHttp.get({
82 url: Api.VIDEO, 85 url: Api.VIDEO,
83 params 86 params
84 }) 87 })
  88 +
  89 +//获取产品列表
  90 +export const getProfileList = (params?: object) =>
  91 + defHttp.get({
  92 + url: Api.DEVICE_PROFILE,
  93 + params
  94 + })
  95 +
  96 +//获取设备列表
  97 +export const getDeviceList = (params: any, data?: object) =>
  98 + defHttp.post({
  99 + url: Api.DEVICE,
  100 + params,
  101 + data
  102 + })
  103 +
  104 +//获取平台视频流播放地址
  105 +export const getVideoUrl = (id: string) =>
  106 + defHttp.get({
  107 + url: `${Api.VIDEOURL}${id}`
  108 + })
  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 props = defineProps({
  7 + drillingIn: {
  8 + type: Boolean,
  9 + default: false
  10 + },
  11 + optionData: {
  12 + type: Object
  13 + }
  14 +})
  15 +
  16 +const emits = defineEmits(['submit'])
  17 +
  18 +const selectOptions = reactive({
  19 + provinceOptions: [],
  20 + cityOptions: [],
  21 + countryOptions: []
  22 +})
  23 +
  24 +const selectValues = reactive({
  25 + provinceValue: 'china',
  26 + cityValue: null,
  27 + countyValue: null
  28 +})
  29 +
  30 +const getAreaLists = async (level = areaEnum.PROVINCE, parentId = 1) => {
  31 + const resp = await getAreaList({
  32 + level,
  33 + parentId
  34 + })
  35 + if (!resp) return []
  36 + return resp.map((item: any) => ({ label: item.name, value: item.code }))
  37 +}
  38 +
  39 +onMounted(async () => {
  40 + selectOptions.provinceOptions = await getAreaLists()
  41 + ;(selectOptions.provinceOptions as never as any).unshift({
  42 + label: '中国',
  43 + value: 'china'
  44 + })
  45 + onHandleSelectProvince(props.optionData?.mapRegion.saveSelect['provinceValue'])
  46 + for (let i in selectValues) Reflect.set(selectValues, i, props.optionData?.mapRegion.saveSelect[i])
  47 +})
  48 +
  49 +const onHandleSelectProvince = async (value: number | string) => {
  50 + selectValues.cityValue = null
  51 + selectValues.countyValue = null
  52 + if (value === 'china') return
  53 + selectOptions.cityOptions = await getAreaLists(areaEnum.CITY, value as any)
  54 +}
  55 +
  56 +const onHandleSelectCity = async (value: number) => {
  57 + selectValues.countyValue = null
  58 + selectOptions.countryOptions = await getAreaLists(areaEnum.COUNTY, value)
  59 +}
  60 +
  61 +const onHandleSubmit = () => {
  62 + emits('submit', selectValues)
  63 +}
  64 +</script>
  65 +
  66 +<template>
  67 + <div class="select-city-content">
  68 + <n-select
  69 + @change="onHandleSelectProvince"
  70 + placeholder="请选择省份"
  71 + v-model:value="selectValues.provinceValue"
  72 + :options="selectOptions.provinceOptions"
  73 + />
  74 + <n-select
  75 + v-if="!props.drillingIn"
  76 + @change="onHandleSelectCity"
  77 + placeholder="请选择城市"
  78 + v-model:value="selectValues.cityValue"
  79 + :options="selectOptions.cityOptions"
  80 + />
  81 + <!-- 保留待用(下钻到区以下) -->
  82 + <!-- <n-select
  83 + v-if="!drillingIn"
  84 + placeholder="请选择区域"
  85 + v-model:value="selectValues.countyValue"
  86 + :options="selectOptions.countryOptions"
  87 + /> -->
  88 + <n-button type="primary" @click="onHandleSubmit">确定</n-button>
  89 + </div>
  90 +</template>
  91 +
  92 +<style lang="scss" scoped>
  93 +.select-city-content {
  94 + display: flex;
  95 + flex-direction: column;
  96 + justify-content: space-between;
  97 + align-items: center;
  98 + gap: 30px;
  99 +}
  100 +</style>
  1 +import { echartOptionProfixHandle, PublicConfigClass } from '@/packages/public'
  2 +import { AddThreeDimensionalMapConfig } from './index'
  3 +import { chartInitConfig } from '@/settings/designSetting'
  4 +import { CreateComponentType } from '@/packages/index.d'
  5 +import cloneDeep from 'lodash/cloneDeep'
  6 +import dataMaps from './data.json'
  7 +
  8 +//省市区枚举
  9 +export const enum areaEnum {
  10 + PROVINCE = 'PROVINCE',
  11 + CITY = 'CITY',
  12 + COUNTY = 'COUNTY'
  13 +}
  14 +
  15 +export const includes = []
  16 +
  17 +export const option = {
  18 + iconColor: 'black',
  19 + showIcon: false,
  20 + iconDistanceRight: 20,
  21 + iconDistanceTop: 20,
  22 + drillingIn: false,
  23 + dataset: dataMaps,
  24 + mapRegion: {
  25 + adcode: 'china',
  26 + showHainanIsLands: true,
  27 + saveSelect: {}
  28 + },
  29 + tooltip: {
  30 + show: true
  31 + },
  32 + geo3D: {
  33 + map: 'centerMap',
  34 + roam: true,
  35 + regionHeight: 0,
  36 + label: {
  37 + show: true,
  38 + textStyle: {
  39 + fontSize: 14,
  40 + color: 'blue',
  41 + borderWidth: 0,
  42 + borderColor: '#000'
  43 + }
  44 + },
  45 + emphasis: {
  46 + label: {
  47 + show: true,
  48 + textStyle: {
  49 + color: 'black',
  50 + fontSize: 32
  51 + }
  52 + },
  53 + itemStyle: {
  54 + color: '#fd9c5a'
  55 + }
  56 + }
  57 + },
  58 + series: [
  59 + {
  60 + type: 'map3D',
  61 + map: 'centerMap',
  62 + name: 'centerMap',
  63 + regionHeight: 3,
  64 + label: {
  65 + show: true,
  66 + color: 'yellow',
  67 + fontSize: 14
  68 + },
  69 + itemStyle: {
  70 + color: 'green',
  71 + borderWidth: 0.8,
  72 + borderColor: 'blue'
  73 + },
  74 + data: []
  75 + },
  76 + {
  77 + name: 'scatter3D',
  78 + type: 'scatter3D',
  79 + coordinateSystem: 'geo3D',
  80 + symbol: 'circle',
  81 + symbolSize: 20,
  82 + animation: true,
  83 + data: dataMaps
  84 + }
  85 + ]
  86 +}
  87 +export const MapDefaultConfig = { ...option }
  88 +export default class Config extends PublicConfigClass implements CreateComponentType {
  89 + public key: string = AddThreeDimensionalMapConfig.key
  90 + public attr = { ...chartInitConfig, w: 750, h: 800, zIndex: -1 }
  91 + public chartConfig = cloneDeep(AddThreeDimensionalMapConfig)
  92 + public option = echartOptionProfixHandle(option, includes)
  93 +}
  1 +<template>
  2 + <!-- Echarts 全局设置 -->
  3 + <global-setting :optionData="optionData"></global-setting>
  4 + <CollapseItem name="地图" :expanded="true">
  5 + <!-- <SettingItemBox name="开启下钻">
  6 + <SettingItem name="">
  7 + <n-switch v-model:value="optionData.drillingIn" size="small"></n-switch>
  8 + </SettingItem>
  9 + </SettingItemBox>
  10 + <SettingItemBox name="返回图标">
  11 + <SettingItem name="">
  12 + <n-switch v-model:value="optionData.drillingIn" size="small"></n-switch>
  13 + </SettingItem>
  14 + </SettingItemBox>
  15 + <SettingItemBox name="图标颜色">
  16 + <SettingItem name="">
  17 + <n-color-picker size="small" :modes="['hex']" v-model:value="optionData.iconColor"></n-color-picker>
  18 + </SettingItem>
  19 + </SettingItemBox>
  20 + <SettingItemBox name="图标距离">
  21 + <SettingItem name="距右">
  22 + <n-input-number
  23 + v-model:value="optionData.iconDistanceRight"
  24 + :min="1"
  25 + size="small"
  26 + placeholder="请输入"
  27 + ></n-input-number>
  28 + </SettingItem>
  29 + <SettingItem name="距上">
  30 + <n-input-number
  31 + v-model:value="optionData.iconDistanceTop"
  32 + :min="1"
  33 + size="small"
  34 + placeholder="请输入"
  35 + ></n-input-number>
  36 + </SettingItem>
  37 + </SettingItemBox> -->
  38 + <SelectCity :optionData="optionData" :drillingIn="optionData.drillingIn" @submit="onHandleSelectValues" />
  39 + <SettingItemBox name="颜色">
  40 + <SettingItem name="区域颜色">
  41 + <n-color-picker size="small" :modes="['hex']" v-model:value="seriesList[0].itemStyle.color"></n-color-picker>
  42 + </SettingItem>
  43 + <SettingItem name="边框颜色">
  44 + <n-color-picker
  45 + size="small"
  46 + :modes="['hex']"
  47 + v-model:value="seriesList[0].itemStyle.borderColor"
  48 + ></n-color-picker>
  49 + </SettingItem>
  50 + <SettingItem name="边框大小">
  51 + <n-input-number
  52 + v-model:value="seriesList[0].itemStyle.borderWidth"
  53 + :min="0"
  54 + size="small"
  55 + placeholder="请输入"
  56 + ></n-input-number>
  57 + </SettingItem>
  58 + <SettingItem name="厚度">
  59 + <n-input-number
  60 + v-model:value="seriesList[0].regionHeight"
  61 + :min="0"
  62 + size="small"
  63 + placeholder="请输入"
  64 + ></n-input-number>
  65 + </SettingItem>
  66 + </SettingItemBox>
  67 + <SettingItemBox name="标题">
  68 + <SettingItem name="是否显示">
  69 + <n-switch v-model:value="seriesList[0].label.show" size="small"></n-switch>
  70 + </SettingItem>
  71 + <SettingItem name="颜色">
  72 + <n-color-picker size="small" :modes="['hex']" v-model:value="seriesList[0].label.color"></n-color-picker>
  73 + </SettingItem>
  74 + <SettingItem name="大小">
  75 + <n-input-number
  76 + v-model:value="seriesList[0].label.fontSize"
  77 + :min="0"
  78 + size="small"
  79 + placeholder="请输入"
  80 + ></n-input-number>
  81 + </SettingItem>
  82 + </SettingItemBox>
  83 + <SettingItemBox name="高亮">
  84 + <SettingItem name="标题显示">
  85 + <n-switch v-model:value="optionData.geo3D.emphasis.label.show" size="small"></n-switch>
  86 + </SettingItem>
  87 + <SettingItem name="颜色">
  88 + <n-color-picker
  89 + size="small"
  90 + :modes="['hex']"
  91 + v-model:value="optionData.geo3D.emphasis.label.textStyle.color"
  92 + ></n-color-picker>
  93 + </SettingItem>
  94 + <SettingItem name="大小">
  95 + <n-input-number
  96 + v-model:value="optionData.geo3D.emphasis.label.textStyle.fontSize"
  97 + :min="0"
  98 + size="small"
  99 + placeholder="请输入"
  100 + ></n-input-number>
  101 + </SettingItem>
  102 + <SettingItem name="区块颜色">
  103 + <n-color-picker
  104 + size="small"
  105 + :modes="['hex']"
  106 + v-model:value="optionData.geo3D.emphasis.itemStyle.color"
  107 + ></n-color-picker>
  108 + </SettingItem>
  109 + </SettingItemBox>
  110 + <SettingItemBox name="标记">
  111 + <SettingItem name="大小">
  112 + <n-input-number
  113 + v-model:value="seriesList[1].symbolSize"
  114 + :min="0"
  115 + :step="10"
  116 + size="small"
  117 + placeholder="请输入"
  118 + ></n-input-number>
  119 + </SettingItem>
  120 + <SettingItem name="形状">
  121 + <n-select :options="symbolOption" v-model:value="seriesList[1].symbol"></n-select>
  122 + </SettingItem>
  123 + </SettingItemBox>
  124 + </CollapseItem>
  125 +</template>
  126 +
  127 +<script setup lang="ts">
  128 +import { PropType, computed, ref } from 'vue'
  129 +import { CollapseItem, SettingItemBox, SettingItem } from '@/components/Pages/ChartItemSetting'
  130 +import { GlobalThemeJsonType } from '@/settings/chartThemes/index'
  131 +import { GlobalSetting } from '@/components/Pages/ChartItemSetting'
  132 +import SelectCity from './components/SelectCity.vue'
  133 +
  134 +const props = defineProps({
  135 + optionData: {
  136 + type: Object as PropType<GlobalThemeJsonType>,
  137 + required: true
  138 + }
  139 +})
  140 +
  141 +const seriesList = computed(() => {
  142 + return props.optionData.series
  143 +})
  144 +
  145 +const symbolOption = ref([
  146 + {
  147 + label: 'circle',
  148 + value: 'circle'
  149 + },
  150 + {
  151 + label: 'rect',
  152 + value: 'rect'
  153 + },
  154 + {
  155 + label: 'roundRect',
  156 + value: 'roundRect'
  157 + },
  158 + {
  159 + label: 'triangle',
  160 + value: 'triangle'
  161 + },
  162 + {
  163 + label: 'diamond',
  164 + value: 'diamond'
  165 + },
  166 + {
  167 + label: 'pin',
  168 + value: 'pin'
  169 + },
  170 + {
  171 + label: 'arrow',
  172 + value: 'arrow'
  173 + },
  174 + {
  175 + label: 'none',
  176 + value: 'none'
  177 + }
  178 +])
  179 +
  180 +const onHandleSelectValues = (values: any) => {
  181 + const { cityValue, countyValue, provinceValue } = values
  182 + props.optionData.mapRegion.saveSelect = values
  183 + props.optionData.mapRegion.adcode = countyValue
  184 + ? countyValue
  185 + : cityValue
  186 + ? cityValue
  187 + : provinceValue === 'china'
  188 + ? 'china'
  189 + : provinceValue
  190 +}
  191 +</script>
  1 +[
  2 + {
  3 + "name": "四川省",
  4 + "value": [104.10068024609373, 30.580525329665175, 20000],
  5 + "height": 5,
  6 + "itemStyle": {
  7 + "color": "pink",
  8 + "opacity": 1,
  9 + "borderWidth": 0.4,
  10 + "borderColor": "#5F9EA0"
  11 + }
  12 + },
  13 + {
  14 + "name": "湖南省",
  15 + "value": [111.73068512890623, 27.86754509366569, 20000],
  16 + "height": 4,
  17 + "itemStyle": {
  18 + "color": "blue",
  19 + "opacity": 1,
  20 + "borderWidth": 0.4,
  21 + "borderColor": "#5F9EA0"
  22 + }
  23 + },
  24 + {
  25 + "name": "吉林省",
  26 + "value": [126.45236481640623, 43.7943407914815, 20000],
  27 + "height": 5,
  28 + "itemStyle": {
  29 + "color": "yellow",
  30 + "opacity": 1,
  31 + "borderWidth": 0.4,
  32 + "borderColor": "#5F9EA0"
  33 + }
  34 + },
  35 + {
  36 + "name": "山东省",
  37 + "value": [118.67404450390623, 36.16387465872037, 20000],
  38 + "height": 6,
  39 + "itemStyle": {
  40 + "color": "orange",
  41 + "opacity": 1,
  42 + "borderWidth": 0.4,
  43 + "borderColor": "#5F9EA0"
  44 + }
  45 + }
  46 +]
  1 +import { ConfigType, PackagesCategoryEnum, ChartFrameEnum } from '@/packages/index.d'
  2 +import { ChatCategoryEnum, ChatCategoryEnumName } from '@/packages/components/Charts/index.d'
  3 +import { useWidgetKey } from '@/packages/external/useWidgetKey'
  4 +
  5 +const { key, conKey, chartKey } = useWidgetKey('AddThreeDimensionalMap', true)
  6 +
  7 +export const AddThreeDimensionalMapConfig: ConfigType = {
  8 + key,
  9 + chartKey,
  10 + conKey,
  11 + title: '3d地图',
  12 + category: ChatCategoryEnum.MAP,
  13 + categoryName: ChatCategoryEnumName.MAP,
  14 + package: PackagesCategoryEnum.CHARTS,
  15 + chartFrame: ChartFrameEnum.COMMON,
  16 + image: 'map3D.png'
  17 +}
  1 +<template>
  2 + <!-- 原生方式,没有使用vue-echarts -->
  3 + <div :style="`width:${w}px;height:${h}px;`" ref="map3DRef"></div>
  4 +</template>
  5 +
  6 +<script setup lang="ts">
  7 +import { onMounted, ref, nextTick, PropType, toRefs, watch } from 'vue'
  8 +import * as echarts from 'echarts'
  9 +import { registerMap } from 'echarts/core'
  10 +import 'echarts-gl'
  11 +import config from './config'
  12 +import cityMap from '../OverrideMapBase/mapGeojson/china-main-city-map.json'
  13 +
  14 +type historyDataType = { name: string; code: string }
  15 +
  16 +const props = defineProps({
  17 + chartConfig: {
  18 + type: Object as PropType<config>,
  19 + required: true
  20 + }
  21 +})
  22 +
  23 +const iconStr = ref(
  24 + 'path://M853.333333 245.333333H245.333333l93.866667-93.866666c12.8-12.8 12.8-34.133333 0-46.933334-12.8-12.8-34.133333-12.8-46.933333 0l-145.066667 145.066667c-12.8 12.8-12.8 34.133333 0 46.933333l145.066667 145.066667c6.4 6.4 14.933333 10.666667 23.466666 10.666667s17.066667-4.266667 23.466667-10.666667c12.8-12.8 12.8-34.133333 0-46.933333L256 311.466667h597.333333c6.4 0 10.666667 4.266667 10.666667 10.666666v426.666667c0 6.4-4.266667 10.666667-10.666667 10.666667H170.666667c-17.066667 0-32 14.933333-32 32s14.933333 32 32 32h682.666666c40.533333 0 74.666667-34.133333 74.666667-74.666667V320c0-40.533333-34.133333-74.666667-74.666667-74.666667z'
  25 +)
  26 +
  27 +const historyData = ref<historyDataType[]>([])
  28 +
  29 +const { w, h } = toRefs(props.chartConfig.attr)
  30 +
  31 +const map3DRef = ref()
  32 +
  33 +const chartInstance = ref()
  34 +
  35 +const saveSelectValue = ref('')
  36 +
  37 +const toolBoxOption = ref({
  38 + show: true,
  39 + right: 110,
  40 + top: 20,
  41 + feature: {
  42 + myFullButton: {
  43 + show: true,
  44 + title: '返回',
  45 + icon: iconStr.value,
  46 + iconStyle: {
  47 + color: ''
  48 + },
  49 + onclick: () => watchAdcode()
  50 + }
  51 + }
  52 +})
  53 +
  54 +watch(
  55 + () => props.chartConfig.option,
  56 + newData => {
  57 + const { iconColor, drillingIn, iconDistanceRight, iconDistanceTop } = newData
  58 + toolBoxOption.value.feature.myFullButton.show = drillingIn
  59 + toolBoxOption.value.feature.myFullButton.iconStyle.color = iconColor
  60 + toolBoxOption.value.right = iconDistanceRight
  61 + toolBoxOption.value.top = iconDistanceTop
  62 + },
  63 + {
  64 + deep: true
  65 + }
  66 +)
  67 +
  68 +props.chartConfig.option = {
  69 + ...props.chartConfig.option,
  70 + ...{ toolbox: toolBoxOption.value }
  71 +}
  72 +
  73 +//地图点击返回
  74 +const watchAdcode = () => {
  75 + if (props.chartConfig.option.drillingIn) {
  76 + const code = historyData.value.at(-2)?.code
  77 + props.chartConfig.option.mapRegion.adcode = code ? code : 'china'
  78 + historyData.value.pop()
  79 + }
  80 +}
  81 +
  82 +//地图点击
  83 +const handleMap3DClick = async (params: any) => {
  84 + if (props.chartConfig.option.drillingIn) {
  85 + const { name } = params
  86 + saveSelectValue.value = name
  87 + const findAdcode = (cityMap as any)[name]
  88 + if (!findAdcode) return
  89 + props.chartConfig.option.mapRegion.adcode = findAdcode
  90 + historyData.value.push({
  91 + name,
  92 + code: findAdcode
  93 + })
  94 + }
  95 +}
  96 +
  97 +//动态获取geojson文件进行注册地图
  98 +const getGeojson = (regionId: string) => {
  99 + try {
  100 + return new Promise<boolean>(resolve => {
  101 + import(`../OverrideMapBase/mapGeojson/${regionId}.json`).then(data => {
  102 + registerMap(regionId, { geoJSON: data.default as any, specialAreas: {} })
  103 + resolve(true)
  104 + })
  105 + })
  106 + } catch (e) {
  107 + console.log(e)
  108 + }
  109 +}
  110 +
  111 +watch(
  112 + () => w.value,
  113 + (value: number) => {
  114 + chartInstance.value.resize({
  115 + width: value + 'px',
  116 + height: h.value + 'px'
  117 + })
  118 + }
  119 +)
  120 +
  121 +const initMap = async () => {
  122 + chartInstance.value = echarts.init(map3DRef.value)
  123 + await nextTick()
  124 + await getGeojson(props.chartConfig.option.mapRegion.adcode)
  125 + await nextTick().then(() => {
  126 + handleSetOption(chartInstance.value, props.chartConfig.option)
  127 + })
  128 + chartInstance.value.on('click', (e: any) => {
  129 + console.log(`地图点击`, e)
  130 + // handleMap3DClick(e)
  131 + })
  132 +}
  133 +
  134 +// 手动触发渲染
  135 +const handleSetOption = (instance: any, option: any) => {
  136 + if (!instance) return
  137 + try {
  138 + instance.clear()
  139 + instance.setOption(option)
  140 + // eslint-disable-next-line no-empty
  141 + } finally {
  142 + }
  143 +}
  144 +
  145 +onMounted(() => {
  146 + initMap()
  147 +})
  148 +
  149 +//监听地图展示区域发生变化
  150 +watch(
  151 + () => `${props.chartConfig.option.mapRegion.adcode}`,
  152 + async (newData: any) => {
  153 + try {
  154 + await getGeojson(newData)
  155 + props.chartConfig.option.geo3D.map = newData
  156 + props.chartConfig.option.series.forEach((item: any) => {
  157 + if (item.type === 'map3D') {
  158 + item.map = newData
  159 + item.data = props.chartConfig.option.dataset
  160 + }
  161 + })
  162 + handleSetOption(chartInstance.value, props.chartConfig.option)
  163 + } catch (error) {
  164 + console.log(error)
  165 + }
  166 + },
  167 + {
  168 + immediate: true
  169 + }
  170 +)
  171 +
  172 +// 监听地图右侧配置项变化
  173 +watch(
  174 + props.chartConfig.option,
  175 + async newData => {
  176 + try {
  177 + handleSetOption(chartInstance.value, newData)
  178 + } catch (error) {
  179 + console.log(error)
  180 + }
  181 + },
  182 + {
  183 + deep: true
  184 + }
  185 +)
  186 +
  187 +// 监听地图dataset配置项变化
  188 +watch(
  189 + () => props.chartConfig.option.dataset,
  190 + newData => {
  191 + try {
  192 + props.chartConfig.option.series.forEach((item: any) => {
  193 + if (item.type === 'map3D') {
  194 + item.data = newData
  195 + }
  196 + })
  197 + handleSetOption(chartInstance.value, props.chartConfig.option)
  198 + } catch (error) {
  199 + console.log(error)
  200 + }
  201 + },
  202 + {
  203 + deep: true
  204 + }
  205 +)
  206 +</script>
  1 +<template>
  2 + <n-drawer display-directive="if" :show="modelShow" :width="502" :placement="placement">
  3 + <n-drawer-content title="设备筛选">
  4 + <n-space vertical>
  5 + <span>组织</span>
  6 + <n-tree-select
  7 + placement="top-start"
  8 + label-field="name"
  9 + v-model:value="searchParams.organizationId"
  10 + key-field="id"
  11 + children-field="children"
  12 + :options="originationOption"
  13 + />
  14 + <span>产品</span>
  15 + <n-select v-model:value="searchParams.deviceProfileIds" :options="deviceProfileOption" />
  16 + <span>设备</span>
  17 + <n-input v-model:value="searchParams.name" type="text" placeholder="请输入设备名称" />
  18 + <span>设备状态</span>
  19 + <n-radio-group v-model:value="searchParams.deviceState" name="radiogroup">
  20 + <n-space>
  21 + <n-radio v-for="(item, index) in deviceStateGroup" :key="index" :value="item.value">
  22 + {{ item.label }}
  23 + </n-radio>
  24 + </n-space>
  25 + </n-radio-group>
  26 + <span>是否告警</span>
  27 + <n-radio-group v-model:value="searchParams.alarmStatus" name="radiogroup">
  28 + <n-space>
  29 + <n-radio v-for="(item, index) in alarmStatusGroup" :key="index" :value="item.value">
  30 + {{ item.label }}
  31 + </n-radio>
  32 + </n-space>
  33 + </n-radio-group>
  34 + <span>配置查询分页</span>
  35 + <n-space justify="space-between">
  36 + <n-input-number :min="1" v-model:value="searchPage.page" clearable />
  37 + <n-input-number :step="10" :min="10" v-model:value="searchPage.pageSize" clearable />
  38 + </n-space>
  39 + </n-space>
  40 + <template #footer>
  41 + <n-button @click="handleCancel" type="tertiary">取消</n-button>
  42 + <div style="visibility: hidden; width: 10px">占位</div>
  43 + <n-button @click="handleSubmit" type="success">确定</n-button>
  44 + </template>
  45 + </n-drawer-content>
  46 + </n-drawer>
  47 +</template>
  48 +
  49 +<script lang="ts" setup>
  50 +import { ref, onMounted, reactive } from 'vue'
  51 +import { NTreeSelect } from 'naive-ui'
  52 +import { getOrganizationList, getProfileList } from '@/api/external/common/index'
  53 +
  54 +interface searchParamsInterface {
  55 + organizationId: string | null
  56 + deviceProfileIds: null
  57 + name: string
  58 + deviceState: string | null
  59 + alarmStatus: number
  60 +}
  61 +
  62 +defineProps({
  63 + modelShow: Boolean
  64 +})
  65 +
  66 +const emit = defineEmits(['searchParams', 'closeDrawer'])
  67 +
  68 +const placement = ref('right')
  69 +
  70 +//设备状态
  71 +const deviceStateGroup = [
  72 + {
  73 + label: '全部',
  74 + value: null
  75 + },
  76 + {
  77 + label: '待激活',
  78 + value: 'INACTIVE'
  79 + },
  80 + {
  81 + label: '在线',
  82 + value: 'ONLINE'
  83 + },
  84 + {
  85 + label: '离线',
  86 + value: 'OFFLINE'
  87 + }
  88 +]
  89 +
  90 +//告警状态
  91 +const alarmStatusGroup = [
  92 + {
  93 + label: '是',
  94 + value: 1
  95 + },
  96 + {
  97 + label: '否',
  98 + value: 0
  99 + }
  100 +]
  101 +
  102 +const searchPage = reactive<{ page: number; pageSize: number }>({
  103 + page: 1,
  104 + pageSize: 10
  105 +})
  106 +
  107 +const searchParams = reactive<searchParamsInterface>({
  108 + organizationId: null,
  109 + deviceProfileIds: null,
  110 + name: '',
  111 + deviceState: null,
  112 + alarmStatus: 0
  113 +})
  114 +
  115 +const originationOption = ref([])
  116 +
  117 +const deviceProfileOption = ref([])
  118 +
  119 +const loadList = async () => {
  120 + const resOrganization = await getOrganizationList()
  121 + const resProfileList = await getProfileList()
  122 + Promise.all([resOrganization, resProfileList]).then(res => {
  123 + originationOption.value = res[0]
  124 + deviceProfileOption.value = res[1].map((item: { name: string; tbProfileId: string }) => ({
  125 + label: item.name,
  126 + value: item.tbProfileId
  127 + }))
  128 + })
  129 +}
  130 +
  131 +const handleSubmit = () => {
  132 + searchParams.deviceProfileIds = [searchParams.deviceProfileIds] as any
  133 + emit('searchParams', searchPage, searchParams)
  134 + handleCancel()
  135 +}
  136 +
  137 +const handleCancel = () => {
  138 + for (let i in searchParams) Reflect.set(searchParams, i, null)
  139 + searchParams.name = ''
  140 + searchParams.alarmStatus = 0
  141 + searchPage.page = 1
  142 + searchPage.pageSize = 10
  143 + emit('closeDrawer')
  144 +}
  145 +
  146 +onMounted(() => {
  147 + loadList()
  148 +})
  149 +</script>
  150 +
  151 +<style lang="scss" scoped></style>
@@ -11,6 +11,30 @@ export type dataJsonType = typeof dataJson //data.json类型 @@ -11,6 +11,30 @@ export type dataJsonType = typeof dataJson //data.json类型
11 11
12 export type dataJsonMarkersType = typeof dataJson.markers[number] //data.json markers类型 12 export type dataJsonMarkersType = typeof dataJson.markers[number] //data.json markers类型
13 13
  14 +//标注数据格式
  15 +export const fileterDevice = (items: any) => {
  16 + const values = items.reduce((acc: any, curr: any) => {
  17 + acc.push({
  18 + name: curr.alias,
  19 + value: 10,
  20 + position: [curr.deviceInfo.longitude, curr.deviceInfo.latitude],
  21 + extraInfo: {
  22 + tbDeviceId: curr.tbDeviceId,
  23 + name: curr.name,
  24 + alias: curr.alias,
  25 + organizationDTO: curr.organizationDTO,
  26 + deviceState: curr.deviceState,
  27 + deviceProfile: curr.deviceProfile,
  28 + deviceInfo: curr.deviceInfo
  29 + }
  30 + })
  31 + return [...acc]
  32 + }, [])
  33 + return {
  34 + markers: values
  35 + }
  36 +}
  37 +
14 export enum ThemeEnum { 38 export enum ThemeEnum {
15 NORMAL = 'normal', 39 NORMAL = 'normal',
16 DARK = 'dark', 40 DARK = 'dark',
1 <template> 1 <template>
2 - <div ref="vChartRef"></div> 2 + <div @mouseenter="handleMouseenter" @mouseleave="handleMouseleave" class="chart-amap" ref="vChartRef">
  3 + <div v-if="showSearchBox" @click.stop="handleOpenSearchBox" class="search-box"></div>
  4 + <search-box :modelShow="modelShow" @searchParams="handleSearchParams" @closeDrawer="handleCloseDrawer"></search-box>
  5 + </div>
3 </template> 6 </template>
4 7
5 <script setup lang="ts"> 8 <script setup lang="ts">
@@ -8,13 +11,14 @@ import AMapLoader from '@amap/amap-jsapi-loader' @@ -8,13 +11,14 @@ import AMapLoader from '@amap/amap-jsapi-loader'
8 import { CreateComponentType } from '@/packages/index.d' 11 import { CreateComponentType } from '@/packages/index.d'
9 import { useChartDataFetch } from '@/hooks' 12 import { useChartDataFetch } from '@/hooks'
10 import { useChartEditStore } from '@/store/modules/chartEditStore/chartEditStore' 13 import { useChartEditStore } from '@/store/modules/chartEditStore/chartEditStore'
11 -import { MarkerEnum, ThemeEnum, dataExtraInfoType, dataJsonType, dataJsonMarkersType } from './config' 14 +import { MarkerEnum, ThemeEnum, dataExtraInfoType, dataJsonType, dataJsonMarkersType, fileterDevice } from './config'
12 import { isArray } from '@/utils' 15 import { isArray } from '@/utils'
13 import djh from './images/djh.png' 16 import djh from './images/djh.png'
14 import online from './images/online.png' 17 import online from './images/online.png'
15 import lx1 from './images/lx1.png' 18 import lx1 from './images/lx1.png'
16 -import { getDeviceActiveTime } from '@/api/external/common/index' 19 +import { getDeviceActiveTime, getDeviceList } from '@/api/external/common/index'
17 import dayjs from 'dayjs' 20 import dayjs from 'dayjs'
  21 +import SearchBox from './components/SearchBox.vue'
18 22
19 const props = defineProps({ 23 const props = defineProps({
20 chartConfig: { 24 chartConfig: {
@@ -23,6 +27,10 @@ const props = defineProps({ @@ -23,6 +27,10 @@ const props = defineProps({
23 } 27 }
24 }) 28 })
25 29
  30 +const modelShow = ref(false)
  31 +
  32 +const showSearchBox = ref(false)
  33 +
26 let { 34 let {
27 amapKey, 35 amapKey,
28 amapStyleKey, 36 amapStyleKey,
@@ -126,6 +134,25 @@ const createInfoWindow = async (extraInfo: dataExtraInfoType) => { @@ -126,6 +134,25 @@ const createInfoWindow = async (extraInfo: dataExtraInfoType) => {
126 } 134 }
127 } 135 }
128 136
  137 +const handleMouseenter = () => (showSearchBox.value = true)
  138 +
  139 +const handleMouseleave = () => (showSearchBox.value = false)
  140 +
  141 +const handleOpenSearchBox = () => (modelShow.value = true)
  142 +
  143 +const handleCloseDrawer = () => (modelShow.value = false)
  144 +
  145 +const handleSearchParams = async (searchPage: any, params: any) => {
  146 + try {
  147 + const { items } = await getDeviceList(searchPage, params)
  148 + const values = fileterDevice(items)
  149 + if (!values) return
  150 + dataHandle(values)
  151 + } finally {
  152 + handleCloseDrawer()
  153 + }
  154 +}
  155 +
129 //地图点击 156 //地图点击
130 const mapClick = (markerInstance: any, markerItem: dataJsonMarkersType) => { 157 const mapClick = (markerInstance: any, markerItem: dataJsonMarkersType) => {
131 markerInstance.setExtData({ 158 markerInstance.setExtData({
@@ -178,7 +205,7 @@ const dataHandle = (newData: dataJsonType) => { @@ -178,7 +205,7 @@ const dataHandle = (newData: dataJsonType) => {
178 // markers.push(markerInstance) //原作者这种方式添加,属于JS API 1.4.8版本的 205 // markers.push(markerInstance) //原作者这种方式添加,属于JS API 1.4.8版本的
179 // markerInstance.setMap(mapIns) 206 // markerInstance.setMap(mapIns)
180 mapIns.add(markerInstance) 207 mapIns.add(markerInstance)
181 - mapClick(markerInstance, markerItem) 208 + mapClick(markerInstance, markerItem) // circle点击无效
182 }) 209 })
183 } 210 }
184 } 211 }
@@ -215,3 +242,20 @@ useChartDataFetch(props.chartConfig, useChartEditStore, (newData: any) => { @@ -215,3 +242,20 @@ useChartDataFetch(props.chartConfig, useChartEditStore, (newData: any) => {
215 dataHandle(newData) 242 dataHandle(newData)
216 }) 243 })
217 </script> 244 </script>
  245 +
  246 +<style lang="scss" scoped>
  247 +.chart-amap {
  248 + position: relative;
  249 + .search-box {
  250 + cursor: pointer;
  251 + position: absolute;
  252 + right: -25px;
  253 + top: 15px;
  254 + z-index: 9999;
  255 + width: 50px;
  256 + height: 50px;
  257 + border-radius: 50%;
  258 + background-color: rgba(255, 255, 255, 0.2);
  259 + }
  260 +}
  261 +</style>