2026-08-06 9e3911480ea589b73dec7ca944334e742e627f6f
feat(bi): 新增仓库坐标配置功能并优化地图可视化效果

- 添加仓库坐标配置的数据对象、服务接口及实现
- 实现仓库坐标的数据库访问层和批量保存功能
- 增加管理后台API接口用于获取和保存仓库坐标
- 定义仓库坐标不存在的错误码常量
- 重构地图可视化组件,实现多层光效和粒子系统
- 添加飞线光流效果和环境光尘背景粒子
- 优化节点大小映射和标签显示样式
- 实现种子式分布的星尘粒子生成算法
已修改1个文件
233 ■■■■ 文件已修改
src/views/bi/warehouse/chart-options.ts 233 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/bi/warehouse/chart-options.ts
@@ -112,75 +112,213 @@
  };
}
/** 中国地图 + 仓库节点 */
function generateStardust(nodes: Array<{ value: number[] }>, count = 50): Array<{ value: number[] }> {
  const particles: Array<{ value: number[] }> = [];
  // 中国地理范围:经度 73~135,纬度 18~54
  const lngRange = [75, 132];
  const latRange = [20, 52];
  for (let i = 0; i < count; i++) {
    // 种子式分布:部分粒子靠近仓库节点,部分随机
    if (i < count * 0.4 && nodes.length > 0) {
      const seed = nodes[i % nodes.length];
      const [lng, lat] = seed.value;
      particles.push({
        value: [lng + (Math.random() - 0.5) * 6, lat + (Math.random() - 0.5) * 4],
      });
    } else {
      particles.push({
        value: [
          lngRange[0] + Math.random() * (lngRange[1] - lngRange[0]),
          latRange[0] + Math.random() * (latRange[1] - latRange[0]),
        ],
      });
    }
  }
  return particles;
}
/** 中国地图 + 仓库节点(高级光态特效版) */
export function getChinaMapOptions(
  warehouseNodes: Array<{ name: string; value: number[]; stock: number }>,
  flyLines: Array<{ from: string; to: string }> = [],
): EChartsOption {
  const nodeData = warehouseNodes.map((n) => ({
    name: n.name,
    value: [...n.value, n.stock],
  }));
  const maxStock = Math.max(1, ...warehouseNodes.map((n) => Math.abs(n.stock)));
  // 节点光晕尺寸映射
  function glowSize(stock: number, base: number, range: number) {
    return base + (Math.abs(stock) / maxStock) * range;
  }
  const stardust = generateStardust(warehouseNodes, 50);
  return {
    geo: {
      map: 'china',
      roam: false,
      label: { show: false },
      // 底层区域发光:用两层 geo 实现,这里增强主 geo 样式
      itemStyle: {
        areaColor: {
          type: 'linear',
          x: 0, y: 0, x2: 0, y2: 1,
          colorStops: [
            { offset: 0, color: '#0d1f42' },
            { offset: 0.5, color: '#0a1835' },
            { offset: 1, color: '#081028' },
            { offset: 0, color: '#0d1f4a' },
            { offset: 0.4, color: '#0a1838' },
            { offset: 0.7, color: '#08102a' },
            { offset: 1, color: '#060e20' },
          ],
        },
        borderColor: 'rgba(0, 229, 255, 0.22)',
        borderWidth: 1.2,
        shadowColor: 'rgba(0, 229, 255, 0.08)',
        borderColor: 'rgba(0, 229, 255, 0.28)',
        borderWidth: 1.5,
        shadowBlur: 18,
        shadowColor: 'rgba(0, 180, 255, 0.25)',
        shadowOffsetX: 0,
        shadowOffsetY: 3,
        shadowOffsetY: 0,
      },
      emphasis: {
        itemStyle: {
          areaColor: '#132848',
          borderColor: 'rgba(0, 229, 255, 0.45)',
          borderWidth: 1.5,
        disabled: true,
        },
      },
      // 第二层 outline 通过 zlevel 更高的 border 模拟
      regions: [],
    },
    series: [
      // ====== Series 0: 外层光晕(大尺寸、极低透明度) ======
      {
        name: 'outerGlow',
        type: 'scatter',
        coordinateSystem: 'geo',
        data: nodeData,
        symbolSize: (val: number[]) => glowSize(val[2] || 0, 50, 30),
        silent: true,
        itemStyle: {
          color: {
            type: 'radial',
            x: 0.5, y: 0.5, r: 0.5,
            colorStops: [
              { offset: 0, color: 'rgba(0,229,255,0.12)' },
              { offset: 0.3, color: 'rgba(0,229,255,0.05)' },
              { offset: 1, color: 'rgba(0,229,255,0)' },
            ],
          },
        },
        emphasis: { disabled: true },
        zlevel: 1,
      },
      // ====== Series 1: 内层光晕(中等尺寸,脉动式) ======
      {
        name: 'innerGlow',
        type: 'scatter',
        coordinateSystem: 'geo',
        data: nodeData,
        symbolSize: (val: number[]) => glowSize(val[2] || 0, 26, 18),
        silent: true,
        itemStyle: {
          color: {
            type: 'radial',
            x: 0.5, y: 0.5, r: 0.5,
            colorStops: [
              { offset: 0, color: 'rgba(0,255,200,0.22)' },
              { offset: 0.4, color: 'rgba(0,229,255,0.1)' },
              { offset: 1, color: 'rgba(0,150,255,0)' },
            ],
          },
        },
        emphasis: { disabled: true },
        zlevel: 2,
      },
      // ====== Series 2: 主节点(effectScatter 涟漪光效) ======
      {
        name: 'warehouseNodes',
        type: 'effectScatter',
        coordinateSystem: 'geo',
        data: warehouseNodes.map((n) => ({
          name: n.name,
          value: [...n.value, n.stock],
        })),
        symbolSize: (val: number[]) => Math.max(8, Math.min(22, (val[2] || 1) / 1500)),
        data: nodeData,
        symbolSize: (val: number[]) => Math.max(10, Math.min(26, glowSize(val[2] || 0, 10, 12))),
        showEffectOn: 'render',
        rippleEffect: { brushType: 'stroke', scale: 4, period: 4.5 },
        rippleEffect: {
          brushType: 'stroke',
          scale: 5.5,
          period: 3.5,
          color: {
            type: 'radial',
            x: 0.5, y: 0.5, r: 0.5,
            colorStops: [
              { offset: 0, color: C.cyan },
              { offset: 0.4, color: 'rgba(0,229,255,0.5)' },
              { offset: 1, color: 'rgba(0,100,255,0)' },
            ],
          },
        },
        label: {
          show: true,
          position: 'bottom',
          distance: 8,
          distance: 14,
          formatter: '{b}',
          fontSize: 10,
          fontSize: 11,
          fontWeight: 'bold',
          color: C.textPrimary,
          textShadowBlur: 8,
          textShadowColor: 'rgba(0,229,255,0.5)',
        },
        itemStyle: {
          color: {
            type: 'radial',
            x: 0.5, y: 0.5, r: 0.5,
            colorStops: [
              { offset: 0, color: '#fff' },
              { offset: 0.3, color: C.cyan },
              { offset: 1, color: 'rgba(0,229,255,0.3)' },
              { offset: 0, color: '#ffffff' },
              { offset: 0.15, color: '#b5f0ff' },
              { offset: 0.45, color: C.cyan },
              { offset: 1, color: 'rgba(0,120,220,0.3)' },
            ],
          },
          shadowBlur: 20,
          shadowBlur: 28,
          shadowColor: C.cyan,
        },
        zlevel: 1,
        emphasis: {
          scale: 1.6,
          itemStyle: {
            shadowBlur: 40,
            shadowColor: '#fff',
      },
          label: {
            fontSize: 13,
            textShadowBlur: 14,
            textShadowColor: 'rgba(0,229,255,0.8)',
          },
        },
        zlevel: 3,
      },
      // ====== Series 3: 亮核(白色高亮核心) ======
      {
        name: 'nodeCores',
        type: 'scatter',
        coordinateSystem: 'geo',
        data: nodeData,
        symbolSize: (val: number[]) => Math.max(3, Math.min(7, glowSize(val[2] || 0, 3, 3))),
        silent: true,
        itemStyle: {
          color: {
            type: 'radial',
            x: 0.5, y: 0.5, r: 0.5,
            colorStops: [
              { offset: 0, color: '#ffffff' },
              { offset: 0.5, color: 'rgba(255,255,255,0.7)' },
              { offset: 1, color: 'rgba(255,255,255,0)' },
            ],
          },
          shadowBlur: 14,
          shadowColor: '#fff',
        },
        emphasis: { disabled: true },
        zlevel: 4,
      },
      // ====== Series 4: 飞线(光流) ======
      {
        name: 'flyLines',
        type: 'lines',
        coordinateSystem: 'geo',
        data: flyLines.map((f) => {
@@ -194,22 +332,49 @@
            x: 0, y: 0, x2: 1, y2: 1,
            colorStops: [
              { offset: 0, color: C.cyan },
              { offset: 0.5, color: '#5ce6ff' },
              { offset: 1, color: C.green },
            ],
          },
          curveness: 0.25,
          width: 1.2,
          opacity: 0.5,
          curveness: 0.28,
          width: 1.5,
          opacity: 0.6,
          shadowBlur: 8,
          shadowColor: 'rgba(0,229,255,0.4)',
        },
        effect: {
          show: true,
          period: 3,
          trailLength: 0.4,
          period: 2.5,
          trailLength: 0.5,
          symbol: 'arrow',
          symbolSize: 6,
          color: C.green,
          symbolSize: 7,
          color: {
            type: 'radial',
            x: 0.5, y: 0.5, r: 0.5,
            colorStops: [
              { offset: 0, color: '#fff' },
              { offset: 0.5, color: C.green },
              { offset: 1, color: 'rgba(0,255,136,0)' },
            ],
        },
        zlevel: 1,
        },
        zlevel: 2,
      },
      // ====== Series 5: 环境光尘(背景粒子) ======
      {
        name: 'stardust',
        type: 'scatter',
        coordinateSystem: 'geo',
        data: stardust,
        symbolSize: 2,
        silent: true,
        itemStyle: {
          color: 'rgba(100,200,255,0.5)',
          shadowBlur: 4,
          shadowColor: 'rgba(100,180,255,0.5)',
        },
        emphasis: { disabled: true },
        zlevel: 0,
      },
    ],
    tooltip: {