chenlu
2025-11-21 d473c7b2b07cfeee3740f40dfdc95d722d8da3c2
north-glass-erp/northglass-erp/src/views/pp/glassOptimize/page/OptimizationRect.vue
@@ -1,15 +1,15 @@
<template>
  <div style="display: flex; height: 100vh;">
  <div style="display: flex; height: 90vh;">
    <!-- Sidebar -->
    <div class="sidebar" style="width: 200px; background: #f4f4f4; padding: 10px;">
    <div class="sidebar" style="width: 200px; background: #f4f4f4; padding: 10px; height: 93%; overflow-y: auto; max-height: 90vh; border-radius: 8px;">
      <div
        v-for="(layout, layoutIndex) in layouts"
        :key="layoutIndex"
        class="sidebar-item"
        @click="selectLayout(layoutIndex)"
        :class="{ 'selected': selectedLayoutIndex === layoutIndex }"
          v-for="(layout, layoutIndex) in layouts"
          :key="layoutIndex"
          class="sidebar-item"
          @click="selectLayout(layoutIndex)"
          :class="{ 'selected': selectedLayoutIndex === layoutIndex }"    style="margin-bottom: 5px;"
      >
        {{ layout.width }} × {{ layout.height }} × {{ layout.SameCount }}
        {{ layout.realWidth }} × {{ layout.realHeight }} × {{ layout.quantity }}
      </div>
    </div>
@@ -27,41 +27,51 @@
        </div>
        <!-- Layout Container -->
        <div class="layout-container" :style="layoutContainerStyle1(layoutIndex)">
        <div class="layout-container" :style="layoutContainerStyle(layoutIndex)">
          <!-- 灰色矩形 -->
          <div
            v-for="(rect, rectIndex) in layout.rects.filter(r => r.isRemain)"
            v-for="(glassDetail, rectIndex) in layout.glassDetails.filter(r => r.isRemain)"
            :key="`gray-${rectIndex}`"
            :ref="(el) => { if (el) rectsElements[layoutIndex + '-' + rectIndex] = el }"
            class="layout-rect"
            :style="rectStyle(rect, layoutIndex)"
            @contextmenu.prevent="handleGrayRectRightClick(layoutIndex, rectIndex)"
          />
            class="layout-glassDetail"
            :style="rectStyle1(glassDetail, layoutIndex)"
            @contextmenu.prevent="handleGrayRectRightClick(layoutIndex, rectIndex,glassDetail)"
          >
<!--            <div class="glassDetail-content">
              <div class="size">{{ glassDetail.width }}×{{ glassDetail.height }}</div>
              <div v-if="showJiaHao" class="jia-hao">{{ glassDetail.JiaHao }}</div>
              <div v-if="showProcessId" class="liuchengka">{{ glassDetail.liuchengka }}</div>
            </div>-->
          </div>
          <!-- 蓝色矩形 -->
          <div
            v-for="(rect, rectIndex) in layout.rects.filter(r => !r.isRemain)"
            v-for="(glassDetail, rectIndex) in layout.glassDetails.filter(r => !r.isRemain)"
            :key="`blue-${rectIndex}`"
            :ref="(el) => { if (el) rectsElements[layoutIndex + '-' + rectIndex] = el }"
            class="layout-rect"
            :style="rectStyle(rect, layoutIndex)"
            class="layout-glassDetail"
            :style="rectStyle(glassDetail, layoutIndex)"
            @contextmenu.prevent="handleRectRightClick(layoutIndex, rectIndex)"
            @mousedown="handleRectDragStart(layoutIndex, rectIndex)"
            @mousemove="handleRectDragging"
            @mouseup="handleRectDragEnd"
            @mouseleave="handleRectDragEnd"
            @click="handleRectClick(layoutIndex, rectIndex)"
          >
            <div class="rect-content">
              <div class="size">{{ rect.w }}×{{ rect.h }}</div>
              <div class="jia-hao">{{ rect.JiaHao }}</div>
            <div class="glassDetail-content">
              <div class="size">{{ glassDetail.realWidth }}×{{ glassDetail.realHeight }}</div>
              <div>{{glassDetail.polySort }}</div>
              <div v-if="showJiaHao" class="jia-hao">{{ glassDetail.rackNo }}</div>
              <div v-if="showProcessId" class="liuchengka">{{ glassDetail.processId }}</div>
            </div>
          </div>
        </div>
        </div>
      </div>
    </div>
    <!-- 提交按钮 -->
    <button @click="submitLayouts" style="position: fixed; bottom: 20px; right: 20px; padding: 10px; background: #4CAF50; color: white; border: none; border-radius: 5px; cursor: pointer;">
    <button @click="submitLayouts" style="position: fixed; top: 90px; right: 20px; padding: 10px; background: #409eff; color: white; border: none; border-radius: 5px; cursor: pointer;">
      保存调整
    </button>
  </div>
