![]()
@@ -117,11 +122,17 @@ const currentMode = ref('outflow')
let chartInstance = null
let sourceChartInstance = null
let salesChartInstance = null
+let resizeObserver = null
let chinaMapData = null
let tradingData = null
let mapSystemConfig = null
let cachedOutlineKey = ''
let cachedOutlinePaths = []
+let unitedMapKey = ''
+let mapBgImage = null
+let mapBgImagePromise = null
+let mapSurfaceTexture = null
+let mapSurfaceTextureKey = ''
// 浮动面板状态
const showSourcePanel = ref(false)
@@ -143,47 +154,50 @@ const modes = [
const BTN_NORMAL = '/images/按钮.png'
const BTN_ACTIVE = '/images/按钮选中.png'
const LEGEND_BG = '/images/图例bg.png'
-const GEO_DATA_INDEX = 2
-const WIREFRAME_GEO_INDEX = 3
+const GEO_DATA_INDEX = 4
+const WIREFRAME_GEO_INDEX = 5
const MAP_ASPECT_SCALE = 0.82
-const MAP_ZOOM_FACTOR = 0.97
-const MAP_LAYOUT_SIZE = '84%'
-const MAP_ANCHOR_CENTER = [104.8, 37.2]
-const MAP_ANCHOR_ZOOM = 1.05
-const MAP_GLOBAL_OFFSET_LNG = 1.45
-const MAP_GLOBAL_OFFSET_LAT = 0.9
-const MAP_GLOBAL_LAYOUT_X = -2.8
-const MAP_GLOBAL_LAYOUT_Y = 3.6
-const MAP_SHADOW_OFFSET_FAR = 1.25
-const MAP_SHADOW_OFFSET_NEAR = 0.75
-const MAP_SHADOW_OFFSET_LNG = -0.45
+const MAP_ZOOM_FACTOR = 1
+const MAP_LAYOUT_SIZE = '88%'
+const MAP_ANCHOR_CENTER = [105.2, 36.8]
+const MAP_ANCHOR_ZOOM = 1.08
+const MAP_GLOBAL_OFFSET_LNG = 1.2
+const MAP_GLOBAL_OFFSET_LAT = 0.85
+const MAP_GLOBAL_LAYOUT_X = -2.2
+const MAP_GLOBAL_LAYOUT_Y = 2.8
+const MAP_SHADOW_OFFSET_FAR = 1.85
+const MAP_SHADOW_OFFSET_MID = 1.2
+const MAP_SHADOW_OFFSET_NEAR = 0.62
+const MAP_SHADOW_OFFSET_LNG = -0.42
const MAP_SHADOW_OFFSET_FAR_LOCAL = 5
-const MAP_SHADOW_OFFSET_NEAR_LOCAL = 3
+const MAP_SHADOW_OFFSET_MID_LOCAL = 3.5
+const MAP_SHADOW_OFFSET_NEAR_LOCAL = 2.2
const MAP_SHADOW_OFFSET_LNG_LOCAL = -2
-const MAP_WIREFRAME_OFFSET = -0.65
-const MAP_WIREFRAME_OFFSET_LNG = 0.12
+const MAP_WIREFRAME_OFFSET = -0.72
+const MAP_WIREFRAME_OFFSET_LNG = 0.1
const MAP_WIREFRAME_OFFSET_LOCAL = -3.5
const MAP_WIREFRAME_OFFSET_LNG_LOCAL = 0.6
-const MAP_FILL_WEST = [45, 181, 181]
-const MAP_FILL_EAST = [12, 123, 176]
-const MAP_GRADIENT_MIN_LNG = 73
-const MAP_GRADIENT_MAX_LNG = 135
-const MAP_GRADIENT_MIN_LAT = 18
-const MAP_GRADIENT_MAX_LAT = 54
-const MAP_WIREFRAME_COLOR = '#f8feff'
-const MAP_INNER_BORDER = 'rgba(6, 38, 62, 0.62)'
-const MAP_INNER_BORDER_WIDTH = 0.8
-const MAP_SHADOW_FAR = '#000000'
-const MAP_SHADOW_NEAR = '#000000'
+const MAP_WIREFRAME_COLOR = '#f2fdff'
+const MAP_INNER_BORDER = 'rgba(4, 32, 52, 0.28)'
+const MAP_INNER_BORDER_WIDTH = 0.45
+const MAP_SHADOW_FILL_FAR = '#010204'
+const MAP_SHADOW_FILL_MID = '#020408'
+const MAP_SHADOW_FILL_NEAR = '#030a12'
const MAP_LABEL_COLOR = '#ffffff'
-const MAP_FILL_OPACITY = 0.88
-const MAP_TEXTURE_SIZE = 128
+// 世界地图背景源图(4318×1078)中中国区域裁剪,用于烘焙进地图纹理
+const MAP_BG_IMAGE = '/images/世界地图背景.png'
+const MAP_BG_NATIVE_WIDTH = 4318
+const MAP_BG_NATIVE_HEIGHT = 1078
+const MAP_BG_CROP_CHINA = { x: 1160, y: 108, width: 2000, height: 828 }
+const MAP_BG_CROP_LOCAL = { x: 1860, y: 332, width: 820, height: 520 }
+const MAP_SURFACE_TEXTURE_WIDTH = 1536
+const MAP_SURFACE_TEXTURE_HEIGHT = 1152
+const MAP_SURFACE_FALLBACK = 'rgb(92, 202, 206)'
+const MAP_SURFACE_COLOR = 'rgba(0, 0, 0, 0)'
// path 符号需朝上绘制,ECharts 会沿路径切线自动旋转
const METEOR_EFFECT_PATH = 'path://M0.5,0 L0.56,0.14 L0.52,1 L0.48,1 L0.44,0.14 Z'
-const mapTextureCache = new Map()
-
// 获取当前模式对应的地图文件路径
const getMapFilePath = (mode) => {
switch (mode) {
@@ -269,9 +283,12 @@ const loadData = async (mapMode = 'china') => {
// 注册地图
const mapName = getMapName(mapMode)
echarts.registerMap(mapName, chinaMapData)
+ unitedMapKey = ''
+ registerUnitedMap(mapMode)
+ mapSurfaceTexture = null
+ mapSurfaceTextureKey = ''
cachedOutlineKey = ''
cachedOutlinePaths = []
- mapTextureCache.clear()
return true
} catch (error) {
@@ -669,8 +686,6 @@ const calculateMapBounds = () => {
}
}
-const clamp = (value, min, max) => Math.min(max, Math.max(min, value))
-
const getFeatureCenter = (feature) => {
const cp = feature?.properties?.cp || feature?.properties?.center
if (Array.isArray(cp) && cp.length >= 2) {
@@ -682,91 +697,201 @@ const getFeatureCenter = (feature) => {
return [104, 35]
}
-const getRegionAreaColor = (lng, lat, minLng, maxLng, minLat, maxLat) => {
- const { r, g, b } = blendDesignColor(lng, lat, minLng, maxLng, minLat, maxLat)
- const cacheKey = `${r},${g},${b},${MAP_FILL_OPACITY}`
+const getUnitedMapName = (mode = currentMode.value) => `${getMapName(mode)}-united`
- if (!mapTextureCache.has(cacheKey)) {
- mapTextureCache.set(cacheKey, createRegionTextureCanvas(r, g, b, MAP_FILL_OPACITY))
+const buildUnitedGeoJson = (geojson) => {
+ if (!geojson?.features?.length) {
+ return geojson
}
+ const coordinates = []
+ geojson.features.forEach((feature) => {
+ const geom = feature.geometry
+ if (!geom) {
+ return
+ }
+ if (geom.type === 'Polygon') {
+ coordinates.push(geom.coordinates)
+ } else if (geom.type === 'MultiPolygon') {
+ geom.coordinates.forEach((polygon) => coordinates.push(polygon))
+ }
+ })
+
return {
- image: mapTextureCache.get(cacheKey),
- repeat: 'repeat'
+ type: 'FeatureCollection',
+ features: [{
+ type: 'Feature',
+ properties: { name: 'united' },
+ geometry: {
+ type: 'MultiPolygon',
+ coordinates
+ }
+ }]
}
}
-const createRegionTextureCanvas = (r, g, b, alpha) => {
- if (typeof document === 'undefined') {
- return null
+const registerUnitedMap = (mapMode = currentMode.value) => {
+ const unitedName = getUnitedMapName(mapMode)
+ const key = `${unitedName}-${chinaMapData?.features?.length || 0}`
+ if (unitedMapKey !== key) {
+ echarts.registerMap(unitedName, buildUnitedGeoJson(chinaMapData))
+ unitedMapKey = key
+ mapSurfaceTexture = null
+ mapSurfaceTextureKey = ''
}
+ return unitedName
+}
+
+const getMapBgCrop = () => (
+ currentMode.value === 'local' ? MAP_BG_CROP_LOCAL : MAP_BG_CROP_CHINA
+)
- const size = MAP_TEXTURE_SIZE
+const loadMapBgImage = () => {
+ if (mapBgImage) {
+ return Promise.resolve(mapBgImage)
+ }
+ if (mapBgImagePromise) {
+ return mapBgImagePromise
+ }
+
+ mapBgImagePromise = new Promise((resolve, reject) => {
+ if (typeof Image === 'undefined') {
+ reject(new Error('Image is not available'))
+ return
+ }
+
+ const image = new Image()
+ image.decoding = 'async'
+ image.onload = () => {
+ mapBgImage = image
+ resolve(image)
+ }
+ image.onerror = () => {
+ mapBgImagePromise = null
+ reject(new Error(`Failed to load ${MAP_BG_IMAGE}`))
+ }
+ image.src = MAP_BG_IMAGE
+ })
+
+ return mapBgImagePromise
+}
+
+const paintMapSurfaceTexture = (ctx, width, height, image, crop) => {
+ ctx.clearRect(0, 0, width, height)
+
+ // 1. 地形纹理打底(略提亮对比,保证纹路可见)
+ ctx.filter = 'brightness(1.28) contrast(1.18)'
+ ctx.drawImage(
+ image,
+ crop.x,
+ crop.y,
+ crop.width,
+ crop.height,
+ 0,
+ 0,
+ width,
+ height
+ )
+ ctx.filter = 'none'
+
+ // 2. overlay 青蓝染色:G/B 均衡,介于青色与蓝色之间
+ ctx.globalCompositeOperation = 'overlay'
+ const tintGradient = ctx.createRadialGradient(
+ width * 0.44,
+ height * 0.48,
+ 0,
+ width * 0.44,
+ height * 0.48,
+ Math.max(width, height) * 0.86
+ )
+ tintGradient.addColorStop(0, 'rgba(158, 236, 228, 0.94)')
+ tintGradient.addColorStop(0.42, 'rgba(108, 212, 214, 0.90)')
+ tintGradient.addColorStop(0.78, 'rgba(82, 194, 200, 0.86)')
+ tintGradient.addColorStop(1, 'rgba(68, 180, 188, 0.82)')
+ ctx.fillStyle = tintGradient
+ ctx.fillRect(0, 0, width, height)
+
+ // 3. soft-light 补一层青蓝,提亮暗部
+ ctx.globalCompositeOperation = 'soft-light'
+ ctx.fillStyle = 'rgba(118, 218, 214, 0.36)'
+ ctx.fillRect(0, 0, width, height)
+
+ // 4. screen 中心高光
+ ctx.globalCompositeOperation = 'screen'
+ const glowGradient = ctx.createRadialGradient(
+ width * 0.43,
+ height * 0.46,
+ 0,
+ width * 0.43,
+ height * 0.46,
+ Math.max(width, height) * 0.58
+ )
+ glowGradient.addColorStop(0, 'rgba(205, 248, 242, 0.34)')
+ glowGradient.addColorStop(0.55, 'rgba(175, 235, 228, 0.14)')
+ glowGradient.addColorStop(1, 'rgba(175, 235, 228, 0)')
+ ctx.fillStyle = glowGradient
+ ctx.fillRect(0, 0, width, height)
+
+ ctx.globalCompositeOperation = 'source-over'
+}
+
+const createMapSurfaceTexture = (image) => {
+ const width = MAP_SURFACE_TEXTURE_WIDTH
+ const height = MAP_SURFACE_TEXTURE_HEIGHT
const canvas = document.createElement('canvas')
- canvas.width = size
- canvas.height = size
+ canvas.width = width
+ canvas.height = height
const ctx = canvas.getContext('2d')
if (!ctx) {
return null
}
- ctx.fillStyle = `rgba(${r}, ${g}, ${b}, ${alpha})`
- ctx.fillRect(0, 0, size, size)
-
- for (let i = 0; i < 420; i += 1) {
- const x = Math.random() * size
- const y = Math.random() * size
- const radius = Math.random() * 1.6 + 0.3
- ctx.fillStyle = `rgba(255, 255, 255, ${Math.random() * 0.1 + 0.02})`
- ctx.beginPath()
- ctx.arc(x, y, radius, 0, Math.PI * 2)
- ctx.fill()
- }
-
- for (let i = 0; i < 260; i += 1) {
- const x = Math.random() * size
- const y = Math.random() * size
- const radius = Math.random() * 2.4 + 0.6
- ctx.fillStyle = `rgba(8, 45, 72, ${Math.random() * 0.08 + 0.02})`
- ctx.beginPath()
- ctx.arc(x, y, radius, 0, Math.PI * 2)
- ctx.fill()
- }
+ paintMapSurfaceTexture(ctx, width, height, image, getMapBgCrop())
+ return canvas
+}
- for (let i = 0; i < 6; i += 1) {
- const x = Math.random() * size
- const y = Math.random() * size
- const radius = Math.random() * 28 + 18
- const gradient = ctx.createRadialGradient(x, y, 0, x, y, radius)
- gradient.addColorStop(0, `rgba(255, 255, 255, ${Math.random() * 0.06 + 0.02})`)
- gradient.addColorStop(1, 'rgba(255, 255, 255, 0)')
- ctx.fillStyle = gradient
- ctx.beginPath()
- ctx.arc(x, y, radius, 0, Math.PI * 2)
- ctx.fill()
+const ensureMapSurfaceTexture = async () => {
+ const key = currentMode.value
+ if (mapSurfaceTexture && mapSurfaceTextureKey === key) {
+ return mapSurfaceTexture
}
- return canvas
+ const image = await loadMapBgImage()
+ mapSurfaceTexture = createMapSurfaceTexture(image)
+ mapSurfaceTextureKey = key
+ return mapSurfaceTexture
}
-const blendDesignColor = (lng, lat, minLng, maxLng, minLat, maxLat) => {
- const lngRatio = clamp((lng - minLng) / (maxLng - minLng), 0, 1)
- const latRatio = clamp((lat - minLat) / (maxLat - minLat), 0, 1)
+const getMapSurfaceAreaStyle = () => {
+ if (mapSurfaceTexture) {
+ return {
+ areaColor: {
+ image: mapSurfaceTexture,
+ repeat: 'no-repeat'
+ }
+ }
+ }
- let r = MAP_FILL_WEST[0] + (MAP_FILL_EAST[0] - MAP_FILL_WEST[0]) * lngRatio
- let g = MAP_FILL_WEST[1] + (MAP_FILL_EAST[1] - MAP_FILL_WEST[1]) * lngRatio
- let b = MAP_FILL_WEST[2] + (MAP_FILL_EAST[2] - MAP_FILL_WEST[2]) * lngRatio
+ return { areaColor: MAP_SURFACE_FALLBACK }
+}
- const northBoost = (0.62 - latRatio) * 16
- const southDepth = (latRatio - 0.38) * 10
- r = clamp(r + northBoost - southDepth * 0.35, 0, 255)
- g = clamp(g + northBoost * 0.95 - southDepth * 0.25, 0, 255)
- b = clamp(b + northBoost * 0.55 - southDepth * 0.15, 0, 255)
+const refreshMapSurfaceTexture = async () => {
+ if (!chartInstance) {
+ return
+ }
- return {
- r: Math.round(r),
- g: Math.round(g),
- b: Math.round(b)
+ try {
+ await ensureMapSurfaceTexture()
+ const mapBounds = calculateMapBounds()
+ const mapName = getMapName(currentMode.value)
+ const unitedMapName = registerUnitedMap(currentMode.value)
+ chartInstance.setOption({
+ geo: buildGeoLayers(mapName, unitedMapName, mapBounds)
+ }, {
+ replaceMerge: ['geo']
+ })
+ } catch (error) {
+ console.warn('地图纹理生成失败:', error)
}
}
@@ -898,49 +1023,6 @@ const getWireframePaths = () => {
return cachedOutlinePaths
}
-const buildRegionFillData = () => {
- if (!chinaMapData?.features?.length) {
- return []
- }
-
- let minLng = MAP_GRADIENT_MIN_LNG
- let maxLng = MAP_GRADIENT_MAX_LNG
- let minLat = MAP_GRADIENT_MIN_LAT
- let maxLat = MAP_GRADIENT_MAX_LAT
-
- if (currentMode.value === 'local') {
- const centers = chinaMapData.features.map(getFeatureCenter)
- const lngs = centers.map((center) => center[0])
- const lats = centers.map((center) => center[1])
- minLng = Math.min(...lngs)
- maxLng = Math.max(...lngs)
- minLat = Math.min(...lats)
- maxLat = Math.max(...lats)
- if (maxLng - minLng < 0.001) {
- maxLng = minLng + 1
- }
- if (maxLat - minLat < 0.001) {
- maxLat = minLat + 1
- }
- }
-
- return chinaMapData.features.map((feature) => {
- const name = feature.properties?.name
- if (!name) {
- return null
- }
- const [lng, lat] = getFeatureCenter(feature)
- return {
- name,
- itemStyle: {
- areaColor: getRegionAreaColor(lng, lat, minLng, maxLng, minLat, maxLat),
- borderColor: MAP_INNER_BORDER,
- borderWidth: MAP_INNER_BORDER_WIDTH
- }
- }
- }).filter(Boolean)
-}
-
const getMapProjection = (mapBounds, latOffset = 0, lngOffset = 0) => {
const isLocal = currentMode.value === 'local'
if (isLocal) {
@@ -965,41 +1047,59 @@ const getMapProjection = (mapBounds, latOffset = 0, lngOffset = 0) => {
}
}
-const buildGeoLayers = (mapName, mapBounds) => {
+const buildShadowFillGeo = (unitedMapName, mapBounds, latOffset, lngOffset, areaColor, z) => ({
+ map: unitedMapName,
+ roam: false,
+ ...getMapProjection(mapBounds, latOffset, lngOffset),
+ silent: true,
+ zlevel: 0,
+ z,
+ label: { show: false },
+ itemStyle: {
+ areaColor,
+ borderColor: 'transparent',
+ borderWidth: 0
+ },
+ emphasis: {
+ disabled: true
+ }
+})
+
+const getShadowOffsets = () => {
+ const isLocal = currentMode.value === 'local'
+ return {
+ far: isLocal ? MAP_SHADOW_OFFSET_FAR_LOCAL : MAP_SHADOW_OFFSET_FAR,
+ mid: isLocal ? MAP_SHADOW_OFFSET_MID_LOCAL : MAP_SHADOW_OFFSET_MID,
+ near: isLocal ? MAP_SHADOW_OFFSET_NEAR_LOCAL : MAP_SHADOW_OFFSET_NEAR,
+ lng: isLocal ? MAP_SHADOW_OFFSET_LNG_LOCAL : MAP_SHADOW_OFFSET_LNG
+ }
+}
+
+const buildGeoLayers = (mapName, unitedMapName, mapBounds) => {
const isLocal = currentMode.value === 'local'
- const shadowFarOffset = isLocal ? MAP_SHADOW_OFFSET_FAR_LOCAL : MAP_SHADOW_OFFSET_FAR
- const shadowNearOffset = isLocal ? MAP_SHADOW_OFFSET_NEAR_LOCAL : MAP_SHADOW_OFFSET_NEAR
- const shadowLngOffset = isLocal ? MAP_SHADOW_OFFSET_LNG_LOCAL : MAP_SHADOW_OFFSET_LNG
const wireframeOffset = isLocal ? MAP_WIREFRAME_OFFSET_LOCAL : MAP_WIREFRAME_OFFSET
const wireframeLngOffset = isLocal ? MAP_WIREFRAME_OFFSET_LNG_LOCAL : MAP_WIREFRAME_OFFSET_LNG
+ const { far, mid, near, lng } = getShadowOffsets()
return [
+ buildShadowFillGeo(unitedMapName, mapBounds, far, lng, MAP_SHADOW_FILL_FAR, 1),
+ buildShadowFillGeo(unitedMapName, mapBounds, mid, lng, MAP_SHADOW_FILL_MID, 2),
+ buildShadowFillGeo(unitedMapName, mapBounds, near, lng, MAP_SHADOW_FILL_NEAR, 3),
{
- map: mapName,
- roam: false,
- ...getMapProjection(mapBounds, shadowFarOffset, shadowLngOffset),
- silent: true,
- zlevel: 0,
- z: 1,
- label: { show: false },
- itemStyle: {
- areaColor: MAP_SHADOW_FAR,
- borderColor: MAP_SHADOW_FAR,
- borderWidth: 0
- }
- },
- {
- map: mapName,
+ map: unitedMapName,
roam: false,
- ...getMapProjection(mapBounds, shadowNearOffset, shadowLngOffset),
+ ...getMapProjection(mapBounds, 0),
silent: true,
- zlevel: 0,
+ zlevel: 1,
z: 2,
label: { show: false },
itemStyle: {
- areaColor: MAP_SHADOW_NEAR,
- borderColor: MAP_SHADOW_NEAR,
+ ...getMapSurfaceAreaStyle(),
+ borderColor: 'transparent',
borderWidth: 0
+ },
+ emphasis: {
+ disabled: true
}
},
{
@@ -1047,6 +1147,7 @@ const getChartOption = () => {
const effectScatterData = generateEffectScatterData()
const mapBounds = calculateMapBounds()
const mapName = getMapName(currentMode.value)
+ const unitedMapName = registerUnitedMap(currentMode.value)
const mapLayout = getMapProjection(mapBounds, 0)
const outlinePaths = getWireframePaths()
const wireframeData = outlinePaths.map((coords) => ({ coords }))
@@ -1113,7 +1214,7 @@ const getChartOption = () => {
return params.name
}
},
- geo: buildGeoLayers(mapName, mapBounds),
+ geo: buildGeoLayers(mapName, unitedMapName, mapBounds),
series: [
{
name: 'mapFill',
@@ -1124,18 +1225,18 @@ const getChartOption = () => {
zlevel: 2,
silent: true,
selectedMode: false,
- data: buildRegionFillData(),
itemStyle: {
+ areaColor: MAP_SURFACE_COLOR,
borderColor: MAP_INNER_BORDER,
borderWidth: MAP_INNER_BORDER_WIDTH
},
label: {
show: true,
color: MAP_LABEL_COLOR,
- fontSize: 11,
+ fontSize: 12,
fontFamily: 'Microsoft YaHei, sans-serif',
fontWeight: 'bold',
- textBorderColor: 'rgba(0, 0, 0, 0.45)',
+ textBorderColor: 'rgba(0, 0, 0, 0.5)',
textBorderWidth: 2
},
emphasis: {
@@ -1153,12 +1254,12 @@ const getChartOption = () => {
data: wireframeData,
lineStyle: {
color: MAP_WIREFRAME_COLOR,
- width: 3.2,
- opacity: 0.96,
+ width: 4.2,
+ opacity: 0.98,
cap: 'round',
join: 'round',
- shadowBlur: 16,
- shadowColor: 'rgba(120, 230, 255, 0.6)'
+ shadowBlur: 22,
+ shadowColor: 'rgba(130, 245, 255, 0.78)'
}
},
{
@@ -1200,12 +1301,12 @@ const getChartOption = () => {
zlevel: 6,
data: linesData,
lineStyle: {
- color: '#7ee8ff',
- width: 1.2,
- opacity: 0.75,
- curveness: 0.28,
- shadowBlur: 8,
- shadowColor: 'rgba(110, 232, 255, 0.55)'
+ color: '#a8f6ff',
+ width: 1.5,
+ opacity: 0.82,
+ curveness: 0.32,
+ shadowBlur: 10,
+ shadowColor: 'rgba(120, 240, 255, 0.65)'
},
emphasis: {
lineStyle: {
@@ -1226,17 +1327,17 @@ const getChartOption = () => {
data: linesData,
effect: {
show: true,
- constantSpeed: 50,
+ constantSpeed: 55,
symbol: METEOR_EFFECT_PATH,
- symbolSize: [5, 20],
- trailLength: 0.14,
+ symbolSize: [5, 22],
+ trailLength: 0.16,
loop: true,
- color: '#ffffff'
+ color: '#e8fbff'
},
lineStyle: {
width: 0,
opacity: 0,
- curveness: 0.28
+ curveness: 0.32
}
},
{
@@ -1292,9 +1393,12 @@ const reloadMapData = async (mapMode) => {
const mapName = getMapName(mapMode)
echarts.registerMap(mapName, chinaMapData)
+ unitedMapKey = ''
+ registerUnitedMap(mapMode)
+ mapSurfaceTexture = null
+ mapSurfaceTextureKey = ''
cachedOutlineKey = ''
cachedOutlinePaths = []
- mapTextureCache.clear()
return true
} catch (error) {
@@ -1303,6 +1407,10 @@ const reloadMapData = async (mapMode) => {
}
}
+const handleChartResize = () => {
+ chartInstance?.resize()
+}
+
// 初始化图表
const initChart = async () => {
if (!chartRef.value) return
@@ -1312,6 +1420,7 @@ const initChart = async () => {
chartInstance = echarts.init(chartRef.value)
chartInstance.setOption(getChartOption())
+ await refreshMapSurfaceTexture()
// 添加鼠标事件监听
chartInstance.on('mouseover', (params) => {
@@ -1333,9 +1442,17 @@ const initChart = async () => {
}
})
- // 监听窗口大小变化
- window.addEventListener('resize', () => {
- chartInstance?.resize()
+ window.addEventListener('resize', handleChartResize)
+ const mountPoint = chartRef.value.parentElement
+ if (mountPoint) {
+ resizeObserver = new ResizeObserver(handleChartResize)
+ resizeObserver.observe(mountPoint)
+ }
+
+ await nextTick()
+ requestAnimationFrame(() => {
+ handleChartResize()
+ setTimeout(handleChartResize, 160)
})
}
@@ -1351,6 +1468,9 @@ const updateChart = async (needReloadMap = false) => {
notMerge: false,
replaceMerge: ['geo', 'series']
})
+ await refreshMapSurfaceTexture()
+ await nextTick()
+ handleChartResize()
}
}
@@ -1371,6 +1491,9 @@ onMounted(() => {
})
onUnmounted(() => {
+ window.removeEventListener('resize', handleChartResize)
+ resizeObserver?.disconnect()
+ resizeObserver = null
if (chartInstance) {
chartInstance.dispose()
}
@@ -1388,12 +1511,15 @@ onUnmounted(() => {
position: relative;
width: 100%;
height: 100%;
+ min-height: 0;
overflow: hidden;
+ background: transparent;
}
.map-card :deep(.card-content) {
position: relative;
height: 100%;
+ min-height: 0;
padding: 0;
}
@@ -1473,15 +1599,31 @@ onUnmounted(() => {
position: absolute;
inset: 0;
overflow: hidden;
+ background: transparent;
+}
+
+.map-world-bg {
+ position: absolute;
+ width: var(--screen-width, 5120px);
+ height: var(--screen-height, 1440px);
+ left: 50%;
+ top: 50%;
+ transform: translate(-50%, -50%);
+ object-fit: fill;
+ pointer-events: none;
+ user-select: none;
+ z-index: 0;
}
.chart-container {
position: absolute;
inset: 0;
- width: 100%;
- height: 100%;
+ width: 100% !important;
+ height: 100% !important;
+ min-width: 0;
+ min-height: 0;
z-index: 1;
- transform: translate(-1.2%, 1.6%);
+ transform: translate(-0.4%, 0.6%);
}
.map-legend {
diff --git a/src/components/ExchangeMonitor.vue b/src/components/ExchangeMonitor.vue
index 4d039f7..ef56de7 100644
--- a/src/components/ExchangeMonitor.vue
+++ b/src/components/ExchangeMonitor.vue
@@ -24,7 +24,7 @@