<template>
  <BaseCard class="map-card">
    <!-- 顶部切换按钮 -->
    <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="" />
          <span class="mode-btn-text">{{ mode.label }}</span>
        </button>
      </div>
    </div>

    <!-- 左侧来源分析面板 -->
    <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">
      <h3>牦牛销售分析</h3>
      <div ref="salesChartRef" class="panel-chart"></div>
    </div>

    <!-- 交易明细浮动框 -->
    <div v-if="showDetailPanel && detailData" class="detail-panel">
      <div class="detail-header">
        <h3>{{ selectedCity }} - 交易明细统计</h3>
        <button @click="closeDetailPanel" class="close-btn">×</button>
      </div>
      <div class="detail-content">
        <table class="detail-table">
          <thead>
            <tr>
              <th>检疫编号</th>
              <th>货主</th>
              <th>启运地点</th>
              <th>到达地点</th>
              <th>车牌号</th>
              <th>牦牛数量</th>
              <th>联系人</th>
              <th>联系方式</th>
            </tr>
          </thead>
          <tbody>
            <tr v-for="(item, index) in detailData" :key="index">
              <td>{{ item.quarantineNo }}</td>
              <td>{{ item.owner }}</td>
              <td>{{ item.origin }}</td>
              <td>{{ item.destination }}</td>
              <td>{{ item.vehiclePlate }}</td>
              <td>{{ item.yakCount }}头</td>
              <td>{{ item.contactPerson }}</td>
              <td>{{ item.contactPhone }}</td>
            </tr>
          </tbody>
        </table>
      </div>
    </div>

    <div class="map-stage" :class="{ 'map-stage--local': mapStageLocal }">
      <!-- 红原模式整页 dashboard-bg 已铺底，此处不重复显示，避免倾斜后与背景错位 -->
      <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>
      <div class="map-legend" aria-hidden="true">
        <img class="map-legend-bg" :src="LEGEND_BG" alt="" />
        <ul class="map-legend-list">
          <li class="map-legend-item">
            <span class="legend-icon legend-icon-line"></span>
            <span class="legend-label">流向</span>
          </li>
          <li class="map-legend-item">
            <span class="legend-icon legend-icon-city"></span>
            <span class="legend-label">交易城市</span>
          </li>
          <li class="map-legend-item">
            <span class="legend-icon legend-icon-node"></span>
            <span class="legend-label">重要节点</span>
          </li>
        </ul>
      </div>
    </div>
  </BaseCard>
</template>

<script setup>
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'

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)
const currentMode = ref('outflow')
const isMapContentSwitching = ref(false)
const mapStageLocal = computed(() => (
  currentMode.value === 'local' && !isMapContentSwitching.value
))
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 = ''
const mapBgImageCache = new Map()
const mapBgImageLoading = new Map()
let mapSurfaceTexture = null
let mapSurfaceTextureDataUrl = ''
let mapSurfaceTextureKey = ''

// 浮动面板状态
const showSourcePanel = ref(false)
const showSalesPanel = ref(false)
const sourceData = ref(null)
const salesData = ref(null)

// 交易明细面板状态
const showDetailPanel = ref(false)
const detailData = ref(null)
const selectedCity = ref('')

const modes = [
  { key: 'outflow', label: '销售网络分布图' },
  { key: 'inflow', label: '源地供应分布图' },
  { key: 'local', label: '红原出栏分布图' }
]

const BTN_NORMAL = '/images/按钮.png'
const BTN_ACTIVE = '/images/按钮选中.png'
const LEGEND_BG = '/images/图例bg.png'
const GEO_DATA_INDEX = 4
const WIREFRAME_GEO_INDEX = 5
const MAP_ASPECT_SCALE = 0.82
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 = 2.6
const MAP_SHADOW_OFFSET_MID_LOCAL = 1.8
const MAP_SHADOW_OFFSET_NEAR_LOCAL = 1.0
const MAP_SHADOW_OFFSET_LNG_LOCAL = -1.5
const MAP_WIREFRAME_OFFSET = -0.72
const MAP_WIREFRAME_OFFSET_LNG = 0.1
const MAP_WIREFRAME_OFFSET_LOCAL = -1.8
const MAP_WIREFRAME_OFFSET_LNG_LOCAL = 0.6
// 国界描边：青蓝霓虹光晕（对齐设计稿，避免过白过曝）
const MAP_WIREFRAME_COLOR = '#B8ECF6'
const MAP_WIREFRAME_HALO_LAYERS = [
  { width: 10, color: 'rgba(128, 208, 228, 0.06)' },
  { width: 7, color: 'rgba(148, 218, 236, 0.12)' },
  { width: 4.6, color: 'rgba(168, 232, 246, 0.22)' }
]
const MAP_WIREFRAME_CORE_WIDTH = 2.6
const MAP_WIREFRAME_CORE_SHADOW_BLUR = 7
const MAP_WIREFRAME_CORE_SHADOW_COLOR = 'rgba(110, 200, 228, 0.22)'
const MAP_INNER_BORDER = 'rgba(1, 12, 22, 0.82)'
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_MID = '#020408'
const MAP_SHADOW_FILL_NEAR = '#030a12'
const MAP_LABEL_COLOR = '#ffffff'
// 世界地图 / 红原模式底图
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 }
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(49, 183, 183)'
const MAP_SURFACE_COLOR = 'rgba(0, 0, 0, 0)'
const MAP_SURFACE_PAINT_VERSION = 5

