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. 198
      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="control-buttons">
<button
v-for="mode in modes"
:key="mode.key"
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=""
/>
<button v-for="mode in modes" :key="mode.key" 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>
</button>
</div>
</div>
<!-- 左侧来源分析面板 -->
<div
v-if="showSourcePanel && sourceData"
class="source-panel floating-panel"
>
<div v-if="showSourcePanel && sourceData" class="source-panel floating-panel">
<h3>牦牛来源分析</h3>
<div ref="sourceChartRef" class="panel-chart"></div>
</div>
<!-- 右侧销售分析面板 -->
<div
v-if="showSalesPanel && salesData"
class="sales-panel floating-panel"
>
<div v-if="showSalesPanel && salesData" class="sales-panel floating-panel">
<h3>牦牛销售分析</h3>
<div ref="salesChartRef" class="panel-chart"></div>
</div>
<!-- 交易明细浮动框 -->
<div
v-if="showDetailPanel && detailData"
class="detail-panel"
>
<div v-if="showDetailPanel && detailData" class="detail-panel">
<div class="detail-header">
<h3>{{ selectedCity }} - 交易明细统计</h3>
<button @click="closeDetailPanel" class="close-btn">×</button>
@ -78,13 +59,11 @@
</div>
</div>
<div class="map-stage">
image.png <img
class="map-world-bg"
src="/images/世界地图背景.png"
alt=""
/>
<div class="map-stage" :class="{ 'map-stage--local': currentMode === 'local' }">
<div class="map-tilt-layer">
<img class="map-world-bg" :src="mapWorldBgSrc" alt="" />
<div ref="chartRef" class="chart-container"></div>
</div>
<div class="map-legend" aria-hidden="true">
<img class="map-legend-bg" :src="LEGEND_BG" alt="" />
<ul class="map-legend-list">
@ -107,7 +86,7 @@
</template>
<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 BaseCard from './BaseCard.vue'
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 FALLBACK_TRADING_JSON = './yak-trading-data.json'
const emit = defineEmits(['mode-change'])
const chartRef = ref(null)
const sourceChartRef = ref(null)
const salesChartRef = ref(null)
@ -129,8 +110,8 @@ let mapSystemConfig = null
let cachedOutlineKey = ''
let cachedOutlinePaths = []
let unitedMapKey = ''
let mapBgImage = null
let mapBgImagePromise = null
const mapBgImageCache = new Map()
const mapBgImageLoading = new Map()
let mapSurfaceTexture = null
let mapSurfaceTextureKey = ''
@ -177,15 +158,25 @@ 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_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_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'
// 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_HEIGHT = 1078
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_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 沿线
const METEOR_EFFECT_PATH = 'path://M0.5,0 L0.56,0.14 L0.52,1 L0.48,1 L0.44,0.14 Z'
@ -742,19 +739,26 @@ const registerUnitedMap = (mapMode = currentMode.value) => {
return unitedName
}
const getMapBgCrop = () => (
currentMode.value === 'local' ? MAP_BG_CROP_LOCAL : MAP_BG_CROP_CHINA
)
const getMapBgCrop = (image) => {
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 = () => {
if (mapBgImage) {
return Promise.resolve(mapBgImage)
const loadMapBgImage = (mode = currentMode.value) => {
const src = getMapBgImageSrc(mode)
if (mapBgImageCache.has(src)) {
return Promise.resolve(mapBgImageCache.get(src))
}
if (mapBgImagePromise) {
return mapBgImagePromise
if (mapBgImageLoading.has(src)) {
return mapBgImageLoading.get(src)
}
mapBgImagePromise = new Promise((resolve, reject) => {
const promise = new Promise((resolve, reject) => {
if (typeof Image === 'undefined') {
reject(new Error('Image is not available'))
return
@ -763,17 +767,19 @@ const loadMapBgImage = () => {
const image = new Image()
image.decoding = 'async'
image.onload = () => {
mapBgImage = image
mapBgImageCache.set(src, image)
mapBgImageLoading.delete(src)
resolve(image)
}
image.onerror = () => {
mapBgImagePromise = null
reject(new Error(`Failed to load ${MAP_BG_IMAGE}`))
mapBgImageLoading.delete(src)
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) => {
@ -846,7 +852,7 @@ const createMapSurfaceTexture = (image) => {
return null
}
paintMapSurfaceTexture(ctx, width, height, image, getMapBgCrop())
paintMapSurfaceTexture(ctx, width, height, image, getMapBgCrop(image))
return canvas
}
@ -856,7 +862,7 @@ const ensureMapSurfaceTexture = async () => {
return mapSurfaceTexture
}
const image = await loadMapBgImage()
const image = await loadMapBgImage(key)
mapSurfaceTexture = createMapSurfaceTexture(image)
mapSurfaceTextureKey = key
return mapSurfaceTexture
@ -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 modeData = getCurrentModeData()
@ -1243,25 +1267,23 @@ const getChartOption = () => {
disabled: true
}
},
...MAP_WIREFRAME_HALO_LAYERS.map((layer, index) => buildWireframeLineSeries(
`mapWireframeHalo${index}`,
4,
wireframeData,
{
name: 'mapWireframe',
type: 'lines',
coordinateSystem: 'geo',
geoIndex: WIREFRAME_GEO_INDEX,
zlevel: 5,
polyline: true,
silent: true,
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)'
color: layer.color,
width: layer.width,
opacity: 1
}
},
)),
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: '交易城市',
type: 'scatter',
@ -1476,6 +1498,7 @@ const updateChart = async (needReloadMap = false) => {
//
watch(currentMode, (newMode, oldMode) => {
emit('mode-change', newMode)
nextTick(async () => {
//
const oldMapName = getMapName(oldMode)
@ -1483,8 +1506,9 @@ watch(currentMode, (newMode, oldMode) => {
const needReloadMap = oldMapName !== newMapName
await updateChart(needReloadMap)
setTimeout(handleChartResize, 580)
})
})
}, { immediate: true })
onMounted(() => {
initChart()
@ -1602,6 +1626,38 @@ onUnmounted(() => {
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 {
position: absolute;
width: var(--screen-width, 5120px);

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

Loading…
Cancel
Save