#
Junjie
7 小时以前 c5d7868e9e5fb8013edb088a70e75fc83d575690
#
3个文件已修改
1913 ■■■■ 已修改文件
src/main/java/com/zy/asrs/controller/ConsoleController.java 117 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/webapp/components/MapCanvas.js 12 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/webapp/views/locMap/locMap.html 1784 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/asrs/controller/ConsoleController.java
@@ -350,21 +350,126 @@
     */
    @GetMapping("/map/{lev}/auth")
    public R getLocMap(@PathVariable Integer lev) {
        Object object = redisUtil.get(RedisKeyType.LOC_MAP_BASE.key);
        List<List<HashMap<String, Object>>> mapNodeList = null;
        if (object != null) {
            mapNodeList = (List<List<HashMap<String, Object>>>) object;
        List<List<HashMap<String, Object>>> mapNodeList = getLocMapBaseSnapshot();
        if (mapNodeList == null || mapNodeList.isEmpty()) {
            return R.error("请先初始化地图");
        }
        List<LocMast> locMastList = locMastService.selectLocByLev(lev);
        boolean needRefreshBase = false;
        for (LocMast locMast : locMastList) {
            String[] locType = locMast.getLocType().split("-");
            HashMap<String, Object> mapNode = mapNodeList.get(Integer.parseInt(locType[0])).get(Integer.parseInt(locType[1]));
            Integer[] pos = parseLocTypePos(locMast.getLocType());
            if (pos == null || !isValidMapNodeIndex(mapNodeList, pos[0], pos[1])) {
                needRefreshBase = true;
                break;
            }
        }
        if (needRefreshBase) {
            refreshLocMapBaseCache();
            mapNodeList = getLocMapBaseSnapshot();
        }
        for (LocMast locMast : locMastList) {
            Integer[] pos = parseLocTypePos(locMast.getLocType());
            if (pos == null || !isValidMapNodeIndex(mapNodeList, pos[0], pos[1])) {
                log.warn("locMap skip invalid locType, locNo={}, locType={}", locMast.getLocNo(), locMast.getLocType());
                continue;
            }
            HashMap<String, Object> mapNode = mapNodeList.get(pos[0]).get(pos[1]);
            mapNode.put("locSts", locMast.getLocSts());
            mapNode.put("locNo", locMast.getLocNo());
        }
        return R.ok().add(mapNodeList);
    }
    private List<List<HashMap<String, Object>>> getLocMapBaseSnapshot() {
        Object object = redisUtil.get(RedisKeyType.LOC_MAP_BASE.key);
        if (!(object instanceof List)) {
            return buildLocMapBase();
        }
        return cloneMapNodeList((List<List<HashMap<String, Object>>>) object);
    }
    private void refreshLocMapBaseCache() {
        List<List<HashMap<String, Object>>> base = buildLocMapBase();
        if (base != null && !base.isEmpty()) {
            redisUtil.set(RedisKeyType.LOC_MAP_BASE.key, base);
        }
    }
    private List<List<HashMap<String, Object>>> buildLocMapBase() {
        BasMap basMap = basMapService.selectOne(new EntityWrapper<BasMap>().eq("lev", 1));
        if (Cools.isEmpty(basMap) || Cools.isEmpty(basMap.getData())) {
            return null;
        }
        List<List<JSONObject>> dataList = JSON.parseObject(basMap.getData(), List.class);
        List<List<HashMap<String, Object>>> mapNodeList = new ArrayList<>();
        for (int i = 0; i < dataList.size(); i++) {
            List<JSONObject> row = dataList.get(i);
            List<HashMap<String, Object>> mapNodeRow = new ArrayList<>();
            for (int j = 0; j < row.size(); j++) {
                JSONObject map = row.get(j);
                HashMap<String, Object> mapNode = new HashMap<>();
                mapNode.put("id", i + "-" + j);
                String nodeType = map.getString("type");
                mapNode.put("type", nodeType);
                if ("shelf".equals(nodeType)) {
                    mapNode.put("value", MapNodeType.NORMAL_PATH.id);
                } else if ("devp".equals(nodeType)) {
                    mapNode.put("value", MapNodeType.DISABLE.id);
                } else if ("crn".equals(nodeType) || "dualCrn".equals(nodeType) || "rgv".equals(nodeType)) {
                    mapNode.put("value", MapNodeType.MAIN_PATH.id);
                } else {
                    mapNode.put("value", MapNodeType.DISABLE.id);
                }
                mapNodeRow.add(mapNode);
            }
            mapNodeList.add(mapNodeRow);
        }
        return mapNodeList;
    }
    private List<List<HashMap<String, Object>>> cloneMapNodeList(List<List<HashMap<String, Object>>> source) {
        List<List<HashMap<String, Object>>> copy = new ArrayList<>();
        for (List<HashMap<String, Object>> row : source) {
            List<HashMap<String, Object>> rowCopy = new ArrayList<>();
            if (row != null) {
                for (HashMap<String, Object> item : row) {
                    rowCopy.add(item == null ? new HashMap<>() : new HashMap<>(item));
                }
            }
            copy.add(rowCopy);
        }
        return copy;
    }
    private Integer[] parseLocTypePos(String locType) {
        if (Cools.isEmpty(locType)) {
            return null;
        }
        String[] parts = locType.split("-");
        if (parts.length < 2) {
            return null;
        }
        try {
            return new Integer[]{Integer.parseInt(parts[0]), Integer.parseInt(parts[1])};
        } catch (NumberFormatException e) {
            log.warn("parse locType fail, locType={}", locType, e);
            return null;
        }
    }
    private boolean isValidMapNodeIndex(List<List<HashMap<String, Object>>> mapNodeList, Integer rowIdx, Integer colIdx) {
        if (mapNodeList == null || rowIdx == null || colIdx == null || rowIdx < 0 || colIdx < 0 || rowIdx >= mapNodeList.size()) {
            return false;
        }
        List<HashMap<String, Object>> row = mapNodeList.get(rowIdx);
        return row != null && colIdx < row.size();
    }
    @RequestMapping(value = "/map/locList")
    public R mapLocList() {
        Object object = redisUtil.get(RedisKeyType.LOC_MAST_MAP_LIST.key);
src/main/webapp/components/MapCanvas.js
@@ -26,6 +26,7 @@
        <div v-show="showMapToolPanel" :style="mapToolBarStyle()">
          <div :style="mapToolRowStyle()">
            <button type="button" @click="toggleStationDirection" :style="mapToolButtonStyle(showStationDirection)">{{ showStationDirection ? '隐藏站点方向' : '显示站点方向' }}</button>
            <button type="button" @click="resetMapView" :style="mapToolButtonStyle(false)">重置视图</button>
            <button type="button" @click="rotateMap" :style="mapToolButtonStyle(false)">旋转</button>
            <button type="button" @click="toggleMirror" :style="mapToolButtonStyle(mapMirrorX)">{{ mapMirrorX ? '取消镜像' : '镜像' }}</button>
          </div>
@@ -2408,6 +2409,11 @@
      this.applyMapTransform(true);
      this.saveMapTransformConfig();
    },
    resetMapView() {
      this.fitStageToContent();
      this.scheduleAdjustLabels();
      this.scheduleShelfChunkCulling();
    },
    toggleStationDirection() {
      this.showStationDirection = !this.showStationDirection;
      this.applyStationDirectionVisibility();
@@ -2640,12 +2646,6 @@
    }
  }
});
src/main/webapp/views/locMap/locMap.html
@@ -3,486 +3,1218 @@
<head>
  <meta charset="UTF-8">
  <title>库位地图</title>
  <link rel="stylesheet" href="../../static/css/animate.min.css">
  <link rel="stylesheet" href="../../static/vue/element/element.css">
  <link rel="stylesheet" href="../../static/css/console_vue.css">
  <link rel="stylesheet" href="../../static/css/toggle-switch.css">
  <script type="text/javascript" src="../../static/js/jquery/jquery-3.3.1.min.js"></script>
  <script type="text/javascript" src="../../static/layui/layui.js"></script>
  <script type="text/javascript" src="../../static/js/handlebars/handlebars-v4.5.3.js"></script>
  <script type="text/javascript" src="../../static/js/common.js"></script>
  <script type="text/javascript" src="../../static/vue/js/vue.min.js"></script>
  <script type="text/javascript" src="../../static/vue/element/element.js"></script>
  <script src="../../static/js/gsap.min.js"></script>
  <script src="../../static/js/pixi-legacy.min.js"></script>
  <style>
    *{
    html, body, #app {
      width: 100%;
      height: 100%;
      margin: 0;
      padding: 0;
      overflow: hidden;
    }
    body {
      background: linear-gradient(180deg, #eef4f8 0%, #e7edf4 100%);
      font-family: "Helvetica Neue", Arial, sans-serif;
    }
    * {
      box-sizing: border-box;
    }
    .locmap-shell {
      position: relative;
      width: 100%;
      height: 100%;
      overflow: hidden;
    }
    .locmap-canvas {
      position: absolute;
      inset: 0;
    }
    .locmap-title-card {
      position: absolute;
      top: 18px;
      left: 18px;
      z-index: 20;
      padding: 14px 16px;
      min-width: 220px;
      border-radius: 18px;
      border: 1px solid rgba(255, 255, 255, 0.42);
      background: rgba(248, 251, 253, 0.92);
      box-shadow: 0 10px 24px rgba(88, 110, 136, 0.08);
      backdrop-filter: blur(6px);
      pointer-events: none;
    }
    .locmap-title {
      color: #243447;
      font-size: 18px;
      font-weight: 700;
      line-height: 1.2;
    }
    .locmap-title-desc {
      margin-top: 6px;
      color: #6b7b8d;
      font-size: 12px;
      line-height: 1.5;
    }
    .locmap-tools {
      position: absolute;
      top: 18px;
      right: 28px;
      z-index: 30;
      display: flex;
      flex-direction: column;
      align-items: flex-end;
      gap: 8px;
    }
    .locmap-fps {
      padding: 4px 10px;
      border-radius: 999px;
      background: rgba(255, 255, 255, 0.7);
      border: 1px solid rgba(160, 180, 205, 0.28);
      color: #48617c;
      font-size: 12px;
      line-height: 18px;
      letter-spacing: 0.04em;
      box-shadow: 0 6px 16px rgba(37, 64, 97, 0.06);
      user-select: none;
    }
    .locmap-tool-toggle,
    .locmap-tool-btn,
    .locmap-floor-btn,
    .locmap-detail-close {
      appearance: none;
      cursor: pointer;
      transition: all .18s ease;
      outline: none;
    }
    .locmap-tool-toggle {
      height: 30px;
      padding: 0 12px;
      border-radius: 999px;
      border: 1px solid rgba(160, 180, 205, 0.3);
      background: rgba(255, 255, 255, 0.82);
      color: #46617b;
      font-size: 12px;
      line-height: 30px;
      box-shadow: 0 6px 16px rgba(37, 64, 97, 0.06);
    }
    .locmap-tool-toggle.is-active {
      border-color: rgba(96, 132, 170, 0.36);
      background: rgba(235, 243, 251, 0.96);
    }
    .locmap-tool-panel {
      display: flex;
      flex-direction: column;
      gap: 8px;
      min-width: 168px;
      padding: 8px;
      border-radius: 14px;
      background: rgba(255, 255, 255, 0.72);
      border: 1px solid rgba(160, 180, 205, 0.3);
      box-shadow: 0 8px 20px rgba(37, 64, 97, 0.08);
      backdrop-filter: blur(4px);
    }
    .locmap-tool-row {
      display: flex;
      flex-wrap: wrap;
      gap: 8px;
      justify-content: flex-end;
    }
    .locmap-tool-btn,
    .locmap-floor-btn {
      min-width: 64px;
      height: 30px;
      padding: 0 12px;
      border-radius: 10px;
      border: 1px solid rgba(160, 180, 205, 0.3);
      background: rgba(255, 255, 255, 0.88);
      color: #4d647d;
      font-size: 12px;
      line-height: 30px;
      white-space: nowrap;
    }
    .locmap-tool-btn:hover,
    .locmap-floor-btn:hover,
    .locmap-detail-close:hover,
    .locmap-tool-toggle:hover {
      transform: translateY(-1px);
    }
    .locmap-tool-btn.is-active,
    .locmap-floor-btn.is-active {
      border-color: rgba(255, 136, 93, 0.38);
      background: rgba(255, 119, 77, 0.16);
      color: #d85a31;
    }
    .locmap-tool-section {
      display: flex;
      flex-direction: column;
      gap: 4px;
      padding-top: 6px;
      border-top: 1px solid rgba(160, 180, 205, 0.22);
    }
    .locmap-tool-label {
      color: #6a7f95;
      font-size: 10px;
      line-height: 14px;
      text-align: right;
    }
    .locmap-detail-panel {
      position: absolute;
      top: 92px;
      right: 18px;
      bottom: 18px;
      z-index: 25;
      width: min(max(320px, 25vw), calc(100vw - 92px));
      border-radius: 20px;
      border: 1px solid rgba(255, 255, 255, 0.42);
      background: rgba(248, 251, 253, 0.94);
      box-shadow: 0 10px 24px rgba(88, 110, 136, 0.08);
      overflow: hidden;
      display: flex;
      flex-direction: column;
      backdrop-filter: blur(6px);
    }
    .locmap-detail-header {
      display: flex;
      align-items: center;
      justify-content: space-between;
      gap: 12px;
      padding: 16px 16px 12px;
      border-bottom: 1px solid rgba(226, 232, 240, 0.72);
      background: rgba(255, 255, 255, 0.24);
    }
    .locmap-detail-title-wrap {
      min-width: 0;
    }
    .locmap-detail-type {
      display: inline-flex;
      align-items: center;
      height: 22px;
      padding: 0 10px;
      border-radius: 999px;
      background: rgba(103, 149, 193, 0.12);
      color: #4b6782;
      font-size: 11px;
      font-weight: 700;
    }
    .locmap-detail-title {
      margin-top: 8px;
      color: #243447;
      font-size: 18px;
      font-weight: 700;
      line-height: 1.2;
    }
    .locmap-detail-subtitle {
      margin-top: 6px;
      color: #6b7b8d;
      font-size: 12px;
      line-height: 1.4;
    }
    .locmap-detail-close {
      flex-shrink: 0;
      width: 32px;
      height: 32px;
      border-radius: 10px;
      border: 1px solid rgba(160, 180, 205, 0.28);
      background: rgba(255, 255, 255, 0.84);
      color: #4d647d;
      font-size: 18px;
      line-height: 30px;
      text-align: center;
    }
    .locmap-detail-body {
      flex: 1;
      min-height: 0;
      padding: 16px;
      overflow: auto;
    }
    .locmap-kv-grid {
      display: grid;
      grid-template-columns: repeat(2, minmax(0, 1fr));
      gap: 12px;
    }
    .locmap-kv-card {
      padding: 14px;
      border-radius: 16px;
      border: 1px solid rgba(224, 232, 239, 0.92);
      background: rgba(255, 255, 255, 0.62);
      box-shadow: 0 8px 18px rgba(148, 163, 184, 0.06);
    }
    .locmap-kv-label {
      color: #7d8fa2;
      font-size: 12px;
      line-height: 1.4;
    }
    .locmap-kv-value {
      margin-top: 8px;
      color: #334155;
      font-size: 18px;
      font-weight: 600;
      line-height: 1.35;
      word-break: break-all;
    }
    .locmap-empty {
      position: absolute;
      right: 18px;
      bottom: 18px;
      z-index: 20;
      width: min(max(260px, 20vw), calc(100vw - 92px));
      padding: 18px;
      border-radius: 18px;
      border: 1px solid rgba(255, 255, 255, 0.4);
      background: rgba(248, 251, 253, 0.92);
      box-shadow: 0 10px 24px rgba(88, 110, 136, 0.08);
      color: #6b7b8d;
      font-size: 12px;
      line-height: 1.7;
      backdrop-filter: blur(6px);
    }
    @media (max-width: 960px) {
      .locmap-title-card {
        right: 18px;
        min-width: 0;
      }
      .locmap-detail-panel {
        top: auto;
        height: min(54vh, 460px);
        width: calc(100vw - 36px);
      }
      .locmap-empty {
        width: calc(100vw - 36px);
      }
      .locmap-kv-grid {
        grid-template-columns: 1fr;
      }
    }
  </style>
</head>
<body>
<div id="app">
  <div id="pixiView">
  <loc-map-canvas></loc-map-canvas>
  </div>
  <!--输出操作和FPS-->
  <div style="position: absolute;top: 20px;right: 50px;">
    <div>FPS:{{mapFps}}</div>
    <el-button @click="drawer = true">操作</el-button>
  </div>
  <el-drawer
          title="操作区域"
          :visible.sync="drawer"
          :with-header="true"
          :modal="false"
  >
    <div class="floorBtnBox" v-for="(lev,idx) in floorList">
      <el-button :style="{background:currentLev === lev ? '#7DCDFF':''}" @click="changeFloor(lev)">{{lev}}F</el-button>
    </div>
<!--    <div>-->
<!--      <el-button @click="testMove()">测试移动车</el-button>-->
<!--    </div>-->
<!--    <div style="margin-top: 10px;">-->
<!--      <el-button @click="resetMap()">重置地图</el-button>-->
<!--    </div>-->
  </el-drawer>
  <el-drawer
          title="库位详情"
          :visible.sync="drawerLocNo"
          :with-header="true"
          :modal="false"
  >
    <div v-if="drawerLocNoData!=null">
      <div style="margin: 10px;">
<!--        <div style="margin-top: 5px;">排:{{drawerLocNoData.row}}</div>-->
<!--        <div style="margin-top: 5px;">列:{{drawerLocNoData.bay}}</div>-->
<!--        <div style="margin-top: 5px;">层:{{drawerLocNoData.lev}}</div>-->
        <div style="margin-top: 5px;">库位号:{{drawerLocNoData.locNo}}</div>
        <div style="margin-top: 5px;">库位状态:{{drawerLocNoData.locSts}}</div>
      </div>
    </div>
  </el-drawer>
  <el-drawer
          title="站点信息"
          :visible.sync="drawerSta"
          :with-header="true"
          :modal="false"
  >
    <div v-if="drawerStaData!=null">
      <div style="margin: 10px;">
        <div style="margin-top: 5px;">站点:{{drawerStaData.siteId}}</div>
        <div style="margin-top: 5px;">工作号:{{drawerStaData.workNo}}</div>
        <div style="margin-top: 5px;">工作状态:{{drawerStaData.wrkSts}}</div>
        <div style="margin-top: 5px;">工作类型:{{drawerStaData.ioType}}</div>
        <div style="margin-top: 5px;">源站:{{drawerStaData.sourceStaNo}}</div>
        <div style="margin-top: 5px;">目标站:{{drawerStaData.staNo}}</div>
        <div style="margin-top: 5px;">源库位:{{drawerStaData.sourceLocNo}}</div>
        <div style="margin-top: 5px;">目标库位:{{drawerStaData.locNo}}</div>
        <div style="margin-top: 5px;">自动:{{drawerStaData.autoing}}</div>
        <div style="margin-top: 5px;">有物:{{drawerStaData.loading}}</div>
        <div style="margin-top: 5px;">能入:{{drawerStaData.canining}}</div>
        <div style="margin-top: 5px;">能出:{{drawerStaData.canouting}}</div>
        <div style="margin-top: 5px;">能出:{{drawerStaData.canouting}}</div>
      </div>
    </div>
  </el-drawer>
</div>
<script>
  let width = 25;
  let height = 25;
  let pixiApp;
  let pixiStageList = [];
  let pixiStaMap = new Map();
  let objectsContainer;
  let objectsContainer3;
  let tracksGraphics;
  let mapRoot;
  let mapContentSize = { width: 0, height: 0 };
  let graphics0;
  let graphicsF;
  let graphics3;
  let graphics4;
  let graphics5;
  let graphics9;
  let graphics67;
  let graphicsLock;
  let ws;
  Vue.component('loc-map-canvas', {
    template: `
      <div class="locmap-shell" ref="shell">
        <div ref="pixiView" class="locmap-canvas"></div>
  var app = new Vue({
    el: '#app',
    data: {
        <div class="locmap-title-card">
          <div class="locmap-title">库位地图</div>
          <div class="locmap-title-desc">点击库位后在右侧查看详情。</div>
        </div>
        <div class="locmap-tools">
          <div class="locmap-fps">FPS {{ mapFps }}</div>
          <button type="button"
                  class="locmap-tool-toggle"
                  :class="{ 'is-active': showMapToolPanel }"
                  @click="toggleMapToolPanel">{{ showMapToolPanel ? '收起操作' : '地图操作' }}</button>
          <div v-show="showMapToolPanel" class="locmap-tool-panel">
            <div class="locmap-tool-row">
              <button type="button" class="locmap-tool-btn" @click="fitStageToContent">重置视图</button>
              <button type="button" class="locmap-tool-btn" @click="rotateMap">旋转</button>
              <button type="button"
                      class="locmap-tool-btn"
                      :class="{ 'is-active': mapMirrorX }"
                      @click="toggleMirror">{{ mapMirrorX ? '取消镜像' : '镜像' }}</button>
            </div>
            <div v-if="floorList && floorList.length > 0" class="locmap-tool-section">
              <div class="locmap-tool-label">楼层</div>
              <div class="locmap-tool-row">
                <button v-for="lev in floorList"
                        :key="'loc-floor-' + lev"
                        type="button"
                        class="locmap-floor-btn"
                        :class="{ 'is-active': currentLev === lev }"
                        @click="changeFloor(lev)">{{ lev }}F</button>
              </div>
            </div>
          </div>
        </div>
        <div v-if="detailPanelOpen" class="locmap-detail-panel" ref="detailPanel">
          <div class="locmap-detail-header">
            <div class="locmap-detail-title-wrap">
              <div class="locmap-detail-type">{{ detailTypeLabel }}</div>
              <div class="locmap-detail-title">{{ detailTitle }}</div>
              <div class="locmap-detail-subtitle">{{ detailSubtitle }}</div>
            </div>
            <button type="button" class="locmap-detail-close" @click="closeDetailPanel">×</button>
          </div>
          <div class="locmap-detail-body">
            <div class="locmap-kv-grid">
              <div v-for="item in detailFields" :key="item.label" class="locmap-kv-card">
                <div class="locmap-kv-label">{{ item.label }}</div>
                <div class="locmap-kv-value">{{ formatDetailValue(item.value) }}</div>
              </div>
            </div>
          </div>
        </div>
        <div v-else class="locmap-empty">
          点击库位可查看库位状态,点击站点可查看站点作业详情。地图操作在右上角工具面板中。
        </div>
      </div>
    `,
    data() {
      return {
        cellWidth: 25,
        cellHeight: 25,
        pixiApp: null,
        pixiStageList: [],
        pixiStaMap: new Map(),
        pixiTrackMap: new Map(),
        pixiLabelList: [],
        mapRoot: null,
        shelvesContainer: null,
        objectsContainer: null,
        objectsOverlayContainer: null,
        tracksGraphics: null,
        mapContentSize: { width: 0, height: 0 },
        textureMap: {},
        locChunkList: [],
        locChunkSize: 1024,
        locCullPadding: 160,
        locCullRaf: null,
        ws: null,
        wsReconnectTimer: null,
        wsReconnectAttempts: 0,
        wsReconnectBaseDelay: 1000,
        wsReconnectMaxDelay: 15000,
        containerResizeObserver: null,
        adjustLabelTimer: null,
      map: [],
        floorList: [],
      currentLev: 1,
      floorList: [], //当前项目楼层
      drawer: false,
      drawerLocNo: false,
      drawerLocNoData: null,
      drawerLocDetls: [],
      reloadMap: true,
      mapFps: 0,
      currentLevStaList: [],//当前楼层站点list
      drawerSta: false,
      drawerStaData: null,
        showMapToolPanel: false,
        detailPanelOpen: false,
        detailType: '',
        detailPayload: null,
        highlightedSprite: null,
        highlightedLocCell: null,
        highlightedLocGraphic: null,
      mapRotation: 0,
      mapMirrorX: false,
      mapConfigCodes: {
        rotate: 'map_canvas_rotation',
        mirror: 'map_canvas_mirror_x'
      }
      };
    },
    computed: {
      detailTypeLabel() {
        return this.detailType === 'site' ? '站点信息' : '库位详情';
      },
      detailTitle() {
        if (this.detailType === 'site' && this.detailPayload) {
          return '站点 ' + this.formatDetailValue(this.detailPayload.siteId);
        }
        if (this.detailType === 'loc' && this.detailPayload) {
          return this.formatDetailValue(this.detailPayload.locNo);
        }
        return '详情';
      },
      detailSubtitle() {
        if (this.detailType === 'site') {
          return '站点作业状态、来源目标和作业参数';
        }
        return '库位当前状态和所在排列层信息';
      },
      detailFields() {
        if (!this.detailPayload) { return []; }
        if (this.detailType === 'site') {
          return [
            { label: '站点', value: this.detailPayload.siteId },
            { label: '工作号', value: this.detailPayload.workNo },
            { label: '工作状态', value: this.detailPayload.wrkSts },
            { label: '工作类型', value: this.detailPayload.ioType },
            { label: '源站', value: this.detailPayload.sourceStaNo },
            { label: '目标站', value: this.detailPayload.staNo },
            { label: '源库位', value: this.detailPayload.sourceLocNo },
            { label: '目标库位', value: this.detailPayload.locNo },
            { label: '自动', value: this.detailPayload.autoing },
            { label: '有物', value: this.detailPayload.loading },
            { label: '能入', value: this.detailPayload.canining },
            { label: '能出', value: this.detailPayload.canouting }
          ];
        }
        return [
          { label: '库位号', value: this.detailPayload.locNo },
          { label: '库位状态', value: this.detailPayload.locSts },
          { label: '排', value: this.detailPayload.row },
          { label: '列', value: this.detailPayload.bay },
          { label: '层', value: this.detailPayload.lev }
        ];
      }
    },
    mounted() {
      this.init()
      this.createMap()
      this.createMap();
      this.startContainerResizeObserve();
      this.loadMapTransformConfig();
      this.initLev();
      this.connectWs();
      setTimeout(() => {
        this.getMap(this.currentLev);
      }, 300);
    },
    watch: {
      map: {
        deep: true,
        handler(val) {
    beforeDestroy() {
      if (this.adjustLabelTimer) {
        clearTimeout(this.adjustLabelTimer);
        this.adjustLabelTimer = null;
        }
      },
      drawerLocNo: {
        deep: true,
        handler(val) {
          if (!val) {
            var sprite = pixiStageList[this.drawerLocNoData.x][this.drawerLocNoData.y];
            updateColor(sprite, 0xFFFFFF);//恢复颜色
      if (this.locCullRaf) {
        cancelAnimationFrame(this.locCullRaf);
        this.locCullRaf = null;
          }
      if (this.containerResizeObserver) {
        this.containerResizeObserver.disconnect();
        this.containerResizeObserver = null;
        }
      window.removeEventListener('resize', this.resizeToContainer);
      if (this.wsReconnectTimer) {
        clearTimeout(this.wsReconnectTimer);
        this.wsReconnectTimer = null;
      }
      if (this.ws && (this.ws.readyState === WebSocket.OPEN || this.ws.readyState === WebSocket.CONNECTING)) {
        try { this.ws.close(); } catch (e) {}
      }
      if (window.gsap && this.pixiApp && this.pixiApp.stage) {
        window.gsap.killTweensOf(this.pixiApp.stage.position);
      }
      this.clearLocChunks();
      if (this.pixiApp) {
        this.pixiApp.destroy(true, { children: true });
        this.pixiApp = null;
      }
    },
    methods: {
      init(){
        let that = this
        ws = new WebSocket("ws://" + window.location.host + baseUrl + "/console/websocket");
        ws.onopen = this.webSocketOnOpen
        ws.onerror = this.webSocketOnError
        ws.onmessage = this.webSocketOnMessage
        ws.onclose = this.webSocketClose
        this.loadMapTransformConfig()
        this.initLev()//初始化楼层信息
        setTimeout(() => {
          that.getMap(this.currentLev)
        }, 1000);
      toggleMapToolPanel() {
        this.showMapToolPanel = !this.showMapToolPanel;
      },
      formatDetailValue(value) {
        return value == null || value === '' ? '-' : value;
      },
      initLev(){
        let that = this
        $.ajax({
          url: baseUrl + "/console/map/lev/list",
          headers: {
            'token': localStorage.getItem('token')
          },
          data: {},
          headers: { token: localStorage.getItem('token') },
          method: 'get',
          success: function(res) {
          success: (res) => {
            if (res.code === 200) {
              that.floorList = res.data;
              this.floorList = Array.isArray(res.data) ? res.data : [];
              if (this.floorList.length > 0 && this.floorList.indexOf(this.currentLev) === -1) {
                this.currentLev = this.floorList[0];
              }
            } else if (res.code === 403) {
              parent.location.href = baseUrl + "/login";
            } else {
              that.$message({
                message: res.msg,
                type: 'error'
              });
              this.showMessage('error', res.msg || '楼层信息加载失败');
            }
          }
        });
      },
      //获取地图数据
      getMap(lev) {
        let that = this;
          $.ajax({
              url: baseUrl + "/console/map/" + lev + "/auth",
              headers: {
                  'token': localStorage.getItem('token')
              },
              data: {},
          headers: { token: localStorage.getItem('token') },
              method: 'get',
              success: function(res) {
                //获取地图数据
                let data = res.data
                that.createMapData(data)
          success: (res) => {
            if (res.code === 200) {
              this.reloadMap = true;
              this.createMapData(res.data || []);
            } else if (res.code === 403) {
              parent.location.href = baseUrl + "/login";
            } else {
              this.showMessage('error', res.msg || '地图加载失败');
              }
          })
          }
        });
      },
      changeFloor(lev) {
        this.currentLev = lev
        this.loadMapTransformConfig()
        this.reloadMap = true
        this.getMap(lev)
        if (this.currentLev === lev) { return; }
        this.currentLev = lev;
        this.reloadMap = true;
        this.closeDetailPanel();
        this.getMap(lev);
      },
      connectWs() {
        if (this.ws && (this.ws.readyState === WebSocket.OPEN || this.ws.readyState === WebSocket.CONNECTING)) {
          return;
        }
        this.ws = new WebSocket("ws://" + window.location.host + baseUrl + "/console/websocket");
        this.ws.onopen = this.webSocketOnOpen;
        this.ws.onerror = this.webSocketOnError;
        this.ws.onmessage = this.webSocketOnMessage;
        this.ws.onclose = this.webSocketClose;
      },
      scheduleReconnectWs() {
        if (this.wsReconnectTimer) { return; }
        const delay = Math.min(this.wsReconnectMaxDelay, this.wsReconnectBaseDelay * Math.pow(2, this.wsReconnectAttempts));
        this.wsReconnectAttempts += 1;
        this.wsReconnectTimer = setTimeout(() => {
          this.wsReconnectTimer = null;
          this.connectWs();
        }, delay);
      },
      webSocketOnOpen() {
        this.wsReconnectAttempts = 0;
      },
      webSocketOnError() {},
      webSocketOnMessage(event) {
        let result = null;
        try {
          result = JSON.parse(event.data);
        } catch (e) {
          return;
        }
        if (!result || !result.url) { return; }
        if (result.url === "/console/map/auth" || result.url === "/console/locMap/auth") {
          let data = [];
          try {
            data = JSON.parse(result.data || '[]');
          } catch (e) {
            data = [];
          }
          this.setMap(data);
        }
      },
      webSocketClose() {
        this.scheduleReconnectWs();
      },
      setMap(data) {
        if (!Array.isArray(data) || this.currentLev == null) { return; }
        this.reloadMap = true;
        this.createMapData(data);
      },
      showMessage(type, message) {
        if (this.$message) {
          this.$message({ type: type, message: message });
        }
      },
      createMap(){
        //Create a Pixi Application
        pixiApp = new PIXI.Application({
          // width: 1500,
          // height: 800,
          backgroundColor: 0xF5F7F9FF,
          resizeTo: window
        this.pixiApp = new PIXI.Application({
          backgroundColor: 0xEEF4F8,
          resizeTo: this.$refs.shell,
          antialias: true,
          autoDensity: true
        });
        //Add the canvas that Pixi automatically created for you to the HTML document
        $("#pixiView").append(pixiApp.view)
        this.$refs.pixiView.appendChild(this.pixiApp.view);
        this.createBaseTextures();
        // 从Graphics对象创建一个纹理
            graphicsF = pixiApp.renderer.generateTexture(getContainer(1000));
        graphics0 = pixiApp.renderer.generateTexture(getContainer(0));
        graphics3 = pixiApp.renderer.generateTexture(getContainer(3));
        graphics4 = pixiApp.renderer.generateTexture(getContainer(4));
        graphics5 = pixiApp.renderer.generateTexture(getContainer(5));
        graphics9 = pixiApp.renderer.generateTexture(getContainer(9));
        graphics67 = pixiApp.renderer.generateTexture(getContainer(67));
        graphicsLock = pixiApp.renderer.generateTexture(getContainer(-999));
        this.mapRoot = new PIXI.Container();
        this.pixiApp.stage.addChild(this.mapRoot);
        mapRoot = new PIXI.Container();
        pixiApp.stage.addChild(mapRoot);
        // 创建一个容器来管理大批量的显示对象
        objectsContainer = new PIXI.Container();
        mapRoot.addChild(objectsContainer);
        this.shelvesContainer = new PIXI.Container();
        this.mapRoot.addChild(this.shelvesContainer);
        tracksGraphics = new PIXI.Graphics();
        mapRoot.addChild(tracksGraphics);
        this.objectsContainer = new PIXI.Container();
        this.mapRoot.addChild(this.objectsContainer);
        // 创建一个容器来管理大批量的显示对象
        objectsContainer3 = new PIXI.Container();
        mapRoot.addChild(objectsContainer3);
        this.tracksGraphics = new PIXI.Graphics();
        this.mapRoot.addChild(this.tracksGraphics);
        //*******************拖动画布*******************
        let stageOriginalPos;
        let mouseDownPoint;
        this.objectsOverlayContainer = new PIXI.Container();
        this.mapRoot.addChild(this.objectsOverlayContainer);
        this.initStageInteractions();
        this.initFpsTicker();
      },
      createBaseTextures() {
        this.textureMap = {
          locEmpty: this.pixiApp.renderer.generateTexture(this.buildCellGraphic(0x5faeff)),
          locFull: this.pixiApp.renderer.generateTexture(this.buildCellGraphic(0xf05d5d)),
          site: this.pixiApp.renderer.generateTexture(this.buildCellGraphic(0xf6ca4b)),
          charge: this.pixiApp.renderer.generateTexture(this.buildCellGraphic(0xffa66b)),
          elevator: this.pixiApp.renderer.generateTexture(this.buildCellGraphic(0x7dd9ff)),
          lock: this.pixiApp.renderer.generateTexture(this.buildCellGraphic(0xf83333))
        };
      },
      buildCellGraphic(fillColor) {
        const graphics = new PIXI.Graphics();
        graphics.beginFill(fillColor);
        graphics.lineStyle(1, 0xffffff, 1);
        graphics.drawRect(0, 0, this.cellWidth, this.cellHeight);
        graphics.endFill();
        return graphics;
      },
      initStageInteractions() {
        let stageOriginalPos = null;
        let mouseDownPoint = null;
        let touchBlank = false;
        pixiApp.renderer.plugins.interaction.on(
                'pointerdown',
                (event) => {
        let pointerDownMoved = false;
        const interaction = this.pixiApp.renderer.plugins.interaction;
        interaction.on('pointerdown', (event) => {
                  const globalPos = event.data.global;
                  // 记录下stage原来的位置
                  stageOriginalPos = [pixiApp.stage.position._x, pixiApp.stage.position._y];
                  // 记录下mouse down的位置
          stageOriginalPos = [this.pixiApp.stage.position.x, this.pixiApp.stage.position.y];
                  mouseDownPoint = [globalPos.x, globalPos.y];
                  if (!event.target) {
                    // 点到了画布的空白位置
                    touchBlank = true;
                  }
                }
        );
          pointerDownMoved = false;
          touchBlank = !event.target;
        });
        pixiApp.renderer.plugins.interaction.on(
                'pointermove',
                (event) => {
        interaction.on('pointermove', (event) => {
                  const globalPos = event.data.global;
                  if (touchBlank) {
                    // 拖拽画布
          if (mouseDownPoint) {
            const dragDx = globalPos.x - mouseDownPoint[0];
            const dragDy = globalPos.y - mouseDownPoint[1];
            if (Math.abs(dragDx) > 4 || Math.abs(dragDy) > 4) {
              pointerDownMoved = true;
            }
          }
          if (!touchBlank || !stageOriginalPos || !mouseDownPoint) { return; }
                    const dx = globalPos.x - mouseDownPoint[0];
                    const dy = globalPos.y - mouseDownPoint[1];
                    pixiApp.stage.position.set(
                            stageOriginalPos[0] + dx,
                            stageOriginalPos[1] + dy
                    );
                  }
                }
        );
          this.pixiApp.stage.position.set(stageOriginalPos[0] + dx, stageOriginalPos[1] + dy);
          this.scheduleAdjustLabels();
          this.scheduleLocChunkCulling();
        });
        pixiApp.renderer.plugins.interaction.on(
                'pointerup',
                (event) => {
        interaction.on('pointerup', (event) => {
          if (touchBlank && !pointerDownMoved && event && event.data && event.data.global) {
            this.handleBlankPointerTap(event.data.global);
          }
                  touchBlank = false;
                }
        );
        //*******************拖动画布*******************
          mouseDownPoint = null;
          stageOriginalPos = null;
        });
        //*******************缩放画布*******************
        pixiApp.view.addEventListener('wheel', (event) => {
        interaction.on('pointerupoutside', () => {
          touchBlank = false;
          mouseDownPoint = null;
          stageOriginalPos = null;
        });
        this.pixiApp.view.addEventListener('wheel', (event) => {
          event.stopPropagation();
          event.preventDefault();
          const rect = pixiApp.view.getBoundingClientRect();
          const rect = this.pixiApp.view.getBoundingClientRect();
          const sx = event.clientX - rect.left;
          const sy = event.clientY - rect.top;
          const oldZoomX = pixiApp.stage.scale.x || 1;
          const oldZoomY = pixiApp.stage.scale.y || 1;
          const oldZoomX = this.pixiApp.stage.scale.x || 1;
          const oldZoomY = this.pixiApp.stage.scale.y || 1;
          const oldZoomAbs = Math.abs(oldZoomX) || 1;
          const delta = event.deltaY;
          let newZoomAbs = oldZoomAbs * 0.999 ** delta;
          let newZoomAbs = oldZoomAbs * Math.pow(0.999, event.deltaY);
          newZoomAbs = Math.max(0.08, Math.min(newZoomAbs, 8));
          const mirrorX = this.mapMirrorX ? -1 : 1;
          const newZoomX = mirrorX * newZoomAbs;
          const newZoomY = newZoomAbs;
          const worldX = (sx - pixiApp.stage.position.x) / oldZoomX;
          const worldY = (sy - pixiApp.stage.position.y) / oldZoomY;
          const worldX = (sx - this.pixiApp.stage.position.x) / oldZoomX;
          const worldY = (sy - this.pixiApp.stage.position.y) / oldZoomY;
          const newPosX = sx - worldX * newZoomX;
          const newPosY = sy - worldY * newZoomY;
          pixiApp.stage.setTransform(newPosX, newPosY, newZoomX, newZoomY, 0, 0, 0, 0, 0);
        });
        //*******************缩放画布*******************
        //*******************FPS*******************
        let g_Time = 0;
        let fpsLastUpdateTs = 0;
        let fpsDeltaSumMs = 0;
        let fpsFrameCount = 0;
        const fpsUpdateInterval = 200;
        pixiApp.ticker.add((delta) => {
          const timeNow = (new Date()).getTime();
          const timeDiff = timeNow - g_Time;
          g_Time = timeNow;
          fpsDeltaSumMs += timeDiff;
          fpsFrameCount += 1;
          if (timeNow - fpsLastUpdateTs >= fpsUpdateInterval) {
            const avgFps = fpsDeltaSumMs > 0 ? (fpsFrameCount * 1000 / fpsDeltaSumMs) : 0;
            this.mapFps = Math.round(avgFps);
            fpsDeltaSumMs = 0;
            fpsFrameCount = 0;
            fpsLastUpdateTs = timeNow;
          this.pixiApp.stage.setTransform(newPosX, newPosY, newZoomX, newZoomY, 0, 0, 0, 0, 0);
          this.scheduleAdjustLabels();
          this.scheduleLocChunkCulling();
        }, { passive: false });
      },
      initFpsTicker() {
        let lastTs = 0;
        let deltaSum = 0;
        let frameCount = 0;
        const updateInterval = 200;
        this.pixiApp.ticker.add(() => {
          const now = Date.now();
          if (!lastTs) {
            lastTs = now;
            return;
          }
          deltaSum += now - lastTs;
          frameCount += 1;
          lastTs = now;
          if (deltaSum >= updateInterval) {
            this.mapFps = deltaSum > 0 ? Math.round(frameCount * 1000 / deltaSum) : 0;
            deltaSum = 0;
            frameCount = 0;
          }
        });
        //*******************FPS*******************
      },
      createMapData(map) {
        if (this.reloadMap) {
          this.reloadMap = false
          pixiStageList = [map.length]//初始化列表
          pixiStaMap = new Map();//重置
          objectsContainer.removeChildren()
          if (tracksGraphics) { tracksGraphics.clear(); }
          map.forEach((item,index) => {
            pixiStageList[index] = [item.length]
            for (let idx = 0; idx < item.length; idx++) {
              let val = item[idx]
              if (val.value < 0 && (val.value != -999)) {
                continue;
        if (!this.reloadMap) {
          this.map = map;
          return;
              }
        this.reloadMap = false;
        this.map = map;
        this.pixiStageList = [];
        this.pixiStaMap = new Map();
        this.pixiLabelList = [];
        this.restoreHighlightedSprite();
        this.clearLocHighlight();
        this.clearLocChunks();
        this.objectsContainer.removeChildren();
        this.objectsOverlayContainer.removeChildren();
        if (this.tracksGraphics) { this.tracksGraphics.clear(); }
              let sprite = getSprite(val.value, idx * width, index * height, val, (e) => {
                if (val.value == 4) {
                  //站点
                  this.openDrawerSta(val)
        let rows = Array.isArray(map) ? map.length : 0;
        let maxCols = 0;
        map.forEach((row, rowIndex) => {
          if (!Array.isArray(row)) { return; }
          this.pixiStageList[rowIndex] = [];
          maxCols = Math.max(maxCols, row.length);
          row.forEach((cell, colIndex) => {
            if (cell.value < 0 && cell.value !== -999) { return; }
            if (parseInt(cell.value, 10) === 0) {
              this.pixiStageList[rowIndex][colIndex] = null;
              return;
            }
            if (this.isTrackCell(cell)) {
              cell.trackMask = this.resolveTrackMask(map, rowIndex, colIndex);
            }
            const sprite = this.createSprite(cell, rowIndex, colIndex);
            if (cell.value === -999) {
              this.objectsOverlayContainer.addChild(sprite);
                }else {
                  //库位
                  this.rightEvent(index, idx, e);
                  updateColor(sprite, 0x9900ff);
              this.objectsContainer.addChild(sprite);
                }
            this.pixiStageList[rowIndex][colIndex] = sprite;
          });
              });
              if (val.value == 4) {
                // 创建文本对象
                const style = new PIXI.TextStyle({
                  fontFamily: 'Arial',
                  fontSize: 10,
                  fill: '#000000',
                });
                const text = new PIXI.Text(val.data, style);
                text.anchor.set(0.5); // 设置文本锚点为中心点
                text.position.set(sprite.width / 2, sprite.height / 2); // 将文本位置设置为Graphics对象的中心点
                // 将文本对象添加到Graphics对象中
                sprite.addChild(text);
                sprite.textObj = text;
                pixiStaMap.set(parseInt(val.data), sprite);//站点数据添加到map中
              }else if (val.value == 67) {
                // 创建提升机文本对象
                const style = new PIXI.TextStyle({
                  fontFamily: 'Arial',
                  fontSize: 10,
                  fill: '#000000',
                });
                const text = new PIXI.Text(val.data, style);
                text.anchor.set(0.5); // 设置文本锚点为中心点
                text.position.set(sprite.width / 2, sprite.height / 2); // 将文本位置设置为Graphics对象的中心点
                // 将文本对象添加到Graphics对象中
                sprite.addChild(text);
                sprite.textObj = text;
                pixiStaMap.set(parseInt(val.data), sprite);//站点数据添加到map中
              }
              if (val.value == -999) {
                pixiShuttleLockPathMap.set(val.locNo, sprite);
                objectsContainer3.addChild(sprite);
              }else {
                objectsContainer.addChild(sprite);
              }
              pixiStageList[index][idx] = sprite
            }
          });
          const b1 = objectsContainer.getLocalBounds();
          const minX = b1.x;
          const minY = b1.y;
          const maxX = b1.x + b1.width;
          const maxY = b1.y + b1.height;
          const contentW = Math.max(0, maxX - minX);
          const contentH = Math.max(0, maxY - minY);
          mapContentSize = { width: contentW, height: contentH };
        this.mapContentSize = {
          width: maxCols * this.cellWidth,
          height: rows * this.cellHeight
        };
        this.buildLocChunks(map, this.mapContentSize.width, this.mapContentSize.height);
          this.drawTracks(map);
          this.applyMapTransform(true);
      },
      createSprite(cell, rowIndex, colIndex) {
        const sprite = this.isTrackCell(cell)
          ? this.createTrackSprite(this.cellWidth, this.cellHeight, cell.trackMask)
          : new PIXI.Sprite(this.getTextureForCell(cell));
        sprite.position.set(colIndex * this.cellWidth, rowIndex * this.cellHeight);
        sprite.baseTint = 0xFFFFFF;
        sprite.cellData = cell;
        if (cell.value === 4 || cell.value === 67) {
          this.attachCellLabel(sprite, cell.data);
          if (cell.value === 4 && cell.data != null) {
            this.pixiStaMap.set(parseInt(cell.data, 10), sprite);
        }
        this.map = map;
        }
        if (!this.isTrackCell(cell)) {
          sprite.interactive = true;
          sprite.buttonMode = true;
          sprite.on('pointerdown', () => {
            if (cell.value === 4) {
              this.openSiteDetail(cell, sprite);
            } else {
              this.openLocDetail(rowIndex, colIndex, sprite);
            }
          });
        }
        return sprite;
      },
      createTrackSprite(width, height, mask) {
        const trackMask = mask != null ? mask : 10;
        const key = width + '-' + height + '-' + trackMask;
        let texture = this.pixiTrackMap.get(key);
        if (!texture) {
          texture = this.createTrackTexture(width, height, trackMask);
          this.pixiTrackMap.set(key, texture);
        }
        return new PIXI.Sprite(texture);
      },
      createTrackTexture(width, height, mask) {
        const TRACK_N = 1;
        const TRACK_E = 2;
        const TRACK_S = 4;
        const TRACK_W = 8;
        const trackMask = mask != null ? mask : (TRACK_E | TRACK_W);
        const g = new PIXI.Graphics();
        const size = Math.max(1, Math.min(width, height));
        const rail = Math.max(2, Math.round(size * 0.12));
        const gap = Math.max(4, Math.round(size * 0.38));
        const midX = Math.round(width / 2);
        const midY = Math.round(height / 2);
        const hasN = (trackMask & TRACK_N) !== 0;
        const hasE = (trackMask & TRACK_E) !== 0;
        const hasS = (trackMask & TRACK_S) !== 0;
        const hasW = (trackMask & TRACK_W) !== 0;
        const hasH = hasW || hasE;
        const hasV = hasN || hasS;
        const isCorner = hasH && hasV && !(hasW && hasE) && !(hasN && hasS);
        const railColor = 0x555555;
        if (hasH && !isCorner) {
          const hStart = hasW ? 0 : midX;
          const hEnd = hasE ? width : midX;
          const hWidth = Math.max(1, hEnd - hStart);
          g.beginFill(railColor);
          g.drawRect(hStart, midY - Math.round(rail / 2), hWidth, rail);
          g.endFill();
        }
        if (hasV && !isCorner) {
          const vStart = hasN ? 0 : midY;
          const vEnd = hasS ? height : midY;
          const vHeight = Math.max(1, vEnd - vStart);
          g.beginFill(railColor);
          g.drawRect(midX - Math.round(rail / 2), vStart, rail, vHeight);
          g.endFill();
        }
        if (isCorner) {
          const cornerEast = hasE;
          const cornerSouth = hasS;
          const centerX = cornerEast ? (width - 1) : 0;
          const centerY = cornerSouth ? (height - 1) : 0;
          const angleStart = (cornerEast && cornerSouth) ? Math.PI : (cornerEast ? Math.PI / 2 : (cornerSouth ? -Math.PI / 2 : 0));
          const angleEnd = (cornerEast && cornerSouth) ? Math.PI * 1.5 : (cornerEast ? Math.PI : (cornerSouth ? 0 : Math.PI / 2));
          const radius = Math.min(Math.abs(centerX - midX), Math.abs(centerY - midY));
          g.lineStyle(rail, railColor, 1);
          g.arc(centerX, centerY, radius, angleStart, angleEnd);
          g.lineStyle(0, 0, 0);
        }
        const rt = PIXI.RenderTexture.create({ width: width, height: height });
        this.pixiApp.renderer.render(g, rt);
        return rt;
      },
      attachCellLabel(sprite, textValue) {
        const text = new PIXI.Text(String(textValue == null ? '' : textValue), {
          fontFamily: 'Arial',
          fontSize: 10,
          fill: '#1f2937',
          align: 'center'
        });
        text.anchor.set(0.5);
        text.position.set(sprite.width / 2, sprite.height / 2);
        sprite.addChild(text);
        sprite.textObj = text;
        this.pixiLabelList.push(sprite);
      },
      getTextureForCell(cell) {
        const value = parseInt(cell.value, 10);
        if (value === 4) { return this.textureMap.site; }
        if (value === 5) { return this.textureMap.charge; }
        if (value === 67) { return this.textureMap.elevator; }
        if (value === -999) { return this.textureMap.lock; }
        return this.textureMap.locEmpty;
      },
      isTrackCell(cell) {
        if (!cell) { return false; }
        const type = cell.type ? String(cell.type).toLowerCase() : '';
        if (type === 'track' || type === 'crn' || type === 'dualcrn' || type === 'rgv') { return true; }
        if (cell.trackSiteNo != null) { return true; }
        const v = parseInt(cell.value, 10);
        if (v === 3 || v === 9) { return true; }
        if (cell.value != null) {
          try {
            const obj = (typeof cell.value === 'string') ? JSON.parse(cell.value) : cell.value;
            if (obj && (obj.trackSiteNo != null || (obj.deviceNo != null && (type === 'crn' || type === 'dualcrn' || type === 'rgv')))) {
              return true;
        const value = parseInt(cell.value, 10);
        return value === 3 || value === 9;
      },
      resolveTrackMask(map, rowIndex, colIndex) {
        const TRACK_N = 1;
        const TRACK_E = 2;
        const TRACK_S = 4;
        const TRACK_W = 8;
        const row = Array.isArray(map) ? map[rowIndex] : null;
        const cell = row && row[colIndex] ? row[colIndex] : null;
        if (!this.isTrackCell(cell)) {
          return TRACK_E | TRACK_W;
            }
          } catch (e) {}
        }
        return false;
        let mask = 0;
        const north = rowIndex > 0 && Array.isArray(map[rowIndex - 1]) ? map[rowIndex - 1][colIndex] : null;
        const east = row && colIndex + 1 < row.length ? row[colIndex + 1] : null;
        const south = rowIndex + 1 < map.length && Array.isArray(map[rowIndex + 1]) ? map[rowIndex + 1][colIndex] : null;
        const west = row && colIndex > 0 ? row[colIndex - 1] : null;
        if (north && this.isTrackCell(north)) { mask |= TRACK_N; }
        if (east && this.isTrackCell(east)) { mask |= TRACK_E; }
        if (south && this.isTrackCell(south)) { mask |= TRACK_S; }
        if (west && this.isTrackCell(west)) { mask |= TRACK_W; }
        return mask || (TRACK_E | TRACK_W);
      },
      drawTracks(map) {
        if (!tracksGraphics || !Array.isArray(map)) { return; }
        tracksGraphics.clear();
        const railColor = 0x6c727a;
        const railWidth = Math.max(1, Math.round(Math.min(width, height) * 0.08));
        tracksGraphics.lineStyle(railWidth, railColor, 1);
        for (let r = 0; r < map.length; r++) {
          const row = map[r];
        if (this.tracksGraphics) { this.tracksGraphics.clear(); }
      },
      buildLocChunks(map, contentW, contentH) {
        this.clearLocChunks();
        if (!this.pixiApp || !this.pixiApp.renderer || !this.shelvesContainer || !Array.isArray(map)) { return; }
        const chunkSize = Math.max(256, parseInt(this.locChunkSize, 10) || 1024);
        const chunkMap = new Map();
        for (let rowIndex = 0; rowIndex < map.length; rowIndex += 1) {
          const row = map[rowIndex];
          if (!Array.isArray(row)) { continue; }
          for (let c = 0; c < row.length; c++) {
            const cell = row[c];
            if (!this.isTrackCell(cell)) { continue; }
            const cx = c * width + width / 2;
            const cy = r * height + height / 2;
            const up = (r - 1 >= 0 && Array.isArray(map[r - 1])) ? map[r - 1][c] : null;
            const right = (c + 1 < row.length) ? row[c + 1] : null;
            const down = (r + 1 < map.length && Array.isArray(map[r + 1])) ? map[r + 1][c] : null;
            const left = (c - 1 >= 0) ? row[c - 1] : null;
            const hasN = this.isTrackCell(up);
            const hasE = this.isTrackCell(right);
            const hasS = this.isTrackCell(down);
            const hasW = this.isTrackCell(left);
            const seg = Math.min(width, height) * 0.5;
            let drew = false;
            if (hasN) { tracksGraphics.moveTo(cx, cy); tracksGraphics.lineTo(cx, cy - seg); drew = true; }
            if (hasE) { tracksGraphics.moveTo(cx, cy); tracksGraphics.lineTo(cx + seg, cy); drew = true; }
            if (hasS) { tracksGraphics.moveTo(cx, cy); tracksGraphics.lineTo(cx, cy + seg); drew = true; }
            if (hasW) { tracksGraphics.moveTo(cx, cy); tracksGraphics.lineTo(cx - seg, cy); drew = true; }
            if (!drew) {
              tracksGraphics.moveTo(cx - seg * 0.4, cy);
              tracksGraphics.lineTo(cx + seg * 0.4, cy);
          for (let colIndex = 0; colIndex < row.length; colIndex += 1) {
            const cell = row[colIndex];
            if (!cell || parseInt(cell.value, 10) !== 0) { continue; }
            const posX = colIndex * this.cellWidth;
            const posY = rowIndex * this.cellHeight;
            const chunkX = Math.floor(posX / chunkSize);
            const chunkY = Math.floor(posY / chunkSize);
            const key = chunkX + ',' + chunkY;
            let list = chunkMap.get(key);
            if (!list) {
              list = [];
              chunkMap.set(key, list);
            }
            list.push({
              x: posX,
              y: posY,
              width: this.cellWidth,
              height: this.cellHeight,
              color: cell.locSts === 'F' ? 0xf05d5d : 0x5faeff
            });
            }
          }
        const chunkList = [];
        chunkMap.forEach((cells, key) => {
          const keyParts = key.split(',');
          const chunkX = parseInt(keyParts[0], 10) || 0;
          const chunkY = parseInt(keyParts[1], 10) || 0;
          const chunkLeft = chunkX * chunkSize;
          const chunkTop = chunkY * chunkSize;
          const chunkWidth = Math.max(1, Math.min(chunkSize, contentW - chunkLeft));
          const chunkHeight = Math.max(1, Math.min(chunkSize, contentH - chunkTop));
          const graphics = new PIXI.Graphics();
          for (let i = 0; i < cells.length; i += 1) {
            const cell = cells[i];
            graphics.beginFill(cell.color);
            graphics.lineStyle(1, 0xffffff, 1);
            graphics.drawRect(cell.x - chunkLeft, cell.y - chunkTop, cell.width, cell.height);
            graphics.endFill();
        }
          const texture = this.pixiApp.renderer.generateTexture(
            graphics,
            PIXI.SCALE_MODES.LINEAR,
            1,
            new PIXI.Rectangle(0, 0, chunkWidth, chunkHeight)
          );
          graphics.destroy(true);
          const sprite = new PIXI.Sprite(texture);
          sprite.position.set(chunkLeft, chunkTop);
          sprite._chunkBounds = {
            x: chunkLeft,
            y: chunkTop,
            width: chunkWidth,
            height: chunkHeight
          };
          this.shelvesContainer.addChild(sprite);
          chunkList.push(sprite);
        });
        this.locChunkList = chunkList;
        this.updateVisibleLocChunks();
      },
      clearLocChunks() {
        if (this.locCullRaf) {
          cancelAnimationFrame(this.locCullRaf);
          this.locCullRaf = null;
        }
        this.locChunkList = [];
        if (!this.shelvesContainer) { return; }
        const children = this.shelvesContainer.removeChildren();
        children.forEach((child) => {
          if (child && typeof child.destroy === 'function') {
            child.destroy({ children: true, texture: true, baseTexture: true });
          }
        });
      },
      getViewportLocalBounds(padding) {
        if (!this.mapRoot || !this.pixiApp) { return null; }
        const viewport = this.getViewportSize();
        const pad = Math.max(0, Number(padding) || 0);
        const points = [
          new PIXI.Point(-pad, -pad),
          new PIXI.Point(viewport.width + pad, -pad),
          new PIXI.Point(-pad, viewport.height + pad),
          new PIXI.Point(viewport.width + pad, viewport.height + pad)
        ];
        let minX = Infinity;
        let minY = Infinity;
        let maxX = -Infinity;
        let maxY = -Infinity;
        points.forEach((point) => {
          const local = this.mapRoot.toLocal(point);
          if (local.x < minX) { minX = local.x; }
          if (local.y < minY) { minY = local.y; }
          if (local.x > maxX) { maxX = local.x; }
          if (local.y > maxY) { maxY = local.y; }
        });
        if (!isFinite(minX) || !isFinite(minY) || !isFinite(maxX) || !isFinite(maxY)) { return null; }
        return { minX: minX, minY: minY, maxX: maxX, maxY: maxY };
      },
      updateVisibleLocChunks() {
        if (!this.locChunkList || this.locChunkList.length === 0) { return; }
        const localBounds = this.getViewportLocalBounds(this.locCullPadding);
        if (!localBounds) { return; }
        for (let i = 0; i < this.locChunkList.length; i += 1) {
          const sprite = this.locChunkList[i];
          const bounds = sprite && sprite._chunkBounds;
          if (!bounds) { continue; }
          const visible = bounds.x < localBounds.maxX &&
            bounds.x + bounds.width > localBounds.minX &&
            bounds.y < localBounds.maxY &&
            bounds.y + bounds.height > localBounds.minY;
          if (sprite.visible !== visible) {
            sprite.visible = visible;
          }
        }
      },
      scheduleLocChunkCulling() {
        if (this.locCullRaf) { return; }
        this.locCullRaf = requestAnimationFrame(() => {
          this.locCullRaf = null;
          this.updateVisibleLocChunks();
        });
      },
      handleBlankPointerTap(globalPos) {
        if (!globalPos || !this.mapRoot || !Array.isArray(this.map) || this.map.length === 0) { return; }
        const local = this.mapRoot.toLocal(new PIXI.Point(globalPos.x, globalPos.y));
        const rowIndex = Math.floor(local.y / this.cellHeight);
        const colIndex = Math.floor(local.x / this.cellWidth);
        if (rowIndex < 0 || colIndex < 0 || rowIndex >= this.map.length) {
          this.closeDetailPanel();
          return;
        }
        const row = this.map[rowIndex];
        if (!Array.isArray(row) || colIndex >= row.length) {
          this.closeDetailPanel();
          return;
        }
        const cell = row[colIndex];
        if (!cell || parseInt(cell.value, 10) !== 0) {
          this.closeDetailPanel();
          return;
        }
        this.openLocDetail(rowIndex, colIndex);
      },
      parseLocNoMeta(locNo) {
        if (locNo == null || locNo === '') { return { row: null, bay: null, lev: null }; }
        const parts = String(locNo).split('-');
        return {
          row: parts.length > 0 ? parts[0] : null,
          bay: parts.length > 1 ? parts[1] : null,
          lev: parts.length > 2 ? parts[2] : null
        };
      },
      openLocDetail(rowIndex, colIndex, sprite) {
        const cell = this.map[rowIndex] && this.map[rowIndex][colIndex] ? this.map[rowIndex][colIndex] : null;
        if (!cell) { return; }
        const locMeta = this.parseLocNoMeta(cell.locNo);
        if (sprite) {
          this.highlightSprite(sprite, 0x915eff);
        } else {
          this.restoreHighlightedSprite();
          this.highlightLocCell(rowIndex, colIndex);
        }
        this.detailType = 'loc';
        this.detailPayload = {
          locNo: cell.locNo,
          locSts: cell.locSts,
          row: cell.row != null ? cell.row : locMeta.row,
          bay: cell.bay != null ? cell.bay : locMeta.bay,
          lev: cell.lev != null ? cell.lev : (locMeta.lev != null ? locMeta.lev : this.currentLev)
        };
        this.detailPanelOpen = true;
      },
      openSiteDetail(cell, sprite) {
        this.clearLocHighlight();
        this.highlightSprite(sprite, 0xff7e47);
        $.ajax({
          url: baseUrl + "/console/site/detail",
          headers: { token: localStorage.getItem('token') },
          data: { siteId: cell.data },
          method: 'post',
          success: (res) => {
            if (res.code === 200) {
              this.detailType = 'site';
              this.detailPayload = res.data || {};
              this.detailPanelOpen = true;
            } else if (res.code === 403) {
              parent.location.href = baseUrl + "/login";
            } else {
              this.showMessage('error', res.msg || '站点详情加载失败');
            }
          }
        });
      },
      highlightSprite(sprite, tint) {
        this.restoreHighlightedSprite();
        if (!sprite) { return; }
        sprite.tint = tint;
        this.highlightedSprite = sprite;
      },
      restoreHighlightedSprite() {
        if (this.highlightedSprite) {
          this.highlightedSprite.tint = this.highlightedSprite.baseTint || 0xFFFFFF;
          this.highlightedSprite = null;
        }
      },
      highlightLocCell(rowIndex, colIndex) {
        this.clearLocHighlight();
        this.highlightedLocCell = { rowIndex: rowIndex, colIndex: colIndex };
        const graphic = new PIXI.Graphics();
        graphic.lineStyle(2, 0x915eff, 0.95);
        graphic.beginFill(0x915eff, 0.14);
        graphic.drawRect(colIndex * this.cellWidth, rowIndex * this.cellHeight, this.cellWidth, this.cellHeight);
        graphic.endFill();
        this.objectsOverlayContainer.addChild(graphic);
        this.highlightedLocGraphic = graphic;
      },
      clearLocHighlight() {
        this.highlightedLocCell = null;
        if (this.highlightedLocGraphic) {
          if (this.highlightedLocGraphic.parent) {
            this.highlightedLocGraphic.parent.removeChild(this.highlightedLocGraphic);
          }
          this.highlightedLocGraphic.destroy(true);
          this.highlightedLocGraphic = null;
        }
      },
      closeDetailPanel() {
        this.detailPanelOpen = false;
        this.detailType = '';
        this.detailPayload = null;
        this.restoreHighlightedSprite();
        this.clearLocHighlight();
      },
      parseRotation(value) {
        const num = parseInt(value, 10);
        if (!isFinite(num)) { return 0; }
        const rot = ((num % 360) + 360) % 360;
        return (rot === 90 || rot === 180 || rot === 270) ? rot : 0;
        const rotation = ((num % 360) + 360) % 360;
        return rotation === 90 || rotation === 180 || rotation === 270 ? rotation : 0;
      },
      parseMirror(value) {
        if (value === true || value === false) { return value; }
@@ -490,11 +1222,45 @@
        const str = String(value).toLowerCase();
        return str === '1' || str === 'true' || str === 'y';
      },
      buildMissingMapConfigList(byCode) {
        const list = [];
        if (!byCode[this.mapConfigCodes.rotate]) {
          list.push({
            name: '地图旋转',
            code: this.mapConfigCodes.rotate,
            value: String(this.mapRotation || 0),
            type: 1,
            status: 1,
            selectType: 'map'
          });
        }
        if (!byCode[this.mapConfigCodes.mirror]) {
          list.push({
            name: '地图镜像',
            code: this.mapConfigCodes.mirror,
            value: this.mapMirrorX ? '1' : '0',
            type: 1,
            status: 1,
            selectType: 'map'
          });
        }
        return list;
      },
      createMapConfigs(list) {
        if (!Array.isArray(list) || list.length === 0) { return; }
        list.forEach((cfg) => {
          $.ajax({
            url: baseUrl + "/config/add/auth",
            headers: { token: localStorage.getItem('token') },
            method: 'POST',
            data: cfg
          });
        });
      },
      loadMapTransformConfig() {
        if (!window.$ || typeof baseUrl === 'undefined') { return; }
        $.ajax({
          url: baseUrl + "/config/listAll/auth",
          headers: { 'token': localStorage.getItem('token') },
          headers: { token: localStorage.getItem('token') },
          dataType: 'json',
          method: 'GET',
          success: (res) => {
@@ -504,210 +1270,148 @@
            }
            const byCode = {};
            res.data.forEach((item) => {
              if (item && item.code) { byCode[item.code] = item; }
              if (item && item.code) {
                byCode[item.code] = item;
              }
            });
            const rotateCfg = byCode[this.mapConfigCodes.rotate];
            const mirrorCfg = byCode[this.mapConfigCodes.mirror];
            if (rotateCfg && rotateCfg.value != null) {
              this.mapRotation = this.parseRotation(rotateCfg.value);
            if (byCode[this.mapConfigCodes.rotate] && byCode[this.mapConfigCodes.rotate].value != null) {
              this.mapRotation = this.parseRotation(byCode[this.mapConfigCodes.rotate].value);
            }
            if (mirrorCfg && mirrorCfg.value != null) {
              this.mapMirrorX = this.parseMirror(mirrorCfg.value);
            if (byCode[this.mapConfigCodes.mirror] && byCode[this.mapConfigCodes.mirror].value != null) {
              this.mapMirrorX = this.parseMirror(byCode[this.mapConfigCodes.mirror].value);
            }
            if (mapContentSize && mapContentSize.width > 0 && mapContentSize.height > 0) {
            this.createMapConfigs(this.buildMissingMapConfigList(byCode));
            if (this.mapContentSize.width > 0 && this.mapContentSize.height > 0) {
              this.applyMapTransform(true);
            }
          }
        });
      },
      saveMapTransformConfig() {
        $.ajax({
          url: baseUrl + "/config/updateBatch",
          headers: { token: localStorage.getItem('token') },
          data: JSON.stringify([
            { code: this.mapConfigCodes.rotate, value: String(this.mapRotation || 0) },
            { code: this.mapConfigCodes.mirror, value: this.mapMirrorX ? '1' : '0' }
          ]),
          dataType: 'json',
          contentType: 'application/json;charset=UTF-8',
          method: 'POST'
        });
      },
      rotateMap() {
        this.mapRotation = (this.mapRotation + 90) % 360;
        this.applyMapTransform(true);
        this.saveMapTransformConfig();
      },
      toggleMirror() {
        this.mapMirrorX = !this.mapMirrorX;
        this.applyMapTransform(true);
        this.saveMapTransformConfig();
      },
      getViewportSize() {
        if (!pixiApp || !pixiApp.renderer) { return { width: 0, height: 0 }; }
        const screen = pixiApp.renderer.screen;
        if (!this.pixiApp || !this.pixiApp.renderer) { return { width: 0, height: 0 }; }
        const screen = this.pixiApp.renderer.screen;
        if (screen && screen.width > 0 && screen.height > 0) {
          return { width: screen.width, height: screen.height };
        }
        const rect = pixiApp.view ? pixiApp.view.getBoundingClientRect() : null;
        const rect = this.pixiApp.view ? this.pixiApp.view.getBoundingClientRect() : null;
        return { width: rect ? rect.width : 0, height: rect ? rect.height : 0 };
      },
      getViewportPadding() {
        return { top: 24, right: 24, bottom: 24, left: 24 };
      },
      getTransformedContentSize() {
        const size = mapContentSize || { width: 0, height: 0 };
        const w = size.width || 0;
        const h = size.height || 0;
        const rot = ((this.mapRotation % 360) + 360) % 360;
        const swap = rot === 90 || rot === 270;
        return { width: swap ? h : w, height: swap ? w : h };
        const width = this.mapContentSize.width || 0;
        const height = this.mapContentSize.height || 0;
        const rotation = ((this.mapRotation % 360) + 360) % 360;
        const swap = rotation === 90 || rotation === 270;
        return { width: swap ? height : width, height: swap ? width : height };
      },
      fitStageToContent() {
        if (!pixiApp || !mapContentSize) { return; }
        if (!this.pixiApp || !this.mapContentSize.width || !this.mapContentSize.height) { return; }
        const size = this.getTransformedContentSize();
        const contentW = size.width || 0;
        const contentH = size.height || 0;
        if (contentW <= 0 || contentH <= 0) { return; }
        const viewport = this.getViewportSize();
        const vw = viewport.width;
        const vh = viewport.height;
        let scale = Math.min(vw / contentW, vh / contentH) * 0.95;
        const padding = this.getViewportPadding();
        const availableW = Math.max(1, viewport.width - padding.left - padding.right);
        const availableH = Math.max(1, viewport.height - padding.top - padding.bottom);
        let scale = Math.min(availableW / size.width, availableH / size.height) * 0.95;
        if (!isFinite(scale) || scale <= 0) { scale = 1; }
        const baseW = mapContentSize.width || contentW;
        const baseH = mapContentSize.height || contentH;
        const mirrorX = this.mapMirrorX ? -1 : 1;
        const scaleX = scale * mirrorX;
        const scaleY = scale;
        const posX = (vw / 2) - (baseW / 2) * scaleX;
        const posY = (vh / 2) - (baseH / 2) * scaleY;
        pixiApp.stage.setTransform(posX, posY, scaleX, scaleY, 0, 0, 0, 0, 0);
        const centerX = padding.left + availableW / 2;
        const centerY = padding.top + availableH / 2;
        const posX = centerX - (this.mapContentSize.width / 2) * scaleX;
        const posY = centerY - (this.mapContentSize.height / 2) * scaleY;
        this.pixiApp.stage.setTransform(posX, posY, scaleX, scaleY, 0, 0, 0, 0, 0);
        this.scheduleAdjustLabels();
        this.scheduleLocChunkCulling();
      },
      applyMapTransform(fitToView) {
        if (!mapRoot || !mapContentSize) { return; }
        const contentW = mapContentSize.width || 0;
        const contentH = mapContentSize.height || 0;
        if (contentW <= 0 || contentH <= 0) { return; }
        mapRoot.pivot.set(contentW / 2, contentH / 2);
        mapRoot.position.set(contentW / 2, contentH / 2);
        mapRoot.rotation = (this.mapRotation % 360) * Math.PI / 180;
        mapRoot.scale.set(1, 1);
        if (fitToView) { this.fitStageToContent(); }
      },
      rightEvent(x, y, e) {
        this.drawerLocNo = true
        this.drawerLocNoData =  {x:x, y: y, z: this.currentLev, locNo: this.map[x][y].locNo,
            locSts: this.map[x][y].locSts,row:this.map[x][y].row, bay: this.map[x][y].bay, lev: this.currentLev};
      },
      webSocketOnOpen(e) {
        console.log("open");
      },
      webSocketOnError(e) {
        console.log(e);
      },
      webSocketOnMessage(e) {
        const result = JSON.parse(e.data);
        if (result.url == "/console/map/auth") {
          this.setMap(JSON.parse(result.data))
        }else if (result.url == "/console/locMap/auth") {
          this.setMap(JSON.parse(result.data))
        }
      },
      webSocketClose(e) {
        console.log("close");
      },
      sendWs(message) {
        if (ws.readyState == WebSocket.OPEN) {
          ws.send(message)
        }
      },
      openDrawerSta(data) {
        let that = this
        this.drawerSta = true;
        $.ajax({
          url: baseUrl + "/console/site/detail",
          headers: {
            'token': localStorage.getItem('token')
          },
          data: {
            siteId: data.data
          },
          method: 'post',
          success: function(res) {
            //解析数据
            let siteInfo = res.data;
            if (res.code == 200) {
              that.drawerStaData = siteInfo;
            }
          }
        })
      },
    }
  })
  function getContainer(value) {
    let graphics = new PIXI.Graphics();
    if (value === 0) {
      graphics.beginFill(0x55aaff);
    } else if (value === 3) {//母轨道
      graphics.beginFill(0x00ff7f);
      graphics.visible = true;
    } else if (value === 4) {//站点
      graphics.beginFill(0xffff00);
      graphics.visible = true;
    } else if (value === 5) {//充电桩
      graphics.beginFill(0xffaa7f);
      graphics.visible = true;
    } else if (value === 9) {//轨迹
      graphics.beginFill(0xff0000);
    }else if (value === 67) {//提升机
      graphics.beginFill(0xaaffff);
    }else if (value === -999) {//路径锁定
      graphics.beginFill(0xf83333);
    }else if (value === 1000) {//满库位
      graphics.beginFill(0xf83333);
    }
    graphics.lineStyle(1, 0xffffff, 1);
    graphics.drawRect(0, 0, width, height);
    graphics.endFill();
    return graphics;
  }
  function getGraphics(color, width, height, x, y) {
    let graphics = new PIXI.Graphics();
    graphics.beginFill(color);
    graphics.lineStyle(1, 0xffffff, 1);
    graphics.drawRect(0, 0, width, height);
    graphics.position.set(x, y);
    graphics.endFill();
    return graphics;
  }
  function getSprite(value, x, y, item, pointerDownEvent) {
    let sprite;
    if (value == 0) {
      if(item.locSts == 'O') {
        sprite = new PIXI.Sprite(graphics0);
      }else if(item.locSts == 'F') {
        sprite = new PIXI.Sprite(graphicsF);
        if (!this.mapRoot || !this.mapContentSize.width || !this.mapContentSize.height) { return; }
        const contentW = this.mapContentSize.width;
        const contentH = this.mapContentSize.height;
        this.mapRoot.pivot.set(contentW / 2, contentH / 2);
        this.mapRoot.position.set(contentW / 2, contentH / 2);
        this.mapRoot.rotation = (this.mapRotation % 360) * Math.PI / 180;
        this.mapRoot.scale.set(1, 1);
        if (fitToView) {
          this.fitStageToContent();
      }else {
        sprite = new PIXI.Sprite(graphics0);
          this.scheduleAdjustLabels();
          this.scheduleLocChunkCulling();
      }
    } else if (value == 3) {
      sprite = new PIXI.Sprite(graphics3);
    } else if (value == 4) {
      sprite = new PIXI.Sprite(graphics4);
    } else if (value == 5) {
      sprite = new PIXI.Sprite(graphics5);
    } else if (value == 9) {
      sprite = new PIXI.Sprite(graphics9);
    } else if (value == 67) {
      sprite = new PIXI.Sprite(graphics67);
    } else if (value == -999) {
      sprite = new PIXI.Sprite(graphicsLock);
      },
      scheduleAdjustLabels() {
        if (this.adjustLabelTimer) {
          clearTimeout(this.adjustLabelTimer);
        }
        this.adjustLabelTimer = setTimeout(() => {
          this.adjustLabelScale();
          this.adjustLabelTimer = null;
        }, 20);
      },
      adjustLabelScale() {
        if (!this.pixiApp || !this.pixiLabelList.length) { return; }
        const scaleX = this.pixiApp.stage.scale.x || 1;
        const scaleY = this.pixiApp.stage.scale.y || 1;
        const scale = Math.max(Math.abs(scaleX), Math.abs(scaleY), 0.0001);
        const mirrorSign = scaleX < 0 ? -1 : 1;
        const inverseRotation = -this.mapRoot.rotation;
        const visible = scale >= 0.25;
        this.pixiLabelList.forEach((sprite) => {
          if (!sprite || !sprite.textObj) { return; }
          const textObj = sprite.textObj;
          let textScale = 1 / scale;
          textScale = Math.max(0.9, Math.min(textScale, 3));
          textObj.scale.set(textScale * mirrorSign, textScale);
          textObj.rotation = inverseRotation;
          textObj.visible = visible;
          textObj.position.set(sprite.width / 2, sprite.height / 2);
        });
      },
      startContainerResizeObserve() {
        this.resizeToContainer = () => {
          if (!this.pixiApp || !this.mapContentSize.width || !this.mapContentSize.height) { return; }
          this.fitStageToContent();
        };
        if (window.ResizeObserver) {
          this.containerResizeObserver = new ResizeObserver(() => {
            this.resizeToContainer();
          });
          this.containerResizeObserver.observe(this.$refs.shell);
    } else {
      sprite = new PIXI.Sprite(graphics0);
          window.addEventListener('resize', this.resizeToContainer);
    }
    sprite.position.set(x, y);
    const type = item && item.type ? String(item.type).toLowerCase() : '';
    const numVal = parseInt(value, 10);
    const isTrackCell = numVal === 3 || numVal === 9 || type === 'track' || type === 'crn' || type === 'dualcrn' || type === 'rgv';
    if (!isTrackCell) {
      sprite.interactive = true; // 必须要设置才能接收事件
      sprite.buttonMode = true; // 让光标在hover时变为手型指针
      sprite.on('pointerdown', (e) => {
        pointerDownEvent(e)
      })
    }
    }
  });
    return sprite;
  }
  /**
   * 更新颜色
   */
  function updateColor(sprite, color) {
    // graphics.clear()
    // graphics.beginFill(color);
    // graphics.drawRect(0, 0, width, height);
    sprite.tint = color;
  }
  new Vue({
    el: '#app'
  });
</script>
</body>
</html>