// 全国：左亮青 → 右深蓝，地形纹路清晰（color 定色相 + 轻 overlay 提饱和）
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) => (
  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'

// 获取当前模式对应的地图文件路径
const getMapFilePath = (mode) => {
  switch (mode) {
    case 'local':
      return './datas/513233.json'
    default:
      return './china.json'
  }
}

// 获取当前模式对应的地图注册名
const getMapName = (mode) => {
  switch (mode) {
    case 'local':
      return 'hongyuan'
    default:
      return 'china'
  }
}

const getHubName = () => {
  return tradingData?.centerCity?.name || mapSystemConfig?.mapHub?.name || DEFAULT_MAP_HUB.name
}

const buildGeoCoordMap = (systemConfig, fallbackGeoCoordMap = {}) => {
  const hub = systemConfig?.mapHub || DEFAULT_MAP_HUB
  return {
    ...fallbackGeoCoordMap,
    ...(systemConfig?.mapGeoCoordMap || {}),
    [hub.name]: hub.coordinates
  }
}

const loadTradingNetwork = async (systemConfig, fallbackPayload) => {
  const hub = systemConfig?.mapHub || DEFAULT_MAP_HUB
  const buildPayload = (tradingModes) => ({
    centerCity: {
      name: hub.name,
      coordinates: hub.coordinates,
      description: hub.description || fallbackPayload?.centerCity?.description || ''
    },
    geoCoordMap: buildGeoCoordMap(systemConfig, fallbackPayload?.geoCoordMap),
    tradingModes,
    flowLevels: systemConfig?.mapFlowLevels || fallbackPayload?.flowLevels || DEFAULT_MAP_FLOW_LEVELS
  })

  try {
    const response = await fetch(MAP_TRADING_API)
    if (!response.ok) {
      throw new Error(`HTTP ${response.status}`)
    }
    const result = await response.json()
    if (result.code !== 1 || !result.data?.tradingModes) {
      throw new Error(result.message || '地图迁徙接口返回异常')
    }
    return buildPayload(result.data.tradingModes)
  } catch (error) {
    console.warn('加载地图迁徙数据失败，使用本地兜底数据:', error)
    if (!fallbackPayload?.tradingModes) {
      return null
    }
    return buildPayload(fallbackPayload.tradingModes)
  }
}

// 加载地图数据和交易数据
const loadData = async (mapMode = 'china') => {
  try {
    const mapFilePath = getMapFilePath(mapMode)
    const [mapResponse, systemConfig, fallbackResponse] = await Promise.all([
      fetch(mapFilePath),
      loadSystemConfig(),
      fetch(FALLBACK_TRADING_JSON)
    ])
    chinaMapData = await mapResponse.json()
    mapSystemConfig = systemConfig
    const fallbackPayload = await fallbackResponse.json()
    tradingData = await loadTradingNetwork(systemConfig, fallbackPayload)
    if (!tradingData) {
      return false
    }

    // 注册地图
    const mapName = getMapName(mapMode)
    echarts.registerMap(mapName, chinaMapData)
    unitedMapKey = ''
    registerUnitedMap(mapMode)
    mapSurfaceTexture = null
    mapSurfaceTextureKey = ''
    cachedOutlineKey = ''
    cachedOutlinePaths = []

    return true
  } catch (error) {
    console.error('数据加载失败:', error)
    return false
  }
}

// 获取流量等级颜色
const getFlowColor = (value) => {
  const { flowLevels } = tradingData
  if (value >= flowLevels.high.threshold) return flowLevels.high.color
  if (value >= flowLevels.medium.threshold) return flowLevels.medium.color
  return flowLevels.low.color
}

// 获取节点颜色（区分流出和流入）
const getNodeColor = (city) => {
  const hubName = getHubName()
  if (currentMode.value === 'outflow') {
    return city === hubName ? '#FFD048' : '#6ecfff'
  }
  if (currentMode.value === 'inflow') {
    return city === hubName ? '#67C23A' : '#5eb8ff'
  }
  return city === hubName ? '#FFD048' : '#6ecfff'
}

// 获取当前模式的数据
const getCurrentModeData = () => {
  if (!tradingData) return { flows: [], title: '', description: '' }
  return tradingData.tradingModes[currentMode.value]
}

// 生成散点数据
const generateScatterData = () => {
  const modeData = getCurrentModeData()
  const scatterData = []
  const citySet = new Set()

  // 收集所有涉及的城市
  modeData.flows.forEach(flow => {
    citySet.add(flow.from)
    citySet.add(flow.to)
  })

  // 为每个城市生成散点数据
  citySet.forEach(city => {
    const coord = tradingData.geoCoordMap[city]
    if (coord) {
      const hubName = getHubName()
      // 计算该城市的总流量
      let totalFlow = 0
      modeData.flows.forEach(flow => {
        if (flow.from === city || flow.to === city) {
          totalFlow += flow.value
        }
      })

      scatterData.push({
        name: city,
        value: coord.concat([totalFlow]),
        symbol: city === hubName ? 'diamond' : 'circle',
        symbolSize: Math.max(6, Math.min(18, totalFlow / 80)),
        itemStyle: {
          color: getNodeColor(city),
          borderColor: '#fff',
          borderWidth: 1.5,
          shadowBlur: 6,
          shadowColor: getNodeColor(city)
        }
      })
    }
  })

  return scatterData
}

// 生成流向线数据
const generateLinesData = () => {
  const modeData = getCurrentModeData()
  return modeData.flows
    .map((flow) => {
      const fromCoord = tradingData.geoCoordMap[flow.from]
      const toCoord = tradingData.geoCoordMap[flow.to]
      if (!fromCoord || !toCoord) {
        return null
      }
      return {
        fromName: flow.from,
        toName: flow.to,
        coords: [fromCoord, toCoord],
        value: flow.value,
        description: flow.description
      }
    })
    .filter(Boolean)
}

// 生成波纹效果数据
const generateEffectScatterData = () => {
  const modeData = getCurrentModeData()
  const effectData = []
  const cityFlows = {}

  // 统计每个城市的流量
  modeData.flows.forEach(flow => {
    cityFlows[flow.from] = (cityFlows[flow.from] || 0) + flow.value
    cityFlows[flow.to] = (cityFlows[flow.to] || 0) + flow.value
  })

  // 为流量大的城市添加波纹效果
  const flowThreshold = tradingData?.flowLevels?.medium?.threshold || 40
  const hubName = getHubName()
  Object.entries(cityFlows).forEach(([city, totalFlow]) => {
    if (totalFlow > flowThreshold) {
      const coord = tradingData.geoCoordMap[city]
      if (coord) {
        effectData.push({
          name: city,
          value: coord.concat([totalFlow]),
          symbol: city === hubName ? 'diamond' : 'circle',
          symbolSize: Math.max(14, Math.min(26, totalFlow / 60)),
          itemStyle: {
            color: getNodeColor(city),
            shadowBlur: 12,
            shadowColor: getNodeColor(city)
          }
        })
      }
    }
  })

  return effectData
}

// 获取城市来源数据
const getCitySourceData = (cityName) => {
  const modeData = getCurrentModeData()
  const sources = []

  modeData.flows.forEach(flow => {
    if (flow.to === cityName) {
      sources.push({
        name: flow.from,
        value: flow.value,
        description: flow.description
      })
    }
  })

  return sources
}

// 获取城市销售数据
const getCitySalesData = (cityName) => {
  const modeData = getCurrentModeData()
  const sales = []

  modeData.flows.forEach(flow => {
    if (flow.from === cityName) {
      sales.push({
        name: flow.to,
        value: flow.value,
        description: flow.description
      })
    }
  })

  return sales
}

// 创建饼图配置
const createPieOption = (data, title) => {
  return {
    backgroundColor: 'transparent',
    tooltip: {
      trigger: 'item',
      backgroundColor: 'rgba(15, 25, 45, 0.95)',
      borderColor: '#00d4ff',
      borderWidth: 1,
      textStyle: {
        color: '#fff',
        fontSize: 12
      },
      formatter: '{b}: {c}头 ({d}%)'
    },
    legend: {
      orient: 'horizontal',
      left: 'center',
      bottom: '5%',
      textStyle: {
        color: '#8cc8ff',
        fontSize: 9
      },
      itemWidth: 10,
      itemHeight: 8,
      itemGap: 8,
      formatter: function (name) {
        const item = data.find(d => d.name === name)
        return item ? `${name}(${item.value})` : name
      }
    },
    series: [{
      type: 'pie',
      radius: ['30%', '55%'],
      center: ['50%', '40%'],
      data: data.map(item => ({
        name: item.name,
        value: item.value,
        itemStyle: {
          borderColor: '#fff',
          borderWidth: 1
        }
      })),
      emphasis: {
        itemStyle: {
          shadowBlur: 10,
          shadowOffsetX: 0,
          shadowColor: 'rgba(0, 212, 255, 0.5)'
        }
      },
      label: {
        show: false
      },
      labelLine: {
        show: false
      }
    }],
    color: ['#00d4ff', '#67c23a', '#e6a23c', '#ff6b6b', '#9c27b0', '#26c6da', '#ffc107', '#8bc34a']
  }
}

// 显示浮动面板
const showFloatingPanels = (cityName) => {
  const sources = getCitySourceData(cityName)
  const sales = getCitySalesData(cityName)

  if (sources.length > 0) {
    sourceData.value = sources
    showSourcePanel.value = true
    nextTick(() => {
      if (sourceChartRef.value && !sourceChartInstance) {
        sourceChartInstance = echarts.init(sourceChartRef.value)
      }
      if (sourceChartInstance) {
        sourceChartInstance.setOption(createPieOption(sources, '来源分布'))
      }
    })
  }

  if (sales.length > 0) {
    salesData.value = sales
    showSalesPanel.value = true
    nextTick(() => {
      if (salesChartRef.value && !salesChartInstance) {
        salesChartInstance = echarts.init(salesChartRef.value)
      }
      if (salesChartInstance) {
        salesChartInstance.setOption(createPieOption(sales, '销售分布'))
      }
    })
  }
}

// 隐藏浮动面板
const hideFloatingPanels = () => {
  showSourcePanel.value = false
  showSalesPanel.value = false
  sourceData.value = null
  salesData.value = null

  if (sourceChartInstance) {
    sourceChartInstance.dispose()
    sourceChartInstance = null
  }
  if (salesChartInstance) {
    salesChartInstance.dispose()
    salesChartInstance = null
  }
}

// 生成交易明细数据
const generateDetailData = (cityName) => {
  // 模拟交易明细数据
  const baseData = [
    {
      quarantineNo: 'JY2024010001',
      owner: '阿坝县扎西畜牧专业合作社',
      origin: '阿坝县若尔盖草原',
      destination: '成都市双流区',
      vehiclePlate: '川A·12345',
      yakCount: 120,
      contactPerson: '扎西多吉',
      contactPhone: '138****1234'
    },
    {
      quarantineNo: 'JY2024010002',
      owner: '红原县草原牧业有限公司',
      origin: '红原县安曲镇',
      destination: '重庆市渝北区',
      vehiclePlate: '川A·23456',
      yakCount: 95,
      contactPerson: '次仁德勒',
      contactPhone: '139****2345'
    },
    {
      quarantineNo: 'JY2024010003',
      owner: '若尔盖县牧民专业合作社',
      origin: '若尔盖县花湖草原',
      destination: '西安市雁塔区',
      vehiclePlate: '川U·34567',
      yakCount: 80,
      contactPerson: '旺久多杰',
      contactPhone: '136****3456'
    },
    {
      quarantineNo: 'JY2024010004',
      owner: '松潘县高原牧业合作社',
      origin: '松潘县川主寺镇',
      destination: '兰州市城关区',
      vehiclePlate: '川A·45678',
      yakCount: 65,
      contactPerson: '桑杰嘉措',
      contactPhone: '137****4567'
    },
    {
      quarantineNo: 'JY2024010005',
      owner: '马尔康市康巴牧业有限公司',
      origin: '马尔康市梭磨乡',
      destination: '昆明市西山区',
      vehiclePlate: '川A·56789',
      yakCount: 55,
      contactPerson: '格桑旺堆',
      contactPhone: '135****5678'
    },
    {
      quarantineNo: 'JY2024010006',
      owner: '壤塘县藏区牧业专业合作社',
      origin: '壤塘县中壤塘镇',
      destination: '贵阳市观山湖区',
      vehiclePlate: '川A·67890',
      yakCount: 45,
      contactPerson: '洛桑扎西',
      contactPhone: '134****6789'
    }
  ]

  // 根据不同城市返回相应的明细数据
  if (cityName === getHubName()) {
    return baseData
  } else {
    // 为其他城市生成相应的数据
    return baseData.map((item, index) => ({
      ...item,
      quarantineNo: `JY2024${String(Math.floor(Math.random() * 9000) + 1000).substr(0, 4)}${String(index + 1).padStart(3, '0')}`,
      owner: `${cityName}${['畜牧专业合作社', '高原牧业有限公司', '牧民专业合作社', '草原牧业集团'][index % 4]}`,
      origin: `${cityName}${['草原', '镇', '乡', '县'][index % 4]}`,
      destination: `${cityName}${['经开区', '高新区', '市中区', '新城区'][index % 4]}`,
      vehiclePlate: `${['川A', '川B', '川C', '川U'][index % 4]}·${Math.floor(Math.random() * 90000) + 10000}`,
      contactPerson: ['扎西', '次仁', '旺久', '桑杰', '格桑', '洛桑'][index % 6] + ['多吉', '德勒', '多杰', '嘉措', '旺堆', '扎西'][Math.floor(Math.random() * 6)],
      contactPhone: `${['138', '139', '136', '137', '135', '134'][index % 6]}****${Math.floor(Math.random() * 9000) + 1000}`
    }))
  }
}

// 显示交易明细面板
const showDetailData = (cityName) => {
  selectedCity.value = cityName
  detailData.value = generateDetailData(cityName)
  showDetailPanel.value = true

  // 隐藏其他面板
  hideFloatingPanels()
}

// 关闭交易明细面板
const closeDetailPanel = () => {
  showDetailPanel.value = false
  detailData.value = null
  selectedCity.value = ''
}



// 计算地图最佳视野范围
const calculateMapBounds = () => {
  if (currentMode.value === 'local') {
    return { center: [104, 35], zoom: 1.0 }
  }

  return {
    center: [...MAP_ANCHOR_CENTER],
    zoom: MAP_ANCHOR_ZOOM * MAP_ZOOM_FACTOR
  }
}

const getFeatureCenter = (feature) => {
  const cp = feature?.properties?.cp || feature?.properties?.center
  if (Array.isArray(cp) && cp.length >= 2) {
    return cp
  }
  if (Array.isArray(cp) && cp.length === 1) {
    return [cp[0], 35]
  }
  return [104, 35]
}

const getUnitedMapName = (mode = currentMode.value) => `${getMapName(mode)}-united`

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 {
    type: 'FeatureCollection',
    features: [{
      type: 'Feature',
      properties: { name: 'united' },
      geometry: {
        type: 'MultiPolygon',
        coordinates
      }
    }]
  }
}

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 = (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 = (mode = currentMode.value) => {
  const src = getMapBgImageSrc(mode)
  if (mapBgImageCache.has(src)) {
    return Promise.resolve(mapBgImageCache.get(src))
  }
  if (mapBgImageLoading.has(src)) {
    return mapBgImageLoading.get(src)
  }

  const promise = 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 = () => {
      mapBgImageCache.set(src, image)
      mapBgImageLoading.delete(src)
      resolve(image)
    }
    image.onerror = () => {
      mapBgImageLoading.delete(src)
      reject(new Error(`Failed to load ${src}`))
    }
    image.src = src
  })

  mapBgImageLoading.set(src, promise)
  return promise
}

