mirror of
https://gitee.com/dromara/go-view.git
synced 2025-06-29 16:29:14 +08:00
572 lines
17 KiB
Vue
572 lines
17 KiB
Vue
<template>
|
|
<div ref="box" :style="{ scale }" style="position: absolute">
|
|
<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, computed, Ref, onMounted, onUnmounted } 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 { useChartCommonData } from '@/hooks'
|
|
|
|
import { isPreview, postMessageToParent } from '@/utils'
|
|
import mapJsonWithoutHainanIsLands from './mapWithoutHainanIsLands.json'
|
|
import mapChinaJson from './mapGeojson/china.json'
|
|
import { DatasetComponent, GridComponent, TooltipComponent, GeoComponent, VisualMapComponent } from 'echarts/components'
|
|
import { customData as customDataConfig } from './config'
|
|
import { publicInterface } from '@/api/path/business.api'
|
|
import { useOriginStore } from '@/store/modules/originStore/originStore'
|
|
import { selectTimeOptions } from '@/views/chart/ContentConfigurations/components/ChartData/index.d'
|
|
import { debounce } from 'lodash'
|
|
import { useGlobalQueryParamsStore } from '@/store/modules/globalQueryParamsStore/globalQueryParamsStore'
|
|
const globalQueryParamsStore = useGlobalQueryParamsStore()
|
|
|
|
const props = defineProps({
|
|
themeSetting: {
|
|
type: Object,
|
|
required: true
|
|
},
|
|
themeColor: {
|
|
type: Object,
|
|
required: true
|
|
},
|
|
chartConfig: {
|
|
type: Object as PropType<config>,
|
|
required: true
|
|
}
|
|
})
|
|
|
|
const customData: Ref<typeof customDataConfig> = computed(() => {
|
|
return props.chartConfig.customData as typeof customDataConfig
|
|
})
|
|
|
|
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 originStore = useOriginStore()
|
|
const systemConfig = originStore.getOriginStore.user.systemConfig
|
|
|
|
const activeAlarmData = computed(() => {
|
|
let obj: any = { levels: [], confirm_statuses: [] }
|
|
if (systemConfig['active_alarm_level']) {
|
|
for (let i = 0; i < Number(systemConfig['active_alarm_level']); i++) {
|
|
obj.levels.push(i + 1)
|
|
}
|
|
}
|
|
if (systemConfig['active_alarm_confirm_status']) {
|
|
obj.confirm_statuses = [...JSON.parse(systemConfig['active_alarm_confirm_status'])]
|
|
}
|
|
return obj
|
|
})
|
|
|
|
const dataMap = computed(() => {
|
|
let obj = {}
|
|
try {
|
|
obj = JSON.parse(customData.value.dataMap)
|
|
} catch (e) {
|
|
console.log(e)
|
|
}
|
|
return obj
|
|
})
|
|
|
|
let getData = () => {
|
|
let obj = dataMap.value as any
|
|
if (JSON.stringify(obj) !== '{}') {
|
|
let ids = []
|
|
for (let k in obj) {
|
|
if (obj[k].roomId) ids.push(obj[k].roomId)
|
|
}
|
|
const param = {
|
|
ids,
|
|
...activeAlarmData.value
|
|
}
|
|
publicInterface('/dcim/dems/device', 'get_space_tree_with_status_v3', param).then((res: any) => {
|
|
if (res.data) {
|
|
colorMap.value = {}
|
|
res.data = res.data.filter((_: any) => _.node_status !== 0)
|
|
res.data.forEach((_: any) => {
|
|
colorMap.value[_.id] = '#f43b42'
|
|
})
|
|
vEchartsSetOption()
|
|
}
|
|
})
|
|
} else {
|
|
vEchartsSetOption()
|
|
}
|
|
}
|
|
getData = debounce(getData, 200)
|
|
|
|
const vChartRef = ref<typeof VChart>()
|
|
const currentMap: Ref<any> = ref(null)
|
|
let colorMap: Ref<any> = ref({})
|
|
const componentList: Ref<any> = ref([])
|
|
|
|
const updateMapFn = (data: any) => {
|
|
if (!data) return
|
|
props.chartConfig.option.series[0].data = data.features.map((it: any) => {
|
|
let obj = (dataMap.value as any)[it.properties.name] || {}
|
|
return {
|
|
name: it.properties.name,
|
|
adcode: it.properties.adcode,
|
|
|
|
value: it.properties.center ? it.properties.center.concat(50) : [],
|
|
itemStyle: {
|
|
color: colorMap.value[obj.roomId] || '#4dca59'
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
//动态获取json注册地图
|
|
const getGeojson = (regionId: string) => {
|
|
return new Promise<boolean>(resolve => {
|
|
console.log(regionId, 'regionId')
|
|
if (regionId === '440600') {
|
|
const chartEditStore = useChartEditStore()
|
|
componentList.value = chartEditStore.getComponentList
|
|
initSpaceName('佛山市')
|
|
get_area_dev_count()
|
|
}
|
|
import(`./mapGeojson/${regionId}.json`).then(data => {
|
|
currentMap.value = data.default
|
|
registerMap(regionId, { geoJSON: data.default as any, specialAreas: {} })
|
|
updateMapFn(data.default)
|
|
resolve(true)
|
|
})
|
|
})
|
|
}
|
|
const get_area_dev_count = async () => {
|
|
const res = await publicInterface('/dcim/dems/device', 'get_area_dev_count', { space_type: 'county' })
|
|
customData.value.devList = res?.data
|
|
console.log(res?.data, 'res?.data')
|
|
props.chartConfig.option.series[1].data = res?.data || []
|
|
}
|
|
//异步时先注册空的 保证初始化不报错
|
|
if (props.chartConfig.option.mapRegion.province) {
|
|
registerMap(`${props.chartConfig.option.mapRegion.province}`, { geoJSON: {} as any, specialAreas: {} })
|
|
} else {
|
|
registerMap(`${props.chartConfig.option.mapRegion.adcode}`, { geoJSON: {} as any, specialAreas: {} })
|
|
}
|
|
|
|
// 进行更换初始化地图 如果为china 单独处理
|
|
const registerMapInitAsync = async () => {
|
|
await nextTick()
|
|
const adCode = `${props.chartConfig.option.mapRegion.adcode}`
|
|
const province = `${props.chartConfig.option.mapRegion.province}`
|
|
if (adCode !== 'china') {
|
|
await getGeojson(province ? province : adCode)
|
|
} else {
|
|
await hainanLandsHandle(props.chartConfig.option.mapRegion.showHainanIsLands)
|
|
}
|
|
vEchartsSetOption()
|
|
}
|
|
registerMapInitAsync()
|
|
|
|
const handleClickMap = (e: any) => {
|
|
let obj = dataMap.value as any
|
|
if (obj && JSON.stringify(obj) !== '{}') {
|
|
let path = obj[e.name]?.skipPath
|
|
if (path) openWeb(path)
|
|
}
|
|
}
|
|
|
|
onMounted(() => {
|
|
if (vChartRef.value) {
|
|
vChartRef.value.chart.on('click', 'series.map', handleClickMap)
|
|
}
|
|
})
|
|
|
|
onUnmounted(() => {
|
|
if (vChartRef.value) vChartRef.value.chart.off('click', 'series.map', handleClickMap)
|
|
})
|
|
|
|
const openWeb = (url: string) => {
|
|
postMessageToParent({
|
|
type: 'changeRouterV1',
|
|
url
|
|
})
|
|
}
|
|
|
|
// 手动触发渲染
|
|
const vEchartsSetOption = () => {
|
|
updateMapFn(currentMap.value)
|
|
option.value = props.chartConfig.option
|
|
setOption(vChartRef.value, props.chartConfig.option)
|
|
}
|
|
|
|
// 处理海南群岛
|
|
const hainanLandsHandle = async (newData: boolean) => {
|
|
if (newData) {
|
|
await getGeojson('china')
|
|
} else {
|
|
registerMap('china', { geoJSON: mapJsonWithoutHainanIsLands as any, specialAreas: {} })
|
|
}
|
|
}
|
|
|
|
const initSpaceName = (text: string) => {
|
|
componentList.value
|
|
.filter((v: any) => v.chartConfig.key === 'TextCommon' && v.option.spaceName)
|
|
.forEach((item: any) => {
|
|
item.option.dataset = `当前:${text}`
|
|
})
|
|
}
|
|
// 点击区域
|
|
const chartPEvents = (e: any) => {
|
|
console.log(e, 'e')
|
|
e.color = 'pink'
|
|
if (props.chartConfig.option.mapRegion.province === 440600) {
|
|
const county = currentMap.value.features.find((item: any) => item.properties.name === e.name)
|
|
|
|
if (county) {
|
|
const space_complete: any = customData.value.devList.find((item: any) => {
|
|
if (item.space.extend_column_string.length) {
|
|
return JSON.parse(item.space.extend_column_string).county_code === `${county.properties.adcode}`
|
|
}
|
|
})
|
|
// 找到空间标识的文字框然后进行赋值
|
|
initSpaceName(space_complete?.space.complete_name || `佛山市/${county.properties.name}`)
|
|
|
|
console.log(space_complete, 'space_complete_space_complete')
|
|
// 设置公共查询条件
|
|
globalQueryParamsStore.setGlobalQueryParams({
|
|
space_complete_id: space_complete?.space?.complete_id || '-999'
|
|
})
|
|
globalQueryParamsStore.setAreaDevCountQueryParams({
|
|
space_complete_id: space_complete?.space?.complete_id || '-999'
|
|
})
|
|
componentList.value.forEach((component: any) => {
|
|
// if (component.commonData.currentSource === 'areaDevCount') {
|
|
// component.commonData['areaDevCount'].space_complete_id = space_complete.space.complete_id
|
|
|
|
// // useChartCommonData(component, useChartEditStore)
|
|
// }
|
|
component.request.immediate = true
|
|
|
|
useChartCommonData(component, useChartEditStore)
|
|
})
|
|
}
|
|
console.log(county, 'county')
|
|
}
|
|
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) => {
|
|
if (newData === 'china') {
|
|
if (props.chartConfig.option.mapRegion.showHainanIsLands) {
|
|
// 显示南海
|
|
hainanLandsHandle(true)
|
|
vEchartsSetOption()
|
|
} else {
|
|
// 隐藏南海
|
|
hainanLandsHandle(false)
|
|
vEchartsSetOption()
|
|
}
|
|
} else {
|
|
await getGeojson(newData)
|
|
}
|
|
props.chartConfig.option.geo.map = newData
|
|
props.chartConfig.option.series.forEach((item: any) => {
|
|
if (item.type === 'map') item.map = newData
|
|
})
|
|
vEchartsSetOption()
|
|
}
|
|
|
|
onMounted(() => {
|
|
nextTick(() => {
|
|
getData()
|
|
})
|
|
})
|
|
|
|
watch(
|
|
() => customData.value.dataMap,
|
|
() => {
|
|
let obj = dataMap.value as any
|
|
props.chartConfig.option.series[1].tooltip.formatter = (v: any) => {
|
|
// if(props.chartConfig.option.mapRegion.province===440600){
|
|
// let str = `<div style="display: flex;align-items: center"><span style="margin-right: 20px;">${v.name}</span><span>${1111}</span></div>`
|
|
// return str
|
|
// }
|
|
let value: any
|
|
if (JSON.stringify(obj) !== '{}') value = obj[v.name]?.value || '-'
|
|
else value = !isNaN(v.value) ? v.value : '-'
|
|
let str = `<div style="display: flex;align-items: center"><span style="margin-right: 20px;">${v.name}</span><span>${value}</span></div>`
|
|
return str
|
|
}
|
|
getData()
|
|
},
|
|
{
|
|
immediate: true
|
|
}
|
|
)
|
|
|
|
let scale = ref(1)
|
|
const handleWheel = (event: any) => {
|
|
// 阻止默认行为(例如页面滚动)
|
|
event.preventDefault()
|
|
if (!option.value.canScroll) return
|
|
// 检查滚动方向
|
|
if (event.deltaY < 0) {
|
|
scale.value = Math.min(3, scale.value + 0.1)
|
|
} else {
|
|
scale.value = Math.max(0.1, scale.value - 0.1)
|
|
}
|
|
}
|
|
|
|
// 查找父元素,确保父元素的类名为 .go-preview-scale
|
|
function findParentWithClass(element: any, className: string) {
|
|
// 当前元素的父元素
|
|
let parent = element.parentElement
|
|
// 遍历父元素链,直到找到匹配的类名或到达文档根元素
|
|
while (parent && !parent.classList.contains(className)) {
|
|
parent = parent.parentElement
|
|
}
|
|
// 返回找到的父元素,或者 null 如果没有匹配的父元素
|
|
return parent
|
|
}
|
|
|
|
const getScaleXY = (v: string) => {
|
|
// 定义正则表达式来匹配 scale 函数中的两个数字
|
|
const regex = /scale\((\d*\.?\d+),\s*(\d*\.?\d+)\)/
|
|
|
|
// 使用正则表达式匹配并提取数字
|
|
const matches = v.match(regex)
|
|
|
|
if (matches) {
|
|
const scaleX = parseFloat(matches[1])
|
|
const scaleY = parseFloat(matches[2])
|
|
return [scaleX, scaleY]
|
|
} else {
|
|
return [1, 1]
|
|
}
|
|
}
|
|
|
|
let offsetX: number, offsetY: number
|
|
let left: number, top: number
|
|
let isDragging = ref(false)
|
|
const box = ref()
|
|
const handleMousedown = (event: any) => {
|
|
if (box.value) {
|
|
let el = box.value
|
|
isDragging.value = true
|
|
|
|
offsetX = event.clientX
|
|
offsetY = event.clientY
|
|
left = el.style.left.replace('px', '') * 1 || 0
|
|
top = el.style.top.replace('px', '') * 1 || 0
|
|
|
|
// 添加鼠标移动和松开事件监听器
|
|
document.addEventListener('mousemove', handleMousemove)
|
|
document.addEventListener('mouseup', handleMouseup)
|
|
}
|
|
}
|
|
|
|
// 当鼠标移动时触发
|
|
const handleMousemove = (event: any) => {
|
|
if (!option.value.canDrag) return
|
|
if (!isDragging.value) return
|
|
if (box.value) {
|
|
let parent = findParentWithClass(box.value, 'go-preview-scale')
|
|
let [scaleX, scaleY] = getScaleXY(parent.style.transform)
|
|
let el = box.value
|
|
|
|
// 计算元素的新位置
|
|
const x = left + (event.clientX - offsetX) / scaleX
|
|
const y = top + (event.clientY - offsetY) / scaleY
|
|
// 更新元素的位置
|
|
el.style.left = `${x}px`
|
|
el.style.top = `${y}px`
|
|
}
|
|
}
|
|
|
|
// 当鼠标松开时触发
|
|
const handleMouseup = (event: any) => {
|
|
if (!option.value.canDrag) return
|
|
isDragging.value = false
|
|
|
|
// 移除鼠标移动和松开事件监听器
|
|
document.removeEventListener('mousemove', handleMousemove)
|
|
document.removeEventListener('mouseup', handleMouseup)
|
|
}
|
|
|
|
onMounted(() => {
|
|
nextTick(() => {
|
|
if (vChartRef.value) {
|
|
vChartRef.value.$el.addEventListener('wheel', handleWheel, { passive: false })
|
|
}
|
|
if (box.value) {
|
|
box.value.addEventListener('mousedown', handleMousedown)
|
|
}
|
|
})
|
|
})
|
|
|
|
onUnmounted(() => {
|
|
vChartRef?.value?.$el?.removeEventListener('wheel', handleWheel, { passive: false })
|
|
box?.value?.removeEventListener('mousedown', handleMousedown)
|
|
})
|
|
|
|
//监听是否显示南海群岛
|
|
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
|
|
}
|
|
)
|
|
//监听地图展示区域发生变化
|
|
watch(
|
|
() => `${props.chartConfig.option.mapRegion.province}`,
|
|
newData => {
|
|
try {
|
|
checkOrMap(newData)
|
|
} catch (error) {
|
|
console.log(error)
|
|
}
|
|
},
|
|
{
|
|
deep: false
|
|
}
|
|
)
|
|
|
|
let timer: unknown
|
|
watch(
|
|
() => [props.chartConfig.request.requestInterval, props.chartConfig.request.requestIntervalUnit].join('&&'),
|
|
() => {
|
|
if (!isPreview()) return
|
|
if (props.chartConfig.request.requestInterval) {
|
|
if (timer) clearInterval(timer as number)
|
|
const obj = selectTimeOptions.find(_ => _.value === props.chartConfig.request.requestIntervalUnit) || { unit: 0 }
|
|
const unit = obj.unit
|
|
const number = unit * props.chartConfig.request.requestInterval
|
|
timer = setInterval(() => {
|
|
getData()
|
|
}, number)
|
|
}
|
|
}
|
|
)
|
|
|
|
onMounted(() => {
|
|
getData()
|
|
if (!isPreview()) return
|
|
const obj = selectTimeOptions.find(_ => _.value === props.chartConfig.request.requestIntervalUnit) || { unit: 0 }
|
|
const unit = obj.unit
|
|
const number = unit * props.chartConfig.request.requestInterval!
|
|
timer = setInterval(() => {
|
|
getData()
|
|
}, number)
|
|
})
|
|
|
|
onUnmounted(() => {
|
|
if (timer) clearInterval(timer as number)
|
|
})
|
|
</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>
|