@ -59,9 +59,18 @@
< / div >
< / div >
< / div >
< / div >
< div class = "map-stage" : class = "{ 'map-stage--local': currentMode === 'local' }" >
< div class = "map-stage" : class = "{ 'map-stage--local': mapStageLocal }" >
< div class = "map-tilt-layer" >
<!-- 红原模式整页 dashboard - bg 已铺底 , 此处不重复显示 , 避免倾斜后与背景错位 -- >
< img class = "map-world-bg" :src ="mapWorldBgSrc" alt = "" / >
< img
v - show = "currentMode !== 'local'"
class = "map-world-bg"
: src = "mapWorldBgSrc"
alt = ""
/ >
< div
class = "map-tilt-layer"
: class = "{ 'map-tilt-layer--switching': isMapContentSwitching }"
>
< div ref = "chartRef" class = "chart-container" > < / div >
< div ref = "chartRef" class = "chart-container" > < / div >
< / div >
< / div >
< div class = "map-legend" aria -hidden = " true " >
< div class = "map-legend" aria -hidden = " true " >
@ -100,6 +109,10 @@ const chartRef = ref(null)
const sourceChartRef = ref ( null )
const sourceChartRef = ref ( null )
const salesChartRef = ref ( null )
const salesChartRef = ref ( null )
const currentMode = ref ( 'outflow' )
const currentMode = ref ( 'outflow' )
const isMapContentSwitching = ref ( false )
const mapStageLocal = computed ( ( ) => (
currentMode . value === 'local' && ! isMapContentSwitching . value
) )
let chartInstance = null
let chartInstance = null
let sourceChartInstance = null
let sourceChartInstance = null
let salesChartInstance = null
let salesChartInstance = null
@ -113,6 +126,7 @@ let unitedMapKey = ''
const mapBgImageCache = new Map ( )
const mapBgImageCache = new Map ( )
const mapBgImageLoading = new Map ( )
const mapBgImageLoading = new Map ( )
let mapSurfaceTexture = null
let mapSurfaceTexture = null
let mapSurfaceTextureDataUrl = ''
let mapSurfaceTextureKey = ''
let mapSurfaceTextureKey = ''
/ / 浮 动 面 板 状 态
/ / 浮 动 面 板 状 态
@ -150,26 +164,27 @@ const MAP_SHADOW_OFFSET_FAR = 1.85
const MAP _SHADOW _OFFSET _MID = 1.2
const MAP _SHADOW _OFFSET _MID = 1.2
const MAP _SHADOW _OFFSET _NEAR = 0.62
const MAP _SHADOW _OFFSET _NEAR = 0.62
const MAP _SHADOW _OFFSET _LNG = - 0.42
const MAP _SHADOW _OFFSET _LNG = - 0.42
const MAP _SHADOW _OFFSET _FAR _LOCAL = 5
const MAP _SHADOW _OFFSET _FAR _LOCAL = 2.6
const MAP _SHADOW _OFFSET _MID _LOCAL = 3.5
const MAP _SHADOW _OFFSET _MID _LOCAL = 1.8
const MAP _SHADOW _OFFSET _NEAR _LOCAL = 2.2
const MAP _SHADOW _OFFSET _NEAR _LOCAL = 1.0
const MAP _SHADOW _OFFSET _LNG _LOCAL = - 2
const MAP _SHADOW _OFFSET _LNG _LOCAL = - 1.5
const MAP _WIREFRAME _OFFSET = - 0.72
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 = - 1.8
const MAP _WIREFRAME _OFFSET _LNG _LOCAL = 0.6
const MAP _WIREFRAME _OFFSET _LNG _LOCAL = 0.6
/ / 国 界 描 边 : 略 偏 青 蓝 ( 非 纯 白 ) , 光 晕 同 色 渐 弱
/ / 国 界 描 边 : 青 蓝 霓 虹 光 晕 ( 对 齐 设 计 稿 , 避 免 过 白 过 曝 )
const MAP _WIREFRAME _COLOR = '#C8E8F4 '
const MAP _WIREFRAME _COLOR = '#B8ECF6 '
const MAP _WIREFRAME _HALO _LAYERS = [
const MAP _WIREFRAME _HALO _LAYERS = [
{ width : 12 , color : 'rgba(148, 208, 228, 0.08 )' } ,
{ width : 10 , color : 'rgba(128, 208, 228, 0.06 )' } ,
{ width : 8.5 , color : 'rgba(162, 220, 238, 0.15 )' } ,
{ width : 7 , color : 'rgba(148, 218, 236, 0.12 )' } ,
{ width : 5.8 , color : 'rgba(176, 232, 246, 0.25 )' }
{ width : 4.6 , color : 'rgba(168, 232, 246, 0.22 )' }
]
]
const MAP _WIREFRAME _CORE _WIDTH = 3.1
const MAP _WIREFRAME _CORE _WIDTH = 2.6
const MAP _WIREFRAME _CORE _SHADOW _BLUR = 8
const MAP _WIREFRAME _CORE _SHADOW _BLUR = 7
const MAP _WIREFRAME _CORE _SHADOW _COLOR = 'rgba(120, 198, 228, 0.24)'
const MAP _WIREFRAME _CORE _SHADOW _COLOR = 'rgba(110, 200, 228, 0.22)'
const MAP _INNER _BORDER = 'rgba(4, 32, 52, 0.28)'
const MAP _INNER _BORDER = 'rgba(1, 12, 22, 0.82)'
const MAP _INNER _BORDER _WIDTH = 0.45
const MAP _INNER _BORDER _WIDTH = 0.95
const MAP _INNER _BORDER _WIDTH _LOCAL = 1.2
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'
@ -183,8 +198,141 @@ 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 _BG _CROP _LOCAL = { x : 1860 , y : 332 , width : 820 , height : 520 }
const MAP _SURFACE _TEXTURE _WIDTH = 1536
const MAP _SURFACE _TEXTURE _WIDTH = 1536
const MAP _SURFACE _TEXTURE _HEIGHT = 1152
const MAP _SURFACE _TEXTURE _HEIGHT = 1152
const MAP _SURFACE _FALLBACK = 'rgb(92, 202, 206 )'
const MAP _SURFACE _FALLBACK = 'rgb(49, 183, 183 )'
const MAP _SURFACE _COLOR = 'rgba(0, 0, 0, 0)'
const MAP _SURFACE _COLOR = 'rgba(0, 0, 0, 0)'
const MAP _SURFACE _PAINT _VERSION = 5
/ / 全 国 : 左 亮 青 → 右 深 蓝 , 地 形 纹 路 清 晰 ( c o l o r 定 色 相 + 轻 o v e r l a y 提 饱 和 )
const WORLD _SURFACE _PAINT = {
brightness : 1.22 ,
contrast : 1.26 ,
lift : {
enabled : true ,
composite : 'screen' ,
color : 'rgba(220, 248, 244, 0.16)'
} ,
tint : {
composite : 'color' ,
mode : 'linear' ,
linearX0 : 0 ,
linearY0 : 0.5 ,
linearX1 : 1 ,
linearY1 : 0.5 ,
stops : [
{ stop : 0 , color : 'rgb(85, 212, 205)' } ,
{ stop : 0.45 , color : 'rgb(52, 175, 188)' } ,
{ stop : 1 , color : 'rgb(14, 131, 184)' }
]
} ,
satBoost : {
enabled : true ,
composite : 'overlay' ,
mode : 'linear' ,
linearX0 : 0 ,
linearY0 : 0.5 ,
linearX1 : 1 ,
linearY1 : 0.5 ,
stops : [
{ stop : 0 , color : 'rgba(145, 245, 236, 0.22)' } ,
{ stop : 0.5 , color : 'rgba(80, 205, 208, 0.12)' } ,
{ stop : 1 , color : 'rgba(18, 135, 180, 0.16)' }
]
} ,
depth : { enabled : false } ,
softLight : { enabled : true , color : 'rgba(125, 222, 215, 0.09)' } ,
glow : {
enabled : true ,
centerX : 0.34 ,
centerY : 0.44 ,
radius : 0.64 ,
stops : [
{ stop : 0 , color : 'rgba(228, 252, 248, 0.22)' } ,
{ stop : 0.48 , color : 'rgba(185, 238, 232, 0.08)' } ,
{ stop : 1 , color : 'rgba(0, 0, 0, 0)' }
]
}
}
/ / 红 原 : 中 西 部 亮 、 边 缘 深 蓝 ( 对 齐 设 计 稿 径 向 )
const LOCAL _SURFACE _PAINT = {
brightness : 1.22 ,
contrast : 1.28 ,
lift : {
enabled : true ,
composite : 'screen' ,
color : 'rgba(232, 252, 248, 0.18)'
} ,
tint : {
composite : 'color' ,
mode : 'radial' ,
centerX : 0.3 ,
centerY : 0.47 ,
radius : 0.78 ,
stops : [
{ stop : 0 , color : 'rgb(188, 246, 238)' } ,
{ stop : 0.28 , color : 'rgb(82, 200, 205)' } ,
{ stop : 0.62 , color : 'rgb(35, 150, 178)' } ,
{ stop : 1 , color : 'rgb(14, 131, 184)' }
]
} ,
satBoost : {
enabled : true ,
composite : 'overlay' ,
mode : 'radial' ,
centerX : 0.3 ,
centerY : 0.47 ,
radius : 0.74 ,
stops : [
{ stop : 0 , color : 'rgba(225, 252, 246, 0.34)' } ,
{ stop : 0.35 , color : 'rgba(98, 218, 212, 0.17)' } ,
{ stop : 1 , color : 'rgba(14, 131, 184, 0.22)' }
]
} ,
depth : { enabled : false } ,
softLight : { enabled : true , color : 'rgba(112, 220, 212, 0.1)' } ,
glow : {
enabled : true ,
centerX : 0.28 ,
centerY : 0.46 ,
radius : 0.5 ,
stops : [
{ stop : 0 , color : 'rgba(255, 255, 253, 0.36)' } ,
{ stop : 0.5 , color : 'rgba(205, 242, 236, 0.11)' } ,
{ stop : 1 , color : 'rgba(0, 0, 0, 0)' }
]
}
}
const getMapSurfacePaintConfig = ( ) => (
currentMode . value === 'local' ? LOCAL _SURFACE _PAINT : WORLD _SURFACE _PAINT
)
const createSurfacePaintGradient = ( ctx , width , height , layer ) => {
if ( layer . mode === 'linear' ) {
return ctx . createLinearGradient (
width * layer . linearX0 ,
height * layer . linearY0 ,
width * layer . linearX1 ,
height * layer . linearY1
)
}
return ctx . createRadialGradient (
width * layer . centerX ,
height * layer . centerY ,
0 ,
width * layer . centerX ,
height * layer . centerY ,
Math . max ( width , height ) * layer . radius
)
}
const applySurfacePaintLayer = ( ctx , width , height , layer ) => {
const gradient = createSurfacePaintGradient ( ctx , width , height , layer )
layer . stops . forEach ( ( { stop , color } ) => gradient . addColorStop ( stop , color ) )
ctx . globalCompositeOperation = layer . composite
ctx . fillStyle = gradient
ctx . fillRect ( 0 , 0 , width , height )
}
const getMapBgImageSrc = ( mode = currentMode . value ) => (
const getMapBgImageSrc = ( mode = currentMode . value ) => (
mode === 'local' ? MAP _BG _IMAGE _LOCAL : MAP _BG _IMAGE _WORLD
mode === 'local' ? MAP _BG _IMAGE _LOCAL : MAP _BG _IMAGE _WORLD
@ -783,10 +931,11 @@ const loadMapBgImage = (mode = currentMode.value) => {
}
}
const paintMapSurfaceTexture = ( ctx , width , height , image , crop ) => {
const paintMapSurfaceTexture = ( ctx , width , height , image , crop ) => {
const paint = getMapSurfacePaintConfig ( )
ctx . clearRect ( 0 , 0 , width , height )
ctx . clearRect ( 0 , 0 , width , height )
/ / 1 . 地 形 纹 理 打 底 ( 略 提 亮 对 比 , 保 证 纹 路 可 见 )
/ / 1 . 地 形 底 图 : 保 留 山 脉 / 河 谷 明 暗 , 勿 过 度 提 亮
ctx . filter = 'brightness(1.28) contrast(1.18)'
ctx . filter = ` brightness( ${ paint . brightness } ) contrast( ${ paint . contrast } ) `
ctx . drawImage (
ctx . drawImage (
image ,
image ,
crop . x ,
crop . x ,
@ -800,43 +949,46 @@ const paintMapSurfaceTexture = (ctx, width, height, image, crop) => {
)
)
ctx . filter = 'none'
ctx . filter = 'none'
/ / 2 . o v e r l a y 青 蓝 染 色 : G / B 均 衡 , 介 于 青 色 与 蓝 色 之 间
/ / 1 . 5 先 整 体 提 亮 暗 部 ( 避 免 c o l o r 混 合 把 地 形 压 得 太 暗 )
ctx . globalCompositeOperation = 'overlay'
if ( paint . lift ? . enabled ) {
const tintGradient = ctx . createRadialGradient (
ctx . globalCompositeOperation = paint . lift . composite || 'screen'
width * 0.44 ,
ctx . fillStyle = paint . lift . color
height * 0.48 ,
ctx . fillRect ( 0 , 0 , width , height )
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 . s o f t - l i g h t 补 一 层 青 蓝 , 提 亮 暗 部
/ / 2 . c o l o r 定 色 相 , 保 留 地 形 明 暗
ctx . globalCompositeOperation = 'soft-light'
applySurfacePaintLayer ( ctx , width , height , paint . tint )
ctx . fillStyle = 'rgba(118, 218, 214, 0.36)'
ctx . fillRect ( 0 , 0 , width , height )
/ / 4 . s c r e e n 中 心 高 光
/ / 2 . 5 轻 量 o v e r l a y 提 亮 饱 和 ( 避 免 单 层 o v e r l a y 洗 成 一 片 亮 蓝 )
ctx . globalCompositeOperation = 'screen'
if ( paint . satBoost ? . enabled ) {
const glowGradient = ctx . createRadialGradient (
applySurfacePaintLayer ( ctx , width , height , paint . satBoost )
width * 0.43 ,
}
height * 0.46 ,
0 ,
/ / 3 . 东 侧 / 边 缘 略 压 暗 , 增 加 左 右 纵 深
width * 0.43 ,
if ( paint . depth ? . enabled ) {
height * 0.46 ,
applySurfacePaintLayer ( ctx , width , height , paint . depth )
Math . max ( width , height ) * 0.58
}
)
glowGradient . addColorStop ( 0 , 'rgba(205, 248, 242, 0.34)' )
/ / 4 . 轻 量 s o f t - l i g h t 提 亮 暗 部
glowGradient . addColorStop ( 0.55 , 'rgba(175, 235, 228, 0.14)' )
if ( paint . softLight ? . enabled ) {
glowGradient . addColorStop ( 1 , 'rgba(175, 235, 228, 0)' )
ctx . globalCompositeOperation = 'soft-light'
ctx . fillStyle = glowGradient
ctx . fillStyle = paint . softLight . color
ctx . fillRect ( 0 , 0 , width , height )
ctx . fillRect ( 0 , 0 , width , height )
}
/ / 5 . 中 心 柔 和 高 光
if ( paint . glow ? . enabled ) {
ctx . globalCompositeOperation = 'screen'
const glowGradient = createSurfacePaintGradient ( ctx , width , height , {
mode : 'radial' ,
centerX : paint . glow . centerX ,
centerY : paint . glow . centerY ,
radius : paint . glow . radius
} )
paint . glow . stops . forEach ( ( { stop , color } ) => glowGradient . addColorStop ( stop , color ) )
ctx . fillStyle = glowGradient
ctx . fillRect ( 0 , 0 , width , height )
}
ctx . globalCompositeOperation = 'source-over'
ctx . globalCompositeOperation = 'source-over'
}
}
@ -856,23 +1008,39 @@ const createMapSurfaceTexture = (image) => {
return canvas
return canvas
}
}
const ensureMapSurfaceTexture = async ( ) => {
const invalidateMapSurfaceTexture = ( ) => {
const key = currentMode . value
mapSurfaceTexture = null
if ( mapSurfaceTexture && mapSurfaceTextureKey === key ) {
mapSurfaceTextureDataUrl = ''
mapSurfaceTextureKey = ''
}
const syncMapSurfaceTextureOutput = ( canvas ) => {
mapSurfaceTexture = canvas
mapSurfaceTextureDataUrl = canvas ? canvas . toDataURL ( 'image/png' ) : ''
return mapSurfaceTexture
}
const ensureMapSurfaceTexture = async ( force = false ) => {
const key = ` ${ currentMode . value } @v ${ MAP _SURFACE _PAINT _VERSION } `
if ( ! force && mapSurfaceTexture && mapSurfaceTextureKey === key ) {
return mapSurfaceTexture
return mapSurfaceTexture
}
}
const image = await loadMapBgImage ( key )
const image = await loadMapBgImage ( key )
mapSurfaceTexture = createMapSurfaceTexture ( image )
const canvas = createMapSurfaceTexture ( image )
if ( ! canvas ) {
return null
}
syncMapSurfaceTextureOutput ( canvas )
mapSurfaceTextureKey = key
mapSurfaceTextureKey = key
return mapSurfaceTexture
return mapSurfaceTexture
}
}
const getMapSurfaceAreaStyle = ( ) => {
const getMapSurfaceAreaStyle = ( ) => {
if ( mapSurfaceTexture ) {
if ( mapSurfaceTextureDataUrl ) {
return {
return {
areaColor : {
areaColor : {
image : mapSurfaceTexture ,
image : mapSurfaceTextureDataUrl ,
repeat : 'no-repeat'
repeat : 'no-repeat'
}
}
}
}
@ -887,7 +1055,8 @@ const refreshMapSurfaceTexture = async () => {
}
}
try {
try {
await ensureMapSurfaceTexture ( )
invalidateMapSurfaceTexture ( )
await ensureMapSurfaceTexture ( true )
const mapBounds = calculateMapBounds ( )
const mapBounds = calculateMapBounds ( )
const mapName = getMapName ( currentMode . value )
const mapName = getMapName ( currentMode . value )
const unitedMapName = registerUnitedMap ( currentMode . value )
const unitedMapName = registerUnitedMap ( currentMode . value )
@ -1252,7 +1421,9 @@ const getChartOption = () => {
itemStyle : {
itemStyle : {
areaColor : MAP _SURFACE _COLOR ,
areaColor : MAP _SURFACE _COLOR ,
borderColor : MAP _INNER _BORDER ,
borderColor : MAP _INNER _BORDER ,
borderWidth : MAP _INNER _BORDER _WIDTH
borderWidth : currentMode . value === 'local'
? MAP _INNER _BORDER _WIDTH _LOCAL
: MAP _INNER _BORDER _WIDTH
} ,
} ,
label : {
label : {
show : true ,
show : true ,
@ -1496,19 +1667,27 @@ const updateChart = async (needReloadMap = false) => {
}
}
}
}
/ / 监 听 模 式 变 化
/ / 监 听 模 式 变 化 : 跨 地 图 切 换 时 先 隐 藏 , 避 免 红 原 扶 正 与 中 国 地 图 替 换 分 两 段 播 放
watch ( currentMode , ( newMode , oldMode ) => {
watch ( currentMode , async ( newMode , oldMode ) => {
emit ( 'mode-change' , newMode )
emit ( 'mode-change' , newMode )
nextTick ( async ( ) => {
/ / 判 断 是 否 需 要 重 新 加 载 地 图 数 据
if ( oldMode === undefined ) {
const oldMapName = getMapName ( oldMode )
return
const newMapName = getMapName ( newMode )
}
const needReloadMap = oldMapName !== newMapName
const needReloadMap = getMapName ( oldMode ) !== getMapName ( newMode )
await updateChart ( needReloadMap )
if ( needReloadMap ) {
setTimeout ( handleChartResize , 580 )
isMapContentSwitching . value = true
} )
}
} , { immediate : true } )
await updateChart ( needReloadMap )
await nextTick ( )
isMapContentSwitching . value = false
handleChartResize ( )
if ( needReloadMap ) {
requestAnimationFrame ( ( ) => handleChartResize ( ) )
}
} , { flush : 'pre' , immediate : true } )
onMounted ( ( ) => {
onMounted ( ( ) => {
initChart ( )
initChart ( )
@ -1636,28 +1815,28 @@ onUnmounted(() => {
inset : 0 ;
inset : 0 ;
transform - style : preserve - 3 d ;
transform - style : preserve - 3 d ;
transform - origin : 50 % 55 % ;
transform - origin : 50 % 55 % ;
transition : transform 0.6 s cubic - bezier ( 0.22 , 1 , 0.36 , 1 ) ;
will - change : transform , opacity ;
will - change : transform ;
backface - visibility : hidden ;
backface - visibility : hidden ;
}
}
. map - tilt - layer -- switching {
opacity : 0 ;
pointer - events : none ;
}
. map - stage -- local . map - tilt - layer {
. map - stage -- local . map - tilt - layer {
transform : rotateX ( 22 deg ) translate3d ( 0 , - 6 % , 22 px ) scale ( 0.92 ) ;
transform : rotateX ( 33 deg ) translate3d ( 0 , - 9 % , 22 px ) scale ( 0.88 ) ;
transition : transform 0.6 s cubic - bezier ( 0.22 , 1 , 0.36 , 1 ) ;
}
}
. map - stage -- local . chart - container {
. map - stage -- local . chart - container {
transform : none ;
transform : none ;
}
}
. map - tilt - layer . map - world - bg ,
. map - tilt - layer . chart - container {
. map - tilt - layer . chart - container {
transform - style : preserve - 3 d ;
transform - style : preserve - 3 d ;
}
}
. 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 , 5120 px ) ;
width : var ( -- screen - width , 5120 px ) ;