const paintMapSurfaceTexture = (ctx, width, height, image, crop) => {
  const paint = getMapSurfacePaintConfig()
  ctx.clearRect(0, 0, width, height)

  // 1. 地形底图：保留山脉/河谷明暗，勿过度提亮
  ctx.filter = `brightness(${paint.brightness}) contrast(${paint.contrast})`
  ctx.drawImage(
    image,
    crop.x,
    crop.y,
    crop.width,
    crop.height,
    0,
    0,
    width,
    height
  )
  ctx.filter = 'none'

  // 1.5 先整体提亮暗部（避免 color 混合把地形压得太暗）
  if (paint.lift?.enabled) {
    ctx.globalCompositeOperation = paint.lift.composite || 'screen'
    ctx.fillStyle = paint.lift.color
    ctx.fillRect(0, 0, width, height)
  }

  // 2. color 定色相，保留地形明暗
  applySurfacePaintLayer(ctx, width, height, paint.tint)

  // 2.5 轻量 overlay 提亮饱和（避免单层 overlay 洗成一片亮蓝）
  if (paint.satBoost?.enabled) {
    applySurfacePaintLayer(ctx, width, height, paint.satBoost)
  }

  // 3. 东侧/边缘略压暗，增加左右纵深
  if (paint.depth?.enabled) {
    applySurfacePaintLayer(ctx, width, height, paint.depth)
  }

  // 4. 轻量 soft-light 提亮暗部
  if (paint.softLight?.enabled) {
    ctx.globalCompositeOperation = 'soft-light'
    ctx.fillStyle = paint.softLight.color
    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'
}

const createMapSurfaceTexture = (image) => {
  const width = MAP_SURFACE_TEXTURE_WIDTH
  const height = MAP_SURFACE_TEXTURE_HEIGHT
  const canvas = document.createElement('canvas')
  canvas.width = width
  canvas.height = height
  const ctx = canvas.getContext('2d')
  if (!ctx) {
    return null
  }

  paintMapSurfaceTexture(ctx, width, height, image, getMapBgCrop(image))
  return canvas
}

const invalidateMapSurfaceTexture = () => {
  mapSurfaceTexture = null
  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
  }

  const image = await loadMapBgImage(key)
  const canvas = createMapSurfaceTexture(image)
  if (!canvas) {
    return null
  }
  syncMapSurfaceTextureOutput(canvas)
  mapSurfaceTextureKey = key
  return mapSurfaceTexture
}

