Commit ba4c57471aa327ccc19761780826f4883ed1362b
Merge branch 'ft' into 'main_dev'
feat(src/packages): 图表里的地图 新增3d地图,支持凸起和标点 See merge request yunteng/thingskit-view!116
Showing
12 changed files
with
903 additions
and
6 deletions
Too many changes to show.
To preserve performance only 12 of 21 files are displayed.
| ... | ... | @@ -10,7 +10,10 @@ enum Api { |
| 10 | 10 | CONFIGURATION = '/configuration/center', |
| 11 | 11 | CONFIGURATION_SHARE = '/configuration/center/share/', |
| 12 | 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 | 19 | export const getDictItemByCode = (value: string) => { |
| ... | ... | @@ -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 | 84 | defHttp.get({ |
| 82 | 85 | url: Api.VIDEO, |
| 83 | 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 | + }) | ... | ... |
src/assets/images/chart/charts/map3D.png
0 → 100644
100 KB
src/packages/components/external/Charts/Maps/AddThreeDimensionalMap/components/SelectCity.vue
0 → 100644
| 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 | 11 | |
| 12 | 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 | 38 | export enum ThemeEnum { |
| 15 | 39 | NORMAL = 'normal', |
| 16 | 40 | DARK = 'dark', | ... | ... |
| 1 | 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 | 6 | </template> |
| 4 | 7 | |
| 5 | 8 | <script setup lang="ts"> |
| ... | ... | @@ -8,13 +11,14 @@ import AMapLoader from '@amap/amap-jsapi-loader' |
| 8 | 11 | import { CreateComponentType } from '@/packages/index.d' |
| 9 | 12 | import { useChartDataFetch } from '@/hooks' |
| 10 | 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 | 15 | import { isArray } from '@/utils' |
| 13 | 16 | import djh from './images/djh.png' |
| 14 | 17 | import online from './images/online.png' |
| 15 | 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 | 20 | import dayjs from 'dayjs' |
| 21 | +import SearchBox from './components/SearchBox.vue' | |
| 18 | 22 | |
| 19 | 23 | const props = defineProps({ |
| 20 | 24 | chartConfig: { |
| ... | ... | @@ -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 | 34 | let { |
| 27 | 35 | amapKey, |
| 28 | 36 | amapStyleKey, |
| ... | ... | @@ -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 | 157 | const mapClick = (markerInstance: any, markerItem: dataJsonMarkersType) => { |
| 131 | 158 | markerInstance.setExtData({ |
| ... | ... | @@ -178,7 +205,7 @@ const dataHandle = (newData: dataJsonType) => { |
| 178 | 205 | // markers.push(markerInstance) //原作者这种方式添加,属于JS API 1.4.8版本的 |
| 179 | 206 | // markerInstance.setMap(mapIns) |
| 180 | 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 | 242 | dataHandle(newData) |
| 216 | 243 | }) |
| 217 | 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> | ... | ... |