Refactor ChinaMap and Dashboard components to streamline mode handling and background image management; enhance button layout in ChinaMap for improved user interaction; update computed properties for dynamic background image sourcing based on selected mode.

main
Swanky 3 days ago
parent 2954dc2e92
commit c9503210c5
  1. BIN
      public/images/红原背景.png
  2. 288
      src/components/ChinaMap.vue
  3. 13
      src/views/Dashboard.vue

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 MiB

@ -3,47 +3,28 @@
<!-- 顶部切换按钮 --> <!-- 顶部切换按钮 -->
<div class="map-controls"> <div class="map-controls">
<div class="control-buttons"> <div class="control-buttons">
<button <button v-for="mode in modes" :key="mode.key" type="button" class="mode-btn"
v-for="mode in modes" :class="{ active: currentMode === mode.key }" @click="currentMode = mode.key">
:key="mode.key" <img class="mode-btn-bg" :src="currentMode === mode.key ? BTN_ACTIVE : BTN_NORMAL" alt="" />
type="button"
class="mode-btn"
:class="{ active: currentMode === mode.key }"
@click="currentMode = mode.key"
>
<img
class="mode-btn-bg"
:src="currentMode === mode.key ? BTN_ACTIVE : BTN_NORMAL"
alt=""
/>
<span class="mode-btn-text">{{ mode.label }}</span> <span class="mode-btn-text">{{ mode.label }}</span>
</button> </button>
</div> </div>
</div> </div>
<!-- 左侧来源分析面板 --> <!-- 左侧来源分析面板 -->
<div <div v-if="showSourcePanel && sourceData" class="source-panel floating-panel">
v-if="showSourcePanel && sourceData"
class="source-panel floating-panel"
>
<h3>牦牛来源分析</h3> <h3>牦牛来源分析</h3>
<div ref="sourceChartRef" class="panel-chart"></div> <div ref="sourceChartRef" class="panel-chart"></div>
</div> </div>
<!-- 右侧销售分析面板 --> <!-- 右侧销售分析面板 -->
<div <div v-if="showSalesPanel && salesData" class="sales-panel floating-panel">
v-if="showSalesPanel && salesData"
class="sales-panel floating-panel"
>
<h3>牦牛销售分析</h3> <h3>牦牛销售分析</h3>
<div ref="salesChartRef" class="panel-chart"></div> <div ref="salesChartRef" class="panel-chart"></div>
</div> </div>
<!-- 交易明细浮动框 --> <!-- 交易明细浮动框 -->
<div <div v-if="showDetailPanel && detailData" class="detail-panel">
v-if="showDetailPanel && detailData"
class="detail-panel"
>
<div class="detail-header"> <div class="detail-header">
<h3>{{ selectedCity }} - 交易明细统计</h3> <h3>{{ selectedCity }} - 交易明细统计</h3>
<button @click="closeDetailPanel" class="close-btn">×</button> <button @click="closeDetailPanel" class="close-btn">×</button>
@ -77,14 +58,12 @@
</table> </table>
</div> </div>
</div> </div>
<div class="map-stage"> <div class="map-stage" :class="{ 'map-stage--local': currentMode === 'local' }">
image.png <img <div class="map-tilt-layer">
class="map-world-bg" <img class="map-world-bg" :src="mapWorldBgSrc" alt="" />
src="/images/世界地图背景.png" <div ref="chartRef" class="chart-container"></div>
alt="" </div>
/>
<div ref="chartRef" class="chart-container"></div>
<div class="map-legend" aria-hidden="true"> <div class="map-legend" aria-hidden="true">
<img class="map-legend-bg" :src="LEGEND_BG" alt="" /> <img class="map-legend-bg" :src="LEGEND_BG" alt="" />
<ul class="map-legend-list"> <ul class="map-legend-list">
@ -107,7 +86,7 @@
</template> </template>
<script setup> <script setup>
import { ref, onMounted, onUnmounted, watch, nextTick } from 'vue' import { ref, computed, onMounted, onUnmounted, watch, nextTick } from 'vue'
import * as echarts from 'echarts' import * as echarts from 'echarts'
import BaseCard from './BaseCard.vue' import BaseCard from './BaseCard.vue'
import { loadSystemConfig, DEFAULT_MAP_HUB, DEFAULT_MAP_FLOW_LEVELS } from '../utils/systemConfig.js' import { loadSystemConfig, DEFAULT_MAP_HUB, DEFAULT_MAP_FLOW_LEVELS } from '../utils/systemConfig.js'
@ -115,6 +94,8 @@ import { loadSystemConfig, DEFAULT_MAP_HUB, DEFAULT_MAP_FLOW_LEVELS } from '../u
const MAP_TRADING_API = '/api/dashboard/map-trading-network' const MAP_TRADING_API = '/api/dashboard/map-trading-network'
const FALLBACK_TRADING_JSON = './yak-trading-data.json' const FALLBACK_TRADING_JSON = './yak-trading-data.json'
const emit = defineEmits(['mode-change'])
const chartRef = ref(null) const chartRef = ref(null)
const sourceChartRef = ref(null) const sourceChartRef = ref(null)
const salesChartRef = ref(null) const salesChartRef = ref(null)
@ -129,8 +110,8 @@ let mapSystemConfig = null
let cachedOutlineKey = '' let cachedOutlineKey = ''
let cachedOutlinePaths = [] let cachedOutlinePaths = []
let unitedMapKey = '' let unitedMapKey = ''
let mapBgImage = null const mapBgImageCache = new Map()
let mapBgImagePromise = null const mapBgImageLoading = new Map()
let mapSurfaceTexture = null let mapSurfaceTexture = null
let mapSurfaceTextureKey = '' let mapSurfaceTextureKey = ''
@ -177,15 +158,25 @@ const MAP_WIREFRAME_OFFSET = -0.72
const MAP_WIREFRAME_OFFSET_LNG = 0.1 const MAP_WIREFRAME_OFFSET_LNG = 0.1
const MAP_WIREFRAME_OFFSET_LOCAL = -3.5 const MAP_WIREFRAME_OFFSET_LOCAL = -3.5
const MAP_WIREFRAME_OFFSET_LNG_LOCAL = 0.6 const MAP_WIREFRAME_OFFSET_LNG_LOCAL = 0.6
const MAP_WIREFRAME_COLOR = '#f2fdff' //
const MAP_WIREFRAME_COLOR = '#C8E8F4'
const MAP_WIREFRAME_HALO_LAYERS = [
{ width: 12, color: 'rgba(148, 208, 228, 0.08)' },
{ width: 8.5, color: 'rgba(162, 220, 238, 0.15)' },
{ width: 5.8, color: 'rgba(176, 232, 246, 0.25)' }
]
const MAP_WIREFRAME_CORE_WIDTH = 3.1
const MAP_WIREFRAME_CORE_SHADOW_BLUR = 8
const MAP_WIREFRAME_CORE_SHADOW_COLOR = 'rgba(120, 198, 228, 0.24)'
const MAP_INNER_BORDER = 'rgba(4, 32, 52, 0.28)' const MAP_INNER_BORDER = 'rgba(4, 32, 52, 0.28)'
const MAP_INNER_BORDER_WIDTH = 0.45 const MAP_INNER_BORDER_WIDTH = 0.45
const MAP_SHADOW_FILL_FAR = '#010204' const MAP_SHADOW_FILL_FAR = '#010204'
const MAP_SHADOW_FILL_MID = '#020408' const MAP_SHADOW_FILL_MID = '#020408'
const MAP_SHADOW_FILL_NEAR = '#030a12' const MAP_SHADOW_FILL_NEAR = '#030a12'
const MAP_LABEL_COLOR = '#ffffff' const MAP_LABEL_COLOR = '#ffffff'
// 4318×1078 // /
const MAP_BG_IMAGE = '/images/世界地图背景.png' const MAP_BG_IMAGE_WORLD = '/images/世界地图背景.png'
const MAP_BG_IMAGE_LOCAL = '/images/红原背景.png'
const MAP_BG_NATIVE_WIDTH = 4318 const MAP_BG_NATIVE_WIDTH = 4318
const MAP_BG_NATIVE_HEIGHT = 1078 const MAP_BG_NATIVE_HEIGHT = 1078
const MAP_BG_CROP_CHINA = { x: 1160, y: 108, width: 2000, height: 828 } const MAP_BG_CROP_CHINA = { x: 1160, y: 108, width: 2000, height: 828 }
@ -195,6 +186,12 @@ const MAP_SURFACE_TEXTURE_HEIGHT = 1152
const MAP_SURFACE_FALLBACK = 'rgb(92, 202, 206)' const MAP_SURFACE_FALLBACK = 'rgb(92, 202, 206)'
const MAP_SURFACE_COLOR = 'rgba(0, 0, 0, 0)' const MAP_SURFACE_COLOR = 'rgba(0, 0, 0, 0)'
const getMapBgImageSrc = (mode = currentMode.value) => (
mode === 'local' ? MAP_BG_IMAGE_LOCAL : MAP_BG_IMAGE_WORLD
)
const mapWorldBgSrc = computed(() => getMapBgImageSrc(currentMode.value))
// path ECharts 沿线 // 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 METEOR_EFFECT_PATH = 'path://M0.5,0 L0.56,0.14 L0.52,1 L0.48,1 L0.44,0.14 Z'
@ -279,7 +276,7 @@ const loadData = async (mapMode = 'china') => {
if (!tradingData) { if (!tradingData) {
return false return false
} }
// //
const mapName = getMapName(mapMode) const mapName = getMapName(mapMode)
echarts.registerMap(mapName, chinaMapData) echarts.registerMap(mapName, chinaMapData)
@ -289,7 +286,7 @@ const loadData = async (mapMode = 'china') => {
mapSurfaceTextureKey = '' mapSurfaceTextureKey = ''
cachedOutlineKey = '' cachedOutlineKey = ''
cachedOutlinePaths = [] cachedOutlinePaths = []
return true return true
} catch (error) { } catch (error) {
console.error('数据加载失败:', error) console.error('数据加载失败:', error)
@ -328,13 +325,13 @@ const generateScatterData = () => {
const modeData = getCurrentModeData() const modeData = getCurrentModeData()
const scatterData = [] const scatterData = []
const citySet = new Set() const citySet = new Set()
// //
modeData.flows.forEach(flow => { modeData.flows.forEach(flow => {
citySet.add(flow.from) citySet.add(flow.from)
citySet.add(flow.to) citySet.add(flow.to)
}) })
// //
citySet.forEach(city => { citySet.forEach(city => {
const coord = tradingData.geoCoordMap[city] const coord = tradingData.geoCoordMap[city]
@ -347,7 +344,7 @@ const generateScatterData = () => {
totalFlow += flow.value totalFlow += flow.value
} }
}) })
scatterData.push({ scatterData.push({
name: city, name: city,
value: coord.concat([totalFlow]), value: coord.concat([totalFlow]),
@ -363,7 +360,7 @@ const generateScatterData = () => {
}) })
} }
}) })
return scatterData return scatterData
} }
@ -393,13 +390,13 @@ const generateEffectScatterData = () => {
const modeData = getCurrentModeData() const modeData = getCurrentModeData()
const effectData = [] const effectData = []
const cityFlows = {} const cityFlows = {}
// //
modeData.flows.forEach(flow => { modeData.flows.forEach(flow => {
cityFlows[flow.from] = (cityFlows[flow.from] || 0) + flow.value cityFlows[flow.from] = (cityFlows[flow.from] || 0) + flow.value
cityFlows[flow.to] = (cityFlows[flow.to] || 0) + flow.value cityFlows[flow.to] = (cityFlows[flow.to] || 0) + flow.value
}) })
// //
const flowThreshold = tradingData?.flowLevels?.medium?.threshold || 40 const flowThreshold = tradingData?.flowLevels?.medium?.threshold || 40
const hubName = getHubName() const hubName = getHubName()
@ -421,7 +418,7 @@ const generateEffectScatterData = () => {
} }
} }
}) })
return effectData return effectData
} }
@ -429,7 +426,7 @@ const generateEffectScatterData = () => {
const getCitySourceData = (cityName) => { const getCitySourceData = (cityName) => {
const modeData = getCurrentModeData() const modeData = getCurrentModeData()
const sources = [] const sources = []
modeData.flows.forEach(flow => { modeData.flows.forEach(flow => {
if (flow.to === cityName) { if (flow.to === cityName) {
sources.push({ sources.push({
@ -439,7 +436,7 @@ const getCitySourceData = (cityName) => {
}) })
} }
}) })
return sources return sources
} }
@ -447,7 +444,7 @@ const getCitySourceData = (cityName) => {
const getCitySalesData = (cityName) => { const getCitySalesData = (cityName) => {
const modeData = getCurrentModeData() const modeData = getCurrentModeData()
const sales = [] const sales = []
modeData.flows.forEach(flow => { modeData.flows.forEach(flow => {
if (flow.from === cityName) { if (flow.from === cityName) {
sales.push({ sales.push({
@ -457,7 +454,7 @@ const getCitySalesData = (cityName) => {
}) })
} }
}) })
return sales return sales
} }
@ -487,7 +484,7 @@ const createPieOption = (data, title) => {
itemWidth: 10, itemWidth: 10,
itemHeight: 8, itemHeight: 8,
itemGap: 8, itemGap: 8,
formatter: function(name) { formatter: function (name) {
const item = data.find(d => d.name === name) const item = data.find(d => d.name === name)
return item ? `${name}(${item.value})` : name return item ? `${name}(${item.value})` : name
} }
@ -526,7 +523,7 @@ const createPieOption = (data, title) => {
const showFloatingPanels = (cityName) => { const showFloatingPanels = (cityName) => {
const sources = getCitySourceData(cityName) const sources = getCitySourceData(cityName)
const sales = getCitySalesData(cityName) const sales = getCitySalesData(cityName)
if (sources.length > 0) { if (sources.length > 0) {
sourceData.value = sources sourceData.value = sources
showSourcePanel.value = true showSourcePanel.value = true
@ -539,7 +536,7 @@ const showFloatingPanels = (cityName) => {
} }
}) })
} }
if (sales.length > 0) { if (sales.length > 0) {
salesData.value = sales salesData.value = sales
showSalesPanel.value = true showSalesPanel.value = true
@ -560,7 +557,7 @@ const hideFloatingPanels = () => {
showSalesPanel.value = false showSalesPanel.value = false
sourceData.value = null sourceData.value = null
salesData.value = null salesData.value = null
if (sourceChartInstance) { if (sourceChartInstance) {
sourceChartInstance.dispose() sourceChartInstance.dispose()
sourceChartInstance = null sourceChartInstance = null
@ -636,7 +633,7 @@ const generateDetailData = (cityName) => {
contactPhone: '134****6789' contactPhone: '134****6789'
} }
] ]
// //
if (cityName === getHubName()) { if (cityName === getHubName()) {
return baseData return baseData
@ -660,7 +657,7 @@ const showDetailData = (cityName) => {
selectedCity.value = cityName selectedCity.value = cityName
detailData.value = generateDetailData(cityName) detailData.value = generateDetailData(cityName)
showDetailPanel.value = true showDetailPanel.value = true
// //
hideFloatingPanels() hideFloatingPanels()
} }
@ -742,19 +739,26 @@ const registerUnitedMap = (mapMode = currentMode.value) => {
return unitedName return unitedName
} }
const getMapBgCrop = () => ( const getMapBgCrop = (image) => {
currentMode.value === 'local' ? MAP_BG_CROP_LOCAL : MAP_BG_CROP_CHINA if (currentMode.value === 'local') {
) if (image?.naturalWidth && image?.naturalHeight) {
return { x: 0, y: 0, width: image.naturalWidth, height: image.naturalHeight }
}
return MAP_BG_CROP_LOCAL
}
return MAP_BG_CROP_CHINA
}
const loadMapBgImage = () => { const loadMapBgImage = (mode = currentMode.value) => {
if (mapBgImage) { const src = getMapBgImageSrc(mode)
return Promise.resolve(mapBgImage) if (mapBgImageCache.has(src)) {
return Promise.resolve(mapBgImageCache.get(src))
} }
if (mapBgImagePromise) { if (mapBgImageLoading.has(src)) {
return mapBgImagePromise return mapBgImageLoading.get(src)
} }
mapBgImagePromise = new Promise((resolve, reject) => { const promise = new Promise((resolve, reject) => {
if (typeof Image === 'undefined') { if (typeof Image === 'undefined') {
reject(new Error('Image is not available')) reject(new Error('Image is not available'))
return return
@ -763,17 +767,19 @@ const loadMapBgImage = () => {
const image = new Image() const image = new Image()
image.decoding = 'async' image.decoding = 'async'
image.onload = () => { image.onload = () => {
mapBgImage = image mapBgImageCache.set(src, image)
mapBgImageLoading.delete(src)
resolve(image) resolve(image)
} }
image.onerror = () => { image.onerror = () => {
mapBgImagePromise = null mapBgImageLoading.delete(src)
reject(new Error(`Failed to load ${MAP_BG_IMAGE}`)) reject(new Error(`Failed to load ${src}`))
} }
image.src = MAP_BG_IMAGE image.src = src
}) })
return mapBgImagePromise mapBgImageLoading.set(src, promise)
return promise
} }
const paintMapSurfaceTexture = (ctx, width, height, image, crop) => { const paintMapSurfaceTexture = (ctx, width, height, image, crop) => {
@ -846,7 +852,7 @@ const createMapSurfaceTexture = (image) => {
return null return null
} }
paintMapSurfaceTexture(ctx, width, height, image, getMapBgCrop()) paintMapSurfaceTexture(ctx, width, height, image, getMapBgCrop(image))
return canvas return canvas
} }
@ -856,7 +862,7 @@ const ensureMapSurfaceTexture = async () => {
return mapSurfaceTexture return mapSurfaceTexture
} }
const image = await loadMapBgImage() const image = await loadMapBgImage(key)
mapSurfaceTexture = createMapSurfaceTexture(image) mapSurfaceTexture = createMapSurfaceTexture(image)
mapSurfaceTextureKey = key mapSurfaceTextureKey = key
return mapSurfaceTexture return mapSurfaceTexture
@ -964,8 +970,8 @@ const stitchBoundarySegments = (segments) => {
return true return true
} }
while (extendTail()) {} while (extendTail()) { }
while (extendHead()) {} while (extendHead()) { }
paths.push(path) paths.push(path)
}) })
@ -1139,6 +1145,24 @@ const buildGeoLayers = (mapName, unitedMapName, mapBounds) => {
] ]
} }
const buildWireframeLineSeries = (name, zlevel, wireframeData, lineStyle) => ({
name,
type: 'lines',
coordinateSystem: 'geo',
geoIndex: WIREFRAME_GEO_INDEX,
zlevel,
polyline: true,
silent: true,
data: wireframeData,
lineStyle: {
cap: 'round',
join: 'round',
shadowOffsetX: 0,
shadowOffsetY: 0,
...lineStyle
}
})
// //
const getChartOption = () => { const getChartOption = () => {
const modeData = getCurrentModeData() const modeData = getCurrentModeData()
@ -1151,7 +1175,7 @@ const getChartOption = () => {
const mapLayout = getMapProjection(mapBounds, 0) const mapLayout = getMapProjection(mapBounds, 0)
const outlinePaths = getWireframePaths() const outlinePaths = getWireframePaths()
const wireframeData = outlinePaths.map((coords) => ({ coords })) const wireframeData = outlinePaths.map((coords) => ({ coords }))
return { return {
backgroundColor: 'transparent', // backgroundColor: 'transparent', //
// title: { // title: {
@ -1177,7 +1201,7 @@ const getChartOption = () => {
color: '#fff', color: '#fff',
fontSize: 13 fontSize: 13
}, },
formatter: function(params) { formatter: function (params) {
if (params.componentType === 'geo') { if (params.componentType === 'geo') {
return `<div style="padding: 8px;"> return `<div style="padding: 8px;">
<strong style="color: #00d4ff;">${params.name}</strong><br/> <strong style="color: #00d4ff;">${params.name}</strong><br/>
@ -1186,16 +1210,16 @@ const getChartOption = () => {
} else if (params.seriesType === 'scatter') { } else if (params.seriesType === 'scatter') {
const value = params.value[2] || 0 const value = params.value[2] || 0
const hubName = getHubName() const hubName = getHubName()
const nodeType = params.name === hubName ? '中心节点' : const nodeType = params.name === hubName ? '中心节点' :
(currentMode.value === 'outflow' ? '销售市场' : '供应来源') (currentMode.value === 'outflow' ? '销售市场' : '供应来源')
return `<div style="padding: 8px;"> return `<div style="padding: 8px;">
<strong style="color: #00d4ff;">${params.name}</strong><br/> <strong style="color: #00d4ff;">${params.name}</strong><br/>
<span style="color: #67c23a;">总流量: ${value} </span><br/> <span style="color: #67c23a;">总流量: ${value} </span><br/>
<span style="color: #8cc8ff;">节点类型: ${nodeType}</span> <span style="color: #8cc8ff;">节点类型: ${nodeType}</span>
</div>` </div>`
} else if (params.seriesType === 'lines') { } else if (params.seriesType === 'lines') {
const lineData = linesData.find(line => const lineData = linesData.find(line =>
line.fromName === params.data.fromName && line.fromName === params.data.fromName &&
line.toName === params.data.toName line.toName === params.data.toName
) )
return `<div style="padding: 8px;"> return `<div style="padding: 8px;">
@ -1243,25 +1267,23 @@ const getChartOption = () => {
disabled: true disabled: true
} }
}, },
{ ...MAP_WIREFRAME_HALO_LAYERS.map((layer, index) => buildWireframeLineSeries(
name: 'mapWireframe', `mapWireframeHalo${index}`,
type: 'lines', 4,
coordinateSystem: 'geo', wireframeData,
geoIndex: WIREFRAME_GEO_INDEX, {
zlevel: 5, color: layer.color,
polyline: true, width: layer.width,
silent: true, opacity: 1
data: wireframeData,
lineStyle: {
color: MAP_WIREFRAME_COLOR,
width: 4.2,
opacity: 0.98,
cap: 'round',
join: 'round',
shadowBlur: 22,
shadowColor: 'rgba(130, 245, 255, 0.78)'
} }
}, )),
buildWireframeLineSeries('mapWireframe', 5, wireframeData, {
color: MAP_WIREFRAME_COLOR,
width: MAP_WIREFRAME_CORE_WIDTH,
opacity: 0.93,
shadowBlur: MAP_WIREFRAME_CORE_SHADOW_BLUR,
shadowColor: MAP_WIREFRAME_CORE_SHADOW_COLOR
}),
{ {
name: '交易城市', name: '交易城市',
type: 'scatter', type: 'scatter',
@ -1347,7 +1369,7 @@ const getChartOption = () => {
geoIndex: GEO_DATA_INDEX, geoIndex: GEO_DATA_INDEX,
zlevel: 6, zlevel: 6,
data: effectScatterData, data: effectScatterData,
symbolSize: function(val) { symbolSize: function (val) {
return Math.max(14, Math.min(26, val[2] / 60)) return Math.max(14, Math.min(26, val[2] / 60))
}, },
showEffectOn: 'render', showEffectOn: 'render',
@ -1390,7 +1412,7 @@ const reloadMapData = async (mapMode) => {
const mapFilePath = getMapFilePath(mapMode) const mapFilePath = getMapFilePath(mapMode)
const mapResponse = await fetch(mapFilePath) const mapResponse = await fetch(mapFilePath)
chinaMapData = await mapResponse.json() chinaMapData = await mapResponse.json()
const mapName = getMapName(mapMode) const mapName = getMapName(mapMode)
echarts.registerMap(mapName, chinaMapData) echarts.registerMap(mapName, chinaMapData)
unitedMapKey = '' unitedMapKey = ''
@ -1399,7 +1421,7 @@ const reloadMapData = async (mapMode) => {
mapSurfaceTextureKey = '' mapSurfaceTextureKey = ''
cachedOutlineKey = '' cachedOutlineKey = ''
cachedOutlinePaths = [] cachedOutlinePaths = []
return true return true
} catch (error) { } catch (error) {
console.error('地图数据重新加载失败:', error) console.error('地图数据重新加载失败:', error)
@ -1414,34 +1436,34 @@ const handleChartResize = () => {
// //
const initChart = async () => { const initChart = async () => {
if (!chartRef.value) return if (!chartRef.value) return
const dataLoaded = await loadData(currentMode.value) const dataLoaded = await loadData(currentMode.value)
if (!dataLoaded) return if (!dataLoaded) return
chartInstance = echarts.init(chartRef.value) chartInstance = echarts.init(chartRef.value)
chartInstance.setOption(getChartOption()) chartInstance.setOption(getChartOption())
await refreshMapSurfaceTexture() await refreshMapSurfaceTexture()
// //
chartInstance.on('mouseover', (params) => { chartInstance.on('mouseover', (params) => {
if (params.seriesType === 'scatter' || params.seriesType === 'effectScatter') { if (params.seriesType === 'scatter' || params.seriesType === 'effectScatter') {
showFloatingPanels(params.name) showFloatingPanels(params.name)
} }
}) })
chartInstance.on('mouseout', (params) => { chartInstance.on('mouseout', (params) => {
if (params.seriesType === 'scatter' || params.seriesType === 'effectScatter') { if (params.seriesType === 'scatter' || params.seriesType === 'effectScatter') {
hideFloatingPanels() hideFloatingPanels()
} }
}) })
// //
chartInstance.on('click', (params) => { chartInstance.on('click', (params) => {
if (params.seriesType === 'scatter' || params.seriesType === 'effectScatter') { if (params.seriesType === 'scatter' || params.seriesType === 'effectScatter') {
showDetailData(params.name) showDetailData(params.name)
} }
}) })
window.addEventListener('resize', handleChartResize) window.addEventListener('resize', handleChartResize)
const mountPoint = chartRef.value.parentElement const mountPoint = chartRef.value.parentElement
if (mountPoint) { if (mountPoint) {
@ -1463,7 +1485,7 @@ const updateChart = async (needReloadMap = false) => {
const mapLoaded = await reloadMapData(currentMode.value) const mapLoaded = await reloadMapData(currentMode.value)
if (!mapLoaded) return if (!mapLoaded) return
} }
chartInstance.setOption(getChartOption(), { chartInstance.setOption(getChartOption(), {
notMerge: false, notMerge: false,
replaceMerge: ['geo', 'series'] replaceMerge: ['geo', 'series']
@ -1476,15 +1498,17 @@ const updateChart = async (needReloadMap = false) => {
// //
watch(currentMode, (newMode, oldMode) => { watch(currentMode, (newMode, oldMode) => {
emit('mode-change', newMode)
nextTick(async () => { nextTick(async () => {
// //
const oldMapName = getMapName(oldMode) const oldMapName = getMapName(oldMode)
const newMapName = getMapName(newMode) const newMapName = getMapName(newMode)
const needReloadMap = oldMapName !== newMapName const needReloadMap = oldMapName !== newMapName
await updateChart(needReloadMap) await updateChart(needReloadMap)
setTimeout(handleChartResize, 580)
}) })
}) }, { immediate: true })
onMounted(() => { onMounted(() => {
initChart() initChart()
@ -1602,6 +1626,38 @@ onUnmounted(() => {
background: transparent; background: transparent;
} }
.map-stage--local {
perspective: 900px;
perspective-origin: 50% 58%;
}
.map-tilt-layer {
position: absolute;
inset: 0;
transform-style: preserve-3d;
transform-origin: 50% 55%;
transition: transform 0.6s cubic-bezier(0.22, 1, 0.36, 1);
will-change: transform;
backface-visibility: hidden;
}
.map-stage--local .map-tilt-layer {
transform: rotateX(22deg) translate3d(0, -6%, 22px) scale(0.92);
}
.map-stage--local .chart-container {
transform: none;
}
.map-tilt-layer .map-world-bg,
.map-tilt-layer .chart-container {
transform-style: preserve-3d;
}
.map-stage--local .map-world-bg {
transform: translate3d(-50%, -50%, 0);
}
.map-world-bg { .map-world-bg {
position: absolute; position: absolute;
width: var(--screen-width, 5120px); width: var(--screen-width, 5120px);
@ -1856,4 +1912,4 @@ onUnmounted(() => {
.detail-content::-webkit-scrollbar-thumb:hover { .detail-content::-webkit-scrollbar-thumb:hover {
background: rgba(0, 212, 255, 0.7); background: rgba(0, 212, 255, 0.7);
} }
</style> </style>

@ -2,7 +2,7 @@
<div class="dashboard-container"> <div class="dashboard-container">
<img <img
class="dashboard-bg" class="dashboard-bg"
src="/images/世界地图背景.png" :src="dashboardBgSrc"
alt="" alt=""
/> />
@ -44,7 +44,7 @@
<!-- 中央地图 --> <!-- 中央地图 -->
<div class="center-section"> <div class="center-section">
<div class="map-container"> <div class="map-container">
<ChinaMap /> <ChinaMap @mode-change="mapCurrentMode = $event" />
</div> </div>
</div> </div>
@ -105,7 +105,7 @@
</template> </template>
<script setup> <script setup>
import { ref, onMounted, nextTick } from 'vue' import { ref, computed, onMounted, nextTick } from 'vue'
import { configManager } from '../utils/config.js' import { configManager } from '../utils/config.js'
import { loadSystemConfig } from '../utils/systemConfig.js' import { loadSystemConfig } from '../utils/systemConfig.js'
import { dataManager } from '../utils/dataManager.js' import { dataManager } from '../utils/dataManager.js'
@ -138,6 +138,13 @@ const isLayoutReady = ref(false) // 控制页面渲染时机
const isSupplyExpanded = ref(false) // const isSupplyExpanded = ref(false) //
const supplyDetailId = ref('') const supplyDetailId = ref('')
const DASHBOARD_BG_WORLD = '/images/世界地图背景.png'
const DASHBOARD_BG_LOCAL = '/images/红原背景.png'
const mapCurrentMode = ref('outflow')
const dashboardBgSrc = computed(() => (
mapCurrentMode.value === 'local' ? DASHBOARD_BG_LOCAL : DASHBOARD_BG_WORLD
))
// / // /
const handleSupplyExpand = (payload) => { const handleSupplyExpand = (payload) => {
if (typeof payload === 'boolean') { if (typeof payload === 'boolean') {

Loading…
Cancel
Save