wuyouming666
2025-03-19 1fdbe0a35b29cfda7baa11ae2665f55911e0d970
版图调整更新,OPT文件下载
16个文件已修改
1 文件已重命名
8个文件已添加
2587 ■■■■ 已修改文件
.gitignore 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
north-glass-erp/northglass-erp/package-lock.json 170 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
north-glass-erp/northglass-erp/src/components/pp/MockData.js 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
north-glass-erp/northglass-erp/src/components/pp/settings.json 74 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
north-glass-erp/northglass-erp/src/router/index.js 25 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
north-glass-erp/northglass-erp/src/views/pp/glassOptimize/Optimization.vue 59 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
north-glass-erp/northglass-erp/src/views/pp/glassOptimize/OptimizationRectPrint.vue 115 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
north-glass-erp/northglass-erp/src/views/pp/glassOptimize/Optimize.vue 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
north-glass-erp/northglass-erp/src/views/pp/glassOptimize/OptimizeControl.vue 59 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
north-glass-erp/northglass-erp/src/views/pp/glassOptimize/OptimizeMove.vue 166 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
north-glass-erp/northglass-erp/src/views/pp/glassOptimize/OptimizeParms.vue 522 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
north-glass-erp/northglass-erp/src/views/pp/glassOptimize/page/OptimizationRect.vue 943 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
north-glass-erp/northglass-erp/src/views/pp/glassOptimize/page/ProjectDetail.vue 5 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
north-glass-erp/northglass-erp/src/views/pp/glassOptimize/page/RectRenderer.vue 66 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
north-glass-erp/src/main/java/com/example/erp/controller/pp/GlassOptimizeController.java 77 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
north-glass-erp/src/main/java/com/example/erp/controller/userInfo/UserController.java 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
north-glass-erp/src/main/java/com/example/erp/entity/pp/LayoutsData.java 82 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
north-glass-erp/src/main/java/com/example/erp/entity/userInfo/User.java 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
north-glass-erp/src/main/java/com/example/erp/mapper/pp/GlassOptimizeMapper.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
north-glass-erp/src/main/java/com/example/erp/mapper/userInfo/UserMapper.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
north-glass-erp/src/main/java/com/example/erp/service/pp/GlassOptimizeService.java 51 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
north-glass-erp/src/main/java/com/example/erp/service/pp/JsonToOptConverter.java 105 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
north-glass-erp/src/main/java/com/example/erp/service/userInfo/UserService.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
north-glass-erp/src/main/resources/mapper/pp/GlassOptimize.xml 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
north-glass-erp/src/main/resources/mapper/userInfo/User.xml 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.gitignore
@@ -18,3 +18,7 @@
/north-glass-erp/src/test/test.iml
/north-glass-erp/.idea/libraries
/north-glass-erp/.idea
/north-glass-erp/src/.idea
/north-glass-erp/src/main/main.iml
north-glass-erp/northglass-erp/package-lock.json
@@ -69,9 +69,9 @@
      }
    },
    "node_modules/@babel/runtime": {
      "version": "7.25.0",
      "resolved": "https://registry.npmmirror.com/@babel/runtime/-/runtime-7.25.0.tgz",
      "integrity": "sha512-7dRy4DwXwtzBrPbZflqxnvfxLF8kdZXPkhymtDeFoFqE6ldzjQFgYTtYIFARcLEYDrqfBfYcZt1WqFxRoyC9Rw==",
      "version": "7.26.10",
      "resolved": "https://registry.npmmirror.com/@babel/runtime/-/runtime-7.26.10.tgz",
      "integrity": "sha512-2WJMeRQPHKSPemqk/awGrAiuFfzBmOIPXKizAsVhWH9YJqLZ0H+HS4c8loHGgW6utJ3E/ejXQUsiGaQy2NZ9Fw==",
      "dependencies": {
        "regenerator-runtime": "^0.14.0"
      },
@@ -1439,12 +1439,6 @@
      "integrity": "sha512-FKWIhiU96bi3xpP9ewRMgANsoVmMUBnMnmpCT6dPMZOunVYJQmJhSRruoI0XSPoHeIif3kyEuiHbFrOJwEJaEA==",
      "license": "MIT"
    },
    "node_modules/dompurify": {
      "version": "2.5.6",
      "resolved": "https://registry.npmmirror.com/dompurify/-/dompurify-2.5.6.tgz",
      "integrity": "sha512-zUTaUBO8pY4+iJMPE1B9XlO2tXVYIcEA4SNGtvDELzTSCQO7RzH+j7S180BmhmJId78lqGU2z19vgVx2Sxs/PQ==",
      "optional": true
    },
    "node_modules/element-plus": {
      "version": "2.9.3",
      "resolved": "https://registry.npmmirror.com/element-plus/-/element-plus-2.9.3.tgz",
@@ -1557,9 +1551,9 @@
      "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="
    },
    "node_modules/fflate": {
      "version": "0.4.8",
      "resolved": "https://registry.npmmirror.com/fflate/-/fflate-0.4.8.tgz",
      "integrity": "sha512-FJqqoDBR00Mdj9ppamLa/Y7vxm+PRmNWA67N846RvsoYVMKB4q3y/de5PA7gUmRMYK/8CMz2GDZQmCRN1wBcWA=="
      "version": "0.8.2",
      "resolved": "https://registry.npmmirror.com/fflate/-/fflate-0.8.2.tgz",
      "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A=="
    },
    "node_modules/file-saver": {
      "version": "2.0.5",
@@ -1728,23 +1722,6 @@
      "version": "3.11.6",
      "resolved": "https://registry.npmmirror.com/jsbarcode/-/jsbarcode-3.11.6.tgz",
      "integrity": "sha512-G5TKGyKY1zJo0ZQKFM1IIMfy0nF2rs92BLlCz+cU4/TazIc4ZH+X1GYeDRt7TKjrYqmPfTjwTBkU/QnQlsYiuA=="
    },
    "node_modules/jspdf": {
      "version": "2.5.1",
      "resolved": "https://registry.npmmirror.com/jspdf/-/jspdf-2.5.1.tgz",
      "integrity": "sha512-hXObxz7ZqoyhxET78+XR34Xu2qFGrJJ2I2bE5w4SM8eFaFEkW2xcGRVUss360fYelwRSid/jT078kbNvmoW0QA==",
      "dependencies": {
        "@babel/runtime": "^7.14.0",
        "atob": "^2.1.2",
        "btoa": "^1.2.1",
        "fflate": "^0.4.8"
      },
      "optionalDependencies": {
        "canvg": "^3.0.6",
        "core-js": "^3.6.0",
        "dompurify": "^2.2.0",
        "html2canvas": "^1.0.0-rc.5"
      }
    },
    "node_modules/local-pkg": {
      "version": "0.4.3",
@@ -7398,6 +7375,29 @@
        "socket.io-client": "^4.5.1"
      }
    },
    "node_modules/vue-plugin-hiprint/node_modules/dompurify": {
      "version": "2.5.8",
      "resolved": "https://registry.npmmirror.com/dompurify/-/dompurify-2.5.8.tgz",
      "integrity": "sha512-o1vSNgrmYMQObbSSvF/1brBYEQPHhV1+gsmrusO7/GXtp1T9rCS8cXFqVxK/9crT1jA6Ccv+5MTSjBNqr7Sovw==",
      "optional": true
    },
    "node_modules/vue-plugin-hiprint/node_modules/jspdf": {
      "version": "2.5.2",
      "resolved": "https://registry.npmmirror.com/jspdf/-/jspdf-2.5.2.tgz",
      "integrity": "sha512-myeX9c+p7znDWPk0eTrujCzNjT+CXdXyk7YmJq5nD5V7uLLKmSXnlQ/Jn/kuo3X09Op70Apm0rQSnFWyGK8uEQ==",
      "dependencies": {
        "@babel/runtime": "^7.23.2",
        "atob": "^2.1.2",
        "btoa": "^1.2.1",
        "fflate": "^0.8.1"
      },
      "optionalDependencies": {
        "canvg": "^3.0.6",
        "core-js": "^3.6.0",
        "dompurify": "^2.5.4",
        "html2canvas": "^1.0.0-rc.5"
      }
    },
    "node_modules/vue-router": {
      "version": "4.2.5",
      "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.2.5.tgz",
@@ -7609,9 +7609,9 @@
      "integrity": "sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw=="
    },
    "@babel/runtime": {
      "version": "7.25.0",
      "resolved": "https://registry.npmmirror.com/@babel/runtime/-/runtime-7.25.0.tgz",
      "integrity": "sha512-7dRy4DwXwtzBrPbZflqxnvfxLF8kdZXPkhymtDeFoFqE6ldzjQFgYTtYIFARcLEYDrqfBfYcZt1WqFxRoyC9Rw==",
      "version": "7.26.10",
      "resolved": "https://registry.npmmirror.com/@babel/runtime/-/runtime-7.26.10.tgz",
      "integrity": "sha512-2WJMeRQPHKSPemqk/awGrAiuFfzBmOIPXKizAsVhWH9YJqLZ0H+HS4c8loHGgW6utJ3E/ejXQUsiGaQy2NZ9Fw==",
      "requires": {
        "regenerator-runtime": "^0.14.0"
      },
@@ -8505,12 +8505,6 @@
      "resolved": "https://registry.npmmirror.com/dom-zindex/-/dom-zindex-1.0.6.tgz",
      "integrity": "sha512-FKWIhiU96bi3xpP9ewRMgANsoVmMUBnMnmpCT6dPMZOunVYJQmJhSRruoI0XSPoHeIif3kyEuiHbFrOJwEJaEA=="
    },
    "dompurify": {
      "version": "2.5.6",
      "resolved": "https://registry.npmmirror.com/dompurify/-/dompurify-2.5.6.tgz",
      "integrity": "sha512-zUTaUBO8pY4+iJMPE1B9XlO2tXVYIcEA4SNGtvDELzTSCQO7RzH+j7S180BmhmJId78lqGU2z19vgVx2Sxs/PQ==",
      "optional": true
    },
    "element-plus": {
      "version": "2.9.3",
      "resolved": "https://registry.npmmirror.com/element-plus/-/element-plus-2.9.3.tgz",
@@ -8609,9 +8603,9 @@
      "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="
    },
    "fflate": {
      "version": "0.4.8",
      "resolved": "https://registry.npmmirror.com/fflate/-/fflate-0.4.8.tgz",
      "integrity": "sha512-FJqqoDBR00Mdj9ppamLa/Y7vxm+PRmNWA67N846RvsoYVMKB4q3y/de5PA7gUmRMYK/8CMz2GDZQmCRN1wBcWA=="
      "version": "0.8.2",
      "resolved": "https://registry.npmmirror.com/fflate/-/fflate-0.8.2.tgz",
      "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A=="
    },
    "file-saver": {
      "version": "2.0.5",
@@ -8723,21 +8717,6 @@
      "version": "3.11.6",
      "resolved": "https://registry.npmmirror.com/jsbarcode/-/jsbarcode-3.11.6.tgz",
      "integrity": "sha512-G5TKGyKY1zJo0ZQKFM1IIMfy0nF2rs92BLlCz+cU4/TazIc4ZH+X1GYeDRt7TKjrYqmPfTjwTBkU/QnQlsYiuA=="
    },
    "jspdf": {
      "version": "2.5.1",
      "resolved": "https://registry.npmmirror.com/jspdf/-/jspdf-2.5.1.tgz",
      "integrity": "sha512-hXObxz7ZqoyhxET78+XR34Xu2qFGrJJ2I2bE5w4SM8eFaFEkW2xcGRVUss360fYelwRSid/jT078kbNvmoW0QA==",
      "requires": {
        "@babel/runtime": "^7.14.0",
        "atob": "^2.1.2",
        "btoa": "^1.2.1",
        "canvg": "^3.0.6",
        "core-js": "^3.6.0",
        "dompurify": "^2.2.0",
        "fflate": "^0.4.8",
        "html2canvas": "^1.0.0-rc.5"
      }
    },
    "local-pkg": {
      "version": "0.4.3",
@@ -8882,9 +8861,9 @@
          "integrity": "sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw=="
        },
        "@babel/runtime": {
          "version": "7.25.0",
          "resolved": "https://registry.npmmirror.com/@babel/runtime/-/runtime-7.25.0.tgz",
          "integrity": "sha512-7dRy4DwXwtzBrPbZflqxnvfxLF8kdZXPkhymtDeFoFqE6ldzjQFgYTtYIFARcLEYDrqfBfYcZt1WqFxRoyC9Rw==",
          "version": "7.26.10",
          "resolved": "https://registry.npmmirror.com/@babel/runtime/-/runtime-7.26.10.tgz",
          "integrity": "sha512-2WJMeRQPHKSPemqk/awGrAiuFfzBmOIPXKizAsVhWH9YJqLZ0H+HS4c8loHGgW6utJ3E/ejXQUsiGaQy2NZ9Fw==",
          "requires": {
            "regenerator-runtime": "^0.14.0"
          },
@@ -9778,12 +9757,6 @@
          "resolved": "https://registry.npmmirror.com/dom-zindex/-/dom-zindex-1.0.6.tgz",
          "integrity": "sha512-FKWIhiU96bi3xpP9ewRMgANsoVmMUBnMnmpCT6dPMZOunVYJQmJhSRruoI0XSPoHeIif3kyEuiHbFrOJwEJaEA=="
        },
        "dompurify": {
          "version": "2.5.6",
          "resolved": "https://registry.npmmirror.com/dompurify/-/dompurify-2.5.6.tgz",
          "integrity": "sha512-zUTaUBO8pY4+iJMPE1B9XlO2tXVYIcEA4SNGtvDELzTSCQO7RzH+j7S180BmhmJId78lqGU2z19vgVx2Sxs/PQ==",
          "optional": true
        },
        "element-plus": {
          "version": "2.9.3",
          "resolved": "https://registry.npmmirror.com/element-plus/-/element-plus-2.9.3.tgz",
@@ -9882,9 +9855,9 @@
          "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="
        },
        "fflate": {
          "version": "0.4.8",
          "resolved": "https://registry.npmmirror.com/fflate/-/fflate-0.4.8.tgz",
          "integrity": "sha512-FJqqoDBR00Mdj9ppamLa/Y7vxm+PRmNWA67N846RvsoYVMKB4q3y/de5PA7gUmRMYK/8CMz2GDZQmCRN1wBcWA=="
          "version": "0.8.2",
          "resolved": "https://registry.npmmirror.com/fflate/-/fflate-0.8.2.tgz",
          "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A=="
        },
        "file-saver": {
          "version": "2.0.5",
@@ -9996,21 +9969,6 @@
          "version": "3.11.6",
          "resolved": "https://registry.npmmirror.com/jsbarcode/-/jsbarcode-3.11.6.tgz",
          "integrity": "sha512-G5TKGyKY1zJo0ZQKFM1IIMfy0nF2rs92BLlCz+cU4/TazIc4ZH+X1GYeDRt7TKjrYqmPfTjwTBkU/QnQlsYiuA=="
        },
        "jspdf": {
          "version": "2.5.1",
          "resolved": "https://registry.npmmirror.com/jspdf/-/jspdf-2.5.1.tgz",
          "integrity": "sha512-hXObxz7ZqoyhxET78+XR34Xu2qFGrJJ2I2bE5w4SM8eFaFEkW2xcGRVUss360fYelwRSid/jT078kbNvmoW0QA==",
          "requires": {
            "@babel/runtime": "^7.14.0",
            "atob": "^2.1.2",
            "btoa": "^1.2.1",
            "canvg": "^3.0.6",
            "core-js": "^3.6.0",
            "dompurify": "^2.2.0",
            "fflate": "^0.4.8",
            "html2canvas": "^1.0.0-rc.5"
          }
        },
        "local-pkg": {
          "version": "0.4.3",
@@ -15563,6 +15521,29 @@
            "jspdf": "^2.5.1",
            "nzh": "^1.0.8",
            "socket.io-client": "^4.5.1"
          },
          "dependencies": {
            "dompurify": {
              "version": "2.5.8",
              "resolved": "https://registry.npmmirror.com/dompurify/-/dompurify-2.5.8.tgz",
              "integrity": "sha512-o1vSNgrmYMQObbSSvF/1brBYEQPHhV1+gsmrusO7/GXtp1T9rCS8cXFqVxK/9crT1jA6Ccv+5MTSjBNqr7Sovw==",
              "optional": true
            },
            "jspdf": {
              "version": "2.5.2",
              "resolved": "https://registry.npmmirror.com/jspdf/-/jspdf-2.5.2.tgz",
              "integrity": "sha512-myeX9c+p7znDWPk0eTrujCzNjT+CXdXyk7YmJq5nD5V7uLLKmSXnlQ/Jn/kuo3X09Op70Apm0rQSnFWyGK8uEQ==",
              "requires": {
                "@babel/runtime": "^7.23.2",
                "atob": "^2.1.2",
                "btoa": "^1.2.1",
                "canvg": "^3.0.6",
                "core-js": "^3.6.0",
                "dompurify": "^2.5.4",
                "fflate": "^0.8.1",
                "html2canvas": "^1.0.0-rc.5"
              }
            }
          }
        },
        "vue-router": {
@@ -21168,6 +21149,29 @@
        "jspdf": "^2.5.1",
        "nzh": "^1.0.8",
        "socket.io-client": "^4.5.1"
      },
      "dependencies": {
        "dompurify": {
          "version": "2.5.8",
          "resolved": "https://registry.npmmirror.com/dompurify/-/dompurify-2.5.8.tgz",
          "integrity": "sha512-o1vSNgrmYMQObbSSvF/1brBYEQPHhV1+gsmrusO7/GXtp1T9rCS8cXFqVxK/9crT1jA6Ccv+5MTSjBNqr7Sovw==",
          "optional": true
        },
        "jspdf": {
          "version": "2.5.2",
          "resolved": "https://registry.npmmirror.com/jspdf/-/jspdf-2.5.2.tgz",
          "integrity": "sha512-myeX9c+p7znDWPk0eTrujCzNjT+CXdXyk7YmJq5nD5V7uLLKmSXnlQ/Jn/kuo3X09Op70Apm0rQSnFWyGK8uEQ==",
          "requires": {
            "@babel/runtime": "^7.23.2",
            "atob": "^2.1.2",
            "btoa": "^1.2.1",
            "canvg": "^3.0.6",
            "core-js": "^3.6.0",
            "dompurify": "^2.5.4",
            "fflate": "^0.8.1",
            "html2canvas": "^1.0.0-rc.5"
          }
        }
      }
    },
    "vue-router": {
north-glass-erp/northglass-erp/src/components/pp/MockData.js
@@ -1,7 +1,6 @@
// MockData.js
const mockLayoutData = {
    result: "ok",
    data: {
      Layouts: [
        {
          mnumber: 9,
@@ -1484,7 +1483,7 @@
        }
      ],
      method: "getResult"
    }
  };
  
  export default mockLayoutData;
north-glass-erp/northglass-erp/src/components/pp/settings.json
New file
@@ -0,0 +1,74 @@
{
    "optimization": {
        "yShapeJoinOptimization": "1",
        "autoMiddleEmptyPairing": "1",
        "smallPieceRotationProhibited": "1",
        "maxFramesOnSite": "10",
        "bendEdgeDistance": "5",
        "positiveTolerance": "0",
        "negativeTolerance": "0",
        "cutterOriginPosition": "topLeft",
        "uniformShapeEdgeTrimAmount": "5",
        "edgeTrimMode": "intelligent",
        "parallelOptimizationChannels": "2",
        "optimizationIterations": "1",
        "finishedProductGrindingAmount": "0",
        "rawPieceEdgeTrimAmount": "0",
        "finishedSinglePieceBelowGrindingAmount": "500"
    },
    "display": {
        "themeColor": "#ffffff",
        "includeProductEdge": true,
        "includeIrregularEdge": true,
        "mergeByFrameNumber": true,
        "frameNumber": true,
        "orderNumber": true,
        "productName": true,
        "processingInfo": true,
        "remarks": true,
        "floorNumber": true,
        "edgeLength": true,
        "manufacturingProcess": true,
        "identifier": true
    },
    "cutting": {
        "cutting_direction": true,
        "show_cutting_path": true,
        "force_continuous_cutting": true,
        "min_radius": 5,
        "min_parallel_offset": 0.1,
        "t_shaped_recess": 2,
        "down_cut_spacing": 0.5,
        "lift_retract": 1
    },
    "server": {
        "output_format": "json",
        "save_location": "local",
        "auto_save": true,
        "save_interval": 60,
        "engineering_file_save_path": "",
        "cutting_code_save_path": "",
        "open_folder_after_save": "",
        "g_code_file_format": "",
        "trf_file_save_path": "",
        "btl_file_save_path": "",
        "optima_file_save_path": "",
        "optimization_depth_limit_enable": "",
        "original_sheet_material_calculation": ""
    },
    "tempering": {
        "tempering_temp": 600,
        "cooling_rate": 50,
        "annealing_time": 3600,
        "furnaceLength": 0,
        "furnaceWidth": 0,
        "maxLoadingRate": 100,
        "chaosLevel": 0,
        "temperingTime": 1200,
        "defaultTemperingMode": "auto",
        "maxArea": 100,
        "maxPieceCount": 100,
        "xAxisInterval": 10,
        "yAxisInterval": 10
    }
}
north-glass-erp/northglass-erp/src/router/index.js
@@ -61,6 +61,10 @@
            }
          ]
        },
{
          path: '/main/glassOptimize/MoveManage',
          component: () => import('@/views/pp/glassOptimize/MoveManage.vue')
        },
        {
          path:'userPassWord',
          name: 'userPassWord',
@@ -337,9 +341,24 @@
              component: () => import('../views/pp/glassOptimize/OptimizePrint.vue'),
            },
            {
              path: 'optimizeMove',
              name: 'optimizeMove',
              component: () => import('../views/pp/glassOptimize/OptimizeMove.vue'),
              path: 'OptimizationRectPrint',
              name: 'OptimizationRectPrint',
              component: () => import('../views/pp/glassOptimize/OptimizationRectPrint.vue'),
            },
            {
              path: 'OptimizeControl',
              name: 'OptimizeControl',
              component: () => import('../views/pp/glassOptimize/OptimizeControl.vue'),
            },
            {
              path: 'Optimization',
              name: 'Optimization',
              component: () => import('../views/pp/glassOptimize/Optimization.vue'),
            },
            {
              path: 'optimizeparms',
              name: 'optimizeparms',
              component: () => import('../views/pp/glassOptimize/OptimizeParms.vue'),
            },
            {
              path: '/optimizeProject/:projectNo/:thickNess/:model',
north-glass-erp/northglass-erp/src/views/pp/glassOptimize/Optimization.vue
New file
@@ -0,0 +1,59 @@
<template>
  <div>
    <RectRenderer
      :layoutData="layoutData"
      :gw="1150"
      :gh="850"
      style="width: 1000px; height: 800px; position: relative;"
    />
  </div>
</template>
<script setup>
import { ref,onMounted } from 'vue';
import RectRenderer from './page/OptimizationRect.vue';
import mockLayoutData from '../../../components/pp/MockData';
import request from "@/utils/request";
import { useI18n } from "vue-i18n";
import { ElMessage } from "element-plus";
const { t } = useI18n();
//const layoutData = ref(mockLayoutData);
// const processId = "P25030309";
const savedProjectNo = localStorage.getItem('projectNo');
const processId = savedProjectNo;
console.log(processId)
const layoutData = ref(null);
const selectLayout = () => {
request.post(`/glassOptimize/selectOptimizeResult/${processId}`)
.then((res) => {
  if (res.code == 200) {
    try {
      // 将字符串数据转换为对象
      // console.log("原始数据:", res.data.data[0].Layouts);
      const parsedData = JSON.parse(res.data.data[0].Layouts);
      layoutData.value = parsedData;
      // console.log("解析后的数据:", layoutData.value);
      // console.log("数据类型:", typeof parsedData);
      ElMessage.success("打开版图成功")
    } catch (error) {
      ElMessage.error("解析数据时出错:", error);
    }
  } else {
  }
})
.catch((error) => {
  console.error("请求失败:", error);
  ElMessage.error(t('basicData.msg.requestFailed'));
});
}
onMounted(() => {
  selectLayout();
});
</script>
north-glass-erp/northglass-erp/src/views/pp/glassOptimize/OptimizationRectPrint.vue
New file
@@ -0,0 +1,115 @@
<template>
  <div>
    <button @click="handlePrint"  style="position: fixed; bottom: 20px; right: 20px; padding: 10px; background: #4CAF50; color: white; border: none; border-radius: 5px; cursor: pointer;">打印版图</button>
    <RectRenderer
      ref="rectRenderer"
      :layoutData="layoutData"
      :gw="1400"
      :gh="1100"
      style="width: 100%; height: 800px; position: relative;"
    />
  </div>
</template>
<script setup>
import { ref } from 'vue';
import RectRenderer from './page/RectRenderer.vue';
import mockLayoutData from '../../../components/pp/MockData';
const layoutData = ref(mockLayoutData);
const rectRenderer = ref(null);
const handlePrint = () => {
  // 创建一个隐藏的iframe
  const iframe = document.createElement('iframe');
  iframe.style.position = 'fixed';
  iframe.style.top = '-100vh';
  iframe.style.left = '-100vw';
  iframe.style.width = '200%';
  iframe.style.height = '200%';
  document.body.appendChild(iframe);
  // 将RectRenderer的内容加载到iframe中
  const iframeDoc = iframe.contentDocument || iframe.contentWindow.document;
  iframeDoc.open();
  iframeDoc.write(`
    <html>
      <head>
        <title>Print Layout</title>
        <style>
          @page {
            size: A4 landscape;
            margin: 20mm;
          }
          body {
            margin: 0;
            padding: 0;
          }
          .layout-wrapper {
            display: flex;
            flex-direction: column;
          }
          .layout-container {
            page-break-inside: avoid;
            break-inside: avoid;
            page-break-after: auto;
            margin-bottom: 20mm;
          }
          .layout-item {
            page-break-inside: avoid;
            break-inside: avoid;
          }
        </style>
      </head>
      <body>
        <div class="layout-wrapper">
          ${rectRenderer.value.$el.outerHTML}
        </div>
      </body>
    </html>
  `);
  iframeDoc.close();
  // 设置打印样式
  const printStyle = iframeDoc.createElement('style');
  printStyle.type = 'text/css';
  printStyle.innerHTML = `
    @page {
      size: A4 landscape;
      margin: 20mm;
    }
    body {
      -webkit-print-color-adjust: exact;
    }
    .layout-wrapper {
      display: flex;
      flex-direction: column;
    }
    .layout-container {
      page-break-inside: avoid;
      break-inside: avoid;
      page-break-after: auto;
      margin-bottom: 20mm;
    }
    .layout-item {
      page-break-inside: avoid;
      break-inside: avoid;
    }
  `;
  iframeDoc.head.appendChild(printStyle);
  // 调整iframe大小以适应内容
  const contentWidth = rectRenderer.value.$el.offsetWidth;
  const contentHeight = rectRenderer.value.$el.offsetHeight;
  iframe.width = contentWidth + 'px';
  iframe.height = contentHeight + 'px';
  // 执行打印
  iframe.contentWindow.print();
  // 清理
  setTimeout(() => {
    document.body.removeChild(iframe);
  }, 100);
};
</script>
north-glass-erp/northglass-erp/src/views/pp/glassOptimize/Optimize.vue
@@ -19,13 +19,13 @@
      <el-breadcrumb :separator-icon="ArrowRight">
        <el-breadcrumb-item @click="changeRouter(1)" :class="indexFlag===1?'indexTag':''" :to="{ path: '/main/glassOptimize/optimizeProject' }">{{'工程信息'}}</el-breadcrumb-item>
        <el-breadcrumb-item v-show="false" @click="changeRouter(2)" :class="indexFlag===2?'indexTag':''" :to="{ path: '/main/order/createOrder' }">{{$t('order.page.createOrder')}}</el-breadcrumb-item>
        <el-breadcrumb-item @click="changeRouter(3)" :class="indexFlag===3?'indexTag':''" :to="{ path: '/main/glassOptimize/optimizePrint' }">打印
        <el-breadcrumb-item @click="changeRouter(3)" :class="indexFlag===3?'indexTag':''" :to="{ path: '/main/glassOptimize/OptimizationRectPrint' }">打印
        </el-breadcrumb-item>
        <el-breadcrumb-item @click="changeRouter(4)" :class="indexFlag===4?'indexTag':''" :to="{ path: '/main/glassOptimize/optimizeMove' }">调整
        <el-breadcrumb-item @click="changeRouter(4)" :class="indexFlag===4?'indexTag':''" :to="{ path: '/main/glassOptimize/Optimization' }">调整
        </el-breadcrumb-item>
        <el-breadcrumb-item @click="changeRouter(5)" :class="indexFlag===5?'indexTag':''" :to="{ path: '/main/glassOptimize/optimizeMove' }">数控
        <el-breadcrumb-item @click="changeRouter(5)" :class="indexFlag===5?'indexTag':''" :to="{ path: '/main/glassOptimize/OptimizeControl' }">数控
        </el-breadcrumb-item>
        <el-breadcrumb-item @click="changeRouter(6)" :class="indexFlag===6?'indexTag':''" :to="{ path: '/main/glassOptimize/optimizeMove' }">设置
        <el-breadcrumb-item @click="changeRouter(6)" :class="indexFlag===6?'indexTag':''" :to="{ path: '/main/glassOptimize/optimizeparms' }">设置
        </el-breadcrumb-item>
        <el-breadcrumb-item></el-breadcrumb-item>
      </el-breadcrumb>
north-glass-erp/northglass-erp/src/views/pp/glassOptimize/OptimizeControl.vue
New file
@@ -0,0 +1,59 @@
<template>
    <div >
      <RectRenderer
    :layoutData="layoutData"
    :gw="1400"
    :gh="1100"
    style="width: 1500px; height: 800px; position: relative;"
  />
    </div>
    <button @click="submitLayouts" style="position: fixed; bottom: 20px; right: 20px; padding: 10px; background: #4CAF50; color: white; border: none; border-radius: 5px; cursor: pointer;">
      保存OPT
    </button>
  </template>
  <script setup>
  import { ref } from 'vue';
  import RectRenderer from './page/RectRenderer.vue';
  import mockLayoutData from '../../../components/pp/MockData';
  import request from "@/utils/request";
import { useI18n } from "vue-i18n";
import { ElMessage } from "element-plus";
const { t } = useI18n();
  const layoutData = ref(mockLayoutData);
  const submitLayouts = async () => {
  try {
    const response = await request.post('/glassOptimize/generateOpt', {
      Layouts: layoutData.value.Layouts
    }, {
      headers: {
        'Content-Type': 'application/json'
      },
      responseType: 'blob' // 以 blob 形式接收响应
    });
    // 处理下载
    const downloadUrl = window.URL.createObjectURL(response);
    const a = document.createElement('a');
    a.href = downloadUrl;
    a.download = 'output.opt';
    document.body.appendChild(a);
    a.click();
    document.body.removeChild(a);
    window.URL.revokeObjectURL(downloadUrl);
    ElMessage.success('OPT文件下载成功,请选择文件路径');
  } catch (error) {
    console.error('下载失败:', error);
    // 显示错误消息给用户
    ElMessage.error('下载失败,请稍后再试');
  }
};
  </script>
north-glass-erp/northglass-erp/src/views/pp/glassOptimize/OptimizeMove.vue
@@ -1,159 +1,23 @@
<template>
    <div style="width:100%;height:2000px;">
        <div class="layoutJSON">
            Displayed as <code>[x, y, w, h]</code>:
            <div class="columns">
              <div v-for="item in layout" :key="item.i">
    <div >
      <RectRenderer
    :layoutData="layoutData"
    :gw="1400"
    :gh="1100"
    style="width: 1500px; height: 800px; position: relative;"
  />
  
                    <b>{{item.i}}</b>: [{{item.x}}, {{item.y}}, {{item.w}}, {{item.h}}]
                </div>
            </div>
        </div>
        <hr/>
        <input type="checkbox" v-model="draggable"/> Draggable
        <input type="checkbox" v-model="resizable"/> Resizable
        <input type="checkbox" v-model="responsive"/> Responsive
        <br/>
        <div style="width:100%;margin-top: 10px;height:100%;">
          <grid-layout v-model:layout="layout"
               :col-num="12"
               :row-height="30"
               :is-draggable="draggable"
               :is-resizable="resizable"
               :vertical-compact="true"
               :use-css-transforms="true"
  >
  
            <grid-item v-for="item in layout"
             :key="item.i"
             :static="item.static"
             :x="item.x"
             :y="item.y"
             :w="item.w"
             :h="item.h"
             :i="item.i"
  >
      <span class="text">{{item.i}}</span>
  </grid-item>
            </grid-layout>
        </div>
    </div>
  </template>
  
  <script>
  import { GridLayout, GridItem } from 'vue3-grid-layout';
  <script setup>
  import { ref } from 'vue';
  import RectRenderer from './page/RectRenderer.vue';
  import mockLayoutData from '../../../components/pp/MockData';
  
  export default {
    components: {
      GridLayout,
      GridItem,
    },
    data() {
        return {
            layout: [
                {"x":0,"y":0,"w":2,"h":2,"i":"0666*343"},
                {"x":2,"y":0,"w":2,"h":4,"i":"1"},
                {"x":4,"y":0,"w":2,"h":5,"i":"2"},
                {"x":6,"y":0,"w":2,"h":3,"i":"3"},
                {"x":8,"y":0,"w":2,"h":3,"i":"4"},
                {"x":10,"y":0,"w":2,"h":3,"i":"5"},
                {"x":0,"y":5,"w":2,"h":5,"i":"6"},
                {"x":2,"y":5,"w":2,"h":5,"i":"7"},
                {"x":4,"y":5,"w":2,"h":5,"i":"8"},
                {"x":6,"y":4,"w":2,"h":4,"i":"9"},
                {"x":8,"y":4,"w":2,"h":4,"i":"10"},
                {"x":10,"y":4,"w":2,"h":4,"i":"11"},
                {"x":0,"y":10,"w":2,"h":5,"i":"12"},
                {"x":2,"y":10,"w":2,"h":5,"i":"13"},
                {"x":4,"y":8,"w":2,"h":4,"i":"14"},
                {"x":6,"y":8,"w":2,"h":4,"i":"15"},
                {"x":8,"y":10,"w":2,"h":5,"i":"16"},
                {"x":10,"y":4,"w":2,"h":2,"i":"17"},
                {"x":0,"y":9,"w":2,"h":3,"i":"18"},
                {"x":2,"y":6,"w":2,"h":2,"i":"19"}
            ],
            draggable: true,
            resizable: true,
            responsive: true,
            index: 0
        }
    },
    methods: {
    }
  }
  const layoutData = ref(mockLayoutData);
  </script>
  <style scoped>
  .vue-grid-layout {
    background: #eee;
  }
  .vue-grid-item:not(.vue-grid-placeholder) {
    background: #ccc;
    border: 1px solid black;
  }
  .vue-grid-item .resizing {
    opacity: 0.9;
  }
  .vue-grid-item .static {
    background: #cce;
  }
  .vue-grid-item .text {
    font-size: 24px;
    text-align: center;
    position: absolute;
    top: 0;
    bottom: 0;
    left: 0;
    right: 0;
    margin: auto;
    height: 100%;
    width: 100%;
  }
  .vue-grid-item .no-drag {
    height: 100%;
    width: 100%;
  }
  .vue-grid-item .minMax {
    font-size: 12px;
  }
  .vue-grid-item .add {
    cursor: pointer;
  }
  .vue-draggable-handle {
    position: absolute;
    width: 20px;
    height: 20px;
    top: 0;
    left: 0;
    background: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='10' height='10'><circle cx='5' cy='5' r='5' fill='#999999'/></svg>") no-repeat;
    background-position: bottom right;
    padding: 0 8px 8px 0;
    background-repeat: no-repeat;
    background-origin: content-box;
    box-sizing: border-box;
    cursor: pointer;
  }
  .layoutJSON {
    background: #ddd;
    border: 1px solid black;
    margin-top: 10px;
    padding: 10px;
  }
  .columns {
    -moz-columns: 120px;
    -webkit-columns: 120px;
    columns: 120px;
  }
  </style>
north-glass-erp/northglass-erp/src/views/pp/glassOptimize/OptimizeParms.vue
New file
@@ -0,0 +1,522 @@
<script setup>
import { ref, reactive, onMounted, watch } from 'vue';
import { useI18n } from "vue-i18n";
import { ElMessage } from "element-plus";
import request from "@/utils/request";
import useUserInfoStore from "@/stores/userInfo";
const { t } = useI18n();
const userStore = useUserInfoStore()
const username = userStore.user.userName
const currentComponent = ref('optimization');
const settings = reactive({
  optimization: {},
  display: {},
  cutting: {},
  server: {},
  tempering: {}
});
const selectComponent = (component) => {
  currentComponent.value = component;
};
const saveToDatabase = () => {
  request.post(`/glassOptimize/optimizeParms`, settings).then((res) => {
    if (res.code == 200 && res.data === true) {
      ElMessage.success(t('basicData.msg.saveSuccess'))
    } 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);
      Object.assign(settings, parsedData);
      console.log('设置已更新:', settings);
    } else {
      console.error('请求失败,状态码:', response.code);
    }
  } catch (error) {
    console.error('请求发生错误:', error);
  }
};
onMounted(() => {
  fetchSettings(username);
});
// 参数映射表,用于将英文参数名映射为中文名称和类型
const paramMapping = {
  yShapeJoinOptimization: { name: '异形拼接优化', type: 'checkbox' },
  autoMiddleEmptyPairing: { name: '自动中空配对', type: 'checkbox' },
  smallPieceRotationProhibited: { name: '小片禁止旋转', type: 'checkbox' },
  maxFramesOnSite: { name: '现场最大可放架子数量', type: 'text' },
  bendEdgeDistance: { name: '掰边距(mm)', type: 'text' },
  positiveTolerance: { name: '正公差(mm)', type: 'text' },
  negativeTolerance: { name: '负公差(mm)', type: 'text' },
  cutterOriginPosition: { name: '切割机原点位置', type: 'select' },
  uniformShapeEdgeTrimAmount: { name: '统一设置异形修边量', type: 'text' },
  edgeTrimMode: { name: '修边模式', type: 'select' },
  parallelOptimizationChannels: { name: '并行优化的通道数量', type: 'text' },
  optimizationIterations: { name: '重复优化次数', type: 'text' },
  finishedProductGrindingAmount: { name: '成品默认磨量', type: 'text' },
  rawPieceEdgeTrimAmount: { name: '原片默认修边量', type: 'text' },
  finishedSinglePieceBelowGrindingAmount: { name: '成品单片小于多少时磨量为', type: 'text' }
};
// 动态生成参数列表
const parameterList = ref(Object.keys(paramMapping).map(key => ({
  key,
  ...paramMapping[key]
})));
</script>
<template>
  <div class="settings-container">
    <div class="sidebar">
      <div
        @click="selectComponent('optimization')"
        :class="{ active: currentComponent === 'optimization' }"
        class="sidebar-item"
      >
        <div class="sidebar-header">优化参数</div>
        <div class="sidebar-content">优化参数设置</div>
      </div>
      <div
        @click="selectComponent('display')"
        :class="{ active: currentComponent === 'display' }"
        class="sidebar-item"
      >
        <div class="sidebar-header">版图显示</div>
        <div class="sidebar-content">版图显示设置</div>
      </div>
      <div
        @click="selectComponent('cutting')"
        :class="{ active: currentComponent === 'cutting' }"
        class="sidebar-item"
      >
        <div class="sidebar-header">刀路</div>
        <div class="sidebar-content">刀路设置</div>
      </div>
      <div
        @click="selectComponent('server')"
        :class="{ active: currentComponent === 'server' }"
        class="sidebar-item"
      >
        <div class="sidebar-header">输出</div>
        <div class="sidebar-content">结果输出设置</div>
      </div>
      <div
        @click="selectComponent('tempering')"
        :class="{ active: currentComponent === 'tempering' }"
        class="sidebar-item"
      >
        <div class="sidebar-header">钢化</div>
        <div class="sidebar-content">钢化设置</div>
      </div>
    </div>
    <div class="main-content">
      <template v-if="currentComponent === 'display'">
        <div class="display-settings">
          <h2>版图显示设置</h2>
          <div class="form-group">
            <label>矩形成品显示颜色</label>
            <input type="color" v-model="settings.display.themeColor" />
          </div>
          <div class="form-group">
            <label>成品尺寸包含磨边量</label>
            <input type="checkbox" v-model="settings.display.includeProductEdge" />
          </div>
          <div class="form-group">
            <label>异形尺寸包含磨边量</label>
            <input type="checkbox" v-model="settings.display.includeIrregularEdge" />
          </div>
          <div class="form-group">
            <label>按架号合并版图</label>
            <input type="checkbox" v-model="settings.display.mergeByFrameNumber" />
          </div>
          <div style="border: 1px solid #d2d0d0; margin-top: 20px;">
            <div style="background-color: #D5EAFF;">小片信息</div>
            <div class="form-group">
              <label>架号</label>
              <input type="checkbox" v-model="settings.display.frameNumber" />
            </div>
            <div class="form-group">
              <label>订单编号</label>
              <input type="checkbox" v-model="settings.display.orderNumber" />
            </div>
            <div class="form-group">
              <label>产品名称</label>
              <input type="checkbox" v-model="settings.display.productName" />
            </div>
            <div class="form-group">
              <label>加工信息</label>
              <input type="checkbox" v-model="settings.display.processingInfo" />
            </div>
            <div class="form-group">
              <label>备注</label>
              <input type="checkbox" v-model="settings.display.remarks" />
            </div>
            <div class="form-group">
              <label>楼层编号</label>
              <input type="checkbox" v-model="settings.display.floorNumber" />
            </div>
            <div class="form-group">
              <label>边长</label>
              <input type="checkbox" v-model="settings.display.edgeLength" />
            </div>
            <div class="form-group">
              <label>工艺</label>
              <input type="checkbox" v-model="settings.display.manufacturingProcess" />
            </div>
            <div class="form-group">
              <label>ID</label>
              <input type="checkbox" v-model="settings.display.identifier" />
            </div>
          </div>
        </div>
      </template>
      <template v-else-if="currentComponent === 'cutting'">
        <div class="display-settings">
          <h2>刀路设置</h2>
          <div class="form-group">
            <label>正向切割</label>
            <input type="checkbox" v-model="settings.cutting.cutting_direction" />
          </div>
          <div class="form-group">
            <label>显示切割方向</label>
            <input type="checkbox" v-model="settings.cutting.show_cutting_path" />
          </div>
          <div class="form-group">
            <label>强制弧线和直线连续切割</label>
            <input type="checkbox" v-model="settings.cutting.force_continuous_cutting" />
          </div>
          <div class="form-group">
            <label>孔洞最小切割半径</label>
            <input type="number" v-model="settings.cutting.min_radius" />
          </div>
          <div class="form-group">
            <label>两条平行线最小间隔</label>
            <input type="number" v-model="settings.cutting.min_parallel_offset" />
          </div>
          <div class="form-group">
            <label>T型退缩</label>
            <input type="number" v-model="settings.cutting.t_shaped_recess" />
          </div>
          <div class="form-group">
            <label>下刀间距</label>
            <input type="number" v-model="settings.cutting.down_cut_spacing" />
          </div>
          <div class="form-group">
            <label>抬刀退缩</label>
            <input type="number" v-model="settings.cutting.lift_retract" />
          </div>
        </div>
      </template>
      <template v-else-if="currentComponent === 'server'">
        <div class="display-settings">
          <h2>结果输出设置</h2>
          <div class="form-group">
            <label>工程文件保存路径</label>
            <input type="text" v-model="settings.server.output_format" />
          </div>
          <div class="form-group">
            <label>切割代码保存路径</label>
            <input type="text" v-model="settings.server.cutting_code_save_path" />
          </div>
          <div class="form-group">
            <label>保存文件后打开所在文件夹</label>
            <input type="text" v-model="settings.server.open_folder_after_save" />
          </div>
          <div class="form-group">
            <label>G代码文件格式</label>
            <input type="text" v-model="settings.server.g_code_file_format" />
          </div>
          <div class="form-group">
            <label>TRF文件保存路径</label>
            <input type="text" v-model="settings.server.trf_file_save_path" />
          </div>
          <div class="form-group">
            <label>保特罗文件保存路径</label>
            <input type="text" v-model="settings.server.btl_file_save_path" />
          </div>
          <div class="form-group">
            <label>OPTIMA文件保存路径</label>
            <input type="text" v-model="settings.server.optima_file_save_path" />
          </div>
          <div class="form-group">
            <label>优化深度限制启用</label>
            <input type="text" v-model="settings.server.optimization_depth_limit_enable" />
          </div>
          <div class="form-group">
            <label>原片切材率计算</label>
            <input type="text" v-model="settings.server.original_sheet_material_calculation" />
          </div>
        </div>
      </template>
      <template v-else-if="currentComponent === 'tempering'">
        <div class="display-settings">
          <h2>钢化设置</h2>
          <div class="form-group">
            <label>炉长(mm)</label>
            <input type="number" v-model="settings.tempering.furnaceLength" step="1" />
          </div>
          <div class="form-group">
            <label>炉宽(mm)</label>
            <input type="number" v-model="settings.tempering.furnaceWidth" step="1" />
          </div>
          <div class="form-group">
            <label>最大装载率(%)</label>
            <input type="number" v-model="settings.tempering.maxLoadingRate" step="1" />
          </div>
          <div class="form-group">
            <label>混乱程度(%)</label>
            <input type="number" v-model="settings.tempering.chaosLevel" step="1" />
          </div>
          <div class="form-group">
            <label>钢化加热时间(秒)</label>
            <input type="number" v-model="settings.tempering.temperingTime" step="1" />
          </div>
          <div class="form-group">
            <label>默认钢化推荐模式</label>
            <select v-model="settings.tempering.defaultTemperingMode">
              <option value="auto">自动推荐</option>
              <option value="manual">手动选择</option>
            </select>
          </div>
          <div class="form-group">
            <label>最大面积设置(㎡)</label>
            <input type="number" v-model="settings.tempering.maxArea" step="0.1" />
          </div>
          <div class="form-group">
            <label>最大片数设置(片)</label>
            <input type="number" v-model="settings.tempering.maxPieceCount" step="1" />
          </div>
          <div class="form-group">
            <label>X轴默认间隔(mm)</label>
            <input type="number" v-model="settings.tempering.xAxisInterval" step="1" />
          </div>
          <div class="form-group">
            <label>Y轴默认间隔(mm)</label>
            <input type="number" v-model="settings.tempering.yAxisInterval" step="1" />
          </div>
        </div>
      </template>
      <template v-else>
        <div class="display-settings">
          <h2>优化参数设置</h2>
          <div class="parameter-list">
            <div
              class="form-group"
              v-for="(param, index) in parameterList"
              :key="index"
            >
              <label class="parameter-name">{{ param.name }}</label>
              <div class="parameter-control">
                <input
                  v-if="param.type === 'checkbox'"
                  type="checkbox"
                  :checked="settings.optimization[param.key] === '1'"
                  @click="() => { settings.optimization[param.key] = settings.optimization[param.key] === '1' ? '0' : '1' }"
                >
                <select
                  v-else-if="param.type === 'select'"
                  :name="param.key"
                  class="cs"
                >
                  <option
                    v-for="option in getOptions(param.key)"
                    :key="option.value"
                    :selected="option.value === settings.optimization[param.key]"
                  >
                    {{ option.label }}
                  </option>
                </select>
                <input
                  v-else
                  :type="param.type || 'text'"
                  :name="param.key"
                  class="cs"
                  v-model="settings.optimization[param.key]"
                >
              </div>
            </div>
          </div>
        </div>
      </template>
    </div>
    <button class="button" @click="saveToDatabase">保存</button>
  </div>