const getMapSurfaceAreaStyle = () => {
  if (mapSurfaceTextureDataUrl) {
    return {
      areaColor: {
        image: mapSurfaceTextureDataUrl,
        repeat: 'no-repeat'
      }
    }
  }

  return { areaColor: MAP_SURFACE_FALLBACK }
}

const refreshMapSurfaceTexture = async () => {
  if (!chartInstance) {
    return
  }

  try {
    invalidateMapSurfaceTexture()
    await ensureMapSurfaceTexture(true)
    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)
  }
}

const pointKey = ([lng, lat]) => `${lng.toFixed(5)},${lat.toFixed(5)}`

const edgeKey = (a, b) => {
  const ka = pointKey(a)
  const kb = pointKey(b)
  return ka < kb ? `${ka}|${kb}` : `${kb}|${ka}`
}

const forEachOuterRing = (geometry, callback) => {
  if (!geometry) return
  if (geometry.type === 'Polygon') {
    if (geometry.coordinates[0]) {
      callback(geometry.coordinates[0])
    }
  } else if (geometry.type === 'MultiPolygon') {
    geometry.coordinates.forEach((polygon) => {
      if (polygon[0]) {
        callback(polygon[0])
      }
    })
  }
}

const stitchBoundarySegments = (segments) => {
  const adj = new Map()
  segments.forEach(([a, b], idx) => {
    const ka = pointKey(a)
    const kb = pointKey(b)
    if (!adj.has(ka)) adj.set(ka, [])
    if (!adj.has(kb)) adj.set(kb, [])
    adj.get(ka).push({ point: b, idx })
    adj.get(kb).push({ point: a, idx })
  })

  const used = new Set()
  const paths = []

  segments.forEach(([a, b], idx) => {
    if (used.has(idx)) {
      return
    }

    used.add(idx)
    const path = [a, b]

    const extendTail = () => {
      const tailKey = pointKey(path[path.length - 1])
      const candidates = (adj.get(tailKey) || []).filter((item) => !used.has(item.idx))
      if (!candidates.length) {
        return false
      }
      const next = candidates[0]
      used.add(next.idx)
      path.push(next.point)
      return true
    }

    const extendHead = () => {
      const headKey = pointKey(path[0])
      const candidates = (adj.get(headKey) || []).filter((item) => !used.has(item.idx))
      if (!candidates.length) {
        return false
      }
      const next = candidates[0]
      used.add(next.idx)
      path.unshift(next.point)
      return true
    }

    while (extendTail()) { }
    while (extendHead()) { }
    paths.push(path)
  })

  return paths
}

