3238a2dd68e782bc2e5711016d140c61067c9409..c53fbb2ee3cb2f5d69aa23b8a4b34c988fa0bc95
1 天以前 Junjie
#
c53fbb 对比 | 目录
1 天以前 Junjie
#
99bb8a 对比 | 目录
1 天以前 pang.jiabao
库位管理排列层精确查询
25a607 对比 | 目录
1 天以前 pang.jiabao
RGV管理新增rgv时增加编号
99278c 对比 | 目录
1 天以前 Junjie
#
c51b87 对比 | 目录
1 天以前 Junjie
#
43a94f 对比 | 目录
4个文件已修改
634 ■■■■■ 已修改文件
src/main/resources/application.yml 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/webapp/components/MapCanvas.js 624 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/webapp/static/js/basRgv/basRgv.js 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/webapp/views/basRgv/basRgv.html 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/application.yml
@@ -72,7 +72,7 @@
  threadControlCount: 10
  liftType: lift
mainProcessPlugin: XiaosongProcess
mainProcessPlugin: FakeProcess
deviceLogStorage:
  # 设备日志存储方式 mysql file
src/main/webapp/components/MapCanvas.js
@@ -2,8 +2,16 @@
  template: `
    <div style="width: 100%; height: 100%; position: relative;">
      <div ref="pixiView"></div>
      <div style="position: absolute; top: 20px; right: 50px;">
      <div v-show="shelfTooltip.visible"
           :style="shelfTooltipStyle()">
        {{ shelfTooltip.text }}
      </div>
      <div style="position: absolute; top: 20px; right: 50px; text-align: right;">
        <div>FPS:{{mapFps}}</div>
        <div style="margin-top: 6px; display: flex; gap: 6px; justify-content: flex-end;">
          <button type="button" @click="rotateMap" style="padding: 2px 8px; font-size: 12px; cursor: pointer;">旋转</button>
          <button type="button" @click="toggleMirror" style="padding: 2px 8px; font-size: 12px; cursor: pointer;">{{ mapMirrorX ? '取消镜像' : '镜像' }}</button>
        </div>
      </div>
    </div>
  `,
@@ -24,6 +32,14 @@
      pixiCrnMap: new Map(),
      pixiDualCrnMap: new Map(),
      pixiRgvMap: new Map(),
      mapRoot: null,
      mapRotation: 0,
      mapMirrorX: false,
      mapContentSize: { width: 0, height: 0 },
      mapConfigCodes: {
        rotate: 'map_canvas_rotation',
        mirror: 'map_canvas_mirror_x'
      },
      pixiShelfMap: new Map(),
      pixiTrackMap: new Map(),
      pixiDevpTextureMap: new Map(),
@@ -34,6 +50,19 @@
      crnList: [],
      dualCrnList: [],
      rgvList: [],
      locListMap: new Map(),
      locListLoaded: false,
      locListLoading: false,
      mapRowOffsets: [],
      mapRowHeights: [],
      mapColOffsets: [],
      mapColWidths: [],
      mapRowColOffsets: [],
      mapRowColWidths: [],
      mapRowShelfCells: [],
      hoveredShelfCell: null,
      hoverPointer: { x: 0, y: 0 },
      hoverRaf: null,
      objectsContainer: null,
      objectsContainer2: null,
      tracksContainer: null,
@@ -43,6 +72,14 @@
      graphicsCrnTrack: null,
      graphicsRgvTrack: null,
      graphicsRgv: null,
      shelfTooltip: {
        visible: false,
        x: 0,
        y: 0,
        text: '',
        item: null
      },
      shelfTooltipMinScale: 0.4,
      timer: null,
      adjustLabelTimer: null,
      isSwitchingFloor: false