</template>
<style>
.settings-container {
  display: flex;
  gap: 20px;
  padding: 20px;
  background-color: #f0f2f5;
}
.sidebar {
  width: 300px;
  background-color: white;
  padding: 15px;
  border-radius: 8px;
  box-shadow: 0 2px 4 rgba(0, 0, 0, 0.1);
}
.sidebar-item {
  cursor: pointer;
  padding: 10px;
  margin-bottom: 5px;
  border-radius: 6px;
  transition: all 0.3s ease;
}
.sidebar-item.active {
  background-color: #f8f9fa;
  transform: translateX(5px);
}
.sidebar-header {
  font-size: 12px;
  color: #666;
  margin-bottom: 5px;
}
.sidebar-content {
  font-size: 14px;
  color:#333;
}
.main-content {
  flex-grow: 1;
  padding: 20px;
}
button {
  padding: 10px 20px;
  background-color: #5cadfe;
  color: white;
  border: none;
  border-radius: 4px;
  cursor: pointer;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
@media (max-width: 768px) {
  .settings-container {
    flex-direction: column;
  }
  .sidebar {
    order: -1;
  }
  .main-content {
    margin-top: 20px;
  }
}
.form-group {
  margin-bottom: 15px;
  display: flex;
  align-items: center;
  gap: 10px;
}
.form-group label {
  font-weight: normal;
  min-width: 120px;
}
.form-group input,
.form-group select {
  flex: 1;
  padding: 5px;
  border: 1px solid #ddd;
  border-radius: 4px;
}
.form-group select {
  cursor: pointer;
}
.display-settings {
  background-color: white;
  padding: 20px;
  border-radius: 8px;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.parameter-list {
  display: flex;
  flex-direction: column;
  gap: 0px;
}
.parameter-item {
  display: flex;
  align-items: center;
  gap: 0px;
}
.parameter-name {
  font-weight: normal;
  min-width: 120px;
}
.parameter-control {
  flex: 1;
}
.button {
  text-align: center;
  width: 70px;
  height: 28px;
  border: none;
  background-color: #5CADFE;
  box-shadow: 0 8px 16px 0 rgba(0, 0, 0, 0), 0 6px 5px 0 rgba(0, 0, 0, 0.19);
  margin-left: 500px;
}
</style>
<script>
// 辅助函数,根据参数键获取选项
function getOptions(key) {
  const optionsMapping = {
    cutterOriginPosition: [
      { value: 'topLeft', label: '左上角' },
      { value: 'bottomLeft', label: '左下角' },
      { value: 'topRight', label: '右上角' },
      { value: 'bottomRight', label: '右下角' }
    ],
    edgeTrimMode: [
      { value: 'intelligent', label: '智能修边' },
      { value: 'complete', label: '完全修边' }
    ]
  };
  return optionsMapping[key] || [];
}
</script>
north-glass-erp/northglass-erp/src/views/pp/glassOptimize/page/OptimizationRect.vue
New file
@@ -0,0 +1,943 @@
<template>
  <div style="display: flex; height: 100vh;">
    <!-- Sidebar -->
    <div class="sidebar" style="width: 200px; background: #f4f4f4; padding: 10px;">
      <div
        v-for="(layout, layoutIndex) in layouts"
        :key="layoutIndex"
        class="sidebar-item"
        @click="selectLayout(layoutIndex)"
        :class="{ 'selected': selectedLayoutIndex === layoutIndex }"
      >
        {{ layout.width }} × {{ layout.height }} × {{ layout.SameCount }}
      </div>
    </div>
    <!-- Main Layout Panel -->
    <div ref="layoutPanel" :class="panelClass" :style="panelStyle">
      <div
        v-for="(layout, layoutIndex) in layouts"
        :key="layoutIndex"
        class="layout-wrapper"
        :style="{ display: selectedLayoutIndex === layoutIndex ? 'block' : 'none', top: '-150px' }"
      >
        <!-- Layout Info Label -->
        <div class="layout-info" :style="layoutInfoStyle(layoutIndex)">
          {{ getCurrentRectInfo(layoutIndex) }}
        </div>
        <!-- Layout Container -->
        <div class="layout-container" :style="layoutContainerStyle(layoutIndex)">
          <!-- 灰色矩形 -->
          <div
            v-for="(rect, rectIndex) in layout.rects.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)"
          />
          <!-- 蓝色矩形 -->
          <div
            v-for="(rect, rectIndex) in layout.rects.filter(r => !r.isRemain)"
            :key="`blue-${rectIndex}`"
            :ref="(el) => { if (el) rectsElements[layoutIndex + '-' + rectIndex] = el }"
            class="layout-rect"
            :style="rectStyle(rect, layoutIndex)"
            @contextmenu.prevent="handleRectRightClick(layoutIndex, rectIndex)"
            @mousedown="handleRectDragStart(layoutIndex, rectIndex)"
            @mousemove="handleRectDragging"
            @mouseup="handleRectDragEnd"
            @mouseleave="handleRectDragEnd"
          >
            <div class="rect-content">
              <div class="size">{{ rect.w }}×{{ rect.h }}</div>
              <div class="jia-hao">{{ rect.JiaHao }}</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>
  </div>
</template>
<script setup>
import { ref, reactive, onMounted, onUnmounted } from 'vue';
import request from "@/utils/request";
import { useI18n } from "vue-i18n";
import { ElMessage, ElMessageBox } from "element-plus";
const { t } = useI18n();
const props = defineProps({
  layoutData: { type: Object, required: true },
  gw: { type: Number, default: 1000 },
  gh: { type: Number, default: 1000 },
  style: { type: String, default: 'width:1000px;height:600px;display:block;background:gray' }
});
const emit = defineEmits(['rectClicked']);
const layoutPanel = ref(null);
const rectsElements = ref({});
const focusIndex = ref(null);
const layouts = ref([]);
const panelClass = ref('');
const panelStyle = ref(props.style);
const rectClass = ref('layout-rect');
const selectedLayoutIndex = ref(0);
const currentRect = ref(null);
const dragging = ref(false);
const dragStartPos = ref({ x: 0, y: 0 });
const dragRect = 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);
    });
  });
  const savedProjectNo = localStorage.getItem('projectNo');
  const processId = savedProjectNo;
  const Layouts = layouts.value;
  request.post(`/glassOptimize/updateOptimizeResult/${processId}`, JSON.stringify({ Layouts }), {
    headers: {
      'Content-Type': 'application/json'
    }
  }).then((res) => {
    if (res.code == 200 && res.data === true) {
      ElMessage.success(t('basicData.msg.saveSuccess'));
    } else {
      ElMessage.warning(res.msg);
    }
  });
};
const showAddDialog = (layoutIndex, rectIndex) => {
  ElMessageBox.prompt('请输入成品的宽度和高度', '添加成品', {
    inputType: 'text',
    inputValidator: (value) => {
      const values = value.split(',').map(v => v.trim());
      if (values.length !== 2) {
        return '请输入两个数字,用逗号分隔';
      }
      const [width, height] = values;
      if (isNaN(width) || isNaN(height)) {
        return '请输入有效的数字';
      }
      if (width <= 0 || height <= 0) {
        return '宽度和高度必须大于0';
      }
      return true;
    },
    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(() => {
    // 用户取消
  });
};
const addNewRect = (layoutIndex, newRect) => {
  const layout = layouts.value[layoutIndex];
  layout.rects.push(newRect);
  adjustGrayRectangles(layoutIndex);
};
const layoutContainerStyle = (layoutIndex) => {
  const containerWidth = (props.gw - 210) / 2;
  const containerHeight = (props.gh - 100) / 3;
  const layout = layouts.value[layoutIndex];
  const scale = Math.min(
    (props.gw - 100) / layout.width,
    (props.gh - 100) / layout.height
  );
  return {
    position: 'absolute',
    left: `${(props.gw - layout.width * scale) / 2}px`,
    top: `${(props.gh - layout.height * scale) / 2}px`,
    width: `${layout.width * scale}px`,
    height: `${layout.height * scale}px`,
    overflow: 'visible',
    border: '1px solid #ccc',
    background: '#fff'
  };
};
const layoutInfoStyle = (layoutIndex) => {
  const layout = layouts.value[layoutIndex];
  const scale = Math.min(
    (props.gw - 100) / layout.width,
    (props.gh - 100) / layout.height
  );
  return {
    position: 'absolute',
    left: `${(props.gw - layout.width * scale) / 2}px`,
    top: `${(props.gh - layout.height * scale) / 2 - 45}px`,
    background: 'none',
    textAlign: 'center',
    zIndex: 1000
  };
};
const rectStyle = (rect, layoutIndex) => {
  const layout = layouts.value[layoutIndex];
  const scale = Math.min(
    (props.gw - 100) / layout.width,
    (props.gh - 100) / layout.height
  );
  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',
    border: '1px solid #000',
    cursor: 'pointer',
    draggable: !rect.isRemain,
    zIndex: rect.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 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.backgroundColor = '#fff';
  contextMenu.style.border = '1px solid #ccc';
  contextMenu.style.padding = '5px';
  contextMenu.style.zIndex = 1001;
  const rotateItem = document.createElement('div');
  rotateItem.textContent = '旋转';
  rotateItem.style.cursor = 'pointer';
  rotateItem.addEventListener('click', () => {
    rotateRect(layoutIndex, rectIndex);
    document.body.removeChild(contextMenu);
  });
  const moveUpAndRotateItem = document.createElement('div');
  moveUpAndRotateItem.textContent = '向上移动并旋转';
  moveUpAndRotateItem.style.cursor = 'pointer';
  moveUpAndRotateItem.addEventListener('click', () => {
    moveRectAndRotate(layoutIndex, rectIndex, 'up');
    document.body.removeChild(contextMenu);
  });
  const moveDownAndRotateItem = document.createElement('div');
  moveDownAndRotateItem.textContent = '向下移动并旋转';
  moveDownAndRotateItem.style.cursor = 'pointer';
  moveDownAndRotateItem.addEventListener('click', () => {
    moveRectAndRotate(layoutIndex, rectIndex, 'down');
    document.body.removeChild(contextMenu);
  });
  const moveLeftAndRotateItem = document.createElement('div');
  moveLeftAndRotateItem.textContent = '向左移动并旋转';
  moveLeftAndRotateItem.style.cursor = 'pointer';
  moveLeftAndRotateItem.addEventListener('click', () => {
    moveRectAndRotate(layoutIndex, rectIndex, 'left');
    document.body.removeChild(contextMenu);
  });
  const moveRightAndRotateItem = document.createElement('div');
  moveRightAndRotateItem.textContent = '向右移动并旋转';
  moveRightAndRotateItem.style.cursor = 'pointer';
  moveRightAndRotateItem.addEventListener('click', () => {
    moveRectAndRotate(layoutIndex, rectIndex, 'right');
    document.body.removeChild(contextMenu);
  });
  const moveUpItem = document.createElement('div');
  moveUpItem.textContent = '向上移动';
  moveUpItem.style.cursor = 'pointer';
  moveUpItem.addEventListener('click', () => {
    moveRect(layoutIndex, rectIndex, 'up');
    document.body.removeChild(contextMenu);
  });
  const moveDownItem = document.createElement('div');
  moveDownItem.textContent = '向下移动';
  moveDownItem.style.cursor = 'pointer';
  moveDownItem.addEventListener('click', () => {
    moveRect(layoutIndex, rectIndex, 'down');
    document.body.removeChild(contextMenu);
  });
  const moveLeftItem = document.createElement('div');
  moveLeftItem.textContent = '向左移动';
  moveLeftItem.style.cursor = 'pointer';
  moveLeftItem.addEventListener('click', () => {
    moveRect(layoutIndex, rectIndex, 'left');
    document.body.removeChild(contextMenu);
  });
  const moveRightItem = document.createElement('div');
  moveRightItem.textContent = '向右移动';
  moveRightItem.style.cursor = 'pointer';
  moveRightItem.addEventListener('click', () => {
    moveRect(layoutIndex, rectIndex, 'right');
    document.body.removeChild(contextMenu);
  });
  const deleteItem = document.createElement('div');
  deleteItem.textContent = '删除';
  deleteItem.style.cursor = 'pointer';
  deleteItem.addEventListener('click', () => {
    deleteRect(layoutIndex, rectIndex);
    document.body.removeChild(contextMenu);
  });
  const addItem = document.createElement('div');
  addItem.textContent = '添加成品';
  addItem.style.cursor = 'pointer';
  addItem.addEventListener('click', () => {
    showAddDialog(layoutIndex, rectIndex);
    document.body.removeChild(contextMenu);
  });
  contextMenu.appendChild(rotateItem);
  contextMenu.appendChild(moveUpAndRotateItem);
  contextMenu.appendChild(moveDownAndRotateItem);
  contextMenu.appendChild(moveLeftAndRotateItem);
  contextMenu.appendChild(moveRightAndRotateItem);
  contextMenu.appendChild(moveUpItem);
  contextMenu.appendChild(moveDownItem);
  contextMenu.appendChild(moveLeftItem);
  contextMenu.appendChild(moveRightItem);
  contextMenu.appendChild(deleteItem);
  contextMenu.appendChild(addItem);
  document.body.appendChild(contextMenu);
};
const handleGrayRectRightClick = (layoutIndex, rectIndex) => {
  const rect = layouts.value[layoutIndex].rects[rectIndex];
  if (!rect.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.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 = '添加成品';
  addItem.style.cursor = 'pointer';
  addItem.addEventListener('click', () => {
    showAddDialog(layoutIndex, rectIndex);
    document.body.removeChild(contextMenu);
  });
  contextMenu.appendChild(mergeLeftItem);
  contextMenu.appendChild(mergeRightItem);
  contextMenu.appendChild(mergeUpItem);
  contextMenu.appendChild(mergeDownItem);
  contextMenu.appendChild(addItem);
  document.body.appendChild(contextMenu);
};
const handleRectDragStart = (layoutIndex, rectIndex) => {
  const layout = layouts.value[layoutIndex];
  const rect = layout.rects[rectIndex];
  if (rect.isRemain) return;
  dragging.value = true;
  dragRect.value = { layoutIndex, rectIndex };
  dragStartPos.value = {
    x: event.clientX,
    y: event.clientY
  };
};
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 scale = Math.min(
    (props.gw - 100) / layout.width,
    (props.gh - 100) / layout.height
  );
  const deltaX = event.clientX - dragStartPos.value.x;
  const deltaY = event.clientY - dragStartPos.value.y;
  // 只调整位置,不调整大小
  const newRect = { ...rect };
  newRect.x += deltaX / scale;
  newRect.y += deltaY / scale;
  // 检查是否与其他蓝色矩形重叠
  const otherRects = layout.rects.filter(r => !r.isRemain && r !== rect);
  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) {
    isValidMove = false;
  }
  if (isValidMove) {
    rect.x = newRect.x;
    rect.y = newRect.y;
    dragStartPos.value = {
      x: event.clientX,
      y: event.clientY
    };
    adjustGrayRectangles(layoutIndex);
  }
};
const handleRectDragEnd = () => {
  if (dragRect.value) {
    const layoutIndex = dragRect.value.layoutIndex;
    const rectIndex = dragRect.value.rectIndex;
    const rect = layouts.value[layoutIndex].rects[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);
  }
  dragging.value = false;
  dragRect.value = null;
};
const adjustAlignmentPosition = (layoutIndex, rectIndex) => {
  const layout = layouts.value[layoutIndex];
  const rect = layout.rects[rectIndex];
  const otherRects = layout.rects.filter((r, i) => i !== rectIndex);
  const threshold = Math.max(rect.w, rect.h) * 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((rect.x + rect.w) - (otherRect.x + otherRect.w)) < threshold) {
      // 不调整宽度
    }
    if (Math.abs(rect.y - otherRect.y) < threshold) {
      rect.y = Math.round((rect.y + otherRect.y) / 2);
    }
    if (Math.abs((rect.y + rect.h) - (otherRect.y + otherRect.h)) < threshold) {
      // 不调整高度
    }
  });
};
const mergeGrayRects = (layoutIndex, rectIndex, direction) => {
  const layout = layouts.value[layoutIndex];
  const rect = layout.rects[rectIndex];
  const grayRects = layout.rects.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 = [];
  grayRects.sort((a, b) => {
    if (a.x !== b.x) return a.x - b.x;
    return a.y - b.y;
  });
  if (grayRects.length === 0) return;
  merged.push({ ...grayRects[0] });
  for (let i = 1; i < grayRects.length; i++) {
    const last = merged[merged.length - 1];
    const current = 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),
        isRemain: true
      });
    }
  }
  const nonGray = rects.filter(r => !r.isRemain);
  rects.splice(0, rects.length, ...nonGray, ...merged);
};
const adjustGrayRectangles = (layoutIndex) => {
  const layout = layouts.value[layoutIndex];
  const rects = layout.rects;
  const nonGrayRects = rects.filter(rect => !rect.isRemain);
  const remainingAreas = calculateRemainingAreas(layout.width, layout.height, nonGrayRects);
  const currentGrayRects = rects.filter(r => r.isRemain);
  currentGrayRects.forEach((_, index) => {
    if (index >= remainingAreas.length) {
      rects.splice(index, 1);
    }
  });
  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
      });
    }
  });
  mergeAdjacentGrayRects(rects);
};
const rotateRect = (layoutIndex, rectIndex) => {
  const layout = layouts.value[layoutIndex];
  const rect = layout.rects[rectIndex];
  const originalState = { ...rect };
  // 旋转矩形
  const temp = rect.w;
  rect.w = rect.h;
  rect.h = temp;
  // 检查旋转后是否与其他蓝色矩形重叠
  const otherRects = layout.rects.filter(r => !r.isRemain && r !== rect);
  let isValidRotation = true;
  otherRects.forEach(otherRect => {
    if (checkOverlap(rect, otherRect)) {
      isValidRotation = false;
    }
  });
  // 检查是否超出布局边界
  if (rect.x + rect.w > layout.width || rect.y + rect.h > layout.height) {
    isValidRotation = false;
  }
  if (isValidRotation) {
    adjustGrayRectangles(layoutIndex);
  } else {
    // 恢复原状
    rect.w = originalState.w;
    rect.h = originalState.h;
    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 temp = rect.w;
  rect.w = rect.h;
  rect.h = temp;
  // 检查旋转后的矩形是否可以放置在某个灰色矩形的位置
  const canPlace = grayRects.some(grayRect => {
    return grayRect.w >= rect.w && grayRect.h >= rect.h;
  });
  if (!canPlace) {
    // 如果不能放置,恢复原状
    const temp = rect.w;
    rect.w = rect.h;
    rect.h = 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 remainingAreas = calculateRemainingAreas(layout.width, layout.height, layout.rects.filter(r => !r.isRemain));
  // 根据方向计算可移动的最大步长
  let maxStep = 0;
  switch (direction) {
    case 'up':
      maxStep = rect.y;
      break;
    case 'down':
      maxStep = layout.height - (rect.y + rect.h);
      break;
    case 'left':
      maxStep = rect.x;
      break;
    case 'right':
      maxStep = layout.width - (rect.x + rect.w);
      break;
  }
  // 移动步长,根据剩余空间动态调整
  const stepSize = maxStep;
  const actualStep = Math.min(maxStep, stepSize);
  // 移动矩形
  switch (direction) {
    case 'up':
      rect.y -= actualStep;
      break;
    case 'down':
      rect.y += actualStep;
      break;
    case 'left':
      rect.x -= actualStep;
      break;
    case 'right':
      rect.x += actualStep;
      break;
  }
  // 检查是否与其他蓝色矩形重叠
  const otherRects = layout.rects.filter(r => !r.isRemain && r !== rect);
  let isValidMove = true;
  otherRects.forEach(otherRect => {
    if (checkOverlap(rect, otherRect)) {
      isValidMove = false;
    }
  });
  // 检查是否超出布局边界
  if (rect.x < 0 || rect.y < 0 ||
      rect.x + rect.w > layout.width ||
      rect.y + rect.h > layout.height) {
    isValidMove = false;
  }
  if (isValidMove) {
    adjustGrayRectangles(layoutIndex);
  } else {
    // 恢复原状
    rect.x = originalState.x;
    rect.y = originalState.y;
    ElMessage.warning('无法移动,存在重叠或超出边界');
  }
};
const deleteRect = (layoutIndex, rectIndex) => {
  const layout = layouts.value[layoutIndex];
  layout.rects.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);
};
const calculateRemainingAreas = (totalWidth, totalHeight, obstacles) => {
  let remaining = [{ x: 0, y: 0, w: totalWidth, h: totalHeight }];
  obstacles.forEach(rect => {
    remaining = cutRemainingAreas(remaining, rect);
  });
  return remaining;
};
const cutRemainingAreas = (remainingAreas, obstacle) => {
  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 {
      newRemaining.push(area);
    }
  });
  return newRemaining;
};
const getCurrentRectInfo = (layoutIndex) => {
  const layout = layouts.value[layoutIndex];
  const rect = layout.rects[focusIndex.value?.rectIndex || 0];
  if (!rect) 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) + '%';
  return `${currentRectIndex}/${totalRects} ${width}×${height} ×1 ${percentage}`;
};
const selectLayout = (layoutIndex) => {
  selectedLayoutIndex.value = layoutIndex;
};
const updateLayout = () => {
  if (!layoutPanel.value) return;
  layouts.value = props.layoutData.Layouts;
};
onMounted(() => {
  setTimeout(updateLayout, 1000);
  selectedLayoutIndex.value = 0;
});
onUnmounted(() => {
  rectsElements.value = {};
});
</script>
<style scoped>
.layout-wrapper {
  position: relative;
  margin-top: 50px;
}
.layout-container {
  position: relative;
  overflow: visible;
}
.layout-info {
  color: #444;
  font-size: 12px;
  background-color: #ffffff;
  padding: 5px 10px;
  border-radius: 3px;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
  font-weight: bold;
}
.rect-content {
  display: grid;
  grid-template-columns: 1fr;
  grid-template-rows: 1fr;
  padding: 5px;
}
.size {
  grid-row: 1;
  grid-column: 1;
  color: #444;
  font-size: 12px;
}
.jia-hao {
  grid-row: 2;
  grid-column: 1;
  margin: auto;
  font-size: 14px;
  font-weight: bold;
}
.sidebar-item {
  padding: 10px;
  cursor: pointer;
}
.sidebar-item.selected {
  background: #ddd;
}
.context-menu {
  position: absolute;
  background-color: #fff;
  border: 1px solid #ccc;
  padding: 5px;
  z-index: 1001;
}
.context-menu div {
  padding: 5px;
  cursor: pointer;
}
.context-menu div:hover {
  background-color: #f0f0f0;
}
</style>
north-glass-erp/northglass-erp/src/views/pp/glassOptimize/page/ProjectDetail.vue
@@ -1,5 +1,5 @@
<script setup>
import {nextTick, onMounted, reactive, ref, watch} from "vue";
import {nextTick, onMounted, reactive, ref, watch,onBeforeUnmount } from "vue";
import {useI18n} from "vue-i18n";
import {Folder, Plus, Setting, Operation,} from "@element-plus/icons-vue";
import OptimizeCompute from "@/views/pp/glassOptimize/page/OptimizeCompute.vue";
@@ -378,6 +378,9 @@
//工程号
const projectNo = ref(route.params.projectNo);
const projectName = ref('');
onBeforeUnmount(() => {
  localStorage.setItem('projectNo', projectNo.value);
});
const fetchData = () => {
  request.post(`/glassOptimize/projectInfo/${projectNo.value}`).then((res) => {
north-glass-erp/northglass-erp/src/views/pp/glassOptimize/page/RectRenderer.vue
File was renamed from north-glass-erp/northglass-erp/src/views/pp/glassOptimize/RectRenderer.vue
@@ -1,18 +1,14 @@
<template>
  <div ref="layoutPanel" :class="panelClass" :style="panelStyle">
    <div v-for="(layout, layoutIndex) in layouts" :key="layoutIndex"
         class="layout-wrapper">
    <div v-for="(layout, layoutIndex) in layouts" :key="layoutIndex" class="layout-wrapper">
      <!-- 布局信息标签 -->
      <div class="layout-info" :style="layoutInfoStyle(layoutIndex)">
        布局{{ layoutIndex + 1 }}
         {{ getCurrentRectInfo(layoutIndex) }}
      </div>
      <!-- 布局容器 -->
      <div class="layout-container"
           :style="layoutContainerStyle(layoutIndex)">
        <div v-for="(rect, rectIndex) in layout.rects" :key="rectIndex"
      <div class="layout-container" :style="layoutContainerStyle(layoutIndex)">
        <div v-for="(rect, rectIndex) in layout.rects" :key="rectIndex"
             :ref="(el) => { if (el) rectsElements[layoutIndex + '-' + rectIndex] = el }"
             :class="rectClass"
             :style="rectStyle(rect, layoutIndex)"
@@ -31,41 +27,26 @@
import { ref, reactive, onMounted, onUnmounted } from 'vue';
const props = defineProps({
  layoutData: {
    type: Object,
    required: true
  },
  gw: {
    type: Number,
    default: 1000
  },
  gh: {
    type: Number,
    default: 1000
  },
  style: {
    type: String,
    default: 'width:1000px;height:600px;display:block;background:gray'
  }
  layoutData: { type: Object, required: true },
  gw: { type: Number, default: 1000 },
  gh: { type: Number, default: 1000 },
  style: { type: String, default: 'width:1000px;height:600px;display:block;background:gray' }
});
const emit = defineEmits(['rectClicked']);
const layoutPanel = ref(null);
const rectsElements = ref({});
const focusIndex = ref(null);
const layouts = ref([]);
const panelClass = ref('');
const panelStyle = ref(props.style);
const rectClass = ref('layout-rect');
const layoutContainerStyle = (layoutIndex) => {
  const containerWidth = (props.gw - 210) / 2; // 两列,每列宽度为gw的一半,减去右边距
  const containerHeight = (props.gh - 100) / 3; // 三行,每行高度为gh的三分之一,减去下边距
  const x = (layoutIndex % 2) * (containerWidth + 50); // 横向排列,加上50px间距
  const y = Math.floor(layoutIndex / 2) * (containerHeight + 50); // 纵向排列,加上50px间距
  const containerWidth = (props.gw - 210) / 2;
  const containerHeight = (props.gh - 100) / 3;
  const x = (layoutIndex % 2) * (containerWidth + 50);
  const y = Math.floor(layoutIndex / 2) * (containerHeight + 50);
  return {
    position: 'absolute',
    left: `${x}px`,
@@ -86,7 +67,7 @@
  return {
    position: 'absolute',
    left: `${x}px`,
    top: `${y - 45}px`, // 将标签放在版图容器的上方
    top: `${y - 45}px`,
    background: 'none',
    textAlign: 'center',
    zIndex: 1000
@@ -101,7 +82,6 @@
    containerWidth / layout.width,
    containerHeight / layout.height
  );
  return {
    position: 'absolute',
    left: `${rect.x * scale}px`,
@@ -119,10 +99,21 @@
  emit('rectClicked', layoutIndex, rectIndex);
};
const getCurrentRectInfo = (layoutIndex) => {
  const layout = layouts.value[layoutIndex];
  const rect = layout.rects[focusIndex.value?.rectIndex || 0];
  if (!rect) 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) + '%';
  return `${currentRectIndex}/${totalRects} ${width}×${height} ×1 ${percentage}`;
};
const updateLayout = () => {
  if (!layoutPanel.value) return;
  layouts.value = props.layoutData.data.Layouts;
  layouts.value = props.layoutData.Layouts;
};
onMounted(() => {
@@ -132,12 +123,14 @@
onUnmounted(() => {
  rectsElements.value = {};
});
</script>
<style scoped>
.layout-wrapper {
  position: relative;
  margin-top:50px;
  margin-top: 50px;
}
.layout-container {
@@ -153,7 +146,6 @@
  border-radius: 3px;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
  font-weight: bold;
}
.rect-content {
north-glass-erp/src/main/java/com/example/erp/controller/pp/GlassOptimizeController.java
@@ -3,14 +3,23 @@
import cn.dev33.satoken.annotation.SaCheckPermission;
import com.example.erp.common.Constants;
import com.example.erp.common.Result;
import com.example.erp.entity.pp.LayoutsData;
import com.example.erp.entity.pp.OptimizeProjectMange;
import com.example.erp.exception.ServiceException;
import com.example.erp.service.pp.GlassOptimizeService;
import com.example.erp.service.pp.JsonToOptConverter;
import com.example.erp.service.userInfo.UserService;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.io.IOException;
import java.sql.Date;
import java.util.Map;
@@ -20,7 +29,8 @@
public class GlassOptimizeController {
    @Autowired
    GlassOptimizeService glassOptimizeService;
    @Autowired
    private UserService userService;
    //工程信息
    @ApiOperation("工程信息接口")
    @PostMapping  ("/projectInfo/{projectNo}")
@@ -134,6 +144,22 @@
        }
    }
    @ApiOperation("优化设置保存")
    @PostMapping("/optimizeParms")
    public Result optimizeParmsSave(@RequestBody Map<String, Object> object) {
        try {
            return Result.seccess(glassOptimizeService.optimizeParmsSave(object));
        } catch (Exception e) {
            e.printStackTrace();
            return Result.error();
        }
    }
    @PostMapping("/selectOptimizeParms/{username}")
    public Result getOptimizeParms(@PathVariable String username) {
        return Result.seccess(userService.getOptimizeParms(username));
    }
@@ -147,6 +173,8 @@
            return Result.error();
        }
    }
@@ -179,4 +207,51 @@
            @RequestBody Map<String,Object> object){
        return  Result.seccess(glassOptimizeService.addProjectSv(optionVal,projectId,projectNmae,object));
    }
    @ApiOperation("保存优化结果接口")
    @PostMapping("/saveOptimizeResult")
    public Result saveOptimizeResult(
            @RequestBody Map<String,Object> object){
        return  Result.seccess(glassOptimizeService.saveOptimizeResult(object));
    }
    @ApiOperation("查询优化结果接口")
    @PostMapping("/selectOptimizeResult/{processId}")
    public Result selectOptimizeResult(
            @PathVariable String processId){
        return  Result.seccess(glassOptimizeService.selectOptimizeResult(processId));
    }
    @ApiOperation("更新优化结果接口")
    @PostMapping("/updateOptimizeResult/{processId}")
    public Result updateOptimizeResult(
            @PathVariable String processId,@RequestBody Map<String,Object> object){
        return  Result.seccess(glassOptimizeService.updateOptimizeResult(object,processId));
    }
    @ApiOperation("OPT文件下载接口")
    @PostMapping("/generateOpt")
    public ResponseEntity<byte[]> generateOptFile(@RequestBody LayoutsData layoutsData) throws IOException {
        // 确保 layoutsData 包含 Layouts 数组
        if (layoutsData == null || layoutsData.getLayouts() == null) {
            throw new IllegalArgumentException("Layouts 数据不能为空");
        }
        // 生成 OPT 文件内容
        String optContent = JsonToOptConverter.generateOptContent(layoutsData);
        // 创建响应头
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(org.springframework.http.MediaType.TEXT_PLAIN);
        headers.setContentDispositionFormData("attachment", "output.opt");
        // 返回文件内容
        return new ResponseEntity<>(optContent.getBytes(), headers, HttpStatus.OK);
    }
}
north-glass-erp/src/main/java/com/example/erp/controller/userInfo/UserController.java
@@ -67,4 +67,5 @@
    public Result updateUserName(@RequestBody Map<String,Object> object){
        return Result.seccess( userService.updateUserName(object));
    }
}
north-glass-erp/src/main/java/com/example/erp/entity/pp/LayoutsData.java
New file
@@ -0,0 +1,82 @@
package com.example.erp.entity.pp;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.List;
public class LayoutsData {
    @JsonProperty("Layouts")
    private List<Layout> layouts;
    public List<Layout> getLayouts() {
        return layouts;
    }
public static  class Layout  {
    @JsonProperty("mnumber")
    private int mNumber;
    @JsonProperty("width")
    private int width;
    @JsonProperty("rects")
    private List<Rect> rects;
    @JsonProperty("wuliao")
    private String material;
    @JsonProperty("SameCount")
    private int sameCount;
    @JsonProperty("height")
    private int height;
    public int getWidth() {
        return width;
    }
    public int getHeight() {
        return height;
    }
    public List<Rect> getRects() {
        return rects;
    }
}
public static  class Rect {
    @JsonProperty("isRemain")
    private boolean isRemain;
    @JsonProperty("h")
    private int height;
    @JsonProperty("DM2")
    private int dm2;
    @JsonProperty("xuhao")
    private String serialNumber;
    @JsonProperty("DM1")
    private int dm1;
    @JsonProperty("liuchengka")
    private String processCard;
    @JsonProperty("JiaHao")
    private String jiaHao;
    @JsonProperty("LM2")
    private int lm2;
    @JsonProperty("LM1")
    private int lm1;
    @JsonProperty("w")
    private int width;
    @JsonProperty("x")
    private int x;
    @JsonProperty("y")
    private int y;
    @JsonProperty("rownumber")
    private int rowNumber;
    public int getWidth() {
        return width;
    }
    public int getHeight() {
        return height;
    }
    public String getProcessCard() {
        return processCard;
    }
}
}
north-glass-erp/src/main/java/com/example/erp/entity/userInfo/User.java
@@ -20,6 +20,7 @@
    private  String createTime;
    private  String updateTime;
    private  Integer state;
    private  String optimizeParms;
    @TableField(select = false,exist = false)
    private  List<UserRole> userRoleList;
north-glass-erp/src/main/java/com/example/erp/mapper/pp/GlassOptimizeMapper.java
@@ -50,7 +50,15 @@
    void addratioResult(Map<String, Object> object);
    //模拟计算保存 工程信息
    void addratioProjectResult(Map<String, Object> object);
    //优化设置保存
    void optimizeParmsSave(String jsonString);
    //优化结果保存
    void optimizeResultSave(String jsonString);
    //优化结果查询
    List<Map<String, Object>> selectOptimizeResult(String processId);
    //优化结果调整
    void updateOptimizeResult(String jsonString,String processId);
    List<Map<String, Object>> getProcessCardDetailmMp(String processId, Integer technologyNumber);
north-glass-erp/src/main/java/com/example/erp/mapper/userInfo/UserMapper.java
@@ -39,4 +39,6 @@
    Boolean updateProcessMp(Integer id, String process);
    List<User> findByAddress(String userName);
    String selectOptimizeParmsById(@Param("username") String username);
}
north-glass-erp/src/main/java/com/example/erp/service/pp/GlassOptimizeService.java
@@ -72,6 +72,57 @@
            return false;
        }
    }
    //优化设置保存
    public Boolean optimizeParmsSave(Map<String, Object> object) {
        try {
            ObjectMapper mapper = new ObjectMapper();
            String jsonString = mapper.writeValueAsString(object);
            glassOptimizeMapper.optimizeParmsSave(jsonString);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }
    //优化调整结果保存
    public Boolean saveOptimizeResult(Map<String, Object> object) {
        try {
            ObjectMapper mapper = new ObjectMapper();
            String jsonString = mapper.writeValueAsString(object);
            glassOptimizeMapper.optimizeResultSave(jsonString);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }
    public Boolean updateOptimizeResult(Map<String, Object> object,String processId){
        try {
            ObjectMapper mapper = new ObjectMapper();
            String jsonString = mapper.writeValueAsString(object);
            glassOptimizeMapper.updateOptimizeResult(jsonString,processId);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }
   //优化调整结果查询
    public Map<String, Object> selectOptimizeResult(String processId) {
        Map<String, Object> map = new HashMap<>();
        map.put("data", glassOptimizeMapper.selectOptimizeResult(processId));
        return map;
    }
    //工程信息
    public Map<String, Object> projectInfoSv(String projectNo) {
        Map<String, Object> map = new HashMap<>();
north-glass-erp/src/main/java/com/example/erp/service/pp/JsonToOptConverter.java
New file
@@ -0,0 +1,105 @@
package com.example.erp.service.pp;
import com.example.erp.entity.pp.LayoutsData;
import com.example.erp.entity.pp.LayoutsData.*;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
import java.util.List;
public class JsonToOptConverter {
    public static String convertToJson(LayoutsData layoutsData) throws IOException {
        ObjectMapper objectMapper = new ObjectMapper();
        return objectMapper.writeValueAsString(layoutsData);
    }
    public static String generateOptContent(LayoutsData layoutsData) {
        StringBuilder optContent = new StringBuilder();
        addHeader(optContent);
        addSignatures(optContent);
        addPatterns(optContent, layoutsData);
        addInfos(optContent, layoutsData);
        return optContent.toString();
    }
    private static void addHeader(StringBuilder optContent) {
        optContent.append("[OPT_Header]\n");
        optContent.append("OPTCutVersion=4.0\n");
        optContent.append("Dimension=mm\n");
        optContent.append("Date=2025/2/25\n\n");
    }
    private static void addSignatures(StringBuilder optContent) {
        optContent.append("[OPT_Signature]\n");
        optContent.append("Creator=OPTIMA S.r.l.\n\n");
    }
    private static void addPatterns(StringBuilder optContent, LayoutsData layoutsData) {
        optContent.append("[OPT_Pattern]\n");
        for (Layout layout : layoutsData.getLayouts()) {
            optContent.append("GlassID=32\n");
            optContent.append("GlassDescription=32\n");
            optContent.append("GlassType=0\n");
            optContent.append("GlassThickness=5\n");
            optContent.append("Pieces=100\n");
            optContent.append("Width=").append(layout.getWidth()).append("\n");
            optContent.append("Height=").append(layout.getHeight()).append("\n");
            optContent.append("TrimLeft=15\n");
            optContent.append("TrimBottom=0\n");
            optContent.append("TrimRight=0\n");
            optContent.append("TrimTop=0\n");
            optContent.append("MinCutDistance=10\n");
            optContent.append("Priority=0\n");
            optContent.append("TravType=XY\n\n");
        }
    }
    private static void addInfos(StringBuilder optContent, LayoutsData layoutsData) {
        int id = 1;
        for (Layout layout : layoutsData.getLayouts()) {
            for (Rect rect : layout.getRects()) {
                optContent.append("[Info]\n");
                optContent.append("Id=").append(id++).append("\n");
                optContent.append("SecondGlassReference=\n");
                optContent.append("RackNo=0\n");
                optContent.append("SheetWidth=").append(rect.getWidth()).append("\n");
                optContent.append("SheetHeight=").append(rect.getHeight()).append("\n");
                optContent.append("Customer=\n");
                optContent.append("PosNo=\n");
                optContent.append("OrderNo=").append(rect.getProcessCard()).append("-1-").append(id - 1).append("\n");
                optContent.append("RackCode=0\n");
                optContent.append("PieceStandardPz=\n");
                optContent.append("UMPz=\n");
                optContent.append("IDPz=\n");
                optContent.append("RealDimXPz=").append(rect.getWidth()).append(".00\n");
                optContent.append("RealDimYPz=").append(rect.getHeight()).append(".00\n");
                optContent.append("GrindingPz=\n");
                optContent.append("GrindingPzX1=0\n");
                optContent.append("GrindingPzY1=0\n");
                optContent.append("GrindingPzX2=0\n");
                optContent.append("GrindingPzY2=0\n");
                optContent.append("BendingPz=\n");
                optContent.append("QuantityPz=77\n");
                optContent.append("LabelsPz=\n");
                optContent.append("LabelsPzCounter=\n");
                optContent.append("RotationAllowed=1\n");
                optContent.append("PreferencePz=\n");
                optContent.append("DescMatCompPz=\n");
                optContent.append("DeliveryDatePz=\n");
                optContent.append("PzNOTE=3C.orzx\n");
                optContent.append("PzNOTE1=\n");
                optContent.append("PzNOTE2=").append(rect.getWidth()).append("\n");
                optContent.append("PzNOTE3=").append(rect.getHeight()).append("\n");
                optContent.append("PzNOTE4=右下-30-30\n");
                optContent.append("PzNOTE5=false-true-false\n");
                optContent.append("PzNOTE6=true-true\n");
                optContent.append("PzNOTE7=").append(rect.getProcessCard()).append("-1-").append(id - 1).append("\n");
                for (int i = 8; i <= 20; i++) {
                    optContent.append("PzNOTE").append(i).append("=\n");
                }
                optContent.append("\n");
            }
        }
    }
}
north-glass-erp/src/main/java/com/example/erp/service/userInfo/UserService.java
@@ -207,5 +207,15 @@
        map.put("users", Collections.singletonList(userList));
        return map;
    }
//查询用户优化参数
public String getOptimizeParms(String username) {
    // 获取当前登录用户
        return userMapper.selectOptimizeParmsById(username);
    }
}
north-glass-erp/src/main/resources/mapper/pp/GlassOptimize.xml
@@ -407,6 +407,7 @@
                LEFT JOIN pp.damage_details dal ON  dal.order_number = g.order_number AND dal.technology_number = g.technology_number and f.process_id=dal.process_id
                LEFT JOIN pp.flow_card fl ON fl.order_id = g.order_id AND fl.order_number = g.order_number AND fl.technology_number = g.technology_number
        WHERE
            p.state IN (1, 2) and
          f.process_id = #{processId}
    </select>
@@ -528,7 +529,7 @@
            );
        </foreach>
    </insert>
    <!--模拟计算结果保存-->
    <insert id="addratioResult" parameterType="map">
        <foreach collection="projectdetail.data[0].ratioResult" item="glass">
            INSERT INTO pp.optimize_heat_layout (
@@ -555,7 +556,7 @@
        </foreach>
    </insert>
    <!--模拟计算工程更新-->
    <update id="addratioProjectResult" parameterType="map">
        UPDATE pp.optimize_project
        SET
@@ -573,6 +574,23 @@
            project_no = #{inputValues.project_no}
    </update>
<!--优化设置保存-->
    <update id="optimizeParmsSave">
        update erp_user_info.user as u
        set u.optimize_parms = #{jsonString}
    </update>
    <update id="updateOptimizeResult">
        update pp.optimize_project_file as u
        set u.content = #{jsonString}
        where  u.project_no=#{processId} and type='优化结果'
    </update>
    <select id="selectOptimizeResult">
        SELECT content as Layouts
                        FROM pp.optimize_project_file
                        WHERE  project_no=#{processId} and type='优化结果';
    </select>
</mapper>
north-glass-erp/src/main/resources/mapper/userInfo/User.xml
@@ -104,4 +104,11 @@
        set address=#{process},update_time=now()
        where id=#{id}
    </update>
        <select id="selectOptimizeParmsById" resultType="String">
            SELECT optimize_parms FROM erp_user_info.user WHERE user_name = #{username}
        </select>
</mapper>