const buildNationalOutlinePaths = (geojson) => {
  if (!geojson?.features?.length) {
    return []
  }

  const edgeCount = new Map()
  const edgeList = []

  geojson.features.forEach((feature) => {
    forEachOuterRing(feature.geometry, (ring) => {
      if (!Array.isArray(ring) || ring.length < 2) {
        return
      }
      for (let i = 0; i < ring.length - 1; i += 1) {
        const a = ring[i]
        const b = ring[i + 1]
        if (!a || !b || (a[0] === b[0] && a[1] === b[1])) {
          continue
        }
        const key = edgeKey(a, b)
        edgeCount.set(key, (edgeCount.get(key) || 0) + 1)
        edgeList.push({ key, a, b })
      }
    })
  })

  const boundarySegments = []
  edgeList.forEach(({ key, a, b }) => {
    if (edgeCount.get(key) === 1) {
      boundarySegments.push([a, b])
    }
  })

  return stitchBoundarySegments(boundarySegments)
}

const getWireframePaths = () => {
  const mapName = getMapName(currentMode.value)
  const cacheKey = `${mapName}-${chinaMapData?.features?.length || 0}`
  if (cacheKey === cachedOutlineKey) {
    return cachedOutlinePaths
  }

  const minPoints = currentMode.value === 'local' ? 8 : 30
  cachedOutlineKey = cacheKey
  cachedOutlinePaths = buildNationalOutlinePaths(chinaMapData)
    .filter((path) => path.length >= minPoints)

  return cachedOutlinePaths
}