@@ -51,6 +88,8 @@
    mounted() {
    this.currentLev = this.lev || 1;
    this.createMap();
    this.loadMapTransformConfig();
    this.loadLocList();
    this.connectWs();
    
    setTimeout(() => {
@@ -66,6 +105,8 @@
  },
  beforeDestroy() {
    if (this.timer) { clearInterval(this.timer); }
    if (this.hoverRaf) { cancelAnimationFrame(this.hoverRaf); this.hoverRaf = null; }
    if (this.pixiApp) { this.pixiApp.destroy(true, { children: true }); }
    window.removeEventListener('resize', this.resizeToContainer);
    if (this.wsReconnectTimer) { clearTimeout(this.wsReconnectTimer); this.wsReconnectTimer = null; }
@@ -137,15 +178,38 @@
      this.shelvesContainer = new PIXI.ParticleContainer(10000, { scale: true, position: true, rotation: false, uvs: false, alpha: false });
      this.tracksContainer.autoResize = true;
      this.shelvesContainer.autoResize = true;
      this.pixiApp.stage.addChild(this.tracksGraphics);
      this.pixiApp.stage.addChild(this.tracksContainer);
      this.pixiApp.stage.addChild(this.shelvesContainer);
      this.pixiApp.stage.addChild(this.objectsContainer);
      this.pixiApp.stage.addChild(this.objectsContainer2);
      this.mapRoot = new PIXI.Container();
      this.pixiApp.stage.addChild(this.mapRoot);
      this.mapRoot.addChild(this.tracksGraphics);
      this.mapRoot.addChild(this.tracksContainer);
      this.mapRoot.addChild(this.shelvesContainer);
      this.mapRoot.addChild(this.objectsContainer);
      this.mapRoot.addChild(this.objectsContainer2);
      this.pixiApp.renderer.roundPixels = true;
      this.hoveredShelfCell = null;
      this.hoverPointer = { x: 0, y: 0 };
      this.hoverRaf = null;
      
      //*******************拖动画布*******************
      //*******************shelf hover*******************
      this.pixiApp.renderer.plugins.interaction.on('pointermove', (event) => {
        if (!this.isShelfTooltipAllowed()) { this.hideShelfTooltip(); return; }
        if (!this.map || !this.mapRoot) { return; }
        const pos = event.data.global;
        this.hoverPointer.x = pos.x;
        this.hoverPointer.y = pos.y;
        if (this.hoverRaf) { return; }
        this.hoverRaf = requestAnimationFrame(() => {
          this.hoverRaf = null;
          this.updateShelfHoverFromPointer(this.hoverPointer);
        });
      });
      this.pixiApp.view.addEventListener('mouseleave', () => {
        this.hoveredShelfCell = null;
        this.hideShelfTooltip();
      });
      //*******************shelf hover*******************
      let stageOriginalPos;
      let mouseDownPoint;
      let touchBlank = false;
@@ -153,7 +217,7 @@
        const globalPos = event.data.global;
        stageOriginalPos = [this.pixiApp.stage.position.x, this.pixiApp.stage.position.y];
        mouseDownPoint = [globalPos.x, globalPos.y];
        if (!event.target) { touchBlank = true; }
        if (!event.target || (event.target && event.target._kind === 'shelf')) { touchBlank = true; }
      });
      this.pixiApp.renderer.plugins.interaction.on('pointermove', (event) => {
        const globalPos = event.data.global;
@@ -164,7 +228,7 @@
        }
      });
      this.pixiApp.renderer.plugins.interaction.on('pointerup', () => { touchBlank = false; });
      //*******************拖动画布*******************
      //*******************缩放画布*******************
      this.pixiApp.view.addEventListener('wheel', (event) => {
@@ -173,14 +237,19 @@
        const rect = this.pixiApp.view.getBoundingClientRect();
        const sx = event.clientX - rect.left;
        const sy = event.clientY - rect.top;
        const oldZoom = this.pixiApp.stage.scale.x;
        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 newZoom = oldZoom * 0.999 ** delta;
        const worldX = (sx - this.pixiApp.stage.position.x) / oldZoom;
        const worldY = (sy - this.pixiApp.stage.position.y) / oldZoom;
        const newPosX = sx - worldX * newZoom;
        const newPosY = sy - worldY * newZoom;
        this.pixiApp.stage.setTransform(newPosX, newPosY, newZoom, newZoom, 0, 0, 0, 0, 0);
        let newZoomAbs = oldZoomAbs * 0.999 ** delta;
        const mirrorX = this.mapMirrorX ? -1 : 1;
        const newZoomX = mirrorX * newZoomAbs;
        const newZoomY = newZoomAbs;
        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;
        this.pixiApp.stage.setTransform(newPosX, newPosY, newZoomX, newZoomY, 0, 0, 0, 0, 0);
          this.scheduleAdjustLabels();
      });
      //*******************缩放画布*******************
