From 10b6d0099a5b381cfa2d7f89eeb3fed20adcccf1 Mon Sep 17 00:00:00 2001 From: panchengyong Date: Fri, 27 Feb 2026 23:50:25 +0800 Subject: [PATCH] =?UTF-8?q?=E6=BD=98=E7=9A=84=E7=AC=AC=E4=B8=80=E6=AC=A1?= =?UTF-8?q?=20commit?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- erp-frontend-vue/.gitignore | 24 + erp-frontend-vue/.vscode/extensions.json | 3 + erp-frontend-vue/README.md | 5 + erp-frontend-vue/index.html | 13 + erp-frontend-vue/package-lock.json | 2764 +++++++++++++++++ erp-frontend-vue/package.json | 34 + erp-frontend-vue/public/vite.svg | 1 + erp-frontend-vue/src/App.vue | 9 + .../src/api/__tests__/customer.test.ts | 156 + erp-frontend-vue/src/api/auth.ts | 58 + erp-frontend-vue/src/api/checkin.ts | 96 + erp-frontend-vue/src/api/contract.ts | 63 + erp-frontend-vue/src/api/customer.ts | 111 + erp-frontend-vue/src/api/deliver.ts | 108 + erp-frontend-vue/src/api/invoice.ts | 77 + erp-frontend-vue/src/api/masterdata/bom.ts | 201 ++ erp-frontend-vue/src/api/masterdata/item.ts | 70 + .../src/api/masterdata/itemtype.ts | 79 + erp-frontend-vue/src/api/masterdata/unit.ts | 65 + .../src/api/masterdata/workshop.ts | 64 + .../src/api/masterdata/workstation.ts | 68 + erp-frontend-vue/src/api/material.ts | 78 + erp-frontend-vue/src/api/mbom.ts | 142 + erp-frontend-vue/src/api/parts.ts | 72 + erp-frontend-vue/src/api/productionPlan.ts | 357 +++ erp-frontend-vue/src/api/purchaseInvoice.ts | 70 + erp-frontend-vue/src/api/purchaseNeed.ts | 60 + erp-frontend-vue/src/api/purchaseOrder.ts | 237 ++ erp-frontend-vue/src/api/purchasePlan.ts | 344 ++ erp-frontend-vue/src/api/rd/ebom.ts | 227 ++ erp-frontend-vue/src/api/reject.ts | 91 + erp-frontend-vue/src/api/request.ts | 105 + erp-frontend-vue/src/api/saleback.ts | 105 + erp-frontend-vue/src/api/salesOrder.ts | 268 ++ erp-frontend-vue/src/api/supplier.ts | 155 + erp-frontend-vue/src/api/system/dept.ts | 91 + erp-frontend-vue/src/api/system/post.ts | 58 + erp-frontend-vue/src/api/system/role.ts | 99 + erp-frontend-vue/src/api/system/user.ts | 123 + erp-frontend-vue/src/api/warehouse/issue.ts | 222 ++ .../src/api/warehouse/issueDetail.ts | 69 + .../src/api/warehouse/issueLine.ts | 59 + erp-frontend-vue/src/api/workOrder.ts | 305 ++ erp-frontend-vue/src/assets/logo-color.png | Bin 0 -> 33131 bytes erp-frontend-vue/src/assets/logo-white.png | Bin 0 -> 11811 bytes erp-frontend-vue/src/assets/vue.svg | 1 + .../src/components/HelloWorld.vue | 41 + .../src/components/print/PrintDialog.vue | 309 ++ .../src/components/print/QrCode.vue | 38 + .../src/components/print/types.ts | 39 + erp-frontend-vue/src/layout/index.vue | 290 ++ erp-frontend-vue/src/main.ts | 23 + erp-frontend-vue/src/permission.ts | 70 + erp-frontend-vue/src/router/index.ts | 538 ++++ erp-frontend-vue/src/stores/user.ts | 111 + erp-frontend-vue/src/style.css | 41 + erp-frontend-vue/src/utils/auth.ts | 13 + erp-frontend-vue/src/views/Dashboard.vue | 179 ++ erp-frontend-vue/src/views/Login.vue | 278 ++ .../src/views/MasterData/Bom/form.vue | 656 ++++ .../src/views/MasterData/Bom/list.vue | 436 +++ .../src/views/MasterData/Item/index.vue | 525 ++++ .../src/views/MasterData/ItemType/index.vue | 285 ++ .../src/views/MasterData/Unit/index.vue | 293 ++ .../src/views/MasterData/Workshop/index.vue | 284 ++ .../views/MasterData/Workstation/index.vue | 305 ++ .../src/views/Production/Mbom/form.vue | 358 +++ .../src/views/Production/Mbom/index.vue | 518 +++ .../src/views/Production/Parts/index.vue | 372 +++ .../src/views/Production/PlanOrder/form.vue | 1557 ++++++++++ .../src/views/Production/PlanOrder/index.vue | 766 +++++ .../views/Production/PurchasePlan/form.vue | 1006 ++++++ .../views/Production/PurchasePlan/index.vue | 763 +++++ .../views/Production/Reports/DetailReport.vue | 360 +++ .../views/Production/Reports/NeedReport.vue | 184 ++ .../views/Production/Reports/TotalReport.vue | 341 ++ .../src/views/Production/WorkOrder/form.vue | 861 +++++ .../src/views/Production/WorkOrder/index.vue | 546 ++++ .../src/views/Purchasing/Checkin/index.vue | 123 + .../src/views/Purchasing/Invoice/index.vue | 133 + .../src/views/Purchasing/Order/form.vue | 949 ++++++ .../src/views/Purchasing/Order/index.vue | 337 ++ .../src/views/Purchasing/Reject/index.vue | 124 + .../Purchasing/Reports/CheckinReport.vue | 93 + .../views/Purchasing/Reports/DetailReport.vue | 104 + .../Purchasing/Reports/InvoiceReport.vue | 101 + .../views/Purchasing/Reports/TotalReport.vue | 96 + .../src/views/Purchasing/Supplier/index.vue | 799 +++++ erp-frontend-vue/src/views/RD/Ebom/form.vue | 963 ++++++ erp-frontend-vue/src/views/RD/Ebom/index.vue | 661 ++++ .../src/views/Sales/Contract/form.vue | 154 + .../src/views/Sales/Contract/index.vue | 133 + .../Sales/Customer/__tests__/index.spec.ts | 73 + .../src/views/Sales/Customer/index.vue | 514 +++ .../src/views/Sales/Deliver/form.vue | 203 ++ .../src/views/Sales/Deliver/index.vue | 130 + .../src/views/Sales/Invoice/form.vue | 135 + .../src/views/Sales/Invoice/index.vue | 128 + .../src/views/Sales/Order/form.vue | 1324 ++++++++ .../src/views/Sales/Order/index.vue | 629 ++++ .../src/views/Sales/Order/view.vue | 635 ++++ .../src/views/Sales/Reports/DetailReport.vue | 147 + .../src/views/Sales/Reports/PlanReport.vue | 135 + .../views/Sales/Reports/SevenDayReport.vue | 149 + .../src/views/Sales/Saleback/form.vue | 183 ++ .../src/views/Sales/Saleback/index.vue | 122 + .../src/views/System/Dept/index.vue | 334 ++ .../src/views/System/Post/index.vue | 287 ++ .../src/views/System/Role/index.vue | 333 ++ .../src/views/System/User/index.vue | 531 ++++ .../src/views/Warehouse/Issue/form.vue | 1408 +++++++++ .../src/views/Warehouse/Issue/index.vue | 459 +++ erp-frontend-vue/tsconfig.app.json | 21 + erp-frontend-vue/tsconfig.json | 13 + erp-frontend-vue/tsconfig.node.json | 26 + erp-frontend-vue/vite.config.ts | 55 + erp-frontend-vue/vitest.config.ts | 10 + 117 files changed, 32547 insertions(+) create mode 100644 erp-frontend-vue/.gitignore create mode 100644 erp-frontend-vue/.vscode/extensions.json create mode 100644 erp-frontend-vue/README.md create mode 100644 erp-frontend-vue/index.html create mode 100644 erp-frontend-vue/package-lock.json create mode 100644 erp-frontend-vue/package.json create mode 100644 erp-frontend-vue/public/vite.svg create mode 100644 erp-frontend-vue/src/App.vue create mode 100644 erp-frontend-vue/src/api/__tests__/customer.test.ts create mode 100644 erp-frontend-vue/src/api/auth.ts create mode 100644 erp-frontend-vue/src/api/checkin.ts create mode 100644 erp-frontend-vue/src/api/contract.ts create mode 100644 erp-frontend-vue/src/api/customer.ts create mode 100644 erp-frontend-vue/src/api/deliver.ts create mode 100644 erp-frontend-vue/src/api/invoice.ts create mode 100644 erp-frontend-vue/src/api/masterdata/bom.ts create mode 100644 erp-frontend-vue/src/api/masterdata/item.ts create mode 100644 erp-frontend-vue/src/api/masterdata/itemtype.ts create mode 100644 erp-frontend-vue/src/api/masterdata/unit.ts create mode 100644 erp-frontend-vue/src/api/masterdata/workshop.ts create mode 100644 erp-frontend-vue/src/api/masterdata/workstation.ts create mode 100644 erp-frontend-vue/src/api/material.ts create mode 100644 erp-frontend-vue/src/api/mbom.ts create mode 100644 erp-frontend-vue/src/api/parts.ts create mode 100644 erp-frontend-vue/src/api/productionPlan.ts create mode 100644 erp-frontend-vue/src/api/purchaseInvoice.ts create mode 100644 erp-frontend-vue/src/api/purchaseNeed.ts create mode 100644 erp-frontend-vue/src/api/purchaseOrder.ts create mode 100644 erp-frontend-vue/src/api/purchasePlan.ts create mode 100644 erp-frontend-vue/src/api/rd/ebom.ts create mode 100644 erp-frontend-vue/src/api/reject.ts create mode 100644 erp-frontend-vue/src/api/request.ts create mode 100644 erp-frontend-vue/src/api/saleback.ts create mode 100644 erp-frontend-vue/src/api/salesOrder.ts create mode 100644 erp-frontend-vue/src/api/supplier.ts create mode 100644 erp-frontend-vue/src/api/system/dept.ts create mode 100644 erp-frontend-vue/src/api/system/post.ts create mode 100644 erp-frontend-vue/src/api/system/role.ts create mode 100644 erp-frontend-vue/src/api/system/user.ts create mode 100644 erp-frontend-vue/src/api/warehouse/issue.ts create mode 100644 erp-frontend-vue/src/api/warehouse/issueDetail.ts create mode 100644 erp-frontend-vue/src/api/warehouse/issueLine.ts create mode 100644 erp-frontend-vue/src/api/workOrder.ts create mode 100644 erp-frontend-vue/src/assets/logo-color.png create mode 100644 erp-frontend-vue/src/assets/logo-white.png create mode 100644 erp-frontend-vue/src/assets/vue.svg create mode 100644 erp-frontend-vue/src/components/HelloWorld.vue create mode 100644 erp-frontend-vue/src/components/print/PrintDialog.vue create mode 100644 erp-frontend-vue/src/components/print/QrCode.vue create mode 100644 erp-frontend-vue/src/components/print/types.ts create mode 100644 erp-frontend-vue/src/layout/index.vue create mode 100644 erp-frontend-vue/src/main.ts create mode 100644 erp-frontend-vue/src/permission.ts create mode 100644 erp-frontend-vue/src/router/index.ts create mode 100644 erp-frontend-vue/src/stores/user.ts create mode 100644 erp-frontend-vue/src/style.css create mode 100644 erp-frontend-vue/src/utils/auth.ts create mode 100644 erp-frontend-vue/src/views/Dashboard.vue create mode 100644 erp-frontend-vue/src/views/Login.vue create mode 100644 erp-frontend-vue/src/views/MasterData/Bom/form.vue create mode 100644 erp-frontend-vue/src/views/MasterData/Bom/list.vue create mode 100644 erp-frontend-vue/src/views/MasterData/Item/index.vue create mode 100644 erp-frontend-vue/src/views/MasterData/ItemType/index.vue create mode 100644 erp-frontend-vue/src/views/MasterData/Unit/index.vue create mode 100644 erp-frontend-vue/src/views/MasterData/Workshop/index.vue create mode 100644 erp-frontend-vue/src/views/MasterData/Workstation/index.vue create mode 100644 erp-frontend-vue/src/views/Production/Mbom/form.vue create mode 100644 erp-frontend-vue/src/views/Production/Mbom/index.vue create mode 100644 erp-frontend-vue/src/views/Production/Parts/index.vue create mode 100644 erp-frontend-vue/src/views/Production/PlanOrder/form.vue create mode 100644 erp-frontend-vue/src/views/Production/PlanOrder/index.vue create mode 100644 erp-frontend-vue/src/views/Production/PurchasePlan/form.vue create mode 100644 erp-frontend-vue/src/views/Production/PurchasePlan/index.vue create mode 100644 erp-frontend-vue/src/views/Production/Reports/DetailReport.vue create mode 100644 erp-frontend-vue/src/views/Production/Reports/NeedReport.vue create mode 100644 erp-frontend-vue/src/views/Production/Reports/TotalReport.vue create mode 100644 erp-frontend-vue/src/views/Production/WorkOrder/form.vue create mode 100644 erp-frontend-vue/src/views/Production/WorkOrder/index.vue create mode 100644 erp-frontend-vue/src/views/Purchasing/Checkin/index.vue create mode 100644 erp-frontend-vue/src/views/Purchasing/Invoice/index.vue create mode 100644 erp-frontend-vue/src/views/Purchasing/Order/form.vue create mode 100644 erp-frontend-vue/src/views/Purchasing/Order/index.vue create mode 100644 erp-frontend-vue/src/views/Purchasing/Reject/index.vue create mode 100644 erp-frontend-vue/src/views/Purchasing/Reports/CheckinReport.vue create mode 100644 erp-frontend-vue/src/views/Purchasing/Reports/DetailReport.vue create mode 100644 erp-frontend-vue/src/views/Purchasing/Reports/InvoiceReport.vue create mode 100644 erp-frontend-vue/src/views/Purchasing/Reports/TotalReport.vue create mode 100644 erp-frontend-vue/src/views/Purchasing/Supplier/index.vue create mode 100644 erp-frontend-vue/src/views/RD/Ebom/form.vue create mode 100644 erp-frontend-vue/src/views/RD/Ebom/index.vue create mode 100644 erp-frontend-vue/src/views/Sales/Contract/form.vue create mode 100644 erp-frontend-vue/src/views/Sales/Contract/index.vue create mode 100644 erp-frontend-vue/src/views/Sales/Customer/__tests__/index.spec.ts create mode 100644 erp-frontend-vue/src/views/Sales/Customer/index.vue create mode 100644 erp-frontend-vue/src/views/Sales/Deliver/form.vue create mode 100644 erp-frontend-vue/src/views/Sales/Deliver/index.vue create mode 100644 erp-frontend-vue/src/views/Sales/Invoice/form.vue create mode 100644 erp-frontend-vue/src/views/Sales/Invoice/index.vue create mode 100644 erp-frontend-vue/src/views/Sales/Order/form.vue create mode 100644 erp-frontend-vue/src/views/Sales/Order/index.vue create mode 100644 erp-frontend-vue/src/views/Sales/Order/view.vue create mode 100644 erp-frontend-vue/src/views/Sales/Reports/DetailReport.vue create mode 100644 erp-frontend-vue/src/views/Sales/Reports/PlanReport.vue create mode 100644 erp-frontend-vue/src/views/Sales/Reports/SevenDayReport.vue create mode 100644 erp-frontend-vue/src/views/Sales/Saleback/form.vue create mode 100644 erp-frontend-vue/src/views/Sales/Saleback/index.vue create mode 100644 erp-frontend-vue/src/views/System/Dept/index.vue create mode 100644 erp-frontend-vue/src/views/System/Post/index.vue create mode 100644 erp-frontend-vue/src/views/System/Role/index.vue create mode 100644 erp-frontend-vue/src/views/System/User/index.vue create mode 100644 erp-frontend-vue/src/views/Warehouse/Issue/form.vue create mode 100644 erp-frontend-vue/src/views/Warehouse/Issue/index.vue create mode 100644 erp-frontend-vue/tsconfig.app.json create mode 100644 erp-frontend-vue/tsconfig.json create mode 100644 erp-frontend-vue/tsconfig.node.json create mode 100644 erp-frontend-vue/vite.config.ts create mode 100644 erp-frontend-vue/vitest.config.ts diff --git a/erp-frontend-vue/.gitignore b/erp-frontend-vue/.gitignore new file mode 100644 index 0000000..a547bf3 --- /dev/null +++ b/erp-frontend-vue/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/erp-frontend-vue/.vscode/extensions.json b/erp-frontend-vue/.vscode/extensions.json new file mode 100644 index 0000000..a7cea0b --- /dev/null +++ b/erp-frontend-vue/.vscode/extensions.json @@ -0,0 +1,3 @@ +{ + "recommendations": ["Vue.volar"] +} diff --git a/erp-frontend-vue/README.md b/erp-frontend-vue/README.md new file mode 100644 index 0000000..33895ab --- /dev/null +++ b/erp-frontend-vue/README.md @@ -0,0 +1,5 @@ +# Vue 3 + TypeScript + Vite + +This template should help get you started developing with Vue 3 and TypeScript in Vite. The template uses Vue 3 ` + + diff --git a/erp-frontend-vue/package-lock.json b/erp-frontend-vue/package-lock.json new file mode 100644 index 0000000..f40ccbc --- /dev/null +++ b/erp-frontend-vue/package-lock.json @@ -0,0 +1,2764 @@ +{ + "name": "erp-frontend-vue", + "version": "0.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@acemir/cssom": { + "version": "0.9.31", + "resolved": "https://registry.npmjs.org/@acemir/cssom/-/cssom-0.9.31.tgz", + "integrity": "sha512-ZnR3GSaH+/vJ0YlHau21FjfLYjMpYVIzTD8M8vIEQvIGxeOXyXdzCI140rrCY862p/C/BbzWsjc1dgnM9mkoTA==", + "dev": true + }, + "@asamuzakjp/css-color": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-4.1.1.tgz", + "integrity": "sha512-B0Hv6G3gWGMn0xKJ0txEi/jM5iFpT3MfDxmhZFb4W047GvytCf1DHQ1D69W3zHI4yWe2aTZAA0JnbMZ7Xc8DuQ==", + "dev": true, + "requires": { + "@csstools/css-calc": "^2.1.4", + "@csstools/css-color-parser": "^3.1.0", + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4", + "lru-cache": "^11.2.4" + } + }, + "@asamuzakjp/dom-selector": { + "version": "6.7.6", + "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-6.7.6.tgz", + "integrity": "sha512-hBaJER6A9MpdG3WgdlOolHmbOYvSk46y7IQN/1+iqiCuUu6iWdQrs9DGKF8ocqsEqWujWf/V7b7vaDgiUmIvUg==", + "dev": true, + "requires": { + "@asamuzakjp/nwsapi": "^2.3.9", + "bidi-js": "^1.0.3", + "css-tree": "^3.1.0", + "is-potential-custom-element-name": "^1.0.1", + "lru-cache": "^11.2.4" + } + }, + "@asamuzakjp/nwsapi": { + "version": "2.3.9", + "resolved": "https://registry.npmjs.org/@asamuzakjp/nwsapi/-/nwsapi-2.3.9.tgz", + "integrity": "sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==", + "dev": true + }, + "@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==" + }, + "@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==" + }, + "@babel/parser": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.6.tgz", + "integrity": "sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ==", + "requires": { + "@babel/types": "^7.28.6" + } + }, + "@babel/types": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.6.tgz", + "integrity": "sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg==", + "requires": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + } + }, + "@bufbuild/protobuf": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-2.11.0.tgz", + "integrity": "sha512-sBXGT13cpmPR5BMgHE6UEEfEaShh5Ror6rfN3yEK5si7QVrtZg8LEPQb0VVhiLRUslD2yLnXtnRzG035J/mZXQ==", + "dev": true + }, + "@csstools/color-helpers": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.1.0.tgz", + "integrity": "sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==", + "dev": true + }, + "@csstools/css-calc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-2.1.4.tgz", + "integrity": "sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==", + "dev": true + }, + "@csstools/css-color-parser": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.1.0.tgz", + "integrity": "sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==", + "dev": true, + "requires": { + "@csstools/color-helpers": "^5.1.0", + "@csstools/css-calc": "^2.1.4" + } + }, + "@csstools/css-parser-algorithms": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.5.tgz", + "integrity": "sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==", + "dev": true + }, + "@csstools/css-syntax-patches-for-csstree": { + "version": "1.0.26", + "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.0.26.tgz", + "integrity": "sha512-6boXK0KkzT5u5xOgF6TKB+CLq9SOpEGmkZw0g5n9/7yg85wab3UzSxB8TxhLJ31L4SGJ6BCFRw/iftTha1CJXA==", + "dev": true + }, + "@csstools/css-tokenizer": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz", + "integrity": "sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==", + "dev": true + }, + "@ctrl/tinycolor": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/@ctrl/tinycolor/-/tinycolor-3.6.1.tgz", + "integrity": "sha512-SITSV6aIXsuVNV3f3O0f2n/cgyEDWoSqtZMYiAmcsYHydcKrOz3gUxB/iXd/Qf08+IZX4KpgNbvUdMBmWz+kcA==" + }, + "@element-plus/icons-vue": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/@element-plus/icons-vue/-/icons-vue-2.3.2.tgz", + "integrity": "sha512-OzIuTaIfC8QXEPmJvB4Y4kw34rSXdCJzxcD1kFStBvr8bK6X1zQAYDo0CNMjojnfTqRQCJ0I7prlErcoRiET2A==" + }, + "@esbuild/aix-ppc64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz", + "integrity": "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==", + "dev": true, + "optional": true + }, + "@esbuild/android-arm": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.2.tgz", + "integrity": "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==", + "dev": true, + "optional": true + }, + "@esbuild/android-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.2.tgz", + "integrity": "sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==", + "dev": true, + "optional": true + }, + "@esbuild/android-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.2.tgz", + "integrity": "sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==", + "dev": true, + "optional": true + }, + "@esbuild/darwin-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.2.tgz", + "integrity": "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==", + "dev": true, + "optional": true + }, + "@esbuild/darwin-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.2.tgz", + "integrity": "sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==", + "dev": true, + "optional": true + }, + "@esbuild/freebsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.2.tgz", + "integrity": "sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==", + "dev": true, + "optional": true + }, + "@esbuild/freebsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.2.tgz", + "integrity": "sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==", + "dev": true, + "optional": true + }, + "@esbuild/linux-arm": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.2.tgz", + "integrity": "sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==", + "dev": true, + "optional": true + }, + "@esbuild/linux-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.2.tgz", + "integrity": "sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==", + "dev": true, + "optional": true + }, + "@esbuild/linux-ia32": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.2.tgz", + "integrity": "sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==", + "dev": true, + "optional": true + }, + "@esbuild/linux-loong64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.2.tgz", + "integrity": "sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==", + "dev": true, + "optional": true + }, + "@esbuild/linux-mips64el": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.2.tgz", + "integrity": "sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==", + "dev": true, + "optional": true + }, + "@esbuild/linux-ppc64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.2.tgz", + "integrity": "sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==", + "dev": true, + "optional": true + }, + "@esbuild/linux-riscv64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.2.tgz", + "integrity": "sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==", + "dev": true, + "optional": true + }, + "@esbuild/linux-s390x": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.2.tgz", + "integrity": "sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==", + "dev": true, + "optional": true + }, + "@esbuild/linux-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.2.tgz", + "integrity": "sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==", + "dev": true, + "optional": true + }, + "@esbuild/netbsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.2.tgz", + "integrity": "sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==", + "dev": true, + "optional": true + }, + "@esbuild/netbsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.2.tgz", + "integrity": "sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==", + "dev": true, + "optional": true + }, + "@esbuild/openbsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.2.tgz", + "integrity": "sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==", + "dev": true, + "optional": true + }, + "@esbuild/openbsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.2.tgz", + "integrity": "sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==", + "dev": true, + "optional": true + }, + "@esbuild/openharmony-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.2.tgz", + "integrity": "sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==", + "dev": true, + "optional": true + }, + "@esbuild/sunos-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.2.tgz", + "integrity": "sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==", + "dev": true, + "optional": true + }, + "@esbuild/win32-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.2.tgz", + "integrity": "sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==", + "dev": true, + "optional": true + }, + "@esbuild/win32-ia32": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.2.tgz", + "integrity": "sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==", + "dev": true, + "optional": true + }, + "@esbuild/win32-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.2.tgz", + "integrity": "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==", + "dev": true, + "optional": true + }, + "@exodus/bytes": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@exodus/bytes/-/bytes-1.9.0.tgz", + "integrity": "sha512-lagqsvnk09NKogQaN/XrtlWeUF8SRhT12odMvbTIIaVObqzwAogL6jhR4DAp0gPuKoM1AOVrKUshJpRdpMFrww==", + "dev": true + }, + "@floating-ui/core": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.3.tgz", + "integrity": "sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==", + "requires": { + "@floating-ui/utils": "^0.2.10" + } + }, + "@floating-ui/dom": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.4.tgz", + "integrity": "sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA==", + "requires": { + "@floating-ui/core": "^1.7.3", + "@floating-ui/utils": "^0.2.10" + } + }, + "@floating-ui/utils": { + "version": "0.2.10", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.10.tgz", + "integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==" + }, + "@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "requires": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "string-width-cjs": { + "version": "npm:string-width@4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "dependencies": { + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + } + } + }, + "strip-ansi-cjs": { + "version": "npm:strip-ansi@6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + }, + "wrap-ansi-cjs": { + "version": "npm:wrap-ansi@7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "dependencies": { + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + } + } + } + } + }, + "@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==" + }, + "@one-ini/wasm": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@one-ini/wasm/-/wasm-0.1.1.tgz", + "integrity": "sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw==", + "dev": true + }, + "@parcel/watcher": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.6.tgz", + "integrity": "sha512-tmmZ3lQxAe/k/+rNnXQRawJ4NjxO2hqiOLTHvWchtGZULp4RyFeh6aU4XdOYBFe2KE1oShQTv4AblOs2iOrNnQ==", + "dev": true, + "optional": true, + "requires": { + "@parcel/watcher-android-arm64": "2.5.6", + "@parcel/watcher-darwin-arm64": "2.5.6", + "@parcel/watcher-darwin-x64": "2.5.6", + "@parcel/watcher-freebsd-x64": "2.5.6", + "@parcel/watcher-linux-arm-glibc": "2.5.6", + "@parcel/watcher-linux-arm-musl": "2.5.6", + "@parcel/watcher-linux-arm64-glibc": "2.5.6", + "@parcel/watcher-linux-arm64-musl": "2.5.6", + "@parcel/watcher-linux-x64-glibc": "2.5.6", + "@parcel/watcher-linux-x64-musl": "2.5.6", + "@parcel/watcher-win32-arm64": "2.5.6", + "@parcel/watcher-win32-ia32": "2.5.6", + "@parcel/watcher-win32-x64": "2.5.6", + "detect-libc": "^2.0.3", + "is-glob": "^4.0.3", + "node-addon-api": "^7.0.0", + "picomatch": "^4.0.3" + } + }, + "@parcel/watcher-android-arm64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.6.tgz", + "integrity": "sha512-YQxSS34tPF/6ZG7r/Ih9xy+kP/WwediEUsqmtf0cuCV5TPPKw/PQHRhueUo6JdeFJaqV3pyjm0GdYjZotbRt/A==", + "dev": true, + "optional": true + }, + "@parcel/watcher-darwin-arm64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.6.tgz", + "integrity": "sha512-Z2ZdrnwyXvvvdtRHLmM4knydIdU9adO3D4n/0cVipF3rRiwP+3/sfzpAwA/qKFL6i1ModaabkU7IbpeMBgiVEA==", + "dev": true, + "optional": true + }, + "@parcel/watcher-darwin-x64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.6.tgz", + "integrity": "sha512-HgvOf3W9dhithcwOWX9uDZyn1lW9R+7tPZ4sug+NGrGIo4Rk1hAXLEbcH1TQSqxts0NYXXlOWqVpvS1SFS4fRg==", + "dev": true, + "optional": true + }, + "@parcel/watcher-freebsd-x64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.6.tgz", + "integrity": "sha512-vJVi8yd/qzJxEKHkeemh7w3YAn6RJCtYlE4HPMoVnCpIXEzSrxErBW5SJBgKLbXU3WdIpkjBTeUNtyBVn8TRng==", + "dev": true, + "optional": true + }, + "@parcel/watcher-linux-arm-glibc": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.6.tgz", + "integrity": "sha512-9JiYfB6h6BgV50CCfasfLf/uvOcJskMSwcdH1PHH9rvS1IrNy8zad6IUVPVUfmXr+u+Km9IxcfMLzgdOudz9EQ==", + "dev": true, + "optional": true + }, + "@parcel/watcher-linux-arm-musl": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.6.tgz", + "integrity": "sha512-Ve3gUCG57nuUUSyjBq/MAM0CzArtuIOxsBdQ+ftz6ho8n7s1i9E1Nmk/xmP323r2YL0SONs1EuwqBp2u1k5fxg==", + "dev": true, + "optional": true + }, + "@parcel/watcher-linux-arm64-glibc": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.6.tgz", + "integrity": "sha512-f2g/DT3NhGPdBmMWYoxixqYr3v/UXcmLOYy16Bx0TM20Tchduwr4EaCbmxh1321TABqPGDpS8D/ggOTaljijOA==", + "dev": true, + "optional": true + }, + "@parcel/watcher-linux-arm64-musl": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.6.tgz", + "integrity": "sha512-qb6naMDGlbCwdhLj6hgoVKJl2odL34z2sqkC7Z6kzir8b5W65WYDpLB6R06KabvZdgoHI/zxke4b3zR0wAbDTA==", + "dev": true, + "optional": true + }, + "@parcel/watcher-linux-x64-glibc": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.6.tgz", + "integrity": "sha512-kbT5wvNQlx7NaGjzPFu8nVIW1rWqV780O7ZtkjuWaPUgpv2NMFpjYERVi0UYj1msZNyCzGlaCWEtzc+exjMGbQ==", + "dev": true, + "optional": true + }, + "@parcel/watcher-linux-x64-musl": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.6.tgz", + "integrity": "sha512-1JRFeC+h7RdXwldHzTsmdtYR/Ku8SylLgTU/reMuqdVD7CtLwf0VR1FqeprZ0eHQkO0vqsbvFLXUmYm/uNKJBg==", + "dev": true, + "optional": true + }, + "@parcel/watcher-win32-arm64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.6.tgz", + "integrity": "sha512-3ukyebjc6eGlw9yRt678DxVF7rjXatWiHvTXqphZLvo7aC5NdEgFufVwjFfY51ijYEWpXbqF5jtrK275z52D4Q==", + "dev": true, + "optional": true + }, + "@parcel/watcher-win32-ia32": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.6.tgz", + "integrity": "sha512-k35yLp1ZMwwee3Ez/pxBi5cf4AoBKYXj00CZ80jUz5h8prpiaQsiRPKQMxoLstNuqe2vR4RNPEAEcjEFzhEz/g==", + "dev": true, + "optional": true + }, + "@parcel/watcher-win32-x64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.6.tgz", + "integrity": "sha512-hbQlYcCq5dlAX9Qx+kFb0FHue6vbjlf0FrNzSKdYK2APUf7tGfGxQCk2ihEREmbR6ZMc0MVAD5RIX/41gpUzTw==", + "dev": true, + "optional": true + }, + "@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "optional": true + }, + "@rolldown/pluginutils": { + "version": "1.0.0-beta.53", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.53.tgz", + "integrity": "sha512-vENRlFU4YbrwVqNDZ7fLvy+JR1CRkyr01jhSiDpE1u6py3OMzQfztQU2jxykW3ALNxO4kSlqIDeYyD0Y9RcQeQ==", + "dev": true + }, + "@rollup/rollup-android-arm-eabi": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.56.0.tgz", + "integrity": "sha512-LNKIPA5k8PF1+jAFomGe3qN3bbIgJe/IlpDBwuVjrDKrJhVWywgnJvflMt/zkbVNLFtF1+94SljYQS6e99klnw==", + "dev": true, + "optional": true + }, + "@rollup/rollup-android-arm64": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.56.0.tgz", + "integrity": "sha512-lfbVUbelYqXlYiU/HApNMJzT1E87UPGvzveGg2h0ktUNlOCxKlWuJ9jtfvs1sKHdwU4fzY7Pl8sAl49/XaEk6Q==", + "dev": true, + "optional": true + }, + "@rollup/rollup-darwin-arm64": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.56.0.tgz", + "integrity": "sha512-EgxD1ocWfhoD6xSOeEEwyE7tDvwTgZc8Bss7wCWe+uc7wO8G34HHCUH+Q6cHqJubxIAnQzAsyUsClt0yFLu06w==", + "dev": true, + "optional": true + }, + "@rollup/rollup-darwin-x64": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.56.0.tgz", + "integrity": "sha512-1vXe1vcMOssb/hOF8iv52A7feWW2xnu+c8BV4t1F//m9QVLTfNVpEdja5ia762j/UEJe2Z1jAmEqZAK42tVW3g==", + "dev": true, + "optional": true + }, + "@rollup/rollup-freebsd-arm64": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.56.0.tgz", + "integrity": "sha512-bof7fbIlvqsyv/DtaXSck4VYQ9lPtoWNFCB/JY4snlFuJREXfZnm+Ej6yaCHfQvofJDXLDMTVxWscVSuQvVWUQ==", + "dev": true, + "optional": true + }, + "@rollup/rollup-freebsd-x64": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.56.0.tgz", + "integrity": "sha512-KNa6lYHloW+7lTEkYGa37fpvPq+NKG/EHKM8+G/g9WDU7ls4sMqbVRV78J6LdNuVaeeK5WB9/9VAFbKxcbXKYg==", + "dev": true, + "optional": true + }, + "@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.56.0.tgz", + "integrity": "sha512-E8jKK87uOvLrrLN28jnAAAChNq5LeCd2mGgZF+fGF5D507WlG/Noct3lP/QzQ6MrqJ5BCKNwI9ipADB6jyiq2A==", + "dev": true, + "optional": true + }, + "@rollup/rollup-linux-arm-musleabihf": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.56.0.tgz", + "integrity": "sha512-jQosa5FMYF5Z6prEpTCCmzCXz6eKr/tCBssSmQGEeozA9tkRUty/5Vx06ibaOP9RCrW1Pvb8yp3gvZhHwTDsJw==", + "dev": true, + "optional": true + }, + "@rollup/rollup-linux-arm64-gnu": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.56.0.tgz", + "integrity": "sha512-uQVoKkrC1KGEV6udrdVahASIsaF8h7iLG0U0W+Xn14ucFwi6uS539PsAr24IEF9/FoDtzMeeJXJIBo5RkbNWvQ==", + "dev": true, + "optional": true + }, + "@rollup/rollup-linux-arm64-musl": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.56.0.tgz", + "integrity": "sha512-vLZ1yJKLxhQLFKTs42RwTwa6zkGln+bnXc8ueFGMYmBTLfNu58sl5/eXyxRa2RarTkJbXl8TKPgfS6V5ijNqEA==", + "dev": true, + "optional": true + }, + "@rollup/rollup-linux-loong64-gnu": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.56.0.tgz", + "integrity": "sha512-FWfHOCub564kSE3xJQLLIC/hbKqHSVxy8vY75/YHHzWvbJL7aYJkdgwD/xGfUlL5UV2SB7otapLrcCj2xnF1dg==", + "dev": true, + "optional": true + }, + "@rollup/rollup-linux-loong64-musl": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.56.0.tgz", + "integrity": "sha512-z1EkujxIh7nbrKL1lmIpqFTc/sr0u8Uk0zK/qIEFldbt6EDKWFk/pxFq3gYj4Bjn3aa9eEhYRlL3H8ZbPT1xvA==", + "dev": true, + "optional": true + }, + "@rollup/rollup-linux-ppc64-gnu": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.56.0.tgz", + "integrity": "sha512-iNFTluqgdoQC7AIE8Q34R3AuPrJGJirj5wMUErxj22deOcY7XwZRaqYmB6ZKFHoVGqRcRd0mqO+845jAibKCkw==", + "dev": true, + "optional": true + }, + "@rollup/rollup-linux-ppc64-musl": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.56.0.tgz", + "integrity": "sha512-MtMeFVlD2LIKjp2sE2xM2slq3Zxf9zwVuw0jemsxvh1QOpHSsSzfNOTH9uYW9i1MXFxUSMmLpeVeUzoNOKBaWg==", + "dev": true, + "optional": true + }, + "@rollup/rollup-linux-riscv64-gnu": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.56.0.tgz", + "integrity": "sha512-in+v6wiHdzzVhYKXIk5U74dEZHdKN9KH0Q4ANHOTvyXPG41bajYRsy7a8TPKbYPl34hU7PP7hMVHRvv/5aCSew==", + "dev": true, + "optional": true + }, + "@rollup/rollup-linux-riscv64-musl": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.56.0.tgz", + "integrity": "sha512-yni2raKHB8m9NQpI9fPVwN754mn6dHQSbDTwxdr9SE0ks38DTjLMMBjrwvB5+mXrX+C0npX0CVeCUcvvvD8CNQ==", + "dev": true, + "optional": true + }, + "@rollup/rollup-linux-s390x-gnu": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.56.0.tgz", + "integrity": "sha512-zhLLJx9nQPu7wezbxt2ut+CI4YlXi68ndEve16tPc/iwoylWS9B3FxpLS2PkmfYgDQtosah07Mj9E0khc3Y+vQ==", + "dev": true, + "optional": true + }, + "@rollup/rollup-linux-x64-gnu": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.56.0.tgz", + "integrity": "sha512-MVC6UDp16ZSH7x4rtuJPAEoE1RwS8N4oK9DLHy3FTEdFoUTCFVzMfJl/BVJ330C+hx8FfprA5Wqx4FhZXkj2Kw==", + "dev": true, + "optional": true + }, + "@rollup/rollup-linux-x64-musl": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.56.0.tgz", + "integrity": "sha512-ZhGH1eA4Qv0lxaV00azCIS1ChedK0V32952Md3FtnxSqZTBTd6tgil4nZT5cU8B+SIw3PFYkvyR4FKo2oyZIHA==", + "dev": true, + "optional": true + }, + "@rollup/rollup-openbsd-x64": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.56.0.tgz", + "integrity": "sha512-O16XcmyDeFI9879pEcmtWvD/2nyxR9mF7Gs44lf1vGGx8Vg2DRNx11aVXBEqOQhWb92WN4z7fW/q4+2NYzCbBA==", + "dev": true, + "optional": true + }, + "@rollup/rollup-openharmony-arm64": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.56.0.tgz", + "integrity": "sha512-LhN/Reh+7F3RCgQIRbgw8ZMwUwyqJM+8pXNT6IIJAqm2IdKkzpCh/V9EdgOMBKuebIrzswqy4ATlrDgiOwbRcQ==", + "dev": true, + "optional": true + }, + "@rollup/rollup-win32-arm64-msvc": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.56.0.tgz", + "integrity": "sha512-kbFsOObXp3LBULg1d3JIUQMa9Kv4UitDmpS+k0tinPBz3watcUiV2/LUDMMucA6pZO3WGE27P7DsfaN54l9ing==", + "dev": true, + "optional": true + }, + "@rollup/rollup-win32-ia32-msvc": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.56.0.tgz", + "integrity": "sha512-vSSgny54D6P4vf2izbtFm/TcWYedw7f8eBrOiGGecyHyQB9q4Kqentjaj8hToe+995nob/Wv48pDqL5a62EWtg==", + "dev": true, + "optional": true + }, + "@rollup/rollup-win32-x64-gnu": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.56.0.tgz", + "integrity": "sha512-FeCnkPCTHQJFbiGG49KjV5YGW/8b9rrXAM2Mz2kiIoktq2qsJxRD5giEMEOD2lPdgs72upzefaUvS+nc8E3UzQ==", + "dev": true, + "optional": true + }, + "@rollup/rollup-win32-x64-msvc": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.56.0.tgz", + "integrity": "sha512-H8AE9Ur/t0+1VXujj90w0HrSOuv0Nq9r1vSZF2t5km20NTfosQsGGUXDaKdQZzwuLts7IyL1fYT4hM95TI9c4g==", + "dev": true, + "optional": true + }, + "@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "dev": true + }, + "@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "dev": true, + "requires": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, + "@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true + }, + "@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true + }, + "@types/lodash": { + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.23.tgz", + "integrity": "sha512-RDvF6wTulMPjrNdCoYRC8gNR880JNGT8uB+REUpC2Ns4pRqQJhGz90wh7rgdXDPpCczF3VGktDuFGVnz8zP7HA==" + }, + "@types/lodash-es": { + "version": "4.17.12", + "resolved": "https://registry.npmjs.org/@types/lodash-es/-/lodash-es-4.17.12.tgz", + "integrity": "sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==", + "requires": { + "@types/lodash": "*" + } + }, + "@types/node": { + "version": "24.10.9", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.9.tgz", + "integrity": "sha512-ne4A0IpG3+2ETuREInjPNhUGis1SFjv1d5asp8MzEAGtOZeTeHVDOYqOgqfhvseqg/iXty2hjBf1zAOb7RNiNw==", + "requires": { + "undici-types": "~7.16.0" + } + }, + "@types/qrcode": { + "version": "1.5.6", + "resolved": "https://registry.npmjs.org/@types/qrcode/-/qrcode-1.5.6.tgz", + "integrity": "sha512-te7NQcV2BOvdj2b1hCAHzAoMNuj65kNBMz0KBaxM6c3VGBOhU0dURQKOtH8CFNI/dsKkwlv32p26qYQTWoB5bw==", + "requires": { + "@types/node": "*" + } + }, + "@types/web-bluetooth": { + "version": "0.0.20", + "resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.20.tgz", + "integrity": "sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==" + }, + "@vitejs/plugin-vue": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-6.0.3.tgz", + "integrity": "sha512-TlGPkLFLVOY3T7fZrwdvKpjprR3s4fxRln0ORDo1VQ7HHyxJwTlrjKU3kpVWTlaAjIEuCTokmjkZnr8Tpc925w==", + "dev": true, + "requires": { + "@rolldown/pluginutils": "1.0.0-beta.53" + } + }, + "@vitest/expect": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.0.18.tgz", + "integrity": "sha512-8sCWUyckXXYvx4opfzVY03EOiYVxyNrHS5QxX3DAIi5dpJAAkyJezHCP77VMX4HKA2LDT/Jpfo8i2r5BE3GnQQ==", + "dev": true, + "requires": { + "@standard-schema/spec": "^1.0.0", + "@types/chai": "^5.2.2", + "@vitest/spy": "4.0.18", + "@vitest/utils": "4.0.18", + "chai": "^6.2.1", + "tinyrainbow": "^3.0.3" + } + }, + "@vitest/mocker": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.0.18.tgz", + "integrity": "sha512-HhVd0MDnzzsgevnOWCBj5Otnzobjy5wLBe4EdeeFGv8luMsGcYqDuFRMcttKWZA5vVO8RFjexVovXvAM4JoJDQ==", + "dev": true, + "requires": { + "@vitest/spy": "4.0.18", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.21" + }, + "dependencies": { + "estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "requires": { + "@types/estree": "^1.0.0" + } + } + } + }, + "@vitest/pretty-format": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.0.18.tgz", + "integrity": "sha512-P24GK3GulZWC5tz87ux0m8OADrQIUVDPIjjj65vBXYG17ZeU3qD7r+MNZ1RNv4l8CGU2vtTRqixrOi9fYk/yKw==", + "dev": true, + "requires": { + "tinyrainbow": "^3.0.3" + } + }, + "@vitest/runner": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.0.18.tgz", + "integrity": "sha512-rpk9y12PGa22Jg6g5M3UVVnTS7+zycIGk9ZNGN+m6tZHKQb7jrP7/77WfZy13Y/EUDd52NDsLRQhYKtv7XfPQw==", + "dev": true, + "requires": { + "@vitest/utils": "4.0.18", + "pathe": "^2.0.3" + } + }, + "@vitest/snapshot": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.0.18.tgz", + "integrity": "sha512-PCiV0rcl7jKQjbgYqjtakly6T1uwv/5BQ9SwBLekVg/EaYeQFPiXcgrC2Y7vDMA8dM1SUEAEV82kgSQIlXNMvA==", + "dev": true, + "requires": { + "@vitest/pretty-format": "4.0.18", + "magic-string": "^0.30.21", + "pathe": "^2.0.3" + } + }, + "@vitest/spy": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.0.18.tgz", + "integrity": "sha512-cbQt3PTSD7P2OARdVW3qWER5EGq7PHlvE+QfzSC0lbwO+xnt7+XH06ZzFjFRgzUX//JmpxrCu92VdwvEPlWSNw==", + "dev": true + }, + "@vitest/utils": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.0.18.tgz", + "integrity": "sha512-msMRKLMVLWygpK3u2Hybgi4MNjcYJvwTb0Ru09+fOyCXIgT5raYP041DRRdiJiI3k/2U6SEbAETB3YtBrUkCFA==", + "dev": true, + "requires": { + "@vitest/pretty-format": "4.0.18", + "tinyrainbow": "^3.0.3" + } + }, + "@volar/language-core": { + "version": "2.4.27", + "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.4.27.tgz", + "integrity": "sha512-DjmjBWZ4tJKxfNC1F6HyYERNHPYS7L7OPFyCrestykNdUZMFYzI9WTyvwPcaNaHlrEUwESHYsfEw3isInncZxQ==", + "dev": true, + "requires": { + "@volar/source-map": "2.4.27" + } + }, + "@volar/source-map": { + "version": "2.4.27", + "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-2.4.27.tgz", + "integrity": "sha512-ynlcBReMgOZj2i6po+qVswtDUeeBRCTgDurjMGShbm8WYZgJ0PA4RmtebBJ0BCYol1qPv3GQF6jK7C9qoVc7lg==", + "dev": true + }, + "@volar/typescript": { + "version": "2.4.27", + "resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-2.4.27.tgz", + "integrity": "sha512-eWaYCcl/uAPInSK2Lze6IqVWaBu/itVqR5InXcHXFyles4zO++Mglt3oxdgj75BDcv1Knr9Y93nowS8U3wqhxg==", + "dev": true, + "requires": { + "@volar/language-core": "2.4.27", + "path-browserify": "^1.0.1", + "vscode-uri": "^3.0.8" + } + }, + "@vue/compiler-core": { + "version": "3.5.27", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.27.tgz", + "integrity": "sha512-gnSBQjZA+//qDZen+6a2EdHqJ68Z7uybrMf3SPjEGgG4dicklwDVmMC1AeIHxtLVPT7sn6sH1KOO+tS6gwOUeQ==", + "requires": { + "@babel/parser": "^7.28.5", + "@vue/shared": "3.5.27", + "entities": "^7.0.0", + "estree-walker": "^2.0.2", + "source-map-js": "^1.2.1" + } + }, + "@vue/compiler-dom": { + "version": "3.5.27", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.27.tgz", + "integrity": "sha512-oAFea8dZgCtVVVTEC7fv3T5CbZW9BxpFzGGxC79xakTr6ooeEqmRuvQydIiDAkglZEAd09LgVf1RoDnL54fu5w==", + "requires": { + "@vue/compiler-core": "3.5.27", + "@vue/shared": "3.5.27" + } + }, + "@vue/compiler-sfc": { + "version": "3.5.27", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.27.tgz", + "integrity": "sha512-sHZu9QyDPeDmN/MRoshhggVOWE5WlGFStKFwu8G52swATgSny27hJRWteKDSUUzUH+wp+bmeNbhJnEAel/auUQ==", + "requires": { + "@babel/parser": "^7.28.5", + "@vue/compiler-core": "3.5.27", + "@vue/compiler-dom": "3.5.27", + "@vue/compiler-ssr": "3.5.27", + "@vue/shared": "3.5.27", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.21", + "postcss": "^8.5.6", + "source-map-js": "^1.2.1" + } + }, + "@vue/compiler-ssr": { + "version": "3.5.27", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.27.tgz", + "integrity": "sha512-Sj7h+JHt512fV1cTxKlYhg7qxBvack+BGncSpH+8vnN+KN95iPIcqB5rsbblX40XorP+ilO7VIKlkuu3Xq2vjw==", + "requires": { + "@vue/compiler-dom": "3.5.27", + "@vue/shared": "3.5.27" + } + }, + "@vue/devtools-api": { + "version": "6.6.4", + "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.6.4.tgz", + "integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==" + }, + "@vue/language-core": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-3.2.3.tgz", + "integrity": "sha512-VpN/GnYDzGLh44AI6i1OB/WsLXo6vwnl0EWHBelGc4TyC0yEq6azwNaed/+Tgr8anFlSdWYnMEkyHJDPe7ii7A==", + "dev": true, + "requires": { + "@volar/language-core": "2.4.27", + "@vue/compiler-dom": "^3.5.0", + "@vue/shared": "^3.5.0", + "alien-signals": "^3.0.0", + "muggle-string": "^0.4.1", + "path-browserify": "^1.0.1", + "picomatch": "^4.0.2" + } + }, + "@vue/reactivity": { + "version": "3.5.27", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.27.tgz", + "integrity": "sha512-vvorxn2KXfJ0nBEnj4GYshSgsyMNFnIQah/wczXlsNXt+ijhugmW+PpJ2cNPe4V6jpnBcs0MhCODKllWG+nvoQ==", + "requires": { + "@vue/shared": "3.5.27" + } + }, + "@vue/runtime-core": { + "version": "3.5.27", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.27.tgz", + "integrity": "sha512-fxVuX/fzgzeMPn/CLQecWeDIFNt3gQVhxM0rW02Tvp/YmZfXQgcTXlakq7IMutuZ/+Ogbn+K0oct9J3JZfyk3A==", + "requires": { + "@vue/reactivity": "3.5.27", + "@vue/shared": "3.5.27" + } + }, + "@vue/runtime-dom": { + "version": "3.5.27", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.27.tgz", + "integrity": "sha512-/QnLslQgYqSJ5aUmb5F0z0caZPGHRB8LEAQ1s81vHFM5CBfnun63rxhvE/scVb/j3TbBuoZwkJyiLCkBluMpeg==", + "requires": { + "@vue/reactivity": "3.5.27", + "@vue/runtime-core": "3.5.27", + "@vue/shared": "3.5.27", + "csstype": "^3.2.3" + } + }, + "@vue/server-renderer": { + "version": "3.5.27", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.27.tgz", + "integrity": "sha512-qOz/5thjeP1vAFc4+BY3Nr6wxyLhpeQgAE/8dDtKo6a6xdk+L4W46HDZgNmLOBUDEkFXV3G7pRiUqxjX0/2zWA==", + "requires": { + "@vue/compiler-ssr": "3.5.27", + "@vue/shared": "3.5.27" + } + }, + "@vue/shared": { + "version": "3.5.27", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.27.tgz", + "integrity": "sha512-dXr/3CgqXsJkZ0n9F3I4elY8wM9jMJpP3pvRG52r6m0tu/MsAFIe6JpXVGeNMd/D9F4hQynWT8Rfuj0bdm9kFQ==" + }, + "@vue/test-utils": { + "version": "2.4.6", + "resolved": "https://registry.npmjs.org/@vue/test-utils/-/test-utils-2.4.6.tgz", + "integrity": "sha512-FMxEjOpYNYiFe0GkaHsnJPXFHxQ6m4t8vI/ElPGpMWxZKpmRvQ33OIrvRXemy6yha03RxhOlQuy+gZMC3CQSow==", + "dev": true, + "requires": { + "js-beautify": "^1.14.9", + "vue-component-type-helpers": "^2.0.0" + } + }, + "@vue/tsconfig": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@vue/tsconfig/-/tsconfig-0.8.1.tgz", + "integrity": "sha512-aK7feIWPXFSUhsCP9PFqPyFOcz4ENkb8hZ2pneL6m2UjCkccvaOhC/5KCKluuBufvp2KzkbdA2W2pk20vLzu3g==", + "dev": true + }, + "@vueuse/core": { + "version": "10.11.1", + "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-10.11.1.tgz", + "integrity": "sha512-guoy26JQktXPcz+0n3GukWIy/JDNKti9v6VEMu6kV2sYBsWuGiTU8OWdg+ADfUbHg3/3DlqySDe7JmdHrktiww==", + "requires": { + "@types/web-bluetooth": "^0.0.20", + "@vueuse/metadata": "10.11.1", + "@vueuse/shared": "10.11.1", + "vue-demi": ">=0.14.8" + }, + "dependencies": { + "vue-demi": { + "version": "0.14.10", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz", + "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==" + } + } + }, + "@vueuse/metadata": { + "version": "10.11.1", + "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-10.11.1.tgz", + "integrity": "sha512-IGa5FXd003Ug1qAZmyE8wF3sJ81xGLSqTqtQ6jaVfkeZ4i5kS2mwQF61yhVqojRnenVew5PldLyRgvdl4YYuSw==" + }, + "@vueuse/shared": { + "version": "10.11.1", + "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-10.11.1.tgz", + "integrity": "sha512-LHpC8711VFZlDaYUXEBbFBCQ7GS3dVU9mjOhhMhXP6txTV4EhYQg/KGnQuvt/sPAtoUKq7VVUnL6mVtFoL42sA==", + "requires": { + "vue-demi": ">=0.14.8" + }, + "dependencies": { + "vue-demi": { + "version": "0.14.10", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz", + "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==" + } + } + }, + "abbrev": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-2.0.0.tgz", + "integrity": "sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==", + "dev": true + }, + "agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "dev": true + }, + "alien-signals": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/alien-signals/-/alien-signals-3.1.2.tgz", + "integrity": "sha512-d9dYqZTS90WLiU0I5c6DHj/HcKkF8ZyGN3G5x8wSbslulz70KOxaqCT0hQCo9KOyhVqzqGojvNdJXoTumZOtcw==", + "dev": true + }, + "ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true + }, + "ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true + }, + "assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true + }, + "async-validator": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/async-validator/-/async-validator-4.2.5.tgz", + "integrity": "sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg==" + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "axios": { + "version": "1.13.3", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.3.tgz", + "integrity": "sha512-ERT8kdX7DZjtUm7IitEyV7InTHAF42iJuMArIiDIV5YtPanJkgw4hw5Dyg9fh0mihdWNn1GKaeIWErfe56UQ1g==", + "requires": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.4", + "proxy-from-env": "^1.1.0" + } + }, + "balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "bidi-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.3.tgz", + "integrity": "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==", + "dev": true, + "requires": { + "require-from-string": "^2.0.2" + } + }, + "brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0" + } + }, + "call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "requires": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + } + }, + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==" + }, + "chai": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", + "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", + "dev": true + }, + "chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "dev": true, + "optional": true, + "requires": { + "readdirp": "^4.0.1" + } + }, + "cliui": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", + "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^6.2.0" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "^2.0.1" + } + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "requires": { + "ansi-regex": "^5.0.1" + } + }, + "wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + } + } + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "colorjs.io": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/colorjs.io/-/colorjs.io-0.5.2.tgz", + "integrity": "sha512-twmVoizEW7ylZSN32OgKdXRmo1qg+wT5/6C3xu5b9QsWzSFAhHLn2xd8ro0diCsKfCj1RdaTP/nrcW+vAoQPIw==", + "dev": true + }, + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "commander": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", + "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", + "dev": true + }, + "config-chain": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz", + "integrity": "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==", + "dev": true, + "requires": { + "ini": "^1.3.4", + "proto-list": "~1.2.1" + } + }, + "cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, + "css-tree": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.1.0.tgz", + "integrity": "sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w==", + "dev": true, + "requires": { + "mdn-data": "2.12.2", + "source-map-js": "^1.0.1" + } + }, + "cssstyle": { + "version": "5.3.7", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-5.3.7.tgz", + "integrity": "sha512-7D2EPVltRrsTkhpQmksIu+LxeWAIEk6wRDMJ1qljlv+CKHJM+cJLlfhWIzNA44eAsHXSNe3+vO6DW1yCYx8SuQ==", + "dev": true, + "requires": { + "@asamuzakjp/css-color": "^4.1.1", + "@csstools/css-syntax-patches-for-csstree": "^1.0.21", + "css-tree": "^3.1.0", + "lru-cache": "^11.2.4" + } + }, + "csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==" + }, + "data-urls": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-6.0.1.tgz", + "integrity": "sha512-euIQENZg6x8mj3fO6o9+fOW8MimUI4PpD/fZBhJfeioZVy9TUpM4UY7KjQNVZFlqwJ0UdzRDzkycB997HEq1BQ==", + "dev": true, + "requires": { + "whatwg-mimetype": "^5.0.0", + "whatwg-url": "^15.1.0" + }, + "dependencies": { + "whatwg-mimetype": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-5.0.0.tgz", + "integrity": "sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==", + "dev": true + } + } + }, + "dayjs": { + "version": "1.11.19", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.19.tgz", + "integrity": "sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw==" + }, + "debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "requires": { + "ms": "^2.1.3" + } + }, + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==" + }, + "decimal.js": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz", + "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", + "dev": true + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==" + }, + "detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "optional": true + }, + "dijkstrajs": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/dijkstrajs/-/dijkstrajs-1.0.3.tgz", + "integrity": "sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==" + }, + "dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "requires": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + } + }, + "eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true + }, + "editorconfig": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/editorconfig/-/editorconfig-1.0.4.tgz", + "integrity": "sha512-L9Qe08KWTlqYMVvMcTIvMAdl1cDUubzRNYL+WfA4bLDMHe4nemKkpmYzkznE1FwLKu0EEmy6obgQKzMJrg4x9Q==", + "dev": true, + "requires": { + "@one-ini/wasm": "0.1.1", + "commander": "^10.0.0", + "minimatch": "9.0.1", + "semver": "^7.5.3" + } + }, + "element-plus": { + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/element-plus/-/element-plus-2.13.1.tgz", + "integrity": "sha512-eG4BDBGdAsUGN6URH1PixzZb0ngdapLivIk1meghS1uEueLvQ3aljSKrCt5x6sYb6mUk8eGtzTQFgsPmLavQcA==", + "requires": { + "@ctrl/tinycolor": "^3.4.1", + "@element-plus/icons-vue": "^2.3.2", + "@floating-ui/dom": "^1.0.1", + "@popperjs/core": "npm:@sxzz/popperjs-es@^2.11.7", + "@types/lodash": "^4.17.20", + "@types/lodash-es": "^4.17.12", + "@vueuse/core": "^10.11.0", + "async-validator": "^4.2.5", + "dayjs": "^1.11.19", + "lodash": "^4.17.21", + "lodash-es": "^4.17.21", + "lodash-unified": "^1.0.3", + "memoize-one": "^6.0.0", + "normalize-wheel-es": "^1.2.0" + }, + "dependencies": { + "@popperjs/core": { + "version": "npm:@sxzz/popperjs-es@2.11.8", + "resolved": "https://registry.npmjs.org/@sxzz/popperjs-es/-/popperjs-es-2.11.8.tgz", + "integrity": "sha512-wOwESXvvED3S8xBmcPWHs2dUuzrE4XiZeFu7e1hROIJkm02a49N120pmOXxY33sBb6hArItm5W5tcg1cBtV+HQ==" + } + } + }, + "emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true + }, + "entities": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz", + "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==" + }, + "es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==" + }, + "es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==" + }, + "es-module-lexer": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "dev": true + }, + "es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "requires": { + "es-errors": "^1.3.0" + } + }, + "es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "requires": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + } + }, + "esbuild": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.2.tgz", + "integrity": "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==", + "dev": true, + "requires": { + "@esbuild/aix-ppc64": "0.27.2", + "@esbuild/android-arm": "0.27.2", + "@esbuild/android-arm64": "0.27.2", + "@esbuild/android-x64": "0.27.2", + "@esbuild/darwin-arm64": "0.27.2", + "@esbuild/darwin-x64": "0.27.2", + "@esbuild/freebsd-arm64": "0.27.2", + "@esbuild/freebsd-x64": "0.27.2", + "@esbuild/linux-arm": "0.27.2", + "@esbuild/linux-arm64": "0.27.2", + "@esbuild/linux-ia32": "0.27.2", + "@esbuild/linux-loong64": "0.27.2", + "@esbuild/linux-mips64el": "0.27.2", + "@esbuild/linux-ppc64": "0.27.2", + "@esbuild/linux-riscv64": "0.27.2", + "@esbuild/linux-s390x": "0.27.2", + "@esbuild/linux-x64": "0.27.2", + "@esbuild/netbsd-arm64": "0.27.2", + "@esbuild/netbsd-x64": "0.27.2", + "@esbuild/openbsd-arm64": "0.27.2", + "@esbuild/openbsd-x64": "0.27.2", + "@esbuild/openharmony-arm64": "0.27.2", + "@esbuild/sunos-x64": "0.27.2", + "@esbuild/win32-arm64": "0.27.2", + "@esbuild/win32-ia32": "0.27.2", + "@esbuild/win32-x64": "0.27.2" + } + }, + "estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==" + }, + "expect-type": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", + "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", + "dev": true + }, + "fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true + }, + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "follow-redirects": { + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", + "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==" + }, + "foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dev": true, + "requires": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + } + }, + "form-data": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + } + }, + "fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "optional": true + }, + "function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==" + }, + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==" + }, + "get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "requires": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + } + }, + "get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "requires": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + } + }, + "glob": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "dev": true, + "requires": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "dependencies": { + "minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "requires": { + "brace-expansion": "^2.0.1" + } + } + } + }, + "gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==" + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==" + }, + "has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "requires": { + "has-symbols": "^1.0.3" + } + }, + "hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "requires": { + "function-bind": "^1.1.2" + } + }, + "html-encoding-sniffer": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-6.0.0.tgz", + "integrity": "sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==", + "dev": true, + "requires": { + "@exodus/bytes": "^1.6.0" + } + }, + "http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dev": true, + "requires": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + } + }, + "https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dev": true, + "requires": { + "agent-base": "^7.1.2", + "debug": "4" + } + }, + "immutable": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.4.tgz", + "integrity": "sha512-p6u1bG3YSnINT5RQmx/yRZBpenIl30kVxkTLDyHLIMk0gict704Q9n+thfDI7lTRm9vXdDYutVzXhzcThxTnXA==", + "dev": true + }, + "ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "optional": true + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" + }, + "is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "optional": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "dev": true + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "requires": { + "@isaacs/cliui": "^8.0.2", + "@pkgjs/parseargs": "^0.11.0" + } + }, + "js-beautify": { + "version": "1.15.4", + "resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.15.4.tgz", + "integrity": "sha512-9/KXeZUKKJwqCXUdBxFJ3vPh467OCckSBmYDwSK/EtV090K+iMJ7zx2S3HLVDIWFQdqMIsZWbnaGiba18aWhaA==", + "dev": true, + "requires": { + "config-chain": "^1.1.13", + "editorconfig": "^1.0.4", + "glob": "^10.4.2", + "js-cookie": "^3.0.5", + "nopt": "^7.2.1" + } + }, + "js-cookie": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz", + "integrity": "sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==", + "dev": true + }, + "jsdom": { + "version": "27.4.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-27.4.0.tgz", + "integrity": "sha512-mjzqwWRD9Y1J1KUi7W97Gja1bwOOM5Ug0EZ6UDK3xS7j7mndrkwozHtSblfomlzyB4NepioNt+B2sOSzczVgtQ==", + "dev": true, + "requires": { + "@acemir/cssom": "^0.9.28", + "@asamuzakjp/dom-selector": "^6.7.6", + "@exodus/bytes": "^1.6.0", + "cssstyle": "^5.3.4", + "data-urls": "^6.0.0", + "decimal.js": "^10.6.0", + "html-encoding-sniffer": "^6.0.0", + "http-proxy-agent": "^7.0.2", + "https-proxy-agent": "^7.0.6", + "is-potential-custom-element-name": "^1.0.1", + "parse5": "^8.0.0", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^6.0.0", + "w3c-xmlserializer": "^5.0.0", + "webidl-conversions": "^8.0.0", + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^15.1.0", + "ws": "^8.18.3", + "xml-name-validator": "^5.0.0" + } + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "requires": { + "p-locate": "^4.1.0" + } + }, + "lodash": { + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", + "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==" + }, + "lodash-es": { + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.23.tgz", + "integrity": "sha512-kVI48u3PZr38HdYz98UmfPnXl2DXrpdctLrFLCd3kOx1xUkOmpFPx7gCWWM5MPkL/fD8zb+Ph0QzjGFs4+hHWg==" + }, + "lodash-unified": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/lodash-unified/-/lodash-unified-1.0.3.tgz", + "integrity": "sha512-WK9qSozxXOD7ZJQlpSqOT+om2ZfcT4yO+03FuzAHD0wF6S0l0090LRPDx3vhTTLZ8cFKpBn+IOcVXK6qOcIlfQ==" + }, + "lru-cache": { + "version": "11.2.4", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.4.tgz", + "integrity": "sha512-B5Y16Jr9LB9dHVkh6ZevG+vAbOsNOYCX+sXvFWFu7B3Iz5mijW3zdbMyhsh8ANd2mSWBYdJgnqi+mL7/LrOPYg==", + "dev": true + }, + "magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "requires": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==" + }, + "mdn-data": { + "version": "2.12.2", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.12.2.tgz", + "integrity": "sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==", + "dev": true + }, + "memoize-one": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-6.0.0.tgz", + "integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==" + }, + "mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" + }, + "mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "requires": { + "mime-db": "1.52.0" + } + }, + "minimatch": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.1.tgz", + "integrity": "sha512-0jWhJpD/MdhPXwPuiRkCbfYfSKp2qnn2eOc279qI7f+osl/l+prKSrvhg157zSYvx/1nmgn2NqdT6k2Z7zSH9w==", + "dev": true, + "requires": { + "brace-expansion": "^2.0.1" + } + }, + "minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "muggle-string": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/muggle-string/-/muggle-string-0.4.1.tgz", + "integrity": "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==", + "dev": true + }, + "nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==" + }, + "node-addon-api": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", + "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", + "dev": true, + "optional": true + }, + "nopt": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-7.2.1.tgz", + "integrity": "sha512-taM24ViiimT/XntxbPyJQzCG+p4EKOpgD3mxFwW38mGjVUrfERQOeY4EDHjdnptttfHuHQXFx+lTP08Q+mLa/w==", + "dev": true, + "requires": { + "abbrev": "^2.0.0" + } + }, + "normalize-wheel-es": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/normalize-wheel-es/-/normalize-wheel-es-1.2.0.tgz", + "integrity": "sha512-Wj7+EJQ8mSuXr2iWfnujrimU35R2W4FAErEyTmJoJ7ucwTn2hOUSsRehMb5RSYkxXGTM7Y9QpvPmp++w5ftoJw==" + }, + "obug": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz", + "integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==", + "dev": true + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "requires": { + "p-limit": "^2.2.0" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" + }, + "package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true + }, + "parse5": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-8.0.0.tgz", + "integrity": "sha512-9m4m5GSgXjL4AjumKzq1Fgfp3Z8rsvjRNbnkVwfu2ImRqE5D0LnY2QfDen18FSY9C573YU5XxSapdHZTZ2WolA==", + "dev": true, + "requires": { + "entities": "^6.0.0" + }, + "dependencies": { + "entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "dev": true + } + } + }, + "path-browserify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", + "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", + "dev": true + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==" + }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true + }, + "path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "requires": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "dependencies": { + "lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true + } + } + }, + "pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true + }, + "picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==" + }, + "picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true + }, + "pngjs": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-5.0.0.tgz", + "integrity": "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==" + }, + "postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "requires": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + } + }, + "proto-list": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", + "integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==", + "dev": true + }, + "proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, + "punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true + }, + "qrcode": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/qrcode/-/qrcode-1.5.4.tgz", + "integrity": "sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg==", + "requires": { + "dijkstrajs": "^1.0.1", + "pngjs": "^5.0.0", + "yargs": "^15.3.1" + } + }, + "readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "dev": true, + "optional": true + }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==" + }, + "require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true + }, + "require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==" + }, + "rollup": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.56.0.tgz", + "integrity": "sha512-9FwVqlgUHzbXtDg9RCMgodF3Ua4Na6Gau+Sdt9vyCN4RhHfVKX2DCHy3BjMLTDd47ITDhYAnTwGulWTblJSDLg==", + "dev": true, + "requires": { + "@rollup/rollup-android-arm-eabi": "4.56.0", + "@rollup/rollup-android-arm64": "4.56.0", + "@rollup/rollup-darwin-arm64": "4.56.0", + "@rollup/rollup-darwin-x64": "4.56.0", + "@rollup/rollup-freebsd-arm64": "4.56.0", + "@rollup/rollup-freebsd-x64": "4.56.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.56.0", + "@rollup/rollup-linux-arm-musleabihf": "4.56.0", + "@rollup/rollup-linux-arm64-gnu": "4.56.0", + "@rollup/rollup-linux-arm64-musl": "4.56.0", + "@rollup/rollup-linux-loong64-gnu": "4.56.0", + "@rollup/rollup-linux-loong64-musl": "4.56.0", + "@rollup/rollup-linux-ppc64-gnu": "4.56.0", + "@rollup/rollup-linux-ppc64-musl": "4.56.0", + "@rollup/rollup-linux-riscv64-gnu": "4.56.0", + "@rollup/rollup-linux-riscv64-musl": "4.56.0", + "@rollup/rollup-linux-s390x-gnu": "4.56.0", + "@rollup/rollup-linux-x64-gnu": "4.56.0", + "@rollup/rollup-linux-x64-musl": "4.56.0", + "@rollup/rollup-openbsd-x64": "4.56.0", + "@rollup/rollup-openharmony-arm64": "4.56.0", + "@rollup/rollup-win32-arm64-msvc": "4.56.0", + "@rollup/rollup-win32-ia32-msvc": "4.56.0", + "@rollup/rollup-win32-x64-gnu": "4.56.0", + "@rollup/rollup-win32-x64-msvc": "4.56.0", + "@types/estree": "1.0.8", + "fsevents": "~2.3.2" + } + }, + "rxjs": { + "version": "7.8.2", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", + "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", + "dev": true, + "requires": { + "tslib": "^2.1.0" + } + }, + "sass": { + "version": "1.97.3", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.97.3.tgz", + "integrity": "sha512-fDz1zJpd5GycprAbu4Q2PV/RprsRtKC/0z82z0JLgdytmcq0+ujJbJ/09bPGDxCLkKY3Np5cRAOcWiVkLXJURg==", + "dev": true, + "optional": true, + "requires": { + "@parcel/watcher": "^2.4.1", + "chokidar": "^4.0.0", + "immutable": "^5.0.2", + "source-map-js": ">=0.6.2 <2.0.0" + } + }, + "sass-embedded": { + "version": "1.97.3", + "resolved": "https://registry.npmjs.org/sass-embedded/-/sass-embedded-1.97.3.tgz", + "integrity": "sha512-eKzFy13Nk+IRHhlAwP3sfuv+PzOrvzUkwJK2hdoCKYcWGSdmwFpeGpWmyewdw8EgBnsKaSBtgf/0b2K635ecSA==", + "dev": true, + "requires": { + "@bufbuild/protobuf": "^2.5.0", + "colorjs.io": "^0.5.0", + "immutable": "^5.0.2", + "rxjs": "^7.4.0", + "sass-embedded-all-unknown": "1.97.3", + "sass-embedded-android-arm": "1.97.3", + "sass-embedded-android-arm64": "1.97.3", + "sass-embedded-android-riscv64": "1.97.3", + "sass-embedded-android-x64": "1.97.3", + "sass-embedded-darwin-arm64": "1.97.3", + "sass-embedded-darwin-x64": "1.97.3", + "sass-embedded-linux-arm": "1.97.3", + "sass-embedded-linux-arm64": "1.97.3", + "sass-embedded-linux-musl-arm": "1.97.3", + "sass-embedded-linux-musl-arm64": "1.97.3", + "sass-embedded-linux-musl-riscv64": "1.97.3", + "sass-embedded-linux-musl-x64": "1.97.3", + "sass-embedded-linux-riscv64": "1.97.3", + "sass-embedded-linux-x64": "1.97.3", + "sass-embedded-unknown-all": "1.97.3", + "sass-embedded-win32-arm64": "1.97.3", + "sass-embedded-win32-x64": "1.97.3", + "supports-color": "^8.1.1", + "sync-child-process": "^1.0.2", + "varint": "^6.0.0" + } + }, + "sass-embedded-all-unknown": { + "version": "1.97.3", + "resolved": "https://registry.npmjs.org/sass-embedded-all-unknown/-/sass-embedded-all-unknown-1.97.3.tgz", + "integrity": "sha512-t6N46NlPuXiY3rlmG6/+1nwebOBOaLFOOVqNQOC2cJhghOD4hh2kHNQQTorCsbY9S1Kir2la1/XLBwOJfui0xg==", + "dev": true, + "optional": true, + "requires": { + "sass": "1.97.3" + } + }, + "sass-embedded-android-arm": { + "version": "1.97.3", + "resolved": "https://registry.npmjs.org/sass-embedded-android-arm/-/sass-embedded-android-arm-1.97.3.tgz", + "integrity": "sha512-cRTtf/KV/q0nzGZoUzVkeIVVFv3L/tS1w4WnlHapphsjTXF/duTxI8JOU1c/9GhRPiMdfeXH7vYNcMmtjwX7jg==", + "dev": true, + "optional": true + }, + "sass-embedded-android-arm64": { + "version": "1.97.3", + "resolved": "https://registry.npmjs.org/sass-embedded-android-arm64/-/sass-embedded-android-arm64-1.97.3.tgz", + "integrity": "sha512-aiZ6iqiHsUsaDx0EFbbmmA0QgxicSxVVN3lnJJ0f1RStY0DthUkquGT5RJ4TPdaZ6ebeJWkboV4bra+CP766eA==", + "dev": true, + "optional": true + }, + "sass-embedded-android-riscv64": { + "version": "1.97.3", + "resolved": "https://registry.npmjs.org/sass-embedded-android-riscv64/-/sass-embedded-android-riscv64-1.97.3.tgz", + "integrity": "sha512-zVEDgl9JJodofGHobaM/q6pNETG69uuBIGQHRo789jloESxxZe82lI3AWJQuPmYCOG5ElfRthqgv89h3gTeLYA==", + "dev": true, + "optional": true + }, + "sass-embedded-android-x64": { + "version": "1.97.3", + "resolved": "https://registry.npmjs.org/sass-embedded-android-x64/-/sass-embedded-android-x64-1.97.3.tgz", + "integrity": "sha512-3ke0le7ZKepyXn/dKKspYkpBC0zUk/BMciyP5ajQUDy4qJwobd8zXdAq6kOkdiMB+d9UFJOmEkvgFJHl3lqwcw==", + "dev": true, + "optional": true + }, + "sass-embedded-darwin-arm64": { + "version": "1.97.3", + "resolved": "https://registry.npmjs.org/sass-embedded-darwin-arm64/-/sass-embedded-darwin-arm64-1.97.3.tgz", + "integrity": "sha512-fuqMTqO4gbOmA/kC5b9y9xxNYw6zDEyfOtMgabS7Mz93wimSk2M1quQaTJnL98Mkcsl2j+7shNHxIS/qpcIDDA==", + "dev": true, + "optional": true + }, + "sass-embedded-darwin-x64": { + "version": "1.97.3", + "resolved": "https://registry.npmjs.org/sass-embedded-darwin-x64/-/sass-embedded-darwin-x64-1.97.3.tgz", + "integrity": "sha512-b/2RBs/2bZpP8lMkyZ0Px0vkVkT8uBd0YXpOwK7iOwYkAT8SsO4+WdVwErsqC65vI5e1e5p1bb20tuwsoQBMVA==", + "dev": true, + "optional": true + }, + "sass-embedded-linux-arm": { + "version": "1.97.3", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-arm/-/sass-embedded-linux-arm-1.97.3.tgz", + "integrity": "sha512-2lPQ7HQQg4CKsH18FTsj2hbw5GJa6sBQgDsls+cV7buXlHjqF8iTKhAQViT6nrpLK/e8nFCoaRgSqEC8xMnXuA==", + "dev": true, + "optional": true + }, + "sass-embedded-linux-arm64": { + "version": "1.97.3", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-arm64/-/sass-embedded-linux-arm64-1.97.3.tgz", + "integrity": "sha512-IP1+2otCT3DuV46ooxPaOKV1oL5rLjteRzf8ldZtfIEcwhSgSsHgA71CbjYgLEwMY9h4jeal8Jfv3QnedPvSjg==", + "dev": true, + "optional": true + }, + "sass-embedded-linux-musl-arm": { + "version": "1.97.3", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-arm/-/sass-embedded-linux-musl-arm-1.97.3.tgz", + "integrity": "sha512-cBTMU68X2opBpoYsSZnI321gnoaiMBEtc+60CKCclN6PCL3W3uXm8g4TLoil1hDD6mqU9YYNlVG6sJ+ZNef6Lg==", + "dev": true, + "optional": true + }, + "sass-embedded-linux-musl-arm64": { + "version": "1.97.3", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-arm64/-/sass-embedded-linux-musl-arm64-1.97.3.tgz", + "integrity": "sha512-Lij0SdZCsr+mNRSyDZ7XtJpXEITrYsaGbOTz5e6uFLJ9bmzUbV7M8BXz2/cA7bhfpRPT7/lwRKPdV4+aR9Ozcw==", + "dev": true, + "optional": true + }, + "sass-embedded-linux-musl-riscv64": { + "version": "1.97.3", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-riscv64/-/sass-embedded-linux-musl-riscv64-1.97.3.tgz", + "integrity": "sha512-sBeLFIzMGshR4WmHAD4oIM7WJVkSoCIEwutzptFtGlSlwfNiijULp+J5hA2KteGvI6Gji35apR5aWj66wEn/iA==", + "dev": true, + "optional": true + }, + "sass-embedded-linux-musl-x64": { + "version": "1.97.3", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-x64/-/sass-embedded-linux-musl-x64-1.97.3.tgz", + "integrity": "sha512-/oWJ+OVrDg7ADDQxRLC/4g1+Nsz1g4mkYS2t6XmyMJKFTFK50FVI2t5sOdFH+zmMp+nXHKM036W94y9m4jjEcw==", + "dev": true, + "optional": true + }, + "sass-embedded-linux-riscv64": { + "version": "1.97.3", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-riscv64/-/sass-embedded-linux-riscv64-1.97.3.tgz", + "integrity": "sha512-l3IfySApLVYdNx0Kjm7Zehte1CDPZVcldma3dZt+TfzvlAEerM6YDgsk5XEj3L8eHBCgHgF4A0MJspHEo2WNfA==", + "dev": true, + "optional": true + }, + "sass-embedded-linux-x64": { + "version": "1.97.3", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-x64/-/sass-embedded-linux-x64-1.97.3.tgz", + "integrity": "sha512-Kwqwc/jSSlcpRjULAOVbndqEy2GBzo6OBmmuBVINWUaJLJ8Kczz3vIsDUWLfWz/kTEw9FHBSiL0WCtYLVAXSLg==", + "dev": true, + "optional": true + }, + "sass-embedded-unknown-all": { + "version": "1.97.3", + "resolved": "https://registry.npmjs.org/sass-embedded-unknown-all/-/sass-embedded-unknown-all-1.97.3.tgz", + "integrity": "sha512-/GHajyYJmvb0IABUQHbVHf1nuHPtIDo/ClMZ81IDr59wT5CNcMe7/dMNujXwWugtQVGI5UGmqXWZQCeoGnct8Q==", + "dev": true, + "optional": true, + "requires": { + "sass": "1.97.3" + } + }, + "sass-embedded-win32-arm64": { + "version": "1.97.3", + "resolved": "https://registry.npmjs.org/sass-embedded-win32-arm64/-/sass-embedded-win32-arm64-1.97.3.tgz", + "integrity": "sha512-RDGtRS1GVvQfMGAmVXNxYiUOvPzn9oO1zYB/XUM9fudDRnieYTcUytpNTQZLs6Y1KfJxgt5Y+giRceC92fT8Uw==", + "dev": true, + "optional": true + }, + "sass-embedded-win32-x64": { + "version": "1.97.3", + "resolved": "https://registry.npmjs.org/sass-embedded-win32-x64/-/sass-embedded-win32-x64-1.97.3.tgz", + "integrity": "sha512-SFRa2lED9UEwV6vIGeBXeBOLKF+rowF3WmNfb/BzhxmdAsKofCXrJ8ePW7OcDVrvNEbTOGwhsReIsF5sH8fVaw==", + "dev": true, + "optional": true + }, + "saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "dev": true, + "requires": { + "xmlchars": "^2.2.0" + } + }, + "semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "dev": true + }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==" + }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true + }, + "siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true + }, + "signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true + }, + "source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==" + }, + "stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true + }, + "std-env": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", + "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", + "dev": true + }, + "string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "requires": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + } + }, + "strip-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "dev": true, + "requires": { + "ansi-regex": "^6.0.1" + } + }, + "supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "dev": true + }, + "sync-child-process": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/sync-child-process/-/sync-child-process-1.0.2.tgz", + "integrity": "sha512-8lD+t2KrrScJ/7KXCSyfhT3/hRq78rC0wBFqNJXv3mZyn6hW2ypM05JmlSvtqRbeq6jqA94oHbxAr2vYsJ8vDA==", + "dev": true, + "requires": { + "sync-message-port": "^1.0.0" + } + }, + "sync-message-port": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/sync-message-port/-/sync-message-port-1.1.3.tgz", + "integrity": "sha512-GTt8rSKje5FilG+wEdfCkOcLL7LWqpMlr2c3LRuKt/YXxcJ52aGSbGBAdI4L3aaqfrBt6y711El53ItyH1NWzg==", + "dev": true + }, + "tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true + }, + "tinyexec": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.2.tgz", + "integrity": "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==", + "dev": true + }, + "tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "requires": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + } + }, + "tinyrainbow": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.0.3.tgz", + "integrity": "sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==", + "dev": true + }, + "tldts": { + "version": "7.0.19", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.0.19.tgz", + "integrity": "sha512-8PWx8tvC4jDB39BQw1m4x8y5MH1BcQ5xHeL2n7UVFulMPH/3Q0uiamahFJ3lXA0zO2SUyRXuVVbWSDmstlt9YA==", + "dev": true, + "requires": { + "tldts-core": "^7.0.19" + } + }, + "tldts-core": { + "version": "7.0.19", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.0.19.tgz", + "integrity": "sha512-lJX2dEWx0SGH4O6p+7FPwYmJ/bu1JbcGJ8RLaG9b7liIgZ85itUVEPbMtWRVrde/0fnDPEPHW10ZsKW3kVsE9A==", + "dev": true + }, + "tough-cookie": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.0.tgz", + "integrity": "sha512-kXuRi1mtaKMrsLUxz3sQYvVl37B0Ns6MzfrtV5DvJceE9bPyspOqk9xxv7XbZWcfLWbFmm997vl83qUWVJA64w==", + "dev": true, + "requires": { + "tldts": "^7.0.5" + } + }, + "tr46": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-6.0.0.tgz", + "integrity": "sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==", + "dev": true, + "requires": { + "punycode": "^2.3.1" + } + }, + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true + }, + "typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true + }, + "undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==" + }, + "varint": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/varint/-/varint-6.0.0.tgz", + "integrity": "sha512-cXEIW6cfr15lFv563k4GuVuW/fiwjknytD37jIOLSdSWuOI6WnO/oKwmP2FQTU2l01LP8/M5TSAJpzUaGe3uWg==", + "dev": true + }, + "vite": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz", + "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", + "dev": true, + "requires": { + "esbuild": "^0.27.0", + "fdir": "^6.5.0", + "fsevents": "~2.3.3", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" + } + }, + "vitest": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.0.18.tgz", + "integrity": "sha512-hOQuK7h0FGKgBAas7v0mSAsnvrIgAvWmRFjmzpJ7SwFHH3g1k2u37JtYwOwmEKhK6ZO3v9ggDBBm0La1LCK4uQ==", + "dev": true, + "requires": { + "@vitest/expect": "4.0.18", + "@vitest/mocker": "4.0.18", + "@vitest/pretty-format": "4.0.18", + "@vitest/runner": "4.0.18", + "@vitest/snapshot": "4.0.18", + "@vitest/spy": "4.0.18", + "@vitest/utils": "4.0.18", + "es-module-lexer": "^1.7.0", + "expect-type": "^1.2.2", + "magic-string": "^0.30.21", + "obug": "^2.1.1", + "pathe": "^2.0.3", + "picomatch": "^4.0.3", + "std-env": "^3.10.0", + "tinybench": "^2.9.0", + "tinyexec": "^1.0.2", + "tinyglobby": "^0.2.15", + "tinyrainbow": "^3.0.3", + "vite": "^6.0.0 || ^7.0.0", + "why-is-node-running": "^2.3.0" + } + }, + "vscode-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.1.0.tgz", + "integrity": "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==", + "dev": true + }, + "vue": { + "version": "3.5.27", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.27.tgz", + "integrity": "sha512-aJ/UtoEyFySPBGarREmN4z6qNKpbEguYHMmXSiOGk69czc+zhs0NF6tEFrY8TZKAl8N/LYAkd4JHVd5E/AsSmw==", + "requires": { + "@vue/compiler-dom": "3.5.27", + "@vue/compiler-sfc": "3.5.27", + "@vue/runtime-dom": "3.5.27", + "@vue/server-renderer": "3.5.27", + "@vue/shared": "3.5.27" + } + }, + "vue-component-type-helpers": { + "version": "2.2.12", + "resolved": "https://registry.npmjs.org/vue-component-type-helpers/-/vue-component-type-helpers-2.2.12.tgz", + "integrity": "sha512-YbGqHZ5/eW4SnkPNR44mKVc6ZKQoRs/Rux1sxC6rdwXb4qpbOSYfDr9DsTHolOTGmIKgM9j141mZbBeg05R1pw==", + "dev": true + }, + "vue-router": { + "version": "4.6.4", + "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.6.4.tgz", + "integrity": "sha512-Hz9q5sa33Yhduglwz6g9skT8OBPii+4bFn88w6J+J4MfEo4KRRpmiNG/hHHkdbRFlLBOqxN8y8gf2Fb0MTUgVg==", + "requires": { + "@vue/devtools-api": "^6.6.4" + } + }, + "vue-tsc": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/vue-tsc/-/vue-tsc-3.2.3.tgz", + "integrity": "sha512-1RdRB7rQXGFMdpo0aXf9spVzWEPGAk7PEb/ejHQwVrcuQA/HsGiixIc3uBQeqY2YjeEEgvr2ShQewBgcN4c1Cw==", + "dev": true, + "requires": { + "@volar/typescript": "2.4.27", + "@vue/language-core": "3.2.3" + } + }, + "w3c-xmlserializer": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", + "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", + "dev": true, + "requires": { + "xml-name-validator": "^5.0.0" + } + }, + "webidl-conversions": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.1.tgz", + "integrity": "sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==", + "dev": true + }, + "whatwg-mimetype": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", + "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", + "dev": true + }, + "whatwg-url": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-15.1.0.tgz", + "integrity": "sha512-2ytDk0kiEj/yu90JOAp44PVPUkO9+jVhyf+SybKlRHSDlvOOZhdPIrr7xTH64l4WixO2cP+wQIcgujkGBPPz6g==", + "dev": true, + "requires": { + "tr46": "^6.0.0", + "webidl-conversions": "^8.0.0" + } + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "which-module": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz", + "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==" + }, + "why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "requires": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + } + }, + "wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "requires": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + } + }, + "ws": { + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz", + "integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==", + "dev": true + }, + "xml-name-validator": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", + "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", + "dev": true + }, + "xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "dev": true + }, + "y18n": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==" + }, + "yargs": { + "version": "15.4.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", + "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", + "requires": { + "cliui": "^6.0.0", + "decamelize": "^1.2.0", + "find-up": "^4.1.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^4.2.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^18.1.2" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "requires": { + "ansi-regex": "^5.0.1" + } + } + } + }, + "yargs-parser": { + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + } + } +} diff --git a/erp-frontend-vue/package.json b/erp-frontend-vue/package.json new file mode 100644 index 0000000..9ed57f3 --- /dev/null +++ b/erp-frontend-vue/package.json @@ -0,0 +1,34 @@ +{ + "name": "erp-frontend-vue", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vue-tsc -b && vite build", + "preview": "vite preview", + "test": "vitest run", + "test:watch": "vitest" + }, + "dependencies": { + "@element-plus/icons-vue": "^2.3.2", + "@types/qrcode": "^1.5.6", + "axios": "^1.13.3", + "element-plus": "^2.13.1", + "qrcode": "^1.5.4", + "vue": "^3.5.24", + "vue-router": "^4.6.4" + }, + "devDependencies": { + "@types/node": "^24.10.1", + "@vitejs/plugin-vue": "^6.0.1", + "@vue/test-utils": "^2.4.6", + "@vue/tsconfig": "^0.8.1", + "jsdom": "^27.4.0", + "sass-embedded": "^1.97.3", + "typescript": "~5.9.3", + "vite": "^7.2.4", + "vitest": "^4.0.18", + "vue-tsc": "^3.1.4" + } +} diff --git a/erp-frontend-vue/public/vite.svg b/erp-frontend-vue/public/vite.svg new file mode 100644 index 0000000..e7b8dfb --- /dev/null +++ b/erp-frontend-vue/public/vite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/erp-frontend-vue/src/App.vue b/erp-frontend-vue/src/App.vue new file mode 100644 index 0000000..803d8f4 --- /dev/null +++ b/erp-frontend-vue/src/App.vue @@ -0,0 +1,9 @@ + + + + + diff --git a/erp-frontend-vue/src/api/__tests__/customer.test.ts b/erp-frontend-vue/src/api/__tests__/customer.test.ts new file mode 100644 index 0000000..82fac3e --- /dev/null +++ b/erp-frontend-vue/src/api/__tests__/customer.test.ts @@ -0,0 +1,156 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest' +import { + getCustomerList, + getCustomerDetail, + createCustomer, + updateCustomer, + deleteCustomer, + updateCustomerStatus, + type Customer, + type CustomerQuery +} from '../customer' + +const mockGet = vi.fn() +const mockPost = vi.fn() +const mockPut = vi.fn() +const mockDelete = vi.fn() + +vi.mock('../request', () => ({ + default: { + get: (...args: unknown[]) => mockGet(...args), + post: (...args: unknown[]) => mockPost(...args), + put: (...args: unknown[]) => mockPut(...args), + delete: (...args: unknown[]) => mockDelete(...args) + } +})) + +describe('customer API', () => { + beforeEach(() => { + vi.clearAllMocks() + }) + + describe('getCustomerList', () => { + it('requests list and maps rows with compat fields', async () => { + mockGet.mockResolvedValue({ + rows: [ + { + clientId: 1, + clientCode: 'C001', + clientName: '客户A', + clientNick: 'A', + contact1: '张三', + contact1Tel: '13800001111', + address: '深圳市', + enableFlag: 'Y', + createTime: '2026-01-25 10:00:00' + } + ], + total: 1 + }) + + const res = await getCustomerList({ + clientCode: 'C001', + pageNum: 1, + pageSize: 10 + }) + + expect(mockGet).toHaveBeenCalledWith('/erp/sl/client/list', { + params: expect.objectContaining({ clientCode: 'C001', pageNum: 1, pageSize: 10 }) + }) + expect(res.list).toHaveLength(1) + expect(res.total).toBe(1) + expect(res.list[0]).toMatchObject({ + clientId: 1, + clientCode: 'C001', + clientName: '客户A', + id: 1, + code: 'C001', + name: '客户A', + contact: '张三', + phone: '13800001111' + }) + }) + + it('maps name param to clientName', async () => { + mockGet.mockResolvedValue({ rows: [], total: 0 }) + await getCustomerList({ name: '测试' } as CustomerQuery) + expect(mockGet).toHaveBeenCalledWith('/erp/sl/client/list', { + params: expect.objectContaining({ clientName: '测试' }) + }) + }) + + it('handles res.list or res.data.rows', async () => { + mockGet.mockResolvedValue({ list: [{ clientId: 2, clientCode: 'C002', clientName: 'B' }], total: 1 }) + const res = await getCustomerList({}) + expect(res.list).toHaveLength(1) + expect(res.list[0]?.clientCode).toBe('C002') + }) + }) + + describe('getCustomerDetail', () => { + it('fetches detail and maps compat fields', async () => { + mockGet.mockResolvedValue({ + data: { + clientId: 1, + clientCode: 'C001', + clientName: '客户A', + contact1: '李四', + contact1Tel: '13900002222' + } + }) + + const res = await getCustomerDetail(1) + expect(mockGet).toHaveBeenCalledWith('/erp/sl/client/1') + expect(res.clientId).toBe(1) + expect(res.id).toBe(1) + expect(res.code).toBe('C001') + expect(res.name).toBe('客户A') + expect(res.contact).toBe('李四') + expect(res.phone).toBe('13900002222') + }) + }) + + describe('createCustomer', () => { + it('POSTs payload', async () => { + mockPost.mockResolvedValue(undefined) + const data: Partial = { + clientCode: 'C003', + clientName: '客户C', + enableFlag: 'Y' + } + await createCustomer(data) + expect(mockPost).toHaveBeenCalledWith('/erp/sl/client', data) + }) + }) + + describe('updateCustomer', () => { + it('PUTs payload', async () => { + mockPut.mockResolvedValue(undefined) + const data: Partial = { clientId: 1, clientName: '更新名' } + await updateCustomer(data) + expect(mockPut).toHaveBeenCalledWith('/erp/sl/client', data) + }) + }) + + describe('deleteCustomer', () => { + it('DELETEs single id', async () => { + mockDelete.mockResolvedValue(undefined) + await deleteCustomer(1) + expect(mockDelete).toHaveBeenCalledWith('/erp/sl/client/1') + }) + + it('DELETEs comma-separated ids', async () => { + mockDelete.mockResolvedValue(undefined) + await deleteCustomer([1, 2, 3]) + expect(mockDelete).toHaveBeenCalledWith('/erp/sl/client/1,2,3') + }) + }) + + describe('updateCustomerStatus', () => { + it('PUTs clientId and enableFlag', async () => { + mockPut.mockResolvedValue(undefined) + await updateCustomerStatus(1, 'N') + expect(mockPut).toHaveBeenCalledWith('/erp/sl/client', { clientId: 1, enableFlag: 'N' }) + }) + }) +}) diff --git a/erp-frontend-vue/src/api/auth.ts b/erp-frontend-vue/src/api/auth.ts new file mode 100644 index 0000000..f71ad73 --- /dev/null +++ b/erp-frontend-vue/src/api/auth.ts @@ -0,0 +1,58 @@ +import axios from 'axios' + +// 创建一个专门用于登录相关的 axios 实例(不带 /erp 前缀) +const authRequest = axios.create({ + baseURL: '', + timeout: 30000 +}) + +// 登录方法 +export function login(username: string, password: string, code: string, uuid: string) { + return authRequest({ + url: '/login', + method: 'post', + data: { username, password, code, uuid } + }).then(res => res.data) +} + +// 注册方法 +export function register(data: { username: string; password: string; confirmPassword: string }) { + return authRequest({ + url: '/register', + method: 'post', + data + }).then(res => res.data) +} + +// 获取用户详细信息 +export function getInfo() { + const token = localStorage.getItem('Admin-Token') + return authRequest({ + url: '/getInfo', + method: 'get', + headers: { + Authorization: token ? `Bearer ${token}` : '' + } + }).then(res => res.data) +} + +// 退出方法 +export function logout() { + const token = localStorage.getItem('Admin-Token') + return authRequest({ + url: '/logout', + method: 'post', + headers: { + Authorization: token ? `Bearer ${token}` : '' + } + }).then(res => res.data) +} + +// 获取验证码 +export function getCodeImg() { + return authRequest({ + url: '/captchaImage', + method: 'get', + timeout: 20000 + }).then(res => res.data) +} diff --git a/erp-frontend-vue/src/api/checkin.ts b/erp-frontend-vue/src/api/checkin.ts new file mode 100644 index 0000000..818c51c --- /dev/null +++ b/erp-frontend-vue/src/api/checkin.ts @@ -0,0 +1,96 @@ +import request from './request' + +export interface CheckinLine { + lineId: number + checkinId: number + checkinCode: string + lineNo: number + orderLineId?: number + trackCode?: string + itemId: number + itemCode: string + itemName: string + specification?: string + unitName?: string + orderQuantity?: number + quantity: number + stockedQuantity?: number + remark?: string +} + +export interface Checkin { + checkinId: number + checkinCode: string + checkinDate: string + status: string + businessType: string + orderId?: number + orderCode?: string + supplierId?: number + supplierName?: string + warehouseId?: number + warehouseName?: string + totalQuantity?: number + stockedQuantity?: number + remark?: string + operatorName?: string + approverName?: string + approveDate?: string + lines: CheckinLine[] + createTime?: string +} + +export interface CheckinQuery { + trackCode?: string + checkinCode?: string + supplierName?: string + itemCode?: string + itemName?: string + beginDate?: string + endDate?: string + status?: string + pageNum?: number + pageSize?: number +} + +export interface CheckinListResponse { + rows: Checkin[] + total: number +} + +// 获取采购到货单列表 +export function getCheckinList(params: CheckinQuery): Promise { + return request.get('/erp/po/checkin/list', { params }).then((res: any) => { + return { + rows: res.rows || [], + total: res.total || 0 + } + }) +} + +// 获取采购到货单详情 +export function getCheckinDetail(checkinId: number): Promise { + return request.get(`/erp/po/checkin/${checkinId}`).then((res: any) => { + return res.data + }) +} + +// 新增采购到货单 +export function createCheckin(data: Partial): Promise { + return request.post('/erp/po/checkin', data) +} + +// 更新采购到货单 +export function updateCheckin(data: Partial): Promise { + return request.put('/erp/po/checkin', data) +} + +// 删除采购到货单 +export function deleteCheckin(checkinIds: string): Promise { + return request.delete(`/erp/po/checkin/${checkinIds}`) +} + +// 注意:采购到货单审核接口后端暂未实现 +// export function approveCheckin(checkinId: number): Promise { +// return request.post(`/erp/po/checkin/audit/${checkinId}`) +// } diff --git a/erp-frontend-vue/src/api/contract.ts b/erp-frontend-vue/src/api/contract.ts new file mode 100644 index 0000000..f81e706 --- /dev/null +++ b/erp-frontend-vue/src/api/contract.ts @@ -0,0 +1,63 @@ +import request from './request' + +export interface Contract { + contractId?: number + contractCode?: string + contractName?: string + clientId?: number + clientCode?: string + clientName?: string + contractDate?: string + startDate?: string + endDate?: string + status?: string + totalAmount?: number + remark?: string + createTime?: string + tenantId?: string +} + +export interface ContractQuery { + contractCode?: string + clientName?: string + status?: string + pageNum?: number + pageSize?: number +} + +export interface ContractListResponse { + list: Contract[] + total: number +} + +// 获取销售合同列表 +export function getContractList(params: ContractQuery): Promise { + return request.get('/sl/contract/list', { params }).then((res: any) => { + return { + list: res.rows || [], + total: res.total || 0 + } + }) +} + +// 获取销售合同详情 +export function getContractDetail(id: number): Promise { + return request.get(`/sl/contract/${id}`).then((res: any) => { + return res.data + }) +} + +// 新增销售合同 +export function createContract(data: Partial): Promise { + return request.post('/sl/contract', data) +} + +// 更新销售合同 +export function updateContract(id: number, data: Partial): Promise { + return request.put('/sl/contract', { ...data, contractId: id }) +} + +// 删除销售合同 +export function deleteContract(id: number): Promise { + return request.delete(`/sl/contract/${id}`) +} diff --git a/erp-frontend-vue/src/api/customer.ts b/erp-frontend-vue/src/api/customer.ts new file mode 100644 index 0000000..e93de8e --- /dev/null +++ b/erp-frontend-vue/src/api/customer.ts @@ -0,0 +1,111 @@ +import request from './request' + +/** 客户档案 - 与 ERP 数据模型 SlClient 对齐 */ +export interface Customer { + clientId?: number + clientCode?: string + clientName?: string + clientNick?: string + /** 兼容表单选择:id = clientId */ + id?: number + /** 兼容表单选择:code = clientCode */ + code?: string + /** 兼容表单选择:name = clientName */ + name?: string + /** 兼容表单选择:contact = contact1 */ + contact?: string + /** 兼容表单选择:phone = contact1Tel */ + phone?: string + clientEn?: string + clientDes?: string + clientLogo?: string + clientType?: string + clientLevel?: string + clientSource?: string + clientIndustry?: string + address?: string + website?: string + email?: string + tel?: string + contact1?: string + contact1Tel?: string + contact2?: string + contact2Tel?: string + creditCode?: string + bankName?: string + bankAccount?: string + taxNo?: string + invoiceAddress?: string + enableFlag?: 'Y' | 'N' + remark?: string + createBy?: string + createTime?: string + updateBy?: string + updateTime?: string +} + +export interface CustomerQuery { + clientCode?: string + clientName?: string + clientNick?: string + /** 兼容:映射为 clientName */ + name?: string + enableFlag?: 'Y' | 'N' + pageNum?: number + pageSize?: number +} + +export interface CustomerListResponse { + list: Customer[] + total: number +} + +const BASE = '/erp/sl/client' + +function mapRow(r: any): Customer { + return { + ...r, + id: r.clientId ?? r.id, + code: r.clientCode ?? r.code, + name: r.clientName ?? r.name, + contact: r.contact1 ?? r.contact, + phone: r.contact1Tel ?? r.phone + } +} + +/** 获取客户列表(分页) */ +export function getCustomerList(params: CustomerQuery): Promise { + const { name, ...rest } = params + const req = { ...rest, clientName: rest.clientName ?? name } as Record + return request.get(`${BASE}/list`, { params: req }).then((res: any) => { + const rows = res.rows ?? res.data?.rows ?? res.list ?? [] + const total = res.total ?? res.data?.total ?? 0 + return { list: rows.map(mapRow), total } + }) +} + +/** 获取客户详情 */ +export function getCustomerDetail(clientId: number): Promise { + return request.get(`${BASE}/${clientId}`).then((res: any) => mapRow(res.data ?? res)) +} + +/** 新增客户 */ +export function createCustomer(data: Partial): Promise { + return request.post(BASE, data) +} + +/** 更新客户 */ +export function updateCustomer(data: Partial): Promise { + return request.put(BASE, data) +} + +/** 删除客户(支持单个或批量,逗号分隔 ID) */ +export function deleteCustomer(clientIds: number | number[]): Promise { + const ids = Array.isArray(clientIds) ? clientIds.join(',') : String(clientIds) + return request.delete(`${BASE}/${ids}`) +} + +/** 更新客户启用状态 */ +export function updateCustomerStatus(clientId: number, enableFlag: 'Y' | 'N'): Promise { + return request.put(BASE, { clientId, enableFlag }) +} diff --git a/erp-frontend-vue/src/api/deliver.ts b/erp-frontend-vue/src/api/deliver.ts new file mode 100644 index 0000000..44237f0 --- /dev/null +++ b/erp-frontend-vue/src/api/deliver.ts @@ -0,0 +1,108 @@ +import request from './request' + +export interface DeliverLine { + lineId?: number + id?: number + deliverId?: number + deliverCode?: string + orderLineId?: number + itemId?: number + itemCode?: string + materialCode?: string + itemName?: string + materialName?: string + specification?: string + spec?: string + unitOfMeasure?: string + unit?: string + orderQty?: number + deliverQty: number + remark?: string +} + +export interface Deliver { + deliverId?: number + id?: number + deliverCode?: string + code?: string + clientId?: number + customerId?: number + clientCode?: string + customerCode?: string + clientName?: string + customerName?: string + deliverDate: string + status: string + totalQty?: number + deliveryAddress?: string + contact?: string + phone?: string + remark?: string + lines?: DeliverLine[] + createTime?: string +} + +export interface DeliverQuery { + code?: string + customerName?: string + status?: string + page?: number + pageSize?: number +} + +export interface DeliverListResponse { + list: Deliver[] + total: number +} + +// 获取发货单列表 +export function getDeliverList(params: DeliverQuery): Promise { + return request.get('/erp/sl/deliver/list', { params }).then((res: any) => { + return { + list: res.rows || [], + total: res.total || 0 + } + }) +} + +// 获取发货单详情 +export function getDeliverDetail(id: number): Promise { + return request.get(`/erp/sl/deliver/${id}`).then((res: any) => { + return res.data + }) +} + +// 新增发货单 +export function createDeliver(data: Partial): Promise { + return request.post('/erp/sl/deliver', data) +} + +// 更新发货单 +export function updateDeliver(id: number, data: Partial): Promise { + return request.put('/erp/sl/deliver', { ...data, deliverId: id }) +} + +// 删除发货单 +export function deleteDeliver(id: number): Promise { + return request.delete(`/erp/sl/deliver/${id}`) +} + +// 审核发货单 +export function auditDeliver(id: number): Promise { + return request.post(`/erp/sl/deliver/audit/${id}`) +} + +// 反审核发货单 +export function unauditDeliver(id: number): Promise { + return request.post(`/erp/sl/deliver/unaudit/${id}`) +} + +// 引入销售订单 +export function getIntroduceOrderList(params: any): Promise { + return request.get('/erp/sl/deliver/introduce', { params }).then((res: any) => { + return { + list: res.rows || [], + total: res.total || 0 + } + }) +} diff --git a/erp-frontend-vue/src/api/invoice.ts b/erp-frontend-vue/src/api/invoice.ts new file mode 100644 index 0000000..8218eb4 --- /dev/null +++ b/erp-frontend-vue/src/api/invoice.ts @@ -0,0 +1,77 @@ +import request from './request' + +export interface Invoice { + invoiceId?: number + id?: number + invoiceCode?: string + code?: string + clientId?: number + customerId?: number + clientCode?: string + customerCode?: string + clientName?: string + customerName?: string + invoiceDate: string + invoiceType?: string + status: string + totalAmount?: number + taxAmount?: number + invoiceNo?: string + remark?: string + createTime?: string +} + +export interface InvoiceQuery { + code?: string + customerName?: string + status?: string + page?: number + pageSize?: number +} + +export interface InvoiceListResponse { + list: Invoice[] + total: number +} + +// 获取销售发票列表 +export function getInvoiceList(params: InvoiceQuery): Promise { + return request.get('/erp/sl/invoice/list', { params }).then((res: any) => { + return { + list: res.rows || [], + total: res.total || 0 + } + }) +} + +// 获取销售发票详情 +export function getInvoiceDetail(id: number): Promise { + return request.get(`/erp/sl/invoice/${id}`).then((res: any) => { + return res.data + }) +} + +// 新增销售发票 +export function createInvoice(data: Partial): Promise { + return request.post('/erp/sl/invoice', data) +} + +// 更新销售发票 +export function updateInvoice(id: number, data: Partial): Promise { + return request.put('/erp/sl/invoice', { ...data, invoiceId: id }) +} + +// 删除销售发票 +export function deleteInvoice(id: number): Promise { + return request.delete(`/erp/sl/invoice/${id}`) +} + +// 审核销售发票 +export function auditInvoice(id: number): Promise { + return request.post(`/erp/sl/invoice/audit/${id}`) +} + +// 反审核销售发票 +export function unauditInvoice(id: number): Promise { + return request.post(`/erp/sl/invoice/unaudit/${id}`) +} diff --git a/erp-frontend-vue/src/api/masterdata/bom.ts b/erp-frontend-vue/src/api/masterdata/bom.ts new file mode 100644 index 0000000..2e62fca --- /dev/null +++ b/erp-frontend-vue/src/api/masterdata/bom.ts @@ -0,0 +1,201 @@ +import request from '../request' + +/** BOM 表头(md_bom) */ +export interface BomHeader { + bomId?: number + bomCode?: string + bomName?: string + version?: string + versionDesc?: string + status?: string + itemId?: number + itemCode?: string + itemName?: string + itemSpec?: string + unitName?: string + baseQty?: number + enableFlag?: string + bomItemId?: number + bomItemCode?: string + bomItemName?: string + bomItemSpec?: string + unitOfMeasure?: string + itemOrProduct?: string + quantity?: number + lineNo?: number + lossRate?: number + supplyType?: string + tenantId?: string + remark?: string + createBy?: string + createTime?: string + updateBy?: string + updateTime?: string +} + +/** BOM 明细(md_product_bom) */ +export interface BomLine { + bomId?: number // 明细行ID(现有表主键) + bomCode?: string + bomName?: string + version?: string + versionDesc?: string + status?: string + itemId?: number // 母件物料ID + itemCode?: string + itemName?: string + itemSpec?: string + bomItemId?: number + bomItemCode?: string + bomItemName?: string + bomItemSpec?: string + unitOfMeasure?: string + unitName?: string + itemOrProduct?: string + quantity?: number + baseQty?: number + lineNo?: number + lossRate?: number + supplyType?: string + enableFlag?: string + tenantId?: string + delFlag?: string + remark?: string + createBy?: string + createTime?: string + updateBy?: string + updateTime?: string + /** 扩展字段(后端可忽略/不落库,页面需要透传) */ + attr1?: string + attr2?: string + // 详情页表格里用到的前端临时字段(后端可忽略) + planRoute?: string + usageType?: string + drawingNo?: string +} + +/** BOM 表头查询参数 */ +export interface BomHeaderQuery { + bomCode?: string + bomName?: string + itemId?: number + itemCode?: string + itemName?: string + itemOrProduct?: string + itemTypeId?: number + status?: string + enableFlag?: string + pageNum?: number + pageSize?: number +} + +/** BOM 表头列表响应 */ +export interface BomHeaderListResponse { + rows: BomHeader[] + total: number +} + +/** BOM 明细查询参数 */ +export interface BomLineQuery { + bomCode?: string + itemId?: number + bomItemCode?: string + bomItemName?: string + status?: string + enableFlag?: string + pageNum?: number + pageSize?: number +} + +/** BOM 明细列表响应 */ +export interface BomLineListResponse { + rows: BomLine[] + total: number +} + +const BOM_HEADER_BASE = '/mes/md/bom' +const BOM_LINE_BASE = '/mes/md/bom/line' + +/** 查询BOM表头列表 */ +export function listBomHeader(query?: BomHeaderQuery): Promise { + return request.get(`${BOM_HEADER_BASE}/list`, { params: query }).then((res: any) => ({ + rows: res.rows || [], + total: res.total || 0 + })) +} + +/** 查询BOM表头详情 */ +export function getBomHeader(bomId: number): Promise<{ data: BomHeader }> { + return request.get(`${BOM_HEADER_BASE}/${bomId}`) +} + +/** 新增BOM表头 */ +export function addBomHeader(data: Partial): Promise<{ data: BomHeader }> { + return request.post(BOM_HEADER_BASE, data) +} + +/** 修改BOM表头 */ +export function updateBomHeader(data: Partial): Promise<{ data: BomHeader }> { + return request.put(BOM_HEADER_BASE, data) +} + +/** 删除BOM表头 */ +export function delBomHeader(bomId: number | number[]): Promise { + const ids = Array.isArray(bomId) ? bomId.join(',') : String(bomId) + return request.delete(`${BOM_HEADER_BASE}/${ids}`) +} + +/** 查询BOM明细列表 */ +export function listBomLine(query?: BomLineQuery): Promise { + return request.get(`${BOM_LINE_BASE}/list`, { params: query }).then((res: any) => ({ + rows: res.rows || [], + total: res.total || 0 + })) +} + +/** 查询BOM明细详情 */ +export function getBomLine(lineId: number): Promise<{ data: BomLine }> { + return request.get(`${BOM_LINE_BASE}/${lineId}`) +} + +/** 新增BOM明细 */ +export function addBomLine(data: Partial): Promise<{ data: BomLine }> { + return request.post(BOM_LINE_BASE, data) +} + +/** 修改BOM明细 */ +export function updateBomLine(data: Partial): Promise<{ data: BomLine }> { + return request.put(BOM_LINE_BASE, data) +} + +/** 删除BOM明细 */ +export function delBomLine(lineId: number | number[]): Promise { + const ids = Array.isArray(lineId) ? lineId.join(',') : String(lineId) + return request.delete(`${BOM_LINE_BASE}/${ids}`) +} + +// ----------------------------- +// 兼容旧命名(当前BOM页面仍在使用) +// ----------------------------- +export type ProductBom = BomHeader +export type ProductBomQuery = BomHeaderQuery +export type ProductBomListResponse = BomHeaderListResponse +export const listProductBom = listBomHeader +export const getProductBom = getBomHeader +export const addProductBom = addBomHeader +export const updateProductBom = updateBomHeader +export const delProductBom = delBomHeader + +/** BOM状态选项 */ +export const bomStatusOptions = [ + { value: 'DRAFT', label: '草稿' }, + { value: 'APPROVED', label: '已审核' }, + { value: 'OBSOLETE', label: '已废弃' } +] + +/** 供应方式选项 */ +export const supplyTypeOptions = [ + { value: 'PURCHASE', label: '采购' }, + { value: 'PRODUCE', label: '自制' }, + { value: 'OUTSOURCE', label: '委外' } +] diff --git a/erp-frontend-vue/src/api/masterdata/item.ts b/erp-frontend-vue/src/api/masterdata/item.ts new file mode 100644 index 0000000..626e2af --- /dev/null +++ b/erp-frontend-vue/src/api/masterdata/item.ts @@ -0,0 +1,70 @@ +import request from '../request' + +export interface MdItem { + itemId?: number + itemCode?: string + itemName?: string + specification?: string + unitOfMeasure?: string + unitName?: string + itemTypeId?: number + itemTypeName?: string + itemOrProduct?: string + enableFlag?: string + safeStockFlag?: string + minStock?: number + maxStock?: number + batchFlag?: string + highValue?: string + remark?: string + createBy?: string + createTime?: string + updateBy?: string + updateTime?: string +} + +export interface MdItemQuery { + itemCode?: string + itemName?: string + itemTypeId?: number + itemOrProduct?: string + enableFlag?: string + pageNum?: number + pageSize?: number +} + +export interface MdItemListResponse { + rows: MdItem[] + total: number +} + +const BASE = '/mes/md/mditem' + +/** 查询物料列表 */ +export function listMdItem(query?: MdItemQuery): Promise { + return request.get(`${BASE}/list`, { params: query }).then((res: any) => ({ + rows: res.rows || [], + total: res.total || 0 + })) +} + +/** 查询物料详细 */ +export function getMdItem(itemId: number): Promise<{ data: MdItem }> { + return request.get(`${BASE}/${itemId}`) +} + +/** 新增物料 */ +export function addMdItem(data: Partial): Promise<{ data: MdItem }> { + return request.post(BASE, data) +} + +/** 修改物料 */ +export function updateMdItem(data: Partial): Promise<{ data: MdItem }> { + return request.put(BASE, data) +} + +/** 删除物料 */ +export function delMdItem(itemId: number | number[]): Promise { + const ids = Array.isArray(itemId) ? itemId.join(',') : String(itemId) + return request.delete(`${BASE}/${ids}`) +} diff --git a/erp-frontend-vue/src/api/masterdata/itemtype.ts b/erp-frontend-vue/src/api/masterdata/itemtype.ts new file mode 100644 index 0000000..64509ec --- /dev/null +++ b/erp-frontend-vue/src/api/masterdata/itemtype.ts @@ -0,0 +1,79 @@ +import request from '../request' + +export interface ItemType { + itemTypeId?: number + parentTypeId?: number + itemTypeCode?: string + itemTypeName?: string + orderNum?: number + enableFlag?: string + itemOrProduct?: string + remark?: string + createBy?: string + createTime?: string + updateBy?: string + updateTime?: string + children?: ItemType[] +} + +export interface ItemTypeQuery { + itemTypeCode?: string + itemTypeName?: string + enableFlag?: string +} + +const BASE = '/mes/md/itemtype' + +/** 查询物料分类列表 */ +export function listItemType(query?: ItemTypeQuery): Promise<{ data: ItemType[] }> { + return request.get(`${BASE}/list`, { params: query }) +} + +/** 查询物料分类下拉树结构 */ +export function getItemTypeTreeselect(): Promise<{ data: any[] }> { + return request.get(`${BASE}/treeselect`) +} + +/** 查询物料分类详细 */ +export function getItemType(itemTypeId: number): Promise<{ data: ItemType }> { + return request.get(`${BASE}/${itemTypeId}`) +} + +/** 新增物料分类 */ +export function addItemType(data: Partial): Promise { + return request.post(BASE, data) +} + +/** 修改物料分类 */ +export function updateItemType(data: Partial): Promise { + return request.put(BASE, data) +} + +/** 删除物料分类 */ +export function delItemType(itemTypeId: number): Promise { + return request.delete(`${BASE}/${itemTypeId}`) +} + +/** 构建分类树 */ +export function handleTree(data: ItemType[], idField = 'itemTypeId', parentField = 'parentTypeId'): ItemType[] { + const map = new Map() + const result: ItemType[] = [] + + data.forEach(item => { + map.set(item[idField as keyof ItemType] as number, { ...item, children: [] }) + }) + + data.forEach(item => { + const current = map.get(item[idField as keyof ItemType] as number)! + const parentId = item[parentField as keyof ItemType] as number + if (parentId && map.has(parentId)) { + const parent = map.get(parentId)! + parent.children = parent.children || [] + parent.children.push(current) + } else { + result.push(current) + } + }) + + return result +} diff --git a/erp-frontend-vue/src/api/masterdata/unit.ts b/erp-frontend-vue/src/api/masterdata/unit.ts new file mode 100644 index 0000000..a0c2163 --- /dev/null +++ b/erp-frontend-vue/src/api/masterdata/unit.ts @@ -0,0 +1,65 @@ +import request from '../request' + +export interface UnitMeasure { + measureId?: number + measureCode?: string + measureName?: string + primaryFlag?: string + primaryId?: number + changeRate?: number + enableFlag?: string + remark?: string + createBy?: string + createTime?: string + updateBy?: string + updateTime?: string +} + +export interface UnitMeasureQuery { + measureCode?: string + measureName?: string + enableFlag?: string + pageNum?: number + pageSize?: number +} + +export interface UnitMeasureListResponse { + rows: UnitMeasure[] + total: number +} + +const BASE = '/mes/md/unitmeasure' + +/** 查询计量单位列表 */ +export function listUnitMeasure(query?: UnitMeasureQuery): Promise { + return request.get(`${BASE}/list`, { params: query }).then((res: any) => ({ + rows: res.rows || [], + total: res.total || 0 + })) +} + +/** 查询所有计量单位(不分页) */ +export function listAllUnitMeasure(): Promise<{ data: UnitMeasure[] }> { + return request.get(`${BASE}/selectall`) +} + +/** 查询计量单位详细 */ +export function getUnitMeasure(measureId: number): Promise<{ data: UnitMeasure }> { + return request.get(`${BASE}/${measureId}`) +} + +/** 新增计量单位 */ +export function addUnitMeasure(data: Partial): Promise { + return request.post(BASE, data) +} + +/** 修改计量单位 */ +export function updateUnitMeasure(data: Partial): Promise { + return request.put(BASE, data) +} + +/** 删除计量单位 */ +export function delUnitMeasure(measureId: number | number[]): Promise { + const ids = Array.isArray(measureId) ? measureId.join(',') : String(measureId) + return request.delete(`${BASE}/${ids}`) +} diff --git a/erp-frontend-vue/src/api/masterdata/workshop.ts b/erp-frontend-vue/src/api/masterdata/workshop.ts new file mode 100644 index 0000000..2eec971 --- /dev/null +++ b/erp-frontend-vue/src/api/masterdata/workshop.ts @@ -0,0 +1,64 @@ +import request from '../request' + +export interface Workshop { + workshopId?: number + workshopCode?: string + workshopName?: string + area?: string + charge?: string + enableFlag?: string + remark?: string + createBy?: string + createTime?: string + updateBy?: string + updateTime?: string +} + +export interface WorkshopQuery { + workshopCode?: string + workshopName?: string + enableFlag?: string + pageNum?: number + pageSize?: number +} + +export interface WorkshopListResponse { + rows: Workshop[] + total: number +} + +const BASE = '/mes/md/workshop' + +/** 查询车间列表 */ +export function listWorkshop(query?: WorkshopQuery): Promise { + return request.get(`${BASE}/list`, { params: query }).then((res: any) => ({ + rows: res.rows || [], + total: res.total || 0 + })) +} + +/** 查询所有车间(不分页) */ +export function listAllWorkshop(): Promise<{ data: Workshop[] }> { + return request.get(`${BASE}/listAll`) +} + +/** 查询车间详细 */ +export function getWorkshop(workshopId: number): Promise<{ data: Workshop }> { + return request.get(`${BASE}/${workshopId}`) +} + +/** 新增车间 */ +export function addWorkshop(data: Partial): Promise { + return request.post(BASE, data) +} + +/** 修改车间 */ +export function updateWorkshop(data: Partial): Promise { + return request.put(BASE, data) +} + +/** 删除车间 */ +export function delWorkshop(workshopId: number | number[]): Promise { + const ids = Array.isArray(workshopId) ? workshopId.join(',') : String(workshopId) + return request.delete(`${BASE}/${ids}`) +} diff --git a/erp-frontend-vue/src/api/masterdata/workstation.ts b/erp-frontend-vue/src/api/masterdata/workstation.ts new file mode 100644 index 0000000..2bf71c4 --- /dev/null +++ b/erp-frontend-vue/src/api/masterdata/workstation.ts @@ -0,0 +1,68 @@ +import request from '../request' + +export interface Workstation { + workstationId?: number + workstationCode?: string + workstationName?: string + workstationAddress?: string + workshopId?: number + workshopName?: string + processId?: number + processName?: string + enableFlag?: string + remark?: string + createBy?: string + createTime?: string + updateBy?: string + updateTime?: string +} + +export interface WorkstationQuery { + workstationCode?: string + workstationName?: string + workshopId?: number + enableFlag?: string + pageNum?: number + pageSize?: number +} + +export interface WorkstationListResponse { + rows: Workstation[] + total: number +} + +const BASE = '/mes/md/workstation' + +/** 查询工作站列表 */ +export function listWorkstation(query?: WorkstationQuery): Promise { + return request.get(`${BASE}/list`, { params: query }).then((res: any) => ({ + rows: res.rows || [], + total: res.total || 0 + })) +} + +/** 查询所有工作站(不分页) */ +export function listAllWorkstation(): Promise<{ data: Workstation[] }> { + return request.get(`${BASE}/listAll`) +} + +/** 查询工作站详细 */ +export function getWorkstation(workstationId: number): Promise<{ data: Workstation }> { + return request.get(`${BASE}/${workstationId}`) +} + +/** 新增工作站 */ +export function addWorkstation(data: Partial): Promise { + return request.post(BASE, data) +} + +/** 修改工作站 */ +export function updateWorkstation(data: Partial): Promise { + return request.put(BASE, data) +} + +/** 删除工作站 */ +export function delWorkstation(workstationId: number | number[]): Promise { + const ids = Array.isArray(workstationId) ? workstationId.join(',') : String(workstationId) + return request.delete(`${BASE}/${ids}`) +} diff --git a/erp-frontend-vue/src/api/material.ts b/erp-frontend-vue/src/api/material.ts new file mode 100644 index 0000000..ef625c8 --- /dev/null +++ b/erp-frontend-vue/src/api/material.ts @@ -0,0 +1,78 @@ +import { listMdItem, getMdItem, type MdItem } from './masterdata/item' + +export interface Material { + id: number + code: string + name: string + spec?: string + unit: string + category?: string + price?: number + status: number +} + +export interface MaterialQuery { + code?: string + name?: string + category?: string + itemOrProduct?: string + page?: number + pageSize?: number +} + +export interface MaterialListResponse { + list: Material[] + total: number +} + +/** 将后端MdItem转换为前端Material */ +function convertMdItemToMaterial(item: MdItem): Material { + return { + id: item.itemId || 0, + code: item.itemCode || '', + name: item.itemName || '', + spec: item.specification, + unit: item.unitOfMeasure || item.unitName || '', + category: item.itemTypeName, + price: 0, // 物料主数据中没有价格,价格在订单中设置 + status: item.enableFlag === 'Y' ? 1 : 0 + } +} + +/** 获取物料列表 */ +export async function getMaterialList(params: MaterialQuery): Promise { + try { + const query: any = { + itemCode: params.code, + itemName: params.name, + pageNum: params.page || 1, + pageSize: params.pageSize || 100 + } + if (params.itemOrProduct) { + query.itemOrProduct = params.itemOrProduct + } + + const res = await listMdItem(query) + // 返回所有物料,让用户自行选择 + const list = res.rows.map(convertMdItemToMaterial) + + return { + list, + total: res.total + } + } catch (error) { + console.error('获取物料列表失败:', error) + return { list: [], total: 0 } + } +} + +/** 获取物料详情 */ +export async function getMaterialDetail(id: number): Promise { + try { + const res = await getMdItem(id) + return convertMdItemToMaterial(res.data) + } catch (error) { + console.error('获取物料详情失败:', error) + throw error + } +} diff --git a/erp-frontend-vue/src/api/mbom.ts b/erp-frontend-vue/src/api/mbom.ts new file mode 100644 index 0000000..166db31 --- /dev/null +++ b/erp-frontend-vue/src/api/mbom.ts @@ -0,0 +1,142 @@ +import request from './request' + +export interface Mbom { + mbomId: number + mbomCode: string + mbomDate: string + status: string + businessType: string + issueStatus?: string + planId: number + planCode: string + salesOrderId?: number + salesOrderCode?: string + deliveryDate?: string + itemId: number + itemCode: string + itemName: string + unitName?: string + supplyType?: string + quantity: number + workshopId?: number + workshopName?: string + issueDate?: string + approverName?: string + approveDate?: string + remark?: string + createTime?: string +} + +/** 物料清单明细行 */ +export interface MbomLine { + lineId: number + mbomId: number + mbomCode?: string + lineNo?: number + bomLevel?: number + itemId: number + itemCode: string + itemName: string + specification?: string + unitName?: string + baseQty?: number + quantity: number + lossRate?: number + supplyType?: string + pickType?: string + remark?: string +} + +/** 物料清单详情(表头 + 明细行) */ +export interface MbomDetail extends Mbom { + lines?: MbomLine[] +} + +export interface MbomQuery { + salesOrderCode?: string + mbomCode?: string + itemCode?: string + itemName?: string + beginDate?: string + endDate?: string + status?: string + supplyType?: string + pageNum?: number + pageSize?: number +} + +export interface MbomListResponse { + rows: Mbom[] + total: number +} + +// 获取物料清单列表 +export function getMbomList(params: MbomQuery): Promise { + return request.get('/erp/mp/mbom/list', { params }).then((res: any) => { + return { + rows: res.rows || [], + total: res.total || 0 + } + }) +} + +// 获取物料清单详情(含明细行) +export function getMbomDetail(mbomId: number): Promise { + return request.get(`/erp/mp/mbom/${mbomId}`).then((res: any) => { + const data = res.data ?? res + return data as MbomDetail + }) +} + +// 获取物料清单明细列表 +export function getMbomLines(mbomId: number): Promise { + return request.get(`/erp/mp/mbomline/mbom/${mbomId}`).then((res: any) => { + const rows = res.rows ?? res.data ?? [] + return rows as MbomLine[] + }) +} + +// 更新物料清单明细行 +export function updateMbomLine(data: Partial): Promise { + return request.put('/erp/mp/mbomline', data) +} + +// 新增物料清单 +export function createMbom(data: Partial): Promise { + return request.post('/erp/mp/mbom', data) +} + +// 更新物料清单 +export function updateMbom(data: Partial): Promise { + return request.put('/erp/mp/mbom', data) +} + +// 删除物料清单 +export function deleteMbom(mbomIds: string): Promise { + return request.delete(`/erp/mp/mbom/${mbomIds}`) +} + +// 审核物料清单 +export function approveMbom(mbomId: number): Promise { + return request.put(`/erp/mp/mbom/approve/${mbomId}`) +} + +// 反审核物料清单 +export function unapproveMbom(mbomId: number): Promise { + return request.put(`/erp/mp/mbom/unapprove/${mbomId}`) +} + +// 下发车间 +export function issueToWorkshop(mbomId: number, workshopId: number, workshopName: string): Promise { + return request.put(`/erp/mp/mbom/issue/${mbomId}/${workshopId}/${workshopName}`) +} + +// 撤销下发车间 +export function revokeIssue(mbomId: number): Promise { + return request.put(`/erp/mp/mbom/revoke-issue/${mbomId}`) +} + +// BOM运算(从生产计划) +export function calcBom(planId: number): Promise { + return request.put(`/erp/mp/plan/bom-calculate/${planId}`) +} diff --git a/erp-frontend-vue/src/api/parts.ts b/erp-frontend-vue/src/api/parts.ts new file mode 100644 index 0000000..bef85ab --- /dev/null +++ b/erp-frontend-vue/src/api/parts.ts @@ -0,0 +1,72 @@ +import request from './request' + +export interface PartsOrder { + partsId: number + partsCode: string + partsDate: string + status: string + itemId: number + itemCode: string + itemName: string + specification?: string + drawingNo?: string + unitName?: string + quantity: number + planQty?: number + completedQty?: number + remark?: string + createTime?: string +} + +export interface PartsQuery { + partsCode?: string + itemCode?: string + itemName?: string + beginDate?: string + endDate?: string + status?: string + pageNum?: number + pageSize?: number +} + +export interface PartsListResponse { + rows: PartsOrder[] + total: number +} + +// 获取零部件订单列表 +export function getPartsOrderList(params: PartsQuery): Promise { + return request.get('/erp/mp/parts/list', { params }).then((res: any) => { + return { + rows: res.rows || [], + total: res.total || 0 + } + }) +} + +// 获取零部件订单详情 +export function getPartsOrderDetail(partsId: number): Promise { + return request.get(`/erp/mp/parts/${partsId}`).then((res: any) => { + return res.data + }) +} + +// 新增零部件订单 +export function createPartsOrder(data: Partial): Promise { + return request.post('/erp/mp/parts', data) +} + +// 更新零部件订单 +export function updatePartsOrder(data: Partial): Promise { + return request.put('/erp/mp/parts', data) +} + +// 删除零部件订单 +export function deletePartsOrder(partsIds: string): Promise { + return request.delete(`/erp/mp/parts/${partsIds}`) +} + +// 审核零部件订单 +export function approvePartsOrder(partsId: number): Promise { + return request.put(`/erp/mp/parts/approve/${partsId}`) +} diff --git a/erp-frontend-vue/src/api/productionPlan.ts b/erp-frontend-vue/src/api/productionPlan.ts new file mode 100644 index 0000000..3c78367 --- /dev/null +++ b/erp-frontend-vue/src/api/productionPlan.ts @@ -0,0 +1,357 @@ +import request from './request' + +// ============ 类型定义 ============ + +/** 生产计划明细行 */ +export interface PlanLine { + lineId?: number + planId?: number + planCode?: string + lineNo?: number + salesLineId?: number + trackCode?: string + itemId?: number + itemCode?: string + itemName?: string + specification?: string + unitId?: number + unitName?: string + quantity?: number + qualityReq?: string + remark?: string + delFlag?: string +} + +/** 生产计划单主表 */ +export interface ProductionPlan { + planId?: number + planCode?: string + planDate?: string + status?: string + businessStatus?: string + businessType?: string + workType?: number + salesOrderId?: number + salesOrderCode?: string + salesUserId?: number + salesUserName?: string + deliveryDate?: string + bomId?: number + /** EBOM单号(选择BOM后显示在表头订单BOM) */ + bomCode?: string + bomVersion?: string + bomDesc?: string + totalQuantity?: number + remark?: string + operatorId?: number + operatorName?: string + approverId?: number + approverName?: string + approveDate?: string + delFlag?: string + createBy?: string + createTime?: string + updateBy?: string + updateTime?: string + lines?: PlanLine[] + slaveList?: PlanLine[] + /** 部门名称(单据视图) */ + deptName?: string + /** 操作员/业务人员(单据视图表头) */ + operatorName?: string + /** 审核日期 */ + approveDate?: string + /** 物料清单(BOM运算结果,详情页只读) */ + mbomList?: MbomLine[] + /** 补料清单(详情页只读) */ + supplementList?: SupplementLine[] +} + +/** 明细视图数据(展开物料维度) */ +export interface PlanDetailView { + lineId: number + planId: number + planCode: string + planDate: string + status: string + workType: number + salesOrderCode?: string + salesUserName?: string + deliveryDate?: string + trackCode?: string + itemId: number + itemCode: string + itemName: string + specification?: string + unitName?: string + quantity: number + /** 物料清单状态:BOM运算 / BOM运算,BOM补料 / 空 */ + mbomStatus?: string +} + +/** 物料清单行(BOM运算结果) */ +export interface MbomLine { + mbomId?: number + mbomCode?: string + mbomStatus?: string + businessType?: string + issueStatus?: string + itemCode?: string + itemName?: string + unitName?: string + supplyType?: string + productionQty?: number + workshopId?: number + workshopName?: string + issueDate?: string +} + +/** 补料清单行 */ +export interface SupplementLine { + supCode?: string + supStatus?: string + businessType?: string + issueStatus?: string + itemCode?: string + itemName?: string + unitName?: string + supplyType?: string + productionQty?: number + supReason?: string + supDate?: string + remark?: string +} + +/** 查询参数 */ +export interface PlanQuery { + salesOrderCode?: string + planCode?: string + itemCode?: string + itemName?: string + beginDate?: string + endDate?: string + workType?: number + status?: string + businessStatus?: string + pageNum?: number + pageSize?: number +} + +/** 分页响应 */ +export interface PlanListResponse { + rows: ProductionPlan[] + total: number +} + +export interface PlanDetailListResponse { + rows: PlanDetailView[] + total: number +} + +/** 引入订单数据类型 */ +export interface ImportOrder { + orderId: number + orderCode: string + orderDate: string + userId?: number + userName?: string + salesmanName?: string + clientName?: string + deliveryDate?: string + deliveryStatus?: string + businessType?: string + remark?: string + lines?: ImportOrderLine[] +} + +/** 引入订单明细 */ +export interface ImportOrderLine { + lineId: number + orderId: number + itemId: number + itemCode: string + itemName: string + specification?: string + unitOfMeasure?: string + quantity: number + qualityReq?: string + remark?: string +} + +// ============ 状态映射 ============ + +export const PLAN_STATUS_MAP: Record = { + 'PREPARE': { label: '开立', type: 'info' }, + 'DRAFT': { label: '开立', type: 'info' }, + '开立': { label: '开立', type: 'info' }, + 'APPROVED': { label: '审核', type: 'success' }, + '审核': { label: '审核', type: 'success' }, + 'CLOSED': { label: '关闭', type: 'info' } +} + +export const BUSINESS_STATUS_OPTIONS = [ + { value: 'NORMAL', label: '正常' }, + { value: 'PAUSE', label: '暂停' }, + { value: 'CANCEL', label: '取消' } +] + +export const WORK_TYPE_OPTIONS = [ + { value: 0, label: '生产计划单' }, + { value: 1, label: '零部件计划单' }, + { value: 2, label: '零部件订单' } +] + +// ============ API 接口 ============ + +const BASE = '/erp/mp/plan' + +/** 获取生产计划单列表(单据视图) */ +export function getProductionPlanList(params: PlanQuery): Promise { + return request.get(`${BASE}/list`, { params }).then((res: any) => { + const rows = res.rows ?? res.data?.rows ?? [] + const total = res.total ?? res.data?.total ?? 0 + return { rows, total } + }) +} + +/** 获取生产计划单明细列表(明细视图,展开物料维度) */ +export function getProductionPlanDetailList(params: PlanQuery): Promise { + return request.get(`${BASE}/lineList`, { params }).then((res: any) => { + const rows = res.rows ?? res.data?.rows ?? [] + const total = res.total ?? res.data?.total ?? 0 + return { rows, total } + }) +} + +/** 获取生产计划单详情(含明细行) */ +export function getProductionPlanDetail(planId: number): Promise { + return request.get(`${BASE}/${planId}`).then((res: any) => { + const data = res.data ?? res + // 统一明细字段名 + if (data.slaveList && !data.lines) { + data.lines = data.slaveList + } + return data + }) +} + +/** 新增生产计划单 */ +export function createProductionPlan(data: Partial): Promise<{ planId: number }> { + const payload = { ...data } + if (payload.lines) { + payload.slaveList = payload.lines + delete payload.lines + } + return request.post(BASE, payload).then((res: any) => res.data ?? res) +} + +/** 更新生产计划单 */ +export function updateProductionPlan(data: Partial): Promise { + const payload = { ...data } + if (payload.lines) { + payload.slaveList = payload.lines + delete payload.lines + } + return request.put(BASE, payload) +} + +/** 删除生产计划单(支持批量) */ +export function deleteProductionPlan(planIds: number | number[] | string): Promise { + const ids = Array.isArray(planIds) ? planIds.join(',') : String(planIds) + return request.delete(`${BASE}/${ids}`) +} + +/** 审核生产计划单(支持批量) */ +export function approveProductionPlan(planIds: number | number[]): Promise { + const ids = Array.isArray(planIds) ? planIds.join(',') : String(planIds) + return request.put(`${BASE}/approve/${ids}`) +} + +/** 反审核生产计划单(支持批量) */ +export function unapproveProductionPlan(planIds: number | number[]): Promise { + const ids = Array.isArray(planIds) ? planIds.join(',') : String(planIds) + return request.put(`${BASE}/reject/${ids}`) +} + +/** 导出生产计划单 */ +export function exportProductionPlan(params: PlanQuery): Promise { + return request.get(`${BASE}/export`, { + params, + responseType: 'blob' + }) +} + +/** BOM运算 */ +export function bomCalculate(planId: number): Promise { + return request.post(`/erp/mp/mbom/calc`, { planId }) +} + +// ============ 引入订单相关 ============ + +/** 获取可引入的销售订单列表(已审核的订单) */ +export function getImportOrderList(params?: { + orderCode?: string + itemCode?: string + itemName?: string + beginDate?: string + endDate?: string +}): Promise { + return request.get('/erp/sl/order/list', { + params: { + ...params, + orderStatus: '审核', + pageNum: 1, + pageSize: 100 + } + }).then((res: any) => { + const rows = res.rows ?? res.data?.rows ?? [] + // 转换字段名 + return rows.map((row: any) => ({ + orderId: row.orderId, + orderCode: row.orderCode, + orderDate: row.orderDate, + userId: row.salesmanId, + userName: row.salesmanName, + salesmanName: row.salesmanName, + clientName: row.clientName, + deliveryDate: row.deliveryDate, + deliveryStatus: row.deliveryDate && new Date(row.deliveryDate) < new Date() ? '超期' : '预计', + businessType: '销售订单', + remark: row.remark + })) + }) +} + +/** 获取销售订单详情(用于引入订单明细) */ +export function getImportOrderDetail(orderId: number): Promise { + return request.get(`/erp/sl/order/${orderId}`).then((res: any) => { + const data = res.data ?? res + const lines = data.lines ?? data.slaveList ?? [] + const salesPersonName = data.salesmanName ?? data.salesUserName ?? '' + return { + orderId: data.orderId, + orderCode: data.orderCode, + orderDate: data.orderDate, + userId: data.salesmanId ?? data.salesUserId, + userName: salesPersonName, + salesmanName: salesPersonName, + clientName: data.clientName, + deliveryDate: data.deliveryDate, + deliveryStatus: data.deliveryDate && new Date(data.deliveryDate) < new Date() ? '超期' : '预计', + businessType: '销售订单', + remark: data.remark, + lines: lines.map((line: any) => ({ + lineId: line.lineId, + orderId: line.orderId, + itemId: line.itemId, + itemCode: line.itemCode, + itemName: line.itemName, + specification: line.specification, + unitOfMeasure: line.unitOfMeasure, + quantity: line.quantity, + qualityReq: line.qualityReq, + remark: line.remark + })) + } + }) +} diff --git a/erp-frontend-vue/src/api/purchaseInvoice.ts b/erp-frontend-vue/src/api/purchaseInvoice.ts new file mode 100644 index 0000000..95c821d --- /dev/null +++ b/erp-frontend-vue/src/api/purchaseInvoice.ts @@ -0,0 +1,70 @@ +import request from './request' + +export interface PurchaseInvoice { + invoiceId: number + invoiceCode: string + invoiceDate: string + status: string + financeStatus: string + businessType: string + supplierId: number + supplierName: string + invoiceNo?: string + invoiceAmount: number + taxRate?: number + taxAmount?: number + totalAmount?: number + remark?: string + operatorName?: string + approverName?: string + approveDate?: string + createTime?: string +} + +export interface PurchaseInvoiceQuery { + invoiceCode?: string + supplierName?: string + invoiceNo?: string + beginDate?: string + endDate?: string + status?: string + pageNum?: number + pageSize?: number +} + +export interface PurchaseInvoiceListResponse { + rows: PurchaseInvoice[] + total: number +} + +// 获取采购发票列表 +export function getPurchaseInvoiceList(params: PurchaseInvoiceQuery): Promise { + return request.get('/erp/po/invoice/list', { params }).then((res: any) => { + return { + rows: res.rows || [], + total: res.total || 0 + } + }) +} + +// 获取采购发票详情 +export function getPurchaseInvoiceDetail(invoiceId: number): Promise { + return request.get(`/erp/po/invoice/${invoiceId}`).then((res: any) => { + return res.data + }) +} + +// 新增采购发票 +export function createPurchaseInvoice(data: Partial): Promise { + return request.post('/erp/po/invoice', data) +} + +// 更新采购发票 +export function updatePurchaseInvoice(data: Partial): Promise { + return request.put('/erp/po/invoice', data) +} + +// 删除采购发票 +export function deletePurchaseInvoice(invoiceIds: string): Promise { + return request.delete(`/erp/po/invoice/${invoiceIds}`) +} diff --git a/erp-frontend-vue/src/api/purchaseNeed.ts b/erp-frontend-vue/src/api/purchaseNeed.ts new file mode 100644 index 0000000..f4eaab9 --- /dev/null +++ b/erp-frontend-vue/src/api/purchaseNeed.ts @@ -0,0 +1,60 @@ +import request from './request' + +export interface PurchaseNeedRow { + purchaseId: number + salesOrderCode: string + planCode: string + purchaseCode: string + itemCode: string + itemName: string + demandQty: number + availableQty: number + purchaseQty: number + orderedQty: number + unorderedQty: number // = purchaseQty - orderedQty (backend returns in totalQuantity) + purchaseDate: string + remark?: string +} + +export interface PurchaseNeedQuery { + salesOrderCode?: string + itemCode?: string + itemName?: string + businessType?: string + beginDate?: string + endDate?: string + pageNum?: number + pageSize?: number +} + +export interface PurchaseNeedListResponse { + rows: PurchaseNeedRow[] + total: number +} + +export interface PurchaseNeedSummary { + totalPurchaseQty: number + totalOrderedQty: number + totalUnorderedQty: number +} + +// 查询采购计划需求列表 +export function getPurchaseNeedList(params: PurchaseNeedQuery): Promise { + return request.get('/erp/mp/purchase/need/list', { params }).then((res: any) => { + const rows = (res.rows || []).map((r: any) => ({ + ...r, + unorderedQty: r.totalQuantity // backend puts unordered qty in totalQuantity field + })) + return { rows, total: res.total || 0 } + }) +} + +// 查询采购计划需求汇总 +export function getPurchaseNeedSummary(params: PurchaseNeedQuery): Promise { + return request.get('/erp/mp/purchase/need/summary', { params }).then((res: any) => res.data || {}) +} + +// 导出采购计划需求表 +export function exportPurchaseNeed(params: PurchaseNeedQuery): Promise { + return request.post('/erp/mp/purchase/need/export', params, { responseType: 'blob' }) +} diff --git a/erp-frontend-vue/src/api/purchaseOrder.ts b/erp-frontend-vue/src/api/purchaseOrder.ts new file mode 100644 index 0000000..8888d50 --- /dev/null +++ b/erp-frontend-vue/src/api/purchaseOrder.ts @@ -0,0 +1,237 @@ +import request from './request' + +export interface PurchaseOrderLine { + lineId: number + orderId: number + orderCode: string + lineNo: number + trackCode?: string + planCode?: string + itemId: number + itemCode: string + itemName: string + specification?: string + unitName?: string + needDate?: string + quantity: number + unitPrice?: number + amount?: number + arrivedQuantity?: number + remark?: string +} + +export interface PurchaseOrder { + orderId: number + orderCode: string + orderDate: string + status: string + businessStatus?: string + businessType: string + orderType: string + materialNeed: string + supplierId?: number + supplierCode?: string + supplierName?: string + deptId?: number + deptName?: string + userId?: number + userName?: string + deliveryDate?: string + contractNo?: string + contractFile?: string + totalQuantity?: number + totalAmount?: number + arrivedQuantity?: number + remark?: string + operatorId?: number + operatorName?: string + approverId?: number + approverName?: string + approveDate?: string + lines: PurchaseOrderLine[] + createTime?: string + // Detail-view join fields + trackCode?: string + planCode?: string + itemCode?: string + itemName?: string + needDate?: string + quantity?: number + lineArrivedQuantity?: number +} + +/** 供应商(采购订单用,数据来源:md_vendor) */ +export interface PoSupplier { + supplierId: number + supplierCode?: string + supplierName: string + supplierNick?: string + tel?: string + contact1?: string + contact1Tel?: string + enableFlag?: string +} + +/** 引入弹窗行(采购计划明细) */ +export interface LeadInRow { + purchaseId: number + purchaseCode: string + salesOrderCode?: string + salesUserName?: string + deliveryDate?: string + itemId: number + itemCode: string + itemName: string + specification?: string + unitName?: string + demandDate?: string + purchaseQty: number + orderedQty: number + unpurchasedQty?: number +} + +export interface PurchaseOrderQuery { + trackCode?: string + orderCode?: string + supplierName?: string + itemCode?: string + itemName?: string + beginDate?: string + endDate?: string + status?: string + pageNum?: number + pageSize?: number +} + +export interface PurchaseOrderListResponse { + rows: PurchaseOrder[] + total: number +} + +// 获取采购订单列表 +export function getPurchaseOrderList(params: PurchaseOrderQuery): Promise { + return request.get('/erp/po/order/list', { params }).then((res: any) => { + return { + rows: res.rows || [], + total: res.total || 0 + } + }) +} + +// 获取采购订单详情 +export function getPurchaseOrderDetail(orderId: number): Promise { + return request.get(`/erp/po/order/${orderId}`).then((res: any) => { + return res.data + }) +} + +// 新增采购订单 +export function createPurchaseOrder(data: Partial): Promise { + return request.post('/erp/po/order', data) +} + +// 更新采购订单 +export function updatePurchaseOrder(data: Partial): Promise { + return request.put('/erp/po/order', data) +} + +// 删除采购订单 +export function deletePurchaseOrder(orderIds: string): Promise { + return request.delete(`/erp/po/order/${orderIds}`) +} + +// 获取采购订单单据视图列表 +export function getPurchaseOrderDocList(params: PurchaseOrderQuery): Promise { + return request.get('/erp/po/order/docList', { params }).then((res: any) => ({ + rows: res.rows || [], + total: res.total || 0 + })) +} + +// 获取采购订单汇总 +export function getPurchaseOrderSummary(params: PurchaseOrderQuery): Promise<{ totalOrderQty: number; totalArrivedQty: number }> { + return request.get('/erp/po/order/summary', { params }).then((res: any) => res.data || {}) +} + +// 生成采购订单编码(后端返回的编码在 data 中,msg 为「操作成功」) +export function genPurchaseOrderCode(): Promise { + return request.get('/erp/po/order/genCode').then((res: any) => res.data ?? res.msg ?? '') +} + +// 审核采购订单 +export function approvePurchaseOrder(orderIds: string): Promise { + return request.put(`/erp/po/order/approve/${orderIds}`) +} + +// 反审核采购订单 +export function unapprovePurchaseOrder(orderIds: string): Promise { + return request.put(`/erp/po/order/unapprove/${orderIds}`) +} + +// 导出采购订单 +export function exportPurchaseOrder(params: PurchaseOrderQuery): Promise { + return request.post('/erp/po/order/export', params, { responseType: 'blob' }) +} + +// ============ 供应商 API(使用 MOM md_vendor,表 erp_po_supplier 在 my_mes 中未建) ============ + +export function getPoSupplierList(params?: { supplierName?: string; contact1?: string; pageNum?: number; pageSize?: number }): Promise<{ rows: PoSupplier[]; total: number }> { + const { supplierName, contact1, pageNum = 1, pageSize = 200, ...rest } = params ?? {} + return request.get('/mes/md/vendor/list', { + params: { enableFlag: 'Y', pageNum, pageSize, vendorName: supplierName, contact1, ...rest } + }).then((res: any) => { + const rows = (res.rows ?? []).map((v: any) => ({ + supplierId: v.vendorId, + supplierCode: v.vendorCode, + supplierName: v.vendorName ?? '', + supplierNick: v.vendorNick, + tel: v.tel, + contact1: v.contact1, + contact1Tel: v.contact1Tel, + enableFlag: v.enableFlag + })) + return { rows, total: res.total ?? 0 } + }) +} + +// ============ 引入采购计划单明细 API ============ + +export function getLeadInList(params?: { salesOrderCode?: string; itemCode?: string; itemName?: string; businessType?: string; pageNum?: number; pageSize?: number }): Promise<{ rows: LeadInRow[]; total: number }> { + return request.get('/erp/po/order/leadIn', { params: { pageNum: 1, pageSize: 200, ...params } }).then((res: any) => { + const rows = (res.rows ?? []).map((r: any) => ({ + ...r, + unpurchasedQty: (r.purchaseQty ?? 0) - (r.orderedQty ?? 0) + })) + return { rows, total: res.total ?? 0 } + }) +} + +// ============ 状态映射 ============ + +export const STATUS_MAP: Record = { + PREPARE: { label: '开立', type: 'info' }, + DRAFT: { label: '开立', type: 'info' }, + APPROVED: { label: '审核', type: 'success' }, + CLOSED: { label: '关闭', type: '' }, + REJECTED: { label: '退回', type: 'warning' } +} + +export const BUSINESS_STATUS_MAP: Record = { + NORMAL: '正常', COMPLETED: '完成', PAUSE: '暂停', CANCEL: '取消' +} + +export const BUSINESS_TYPE_OPTIONS = [ + { value: '原材料', label: '原材料' }, + { value: '零部件', label: '零部件' }, + { value: '装配件', label: '装配件' }, + { value: '成品', label: '成品' }, + { value: '五金件', label: '五金件' }, + { value: '包装物', label: '包装物' } +] + +export const BUSINESS_STATUS_OPTIONS = [ + { value: 'NORMAL', label: '正常' }, + { value: 'COMPLETED', label: '完成' }, + { value: 'PAUSE', label: '暂停' }, + { value: 'CANCEL', label: '取消' } +] diff --git a/erp-frontend-vue/src/api/purchasePlan.ts b/erp-frontend-vue/src/api/purchasePlan.ts new file mode 100644 index 0000000..8720493 --- /dev/null +++ b/erp-frontend-vue/src/api/purchasePlan.ts @@ -0,0 +1,344 @@ +import request from './request' + +// ============ Type Definitions ============ + +/** Purchase plan line item */ +export interface PurchasePlanLine { + purchaseId?: number + purchaseCode?: string + lineNo?: number + itemId?: number + itemCode: string + itemName: string + specification?: string + unitName?: string + demandDate?: string + demandQty?: number + availableQty?: number + purchaseQty: number + orderedQty?: number + remark?: string +} + +/** Purchase plan flat row (detail view) */ +export interface PurchasePlan { + purchaseId: number + purchaseCode: string + purchaseDate: string + status: string + needType: number + businessType?: string + businessStatus?: string + planId?: number + planCode?: string + salesOrderId?: number + salesOrderCode?: string + salesUserName?: string + deliveryDate?: string + itemId?: number + itemCode?: string + itemName?: string + specification?: string + unitName?: string + totalQuantity?: number + demandQty?: number + availableQty?: number + purchaseQty: number + orderedQty?: number + remark?: string + deptId?: number + deptName?: string + operatorId?: number + operatorName?: string + operatorName2?: string + approverId?: number + approverName?: string + approveDate?: string + createTime?: string +} + +/** Purchase plan detail (header + lines from /detail/{id} endpoint) */ +export interface PurchasePlanDetail { + purchaseId?: number + purchaseCode: string + purchaseDate: string + status: string + businessType?: string + businessStatus?: string + needType: number + planId?: number + planCode?: string + salesOrderId?: number + salesOrderCode?: string + salesUserName?: string + deliveryDate?: string + headerItemCode?: string + headerItemName?: string + unitName?: string + totalQuantity?: number + deptId?: number + deptName?: string + operatorId?: number + operatorName?: string + operatorName2?: string + approverId?: number + approverName?: string + approveDate?: string + remark?: string + lines: PurchasePlanLine[] +} + +/** List query params */ +export interface PurchasePlanQuery { + salesOrderCode?: string + purchaseCode?: string + itemCode?: string + itemName?: string + needType?: number + status?: string + businessStatus?: string + 'params[beginDate]'?: string + 'params[endDate]'?: string + 'params[itemTypeId]'?: number | string + pageNum?: number + pageSize?: number +} + +/** Summary response */ +export interface PurchasePlanSummary { + totalPurchaseQty: number + totalOrderedQty: number +} + +/** Import dialog - production plan parent row */ +export interface ImportPlanRow { + planId: number + planCode: string + salesOrderCode?: string + salesUserName?: string + deliveryDate?: string + itemCode?: string + itemName?: string + unitName?: string + quantity?: number + children?: ImportPlanMaterial[] +} + +/** Import dialog - material detail child row */ +export interface ImportPlanMaterial { + mbomId?: number + /** Stable unique key for table row-key (mbomId or composite fallback) */ + materialKey?: string + planId?: number + planCode?: string + salesOrderCode?: string + salesUserName?: string + deliveryDate?: string + itemId?: number + itemCode: string + itemName: string + specification?: string + unitName?: string + demandQty: number + availableQty?: number + purchasedQty?: number + unpurchasedQty?: number + remark?: string +} + +// ============ Status Maps ============ + +export const STATUS_MAP: Record = { + 'PREPARE': { label: '开立', type: 'info' }, + 'DRAFT': { label: '开立', type: 'info' }, + 'APPROVED': { label: '审核', type: 'success' }, + 'REJECTED': { label: '退回', type: 'warning' } +} + +export const STATUS_OPTIONS = [ + { value: 'PREPARE', label: '开立' }, + { value: 'APPROVED', label: '审核' }, + { value: 'REJECTED', label: '退回' } +] + +export const BUSINESS_STATUS_OPTIONS = [ + { value: 'NORMAL', label: '正常' }, + { value: 'PAUSE', label: '暂停' }, + { value: 'CANCEL', label: '取消' }, + { value: 'COMPLETED', label: '完成' } +] + +export const BUSINESS_STATUS_MAP: Record = { + 'NORMAL': '正常', + 'PAUSE': '暂停', + 'CANCEL': '取消', + 'COMPLETED': '完成' +} + +export const BUSINESS_TYPE_OPTIONS = [ + { value: '原材料', label: '原材料' }, + { value: '零部件', label: '零部件' }, + { value: '装配件', label: '装配件' }, + { value: '成品', label: '成品' }, + { value: '五金件', label: '五金件' }, + { value: '包装物', label: '包装物' } +] + +export const NEED_TYPE_OPTIONS = [ + { value: 0, label: '订单用料' }, + { value: 1, label: '库存备料' } +] + +export function isEditableStatus(status: string): boolean { + return status === 'PREPARE' || status === 'DRAFT' || status === 'REJECTED' +} + +export function isApprovedStatus(status: string): boolean { + return status === 'APPROVED' +} + +// ============ API Functions ============ + +const BASE = '/erp/mp/purchase' + +export function getPurchasePlanList(params: PurchasePlanQuery) { + return request.get(`${BASE}/list`, { params }).then((res: any) => ({ + rows: (res.rows ?? []) as PurchasePlan[], + total: (res.total ?? 0) as number + })) +} + +export function getPurchasePlanDocList(params: PurchasePlanQuery) { + return request.get(`${BASE}/docList`, { params }).then((res: any) => ({ + rows: (res.rows ?? []) as PurchasePlan[], + total: (res.total ?? 0) as number + })) +} + +export function getPurchasePlanSummary(params: PurchasePlanQuery) { + return request.get(`${BASE}/summary`, { params }).then((res: any) => { + const d = res.data ?? res + return { totalPurchaseQty: d.totalPurchaseQty ?? 0, totalOrderedQty: d.totalOrderedQty ?? 0 } as PurchasePlanSummary + }) +} + +export function getPurchasePlanDetail(idOrCode: number | string) { + return request.get(`${BASE}/detail/${idOrCode}`).then((res: any) => { + const d = res.data ?? res + if (!d.lines) d.lines = d.slaveList ?? d.details ?? [] + return d as PurchasePlanDetail + }) +} + +export function getPurchasePlanById(purchaseId: number) { + return request.get(`${BASE}/${purchaseId}`).then((res: any) => (res.data ?? res) as PurchasePlan) +} + +export function createPurchasePlan(data: Partial) { + return request.post(BASE, data).then((res: any) => res.data ?? res) +} + +export function updatePurchasePlan(data: Partial) { + return request.put(BASE, data) +} + +export function deletePurchasePlan(purchaseIds: string) { + return request.delete(`${BASE}/${purchaseIds}`) +} + +export function deletePurchasePlanByCode(purchaseCode: string) { + return request.delete(`${BASE}/byCode/${purchaseCode}`) +} + +export function approvePurchasePlan(purchaseIds: string | number) { + return request.put(`${BASE}/approve/${purchaseIds}`) +} + +export function unapprovePurchasePlan(purchaseIds: string | number) { + return request.put(`${BASE}/unapprove/${purchaseIds}`) +} + +export function genPurchaseCode() { + return request.get(`${BASE}/genCode`).then((res: any) => (res.data ?? res.msg ?? '') as string) +} + +export function exportPurchasePlan(params: PurchasePlanQuery) { + return request.post(`${BASE}/export`, params, { responseType: 'blob' }) +} + +export function generateFromMbom(mbomId: number) { + return request.post(`${BASE}/generate/${mbomId}`) +} + +// ============ Import Dialog APIs ============ + +export function getImportPlanList(params?: { + salesOrderCode?: string + itemCode?: string + itemName?: string + beginDate?: string + endDate?: string +}) { + const q: any = { status: 'APPROVED', pageNum: 1, pageSize: 200 } + if (params?.salesOrderCode) q.salesOrderCode = params.salesOrderCode + if (params?.itemCode) q.itemCode = params.itemCode + if (params?.itemName) q.itemName = params.itemName + if (params?.beginDate) q['params[beginDate]'] = params.beginDate + if (params?.endDate) q['params[endDate]'] = params.endDate + + return request.get('/erp/mp/plan/list', { params: q }).then((res: any) => { + const rows = res.rows ?? res.data?.rows ?? [] + return rows.map((row: any) => ({ + planId: row.planId, + planCode: row.planCode, + salesOrderCode: row.salesOrderCode, + salesUserName: row.salesUserName, + deliveryDate: row.deliveryDate, + itemCode: row.itemCode, + itemName: row.itemName, + unitName: row.unitName, + quantity: row.totalQuantity ?? row.quantity, + children: [] + })) as ImportPlanRow[] + }) +} + +export function getImportPlanMaterials(planId: number) { + // 查询该生产计划下的MBOM明细行(子件物料),不再过滤supplyType以展示所有物料 + return request.get(`/erp/mp/mbomline/plan/${planId}`).then((res: any) => { + const rows = res.data ?? res.rows ?? [] + return rows.map((row: any, index: number) => { + const lineId = row.lineId ?? row.mbomId + const materialKey = + lineId != null ? String(lineId) : `plan_${planId}_${row.itemId ?? row.itemCode ?? index}_${index}` + const demandQty = row.quantity ?? 0 + const purchasedQty = row.purchasedQty ?? row.orderedQty ?? 0 + return { + mbomId: lineId, + materialKey, + planId, + planCode: row.planCode ?? row.mbomCode, + itemId: row.itemId, + itemCode: row.itemCode, + itemName: row.itemName, + specification: row.specification, + unitName: row.unitName, + demandQty, + availableQty: row.availableQty ?? row.stockQty ?? 0, + purchasedQty, + unpurchasedQty: row.unpurchasedQty ?? (demandQty - purchasedQty), + remark: row.remark ?? '' + } + }) as ImportPlanMaterial[] + }) +} + +export function getItemTypeList() { + return request.get('/mes/md/itemtype/list', { params: { pageNum: 1, pageSize: 100 } }).then((res: any) => { + const rows = res.rows ?? res.data?.rows ?? (Array.isArray(res.data) ? res.data : []) + return rows.map((row: any) => ({ + itemTypeId: row.itemTypeId, + itemTypeName: row.itemTypeName + })) as Array<{ itemTypeId: number; itemTypeName: string }> + }) +} diff --git a/erp-frontend-vue/src/api/rd/ebom.ts b/erp-frontend-vue/src/api/rd/ebom.ts new file mode 100644 index 0000000..7524383 --- /dev/null +++ b/erp-frontend-vue/src/api/rd/ebom.ts @@ -0,0 +1,227 @@ +import request from '../request' + +// ==================== 类型定义 ==================== + +/** EBOM 表头 */ +export interface EbomHeader { + bomId?: number + bomCode?: string + bomName?: string + bomDate?: string + version?: string + versionDesc?: string + status?: string + businessType?: string + businessStatus?: string + drawingNo?: string + itemId?: number + itemCode?: string + itemName?: string + itemSpec?: string + unitName?: string + baseQty?: number + operatorId?: number + operatorName?: string + deptId?: number + deptName?: string + approverId?: number + approverName?: string + approveDate?: string + enableFlag?: string + remark?: string + tenantId?: string + delFlag?: string + createBy?: string + createTime?: string + updateBy?: string + updateTime?: string + /** 明细行 (表单提交时携带) */ + lines?: EbomLine[] +} + +/** EBOM 明细行 */ +export interface EbomLine { + lineId?: number + bomId?: number + bomCode?: string + lineNo?: number + bomItemId?: number + bomItemCode?: string + bomItemName?: string + bomItemSpec?: string + unitOfMeasure?: string + unitName?: string + itemOrProduct?: string + quantity?: number + numerator?: number + denominator?: number + lossRate?: number + supplyType?: string + pickType?: string + usageType?: string + routeId?: number + routeName?: string + drawingNo?: string + remark?: string + tenantId?: string + delFlag?: string + /** 前端临时字段 */ + _isNew?: boolean +} + +/** 查询参数 */ +export interface EbomQuery { + bomCode?: string + itemCode?: string + itemName?: string + itemTypeId?: number + status?: string + businessStatus?: string + beginDate?: string + endDate?: string + workType?: number + pageNum?: number + pageSize?: number +} + +/** 列表响应 */ +export interface EbomListResponse { + rows: EbomHeader[] + total: number +} + +// ==================== 常量定义 ==================== + +/** 单据状态映射 */ +export const STATUS_MAP: Record = { + DRAFT: { label: '开立', type: 'info' }, + APPROVED: { label: '审核', type: 'success' }, + REJECTED: { label: '退回', type: 'warning' } +} + +/** 单据状态选项 */ +export const STATUS_OPTIONS = [ + { value: 'DRAFT', label: '开立' }, + { value: 'APPROVED', label: '审核' }, + { value: 'REJECTED', label: '退回' } +] + +/** 业务状态选项 */ +export const BIZ_STATUS_OPTIONS = [ + { value: 'NORMAL', label: '正常' }, + { value: 'PAUSE', label: '暂停' }, + { value: 'CANCEL', label: '取消' } +] + +/** 业务类型选项 */ +export const BIZ_TYPE_OPTIONS = [ + { value: 'MECHANICAL', label: '机械BOM' }, + { value: 'ELECTRICAL', label: '电气BOM' }, + { value: 'ASSEMBLY', label: '装配BOM' }, + { value: 'GENERAL', label: '通用BOM' } +] + +/** 领料方式选项 */ +export const PICK_TYPE_OPTIONS = [ + { value: 'ORDER', label: '按单领用' }, + { value: 'DIRECT', label: '直接领用' } +] + +/** 用量方式选项 */ +export const USAGE_TYPE_OPTIONS = [ + { value: 'RATIO', label: '按比例' }, + { value: 'FIXED', label: '固定用量' } +] + +/** 供应方式选项 */ +export const SUPPLY_TYPE_OPTIONS = [ + { value: 'PURCHASE', label: '采购' }, + { value: 'PRODUCE', label: '生产' }, + { value: 'OUTSOURCE', label: '委外' }, + { value: 'ASSEMBLY', label: '装配' }, + { value: 'PROCESS', label: '加工' } +] + +// ==================== API 函数 ==================== + +const BASE = '/mes/md/bom' +const LINE_BASE = '/mes/md/bom/line' + +/** 查询EBOM列表(单据视图) */ +export function listEbom(query?: EbomQuery): Promise { + return request.get(`${BASE}/list`, { params: query }).then((res: any) => ({ + rows: res.rows || [], + total: res.total || 0 + })) +} + +/** 查询EBOM列表(明细视图 - 含物料信息的展开列表) */ +export function listEbomDetail(query?: EbomQuery): Promise { + return request.get(`${BASE}/list`, { params: query }).then((res: any) => ({ + rows: res.rows || [], + total: res.total || 0 + })) +} + +/** 获取EBOM详情 */ +export function getEbom(bomId: number): Promise<{ data: EbomHeader }> { + return request.get(`${BASE}/${bomId}`) +} + +/** 新增EBOM */ +export function addEbom(data: Partial): Promise<{ data: EbomHeader }> { + return request.post(BASE, data) +} + +/** 修改EBOM */ +export function updateEbom(data: Partial): Promise<{ data: EbomHeader }> { + return request.put(BASE, data) +} + +/** 删除EBOM */ +export function deleteEbom(ids: number | number[]): Promise { + const idStr = Array.isArray(ids) ? ids.join(',') : String(ids) + return request.delete(`${BASE}/${idStr}`) +} + +/** 审核EBOM */ +export function approveEbom(bomId: number): Promise { + return request.put(`${BASE}/approve`, { bomId }) +} + +/** 反审核EBOM */ +export function unapproveEbom(bomId: number): Promise { + return request.put(`${BASE}/reject`, { bomId }) +} + +/** 查询BOM明细列表 */ +export function listEbomLines(query?: { bomCode?: string; pageNum?: number; pageSize?: number }): Promise<{ rows: EbomLine[]; total: number }> { + return request.get(`${LINE_BASE}/list`, { params: query }).then((res: any) => ({ + rows: res.rows || [], + total: res.total || 0 + })) +} + +/** 新增BOM明细 */ +export function addEbomLine(data: Partial): Promise<{ data: EbomLine }> { + return request.post(LINE_BASE, data) +} + +/** 修改BOM明细 */ +export function updateEbomLine(data: Partial): Promise<{ data: EbomLine }> { + return request.put(LINE_BASE, data) +} + +/** 删除BOM明细 */ +export function deleteEbomLine(ids: number | number[]): Promise { + const idStr = Array.isArray(ids) ? ids.join(',') : String(ids) + return request.delete(`${LINE_BASE}/${idStr}`) +} + +/** 导出EBOM列表(GET,后端与 POST /export 行为一致) */ +export function exportEbom(query?: EbomQuery): Promise { + return request.get(`${BASE}/export`, { + params: query, + responseType: 'blob' + }) +} diff --git a/erp-frontend-vue/src/api/reject.ts b/erp-frontend-vue/src/api/reject.ts new file mode 100644 index 0000000..4470d22 --- /dev/null +++ b/erp-frontend-vue/src/api/reject.ts @@ -0,0 +1,91 @@ +import request from './request' + +export interface RejectLine { + lineId: number + rejectId: number + rejectCode: string + lineNo: number + checkinLineId?: number + trackCode?: string + itemId: number + itemCode: string + itemName: string + specification?: string + unitName?: string + quantity: number + outQuantity?: number + returnQuantity?: number + reason?: string + remark?: string +} + +export interface Reject { + rejectId: number + rejectCode: string + rejectDate: string + status: string + businessType: string + supplierId: number + supplierName: string + checkinId?: number + checkinCode?: string + totalQuantity?: number + outQuantity?: number + returnQuantity?: number + reason?: string + remark?: string + operatorName?: string + approverName?: string + approveDate?: string + lines: RejectLine[] + createTime?: string +} + +export interface RejectQuery { + trackCode?: string + rejectCode?: string + itemCode?: string + itemName?: string + beginDate?: string + endDate?: string + status?: string + pageNum?: number + pageSize?: number +} + +export interface RejectListResponse { + rows: Reject[] + total: number +} + +// 获取采购退货单列表 +export function getRejectList(params: RejectQuery): Promise { + return request.get('/erp/po/reject/list', { params }).then((res: any) => { + return { + rows: res.rows || [], + total: res.total || 0 + } + }) +} + +// 获取采购退货单详情 +export function getRejectDetail(rejectId: number): Promise { + return request.get(`/erp/po/reject/${rejectId}`).then((res: any) => { + return res.data + }) +} + +// 新增采购退货单 +export function createReject(data: Partial): Promise { + return request.post('/erp/po/reject', data) +} + +// 更新采购退货单 +export function updateReject(data: Partial): Promise { + return request.put('/erp/po/reject', data) +} + +// 删除采购退货单 +export function deleteReject(rejectIds: string): Promise { + return request.delete(`/erp/po/reject/${rejectIds}`) +} diff --git a/erp-frontend-vue/src/api/request.ts b/erp-frontend-vue/src/api/request.ts new file mode 100644 index 0000000..d938132 --- /dev/null +++ b/erp-frontend-vue/src/api/request.ts @@ -0,0 +1,105 @@ +import axios from 'axios' +import { ElMessage, ElMessageBox } from 'element-plus' +import { getToken, removeToken } from '@/utils/auth' +import router from '@/router' + +// 是否显示重新登录弹窗 +let isReloginShowing = false + +const request = axios.create({ + baseURL: '', + timeout: 30000 +}) + +// 请求拦截器 +request.interceptors.request.use( + (config) => { + // 根据URL前缀动态设置baseURL + // 支持 /mes/*、/erp/* 和 /system/* 三种API地址 + if (config.url) { + if (config.url.startsWith('/mes/')) { + // /mes/* 接口直接使用,不添加额外前缀 + // 保持原样 + } else if (config.url.startsWith('/erp/')) { + // /erp/* 接口直接使用,不添加额外前缀 + // 保持原样 + } else if (config.url.startsWith('/system/')) { + // /system/* 接口直接使用,不添加额外前缀(组织架构模块) + // 保持原样 + } else { + // 其他接口默认添加 /erp 前缀 + config.url = '/erp' + config.url + } + } + + // 添加 Token 到请求头 + const token = getToken() + if (token) { + config.headers = config.headers || {} + config.headers['Authorization'] = `Bearer ${token}` + } + + return config + }, + (error) => { + return Promise.reject(error) + } +) + +// 响应拦截器 +request.interceptors.response.use( + (response) => { + const res = response.data + const code = res.code || 200 + + // 处理 401 未授权 + if (code === 401) { + if (!isReloginShowing) { + isReloginShowing = true + ElMessageBox.confirm( + '登录状态已过期,您可以继续留在该页面,或者重新登录', + '系统提示', + { + confirmButtonText: '重新登录', + cancelButtonText: '取消', + type: 'warning' + } + ).then(() => { + isReloginShowing = false + removeToken() + router.push('/login') + }).catch(() => { + isReloginShowing = false + }) + } + return Promise.reject(new Error('无效的会话,或者会话已过期,请重新登录。')) + } + + // 处理其他错误码 + if (code !== 200) { + const msg = res.msg || res.message || '请求失败' + ElMessage.error(msg) + return Promise.reject(new Error(msg)) + } + + return res + }, + (error) => { + console.error('Request error:', error) + let message = error.message || '网络错误' + + if (message === 'Network Error') { + message = '后端接口连接异常' + } else if (message.includes('timeout')) { + message = '系统接口请求超时' + } else if (message.includes('Request failed with status code')) { + const status = message.substring(message.length - 3) + message = `系统接口 ${status} 异常` + } + + ElMessage.error(message) + return Promise.reject(error) + } +) + +export default request diff --git a/erp-frontend-vue/src/api/saleback.ts b/erp-frontend-vue/src/api/saleback.ts new file mode 100644 index 0000000..51177fd --- /dev/null +++ b/erp-frontend-vue/src/api/saleback.ts @@ -0,0 +1,105 @@ +import request from './request' + +export interface SalebackLine { + lineId?: number + id?: number + salebackId?: number + salebackCode?: string + deliverLineId?: number + itemId?: number + itemCode?: string + materialCode?: string + itemName?: string + materialName?: string + specification?: string + spec?: string + unitOfMeasure?: string + unit?: string + returnQty: number + reason?: string +} + +export interface Saleback { + salebackId?: number + id?: number + salebackCode?: string + code?: string + clientId?: number + customerId?: number + clientCode?: string + customerCode?: string + clientName?: string + customerName?: string + returnDate: string + status: string + totalQty?: number + reason?: string + remark?: string + lines?: SalebackLine[] + createTime?: string +} + +export interface SalebackQuery { + code?: string + customerName?: string + status?: string + page?: number + pageSize?: number +} + +export interface SalebackListResponse { + list: Saleback[] + total: number +} + +// 获取退货单列表 +export function getSalebackList(params: SalebackQuery): Promise { + return request.get('/erp/sl/saleback/list', { params }).then((res: any) => { + return { + list: res.rows || [], + total: res.total || 0 + } + }) +} + +// 获取退货单详情 +export function getSalebackDetail(id: number): Promise { + return request.get(`/erp/sl/saleback/${id}`).then((res: any) => { + return res.data + }) +} + +// 新增退货单 +export function createSaleback(data: Partial): Promise { + return request.post('/erp/sl/saleback', data) +} + +// 更新退货单 +export function updateSaleback(id: number, data: Partial): Promise { + return request.put('/erp/sl/saleback', { ...data, salebackId: id }) +} + +// 删除退货单 +export function deleteSaleback(id: number): Promise { + return request.delete(`/erp/sl/saleback/${id}`) +} + +// 审核退货单 +export function auditSaleback(id: number): Promise { + return request.post(`/erp/sl/saleback/audit/${id}`) +} + +// 反审核退货单 +export function unauditSaleback(id: number): Promise { + return request.post(`/erp/sl/saleback/unaudit/${id}`) +} + +// 引入发货单 +export function getIntroduceDeliverList(params: any): Promise { + return request.get('/erp/sl/saleback/introduce', { params }).then((res: any) => { + return { + list: res.rows || [], + total: res.total || 0 + } + }) +} diff --git a/erp-frontend-vue/src/api/salesOrder.ts b/erp-frontend-vue/src/api/salesOrder.ts new file mode 100644 index 0000000..cbb4b82 --- /dev/null +++ b/erp-frontend-vue/src/api/salesOrder.ts @@ -0,0 +1,268 @@ +import request from './request' + +// ============ 类型定义 ============ + +/** 订单明细行 */ +export interface SalesOrderLine { + lineId?: number + orderId?: number + itemId?: number + itemCode?: string + itemName?: string + specification?: string + unitOfMeasure?: string + quantity?: number // 可选,新增时默认为空 + unitPrice?: number + amount?: number + deliveredQty?: number + qualityReq?: string + remark?: string + delFlag?: string +} + +/** 销售订单主表 */ +export interface SalesOrder { + orderId?: number + orderCode?: string + orderName?: string + orderDate?: string + orderStatus?: string + // 客户信息 + clientId?: number + clientCode?: string + clientName?: string + // 业务信息 + contractNo?: string + bizType?: string + bizStatus?: string + salesType?: string + supplyMode?: string + deliveryStatus?: string + // 部门/人员 + deptId?: number + deptName?: string + salesmanId?: number + salesmanName?: string + operatorId?: number + operatorName?: string + auditorId?: number + auditorName?: string + auditDate?: string + // 收货信息 + receiver?: string + receiverPhone?: string + receiverAddress?: string + destCountry?: string + paymentTerms?: string + deliveryDate?: string + // 金额 + totalAmount?: number + currency?: string + // 其他 + remark?: string + delFlag?: string + createBy?: string + createTime?: string + updateBy?: string + updateTime?: string + // 明细行 + lines?: SalesOrderLine[] + slaveList?: SalesOrderLine[] +} + +/** 明细视图数据(展开物料维度) */ +export interface SalesOrderDetailView { + orderId: number + orderCode: string + orderDate: string + orderStatus: string + contractNo?: string + clientId: number + clientName: string + salesmanName?: string + salesType?: string + // 物料信息 + lineId: number + itemId: number + itemCode: string + itemName: string + specification?: string + quantity: number + unitPrice?: number + amount?: number + deliveredQty?: number +} + +/** 查询参数 */ +export interface SalesOrderQuery { + orderCode?: string + contractNo?: string + clientName?: string + salesmanName?: string + salesType?: string + itemName?: string + orderStatus?: string + beginOrderDate?: string + endOrderDate?: string + pageNum?: number + pageSize?: number +} + +/** 分页响应 */ +export interface SalesOrderListResponse { + list: SalesOrder[] + total: number +} + +export interface SalesOrderDetailListResponse { + list: SalesOrderDetailView[] + total: number +} + +// ============ 状态映射 ============ + +export const ORDER_STATUS_MAP: Record = { + 'DRAFT': { label: '草稿', type: 'info' }, + '开立': { label: '开立', type: '' }, + '审核': { label: '审核', type: 'success' }, + '退回': { label: '退回', type: 'warning' }, + '关闭': { label: '关闭', type: 'danger' } +} + +export const SALES_TYPE_OPTIONS = [ + { value: 'factory', label: '工厂订单' }, + { value: 'stock', label: '库存供应' }, + { value: 'purchase', label: '买进卖出' } +] + +export const SUPPLY_MODE_OPTIONS = [ + { value: 'plan', label: '计划生产' }, + { value: 'stock', label: '库存供应' } +] + +export const DELIVERY_STATUS_OPTIONS = [ + { value: 'estimate', label: '预计' }, + { value: 'confirm', label: '确认' } +] + +export const BIZ_TYPE_OPTIONS = [ + { value: 'sales_order', label: '销售订单' } +] + +export const BIZ_STATUS_OPTIONS = [ + { value: 'normal', label: '正常' }, + { value: 'suspend', label: '挂起' } +] + +// ============ API 接口 ============ + +const BASE = '/erp/sl/order' + +/** 获取销售订单列表(单据视图) */ +export function getSalesOrderList(params: SalesOrderQuery): Promise { + return request.get(`${BASE}/list`, { params }).then((res: any) => { + const rows = res.rows ?? res.data?.rows ?? [] + const total = res.total ?? res.data?.total ?? 0 + return { list: rows, total } + }) +} + +/** 获取销售订单明细列表(明细视图,展开物料维度) */ +export function getSalesOrderDetailList(params: SalesOrderQuery): Promise { + return request.get(`${BASE}/lineList`, { params }).then((res: any) => { + const rows = res.rows ?? res.data?.rows ?? [] + const total = res.total ?? res.data?.total ?? 0 + return { list: rows, total } + }) +} + +/** 获取销售订单详情(含明细行) */ +export function getSalesOrderDetail(orderId: number): Promise { + return request.get(`${BASE}/${orderId}`).then((res: any) => { + const data = res.data ?? res + // 统一明细字段名 + if (data.slaveList && !data.lines) { + data.lines = data.slaveList + } + // 后端返回 salesUserId/salesUserName,前端表单使用 salesmanId/salesmanName,便于下拉回显 + if (data.salesUserId !== undefined) data.salesmanId = data.salesUserId + if (data.salesUserName !== undefined) data.salesmanName = data.salesUserName + return data + }) +} + +/** 构建保存用 payload:明细转 slaveList,销售人员字段对齐后端 salesUserId/salesUserName */ +function buildOrderPayload(data: Partial): Record { + const payload: Record = { ...data } + if (payload.lines) { + payload.slaveList = payload.lines + delete payload.lines + } + // 后端实体为 salesUserId / salesUserName,前端表单为 salesmanId / salesmanName,此处统一发给后端 + if (payload.salesmanId !== undefined) payload.salesUserId = payload.salesmanId + if (payload.salesmanName !== undefined) payload.salesUserName = payload.salesmanName + return payload +} + +/** 新增销售订单 */ +export function createSalesOrder(data: Partial): Promise<{ orderId: number }> { + return request.post(BASE, buildOrderPayload(data)).then((res: any) => res.data ?? res) +} + +/** 更新销售订单 */ +export function updateSalesOrder(data: Partial): Promise { + return request.put(BASE, buildOrderPayload(data)) +} + +/** 删除销售订单(支持批量) */ +export function deleteSalesOrder(orderIds: number | number[]): Promise { + const ids = Array.isArray(orderIds) ? orderIds.join(',') : String(orderIds) + return request.delete(`${BASE}/${ids}`) +} + +/** 审核销售订单(支持批量) */ +export function auditSalesOrder(orderIds: number | number[]): Promise { + if (orderIds == null || (Array.isArray(orderIds) && orderIds.length === 0)) { + return Promise.reject(new Error('订单ID不能为空')) + } + const ids = Array.isArray(orderIds) ? orderIds.join(',') : String(orderIds) + return request.post(`${BASE}/audit/${ids}`) +} + +/** 反审核销售订单(支持批量) */ +export function unauditSalesOrder(orderIds: number | number[]): Promise { + if (orderIds == null || (Array.isArray(orderIds) && orderIds.length === 0)) { + return Promise.reject(new Error('订单ID不能为空')) + } + const ids = Array.isArray(orderIds) ? orderIds.join(',') : String(orderIds) + return request.post(`${BASE}/unaudit/${ids}`) +} + +/** 导出销售订单 */ +export function exportSalesOrder(params: SalesOrderQuery): Promise { + return request.get(`${BASE}/export`, { + params, + responseType: 'blob' + }) +} + +// ============ 辅助接口 ============ + +/** 从销售合同引入订单明细 */ +export function importFromContract(contractId: number): Promise<{ lines: SalesOrderLine[] }> { + return request.get(`/erp/sl/contract/${contractId}/lines`).then((res: any) => { + return { lines: res.data ?? res.rows ?? [] } + }) +} + +/** 获取可引入的合同列表 */ +export function getAvailableContracts(clientId?: number): Promise { + return request.get('/erp/sl/contract/available', { params: { clientId } }).then((res: any) => { + return res.rows ?? res.data ?? [] + }) +} + +/** 生成订单编码(可选,后端自动生成时不需要) */ +export function generateOrderCode(): Promise { + return request.get(`${BASE}/generateCode`).then((res: any) => res.data ?? res) +} diff --git a/erp-frontend-vue/src/api/supplier.ts b/erp-frontend-vue/src/api/supplier.ts new file mode 100644 index 0000000..79afa6b --- /dev/null +++ b/erp-frontend-vue/src/api/supplier.ts @@ -0,0 +1,155 @@ +import request from './request' + +// ============ 类型定义 ============ + +/** 供应商主数据 */ +export interface Supplier { + vendorId?: number + vendorCode?: string + vendorName?: string + vendorNick?: string + vendorEn?: string + vendorDes?: string + vendorType?: string + vendorLevel?: string + vendorScore?: number + vendorLogo?: string + address?: string + province?: string + city?: string + zipCode?: string + website?: string + email?: string + tel?: string + fax?: string + contact1?: string + contact1Tel?: string + contact1Email?: string + contact2?: string + contact2Tel?: string + contact2Email?: string + creditCode?: string + bankName?: string + bankAccount?: string + bankAddress?: string + taxNo?: string + supplyItems?: string + remark?: string + enableFlag?: string + delFlag?: string + createBy?: string + createTime?: string + updateBy?: string + updateTime?: string +} + +/** 查询参数 */ +export interface SupplierQuery { + vendorCode?: string + vendorName?: string + vendorNick?: string + vendorEn?: string + vendorType?: string + vendorLevel?: string + enableFlag?: string + pageNum?: number + pageSize?: number +} + +/** 分页响应 */ +export interface SupplierListResponse { + rows: Supplier[] + total: number +} + +// ============ 状态枚举 ============ + +/** 供应商等级选项 */ +export const VENDOR_LEVEL_OPTIONS = [ + { value: 'A', label: 'A级' }, + { value: 'B', label: 'B级' }, + { value: 'C', label: 'C级' }, + { value: 'D', label: 'D级' } +] + +/** 供应商类型选项 */ +export const VENDOR_TYPE_OPTIONS = [ + { value: 'CORE', label: '核心供应商' }, + { value: 'NORMAL', label: '普通供应商' }, + { value: 'TEMP', label: '临时供应商' } +] + +/** 是否启用选项 */ +export const ENABLE_FLAG_OPTIONS = [ + { value: 'Y', label: '启用' }, + { value: 'N', label: '停用' } +] + +// ============ API 接口 ============ + +const BASE = '/mes/md/vendor' + +/** 获取供应商列表 */ +export function getSupplierList(params: SupplierQuery): Promise { + return request.get(`${BASE}/list`, { params }).then((res: any) => { + const rows = res.rows ?? res.data?.rows ?? [] + const total = res.total ?? res.data?.total ?? 0 + return { rows, total } + }) +} + +/** 获取供应商详情 */ +export function getSupplierDetail(vendorId: number): Promise { + return request.get(`${BASE}/${vendorId}`).then((res: any) => { + return res.data ?? res + }) +} + +/** 新增供应商 */ +export function createSupplier(data: Partial): Promise<{ vendorId: number }> { + return request.post(BASE, data).then((res: any) => res.data ?? res) +} + +/** 更新供应商 */ +export function updateSupplier(data: Partial): Promise { + return request.put(BASE, data) +} + +/** 删除供应商(支持批量) */ +export function deleteSupplier(vendorIds: number | number[] | string): Promise { + const ids = Array.isArray(vendorIds) ? vendorIds.join(',') : String(vendorIds) + return request.delete(`${BASE}/${ids}`) +} + +/** 导出供应商列表 */ +export function exportSupplier(params: SupplierQuery): Promise { + return request.get(`${BASE}/export`, { + params, + responseType: 'blob' + }) +} + +/** 下载导入模板 */ +export function downloadImportTemplate(): Promise { + return request.get(`${BASE}/importTemplate`, { + responseType: 'blob' + }) +} + +/** 导入供应商数据 */ +export function importSupplier(file: File, updateSupport: boolean = false): Promise { + const formData = new FormData() + formData.append('file', file) + return request.post(`${BASE}/importData?updateSupport=${updateSupport}`, formData, { + headers: { 'Content-Type': 'multipart/form-data' } + }) +} + +/** 获取供应商选项列表(用于下拉选择) */ +export function getSupplierOptions(): Promise { + return request.get(`${BASE}/list`, { + params: { enableFlag: 'Y', pageNum: 1, pageSize: 1000 } + }).then((res: any) => { + return res.rows ?? res.data?.rows ?? [] + }) +} diff --git a/erp-frontend-vue/src/api/system/dept.ts b/erp-frontend-vue/src/api/system/dept.ts new file mode 100644 index 0000000..c2527d6 --- /dev/null +++ b/erp-frontend-vue/src/api/system/dept.ts @@ -0,0 +1,91 @@ +import request from '../request' + +export interface Dept { + deptId?: number + parentId?: number + ancestors?: string + deptCode?: string + deptName?: string + orderNum?: number + leader?: string + phone?: string + email?: string + status?: string + delFlag?: string + createBy?: string + createTime?: string + updateBy?: string + updateTime?: string + children?: Dept[] +} + +export interface DeptQuery { + deptName?: string + status?: string +} + +const BASE = '/system/dept' + +/** 查询部门列表 */ +export function listDept(query?: DeptQuery): Promise<{ data: Dept[] }> { + return request.get(`${BASE}/list`, { params: query }) +} + +/** 查询部门列表(排除节点) */ +export function listDeptExcludeChild(deptId: number): Promise<{ data: Dept[] }> { + return request.get(`${BASE}/list/exclude/${deptId}`) +} + +/** 查询部门详细 */ +export function getDept(deptId: number): Promise<{ data: Dept }> { + return request.get(`${BASE}/${deptId}`) +} + +/** 查询部门下拉树结构 */ +export function getDeptTreeselect(): Promise<{ data: any[] }> { + return request.get(`${BASE}/treeselect`) +} + +/** 根据角色ID查询部门树结构 */ +export function getRoleDeptTreeselect(roleId: number): Promise<{ data: any }> { + return request.get(`${BASE}/roleDeptTreeselect/${roleId}`) +} + +/** 新增部门 */ +export function addDept(data: Partial): Promise { + return request.post(BASE, data) +} + +/** 修改部门 */ +export function updateDept(data: Partial): Promise { + return request.put(BASE, data) +} + +/** 删除部门 */ +export function delDept(deptId: number): Promise { + return request.delete(`${BASE}/${deptId}`) +} + +/** 构建部门树 */ +export function handleTree(data: Dept[], idField = 'deptId', parentField = 'parentId'): Dept[] { + const map = new Map() + const result: Dept[] = [] + + data.forEach(item => { + map.set(item[idField as keyof Dept] as number, { ...item, children: [] }) + }) + + data.forEach(item => { + const current = map.get(item[idField as keyof Dept] as number)! + const parentId = item[parentField as keyof Dept] as number + if (parentId && map.has(parentId)) { + const parent = map.get(parentId)! + parent.children = parent.children || [] + parent.children.push(current) + } else { + result.push(current) + } + }) + + return result +} diff --git a/erp-frontend-vue/src/api/system/post.ts b/erp-frontend-vue/src/api/system/post.ts new file mode 100644 index 0000000..d0b1b9a --- /dev/null +++ b/erp-frontend-vue/src/api/system/post.ts @@ -0,0 +1,58 @@ +import request from '../request' + +export interface Post { + postId?: number + postCode?: string + postName?: string + postSort?: number + status?: string + createBy?: string + createTime?: string + updateBy?: string + updateTime?: string + remark?: string +} + +export interface PostQuery { + postCode?: string + postName?: string + status?: string + pageNum?: number + pageSize?: number +} + +export interface PostListResponse { + rows: Post[] + total: number +} + +const BASE = '/system/post' + +/** 查询岗位列表 */ +export function listPost(query?: PostQuery): Promise { + return request.get(`${BASE}/list`, { params: query }).then((res: any) => ({ + rows: res.rows || [], + total: res.total || 0 + })) +} + +/** 查询岗位详细 */ +export function getPost(postId: number): Promise<{ data: Post }> { + return request.get(`${BASE}/${postId}`) +} + +/** 新增岗位 */ +export function addPost(data: Partial): Promise { + return request.post(BASE, data) +} + +/** 修改岗位 */ +export function updatePost(data: Partial): Promise { + return request.put(BASE, data) +} + +/** 删除岗位 */ +export function delPost(postId: number | number[]): Promise { + const ids = Array.isArray(postId) ? postId.join(',') : String(postId) + return request.delete(`${BASE}/${ids}`) +} diff --git a/erp-frontend-vue/src/api/system/role.ts b/erp-frontend-vue/src/api/system/role.ts new file mode 100644 index 0000000..042e404 --- /dev/null +++ b/erp-frontend-vue/src/api/system/role.ts @@ -0,0 +1,99 @@ +import request from '../request' + +export interface Role { + roleId?: number + roleName?: string + roleKey?: string + roleSort?: number + dataScope?: string + menuCheckStrictly?: boolean + deptCheckStrictly?: boolean + status?: string + delFlag?: string + createBy?: string + createTime?: string + updateBy?: string + updateTime?: string + remark?: string + menuIds?: number[] + deptIds?: number[] +} + +export interface RoleQuery { + roleName?: string + roleKey?: string + status?: string + pageNum?: number + pageSize?: number +} + +export interface RoleListResponse { + rows: Role[] + total: number +} + +const BASE = '/system/role' + +/** 查询角色列表 */ +export function listRole(query?: RoleQuery): Promise { + return request.get(`${BASE}/list`, { params: query }).then((res: any) => ({ + rows: res.rows || [], + total: res.total || 0 + })) +} + +/** 查询角色详细 */ +export function getRole(roleId: number): Promise<{ data: Role }> { + return request.get(`${BASE}/${roleId}`) +} + +/** 新增角色 */ +export function addRole(data: Partial): Promise { + return request.post(BASE, data) +} + +/** 修改角色 */ +export function updateRole(data: Partial): Promise { + return request.put(BASE, data) +} + +/** 角色数据权限 */ +export function dataScope(data: Partial): Promise { + return request.put(`${BASE}/dataScope`, data) +} + +/** 角色状态修改 */ +export function changeRoleStatus(roleId: number, status: string): Promise { + return request.put(`${BASE}/changeStatus`, { roleId, status }) +} + +/** 删除角色 */ +export function delRole(roleId: number | number[]): Promise { + const ids = Array.isArray(roleId) ? roleId.join(',') : String(roleId) + return request.delete(`${BASE}/${ids}`) +} + +/** 查询角色已授权用户列表 */ +export function allocatedUserList(query: any): Promise { + return request.get(`${BASE}/authUser/allocatedList`, { params: query }) +} + +/** 查询角色未授权用户列表 */ +export function unallocatedUserList(query: any): Promise { + return request.get(`${BASE}/authUser/unallocatedList`, { params: query }) +} + +/** 取消用户授权角色 */ +export function authUserCancel(data: any): Promise { + return request.put(`${BASE}/authUser/cancel`, data) +} + +/** 批量取消用户授权角色 */ +export function authUserCancelAll(data: any): Promise { + return request.put(`${BASE}/authUser/cancelAll`, data) +} + +/** 授权用户选择 */ +export function authUserSelectAll(data: any): Promise { + return request.put(`${BASE}/authUser/selectAll`, data) +} diff --git a/erp-frontend-vue/src/api/system/user.ts b/erp-frontend-vue/src/api/system/user.ts new file mode 100644 index 0000000..80b84a6 --- /dev/null +++ b/erp-frontend-vue/src/api/system/user.ts @@ -0,0 +1,123 @@ +import request from '../request' + +export interface User { + userId?: number + deptId?: number + userName?: string + nickName?: string + userType?: string + email?: string + phonenumber?: string + sex?: string + avatar?: string + password?: string + status?: string + delFlag?: string + loginIp?: string + loginDate?: string + createBy?: string + createTime?: string + updateBy?: string + updateTime?: string + remark?: string + dept?: { + deptId?: number + deptName?: string + } + roles?: any[] + roleIds?: number[] + postIds?: number[] +} + +export interface UserQuery { + userName?: string + phonenumber?: string + status?: string + deptId?: number + pageNum?: number + pageSize?: number + beginTime?: string + endTime?: string +} + +export interface UserListResponse { + rows: User[] + total: number +} + +const BASE = '/system/user' + +/** 查询用户列表 */ +export function listUser(query?: UserQuery): Promise { + return request.get(`${BASE}/list`, { params: query }).then((res: any) => ({ + rows: res.rows || [], + total: res.total || 0 + })) +} + +/** 查询用户详细 */ +export function getUser(userId?: number): Promise<{ + data: User + roles: any[] + posts: any[] + postIds: number[] + roleIds: number[] +}> { + return request.get(userId ? `${BASE}/${userId}` : BASE) +} + +/** 新增用户 */ +export function addUser(data: Partial): Promise { + return request.post(BASE, data) +} + +/** 修改用户 */ +export function updateUser(data: Partial): Promise { + return request.put(BASE, data) +} + +/** 删除用户 */ +export function delUser(userId: number | number[]): Promise { + const ids = Array.isArray(userId) ? userId.join(',') : String(userId) + return request.delete(`${BASE}/${ids}`) +} + +/** 用户密码重置 */ +export function resetUserPwd(userId: number, password: string): Promise { + return request.put(BASE + '/resetPwd', { userId, password }) +} + +/** 用户状态修改 */ +export function changeUserStatus(userId: number, status: string): Promise { + return request.put(BASE + '/changeStatus', { userId, status }) +} + +/** 查询用户个人信息 */ +export function getUserProfile(): Promise<{ data: User }> { + return request.get(`${BASE}/profile`) +} + +/** 修改用户个人信息 */ +export function updateUserProfile(data: Partial): Promise { + return request.put(`${BASE}/profile`, data) +} + +/** 用户密码重置 */ +export function updateUserPwd(oldPassword: string, newPassword: string): Promise { + return request.put(`${BASE}/profile/updatePwd`, { oldPassword, newPassword }) +} + +/** 用户头像上传 */ +export function uploadAvatar(data: FormData): Promise<{ imgUrl: string }> { + return request.post(`${BASE}/profile/avatar`, data) +} + +/** 查询授权角色 */ +export function getAuthRole(userId: number): Promise<{ user: User; roles: any[] }> { + return request.get(`${BASE}/authRole/${userId}`) +} + +/** 保存授权角色 */ +export function updateAuthRole(data: { userId: number; roleIds: string }): Promise { + return request.put(`${BASE}/authRole`, data) +} diff --git a/erp-frontend-vue/src/api/warehouse/issue.ts b/erp-frontend-vue/src/api/warehouse/issue.ts new file mode 100644 index 0000000..60ef750 --- /dev/null +++ b/erp-frontend-vue/src/api/warehouse/issue.ts @@ -0,0 +1,222 @@ +import request from '../request' + +// ============ 类型定义 ============ + +/** 生产领料单头 */ +export interface IssueHeader { + issueId?: number + issueCode?: string + issueName?: string + workstationId?: number + workstationCode?: string + workstationName?: string + workorderId?: number + workorderCode?: string + taskId?: number + taskCode?: string + clientId?: number + clientCode?: string + clientName?: string + clientNick?: string + requiredTime?: string + issueDate?: string + status?: string + remark?: string + createBy?: string + createTime?: string + updateBy?: string + updateTime?: string +} + +/** 查询参数 */ +export interface IssueHeaderQuery { + issueCode?: string + issueName?: string + workorderCode?: string + workstationName?: string + status?: string + beginIssueDate?: string + endIssueDate?: string + pageNum?: number + pageSize?: number +} + +/** 分页响应 */ +export interface IssueHeaderListResponse { + rows: IssueHeader[] + total: number +} + +// ============ 状态映射 ============ + +export const STATUS_MAP: Record = { + 'PREPARE': { label: '草稿', type: 'info' }, + 'CONFIRMED': { label: '已确认', type: '' }, + 'APPROVED': { label: '已审核', type: 'success' }, + 'FINISHED': { label: '已完成', type: 'success' } +} + +// ============ API 接口 ============ + +const BASE = '/mes/wm/issueheader' + +/** 查询领料单列表 */ +export function getIssueHeaderList(params: IssueHeaderQuery): Promise { + return request.get(`${BASE}/list`, { params }).then((res: any) => { + const rows = res.rows ?? res.data?.rows ?? [] + const total = res.total ?? res.data?.total ?? 0 + return { rows, total } + }) +} + +/** 获取领料单详情 */ +export function getIssueHeaderDetail(issueId: number): Promise { + return request.get(`${BASE}/${issueId}`).then((res: any) => { + return res.data ?? res + }) +} + +/** 将日期字段规范为 yyyy-MM-dd HH:mm:ss(后端 JsonFormat 要求) */ +function normalizeDateFields(data: Partial): Partial { + const out = { ...data } + if (typeof out.issueDate === 'string' && out.issueDate.length === 10) { + out.issueDate = out.issueDate + ' 00:00:00' + } + if (typeof out.requiredTime === 'string' && out.requiredTime.length === 10) { + out.requiredTime = out.requiredTime + ' 00:00:00' + } + return out +} + +/** 新增领料单,返回服务端生成的领料单对象(含 issueId、issueCode) */ +export function createIssueHeader(data: Partial): Promise { + return request.post(BASE, normalizeDateFields(data)).then((res: any) => res.data ?? res) +} + +/** 修改领料单 */ +export function updateIssueHeader(data: Partial): Promise { + return request.put(BASE, normalizeDateFields(data)) +} + +/** 删除领料单(支持批量) */ +export function deleteIssueHeader(ids: number | number[] | string): Promise { + const idStr = Array.isArray(ids) ? ids.join(',') : String(ids) + return request.delete(`${BASE}/${idStr}`) +} + +/** 执行出库 */ +export function executeIssue(issueId: number): Promise { + return request.put(`${BASE}/${issueId}`) +} + +/** 校验领料数量与明细数量匹配 */ +export function checkQuantity(issueId: number): Promise { + return request.get(`${BASE}/checkQuantity/${issueId}`).then((res: any) => { + return res.data ?? res + }) +} + +/** 导出领料单 */ +export function exportIssueHeader(params: IssueHeaderQuery): Promise { + return request.post(`${BASE}/export`, params, { responseType: 'blob' }) +} + +// ============ 辅助选择 API ============ + +/** 工单列表(选择弹窗) */ +export function getWorkorderSelectList(params?: { + workorderCode?: string + workorderName?: string + productName?: string + pageNum?: number + pageSize?: number +}): Promise<{ rows: any[]; total: number }> { + return request.get('/mes/pro/workorder/list', { + params: { pageNum: 1, pageSize: 100, ...params } + }).then((res: any) => ({ + rows: res.rows ?? [], + total: res.total ?? 0 + })) +} + +/** 物料列表(选择弹窗) */ +export function getItemSelectList(params?: { + itemCode?: string + itemName?: string + pageNum?: number + pageSize?: number +}): Promise<{ rows: any[]; total: number }> { + return request.get('/mes/md/mditem/list', { + params: { pageNum: 1, pageSize: 100, ...params } + }).then((res: any) => ({ + rows: res.rows ?? [], + total: res.total ?? 0 + })) +} + +/** 客户列表(选择弹窗) */ +export function getClientSelectList(params?: { + clientCode?: string + clientName?: string + pageNum?: number + pageSize?: number +}): Promise<{ rows: any[]; total: number }> { + return request.get('/mes/md/client/list', { + params: { pageNum: 1, pageSize: 100, ...params } + }).then((res: any) => ({ + rows: res.rows ?? [], + total: res.total ?? 0 + })) +} + +/** 工作站列表(选择弹窗) */ +export function getWorkstationSelectList(params?: { + workstationCode?: string + workstationName?: string + pageNum?: number + pageSize?: number +}): Promise<{ rows: any[]; total: number }> { + return request.get('/mes/md/workstation/list', { + params: { pageNum: 1, pageSize: 100, ...params } + }).then((res: any) => ({ + rows: res.rows ?? [], + total: res.total ?? 0 + })) +} + +/** 仓库列表 */ +export function getWarehouseList(params?: { + warehouseCode?: string + warehouseName?: string +}): Promise<{ rows: any[]; total: number }> { + return request.get('/mes/wm/warehouse/list', { + params: { pageNum: 1, pageSize: 200, ...params } + }).then((res: any) => ({ + rows: res.rows ?? [], + total: res.total ?? 0 + })) +} + +/** 库区列表(按仓库筛选) */ +export function getLocationList(params?: { + warehouseId?: number +}): Promise<{ rows: any[]; total: number }> { + return request.get('/mes/wm/location/list', { + params: { pageNum: 1, pageSize: 200, ...params } + }).then((res: any) => ({ + rows: res.rows ?? [], + total: res.total ?? 0 + })) +} + +/** 库位列表(按库区筛选) */ +export function getAreaList(params?: { + locationId?: number +}): Promise<{ rows: any[]; total: number }> { + return request.get('/mes/wm/area/list', { + params: { pageNum: 1, pageSize: 200, ...params } + }).then((res: any) => ({ + rows: res.rows ?? [], + total: res.total ?? 0 + })) +} diff --git a/erp-frontend-vue/src/api/warehouse/issueDetail.ts b/erp-frontend-vue/src/api/warehouse/issueDetail.ts new file mode 100644 index 0000000..9914c2a --- /dev/null +++ b/erp-frontend-vue/src/api/warehouse/issueDetail.ts @@ -0,0 +1,69 @@ +import request from '../request' + +// ============ 类型定义 ============ + +/** 出库明细 */ +export interface IssueDetail { + detailId?: number + issueId?: number + lineId?: number + materialStockId?: string + itemId?: number + itemCode?: string + itemName?: string + specification?: string + unitOfMeasure?: string + unitName?: string + quantity?: number + batchId?: number + batchCode?: string + warehouseId?: number + warehouseCode?: string + warehouseName?: string + locationId?: number + locationCode?: string + locationName?: string + areaId?: number + areaCode?: string + areaName?: string + remark?: string + createBy?: string + createTime?: string + updateBy?: string + updateTime?: string +} + +// ============ API 接口 ============ + +const BASE = '/mes/wm/issuedetail' + +/** 查询出库明细列表 */ +export function getIssueDetailList(params: { + issueId?: number + lineId?: number + pageNum?: number + pageSize?: number +}): Promise<{ rows: IssueDetail[]; total: number }> { + return request.get(`${BASE}/list`, { + params: { pageNum: 1, pageSize: 200, ...params } + }).then((res: any) => ({ + rows: res.rows ?? res.data?.rows ?? [], + total: res.total ?? res.data?.total ?? 0 + })) +} + +/** 新增出库明细 */ +export function createIssueDetail(data: Partial): Promise { + return request.post(BASE, data).then((res: any) => res.data ?? res) +} + +/** 修改出库明细 */ +export function updateIssueDetail(data: Partial): Promise { + return request.put(BASE, data) +} + +/** 删除出库明细(支持批量) */ +export function deleteIssueDetail(ids: number | number[] | string): Promise { + const idStr = Array.isArray(ids) ? ids.join(',') : String(ids) + return request.delete(`${BASE}/${idStr}`) +} diff --git a/erp-frontend-vue/src/api/warehouse/issueLine.ts b/erp-frontend-vue/src/api/warehouse/issueLine.ts new file mode 100644 index 0000000..fc97aa4 --- /dev/null +++ b/erp-frontend-vue/src/api/warehouse/issueLine.ts @@ -0,0 +1,59 @@ +import request from '../request' + +// ============ 类型定义 ============ + +/** 领料行 */ +export interface IssueLine { + lineId?: number + issueId?: number + materialStockId?: number + itemId?: number + itemCode?: string + itemName?: string + specification?: string + unitOfMeasure?: string + unitName?: string + quantityIssued?: number + quantity?: number + batchId?: number + batchCode?: string + remark?: string + createBy?: string + createTime?: string + updateBy?: string + updateTime?: string +} + +// ============ API 接口 ============ + +const BASE = '/mes/wm/issueline' + +/** 查询领料行列表 */ +export function getIssueLineList(params: { + issueId?: number + pageNum?: number + pageSize?: number +}): Promise<{ rows: IssueLine[]; total: number }> { + return request.get(`${BASE}/list`, { + params: { pageNum: 1, pageSize: 200, ...params } + }).then((res: any) => ({ + rows: res.rows ?? res.data?.rows ?? [], + total: res.total ?? res.data?.total ?? 0 + })) +} + +/** 新增领料行 */ +export function createIssueLine(data: Partial): Promise { + return request.post(BASE, data).then((res: any) => res.data ?? res) +} + +/** 修改领料行 */ +export function updateIssueLine(data: Partial): Promise { + return request.put(BASE, data) +} + +/** 删除领料行(支持批量) */ +export function deleteIssueLine(ids: number | number[] | string): Promise { + const idStr = Array.isArray(ids) ? ids.join(',') : String(ids) + return request.delete(`${BASE}/${idStr}`) +} diff --git a/erp-frontend-vue/src/api/workOrder.ts b/erp-frontend-vue/src/api/workOrder.ts new file mode 100644 index 0000000..7c1a57c --- /dev/null +++ b/erp-frontend-vue/src/api/workOrder.ts @@ -0,0 +1,305 @@ +import request from './request' + +// ============ 类型定义 ============ + +/** 生产工单 BOM 物料行 */ +export interface WorkOrderBom { + lineId?: number + workorderId?: number + itemId?: number + itemCode?: string + itemName?: string + itemSpc?: string + unitOfMeasure?: string + unitName?: string + itemOrProduct?: string + quantity?: number + remark?: string +} + +/** 生产工单主表 */ +export interface WorkOrder { + workorderId?: number + workorderCode?: string + workorderName?: string + workorderType?: string + orderSource?: string + sourceCode?: string + productId?: number + productCode?: string + productName?: string + productSpc?: string + unitOfMeasure?: string + unitName?: string + routeId?: number + routeCode?: string + routeName?: string + batchCode?: string + quantity?: number + quantityProduced?: number + quantityChanged?: number + quantityScheduled?: number + clientId?: number + clientCode?: string + clientName?: string + vendorId?: number + vendorCode?: string + vendorName?: string + productionDate?: string // 前端字段名,映射到后端 requestDate + requestDate?: string // 后端实际字段名(数据库列 request_date) + finishDate?: string + cancelDate?: string + parentId?: number + ancestors?: string + status?: string + remark?: string + createBy?: string + createTime?: string + updateBy?: string + updateTime?: string + // v3.1 新增字段 + orderDate?: string // 单据日期 + businessType?: string // 业务类型 + businessStatus?: string // 业务状态 + operatorName?: string // 操作员 + approverName?: string // 审核员 + approveDate?: string // 审核日期 + salesOrderCode?: string // 跟单编号(销售订单号) + deliveryDate?: string // 订单交期 + drawingNo?: string // 图纸号 + productionLine?: string // 生产线 + processTime?: number // 加工时长(分钟) + qualityReq?: string // 质量要求 +} + +/** 查询参数 */ +export interface WorkOrderQuery { + workorderCode?: string + workorderName?: string + workorderType?: string + sourceCode?: string + productCode?: string + productName?: string + status?: string + beginProductionDate?: string // 前端用,映射到后端 beginRequestDate + endProductionDate?: string // 前端用,映射到后端 endRequestDate + pageNum?: number + pageSize?: number +} + +/** 分页响应 */ +export interface WorkOrderListResponse { + rows: WorkOrder[] + total: number +} + +// ============ 状态映射 ============ + +export const STATUS_MAP: Record = { + 'PREPARE': { label: '草稿', type: 'info' }, + 'CONFIRMED': { label: '已确认', type: '' }, + 'APPROVED': { label: '已审核', type: 'success' }, + 'FINISHED': { label: '已完成', type: 'success' }, + 'CANCELED': { label: '已取消', type: 'danger' } +} + +export const WORKORDER_TYPE_OPTIONS = [ + { value: 'SELF', label: '自产' }, + { value: 'OUTSOURCE', label: '委外' }, + { value: 'PROCESSING', label: '加工车间' } +] + +export const BUSINESS_STATUS_OPTIONS = [ + { value: 'NORMAL', label: '正常' }, + { value: 'PAUSE', label: '暂停' }, + { value: 'CANCEL', label: '取消' } +] + +// ============ API 接口 ============ + +const BASE = '/mes/pro/workorder' + +/** 查询生产工单列表 */ +export function getWorkOrderList(params: WorkOrderQuery): Promise { + // 将前端日期查询参数映射为后端字段名 + const backendParams: Record = { ...params } + if (backendParams.beginProductionDate) { + backendParams.beginRequestDate = backendParams.beginProductionDate + delete backendParams.beginProductionDate + } + if (backendParams.endProductionDate) { + backendParams.endRequestDate = backendParams.endProductionDate + delete backendParams.endProductionDate + } + return request.get(`${BASE}/list`, { params: backendParams }).then((res: any) => { + const rows = (res.rows ?? res.data?.rows ?? []).map((row: any) => { + // 后端返回 requestDate,前端显示用 productionDate + if (row.requestDate && !row.productionDate) { + row.productionDate = row.requestDate + } + return row + }) + const total = res.total ?? res.data?.total ?? 0 + return { rows, total } + }) +} + +/** 获取生产工单详情 */ +export function getWorkOrderDetail(workorderId: number): Promise { + return request.get(`${BASE}/${workorderId}`).then((res: any) => { + const data = res.data ?? res + // 后端返回 requestDate,前端用 productionDate + if (data.requestDate && !data.productionDate) { + data.productionDate = data.requestDate + } + return data + }) +} + +/** 将前端字段名映射为后端字段名 */ +function toBackendPayload(data: Partial): Record { + const payload: Record = { ...data } + // 前端用 productionDate,后端用 requestDate + if (payload.productionDate !== undefined) { + payload.requestDate = payload.productionDate + delete payload.productionDate + } + return payload +} + +/** 新增生产工单 */ +export function createWorkOrder(data: Partial): Promise { + return request.post(BASE, toBackendPayload(data)).then((res: any) => res.data ?? res) +} + +/** 修改生产工单 */ +export function updateWorkOrder(data: Partial): Promise { + return request.put(BASE, toBackendPayload(data)) +} + +/** 删除生产工单(支持批量) */ +export function deleteWorkOrder(workorderIds: number | number[] | string): Promise { + const ids = Array.isArray(workorderIds) ? workorderIds.join(',') : String(workorderIds) + return request.delete(`${BASE}/${ids}`) +} + +/** 完工工单 */ +export function finishWorkOrder(workorderId: number): Promise { + return request.put(`${BASE}/finish/${workorderId}`) +} + +/** 取消工单 */ +export function cancelWorkOrder(workorderId: number): Promise { + return request.put(`${BASE}/cancel/${workorderId}`) +} + +/** 获取工单 BOM 组成列表(直接查 pro_workorder_bom 表,字段名直接对应) */ +export function getWorkOrderBomList(workorderId: number): Promise { + return request.get('/mes/pro/workorderbom/list', { + params: { workorderId, pageNum: 1, pageSize: 500 } + }).then((res: any) => { + return res.rows ?? res.data?.rows ?? [] + }) +} + +/** 导出生产工单 */ +export function exportWorkOrder(params: WorkOrderQuery): Promise { + return request.post(`${BASE}/export`, params, { responseType: 'blob' }) +} + +/** 一键领料:根据工单 BOM 自动创建领料单,返回领料单 ID */ +export function quickIssue(workorderId: number): Promise { + return request.post(`${BASE}/quickIssue/${workorderId}`).then((res: any) => res.data ?? res) +} + +/** 审核工单(确认):PREPARE → CONFIRMED */ +export function confirmWorkOrder(workorderId: number): Promise { + return request.put(`${BASE}/confirm/${workorderId}`) +} + +/** 反审核工单:CONFIRMED → PREPARE */ +export function unconfirmWorkOrder(workorderId: number): Promise { + return request.put(`${BASE}/unconfirm/${workorderId}`) +} + +/** 查询工单关联的领料明细列表 */ +export function getIssueListByWorkorder(workorderId: number): Promise<{ rows: any[]; total: number }> { + return request.get('/mes/wm/issueheader/list', { + params: { workorderId, pageNum: 1, pageSize: 100 } + }).then((res: any) => ({ + rows: res.rows ?? [], + total: res.total ?? 0 + })) +} + +// ============ 工序任务(ProTask)API ============ + +/** 工序任务 */ +export interface ProTask { + taskId?: number + taskCode?: string + taskName?: string + workorderId?: number + workorderCode?: string + processId?: number + processCode?: string + processName?: string + quantity?: number + quantityProduced?: number + quantityQuanlified?: number + quantityUnquanlified?: number + startTime?: string + duration?: number + endTime?: string + colorCode?: string + status?: string + remark?: string + createBy?: string + createTime?: string + updateBy?: string + updateTime?: string +} + +/** 根据工单 ID 获取工序任务列表 */ +export function getTaskListByWorkorder(workorderId: number): Promise { + return request.get('/mes/pro/protask/list', { + params: { workorderId, pageNum: 1, pageSize: 500 } + }).then((res: any) => { + return res.rows ?? res.data?.rows ?? [] + }) +} + +// ============ 工艺路线选择 API ============ + +/** 工艺路线列表(选择弹窗,不默认过滤 enableFlag 以避免列表为空) */ +export function getRouteList(params?: { + routeCode?: string + routeName?: string + enableFlag?: string + pageNum?: number + pageSize?: number +}): Promise<{ rows: any[]; total: number }> { + return request.get('/mes/pro/proroute/list', { + params: { pageNum: 1, pageSize: 100, ...params } + }).then((res: any) => ({ + rows: res.rows ?? [], + total: res.total ?? 0 + })) +} + +// ============ 客户选择 API(备用) ============ + +/** 客户列表(选择弹窗) */ +export function getClientList(params?: { + clientCode?: string + clientName?: string + pageNum?: number + pageSize?: number +}): Promise<{ rows: any[]; total: number }> { + return request.get('/mes/md/client/list', { + params: { pageNum: 1, pageSize: 100, ...params } + }).then((res: any) => ({ + rows: res.rows ?? [], + total: res.total ?? 0 + })) +} diff --git a/erp-frontend-vue/src/assets/logo-color.png b/erp-frontend-vue/src/assets/logo-color.png new file mode 100644 index 0000000000000000000000000000000000000000..22c691b67f458a7375c4a632370dfafa2e0e22e0 GIT binary patch literal 33131 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 Bk&yrZ literal 0 HcmV?d00001 diff --git a/erp-frontend-vue/src/assets/logo-white.png b/erp-frontend-vue/src/assets/logo-white.png new file mode 100644 index 0000000000000000000000000000000000000000..500a153c9db30e49d7200b11e17b3085d0408d4c GIT binary patch literal 11811 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 literal 0 HcmV?d00001 diff --git a/erp-frontend-vue/src/assets/vue.svg b/erp-frontend-vue/src/assets/vue.svg new file mode 100644 index 0000000..770e9d3 --- /dev/null +++ b/erp-frontend-vue/src/assets/vue.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/erp-frontend-vue/src/components/HelloWorld.vue b/erp-frontend-vue/src/components/HelloWorld.vue new file mode 100644 index 0000000..b58e52b --- /dev/null +++ b/erp-frontend-vue/src/components/HelloWorld.vue @@ -0,0 +1,41 @@ + + + + + diff --git a/erp-frontend-vue/src/components/print/PrintDialog.vue b/erp-frontend-vue/src/components/print/PrintDialog.vue new file mode 100644 index 0000000..7fb9fb1 --- /dev/null +++ b/erp-frontend-vue/src/components/print/PrintDialog.vue @@ -0,0 +1,309 @@ + + + + + + + diff --git a/erp-frontend-vue/src/components/print/QrCode.vue b/erp-frontend-vue/src/components/print/QrCode.vue new file mode 100644 index 0000000..394bbcb --- /dev/null +++ b/erp-frontend-vue/src/components/print/QrCode.vue @@ -0,0 +1,38 @@ + + + diff --git a/erp-frontend-vue/src/components/print/types.ts b/erp-frontend-vue/src/components/print/types.ts new file mode 100644 index 0000000..ebe9223 --- /dev/null +++ b/erp-frontend-vue/src/components/print/types.ts @@ -0,0 +1,39 @@ +/** 表头字段 */ +export interface PrintHeaderField { + label: string // "单据编码" + value: string | number // "SCDD000001" + span?: number // 占列数,默认 1(共 4 列一行) +} + +/** 明细表列 */ +export interface PrintColumn { + prop: string // 数据字段名 + label: string // 列标题 + width?: string // 列宽 + align?: 'left' | 'center' | 'right' + type?: 'text' | 'index' | 'qrcode' | 'amount' + qrCodeProp?: string // type=qrcode 时的编码内容字段 + formatter?: (row: any, index: number) => string +} + +/** 合计行 */ +export interface PrintSummary { + label: string + value: string | number +} + +/** 完整打印配置 */ +export interface PrintConfig { + systemName?: string // 默认 "升阳云ERP" + title: string // "销售订单" | "到货通知单" | ... + subtitle?: string // "加工车间" 等 + qrCodeValue?: string // 右上角二维码内容 + headerFields: PrintHeaderField[] // 4 列网格,自动换行 + columns: PrintColumn[] // 明细表列 + data: any[] // 明细表数据 + summaries?: PrintSummary[] // 合计行(可选) + footer: { + creator?: string // 制单人 + approver?: string // 审核人 + } +} diff --git a/erp-frontend-vue/src/layout/index.vue b/erp-frontend-vue/src/layout/index.vue new file mode 100644 index 0000000..0aa90e2 --- /dev/null +++ b/erp-frontend-vue/src/layout/index.vue @@ -0,0 +1,290 @@ + + + + + diff --git a/erp-frontend-vue/src/main.ts b/erp-frontend-vue/src/main.ts new file mode 100644 index 0000000..38990ea --- /dev/null +++ b/erp-frontend-vue/src/main.ts @@ -0,0 +1,23 @@ +import { createApp } from 'vue' +import ElementPlus from 'element-plus' +import 'element-plus/dist/index.css' +// @ts-ignore +import zhCn from 'element-plus/dist/locale/zh-cn.mjs' +import * as ElementPlusIconsVue from '@element-plus/icons-vue' +import router from './router' +import App from './App.vue' +import './style.css' + +// 路由守卫(需要在 router 使用之后引入) +import './permission' + +const app = createApp(App) + +// 注册所有 Element Plus 图标 +for (const [key, component] of Object.entries(ElementPlusIconsVue)) { + app.component(key, component) +} + +app.use(ElementPlus, { locale: zhCn }) +app.use(router) +app.mount('#app') diff --git a/erp-frontend-vue/src/permission.ts b/erp-frontend-vue/src/permission.ts new file mode 100644 index 0000000..a08fb86 --- /dev/null +++ b/erp-frontend-vue/src/permission.ts @@ -0,0 +1,70 @@ +import router from './router' +import { ElMessage } from 'element-plus' +import { getToken } from '@/utils/auth' +import { useUserStore } from '@/stores/user' + +// 白名单路由(不需要登录即可访问) +const whiteList = ['/login', '/register'] + +// DEV模式:后端未运行时跳过认证,允许直接访问页面 +const DEV_SKIP_AUTH = import.meta.env.DEV && !import.meta.env.VITE_REQUIRE_AUTH + +// 路由守卫 +router.beforeEach(async (to, from, next) => { + // 设置页面标题 + document.title = to.meta.title ? `${to.meta.title} - ERP系统` : 'ERP系统' + + // DEV模式跳过认证:设置模拟角色以通过权限检查 + if (DEV_SKIP_AUTH) { + const userStore = useUserStore() + if (userStore.state.userInfo.roles.length === 0) { + userStore.state.userInfo.roles = ['admin'] + userStore.state.userInfo.permissions = ['*:*:*'] + userStore.state.userInfo.userName = 'dev' + userStore.state.userInfo.nickName = '开发模式' + } + next() + return + } + + const token = getToken() + const userStore = useUserStore() + + if (token) { + // 已登录 + if (to.path === '/login') { + // 已登录状态下访问登录页,重定向到首页 + next({ path: '/' }) + } else { + // 检查是否已获取用户信息 + if (userStore.state.userInfo.roles.length === 0) { + try { + // 获取用户信息 + await userStore.getUserInfo() + // 确保路由完成后再跳转 + next({ ...to, replace: true }) + } catch (error: any) { + // 获取用户信息失败,可能是 token 过期 + await userStore.fedLogout() + ElMessage.error(error?.message || '获取用户信息失败,请重新登录') + next(`/login?redirect=${to.fullPath}`) + } + } else { + next() + } + } + } else { + // 未登录 + if (whiteList.includes(to.path)) { + // 在白名单中,直接进入 + next() + } else { + // 不在白名单中,重定向到登录页 + next(`/login?redirect=${to.fullPath}`) + } + } +}) + +router.afterEach(() => { + // 可以在这里添加 NProgress 完成等逻辑 +}) diff --git a/erp-frontend-vue/src/router/index.ts b/erp-frontend-vue/src/router/index.ts new file mode 100644 index 0000000..028fba3 --- /dev/null +++ b/erp-frontend-vue/src/router/index.ts @@ -0,0 +1,538 @@ +import { createRouter, createWebHistory } from 'vue-router' +import type { RouteRecordRaw } from 'vue-router' +import Layout from '@/layout/index.vue' + +const routes: RouteRecordRaw[] = [ + { + path: '/login', + name: 'Login', + component: () => import('@/views/Login.vue'), + meta: { title: '登录' } + }, + { + path: '/', + component: Layout, + redirect: '/dashboard', + children: [ + { + path: 'dashboard', + name: 'Dashboard', + component: () => import('@/views/Dashboard.vue'), + meta: { title: '首页' } + } + ] + }, + { + path: '/sales', + component: Layout, + redirect: '/sales/customer', + meta: { title: '销售管理' }, + children: [ + { + path: 'customer', + name: 'CustomerList', + component: () => import('@/views/Sales/Customer/index.vue'), + meta: { title: '客户档案' } + }, + { + path: 'contract', + name: 'ContractList', + component: () => import('@/views/Sales/Contract/index.vue'), + meta: { title: '销售合同' } + }, + { + path: 'contract/new', + name: 'ContractNew', + component: () => import('@/views/Sales/Contract/form.vue'), + meta: { title: '新增销售合同' } + }, + { + path: 'contract/view/:id', + name: 'ContractView', + component: () => import('@/views/Sales/Contract/form.vue'), + meta: { title: '合同详情' } + }, + { + path: 'order', + name: 'SalesOrderList', + component: () => import('@/views/Sales/Order/index.vue'), + meta: { title: '销售订单' } + }, + { + path: 'order/form', + name: 'SalesOrderNew', + component: () => import('@/views/Sales/Order/form.vue'), + meta: { title: '新增销售订单' } + }, + { + path: 'order/form/:id', + name: 'SalesOrderEdit', + component: () => import('@/views/Sales/Order/form.vue'), + meta: { title: '编辑销售订单' } + }, + { + path: 'order/view/:id', + name: 'SalesOrderView', + component: () => import('@/views/Sales/Order/view.vue'), + meta: { title: '销售订单详情' } + }, + { + path: 'order/progress/:id', + name: 'SalesOrderProgress', + component: () => import('@/views/Sales/Order/index.vue'), + meta: { title: '生产进度' } + }, + { + path: 'hold-order', + name: 'HoldOrderList', + component: () => import('@/views/Sales/Order/index.vue'), + meta: { title: '备货订单' } + }, + { + path: 'deliver', + name: 'DeliverList', + component: () => import('@/views/Sales/Deliver/index.vue'), + meta: { title: '发货通知单' } + }, + { + path: 'deliver/new', + name: 'DeliverNew', + component: () => import('@/views/Sales/Deliver/form.vue'), + meta: { title: '新增发货通知单' } + }, + { + path: 'invoice', + name: 'InvoiceList', + component: () => import('@/views/Sales/Invoice/index.vue'), + meta: { title: '开票结算单' } + }, + { + path: 'invoice/new', + name: 'InvoiceNew', + component: () => import('@/views/Sales/Invoice/form.vue'), + meta: { title: '新增开票结算单' } + }, + { + path: 'saleback', + name: 'SalebackList', + component: () => import('@/views/Sales/Saleback/index.vue'), + meta: { title: '退货通知单' } + }, + { + path: 'saleback/new', + name: 'SalebackNew', + component: () => import('@/views/Sales/Saleback/form.vue'), + meta: { title: '新增退货通知单' } + }, + { + path: 'plan', + name: 'PlanReport', + component: () => import('@/views/Sales/Reports/PlanReport.vue'), + meta: { title: '销售订单计划表' } + }, + { + path: 'detail', + name: 'DetailReport', + component: () => import('@/views/Sales/Reports/DetailReport.vue'), + meta: { title: '销售订单明细表' } + }, + { + path: 'seven-day', + name: 'SevenDayReport', + component: () => import('@/views/Sales/Reports/SevenDayReport.vue'), + meta: { title: '7天交货明细表' } + } + ] + }, + { + path: '/purchasing', + component: Layout, + redirect: '/purchasing/supplier', + meta: { title: '采购管理' }, + children: [ + { + path: 'supplier', + name: 'SupplierList', + component: () => import('@/views/Purchasing/Supplier/index.vue'), + meta: { title: '供应商' } + }, + { + path: 'order', + name: 'PurchaseOrderList', + component: () => import('@/views/Purchasing/Order/index.vue'), + meta: { title: '采购订单' } + }, + { + path: 'order/new', + name: 'PurchaseOrderNew', + component: () => import('@/views/Purchasing/Order/form.vue'), + meta: { title: '新增采购订单' } + }, + { + path: 'order/edit/:id', + name: 'PurchaseOrderEdit', + component: () => import('@/views/Purchasing/Order/form.vue'), + meta: { title: '编辑采购订单' } + }, + { + path: 'order/view/:id', + name: 'PurchaseOrderView', + component: () => import('@/views/Purchasing/Order/form.vue'), + meta: { title: '采购订单详情' } + }, + { + path: 'stock-order', + name: 'StockOrderList', + component: () => import('@/views/Purchasing/Order/index.vue'), + meta: { title: '备库订单' } + }, + { + path: 'checkin', + name: 'CheckinList', + component: () => import('@/views/Purchasing/Checkin/index.vue'), + meta: { title: '采购到货单' } + }, + { + path: 'invoice', + name: 'PurchaseInvoiceList', + component: () => import('@/views/Purchasing/Invoice/index.vue'), + meta: { title: '采购发票' } + }, + { + path: 'reject', + name: 'RejectList', + component: () => import('@/views/Purchasing/Reject/index.vue'), + meta: { title: '采购退货单' } + }, + { + path: 'report/detail', + name: 'PoDetailReport', + component: () => import('@/views/Purchasing/Reports/DetailReport.vue'), + meta: { title: '采购执行明细表' } + }, + { + path: 'report/total', + name: 'PoTotalReport', + component: () => import('@/views/Purchasing/Reports/TotalReport.vue'), + meta: { title: '采购执行汇总表' } + }, + { + path: 'report/checkin', + name: 'PoCheckinReport', + component: () => import('@/views/Purchasing/Reports/CheckinReport.vue'), + meta: { title: '采购到货明细表' } + }, + { + path: 'report/fapiao', + name: 'PoInvoiceReport', + component: () => import('@/views/Purchasing/Reports/InvoiceReport.vue'), + meta: { title: '采购发票明细表' } + } + ] + }, + { + path: '/production', + component: Layout, + redirect: '/production/plan-order', + meta: { title: '生产计划' }, + children: [ + { + path: 'plan-order', + name: 'ProductionPlanList', + component: () => import('@/views/Production/PlanOrder/index.vue'), + meta: { title: '生产计划单' } + }, + { + path: 'plan-order/new', + name: 'ProductionPlanNew', + component: () => import('@/views/Production/PlanOrder/form.vue'), + meta: { title: '新增生产计划单' } + }, + { + path: 'plan-order/edit/:id', + name: 'ProductionPlanEdit', + component: () => import('@/views/Production/PlanOrder/form.vue'), + meta: { title: '编辑生产计划单' } + }, + { + path: 'plan-order/view/:id', + name: 'ProductionPlanView', + component: () => import('@/views/Production/PlanOrder/form.vue'), + meta: { title: '生产计划单详情' } + }, + { + path: 'work-order', + name: 'WorkOrderList', + component: () => import('@/views/Production/WorkOrder/index.vue'), + meta: { title: '生产订单' } + }, + { + path: 'work-order/new', + name: 'WorkOrderNew', + component: () => import('@/views/Production/WorkOrder/form.vue'), + meta: { title: '新增生产工单' } + }, + { + path: 'work-order/edit/:id', + name: 'WorkOrderEdit', + component: () => import('@/views/Production/WorkOrder/form.vue'), + meta: { title: '编辑生产工单' } + }, + { + path: 'work-order/view/:id', + name: 'WorkOrderView', + component: () => import('@/views/Production/WorkOrder/form.vue'), + meta: { title: '查看生产工单' } + }, + { + path: 'parts', + name: 'PartsOrderList', + component: () => import('@/views/Production/Parts/index.vue'), + meta: { title: '零部件订单' } + }, + { + path: 'mbom', + name: 'MbomList', + component: () => import('@/views/Production/Mbom/index.vue'), + meta: { title: '物料清单' } + }, + { + path: 'mbom/view/:id', + name: 'MbomView', + component: () => import('@/views/Production/Mbom/form.vue'), + meta: { title: '查看物料清单' } + }, + { + path: 'mbom/edit/:id', + name: 'MbomEdit', + component: () => import('@/views/Production/Mbom/form.vue'), + meta: { title: '编辑物料清单' } + }, + { + path: 'purchase-plan', + name: 'PurchasePlanList', + component: () => import('@/views/Production/PurchasePlan/index.vue'), + meta: { title: '采购计划单' } + }, + { + path: 'purchase-plan/new', + name: 'PurchasePlanNew', + component: () => import('@/views/Production/PurchasePlan/form.vue'), + meta: { title: '新增采购计划' } + }, + { + path: 'purchase-plan/edit/:id', + name: 'PurchasePlanEdit', + component: () => import('@/views/Production/PurchasePlan/form.vue'), + meta: { title: '编辑采购计划' } + }, + { + path: 'purchase-plan/view/:id', + name: 'PurchasePlanView', + component: () => import('@/views/Production/PurchasePlan/form.vue'), + meta: { title: '查看采购计划' } + }, + { + path: 'stock-plan', + name: 'StockPlanList', + component: () => import('@/views/Production/PurchasePlan/index.vue'), + meta: { title: '备库计划单' } + }, + { + path: 'report/detail', + name: 'MpDetailReport', + component: () => import('@/views/Production/Reports/DetailReport.vue'), + meta: { title: '计划执行明细表' } + }, + { + path: 'report/total', + name: 'MpTotalReport', + component: () => import('@/views/Production/Reports/TotalReport.vue'), + meta: { title: '计划执行汇总表' } + }, + { + path: 'report/need', + name: 'PurchaseNeedReport', + component: () => import('@/views/Production/Reports/NeedReport.vue'), + meta: { title: '采购计划需求表' } + } + ] + }, + { + path: '/system', + component: Layout, + redirect: '/system/user', + meta: { title: '组织架构' }, + children: [ + { + path: 'user', + name: 'UserList', + component: () => import('@/views/System/User/index.vue'), + meta: { title: '用户管理' } + }, + { + path: 'role', + name: 'RoleList', + component: () => import('@/views/System/Role/index.vue'), + meta: { title: '角色管理' } + }, + { + path: 'dept', + name: 'DeptList', + component: () => import('@/views/System/Dept/index.vue'), + meta: { title: '部门管理' } + }, + { + path: 'post', + name: 'PostList', + component: () => import('@/views/System/Post/index.vue'), + meta: { title: '岗位管理' } + }, + { + path: 'user-auth/role/:userId', + name: 'UserAuthRole', + component: () => import('@/views/System/User/index.vue'), + meta: { title: '分配角色' } + }, + { + path: 'role-auth/user/:roleId', + name: 'RoleAuthUser', + component: () => import('@/views/System/Role/index.vue'), + meta: { title: '分配用户' } + } + ] + }, + { + path: '/rd', + component: Layout, + redirect: '/rd/ebom', + meta: { title: '研发管理' }, + children: [ + { + path: 'ebom', + name: 'EbomList', + component: () => import('@/views/RD/Ebom/index.vue'), + meta: { title: '产品BOM' } + }, + { + path: 'ebom/new', + name: 'EbomNew', + component: () => import('@/views/RD/Ebom/form.vue'), + meta: { title: '新增EBOM' } + }, + { + path: 'ebom/edit/:id', + name: 'EbomEdit', + component: () => import('@/views/RD/Ebom/form.vue'), + meta: { title: '编辑EBOM' } + }, + { + path: 'ebom/view/:id', + name: 'EbomView', + component: () => import('@/views/RD/Ebom/form.vue'), + meta: { title: 'EBOM详情' } + } + ] + }, + { + path: '/masterdata', + component: Layout, + redirect: '/masterdata/item', + meta: { title: '主数据' }, + children: [ + { + path: 'item', + name: 'MdItemList', + component: () => import('@/views/MasterData/Item/index.vue'), + meta: { title: '物料管理' } + }, + { + path: 'itemtype', + name: 'ItemTypeList', + component: () => import('@/views/MasterData/ItemType/index.vue'), + meta: { title: '物料分类' } + }, + { + path: 'unit', + name: 'UnitList', + component: () => import('@/views/MasterData/Unit/index.vue'), + meta: { title: '计量单位' } + }, + { + path: 'workshop', + name: 'WorkshopList', + component: () => import('@/views/MasterData/Workshop/index.vue'), + meta: { title: '车间管理' } + }, + { + path: 'workstation', + name: 'WorkstationList', + component: () => import('@/views/MasterData/Workstation/index.vue'), + meta: { title: '工作站管理' } + }, + { + path: 'bom', + name: 'BomList', + component: () => import('@/views/MasterData/Bom/list.vue'), + meta: { title: '产品BOM' } + }, + { + path: 'bom/new', + name: 'BomNew', + component: () => import('@/views/MasterData/Bom/form.vue'), + meta: { title: '新增产品BOM' } + }, + { + path: 'bom/edit/:id', + name: 'BomEdit', + component: () => import('@/views/MasterData/Bom/form.vue'), + meta: { title: '编辑产品BOM' } + }, + { + path: 'bom/view/:id', + name: 'BomView', + component: () => import('@/views/MasterData/Bom/form.vue'), + meta: { title: '产品BOM详情' } + } + ] + }, + { + path: '/warehouse', + component: Layout, + redirect: '/warehouse/issue', + meta: { title: '仓储管理' }, + children: [ + { + path: 'issue', + name: 'IssueList', + component: () => import('@/views/Warehouse/Issue/index.vue'), + meta: { title: '生产领料' } + }, + { + path: 'issue/new', + name: 'IssueNew', + component: () => import('@/views/Warehouse/Issue/form.vue'), + meta: { title: '新增领料单' } + }, + { + path: 'issue/edit/:id', + name: 'IssueEdit', + component: () => import('@/views/Warehouse/Issue/form.vue'), + meta: { title: '编辑领料单' } + }, + { + path: 'issue/view/:id', + name: 'IssueView', + component: () => import('@/views/Warehouse/Issue/form.vue'), + meta: { title: '查看领料单' } + } + ] + } +] + +const router = createRouter({ + history: createWebHistory(), + routes +}) + +export default router diff --git a/erp-frontend-vue/src/stores/user.ts b/erp-frontend-vue/src/stores/user.ts new file mode 100644 index 0000000..05a636f --- /dev/null +++ b/erp-frontend-vue/src/stores/user.ts @@ -0,0 +1,111 @@ +import { reactive } from 'vue' +import { login, logout, getInfo } from '@/api/auth' +import { getToken, setToken, removeToken } from '@/utils/auth' + +export interface UserInfo { + userId: number | null + userName: string + nickName: string + avatar: string + roles: string[] + permissions: string[] +} + +export interface UserState { + token: string | null + userInfo: UserInfo + isLoggedIn: boolean +} + +const state = reactive({ + token: getToken(), + userInfo: { + userId: null, + userName: '', + nickName: '', + avatar: '', + roles: [], + permissions: [] + }, + isLoggedIn: !!getToken() +}) + +// 登录 +async function loginAction(username: string, password: string, code: string, uuid: string) { + const trimmedUsername = username.trim() + const res = await login(trimmedUsername, password, code, uuid) + if (res.token) { + setToken(res.token) + state.token = res.token + state.isLoggedIn = true + } + return res +} + +// 获取用户信息 +async function getUserInfo() { + const res = await getInfo() + const user = res.user + if (res.roles && res.roles.length > 0) { + state.userInfo.roles = res.roles + state.userInfo.permissions = res.permissions || [] + } else { + state.userInfo.roles = ['ROLE_DEFAULT'] + } + state.userInfo.userId = user.userId + state.userInfo.userName = user.userName + state.userInfo.nickName = user.nickName + state.userInfo.avatar = user.avatar || '' + return res +} + +// 退出登录 +async function logoutAction() { + try { + await logout() + } catch (e) { + // 忽略退出接口错误 + } + resetState() +} + +// 前端登出(不调用后端接口) +function fedLogout() { + resetState() +} + +// 重置状态 +function resetState() { + removeToken() + state.token = null + state.isLoggedIn = false + state.userInfo = { + userId: null, + userName: '', + nickName: '', + avatar: '', + roles: [], + permissions: [] + } +} + +// 检查是否有权限 +function hasRole(role: string): boolean { + return state.userInfo.roles.includes(role) || state.userInfo.roles.includes('admin') +} + +function hasPermission(permission: string): boolean { + return state.userInfo.permissions.includes('*:*:*') || + state.userInfo.permissions.includes(permission) +} + +export const useUserStore = () => ({ + state, + loginAction, + getUserInfo, + logoutAction, + fedLogout, + resetState, + hasRole, + hasPermission +}) diff --git a/erp-frontend-vue/src/style.css b/erp-frontend-vue/src/style.css new file mode 100644 index 0000000..d652c79 --- /dev/null +++ b/erp-frontend-vue/src/style.css @@ -0,0 +1,41 @@ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +html, body, #app { + width: 100%; + height: 100%; + font-family: 'Helvetica Neue', Helvetica, 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', SimSun, sans-serif; +} + +/* 自定义滚动条 */ +::-webkit-scrollbar { + width: 6px; + height: 6px; +} + +::-webkit-scrollbar-thumb { + background-color: #c0c4cc; + border-radius: 3px; +} + +::-webkit-scrollbar-track { + background-color: #f5f7fa; +} + +/* 工具栏按钮样式 */ +.toolbar-btn { + margin-right: 8px; +} + +/* 表单间距 */ +.el-form-item { + margin-bottom: 18px; +} + +/* 表格样式优化 */ +.el-table th.el-table__cell { + background-color: #f5f7fa; +} diff --git a/erp-frontend-vue/src/utils/auth.ts b/erp-frontend-vue/src/utils/auth.ts new file mode 100644 index 0000000..0221ecd --- /dev/null +++ b/erp-frontend-vue/src/utils/auth.ts @@ -0,0 +1,13 @@ +const TokenKey = 'Admin-Token' + +export function getToken(): string | null { + return localStorage.getItem(TokenKey) +} + +export function setToken(token: string): void { + localStorage.setItem(TokenKey, token) +} + +export function removeToken(): void { + localStorage.removeItem(TokenKey) +} diff --git a/erp-frontend-vue/src/views/Dashboard.vue b/erp-frontend-vue/src/views/Dashboard.vue new file mode 100644 index 0000000..199a757 --- /dev/null +++ b/erp-frontend-vue/src/views/Dashboard.vue @@ -0,0 +1,179 @@ + + + + + diff --git a/erp-frontend-vue/src/views/Login.vue b/erp-frontend-vue/src/views/Login.vue new file mode 100644 index 0000000..494b08e --- /dev/null +++ b/erp-frontend-vue/src/views/Login.vue @@ -0,0 +1,278 @@ + + + + + diff --git a/erp-frontend-vue/src/views/MasterData/Bom/form.vue b/erp-frontend-vue/src/views/MasterData/Bom/form.vue new file mode 100644 index 0000000..b7d55ed --- /dev/null +++ b/erp-frontend-vue/src/views/MasterData/Bom/form.vue @@ -0,0 +1,656 @@ + + + + + + diff --git a/erp-frontend-vue/src/views/MasterData/Bom/list.vue b/erp-frontend-vue/src/views/MasterData/Bom/list.vue new file mode 100644 index 0000000..af85c10 --- /dev/null +++ b/erp-frontend-vue/src/views/MasterData/Bom/list.vue @@ -0,0 +1,436 @@ + + + + + + diff --git a/erp-frontend-vue/src/views/MasterData/Item/index.vue b/erp-frontend-vue/src/views/MasterData/Item/index.vue new file mode 100644 index 0000000..2bfb907 --- /dev/null +++ b/erp-frontend-vue/src/views/MasterData/Item/index.vue @@ -0,0 +1,525 @@ + + + + + diff --git a/erp-frontend-vue/src/views/MasterData/ItemType/index.vue b/erp-frontend-vue/src/views/MasterData/ItemType/index.vue new file mode 100644 index 0000000..8330a1e --- /dev/null +++ b/erp-frontend-vue/src/views/MasterData/ItemType/index.vue @@ -0,0 +1,285 @@ + + + + + diff --git a/erp-frontend-vue/src/views/MasterData/Unit/index.vue b/erp-frontend-vue/src/views/MasterData/Unit/index.vue new file mode 100644 index 0000000..15e9e49 --- /dev/null +++ b/erp-frontend-vue/src/views/MasterData/Unit/index.vue @@ -0,0 +1,293 @@ + + + + + diff --git a/erp-frontend-vue/src/views/MasterData/Workshop/index.vue b/erp-frontend-vue/src/views/MasterData/Workshop/index.vue new file mode 100644 index 0000000..93806bf --- /dev/null +++ b/erp-frontend-vue/src/views/MasterData/Workshop/index.vue @@ -0,0 +1,284 @@ + + + + + diff --git a/erp-frontend-vue/src/views/MasterData/Workstation/index.vue b/erp-frontend-vue/src/views/MasterData/Workstation/index.vue new file mode 100644 index 0000000..06d47af --- /dev/null +++ b/erp-frontend-vue/src/views/MasterData/Workstation/index.vue @@ -0,0 +1,305 @@ + + + + + diff --git a/erp-frontend-vue/src/views/Production/Mbom/form.vue b/erp-frontend-vue/src/views/Production/Mbom/form.vue new file mode 100644 index 0000000..bfcd92a --- /dev/null +++ b/erp-frontend-vue/src/views/Production/Mbom/form.vue @@ -0,0 +1,358 @@ + + + + + diff --git a/erp-frontend-vue/src/views/Production/Mbom/index.vue b/erp-frontend-vue/src/views/Production/Mbom/index.vue new file mode 100644 index 0000000..07b9a3c --- /dev/null +++ b/erp-frontend-vue/src/views/Production/Mbom/index.vue @@ -0,0 +1,518 @@ + + + + + diff --git a/erp-frontend-vue/src/views/Production/Parts/index.vue b/erp-frontend-vue/src/views/Production/Parts/index.vue new file mode 100644 index 0000000..9b2bb56 --- /dev/null +++ b/erp-frontend-vue/src/views/Production/Parts/index.vue @@ -0,0 +1,372 @@ + + + + + diff --git a/erp-frontend-vue/src/views/Production/PlanOrder/form.vue b/erp-frontend-vue/src/views/Production/PlanOrder/form.vue new file mode 100644 index 0000000..e6c8681 --- /dev/null +++ b/erp-frontend-vue/src/views/Production/PlanOrder/form.vue @@ -0,0 +1,1557 @@ + + + + + diff --git a/erp-frontend-vue/src/views/Production/PlanOrder/index.vue b/erp-frontend-vue/src/views/Production/PlanOrder/index.vue new file mode 100644 index 0000000..8b7fe66 --- /dev/null +++ b/erp-frontend-vue/src/views/Production/PlanOrder/index.vue @@ -0,0 +1,766 @@ + + + + + diff --git a/erp-frontend-vue/src/views/Production/PurchasePlan/form.vue b/erp-frontend-vue/src/views/Production/PurchasePlan/form.vue new file mode 100644 index 0000000..5ca2c20 --- /dev/null +++ b/erp-frontend-vue/src/views/Production/PurchasePlan/form.vue @@ -0,0 +1,1006 @@ + + + + + diff --git a/erp-frontend-vue/src/views/Production/PurchasePlan/index.vue b/erp-frontend-vue/src/views/Production/PurchasePlan/index.vue new file mode 100644 index 0000000..358dc30 --- /dev/null +++ b/erp-frontend-vue/src/views/Production/PurchasePlan/index.vue @@ -0,0 +1,763 @@ + + + + + diff --git a/erp-frontend-vue/src/views/Production/Reports/DetailReport.vue b/erp-frontend-vue/src/views/Production/Reports/DetailReport.vue new file mode 100644 index 0000000..2696077 --- /dev/null +++ b/erp-frontend-vue/src/views/Production/Reports/DetailReport.vue @@ -0,0 +1,360 @@ + + + + + diff --git a/erp-frontend-vue/src/views/Production/Reports/NeedReport.vue b/erp-frontend-vue/src/views/Production/Reports/NeedReport.vue new file mode 100644 index 0000000..eec4f79 --- /dev/null +++ b/erp-frontend-vue/src/views/Production/Reports/NeedReport.vue @@ -0,0 +1,184 @@ + + + + + diff --git a/erp-frontend-vue/src/views/Production/Reports/TotalReport.vue b/erp-frontend-vue/src/views/Production/Reports/TotalReport.vue new file mode 100644 index 0000000..0e520e0 --- /dev/null +++ b/erp-frontend-vue/src/views/Production/Reports/TotalReport.vue @@ -0,0 +1,341 @@ + + + + + diff --git a/erp-frontend-vue/src/views/Production/WorkOrder/form.vue b/erp-frontend-vue/src/views/Production/WorkOrder/form.vue new file mode 100644 index 0000000..2744c8a --- /dev/null +++ b/erp-frontend-vue/src/views/Production/WorkOrder/form.vue @@ -0,0 +1,861 @@ + + + + + diff --git a/erp-frontend-vue/src/views/Production/WorkOrder/index.vue b/erp-frontend-vue/src/views/Production/WorkOrder/index.vue new file mode 100644 index 0000000..5981a1d --- /dev/null +++ b/erp-frontend-vue/src/views/Production/WorkOrder/index.vue @@ -0,0 +1,546 @@ + + + + + diff --git a/erp-frontend-vue/src/views/Purchasing/Checkin/index.vue b/erp-frontend-vue/src/views/Purchasing/Checkin/index.vue new file mode 100644 index 0000000..6e0388e --- /dev/null +++ b/erp-frontend-vue/src/views/Purchasing/Checkin/index.vue @@ -0,0 +1,123 @@ + + + + + diff --git a/erp-frontend-vue/src/views/Purchasing/Invoice/index.vue b/erp-frontend-vue/src/views/Purchasing/Invoice/index.vue new file mode 100644 index 0000000..542c730 --- /dev/null +++ b/erp-frontend-vue/src/views/Purchasing/Invoice/index.vue @@ -0,0 +1,133 @@ + + + + + diff --git a/erp-frontend-vue/src/views/Purchasing/Order/form.vue b/erp-frontend-vue/src/views/Purchasing/Order/form.vue new file mode 100644 index 0000000..56684ce --- /dev/null +++ b/erp-frontend-vue/src/views/Purchasing/Order/form.vue @@ -0,0 +1,949 @@ + + + + + diff --git a/erp-frontend-vue/src/views/Purchasing/Order/index.vue b/erp-frontend-vue/src/views/Purchasing/Order/index.vue new file mode 100644 index 0000000..6445ddd --- /dev/null +++ b/erp-frontend-vue/src/views/Purchasing/Order/index.vue @@ -0,0 +1,337 @@ + + + + + diff --git a/erp-frontend-vue/src/views/Purchasing/Reject/index.vue b/erp-frontend-vue/src/views/Purchasing/Reject/index.vue new file mode 100644 index 0000000..42ea4b3 --- /dev/null +++ b/erp-frontend-vue/src/views/Purchasing/Reject/index.vue @@ -0,0 +1,124 @@ + + + + + diff --git a/erp-frontend-vue/src/views/Purchasing/Reports/CheckinReport.vue b/erp-frontend-vue/src/views/Purchasing/Reports/CheckinReport.vue new file mode 100644 index 0000000..04aa9c5 --- /dev/null +++ b/erp-frontend-vue/src/views/Purchasing/Reports/CheckinReport.vue @@ -0,0 +1,93 @@ + + + + + diff --git a/erp-frontend-vue/src/views/Purchasing/Reports/DetailReport.vue b/erp-frontend-vue/src/views/Purchasing/Reports/DetailReport.vue new file mode 100644 index 0000000..cd3c403 --- /dev/null +++ b/erp-frontend-vue/src/views/Purchasing/Reports/DetailReport.vue @@ -0,0 +1,104 @@ + + + + + diff --git a/erp-frontend-vue/src/views/Purchasing/Reports/InvoiceReport.vue b/erp-frontend-vue/src/views/Purchasing/Reports/InvoiceReport.vue new file mode 100644 index 0000000..a4f662a --- /dev/null +++ b/erp-frontend-vue/src/views/Purchasing/Reports/InvoiceReport.vue @@ -0,0 +1,101 @@ + + + + + diff --git a/erp-frontend-vue/src/views/Purchasing/Reports/TotalReport.vue b/erp-frontend-vue/src/views/Purchasing/Reports/TotalReport.vue new file mode 100644 index 0000000..074523f --- /dev/null +++ b/erp-frontend-vue/src/views/Purchasing/Reports/TotalReport.vue @@ -0,0 +1,96 @@ + + + + + diff --git a/erp-frontend-vue/src/views/Purchasing/Supplier/index.vue b/erp-frontend-vue/src/views/Purchasing/Supplier/index.vue new file mode 100644 index 0000000..2951f37 --- /dev/null +++ b/erp-frontend-vue/src/views/Purchasing/Supplier/index.vue @@ -0,0 +1,799 @@ + + + + + diff --git a/erp-frontend-vue/src/views/RD/Ebom/form.vue b/erp-frontend-vue/src/views/RD/Ebom/form.vue new file mode 100644 index 0000000..9258a41 --- /dev/null +++ b/erp-frontend-vue/src/views/RD/Ebom/form.vue @@ -0,0 +1,963 @@ + + + + + diff --git a/erp-frontend-vue/src/views/RD/Ebom/index.vue b/erp-frontend-vue/src/views/RD/Ebom/index.vue new file mode 100644 index 0000000..3736f2b --- /dev/null +++ b/erp-frontend-vue/src/views/RD/Ebom/index.vue @@ -0,0 +1,661 @@ + + + + + diff --git a/erp-frontend-vue/src/views/Sales/Contract/form.vue b/erp-frontend-vue/src/views/Sales/Contract/form.vue new file mode 100644 index 0000000..8803323 --- /dev/null +++ b/erp-frontend-vue/src/views/Sales/Contract/form.vue @@ -0,0 +1,154 @@ + + + + + diff --git a/erp-frontend-vue/src/views/Sales/Contract/index.vue b/erp-frontend-vue/src/views/Sales/Contract/index.vue new file mode 100644 index 0000000..c2889d0 --- /dev/null +++ b/erp-frontend-vue/src/views/Sales/Contract/index.vue @@ -0,0 +1,133 @@ + + + + + diff --git a/erp-frontend-vue/src/views/Sales/Customer/__tests__/index.spec.ts b/erp-frontend-vue/src/views/Sales/Customer/__tests__/index.spec.ts new file mode 100644 index 0000000..148eae9 --- /dev/null +++ b/erp-frontend-vue/src/views/Sales/Customer/__tests__/index.spec.ts @@ -0,0 +1,73 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest' +import { mount } from '@vue/test-utils' +import ElementPlus from 'element-plus' +import Customer from '../index.vue' +import * as customerApi from '@/api/customer' + +vi.mock('@/api/customer', () => ({ + getCustomerList: vi.fn(), + getCustomerDetail: vi.fn(), + createCustomer: vi.fn(), + updateCustomer: vi.fn(), + deleteCustomer: vi.fn(), + updateCustomerStatus: vi.fn() +})) + +describe('Customer 客户档案', () => { + beforeEach(() => { + vi.clearAllMocks() + vi.mocked(customerApi.getCustomerList).mockResolvedValue({ list: [], total: 0 }) + }) + + it('renders and loads list', async () => { + vi.mocked(customerApi.getCustomerList).mockResolvedValue({ + list: [ + { + clientId: 1, + clientCode: 'C001', + clientName: '客户A', + clientNick: 'A', + contact1: '张三', + contact1Tel: '13800001111', + address: '深圳', + enableFlag: 'Y', + createTime: '2026-01-25 10:00:00' + } + ], + total: 1 + }) + + const wrapper = mount(Customer, { + global: { + plugins: [ElementPlus], + stubs: { + ElPopconfirm: { template: '
' } + } + } + }) + + await vi.waitFor(() => { + expect(customerApi.getCustomerList).toHaveBeenCalled() + }) + + expect(wrapper.find('.search-card').exists()).toBe(true) + expect(wrapper.find('.table-card').exists()).toBe(true) + expect(wrapper.find('table').exists()).toBe(true) + expect(wrapper.find('.pagination-wrapper').exists()).toBe(true) + }) + + it('has query inputs and toolbar buttons', async () => { + const wrapper = mount(Customer, { + global: { + plugins: [ElementPlus], + stubs: { + ElPopconfirm: { template: '
' } + } + } + }) + + await wrapper.vm.$nextTick() + expect(wrapper.find('.search-card').exists()).toBe(true) + expect(wrapper.text()).toMatch(/新增|刷新|导出|批量删除/) + }) +}) diff --git a/erp-frontend-vue/src/views/Sales/Customer/index.vue b/erp-frontend-vue/src/views/Sales/Customer/index.vue new file mode 100644 index 0000000..734ec7c --- /dev/null +++ b/erp-frontend-vue/src/views/Sales/Customer/index.vue @@ -0,0 +1,514 @@ + + + + + diff --git a/erp-frontend-vue/src/views/Sales/Deliver/form.vue b/erp-frontend-vue/src/views/Sales/Deliver/form.vue new file mode 100644 index 0000000..d4fdc08 --- /dev/null +++ b/erp-frontend-vue/src/views/Sales/Deliver/form.vue @@ -0,0 +1,203 @@ + + + + + diff --git a/erp-frontend-vue/src/views/Sales/Deliver/index.vue b/erp-frontend-vue/src/views/Sales/Deliver/index.vue new file mode 100644 index 0000000..7385650 --- /dev/null +++ b/erp-frontend-vue/src/views/Sales/Deliver/index.vue @@ -0,0 +1,130 @@ + + + + + diff --git a/erp-frontend-vue/src/views/Sales/Invoice/form.vue b/erp-frontend-vue/src/views/Sales/Invoice/form.vue new file mode 100644 index 0000000..c05ac3e --- /dev/null +++ b/erp-frontend-vue/src/views/Sales/Invoice/form.vue @@ -0,0 +1,135 @@ + + + + + diff --git a/erp-frontend-vue/src/views/Sales/Invoice/index.vue b/erp-frontend-vue/src/views/Sales/Invoice/index.vue new file mode 100644 index 0000000..f8003a6 --- /dev/null +++ b/erp-frontend-vue/src/views/Sales/Invoice/index.vue @@ -0,0 +1,128 @@ + + + + + diff --git a/erp-frontend-vue/src/views/Sales/Order/form.vue b/erp-frontend-vue/src/views/Sales/Order/form.vue new file mode 100644 index 0000000..e717e9e --- /dev/null +++ b/erp-frontend-vue/src/views/Sales/Order/form.vue @@ -0,0 +1,1324 @@ + + + + + diff --git a/erp-frontend-vue/src/views/Sales/Order/index.vue b/erp-frontend-vue/src/views/Sales/Order/index.vue new file mode 100644 index 0000000..67b93ca --- /dev/null +++ b/erp-frontend-vue/src/views/Sales/Order/index.vue @@ -0,0 +1,629 @@ + + + + + diff --git a/erp-frontend-vue/src/views/Sales/Order/view.vue b/erp-frontend-vue/src/views/Sales/Order/view.vue new file mode 100644 index 0000000..4853d5e --- /dev/null +++ b/erp-frontend-vue/src/views/Sales/Order/view.vue @@ -0,0 +1,635 @@ + + + + + diff --git a/erp-frontend-vue/src/views/Sales/Reports/DetailReport.vue b/erp-frontend-vue/src/views/Sales/Reports/DetailReport.vue new file mode 100644 index 0000000..76a9ce6 --- /dev/null +++ b/erp-frontend-vue/src/views/Sales/Reports/DetailReport.vue @@ -0,0 +1,147 @@ + + + + + diff --git a/erp-frontend-vue/src/views/Sales/Reports/PlanReport.vue b/erp-frontend-vue/src/views/Sales/Reports/PlanReport.vue new file mode 100644 index 0000000..81d84b4 --- /dev/null +++ b/erp-frontend-vue/src/views/Sales/Reports/PlanReport.vue @@ -0,0 +1,135 @@ + + + + + diff --git a/erp-frontend-vue/src/views/Sales/Reports/SevenDayReport.vue b/erp-frontend-vue/src/views/Sales/Reports/SevenDayReport.vue new file mode 100644 index 0000000..84e5544 --- /dev/null +++ b/erp-frontend-vue/src/views/Sales/Reports/SevenDayReport.vue @@ -0,0 +1,149 @@ + + + + + diff --git a/erp-frontend-vue/src/views/Sales/Saleback/form.vue b/erp-frontend-vue/src/views/Sales/Saleback/form.vue new file mode 100644 index 0000000..df7085b --- /dev/null +++ b/erp-frontend-vue/src/views/Sales/Saleback/form.vue @@ -0,0 +1,183 @@ + + + + + diff --git a/erp-frontend-vue/src/views/Sales/Saleback/index.vue b/erp-frontend-vue/src/views/Sales/Saleback/index.vue new file mode 100644 index 0000000..9ab7729 --- /dev/null +++ b/erp-frontend-vue/src/views/Sales/Saleback/index.vue @@ -0,0 +1,122 @@ + + + + + diff --git a/erp-frontend-vue/src/views/System/Dept/index.vue b/erp-frontend-vue/src/views/System/Dept/index.vue new file mode 100644 index 0000000..5e7cad1 --- /dev/null +++ b/erp-frontend-vue/src/views/System/Dept/index.vue @@ -0,0 +1,334 @@ + + + + + diff --git a/erp-frontend-vue/src/views/System/Post/index.vue b/erp-frontend-vue/src/views/System/Post/index.vue new file mode 100644 index 0000000..b0ba8cb --- /dev/null +++ b/erp-frontend-vue/src/views/System/Post/index.vue @@ -0,0 +1,287 @@ + + + + + diff --git a/erp-frontend-vue/src/views/System/Role/index.vue b/erp-frontend-vue/src/views/System/Role/index.vue new file mode 100644 index 0000000..e1827ee --- /dev/null +++ b/erp-frontend-vue/src/views/System/Role/index.vue @@ -0,0 +1,333 @@ + + + + + diff --git a/erp-frontend-vue/src/views/System/User/index.vue b/erp-frontend-vue/src/views/System/User/index.vue new file mode 100644 index 0000000..b2945a4 --- /dev/null +++ b/erp-frontend-vue/src/views/System/User/index.vue @@ -0,0 +1,531 @@ + + + + + diff --git a/erp-frontend-vue/src/views/Warehouse/Issue/form.vue b/erp-frontend-vue/src/views/Warehouse/Issue/form.vue new file mode 100644 index 0000000..57628e6 --- /dev/null +++ b/erp-frontend-vue/src/views/Warehouse/Issue/form.vue @@ -0,0 +1,1408 @@ + + + + + diff --git a/erp-frontend-vue/src/views/Warehouse/Issue/index.vue b/erp-frontend-vue/src/views/Warehouse/Issue/index.vue new file mode 100644 index 0000000..b278f17 --- /dev/null +++ b/erp-frontend-vue/src/views/Warehouse/Issue/index.vue @@ -0,0 +1,459 @@ + + + + + diff --git a/erp-frontend-vue/tsconfig.app.json b/erp-frontend-vue/tsconfig.app.json new file mode 100644 index 0000000..1349c24 --- /dev/null +++ b/erp-frontend-vue/tsconfig.app.json @@ -0,0 +1,21 @@ +{ + "extends": "@vue/tsconfig/tsconfig.dom.json", + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", + "types": ["vite/client"], + "baseUrl": ".", + "paths": { + "@/*": ["./src/*"] + }, + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "erasableSyntaxOnly": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true + }, + "include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"], + "exclude": ["src/**/__tests__/**", "src/**/*.test.ts", "src/**/*.spec.ts"] +} diff --git a/erp-frontend-vue/tsconfig.json b/erp-frontend-vue/tsconfig.json new file mode 100644 index 0000000..fec8c8e --- /dev/null +++ b/erp-frontend-vue/tsconfig.json @@ -0,0 +1,13 @@ +{ + "files": [], + "references": [ + { "path": "./tsconfig.app.json" }, + { "path": "./tsconfig.node.json" } + ], + "compilerOptions": { + "baseUrl": ".", + "paths": { + "@/*": ["./src/*"] + } + } +} diff --git a/erp-frontend-vue/tsconfig.node.json b/erp-frontend-vue/tsconfig.node.json new file mode 100644 index 0000000..8a67f62 --- /dev/null +++ b/erp-frontend-vue/tsconfig.node.json @@ -0,0 +1,26 @@ +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", + "target": "ES2023", + "lib": ["ES2023"], + "module": "ESNext", + "types": ["node"], + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "moduleDetection": "force", + "noEmit": true, + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "erasableSyntaxOnly": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true + }, + "include": ["vite.config.ts"] +} diff --git a/erp-frontend-vue/vite.config.ts b/erp-frontend-vue/vite.config.ts new file mode 100644 index 0000000..009794b --- /dev/null +++ b/erp-frontend-vue/vite.config.ts @@ -0,0 +1,55 @@ +import { defineConfig } from 'vite' +import vue from '@vitejs/plugin-vue' +import path from 'path' + +export default defineConfig({ + plugins: [vue()], + resolve: { + alias: { + '@': path.resolve(__dirname, './src') + } + }, + server: { + port: 5177, + strictPort: false, // 端口被占用时自动尝试下一个可用端口 + proxy: { + '/api': { + target: 'http://localhost:8080', + changeOrigin: true + }, + '/erp': { + target: 'http://localhost:8080', + changeOrigin: true + }, + '/mes': { + target: 'http://localhost:8080', + changeOrigin: true + }, + '/system': { + target: 'http://localhost:8080', + changeOrigin: true + }, + // 登录相关接口代理 + '/login': { + target: 'http://localhost:8080', + changeOrigin: true + }, + '/logout': { + target: 'http://localhost:8080', + changeOrigin: true + }, + '/captchaImage': { + target: 'http://localhost:8080', + changeOrigin: true + }, + '/getInfo': { + target: 'http://localhost:8080', + changeOrigin: true + }, + '/register': { + target: 'http://localhost:8080', + changeOrigin: true + } + } + } +}) diff --git a/erp-frontend-vue/vitest.config.ts b/erp-frontend-vue/vitest.config.ts new file mode 100644 index 0000000..03bd62a --- /dev/null +++ b/erp-frontend-vue/vitest.config.ts @@ -0,0 +1,10 @@ +import { defineConfig } from 'vitest/config' +import viteConfig from './vite.config' + +export default defineConfig({ + ...viteConfig, + test: { + environment: 'jsdom', + globals: true + } +})