const getMapProjection = (mapBounds, latOffset = 0, lngOffset = 0) => {
  const isLocal = currentMode.value === 'local'
  if (isLocal) {
    return {
      aspectScale: MAP_ASPECT_SCALE,
      layoutCenter: [
        `${50 + lngOffset + MAP_GLOBAL_LAYOUT_X}%`,
        `${50 + latOffset + MAP_GLOBAL_LAYOUT_Y}%`
      ],
      layoutSize: MAP_LAYOUT_SIZE
    }
  }
  const center = mapBounds.center || MAP_ANCHOR_CENTER
  const zoom = mapBounds.zoom || MAP_ANCHOR_ZOOM
  return {
    aspectScale: MAP_ASPECT_SCALE,
    zoom,
    center: [
      center[0] + lngOffset + MAP_GLOBAL_OFFSET_LNG,
      center[1] + latOffset + MAP_GLOBAL_OFFSET_LAT
    ]
  }
}

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 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: unitedMapName,
      roam: false,
      ...getMapProjection(mapBounds, 0),
      silent: true,
      zlevel: 1,
      z: 2,
      label: { show: false },
      itemStyle: {
        ...getMapSurfaceAreaStyle(),
        borderColor: 'transparent',
        borderWidth: 0
      },
      emphasis: {
        disabled: true
      }
    },
    {
      map: mapName,
      roam: false,
      ...getMapProjection(mapBounds, 0),
      silent: true,
      zlevel: 1,
      z: 1,
      label: { show: false },
      itemStyle: {
        areaColor: 'rgba(0, 0, 0, 0)',
        borderColor: 'transparent',
        borderWidth: 0
      },
      emphasis: {
        disabled: true
      }
    },
    {
      map: mapName,
      roam: false,
      ...getMapProjection(mapBounds, wireframeOffset, wireframeLngOffset),
      silent: true,
      zlevel: 4,
      z: 1,
      label: { show: false },
      itemStyle: {
        areaColor: 'rgba(0, 0, 0, 0)',
        borderColor: 'transparent',
        borderWidth: 0
      },
      emphasis: {
        disabled: true
      }
    }
  ]
}

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()
  const scatterData = generateScatterData()
  const linesData = generateLinesData()
  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 }))

  return {
    backgroundColor: 'transparent', // 透明背景，与卡片一致
    // title: {
    //   text: modeData.title,
    //   subtext: modeData.description,
    //   left: 'center',
    //   textStyle: {
    //     color: '#ffffff',
    //     fontSize: 20,
    //     fontWeight: 'bold'
    //   },
    //   subtextStyle: {
    //     color: '#8cc8ff',
    //     fontSize: 14
    //   }
    // },
    tooltip: {
      trigger: 'item',
      backgroundColor: 'rgba(15, 25, 45, 0.95)',
      borderColor: '#00d4ff',
      borderWidth: 1,
      textStyle: {
        color: '#fff',
        fontSize: 13
      },
      formatter: function (params) {
        if (params.componentType === 'geo') {
          return `<div style="padding: 8px;">
            <strong style="color: #00d4ff;">${params.name}</strong><br/>
            <span style="color: #8cc8ff;">省份/地区</span>
          </div>`
        } else if (params.seriesType === 'scatter') {
          const value = params.value[2] || 0
          const hubName = getHubName()
          const nodeType = params.name === hubName ? '中心节点' :
            (currentMode.value === 'outflow' ? '销售市场' : '供应来源')
          return `<div style="padding: 8px;">
            <strong style="color: #00d4ff;">${params.name}</strong><br/>
            <span style="color: #67c23a;">总流量: ${value} 头</span><br/>
            <span style="color: #8cc8ff;">节点类型: ${nodeType}</span>
          </div>`
        } else if (params.seriesType === 'lines') {
          const lineData = linesData.find(line =>
            line.fromName === params.data.fromName &&
            line.toName === params.data.toName
          )
          return `<div style="padding: 8px;">
            <strong style="color: #00d4ff;">${params.data.fromName} → ${params.data.toName}</strong><br/>
            <span style="color: #67c23a;">流量: ${params.data.value} 头</span><br/>
            <span style="color: #8cc8ff;">${lineData ? lineData.description : ''}</span>
          </div>`
        } else if (params.seriesType === 'effectScatter') {
          const value = params.value[2] || 0
          return `<div style="padding: 8px;">
            <strong style="color: #00d4ff;">${params.name}</strong><br/>
            <span style="color: #67c23a;">总流量: ${value} 头</span><br/>
            <span style="color: #e6a23c;">重要交易节点</span>
          </div>`
        }
        return params.name
      }
    },
    geo: buildGeoLayers(mapName, unitedMapName, mapBounds),
    series: [
      {
        name: 'mapFill',
        type: 'map',
        map: mapName,
        roam: false,
        ...mapLayout,
        zlevel: 2,
        silent: true,
        selectedMode: false,
        itemStyle: {
          areaColor: MAP_SURFACE_COLOR,
          borderColor: MAP_INNER_BORDER,
          borderWidth: currentMode.value === 'local'
            ? MAP_INNER_BORDER_WIDTH_LOCAL
            : MAP_INNER_BORDER_WIDTH
        },
        label: {
          show: true,
          color: MAP_LABEL_COLOR,
          fontSize: 12,
          fontFamily: 'Microsoft YaHei, sans-serif',
          fontWeight: 'bold',
          textBorderColor: 'rgba(0, 0, 0, 0.5)',
          textBorderWidth: 2
        },
        emphasis: {
          disabled: true
        }
      },
      ...MAP_WIREFRAME_HALO_LAYERS.map((layer, index) => buildWireframeLineSeries(
        `mapWireframeHalo${index}`,
        4,
        wireframeData,
        {
          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',
        coordinateSystem: 'geo',
        geoIndex: GEO_DATA_INDEX,
        zlevel: 6,
        data: scatterData,
        symbolSize: 8,
        label: {
          show: true,
          position: 'top',
          color: MAP_LABEL_COLOR,
          fontSize: 11,
          fontWeight: 'bold',
          formatter: '{b}',
          textBorderColor: 'rgba(0, 0, 0, 0.35)',
          textBorderWidth: 2
        },
        emphasis: {
          label: {
            show: true,
            fontSize: 13,
            color: MAP_LABEL_COLOR
          },
          itemStyle: {
            borderColor: '#00d4ff',
            borderWidth: 3,
            shadowBlur: 15
          }
        }
      },
      {
        name: '流向',
        type: 'lines',
        coordinateSystem: 'geo',
        geoIndex: GEO_DATA_INDEX,
        zlevel: 6,
        data: linesData,
        lineStyle: {
          color: '#a8f6ff',
          width: 1.5,
          opacity: 0.82,
          curveness: 0.32,
          shadowBlur: 10,
          shadowColor: 'rgba(120, 240, 255, 0.65)'
        },
        emphasis: {
          lineStyle: {
            width: 2.5,
            opacity: 1,
            shadowBlur: 6
          }
        }
      },
      {
        name: '流向流光',
        type: 'lines',
        coordinateSystem: 'geo',
        geoIndex: GEO_DATA_INDEX,
        zlevel: 8,
        silent: true,
        animation: false,
        data: linesData,
        effect: {
          show: true,
          constantSpeed: 55,
          symbol: METEOR_EFFECT_PATH,
          symbolSize: [5, 22],
          trailLength: 0.16,
          loop: true,
          color: '#e8fbff'
        },
        lineStyle: {
          width: 0,
          opacity: 0,
          curveness: 0.32
        }
      },
      {
        name: '重要节点',
        type: 'effectScatter',
        coordinateSystem: 'geo',
        geoIndex: GEO_DATA_INDEX,
        zlevel: 6,
        data: effectScatterData,
        symbolSize: function (val) {
          return Math.max(14, Math.min(26, val[2] / 60))
        },
        showEffectOn: 'render',
        rippleEffect: {
          period: 3,
          scale: 3,
          brushType: 'stroke'
        },
        label: {
          show: true,
          position: 'top',
          color: MAP_LABEL_COLOR,
          fontSize: 12,
          fontWeight: 'bold',
          formatter: '{b}',
          textBorderColor: 'rgba(0, 0, 0, 0.35)',
          textBorderWidth: 2
        },
        itemStyle: {
          shadowBlur: 15
        },
        emphasis: {
          scale: true,
          itemStyle: {
            borderColor: '#00d4ff',
            borderWidth: 3,
            shadowBlur: 20
          }
        }
      }
    ],
    animationDuration: 2500,
    animationEasing: 'cubicInOut'
  }
}

// 重新加载地图数据
const reloadMapData = async (mapMode) => {
  try {
    const mapFilePath = getMapFilePath(mapMode)
    const mapResponse = await fetch(mapFilePath)
    chinaMapData = await mapResponse.json()

    const mapName = getMapName(mapMode)
    echarts.registerMap(mapName, chinaMapData)
    unitedMapKey = ''
    registerUnitedMap(mapMode)
    mapSurfaceTexture = null
    mapSurfaceTextureKey = ''
    cachedOutlineKey = ''
    cachedOutlinePaths = []

    return true
  } catch (error) {
    console.error('地图数据重新加载失败:', error)
    return false
  }
}

const handleChartResize = () => {
  chartInstance?.resize()
}

// 初始化图表
const initChart = async () => {
  if (!chartRef.value) return

  const dataLoaded = await loadData(currentMode.value)
  if (!dataLoaded) return

  chartInstance = echarts.init(chartRef.value)
  chartInstance.setOption(getChartOption())
  await refreshMapSurfaceTexture()

  // 添加鼠标事件监听
  chartInstance.on('mouseover', (params) => {
    if (params.seriesType === 'scatter' || params.seriesType === 'effectScatter') {
      showFloatingPanels(params.name)
    }
  })

  chartInstance.on('mouseout', (params) => {
    if (params.seriesType === 'scatter' || params.seriesType === 'effectScatter') {
      hideFloatingPanels()
    }
  })

  // 添加点击事件监听
  chartInstance.on('click', (params) => {
    if (params.seriesType === 'scatter' || params.seriesType === 'effectScatter') {
      showDetailData(params.name)
    }
  })

  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)
  })
}

