diff --git a/src/packages/components/Charts/Maps/MapBase/config.ts b/src/packages/components/Charts/Maps/MapBase/config.ts index dd020e50..d9844001 100644 --- a/src/packages/components/Charts/Maps/MapBase/config.ts +++ b/src/packages/components/Charts/Maps/MapBase/config.ts @@ -11,7 +11,10 @@ export const option = { dataset: dataJson, mapRegion: { adcode: 'china', - showHainanIsLands: true + showHainanIsLands: true, + enter: false, + backSize: 20, + backColor: '#ffffff' }, tooltip: { show: true, @@ -103,19 +106,19 @@ export const option = { borderColor: 'rgba(147, 235, 248, 0.8)', textStyle: { color: '#FFFFFF', - fontSize: 12, + fontSize: 12 } }, label: { show: false, color: '#FFFFFF', - fontSize: 12, + fontSize: 12 }, emphasis: { disabled: false, label: { color: '#FFFFFF', - fontSize: 12, + fontSize: 12 }, itemStyle: { areaColor: '#389BB7', @@ -148,6 +151,26 @@ export const option = { shadowOffsetY: 2, shadowBlur: 10 } + }, + { + type: 'lines', + zlevel: 2, + effect: { + show: true, + period: 4, //箭头指向速度,值越小速度越快 + trailLength: 0.4, //特效尾迹长度[0,1]值越大,尾迹越长重 + symbol: 'arrow', //箭头图标 + symbolSize: 7 //图标大小 + }, + lineStyle: { + normal: { + color: '#4fb6d2', + width: 1, //线条宽度 + opacity: 0.1, //尾迹线条透明度 + curveness: 0.3 //尾迹线条曲直度 + } + }, + data: [] } ] } diff --git a/src/packages/components/Charts/Maps/MapBase/config.vue b/src/packages/components/Charts/Maps/MapBase/config.vue index b3ef4961..e0363d8f 100644 --- a/src/packages/components/Charts/Maps/MapBase/config.vue +++ b/src/packages/components/Charts/Maps/MapBase/config.vue @@ -69,11 +69,7 @@ </n-space> </SettingItem> <SettingItem name="字体颜色"> - <n-color-picker - size="small" - :modes="['hex']" - v-model:value="seriesList[1].label.color" - ></n-color-picker> + <n-color-picker size="small" :modes="['hex']" v-model:value="seriesList[1].label.color"></n-color-picker> </SettingItem> <SettingItem name="字体大小"> <n-input-number @@ -129,7 +125,7 @@ ></n-color-picker> </SettingItem> </SettingItemBox> - + <SettingItemBox name="悬浮弹窗"> <SettingItem name="显示"> <n-space> @@ -180,6 +176,22 @@ <SettingItem> <n-checkbox v-model:checked="mapRegion.showHainanIsLands" size="small">显示南海群岛</n-checkbox> </SettingItem> + <SettingItem v-if="seriesList[2]"> + <n-checkbox v-model:checked="mapRegion.enter" size="small">点击进入下级</n-checkbox> + </SettingItem> + </SettingItemBox> + <SettingItemBox name="返回图标" v-if="mapRegion.enter"> + <SettingItem name="颜色"> + <n-color-picker size="small" :modes="['hex']" v-model:value="mapRegion.backColor"></n-color-picker> + </SettingItem> + <SettingItem name="大小"> + <n-input-number + v-model:value="mapRegion.backSize" + :min="1" + size="small" + placeholder="请输入字体大小" + ></n-input-number> + </SettingItem> </SettingItemBox> </CollapseItem> <CollapseItem name="标记" :expanded="true"> @@ -191,7 +203,7 @@ <n-color-picker size="small" :modes="['hex']" v-model:value="seriesList[0].itemStyle.color"></n-color-picker> </SettingItem> </SettingItemBox> - + <SettingItemBox name="文本"> <SettingItem name="显示"> <n-space> @@ -223,6 +235,47 @@ </SettingItem> </SettingItemBox> </CollapseItem> + + <CollapseItem v-if="seriesList[2]" name="飞线" :expanded="true"> + <SettingItemBox name="箭头"> + <SettingItem name="速度"> + <n-tooltip trigger="hover"> + <template #trigger> + <n-input-number v-model:value="seriesList[2].effect.period" size="small" :min="0"></n-input-number> + </template> + 值越小速度越快 + </n-tooltip> + </SettingItem> + <SettingItem name="尾迹"> + <n-tooltip trigger="hover"> + <template #trigger> + <n-input-number + v-model:value="seriesList[2].effect.trailLength" + size="small" + :min="0" + :max="1" + ></n-input-number> + </template> + 特效尾迹长度[0,1]值越大,尾迹越长重 + </n-tooltip> + </SettingItem> + <SettingItem name="大小"> + <n-input-number v-model:value="seriesList[2].effect.symbolSize" size="small" :min="0"></n-input-number> + </SettingItem> + </SettingItemBox> + <SettingItemBox name="配置"> + <SettingItem name="颜色"> + <n-color-picker + size="small" + :modes="['hex']" + v-model:value="seriesList[2].lineStyle.normal.color" + ></n-color-picker> + </SettingItem> + <SettingItem name="宽度"> + <n-input-number v-model:value="seriesList[2].lineStyle.normal.width" size="small" :min="1"></n-input-number> + </SettingItem> + </SettingItemBox> + </CollapseItem> </template> <script setup lang="ts"> diff --git a/src/packages/components/Charts/Maps/MapBase/data.json b/src/packages/components/Charts/Maps/MapBase/data.json index 3deec4f2..58d86ddd 100644 --- a/src/packages/components/Charts/Maps/MapBase/data.json +++ b/src/packages/components/Charts/Maps/MapBase/data.json @@ -21,6 +21,32 @@ "value": [126.642464, 45.756967, 101] } ], + "line": [ + { + "coords": [ + [113.665412, 34.757975], + [116.405285, 39.904989] + ] + }, + { + "coords": [ + [101.778916, 36.623178], + [116.405285, 39.904989] + ] + }, + { + "coords": [ + [106.278179, 38.46637], + [116.405285, 39.904989] + ] + }, + { + "coords": [ + [126.642464, 45.756967], + [116.405285, 39.904989] + ] + } + ], "map": [ { "name": "北京市", diff --git a/src/packages/components/Charts/Maps/MapBase/index.vue b/src/packages/components/Charts/Maps/MapBase/index.vue index a6300819..95feda3c 100644 --- a/src/packages/components/Charts/Maps/MapBase/index.vue +++ b/src/packages/components/Charts/Maps/MapBase/index.vue @@ -1,156 +1,256 @@ -<template> - <v-chart ref="vChartRef" :init-options="initOptions" :theme="themeColor" :option="option.value" :manual-update="isPreview()" autoresize> - </v-chart> -</template> - -<script setup lang="ts"> -import { PropType, reactive, watch, ref, nextTick } from 'vue' -import config, { includes } from './config' -import VChart from 'vue-echarts' -import { useCanvasInitOptions } from '@/hooks/useCanvasInitOptions.hook' -import { use, registerMap } from 'echarts/core' -import { EffectScatterChart, MapChart } from 'echarts/charts' -import { CanvasRenderer } from 'echarts/renderers' -import { useChartDataFetch } from '@/hooks' -import { mergeTheme, setOption } from '@/packages/public/chart' -import { useChartEditStore } from '@/store/modules/chartEditStore/chartEditStore' -import { isPreview } from '@/utils' -import mapJsonWithoutHainanIsLands from './mapWithoutHainanIsLands.json' -import { DatasetComponent, GridComponent, TooltipComponent, GeoComponent, VisualMapComponent } from 'echarts/components' - -const props = defineProps({ - themeSetting: { - type: Object, - required: true - }, - themeColor: { - type: Object, - required: true - }, - chartConfig: { - type: Object as PropType<config>, - required: true - } -}) - -const initOptions = useCanvasInitOptions(props.chartConfig.option, props.themeSetting) - -use([ - MapChart, - DatasetComponent, - CanvasRenderer, - GridComponent, - TooltipComponent, - GeoComponent, - EffectScatterChart, - VisualMapComponent -]) - -const option = reactive({ - value: mergeTheme(props.chartConfig.option, props.themeSetting, includes) -}) -const vChartRef = ref<typeof VChart>() - -//动态获取json注册地图 -const getGeojson = (regionId: string) => { - return new Promise<boolean>(resolve => { - import(`./mapGeojson/${regionId}.json`).then(data => { - registerMap(regionId, { geoJSON: data.default as any, specialAreas: {} }) - resolve(true) - }) - }) -} - -//异步时先注册空的 保证初始化不报错 -registerMap(`${props.chartConfig.option.mapRegion.adcode}`, { geoJSON: {} as any, specialAreas: {} }) - -// 进行更换初始化地图 如果为china 单独处理 -const registerMapInitAsync = async () => { - await nextTick() - const adCode = `${props.chartConfig.option.mapRegion.adcode}`; - if (adCode !== 'china') { - await getGeojson(adCode) - } else { - await hainanLandsHandle(props.chartConfig.option.mapRegion.showHainanIsLands) - } - vEchartsSetOption() -} -registerMapInitAsync() - -// 手动触发渲染 -const vEchartsSetOption = () => { - option.value = props.chartConfig.option - setOption(vChartRef.value, props.chartConfig.option) -} - -// 更新数据处理 -const dataSetHandle = async (dataset: any) => { - props.chartConfig.option.series.forEach((item: any) => { - if (item.type === 'effectScatter' && dataset.point) item.data = dataset.point - else if (item.type === 'map' && dataset.map) item.data = dataset.map - }) - if (dataset.pieces) props.chartConfig.option.visualMap.pieces = dataset.pieces - - isPreview() && vEchartsSetOption() -} -// 处理海南群岛 -const hainanLandsHandle = async (newData: boolean) => { - if (newData) { - await getGeojson('china') - } else { - registerMap('china', { geoJSON: mapJsonWithoutHainanIsLands as any, specialAreas: {} }) - } -} -//监听 dataset 数据发生变化 -watch( - () => props.chartConfig.option.dataset, - newData => { - dataSetHandle(newData) - }, - { - immediate: true, - deep: false - } -) - -//监听是否显示南海群岛 -watch( - () => props.chartConfig.option.mapRegion.showHainanIsLands, - async newData => { - try { - await hainanLandsHandle(newData) - vEchartsSetOption() - } catch (error) { - console.log(error) - } - }, - { - deep: false - } -) - -//监听地图展示区域发生变化 -watch( - () => `${props.chartConfig.option.mapRegion.adcode}`, - async newData => { - try { - await getGeojson(newData) - props.chartConfig.option.geo.map = newData - props.chartConfig.option.series.forEach((item: any) => { - if (item.type === 'map') item.map = newData - }) - vEchartsSetOption() - } catch (error) { - console.log(error) - } - }, - { - deep: false - } -) - -// 预览 -useChartDataFetch(props.chartConfig, useChartEditStore, (newData: any) => { - dataSetHandle(newData) -}) -</script> +<template> + <div> + <div class="back-icon" v-if="(enter && levelHistory.length !== 0) || (enter && !isPreview())" @click="backLevel"> + <n-icon :color="backColor" :size="backSize * 1.1"> + <ArrowBackIcon /> + </n-icon> + <span + :style="{ + 'font-weight': 200, + color: backColor, + 'font-size': `${backSize}px` + }" + > + 返回上级 + </span> + </div> + <v-chart + ref="vChartRef" + :init-options="initOptions" + :theme="themeColor" + :option="option.value" + :manual-update="isPreview()" + autoresize + @click="chartPEvents" + > + </v-chart> + </div> +</template> + +<script setup lang="ts"> +import { PropType, reactive, watch, ref, nextTick, toRefs } from 'vue' +import config, { includes } from './config' +import VChart from 'vue-echarts' +import { icon } from '@/plugins' +import { useCanvasInitOptions } from '@/hooks/useCanvasInitOptions.hook' +import { use, registerMap } from 'echarts/core' +import { EffectScatterChart, MapChart } from 'echarts/charts' +import { CanvasRenderer } from 'echarts/renderers' +import { useChartDataFetch } from '@/hooks' +import { mergeTheme, setOption } from '@/packages/public/chart' +import { useChartEditStore } from '@/store/modules/chartEditStore/chartEditStore' +import { isPreview } from '@/utils' +import mapJsonWithoutHainanIsLands from './mapWithoutHainanIsLands.json' +import mapChinaJson from './mapGeojson/china.json' +import { DatasetComponent, GridComponent, TooltipComponent, GeoComponent, VisualMapComponent } from 'echarts/components' + +const props = defineProps({ + themeSetting: { + type: Object, + required: true + }, + themeColor: { + type: Object, + required: true + }, + chartConfig: { + type: Object as PropType<config>, + required: true + } +}) + +const { ArrowBackIcon } = icon.ionicons5 +let levelHistory: any = ref([]) + +const { backColor, backSize, enter } = toRefs(props.chartConfig.option.mapRegion) +const initOptions = useCanvasInitOptions(props.chartConfig.option, props.themeSetting) + +use([ + MapChart, + DatasetComponent, + CanvasRenderer, + GridComponent, + TooltipComponent, + GeoComponent, + EffectScatterChart, + VisualMapComponent +]) + +const option = reactive({ + value: mergeTheme(props.chartConfig.option, props.themeSetting, includes) +}) +const vChartRef = ref<typeof VChart>() + +//动态获取json注册地图 +const getGeojson = (regionId: string) => { + return new Promise<boolean>(resolve => { + import(`./mapGeojson/${regionId}.json`).then(data => { + registerMap(regionId, { geoJSON: data.default as any, specialAreas: {} }) + resolve(true) + }) + }) +} + +//异步时先注册空的 保证初始化不报错 +registerMap(`${props.chartConfig.option.mapRegion.adcode}`, { geoJSON: {} as any, specialAreas: {} }) + +// 进行更换初始化地图 如果为china 单独处理 +const registerMapInitAsync = async () => { + await nextTick() + const adCode = `${props.chartConfig.option.mapRegion.adcode}` + if (adCode !== 'china') { + await getGeojson(adCode) + } else { + await hainanLandsHandle(props.chartConfig.option.mapRegion.showHainanIsLands) + } + vEchartsSetOption() +} +registerMapInitAsync() + +// 手动触发渲染 +const vEchartsSetOption = () => { + option.value = props.chartConfig.option + setOption(vChartRef.value, props.chartConfig.option) +} + +// 更新数据处理 +const dataSetHandle = async (dataset: any) => { + props.chartConfig.option.series.forEach((item: any) => { + if (item.type === 'effectScatter' && dataset.point) item.data = dataset.point + else if (item.type === 'lines' && dataset.line) { + item.data = dataset.line.map((it: any) => { + return { + ...it, + lineStyle: { + color: props.chartConfig.option.series[2].lineStyle.normal.color + } + } + }) + } else if (item.type === 'map' && dataset.map) item.data = dataset.map + }) + if (dataset.pieces) props.chartConfig.option.visualMap.pieces = dataset.pieces + + isPreview() && vEchartsSetOption() +} +// 处理海南群岛 +const hainanLandsHandle = async (newData: boolean) => { + if (newData) { + await getGeojson('china') + } else { + registerMap('china', { geoJSON: mapJsonWithoutHainanIsLands as any, specialAreas: {} }) + } +} + +// 点击区域 +const chartPEvents = (e: any) => { + if (e.seriesType !== 'map') return + if (!props.chartConfig.option.mapRegion.enter) { + return + } + mapChinaJson.features.forEach(item => { + var pattern = new RegExp(e.name) + if (pattern.test(item.properties.name)) { + let code = String(item.properties.adcode) + levelHistory.value.push(code) + checkOrMap(code) + } + }) +} + +// 返回上一级 +const backLevel = () => { + levelHistory.value = [] + if (levelHistory.value.length > 1) { + levelHistory.value.pop() + const code = levelHistory[levelHistory.value.length - 1] + checkOrMap(code) + } else { + checkOrMap('china') + } +} + +// 切换地图 +const checkOrMap = async (newData: string) => { + await getGeojson(newData) + props.chartConfig.option.geo.map = newData + props.chartConfig.option.series.forEach((item: any) => { + if (item.type === 'map') item.map = newData + }) + vEchartsSetOption() +} + +//监听 dataset 数据发生变化 +watch( + () => props.chartConfig.option.dataset, + newData => { + dataSetHandle(newData) + }, + { + immediate: true, + deep: false + } +) + +// 监听线的颜色 +if (props.chartConfig.option.series[2] && !isPreview()) { + watch( + () => props.chartConfig.option.series[2].lineStyle.normal.color, + () => { + dataSetHandle(props.chartConfig.option.dataset) + }, + { + deep: false + } + ) +} + +//监听是否显示南海群岛 +if (!isPreview()) { + watch( + () => props.chartConfig.option.mapRegion.showHainanIsLands, + async newData => { + try { + await hainanLandsHandle(newData) + vEchartsSetOption() + } catch (error) { + console.log(error) + } + }, + { + deep: false + } + ) +} +//监听地图展示区域发生变化 +watch( + () => `${props.chartConfig.option.mapRegion.adcode}`, + newData => { + try { + checkOrMap(newData) + } catch (error) { + console.log(error) + } + }, + { + deep: false + } +) + +// 预览 +useChartDataFetch(props.chartConfig, useChartEditStore, (newData: any) => { + dataSetHandle(newData) +}) +</script> + +<style scope lang="scss"> +.back-icon { + z-index: 50; + cursor: pointer; + position: absolute; + display: flex; + align-items: center; + top: 0; + left: 0; + gap: 2px; +} +</style>