| | |
| | | <template>
|
| | | <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.realWidth }} × {{ layout.realHeight }} × {{ layout.quantity }}
|
| | | {{ layout.realWidth }} × {{ layout.realHeight }} × {{ layout.quantity }}
|
| | | </div>
|
| | | </div>
|
| | |
|
| | |
| | | </div>
|
| | | </div>
|
| | |
|
| | | <!-- 提交按钮 -->
|
| | | <button @click="submitLayouts" style="position: fixed; top: 90px; 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>
|
| | |
| | |
|
| | | <script setup>
|
| | | import { ref, reactive, onMounted, onUnmounted } from 'vue';
|
| | | import { useRouter } from 'vue-router'; // 添加这行
|
| | | import { useRouter } from 'vue-router';
|
| | | import request from "@/utils/request";
|
| | | const router = useRouter(); // 添加这行
|
| | | const router = useRouter();
|
| | | import { useI18n } from "vue-i18n";
|
| | | import { ElMessage, ElMessageBox } from "element-plus";
|
| | | import useUserInfoStore from "@/stores/userInfo";
|
| | |
| | | const submitLayouts = async () => {
|
| | | layouts.value.forEach(layout => {
|
| | | layout.glassDetails.forEach(glassDetail => {
|
| | | glassDetail.x = Math.round(glassDetail.x);
|
| | | glassDetail.y = Math.round(glassDetail.y);
|
| | | glassDetail.width = Math.round(glassDetail.width);
|
| | | glassDetail.height = Math.round(glassDetail.height);
|
| | | 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 rectIndex = dragRect.value.rectIndex;
|
| | | const layout = layouts.value[layoutIndex];
|
| | | 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
|
| | |
| | | if (isValidMove) {
|
| | | 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
|
| | |
| | | (props.gh - 100) / layout.height
|
| | | );
|
| | |
|
| | | glassDetail.x = Math.round(glassDetail.x);
|
| | | glassDetail.y = Math.round(glassDetail.y);
|
| | | glassDetail.x = parseFloat(glassDetail.x.toFixed(2));
|
| | | glassDetail.y = parseFloat(glassDetail.y.toFixed(2));
|
| | | adjustAlignmentPosition(layoutIndex, rectIndex);
|
| | | }
|
| | |
|
| | |
| | | const grayRects = glassDetails.filter(r => r.isRemain);
|
| | | const nonGrayRects = glassDetails.filter(r => !r.isRemain);
|
| | |
|
| | | // 按坐标排序,优先按x坐标,其次按y坐标
|
| | | // 按坐标排序,优先按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;
|
| | |
|
| | | const merged = [];
|
| | |
|
| | | // 从第一个矩形开始
|
| | | let current = { ...grayRects[0] };
|
| | |
|
| | | // 遍历所有余料矩形进行合并
|
| | |
| | | else {
|
| | | // 无法合并,保存当前矩形,开始新的合并
|
| | | merged.push({
|
| | | x: Math.round(current.x),
|
| | | y: Math.round(current.y),
|
| | | width: Math.round(current.width),
|
| | | height: Math.round(current.height),
|
| | | x: current.x,
|
| | | y: current.y,
|
| | | width: current.width,
|
| | | height: current.height,
|
| | | isRemain: true
|
| | | });
|
| | | current = { ...next };
|
| | | }
|
| | | }
|
| | |
|
| | | // 添加最后一个矩形
|
| | | merged.push({
|
| | | x: current.x,
|
| | | y: current.y,
|
| | | width: current.width,
|
| | | height: current.height,
|
| | | isRemain: true
|
| | | });
|
| | |
|
| | | // 重新构建数组:非余料 + 合并后的余料
|
| | | glassDetails.splice(0, glassDetails.length, ...nonGrayRects, ...merged);
|
| | | };
|
| | |
| | | // 添加新的余料矩形
|
| | | uniqueArr.forEach((area) => {
|
| | | newGlassDetails.push({
|
| | | x: Math.round(area.x),
|
| | | y: Math.round(area.y),
|
| | | width: Math.round(area.width),
|
| | | height: Math.round(area.height),
|
| | | x: area.x,
|
| | | y: area.y,
|
| | | width: area.width,
|
| | | height: area.height,
|
| | | isRemain: true
|
| | | });
|
| | | });
|
| | |
| | | }
|
| | |
|
| | | 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 {
|
| | | glassDetail.width = originalState.width;
|
| | |
| | | return;
|
| | | }
|
| | |
|
| | |
|
| | | // 保存原始坐标
|
| | | const originalX = glassDetail.x;
|
| | | const originalY = glassDetail.y;
|
| | |
|
| | | switch (direction) {
|
| | | case 'up':
|
| | | glassDetail.y += maxStep;
|
| | |
| | | }
|
| | |
|
| | | 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 {
|
| | | glassDetail.x = originalState.x;
|
| | |
| | | const newRemaining = [];
|
| | |
|
| | | remainingAreas.forEach(area => {
|
| | | // 如果障碍物与当前区域有重叠
|
| | | if (checkOverlap(area, obstacle)) {
|
| | | // 切分当前区域为最多4个新区域
|
| | |
|
| | | // 左侧区域
|
| | | if (obstacle.x > area.x) {
|
| | | newRemaining.push({
|
| | | x: area.x,
|
| | | y: area.y,
|
| | | width: obstacle.x - area.x,
|
| | | height: area.height
|
| | | });
|
| | | }
|
| | |
|
| | | // 右侧区域
|
| | | if (obstacle.x + obstacle.width < area.x + area.width) {
|
| | | newRemaining.push({
|
| | | x: obstacle.x + obstacle.width,
|
| | | y: area.y,
|
| | | width: area.x + area.width - (obstacle.x + obstacle.width),
|
| | | height: area.height
|
| | | });
|
| | | }
|
| | |
|
| | | // 上方区域(仅在障碍物左右边界之间的区域)
|
| | | if (obstacle.y > area.y) {
|
| | | const startX = Math.max(area.x, obstacle.x);
|
| | | const endX = Math.min(area.x + area.width, obstacle.x + obstacle.width);
|
| | | if (endX > startX) {
|
| | | newRemaining.push({
|
| | | x: startX,
|
| | | y: area.y,
|
| | | width: endX - startX,
|
| | | height: obstacle.y - area.y
|
| | | });
|
| | | }
|
| | | }
|
| | |
|
| | | // 下方区域(仅在障碍物左右边界之间的区域)
|
| | | if (obstacle.y + obstacle.height < area.y + area.height) {
|
| | | const startX = Math.max(area.x, obstacle.x);
|
| | | const endX = Math.min(area.x + area.width, obstacle.x + obstacle.width);
|
| | | if (endX > startX) {
|
| | | newRemaining.push({
|
| | | x: startX,
|
| | | y: obstacle.y + obstacle.height,
|
| | | width: endX - startX,
|
| | | height: area.y + area.height - (obstacle.y + obstacle.height)
|
| | | });
|
| | | }
|
| | | }
|
| | | } 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
|
| | | });
|
| | | }
|
| | | });
|
| | |
|
| | |
| | | // 更新矩形位置
|
| | | 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));
|
| | | });
|
| | | }
|
| | | });
|
| | |
|
| | | // 更新布局
|
| | |
| | |
|
| | | // 更新矩形位置
|
| | | 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));
|
| | | });
|
| | | }
|
| | | });
|
| | |
|
| | | // 更新布局
|