// 更新图表
const updateChart = async (needReloadMap = false) => {
  if (chartInstance && tradingData) {
    if (needReloadMap) {
      const mapLoaded = await reloadMapData(currentMode.value)
      if (!mapLoaded) return
    }

    chartInstance.setOption(getChartOption(), {
      notMerge: false,
      replaceMerge: ['geo', 'series']
    })
    await refreshMapSurfaceTexture()
    await nextTick()
    handleChartResize()
  }
}

// 监听模式变化：跨地图切换时先隐藏，避免红原扶正与中国地图替换分两段播放
watch(currentMode, async (newMode, oldMode) => {
  emit('mode-change', newMode)

  if (oldMode === undefined) {
    return
  }

  const needReloadMap = getMapName(oldMode) !== getMapName(newMode)
  if (needReloadMap) {
    isMapContentSwitching.value = true
  }

  await updateChart(needReloadMap)
  await nextTick()
  isMapContentSwitching.value = false
  handleChartResize()
  if (needReloadMap) {
    requestAnimationFrame(() => handleChartResize())
  }
}, { flush: 'pre', immediate: true })

onMounted(() => {
  initChart()
})

onUnmounted(() => {
  window.removeEventListener('resize', handleChartResize)
  resizeObserver?.disconnect()
  resizeObserver = null
  if (chartInstance) {
    chartInstance.dispose()
  }
  if (sourceChartInstance) {
    sourceChartInstance.dispose()
  }
  if (salesChartInstance) {
    salesChartInstance.dispose()
  }
})
</script>

<style scoped>
.map-card {
  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;
}

.map-controls {
  position: absolute;
  top: 8px;
  left: 50%;
  transform: translateX(-50%);
  z-index: 1000;
  background: transparent;
  border: none;
  padding: 0;
  box-shadow: none;
}

.control-buttons {
  display: flex;
  gap: 10px;
  justify-content: center;
  align-items: center;
  flex-wrap: nowrap;
}

.mode-btn {
  position: relative;
  width: 235px;
  height: 52px;
  padding: 0;
  border: none;
  background: transparent;
  cursor: pointer;
  flex-shrink: 0;
  transition: transform 0.2s ease, filter 0.2s ease;
}

.mode-btn:hover {
  transform: translateY(-1px);
  filter: brightness(1.08);
}

.mode-btn.active {
  filter: brightness(1.05);
}

.mode-btn-bg {
  position: absolute;
  inset: 0;
  width: 235px;
  height: 52px;
  object-fit: fill;
  pointer-events: none;
  user-select: none;
}