@@ -69,10 +79,17 @@
<script setup>
import { ref, reactive, onMounted, onUnmounted } from 'vue';
import { useRouter } from 'vue-router';
import request from "@/utils/request";
const router = useRouter();
import { useI18n } from "vue-i18n";
import { ElMessage, ElMessageBox } from "element-plus";
import useUserInfoStore from "@/stores/userInfo";
const { t } = useI18n();
const userStore = useUserInfoStore()
const username = userStore.user.userName
let clickEventListener = null;
const props = defineProps({
  layoutData: { type: Object, required: true },
  gw: { type: Number, default: 1000 },
@@ -85,41 +102,86 @@
const rectsElements = ref({});
const focusIndex = ref(null);
const layouts = ref([]);
const layoutsHead = ref();
const panelClass = ref('');
const panelStyle = ref(props.style);
const rectClass = ref('layout-rect');
const rectClass = ref('layout-glassDetail');
const selectedLayoutIndex = ref(0);
const currentRect = ref(null);
const dragging = ref(false);
const dragStartPos = ref({ x: 0, y: 0 });
const dragRect = ref(null);
const showJiaHao = ref(false);
const showProcessId = ref(false);
const themeColor = ref(null);
// 提交布局数据到后端
//保存调整
const submitLayouts = async () => {
  layouts.value.forEach(layout => {
    layout.rects.forEach(rect => {
      rect.x = Math.round(rect.x);
      rect.y = Math.round(rect.y);
      rect.w = Math.round(rect.w);
      rect.h = Math.round(rect.h);
    layout.glassDetails.forEach(glassDetail => {
      glassDetail.x = parseFloat(glassDetail.x.toFixed(2));
      glassDetail.y = parseFloat(glassDetail.y.toFixed(2));
      glassDetail.width = parseFloat(glassDetail.width.toFixed(2));
      glassDetail.height = parseFloat(glassDetail.height.toFixed(2));
    });
  });
  const savedProjectNo = localStorage.getItem('projectNo');
  const processId = savedProjectNo;
  const Layouts = layouts.value;
  request.post(`/glassOptimize/updateOptimizeResult/${processId}`, JSON.stringify({ Layouts }), {
  // 构造与原始数据结构一致的对象
  const saveData = {
    projectNo: processId,
    layouts: layouts.value,
    // 复制原始数据中的其他必要字段
    ...layoutsHead.value
  };
  // 确保 Layouts 字段是序列化的字符串
  const requestData = {
    Layouts: JSON.stringify(saveData)
  };
  request.post(`/glassOptimize/updateOptimizeResult/${processId}`, layoutsHead.value, {
    headers: {
      'Content-Type': 'application/json'
    }
  }).then((res) => {
    if (res.code == 200 && res.data === true) {
      ElMessage.success(t('basicData.msg.saveSuccess'));
      // // 保存成功后跳转到数控界面
      // router.push({ path: '/main/glassOptimize/OptimizeControl' });
    } else {
      ElMessage.warning(res.msg);
    }
  });
};
//查询设置的基础信息架号,矩形颜色,订单序号等
const fetchSettings = async (username) => {
  try {
    const response = await request.post(`/glassOptimize/selectOptimizeParms/${username}`);
    if (response.code == 200) {
      if (!response.data) {
        console.error('响应数据为空');
        return;
      }
      const parsedData = JSON.parse(response.data);
      if (parsedData.display && parsedData.frameNumber) {
        showJiaHao.value = parsedData.display.frameNumber;
      }
      if (parsedData.display && parsedData.orderNumber) {
        showProcessId.value = parsedData.display.orderNumber;
      }
      if (parsedData.display) {
        themeColor.value = parsedData.display.themeColor;
      }
    } else {
      console.error('请求失败,状态码:', response.code);
    }
  } catch (error) {
    console.error('请求发生错误:', error);
  }
};
//添加成品
const showAddDialog = (layoutIndex, rectIndex) => {
  ElMessageBox.prompt('请输入成品的宽度和高度', '添加成品', {
    inputType: 'text',
@@ -139,99 +201,163 @@
    },
    inputErrorMessage: '输入格式不正确'
  })
  .then(({ value }) => {
    const values = value.split(',').map(v => parseFloat(v.trim()));
    const newRect = {
      x: 0,
      y: 0,
      w: values[0],
      h: values[1],
      isRemain: false,
      JiaHao: `新成品`
    };
    addNewRect(layoutIndex, newRect);
  })
  .catch(() => {
    // 用户取消
  });
    .then(({ value }) => {
      const values = value.split(',').map(v => parseFloat(v.trim()));
      const newRect = {
        x: 0,
        y: 0,
        width: values[0],
        height: values[1],
        isRemain: false
      };
      addNewRect(layoutIndex, newRect);
    })
    .catch(() => {
      // 用户取消
    });
};
//添加成品逻辑判断
const addNewRect = (layoutIndex, newRect) => {
  const layout = layouts.value[layoutIndex];
  layout.rects.push(newRect);
  adjustGrayRectangles(layoutIndex);
  const bestFitPosition = findBestFitPosition(layoutIndex, newRect);
  if (bestFitPosition) {
    newRect.x = bestFitPosition.x;
    newRect.y = bestFitPosition.y;
    layout.glassDetails.push(newRect);
    adjustGrayRectangles(layoutIndex);
  } else {
    ElMessage.warning('无法放置,没有足够的空间');
  }
};
const layoutContainerStyle = (layoutIndex) => {
  const containerWidth = (props.gw - 210) / 2;
  const containerHeight = (props.gh - 100) / 3;
//添加成品判断是否可以放下
const findBestFitPosition = (layoutIndex, newRect) => {
  const layout = layouts.value[layoutIndex];
  const scale = Math.min(
    (props.gw - 100) / layout.width,
    (props.gh - 100) / layout.height
  const obstacles = layout.glassDetails.filter(r => !r.isRemain);
  let bestFit = null;
  let minAreaDifference = Infinity;
  const remainingAreas = calculateRemainingAreas(layout.width, layout.height, obstacles);
  remainingAreas.forEach(area => {
    if (newRect.width <= area.width && newRect.height <= area.height) {
      const areaDifference = Math.abs(area.width * area.height - newRect.width * newRect.height);
      if (areaDifference < minAreaDifference) {
        minAreaDifference = areaDifference;
        bestFit = {
          x: area.x,
          y: area.y,
          width: newRect.width,
          height: newRect.height
        };
      }
    }
  });
  return bestFit;
};
//版图内容样式加载
const layoutContainerStyle = (layoutIndex) => {
  const layout = layouts.value[layoutIndex];
  const scale = Math.min(0.25
  );
  return {
    position: 'absolute',
    left: `${(props.gw - layout.width * scale) / 2}px`,
    top: `${(props.gh - layout.height * scale) / 2}px`,
    left: `${layout.leftTrim * scale}px`,
    bottom: `${layout.upTrim * scale}px`,
    width: `${layout.width * scale}px`,
    height: `${layout.height * scale}px`,
    overflow: 'visible',
    border: '1px solid #ccc',
    //border: '1px solid #ccc',
    background: '#fff'
  };
};
const layoutInfoStyle = (layoutIndex) => {
const layoutContainerStyle1 = (layoutIndex) => {
  const layout = layouts.value[layoutIndex];
  const scale = Math.min(
    (props.gw - 100) / layout.width,
    (props.gh - 100) / layout.height
  const scale = Math.min(0.25
  );
  return {
    position: 'absolute',
    left: `${(props.gw - layout.width * scale) / 2}px`,
    top: `${(props.gh - layout.height * scale) / 2 - 45}px`,
    left: `20px`,
    top: `140px`,
    width: `${layout.realWidth * scale}px`,
    height: `${layout.realHeight * scale}px`,
    overflow: 'visible',
    //border: '1px solid #ccc',
    background: '#fff'
  };
};
//版图内容头部样式加载
const layoutInfoStyle = (layoutIndex) => {
  const layout = layouts.value[layoutIndex];
  const scale = Math.min(0.25
  );
  return {
    position: 'absolute',
    left: `20px`,
    top: `100px`,
    background: 'none',
    textAlign: 'center',
    zIndex: 1000
  };
};
const rectStyle = (rect, layoutIndex) => {
//版图内容小片样式加载
const rectStyle = (glassDetail, layoutIndex) => {
  const layout = layouts.value[layoutIndex];
  const scale = Math.min(
    (props.gw - 100) / layout.width,
    (props.gh - 100) / layout.height
  const scale = Math.min(0.25
  );
  return {
    position: 'absolute',
    left: `${rect.x * scale}px`,
    top: `${rect.y * scale}px`,
    width: `${rect.w * scale}px`,
    height: `${rect.h * scale}px`,
    backgroundColor: rect.isRemain ? '#f0f0f0' : '#a0d8ef',
    left: `${glassDetail.x * scale}px`,
    top: `${glassDetail.y * scale}px`,
    width: `${glassDetail.width * scale}px`,
    height: `${glassDetail.height * scale}px`,
    backgroundColor: glassDetail.isRemain ? '#f0f0f0' : themeColor.value,
    border: '1px solid #000',
    cursor: 'pointer',
    draggable: !rect.isRemain,
    zIndex: rect.isRemain ? 1 : 2
    draggable: !glassDetail.isRemain,
    zIndex: glassDetail.isRemain ? 1 : 2
  };
};
const rectStyle1 = (glassDetail, layoutIndex) => {
  const layout = layouts.value[layoutIndex];
  const scale = Math.min(0.25
  );
  return {
    position: 'absolute',
    left: `${glassDetail.x * scale}px`,
    top: `${glassDetail.y * scale}px`,
    width: `${glassDetail.width * scale}px`,
    height: `${glassDetail.height * scale}px`,
    backgroundColor: glassDetail.isRemain ? '#f0f0f0' : themeColor.value,
    border: '1px solid #000',
    cursor: 'pointer',
    draggable: !glassDetail.isRemain,
    zIndex: glassDetail.isRemain ? 1 : 2
  };
};
//点击小片
const handleRectClick = (layoutIndex, rectIndex) => {
  focusIndex.value = { layoutIndex, rectIndex };
  emit('rectClicked', layoutIndex, rectIndex);
};
//小片右键菜单功能
const handleRectRightClick = (layoutIndex, rectIndex) => {
  const rect = layouts.value[layoutIndex].rects[rectIndex];
  if (rect.isRemain) return;
  const glassDetail = layouts.value[layoutIndex].glassDetails[rectIndex];
  if (glassDetail.isRemain) return;
  const contextMenu = document.createElement('div');
  contextMenu.className = 'context-menu';
  contextMenu.style.position = 'absolute';
  contextMenu.style.left = `${event.clientX}px`;
  contextMenu.style.top = `${event.clientY}px`;
  contextMenu.style.bottom = `${event.clientY}px`;
  contextMenu.style.backgroundColor = '#fff';
  contextMenu.style.border = '1px solid #ccc';
  contextMenu.style.padding = '5px';
@@ -249,7 +375,7 @@
  moveUpAndRotateItem.textContent = '向上移动并旋转';
  moveUpAndRotateItem.style.cursor = 'pointer';
  moveUpAndRotateItem.addEventListener('click', () => {
    moveRectAndRotate(layoutIndex, rectIndex, 'up');
    moveRectAndRotate(layoutIndex, rectIndex, 'down');
    document.body.removeChild(contextMenu);
  });
@@ -257,7 +383,7 @@
  moveDownAndRotateItem.textContent = '向下移动并旋转';
  moveDownAndRotateItem.style.cursor = 'pointer';
  moveDownAndRotateItem.addEventListener('click', () => {
    moveRectAndRotate(layoutIndex, rectIndex, 'down');
    moveRectAndRotate(layoutIndex, rectIndex, 'up');
    document.body.removeChild(contextMenu);
  });
@@ -281,7 +407,7 @@
  moveUpItem.textContent = '向上移动';
  moveUpItem.style.cursor = 'pointer';
  moveUpItem.addEventListener('click', () => {
    moveRect(layoutIndex, rectIndex, 'up');
    moveRect(layoutIndex, rectIndex, 'down');
    document.body.removeChild(contextMenu);
  });
@@ -289,7 +415,7 @@
  moveDownItem.textContent = '向下移动';
  moveDownItem.style.cursor = 'pointer';
  moveDownItem.addEventListener('click', () => {
    moveRect(layoutIndex, rectIndex, 'down');
    moveRect(layoutIndex, rectIndex, 'up');
    document.body.removeChild(contextMenu);
  });
@@ -325,6 +451,22 @@
    document.body.removeChild(contextMenu);
  });
  const mirrorXItem = document.createElement('div');
  mirrorXItem.textContent = 'X镜像';
  mirrorXItem.style.cursor = 'pointer';
  mirrorXItem.addEventListener('click', () => {
    mirrorLayoutX(layoutIndex);
    document.body.removeChild(contextMenu);
  });
  const mirrorYItem = document.createElement('div');
  mirrorYItem.textContent = 'Y镜像';
  mirrorYItem.style.cursor = 'pointer';
  mirrorYItem.addEventListener('click', () => {
    mirrorLayoutY(layoutIndex);
    document.body.removeChild(contextMenu);
  });
  contextMenu.appendChild(rotateItem);
  contextMenu.appendChild(moveUpAndRotateItem);
  contextMenu.appendChild(moveDownAndRotateItem);
@@ -336,55 +478,26 @@
  contextMenu.appendChild(moveRightItem);
  contextMenu.appendChild(deleteItem);
  contextMenu.appendChild(addItem);
  contextMenu.appendChild(mirrorXItem);
  contextMenu.appendChild(mirrorYItem);
  document.body.appendChild(contextMenu);
};
const handleGrayRectRightClick = (layoutIndex, rectIndex) => {
  const rect = layouts.value[layoutIndex].rects[rectIndex];
  if (!rect.isRemain) return;
//余料右键菜单功能
const handleGrayRectRightClick = (layoutIndex, rectIndex,glassDetails) => {
  //const glassDetail = glassDetails[rectIndex];
  if (!glassDetails.isRemain) return;
  const contextMenu = document.createElement('div');
  contextMenu.className = 'context-menu';
  contextMenu.style.position = 'absolute';
  contextMenu.style.left = `${event.clientX}px`;
  contextMenu.style.top = `${event.clientY}px`;
  contextMenu.style.bottom = `${event.clientY}px`;
  contextMenu.style.backgroundColor = '#fff';
  contextMenu.style.border = '1px solid #ccc';
  contextMenu.style.padding = '5px';
  contextMenu.style.zIndex = 1001;
  const mergeLeftItem = document.createElement('div');
  mergeLeftItem.textContent = '向左合并';
  mergeLeftItem.style.cursor = 'pointer';
  mergeLeftItem.addEventListener('click', () => {
    mergeGrayRects(layoutIndex, rectIndex, 'left');
    document.body.removeChild(contextMenu);
  });
  const mergeRightItem = document.createElement('div');
  mergeRightItem.textContent = '向右合并';
  mergeRightItem.style.cursor = 'pointer';
  mergeRightItem.addEventListener('click', () => {
    mergeGrayRects(layoutIndex, rectIndex, 'right');
    document.body.removeChild(contextMenu);
  });
  const mergeUpItem = document.createElement('div');
  mergeUpItem.textContent = '向上合并';
  mergeUpItem.style.cursor = 'pointer';
  mergeUpItem.addEventListener('click', () => {
    mergeGrayRects(layoutIndex, rectIndex, 'up');
    document.body.removeChild(contextMenu);
  });
  const mergeDownItem = document.createElement('div');
  mergeDownItem.textContent = '向下合并';
  mergeDownItem.style.cursor = 'pointer';
  mergeDownItem.addEventListener('click', () => {
    mergeGrayRects(layoutIndex, rectIndex, 'down');
    document.body.removeChild(contextMenu);
  });
  const addItem = document.createElement('div');
  addItem.textContent = '添加成品';
@@ -394,19 +507,34 @@
    document.body.removeChild(contextMenu);
  });
  contextMenu.appendChild(mergeLeftItem);
  contextMenu.appendChild(mergeRightItem);
  contextMenu.appendChild(mergeUpItem);
  contextMenu.appendChild(mergeDownItem);
  const mirrorXItem = document.createElement('div');
  mirrorXItem.textContent = 'X镜像';
  mirrorXItem.style.cursor = 'pointer';
  mirrorXItem.addEventListener('click', () => {
    mirrorLayoutX(layoutIndex);
    document.body.removeChild(contextMenu);
  });
  const mirrorYItem = document.createElement('div');
  mirrorYItem.textContent = 'Y镜像';
  mirrorYItem.style.cursor = 'pointer';
  mirrorYItem.addEventListener('click', () => {
    mirrorLayoutY(layoutIndex);
    document.body.removeChild(contextMenu);
  });
  contextMenu.appendChild(addItem);
  contextMenu.appendChild(mirrorXItem);
  contextMenu.appendChild(mirrorYItem);
  document.body.appendChild(contextMenu);
};
//小片鼠标按下事件
const handleRectDragStart = (layoutIndex, rectIndex) => {
  const layout = layouts.value[layoutIndex];
  const rect = layout.rects[rectIndex];
  if (rect.isRemain) return;
  const glassDetail = layout.glassDetails[rectIndex];
  if (glassDetail.isRemain) return;
  dragging.value = true;
  dragRect.value = { layoutIndex, rectIndex };
@@ -416,13 +544,19 @@
  };
};
//小片鼠标移动事件
const handleRectDragging = (event) => {
  if (!dragging.value || !dragRect.value) return;
  const layoutIndex = dragRect.value.layoutIndex;
  const rectIndex = dragRect.value.rectIndex;
  const layout = layouts.value[layoutIndex];
  const rect = layout.rects[rectIndex];
  const glassDetail = layout.glassDetails[rectIndex];
  // 保存原始坐标用于计算偏移量
  const originalX = glassDetail.x;
  const originalY = glassDetail.y;
  const scale = Math.min(
    (props.gw - 100) / layout.width,
    (props.gh - 100) / layout.height
@@ -431,31 +565,43 @@
  const deltaX = event.clientX - dragStartPos.value.x;
  const deltaY = event.clientY - dragStartPos.value.y;
  // 只调整位置,不调整大小
  const newRect = { ...rect };
  const newRect = { ...glassDetail };
  newRect.x += deltaX / scale;
  newRect.y += deltaY / scale;
  // 检查是否与其他蓝色矩形重叠
  const otherRects = layout.rects.filter(r => !r.isRemain && r !== rect);
  const otherRects = layout.glassDetails.filter(r => !r.isRemain && r !== glassDetail);
  let isValidMove = true;
  otherRects.forEach(otherRect => {
    if (checkOverlap(newRect, otherRect)) {
      isValidMove = false;
    }
  });
  // 检查是否超出布局边界
  if (newRect.x < 0 || newRect.y < 0 ||
      newRect.x + newRect.w > layout.width ||
      newRect.y + newRect.h > layout.height) {
      newRect.x + newRect.width > layout.width ||
      newRect.y + newRect.height > layout.height) {
    isValidMove = false;
  }
  if (isValidMove) {
    rect.x = newRect.x;
    rect.y = newRect.y;
    glassDetail.x = newRect.x;
    glassDetail.y = newRect.y;
    // 更新glassPoint坐标
    if (glassDetail.glassPoint && Array.isArray(glassDetail.glassPoint)) {
      const offsetX = glassDetail.x - originalX;
      const offsetY = glassDetail.y - originalY;
      glassDetail.glassPoint.forEach(point => {
        point.X += offsetX;
        point.Y += offsetY;
        // 添加精度控制
        point.X = parseFloat(point.X.toFixed(2));
        point.Y = parseFloat(point.Y.toFixed(2));
      });
    }
    dragStartPos.value = {
      x: event.clientX,
      y: event.clientY
@@ -464,26 +610,21 @@
  }
};
//小片鼠标松开事件
const handleRectDragEnd = () => {
  if (dragRect.value) {
    const layoutIndex = dragRect.value.layoutIndex;
    const rectIndex = dragRect.value.rectIndex;
    const rect = layouts.value[layoutIndex].rects[rectIndex];
    const glassDetail = layouts.value[layoutIndex].glassDetails[rectIndex];
    const layout = layouts.value[layoutIndex];
    const scale = Math.min(
      (props.gw - 100) / layout.width,
      (props.gh - 100) / layout.height
    );
    // 拖动结束后自动对齐到最近的整数位置
    rect.x = Math.round(rect.x);
    rect.y = Math.round(rect.y);
    // 只调整位置对齐,不调整大小
    //adjustAlignmentPosition(layoutIndex, rectIndex);
    // 调整灰色矩形
    adjustGrayRectangles(layoutIndex);
    glassDetail.x = parseFloat(glassDetail.x.toFixed(2));
    glassDetail.y = parseFloat(glassDetail.y.toFixed(2));
    adjustAlignmentPosition(layoutIndex, rectIndex);
  }
  dragging.value = false;
@@ -492,383 +633,662 @@
const adjustAlignmentPosition = (layoutIndex, rectIndex) => {
  const layout = layouts.value[layoutIndex];
  const rect = layout.rects[rectIndex];
  const otherRects = layout.rects.filter((r, i) => i !== rectIndex);
  const glassDetail = layout.glassDetails[rectIndex];
  const otherRects = layout.glassDetails.filter((r, i) => i !== rectIndex);
  const threshold = Math.max(rect.w, rect.h) * 0.1;
  const threshold = Math.max(glassDetail.width, glassDetail.height) * 0.1;
  otherRects.forEach(otherRect => {
    if (Math.abs(rect.x - otherRect.x) < threshold) {
      rect.x = Math.round((rect.x + otherRect.x) / 2);
    // 水平对齐
    if (Math.abs(glassDetail.x - otherRect.x) < threshold) {
      glassDetail.x = Math.round((glassDetail.x + otherRect.x) / 2);
    }
    if (Math.abs((rect.x + rect.w) - (otherRect.x + otherRect.w)) < threshold) {
      // 不调整宽度
    // 水平对齐右侧边缘
    if (Math.abs((glassDetail.x + glassDetail.width) - (otherRect.x + otherRect.width)) < threshold) {
      glassDetail.x = Math.round((otherRect.x + otherRect.width - glassDetail.width));
    }
    if (Math.abs(rect.y - otherRect.y) < threshold) {
      rect.y = Math.round((rect.y + otherRect.y) / 2);
    // 垂直对齐
    if (Math.abs(glassDetail.y - otherRect.y) < threshold) {
      glassDetail.y = Math.round((glassDetail.y + otherRect.y) / 2);
    }
    if (Math.abs((rect.y + rect.h) - (otherRect.y + otherRect.h)) < threshold) {
      // 不调整高度
    // 垂直对齐上边缘
    if (Math.abs((glassDetail.y + glassDetail.height) - (otherRect.y + otherRect.height)) < threshold) {
      glassDetail.y = Math.round((otherRect.y + otherRect.height - glassDetail.height));
    }
  });
  // 确保矩形不会超出布局边界
  glassDetail.x = Math.max(0, glassDetail.x);
  glassDetail.y = Math.max(0, glassDetail.y);
  glassDetail.x = Math.min(glassDetail.x, layout.width - glassDetail.width);
  glassDetail.y = Math.min(glassDetail.y, layout.height - glassDetail.height);
  // 调整后重新计算灰色余料
  adjustGrayRectangles(layoutIndex);
};
const mergeGrayRects = (layoutIndex, rectIndex, direction) => {
  const layout = layouts.value[layoutIndex];
  const rect = layout.rects[rectIndex];
  const grayRects = layout.rects.filter(r => r.isRemain);
const mergeAdjacentGrayRects = (glassDetails, totalWidth, totalHeight) => {
  const grayRects = glassDetails.filter(r => r.isRemain);
  const nonGrayRects = glassDetails.filter(r => !r.isRemain);
  let targetRect = null;
  switch (direction) {
    case 'left':
      targetRect = grayRects.find(r => r.x + r.w === rect.x && r.y === rect.y && r.h === rect.h);
      break;
    case 'right':
      targetRect = grayRects.find(r => r.x === rect.x + rect.w && r.y === rect.y && r.h === rect.h);
      break;
    case 'up':
      targetRect = grayRects.find(r => r.y + r.h === rect.y && r.x === rect.x && r.w === rect.w);
      break;
    case 'down':
      targetRect = grayRects.find(r => r.y === rect.y + rect.h && r.x === rect.x && r.w === rect.w);
      break;
  }
  if (targetRect) {
    const mergedRect = {
      x: Math.min(rect.x, targetRect.x),
      y: Math.min(rect.y, targetRect.y),
      w: Math.max(rect.x + rect.w, targetRect.x + targetRect.w) - Math.min(rect.x, targetRect.x),
      h: Math.max(rect.y + rect.h, targetRect.y + targetRect.h) - Math.min(rect.y, targetRect.y),
      isRemain: true
    };
    const index = layout.rects.indexOf(rect);
    layout.rects.splice(index, 1);
    const targetIndex = layout.rects.indexOf(targetRect);
    layout.rects.splice(targetIndex, 1);
    layout.rects.push(mergedRect);
    adjustGrayRectangles(layoutIndex);
  } else {
    ElMessage.warning('无法合并,没有相邻的灰色矩形');
  }
};
const mergeAdjacentGrayRects = (rects) => {
  const grayRects = rects.filter(r => r.isRemain);
  let merged = [];
  // 按坐标排序,优先按y坐标,其次按x坐标(这样更符合从上到下、从左到右的阅读习惯)
  grayRects.sort((a, b) => {
    if (a.x !== b.x) return a.x - b.x;
    return a.y - b.y;
    if (a.y !== b.y) return a.y - b.y;
    return a.x - b.x;
  });
  if (grayRects.length === 0) return;
  merged.push({ ...grayRects[0] });
  const merged = [];
  let current = { ...grayRects[0] };
  // 遍历所有余料矩形进行合并
  for (let i = 1; i < grayRects.length; i++) {
    const last = merged[merged.length - 1];
    const current = grayRects[i];
    const next = grayRects[i];
    if (current.x === last.x + last.w &&
        current.y === last.y &&
        current.h === last.h) {
      last.w += current.w;
      last.x = Math.round(last.x);
      last.y = Math.round(last.y);
      last.w = Math.round(last.w);
      last.h = Math.round(last.h);
    } else if (current.y === last.y + last.h &&
               current.x === last.x &&
               current.w === last.w) {
      last.h += current.h;
      last.x = Math.round(last);
      last.y = Math.round(last.y);
      last.w = Math.round(last.w);
      last.h = Math.round(last.h);
    } else {
      merged.push({
        x: Math.round(current.x),
        y: Math.round(current.y),
        w: Math.round(current.w),
        h: Math.round(current.h),
    // 检查是否可以水平合并(同一行,高度相同,相邻)
    if (current.y === next.y &&
        current.height === next.height &&
        current.x + current.width === next.x) {
      // 水平合并
      current.width += next.width;
    }
    // 检查是否可以垂直合并(同一列,宽度相同,相邻)
    else if (current.x === next.x &&
        current.width === next.width &&
        current.y + current.height === next.y) {
      // 垂直合并
      current.height += next.height;
    }
    else {
      // 无法合并,保存当前矩形,开始新的合并
      merged.push({
        x: current.x,
        y: current.y,
        width: current.width,
        height: current.height,
        isRemain: true
      });
      current = { ...next };
    }
  }
  const nonGray = rects.filter(r => !r.isRemain);
  rects.splice(0, rects.length, ...nonGray, ...merged);
  // 添加最后一个矩形
  merged.push({
    x: current.x,
    y: current.y,
    width: current.width,
    height: current.height,
    isRemain: true
  });
  // 重新构建数组:非余料 + 合并后的余料
  glassDetails.splice(0, glassDetails.length, ...nonGrayRects, ...merged);
};
// 确保返回的区域不重叠
const calculateRemainingAreas = (totalWidth, totalHeight, obstacles) => {
  // 从整个原片开始
  let remaining = [{ x: 0, y: 0, width: totalWidth, height: totalHeight }];
  // 逐个处理障碍物(已放置的玻璃片)
  obstacles.forEach(obstacle => {
    remaining = cutRemainingAreas(remaining, obstacle, totalWidth, totalHeight);
  });
  // 对结果进行排序,确保一致性
  remaining.sort((a, b) => {
    if (a.x !== b.x) return a.x - b.x;
    return a.y - b.y;
  });
  return remaining;
};
// 调整后重新计算灰色余料
const adjustGrayRectangles = (layoutIndex) => {
  const layout = layouts.value[layoutIndex];
  const rects = layout.rects;
  const nonGrayRects = rects.filter(rect => !rect.isRemain);
  const glassDetails = layout.glassDetails;
  const remainingAreas = calculateRemainingAreas(layout.width, layout.height, nonGrayRects);
  // 1. 筛选出非余料的玻璃片(即实际要切割的玻璃片)
  const nonGrayRects = glassDetails.filter(glassDetail => !glassDetail.isRemain);
  const currentGrayRects = rects.filter(r => r.isRemain);
  currentGrayRects.forEach((_, index) => {
    if (index >= remainingAreas.length) {
      rects.splice(index, 1);
    }
  // 2. 计算剩余可用区域
  let remainingAreas = calculateRemainingAreas(layout.width, layout.height, nonGrayRects);
  // 3. 去重处理 - 更严格的去重逻辑
  const uniqueArr = removeDuplicateAreas(remainingAreas);
  // 4. 获取当前已存在的余料矩形(需要保留引用以便更新)
  const currentGrayRects = glassDetails.filter(r => r.isRemain);
  // 5. 清除所有现有的余料矩形
  // 先收集非余料矩形
  const nonRemainRects = glassDetails.filter(r => !r.isRemain);
  // 6. 重新构建玻璃详情数组
  // 保留非余料矩形
  const newGlassDetails = [...nonRemainRects];
  // 添加新的余料矩形
  uniqueArr.forEach((area) => {
    newGlassDetails.push({
      x: area.x,
      y: area.y,
      width: area.width,
      height: area.height,
      isRemain: true
    });
  });
  remainingAreas.forEach((area, index) => {
    if (index < currentGrayRects.length) {
      currentGrayRects[index].x = Math.round(area.x);
      currentGrayRects[index].y = Math.round(area.y);
      currentGrayRects[index].w = Math.round(area.w);
      currentGrayRects[index].h = Math.round(area.h);
    } else {
      rects.push({
        x: Math.round(area.x),
        y: Math.round(area.y),
        w: Math.round(area.w),
        h: Math.round(area.h),
        isRemain: true
      });
    }
  });
  // 7. 更新布局的玻璃详情
  layout.glassDetails = newGlassDetails;
  mergeAdjacentGrayRects(rects);
  // 8. 合并相邻的余料矩形
  mergeAdjacentGrayRects(layout.glassDetails, layout.width, layout.height);
};
const removeDuplicateAreas = (areas) => {
  const result = [];
  areas.forEach(area => {
    // 检查是否与已存在的区域重叠或相等
    const isDuplicate = result.some(existingArea => {
      return (
          existingArea.x === area.x &&
          existingArea.y === area.y &&
          existingArea.width === area.width &&
          existingArea.height === area.height
      );
    });
    if (!isDuplicate) {
      result.push(area);
    }
  });
  return result;
};
//旋转方法
const rotateRect = (layoutIndex, rectIndex) => {
  const layout = layouts.value[layoutIndex];
  const rect = layout.rects[rectIndex];
  const originalState = { ...rect };
  const glassDetail = layout.glassDetails[rectIndex];
  const originalState = { ...glassDetail };
  // 旋转矩形
  const temp = rect.w;
  rect.w = rect.h;
  rect.h = temp;
  const temp = glassDetail.width;
  glassDetail.width = glassDetail.height;
  glassDetail.height = temp;
  // 检查旋转后是否与其他蓝色矩形重叠
  const otherRects = layout.rects.filter(r => !r.isRemain && r !== rect);
  const otherRects = layout.glassDetails.filter(r => !r.isRemain && r !== glassDetail);
  let isValidRotation = true;
  otherRects.forEach(otherRect => {
    if (checkOverlap(rect, otherRect)) {
    if (checkOverlap(glassDetail, otherRect)) {
      isValidRotation = false;
    }
  });
  // 检查是否超出布局边界
  if (rect.x + rect.w > layout.width || rect.y + rect.h > layout.height) {
  if (glassDetail.x + glassDetail.width > layout.width || glassDetail.y + glassDetail.height > layout.height) {
    isValidRotation = false;
  }
  if (isValidRotation) {
    // 更新glassPoint坐标(如果存在)
    if (glassDetail.glassPoint && Array.isArray(glassDetail.glassPoint)) {
      // 保存原始点坐标
      const originalPoints = JSON.parse(JSON.stringify(glassDetail.glassPoint));
      // 旋转点坐标(以矩形左上角为原点的旋转)
      glassDetail.glassPoint.forEach((point, index) => {
        // 计算相对于矩形左上角的坐标
        const relX = originalPoints[index].X - originalState.x;
        const relY = originalPoints[index].Y - originalState.y;
        // 旋转90度后的坐标(顺时针)
        point.X = originalState.x + relY;
        point.Y = originalState.y + (originalState.width - relX);
        // 添加精度控制
        point.X = parseFloat(point.X.toFixed(2));
        point.Y = parseFloat(point.Y.toFixed(2));
      });
    }
    adjustGrayRectangles(layoutIndex);
  } else {
    // 恢复原状
    rect.w = originalState.w;
    rect.h = originalState.h;
    glassDetail.width = originalState.width;
    glassDetail.height = originalState.height;
    ElMessage.warning('无法旋转,存在重叠或超出边界');
  }
};
//移动旋转方法
const moveRectAndRotate = (layoutIndex, rectIndex, direction) => {
  const layout = layouts.value[layoutIndex];
  const rect = layout.rects[rectIndex];
  const grayRects = layout.rects.filter(r => r.isRemain);
  const glassDetail = layout.glassDetails[rectIndex];
  const grayRects = layout.glassDetails.filter(r => r.isRemain);
  // 旋转矩形
  const temp = rect.w;
  rect.w = rect.h;
  rect.h = temp;
  const temp = glassDetail.width;
  glassDetail.width = glassDetail.height;
  glassDetail.height = temp;
  // 检查旋转后的矩形是否可以放置在某个灰色矩形的位置
  const canPlace = grayRects.some(grayRect => {
    return grayRect.w >= rect.w && grayRect.h >= rect.h;
    return grayRect.width >= glassDetail.width && grayRect.height >= glassDetail.height;
  });
  if (!canPlace) {
    // 如果不能放置,恢复原状
    const temp = rect.w;
    rect.w = rect.h;
    rect.h = temp;
    const temp = glassDetail.width;
    glassDetail.width = glassDetail.height;
    glassDetail.height = temp;
    ElMessage.warning('无法旋转,没有足够的空间');
    return;
  }
  // 调整灰色矩形
  adjustGrayRectangles(layoutIndex);
  // 移动矩形
  moveRect(layoutIndex, rectIndex, direction);
};
//移动方法
const moveRect = (layoutIndex, rectIndex, direction) => {
  const layout = layouts.value[layoutIndex];
  const rect = layout.rects[rectIndex];
  const originalState = { ...rect };
  const glassDetail = layout.glassDetails[rectIndex];
  const originalState = { ...glassDetail };
  // 计算剩余空间
  const remainingAreas = calculateRemainingAreas(layout.width, layout.height, layout.rects.filter(r => !r.isRemain));
  // 根据方向计算可移动的最大步长
  let maxStep = 0;
  const obstacles = layout.glassDetails.filter(r => r.isRemain || r !== glassDetail);
  switch (direction) {
    case 'up':
      maxStep = rect.y;
      maxStep = getAvailableSpaceUp(glassDetail, layout, obstacles);
      break;
    case 'down':
      maxStep = layout.height - (rect.y + rect.h);
      maxStep = getAvailableSpaceDown(glassDetail, layout, obstacles);
      break;
    case 'left':
      maxStep = rect.x;
      maxStep = getAvailableSpaceLeft(glassDetail, layout, obstacles);
      break;
    case 'right':
      maxStep = layout.width - (rect.x + rect.w);
      maxStep = getAvailableSpaceRight(glassDetail, layout, obstacles);
      break;
  }
  // 移动步长,根据剩余空间动态调整
  const stepSize = maxStep;
  const actualStep = Math.min(maxStep, stepSize);
  if (maxStep <= 0) {
    ElMessage.warning('无法移动,没有足够的空间');
    return;
  }
  // 移动矩形
  // 保存原始坐标
  const originalX = glassDetail.x;
  const originalY = glassDetail.y;
  switch (direction) {
    case 'up':
      rect.y -= actualStep;
      glassDetail.y += maxStep;
      break;
    case 'down':
      rect.y += actualStep;
      glassDetail.y -= maxStep;
      break;
    case 'left':
      rect.x -= actualStep;
      glassDetail.x -= maxStep;
      break;
    case 'right':
      rect.x += actualStep;
      glassDetail.x += maxStep;
      break;
  }
  // 检查是否与其他蓝色矩形重叠
  const otherRects = layout.rects.filter(r => !r.isRemain && r !== rect);
  const otherRects = layout.glassDetails.filter(r => !r.isRemain && r !== glassDetail);
  let isValidMove = true;
  otherRects.forEach(otherRect => {
    if (checkOverlap(rect, otherRect)) {
    if (checkOverlap(glassDetail, otherRect)) {
      isValidMove = false;
    }
  });
  // 检查是否超出布局边界
  if (rect.x < 0 || rect.y < 0 ||
      rect.x + rect.w > layout.width ||
      rect.y + rect.h > layout.height) {
  if (glassDetail.x < 0 || glassDetail.y < 0 ||
      glassDetail.x + glassDetail.width > layout.width ||
      glassDetail.y + glassDetail.height > layout.height) {
    isValidMove = false;
  }
  if (isValidMove) {
    // 更新glassPoint坐标
    if (glassDetail.glassPoint && Array.isArray(glassDetail.glassPoint)) {
      const offsetX = glassDetail.x - originalX;
      const offsetY = glassDetail.y - originalY;
      glassDetail.glassPoint.forEach(point => {
        point.X += offsetX;
        point.Y += offsetY;
        // 添加精度控制
        point.X = parseFloat(point.X.toFixed(2));
        point.Y = parseFloat(point.Y.toFixed(2));
      });
    }
    adjustGrayRectangles(layoutIndex);
  } else {
    // 恢复原状
    rect.x = originalState.x;
    rect.y = originalState.y;
    glassDetail.x = originalState.x;
    glassDetail.y = originalState.y;
    ElMessage.warning('无法移动,存在重叠或超出边界');
  }
};
//删除小片
const deleteRect = (layoutIndex, rectIndex) => {
  const layout = layouts.value[layoutIndex];
  layout.rects.splice(rectIndex, 1);
  layout.glassDetails.splice(rectIndex, 1);
  adjustGrayRectangles(layoutIndex);
};
//判断原片跟小片移动是否超出
const checkOverlap = (rect1, rect2) => {
  return !(rect1.x + rect1.w <= rect2.x ||
           rect1.x >= rect2.x + rect2.w ||
           rect1.y + rect1.h <= rect2.y ||
           rect1.y >= rect2.y + rect2.h);
  return !(rect1.x + rect1.width <= rect2.x ||
           rect1.x >= rect2.x + rect2.width ||
           rect1.y + rect1.height <= rect2.y ||
           rect1.y >= rect2.y + rect2.height);
};
const calculateRemainingAreas = (totalWidth, totalHeight, obstacles) => {
  let remaining = [{ x: 0, y: 0, w: totalWidth, h: totalHeight }];
  obstacles.forEach(rect => {
    remaining = cutRemainingAreas(remaining, rect);
  });
  return remaining;
};
//重新计算余料坐标以及尺寸1
// const calculateRemainingAreas = (totalWidth, totalHeight, obstacles) => {
//   let remaining = [{ x: 0, y: 0, width: totalWidth, height: totalHeight }];
//   obstacles.forEach(glassDetail => {
//     remaining = cutRemainingAreas(remaining, glassDetail,totalWidth,totalHeight);
//   });
//   return remaining;
// };
const cutRemainingAreas = (remainingAreas, obstacle) => {
//重新计算余料坐标以及尺寸2
const cutRemainingAreas = (remainingAreas, obstacle, totalWidth, totalHeight) => {
  const newRemaining = [];
  remainingAreas.forEach(area => {
    if (checkOverlap(area, obstacle)) {
      if (obstacle.x > area.x) {
        newRemaining.push({
          x: area.x,
          y: area.y,
          w: obstacle.x - area.x,
          h: area.h
        });
      }
      if (obstacle.x + obstacle.w < area.x + area.w) {
        newRemaining.push({
          x: obstacle.x + obstacle.w,
          y: area.y,
          w: area.w - (obstacle.x + obstacle.w - area.x),
          h: area.h
        });
      }
      if (obstacle.y > area.y) {
        newRemaining.push({
          x: area.x,
          y: area.y,
          w: area.w,
          h: obstacle.y - area.y
        });
      }
      if (obstacle.y + obstacle.h < area.y + area.h) {
        newRemaining.push({
          x: area.x,
          y: obstacle.y + obstacle.h,
          w: area.w,
          h: area.h - (obstacle.y + obstacle.h - area.y)
        });
      }
    } else {
    // 如果障碍物与当前区域没有重叠,保留原区域
    if (!checkOverlap(area, obstacle)) {
      newRemaining.push(area);
      return;
    }
    // 计算重叠区域的边界
    const overlapLeft = Math.max(area.x, obstacle.x);
    const overlapRight = Math.min(area.x + area.width, obstacle.x + obstacle.width);
    const overlapTop = Math.max(area.y, obstacle.y);
    const overlapBottom = Math.min(area.y + area.height, obstacle.y + obstacle.height);
    // 生成四个可能的新区域(上、下、左、右)
    // 上方区域
    if (overlapTop > area.y) {
      newRemaining.push({
        x: area.x,
        y: area.y,
        width: area.width,
        height: overlapTop - area.y
      });
    }
    // 下方区域
    if (overlapBottom < area.y + area.height) {
      newRemaining.push({
        x: area.x,
        y: overlapBottom,
        width: area.width,
        height: area.y + area.height - overlapBottom
      });
    }
    // 左方区域(仅在重叠区域的垂直范围内)
    if (overlapLeft > area.x) {
      const regionTop = overlapTop;
      const regionBottom = overlapBottom;
      newRemaining.push({
        x: area.x,
        y: regionTop,
        width: overlapLeft - area.x,
        height: regionBottom - regionTop
      });
    }
    // 右方区域(仅在重叠区域的垂直范围内)
    if (overlapRight < area.x + area.width) {
      const regionTop = overlapTop;
      const regionBottom = overlapBottom;
      newRemaining.push({
        x: overlapRight,
        y: regionTop,
        width: area.x + area.width - overlapRight,
        height: regionBottom - regionTop
      });
    }
  });
  return newRemaining;
};
//定义每个版图的综合内容
const getCurrentRectInfo = (layoutIndex) => {
  const layout = layouts.value[layoutIndex];
  const rect = layout.rects[focusIndex.value?.rectIndex || 0];
  if (!rect) return '';
/*  const glassDetail = layout.glassDetails[focusIndex.value?.rectIndex || 0];
  if (!glassDetail) return '';*/
  const totalRects = layouts.value.length;
  const currentRectIndex = layoutIndex + 1;
  const width = layout.width;
  const height = layout.height;
  const percentage = ((rect.w / layout.width) * 100).toFixed(1) + '%';
  const width = layout.realWidth;
  const height = layout.realHeight;
  const percentage = (layout.usageRate * 100).toFixed(2) + '%';
  return `${currentRectIndex}/${totalRects} ${width}×${height} ×1 ${percentage}`;
};
//点击左边版图切换
const selectLayout = (layoutIndex) => {
  selectedLayoutIndex.value = layoutIndex;
};
//把传输的数据赋值
const updateLayout = () => {
  if (!layoutPanel.value) return;
  layouts.value = props.layoutData.Layouts;
  layoutsHead.value = props.layoutData;
  layouts.value = props.layoutData.layouts;
};
//向上移动计算坐标
const getAvailableSpaceUp = (glassDetail, layout, obstacles) => {
  let maxSpace = layout.height - glassDetail.y - glassDetail.height;
  obstacles.forEach(obstacle => {
    if (obstacle.y > glassDetail.y + glassDetail.height &&
        obstacle.x <= glassDetail.x + glassDetail.width &&
        obstacle.x + obstacle.width >= glassDetail.x) {
      maxSpace = Math.min(maxSpace, obstacle.y - (glassDetail.y + glassDetail.height));
    }
  });
  return maxSpace;
};
//向下移动计算坐标
const getAvailableSpaceDown = (glassDetail, layout, obstacles) => {
  let maxSpace = glassDetail.y;
  obstacles.forEach(obstacle => {
    if (obstacle.y + obstacle.height < glassDetail.y &&
        obstacle.x <= glassDetail.x + glassDetail.width &&
        obstacle.x + obstacle.width >= glassDetail.x) {
      maxSpace = Math.min(maxSpace, glassDetail.y - (obstacle.y + obstacle.height));
    }
  });
  return maxSpace;
};
//向左移动计算坐标
const getAvailableSpaceLeft = (glassDetail, layout, obstacles) => {
  let maxSpace = glassDetail.x;
  obstacles.forEach(obstacle => {
    if (obstacle.x + obstacle.width < glassDetail.x &&
        obstacle.y <= glassDetail.y + glassDetail.height &&
        obstacle.y + obstacle.height >= glassDetail.y) {
      maxSpace = Math.min(maxSpace, glassDetail.x - (obstacle.x + obstacle.width));
    }
  });
  return maxSpace;
};
//向右移动计算坐标
const getAvailableSpaceRight = (glassDetail, layout, obstacles) => {
  let maxSpace = layout.width - (glassDetail.x + glassDetail.width);
  obstacles.forEach(obstacle => {
    if (obstacle.x > glassDetail.x + glassDetail.width &&
        obstacle.y <= glassDetail.y + glassDetail.height &&
        obstacle.y + obstacle.height >= glassDetail.y) {
      maxSpace = Math.min(maxSpace, obstacle.x - (glassDetail.x + glassDetail.width));
    }
  });
  return maxSpace;
};
let moveInterval = null;
//按下按键上下左右
const handleKeyDown = (event) => {
  if (!focusIndex.value) return;
  const { layoutIndex, rectIndex } = focusIndex.value;
  const layout = layouts.value[layoutIndex];
  const glassDetail = layout.glassDetails[rectIndex];
  const obstacles = layout.glassDetails.filter(r => r.isRemain || r !== glassDetail);
  switch (event.key) {
    case 'ArrowUp':
      event.preventDefault();
      if (!moveInterval) {
        moveInterval = setInterval(() => {
          moveRect(layoutIndex, rectIndex, 'down');
        }, 50);
      }
      break;
    case 'ArrowDown':
      event.preventDefault();
      if (!moveInterval) {
        moveInterval = setInterval(() => {
          moveRect(layoutIndex, rectIndex, 'up');
        }, 50);
      }
      break;
    case 'ArrowLeft':
      event.preventDefault();
      if (!moveInterval) {
        moveInterval = setInterval(() => {
          moveRect(layoutIndex, rectIndex, 'left');
        }, 50);
      }
      break;
    case 'ArrowRight':
      event.preventDefault();
      if (!moveInterval) {
        moveInterval = setInterval(() => {
          moveRect(layoutIndex, rectIndex, 'right');
        }, 50);
      }
      break;
  }
};
//松开按键上下左右
const handleKeyUp = (event) => {
  if (event.key === 'ArrowUp' || event.key === 'ArrowDown' ||
      event.key === 'ArrowLeft' || event.key === 'ArrowRight') {
    if (moveInterval) {
      clearInterval(moveInterval);
      moveInterval = null;
    }
  }
};
//x镜像
const mirrorLayoutX = (layoutIndex) => {
  const layout = layouts.value[layoutIndex];
  const width = layout.width;
  const glassDetails = [...layout.glassDetails]; // 创建副本避免直接修改
  glassDetails.forEach(glassDetail => {
    // 计算X镜像后的坐标
    const newX = width - glassDetail.x - glassDetail.width;
    const newY = glassDetail.y;
    // 更新矩形位置
    glassDetail.x = newX;
    glassDetail.y = newY;
    // 更新glassPoint坐标
    if (glassDetail.glassPoint && Array.isArray(glassDetail.glassPoint)) {
      glassDetail.glassPoint.forEach(point => {
        point.X = width - point.X;
        point.X = parseFloat(point.X.toFixed(2));
        point.Y = parseFloat(point.Y.toFixed(2));
      });
    }
  });
  // 更新布局
  layout.glassDetails = glassDetails;
  adjustGrayRectangles(layoutIndex);
};
//y镜像
const mirrorLayoutY = (layoutIndex) => {
  const layout = layouts.value[layoutIndex];
  const height = layout.height;
  const glassDetails = [...layout.glassDetails]; // 创建副本避免直接修改
  glassDetails.forEach(glassDetail => {
    // 计算Y镜像后的坐标
    const newX = glassDetail.x;
    const newY = height - glassDetail.y - glassDetail.height;
    // 更新矩形位置
    glassDetail.y = newY;
    // 更新glassPoint坐标
    if (glassDetail.glassPoint && Array.isArray(glassDetail.glassPoint)) {
      glassDetail.glassPoint.forEach(point => {
        point.Y = height - point.Y;
        // 添加精度控制
        point.X = parseFloat(point.X.toFixed(2));
        point.Y = parseFloat(point.Y.toFixed(2));
      });
    }
  });
  // 更新布局
  layout.glassDetails = glassDetails;
  adjustGrayRectangles(layoutIndex);
};
onMounted(() => {
  setTimeout(updateLayout, 1000);
  fetchSettings(username);
  updateLayout();
  selectedLayoutIndex.value = 0;
  clickEventListener = (event) => {
    const contextMenus = document.querySelectorAll('.context-menu');
    if (contextMenus.length > 0) {
      contextMenus.forEach(menu => menu.remove());
    }
  };
  document.addEventListener('click', clickEventListener);
  document.addEventListener('keydown', handleKeyDown);
  document.addEventListener('keyup', handleKeyUp);
});
onUnmounted(() => {
  rectsElements.value = {};
  if (clickEventListener) {
    document.removeEventListener('click', clickEventListener);
    clickEventListener = null;
  }
  document.removeEventListener('keydown', handleKeyDown);
  document.removeEventListener('keyup', handleKeyUp);
});
</script>
@@ -876,6 +1296,10 @@
.layout-wrapper {
  position: relative;
  margin-top: 50px;
}
.layout-glassDetail {
  user-select: none;
}
.layout-container {
@@ -893,26 +1317,29 @@
  font-weight: bold;
}
.rect-content {
  display: grid;
  grid-template-columns: 1fr;
  grid-template-rows: 1fr;
.glassDetail-content {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  padding: 5px;
  min-width: 60px;
  min-height: 20px;
  white-space: normal;
  overflow-wrap: break-word;
}
.size {
  grid-row: 1;
  grid-column: 1;
  color: #444;
  font-size: 12px;
  color: #444;
  white-space: normal;
  word-wrap: break-word;
}
.jia-hao {
  grid-row: 2;
  grid-column: 1;
  margin: auto;
.jia-hao, .liuchengka {
  font-size: 14px;
  font-weight: bold;
  white-space: normal;
  word-wrap: break-word;
}
.sidebar-item {