@@ -221,6 +290,12 @@
    changeFloor(lev) {
      this.currentLev = lev;
      this.isSwitchingFloor = true;
      this.hideShelfTooltip();
      this.hoveredShelfCell = null;
      this.mapRowOffsets = [];
      this.mapRowHeights = [];
      this.mapColOffsets = [];
      this.mapColWidths = [];
      if (this.adjustLabelTimer) { clearTimeout(this.adjustLabelTimer); this.adjustLabelTimer = null; }
      this.objectsContainer.removeChildren();
      this.objectsContainer2.removeChildren();
@@ -238,6 +313,12 @@
      this.getMap();
    },
    createMapData(map) {
      this.hideShelfTooltip();
      this.hoveredShelfCell = null;
      this.mapRowOffsets = [];
      this.mapRowHeights = [];
      this.mapColOffsets = [];
      this.mapColWidths = [];
      if (window.gsap) {
        this.pixiStaMap && this.pixiStaMap.forEach((s) => { try { window.gsap.killTweensOf(s); } catch (e) {} });
        this.pixiCrnMap && this.pixiCrnMap.forEach((s) => { try { window.gsap.killTweensOf(s); } catch (e) {} });
@@ -327,6 +408,8 @@
          let val = row[colIndex];
          let cellWidth = val.width;
          let cellHeight = val.height;
          val.rowIndex = rowIndex;
          val.colIndex = colIndex;
          if (val.isMergedPart) {
            val.posX = anchorX;
            val.posY = yOffsets[rowIndex];
@@ -347,12 +430,16 @@
        }
      });
      this.buildShelfHitGrid(map, rowHeightScaled, yOffsets);
      this.drawTracks(map);
      map.forEach((item, index) => {
        this.pixiStageList[index] = [item.length];
        for (let idx = 0; idx < item.length; idx++) {
          let val = item[idx];
          val.rowIndex = index;
          val.colIndex = idx;
          if (val.type === 'merge') { continue; }
          if (val.type == undefined || val.type === 'none') { continue; }
          if (this.isTrackType(val)) {
@@ -386,7 +473,7 @@
        sprite.textObj = text;
        sprite.position.set(item.posX, item.posY);
        sprite.interactive = true; // 必须要设置才能接收事件
        sprite.buttonMode = true; // 让光标在hover时变为手型指针
        sprite.buttonMode = true; // 让光标在hover时变为手型指事件
        sprite.on('pointerdown', () => {
          if (window.gsap) { window.gsap.killTweensOf(sprite); }
          sprite.alpha = 1;
@@ -452,7 +539,7 @@
        sprite.textObj = text;
        sprite.position.set(item.posX, item.posY);
        sprite.interactive = true; // 必须要设置才能接收事件
        sprite.buttonMode = true; // 让光标在hover时变为手型指针
        sprite.buttonMode = true; // 让光标在hover时变为手型指事件
        sprite.on('pointerdown', () => {
          if (window.gsap) { window.gsap.killTweensOf(sprite); }
          sprite.alpha = 1;
@@ -483,14 +570,8 @@
          if (bottom > contentH) { contentH = bottom; }
        }
      }
      const vw = this.pixiApp.view.width;
      const vh = this.pixiApp.view.height;
      let scale = Math.min(vw / contentW, vh / contentH) * 0.95;
      if (!isFinite(scale) || scale <= 0) { scale = 1; }
      const posX = (vw - contentW * scale) / 2;
      const posY = (vh - contentH * scale) / 2;
      this.pixiApp.stage.setTransform(posX, posY, scale, scale, 0, 0, 0, 0, 0);
      this.adjustLabelScale();
      this.mapContentSize = { width: contentW, height: contentH };
      this.applyMapTransform(true);
      this.map = map;
      this.isSwitchingFloor = false;
    },
@@ -1311,13 +1392,262 @@
    getTrackSiteNo(obj) {
      if (this.isJson(obj)) { let data = JSON.parse(obj); if (data.trackSiteNo == null || data.trackSiteNo == undefined) { return -1; } return data.trackSiteNo; } else { return -1; }
    },
    buildShelfHitGrid(map, rowHeights, rowOffsets) {
      if (!map || !Array.isArray(map)) { return; }
      this.mapRowOffsets = Array.isArray(rowOffsets) ? rowOffsets.slice() : [];
      this.mapRowHeights = Array.isArray(rowHeights) ? rowHeights.slice() : [];
      const rowColOffsets = [];
      const rowColWidths = [];
      const rowShelfCells = new Array(map.length);
      let maxCols = 0;
      for (let r = 0; r < map.length; r++) {
        const row = map[r];
        if (row && row.length > maxCols) { maxCols = row.length; }
        rowShelfCells[r] = [];
      }
      const colWidths = new Array(maxCols);
      for (let c = 0; c < maxCols; c++) {
        let w = null;
        for (let r = 0; r < map.length; r++) {
          const cell = map[r] && map[r][c];
          if (!cell) { continue; }
          if (cell.cellWidth != null && cell.cellWidth !== '') {
            const base = Number(cell.cellWidth);
            if (isFinite(base) && base > 0) { w = base / 40; break; }
          }
        }
        colWidths[c] = (w && isFinite(w) && w > 0) ? w : 25;
      }
      const colOffsets = new Array(maxCols);
      let xCursor = 0;
      for (let c = 0; c < maxCols; c++) {
        colOffsets[c] = xCursor;
        xCursor += colWidths[c];
      }
      for (let r = 0; r < map.length; r++) {
        const row = map[r];
        if (!row || row.length === 0) {
          rowColOffsets[r] = [];
          rowColWidths[r] = [];
          continue;
        }
        const widths = new Array(row.length);
        for (let c = 0; c < row.length; c++) {
          const cell = row[c];
          let w = null;
          if (cell && cell.cellWidth != null && cell.cellWidth !== '') {
            const base = Number(cell.cellWidth);
            if (isFinite(base) && base > 0) { w = base / 40; }
          }
          widths[c] = (w && isFinite(w) && w > 0) ? w : 25;
        }
        const offsets = new Array(row.length);
        let x = 0;
        for (let c = 0; c < row.length; c++) {
          offsets[c] = x;
          x += widths[c];
        }
        rowColOffsets[r] = offsets;
        rowColWidths[r] = widths;
      }
      this.mapColWidths = colWidths;
      this.mapColOffsets = colOffsets;
      this.mapRowColOffsets = rowColOffsets;
      this.mapRowColWidths = rowColWidths;
      this.mapRowShelfCells = rowShelfCells;
      for (let r = 0; r < map.length; r++) {
        const row = map[r];
        if (!row) { continue; }
        for (let c = 0; c < row.length; c++) {
          const cell = row[c];
          if (!cell || cell.type !== 'shelf') { continue; }
          const startRow = this.findIndexByOffsets(this.mapRowOffsets, this.mapRowHeights, cell.posY + 0.01);
          const endRow = this.findIndexByOffsets(this.mapRowOffsets, this.mapRowHeights, cell.posY + cell.height - 0.01);
          if (startRow < 0) { continue; }
          const last = endRow >= 0 ? endRow : startRow;
          for (let rr = startRow; rr <= last; rr++) {
            if (!rowShelfCells[rr]) { rowShelfCells[rr] = []; }
            rowShelfCells[rr].push(cell);
          }
        }
      }
    },
    findIndexByOffsets(offsets, sizes, value) {
      if (!offsets || !sizes || offsets.length === 0) { return -1; }
      for (let i = 0; i < offsets.length; i++) {
        const start = offsets[i];
        const end = start + (sizes[i] || 0);
        if (value >= start && value < end) { return i; }
      }
      return -1;
    },
    updateShelfHoverFromPointer(globalPos) {
      if (!this.map || !this.mapRoot) { return; }
      if (!this.mapRowOffsets.length || !this.mapColOffsets.length) { return; }
      const local = this.mapRoot.toLocal(new PIXI.Point(globalPos.x, globalPos.y));
      const rowIndex = this.findIndexByOffsets(this.mapRowOffsets, this.mapRowHeights, local.y);
      if (rowIndex < 0) { if (this.hoveredShelfCell) { this.hoveredShelfCell = null; this.hideShelfTooltip(); } return; }
      let cell = null;
      if (this.mapRowShelfCells && this.mapRowShelfCells[rowIndex]) {
        const list = this.mapRowShelfCells[rowIndex];
        for (let i = 0; i < list.length; i++) {
          const it = list[i];
          if (!it) { continue; }
          if (local.x >= it.posX && local.x < it.posX + it.width &&
              local.y >= it.posY && local.y < it.posY + it.height) {
            cell = it;
            break;
          }
        }
      }
      if (!cell || cell.type !== 'shelf') { if (this.hoveredShelfCell) { this.hoveredShelfCell = null; this.hideShelfTooltip(); } return; }
      if (this.hoveredShelfCell !== cell) {
        this.hoveredShelfCell = cell;
        this.shelfTooltip.item = cell;
        this.shelfTooltip.text = this.getShelfArrangeInfo(cell);
        this.shelfTooltip.visible = true;
      }
      this.updateShelfTooltipPositionByGlobal(globalPos);
    },
    normalizeLocTypeKey(value) {
      if (value == null) { return null; }
      const str = String(value).trim();
      if (!str) { return null; }
      const parts = str.split('-').filter(p => p !== '');
      if (parts.length >= 3) { return parts.slice(0, parts.length - 1).join('-'); }
      return str;
    },
    loadLocList() {
      if (!window.$ || typeof baseUrl === 'undefined') { return; }
      if (this.locListLoading) { return; }
      this.locListLoading = true;
      $.ajax({
        url: baseUrl + "/console/map/locList",
        headers: { 'token': localStorage.getItem('token') },
        dataType: 'json',
        method: 'GET',
        success: (res) => {
          if (res && !Array.isArray(res)) {
            if (res.code === 403) { parent.location.href = baseUrl + "/login"; return; }
            if (res.code !== 200) { return; }
          }
          const list = Array.isArray(res) ? res : (res && res.code === 200 ? res.data : null);
          if (!list || !Array.isArray(list)) { return; }
          const map = new Map();
          list.forEach((item) => {
            if (!item) { return; }
            const locType = item.locType != null ? item.locType : item.loc_type;
            if (locType != null && locType !== '') {
              const normalizedType = this.normalizeLocTypeKey(locType);
              if (normalizedType && !map.has(normalizedType)) { map.set(normalizedType, item); }
            }
          });
          this.locListMap = map;
          this.locListLoaded = true;
          if (this.shelfTooltip.visible) {
            this.shelfTooltip.text = this.getShelfArrangeInfo(this.shelfTooltip.item);
          }
        },
        complete: () => {
          this.locListLoading = false;
        }
      });
    },
    showShelfTooltip(e, item) {
      if (!item) { return; }
      if (!this.isShelfTooltipAllowed()) { this.hideShelfTooltip(); return; }
      if (!this.locListLoaded && !this.locListLoading) { this.loadLocList(); }
      this.shelfTooltip.item = item;
      this.shelfTooltip.text = this.getShelfArrangeInfo(item);
      this.updateShelfTooltipPosition(e);
      this.shelfTooltip.visible = true;
    },
    updateShelfTooltipPosition(e) {
      if (!e || !e.data || !e.data.global) { return; }
      this.updateShelfTooltipPositionByGlobal(e.data.global);
    },
    updateShelfTooltipPositionByGlobal(globalPos) {
      if (!this.isShelfTooltipAllowed()) { this.hideShelfTooltip(); return; }
      if (!globalPos) { return; }
      this.shelfTooltip.x = globalPos.x + 12;
      this.shelfTooltip.y = globalPos.y + 12;
    },
    hideShelfTooltip() {
      this.shelfTooltip.visible = false;
      this.shelfTooltip.item = null;
    },
    isShelfTooltipAllowed() {
      return this.getStageAbsScale() >= this.shelfTooltipMinScale;
    },
    getStageAbsScale() {
      if (!this.pixiApp || !this.pixiApp.stage) { return 1; }
      return Math.abs(this.pixiApp.stage.scale.x || 1);
    },
    updateShelfTooltipVisibilityByScale() {
      if (this.shelfTooltip.visible && !this.isShelfTooltipAllowed()) {
        this.hideShelfTooltip();
        this.hoveredShelfCell = null;
      }
    },
    getShelfArrangeInfo(item) {
      const parts = [];
      const matchKey = this.getShelfMatchKey(item);
      if (matchKey != null) { parts.push('坐标:' + matchKey); }
      const locInfo = (matchKey != null) ? this.locListMap.get(matchKey) : null;
      if (locInfo) {
        const locNo = locInfo.locNo != null ? locInfo.locNo : locInfo.loc_no;
        const displayLocNo = this.stripLocLayer(locNo);
        if (displayLocNo != null) { parts.push('排列:' + displayLocNo); }
      }
      return parts.join('  ');
    },
    getShelfMatchKey(item) {
      if (!item) { return null; }
      const direct = item.locType != null ? item.locType : (item.loc_type != null ? item.loc_type : null);
      const directKey = this.normalizeLocTypeKey(direct);
      if (directKey) { return directKey; }
      const rowIndex = item.rowIndex;
      const colIndex = item.colIndex;
      if (rowIndex == null || colIndex == null) { return null; }
      const key0 = rowIndex + '-' + colIndex;
      if (this.locListLoaded && this.locListMap && this.locListMap.size > 0) {
        if (this.locListMap.has(key0)) { return key0; }
      }
      return null;
    },
    stripLocLayer(locNo) {
      if (locNo == null) { return null; }
      const str = String(locNo).trim();
      if (!str) { return null; }
      const parts = str.split('-').filter(p => p !== '');
      if (parts.length >= 3) { return parts.slice(0, parts.length - 1).join('-'); }
      return str;
    },
    shelfTooltipStyle() {
      return {
        position: 'absolute',
        left: this.shelfTooltip.x + 'px',
        top: this.shelfTooltip.y + 'px',
        background: 'rgba(0,0,0,0.75)',
        color: '#ffffff',
        padding: '4px 8px',
        borderRadius: '4px',
        fontSize: '12px',
        pointerEvents: 'none',
        whiteSpace: 'nowrap',
        zIndex: 10
      };
    },
    adjustLabelScale() {
      const s = this.pixiApp && this.pixiApp.stage ? (this.pixiApp.stage.scale.x || 1) : 1;
      const s = this.pixiApp && this.pixiApp.stage ? Math.abs(this.pixiApp.stage.scale.x || 1) : 1;
      const minPx = 14;
      const vw = this.pixiApp.view.width;
      const vh = this.pixiApp.view.height;
      const pos = this.pixiApp.stage.position;
      const margin = 50;
      const mirrorSign = this.mapMirrorX ? -1 : 1;
      const inverseRotation = -((this.mapRotation % 360) * Math.PI / 180);
      const tmpPoint = new PIXI.Point();
      this.pixiStaMap && this.pixiStaMap.forEach((sprite) => {
        const textObj = sprite && sprite.textObj;
        if (!textObj) { return; }
@@ -1325,11 +1655,11 @@
        let scale = minPx / (base * s);
        if (!isFinite(scale)) { scale = 1; }
        scale = Math.max(0.8, Math.min(scale, 3));
        textObj.scale.set(scale);
        textObj.scale.set(scale * mirrorSign, scale);
        textObj.rotation = inverseRotation;
        textObj.position.set(sprite.width / 2, sprite.height / 2);
        const sx = pos.x + sprite.x * s;
        const sy = pos.y + sprite.y * s;
        const on = sx >= -margin && sy >= -margin && sx <= vw + margin && sy <= vh + margin;
        sprite.getGlobalPosition(tmpPoint);
        const on = tmpPoint.x >= -margin && tmpPoint.y >= -margin && tmpPoint.x <= vw + margin && tmpPoint.y <= vh + margin;
        textObj.visible = (s >= 0.25) && on;
      });
      this.pixiCrnMap && this.pixiCrnMap.forEach((sprite) => {
@@ -1339,11 +1669,11 @@
        let scale = minPx / (base * s);
        if (!isFinite(scale)) { scale = 1; }
        scale = Math.max(0.8, Math.min(scale, 3));
        textObj.scale.set(scale);
        textObj.scale.set(scale * mirrorSign, scale);
        textObj.rotation = inverseRotation;
        textObj.position.set(sprite.width / 2, sprite.height / 2);
        const sx = pos.x + sprite.x * s;
        const sy = pos.y + sprite.y * s;
        const on = sx >= -margin && sy >= -margin && sx <= vw + margin && sy <= vh + margin;
        sprite.getGlobalPosition(tmpPoint);
        const on = tmpPoint.x >= -margin && tmpPoint.y >= -margin && tmpPoint.x <= vw + margin && tmpPoint.y <= vh + margin;
        textObj.visible = (s >= 0.25) && on;
      });
      this.pixiDualCrnMap && this.pixiDualCrnMap.forEach((sprite) => {
@@ -1353,11 +1683,11 @@
        let scale = minPx / (base * s);
        if (!isFinite(scale)) { scale = 1; }
        scale = Math.max(0.8, Math.min(scale, 3));
        textObj.scale.set(scale);
        textObj.scale.set(scale * mirrorSign, scale);
        textObj.rotation = inverseRotation;
        textObj.position.set(sprite.width / 2, sprite.height / 2);
        const sx = pos.x + sprite.x * s;
        const sy = pos.y + sprite.y * s;
        const on = sx >= -margin && sy >= -margin && sx <= vw + margin && sy <= vh + margin;
        sprite.getGlobalPosition(tmpPoint);
        const on = tmpPoint.x >= -margin && tmpPoint.y >= -margin && tmpPoint.x <= vw + margin && tmpPoint.y <= vh + margin;
        textObj.visible = (s >= 0.25) && on;
      });
      this.pixiRgvMap && this.pixiRgvMap.forEach((sprite) => {
@@ -1367,20 +1697,226 @@
        let scale = minPx / (base * s);
        if (!isFinite(scale)) { scale = 1; }
        scale = Math.max(0.8, Math.min(scale, 3));
        textObj.scale.set(scale);
        textObj.scale.set(scale * mirrorSign, scale);
        textObj.rotation = inverseRotation;
        textObj.position.set(sprite.width / 2, sprite.height / 2);
        const sx = pos.x + sprite.x * s;
        const sy = pos.y * s + pos.y;
        const on = sx >= -margin && sy >= -margin && sx <= vw + margin && sy <= vh + margin;
        sprite.getGlobalPosition(tmpPoint);
        const on = tmpPoint.x >= -margin && tmpPoint.y >= -margin && tmpPoint.x <= vw + margin && tmpPoint.y <= vh + margin;
        textObj.visible = (s >= 0.25) && on;
      });
    },
    rotateMap() {
      this.mapRotation = (this.mapRotation + 90) % 360;
      this.applyMapTransform(true);
      this.saveMapTransformConfig();
    },
    toggleMirror() {
      this.mapMirrorX = !this.mapMirrorX;
      this.applyMapTransform(true);
      this.saveMapTransformConfig();
    },
    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;
    },
    parseMirror(value) {
      if (value === true || value === false) { return value; }
      if (value == null) { return false; }
      const str = String(value).toLowerCase();
      return str === '1' || str === 'true' || str === 'y';
    },
    loadMapTransformConfig() {
      if (!window.$ || typeof baseUrl === 'undefined') { return; }
      $.ajax({
        url: baseUrl + "/config/listAll/auth",
        headers: { 'token': localStorage.getItem('token') },
        dataType: 'json',
        method: 'GET',
        success: (res) => {
          if (!res || res.code !== 200 || !Array.isArray(res.data)) {
            if (res && res.code === 403) { parent.location.href = baseUrl + "/login"; }
            return;
          }
          const byCode = {};
          res.data.forEach((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 (mirrorCfg && mirrorCfg.value != null) {
            this.mapMirrorX = this.parseMirror(mirrorCfg.value);
          }
          if (rotateCfg == null || mirrorCfg == null) {
            this.createMapTransformConfigIfMissing(rotateCfg, mirrorCfg);
          }
          if (this.mapContentSize && this.mapContentSize.width > 0) {
            this.applyMapTransform(true);
          }
        }
      });
    },
    createMapTransformConfigIfMissing(rotateCfg, mirrorCfg) {
      if (!window.$ || typeof baseUrl === 'undefined') { return; }
      const createList = [];
      if (!rotateCfg) {
        createList.push({
          name: '地图旋转',
          code: this.mapConfigCodes.rotate,
          value: String(this.mapRotation || 0),
          type: 1,
          status: 1,
          selectType: 'map'
        });
      }
      if (!mirrorCfg) {
        createList.push({
          name: '地图镜像',
          code: this.mapConfigCodes.mirror,
          value: this.mapMirrorX ? '1' : '0',
          type: 1,
          status: 1,
          selectType: 'map'
        });
      }
      createList.forEach((cfg) => {
        $.ajax({
          url: baseUrl + "/config/add/auth",
          headers: { 'token': localStorage.getItem('token') },
          method: 'POST',
          data: cfg
        });
      });
    },
    saveMapTransformConfig() {
      if (!window.$ || typeof baseUrl === 'undefined') { return; }
      const updateList = [
        { code: this.mapConfigCodes.rotate, value: String(this.mapRotation || 0) },
        { code: this.mapConfigCodes.mirror, value: this.mapMirrorX ? '1' : '0' }
      ];
      $.ajax({
        url: baseUrl + "/config/updateBatch",
        headers: { 'token': localStorage.getItem('token') },
        data: JSON.stringify(updateList),
        dataType: 'json',
        contentType: 'application/json;charset=UTF-8',
        method: 'POST'
      });
    },
    getTransformedContentSize() {
      const size = this.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 };
    },
    fitStageToContent() {
      if (!this.pixiApp || !this.mapContentSize) { return; }
      const size = this.getTransformedContentSize();
      const contentW = size.width || 0;
      const contentH = size.height || 0;
      if (contentW <= 0 || contentH <= 0) { return; }
      const vw = this.pixiApp.view.width;
      const vh = this.pixiApp.view.height;
      let scale = Math.min(vw / contentW, vh / contentH) * 0.95;
      if (!isFinite(scale) || scale <= 0) { scale = 1; }
      const baseW = this.mapContentSize.width || contentW;
      const baseH = this.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;
      this.pixiApp.stage.setTransform(posX, posY, scaleX, scaleY, 0, 0, 0, 0, 0);
    },
    applyMapTransform(fitToView) {
      if (!this.mapRoot || !this.mapContentSize) { return; }
      const contentW = this.mapContentSize.width || 0;
      const contentH = this.mapContentSize.height || 0;
      if (contentW <= 0 || contentH <= 0) { return; }
      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(); }
      this.scheduleAdjustLabels();
    },
    scheduleAdjustLabels() {
      if (this.adjustLabelTimer) { clearTimeout(this.adjustLabelTimer); }
      this.adjustLabelTimer = setTimeout(() => {
        this.adjustLabelScale();
        this.updateShelfTooltipVisibilityByScale();
        this.adjustLabelTimer = null;
      }, 20);
    }
  }
});
src/main/webapp/static/js/basRgv/basRgv.js
@@ -146,7 +146,7 @@
        admin.open({
            type: 1,
            area: '600px',
            title: (mData ? '修改' : '添加') + '订单状态',
            title: (mData ? '修改' : '添加') + 'RGV',
            content: $('#editDialog').html(),
            success: function (layero, dIndex) {
                layDateRender(mData);
src/main/webapp/views/basRgv/basRgv.html
@@ -73,6 +73,12 @@
        <div class="layui-row">
            <div class="layui-col-md12">
                <div class="layui-form-item">
                    <label class="layui-form-label">RGV编号: </label>
                    <div class="layui-input-block">
                        <input class="layui-input" name="rgvNo" placeholder="请输入RGV编号">
                    </div>
                </div>
                <div class="layui-form-item">
                    <label class="layui-form-label">状态: </label>
                    <div class="layui-input-block">
                        <select name="status">