.mode-btn-text {
  position: relative;
  z-index: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 100%;
  font-family: 'Microsoft YaHei', sans-serif;
  font-size: 16px;
  font-weight: 600;
  color: #d9f7ff;
  letter-spacing: 0.5px;
  text-shadow: 0 1px 4px rgba(0, 0, 0, 0.45);
  pointer-events: none;
}

.mode-btn.active .mode-btn-text {
  color: #fff7e8;
}

.map-stage {
  position: absolute;
  inset: 0;
  overflow: hidden;
  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%;
  will-change: transform, opacity;
  backface-visibility: hidden;
}

.map-tilt-layer--switching {
  opacity: 0;
  pointer-events: none;
}

.map-stage--local .map-tilt-layer {
  transform: rotateX(33deg) translate3d(0, -9%, 22px) scale(0.88);
  transition: transform 0.6s cubic-bezier(0.22, 1, 0.36, 1);
}

.map-stage--local .chart-container {
  transform: none;
}

.map-tilt-layer .chart-container {
  transform-style: preserve-3d;
}

.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% !important;
  height: 100% !important;
  min-width: 0;
  min-height: 0;
  z-index: 1;
  transform: translate(-0.4%, 0.6%);
}

.map-legend {
  position: absolute;
  left: 14px;
  bottom: 18px;
  width: 183px;
  height: 147px;
  z-index: 100;
  pointer-events: none;
  user-select: none;
}

.map-legend-bg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: fill;
  pointer-events: none;
}

.map-legend-list {
  position: relative;
  z-index: 1;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 13px;
  height: 100%;
  margin: 0;
  padding: 24px 18px 20px 26px;
  list-style: none;
}

.map-legend-item {
  display: flex;
  align-items: center;
  gap: 10px;
  min-height: 18px;
}

.legend-icon {
  flex-shrink: 0;
}

.legend-icon-line {
  width: 22px;
  height: 2px;
  border-radius: 1px;
  background: linear-gradient(90deg, rgba(126, 232, 255, 0.35), #7ee8ff 45%, rgba(126, 232, 255, 0.35));
  box-shadow: 0 0 6px rgba(110, 232, 255, 0.75);
}

.legend-icon-city {
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: #ffd048;
  border: 1px solid rgba(255, 255, 255, 0.95);
  box-shadow: 0 0 6px rgba(255, 208, 72, 0.65);
}

.legend-icon-node {
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: #6ecfff;
  border: 1px solid rgba(255, 255, 255, 0.95);
  box-shadow: 0 0 6px rgba(110, 207, 255, 0.65);
}

.legend-label {
  font-family: 'Microsoft YaHei', sans-serif;
  font-size: 20px;
  font-weight: 400;
  color: #eefaff;
  line-height: 1;
  letter-spacing: 0.5px;
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.35);
}

.floating-panel {
  position: absolute;
  width: 280px;
  height: 380px;
  background: rgba(15, 25, 45, 0.95);
  border: 1px solid #00d4ff;
  border-radius: 8px;
  backdrop-filter: blur(10px);
  z-index: 1100;
  box-shadow: 0 8px 24px rgba(0, 212, 255, 0.3);
  transition: all 0.3s ease;
  top: 80px;
}

.source-panel {
  left: 20px;
}

.sales-panel {
  right: 20px;
}

.floating-panel h3 {
  color: #fff;
  font-size: 14px;
  font-weight: bold;
  margin: 0;
  padding: 12px 16px 8px;
  border-bottom: 1px solid rgba(0, 212, 255, 0.3);
  text-align: center;
}

.panel-chart {
  width: 100%;
  height: 340px;
  padding: 0;
}

.detail-panel {
  position: absolute;
  left: 50%;
  bottom: 20px;
  transform: translateX(-50%);
  width: 80%;
  max-width: 1200px;
  max-height: 400px;
  background: rgba(15, 25, 45, 0.98);
  border: 1px solid #00d4ff;
  border-radius: 8px;
  backdrop-filter: blur(15px);
  z-index: 1200;
  box-shadow: 0 12px 32px rgba(0, 212, 255, 0.4);
  overflow: hidden;
}

.detail-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 15px 20px;
  border-bottom: 1px solid rgba(0, 212, 255, 0.3);
  background: rgba(0, 212, 255, 0.1);
}

.detail-header h3 {
  color: #fff;
  font-size: 16px;
  font-weight: bold;
  margin: 0;
}

.close-btn {
  background: none;
  border: none;
  color: #8cc8ff;
  font-size: 24px;
  font-weight: bold;
  cursor: pointer;
  padding: 0;
  width: 30px;
  height: 30px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: all 0.3s ease;
}

.close-btn:hover {
  background: rgba(0, 212, 255, 0.2);
  color: #00d4ff;
  transform: scale(1.1);
}

.detail-content {
  max-height: 320px;
  overflow-y: auto;
  padding: 0;
}

.detail-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 13px;
}

.detail-table th {
  background: rgba(0, 212, 255, 0.15);
  color: #00d4ff;
  font-weight: bold;
  padding: 12px 15px;
  text-align: left;
  border-bottom: 1px solid rgba(0, 212, 255, 0.3);
  position: sticky;
  top: 0;
  z-index: 10;
}

.detail-table td {
  color: #8cc8ff;
  padding: 10px 15px;
  border-bottom: 1px solid rgba(0, 212, 255, 0.1);
}

.detail-table tr:hover {
  background: rgba(0, 212, 255, 0.05);
}

.detail-table tr:hover td {
  color: #fff;
}

/* 滚动条样式 */
.detail-content::-webkit-scrollbar {
  width: 6px;
}

.detail-content::-webkit-scrollbar-track {
  background: rgba(0, 212, 255, 0.1);
  border-radius: 3px;
}

.detail-content::-webkit-scrollbar-thumb {
  background: rgba(0, 212, 255, 0.5);
  border-radius: 3px;
}

.detail-content::-webkit-scrollbar-thumb:hover {
  background: rgba(0, 212, 255, 0.7);
}
</style>