Compare commits
No commits in common. "929" and "粤公学标字〔2026〕5号" have entirely different histories.
929
...
粤公学标字〔2026
554
bun.lock
554
bun.lock
@ -18,20 +18,18 @@
|
|||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"decimal.js": "^10.6.0",
|
"decimal.js": "^10.6.0",
|
||||||
"docxtemplater": "^3.68.5",
|
|
||||||
"exceljs": "^4.4.0",
|
"exceljs": "^4.4.0",
|
||||||
"file-saver": "^2.0.5",
|
|
||||||
"localforage": "^1.10.0",
|
"localforage": "^1.10.0",
|
||||||
"lucide-vue-next": "^0.563.0",
|
"lucide-vue-next": "^0.563.0",
|
||||||
"motion-v": "^2.0.0",
|
"motion-v": "^2.0.0",
|
||||||
"pinia": "^3.0.4",
|
"pinia": "^3.0.4",
|
||||||
"pinia-plugin-persistedstate": "^4.7.1",
|
"pinia-plugin-persistedstate": "^4.7.1",
|
||||||
"pizzip": "^3.2.0",
|
|
||||||
"reka-ui": "^2.8.0",
|
"reka-ui": "^2.8.0",
|
||||||
"tailwind-merge": "^3.4.0",
|
"tailwind-merge": "^3.4.0",
|
||||||
"tailwindcss": "^4.1.18",
|
"tailwindcss": "^4.1.18",
|
||||||
"vue": "^3.5.25",
|
"vue": "^3.5.25",
|
||||||
"vue-i18n": "^11.3.0",
|
"vue-i18n": "^11.3.0",
|
||||||
|
"vue-router": "^4.6.4",
|
||||||
"vuedraggable": "^4.1.0",
|
"vuedraggable": "^4.1.0",
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@ -47,554 +45,548 @@
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
"packages": {
|
"packages": {
|
||||||
"@ag-grid-community/locale": ["@ag-grid-community/locale@35.1.0", "", {}, "sha512-Tez1imtqfipMT3O1Ay+dyDcFJIj6H6gXBp45s44pwkzWQzxO20IBpZUrmAPTNRMYVZNXqCVbNsozWrPaVFAgeQ=="],
|
"@ag-grid-community/locale": ["@ag-grid-community/locale@35.1.0", "https://registry.npmmirror.com/@ag-grid-community/locale/-/locale-35.1.0.tgz", {}, "sha512-Tez1imtqfipMT3O1Ay+dyDcFJIj6H6gXBp45s44pwkzWQzxO20IBpZUrmAPTNRMYVZNXqCVbNsozWrPaVFAgeQ=="],
|
||||||
|
|
||||||
"@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="],
|
"@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "https://registry.npmmirror.com/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="],
|
||||||
|
|
||||||
"@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="],
|
"@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "https://registry.npmmirror.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="],
|
||||||
|
|
||||||
"@babel/parser": ["@babel/parser@7.29.0", "", { "dependencies": { "@babel/types": "^7.29.0" }, "bin": "./bin/babel-parser.js" }, "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww=="],
|
"@babel/parser": ["@babel/parser@7.29.0", "https://registry.npmmirror.com/@babel/parser/-/parser-7.29.0.tgz", { "dependencies": { "@babel/types": "^7.29.0" }, "bin": "./bin/babel-parser.js" }, "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww=="],
|
||||||
|
|
||||||
"@babel/types": ["@babel/types@7.29.0", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A=="],
|
"@babel/types": ["@babel/types@7.29.0", "https://registry.npmmirror.com/@babel/types/-/types-7.29.0.tgz", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A=="],
|
||||||
|
|
||||||
"@emnapi/core": ["@emnapi/core@1.8.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" } }, "sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg=="],
|
"@emnapi/core": ["@emnapi/core@1.8.1", "https://registry.npmmirror.com/@emnapi/core/-/core-1.8.1.tgz", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" } }, "sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg=="],
|
||||||
|
|
||||||
"@emnapi/runtime": ["@emnapi/runtime@1.8.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg=="],
|
"@emnapi/runtime": ["@emnapi/runtime@1.8.1", "https://registry.npmmirror.com/@emnapi/runtime/-/runtime-1.8.1.tgz", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg=="],
|
||||||
|
|
||||||
"@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.1.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ=="],
|
"@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.1.0", "https://registry.npmmirror.com/@emnapi/wasi-threads/-/wasi-threads-1.1.0.tgz", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ=="],
|
||||||
|
|
||||||
"@fast-csv/format": ["@fast-csv/format@4.3.5", "", { "dependencies": { "@types/node": "^14.0.1", "lodash.escaperegexp": "^4.1.2", "lodash.isboolean": "^3.0.3", "lodash.isequal": "^4.5.0", "lodash.isfunction": "^3.0.9", "lodash.isnil": "^4.0.0" } }, "sha512-8iRn6QF3I8Ak78lNAa+Gdl5MJJBM5vRHivFtMRUWINdevNo00K7OXxS2PshawLKTejVwieIlPmK5YlLu6w4u8A=="],
|
"@fast-csv/format": ["@fast-csv/format@4.3.5", "https://registry.npmmirror.com/@fast-csv/format/-/format-4.3.5.tgz", { "dependencies": { "@types/node": "^14.0.1", "lodash.escaperegexp": "^4.1.2", "lodash.isboolean": "^3.0.3", "lodash.isequal": "^4.5.0", "lodash.isfunction": "^3.0.9", "lodash.isnil": "^4.0.0" } }, "sha512-8iRn6QF3I8Ak78lNAa+Gdl5MJJBM5vRHivFtMRUWINdevNo00K7OXxS2PshawLKTejVwieIlPmK5YlLu6w4u8A=="],
|
||||||
|
|
||||||
"@fast-csv/parse": ["@fast-csv/parse@4.3.6", "", { "dependencies": { "@types/node": "^14.0.1", "lodash.escaperegexp": "^4.1.2", "lodash.groupby": "^4.6.0", "lodash.isfunction": "^3.0.9", "lodash.isnil": "^4.0.0", "lodash.isundefined": "^3.0.1", "lodash.uniq": "^4.5.0" } }, "sha512-uRsLYksqpbDmWaSmzvJcuApSEe38+6NQZBUsuAyMZKqHxH0g1wcJgsKUvN3WC8tewaqFjBMMGrkHmC+T7k8LvA=="],
|
"@fast-csv/parse": ["@fast-csv/parse@4.3.6", "https://registry.npmmirror.com/@fast-csv/parse/-/parse-4.3.6.tgz", { "dependencies": { "@types/node": "^14.0.1", "lodash.escaperegexp": "^4.1.2", "lodash.groupby": "^4.6.0", "lodash.isfunction": "^3.0.9", "lodash.isnil": "^4.0.0", "lodash.isundefined": "^3.0.1", "lodash.uniq": "^4.5.0" } }, "sha512-uRsLYksqpbDmWaSmzvJcuApSEe38+6NQZBUsuAyMZKqHxH0g1wcJgsKUvN3WC8tewaqFjBMMGrkHmC+T7k8LvA=="],
|
||||||
|
|
||||||
"@floating-ui/core": ["@floating-ui/core@1.7.4", "", { "dependencies": { "@floating-ui/utils": "^0.2.10" } }, "sha512-C3HlIdsBxszvm5McXlB8PeOEWfBhcGBTZGkGlWc2U0KFY5IwG5OQEuQ8rq52DZmcHDlPLd+YFBK+cZcytwIFWg=="],
|
"@floating-ui/core": ["@floating-ui/core@1.7.4", "https://registry.npmmirror.com/@floating-ui/core/-/core-1.7.4.tgz", { "dependencies": { "@floating-ui/utils": "^0.2.10" } }, "sha512-C3HlIdsBxszvm5McXlB8PeOEWfBhcGBTZGkGlWc2U0KFY5IwG5OQEuQ8rq52DZmcHDlPLd+YFBK+cZcytwIFWg=="],
|
||||||
|
|
||||||
"@floating-ui/dom": ["@floating-ui/dom@1.7.5", "", { "dependencies": { "@floating-ui/core": "^1.7.4", "@floating-ui/utils": "^0.2.10" } }, "sha512-N0bD2kIPInNHUHehXhMke1rBGs1dwqvC9O9KYMyyjK7iXt7GAhnro7UlcuYcGdS/yYOlq0MAVgrow8IbWJwyqg=="],
|
"@floating-ui/dom": ["@floating-ui/dom@1.7.5", "https://registry.npmmirror.com/@floating-ui/dom/-/dom-1.7.5.tgz", { "dependencies": { "@floating-ui/core": "^1.7.4", "@floating-ui/utils": "^0.2.10" } }, "sha512-N0bD2kIPInNHUHehXhMke1rBGs1dwqvC9O9KYMyyjK7iXt7GAhnro7UlcuYcGdS/yYOlq0MAVgrow8IbWJwyqg=="],
|
||||||
|
|
||||||
"@floating-ui/utils": ["@floating-ui/utils@0.2.10", "", {}, "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ=="],
|
"@floating-ui/utils": ["@floating-ui/utils@0.2.10", "https://registry.npmmirror.com/@floating-ui/utils/-/utils-0.2.10.tgz", {}, "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ=="],
|
||||||
|
|
||||||
"@floating-ui/vue": ["@floating-ui/vue@1.1.10", "", { "dependencies": { "@floating-ui/dom": "^1.7.5", "@floating-ui/utils": "^0.2.10", "vue-demi": ">=0.13.0" } }, "sha512-vdf8f6rHnFPPLRsmL4p12wYl+Ux4mOJOkjzKEMYVnwdf7UFdvBtHlLvQyx8iKG5vhPRbDRgZxdtpmyigDPjzYg=="],
|
"@floating-ui/vue": ["@floating-ui/vue@1.1.10", "https://registry.npmmirror.com/@floating-ui/vue/-/vue-1.1.10.tgz", { "dependencies": { "@floating-ui/dom": "^1.7.5", "@floating-ui/utils": "^0.2.10", "vue-demi": ">=0.13.0" } }, "sha512-vdf8f6rHnFPPLRsmL4p12wYl+Ux4mOJOkjzKEMYVnwdf7UFdvBtHlLvQyx8iKG5vhPRbDRgZxdtpmyigDPjzYg=="],
|
||||||
|
|
||||||
"@iconify/types": ["@iconify/types@2.0.0", "", {}, "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg=="],
|
"@iconify/types": ["@iconify/types@2.0.0", "https://registry.npmmirror.com/@iconify/types/-/types-2.0.0.tgz", {}, "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg=="],
|
||||||
|
|
||||||
"@iconify/vue": ["@iconify/vue@5.0.0", "", { "dependencies": { "@iconify/types": "^2.0.0" }, "peerDependencies": { "vue": ">=3" } }, "sha512-C+KuEWIF5nSBrobFJhT//JS87OZ++QDORB6f2q2Wm6fl2mueSTpFBeBsveK0KW9hWiZ4mNiPjsh6Zs4jjdROSg=="],
|
"@iconify/vue": ["@iconify/vue@5.0.0", "https://registry.npmmirror.com/@iconify/vue/-/vue-5.0.0.tgz", { "dependencies": { "@iconify/types": "^2.0.0" }, "peerDependencies": { "vue": ">=3" } }, "sha512-C+KuEWIF5nSBrobFJhT//JS87OZ++QDORB6f2q2Wm6fl2mueSTpFBeBsveK0KW9hWiZ4mNiPjsh6Zs4jjdROSg=="],
|
||||||
|
|
||||||
"@internationalized/date": ["@internationalized/date@3.12.0", "", { "dependencies": { "@swc/helpers": "^0.5.0" } }, "sha512-/PyIMzK29jtXaGU23qTvNZxvBXRtKbNnGDFD+PY6CZw/Y8Ex8pFUzkuCJCG9aOqmShjqhS9mPqP6Dk5onQY8rQ=="],
|
"@internationalized/date": ["@internationalized/date@3.12.0", "https://registry.npmmirror.com/@internationalized/date/-/date-3.12.0.tgz", { "dependencies": { "@swc/helpers": "^0.5.0" } }, "sha512-/PyIMzK29jtXaGU23qTvNZxvBXRtKbNnGDFD+PY6CZw/Y8Ex8pFUzkuCJCG9aOqmShjqhS9mPqP6Dk5onQY8rQ=="],
|
||||||
|
|
||||||
"@internationalized/number": ["@internationalized/number@3.6.5", "", { "dependencies": { "@swc/helpers": "^0.5.0" } }, "sha512-6hY4Kl4HPBvtfS62asS/R22JzNNy8vi/Ssev7x6EobfCp+9QIB2hKvI2EtbdJ0VSQacxVNtqhE/NmF/NZ0gm6g=="],
|
"@internationalized/number": ["@internationalized/number@3.6.5", "https://registry.npmmirror.com/@internationalized/number/-/number-3.6.5.tgz", { "dependencies": { "@swc/helpers": "^0.5.0" } }, "sha512-6hY4Kl4HPBvtfS62asS/R22JzNNy8vi/Ssev7x6EobfCp+9QIB2hKvI2EtbdJ0VSQacxVNtqhE/NmF/NZ0gm6g=="],
|
||||||
|
|
||||||
"@intlify/core-base": ["@intlify/core-base@11.4.6", "", { "dependencies": { "@intlify/devtools-types": "11.4.6", "@intlify/message-compiler": "11.4.6", "@intlify/shared": "11.4.6" } }, "sha512-EOeHO95XESK9IFHgHeZXunsM/WBAoCA0DlaWODvx14vKmetAuS97t+l6Xe9hTUqntPpF93vtVSjjUDafw3wXMw=="],
|
"@intlify/core-base": ["@intlify/core-base@11.3.1", "https://registry.npmmirror.com/@intlify/core-base/-/core-base-11.3.1.tgz", { "dependencies": { "@intlify/devtools-types": "11.3.1", "@intlify/message-compiler": "11.3.1", "@intlify/shared": "11.3.1" } }, "sha512-9nG3ItSD5ApZHmTbv2UFqvJSy3m+u6C/orMohukNKoT/Yuwiz8tPtlNw6ylLuPqSP2kP7ZF4Cdqwp6V1m3BQgw=="],
|
||||||
|
|
||||||
"@intlify/devtools-types": ["@intlify/devtools-types@11.4.6", "", { "dependencies": { "@intlify/core-base": "11.4.6", "@intlify/shared": "11.4.6" } }, "sha512-wowQPpNem56b2d43IJmqbrzG2FeBKe5f/kUGlpNuBmXs6OSqncF8m1+1lxHuW8ISZJF0ma2RkW3iLkw0g0G4VA=="],
|
"@intlify/devtools-types": ["@intlify/devtools-types@11.3.1", "https://registry.npmmirror.com/@intlify/devtools-types/-/devtools-types-11.3.1.tgz", { "dependencies": { "@intlify/core-base": "11.3.1", "@intlify/shared": "11.3.1" } }, "sha512-qrOknIx294W4YYVYBgldDOeOnv2NlpabW+aYGjMuXMSrY36f7GCAPlEBE2G+qTC5x0oAWDBSY5BmvLlPUx1exg=="],
|
||||||
|
|
||||||
"@intlify/message-compiler": ["@intlify/message-compiler@11.4.6", "", { "dependencies": { "@intlify/shared": "11.4.6", "source-map-js": "^1.0.2" } }, "sha512-5nj3jULqeTAC1WovwMs1LQWgatTa2pM/rXN9T3XW8rdOtXW9ZF6/GLSNFTKDQmPLwclhPdgUWLJ/4w3fMeeC/Q=="],
|
"@intlify/message-compiler": ["@intlify/message-compiler@11.3.1", "https://registry.npmmirror.com/@intlify/message-compiler/-/message-compiler-11.3.1.tgz", { "dependencies": { "@intlify/shared": "11.3.1", "source-map-js": "^1.0.2" } }, "sha512-uIa4YurbphU+4Cl5CoL6nq/c7uQhVNRowEelgboNmXNs+UEcyFLQBESwaUjMvdtYxzA2qh+vGim080KZ84ruDA=="],
|
||||||
|
|
||||||
"@intlify/shared": ["@intlify/shared@11.4.6", "", {}, "sha512-m1p1HHAMLhqSpTRH7VnXdrN0CQ4y+9vunFkpLkbD8soIuBsnQdawZXqMCgvwI2UVF9Ww7sVaw7g9tV2VO7shoA=="],
|
"@intlify/shared": ["@intlify/shared@11.3.1", "https://registry.npmmirror.com/@intlify/shared/-/shared-11.3.1.tgz", {}, "sha512-9GWc5PKuRdeWkT7FJN43c/+rD6xpSB3WtizewkfFCK/0XzYqCk4gQBWWcTdfKo8ylEcHwqYsR2Z3HRE3XhEHrQ=="],
|
||||||
|
|
||||||
"@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="],
|
"@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "https://registry.npmmirror.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="],
|
||||||
|
|
||||||
"@jridgewell/remapping": ["@jridgewell/remapping@2.3.5", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="],
|
"@jridgewell/remapping": ["@jridgewell/remapping@2.3.5", "https://registry.npmmirror.com/@jridgewell/remapping/-/remapping-2.3.5.tgz", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="],
|
||||||
|
|
||||||
"@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="],
|
"@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "https://registry.npmmirror.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="],
|
||||||
|
|
||||||
"@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="],
|
"@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "https://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="],
|
||||||
|
|
||||||
"@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="],
|
"@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "https://registry.npmmirror.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="],
|
||||||
|
|
||||||
"@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.1", "", { "dependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1", "@tybys/wasm-util": "^0.10.1" } }, "sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A=="],
|
"@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.1", "https://registry.npmmirror.com/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.1.tgz", { "dependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1", "@tybys/wasm-util": "^0.10.1" } }, "sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A=="],
|
||||||
|
|
||||||
"@noble/ciphers": ["@noble/ciphers@2.2.0", "", {}, "sha512-Z6pjIZ/8IJcCGzb2S/0Px5J81yij85xASuk1teLNeg75bfT07MV3a/O2Mtn1I2se43k3lkVEcFaR10N4cgQcZA=="],
|
"@noble/ciphers": ["@noble/ciphers@2.1.1", "https://registry.npmmirror.com/@noble/ciphers/-/ciphers-2.1.1.tgz", {}, "sha512-bysYuiVfhxNJuldNXlFEitTVdNnYUc+XNJZd7Qm2a5j1vZHgY+fazadNFWFaMK/2vye0JVlxV3gHmC0WDfAOQw=="],
|
||||||
|
|
||||||
"@noble/hashes": ["@noble/hashes@2.2.0", "", {}, "sha512-IYqDGiTXab6FniAgnSdZwgWbomxpy9FtYvLKs7wCUs2a8RkITG+DFGO1DM9cr+E3/RgADRpFjrKVaJ1z6sjtEg=="],
|
"@noble/hashes": ["@noble/hashes@2.0.1", "https://registry.npmmirror.com/@noble/hashes/-/hashes-2.0.1.tgz", {}, "sha512-XlOlEbQcE9fmuXxrVTXCTlG2nlRXa9Rj3rr5Ue/+tX+nmkgbX720YHh0VR3hBF9xDvwnb8D2shVGOwNx+ulArw=="],
|
||||||
|
|
||||||
"@oxc-project/runtime": ["@oxc-project/runtime@0.114.0", "", {}, "sha512-mVGQvr/uFJGQ3hsvgQ1sJfh79t5owyZZZtw+VaH+WhtvsmtgjT6imznB9sz2Q67Q0/4obM9mOOtQscU4aJteSg=="],
|
"@oxc-project/runtime": ["@oxc-project/runtime@0.114.0", "https://registry.npmmirror.com/@oxc-project/runtime/-/runtime-0.114.0.tgz", {}, "sha512-mVGQvr/uFJGQ3hsvgQ1sJfh79t5owyZZZtw+VaH+WhtvsmtgjT6imznB9sz2Q67Q0/4obM9mOOtQscU4aJteSg=="],
|
||||||
|
|
||||||
"@oxc-project/types": ["@oxc-project/types@0.114.0", "", {}, "sha512-//nBfbzHQHvJs8oFIjv6coZ6uxQ4alLfiPe6D5vit6c4pmxATHHlVwgB1k+Hv4yoAMyncdxgRBF5K4BYWUCzvA=="],
|
"@oxc-project/types": ["@oxc-project/types@0.114.0", "https://registry.npmmirror.com/@oxc-project/types/-/types-0.114.0.tgz", {}, "sha512-//nBfbzHQHvJs8oFIjv6coZ6uxQ4alLfiPe6D5vit6c4pmxATHHlVwgB1k+Hv4yoAMyncdxgRBF5K4BYWUCzvA=="],
|
||||||
|
|
||||||
"@rolldown/binding-android-arm64": ["@rolldown/binding-android-arm64@1.0.0-rc.5", "", { "os": "android", "cpu": "arm64" }, "sha512-zCEmUrt1bggwgBgeKLxNj217J1OrChrp3jJt24VK9jAharSTeVaHODNL+LpcQVhRz+FktYWfT9cjo5oZ99ZLpg=="],
|
"@rolldown/binding-android-arm64": ["@rolldown/binding-android-arm64@1.0.0-rc.5", "https://registry.npmmirror.com/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.5.tgz", { "os": "android", "cpu": "arm64" }, "sha512-zCEmUrt1bggwgBgeKLxNj217J1OrChrp3jJt24VK9jAharSTeVaHODNL+LpcQVhRz+FktYWfT9cjo5oZ99ZLpg=="],
|
||||||
|
|
||||||
"@rolldown/binding-darwin-arm64": ["@rolldown/binding-darwin-arm64@1.0.0-rc.5", "", { "os": "darwin", "cpu": "arm64" }, "sha512-ZP9xb9lPAex36pvkNWCjSEJW/Gfdm9I3ssiqOFLmpZ/vosPXgpoGxCmh+dX1Qs+/bWQE6toNFXWWL8vYoKoK9Q=="],
|
"@rolldown/binding-darwin-arm64": ["@rolldown/binding-darwin-arm64@1.0.0-rc.5", "https://registry.npmmirror.com/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.5.tgz", { "os": "darwin", "cpu": "arm64" }, "sha512-ZP9xb9lPAex36pvkNWCjSEJW/Gfdm9I3ssiqOFLmpZ/vosPXgpoGxCmh+dX1Qs+/bWQE6toNFXWWL8vYoKoK9Q=="],
|
||||||
|
|
||||||
"@rolldown/binding-darwin-x64": ["@rolldown/binding-darwin-x64@1.0.0-rc.5", "", { "os": "darwin", "cpu": "x64" }, "sha512-7IdrPunf6dp9mywMgTOKMMGDnMHQ6+h5gRl6LW8rhD8WK2kXX0IwzcM5Zc0B5J7xQs8QWOlKjv8BJsU/1CD3pg=="],
|
"@rolldown/binding-darwin-x64": ["@rolldown/binding-darwin-x64@1.0.0-rc.5", "https://registry.npmmirror.com/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.5.tgz", { "os": "darwin", "cpu": "x64" }, "sha512-7IdrPunf6dp9mywMgTOKMMGDnMHQ6+h5gRl6LW8rhD8WK2kXX0IwzcM5Zc0B5J7xQs8QWOlKjv8BJsU/1CD3pg=="],
|
||||||
|
|
||||||
"@rolldown/binding-freebsd-x64": ["@rolldown/binding-freebsd-x64@1.0.0-rc.5", "", { "os": "freebsd", "cpu": "x64" }, "sha512-o/JCk+dL0IN68EBhZ4DqfsfvxPfMeoM6cJtxORC1YYoxGHZyth2Kb2maXDb4oddw2wu8iIbnYXYPEzBtAF5CAg=="],
|
"@rolldown/binding-freebsd-x64": ["@rolldown/binding-freebsd-x64@1.0.0-rc.5", "https://registry.npmmirror.com/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.5.tgz", { "os": "freebsd", "cpu": "x64" }, "sha512-o/JCk+dL0IN68EBhZ4DqfsfvxPfMeoM6cJtxORC1YYoxGHZyth2Kb2maXDb4oddw2wu8iIbnYXYPEzBtAF5CAg=="],
|
||||||
|
|
||||||
"@rolldown/binding-linux-arm-gnueabihf": ["@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.5", "", { "os": "linux", "cpu": "arm" }, "sha512-IIBwTtA6VwxQLcEgq2mfrUgam7VvPZjhd/jxmeS1npM+edWsrrpRLHUdze+sk4rhb8/xpP3flemgcZXXUW6ukw=="],
|
"@rolldown/binding-linux-arm-gnueabihf": ["@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.5", "https://registry.npmmirror.com/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.5.tgz", { "os": "linux", "cpu": "arm" }, "sha512-IIBwTtA6VwxQLcEgq2mfrUgam7VvPZjhd/jxmeS1npM+edWsrrpRLHUdze+sk4rhb8/xpP3flemgcZXXUW6ukw=="],
|
||||||
|
|
||||||
"@rolldown/binding-linux-arm64-gnu": ["@rolldown/binding-linux-arm64-gnu@1.0.0-rc.5", "", { "os": "linux", "cpu": "arm64" }, "sha512-KSol1De1spMZL+Xg7K5IBWXIvRWv7+pveaxFWXpezezAG7CS6ojzRjtCGCiLxQricutTAi/LkNWKMsd2wNhMKQ=="],
|
"@rolldown/binding-linux-arm64-gnu": ["@rolldown/binding-linux-arm64-gnu@1.0.0-rc.5", "https://registry.npmmirror.com/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.5.tgz", { "os": "linux", "cpu": "arm64" }, "sha512-KSol1De1spMZL+Xg7K5IBWXIvRWv7+pveaxFWXpezezAG7CS6ojzRjtCGCiLxQricutTAi/LkNWKMsd2wNhMKQ=="],
|
||||||
|
|
||||||
"@rolldown/binding-linux-arm64-musl": ["@rolldown/binding-linux-arm64-musl@1.0.0-rc.5", "", { "os": "linux", "cpu": "arm64" }, "sha512-WFljyDkxtXRlWxMjxeegf7xMYXxUr8u7JdXlOEWKYgDqEgxUnSEsVDxBiNWQ1D5kQKwf8Wo4sVKEYPRhCdsjwA=="],
|
"@rolldown/binding-linux-arm64-musl": ["@rolldown/binding-linux-arm64-musl@1.0.0-rc.5", "https://registry.npmmirror.com/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.5.tgz", { "os": "linux", "cpu": "arm64" }, "sha512-WFljyDkxtXRlWxMjxeegf7xMYXxUr8u7JdXlOEWKYgDqEgxUnSEsVDxBiNWQ1D5kQKwf8Wo4sVKEYPRhCdsjwA=="],
|
||||||
|
|
||||||
"@rolldown/binding-linux-x64-gnu": ["@rolldown/binding-linux-x64-gnu@1.0.0-rc.5", "", { "os": "linux", "cpu": "x64" }, "sha512-CUlplTujmbDWp2gamvrqVKi2Or8lmngXT1WxsizJfts7JrvfGhZObciaY/+CbdbS9qNnskvwMZNEhTPrn7b+WA=="],
|
"@rolldown/binding-linux-x64-gnu": ["@rolldown/binding-linux-x64-gnu@1.0.0-rc.5", "https://registry.npmmirror.com/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.5.tgz", { "os": "linux", "cpu": "x64" }, "sha512-CUlplTujmbDWp2gamvrqVKi2Or8lmngXT1WxsizJfts7JrvfGhZObciaY/+CbdbS9qNnskvwMZNEhTPrn7b+WA=="],
|
||||||
|
|
||||||
"@rolldown/binding-linux-x64-musl": ["@rolldown/binding-linux-x64-musl@1.0.0-rc.5", "", { "os": "linux", "cpu": "x64" }, "sha512-wdf7g9NbVZCeAo2iGhsjJb7I8ZFfs6X8bumfrWg82VK+8P6AlLXwk48a1ASiJQDTS7Svq2xVzZg3sGO2aXpHRA=="],
|
"@rolldown/binding-linux-x64-musl": ["@rolldown/binding-linux-x64-musl@1.0.0-rc.5", "https://registry.npmmirror.com/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.5.tgz", { "os": "linux", "cpu": "x64" }, "sha512-wdf7g9NbVZCeAo2iGhsjJb7I8ZFfs6X8bumfrWg82VK+8P6AlLXwk48a1ASiJQDTS7Svq2xVzZg3sGO2aXpHRA=="],
|
||||||
|
|
||||||
"@rolldown/binding-openharmony-arm64": ["@rolldown/binding-openharmony-arm64@1.0.0-rc.5", "", { "os": "none", "cpu": "arm64" }, "sha512-0CWY7ubu12nhzz+tkpHjoG3IRSTlWYe0wrfJRf4qqjqQSGtAYgoL9kwzdvlhaFdZ5ffVeyYw9qLsChcjUMEloQ=="],
|
"@rolldown/binding-openharmony-arm64": ["@rolldown/binding-openharmony-arm64@1.0.0-rc.5", "https://registry.npmmirror.com/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.5.tgz", { "os": "none", "cpu": "arm64" }, "sha512-0CWY7ubu12nhzz+tkpHjoG3IRSTlWYe0wrfJRf4qqjqQSGtAYgoL9kwzdvlhaFdZ5ffVeyYw9qLsChcjUMEloQ=="],
|
||||||
|
|
||||||
"@rolldown/binding-wasm32-wasi": ["@rolldown/binding-wasm32-wasi@1.0.0-rc.5", "", { "dependencies": { "@napi-rs/wasm-runtime": "^1.1.1" }, "cpu": "none" }, "sha512-LztXnGzv6t2u830mnZrFLRVqT/DPJ9DL4ZTz/y93rqUVkeHjMMYIYaFj+BUthiYxbVH9dH0SZYufETspKY/NhA=="],
|
"@rolldown/binding-wasm32-wasi": ["@rolldown/binding-wasm32-wasi@1.0.0-rc.5", "https://registry.npmmirror.com/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.5.tgz", { "dependencies": { "@napi-rs/wasm-runtime": "^1.1.1" }, "cpu": "none" }, "sha512-LztXnGzv6t2u830mnZrFLRVqT/DPJ9DL4ZTz/y93rqUVkeHjMMYIYaFj+BUthiYxbVH9dH0SZYufETspKY/NhA=="],
|
||||||
|
|
||||||
"@rolldown/binding-win32-arm64-msvc": ["@rolldown/binding-win32-arm64-msvc@1.0.0-rc.5", "", { "os": "win32", "cpu": "arm64" }, "sha512-jUct1XVeGtyjqJXEAfvdFa8xoigYZ2rge7nYEm70ppQxpfH9ze2fbIrpHmP2tNM2vL/F6Dd0CpXhpjPbC6bSxQ=="],
|
"@rolldown/binding-win32-arm64-msvc": ["@rolldown/binding-win32-arm64-msvc@1.0.0-rc.5", "https://registry.npmmirror.com/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.5.tgz", { "os": "win32", "cpu": "arm64" }, "sha512-jUct1XVeGtyjqJXEAfvdFa8xoigYZ2rge7nYEm70ppQxpfH9ze2fbIrpHmP2tNM2vL/F6Dd0CpXhpjPbC6bSxQ=="],
|
||||||
|
|
||||||
"@rolldown/binding-win32-x64-msvc": ["@rolldown/binding-win32-x64-msvc@1.0.0-rc.5", "", { "os": "win32", "cpu": "x64" }, "sha512-VQ8F9ld5gw29epjnVGdrx8ugiLTe8BMqmhDYy7nGbdeDo4HAt4bgdZvLbViEhg7DZyHLpiEUlO5/jPSUrIuxRQ=="],
|
"@rolldown/binding-win32-x64-msvc": ["@rolldown/binding-win32-x64-msvc@1.0.0-rc.5", "https://registry.npmmirror.com/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.5.tgz", { "os": "win32", "cpu": "x64" }, "sha512-VQ8F9ld5gw29epjnVGdrx8ugiLTe8BMqmhDYy7nGbdeDo4HAt4bgdZvLbViEhg7DZyHLpiEUlO5/jPSUrIuxRQ=="],
|
||||||
|
|
||||||
"@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-rc.2", "", {}, "sha512-izyXV/v+cHiRfozX62W9htOAvwMo4/bXKDrQ+vom1L1qRuexPock/7VZDAhnpHCLNejd3NJ6hiab+tO0D44Rgw=="],
|
"@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-rc.2", "https://registry.npmmirror.com/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.2.tgz", {}, "sha512-izyXV/v+cHiRfozX62W9htOAvwMo4/bXKDrQ+vom1L1qRuexPock/7VZDAhnpHCLNejd3NJ6hiab+tO0D44Rgw=="],
|
||||||
|
|
||||||
"@swc/helpers": ["@swc/helpers@0.5.18", "", { "dependencies": { "tslib": "^2.8.0" } }, "sha512-TXTnIcNJQEKwThMMqBXsZ4VGAza6bvN4pa41Rkqoio6QBKMvo+5lexeTMScGCIxtzgQJzElcvIltani+adC5PQ=="],
|
"@swc/helpers": ["@swc/helpers@0.5.18", "https://registry.npmmirror.com/@swc/helpers/-/helpers-0.5.18.tgz", { "dependencies": { "tslib": "^2.8.0" } }, "sha512-TXTnIcNJQEKwThMMqBXsZ4VGAza6bvN4pa41Rkqoio6QBKMvo+5lexeTMScGCIxtzgQJzElcvIltani+adC5PQ=="],
|
||||||
|
|
||||||
"@tailwindcss/node": ["@tailwindcss/node@4.2.0", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "enhanced-resolve": "^5.19.0", "jiti": "^2.6.1", "lightningcss": "1.31.1", "magic-string": "^0.30.21", "source-map-js": "^1.2.1", "tailwindcss": "4.2.0" } }, "sha512-Yv+fn/o2OmL5fh/Ir62VXItdShnUxfpkMA4Y7jdeC8O81WPB8Kf6TT6GSHvnqgSwDzlB5iT7kDpeXxLsUS0T6Q=="],
|
"@tailwindcss/node": ["@tailwindcss/node@4.2.0", "https://registry.npmmirror.com/@tailwindcss/node/-/node-4.2.0.tgz", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "enhanced-resolve": "^5.19.0", "jiti": "^2.6.1", "lightningcss": "1.31.1", "magic-string": "^0.30.21", "source-map-js": "^1.2.1", "tailwindcss": "4.2.0" } }, "sha512-Yv+fn/o2OmL5fh/Ir62VXItdShnUxfpkMA4Y7jdeC8O81WPB8Kf6TT6GSHvnqgSwDzlB5iT7kDpeXxLsUS0T6Q=="],
|
||||||
|
|
||||||
"@tailwindcss/oxide": ["@tailwindcss/oxide@4.2.0", "", { "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.2.0", "@tailwindcss/oxide-darwin-arm64": "4.2.0", "@tailwindcss/oxide-darwin-x64": "4.2.0", "@tailwindcss/oxide-freebsd-x64": "4.2.0", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.2.0", "@tailwindcss/oxide-linux-arm64-gnu": "4.2.0", "@tailwindcss/oxide-linux-arm64-musl": "4.2.0", "@tailwindcss/oxide-linux-x64-gnu": "4.2.0", "@tailwindcss/oxide-linux-x64-musl": "4.2.0", "@tailwindcss/oxide-wasm32-wasi": "4.2.0", "@tailwindcss/oxide-win32-arm64-msvc": "4.2.0", "@tailwindcss/oxide-win32-x64-msvc": "4.2.0" } }, "sha512-AZqQzADaj742oqn2xjl5JbIOzZB/DGCYF/7bpvhA8KvjUj9HJkag6bBuwZvH1ps6dfgxNHyuJVlzSr2VpMgdTQ=="],
|
"@tailwindcss/oxide": ["@tailwindcss/oxide@4.2.0", "https://registry.npmmirror.com/@tailwindcss/oxide/-/oxide-4.2.0.tgz", { "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.2.0", "@tailwindcss/oxide-darwin-arm64": "4.2.0", "@tailwindcss/oxide-darwin-x64": "4.2.0", "@tailwindcss/oxide-freebsd-x64": "4.2.0", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.2.0", "@tailwindcss/oxide-linux-arm64-gnu": "4.2.0", "@tailwindcss/oxide-linux-arm64-musl": "4.2.0", "@tailwindcss/oxide-linux-x64-gnu": "4.2.0", "@tailwindcss/oxide-linux-x64-musl": "4.2.0", "@tailwindcss/oxide-wasm32-wasi": "4.2.0", "@tailwindcss/oxide-win32-arm64-msvc": "4.2.0", "@tailwindcss/oxide-win32-x64-msvc": "4.2.0" } }, "sha512-AZqQzADaj742oqn2xjl5JbIOzZB/DGCYF/7bpvhA8KvjUj9HJkag6bBuwZvH1ps6dfgxNHyuJVlzSr2VpMgdTQ=="],
|
||||||
|
|
||||||
"@tailwindcss/oxide-android-arm64": ["@tailwindcss/oxide-android-arm64@4.2.0", "", { "os": "android", "cpu": "arm64" }, "sha512-F0QkHAVaW/JNBWl4CEKWdZ9PMb0khw5DCELAOnu+RtjAfx5Zgw+gqCHFvqg3AirU1IAd181fwOtJQ5I8Yx5wtw=="],
|
"@tailwindcss/oxide-android-arm64": ["@tailwindcss/oxide-android-arm64@4.2.0", "https://registry.npmmirror.com/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.2.0.tgz", { "os": "android", "cpu": "arm64" }, "sha512-F0QkHAVaW/JNBWl4CEKWdZ9PMb0khw5DCELAOnu+RtjAfx5Zgw+gqCHFvqg3AirU1IAd181fwOtJQ5I8Yx5wtw=="],
|
||||||
|
|
||||||
"@tailwindcss/oxide-darwin-arm64": ["@tailwindcss/oxide-darwin-arm64@4.2.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-I0QylkXsBsJMZ4nkUNSR04p6+UptjcwhcVo3Zu828ikiEqHjVmQL9RuQ6uT/cVIiKpvtVA25msu/eRV97JeNSA=="],
|
"@tailwindcss/oxide-darwin-arm64": ["@tailwindcss/oxide-darwin-arm64@4.2.0", "https://registry.npmmirror.com/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.2.0.tgz", { "os": "darwin", "cpu": "arm64" }, "sha512-I0QylkXsBsJMZ4nkUNSR04p6+UptjcwhcVo3Zu828ikiEqHjVmQL9RuQ6uT/cVIiKpvtVA25msu/eRV97JeNSA=="],
|
||||||
|
|
||||||
"@tailwindcss/oxide-darwin-x64": ["@tailwindcss/oxide-darwin-x64@4.2.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-6TmQIn4p09PBrmnkvbYQ0wbZhLtbaksCDx7Y7R3FYYx0yxNA7xg5KP7dowmQ3d2JVdabIHvs3Hx4K3d5uCf8xg=="],
|
"@tailwindcss/oxide-darwin-x64": ["@tailwindcss/oxide-darwin-x64@4.2.0", "https://registry.npmmirror.com/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.2.0.tgz", { "os": "darwin", "cpu": "x64" }, "sha512-6TmQIn4p09PBrmnkvbYQ0wbZhLtbaksCDx7Y7R3FYYx0yxNA7xg5KP7dowmQ3d2JVdabIHvs3Hx4K3d5uCf8xg=="],
|
||||||
|
|
||||||
"@tailwindcss/oxide-freebsd-x64": ["@tailwindcss/oxide-freebsd-x64@4.2.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-qBudxDvAa2QwGlq9y7VIzhTvp2mLJ6nD/G8/tI70DCDoneaUeLWBJaPcbfzqRIWraj+o969aDQKvKW9dvkUizw=="],
|
"@tailwindcss/oxide-freebsd-x64": ["@tailwindcss/oxide-freebsd-x64@4.2.0", "https://registry.npmmirror.com/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.2.0.tgz", { "os": "freebsd", "cpu": "x64" }, "sha512-qBudxDvAa2QwGlq9y7VIzhTvp2mLJ6nD/G8/tI70DCDoneaUeLWBJaPcbfzqRIWraj+o969aDQKvKW9dvkUizw=="],
|
||||||
|
|
||||||
"@tailwindcss/oxide-linux-arm-gnueabihf": ["@tailwindcss/oxide-linux-arm-gnueabihf@4.2.0", "", { "os": "linux", "cpu": "arm" }, "sha512-7XKkitpy5NIjFZNUQPeUyNJNJn1CJeV7rmMR+exHfTuOsg8rxIO9eNV5TSEnqRcaOK77zQpsyUkBWmPy8FgdSg=="],
|
"@tailwindcss/oxide-linux-arm-gnueabihf": ["@tailwindcss/oxide-linux-arm-gnueabihf@4.2.0", "https://registry.npmmirror.com/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.2.0.tgz", { "os": "linux", "cpu": "arm" }, "sha512-7XKkitpy5NIjFZNUQPeUyNJNJn1CJeV7rmMR+exHfTuOsg8rxIO9eNV5TSEnqRcaOK77zQpsyUkBWmPy8FgdSg=="],
|
||||||
|
|
||||||
"@tailwindcss/oxide-linux-arm64-gnu": ["@tailwindcss/oxide-linux-arm64-gnu@4.2.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-Mff5a5Q3WoQR01pGU1gr29hHM1N93xYrKkGXfPw/aRtK4bOc331Ho4Tgfsm5WDGvpevqMpdlkCojT3qlCQbCpA=="],
|
"@tailwindcss/oxide-linux-arm64-gnu": ["@tailwindcss/oxide-linux-arm64-gnu@4.2.0", "https://registry.npmmirror.com/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.2.0.tgz", { "os": "linux", "cpu": "arm64" }, "sha512-Mff5a5Q3WoQR01pGU1gr29hHM1N93xYrKkGXfPw/aRtK4bOc331Ho4Tgfsm5WDGvpevqMpdlkCojT3qlCQbCpA=="],
|
||||||
|
|
||||||
"@tailwindcss/oxide-linux-arm64-musl": ["@tailwindcss/oxide-linux-arm64-musl@4.2.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-XKcSStleEVnbH6W/9DHzZv1YhjE4eSS6zOu2eRtYAIh7aV4o3vIBs+t/B15xlqoxt6ef/0uiqJVB6hkHjWD/0A=="],
|
"@tailwindcss/oxide-linux-arm64-musl": ["@tailwindcss/oxide-linux-arm64-musl@4.2.0", "https://registry.npmmirror.com/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.2.0.tgz", { "os": "linux", "cpu": "arm64" }, "sha512-XKcSStleEVnbH6W/9DHzZv1YhjE4eSS6zOu2eRtYAIh7aV4o3vIBs+t/B15xlqoxt6ef/0uiqJVB6hkHjWD/0A=="],
|
||||||
|
|
||||||
"@tailwindcss/oxide-linux-x64-gnu": ["@tailwindcss/oxide-linux-x64-gnu@4.2.0", "", { "os": "linux", "cpu": "x64" }, "sha512-/hlXCBqn9K6fi7eAM0RsobHwJYa5V/xzWspVTzxnX+Ft9v6n+30Pz8+RxCn7sQL/vRHHLS30iQPrHQunu6/vJA=="],
|
"@tailwindcss/oxide-linux-x64-gnu": ["@tailwindcss/oxide-linux-x64-gnu@4.2.0", "https://registry.npmmirror.com/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.2.0.tgz", { "os": "linux", "cpu": "x64" }, "sha512-/hlXCBqn9K6fi7eAM0RsobHwJYa5V/xzWspVTzxnX+Ft9v6n+30Pz8+RxCn7sQL/vRHHLS30iQPrHQunu6/vJA=="],
|
||||||
|
|
||||||
"@tailwindcss/oxide-linux-x64-musl": ["@tailwindcss/oxide-linux-x64-musl@4.2.0", "", { "os": "linux", "cpu": "x64" }, "sha512-lKUaygq4G7sWkhQbfdRRBkaq4LY39IriqBQ+Gk6l5nKq6Ay2M2ZZb1tlIyRNgZKS8cbErTwuYSor0IIULC0SHw=="],
|
"@tailwindcss/oxide-linux-x64-musl": ["@tailwindcss/oxide-linux-x64-musl@4.2.0", "https://registry.npmmirror.com/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.2.0.tgz", { "os": "linux", "cpu": "x64" }, "sha512-lKUaygq4G7sWkhQbfdRRBkaq4LY39IriqBQ+Gk6l5nKq6Ay2M2ZZb1tlIyRNgZKS8cbErTwuYSor0IIULC0SHw=="],
|
||||||
|
|
||||||
"@tailwindcss/oxide-wasm32-wasi": ["@tailwindcss/oxide-wasm32-wasi@4.2.0", "", { "dependencies": { "@emnapi/core": "^1.8.1", "@emnapi/runtime": "^1.8.1", "@emnapi/wasi-threads": "^1.1.0", "@napi-rs/wasm-runtime": "^1.1.1", "@tybys/wasm-util": "^0.10.1", "tslib": "^2.8.1" }, "cpu": "none" }, "sha512-xuDjhAsFdUuFP5W9Ze4k/o4AskUtI8bcAGU4puTYprr89QaYFmhYOPfP+d1pH+k9ets6RoE23BXZM1X1jJqoyw=="],
|
"@tailwindcss/oxide-wasm32-wasi": ["@tailwindcss/oxide-wasm32-wasi@4.2.0", "https://registry.npmmirror.com/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.2.0.tgz", { "dependencies": { "@emnapi/core": "^1.8.1", "@emnapi/runtime": "^1.8.1", "@emnapi/wasi-threads": "^1.1.0", "@napi-rs/wasm-runtime": "^1.1.1", "@tybys/wasm-util": "^0.10.1", "tslib": "^2.8.1" }, "cpu": "none" }, "sha512-xuDjhAsFdUuFP5W9Ze4k/o4AskUtI8bcAGU4puTYprr89QaYFmhYOPfP+d1pH+k9ets6RoE23BXZM1X1jJqoyw=="],
|
||||||
|
|
||||||
"@tailwindcss/oxide-win32-arm64-msvc": ["@tailwindcss/oxide-win32-arm64-msvc@4.2.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-2UU/15y1sWDEDNJXxEIrfWKC2Yb4YgIW5Xz2fKFqGzFWfoMHWFlfa1EJlGO2Xzjkq/tvSarh9ZTjvbxqWvLLXA=="],
|
"@tailwindcss/oxide-win32-arm64-msvc": ["@tailwindcss/oxide-win32-arm64-msvc@4.2.0", "https://registry.npmmirror.com/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.2.0.tgz", { "os": "win32", "cpu": "arm64" }, "sha512-2UU/15y1sWDEDNJXxEIrfWKC2Yb4YgIW5Xz2fKFqGzFWfoMHWFlfa1EJlGO2Xzjkq/tvSarh9ZTjvbxqWvLLXA=="],
|
||||||
|
|
||||||
"@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@4.2.0", "", { "os": "win32", "cpu": "x64" }, "sha512-CrFadmFoc+z76EV6LPG1jx6XceDsaCG3lFhyLNo/bV9ByPrE+FnBPckXQVP4XRkN76h3Fjt/a+5Er/oA/nCBvQ=="],
|
"@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@4.2.0", "https://registry.npmmirror.com/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.2.0.tgz", { "os": "win32", "cpu": "x64" }, "sha512-CrFadmFoc+z76EV6LPG1jx6XceDsaCG3lFhyLNo/bV9ByPrE+FnBPckXQVP4XRkN76h3Fjt/a+5Er/oA/nCBvQ=="],
|
||||||
|
|
||||||
"@tailwindcss/vite": ["@tailwindcss/vite@4.2.0", "", { "dependencies": { "@tailwindcss/node": "4.2.0", "@tailwindcss/oxide": "4.2.0", "tailwindcss": "4.2.0" }, "peerDependencies": { "vite": "^5.2.0 || ^6 || ^7" } }, "sha512-da9mFCaHpoOgtQiWtDGIikTrSpUFBtIZCG3jy/u2BGV+l/X1/pbxzmIUxNt6JWm19N3WtGi4KlJdSH/Si83WOA=="],
|
"@tailwindcss/vite": ["@tailwindcss/vite@4.2.0", "https://registry.npmmirror.com/@tailwindcss/vite/-/vite-4.2.0.tgz", { "dependencies": { "@tailwindcss/node": "4.2.0", "@tailwindcss/oxide": "4.2.0", "tailwindcss": "4.2.0" }, "peerDependencies": { "vite": "^5.2.0 || ^6 || ^7" } }, "sha512-da9mFCaHpoOgtQiWtDGIikTrSpUFBtIZCG3jy/u2BGV+l/X1/pbxzmIUxNt6JWm19N3WtGi4KlJdSH/Si83WOA=="],
|
||||||
|
|
||||||
"@tanstack/virtual-core": ["@tanstack/virtual-core@3.13.18", "", {}, "sha512-Mx86Hqu1k39icq2Zusq+Ey2J6dDWTjDvEv43PJtRCoEYTLyfaPnxIQ6iy7YAOK0NV/qOEmZQ/uCufrppZxTgcg=="],
|
"@tanstack/virtual-core": ["@tanstack/virtual-core@3.13.18", "https://registry.npmmirror.com/@tanstack/virtual-core/-/virtual-core-3.13.18.tgz", {}, "sha512-Mx86Hqu1k39icq2Zusq+Ey2J6dDWTjDvEv43PJtRCoEYTLyfaPnxIQ6iy7YAOK0NV/qOEmZQ/uCufrppZxTgcg=="],
|
||||||
|
|
||||||
"@tanstack/vue-virtual": ["@tanstack/vue-virtual@3.13.18", "", { "dependencies": { "@tanstack/virtual-core": "3.13.18" }, "peerDependencies": { "vue": "^2.7.0 || ^3.0.0" } }, "sha512-6pT8HdHtTU5Z+t906cGdCroUNA5wHjFXsNss9gwk7QAr1VNZtz9IQCs2Nhx0gABK48c+OocHl2As+TMg8+Hy4A=="],
|
"@tanstack/vue-virtual": ["@tanstack/vue-virtual@3.13.18", "https://registry.npmmirror.com/@tanstack/vue-virtual/-/vue-virtual-3.13.18.tgz", { "dependencies": { "@tanstack/virtual-core": "3.13.18" }, "peerDependencies": { "vue": "^2.7.0 || ^3.0.0" } }, "sha512-6pT8HdHtTU5Z+t906cGdCroUNA5wHjFXsNss9gwk7QAr1VNZtz9IQCs2Nhx0gABK48c+OocHl2As+TMg8+Hy4A=="],
|
||||||
|
|
||||||
"@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="],
|
"@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "https://registry.npmmirror.com/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="],
|
||||||
|
|
||||||
"@types/bun": ["@types/bun@1.3.9", "", { "dependencies": { "bun-types": "1.3.9" } }, "sha512-KQ571yULOdWJiMH+RIWIOZ7B2RXQGpL1YQrBtLIV3FqDcCu6FsbFUBwhdKUlCKUpS3PJDsHlJ1QKlpxoVR+xtw=="],
|
"@types/bun": ["@types/bun@1.3.9", "https://registry.npmmirror.com/@types/bun/-/bun-1.3.9.tgz", { "dependencies": { "bun-types": "1.3.9" } }, "sha512-KQ571yULOdWJiMH+RIWIOZ7B2RXQGpL1YQrBtLIV3FqDcCu6FsbFUBwhdKUlCKUpS3PJDsHlJ1QKlpxoVR+xtw=="],
|
||||||
|
|
||||||
"@types/node": ["@types/node@24.10.13", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-oH72nZRfDv9lADUBSo104Aq7gPHpQZc4BTx38r9xf9pg5LfP6EzSyH2n7qFmmxRQXh7YlUXODcYsg6PuTDSxGg=="],
|
"@types/node": ["@types/node@24.10.13", "https://registry.npmmirror.com/@types/node/-/node-24.10.13.tgz", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-oH72nZRfDv9lADUBSo104Aq7gPHpQZc4BTx38r9xf9pg5LfP6EzSyH2n7qFmmxRQXh7YlUXODcYsg6PuTDSxGg=="],
|
||||||
|
|
||||||
"@types/web-bluetooth": ["@types/web-bluetooth@0.0.21", "", {}, "sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA=="],
|
"@types/web-bluetooth": ["@types/web-bluetooth@0.0.21", "https://registry.npmmirror.com/@types/web-bluetooth/-/web-bluetooth-0.0.21.tgz", {}, "sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA=="],
|
||||||
|
|
||||||
"@vitejs/plugin-vue": ["@vitejs/plugin-vue@6.0.4", "", { "dependencies": { "@rolldown/pluginutils": "1.0.0-rc.2" }, "peerDependencies": { "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0", "vue": "^3.2.25" } }, "sha512-uM5iXipgYIn13UUQCZNdWkYk+sysBeA97d5mHsAoAt1u/wpN3+zxOmsVJWosuzX+IMGRzeYUNytztrYznboIkQ=="],
|
"@vitejs/plugin-vue": ["@vitejs/plugin-vue@6.0.4", "https://registry.npmmirror.com/@vitejs/plugin-vue/-/plugin-vue-6.0.4.tgz", { "dependencies": { "@rolldown/pluginutils": "1.0.0-rc.2" }, "peerDependencies": { "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0", "vue": "^3.2.25" } }, "sha512-uM5iXipgYIn13UUQCZNdWkYk+sysBeA97d5mHsAoAt1u/wpN3+zxOmsVJWosuzX+IMGRzeYUNytztrYznboIkQ=="],
|
||||||
|
|
||||||
"@volar/language-core": ["@volar/language-core@2.4.28", "", { "dependencies": { "@volar/source-map": "2.4.28" } }, "sha512-w4qhIJ8ZSitgLAkVay6AbcnC7gP3glYM3fYwKV3srj8m494E3xtrCv6E+bWviiK/8hs6e6t1ij1s2Endql7vzQ=="],
|
"@volar/language-core": ["@volar/language-core@2.4.28", "https://registry.npmmirror.com/@volar/language-core/-/language-core-2.4.28.tgz", { "dependencies": { "@volar/source-map": "2.4.28" } }, "sha512-w4qhIJ8ZSitgLAkVay6AbcnC7gP3glYM3fYwKV3srj8m494E3xtrCv6E+bWviiK/8hs6e6t1ij1s2Endql7vzQ=="],
|
||||||
|
|
||||||
"@volar/source-map": ["@volar/source-map@2.4.28", "", {}, "sha512-yX2BDBqJkRXfKw8my8VarTyjv48QwxdJtvRgUpNE5erCsgEUdI2DsLbpa+rOQVAJYshY99szEcRDmyHbF10ggQ=="],
|
"@volar/source-map": ["@volar/source-map@2.4.28", "https://registry.npmmirror.com/@volar/source-map/-/source-map-2.4.28.tgz", {}, "sha512-yX2BDBqJkRXfKw8my8VarTyjv48QwxdJtvRgUpNE5erCsgEUdI2DsLbpa+rOQVAJYshY99szEcRDmyHbF10ggQ=="],
|
||||||
|
|
||||||
"@volar/typescript": ["@volar/typescript@2.4.28", "", { "dependencies": { "@volar/language-core": "2.4.28", "path-browserify": "^1.0.1", "vscode-uri": "^3.0.8" } }, "sha512-Ja6yvWrbis2QtN4ClAKreeUZPVYMARDYZl9LMEv1iQ1QdepB6wn0jTRxA9MftYmYa4DQ4k/DaSZpFPUfxl8giw=="],
|
"@volar/typescript": ["@volar/typescript@2.4.28", "https://registry.npmmirror.com/@volar/typescript/-/typescript-2.4.28.tgz", { "dependencies": { "@volar/language-core": "2.4.28", "path-browserify": "^1.0.1", "vscode-uri": "^3.0.8" } }, "sha512-Ja6yvWrbis2QtN4ClAKreeUZPVYMARDYZl9LMEv1iQ1QdepB6wn0jTRxA9MftYmYa4DQ4k/DaSZpFPUfxl8giw=="],
|
||||||
|
|
||||||
"@vue/compiler-core": ["@vue/compiler-core@3.5.28", "", { "dependencies": { "@babel/parser": "^7.29.0", "@vue/shared": "3.5.28", "entities": "^7.0.1", "estree-walker": "^2.0.2", "source-map-js": "^1.2.1" } }, "sha512-kviccYxTgoE8n6OCw96BNdYlBg2GOWfBuOW4Vqwrt7mSKWKwFVvI8egdTltqRgITGPsTFYtKYfxIG8ptX2PJHQ=="],
|
"@vue/compiler-core": ["@vue/compiler-core@3.5.28", "https://registry.npmmirror.com/@vue/compiler-core/-/compiler-core-3.5.28.tgz", { "dependencies": { "@babel/parser": "^7.29.0", "@vue/shared": "3.5.28", "entities": "^7.0.1", "estree-walker": "^2.0.2", "source-map-js": "^1.2.1" } }, "sha512-kviccYxTgoE8n6OCw96BNdYlBg2GOWfBuOW4Vqwrt7mSKWKwFVvI8egdTltqRgITGPsTFYtKYfxIG8ptX2PJHQ=="],
|
||||||
|
|
||||||
"@vue/compiler-dom": ["@vue/compiler-dom@3.5.28", "", { "dependencies": { "@vue/compiler-core": "3.5.28", "@vue/shared": "3.5.28" } }, "sha512-/1ZepxAb159jKR1btkefDP+J2xuWL5V3WtleRmxaT+K2Aqiek/Ab/+Ebrw2pPj0sdHO8ViAyyJWfhXXOP/+LQA=="],
|
"@vue/compiler-dom": ["@vue/compiler-dom@3.5.28", "https://registry.npmmirror.com/@vue/compiler-dom/-/compiler-dom-3.5.28.tgz", { "dependencies": { "@vue/compiler-core": "3.5.28", "@vue/shared": "3.5.28" } }, "sha512-/1ZepxAb159jKR1btkefDP+J2xuWL5V3WtleRmxaT+K2Aqiek/Ab/+Ebrw2pPj0sdHO8ViAyyJWfhXXOP/+LQA=="],
|
||||||
|
|
||||||
"@vue/compiler-sfc": ["@vue/compiler-sfc@3.5.28", "", { "dependencies": { "@babel/parser": "^7.29.0", "@vue/compiler-core": "3.5.28", "@vue/compiler-dom": "3.5.28", "@vue/compiler-ssr": "3.5.28", "@vue/shared": "3.5.28", "estree-walker": "^2.0.2", "magic-string": "^0.30.21", "postcss": "^8.5.6", "source-map-js": "^1.2.1" } }, "sha512-6TnKMiNkd6u6VeVDhZn/07KhEZuBSn43Wd2No5zaP5s3xm8IqFTHBj84HJah4UepSUJTro5SoqqlOY22FKY96g=="],
|
"@vue/compiler-sfc": ["@vue/compiler-sfc@3.5.28", "https://registry.npmmirror.com/@vue/compiler-sfc/-/compiler-sfc-3.5.28.tgz", { "dependencies": { "@babel/parser": "^7.29.0", "@vue/compiler-core": "3.5.28", "@vue/compiler-dom": "3.5.28", "@vue/compiler-ssr": "3.5.28", "@vue/shared": "3.5.28", "estree-walker": "^2.0.2", "magic-string": "^0.30.21", "postcss": "^8.5.6", "source-map-js": "^1.2.1" } }, "sha512-6TnKMiNkd6u6VeVDhZn/07KhEZuBSn43Wd2No5zaP5s3xm8IqFTHBj84HJah4UepSUJTro5SoqqlOY22FKY96g=="],
|
||||||
|
|
||||||
"@vue/compiler-ssr": ["@vue/compiler-ssr@3.5.28", "", { "dependencies": { "@vue/compiler-dom": "3.5.28", "@vue/shared": "3.5.28" } }, "sha512-JCq//9w1qmC6UGLWJX7RXzrGpKkroubey/ZFqTpvEIDJEKGgntuDMqkuWiZvzTzTA5h2qZvFBFHY7fAAa9475g=="],
|
"@vue/compiler-ssr": ["@vue/compiler-ssr@3.5.28", "https://registry.npmmirror.com/@vue/compiler-ssr/-/compiler-ssr-3.5.28.tgz", { "dependencies": { "@vue/compiler-dom": "3.5.28", "@vue/shared": "3.5.28" } }, "sha512-JCq//9w1qmC6UGLWJX7RXzrGpKkroubey/ZFqTpvEIDJEKGgntuDMqkuWiZvzTzTA5h2qZvFBFHY7fAAa9475g=="],
|
||||||
|
|
||||||
"@vue/devtools-api": ["@vue/devtools-api@7.7.9", "", { "dependencies": { "@vue/devtools-kit": "^7.7.9" } }, "sha512-kIE8wvwlcZ6TJTbNeU2HQNtaxLx3a84aotTITUuL/4bzfPxzajGBOoqjMhwZJ8L9qFYDU/lAYMEEm11dnZOD6g=="],
|
"@vue/devtools-api": ["@vue/devtools-api@7.7.9", "https://registry.npmmirror.com/@vue/devtools-api/-/devtools-api-7.7.9.tgz", { "dependencies": { "@vue/devtools-kit": "^7.7.9" } }, "sha512-kIE8wvwlcZ6TJTbNeU2HQNtaxLx3a84aotTITUuL/4bzfPxzajGBOoqjMhwZJ8L9qFYDU/lAYMEEm11dnZOD6g=="],
|
||||||
|
|
||||||
"@vue/devtools-kit": ["@vue/devtools-kit@7.7.9", "", { "dependencies": { "@vue/devtools-shared": "^7.7.9", "birpc": "^2.3.0", "hookable": "^5.5.3", "mitt": "^3.0.1", "perfect-debounce": "^1.0.0", "speakingurl": "^14.0.1", "superjson": "^2.2.2" } }, "sha512-PyQ6odHSgiDVd4hnTP+aDk2X4gl2HmLDfiyEnn3/oV+ckFDuswRs4IbBT7vacMuGdwY/XemxBoh302ctbsptuA=="],
|
"@vue/devtools-kit": ["@vue/devtools-kit@7.7.9", "https://registry.npmmirror.com/@vue/devtools-kit/-/devtools-kit-7.7.9.tgz", { "dependencies": { "@vue/devtools-shared": "^7.7.9", "birpc": "^2.3.0", "hookable": "^5.5.3", "mitt": "^3.0.1", "perfect-debounce": "^1.0.0", "speakingurl": "^14.0.1", "superjson": "^2.2.2" } }, "sha512-PyQ6odHSgiDVd4hnTP+aDk2X4gl2HmLDfiyEnn3/oV+ckFDuswRs4IbBT7vacMuGdwY/XemxBoh302ctbsptuA=="],
|
||||||
|
|
||||||
"@vue/devtools-shared": ["@vue/devtools-shared@7.7.9", "", { "dependencies": { "rfdc": "^1.4.1" } }, "sha512-iWAb0v2WYf0QWmxCGy0seZNDPdO3Sp5+u78ORnyeonS6MT4PC7VPrryX2BpMJrwlDeaZ6BD4vP4XKjK0SZqaeA=="],
|
"@vue/devtools-shared": ["@vue/devtools-shared@7.7.9", "https://registry.npmmirror.com/@vue/devtools-shared/-/devtools-shared-7.7.9.tgz", { "dependencies": { "rfdc": "^1.4.1" } }, "sha512-iWAb0v2WYf0QWmxCGy0seZNDPdO3Sp5+u78ORnyeonS6MT4PC7VPrryX2BpMJrwlDeaZ6BD4vP4XKjK0SZqaeA=="],
|
||||||
|
|
||||||
"@vue/language-core": ["@vue/language-core@3.2.5", "", { "dependencies": { "@volar/language-core": "2.4.28", "@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" } }, "sha512-d3OIxN/+KRedeM5wQ6H6NIpwS3P5gC9nmyaHgBk+rO6dIsjY+tOh4UlPpiZbAh3YtLdCGEX4M16RmsBqPmJV+g=="],
|
"@vue/language-core": ["@vue/language-core@3.2.5", "https://registry.npmmirror.com/@vue/language-core/-/language-core-3.2.5.tgz", { "dependencies": { "@volar/language-core": "2.4.28", "@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" } }, "sha512-d3OIxN/+KRedeM5wQ6H6NIpwS3P5gC9nmyaHgBk+rO6dIsjY+tOh4UlPpiZbAh3YtLdCGEX4M16RmsBqPmJV+g=="],
|
||||||
|
|
||||||
"@vue/reactivity": ["@vue/reactivity@3.5.28", "", { "dependencies": { "@vue/shared": "3.5.28" } }, "sha512-gr5hEsxvn+RNyu9/9o1WtdYdwDjg5FgjUSBEkZWqgTKlo/fvwZ2+8W6AfKsc9YN2k/+iHYdS9vZYAhpi10kNaw=="],
|
"@vue/reactivity": ["@vue/reactivity@3.5.28", "https://registry.npmmirror.com/@vue/reactivity/-/reactivity-3.5.28.tgz", { "dependencies": { "@vue/shared": "3.5.28" } }, "sha512-gr5hEsxvn+RNyu9/9o1WtdYdwDjg5FgjUSBEkZWqgTKlo/fvwZ2+8W6AfKsc9YN2k/+iHYdS9vZYAhpi10kNaw=="],
|
||||||
|
|
||||||
"@vue/runtime-core": ["@vue/runtime-core@3.5.28", "", { "dependencies": { "@vue/reactivity": "3.5.28", "@vue/shared": "3.5.28" } }, "sha512-POVHTdbgnrBBIpnbYU4y7pOMNlPn2QVxVzkvEA2pEgvzbelQq4ZOUxbp2oiyo+BOtiYlm8Q44wShHJoBvDPAjQ=="],
|
"@vue/runtime-core": ["@vue/runtime-core@3.5.28", "https://registry.npmmirror.com/@vue/runtime-core/-/runtime-core-3.5.28.tgz", { "dependencies": { "@vue/reactivity": "3.5.28", "@vue/shared": "3.5.28" } }, "sha512-POVHTdbgnrBBIpnbYU4y7pOMNlPn2QVxVzkvEA2pEgvzbelQq4ZOUxbp2oiyo+BOtiYlm8Q44wShHJoBvDPAjQ=="],
|
||||||
|
|
||||||
"@vue/runtime-dom": ["@vue/runtime-dom@3.5.28", "", { "dependencies": { "@vue/reactivity": "3.5.28", "@vue/runtime-core": "3.5.28", "@vue/shared": "3.5.28", "csstype": "^3.2.3" } }, "sha512-4SXxSF8SXYMuhAIkT+eBRqOkWEfPu6nhccrzrkioA6l0boiq7sp18HCOov9qWJA5HML61kW8p/cB4MmBiG9dSA=="],
|
"@vue/runtime-dom": ["@vue/runtime-dom@3.5.28", "https://registry.npmmirror.com/@vue/runtime-dom/-/runtime-dom-3.5.28.tgz", { "dependencies": { "@vue/reactivity": "3.5.28", "@vue/runtime-core": "3.5.28", "@vue/shared": "3.5.28", "csstype": "^3.2.3" } }, "sha512-4SXxSF8SXYMuhAIkT+eBRqOkWEfPu6nhccrzrkioA6l0boiq7sp18HCOov9qWJA5HML61kW8p/cB4MmBiG9dSA=="],
|
||||||
|
|
||||||
"@vue/server-renderer": ["@vue/server-renderer@3.5.28", "", { "dependencies": { "@vue/compiler-ssr": "3.5.28", "@vue/shared": "3.5.28" }, "peerDependencies": { "vue": "3.5.28" } }, "sha512-pf+5ECKGj8fX95bNincbzJ6yp6nyzuLDhYZCeFxUNp8EBrQpPpQaLX3nNCp49+UbgbPun3CeVE+5CXVV1Xydfg=="],
|
"@vue/server-renderer": ["@vue/server-renderer@3.5.28", "https://registry.npmmirror.com/@vue/server-renderer/-/server-renderer-3.5.28.tgz", { "dependencies": { "@vue/compiler-ssr": "3.5.28", "@vue/shared": "3.5.28" }, "peerDependencies": { "vue": "3.5.28" } }, "sha512-pf+5ECKGj8fX95bNincbzJ6yp6nyzuLDhYZCeFxUNp8EBrQpPpQaLX3nNCp49+UbgbPun3CeVE+5CXVV1Xydfg=="],
|
||||||
|
|
||||||
"@vue/shared": ["@vue/shared@3.5.28", "", {}, "sha512-cfWa1fCGBxrvaHRhvV3Is0MgmrbSCxYTXCSCau2I0a1Xw1N1pHAvkWCiXPRAqjvToILvguNyEwjevUqAuBQWvQ=="],
|
"@vue/shared": ["@vue/shared@3.5.28", "https://registry.npmmirror.com/@vue/shared/-/shared-3.5.28.tgz", {}, "sha512-cfWa1fCGBxrvaHRhvV3Is0MgmrbSCxYTXCSCau2I0a1Xw1N1pHAvkWCiXPRAqjvToILvguNyEwjevUqAuBQWvQ=="],
|
||||||
|
|
||||||
"@vue/tsconfig": ["@vue/tsconfig@0.8.1", "", { "peerDependencies": { "typescript": "5.x", "vue": "^3.4.0" }, "optionalPeers": ["typescript", "vue"] }, "sha512-aK7feIWPXFSUhsCP9PFqPyFOcz4ENkb8hZ2pneL6m2UjCkccvaOhC/5KCKluuBufvp2KzkbdA2W2pk20vLzu3g=="],
|
"@vue/tsconfig": ["@vue/tsconfig@0.8.1", "https://registry.npmmirror.com/@vue/tsconfig/-/tsconfig-0.8.1.tgz", { "peerDependencies": { "typescript": "5.x", "vue": "^3.4.0" }, "optionalPeers": ["typescript", "vue"] }, "sha512-aK7feIWPXFSUhsCP9PFqPyFOcz4ENkb8hZ2pneL6m2UjCkccvaOhC/5KCKluuBufvp2KzkbdA2W2pk20vLzu3g=="],
|
||||||
|
|
||||||
"@vueuse/core": ["@vueuse/core@14.2.1", "", { "dependencies": { "@types/web-bluetooth": "^0.0.21", "@vueuse/metadata": "14.2.1", "@vueuse/shared": "14.2.1" }, "peerDependencies": { "vue": "^3.5.0" } }, "sha512-3vwDzV+GDUNpdegRY6kzpLm4Igptq+GA0QkJ3W61Iv27YWwW/ufSlOfgQIpN6FZRMG0mkaz4gglJRtq5SeJyIQ=="],
|
"@vueuse/core": ["@vueuse/core@14.2.1", "https://registry.npmmirror.com/@vueuse/core/-/core-14.2.1.tgz", { "dependencies": { "@types/web-bluetooth": "^0.0.21", "@vueuse/metadata": "14.2.1", "@vueuse/shared": "14.2.1" }, "peerDependencies": { "vue": "^3.5.0" } }, "sha512-3vwDzV+GDUNpdegRY6kzpLm4Igptq+GA0QkJ3W61Iv27YWwW/ufSlOfgQIpN6FZRMG0mkaz4gglJRtq5SeJyIQ=="],
|
||||||
|
|
||||||
"@vueuse/metadata": ["@vueuse/metadata@14.2.1", "", {}, "sha512-1ButlVtj5Sb/HDtIy1HFr1VqCP4G6Ypqt5MAo0lCgjokrk2mvQKsK2uuy0vqu/Ks+sHfuHo0B9Y9jn9xKdjZsw=="],
|
"@vueuse/metadata": ["@vueuse/metadata@14.2.1", "https://registry.npmmirror.com/@vueuse/metadata/-/metadata-14.2.1.tgz", {}, "sha512-1ButlVtj5Sb/HDtIy1HFr1VqCP4G6Ypqt5MAo0lCgjokrk2mvQKsK2uuy0vqu/Ks+sHfuHo0B9Y9jn9xKdjZsw=="],
|
||||||
|
|
||||||
"@vueuse/shared": ["@vueuse/shared@14.2.1", "", { "peerDependencies": { "vue": "^3.5.0" } }, "sha512-shTJncjV9JTI4oVNyF1FQonetYAiTBd+Qj7cY89SWbXSkx7gyhrgtEdF2ZAVWS1S3SHlaROO6F2IesJxQEkZBw=="],
|
"@vueuse/shared": ["@vueuse/shared@14.2.1", "https://registry.npmmirror.com/@vueuse/shared/-/shared-14.2.1.tgz", { "peerDependencies": { "vue": "^3.5.0" } }, "sha512-shTJncjV9JTI4oVNyF1FQonetYAiTBd+Qj7cY89SWbXSkx7gyhrgtEdF2ZAVWS1S3SHlaROO6F2IesJxQEkZBw=="],
|
||||||
|
|
||||||
"@xmldom/xmldom": ["@xmldom/xmldom@0.9.10", "", {}, "sha512-A9gOqLdi6cV4ibazAjcQufGj0B1y/vDqYrcuP6d/6x8P27gRS8643Dj9o1dEKtB6O7fwxb2FgBmJS2mX7gpvdw=="],
|
"ag-charts-community": ["ag-charts-community@13.1.0", "https://registry.npmmirror.com/ag-charts-community/-/ag-charts-community-13.1.0.tgz", { "dependencies": { "ag-charts-core": "13.1.0", "ag-charts-locale": "13.1.0", "ag-charts-types": "13.1.0" } }, "sha512-w+uFTjxlAoTq1+8tgUORtB/zr9jm38ibXzbbWnkBP9Dep9yahi5a1jZL7yExAX35uq3g9QtjTh0Oj/QPDBQ9Ew=="],
|
||||||
|
|
||||||
"ag-charts-community": ["ag-charts-community@13.1.0", "", { "dependencies": { "ag-charts-core": "13.1.0", "ag-charts-locale": "13.1.0", "ag-charts-types": "13.1.0" } }, "sha512-w+uFTjxlAoTq1+8tgUORtB/zr9jm38ibXzbbWnkBP9Dep9yahi5a1jZL7yExAX35uq3g9QtjTh0Oj/QPDBQ9Ew=="],
|
"ag-charts-core": ["ag-charts-core@13.1.0", "https://registry.npmmirror.com/ag-charts-core/-/ag-charts-core-13.1.0.tgz", { "dependencies": { "ag-charts-types": "13.1.0" } }, "sha512-mLHJZ8oU5CPeLRURescdISCtMsiiA/m4d1iBr6aQBEgiTVogRMGpFpsYNtQiYtoW2sRh+62I9sN8fhC3JQjX/g=="],
|
||||||
|
|
||||||
"ag-charts-core": ["ag-charts-core@13.1.0", "", { "dependencies": { "ag-charts-types": "13.1.0" } }, "sha512-mLHJZ8oU5CPeLRURescdISCtMsiiA/m4d1iBr6aQBEgiTVogRMGpFpsYNtQiYtoW2sRh+62I9sN8fhC3JQjX/g=="],
|
"ag-charts-enterprise": ["ag-charts-enterprise@13.1.0", "https://registry.npmmirror.com/ag-charts-enterprise/-/ag-charts-enterprise-13.1.0.tgz", { "dependencies": { "ag-charts-community": "13.1.0", "ag-charts-core": "13.1.0" } }, "sha512-WyKIqvkOdtdvEJxq76hjTacXTCpIR2lq1JDMYc5MtoHYtiVt1KHApsxS0nbutp/CxGKRgdOqJtxUF+3r33pgPw=="],
|
||||||
|
|
||||||
"ag-charts-enterprise": ["ag-charts-enterprise@13.1.0", "", { "dependencies": { "ag-charts-community": "13.1.0", "ag-charts-core": "13.1.0" } }, "sha512-WyKIqvkOdtdvEJxq76hjTacXTCpIR2lq1JDMYc5MtoHYtiVt1KHApsxS0nbutp/CxGKRgdOqJtxUF+3r33pgPw=="],
|
"ag-charts-locale": ["ag-charts-locale@13.1.0", "https://registry.npmmirror.com/ag-charts-locale/-/ag-charts-locale-13.1.0.tgz", {}, "sha512-mPgJnVsOI4Cf17CAlRh8BvLz19e165sdQJeUXNaB7M+DPB+pxODOcfx4oqZlR4Wc8Zu++TGb/2ueHa/aeV2qeQ=="],
|
||||||
|
|
||||||
"ag-charts-locale": ["ag-charts-locale@13.1.0", "", {}, "sha512-mPgJnVsOI4Cf17CAlRh8BvLz19e165sdQJeUXNaB7M+DPB+pxODOcfx4oqZlR4Wc8Zu++TGb/2ueHa/aeV2qeQ=="],
|
"ag-charts-types": ["ag-charts-types@13.1.0", "https://registry.npmmirror.com/ag-charts-types/-/ag-charts-types-13.1.0.tgz", {}, "sha512-DytRM3CXli+Y013SC1Mr8lQBrhVTACK+11ilDHOhwUM0sRpmGuR51XFGcBKOliW1Vas1AycP31Cm3Pp0jx3hqw=="],
|
||||||
|
|
||||||
"ag-charts-types": ["ag-charts-types@13.1.0", "", {}, "sha512-DytRM3CXli+Y013SC1Mr8lQBrhVTACK+11ilDHOhwUM0sRpmGuR51XFGcBKOliW1Vas1AycP31Cm3Pp0jx3hqw=="],
|
"ag-grid-community": ["ag-grid-community@35.1.0", "https://registry.npmmirror.com/ag-grid-community/-/ag-grid-community-35.1.0.tgz", { "dependencies": { "ag-charts-types": "13.1.0" } }, "sha512-yWFQfRNjv3KUBkHHzFdDOYGjPcDMU0B8Up4qG651diFlGRUGEGVs94SK73niWvk1FDZdpV9oWrwq3f30/qAoVg=="],
|
||||||
|
|
||||||
"ag-grid-community": ["ag-grid-community@35.1.0", "", { "dependencies": { "ag-charts-types": "13.1.0" } }, "sha512-yWFQfRNjv3KUBkHHzFdDOYGjPcDMU0B8Up4qG651diFlGRUGEGVs94SK73niWvk1FDZdpV9oWrwq3f30/qAoVg=="],
|
"ag-grid-enterprise": ["ag-grid-enterprise@35.1.0", "https://registry.npmmirror.com/ag-grid-enterprise/-/ag-grid-enterprise-35.1.0.tgz", { "dependencies": { "ag-grid-community": "35.1.0" }, "optionalDependencies": { "ag-charts-community": "13.1.0", "ag-charts-enterprise": "13.1.0" } }, "sha512-Zhod3fpgWa9KE0JNFkkkb8/3Qv66UR9KF3wFyCz++wQUtQm5wdExul4UA8wm1ukvBmD6QyBLQ5Cs9zDnIEb0uQ=="],
|
||||||
|
|
||||||
"ag-grid-enterprise": ["ag-grid-enterprise@35.1.0", "", { "dependencies": { "ag-grid-community": "35.1.0" }, "optionalDependencies": { "ag-charts-community": "13.1.0", "ag-charts-enterprise": "13.1.0" } }, "sha512-Zhod3fpgWa9KE0JNFkkkb8/3Qv66UR9KF3wFyCz++wQUtQm5wdExul4UA8wm1ukvBmD6QyBLQ5Cs9zDnIEb0uQ=="],
|
"ag-grid-vue3": ["ag-grid-vue3@35.1.0", "https://registry.npmmirror.com/ag-grid-vue3/-/ag-grid-vue3-35.1.0.tgz", { "dependencies": { "ag-grid-community": "35.1.0" }, "peerDependencies": { "vue": "^3.5.0" } }, "sha512-BvM7yrFxRB/r5hZ4xSyE6T2lU2Rj+Ls6RH5tTu/n8DmhCTmLj4QCEkoU7EuaE0/Az3uEHOubYMaCX4jcDf181A=="],
|
||||||
|
|
||||||
"ag-grid-vue3": ["ag-grid-vue3@35.1.0", "", { "dependencies": { "ag-grid-community": "35.1.0" }, "peerDependencies": { "vue": "^3.5.0" } }, "sha512-BvM7yrFxRB/r5hZ4xSyE6T2lU2Rj+Ls6RH5tTu/n8DmhCTmLj4QCEkoU7EuaE0/Az3uEHOubYMaCX4jcDf181A=="],
|
"alien-signals": ["alien-signals@3.1.2", "https://registry.npmmirror.com/alien-signals/-/alien-signals-3.1.2.tgz", {}, "sha512-d9dYqZTS90WLiU0I5c6DHj/HcKkF8ZyGN3G5x8wSbslulz70KOxaqCT0hQCo9KOyhVqzqGojvNdJXoTumZOtcw=="],
|
||||||
|
|
||||||
"alien-signals": ["alien-signals@3.1.2", "", {}, "sha512-d9dYqZTS90WLiU0I5c6DHj/HcKkF8ZyGN3G5x8wSbslulz70KOxaqCT0hQCo9KOyhVqzqGojvNdJXoTumZOtcw=="],
|
"archiver": ["archiver@5.3.2", "https://registry.npmmirror.com/archiver/-/archiver-5.3.2.tgz", { "dependencies": { "archiver-utils": "^2.1.0", "async": "^3.2.4", "buffer-crc32": "^0.2.1", "readable-stream": "^3.6.0", "readdir-glob": "^1.1.2", "tar-stream": "^2.2.0", "zip-stream": "^4.1.0" } }, "sha512-+25nxyyznAXF7Nef3y0EbBeqmGZgeN/BxHX29Rs39djAfaFalmQ89SE6CWyDCHzGL0yt/ycBtNOmGTW0FyGWNw=="],
|
||||||
|
|
||||||
"archiver": ["archiver@5.3.2", "", { "dependencies": { "archiver-utils": "^2.1.0", "async": "^3.2.4", "buffer-crc32": "^0.2.1", "readable-stream": "^3.6.0", "readdir-glob": "^1.1.2", "tar-stream": "^2.2.0", "zip-stream": "^4.1.0" } }, "sha512-+25nxyyznAXF7Nef3y0EbBeqmGZgeN/BxHX29Rs39djAfaFalmQ89SE6CWyDCHzGL0yt/ycBtNOmGTW0FyGWNw=="],
|
"archiver-utils": ["archiver-utils@2.1.0", "https://registry.npmmirror.com/archiver-utils/-/archiver-utils-2.1.0.tgz", { "dependencies": { "glob": "^7.1.4", "graceful-fs": "^4.2.0", "lazystream": "^1.0.0", "lodash.defaults": "^4.2.0", "lodash.difference": "^4.5.0", "lodash.flatten": "^4.4.0", "lodash.isplainobject": "^4.0.6", "lodash.union": "^4.6.0", "normalize-path": "^3.0.0", "readable-stream": "^2.0.0" } }, "sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw=="],
|
||||||
|
|
||||||
"archiver-utils": ["archiver-utils@2.1.0", "", { "dependencies": { "glob": "^7.1.4", "graceful-fs": "^4.2.0", "lazystream": "^1.0.0", "lodash.defaults": "^4.2.0", "lodash.difference": "^4.5.0", "lodash.flatten": "^4.4.0", "lodash.isplainobject": "^4.0.6", "lodash.union": "^4.6.0", "normalize-path": "^3.0.0", "readable-stream": "^2.0.0" } }, "sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw=="],
|
"aria-hidden": ["aria-hidden@1.2.6", "https://registry.npmmirror.com/aria-hidden/-/aria-hidden-1.2.6.tgz", { "dependencies": { "tslib": "^2.0.0" } }, "sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA=="],
|
||||||
|
|
||||||
"aria-hidden": ["aria-hidden@1.2.6", "", { "dependencies": { "tslib": "^2.0.0" } }, "sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA=="],
|
"async": ["async@3.2.6", "https://registry.npmmirror.com/async/-/async-3.2.6.tgz", {}, "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA=="],
|
||||||
|
|
||||||
"async": ["async@3.2.6", "", {}, "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA=="],
|
"balanced-match": ["balanced-match@1.0.2", "https://registry.npmmirror.com/balanced-match/-/balanced-match-1.0.2.tgz", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
|
||||||
|
|
||||||
"balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
|
"base64-js": ["base64-js@1.5.1", "https://registry.npmmirror.com/base64-js/-/base64-js-1.5.1.tgz", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="],
|
||||||
|
|
||||||
"base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="],
|
"big-integer": ["big-integer@1.6.52", "https://registry.npmmirror.com/big-integer/-/big-integer-1.6.52.tgz", {}, "sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg=="],
|
||||||
|
|
||||||
"big-integer": ["big-integer@1.6.52", "", {}, "sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg=="],
|
"binary": ["binary@0.3.0", "https://registry.npmmirror.com/binary/-/binary-0.3.0.tgz", { "dependencies": { "buffers": "~0.1.1", "chainsaw": "~0.1.0" } }, "sha512-D4H1y5KYwpJgK8wk1Cue5LLPgmwHKYSChkbspQg5JtVuR5ulGckxfR62H3AE9UDkdMC8yyXlqYihuz3Aqg2XZg=="],
|
||||||
|
|
||||||
"binary": ["binary@0.3.0", "", { "dependencies": { "buffers": "~0.1.1", "chainsaw": "~0.1.0" } }, "sha512-D4H1y5KYwpJgK8wk1Cue5LLPgmwHKYSChkbspQg5JtVuR5ulGckxfR62H3AE9UDkdMC8yyXlqYihuz3Aqg2XZg=="],
|
"birpc": ["birpc@2.9.0", "https://registry.npmmirror.com/birpc/-/birpc-2.9.0.tgz", {}, "sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw=="],
|
||||||
|
|
||||||
"birpc": ["birpc@2.9.0", "", {}, "sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw=="],
|
"bl": ["bl@4.1.0", "https://registry.npmmirror.com/bl/-/bl-4.1.0.tgz", { "dependencies": { "buffer": "^5.5.0", "inherits": "^2.0.4", "readable-stream": "^3.4.0" } }, "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w=="],
|
||||||
|
|
||||||
"bl": ["bl@4.1.0", "", { "dependencies": { "buffer": "^5.5.0", "inherits": "^2.0.4", "readable-stream": "^3.4.0" } }, "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w=="],
|
"bluebird": ["bluebird@3.4.7", "https://registry.npmmirror.com/bluebird/-/bluebird-3.4.7.tgz", {}, "sha512-iD3898SR7sWVRHbiQv+sHUtHnMvC1o3nW5rAcqnq3uOn07DSAppZYUkIGslDz6gXC7HfunPe7YVBgoEJASPcHA=="],
|
||||||
|
|
||||||
"bluebird": ["bluebird@3.4.7", "", {}, "sha512-iD3898SR7sWVRHbiQv+sHUtHnMvC1o3nW5rAcqnq3uOn07DSAppZYUkIGslDz6gXC7HfunPe7YVBgoEJASPcHA=="],
|
"brace-expansion": ["brace-expansion@2.0.2", "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-2.0.2.tgz", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="],
|
||||||
|
|
||||||
"brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="],
|
"buffer": ["buffer@5.7.1", "https://registry.npmmirror.com/buffer/-/buffer-5.7.1.tgz", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" } }, "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ=="],
|
||||||
|
|
||||||
"buffer": ["buffer@5.7.1", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" } }, "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ=="],
|
"buffer-crc32": ["buffer-crc32@0.2.13", "https://registry.npmmirror.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz", {}, "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ=="],
|
||||||
|
|
||||||
"buffer-crc32": ["buffer-crc32@0.2.13", "", {}, "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ=="],
|
"buffer-indexof-polyfill": ["buffer-indexof-polyfill@1.0.2", "https://registry.npmmirror.com/buffer-indexof-polyfill/-/buffer-indexof-polyfill-1.0.2.tgz", {}, "sha512-I7wzHwA3t1/lwXQh+A5PbNvJxgfo5r3xulgpYDB5zckTu/Z9oUK9biouBKQUjEqzaz3HnAT6TYoovmE+GqSf7A=="],
|
||||||
|
|
||||||
"buffer-indexof-polyfill": ["buffer-indexof-polyfill@1.0.2", "", {}, "sha512-I7wzHwA3t1/lwXQh+A5PbNvJxgfo5r3xulgpYDB5zckTu/Z9oUK9biouBKQUjEqzaz3HnAT6TYoovmE+GqSf7A=="],
|
"buffers": ["buffers@0.1.1", "https://registry.npmmirror.com/buffers/-/buffers-0.1.1.tgz", {}, "sha512-9q/rDEGSb/Qsvv2qvzIzdluL5k7AaJOTrw23z9reQthrbF7is4CtlT0DXyO1oei2DCp4uojjzQ7igaSHp1kAEQ=="],
|
||||||
|
|
||||||
"buffers": ["buffers@0.1.1", "", {}, "sha512-9q/rDEGSb/Qsvv2qvzIzdluL5k7AaJOTrw23z9reQthrbF7is4CtlT0DXyO1oei2DCp4uojjzQ7igaSHp1kAEQ=="],
|
"bun-types": ["bun-types@1.3.9", "https://registry.npmmirror.com/bun-types/-/bun-types-1.3.9.tgz", { "dependencies": { "@types/node": "*" } }, "sha512-+UBWWOakIP4Tswh0Bt0QD0alpTY8cb5hvgiYeWCMet9YukHbzuruIEeXC2D7nMJPB12kbh8C7XJykSexEqGKJg=="],
|
||||||
|
|
||||||
"bun-types": ["bun-types@1.3.9", "", { "dependencies": { "@types/node": "*" } }, "sha512-+UBWWOakIP4Tswh0Bt0QD0alpTY8cb5hvgiYeWCMet9YukHbzuruIEeXC2D7nMJPB12kbh8C7XJykSexEqGKJg=="],
|
"chainsaw": ["chainsaw@0.1.0", "https://registry.npmmirror.com/chainsaw/-/chainsaw-0.1.0.tgz", { "dependencies": { "traverse": ">=0.3.0 <0.4" } }, "sha512-75kWfWt6MEKNC8xYXIdRpDehRYY/tNSgwKaJq+dbbDcxORuVrrQ+SEHoWsniVn9XPYfP4gmdWIeDk/4YNp1rNQ=="],
|
||||||
|
|
||||||
"chainsaw": ["chainsaw@0.1.0", "", { "dependencies": { "traverse": ">=0.3.0 <0.4" } }, "sha512-75kWfWt6MEKNC8xYXIdRpDehRYY/tNSgwKaJq+dbbDcxORuVrrQ+SEHoWsniVn9XPYfP4gmdWIeDk/4YNp1rNQ=="],
|
"class-variance-authority": ["class-variance-authority@0.7.1", "https://registry.npmmirror.com/class-variance-authority/-/class-variance-authority-0.7.1.tgz", { "dependencies": { "clsx": "^2.1.1" } }, "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg=="],
|
||||||
|
|
||||||
"class-variance-authority": ["class-variance-authority@0.7.1", "", { "dependencies": { "clsx": "^2.1.1" } }, "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg=="],
|
"clsx": ["clsx@2.1.1", "https://registry.npmmirror.com/clsx/-/clsx-2.1.1.tgz", {}, "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="],
|
||||||
|
|
||||||
"clsx": ["clsx@2.1.1", "", {}, "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="],
|
"compress-commons": ["compress-commons@4.1.2", "https://registry.npmmirror.com/compress-commons/-/compress-commons-4.1.2.tgz", { "dependencies": { "buffer-crc32": "^0.2.13", "crc32-stream": "^4.0.2", "normalize-path": "^3.0.0", "readable-stream": "^3.6.0" } }, "sha512-D3uMHtGc/fcO1Gt1/L7i1e33VOvD4A9hfQLP+6ewd+BvG/gQ84Yh4oftEhAdjSMgBgwGL+jsppT7JYNpo6MHHg=="],
|
||||||
|
|
||||||
"compress-commons": ["compress-commons@4.1.2", "", { "dependencies": { "buffer-crc32": "^0.2.13", "crc32-stream": "^4.0.2", "normalize-path": "^3.0.0", "readable-stream": "^3.6.0" } }, "sha512-D3uMHtGc/fcO1Gt1/L7i1e33VOvD4A9hfQLP+6ewd+BvG/gQ84Yh4oftEhAdjSMgBgwGL+jsppT7JYNpo6MHHg=="],
|
"concat-map": ["concat-map@0.0.1", "https://registry.npmmirror.com/concat-map/-/concat-map-0.0.1.tgz", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="],
|
||||||
|
|
||||||
"concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="],
|
"copy-anything": ["copy-anything@4.0.5", "https://registry.npmmirror.com/copy-anything/-/copy-anything-4.0.5.tgz", { "dependencies": { "is-what": "^5.2.0" } }, "sha512-7Vv6asjS4gMOuILabD3l739tsaxFQmC+a7pLZm02zyvs8p977bL3zEgq3yDk5rn9B0PbYgIv++jmHcuUab4RhA=="],
|
||||||
|
|
||||||
"copy-anything": ["copy-anything@4.0.5", "", { "dependencies": { "is-what": "^5.2.0" } }, "sha512-7Vv6asjS4gMOuILabD3l739tsaxFQmC+a7pLZm02zyvs8p977bL3zEgq3yDk5rn9B0PbYgIv++jmHcuUab4RhA=="],
|
"core-util-is": ["core-util-is@1.0.3", "https://registry.npmmirror.com/core-util-is/-/core-util-is-1.0.3.tgz", {}, "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ=="],
|
||||||
|
|
||||||
"core-util-is": ["core-util-is@1.0.3", "", {}, "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ=="],
|
"crc-32": ["crc-32@1.2.2", "https://registry.npmmirror.com/crc-32/-/crc-32-1.2.2.tgz", { "bin": { "crc32": "bin/crc32.njs" } }, "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ=="],
|
||||||
|
|
||||||
"crc-32": ["crc-32@1.2.2", "", { "bin": { "crc32": "bin/crc32.njs" } }, "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ=="],
|
"crc32-stream": ["crc32-stream@4.0.3", "https://registry.npmmirror.com/crc32-stream/-/crc32-stream-4.0.3.tgz", { "dependencies": { "crc-32": "^1.2.0", "readable-stream": "^3.4.0" } }, "sha512-NT7w2JVU7DFroFdYkeq8cywxrgjPHWkdX1wjpRQXPX5Asews3tA+Ght6lddQO5Mkumffp3X7GEqku3epj2toIw=="],
|
||||||
|
|
||||||
"crc32-stream": ["crc32-stream@4.0.3", "", { "dependencies": { "crc-32": "^1.2.0", "readable-stream": "^3.4.0" } }, "sha512-NT7w2JVU7DFroFdYkeq8cywxrgjPHWkdX1wjpRQXPX5Asews3tA+Ght6lddQO5Mkumffp3X7GEqku3epj2toIw=="],
|
"csstype": ["csstype@3.2.3", "https://registry.npmmirror.com/csstype/-/csstype-3.2.3.tgz", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="],
|
||||||
|
|
||||||
"csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="],
|
"dayjs": ["dayjs@1.11.19", "https://registry.npmmirror.com/dayjs/-/dayjs-1.11.19.tgz", {}, "sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw=="],
|
||||||
|
|
||||||
"dayjs": ["dayjs@1.11.19", "", {}, "sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw=="],
|
"decimal.js": ["decimal.js@10.6.0", "https://registry.npmmirror.com/decimal.js/-/decimal.js-10.6.0.tgz", {}, "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg=="],
|
||||||
|
|
||||||
"decimal.js": ["decimal.js@10.6.0", "", {}, "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg=="],
|
"defu": ["defu@6.1.4", "https://registry.npmmirror.com/defu/-/defu-6.1.4.tgz", {}, "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg=="],
|
||||||
|
|
||||||
"defu": ["defu@6.1.4", "", {}, "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg=="],
|
"detect-libc": ["detect-libc@2.1.2", "https://registry.npmmirror.com/detect-libc/-/detect-libc-2.1.2.tgz", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="],
|
||||||
|
|
||||||
"detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="],
|
"duplexer2": ["duplexer2@0.1.4", "https://registry.npmmirror.com/duplexer2/-/duplexer2-0.1.4.tgz", { "dependencies": { "readable-stream": "^2.0.2" } }, "sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA=="],
|
||||||
|
|
||||||
"docxtemplater": ["docxtemplater@3.69.0", "", { "dependencies": { "@xmldom/xmldom": "^0.9.10" } }, "sha512-l1zDGXj4CHdBCkGPvmVOsEzc4DDpMxLXgnNd1zllEck9gxCGkkV5vv1tOD5JhudaM73nTIgymy4wil2u9O/uhQ=="],
|
"end-of-stream": ["end-of-stream@1.4.5", "https://registry.npmmirror.com/end-of-stream/-/end-of-stream-1.4.5.tgz", { "dependencies": { "once": "^1.4.0" } }, "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg=="],
|
||||||
|
|
||||||
"duplexer2": ["duplexer2@0.1.4", "", { "dependencies": { "readable-stream": "^2.0.2" } }, "sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA=="],
|
"enhanced-resolve": ["enhanced-resolve@5.19.0", "https://registry.npmmirror.com/enhanced-resolve/-/enhanced-resolve-5.19.0.tgz", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.3.0" } }, "sha512-phv3E1Xl4tQOShqSte26C7Fl84EwUdZsyOuSSk9qtAGyyQs2s3jJzComh+Abf4g187lUUAvH+H26omrqia2aGg=="],
|
||||||
|
|
||||||
"end-of-stream": ["end-of-stream@1.4.5", "", { "dependencies": { "once": "^1.4.0" } }, "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg=="],
|
"entities": ["entities@7.0.1", "https://registry.npmmirror.com/entities/-/entities-7.0.1.tgz", {}, "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA=="],
|
||||||
|
|
||||||
"enhanced-resolve": ["enhanced-resolve@5.19.0", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.3.0" } }, "sha512-phv3E1Xl4tQOShqSte26C7Fl84EwUdZsyOuSSk9qtAGyyQs2s3jJzComh+Abf4g187lUUAvH+H26omrqia2aGg=="],
|
"estree-walker": ["estree-walker@2.0.2", "https://registry.npmmirror.com/estree-walker/-/estree-walker-2.0.2.tgz", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="],
|
||||||
|
|
||||||
"entities": ["entities@7.0.1", "", {}, "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA=="],
|
"exceljs": ["exceljs@4.4.0", "https://registry.npmmirror.com/exceljs/-/exceljs-4.4.0.tgz", { "dependencies": { "archiver": "^5.0.0", "dayjs": "^1.8.34", "fast-csv": "^4.3.1", "jszip": "^3.10.1", "readable-stream": "^3.6.0", "saxes": "^5.0.1", "tmp": "^0.2.0", "unzipper": "^0.10.11", "uuid": "^8.3.0" } }, "sha512-XctvKaEMaj1Ii9oDOqbW/6e1gXknSY4g/aLCDicOXqBE4M0nRWkUu0PTp++UPNzoFY12BNHMfs/VadKIS6llvg=="],
|
||||||
|
|
||||||
"estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="],
|
"fast-csv": ["fast-csv@4.3.6", "https://registry.npmmirror.com/fast-csv/-/fast-csv-4.3.6.tgz", { "dependencies": { "@fast-csv/format": "4.3.5", "@fast-csv/parse": "4.3.6" } }, "sha512-2RNSpuwwsJGP0frGsOmTb9oUF+VkFSM4SyLTDgwf2ciHWTarN0lQTC+F2f/t5J9QjW+c65VFIAAu85GsvMIusw=="],
|
||||||
|
|
||||||
"exceljs": ["exceljs@4.4.0", "", { "dependencies": { "archiver": "^5.0.0", "dayjs": "^1.8.34", "fast-csv": "^4.3.1", "jszip": "^3.10.1", "readable-stream": "^3.6.0", "saxes": "^5.0.1", "tmp": "^0.2.0", "unzipper": "^0.10.11", "uuid": "^8.3.0" } }, "sha512-XctvKaEMaj1Ii9oDOqbW/6e1gXknSY4g/aLCDicOXqBE4M0nRWkUu0PTp++UPNzoFY12BNHMfs/VadKIS6llvg=="],
|
"fdir": ["fdir@6.5.0", "https://registry.npmmirror.com/fdir/-/fdir-6.5.0.tgz", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="],
|
||||||
|
|
||||||
"fast-csv": ["fast-csv@4.3.6", "", { "dependencies": { "@fast-csv/format": "4.3.5", "@fast-csv/parse": "4.3.6" } }, "sha512-2RNSpuwwsJGP0frGsOmTb9oUF+VkFSM4SyLTDgwf2ciHWTarN0lQTC+F2f/t5J9QjW+c65VFIAAu85GsvMIusw=="],
|
"framer-motion": ["framer-motion@12.34.3", "https://registry.npmmirror.com/framer-motion/-/framer-motion-12.34.3.tgz", { "dependencies": { "motion-dom": "^12.34.3", "motion-utils": "^12.29.2", "tslib": "^2.4.0" }, "peerDependencies": { "@emotion/is-prop-valid": "*", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/is-prop-valid", "react", "react-dom"] }, "sha512-v81ecyZKYO/DfpTwHivqkxSUBzvceOpoI+wLfgCgoUIKxlFKEXdg0oR9imxwXumT4SFy8vRk9xzJ5l3/Du/55Q=="],
|
||||||
|
|
||||||
"fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="],
|
"fs-constants": ["fs-constants@1.0.0", "https://registry.npmmirror.com/fs-constants/-/fs-constants-1.0.0.tgz", {}, "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow=="],
|
||||||
|
|
||||||
"file-saver": ["file-saver@2.0.5", "", {}, "sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA=="],
|
"fs.realpath": ["fs.realpath@1.0.0", "https://registry.npmmirror.com/fs.realpath/-/fs.realpath-1.0.0.tgz", {}, "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="],
|
||||||
|
|
||||||
"framer-motion": ["framer-motion@12.34.3", "", { "dependencies": { "motion-dom": "^12.34.3", "motion-utils": "^12.29.2", "tslib": "^2.4.0" }, "peerDependencies": { "@emotion/is-prop-valid": "*", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/is-prop-valid", "react", "react-dom"] }, "sha512-v81ecyZKYO/DfpTwHivqkxSUBzvceOpoI+wLfgCgoUIKxlFKEXdg0oR9imxwXumT4SFy8vRk9xzJ5l3/Du/55Q=="],
|
"fsevents": ["fsevents@2.3.3", "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.3.tgz", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
|
||||||
|
|
||||||
"fs-constants": ["fs-constants@1.0.0", "", {}, "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow=="],
|
"fstream": ["fstream@1.0.12", "https://registry.npmmirror.com/fstream/-/fstream-1.0.12.tgz", { "dependencies": { "graceful-fs": "^4.1.2", "inherits": "~2.0.0", "mkdirp": ">=0.5 0", "rimraf": "2" } }, "sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg=="],
|
||||||
|
|
||||||
"fs.realpath": ["fs.realpath@1.0.0", "", {}, "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="],
|
"glob": ["glob@7.2.3", "https://registry.npmmirror.com/glob/-/glob-7.2.3.tgz", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="],
|
||||||
|
|
||||||
"fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
|
"graceful-fs": ["graceful-fs@4.2.11", "https://registry.npmmirror.com/graceful-fs/-/graceful-fs-4.2.11.tgz", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="],
|
||||||
|
|
||||||
"fstream": ["fstream@1.0.12", "", { "dependencies": { "graceful-fs": "^4.1.2", "inherits": "~2.0.0", "mkdirp": ">=0.5 0", "rimraf": "2" } }, "sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg=="],
|
"hey-listen": ["hey-listen@1.0.8", "https://registry.npmmirror.com/hey-listen/-/hey-listen-1.0.8.tgz", {}, "sha512-COpmrF2NOg4TBWUJ5UVyaCU2A88wEMkUPK4hNqyCkqHbxT92BbvfjoSozkAIIm6XhicGlJHhFdullInrdhwU8Q=="],
|
||||||
|
|
||||||
"glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="],
|
"hookable": ["hookable@5.5.3", "https://registry.npmmirror.com/hookable/-/hookable-5.5.3.tgz", {}, "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ=="],
|
||||||
|
|
||||||
"graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="],
|
"ieee754": ["ieee754@1.2.1", "https://registry.npmmirror.com/ieee754/-/ieee754-1.2.1.tgz", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="],
|
||||||
|
|
||||||
"hey-listen": ["hey-listen@1.0.8", "", {}, "sha512-COpmrF2NOg4TBWUJ5UVyaCU2A88wEMkUPK4hNqyCkqHbxT92BbvfjoSozkAIIm6XhicGlJHhFdullInrdhwU8Q=="],
|
"immediate": ["immediate@3.0.6", "https://registry.npmmirror.com/immediate/-/immediate-3.0.6.tgz", {}, "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ=="],
|
||||||
|
|
||||||
"hookable": ["hookable@5.5.3", "", {}, "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ=="],
|
"inflight": ["inflight@1.0.6", "https://registry.npmmirror.com/inflight/-/inflight-1.0.6.tgz", { "dependencies": { "once": "^1.3.0", "wrappy": "1" } }, "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA=="],
|
||||||
|
|
||||||
"ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="],
|
"inherits": ["inherits@2.0.4", "https://registry.npmmirror.com/inherits/-/inherits-2.0.4.tgz", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="],
|
||||||
|
|
||||||
"immediate": ["immediate@3.0.6", "", {}, "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ=="],
|
"is-what": ["is-what@5.5.0", "https://registry.npmmirror.com/is-what/-/is-what-5.5.0.tgz", {}, "sha512-oG7cgbmg5kLYae2N5IVd3jm2s+vldjxJzK1pcu9LfpGuQ93MQSzo0okvRna+7y5ifrD+20FE8FvjusyGaz14fw=="],
|
||||||
|
|
||||||
"inflight": ["inflight@1.0.6", "", { "dependencies": { "once": "^1.3.0", "wrappy": "1" } }, "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA=="],
|
"isarray": ["isarray@1.0.0", "https://registry.npmmirror.com/isarray/-/isarray-1.0.0.tgz", {}, "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ=="],
|
||||||
|
|
||||||
"inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="],
|
"jiti": ["jiti@2.6.1", "https://registry.npmmirror.com/jiti/-/jiti-2.6.1.tgz", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="],
|
||||||
|
|
||||||
"is-what": ["is-what@5.5.0", "", {}, "sha512-oG7cgbmg5kLYae2N5IVd3jm2s+vldjxJzK1pcu9LfpGuQ93MQSzo0okvRna+7y5ifrD+20FE8FvjusyGaz14fw=="],
|
"jszip": ["jszip@3.10.1", "https://registry.npmmirror.com/jszip/-/jszip-3.10.1.tgz", { "dependencies": { "lie": "~3.3.0", "pako": "~1.0.2", "readable-stream": "~2.3.6", "setimmediate": "^1.0.5" } }, "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g=="],
|
||||||
|
|
||||||
"isarray": ["isarray@1.0.0", "", {}, "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ=="],
|
"lazystream": ["lazystream@1.0.1", "https://registry.npmmirror.com/lazystream/-/lazystream-1.0.1.tgz", { "dependencies": { "readable-stream": "^2.0.5" } }, "sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw=="],
|
||||||
|
|
||||||
"jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="],
|
"lie": ["lie@3.1.1", "https://registry.npmmirror.com/lie/-/lie-3.1.1.tgz", { "dependencies": { "immediate": "~3.0.5" } }, "sha512-RiNhHysUjhrDQntfYSfY4MU24coXXdEOgw9WGcKHNeEwffDYbF//u87M1EWaMGzuFoSbqW0C9C6lEEhDOAswfw=="],
|
||||||
|
|
||||||
"jszip": ["jszip@3.10.1", "", { "dependencies": { "lie": "~3.3.0", "pako": "~1.0.2", "readable-stream": "~2.3.6", "setimmediate": "^1.0.5" } }, "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g=="],
|
"lightningcss": ["lightningcss@1.31.1", "https://registry.npmmirror.com/lightningcss/-/lightningcss-1.31.1.tgz", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.31.1", "lightningcss-darwin-arm64": "1.31.1", "lightningcss-darwin-x64": "1.31.1", "lightningcss-freebsd-x64": "1.31.1", "lightningcss-linux-arm-gnueabihf": "1.31.1", "lightningcss-linux-arm64-gnu": "1.31.1", "lightningcss-linux-arm64-musl": "1.31.1", "lightningcss-linux-x64-gnu": "1.31.1", "lightningcss-linux-x64-musl": "1.31.1", "lightningcss-win32-arm64-msvc": "1.31.1", "lightningcss-win32-x64-msvc": "1.31.1" } }, "sha512-l51N2r93WmGUye3WuFoN5k10zyvrVs0qfKBhyC5ogUQ6Ew6JUSswh78mbSO+IU3nTWsyOArqPCcShdQSadghBQ=="],
|
||||||
|
|
||||||
"lazystream": ["lazystream@1.0.1", "", { "dependencies": { "readable-stream": "^2.0.5" } }, "sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw=="],
|
"lightningcss-android-arm64": ["lightningcss-android-arm64@1.31.1", "https://registry.npmmirror.com/lightningcss-android-arm64/-/lightningcss-android-arm64-1.31.1.tgz", { "os": "android", "cpu": "arm64" }, "sha512-HXJF3x8w9nQ4jbXRiNppBCqeZPIAfUo8zE/kOEGbW5NZvGc/K7nMxbhIr+YlFlHW5mpbg/YFPdbnCh1wAXCKFg=="],
|
||||||
|
|
||||||
"lie": ["lie@3.1.1", "", { "dependencies": { "immediate": "~3.0.5" } }, "sha512-RiNhHysUjhrDQntfYSfY4MU24coXXdEOgw9WGcKHNeEwffDYbF//u87M1EWaMGzuFoSbqW0C9C6lEEhDOAswfw=="],
|
"lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.31.1", "https://registry.npmmirror.com/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.31.1.tgz", { "os": "darwin", "cpu": "arm64" }, "sha512-02uTEqf3vIfNMq3h/z2cJfcOXnQ0GRwQrkmPafhueLb2h7mqEidiCzkE4gBMEH65abHRiQvhdcQ+aP0D0g67sg=="],
|
||||||
|
|
||||||
"lightningcss": ["lightningcss@1.31.1", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.31.1", "lightningcss-darwin-arm64": "1.31.1", "lightningcss-darwin-x64": "1.31.1", "lightningcss-freebsd-x64": "1.31.1", "lightningcss-linux-arm-gnueabihf": "1.31.1", "lightningcss-linux-arm64-gnu": "1.31.1", "lightningcss-linux-arm64-musl": "1.31.1", "lightningcss-linux-x64-gnu": "1.31.1", "lightningcss-linux-x64-musl": "1.31.1", "lightningcss-win32-arm64-msvc": "1.31.1", "lightningcss-win32-x64-msvc": "1.31.1" } }, "sha512-l51N2r93WmGUye3WuFoN5k10zyvrVs0qfKBhyC5ogUQ6Ew6JUSswh78mbSO+IU3nTWsyOArqPCcShdQSadghBQ=="],
|
"lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.31.1", "https://registry.npmmirror.com/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.31.1.tgz", { "os": "darwin", "cpu": "x64" }, "sha512-1ObhyoCY+tGxtsz1lSx5NXCj3nirk0Y0kB/g8B8DT+sSx4G9djitg9ejFnjb3gJNWo7qXH4DIy2SUHvpoFwfTA=="],
|
||||||
|
|
||||||
"lightningcss-android-arm64": ["lightningcss-android-arm64@1.31.1", "", { "os": "android", "cpu": "arm64" }, "sha512-HXJF3x8w9nQ4jbXRiNppBCqeZPIAfUo8zE/kOEGbW5NZvGc/K7nMxbhIr+YlFlHW5mpbg/YFPdbnCh1wAXCKFg=="],
|
"lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.31.1", "https://registry.npmmirror.com/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.31.1.tgz", { "os": "freebsd", "cpu": "x64" }, "sha512-1RINmQKAItO6ISxYgPwszQE1BrsVU5aB45ho6O42mu96UiZBxEXsuQ7cJW4zs4CEodPUioj/QrXW1r9pLUM74A=="],
|
||||||
|
|
||||||
"lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.31.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-02uTEqf3vIfNMq3h/z2cJfcOXnQ0GRwQrkmPafhueLb2h7mqEidiCzkE4gBMEH65abHRiQvhdcQ+aP0D0g67sg=="],
|
"lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.31.1", "https://registry.npmmirror.com/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.31.1.tgz", { "os": "linux", "cpu": "arm" }, "sha512-OOCm2//MZJ87CdDK62rZIu+aw9gBv4azMJuA8/KB74wmfS3lnC4yoPHm0uXZ/dvNNHmnZnB8XLAZzObeG0nS1g=="],
|
||||||
|
|
||||||
"lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.31.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-1ObhyoCY+tGxtsz1lSx5NXCj3nirk0Y0kB/g8B8DT+sSx4G9djitg9ejFnjb3gJNWo7qXH4DIy2SUHvpoFwfTA=="],
|
"lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.31.1", "https://registry.npmmirror.com/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.31.1.tgz", { "os": "linux", "cpu": "arm64" }, "sha512-WKyLWztD71rTnou4xAD5kQT+982wvca7E6QoLpoawZ1gP9JM0GJj4Tp5jMUh9B3AitHbRZ2/H3W5xQmdEOUlLg=="],
|
||||||
|
|
||||||
"lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.31.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-1RINmQKAItO6ISxYgPwszQE1BrsVU5aB45ho6O42mu96UiZBxEXsuQ7cJW4zs4CEodPUioj/QrXW1r9pLUM74A=="],
|
"lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.31.1", "https://registry.npmmirror.com/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.31.1.tgz", { "os": "linux", "cpu": "arm64" }, "sha512-mVZ7Pg2zIbe3XlNbZJdjs86YViQFoJSpc41CbVmKBPiGmC4YrfeOyz65ms2qpAobVd7WQsbW4PdsSJEMymyIMg=="],
|
||||||
|
|
||||||
"lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.31.1", "", { "os": "linux", "cpu": "arm" }, "sha512-OOCm2//MZJ87CdDK62rZIu+aw9gBv4azMJuA8/KB74wmfS3lnC4yoPHm0uXZ/dvNNHmnZnB8XLAZzObeG0nS1g=="],
|
"lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.31.1", "https://registry.npmmirror.com/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.31.1.tgz", { "os": "linux", "cpu": "x64" }, "sha512-xGlFWRMl+0KvUhgySdIaReQdB4FNudfUTARn7q0hh/V67PVGCs3ADFjw+6++kG1RNd0zdGRlEKa+T13/tQjPMA=="],
|
||||||
|
|
||||||
"lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.31.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-WKyLWztD71rTnou4xAD5kQT+982wvca7E6QoLpoawZ1gP9JM0GJj4Tp5jMUh9B3AitHbRZ2/H3W5xQmdEOUlLg=="],
|
"lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.31.1", "https://registry.npmmirror.com/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.31.1.tgz", { "os": "linux", "cpu": "x64" }, "sha512-eowF8PrKHw9LpoZii5tdZwnBcYDxRw2rRCyvAXLi34iyeYfqCQNA9rmUM0ce62NlPhCvof1+9ivRaTY6pSKDaA=="],
|
||||||
|
|
||||||
"lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.31.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-mVZ7Pg2zIbe3XlNbZJdjs86YViQFoJSpc41CbVmKBPiGmC4YrfeOyz65ms2qpAobVd7WQsbW4PdsSJEMymyIMg=="],
|
"lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.31.1", "https://registry.npmmirror.com/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.31.1.tgz", { "os": "win32", "cpu": "arm64" }, "sha512-aJReEbSEQzx1uBlQizAOBSjcmr9dCdL3XuC/6HLXAxmtErsj2ICo5yYggg1qOODQMtnjNQv2UHb9NpOuFtYe4w=="],
|
||||||
|
|
||||||
"lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.31.1", "", { "os": "linux", "cpu": "x64" }, "sha512-xGlFWRMl+0KvUhgySdIaReQdB4FNudfUTARn7q0hh/V67PVGCs3ADFjw+6++kG1RNd0zdGRlEKa+T13/tQjPMA=="],
|
"lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.31.1", "https://registry.npmmirror.com/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.31.1.tgz", { "os": "win32", "cpu": "x64" }, "sha512-I9aiFrbd7oYHwlnQDqr1Roz+fTz61oDDJX7n9tYF9FJymH1cIN1DtKw3iYt6b8WZgEjoNwVSncwF4wx/ZedMhw=="],
|
||||||
|
|
||||||
"lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.31.1", "", { "os": "linux", "cpu": "x64" }, "sha512-eowF8PrKHw9LpoZii5tdZwnBcYDxRw2rRCyvAXLi34iyeYfqCQNA9rmUM0ce62NlPhCvof1+9ivRaTY6pSKDaA=="],
|
"listenercount": ["listenercount@1.0.1", "https://registry.npmmirror.com/listenercount/-/listenercount-1.0.1.tgz", {}, "sha512-3mk/Zag0+IJxeDrxSgaDPy4zZ3w05PRZeJNnlWhzFz5OkX49J4krc+A8X2d2M69vGMBEX0uyl8M+W+8gH+kBqQ=="],
|
||||||
|
|
||||||
"lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.31.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-aJReEbSEQzx1uBlQizAOBSjcmr9dCdL3XuC/6HLXAxmtErsj2ICo5yYggg1qOODQMtnjNQv2UHb9NpOuFtYe4w=="],
|
"localforage": ["localforage@1.10.0", "https://registry.npmmirror.com/localforage/-/localforage-1.10.0.tgz", { "dependencies": { "lie": "3.1.1" } }, "sha512-14/H1aX7hzBBmmh7sGPd+AOMkkIrHM3Z1PAyGgZigA1H1p5O5ANnMyWzvpAETtG68/dC4pC0ncy3+PPGzXZHPg=="],
|
||||||
|
|
||||||
"lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.31.1", "", { "os": "win32", "cpu": "x64" }, "sha512-I9aiFrbd7oYHwlnQDqr1Roz+fTz61oDDJX7n9tYF9FJymH1cIN1DtKw3iYt6b8WZgEjoNwVSncwF4wx/ZedMhw=="],
|
"lodash.defaults": ["lodash.defaults@4.2.0", "https://registry.npmmirror.com/lodash.defaults/-/lodash.defaults-4.2.0.tgz", {}, "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ=="],
|
||||||
|
|
||||||
"listenercount": ["listenercount@1.0.1", "", {}, "sha512-3mk/Zag0+IJxeDrxSgaDPy4zZ3w05PRZeJNnlWhzFz5OkX49J4krc+A8X2d2M69vGMBEX0uyl8M+W+8gH+kBqQ=="],
|
"lodash.difference": ["lodash.difference@4.5.0", "https://registry.npmmirror.com/lodash.difference/-/lodash.difference-4.5.0.tgz", {}, "sha512-dS2j+W26TQ7taQBGN8Lbbq04ssV3emRw4NY58WErlTO29pIqS0HmoT5aJ9+TUQ1N3G+JOZSji4eugsWwGp9yPA=="],
|
||||||
|
|
||||||
"localforage": ["localforage@1.10.0", "", { "dependencies": { "lie": "3.1.1" } }, "sha512-14/H1aX7hzBBmmh7sGPd+AOMkkIrHM3Z1PAyGgZigA1H1p5O5ANnMyWzvpAETtG68/dC4pC0ncy3+PPGzXZHPg=="],
|
"lodash.escaperegexp": ["lodash.escaperegexp@4.1.2", "https://registry.npmmirror.com/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz", {}, "sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw=="],
|
||||||
|
|
||||||
"lodash.defaults": ["lodash.defaults@4.2.0", "", {}, "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ=="],
|
"lodash.flatten": ["lodash.flatten@4.4.0", "https://registry.npmmirror.com/lodash.flatten/-/lodash.flatten-4.4.0.tgz", {}, "sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g=="],
|
||||||
|
|
||||||
"lodash.difference": ["lodash.difference@4.5.0", "", {}, "sha512-dS2j+W26TQ7taQBGN8Lbbq04ssV3emRw4NY58WErlTO29pIqS0HmoT5aJ9+TUQ1N3G+JOZSji4eugsWwGp9yPA=="],
|
"lodash.groupby": ["lodash.groupby@4.6.0", "https://registry.npmmirror.com/lodash.groupby/-/lodash.groupby-4.6.0.tgz", {}, "sha512-5dcWxm23+VAoz+awKmBaiBvzox8+RqMgFhi7UvX9DHZr2HdxHXM/Wrf8cfKpsW37RNrvtPn6hSwNqurSILbmJw=="],
|
||||||
|
|
||||||
"lodash.escaperegexp": ["lodash.escaperegexp@4.1.2", "", {}, "sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw=="],
|
"lodash.isboolean": ["lodash.isboolean@3.0.3", "https://registry.npmmirror.com/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", {}, "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg=="],
|
||||||
|
|
||||||
"lodash.flatten": ["lodash.flatten@4.4.0", "", {}, "sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g=="],
|
"lodash.isequal": ["lodash.isequal@4.5.0", "https://registry.npmmirror.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz", {}, "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ=="],
|
||||||
|
|
||||||
"lodash.groupby": ["lodash.groupby@4.6.0", "", {}, "sha512-5dcWxm23+VAoz+awKmBaiBvzox8+RqMgFhi7UvX9DHZr2HdxHXM/Wrf8cfKpsW37RNrvtPn6hSwNqurSILbmJw=="],
|
"lodash.isfunction": ["lodash.isfunction@3.0.9", "https://registry.npmmirror.com/lodash.isfunction/-/lodash.isfunction-3.0.9.tgz", {}, "sha512-AirXNj15uRIMMPihnkInB4i3NHeb4iBtNg9WRWuK2o31S+ePwwNmDPaTL3o7dTJ+VXNZim7rFs4rxN4YU1oUJw=="],
|
||||||
|
|
||||||
"lodash.isboolean": ["lodash.isboolean@3.0.3", "", {}, "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg=="],
|
"lodash.isnil": ["lodash.isnil@4.0.0", "https://registry.npmmirror.com/lodash.isnil/-/lodash.isnil-4.0.0.tgz", {}, "sha512-up2Mzq3545mwVnMhTDMdfoG1OurpA/s5t88JmQX809eH3C8491iu2sfKhTfhQtKY78oPNhiaHJUpT/dUDAAtng=="],
|
||||||
|
|
||||||
"lodash.isequal": ["lodash.isequal@4.5.0", "", {}, "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ=="],
|
"lodash.isplainobject": ["lodash.isplainobject@4.0.6", "https://registry.npmmirror.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", {}, "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA=="],
|
||||||
|
|
||||||
"lodash.isfunction": ["lodash.isfunction@3.0.9", "", {}, "sha512-AirXNj15uRIMMPihnkInB4i3NHeb4iBtNg9WRWuK2o31S+ePwwNmDPaTL3o7dTJ+VXNZim7rFs4rxN4YU1oUJw=="],
|
"lodash.isundefined": ["lodash.isundefined@3.0.1", "https://registry.npmmirror.com/lodash.isundefined/-/lodash.isundefined-3.0.1.tgz", {}, "sha512-MXB1is3s899/cD8jheYYE2V9qTHwKvt+npCwpD+1Sxm3Q3cECXCiYHjeHWXNwr6Q0SOBPrYUDxendrO6goVTEA=="],
|
||||||
|
|
||||||
"lodash.isnil": ["lodash.isnil@4.0.0", "", {}, "sha512-up2Mzq3545mwVnMhTDMdfoG1OurpA/s5t88JmQX809eH3C8491iu2sfKhTfhQtKY78oPNhiaHJUpT/dUDAAtng=="],
|
"lodash.union": ["lodash.union@4.6.0", "https://registry.npmmirror.com/lodash.union/-/lodash.union-4.6.0.tgz", {}, "sha512-c4pB2CdGrGdjMKYLA+XiRDO7Y0PRQbm/Gzg8qMj+QH+pFVAoTp5sBpO0odL3FjoPCGjK96p6qsP+yQoiLoOBcw=="],
|
||||||
|
|
||||||
"lodash.isplainobject": ["lodash.isplainobject@4.0.6", "", {}, "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA=="],
|
"lodash.uniq": ["lodash.uniq@4.5.0", "https://registry.npmmirror.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz", {}, "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ=="],
|
||||||
|
|
||||||
"lodash.isundefined": ["lodash.isundefined@3.0.1", "", {}, "sha512-MXB1is3s899/cD8jheYYE2V9qTHwKvt+npCwpD+1Sxm3Q3cECXCiYHjeHWXNwr6Q0SOBPrYUDxendrO6goVTEA=="],
|
"lucide-vue-next": ["lucide-vue-next@0.563.0", "https://registry.npmmirror.com/lucide-vue-next/-/lucide-vue-next-0.563.0.tgz", { "peerDependencies": { "vue": ">=3.0.1" } }, "sha512-zsE/lCKtmaa7bGfhSpN84br1K9YoQ5pCN+2oKWjQQG3Lo6ufUUKBuHSjNFI6RvUevxaajNXb8XwFUKeTXG3sIA=="],
|
||||||
|
|
||||||
"lodash.union": ["lodash.union@4.6.0", "", {}, "sha512-c4pB2CdGrGdjMKYLA+XiRDO7Y0PRQbm/Gzg8qMj+QH+pFVAoTp5sBpO0odL3FjoPCGjK96p6qsP+yQoiLoOBcw=="],
|
"magic-string": ["magic-string@0.30.21", "https://registry.npmmirror.com/magic-string/-/magic-string-0.30.21.tgz", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="],
|
||||||
|
|
||||||
"lodash.uniq": ["lodash.uniq@4.5.0", "", {}, "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ=="],
|
"minimatch": ["minimatch@5.1.9", "https://registry.npmmirror.com/minimatch/-/minimatch-5.1.9.tgz", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw=="],
|
||||||
|
|
||||||
"lucide-vue-next": ["lucide-vue-next@0.563.0", "", { "peerDependencies": { "vue": ">=3.0.1" } }, "sha512-zsE/lCKtmaa7bGfhSpN84br1K9YoQ5pCN+2oKWjQQG3Lo6ufUUKBuHSjNFI6RvUevxaajNXb8XwFUKeTXG3sIA=="],
|
"mitt": ["mitt@3.0.1", "https://registry.npmmirror.com/mitt/-/mitt-3.0.1.tgz", {}, "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw=="],
|
||||||
|
|
||||||
"magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="],
|
"mkdirp": ["mkdirp@3.0.1", "https://registry.npmmirror.com/mkdirp/-/mkdirp-3.0.1.tgz", { "bin": { "mkdirp": "dist/cjs/src/bin.js" } }, "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg=="],
|
||||||
|
|
||||||
"minimatch": ["minimatch@5.1.9", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw=="],
|
"motion-dom": ["motion-dom@12.34.3", "https://registry.npmmirror.com/motion-dom/-/motion-dom-12.34.3.tgz", { "dependencies": { "motion-utils": "^12.29.2" } }, "sha512-sYgFe+pR9aIM7o4fhs2aXtOI+oqlUd33N9Yoxcgo1Fv7M20sRkHtCmzE/VRNIcq7uNJ+qio+Xubt1FXH3pQ+eQ=="],
|
||||||
|
|
||||||
"mitt": ["mitt@3.0.1", "", {}, "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw=="],
|
"motion-utils": ["motion-utils@12.29.2", "https://registry.npmmirror.com/motion-utils/-/motion-utils-12.29.2.tgz", {}, "sha512-G3kc34H2cX2gI63RqU+cZq+zWRRPSsNIOjpdl9TN4AQwC4sgwYPl/Q/Obf/d53nOm569T0fYK+tcoSV50BWx8A=="],
|
||||||
|
|
||||||
"mkdirp": ["mkdirp@3.0.1", "", { "bin": { "mkdirp": "dist/cjs/src/bin.js" } }, "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg=="],
|
"motion-v": ["motion-v@2.0.0", "https://registry.npmmirror.com/motion-v/-/motion-v-2.0.0.tgz", { "dependencies": { "framer-motion": "^12.29.2", "hey-listen": "^1.0.8", "motion-dom": "^12.29.2", "motion-utils": "^12.29.2" }, "peerDependencies": { "@vueuse/core": ">=10.0.0", "vue": ">=3.0.0" } }, "sha512-oQuQMrPhti+Zps6OosOaW3b/eqzaGAuwI54XHJKq/dIWtQWcNzfyhTo4VB5xmp7yLN+3BE9FKF6skLsynfgbHQ=="],
|
||||||
|
|
||||||
"motion-dom": ["motion-dom@12.34.3", "", { "dependencies": { "motion-utils": "^12.29.2" } }, "sha512-sYgFe+pR9aIM7o4fhs2aXtOI+oqlUd33N9Yoxcgo1Fv7M20sRkHtCmzE/VRNIcq7uNJ+qio+Xubt1FXH3pQ+eQ=="],
|
"muggle-string": ["muggle-string@0.4.1", "https://registry.npmmirror.com/muggle-string/-/muggle-string-0.4.1.tgz", {}, "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ=="],
|
||||||
|
|
||||||
"motion-utils": ["motion-utils@12.29.2", "", {}, "sha512-G3kc34H2cX2gI63RqU+cZq+zWRRPSsNIOjpdl9TN4AQwC4sgwYPl/Q/Obf/d53nOm569T0fYK+tcoSV50BWx8A=="],
|
"nanoid": ["nanoid@3.3.11", "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.11.tgz", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="],
|
||||||
|
|
||||||
"motion-v": ["motion-v@2.0.0", "", { "dependencies": { "framer-motion": "^12.29.2", "hey-listen": "^1.0.8", "motion-dom": "^12.29.2", "motion-utils": "^12.29.2" }, "peerDependencies": { "@vueuse/core": ">=10.0.0", "vue": ">=3.0.0" } }, "sha512-oQuQMrPhti+Zps6OosOaW3b/eqzaGAuwI54XHJKq/dIWtQWcNzfyhTo4VB5xmp7yLN+3BE9FKF6skLsynfgbHQ=="],
|
"normalize-path": ["normalize-path@3.0.0", "https://registry.npmmirror.com/normalize-path/-/normalize-path-3.0.0.tgz", {}, "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA=="],
|
||||||
|
|
||||||
"muggle-string": ["muggle-string@0.4.1", "", {}, "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ=="],
|
"ohash": ["ohash@2.0.11", "https://registry.npmmirror.com/ohash/-/ohash-2.0.11.tgz", {}, "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ=="],
|
||||||
|
|
||||||
"nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="],
|
"once": ["once@1.4.0", "https://registry.npmmirror.com/once/-/once-1.4.0.tgz", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="],
|
||||||
|
|
||||||
"normalize-path": ["normalize-path@3.0.0", "", {}, "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA=="],
|
"pako": ["pako@1.0.11", "https://registry.npmmirror.com/pako/-/pako-1.0.11.tgz", {}, "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw=="],
|
||||||
|
|
||||||
"ohash": ["ohash@2.0.11", "", {}, "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ=="],
|
"path-browserify": ["path-browserify@1.0.1", "https://registry.npmmirror.com/path-browserify/-/path-browserify-1.0.1.tgz", {}, "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g=="],
|
||||||
|
|
||||||
"once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="],
|
"path-is-absolute": ["path-is-absolute@1.0.1", "https://registry.npmmirror.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz", {}, "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg=="],
|
||||||
|
|
||||||
"pako": ["pako@2.2.0", "", {}, "sha512-zJq6RP/5q+TO2OpFV3FHzlPnFjmkb7Nc99a5SNjJE+uu/PkpChs+NIZSSzbBoD+6kjiISXjfYdwj1ZRQ81dz/w=="],
|
"perfect-debounce": ["perfect-debounce@1.0.0", "https://registry.npmmirror.com/perfect-debounce/-/perfect-debounce-1.0.0.tgz", {}, "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA=="],
|
||||||
|
|
||||||
"path-browserify": ["path-browserify@1.0.1", "", {}, "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g=="],
|
"picocolors": ["picocolors@1.1.1", "https://registry.npmmirror.com/picocolors/-/picocolors-1.1.1.tgz", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
|
||||||
|
|
||||||
"path-is-absolute": ["path-is-absolute@1.0.1", "", {}, "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg=="],
|
"picomatch": ["picomatch@4.0.3", "https://registry.npmmirror.com/picomatch/-/picomatch-4.0.3.tgz", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="],
|
||||||
|
|
||||||
"perfect-debounce": ["perfect-debounce@1.0.0", "", {}, "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA=="],
|
"pinia": ["pinia@3.0.4", "https://registry.npmmirror.com/pinia/-/pinia-3.0.4.tgz", { "dependencies": { "@vue/devtools-api": "^7.7.7" }, "peerDependencies": { "typescript": ">=4.5.0", "vue": "^3.5.11" }, "optionalPeers": ["typescript"] }, "sha512-l7pqLUFTI/+ESXn6k3nu30ZIzW5E2WZF/LaHJEpoq6ElcLD+wduZoB2kBN19du6K/4FDpPMazY2wJr+IndBtQw=="],
|
||||||
|
|
||||||
"picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
|
"pinia-plugin-persistedstate": ["pinia-plugin-persistedstate@4.7.1", "https://registry.npmmirror.com/pinia-plugin-persistedstate/-/pinia-plugin-persistedstate-4.7.1.tgz", { "dependencies": { "defu": "^6.1.4" }, "peerDependencies": { "@nuxt/kit": ">=3.0.0", "@pinia/nuxt": ">=0.10.0", "pinia": ">=3.0.0" }, "optionalPeers": ["@nuxt/kit", "@pinia/nuxt", "pinia"] }, "sha512-WHOqh2esDlR3eAaknPbqXrkkj0D24h8shrDPqysgCFR6ghqP/fpFfJmMPJp0gETHsvrh9YNNg6dQfo2OEtDnIQ=="],
|
||||||
|
|
||||||
"picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="],
|
"postcss": ["postcss@8.5.6", "https://registry.npmmirror.com/postcss/-/postcss-8.5.6.tgz", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="],
|
||||||
|
|
||||||
"pinia": ["pinia@3.0.4", "", { "dependencies": { "@vue/devtools-api": "^7.7.7" }, "peerDependencies": { "typescript": ">=4.5.0", "vue": "^3.5.11" }, "optionalPeers": ["typescript"] }, "sha512-l7pqLUFTI/+ESXn6k3nu30ZIzW5E2WZF/LaHJEpoq6ElcLD+wduZoB2kBN19du6K/4FDpPMazY2wJr+IndBtQw=="],
|
"process-nextick-args": ["process-nextick-args@2.0.1", "https://registry.npmmirror.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz", {}, "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="],
|
||||||
|
|
||||||
"pinia-plugin-persistedstate": ["pinia-plugin-persistedstate@4.7.1", "", { "dependencies": { "defu": "^6.1.4" }, "peerDependencies": { "@nuxt/kit": ">=3.0.0", "@pinia/nuxt": ">=0.10.0", "pinia": ">=3.0.0" }, "optionalPeers": ["@nuxt/kit", "@pinia/nuxt", "pinia"] }, "sha512-WHOqh2esDlR3eAaknPbqXrkkj0D24h8shrDPqysgCFR6ghqP/fpFfJmMPJp0gETHsvrh9YNNg6dQfo2OEtDnIQ=="],
|
"readable-stream": ["readable-stream@3.6.2", "https://registry.npmmirror.com/readable-stream/-/readable-stream-3.6.2.tgz", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="],
|
||||||
|
|
||||||
"pizzip": ["pizzip@3.2.0", "", { "dependencies": { "pako": "^2.1.0" } }, "sha512-X4NPNICxCfIK8VYhF6wbksn81vTiziyLbvKuORVAmolvnUzl1A1xmz9DAWKxPRq9lZg84pJOOAMq3OE61bD8IQ=="],
|
"readdir-glob": ["readdir-glob@1.1.3", "https://registry.npmmirror.com/readdir-glob/-/readdir-glob-1.1.3.tgz", { "dependencies": { "minimatch": "^5.1.0" } }, "sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA=="],
|
||||||
|
|
||||||
"postcss": ["postcss@8.5.6", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="],
|
"reka-ui": ["reka-ui@2.8.2", "https://registry.npmmirror.com/reka-ui/-/reka-ui-2.8.2.tgz", { "dependencies": { "@floating-ui/dom": "^1.6.13", "@floating-ui/vue": "^1.1.6", "@internationalized/date": "^3.5.0", "@internationalized/number": "^3.5.0", "@tanstack/vue-virtual": "^3.12.0", "@vueuse/core": "^14.1.0", "@vueuse/shared": "^14.1.0", "aria-hidden": "^1.2.4", "defu": "^6.1.4", "ohash": "^2.0.11" }, "peerDependencies": { "vue": ">= 3.2.0" } }, "sha512-8lTKcJhmG+D3UyJxhBnNnW/720sLzm0pbA9AC1MWazmJ5YchJAyTSl+O00xP/kxBmEN0fw5JqWVHguiFmsGjzA=="],
|
||||||
|
|
||||||
"process-nextick-args": ["process-nextick-args@2.0.1", "", {}, "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="],
|
"rfdc": ["rfdc@1.4.1", "https://registry.npmmirror.com/rfdc/-/rfdc-1.4.1.tgz", {}, "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA=="],
|
||||||
|
|
||||||
"readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="],
|
"rimraf": ["rimraf@2.7.1", "https://registry.npmmirror.com/rimraf/-/rimraf-2.7.1.tgz", { "dependencies": { "glob": "^7.1.3" }, "bin": { "rimraf": "./bin.js" } }, "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w=="],
|
||||||
|
|
||||||
"readdir-glob": ["readdir-glob@1.1.3", "", { "dependencies": { "minimatch": "^5.1.0" } }, "sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA=="],
|
"rolldown": ["rolldown@1.0.0-rc.5", "https://registry.npmmirror.com/rolldown/-/rolldown-1.0.0-rc.5.tgz", { "dependencies": { "@oxc-project/types": "=0.114.0", "@rolldown/pluginutils": "1.0.0-rc.5" }, "optionalDependencies": { "@rolldown/binding-android-arm64": "1.0.0-rc.5", "@rolldown/binding-darwin-arm64": "1.0.0-rc.5", "@rolldown/binding-darwin-x64": "1.0.0-rc.5", "@rolldown/binding-freebsd-x64": "1.0.0-rc.5", "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.5", "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.5", "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.5", "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.5", "@rolldown/binding-linux-x64-musl": "1.0.0-rc.5", "@rolldown/binding-openharmony-arm64": "1.0.0-rc.5", "@rolldown/binding-wasm32-wasi": "1.0.0-rc.5", "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.5", "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.5" }, "bin": { "rolldown": "bin/cli.mjs" } }, "sha512-0AdalTs6hNTioaCYIkAa7+xsmHBfU5hCNclZnM/lp7lGGDuUOb6N4BVNtwiomybbencDjq/waKjTImqiGCs5sw=="],
|
||||||
|
|
||||||
"reka-ui": ["reka-ui@2.8.2", "", { "dependencies": { "@floating-ui/dom": "^1.6.13", "@floating-ui/vue": "^1.1.6", "@internationalized/date": "^3.5.0", "@internationalized/number": "^3.5.0", "@tanstack/vue-virtual": "^3.12.0", "@vueuse/core": "^14.1.0", "@vueuse/shared": "^14.1.0", "aria-hidden": "^1.2.4", "defu": "^6.1.4", "ohash": "^2.0.11" }, "peerDependencies": { "vue": ">= 3.2.0" } }, "sha512-8lTKcJhmG+D3UyJxhBnNnW/720sLzm0pbA9AC1MWazmJ5YchJAyTSl+O00xP/kxBmEN0fw5JqWVHguiFmsGjzA=="],
|
"safe-buffer": ["safe-buffer@5.1.2", "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.1.2.tgz", {}, "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="],
|
||||||
|
|
||||||
"rfdc": ["rfdc@1.4.1", "", {}, "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA=="],
|
"saxes": ["saxes@5.0.1", "https://registry.npmmirror.com/saxes/-/saxes-5.0.1.tgz", { "dependencies": { "xmlchars": "^2.2.0" } }, "sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw=="],
|
||||||
|
|
||||||
"rimraf": ["rimraf@2.7.1", "", { "dependencies": { "glob": "^7.1.3" }, "bin": { "rimraf": "./bin.js" } }, "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w=="],
|
"setimmediate": ["setimmediate@1.0.5", "https://registry.npmmirror.com/setimmediate/-/setimmediate-1.0.5.tgz", {}, "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA=="],
|
||||||
|
|
||||||
"rolldown": ["rolldown@1.0.0-rc.5", "", { "dependencies": { "@oxc-project/types": "=0.114.0", "@rolldown/pluginutils": "1.0.0-rc.5" }, "optionalDependencies": { "@rolldown/binding-android-arm64": "1.0.0-rc.5", "@rolldown/binding-darwin-arm64": "1.0.0-rc.5", "@rolldown/binding-darwin-x64": "1.0.0-rc.5", "@rolldown/binding-freebsd-x64": "1.0.0-rc.5", "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.5", "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.5", "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.5", "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.5", "@rolldown/binding-linux-x64-musl": "1.0.0-rc.5", "@rolldown/binding-openharmony-arm64": "1.0.0-rc.5", "@rolldown/binding-wasm32-wasi": "1.0.0-rc.5", "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.5", "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.5" }, "bin": { "rolldown": "bin/cli.mjs" } }, "sha512-0AdalTs6hNTioaCYIkAa7+xsmHBfU5hCNclZnM/lp7lGGDuUOb6N4BVNtwiomybbencDjq/waKjTImqiGCs5sw=="],
|
"sortablejs": ["sortablejs@1.14.0", "https://registry.npmmirror.com/sortablejs/-/sortablejs-1.14.0.tgz", {}, "sha512-pBXvQCs5/33fdN1/39pPL0NZF20LeRbLQ5jtnheIPN9JQAaufGjKdWduZn4U7wCtVuzKhmRkI0DFYHYRbB2H1w=="],
|
||||||
|
|
||||||
"safe-buffer": ["safe-buffer@5.1.2", "", {}, "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="],
|
"source-map-js": ["source-map-js@1.2.1", "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.2.1.tgz", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
|
||||||
|
|
||||||
"saxes": ["saxes@5.0.1", "", { "dependencies": { "xmlchars": "^2.2.0" } }, "sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw=="],
|
"speakingurl": ["speakingurl@14.0.1", "https://registry.npmmirror.com/speakingurl/-/speakingurl-14.0.1.tgz", {}, "sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ=="],
|
||||||
|
|
||||||
"setimmediate": ["setimmediate@1.0.5", "", {}, "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA=="],
|
"string_decoder": ["string_decoder@1.3.0", "https://registry.npmmirror.com/string_decoder/-/string_decoder-1.3.0.tgz", { "dependencies": { "safe-buffer": "~5.2.0" } }, "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA=="],
|
||||||
|
|
||||||
"sortablejs": ["sortablejs@1.14.0", "", {}, "sha512-pBXvQCs5/33fdN1/39pPL0NZF20LeRbLQ5jtnheIPN9JQAaufGjKdWduZn4U7wCtVuzKhmRkI0DFYHYRbB2H1w=="],
|
"superjson": ["superjson@2.2.6", "https://registry.npmmirror.com/superjson/-/superjson-2.2.6.tgz", { "dependencies": { "copy-anything": "^4" } }, "sha512-H+ue8Zo4vJmV2nRjpx86P35lzwDT3nItnIsocgumgr0hHMQ+ZGq5vrERg9kJBo5AWGmxZDhzDo+WVIJqkB0cGA=="],
|
||||||
|
|
||||||
"source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
|
"tailwind-merge": ["tailwind-merge@3.5.0", "https://registry.npmmirror.com/tailwind-merge/-/tailwind-merge-3.5.0.tgz", {}, "sha512-I8K9wewnVDkL1NTGoqWmVEIlUcB9gFriAEkXkfCjX5ib8ezGxtR3xD7iZIxrfArjEsH7F1CHD4RFUtxefdqV/A=="],
|
||||||
|
|
||||||
"speakingurl": ["speakingurl@14.0.1", "", {}, "sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ=="],
|
"tailwindcss": ["tailwindcss@4.2.0", "https://registry.npmmirror.com/tailwindcss/-/tailwindcss-4.2.0.tgz", {}, "sha512-yYzTZ4++b7fNYxFfpnberEEKu43w44aqDMNM9MHMmcKuCH7lL8jJ4yJ7LGHv7rSwiqM0nkiobF9I6cLlpS2P7Q=="],
|
||||||
|
|
||||||
"string_decoder": ["string_decoder@1.3.0", "", { "dependencies": { "safe-buffer": "~5.2.0" } }, "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA=="],
|
"tapable": ["tapable@2.3.0", "https://registry.npmmirror.com/tapable/-/tapable-2.3.0.tgz", {}, "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg=="],
|
||||||
|
|
||||||
"superjson": ["superjson@2.2.6", "", { "dependencies": { "copy-anything": "^4" } }, "sha512-H+ue8Zo4vJmV2nRjpx86P35lzwDT3nItnIsocgumgr0hHMQ+ZGq5vrERg9kJBo5AWGmxZDhzDo+WVIJqkB0cGA=="],
|
"tar-stream": ["tar-stream@2.2.0", "https://registry.npmmirror.com/tar-stream/-/tar-stream-2.2.0.tgz", { "dependencies": { "bl": "^4.0.3", "end-of-stream": "^1.4.1", "fs-constants": "^1.0.0", "inherits": "^2.0.3", "readable-stream": "^3.1.1" } }, "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ=="],
|
||||||
|
|
||||||
"tailwind-merge": ["tailwind-merge@3.5.0", "", {}, "sha512-I8K9wewnVDkL1NTGoqWmVEIlUcB9gFriAEkXkfCjX5ib8ezGxtR3xD7iZIxrfArjEsH7F1CHD4RFUtxefdqV/A=="],
|
"tinyglobby": ["tinyglobby@0.2.15", "https://registry.npmmirror.com/tinyglobby/-/tinyglobby-0.2.15.tgz", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="],
|
||||||
|
|
||||||
"tailwindcss": ["tailwindcss@4.2.0", "", {}, "sha512-yYzTZ4++b7fNYxFfpnberEEKu43w44aqDMNM9MHMmcKuCH7lL8jJ4yJ7LGHv7rSwiqM0nkiobF9I6cLlpS2P7Q=="],
|
"tmp": ["tmp@0.2.5", "https://registry.npmmirror.com/tmp/-/tmp-0.2.5.tgz", {}, "sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow=="],
|
||||||
|
|
||||||
"tapable": ["tapable@2.3.0", "", {}, "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg=="],
|
"traverse": ["traverse@0.3.9", "https://registry.npmmirror.com/traverse/-/traverse-0.3.9.tgz", {}, "sha512-iawgk0hLP3SxGKDfnDJf8wTz4p2qImnyihM5Hh/sGvQ3K37dPi/w8sRhdNIxYA1TwFwc5mDhIJq+O0RsvXBKdQ=="],
|
||||||
|
|
||||||
"tar-stream": ["tar-stream@2.2.0", "", { "dependencies": { "bl": "^4.0.3", "end-of-stream": "^1.4.1", "fs-constants": "^1.0.0", "inherits": "^2.0.3", "readable-stream": "^3.1.1" } }, "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ=="],
|
"tslib": ["tslib@2.8.1", "https://registry.npmmirror.com/tslib/-/tslib-2.8.1.tgz", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
|
||||||
|
|
||||||
"tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="],
|
"tw-animate-css": ["tw-animate-css@1.4.0", "https://registry.npmmirror.com/tw-animate-css/-/tw-animate-css-1.4.0.tgz", {}, "sha512-7bziOlRqH0hJx80h/3mbicLW7o8qLsH5+RaLR2t+OHM3D0JlWGODQKQ4cxbK7WlvmUxpcj6Kgu6EKqjrGFe3QQ=="],
|
||||||
|
|
||||||
"tmp": ["tmp@0.2.5", "", {}, "sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow=="],
|
"typescript": ["typescript@5.9.3", "https://registry.npmmirror.com/typescript/-/typescript-5.9.3.tgz", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
|
||||||
|
|
||||||
"traverse": ["traverse@0.3.9", "", {}, "sha512-iawgk0hLP3SxGKDfnDJf8wTz4p2qImnyihM5Hh/sGvQ3K37dPi/w8sRhdNIxYA1TwFwc5mDhIJq+O0RsvXBKdQ=="],
|
"undici-types": ["undici-types@7.16.0", "https://registry.npmmirror.com/undici-types/-/undici-types-7.16.0.tgz", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
|
||||||
|
|
||||||
"tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
|
"unzipper": ["unzipper@0.10.14", "https://registry.npmmirror.com/unzipper/-/unzipper-0.10.14.tgz", { "dependencies": { "big-integer": "^1.6.17", "binary": "~0.3.0", "bluebird": "~3.4.1", "buffer-indexof-polyfill": "~1.0.0", "duplexer2": "~0.1.4", "fstream": "^1.0.12", "graceful-fs": "^4.2.2", "listenercount": "~1.0.1", "readable-stream": "~2.3.6", "setimmediate": "~1.0.4" } }, "sha512-ti4wZj+0bQTiX2KmKWuwj7lhV+2n//uXEotUmGuQqrbVZSEGFMbI68+c6JCQ8aAmUWYvtHEz2A8K6wXvueR/6g=="],
|
||||||
|
|
||||||
"tw-animate-css": ["tw-animate-css@1.4.0", "", {}, "sha512-7bziOlRqH0hJx80h/3mbicLW7o8qLsH5+RaLR2t+OHM3D0JlWGODQKQ4cxbK7WlvmUxpcj6Kgu6EKqjrGFe3QQ=="],
|
"util-deprecate": ["util-deprecate@1.0.2", "https://registry.npmmirror.com/util-deprecate/-/util-deprecate-1.0.2.tgz", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="],
|
||||||
|
|
||||||
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
|
"uuid": ["uuid@8.3.2", "https://registry.npmmirror.com/uuid/-/uuid-8.3.2.tgz", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="],
|
||||||
|
|
||||||
"undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
|
"vite": ["vite@8.0.0-beta.15", "https://registry.npmmirror.com/vite/-/vite-8.0.0-beta.15.tgz", { "dependencies": { "@oxc-project/runtime": "0.114.0", "lightningcss": "^1.31.1", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rolldown": "1.0.0-rc.5", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "@vitejs/devtools": "^0.0.0-alpha.31", "esbuild": "^0.27.0", "jiti": ">=1.21.0", "less": "^4.0.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "@vitejs/devtools", "esbuild", "jiti", "less", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-RHX7IvsJlEfjyA1rS7MY0UsmF91etdLAamslHR5lfuO3W/BXRdXm2tRE64ztpSPZbKqB4wAAZ0AwtF6QzfKZLA=="],
|
||||||
|
|
||||||
"unzipper": ["unzipper@0.10.14", "", { "dependencies": { "big-integer": "^1.6.17", "binary": "~0.3.0", "bluebird": "~3.4.1", "buffer-indexof-polyfill": "~1.0.0", "duplexer2": "~0.1.4", "fstream": "^1.0.12", "graceful-fs": "^4.2.2", "listenercount": "~1.0.1", "readable-stream": "~2.3.6", "setimmediate": "~1.0.4" } }, "sha512-ti4wZj+0bQTiX2KmKWuwj7lhV+2n//uXEotUmGuQqrbVZSEGFMbI68+c6JCQ8aAmUWYvtHEz2A8K6wXvueR/6g=="],
|
"vscode-uri": ["vscode-uri@3.1.0", "https://registry.npmmirror.com/vscode-uri/-/vscode-uri-3.1.0.tgz", {}, "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ=="],
|
||||||
|
|
||||||
"util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="],
|
"vue": ["vue@3.5.28", "https://registry.npmmirror.com/vue/-/vue-3.5.28.tgz", { "dependencies": { "@vue/compiler-dom": "3.5.28", "@vue/compiler-sfc": "3.5.28", "@vue/runtime-dom": "3.5.28", "@vue/server-renderer": "3.5.28", "@vue/shared": "3.5.28" }, "peerDependencies": { "typescript": "*" }, "optionalPeers": ["typescript"] }, "sha512-BRdrNfeoccSoIZeIhyPBfvWSLFP4q8J3u8Ju8Ug5vu3LdD+yTM13Sg4sKtljxozbnuMu1NB1X5HBHRYUzFocKg=="],
|
||||||
|
|
||||||
"uuid": ["uuid@8.3.2", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="],
|
"vue-demi": ["vue-demi@0.14.10", "https://registry.npmmirror.com/vue-demi/-/vue-demi-0.14.10.tgz", { "peerDependencies": { "@vue/composition-api": "^1.0.0-rc.1", "vue": "^3.0.0-0 || ^2.6.0" }, "optionalPeers": ["@vue/composition-api"], "bin": { "vue-demi-fix": "bin/vue-demi-fix.js", "vue-demi-switch": "bin/vue-demi-switch.js" } }, "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg=="],
|
||||||
|
|
||||||
"vite": ["vite@8.0.0-beta.15", "", { "dependencies": { "@oxc-project/runtime": "0.114.0", "lightningcss": "^1.31.1", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rolldown": "1.0.0-rc.5", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "@vitejs/devtools": "^0.0.0-alpha.31", "esbuild": "^0.27.0", "jiti": ">=1.21.0", "less": "^4.0.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "@vitejs/devtools", "esbuild", "jiti", "less", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-RHX7IvsJlEfjyA1rS7MY0UsmF91etdLAamslHR5lfuO3W/BXRdXm2tRE64ztpSPZbKqB4wAAZ0AwtF6QzfKZLA=="],
|
"vue-i18n": ["vue-i18n@11.3.1", "https://registry.npmmirror.com/vue-i18n/-/vue-i18n-11.3.1.tgz", { "dependencies": { "@intlify/core-base": "11.3.1", "@intlify/devtools-types": "11.3.1", "@intlify/shared": "11.3.1", "@vue/devtools-api": "^6.5.0" }, "peerDependencies": { "vue": "^3.0.0" } }, "sha512-azq8fhVnCwJAw0iXW7i44h9P+Bj+snNuevBAaJ9bxn0I3YVsRU3deVFPNnTfZ2uxVJefGp83JUmL68ddCPw5Pw=="],
|
||||||
|
|
||||||
"vscode-uri": ["vscode-uri@3.1.0", "", {}, "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ=="],
|
"vue-router": ["vue-router@4.6.4", "https://registry.npmmirror.com/vue-router/-/vue-router-4.6.4.tgz", { "dependencies": { "@vue/devtools-api": "^6.6.4" }, "peerDependencies": { "vue": "^3.5.0" } }, "sha512-Hz9q5sa33Yhduglwz6g9skT8OBPii+4bFn88w6J+J4MfEo4KRRpmiNG/hHHkdbRFlLBOqxN8y8gf2Fb0MTUgVg=="],
|
||||||
|
|
||||||
"vue": ["vue@3.5.28", "", { "dependencies": { "@vue/compiler-dom": "3.5.28", "@vue/compiler-sfc": "3.5.28", "@vue/runtime-dom": "3.5.28", "@vue/server-renderer": "3.5.28", "@vue/shared": "3.5.28" }, "peerDependencies": { "typescript": "*" }, "optionalPeers": ["typescript"] }, "sha512-BRdrNfeoccSoIZeIhyPBfvWSLFP4q8J3u8Ju8Ug5vu3LdD+yTM13Sg4sKtljxozbnuMu1NB1X5HBHRYUzFocKg=="],
|
"vue-tsc": ["vue-tsc@3.2.5", "https://registry.npmmirror.com/vue-tsc/-/vue-tsc-3.2.5.tgz", { "dependencies": { "@volar/typescript": "2.4.28", "@vue/language-core": "3.2.5" }, "peerDependencies": { "typescript": ">=5.0.0" }, "bin": { "vue-tsc": "bin/vue-tsc.js" } }, "sha512-/htfTCMluQ+P2FISGAooul8kO4JMheOTCbCy4M6dYnYYjqLe3BExZudAua6MSIKSFYQtFOYAll7XobYwcpokGA=="],
|
||||||
|
|
||||||
"vue-demi": ["vue-demi@0.14.10", "", { "peerDependencies": { "@vue/composition-api": "^1.0.0-rc.1", "vue": "^3.0.0-0 || ^2.6.0" }, "optionalPeers": ["@vue/composition-api"], "bin": { "vue-demi-fix": "bin/vue-demi-fix.js", "vue-demi-switch": "bin/vue-demi-switch.js" } }, "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg=="],
|
"vuedraggable": ["vuedraggable@4.1.0", "https://registry.npmmirror.com/vuedraggable/-/vuedraggable-4.1.0.tgz", { "dependencies": { "sortablejs": "1.14.0" }, "peerDependencies": { "vue": "^3.0.1" } }, "sha512-FU5HCWBmsf20GpP3eudURW3WdWTKIbEIQxh9/8GE806hydR9qZqRRxRE3RjqX7PkuLuMQG/A7n3cfj9rCEchww=="],
|
||||||
|
|
||||||
"vue-i18n": ["vue-i18n@11.4.6", "", { "dependencies": { "@intlify/core-base": "11.4.6", "@intlify/devtools-types": "11.4.6", "@intlify/shared": "11.4.6", "@vue/devtools-api": "^6.5.0" }, "peerDependencies": { "vue": "^3.0.0" } }, "sha512-l0gE7Rfy0phCa5ChKYkOq543Wgd39BCK6hkktfr1Ed4D99oRkgPK9ffShASZdeC8OJxGfdWmpYoAaAH6iLEuIg=="],
|
"wrappy": ["wrappy@1.0.2", "https://registry.npmmirror.com/wrappy/-/wrappy-1.0.2.tgz", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="],
|
||||||
|
|
||||||
"vue-tsc": ["vue-tsc@3.2.5", "", { "dependencies": { "@volar/typescript": "2.4.28", "@vue/language-core": "3.2.5" }, "peerDependencies": { "typescript": ">=5.0.0" }, "bin": { "vue-tsc": "bin/vue-tsc.js" } }, "sha512-/htfTCMluQ+P2FISGAooul8kO4JMheOTCbCy4M6dYnYYjqLe3BExZudAua6MSIKSFYQtFOYAll7XobYwcpokGA=="],
|
"xmlchars": ["xmlchars@2.2.0", "https://registry.npmmirror.com/xmlchars/-/xmlchars-2.2.0.tgz", {}, "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw=="],
|
||||||
|
|
||||||
"vuedraggable": ["vuedraggable@4.1.0", "", { "dependencies": { "sortablejs": "1.14.0" }, "peerDependencies": { "vue": "^3.0.1" } }, "sha512-FU5HCWBmsf20GpP3eudURW3WdWTKIbEIQxh9/8GE806hydR9qZqRRxRE3RjqX7PkuLuMQG/A7n3cfj9rCEchww=="],
|
"zip-stream": ["zip-stream@4.1.1", "https://registry.npmmirror.com/zip-stream/-/zip-stream-4.1.1.tgz", { "dependencies": { "archiver-utils": "^3.0.4", "compress-commons": "^4.1.2", "readable-stream": "^3.6.0" } }, "sha512-9qv4rlDiopXg4E69k+vMHjNN63YFMe9sZMrdlvKnCjlCRWeCBswPPMPUfx+ipsAWq1LXHe70RcbaHdJJpS6hyQ=="],
|
||||||
|
|
||||||
"wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="],
|
"@fast-csv/format/@types/node": ["@types/node@14.18.63", "https://registry.npmmirror.com/@types/node/-/node-14.18.63.tgz", {}, "sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ=="],
|
||||||
|
|
||||||
"xmlchars": ["xmlchars@2.2.0", "", {}, "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw=="],
|
"@fast-csv/parse/@types/node": ["@types/node@14.18.63", "https://registry.npmmirror.com/@types/node/-/node-14.18.63.tgz", {}, "sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ=="],
|
||||||
|
|
||||||
"zip-stream": ["zip-stream@4.1.1", "", { "dependencies": { "archiver-utils": "^3.0.4", "compress-commons": "^4.1.2", "readable-stream": "^3.6.0" } }, "sha512-9qv4rlDiopXg4E69k+vMHjNN63YFMe9sZMrdlvKnCjlCRWeCBswPPMPUfx+ipsAWq1LXHe70RcbaHdJJpS6hyQ=="],
|
"@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.8.1", "https://registry.npmmirror.com/@emnapi/core/-/core-1.8.1.tgz", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" }, "bundled": true }, "sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg=="],
|
||||||
|
|
||||||
"@fast-csv/format/@types/node": ["@types/node@14.18.63", "", {}, "sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ=="],
|
"@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.8.1", "https://registry.npmmirror.com/@emnapi/runtime/-/runtime-1.8.1.tgz", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg=="],
|
||||||
|
|
||||||
"@fast-csv/parse/@types/node": ["@types/node@14.18.63", "", {}, "sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ=="],
|
"@tailwindcss/oxide-wasm32-wasi/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.1.0", "https://registry.npmmirror.com/@emnapi/wasi-threads/-/wasi-threads-1.1.0.tgz", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ=="],
|
||||||
|
|
||||||
"@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.8.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" }, "bundled": true }, "sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg=="],
|
"@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.1", "https://registry.npmmirror.com/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.1.tgz", { "dependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1", "@tybys/wasm-util": "^0.10.1" }, "bundled": true }, "sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A=="],
|
||||||
|
|
||||||
"@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.8.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg=="],
|
"@tailwindcss/oxide-wasm32-wasi/@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "https://registry.npmmirror.com/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="],
|
||||||
|
|
||||||
"@tailwindcss/oxide-wasm32-wasi/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.1.0", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ=="],
|
"@tailwindcss/oxide-wasm32-wasi/tslib": ["tslib@2.8.1", "https://registry.npmmirror.com/tslib/-/tslib-2.8.1.tgz", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
|
||||||
|
|
||||||
"@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.1", "", { "dependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1", "@tybys/wasm-util": "^0.10.1" }, "bundled": true }, "sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A=="],
|
"archiver-utils/readable-stream": ["readable-stream@2.3.8", "https://registry.npmmirror.com/readable-stream/-/readable-stream-2.3.8.tgz", { "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } }, "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA=="],
|
||||||
|
|
||||||
"@tailwindcss/oxide-wasm32-wasi/@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="],
|
"duplexer2/readable-stream": ["readable-stream@2.3.8", "https://registry.npmmirror.com/readable-stream/-/readable-stream-2.3.8.tgz", { "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } }, "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA=="],
|
||||||
|
|
||||||
"@tailwindcss/oxide-wasm32-wasi/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
|
"glob/minimatch": ["minimatch@3.1.5", "https://registry.npmmirror.com/minimatch/-/minimatch-3.1.5.tgz", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="],
|
||||||
|
|
||||||
"archiver-utils/readable-stream": ["readable-stream@2.3.8", "", { "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } }, "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA=="],
|
"jszip/lie": ["lie@3.3.0", "https://registry.npmmirror.com/lie/-/lie-3.3.0.tgz", { "dependencies": { "immediate": "~3.0.5" } }, "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ=="],
|
||||||
|
|
||||||
"duplexer2/readable-stream": ["readable-stream@2.3.8", "", { "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } }, "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA=="],
|
"jszip/readable-stream": ["readable-stream@2.3.8", "https://registry.npmmirror.com/readable-stream/-/readable-stream-2.3.8.tgz", { "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } }, "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA=="],
|
||||||
|
|
||||||
"glob/minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="],
|
"lazystream/readable-stream": ["readable-stream@2.3.8", "https://registry.npmmirror.com/readable-stream/-/readable-stream-2.3.8.tgz", { "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } }, "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA=="],
|
||||||
|
|
||||||
"jszip/lie": ["lie@3.3.0", "", { "dependencies": { "immediate": "~3.0.5" } }, "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ=="],
|
"reka-ui/@internationalized/date": ["@internationalized/date@3.11.0", "https://registry.npmmirror.com/@internationalized/date/-/date-3.11.0.tgz", { "dependencies": { "@swc/helpers": "^0.5.0" } }, "sha512-BOx5huLAWhicM9/ZFs84CzP+V3gBW6vlpM02yzsdYC7TGlZJX1OJiEEHcSayF00Z+3jLlm4w79amvSt6RqKN3Q=="],
|
||||||
|
|
||||||
"jszip/pako": ["pako@1.0.11", "", {}, "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw=="],
|
"rolldown/@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-rc.5", "https://registry.npmmirror.com/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.5.tgz", {}, "sha512-RxlLX/DPoarZ9PtxVrQgZhPoor987YtKQqCo5zkjX+0S0yLJ7Vv515Wk6+xtTL67VONKJKxETWZwuZjss2idYw=="],
|
||||||
|
|
||||||
"jszip/readable-stream": ["readable-stream@2.3.8", "", { "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } }, "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA=="],
|
"string_decoder/safe-buffer": ["safe-buffer@5.2.1", "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.2.1.tgz", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="],
|
||||||
|
|
||||||
"lazystream/readable-stream": ["readable-stream@2.3.8", "", { "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } }, "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA=="],
|
"unzipper/readable-stream": ["readable-stream@2.3.8", "https://registry.npmmirror.com/readable-stream/-/readable-stream-2.3.8.tgz", { "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } }, "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA=="],
|
||||||
|
|
||||||
"reka-ui/@internationalized/date": ["@internationalized/date@3.11.0", "", { "dependencies": { "@swc/helpers": "^0.5.0" } }, "sha512-BOx5huLAWhicM9/ZFs84CzP+V3gBW6vlpM02yzsdYC7TGlZJX1OJiEEHcSayF00Z+3jLlm4w79amvSt6RqKN3Q=="],
|
"vue-i18n/@vue/devtools-api": ["@vue/devtools-api@6.6.4", "https://registry.npmmirror.com/@vue/devtools-api/-/devtools-api-6.6.4.tgz", {}, "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g=="],
|
||||||
|
|
||||||
"rolldown/@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-rc.5", "", {}, "sha512-RxlLX/DPoarZ9PtxVrQgZhPoor987YtKQqCo5zkjX+0S0yLJ7Vv515Wk6+xtTL67VONKJKxETWZwuZjss2idYw=="],
|
"vue-router/@vue/devtools-api": ["@vue/devtools-api@6.6.4", "https://registry.npmmirror.com/@vue/devtools-api/-/devtools-api-6.6.4.tgz", {}, "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g=="],
|
||||||
|
|
||||||
"string_decoder/safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="],
|
"zip-stream/archiver-utils": ["archiver-utils@3.0.4", "https://registry.npmmirror.com/archiver-utils/-/archiver-utils-3.0.4.tgz", { "dependencies": { "glob": "^7.2.3", "graceful-fs": "^4.2.0", "lazystream": "^1.0.0", "lodash.defaults": "^4.2.0", "lodash.difference": "^4.5.0", "lodash.flatten": "^4.4.0", "lodash.isplainobject": "^4.0.6", "lodash.union": "^4.6.0", "normalize-path": "^3.0.0", "readable-stream": "^3.6.0" } }, "sha512-KVgf4XQVrTjhyWmx6cte4RxonPLR9onExufI1jhvw/MQ4BB6IsZD5gT8Lq+u/+pRkWna/6JoHpiQioaqFP5Rzw=="],
|
||||||
|
|
||||||
"unzipper/readable-stream": ["readable-stream@2.3.8", "", { "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } }, "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA=="],
|
"archiver-utils/readable-stream/string_decoder": ["string_decoder@1.1.1", "https://registry.npmmirror.com/string_decoder/-/string_decoder-1.1.1.tgz", { "dependencies": { "safe-buffer": "~5.1.0" } }, "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg=="],
|
||||||
|
|
||||||
"vue-i18n/@vue/devtools-api": ["@vue/devtools-api@6.6.4", "", {}, "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g=="],
|
"duplexer2/readable-stream/string_decoder": ["string_decoder@1.1.1", "https://registry.npmmirror.com/string_decoder/-/string_decoder-1.1.1.tgz", { "dependencies": { "safe-buffer": "~5.1.0" } }, "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg=="],
|
||||||
|
|
||||||
"zip-stream/archiver-utils": ["archiver-utils@3.0.4", "", { "dependencies": { "glob": "^7.2.3", "graceful-fs": "^4.2.0", "lazystream": "^1.0.0", "lodash.defaults": "^4.2.0", "lodash.difference": "^4.5.0", "lodash.flatten": "^4.4.0", "lodash.isplainobject": "^4.0.6", "lodash.union": "^4.6.0", "normalize-path": "^3.0.0", "readable-stream": "^3.6.0" } }, "sha512-KVgf4XQVrTjhyWmx6cte4RxonPLR9onExufI1jhvw/MQ4BB6IsZD5gT8Lq+u/+pRkWna/6JoHpiQioaqFP5Rzw=="],
|
"glob/minimatch/brace-expansion": ["brace-expansion@1.1.12", "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-1.1.12.tgz", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="],
|
||||||
|
|
||||||
"archiver-utils/readable-stream/string_decoder": ["string_decoder@1.1.1", "", { "dependencies": { "safe-buffer": "~5.1.0" } }, "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg=="],
|
"jszip/readable-stream/string_decoder": ["string_decoder@1.1.1", "https://registry.npmmirror.com/string_decoder/-/string_decoder-1.1.1.tgz", { "dependencies": { "safe-buffer": "~5.1.0" } }, "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg=="],
|
||||||
|
|
||||||
"duplexer2/readable-stream/string_decoder": ["string_decoder@1.1.1", "", { "dependencies": { "safe-buffer": "~5.1.0" } }, "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg=="],
|
"lazystream/readable-stream/string_decoder": ["string_decoder@1.1.1", "https://registry.npmmirror.com/string_decoder/-/string_decoder-1.1.1.tgz", { "dependencies": { "safe-buffer": "~5.1.0" } }, "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg=="],
|
||||||
|
|
||||||
"glob/minimatch/brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="],
|
"unzipper/readable-stream/string_decoder": ["string_decoder@1.1.1", "https://registry.npmmirror.com/string_decoder/-/string_decoder-1.1.1.tgz", { "dependencies": { "safe-buffer": "~5.1.0" } }, "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg=="],
|
||||||
|
|
||||||
"jszip/readable-stream/string_decoder": ["string_decoder@1.1.1", "", { "dependencies": { "safe-buffer": "~5.1.0" } }, "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg=="],
|
|
||||||
|
|
||||||
"lazystream/readable-stream/string_decoder": ["string_decoder@1.1.1", "", { "dependencies": { "safe-buffer": "~5.1.0" } }, "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg=="],
|
|
||||||
|
|
||||||
"unzipper/readable-stream/string_decoder": ["string_decoder@1.1.1", "", { "dependencies": { "safe-buffer": "~5.1.0" } }, "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg=="],
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
117
data.js
Normal file
117
data.js
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
const DEFAULT_FILE_NAME = 'data'
|
||||||
|
const DEFAULT_MIME_TYPE = 'application/octet-stream'
|
||||||
|
|
||||||
|
const encoder = new TextEncoder()
|
||||||
|
const decoder = new TextDecoder()
|
||||||
|
|
||||||
|
const normalizeSuffix = (suffix) => {
|
||||||
|
const value = String(suffix || '').trim()
|
||||||
|
if (!value) throw new Error('INVALID_SUFFIX')
|
||||||
|
return value.startsWith('.') ? value : `.${value}`
|
||||||
|
}
|
||||||
|
|
||||||
|
const sanitizeFileNamePart = (value) => {
|
||||||
|
const cleaned = String(value || '')
|
||||||
|
.replace(/[\\/:*?"<>|]/g, '_')
|
||||||
|
.replace(/\s+/g, ' ')
|
||||||
|
.trim()
|
||||||
|
|
||||||
|
return cleaned || DEFAULT_FILE_NAME
|
||||||
|
}
|
||||||
|
|
||||||
|
const formatTimestamp = (date = new Date()) => {
|
||||||
|
const year = date.getFullYear()
|
||||||
|
const month = String(date.getMonth() + 1).padStart(2, '0')
|
||||||
|
const day = String(date.getDate()).padStart(2, '0')
|
||||||
|
const hour = String(date.getHours()).padStart(2, '0')
|
||||||
|
const minute = String(date.getMinutes()).padStart(2, '0')
|
||||||
|
const second = String(date.getSeconds()).padStart(2, '0')
|
||||||
|
|
||||||
|
return `${year}${month}${day}-${hour}${minute}${second}`
|
||||||
|
}
|
||||||
|
|
||||||
|
const encodeData = (data) => {
|
||||||
|
const json = JSON.stringify(data)
|
||||||
|
return encoder.encode(json)
|
||||||
|
}
|
||||||
|
|
||||||
|
const decodeData = (bytes) => {
|
||||||
|
const text = decoder.decode(bytes)
|
||||||
|
return JSON.parse(text)
|
||||||
|
}
|
||||||
|
|
||||||
|
const downloadBlob = (blob, fileName) => {
|
||||||
|
const url = URL.createObjectURL(blob)
|
||||||
|
const link = document.createElement('a')
|
||||||
|
|
||||||
|
link.href = url
|
||||||
|
link.download = fileName
|
||||||
|
document.body.appendChild(link)
|
||||||
|
link.click()
|
||||||
|
document.body.removeChild(link)
|
||||||
|
URL.revokeObjectURL(url)
|
||||||
|
}
|
||||||
|
|
||||||
|
const pickFile = (accept) => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const input = document.createElement('input')
|
||||||
|
input.type = 'file'
|
||||||
|
input.accept = accept
|
||||||
|
input.style.display = 'none'
|
||||||
|
|
||||||
|
const cleanup = () => {
|
||||||
|
input.removeEventListener('change', handleChange)
|
||||||
|
input.remove()
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleChange = () => {
|
||||||
|
const file = input.files?.[0] || null
|
||||||
|
cleanup()
|
||||||
|
|
||||||
|
if (!file) {
|
||||||
|
reject(new Error('FILE_NOT_SELECTED'))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve(file)
|
||||||
|
}
|
||||||
|
|
||||||
|
input.addEventListener('change', handleChange, { once: true })
|
||||||
|
document.body.appendChild(input)
|
||||||
|
input.click()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const exportData = async (data, suffix, options = {}) => {
|
||||||
|
const normalizedSuffix = normalizeSuffix(suffix)
|
||||||
|
const bytes = encodeData(data)
|
||||||
|
const blob = new Blob([bytes], {
|
||||||
|
type: options.mimeType || DEFAULT_MIME_TYPE
|
||||||
|
})
|
||||||
|
const baseName = sanitizeFileNamePart(options.fileName)
|
||||||
|
const fileName = `${baseName}-${formatTimestamp()}${normalizedSuffix}`
|
||||||
|
const shouldDownload = options.download !== false
|
||||||
|
|
||||||
|
if (shouldDownload) {
|
||||||
|
downloadBlob(blob, fileName)
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
blob,
|
||||||
|
fileName,
|
||||||
|
bytes
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const importData = async (suffix) => {
|
||||||
|
const normalizedSuffix = normalizeSuffix(suffix)
|
||||||
|
const file = await pickFile(normalizedSuffix)
|
||||||
|
const fileName = String(file.name || '')
|
||||||
|
|
||||||
|
if (!fileName.toLowerCase().endsWith(normalizedSuffix.toLowerCase())) {
|
||||||
|
throw new Error('INVALID_FILE_SUFFIX')
|
||||||
|
}
|
||||||
|
|
||||||
|
const bytes = new Uint8Array(await file.arrayBuffer())
|
||||||
|
return decodeData(bytes)
|
||||||
|
}
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
14
index.html
14
index.html
@ -4,7 +4,19 @@
|
|||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<link rel="icon" type="image/svg+xml" href="/favicon.ico" />
|
<link rel="icon" type="image/svg+xml" href="/favicon.ico" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>联众咨询</title>
|
<title>交通运输工程造价咨询服务预算编制规范</title>
|
||||||
|
|
||||||
|
<!-- 👇 企微 / 微信分享专用 meta 👇 -->
|
||||||
|
<meta name="description" content="交通运输工程造价咨询服务预算编制规范工具,依据 T/GDHS 017-2026 标准,提供专业预算编制、计算、导出功能。" />
|
||||||
|
|
||||||
|
<!-- 微信开放平台标签(解决不显示封面/标题问题) -->
|
||||||
|
<meta property="og:title" content="交通运输工程造价咨询服务预算编制工具" />
|
||||||
|
<meta property="og:description" content="依据 T/GDHS 017-2026 标准,专业工程造价预算编制工具。" />
|
||||||
|
<meta property="og:type" content="website" />
|
||||||
|
<meta property="og:image" content="https://jtzjfw.lianzhong.com.cn/logo.jpg" /> <!-- 必须替换成你自己的在线图片 -->
|
||||||
|
|
||||||
|
<!-- 企微专用优化 -->
|
||||||
|
<meta name="wx:cover" content="https://jtzjfw.lianzhong.com.cn/logo.jpg" />
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="app"></div>
|
<div id="app"></div>
|
||||||
|
|||||||
67
package-lock.json
generated
67
package-lock.json
generated
@ -21,20 +21,18 @@
|
|||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"decimal.js": "^10.6.0",
|
"decimal.js": "^10.6.0",
|
||||||
"docxtemplater": "^3.68.5",
|
|
||||||
"exceljs": "^4.4.0",
|
"exceljs": "^4.4.0",
|
||||||
"file-saver": "^2.0.5",
|
|
||||||
"localforage": "^1.10.0",
|
"localforage": "^1.10.0",
|
||||||
"lucide-vue-next": "^0.563.0",
|
"lucide-vue-next": "^0.563.0",
|
||||||
"motion-v": "^2.0.0",
|
"motion-v": "^2.0.0",
|
||||||
"pinia": "^3.0.4",
|
"pinia": "^3.0.4",
|
||||||
"pinia-plugin-persistedstate": "^4.7.1",
|
"pinia-plugin-persistedstate": "^4.7.1",
|
||||||
"pizzip": "^3.2.0",
|
|
||||||
"reka-ui": "^2.8.0",
|
"reka-ui": "^2.8.0",
|
||||||
"tailwind-merge": "^3.4.0",
|
"tailwind-merge": "^3.4.0",
|
||||||
"tailwindcss": "^4.1.18",
|
"tailwindcss": "^4.1.18",
|
||||||
"vue": "^3.5.25",
|
"vue": "^3.5.25",
|
||||||
"vue-i18n": "^11.3.0",
|
"vue-i18n": "^11.3.0",
|
||||||
|
"vue-router": "^4.6.4",
|
||||||
"vuedraggable": "^4.1.0"
|
"vuedraggable": "^4.1.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@ -659,15 +657,6 @@
|
|||||||
"vue": "^3.5.0"
|
"vue": "^3.5.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@xmldom/xmldom": {
|
|
||||||
"version": "0.9.9",
|
|
||||||
"resolved": "https://registry.npmmirror.com/@xmldom/xmldom/-/xmldom-0.9.9.tgz",
|
|
||||||
"integrity": "sha512-qycIHAucxy/LXAYIjmLmtQ8q9GPnMbnjG1KXhWm9o5sCr6pOYDATkMPiTNa6/v8eELyqOQ2FsEqeoFYmgv/gJg==",
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=14.6"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/ag-charts-community": {
|
"node_modules/ag-charts-community": {
|
||||||
"version": "13.1.0",
|
"version": "13.1.0",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@ -1024,18 +1013,6 @@
|
|||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/docxtemplater": {
|
|
||||||
"version": "3.68.5",
|
|
||||||
"resolved": "https://registry.npmmirror.com/docxtemplater/-/docxtemplater-3.68.5.tgz",
|
|
||||||
"integrity": "sha512-2xcHvTXjMA0jdX6PRh1BUTLrcRQ86Re/QJKWCUCX/vv5RKzntjNNkpR/O4AUoJY1TdoqxA+d04L4xgoAUNf/kw==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"@xmldom/xmldom": "^0.9.8"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=0.10"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/duplexer2": {
|
"node_modules/duplexer2": {
|
||||||
"version": "0.1.4",
|
"version": "0.1.4",
|
||||||
"license": "BSD-3-Clause",
|
"license": "BSD-3-Clause",
|
||||||
@ -1140,12 +1117,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/file-saver": {
|
|
||||||
"version": "2.0.5",
|
|
||||||
"resolved": "https://registry.npmmirror.com/file-saver/-/file-saver-2.0.5.tgz",
|
|
||||||
"integrity": "sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA==",
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/framer-motion": {
|
"node_modules/framer-motion": {
|
||||||
"version": "12.34.3",
|
"version": "12.34.3",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@ -1667,21 +1638,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/pizzip": {
|
|
||||||
"version": "3.2.0",
|
|
||||||
"resolved": "https://registry.npmmirror.com/pizzip/-/pizzip-3.2.0.tgz",
|
|
||||||
"integrity": "sha512-X4NPNICxCfIK8VYhF6wbksn81vTiziyLbvKuORVAmolvnUzl1A1xmz9DAWKxPRq9lZg84pJOOAMq3OE61bD8IQ==",
|
|
||||||
"license": "(MIT OR GPL-3.0)",
|
|
||||||
"dependencies": {
|
|
||||||
"pako": "^2.1.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/pizzip/node_modules/pako": {
|
|
||||||
"version": "2.1.0",
|
|
||||||
"resolved": "https://registry.npmmirror.com/pako/-/pako-2.1.0.tgz",
|
|
||||||
"integrity": "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==",
|
|
||||||
"license": "(MIT AND Zlib)"
|
|
||||||
},
|
|
||||||
"node_modules/postcss": {
|
"node_modules/postcss": {
|
||||||
"version": "8.5.6",
|
"version": "8.5.6",
|
||||||
"funding": [
|
"funding": [
|
||||||
@ -2172,6 +2128,27 @@
|
|||||||
"integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==",
|
"integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/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==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@vue/devtools-api": "^6.6.4"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/posva"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"vue": "^3.5.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/vue-router/node_modules/@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==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/vue-tsc": {
|
"node_modules/vue-tsc": {
|
||||||
"version": "3.2.5",
|
"version": "3.2.5",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
|||||||
13
package.json
13
package.json
@ -1,13 +1,14 @@
|
|||||||
{
|
{
|
||||||
"name": "my-vue-app",
|
"name": "ZWJJ2026",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.0.0",
|
"version": "1.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "bunx --bun vite",
|
"dev": "bunx --bun vite",
|
||||||
"build": " bunx --bun vite build",
|
"build": "bunx vue-tsc -b && bunx --bun vite build",
|
||||||
"preview": "bunx --bun vite preview",
|
"preview": "bunx --bun vite preview",
|
||||||
"type-check": "bunx vue-tsc --noEmit"
|
"type-check": "bunx vue-tsc --noEmit",
|
||||||
|
"dockerPush": "bun run build && docker build -f Dockerfile.dist -t wintsa/zwzjjstool2026:latest . && docker push wintsa/zwzjjstool2026:latest"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ag-grid-community/locale": "^35.1.0",
|
"@ag-grid-community/locale": "^35.1.0",
|
||||||
@ -23,20 +24,18 @@
|
|||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"decimal.js": "^10.6.0",
|
"decimal.js": "^10.6.0",
|
||||||
"docxtemplater": "^3.68.5",
|
|
||||||
"exceljs": "^4.4.0",
|
"exceljs": "^4.4.0",
|
||||||
"file-saver": "^2.0.5",
|
|
||||||
"localforage": "^1.10.0",
|
"localforage": "^1.10.0",
|
||||||
"lucide-vue-next": "^0.563.0",
|
"lucide-vue-next": "^0.563.0",
|
||||||
"motion-v": "^2.0.0",
|
"motion-v": "^2.0.0",
|
||||||
"pinia": "^3.0.4",
|
"pinia": "^3.0.4",
|
||||||
"pinia-plugin-persistedstate": "^4.7.1",
|
"pinia-plugin-persistedstate": "^4.7.1",
|
||||||
"pizzip": "^3.2.0",
|
|
||||||
"reka-ui": "^2.8.0",
|
"reka-ui": "^2.8.0",
|
||||||
"tailwind-merge": "^3.4.0",
|
"tailwind-merge": "^3.4.0",
|
||||||
"tailwindcss": "^4.1.18",
|
"tailwindcss": "^4.1.18",
|
||||||
"vue": "^3.5.25",
|
"vue": "^3.5.25",
|
||||||
"vue-i18n": "^11.3.0",
|
"vue-i18n": "^11.3.0",
|
||||||
|
"vue-router": "^4.6.4",
|
||||||
"vuedraggable": "^4.1.0"
|
"vuedraggable": "^4.1.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 1.1 MiB |
@ -1 +0,0 @@
|
|||||||
iVBORw0KGgoAAAANSUhEUgAAAfQAAAH0CAYAAADl8HLGAAAACXBIWXMAAA7EAAAOxAGVKw4bAAAKT2lDQ1BQaG90b3Nob3AgSUNDIHByb2ZpbGUAAHjanVNnVFPpFj333vRCS4iAlEtvUhUIIFJCi4AUkSYqIQkQSoghodkVUcERRUUEG8igiAOOjoCMFVEsDIoK2AfkIaKOg6OIisr74Xuja9a89+bN/rXXPues852zzwfACAyWSDNRNYAMqUIeEeCDx8TG4eQuQIEKJHAAEAizZCFz/SMBAPh+PDwrIsAHvgABeNMLCADATZvAMByH/w/qQplcAYCEAcB0kThLCIAUAEB6jkKmAEBGAYCdmCZTAKAEAGDLY2LjAFAtAGAnf+bTAICd+Jl7AQBblCEVAaCRACATZYhEAGg7AKzPVopFAFgwABRmS8Q5ANgtADBJV2ZIALC3AMDOEAuyAAgMADBRiIUpAAR7AGDIIyN4AISZABRG8lc88SuuEOcqAAB4mbI8uSQ5RYFbCC1xB1dXLh4ozkkXKxQ2YQJhmkAuwnmZGTKBNA/g88wAAKCRFRHgg/P9eM4Ors7ONo62Dl8t6r8G/yJiYuP+5c+rcEAAAOF0ftH+LC+zGoA7BoBt/qIl7gRoXgugdfeLZrIPQLUAoOnaV/Nw+H48PEWhkLnZ2eXk5NhKxEJbYcpXff5nwl/AV/1s+X48/Pf14L7iJIEyXYFHBPjgwsz0TKUcz5IJhGLc5o9H/LcL//wd0yLESWK5WCoU41EScY5EmozzMqUiiUKSKcUl0v9k4t8s+wM+3zUAsGo+AXuRLahdYwP2SycQWHTA4vcAAPK7b8HUKAgDgGiD4c93/+8//UegJQCAZkmScQAAXkQkLlTKsz/HCAAARKCBKrBBG/TBGCzABhzBBdzBC/xgNoRCJMTCQhBCCmSAHHJgKayCQiiGzbAdKmAv1EAdNMBRaIaTcA4uwlW4Dj1wD/phCJ7BKLyBCQRByAgTYSHaiAFiilgjjggXmYX4IcFIBBKLJCDJiBRRIkuRNUgxUopUIFVIHfI9cgI5h1xGupE7yAAygvyGvEcxlIGyUT3UDLVDuag3GoRGogvQZHQxmo8WoJvQcrQaPYw2oefQq2gP2o8+Q8cwwOgYBzPEbDAuxsNCsTgsCZNjy7EirAyrxhqwVqwDu4n1Y8+xdwQSgUXACTYEd0IgYR5BSFhMWE7YSKggHCQ0EdoJNwkDhFHCJyKTqEu0JroR+cQYYjIxh1hILCPWEo8TLxB7iEPENyQSiUMyJ7mQAkmxpFTSEtJG0m5SI+ksqZs0SBojk8naZGuyBzmULCAryIXkneTD5DPkG+Qh8lsKnWJAcaT4U+IoUspqShnlEOU05QZlmDJBVaOaUt2ooVQRNY9aQq2htlKvUYeoEzR1mjnNgxZJS6WtopXTGmgXaPdpr+h0uhHdlR5Ol9BX0svpR+iX6AP0dwwNhhWDx4hnKBmbGAcYZxl3GK+YTKYZ04sZx1QwNzHrmOeZD5lvVVgqtip8FZHKCpVKlSaVGyovVKmqpqreqgtV81XLVI+pXlN9rkZVM1PjqQnUlqtVqp1Q61MbU2epO6iHqmeob1Q/pH5Z/YkGWcNMw09DpFGgsV/jvMYgC2MZs3gsIWsNq4Z1gTXEJrHN2Xx2KruY/R27iz2qqaE5QzNKM1ezUvOUZj8H45hx+Jx0TgnnKKeX836K3hTvKeIpG6Y0TLkxZVxrqpaXllirSKtRq0frvTau7aedpr1Fu1n7gQ5Bx0onXCdHZ4/OBZ3nU9lT3acKpxZNPTr1ri6qa6UbobtEd79up+6Ynr5egJ5Mb6feeb3n+hx9L/1U/W36p/VHDFgGswwkBtsMzhg8xTVxbzwdL8fb8VFDXcNAQ6VhlWGX4SU
|
|
||||||
BIN
public/related-files.png
Normal file
BIN
public/related-files.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 856 B |
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
Before Width: | Height: | Size: 1.7 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 1.5 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 1.4 KiB |
322
src/App.vue
322
src/App.vue
@ -1,323 +1,3 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import { computed, onBeforeUnmount, onMounted, ref } from 'vue'
|
|
||||||
import { useI18n } from 'vue-i18n'
|
|
||||||
import { useTabStore } from '@/pinia/tab'
|
|
||||||
import HomeEntryView from '@/features/workbench/components/HomeEntryView.vue'
|
|
||||||
import Tab from '@/layout/tab.vue'
|
|
||||||
import { waitForHydration } from '@/pinia/Plugin/indexdb'
|
|
||||||
import localforage from 'localforage'
|
|
||||||
import {
|
|
||||||
buildProjectUrl,
|
|
||||||
DEFAULT_PROJECT_ID,
|
|
||||||
ensureProjectIdInUrl,
|
|
||||||
FORCE_HOME_QUERY_KEY,
|
|
||||||
getProjectDbName,
|
|
||||||
NEW_PROJECT_QUERY_KEY,
|
|
||||||
PROJECT_TAB_ID,
|
|
||||||
QUICK_PROJECT_ID
|
|
||||||
} from '@/lib/workspace'
|
|
||||||
import { collectActiveProjectSessionLocks, initProjectSessionLock } from '@/lib/projectSessionLock'
|
|
||||||
import { listenProjectDeleted, listenResetAll } from '@/lib/projectEvents'
|
|
||||||
import { listProjects, type ProjectMeta } from '@/lib/projectRegistry'
|
|
||||||
import { closePage } from './lib/utils'
|
|
||||||
|
|
||||||
const tabStore = useTabStore()
|
|
||||||
const { t } = useI18n()
|
|
||||||
const isReady = ref(false)
|
|
||||||
const lockConflict = ref(false)
|
|
||||||
const currentProjectId = ref('')
|
|
||||||
const currentProjectName = ref('')
|
|
||||||
const conflictProjectList = ref<ProjectMeta[]>([])
|
|
||||||
const openedProjectIds = ref<string[]>([])
|
|
||||||
const closeCountdown = ref(10)
|
|
||||||
const isNewProjectRequest = ref(false)
|
|
||||||
const isForceHomeRequest = ref(false)
|
|
||||||
let closeCountdownTimer: ReturnType<typeof setInterval> | null = null
|
|
||||||
let releaseLock: (() => void) | null = null
|
|
||||||
let stopProjectDeletedListener: (() => void) | null = null
|
|
||||||
let stopResetAllListener: (() => void) | null = null
|
|
||||||
let isHandlingDeletedProject = false
|
|
||||||
let isHandlingGlobalReset = false
|
|
||||||
|
|
||||||
const showHomeEntry = computed(() => !tabStore.hasCompletedSetup)
|
|
||||||
|
|
||||||
const handleImportComplete = () => {
|
|
||||||
tabStore.hasCompletedSetup = true
|
|
||||||
}
|
|
||||||
|
|
||||||
const initCurrentProjectLock = () => {
|
|
||||||
if (releaseLock) return
|
|
||||||
const projectId = String(currentProjectId.value || '').trim()
|
|
||||||
if (!projectId || projectId === QUICK_PROJECT_ID) {
|
|
||||||
lockConflict.value = false
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const lock = initProjectSessionLock({
|
|
||||||
projectId,
|
|
||||||
onConflict: (next) => {
|
|
||||||
lockConflict.value = next
|
|
||||||
if (next) {
|
|
||||||
refreshConflictProjectList()
|
|
||||||
startCloseCountdown()
|
|
||||||
} else {
|
|
||||||
clearCloseCountdown()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
releaseLock = lock.release
|
|
||||||
}
|
|
||||||
|
|
||||||
const refreshConflictProjectList = () => {
|
|
||||||
void (async () => {
|
|
||||||
const projects = listProjects()
|
|
||||||
const enriched = await Promise.all(
|
|
||||||
projects.map(async (project) => {
|
|
||||||
try {
|
|
||||||
const kvStoreInstance = localforage.createInstance({
|
|
||||||
name: getProjectDbName(project.id),
|
|
||||||
storeName: 'pinia-kv'
|
|
||||||
})
|
|
||||||
const kvState = await kvStoreInstance.getItem<any>('pinia-kv')
|
|
||||||
const entries = kvState?.entries && typeof kvState.entries === 'object' ? kvState.entries : null
|
|
||||||
const projectInfo = entries?.['xm-base-info-v1']
|
|
||||||
const projectName =
|
|
||||||
projectInfo && typeof projectInfo === 'object' && typeof projectInfo.projectName === 'string'
|
|
||||||
? projectInfo.projectName.trim()
|
|
||||||
: ''
|
|
||||||
return {
|
|
||||||
...project,
|
|
||||||
name: projectName || project.name
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
return project
|
|
||||||
}
|
|
||||||
})
|
|
||||||
)
|
|
||||||
conflictProjectList.value = enriched
|
|
||||||
const hit = enriched.find(item => item.id === currentProjectId.value)
|
|
||||||
currentProjectName.value = hit?.name || currentProjectId.value
|
|
||||||
openedProjectIds.value = Array.from(collectActiveProjectSessionLocks(enriched.map(item => item.id)))
|
|
||||||
})()
|
|
||||||
}
|
|
||||||
|
|
||||||
const clearCloseCountdown = () => {
|
|
||||||
if (!closeCountdownTimer) return
|
|
||||||
clearInterval(closeCountdownTimer)
|
|
||||||
closeCountdownTimer = null
|
|
||||||
}
|
|
||||||
|
|
||||||
const startCloseCountdown = () => {
|
|
||||||
clearCloseCountdown()
|
|
||||||
closeCountdown.value = 10
|
|
||||||
closeCountdownTimer = setInterval(() => {
|
|
||||||
closeCountdown.value -= 1
|
|
||||||
if (closeCountdown.value <= 0) {
|
|
||||||
clearCloseCountdown()
|
|
||||||
try {
|
|
||||||
closePage()
|
|
||||||
} catch {
|
|
||||||
|
|
||||||
// 部分浏览器会阻止关闭,保留阻断页。
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, 1000)
|
|
||||||
}
|
|
||||||
|
|
||||||
const isConflictProjectOpen = (projectId: string) => openedProjectIds.value.includes(projectId)
|
|
||||||
|
|
||||||
const openProjectInNewTab = (projectId: string, options?: { newProject?: boolean }) => {
|
|
||||||
if (isConflictProjectOpen(projectId)) return
|
|
||||||
const href = buildProjectUrl(projectId, options)
|
|
||||||
window.open(href, '_blank', 'noopener')
|
|
||||||
}
|
|
||||||
|
|
||||||
const createProjectAndOpen = () => {
|
|
||||||
|
|
||||||
refreshConflictProjectList()
|
|
||||||
openProjectInNewTab(DEFAULT_PROJECT_ID, { newProject: true })
|
|
||||||
}
|
|
||||||
|
|
||||||
const syncRouteRequestFlags = () => {
|
|
||||||
try {
|
|
||||||
const url = new URL(window.location.href)
|
|
||||||
isNewProjectRequest.value = url.searchParams.get(NEW_PROJECT_QUERY_KEY) === '1'
|
|
||||||
isForceHomeRequest.value = url.searchParams.get(FORCE_HOME_QUERY_KEY) === '1'
|
|
||||||
} catch {
|
|
||||||
isNewProjectRequest.value = false
|
|
||||||
isForceHomeRequest.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const formatProjectEditedTime = (value: string) => {
|
|
||||||
const date = new Date(value)
|
|
||||||
if (Number.isNaN(date.getTime())) return '-'
|
|
||||||
const pad = (num: number) => String(num).padStart(2, '0')
|
|
||||||
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}`
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleReleaseProjectLock = () => {
|
|
||||||
if (!releaseLock) return
|
|
||||||
releaseLock()
|
|
||||||
releaseLock = null
|
|
||||||
lockConflict.value = false
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleProjectDeleted = (deletedProjectId: string) => {
|
|
||||||
if (String(deletedProjectId || '').trim() !== currentProjectId.value) return
|
|
||||||
if (isHandlingDeletedProject) return
|
|
||||||
isHandlingDeletedProject = true
|
|
||||||
handleReleaseProjectLock()
|
|
||||||
tabStore.resetTabs()
|
|
||||||
tabStore.hasCompletedSetup = false
|
|
||||||
const href = buildProjectUrl(DEFAULT_PROJECT_ID, { forceHome: true })
|
|
||||||
try {
|
|
||||||
window.close()
|
|
||||||
} catch {
|
|
||||||
// ignore and fallback to redirect
|
|
||||||
}
|
|
||||||
window.setTimeout(() => {
|
|
||||||
window.location.href = href
|
|
||||||
}, 120)
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleResetAll = () => {
|
|
||||||
if (isHandlingGlobalReset) return
|
|
||||||
isHandlingGlobalReset = true
|
|
||||||
handleReleaseProjectLock()
|
|
||||||
tabStore.resetTabs()
|
|
||||||
tabStore.hasCompletedSetup = false
|
|
||||||
const href = buildProjectUrl(DEFAULT_PROJECT_ID, { forceHome: true })
|
|
||||||
try {
|
|
||||||
window.close()
|
|
||||||
} catch {
|
|
||||||
// ignore and fallback to redirect
|
|
||||||
}
|
|
||||||
window.setTimeout(() => {
|
|
||||||
window.location.href = href
|
|
||||||
}, 120)
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
|
|
||||||
currentProjectId.value = ensureProjectIdInUrl()
|
|
||||||
syncRouteRequestFlags()
|
|
||||||
refreshConflictProjectList()
|
|
||||||
if (!isForceHomeRequest.value && currentProjectId.value !== QUICK_PROJECT_ID && currentProjectId.value !== DEFAULT_PROJECT_ID) {
|
|
||||||
initCurrentProjectLock()
|
|
||||||
}
|
|
||||||
|
|
||||||
window.addEventListener('home-import-selected', handleImportComplete)
|
|
||||||
window.addEventListener('jgjs-release-project-lock', handleReleaseProjectLock)
|
|
||||||
stopProjectDeletedListener = listenProjectDeleted(handleProjectDeleted)
|
|
||||||
stopResetAllListener = listenResetAll(handleResetAll)
|
|
||||||
waitForHydration('tabs').then(() => {
|
|
||||||
if (isForceHomeRequest.value) {
|
|
||||||
tabStore.resetTabs()
|
|
||||||
tabStore.hasCompletedSetup = false
|
|
||||||
}
|
|
||||||
if (!tabStore.hasCompletedSetup && !isNewProjectRequest.value && !isForceHomeRequest.value) {
|
|
||||||
const hasProjects = listProjects().length > 0
|
|
||||||
if (hasProjects && currentProjectId.value !== DEFAULT_PROJECT_ID) {
|
|
||||||
if (Array.isArray(tabStore.tabs) && tabStore.tabs.length > 0) {
|
|
||||||
tabStore.hasCompletedSetup = true
|
|
||||||
} else {
|
|
||||||
tabStore.enterWorkspace({
|
|
||||||
id: PROJECT_TAB_ID,
|
|
||||||
title: t('home.cards.projectBudget'),
|
|
||||||
componentName: 'ProjectCalcView'
|
|
||||||
})
|
|
||||||
tabStore.hasCompletedSetup = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (tabStore.hasCompletedSetup && Array.isArray(tabStore.tabs) && tabStore.tabs.length > 0) {
|
|
||||||
const activeId = typeof tabStore.activeTabId === 'string' ? tabStore.activeTabId : ''
|
|
||||||
const hasActive = Boolean(activeId) && tabStore.tabs.some(tab => tab.id === activeId)
|
|
||||||
if (!hasActive) {
|
|
||||||
tabStore.activeTabId = tabStore.tabs[0]?.id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!releaseLock) {
|
|
||||||
lockConflict.value = false
|
|
||||||
clearCloseCountdown()
|
|
||||||
}
|
|
||||||
isReady.value = true
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
onBeforeUnmount(() => {
|
|
||||||
clearCloseCountdown()
|
|
||||||
window.removeEventListener('home-import-selected', handleImportComplete)
|
|
||||||
window.removeEventListener('jgjs-release-project-lock', handleReleaseProjectLock)
|
|
||||||
if (stopProjectDeletedListener) {
|
|
||||||
stopProjectDeletedListener()
|
|
||||||
stopProjectDeletedListener = null
|
|
||||||
}
|
|
||||||
if (stopResetAllListener) {
|
|
||||||
stopResetAllListener()
|
|
||||||
stopResetAllListener = null
|
|
||||||
}
|
|
||||||
if (releaseLock) {
|
|
||||||
releaseLock()
|
|
||||||
releaseLock = null
|
|
||||||
}
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<template v-if="isReady">
|
<RouterView />
|
||||||
<div
|
|
||||||
v-if="lockConflict"
|
|
||||||
class="flex min-h-screen items-center justify-center bg-slate-50 px-4"
|
|
||||||
>
|
|
||||||
<div class="w-full max-w-lg rounded-xl border border-red-200 bg-white p-6 shadow-sm">
|
|
||||||
<h2 class="text-lg font-semibold text-slate-900">{{ t('app.projectConflict.title') }}</h2>
|
|
||||||
<p class="mt-2 text-sm leading-6 text-slate-600">
|
|
||||||
{{ t('app.projectConflict.desc', { name: currentProjectName }) }}
|
|
||||||
</p>
|
|
||||||
<p class="mt-2 text-xs text-slate-500">
|
|
||||||
{{ t('app.projectConflict.countdown', { seconds: closeCountdown }) }}
|
|
||||||
</p>
|
|
||||||
<div class="mt-4 max-h-52 space-y-2 overflow-auto rounded-md border border-slate-200 bg-slate-50 p-2">
|
|
||||||
<button
|
|
||||||
v-for="project in conflictProjectList"
|
|
||||||
:key="project.id"
|
|
||||||
type="button"
|
|
||||||
class="flex w-full items-center justify-between rounded-md border border-transparent bg-white px-3 py-2 text-left text-sm transition"
|
|
||||||
:class="isConflictProjectOpen(project.id) ? 'cursor-not-allowed opacity-60' : 'cursor-pointer hover:border-slate-200 hover:bg-slate-100'"
|
|
||||||
:disabled="isConflictProjectOpen(project.id)"
|
|
||||||
@click="openProjectInNewTab(project.id)"
|
|
||||||
>
|
|
||||||
<span class="font-medium text-slate-700">
|
|
||||||
{{ project.name }}
|
|
||||||
<span v-if="isConflictProjectOpen(project.id)" class="ml-1 text-xs text-slate-500">{{ t('app.projectConflict.opened') }}</span>
|
|
||||||
</span>
|
|
||||||
<span class="text-xs text-slate-500">{{ t('app.projectConflict.lastEdited', { time: formatProjectEditedTime(project.updatedAt) }) }}</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div class="mt-4 flex items-center gap-2">
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="cursor-pointer rounded-md border border-slate-300 bg-white px-3 py-2 text-sm text-slate-700 hover:bg-slate-100"
|
|
||||||
@click="createProjectAndOpen"
|
|
||||||
>
|
|
||||||
{{ t('app.projectConflict.createAndOpen') }}
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="rounded-md border border-slate-300 bg-white px-3 py-2 text-sm text-slate-700"
|
|
||||||
:class="isConflictProjectOpen('default') ? 'cursor-not-allowed opacity-60' : 'cursor-pointer hover:bg-slate-100'"
|
|
||||||
:disabled="isConflictProjectOpen('default')"
|
|
||||||
@click="openProjectInNewTab('default')"
|
|
||||||
>
|
|
||||||
{{ t('app.projectConflict.openDefault') }}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<template v-else>
|
|
||||||
<HomeEntryView v-if="showHomeEntry" />
|
|
||||||
<Tab v-else />
|
|
||||||
</template>
|
|
||||||
</template>
|
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
318
src/features/app/components/WorkspaceShell.vue
Normal file
318
src/features/app/components/WorkspaceShell.vue
Normal file
@ -0,0 +1,318 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { computed, onBeforeUnmount, onMounted, ref } from 'vue'
|
||||||
|
import { useI18n } from 'vue-i18n'
|
||||||
|
import { useTabStore } from '@/pinia/tab'
|
||||||
|
import HomeEntryView from '@/features/workbench/components/HomeEntryView.vue'
|
||||||
|
import Tab from '@/layout/tab.vue'
|
||||||
|
import { waitForHydration } from '@/pinia/Plugin/indexdb'
|
||||||
|
import localforage from 'localforage'
|
||||||
|
import {
|
||||||
|
buildProjectUrl,
|
||||||
|
DEFAULT_PROJECT_ID,
|
||||||
|
ensureProjectIdInUrl,
|
||||||
|
FORCE_HOME_QUERY_KEY,
|
||||||
|
getProjectDbName,
|
||||||
|
NEW_PROJECT_QUERY_KEY,
|
||||||
|
PROJECT_TAB_ID,
|
||||||
|
QUICK_PROJECT_ID
|
||||||
|
} from '@/lib/workspace'
|
||||||
|
import { collectActiveProjectSessionLocks, initProjectSessionLock } from '@/lib/projectSessionLock'
|
||||||
|
import { listenProjectDeleted, listenResetAll } from '@/lib/projectEvents'
|
||||||
|
import { listProjects, type ProjectMeta } from '@/lib/projectRegistry'
|
||||||
|
|
||||||
|
const tabStore = useTabStore()
|
||||||
|
const { t } = useI18n()
|
||||||
|
const isReady = ref(false)
|
||||||
|
const lockConflict = ref(false)
|
||||||
|
const currentProjectId = ref('')
|
||||||
|
const currentProjectName = ref('')
|
||||||
|
const conflictProjectList = ref<ProjectMeta[]>([])
|
||||||
|
const openedProjectIds = ref<string[]>([])
|
||||||
|
const closeCountdown = ref(10)
|
||||||
|
const isNewProjectRequest = ref(false)
|
||||||
|
const isForceHomeRequest = ref(false)
|
||||||
|
let closeCountdownTimer: ReturnType<typeof setInterval> | null = null
|
||||||
|
let releaseLock: (() => void) | null = null
|
||||||
|
let stopProjectDeletedListener: (() => void) | null = null
|
||||||
|
let stopResetAllListener: (() => void) | null = null
|
||||||
|
let isHandlingDeletedProject = false
|
||||||
|
let isHandlingGlobalReset = false
|
||||||
|
|
||||||
|
const showHomeEntry = computed(() => !tabStore.hasCompletedSetup)
|
||||||
|
|
||||||
|
const handleImportComplete = () => {
|
||||||
|
tabStore.hasCompletedSetup = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const initCurrentProjectLock = () => {
|
||||||
|
if (releaseLock) return
|
||||||
|
const projectId = String(currentProjectId.value || '').trim()
|
||||||
|
if (!projectId || projectId === QUICK_PROJECT_ID) {
|
||||||
|
lockConflict.value = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const lock = initProjectSessionLock({
|
||||||
|
projectId,
|
||||||
|
onConflict: (next) => {
|
||||||
|
lockConflict.value = next
|
||||||
|
if (next) {
|
||||||
|
refreshConflictProjectList()
|
||||||
|
startCloseCountdown()
|
||||||
|
} else {
|
||||||
|
clearCloseCountdown()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
releaseLock = lock.release
|
||||||
|
}
|
||||||
|
|
||||||
|
const refreshConflictProjectList = () => {
|
||||||
|
void (async () => {
|
||||||
|
const projects = listProjects()
|
||||||
|
const enriched = await Promise.all(
|
||||||
|
projects.map(async (project) => {
|
||||||
|
try {
|
||||||
|
const kvStoreInstance = localforage.createInstance({
|
||||||
|
name: getProjectDbName(project.id),
|
||||||
|
storeName: 'pinia-kv'
|
||||||
|
})
|
||||||
|
const kvState = await kvStoreInstance.getItem<any>('pinia-kv')
|
||||||
|
const entries = kvState?.entries && typeof kvState.entries === 'object' ? kvState.entries : null
|
||||||
|
const projectInfo = entries?.['xm-base-info-v1']
|
||||||
|
const projectName =
|
||||||
|
projectInfo && typeof projectInfo === 'object' && typeof projectInfo.projectName === 'string'
|
||||||
|
? projectInfo.projectName.trim()
|
||||||
|
: ''
|
||||||
|
return {
|
||||||
|
...project,
|
||||||
|
name: projectName || project.name
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
return project
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
conflictProjectList.value = enriched
|
||||||
|
const hit = enriched.find(item => item.id === currentProjectId.value)
|
||||||
|
currentProjectName.value = hit?.name || currentProjectId.value
|
||||||
|
openedProjectIds.value = Array.from(collectActiveProjectSessionLocks(enriched.map(item => item.id)))
|
||||||
|
})()
|
||||||
|
}
|
||||||
|
|
||||||
|
const clearCloseCountdown = () => {
|
||||||
|
if (!closeCountdownTimer) return
|
||||||
|
clearInterval(closeCountdownTimer)
|
||||||
|
closeCountdownTimer = null
|
||||||
|
}
|
||||||
|
|
||||||
|
const startCloseCountdown = () => {
|
||||||
|
clearCloseCountdown()
|
||||||
|
closeCountdown.value = 10
|
||||||
|
closeCountdownTimer = setInterval(() => {
|
||||||
|
closeCountdown.value -= 1
|
||||||
|
if (closeCountdown.value <= 0) {
|
||||||
|
clearCloseCountdown()
|
||||||
|
backToHome()
|
||||||
|
}
|
||||||
|
}, 1000)
|
||||||
|
}
|
||||||
|
|
||||||
|
const isConflictProjectOpen = (projectId: string) => openedProjectIds.value.includes(projectId)
|
||||||
|
|
||||||
|
const openProjectInNewTab = (projectId: string, options?: { newProject?: boolean }) => {
|
||||||
|
if (isConflictProjectOpen(projectId)) return
|
||||||
|
const href = buildProjectUrl(projectId, options)
|
||||||
|
window.open(href, '_blank', 'noopener')
|
||||||
|
}
|
||||||
|
|
||||||
|
const createProjectAndOpen = () => {
|
||||||
|
refreshConflictProjectList()
|
||||||
|
openProjectInNewTab(DEFAULT_PROJECT_ID, { newProject: true })
|
||||||
|
}
|
||||||
|
|
||||||
|
const backToHome = () => {
|
||||||
|
window.location.href = buildProjectUrl(DEFAULT_PROJECT_ID, { forceHome: true })
|
||||||
|
}
|
||||||
|
|
||||||
|
const syncRouteRequestFlags = () => {
|
||||||
|
try {
|
||||||
|
const url = new URL(window.location.href)
|
||||||
|
isNewProjectRequest.value = url.searchParams.get(NEW_PROJECT_QUERY_KEY) === '1'
|
||||||
|
isForceHomeRequest.value = url.searchParams.get(FORCE_HOME_QUERY_KEY) === '1'
|
||||||
|
} catch {
|
||||||
|
isNewProjectRequest.value = false
|
||||||
|
isForceHomeRequest.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const formatProjectEditedTime = (value: string) => {
|
||||||
|
const date = new Date(value)
|
||||||
|
if (Number.isNaN(date.getTime())) return '-'
|
||||||
|
const pad = (num: number) => String(num).padStart(2, '0')
|
||||||
|
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}`
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleReleaseProjectLock = () => {
|
||||||
|
if (!releaseLock) return
|
||||||
|
releaseLock()
|
||||||
|
releaseLock = null
|
||||||
|
lockConflict.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleProjectDeleted = (deletedProjectId: string) => {
|
||||||
|
if (String(deletedProjectId || '').trim() !== currentProjectId.value) return
|
||||||
|
if (isHandlingDeletedProject) return
|
||||||
|
isHandlingDeletedProject = true
|
||||||
|
handleReleaseProjectLock()
|
||||||
|
tabStore.resetTabs()
|
||||||
|
tabStore.hasCompletedSetup = false
|
||||||
|
const href = buildProjectUrl(DEFAULT_PROJECT_ID, { forceHome: true })
|
||||||
|
try {
|
||||||
|
window.close()
|
||||||
|
} catch {
|
||||||
|
// ignore and fallback to redirect
|
||||||
|
}
|
||||||
|
window.setTimeout(() => {
|
||||||
|
window.location.href = href
|
||||||
|
}, 120)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleResetAll = () => {
|
||||||
|
if (isHandlingGlobalReset) return
|
||||||
|
isHandlingGlobalReset = true
|
||||||
|
handleReleaseProjectLock()
|
||||||
|
tabStore.resetTabs()
|
||||||
|
tabStore.hasCompletedSetup = false
|
||||||
|
const href = buildProjectUrl(DEFAULT_PROJECT_ID, { forceHome: true })
|
||||||
|
try {
|
||||||
|
window.close()
|
||||||
|
} catch {
|
||||||
|
// ignore and fallback to redirect
|
||||||
|
}
|
||||||
|
window.setTimeout(() => {
|
||||||
|
window.location.href = href
|
||||||
|
}, 120)
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
currentProjectId.value = ensureProjectIdInUrl()
|
||||||
|
syncRouteRequestFlags()
|
||||||
|
refreshConflictProjectList()
|
||||||
|
if (!isForceHomeRequest.value && currentProjectId.value !== QUICK_PROJECT_ID && currentProjectId.value !== DEFAULT_PROJECT_ID) {
|
||||||
|
initCurrentProjectLock()
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener('home-import-selected', handleImportComplete)
|
||||||
|
window.addEventListener('jgjs-release-project-lock', handleReleaseProjectLock)
|
||||||
|
stopProjectDeletedListener = listenProjectDeleted(handleProjectDeleted)
|
||||||
|
stopResetAllListener = listenResetAll(handleResetAll)
|
||||||
|
waitForHydration('tabs').then(() => {
|
||||||
|
if (isForceHomeRequest.value) {
|
||||||
|
tabStore.resetTabs()
|
||||||
|
tabStore.hasCompletedSetup = false
|
||||||
|
}
|
||||||
|
if (!tabStore.hasCompletedSetup && !isNewProjectRequest.value && !isForceHomeRequest.value) {
|
||||||
|
const hasProjects = listProjects().length > 0
|
||||||
|
if (hasProjects && currentProjectId.value !== DEFAULT_PROJECT_ID) {
|
||||||
|
if (Array.isArray(tabStore.tabs) && tabStore.tabs.length > 0) {
|
||||||
|
tabStore.hasCompletedSetup = true
|
||||||
|
} else {
|
||||||
|
tabStore.enterWorkspace({
|
||||||
|
id: PROJECT_TAB_ID,
|
||||||
|
title: t('home.cards.projectBudget'),
|
||||||
|
componentName: 'ProjectCalcView'
|
||||||
|
})
|
||||||
|
tabStore.hasCompletedSetup = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (tabStore.hasCompletedSetup && Array.isArray(tabStore.tabs) && tabStore.tabs.length > 0) {
|
||||||
|
const activeId = typeof tabStore.activeTabId === 'string' ? tabStore.activeTabId : ''
|
||||||
|
const hasActive = Boolean(activeId) && tabStore.tabs.some(tab => tab.id === activeId)
|
||||||
|
if (!hasActive) {
|
||||||
|
tabStore.activeTabId = tabStore.tabs[0]?.id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!releaseLock) {
|
||||||
|
lockConflict.value = false
|
||||||
|
clearCloseCountdown()
|
||||||
|
}
|
||||||
|
isReady.value = true
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
clearCloseCountdown()
|
||||||
|
window.removeEventListener('home-import-selected', handleImportComplete)
|
||||||
|
window.removeEventListener('jgjs-release-project-lock', handleReleaseProjectLock)
|
||||||
|
if (stopProjectDeletedListener) {
|
||||||
|
stopProjectDeletedListener()
|
||||||
|
stopProjectDeletedListener = null
|
||||||
|
}
|
||||||
|
if (stopResetAllListener) {
|
||||||
|
stopResetAllListener()
|
||||||
|
stopResetAllListener = null
|
||||||
|
}
|
||||||
|
if (releaseLock) {
|
||||||
|
releaseLock()
|
||||||
|
releaseLock = null
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<template v-if="isReady">
|
||||||
|
<div
|
||||||
|
v-if="lockConflict"
|
||||||
|
class="flex min-h-screen items-center justify-center bg-slate-50 px-4"
|
||||||
|
>
|
||||||
|
<div class="w-full max-w-lg rounded-xl border border-red-200 bg-white p-6 shadow-sm">
|
||||||
|
<h2 class="text-lg font-semibold text-slate-900">{{ t('app.projectConflict.title') }}</h2>
|
||||||
|
<p class="mt-2 text-sm leading-6 text-slate-600">
|
||||||
|
{{ t('app.projectConflict.desc', { name: currentProjectName }) }}
|
||||||
|
</p>
|
||||||
|
<p class="mt-2 text-xs text-slate-500">
|
||||||
|
{{ t('app.projectConflict.countdown', { seconds: closeCountdown }) }}
|
||||||
|
</p>
|
||||||
|
<div class="mt-4 max-h-52 space-y-2 overflow-auto rounded-md border border-slate-200 bg-slate-50 p-2">
|
||||||
|
<button
|
||||||
|
v-for="project in conflictProjectList"
|
||||||
|
:key="project.id"
|
||||||
|
type="button"
|
||||||
|
class="flex w-full items-center justify-between rounded-md border border-transparent bg-white px-3 py-2 text-left text-sm transition"
|
||||||
|
:class="isConflictProjectOpen(project.id) ? 'cursor-not-allowed opacity-60' : 'cursor-pointer hover:border-slate-200 hover:bg-slate-100'"
|
||||||
|
:disabled="isConflictProjectOpen(project.id)"
|
||||||
|
@click="openProjectInNewTab(project.id)"
|
||||||
|
>
|
||||||
|
<span class="font-medium text-slate-700">
|
||||||
|
{{ project.name }}
|
||||||
|
<span v-if="isConflictProjectOpen(project.id)" class="ml-1 text-xs text-slate-500">{{ t('app.projectConflict.opened') }}</span>
|
||||||
|
</span>
|
||||||
|
<span class="text-xs text-slate-500">{{ t('app.projectConflict.lastEdited', { time: formatProjectEditedTime(project.updatedAt) }) }}</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="mt-4 flex items-center gap-2">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="cursor-pointer rounded-md border border-slate-300 bg-white px-3 py-2 text-sm text-slate-700 hover:bg-slate-100"
|
||||||
|
@click="createProjectAndOpen"
|
||||||
|
>
|
||||||
|
{{ t('app.projectConflict.createAndOpen') }}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="rounded-md border border-slate-300 bg-white px-3 py-2 text-sm text-slate-700"
|
||||||
|
:class="'cursor-pointer hover:bg-slate-100'"
|
||||||
|
@click="backToHome"
|
||||||
|
>
|
||||||
|
{{ t('app.projectConflict.openDefault') }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<template v-else>
|
||||||
|
<HomeEntryView v-if="showHomeEntry" />
|
||||||
|
<Tab v-else />
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
197
src/features/disclaimer/components/DisclaimerPage.vue
Normal file
197
src/features/disclaimer/components/DisclaimerPage.vue
Normal file
@ -0,0 +1,197 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { computed, ref, watchEffect } from 'vue'
|
||||||
|
import { useI18n } from 'vue-i18n'
|
||||||
|
import { Languages } from 'lucide-vue-next'
|
||||||
|
import { Button } from '@/components/ui/button'
|
||||||
|
import { DEFAULT_LOCALE, setAppLocale, type AppLocale } from '@/i18n'
|
||||||
|
import {
|
||||||
|
DISCLAIMER_ENTRY_QUERY_KEY,
|
||||||
|
DISCLAIMER_RETURN_URL_QUERY_KEY,
|
||||||
|
hasAcceptedRestrictedDisclaimer,
|
||||||
|
persistRestrictedDisclaimerAcceptance,
|
||||||
|
readRestrictedEntryCodeFromUrl
|
||||||
|
} from '@/lib/workspace'
|
||||||
|
|
||||||
|
const { t, locale } = useI18n()
|
||||||
|
const accepted = ref(false)
|
||||||
|
|
||||||
|
const parsedParams = (() => {
|
||||||
|
try {
|
||||||
|
const url = new URL(window.location.href)
|
||||||
|
const returnUrl = String(url.searchParams.get(DISCLAIMER_RETURN_URL_QUERY_KEY) || '').trim()
|
||||||
|
const parsedReturnUrl = returnUrl ? new URL(returnUrl, window.location.href) : null
|
||||||
|
const entryFromReturnUrl = String(parsedReturnUrl?.searchParams.get(DISCLAIMER_ENTRY_QUERY_KEY) || '').trim()
|
||||||
|
return {
|
||||||
|
returnUrl,
|
||||||
|
entry: entryFromReturnUrl || readRestrictedEntryCodeFromUrl(parsedReturnUrl)
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
return {
|
||||||
|
returnUrl: '',
|
||||||
|
entry: readRestrictedEntryCodeFromUrl()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})()
|
||||||
|
|
||||||
|
const hasRestrictedEntry = computed(() => Boolean(parsedParams.entry))
|
||||||
|
const backHref = computed(() => parsedParams.returnUrl || './?projectId=default')
|
||||||
|
|
||||||
|
const sections = computed(() => [
|
||||||
|
{
|
||||||
|
title: t('disclaimerPage.sections.standardBasisTitle'),
|
||||||
|
paragraphs: [
|
||||||
|
t('disclaimerPage.sections.standardBasisP1'),
|
||||||
|
t('disclaimerPage.sections.standardBasisP2'),
|
||||||
|
t('disclaimerPage.sections.standardBasisP3')
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('disclaimerPage.sections.referenceOnlyTitle'),
|
||||||
|
paragraphs: [t('disclaimerPage.sections.referenceOnlyP1')]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('disclaimerPage.sections.accuracyTitle'),
|
||||||
|
paragraphs: [t('disclaimerPage.sections.accuracyP1')]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('disclaimerPage.sections.riskTitle'),
|
||||||
|
paragraphs: [t('disclaimerPage.sections.riskP1')]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('disclaimerPage.sections.liabilityTitle'),
|
||||||
|
paragraphs: [t('disclaimerPage.sections.liabilityP1'), t('disclaimerPage.sections.liabilityP2')]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('disclaimerPage.sections.interruptionTitle'),
|
||||||
|
paragraphs: [t('disclaimerPage.sections.interruptionP1')]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('disclaimerPage.sections.externalTitle'),
|
||||||
|
paragraphs: [t('disclaimerPage.sections.externalP1')]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('disclaimerPage.sections.lawTitle'),
|
||||||
|
paragraphs: [t('disclaimerPage.sections.lawP1')]
|
||||||
|
}
|
||||||
|
])
|
||||||
|
|
||||||
|
const toggleLocale = () => {
|
||||||
|
const next = locale.value === 'en-US' ? 'zh-CN' : 'en-US'
|
||||||
|
setAppLocale(next as AppLocale)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleContinue = () => {
|
||||||
|
if (!accepted.value) return
|
||||||
|
if (parsedParams.entry) {
|
||||||
|
persistRestrictedDisclaimerAcceptance(parsedParams.entry)
|
||||||
|
}
|
||||||
|
window.location.href = backHref.value
|
||||||
|
}
|
||||||
|
|
||||||
|
watchEffect(() => {
|
||||||
|
const lang = locale.value || DEFAULT_LOCALE
|
||||||
|
document.documentElement.lang = lang
|
||||||
|
document.title = t('disclaimerPage.documentTitle')
|
||||||
|
})
|
||||||
|
|
||||||
|
accepted.value = parsedParams.entry ? hasAcceptedRestrictedDisclaimer(parsedParams.entry) : false
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<main class="min-h-screen bg-[linear-gradient(180deg,#f7fbff_0%,#eef4f8_44%,#e4edf3_100%)] text-slate-900">
|
||||||
|
<div class="mx-auto w-full max-w-5xl px-4 py-6 sm:px-6 lg:px-8 lg:py-10">
|
||||||
|
<div class="mb-4 flex justify-end">
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
class="h-9 cursor-pointer gap-2 rounded-full border-slate-300/80 bg-white/85 px-4 text-xs text-slate-700 shadow-sm backdrop-blur"
|
||||||
|
@click="toggleLocale"
|
||||||
|
>
|
||||||
|
<Languages class="h-3.5 w-3.5" />
|
||||||
|
<span>{{ locale === 'en-US' ? 'EN' : '中' }}</span>
|
||||||
|
<span class="hidden sm:inline">{{ t('disclaimerPage.actions.switchLocale') }}</span>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<section class="overflow-hidden rounded-[28px] border border-slate-200/70 bg-white/85 shadow-[0_24px_80px_rgba(15,23,42,0.10)] backdrop-blur">
|
||||||
|
<div class="relative overflow-hidden px-5 py-6 sm:px-8 sm:py-8 lg:px-10">
|
||||||
|
<div class="pointer-events-none absolute inset-x-0 top-0 h-40 bg-[radial-gradient(circle_at_top,rgba(14,116,144,0.18),transparent_68%)]" />
|
||||||
|
<div class="relative">
|
||||||
|
<p class="text-xs font-bold tracking-[0.24em] text-teal-700">{{ t('disclaimerPage.eyebrow') }}</p>
|
||||||
|
<h1 class="mt-3 max-w-4xl text-2xl font-semibold leading-tight tracking-tight sm:text-3xl">
|
||||||
|
{{ t('disclaimerPage.pageTitle') }}
|
||||||
|
</h1>
|
||||||
|
<p class="mt-4 text-sm text-slate-500">
|
||||||
|
<span>{{ t('disclaimerPage.lastUpdatedLabel') }}</span>
|
||||||
|
<span>{{ t('disclaimerPage.lastUpdatedValue') }}</span>
|
||||||
|
</p>
|
||||||
|
<p class="mt-5 max-w-4xl text-sm leading-7 text-slate-600 sm:text-[15px]">
|
||||||
|
{{ t('disclaimerPage.leadText') }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="h-px bg-[linear-gradient(90deg,rgba(148,163,184,0),rgba(148,163,184,0.7),rgba(148,163,184,0))]" />
|
||||||
|
|
||||||
|
<div class="px-5 py-6 sm:px-8 lg:px-10 lg:py-8">
|
||||||
|
<section
|
||||||
|
v-for="section in sections"
|
||||||
|
:key="section.title"
|
||||||
|
class="mb-6 border-b border-slate-100 pb-6 last:mb-0 last:border-b-0 last:pb-0"
|
||||||
|
>
|
||||||
|
<h2 class="text-lg font-semibold leading-7 text-slate-900 sm:text-[20px]">{{ section.title }}</h2>
|
||||||
|
<p
|
||||||
|
v-for="paragraph in section.paragraphs"
|
||||||
|
:key="paragraph"
|
||||||
|
class="mt-3 text-sm leading-7 text-slate-600 sm:text-[15px]"
|
||||||
|
>
|
||||||
|
{{ paragraph }}
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section
|
||||||
|
v-if="hasRestrictedEntry"
|
||||||
|
class="mt-6 rounded-[24px] border border-teal-200/70 bg-[linear-gradient(135deg,rgba(15,118,110,0.08),rgba(14,165,233,0.08))] p-5 sm:p-6"
|
||||||
|
>
|
||||||
|
<h2 class="text-base font-semibold text-slate-900">{{ t('disclaimerPage.confirm.title') }}</h2>
|
||||||
|
<p class="mt-3 text-sm leading-7 text-slate-600">{{ t('disclaimerPage.confirm.desc1') }}</p>
|
||||||
|
<p class="mt-1 text-sm leading-7 text-slate-600">{{ t('disclaimerPage.confirm.desc2') }}</p>
|
||||||
|
|
||||||
|
<label class="mt-4 flex cursor-pointer items-start gap-3 rounded-2xl border border-slate-200/80 bg-white/80 px-4 py-3 text-sm leading-6 text-slate-700">
|
||||||
|
<input v-model="accepted" type="checkbox" class="mt-1 h-4 w-4 accent-teal-700" />
|
||||||
|
<span>{{ t('disclaimerPage.confirm.checkbox') }}</span>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<div class="mt-5 flex flex-col gap-3 sm:flex-row">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="inline-flex min-h-11 items-center justify-center rounded-full bg-teal-700 px-5 text-sm font-semibold text-white shadow-[0_12px_24px_rgba(15,118,110,0.18)] transition hover:bg-teal-800 disabled:cursor-not-allowed disabled:opacity-55 disabled:shadow-none"
|
||||||
|
:disabled="!accepted"
|
||||||
|
@click="handleContinue"
|
||||||
|
>
|
||||||
|
{{ t('disclaimerPage.confirm.continue') }}
|
||||||
|
</button>
|
||||||
|
<a
|
||||||
|
:href="backHref"
|
||||||
|
class="inline-flex min-h-11 items-center justify-center rounded-full border border-slate-300 bg-white px-5 text-sm font-semibold text-slate-700 transition hover:bg-slate-50"
|
||||||
|
>
|
||||||
|
{{ t('disclaimerPage.actions.back') }}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p class="mt-4 text-xs leading-5 text-slate-500">{{ t('disclaimerPage.confirm.hint') }}</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<div v-else class="mt-6 flex">
|
||||||
|
<a
|
||||||
|
:href="backHref"
|
||||||
|
class="inline-flex min-h-11 items-center justify-center rounded-full border border-slate-300 bg-white px-5 text-sm font-semibold text-slate-700 transition hover:bg-slate-50"
|
||||||
|
>
|
||||||
|
{{ t('disclaimerPage.actions.back') }}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
</template>
|
||||||
@ -74,7 +74,6 @@ import {
|
|||||||
ToastTitle,
|
ToastTitle,
|
||||||
ToastViewport
|
ToastViewport
|
||||||
} from 'reka-ui'
|
} from 'reka-ui'
|
||||||
import {useDataStore} from '@/pinia/zx'
|
|
||||||
|
|
||||||
const STORAGE_KEY = 'ht-card-v1'
|
const STORAGE_KEY = 'ht-card-v1'
|
||||||
const tabStore = useTabStore()
|
const tabStore = useTabStore()
|
||||||
@ -334,18 +333,8 @@ const loadContractBudgetFee = async (contractId: string) => {
|
|||||||
loadHtMainTotalFee(`htExtraFee-${contractId}-additional-work`),
|
loadHtMainTotalFee(`htExtraFee-${contractId}-additional-work`),
|
||||||
loadHtMainTotalFee(`htExtraFee-${contractId}-reserve`)
|
loadHtMainTotalFee(`htExtraFee-${contractId}-reserve`)
|
||||||
])
|
])
|
||||||
|
const parts = [serviceFee, additionalFee, reserveFee]
|
||||||
const zxRows = await useDataStore().query([
|
const total = sumNullableNumbers(parts)
|
||||||
{ field: 'type', value: `${contractId}-zxFw`, operator: 'eq' }
|
|
||||||
])
|
|
||||||
const totalFinalFee = zxRows.reduce((sum, row) => {
|
|
||||||
return sum + (Number(row.finalFee) || 0)
|
|
||||||
}, 0)
|
|
||||||
|
|
||||||
const total = totalFinalFee;
|
|
||||||
|
|
||||||
/*const parts = [serviceFee, additionalFee, reserveFee]
|
|
||||||
const total = sumNullableNumbers(parts)*/
|
|
||||||
return total == null ? null : roundTo(total, 2)
|
return total == null ? null : roundTo(total, 2)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, defineComponent, h, nextTick, onActivated, onBeforeUnmount, onMounted, ref, shallowRef, watch, type PropType } from 'vue'
|
import { computed, defineComponent, h, nextTick, onActivated, onBeforeUnmount, onMounted, ref, shallowRef, watch, type PropType } from 'vue'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
import { AgGridVue } from 'ag-grid-vue3'
|
import { AgGridVue } from 'ag-grid-vue3'
|
||||||
@ -60,23 +60,12 @@ const rowData = ref<SummaryRow[]>([])
|
|||||||
const explanationText = ref('')
|
const explanationText = ref('')
|
||||||
let reloadTimer: ReturnType<typeof setTimeout> | null = null
|
let reloadTimer: ReturnType<typeof setTimeout> | null = null
|
||||||
|
|
||||||
/**
|
|
||||||
* 对数值数组求和(保留3位小数)
|
|
||||||
* @param values 数值数组(可包含 null/undefined)
|
|
||||||
* @returns 求和结果(无有效值时返回 null)
|
|
||||||
*/
|
|
||||||
const sum3 = (values: Array<number | null | undefined>) => {
|
const sum3 = (values: Array<number | null | undefined>) => {
|
||||||
const valid = values.filter((v): v is number => toFiniteNumberOrNull(v) != null)
|
const valid = values.filter((v): v is number => toFiniteNumberOrNull(v) != null)
|
||||||
if (valid.length === 0) return null
|
if (valid.length === 0) return null
|
||||||
return roundTo(valid.reduce((a, b) => a + b, 0), 3)
|
return roundTo(valid.reduce((a, b) => a + b, 0), 3)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 计算时工法的总费用
|
|
||||||
* 遍历时工法明细行,优先使用 serviceBudget,否则通过 adoptedBudgetUnitPrice × personnelCount × workdayCount 计算
|
|
||||||
* @param state 时工法状态对象
|
|
||||||
* @returns 总费用(保留3位小数,无有效值时返回 null)
|
|
||||||
*/
|
|
||||||
const sumHourlyMethodFee = (state: HourlyMethodStateLike | null): number | null => {
|
const sumHourlyMethodFee = (state: HourlyMethodStateLike | null): number | null => {
|
||||||
const rows = Array.isArray(state?.detailRows) ? state.detailRows : []
|
const rows = Array.isArray(state?.detailRows) ? state.detailRows : []
|
||||||
if (rows.length === 0) return null
|
if (rows.length === 0) return null
|
||||||
@ -99,13 +88,6 @@ const sumHourlyMethodFee = (state: HourlyMethodStateLike | null): number | null
|
|||||||
return hasValid ? roundTo(total, 3) : null
|
return hasValid ? roundTo(total, 3) : null
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 计算工程量清单法的总费用
|
|
||||||
* 遍历工程量清单明细行,优先使用 budgetFee,否则通过 quantity × unitPrice 计算
|
|
||||||
* 跳过固定小计行(fee-subtotal-fixed)
|
|
||||||
* @param state 工程量清单法状态对象
|
|
||||||
* @returns 总费用(保留3位小数,无有效值时返回 null)
|
|
||||||
*/
|
|
||||||
const sumQuantityMethodFee = (state: QuantityMethodStateLike | null): number | null => {
|
const sumQuantityMethodFee = (state: QuantityMethodStateLike | null): number | null => {
|
||||||
const rows = Array.isArray(state?.detailRows) ? state.detailRows : []
|
const rows = Array.isArray(state?.detailRows) ? state.detailRows : []
|
||||||
if (rows.length === 0) return null
|
if (rows.length === 0) return null
|
||||||
@ -128,12 +110,6 @@ const sumQuantityMethodFee = (state: QuantityMethodStateLike | null): number | n
|
|||||||
return hasValid ? roundTo(total, 3) : null
|
return hasValid ? roundTo(total, 3) : null
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 加载合同段某行的三种计费方式(费率法、时工法、工程量清单法)并汇总
|
|
||||||
* @param mainStorageKey 主存储键(如 htExtraFee-{contractId}-additional-work)
|
|
||||||
* @param rowId 行ID
|
|
||||||
* @returns 包含subtotal(小计)、m0(费率法详情)、m4(时工法详情)、m5(工程量清单法详情)的对象
|
|
||||||
*/
|
|
||||||
const loadHtMethodSummaryByRow = async (mainStorageKey: string, rowId: string): Promise<{
|
const loadHtMethodSummaryByRow = async (mainStorageKey: string, rowId: string): Promise<{
|
||||||
subtotal: number | null
|
subtotal: number | null
|
||||||
m0: { coe: string; fee: number } | null
|
m0: { coe: string; fee: number } | null
|
||||||
@ -163,12 +139,6 @@ const loadHtMethodSummaryByRow = async (mainStorageKey: string, rowId: string):
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 构建附加工作或预备费的汇总行数据
|
|
||||||
* @param rowType 行类型('additional' 附加工作 或 'reserve' 预备费)
|
|
||||||
* @param list 费用项目列表(包含 id、name、code)
|
|
||||||
* @returns 包含 rows(汇总行数组)和 explainLines(说明文本数组)的对象
|
|
||||||
*/
|
|
||||||
const buildFeeRows = async (
|
const buildFeeRows = async (
|
||||||
rowType: 'additional' | 'reserve',
|
rowType: 'additional' | 'reserve',
|
||||||
list: Array<{ id: string | number; name: string; code: unknown }>
|
list: Array<{ id: string | number; name: string; code: unknown }>
|
||||||
@ -212,11 +182,6 @@ const buildFeeRows = async (
|
|||||||
return { rows, explainLines }
|
return { rows, explainLines }
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 构建咨询服务汇总行数据
|
|
||||||
* 从合同状态中筛选已选中的服务项,转换为 SummaryRow 格式
|
|
||||||
* @returns 咨询服务汇总行数组
|
|
||||||
*/
|
|
||||||
const buildServiceRows = (): SummaryRow[] => {
|
const buildServiceRows = (): SummaryRow[] => {
|
||||||
const contractState = zxFwPricingStore.getContractState(props.contractId)
|
const contractState = zxFwPricingStore.getContractState(props.contractId)
|
||||||
const selectedSet = new Set((contractState?.selectedIds || []).map(id => String(id)))
|
const selectedSet = new Set((contractState?.selectedIds || []).map(id => String(id)))
|
||||||
@ -237,11 +202,6 @@ const buildServiceRows = (): SummaryRow[] => {
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 重新加载所有汇总行数据
|
|
||||||
* 包括:咨询服务、附加工作、预备费
|
|
||||||
* 同时生成说明文本
|
|
||||||
*/
|
|
||||||
const reloadRows = async () => {
|
const reloadRows = async () => {
|
||||||
await zxFwPricingStore.loadContract(props.contractId)
|
await zxFwPricingStore.loadContract(props.contractId)
|
||||||
const [additionalResult, reserveResult] = await Promise.all([
|
const [additionalResult, reserveResult] = await Promise.all([
|
||||||
@ -259,10 +219,6 @@ const reloadRows = async () => {
|
|||||||
explanationText.value = lines.join('\n')
|
explanationText.value = lines.join('\n')
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 延迟重新加载汇总数据(防抖 80ms)
|
|
||||||
* 避免频繁触发数据加载
|
|
||||||
*/
|
|
||||||
const scheduleReload = () => {
|
const scheduleReload = () => {
|
||||||
if (reloadTimer) clearTimeout(reloadTimer)
|
if (reloadTimer) clearTimeout(reloadTimer)
|
||||||
reloadTimer = setTimeout(() => {
|
reloadTimer = setTimeout(() => {
|
||||||
@ -270,10 +226,6 @@ const scheduleReload = () => {
|
|||||||
}, 80)
|
}, 80)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 刷新签名(用于 watch 监听数据变化)
|
|
||||||
* 监听合同数据、附加工作、预备费的主状态和方法状态变化
|
|
||||||
*/
|
|
||||||
const refreshSignature = computed(() => {
|
const refreshSignature = computed(() => {
|
||||||
const additionalKey = `htExtraFee-${props.contractId}-additional-work`
|
const additionalKey = `htExtraFee-${props.contractId}-additional-work`
|
||||||
const reserveKey = `htExtraFee-${props.contractId}-reserve`
|
const reserveKey = `htExtraFee-${props.contractId}-reserve`
|
||||||
@ -286,10 +238,6 @@ const refreshSignature = computed(() => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
/**
|
|
||||||
* 计算总计行数据
|
|
||||||
* 对所有汇总行的各字段进行求和
|
|
||||||
*/
|
|
||||||
const totalRow = computed<SummaryRow>(() => {
|
const totalRow = computed<SummaryRow>(() => {
|
||||||
const sumField = (pick: (row: SummaryRow) => number | null | undefined) =>
|
const sumField = (pick: (row: SummaryRow) => number | null | undefined) =>
|
||||||
sum3(rowData.value.map(pick))
|
sum3(rowData.value.map(pick))
|
||||||
@ -354,6 +302,9 @@ const columnDefs: ColDef<SummaryRow>[] = [
|
|||||||
minWidth: 90,
|
minWidth: 90,
|
||||||
maxWidth: 140,
|
maxWidth: 140,
|
||||||
colSpan: params => (params.data?.rowType === 'total' ? 2 : 1),
|
colSpan: params => (params.data?.rowType === 'total' ? 2 : 1),
|
||||||
|
cellClassRules: {
|
||||||
|
'ag-summary-label-cell': params => params.data?.rowType === 'total'
|
||||||
|
},
|
||||||
valueFormatter: params => {
|
valueFormatter: params => {
|
||||||
if (params.data?.rowType === 'total') return params.data.name || t('htSummary.total')
|
if (params.data?.rowType === 'total') return params.data.name || t('htSummary.total')
|
||||||
return typeof params.value === 'string' ? params.value : ''
|
return typeof params.value === 'string' ? params.value : ''
|
||||||
@ -471,28 +422,14 @@ const summaryGridOptions: GridOptions<SummaryRow> = {
|
|||||||
getRowClass: params => (params.data?.rowType === 'additional' || params.data?.rowType === 'reserve' ? 'ht-summary-fee-row' : '')
|
getRowClass: params => (params.data?.rowType === 'additional' || params.data?.rowType === 'reserve' ? 'ht-summary-fee-row' : '')
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* AG Grid 准备就绪回调
|
|
||||||
* 初始化 gridApi 并同步自动行高
|
|
||||||
* @param event Grid 准备就绪事件
|
|
||||||
*/
|
|
||||||
const onGridReady = (event: GridReadyEvent<SummaryRow>) => {
|
const onGridReady = (event: GridReadyEvent<SummaryRow>) => {
|
||||||
gridApi.value = event.api
|
gridApi.value = event.api
|
||||||
void syncAutoRowHeights()
|
void syncAutoRowHeights()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 检查 Grid API 是否可用
|
|
||||||
* @param api Grid API 实例
|
|
||||||
* @returns API 是否有效
|
|
||||||
*/
|
|
||||||
const isGridApiAlive = (api: GridApi<SummaryRow> | null | undefined): api is GridApi<SummaryRow> =>
|
const isGridApiAlive = (api: GridApi<SummaryRow> | null | undefined): api is GridApi<SummaryRow> =>
|
||||||
Boolean(api && !api.isDestroyed?.())
|
Boolean(api && !api.isDestroyed?.())
|
||||||
|
|
||||||
/**
|
|
||||||
* 同步自动行高
|
|
||||||
* 触发 AG Grid 重新计算行高并刷新单元格
|
|
||||||
*/
|
|
||||||
const syncAutoRowHeights = async () => {
|
const syncAutoRowHeights = async () => {
|
||||||
await nextTick()
|
await nextTick()
|
||||||
const api = gridApi.value
|
const api = gridApi.value
|
||||||
@ -501,20 +438,10 @@ const syncAutoRowHeights = async () => {
|
|||||||
api.refreshCells({ force: true })
|
api.refreshCells({ force: true })
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 首次数据渲染完成回调
|
|
||||||
* 同步自动行高以确保显示正确
|
|
||||||
* @param _event 首次数据渲染事件
|
|
||||||
*/
|
|
||||||
const onFirstDataRendered = (_event: FirstDataRenderedEvent<SummaryRow>) => {
|
const onFirstDataRendered = (_event: FirstDataRenderedEvent<SummaryRow>) => {
|
||||||
void syncAutoRowHeights()
|
void syncAutoRowHeights()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 行数据更新回调
|
|
||||||
* 同步自动行高以适应新数据
|
|
||||||
* @param _event 行数据更新事件
|
|
||||||
*/
|
|
||||||
const onRowDataUpdated = (_event: RowDataUpdatedEvent<SummaryRow>) => {
|
const onRowDataUpdated = (_event: RowDataUpdatedEvent<SummaryRow>) => {
|
||||||
void syncAutoRowHeights()
|
void syncAutoRowHeights()
|
||||||
}
|
}
|
||||||
|
|||||||
@ -19,7 +19,6 @@ import TypeLine from '@/layout/typeLine.vue';
|
|||||||
import { useZxFwPricingStore } from '@/pinia/zxFwPricing'
|
import { useZxFwPricingStore } from '@/pinia/zxFwPricing'
|
||||||
import { roundTo, sumNullableNumbers, toFiniteNumber } from '@/lib/decimal'
|
import { roundTo, sumNullableNumbers, toFiniteNumber } from '@/lib/decimal'
|
||||||
import { formatThousands } from '@/lib/numberFormat'
|
import { formatThousands } from '@/lib/numberFormat'
|
||||||
import {useDataStore} from '@/pinia/zx'
|
|
||||||
|
|
||||||
// 1. 完善 Props 类型 + 添加校验(可选但推荐)
|
// 1. 完善 Props 类型 + 添加校验(可选但推荐)
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
@ -153,15 +152,7 @@ const refreshContractBudget = async () => {
|
|||||||
])
|
])
|
||||||
const parts = [serviceFee, additionalFee, reserveFee]
|
const parts = [serviceFee, additionalFee, reserveFee]
|
||||||
const total = sumNullableNumbers(parts)
|
const total = sumNullableNumbers(parts)
|
||||||
|
contractBudget.value = total == null ? null : roundTo(total, 2)
|
||||||
const zxRows = await useDataStore().query([
|
|
||||||
{ field: 'type', value: `${props.contractId}-zxFw`, operator: 'eq' }
|
|
||||||
])
|
|
||||||
const totalFinalFee = zxRows.reduce((sum, row) => {
|
|
||||||
return sum + (Number(row.finalFee) || 0)
|
|
||||||
}, 0)
|
|
||||||
|
|
||||||
contractBudget.value = totalFinalFee == null ? null : roundTo(totalFinalFee, 2)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const budgetRefreshSignature = computed(() => {
|
const budgetRefreshSignature = computed(() => {
|
||||||
@ -343,11 +334,11 @@ const xmCategories = computed<XmCategoryItem[]>(() => [
|
|||||||
{ key: 'base-info', label: t('htCard.categories.baseInfo'), component: htBaseInfoView },
|
{ key: 'base-info', label: t('htCard.categories.baseInfo'), component: htBaseInfoView },
|
||||||
{ key: 'info', label: t('htCard.categories.scaleInfo'), component: htView },
|
{ key: 'info', label: t('htCard.categories.scaleInfo'), component: htView },
|
||||||
{ key: 'contract', label: t('htCard.categories.services'), component: zxfwView },
|
{ key: 'contract', label: t('htCard.categories.services'), component: zxfwView },
|
||||||
// { key: 'consult-category-factor', label: t('htCard.categories.consultFactor'), component: consultCategoryFactorView },
|
{ key: 'consult-category-factor', label: t('htCard.categories.consultFactor'), component: consultCategoryFactorView },
|
||||||
// { key: 'major-factor', label: t('htCard.categories.majorFactor'), component: majorFactorView },
|
{ key: 'major-factor', label: t('htCard.categories.majorFactor'), component: majorFactorView },
|
||||||
// { key: 'additional-work-fee', label: t('htCard.categories.additionalFee'), component: additionalWorkFeeView },
|
{ key: 'additional-work-fee', label: t('htCard.categories.additionalFee'), component: additionalWorkFeeView },
|
||||||
// { key: 'reserve-fee', label: t('htCard.categories.reserveFee'), component: reserveFeeView },
|
{ key: 'reserve-fee', label: t('htCard.categories.reserveFee'), component: reserveFeeView },
|
||||||
// { key: 'all', label: t('htCard.categories.summary'), component: summaryView },
|
{ key: 'all', label: t('htCard.categories.summary'), component: summaryView },
|
||||||
]);
|
]);
|
||||||
|
|
||||||
watch(budgetRefreshSignature, (next, prev) => {
|
watch(budgetRefreshSignature, (next, prev) => {
|
||||||
|
|||||||
@ -16,10 +16,16 @@ const XM_DB_KEY = computed(() => {
|
|||||||
})
|
})
|
||||||
const BASE_INFO_KEY = computed(() => props.projectInfoKey || 'xm-base-info-v1')
|
const BASE_INFO_KEY = computed(() => props.projectInfoKey || 'xm-base-info-v1')
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
|
const titleText = computed(() => `${t('htInfo.scaleDetailTitle')}:${t('htInfo.scaleDetailHint')}`)
|
||||||
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<CommonAgGrid :title="t('htInfo.scaleDetailTitle')" :dbKey="DB_KEY" :xmInfoKey="XM_DB_KEY" :base-info-key="BASE_INFO_KEY"/>
|
<CommonAgGrid
|
||||||
|
:title="titleText"
|
||||||
|
:dbKey="DB_KEY"
|
||||||
|
:xmInfoKey="XM_DB_KEY"
|
||||||
|
:base-info-key="BASE_INFO_KEY"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -310,7 +310,7 @@ const buildDefaultRows = (projectCountValue = getTargetProjectCount()): DetailRo
|
|||||||
consultCategoryFactor: null,
|
consultCategoryFactor: null,
|
||||||
majorFactor: null,
|
majorFactor: null,
|
||||||
workStageFactor: 1,
|
workStageFactor: 1,
|
||||||
workRatio: 100,
|
workRatio: 1,
|
||||||
budgetFee: null,
|
budgetFee: null,
|
||||||
budgetFeeBasic: null,
|
budgetFeeBasic: null,
|
||||||
budgetFeeOptional: null,
|
budgetFeeOptional: null,
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -20,7 +20,6 @@ import MethodUnavailableNotice from '@/features/shared/components/MethodUnavaila
|
|||||||
import { buildProjectScopedSessionKey } from '@/lib/pricingPersistControl'
|
import { buildProjectScopedSessionKey } from '@/lib/pricingPersistControl'
|
||||||
|
|
||||||
import { AG_GRID_LOCALE_CN } from '@ag-grid-community/locale';
|
import { AG_GRID_LOCALE_CN } from '@ag-grid-community/locale';
|
||||||
import {useDataStore} from "@/pinia/zx";
|
|
||||||
|
|
||||||
interface DetailRow {
|
interface DetailRow {
|
||||||
id: string
|
id: string
|
||||||
@ -31,12 +30,9 @@ interface DetailRow {
|
|||||||
workload: number | null
|
workload: number | null
|
||||||
basicFee: number | null
|
basicFee: number | null
|
||||||
budgetBase: string
|
budgetBase: string
|
||||||
budgetReferenceUnitPrice: string | number | null
|
budgetReferenceUnitPrice: string
|
||||||
budgetAdoptedUnitPrice: number | null
|
budgetAdoptedUnitPrice: number | null
|
||||||
consultCategoryFactor: number | null
|
consultCategoryFactor: number | null
|
||||||
cLow: number | null
|
|
||||||
cMid: number | null
|
|
||||||
cHigh: number | null
|
|
||||||
serviceFee: number | null
|
serviceFee: number | null
|
||||||
remark: string
|
remark: string
|
||||||
path: string[]
|
path: string[]
|
||||||
@ -49,7 +45,7 @@ interface XmInfoState {
|
|||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
contractId: string,
|
contractId: string,
|
||||||
contractName: string,
|
|
||||||
serviceId: string | number
|
serviceId: string | number
|
||||||
}>()
|
}>()
|
||||||
const zxFwPricingStore = useZxFwPricingStore()
|
const zxFwPricingStore = useZxFwPricingStore()
|
||||||
@ -64,28 +60,15 @@ let factorDefaultsLoaded = false
|
|||||||
const paneInstanceCreatedAt = Date.now()
|
const paneInstanceCreatedAt = Date.now()
|
||||||
const gridApi = ref<GridApi<DetailRow> | null>(null)
|
const gridApi = ref<GridApi<DetailRow> | null>(null)
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取服务对应的咨询服务分类因子
|
|
||||||
* @returns 咨询服务分类因子值,如果没有则返回 null
|
|
||||||
*/
|
|
||||||
const getDefaultConsultCategoryFactor = () =>
|
const getDefaultConsultCategoryFactor = () =>
|
||||||
consultCategoryFactorMap.value.get(String(props.serviceId)) ?? null
|
consultCategoryFactorMap.value.get(String(props.serviceId)) ?? null
|
||||||
|
|
||||||
/**
|
|
||||||
* 确保咨询服务分类因子默认值已加载
|
|
||||||
* 从合同段的咨询因子配置中加载,避免重复加载
|
|
||||||
*/
|
|
||||||
const ensureFactorDefaultsLoaded = async () => {
|
const ensureFactorDefaultsLoaded = async () => {
|
||||||
if (factorDefaultsLoaded) return
|
if (factorDefaultsLoaded) return
|
||||||
consultCategoryFactorMap.value = await loadConsultCategoryFactorMap(HT_CONSULT_FACTOR_KEY.value)
|
consultCategoryFactorMap.value = await loadConsultCategoryFactorMap(HT_CONSULT_FACTOR_KEY.value)
|
||||||
factorDefaultsLoaded = true
|
factorDefaultsLoaded = true
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 检查是否应该跳过数据持久化
|
|
||||||
* 用于在清空操作后的短时间内避免旧数据回填
|
|
||||||
* @returns 是否应该跳过持久化
|
|
||||||
*/
|
|
||||||
const shouldSkipPersist = () => {
|
const shouldSkipPersist = () => {
|
||||||
const storageKey = buildProjectScopedSessionKey(PRICING_CLEAR_SKIP_PREFIX, DB_KEY.value)
|
const storageKey = buildProjectScopedSessionKey(PRICING_CLEAR_SKIP_PREFIX, DB_KEY.value)
|
||||||
const raw = sessionStorage.getItem(storageKey)
|
const raw = sessionStorage.getItem(storageKey)
|
||||||
@ -109,11 +92,6 @@ const shouldSkipPersist = () => {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 检查是否应该强制加载默认数据
|
|
||||||
* 用于在恢复默认操作后强制使用初始值
|
|
||||||
* @returns 是否应该强制加载默认数据
|
|
||||||
*/
|
|
||||||
const shouldForceDefaultLoad = () => {
|
const shouldForceDefaultLoad = () => {
|
||||||
const storageKey = buildProjectScopedSessionKey(PRICING_FORCE_DEFAULT_PREFIX, DB_KEY.value)
|
const storageKey = buildProjectScopedSessionKey(PRICING_FORCE_DEFAULT_PREFIX, DB_KEY.value)
|
||||||
const raw = sessionStorage.getItem(storageKey)
|
const raw = sessionStorage.getItem(storageKey)
|
||||||
@ -123,12 +101,8 @@ const shouldForceDefaultLoad = () => {
|
|||||||
return Number.isFinite(forceUntil) && Date.now() <= forceUntil
|
return Number.isFinite(forceUntil) && Date.now() <= forceUntil
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取当前服务工作量法的状态
|
|
||||||
* @returns 工作量法状态对象,包含明细行数据
|
|
||||||
*/
|
|
||||||
const getMethodState = () =>
|
const getMethodState = () =>
|
||||||
zxFwPricingStore.getServicePricingMethodState<DetailRow>(props.contractId, props.serviceId, 'serviceFee')
|
zxFwPricingStore.getServicePricingMethodState<DetailRow>(props.contractId, props.serviceId, 'workload')
|
||||||
|
|
||||||
const detailRows = computed<DetailRow[]>({
|
const detailRows = computed<DetailRow[]>({
|
||||||
get: () => {
|
get: () => {
|
||||||
@ -136,7 +110,7 @@ const detailRows = computed<DetailRow[]>({
|
|||||||
return Array.isArray(rows) ? rows : []
|
return Array.isArray(rows) ? rows : []
|
||||||
},
|
},
|
||||||
set: rows => {
|
set: rows => {
|
||||||
zxFwPricingStore.setServicePricingMethodState(props.contractId, props.serviceId, 'serviceFee', {
|
zxFwPricingStore.setServicePricingMethodState(props.contractId, props.serviceId, 'workload', {
|
||||||
detailRows: rows
|
detailRows: rows
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -153,17 +127,9 @@ type taskLite = {
|
|||||||
maxPrice: number | null
|
maxPrice: number | null
|
||||||
minPrice: number | null
|
minPrice: number | null
|
||||||
defPrice: number | null
|
defPrice: number | null
|
||||||
midPrice: number | null
|
|
||||||
highPrice: number | null
|
|
||||||
desc: string | null
|
desc: string | null
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取任务显示名称
|
|
||||||
* 根据当前语言环境返回中文或英文名称
|
|
||||||
* @param task 任务对象
|
|
||||||
* @returns 任务显示名称
|
|
||||||
*/
|
|
||||||
const getTaskDisplayName = (task: taskLite | undefined) => {
|
const getTaskDisplayName = (task: taskLite | undefined) => {
|
||||||
if (!task) return ''
|
if (!task) return ''
|
||||||
return String(locale.value).toLowerCase().startsWith('en')
|
return String(locale.value).toLowerCase().startsWith('en')
|
||||||
@ -171,12 +137,6 @@ const getTaskDisplayName = (task: taskLite | undefined) => {
|
|||||||
: task.name
|
: task.name
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 格式化任务参考单价
|
|
||||||
* 根据任务的最小/最大价格生成价格区间字符串
|
|
||||||
* @param task 任务对象
|
|
||||||
* @returns 格式化后的价格区间字符串(如 "100元-200元")
|
|
||||||
*/
|
|
||||||
const formatTaskReferenceUnitPrice = (task: taskLite) => {
|
const formatTaskReferenceUnitPrice = (task: taskLite) => {
|
||||||
const unit = task.unit || ''
|
const unit = task.unit || ''
|
||||||
const hasMin = typeof task.minPrice === 'number' && Number.isFinite(task.minPrice)
|
const hasMin = typeof task.minPrice === 'number' && Number.isFinite(task.minPrice)
|
||||||
@ -187,16 +147,8 @@ const formatTaskReferenceUnitPrice = (task: taskLite) => {
|
|||||||
return ''
|
return ''
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取当前服务对应的工作任务 ID 列表
|
|
||||||
* 从 taskList 中筛选出属于当前服务的任务,并按 ID 排序
|
|
||||||
* @returns 任务 ID 数组
|
|
||||||
*/
|
|
||||||
const getSourceTaskIds = () => {
|
const getSourceTaskIds = () => {
|
||||||
// 从 serviceId 中提取最后的数字部分,例如:ct-1776069559338-f621-zx-9 -> 9
|
const currentServiceId = Number(props.serviceId)
|
||||||
const serviceIdStr = String(props.serviceId)
|
|
||||||
const match = serviceIdStr.match(/-(\d+)$/)
|
|
||||||
const currentServiceId = match ? Number(match[1]) : NaN
|
|
||||||
return Object.entries(taskList as Record<string, taskLite>)
|
return Object.entries(taskList as Record<string, taskLite>)
|
||||||
.filter(([, task]) => Number(task.serviceID) === currentServiceId)
|
.filter(([, task]) => Number(task.serviceID) === currentServiceId)
|
||||||
.map(([key]) => Number(key))
|
.map(([key]) => Number(key))
|
||||||
@ -206,11 +158,6 @@ const getSourceTaskIds = () => {
|
|||||||
|
|
||||||
const isWorkloadMethodApplicable = computed(() => getSourceTaskIds().length > 0)
|
const isWorkloadMethodApplicable = computed(() => getSourceTaskIds().length > 0)
|
||||||
|
|
||||||
/**
|
|
||||||
* 构建默认明细行数据
|
|
||||||
* 根据当前服务的任务列表生成初始行数据
|
|
||||||
* @returns 默认明细行数组
|
|
||||||
*/
|
|
||||||
const buildDefaultRows = (): DetailRow[] => {
|
const buildDefaultRows = (): DetailRow[] => {
|
||||||
const rows: DetailRow[] = []
|
const rows: DetailRow[] = []
|
||||||
const sourceTaskIds = getSourceTaskIds()
|
const sourceTaskIds = getSourceTaskIds()
|
||||||
@ -219,24 +166,22 @@ const buildDefaultRows = (): DetailRow[] => {
|
|||||||
const task = (taskList as Record<string, taskLite | undefined>)[String(taskId)]
|
const task = (taskList as Record<string, taskLite | undefined>)[String(taskId)]
|
||||||
const taskCode = task?.code || task?.ref || ''
|
const taskCode = task?.code || task?.ref || ''
|
||||||
if (!taskCode || !task?.name) continue
|
if (!taskCode || !task?.name) continue
|
||||||
const contractId = props.contractId;
|
const rowId = `task-${taskId}-${order}`
|
||||||
const rowId = `${contractId}-task-${taskId}`
|
|
||||||
rows.push({
|
rows.push({
|
||||||
id: rowId,
|
id: rowId,
|
||||||
taskCode,
|
taskCode,
|
||||||
taskName: getTaskDisplayName(task),
|
taskName: getTaskDisplayName(task),
|
||||||
unit: task.unit || '',
|
unit: task.unit || '',
|
||||||
conversion: typeof task.conversion === 'number' && Number.isFinite(task.conversion) ? task.conversion : null,
|
conversion: typeof task.conversion === 'number' && Number.isFinite(task.conversion) ? task.conversion : null,
|
||||||
workload: typeof task.midPrice === 'number' && Number.isFinite(task.midPrice) ? task.midPrice : null,
|
workload: null,
|
||||||
basicFee: null,
|
basicFee: null,
|
||||||
budgetBase: task.basicParam || '',
|
budgetBase: task.basicParam || '',
|
||||||
budgetReferenceUnitPrice: null,
|
budgetReferenceUnitPrice: formatTaskReferenceUnitPrice(task),
|
||||||
budgetAdoptedUnitPrice:
|
budgetAdoptedUnitPrice:
|
||||||
typeof task.defPrice === 'number' && Number.isFinite(task.defPrice) ? task.defPrice : null,
|
typeof task.defPrice === 'number' && Number.isFinite(task.defPrice) ? task.defPrice : null,
|
||||||
consultCategoryFactor: typeof task.highPrice === 'number' && Number.isFinite(task.highPrice) ? task.highPrice : null,
|
consultCategoryFactor: getDefaultConsultCategoryFactor(),
|
||||||
serviceFee: null,
|
serviceFee: null,
|
||||||
remark: task.desc|| '',
|
remark: task.desc|| '',
|
||||||
type: 'task',
|
|
||||||
path: [rowId]
|
path: [rowId]
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -246,12 +191,6 @@ const buildDefaultRows = (): DetailRow[] => {
|
|||||||
|
|
||||||
const isNoTaskRow = (row: DetailRow | undefined) => row?.id?.startsWith('task-none-') ?? false
|
const isNoTaskRow = (row: DetailRow | undefined) => row?.id?.startsWith('task-none-') ?? false
|
||||||
|
|
||||||
/**
|
|
||||||
* 合并数据库中的行数据与默认行数据
|
|
||||||
* 保留用户编辑的值(工作量、单价、因子等),缺失时使用默认值
|
|
||||||
* @param rowsFromDb 从数据库加载的行数据
|
|
||||||
* @returns 合并后的明细行数组
|
|
||||||
*/
|
|
||||||
const mergeWithDictRows = (rowsFromDb: DetailRow[] | undefined): DetailRow[] => {
|
const mergeWithDictRows = (rowsFromDb: DetailRow[] | undefined): DetailRow[] => {
|
||||||
const dbValueMap = new Map<string, DetailRow>()
|
const dbValueMap = new Map<string, DetailRow>()
|
||||||
for (const row of rowsFromDb || []) {
|
for (const row of rowsFromDb || []) {
|
||||||
@ -263,38 +202,20 @@ const mergeWithDictRows = (rowsFromDb: DetailRow[] | undefined): DetailRow[] =>
|
|||||||
if (!fromDb) return row
|
if (!fromDb) return row
|
||||||
const hasRemark = Object.prototype.hasOwnProperty.call(fromDb, 'remark')
|
const hasRemark = Object.prototype.hasOwnProperty.call(fromDb, 'remark')
|
||||||
const hasConsultCategoryFactor = Object.prototype.hasOwnProperty.call(fromDb, 'consultCategoryFactor')
|
const hasConsultCategoryFactor = Object.prototype.hasOwnProperty.call(fromDb, 'consultCategoryFactor')
|
||||||
// ✅ 计算 cLow 值
|
|
||||||
let cLowValue: number | null = null
|
|
||||||
let cMidValue: number | null = null
|
|
||||||
let cHighValue: number | null = null
|
|
||||||
const refPrice = fromDb.budgetReferenceUnitPrice
|
|
||||||
const lowPrice = row.budgetAdoptedUnitPrice
|
|
||||||
const midPrice = row.workload
|
|
||||||
const highPrice = row.consultCategoryFactor
|
|
||||||
cLowValue = refPrice * lowPrice
|
|
||||||
cMidValue = refPrice * midPrice
|
|
||||||
cHighValue = refPrice * highPrice
|
|
||||||
let serviceFee = cMidValue
|
|
||||||
|
|
||||||
if (fromDb.serviceFee != null && fromDb.serviceFee != 0) {
|
|
||||||
serviceFee = fromDb.serviceFee
|
|
||||||
}
|
|
||||||
return {
|
return {
|
||||||
...row,
|
...row,
|
||||||
workload: typeof fromDb.workload === 'number' ? fromDb.workload : null,
|
workload: typeof fromDb.workload === 'number' ? fromDb.workload : null,
|
||||||
basicFee: typeof fromDb.basicFee === 'number' ? fromDb.basicFee : null,
|
basicFee: typeof fromDb.basicFee === 'number' ? fromDb.basicFee : null,
|
||||||
budgetReferenceUnitPrice: fromDb.budgetReferenceUnitPrice ?? row.budgetReferenceUnitPrice,
|
budgetAdoptedUnitPrice:
|
||||||
budgetAdoptedUnitPrice: 1, // 固定为 1
|
typeof fromDb.budgetAdoptedUnitPrice === 'number' ? fromDb.budgetAdoptedUnitPrice : null,
|
||||||
cLow: cLowValue,
|
|
||||||
cMid: cMidValue,
|
|
||||||
cHigh: cHighValue,
|
|
||||||
consultCategoryFactor:
|
consultCategoryFactor:
|
||||||
typeof fromDb.consultCategoryFactor === 'number'
|
typeof fromDb.consultCategoryFactor === 'number'
|
||||||
? fromDb.consultCategoryFactor
|
? fromDb.consultCategoryFactor
|
||||||
: hasConsultCategoryFactor
|
: hasConsultCategoryFactor
|
||||||
? null
|
? null
|
||||||
: getDefaultConsultCategoryFactor(),
|
: getDefaultConsultCategoryFactor(),
|
||||||
serviceFee: serviceFee,
|
serviceFee: typeof fromDb.serviceFee === 'number' ? fromDb.serviceFee : null,
|
||||||
remark: typeof fromDb.remark === 'string' ? fromDb.remark : hasRemark ? '' : row.remark
|
remark: typeof fromDb.remark === 'string' ? fromDb.remark : hasRemark ? '' : row.remark
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -306,12 +227,6 @@ const parseSanitizedNumberOrNull = (value: unknown) =>
|
|||||||
const parseSanitizedAdoptedPriceOrNull = (value: unknown) =>
|
const parseSanitizedAdoptedPriceOrNull = (value: unknown) =>
|
||||||
parseNumberOrNull(value, { sanitize: true, precision: 6 })
|
parseNumberOrNull(value, { sanitize: true, precision: 6 })
|
||||||
|
|
||||||
/**
|
|
||||||
* 计算基础费用
|
|
||||||
* 公式:基础费用 = 采用单价 × 换算系数 × 工作量
|
|
||||||
* @param row 明细行数据
|
|
||||||
* @returns 基础费用(保留2位小数),如果数据不完整则返回 null
|
|
||||||
*/
|
|
||||||
const calcBasicFee = (row: DetailRow | undefined) => {
|
const calcBasicFee = (row: DetailRow | undefined) => {
|
||||||
if (!row || isNoTaskRow(row)) return null
|
if (!row || isNoTaskRow(row)) return null
|
||||||
const price = row.budgetAdoptedUnitPrice
|
const price = row.budgetAdoptedUnitPrice
|
||||||
@ -330,12 +245,6 @@ const calcBasicFee = (row: DetailRow | undefined) => {
|
|||||||
return roundTo(toDecimal(price).mul(conversion).mul(workload), 2)
|
return roundTo(toDecimal(price).mul(conversion).mul(workload), 2)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 计算服务费用
|
|
||||||
* 公式:服务费用 = 基础费用 × 咨询服务分类因子
|
|
||||||
* @param row 明细行数据
|
|
||||||
* @returns 服务费用(保留2位小数),如果数据不完整则返回 null
|
|
||||||
*/
|
|
||||||
const calcServiceFee = (row: DetailRow | undefined) => {
|
const calcServiceFee = (row: DetailRow | undefined) => {
|
||||||
if (!row || isNoTaskRow(row)) return null
|
if (!row || isNoTaskRow(row)) return null
|
||||||
const factor = row.consultCategoryFactor
|
const factor = row.consultCategoryFactor
|
||||||
@ -350,12 +259,6 @@ const calcServiceFee = (row: DetailRow | undefined) => {
|
|||||||
return roundTo(toDecimal(basicFee).mul(factor), 2)
|
return roundTo(toDecimal(basicFee).mul(factor), 2)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 格式化可编辑数字单元格
|
|
||||||
* 根据行类型和值状态返回相应的显示文本
|
|
||||||
* @param params AG Grid 单元格参数
|
|
||||||
* @returns 格式化后的显示文本
|
|
||||||
*/
|
|
||||||
const formatEditableNumber = (params: any) => {
|
const formatEditableNumber = (params: any) => {
|
||||||
if (isNoTaskRow(params.data)) return t('workloadPricing.none')
|
if (isNoTaskRow(params.data)) return t('workloadPricing.none')
|
||||||
if (!params.node?.group && !params.node?.rowPinned && (params.value == null || params.value === '')) {
|
if (!params.node?.group && !params.node?.rowPinned && (params.value == null || params.value === '')) {
|
||||||
@ -365,12 +268,6 @@ const formatEditableNumber = (params: any) => {
|
|||||||
return formatThousandsFlexible(params.value, 3)
|
return formatThousandsFlexible(params.value, 3)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 判断两行是否应该合并显示(按任务名称和预算基数)
|
|
||||||
* 用于 AG Grid 的 spanRows 功能,相同任务和预算基数的行合并显示
|
|
||||||
* @param params AG Grid 行比较参数
|
|
||||||
* @returns 是否应该合并
|
|
||||||
*/
|
|
||||||
const spanRowsByTaskName = (params: any) => {
|
const spanRowsByTaskName = (params: any) => {
|
||||||
const rowA = params?.nodeA?.data as DetailRow | undefined
|
const rowA = params?.nodeA?.data as DetailRow | undefined
|
||||||
const rowB = params?.nodeB?.data as DetailRow | undefined
|
const rowB = params?.nodeB?.data as DetailRow | undefined
|
||||||
@ -388,6 +285,9 @@ const columnDefs: ColDef<DetailRow>[] = [
|
|||||||
width: 120,
|
width: 120,
|
||||||
pinned: 'left',
|
pinned: 'left',
|
||||||
colSpan: params => (params.node?.rowPinned ? 2 : 1),
|
colSpan: params => (params.node?.rowPinned ? 2 : 1),
|
||||||
|
cellClassRules: {
|
||||||
|
'ag-summary-label-cell': params => Boolean(params.node?.rowPinned)
|
||||||
|
},
|
||||||
valueFormatter: params => (params.node?.rowPinned ? t('workloadPricing.total') : params.value || '')
|
valueFormatter: params => (params.node?.rowPinned ? t('workloadPricing.total') : params.value || '')
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -407,7 +307,8 @@ const columnDefs: ColDef<DetailRow>[] = [
|
|||||||
headerName: t('workloadPricing.columns.budgetBase'),
|
headerName: t('workloadPricing.columns.budgetBase'),
|
||||||
field: 'budgetBase',
|
field: 'budgetBase',
|
||||||
minWidth: 150,
|
minWidth: 150,
|
||||||
autoHeight: true,
|
autoHeight: true,
|
||||||
|
|
||||||
width: 180,
|
width: 180,
|
||||||
colSpan: params => (params.node?.rowPinned ? 3 : 1),
|
colSpan: params => (params.node?.rowPinned ? 3 : 1),
|
||||||
spanRows: spanRowsByTaskName,
|
spanRows: spanRowsByTaskName,
|
||||||
@ -418,16 +319,15 @@ const columnDefs: ColDef<DetailRow>[] = [
|
|||||||
field: 'budgetReferenceUnitPrice',
|
field: 'budgetReferenceUnitPrice',
|
||||||
minWidth: 170,
|
minWidth: 170,
|
||||||
flex: 1,
|
flex: 1,
|
||||||
editable: params => !params.node?.group && !params.node?.rowPinned && !isNoTaskRow(params.data),
|
valueFormatter: params => params.value || ''
|
||||||
valueFormatter: formatEditableNumber
|
|
||||||
},
|
},
|
||||||
/*{
|
{
|
||||||
headerName: t('workloadPricing.columns.budgetAdoptedUnitPrice'),
|
headerName: t('workloadPricing.columns.budgetAdoptedUnitPrice'),
|
||||||
field: 'budgetAdoptedUnitPrice',
|
field: 'budgetAdoptedUnitPrice',
|
||||||
headerClass: 'ag-right-aligned-header',
|
headerClass: 'ag-right-aligned-header',
|
||||||
minWidth: 170,
|
minWidth: 170,
|
||||||
flex: 1,
|
flex: 1,
|
||||||
// editable: params => !params.node?.group && !params.node?.rowPinned && !isNoTaskRow(params.data),
|
editable: params => !params.node?.group && !params.node?.rowPinned && !isNoTaskRow(params.data),
|
||||||
cellClass: params => (!params.node?.group && !params.node?.rowPinned ? 'editable-cell-line' : ''),
|
cellClass: params => (!params.node?.group && !params.node?.rowPinned ? 'editable-cell-line' : ''),
|
||||||
cellClassRules: {
|
cellClassRules: {
|
||||||
'ag-right-aligned-cell': () => true,
|
'ag-right-aligned-cell': () => true,
|
||||||
@ -447,58 +347,8 @@ const columnDefs: ColDef<DetailRow>[] = [
|
|||||||
const unit = params.data?.unit || ''
|
const unit = params.data?.unit || ''
|
||||||
return `${formatThousandsFlexible(params.value, 6)}${unit}`
|
return `${formatThousandsFlexible(params.value, 6)}${unit}`
|
||||||
}
|
}
|
||||||
},*/
|
|
||||||
{
|
|
||||||
headerName: t('workloadPricing.columns.budgetAdoptedUnitPrice'),
|
|
||||||
field: 'budgetAdoptedUnitPrice',
|
|
||||||
headerClass: 'ag-right-aligned-header',
|
|
||||||
minWidth: 170,
|
|
||||||
flex: 1,
|
|
||||||
// ❌ 移除 editable 或设置为 false
|
|
||||||
editable: false,
|
|
||||||
// ✅ 添加单元格样式
|
|
||||||
cellClass: 'ag-right-aligned-cell',
|
|
||||||
// ✅ 移除 cellClassRules 中的 editable-cell 相关规则
|
|
||||||
cellClassRules: {
|
|
||||||
'ag-right-aligned-cell': () => true
|
|
||||||
},
|
|
||||||
// ✅ 固定显示值为 1
|
|
||||||
valueFormatter: params => {
|
|
||||||
const unit = params.data?.unit || ''
|
|
||||||
return `1`
|
|
||||||
// return `1${unit}`
|
|
||||||
},
|
|
||||||
// ✅ 阻止任何编辑尝试
|
|
||||||
valueParser: () => 1,
|
|
||||||
// ✅ 禁用单元格编辑
|
|
||||||
cellEditor: undefined
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
headerName: t('workloadPricing.columns.workload'),
|
|
||||||
field: 'workload',
|
|
||||||
headerClass: 'ag-right-aligned-header',
|
|
||||||
minWidth: 170,
|
|
||||||
flex: 1,
|
|
||||||
// ❌ 移除 editable 或设置为 false
|
|
||||||
editable: false,
|
|
||||||
// ✅ 添加单元格样式
|
|
||||||
cellClass: 'ag-right-aligned-cell',
|
|
||||||
// ✅ 移除 cellClassRules 中的 editable-cell 相关规则
|
|
||||||
cellClassRules: {
|
|
||||||
'ag-right-aligned-cell': () => true
|
|
||||||
},
|
|
||||||
// ✅ 固定显示值为 1
|
|
||||||
valueFormatter: params => {
|
|
||||||
const unit = params.data?.unit || ''
|
|
||||||
return `2`
|
|
||||||
// return `1${unit}`
|
|
||||||
},
|
|
||||||
// ✅ 阻止任何编辑尝试
|
|
||||||
valueParser: () => 1,
|
|
||||||
// ✅ 禁用单元格编辑
|
|
||||||
cellEditor: undefined
|
|
||||||
},
|
|
||||||
/*{
|
|
||||||
headerName: t('workloadPricing.columns.workload'),
|
headerName: t('workloadPricing.columns.workload'),
|
||||||
field: 'workload',
|
field: 'workload',
|
||||||
minWidth: 140,
|
minWidth: 140,
|
||||||
@ -517,33 +367,8 @@ const columnDefs: ColDef<DetailRow>[] = [
|
|||||||
aggFunc: decimalAggSum,
|
aggFunc: decimalAggSum,
|
||||||
valueParser: params => parseSanitizedNumberOrNull(params.newValue),
|
valueParser: params => parseSanitizedNumberOrNull(params.newValue),
|
||||||
valueFormatter: formatEditableNumber
|
valueFormatter: formatEditableNumber
|
||||||
},*/
|
|
||||||
{
|
|
||||||
headerName: t('workloadPricing.columns.consultCategoryFactor'),
|
|
||||||
field: 'consultCategoryFactor',
|
|
||||||
headerClass: 'ag-right-aligned-header',
|
|
||||||
minWidth: 170,
|
|
||||||
flex: 1,
|
|
||||||
// ❌ 移除 editable 或设置为 false
|
|
||||||
editable: false,
|
|
||||||
// ✅ 添加单元格样式
|
|
||||||
cellClass: 'ag-right-aligned-cell',
|
|
||||||
// ✅ 移除 cellClassRules 中的 editable-cell 相关规则
|
|
||||||
cellClassRules: {
|
|
||||||
'ag-right-aligned-cell': () => true
|
|
||||||
},
|
|
||||||
// ✅ 固定显示值为 1
|
|
||||||
valueFormatter: params => {
|
|
||||||
const unit = params.data?.unit || ''
|
|
||||||
return `3`
|
|
||||||
// return `1${unit}`
|
|
||||||
},
|
|
||||||
// ✅ 阻止任何编辑尝试
|
|
||||||
valueParser: () => 1,
|
|
||||||
// ✅ 禁用单元格编辑
|
|
||||||
cellEditor: undefined
|
|
||||||
},
|
},
|
||||||
/*{
|
{
|
||||||
headerName: t('workloadPricing.columns.consultCategoryFactor'),
|
headerName: t('workloadPricing.columns.consultCategoryFactor'),
|
||||||
field: 'consultCategoryFactor',
|
field: 'consultCategoryFactor',
|
||||||
width: 80,
|
width: 80,
|
||||||
@ -562,54 +387,6 @@ const columnDefs: ColDef<DetailRow>[] = [
|
|||||||
},
|
},
|
||||||
valueParser: params => parseSanitizedNumberOrNull(params.newValue),
|
valueParser: params => parseSanitizedNumberOrNull(params.newValue),
|
||||||
valueFormatter: formatEditableNumber
|
valueFormatter: formatEditableNumber
|
||||||
},*/
|
|
||||||
{
|
|
||||||
headerName: t('workloadPricing.columns.cLow'),
|
|
||||||
field: 'cLow',
|
|
||||||
width: 100,
|
|
||||||
minWidth: 70,
|
|
||||||
maxWidth: 120,
|
|
||||||
// ✅ 设置为不可编辑
|
|
||||||
editable: false,
|
|
||||||
// ✅ 只读样式
|
|
||||||
cellClass: 'ag-right-aligned-cell',
|
|
||||||
cellClassRules: {
|
|
||||||
'ag-right-aligned-cell': () => true
|
|
||||||
}/*,
|
|
||||||
valueParser: params => parseSanitizedNumberOrNull(params.newValue),
|
|
||||||
valueFormatter: formatEditableNumber*/
|
|
||||||
},
|
|
||||||
{
|
|
||||||
headerName: t('workloadPricing.columns.cMid'),
|
|
||||||
field: 'cMid',
|
|
||||||
width: 100,
|
|
||||||
minWidth: 70,
|
|
||||||
maxWidth: 120,
|
|
||||||
// ✅ 设置为不可编辑
|
|
||||||
editable: false,
|
|
||||||
// ✅ 只读样式
|
|
||||||
cellClass: 'ag-right-aligned-cell',
|
|
||||||
cellClassRules: {
|
|
||||||
'ag-right-aligned-cell': () => true
|
|
||||||
}/*,
|
|
||||||
valueParser: params => parseSanitizedNumberOrNull(params.newValue),
|
|
||||||
valueFormatter: formatEditableNumber*/
|
|
||||||
},
|
|
||||||
{
|
|
||||||
headerName: t('workloadPricing.columns.cHigh'),
|
|
||||||
field: 'cHigh',
|
|
||||||
width: 100,
|
|
||||||
minWidth: 70,
|
|
||||||
maxWidth: 120,
|
|
||||||
// ✅ 设置为不可编辑
|
|
||||||
editable: false,
|
|
||||||
// ✅ 只读样式
|
|
||||||
cellClass: 'ag-right-aligned-cell',
|
|
||||||
cellClassRules: {
|
|
||||||
'ag-right-aligned-cell': () => true
|
|
||||||
}/*,
|
|
||||||
valueParser: params => parseSanitizedNumberOrNull(params.newValue),
|
|
||||||
valueFormatter: formatEditableNumber*/
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
headerName: t('workloadPricing.columns.serviceFee'),
|
headerName: t('workloadPricing.columns.serviceFee'),
|
||||||
@ -617,19 +394,17 @@ const columnDefs: ColDef<DetailRow>[] = [
|
|||||||
headerClass: 'ag-right-aligned-header',
|
headerClass: 'ag-right-aligned-header',
|
||||||
minWidth: 150,
|
minWidth: 150,
|
||||||
flex: 1,
|
flex: 1,
|
||||||
editable: params => !params.node?.group && !params.node?.rowPinned && !isNoTaskRow(params.data),
|
editable: false,
|
||||||
cellClass: params => (!params.node?.group && !params.node?.rowPinned ? 'editable-cell-line' : ''),
|
|
||||||
cellClassRules: {
|
cellClassRules: {
|
||||||
'ag-right-aligned-cell': () => true,
|
'ag-right-aligned-cell': () => true
|
||||||
|
|
||||||
'editable-cell-empty': params =>
|
|
||||||
!params.node?.group &&
|
|
||||||
!params.node?.rowPinned &&
|
|
||||||
!isNoTaskRow(params.data) &&
|
|
||||||
(params.value == null || params.value === '')
|
|
||||||
},
|
},
|
||||||
valueParser: params => parseSanitizedNumberOrNull(params.newValue),
|
valueGetter: params => (params.node?.rowPinned ? params.data?.serviceFee ?? null : calcServiceFee(params.data)),
|
||||||
valueFormatter: formatEditableNumber
|
aggFunc: decimalAggSum,
|
||||||
|
valueFormatter: params => {
|
||||||
|
if (isNoTaskRow(params.data)) return t('workloadPricing.none')
|
||||||
|
if (params.value == null || params.value === '') return ''
|
||||||
|
return formatThousandsFlexible(roundTo(params.value, 3), 3)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
headerName: t('workloadPricing.columns.remark'),
|
headerName: t('workloadPricing.columns.remark'),
|
||||||
@ -658,9 +433,7 @@ const columnDefs: ColDef<DetailRow>[] = [
|
|||||||
]
|
]
|
||||||
const gridColumnDefs = computed(() => withReadonlyAutoHeight(columnDefs))
|
const gridColumnDefs = computed(() => withReadonlyAutoHeight(columnDefs))
|
||||||
|
|
||||||
const totalWorkload = computed(() => {
|
const totalWorkload = computed(() => sumByNumber(detailRows.value, row => row.workload))
|
||||||
return 1;
|
|
||||||
})
|
|
||||||
const totalBasicFee = computed(() => sumByNumber(detailRows.value, row => calcBasicFee(row)))
|
const totalBasicFee = computed(() => sumByNumber(detailRows.value, row => calcBasicFee(row)))
|
||||||
const totalServiceFee = computed(() => sumNullableBy(detailRows.value, row => calcServiceFee(row)))
|
const totalServiceFee = computed(() => sumNullableBy(detailRows.value, row => calcServiceFee(row)))
|
||||||
const pinnedTopRowData = computed(() =>
|
const pinnedTopRowData = computed(() =>
|
||||||
@ -684,63 +457,44 @@ const pinnedTopRowData = computed(() =>
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 构建用于持久化的明细行数据
|
|
||||||
* 计算并附加基础费用和服务费用字段
|
|
||||||
* @returns 包含计算字段的明细行数组
|
|
||||||
*/
|
|
||||||
const buildPersistDetailRows = () =>
|
const buildPersistDetailRows = () =>
|
||||||
detailRows.value.map(row => ({
|
detailRows.value.map(row => ({
|
||||||
...row,
|
...row,
|
||||||
basicFee: calcBasicFee(row),
|
basicFee: calcBasicFee(row),
|
||||||
serviceFee: row.serviceFee
|
serviceFee: calcServiceFee(row)
|
||||||
}))
|
}))
|
||||||
|
|
||||||
/**
|
|
||||||
* 保存数据到 IndexedDB 并同步到 zxFw store
|
|
||||||
* 保存时机:单元格编辑、批量操作结束、组件失活/卸载时
|
|
||||||
* 同步工作量法的总服务费用到咨询服务汇总表
|
|
||||||
*/
|
|
||||||
const saveToIndexedDB = async () => {
|
const saveToIndexedDB = async () => {
|
||||||
|
if (!isWorkloadMethodApplicable.value) return
|
||||||
|
if (shouldSkipPersist()) return
|
||||||
try {
|
try {
|
||||||
const rows = detailRows.value.map(row => ({ ...row, type: `${props.contractId}-task`}))
|
const payload = {
|
||||||
const stats = await useDataStore().upsertBatch(rows)
|
detailRows: JSON.parse(JSON.stringify(buildPersistDetailRows()))
|
||||||
console.log('💾 数据保存成功:', stats)
|
}
|
||||||
/*zxFwPricingStore.setServicePricingMethodState(
|
zxFwPricingStore.setServicePricingMethodState(
|
||||||
props.contractId,
|
props.contractId,
|
||||||
props.serviceId,
|
props.serviceId,
|
||||||
'serviceFee',
|
'workload',
|
||||||
payload,
|
payload,
|
||||||
{ force: true }
|
{ force: true }
|
||||||
)*/
|
)
|
||||||
// const synced = await syncPricingTotalToZxFw({
|
const synced = await syncPricingTotalToZxFw({
|
||||||
// contractId: props.contractId,
|
contractId: props.contractId,
|
||||||
// serviceId: props.serviceId,
|
serviceId: props.serviceId,
|
||||||
// field: 'serviceFee',
|
field: 'workload',
|
||||||
// value: totalWorkload.value
|
value: totalServiceFee.value
|
||||||
// })
|
})
|
||||||
// if (!synced) return
|
if (!synced) return
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('saveToIndexedDB failed:', error)
|
console.error('saveToIndexedDB failed:', error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 判断两个可空数值是否相等(考虑精度)
|
|
||||||
* @param left 第一个数值
|
|
||||||
* @param right 第二个数值
|
|
||||||
* @returns 是否相等
|
|
||||||
*/
|
|
||||||
const isSameNullableNumber = (left: number | null | undefined, right: number | null | undefined) => {
|
const isSameNullableNumber = (left: number | null | undefined, right: number | null | undefined) => {
|
||||||
if (left == null && right == null) return true
|
if (left == null && right == null) return true
|
||||||
if (left == null || right == null) return false
|
if (left == null || right == null) return false
|
||||||
return roundTo(left, 6) === roundTo(right, 6)
|
return roundTo(left, 6) === roundTo(right, 6)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 从合同段同步咨询服务分类因子
|
|
||||||
* 当合同段的咨询因子配置变化时,更新当前工作量的因子默认值
|
|
||||||
*/
|
|
||||||
const syncLinkedConsultFactorFromHt = async () => {
|
const syncLinkedConsultFactorFromHt = async () => {
|
||||||
if (!isWorkloadMethodApplicable.value || detailRows.value.length === 0) return
|
if (!isWorkloadMethodApplicable.value || detailRows.value.length === 0) return
|
||||||
consultCategoryFactorMap.value = await loadConsultCategoryFactorMap(HT_CONSULT_FACTOR_KEY.value)
|
consultCategoryFactorMap.value = await loadConsultCategoryFactorMap(HT_CONSULT_FACTOR_KEY.value)
|
||||||
@ -766,30 +520,23 @@ const linkedConsultFactorSignature = computed(() => JSON.stringify({
|
|||||||
?? null
|
?? null
|
||||||
}))
|
}))
|
||||||
|
|
||||||
/**
|
|
||||||
* 从 IndexedDB 加载工作量法数据
|
|
||||||
* 加载时机:组件挂载、激活、storageKey 变化时
|
|
||||||
* 优先加载历史数据,没有则使用默认数据
|
|
||||||
*/
|
|
||||||
const loadFromIndexedDB = async () => {
|
const loadFromIndexedDB = async () => {
|
||||||
try {
|
try {
|
||||||
if (!isWorkloadMethodApplicable.value) {
|
if (!isWorkloadMethodApplicable.value) {
|
||||||
detailRows.value = []
|
detailRows.value = []
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const taskRows = await useDataStore().query([
|
|
||||||
{ field: 'type', value: `${props.contractId}-task`, operator: 'eq' }
|
await ensureFactorDefaultsLoaded()
|
||||||
])
|
|
||||||
/*await ensureFactorDefaultsLoaded()
|
|
||||||
|
|
||||||
if (shouldForceDefaultLoad()) {
|
if (shouldForceDefaultLoad()) {
|
||||||
detailRows.value = buildDefaultRows()
|
detailRows.value = buildDefaultRows()
|
||||||
return
|
return
|
||||||
}*/
|
}
|
||||||
|
|
||||||
// const data = await zxFwPricingStore.loadServicePricingMethodState<DetailRow>(props.contractId, props.serviceId, 'serviceFee')
|
const data = await zxFwPricingStore.loadServicePricingMethodState<DetailRow>(props.contractId, props.serviceId, 'workload')
|
||||||
if (taskRows) {
|
if (data) {
|
||||||
detailRows.value = mergeWithDictRows(taskRows)
|
detailRows.value = mergeWithDictRows(data.detailRows)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -800,10 +547,6 @@ const loadFromIndexedDB = async () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 根据任务词典更新行标签
|
|
||||||
* 当语言切换或任务词典更新时,同步更新任务名称、单位等信息
|
|
||||||
*/
|
|
||||||
const relabelRowsFromTaskDict = async () => {
|
const relabelRowsFromTaskDict = async () => {
|
||||||
if (!isWorkloadMethodApplicable.value || detailRows.value.length === 0) return
|
if (!isWorkloadMethodApplicable.value || detailRows.value.length === 0) return
|
||||||
let changed = false
|
let changed = false
|
||||||
@ -841,46 +584,24 @@ const relabelRowsFromTaskDict = async () => {
|
|||||||
|
|
||||||
let isBulkClipboardMutation = false
|
let isBulkClipboardMutation = false
|
||||||
|
|
||||||
/**
|
|
||||||
* 提交网格变更并保存
|
|
||||||
* 在单元格编辑完成后调用,触发数据持久化
|
|
||||||
*/
|
|
||||||
const commitGridChanges = () => {
|
const commitGridChanges = () => {
|
||||||
void saveToIndexedDB()
|
void saveToIndexedDB()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 处理单元格值变化事件
|
|
||||||
* 触发时机:用户编辑完单元格后
|
|
||||||
* 批量操作期间跳过,避免重复保存
|
|
||||||
*/
|
|
||||||
const handleCellValueChanged = () => {
|
const handleCellValueChanged = () => {
|
||||||
if (isBulkClipboardMutation) return
|
if (isBulkClipboardMutation) return
|
||||||
commitGridChanges()
|
commitGridChanges()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 处理批量粘贴/填充操作开始
|
|
||||||
* 设置标志位,避免在批量操作过程中频繁保存
|
|
||||||
*/
|
|
||||||
const handleBulkMutationStart = () => {
|
const handleBulkMutationStart = () => {
|
||||||
isBulkClipboardMutation = true
|
isBulkClipboardMutation = true
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 处理批量粘贴/填充操作结束
|
|
||||||
* 清除标志位并保存所有变更
|
|
||||||
*/
|
|
||||||
const handleBulkMutationEnd = () => {
|
const handleBulkMutationEnd = () => {
|
||||||
isBulkClipboardMutation = false
|
isBulkClipboardMutation = false
|
||||||
commitGridChanges()
|
commitGridChanges()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* AG Grid 初始化完成回调
|
|
||||||
* 记录 gridApi 实例,用于后续刷新单元格等操作
|
|
||||||
* @param event AG Grid 初始化事件
|
|
||||||
*/
|
|
||||||
const handleGridReady = (event: GridReadyEvent<DetailRow>) => {
|
const handleGridReady = (event: GridReadyEvent<DetailRow>) => {
|
||||||
gridApi.value = event.api
|
gridApi.value = event.api
|
||||||
}
|
}
|
||||||
@ -898,21 +619,6 @@ watch(
|
|||||||
void relabelRowsFromTaskDict()
|
void relabelRowsFromTaskDict()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
watch(
|
|
||||||
() => useDataStore().items,
|
|
||||||
async (newItems) => {
|
|
||||||
if (Object.keys(newItems).length > 0) {
|
|
||||||
loadFromIndexedDB();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{ immediate: true, deep: true }
|
|
||||||
)
|
|
||||||
/**
|
|
||||||
* 处理复制到剪贴板的单元格数据
|
|
||||||
* 将数组类型的数据转换为 JSON 字符串
|
|
||||||
* @param params AG Grid 剪贴板参数
|
|
||||||
* @returns 处理后的单元格值
|
|
||||||
*/
|
|
||||||
const processCellForClipboard = (params: any) => {
|
const processCellForClipboard = (params: any) => {
|
||||||
if (Array.isArray(params.value)) {
|
if (Array.isArray(params.value)) {
|
||||||
return JSON.stringify(params.value); // 数组转字符串复制
|
return JSON.stringify(params.value); // 数组转字符串复制
|
||||||
@ -920,12 +626,6 @@ const processCellForClipboard = (params: any) => {
|
|||||||
return params.value;
|
return params.value;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* 处理从剪贴板粘贴的单元格数据
|
|
||||||
* 根据字段类型解析数值(单价、工作量、因子等)
|
|
||||||
* @param params AG Grid 剪贴板参数
|
|
||||||
* @returns 解析后的单元格值
|
|
||||||
*/
|
|
||||||
const processCellFromClipboard = (params: any) => {
|
const processCellFromClipboard = (params: any) => {
|
||||||
const field = String(params.column?.getColDef?.().field || '')
|
const field = String(params.column?.getColDef?.().field || '')
|
||||||
if (field === 'budgetAdoptedUnitPrice') {
|
if (field === 'budgetAdoptedUnitPrice') {
|
||||||
@ -968,7 +668,7 @@ const mydiyTheme = myTheme.withParams({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="isWorkloadMethodApplicable" :class="agGridWrapClass">
|
<div v-if="isWorkloadMethodApplicable" :class="agGridWrapClass">
|
||||||
<AgGridVue :style="agGridStyle" :rowData="detailRows"
|
<AgGridVue :style="agGridStyle" :rowData="detailRows" :pinnedTopRowData="pinnedTopRowData"
|
||||||
:columnDefs="gridColumnDefs" :gridOptions="gridOptions" :theme="mydiyTheme" :treeData="false"
|
:columnDefs="gridColumnDefs" :gridOptions="gridOptions" :theme="mydiyTheme" :treeData="false"
|
||||||
:animateRows="true"
|
:animateRows="true"
|
||||||
:enableCellSpan="true"
|
:enableCellSpan="true"
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -279,6 +279,9 @@ const columnDefs: ColDef<FeeRow>[] = [
|
|||||||
: typeof params.node?.rowIndex === 'number'
|
: typeof params.node?.rowIndex === 'number'
|
||||||
? params.node.rowIndex + 1
|
? params.node.rowIndex + 1
|
||||||
: '',
|
: '',
|
||||||
|
cellClassRules: {
|
||||||
|
'ag-summary-label-cell': params => isSubtotalRow(params.data)
|
||||||
|
},
|
||||||
colSpan: params => (isSubtotalRow(params.data) ? 2 : 1)
|
colSpan: params => (isSubtotalRow(params.data) ? 2 : 1)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@ -375,6 +375,12 @@ const saveToIndexedDB = async (force = false) => {
|
|||||||
const snapshot = JSON.stringify(payload.detailRows)
|
const snapshot = JSON.stringify(payload.detailRows)
|
||||||
if (!force && snapshot === lastSavedSnapshot.value) return
|
if (!force && snapshot === lastSavedSnapshot.value) return
|
||||||
zxFwPricingStore.setHtFeeMainState(props.storageKey, payload, { force })
|
zxFwPricingStore.setHtFeeMainState(props.storageKey, payload, { force })
|
||||||
|
if (String(props.storageKey || '').includes('-additional-work')) {
|
||||||
|
const contractId = String(props.contractId || '').trim()
|
||||||
|
if (contractId) {
|
||||||
|
await zxFwPricingStore.syncHtExtraFeeByContractBase(contractId)
|
||||||
|
}
|
||||||
|
}
|
||||||
lastSavedSnapshot.value = snapshot
|
lastSavedSnapshot.value = snapshot
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('saveToIndexedDB failed:', error)
|
console.error('saveToIndexedDB failed:', error)
|
||||||
@ -498,6 +504,7 @@ const columnDefs: ColDef<FeeMethodRow>[] = [
|
|||||||
? ''
|
? ''
|
||||||
: 'editable-cell-line',
|
: 'editable-cell-line',
|
||||||
cellClassRules: {
|
cellClassRules: {
|
||||||
|
'ag-summary-label-cell': params => isSummaryRow(params.data),
|
||||||
'editable-cell-empty': params => params.value == null || params.value === ''
|
'editable-cell-empty': params => params.value == null || params.value === ''
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
<!--
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed } from 'vue'
|
import { computed, watch } from 'vue'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
|
|
||||||
interface ServiceItem {
|
interface ServiceItem {
|
||||||
@ -11,6 +10,7 @@ interface ServiceItem {
|
|||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
services: ServiceItem[]
|
services: ServiceItem[]
|
||||||
|
serviceRows?: string[][]
|
||||||
modelValue: string[]
|
modelValue: string[]
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
@ -19,68 +19,67 @@ const emit = defineEmits<{
|
|||||||
}>()
|
}>()
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
|
|
||||||
/** 互斥规则配置 */
|
const selectedSet = computed(() => new Set(props.modelValue))
|
||||||
const MUTUAL_EXCLUSION_RULES: Record<string, string[]> = {
|
const serviceById = computed(() => new Map(props.services.map(item => [item.id, item])))
|
||||||
'D1': ['D2-1', 'D2-2', 'D3-1', 'D3-2', 'D3-3', 'D3-4', 'D3-6-1', 'D3-7'],
|
const firstRowIds = computed(() => {
|
||||||
'D2-1': ['D1', 'D2-2', 'D3-4', 'D3-6-1', 'D3-7'],
|
const rows = Array.isArray(props.serviceRows) ? props.serviceRows : []
|
||||||
'D2-2': ['D2-1', 'D3-1', 'D3-2', 'D3-3']
|
if (rows.length === 0) return [] as string[]
|
||||||
}
|
const firstRow = Array.isArray(rows[0]) ? rows[0] : []
|
||||||
|
return firstRow.filter(id => serviceById.value.has(id))
|
||||||
|
})
|
||||||
|
const firstRowIdSet = computed(() => new Set(firstRowIds.value))
|
||||||
|
const groupedRows = computed(() => {
|
||||||
|
const rows = Array.isArray(props.serviceRows) ? props.serviceRows : []
|
||||||
|
if (rows.length === 0) return [] as ServiceItem[][]
|
||||||
|
|
||||||
/** 当前已选服务的 code 集合 */
|
const used = new Set<string>()
|
||||||
const selectedCodes = computed(() => {
|
const grouped = rows
|
||||||
return props.services
|
.map(row => row.map(id => serviceById.value.get(id)).filter((item): item is ServiceItem => Boolean(item)))
|
||||||
.filter(s => selectedSet.value.has(s.id))
|
.map(row => row.filter(item => {
|
||||||
.map(s => s.code)
|
if (used.has(item.id)) return false
|
||||||
|
used.add(item.id)
|
||||||
|
return true
|
||||||
|
}))
|
||||||
|
.filter(row => row.length > 0)
|
||||||
|
|
||||||
|
const leftovers = props.services.filter(item => !used.has(item.id))
|
||||||
|
if (leftovers.length > 0) grouped.push(leftovers)
|
||||||
|
return grouped
|
||||||
})
|
})
|
||||||
|
|
||||||
/** 获取某服务被哪些服务互斥 */
|
const toggleService = (id: string, checked: boolean) => {
|
||||||
const getBlockedBy = (targetCode: string): string[] => {
|
|
||||||
const blockers: string[] = []
|
|
||||||
for (const [blockerCode, blockedCodes] of Object.entries(MUTUAL_EXCLUSION_RULES)) {
|
|
||||||
if (selectedCodes.value.includes(blockerCode) && blockedCodes.includes(targetCode)) {
|
|
||||||
blockers.push(blockerCode)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return blockers
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 判断某服务是否被禁用 */
|
|
||||||
const isServiceDisabled = (item: ServiceItem): { disabled: boolean; reason: string } => {
|
|
||||||
const blockers = getBlockedBy(item.code)
|
|
||||||
if (blockers.length > 0) {
|
|
||||||
const blockerServices = blockers.map(bc => {
|
|
||||||
const s = props.services.find(s => s.code === bc)
|
|
||||||
return s ? `${s.code}${s.name}` : bc
|
|
||||||
})
|
|
||||||
return { disabled: true, reason: `与已选的${blockerServices.join('、')}互斥` }
|
|
||||||
}
|
|
||||||
return { disabled: false, reason: '' }
|
|
||||||
}
|
|
||||||
|
|
||||||
const selectedSet = computed(() => new Set(props.modelValue))
|
|
||||||
|
|
||||||
const toggleService = (item: ServiceItem, checked: boolean) => {
|
|
||||||
const { disabled } = isServiceDisabled(item)
|
|
||||||
// 如果尝试勾选被禁用的服务,直接忽略
|
|
||||||
if (checked && disabled) return
|
|
||||||
|
|
||||||
const next = new Set(props.modelValue)
|
const next = new Set(props.modelValue)
|
||||||
if (checked) {
|
if (checked) {
|
||||||
next.add(item.id)
|
if (firstRowIdSet.value.has(id)) {
|
||||||
// 勾选时,自动取消互斥的服务
|
firstRowIds.value.forEach(firstId => next.delete(firstId))
|
||||||
const blockedCodes = MUTUAL_EXCLUSION_RULES[item.code] || []
|
|
||||||
for (const blockedCode of blockedCodes) {
|
|
||||||
const blockedService = props.services.find(s => s.code === blockedCode)
|
|
||||||
if (blockedService) {
|
|
||||||
next.delete(blockedService.id)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
next.add(id)
|
||||||
} else {
|
} else {
|
||||||
next.delete(item.id)
|
next.delete(id)
|
||||||
}
|
}
|
||||||
emit('update:modelValue', props.services.map(s => s.id).filter(id => next.has(id)))
|
emit('update:modelValue', props.services.map(item => item.id).filter(itemId => next.has(itemId)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const isFirstRowDisabled = (id: string) => {
|
||||||
|
if (!firstRowIdSet.value.has(id)) return false
|
||||||
|
for (const selectedId of props.modelValue) {
|
||||||
|
if (selectedId !== id && firstRowIdSet.value.has(selectedId)) return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => [props.modelValue, firstRowIds.value] as const,
|
||||||
|
() => {
|
||||||
|
const firstSelected = props.modelValue.filter(id => firstRowIdSet.value.has(id))
|
||||||
|
if (firstSelected.length <= 1) return
|
||||||
|
const keepId = firstRowIds.value.find(id => firstSelected.includes(id)) || firstSelected[0]
|
||||||
|
const next = props.modelValue.filter(id => !firstRowIdSet.value.has(id) || id === keepId)
|
||||||
|
emit('update:modelValue', next)
|
||||||
|
},
|
||||||
|
{ immediate: true, deep: true }
|
||||||
|
)
|
||||||
|
|
||||||
const clearAll = () => {
|
const clearAll = () => {
|
||||||
emit('update:modelValue', [])
|
emit('update:modelValue', [])
|
||||||
}
|
}
|
||||||
@ -89,7 +88,10 @@ const clearAll = () => {
|
|||||||
<template>
|
<template>
|
||||||
<div class="rounded-lg border bg-card p-2.5 shadow-sm">
|
<div class="rounded-lg border bg-card p-2.5 shadow-sm">
|
||||||
<div class="mb-1 flex items-center justify-between gap-2">
|
<div class="mb-1 flex items-center justify-between gap-2">
|
||||||
<label class="block text-[11px] font-medium text-foreground leading-none">{{ t('serviceSelector.title') }}</label>
|
<div class="flex min-w-0 items-center gap-1.5">
|
||||||
|
<label class="block shrink-0 text-sm font-semibold leading-none text-slate-900">{{ t('serviceSelector.title') }}</label>
|
||||||
|
<span class="min-w-0 text-xs leading-5 text-muted-foreground">{{ t('serviceSelector.titleHint') }}</span>
|
||||||
|
</div>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="cursor-pointer h-6 rounded-md border px-2 text-[13px] text-muted-foreground transition hover:bg-accent"
|
class="cursor-pointer h-6 rounded-md border px-2 text-[13px] text-muted-foreground transition hover:bg-accent"
|
||||||
@ -99,101 +101,36 @@ const clearAll = () => {
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="rounded-md border p-1.5">
|
<div class="rounded-md border p-1.5">
|
||||||
<div class="flex flex-wrap items-start gap-1">
|
<div v-if="groupedRows.length > 0" class="flex flex-col gap-1.5">
|
||||||
<label
|
<div
|
||||||
v-for="item in props.services"
|
v-for="(row, rowIndex) in groupedRows"
|
||||||
:key="item.id"
|
:key="`service-row-${rowIndex}`"
|
||||||
:class="[
|
class="flex flex-wrap items-start gap-1 border-b border-slate-200 pb-1.5 last:border-b-0 last:pb-0"
|
||||||
'inline-flex w-fit max-w-full cursor-pointer items-start gap-1.5 rounded-md border px-2 py-1 text-[11px] leading-4',
|
|
||||||
isServiceDisabled(item).disabled
|
|
||||||
? 'opacity-50 cursor-not-allowed hover:bg-transparent'
|
|
||||||
: 'hover:bg-muted/60'
|
|
||||||
]"
|
|
||||||
:title="isServiceDisabled(item).reason"
|
|
||||||
>
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
class="mt-0.5"
|
|
||||||
:checked="selectedSet.has(item.id)"
|
|
||||||
:disabled="isServiceDisabled(item).disabled"
|
|
||||||
@change="toggleService(item, ($event.target as HTMLInputElement).checked)"
|
|
||||||
/>
|
|
||||||
<span class="text-muted-foreground shrink-0">{{ item.code }}</span>
|
|
||||||
<span class="text-foreground break-words">{{ item.name }}</span>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<div v-if="props.services.length === 0" class="px-2 py-4 text-center text-xs text-muted-foreground">
|
|
||||||
{{ t('serviceSelector.empty') }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
-->
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { computed } from 'vue'
|
|
||||||
import { useI18n } from 'vue-i18n'
|
|
||||||
|
|
||||||
interface ServiceItem {
|
|
||||||
id: string
|
|
||||||
code: string
|
|
||||||
name: string
|
|
||||||
}
|
|
||||||
|
|
||||||
const props = defineProps<{
|
|
||||||
services: ServiceItem[]
|
|
||||||
modelValue: string[]
|
|
||||||
}>()
|
|
||||||
|
|
||||||
const emit = defineEmits<{
|
|
||||||
(e: 'update:modelValue', value: string[]): void
|
|
||||||
}>()
|
|
||||||
const { t } = useI18n()
|
|
||||||
|
|
||||||
const selectedSet = computed(() => new Set(props.modelValue))
|
|
||||||
|
|
||||||
const toggleService = (item: ServiceItem, checked: boolean) => {
|
|
||||||
const next = new Set(props.modelValue)
|
|
||||||
if (checked) {
|
|
||||||
next.add(item.id)
|
|
||||||
} else {
|
|
||||||
next.delete(item.id)
|
|
||||||
}
|
|
||||||
emit('update:modelValue', props.services.map(s => s.id).filter(id => next.has(id)))
|
|
||||||
}
|
|
||||||
|
|
||||||
const clearAll = () => {
|
|
||||||
emit('update:modelValue', [])
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div class="rounded-lg border bg-card p-2.5 shadow-sm">
|
|
||||||
<div class="mb-1 flex items-center justify-between gap-2">
|
|
||||||
<label class="block text-[11px] font-medium text-foreground leading-none">{{ t('serviceSelector.title') }}</label>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="cursor-pointer h-6 rounded-md border px-2 text-[13px] text-muted-foreground transition hover:bg-accent"
|
|
||||||
@click="clearAll"
|
|
||||||
>
|
|
||||||
{{ t('serviceSelector.clear') }}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div class="rounded-md border p-1.5">
|
|
||||||
<div class="flex flex-wrap items-start gap-1">
|
|
||||||
<label
|
|
||||||
v-for="item in props.services"
|
|
||||||
:key="item.id"
|
|
||||||
class="inline-flex w-fit max-w-full cursor-pointer items-start gap-1.5 rounded-md border px-2 py-1 text-[11px] leading-4 hover:bg-muted/60"
|
|
||||||
>
|
>
|
||||||
<input
|
<label
|
||||||
|
v-for="item in row"
|
||||||
|
:key="item.id"
|
||||||
|
:class="[
|
||||||
|
'inline-flex w-fit max-w-full items-start gap-1.5 rounded-md border px-2 py-1 text-[11px] leading-4 transition',
|
||||||
|
isFirstRowDisabled(item.id)
|
||||||
|
? 'cursor-not-allowed border-slate-300 bg-slate-100/80 text-slate-400 opacity-80'
|
||||||
|
: 'cursor-pointer hover:bg-muted/60'
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
class="mt-0.5"
|
:class="[
|
||||||
|
'mt-0.5',
|
||||||
|
isFirstRowDisabled(item.id) ? 'cursor-not-allowed accent-slate-300' : 'cursor-pointer'
|
||||||
|
]"
|
||||||
:checked="selectedSet.has(item.id)"
|
:checked="selectedSet.has(item.id)"
|
||||||
@change="toggleService(item, ($event.target as HTMLInputElement).checked)"
|
:disabled="isFirstRowDisabled(item.id)"
|
||||||
/>
|
@change="toggleService(item.id, ($event.target as HTMLInputElement).checked)"
|
||||||
<span class="text-muted-foreground shrink-0">{{ item.code }}</span>
|
/>
|
||||||
<span class="text-foreground break-words">{{ item.name }}</span>
|
<span class="text-muted-foreground shrink-0">{{ item.code }}</span>
|
||||||
</label>
|
<span class="text-foreground break-words">{{ item.name }}</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="props.services.length === 0" class="px-2 py-4 text-center text-xs text-muted-foreground">
|
<div v-if="props.services.length === 0" class="px-2 py-4 text-center text-xs text-muted-foreground">
|
||||||
{{ t('serviceSelector.empty') }}
|
{{ t('serviceSelector.empty') }}
|
||||||
|
|||||||
@ -31,7 +31,6 @@ import { getServiceDictItemById, getWorkListEntries, wholeProcessTasks } from '@
|
|||||||
import { WorkType } from '@/sql'
|
import { WorkType } from '@/sql'
|
||||||
import { useZxFwPricingStore } from '@/pinia/zxFwPricing'
|
import { useZxFwPricingStore } from '@/pinia/zxFwPricing'
|
||||||
import { useKvStore } from '@/pinia/kv'
|
import { useKvStore } from '@/pinia/kv'
|
||||||
import { useDataStore } from '@/pinia/zx'
|
|
||||||
import { Trash2 } from 'lucide-vue-next'
|
import { Trash2 } from 'lucide-vue-next'
|
||||||
|
|
||||||
interface WorkContentRow {
|
interface WorkContentRow {
|
||||||
@ -149,137 +148,22 @@ const loadProjectIndustryId = async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const buildDefaultRowsFromDict = async (): Promise<WorkContentRow[]> => {
|
const buildDefaultRowsFromDict = async (): Promise<WorkContentRow[]> => {
|
||||||
const rows: WorkContentRow[] = []
|
|
||||||
const contentRows = await useDataStore().query([
|
|
||||||
{ field: 'type', value: `${props.contractId}-content`, operator: 'eq' }
|
|
||||||
])
|
|
||||||
let contentArray = [];
|
|
||||||
if (contentRows.length > 0) {
|
|
||||||
contentArray = contentRows[0].value;
|
|
||||||
}
|
|
||||||
const entries = getWorkListEntries(locale.value) as Array<{
|
|
||||||
text: string
|
|
||||||
serviceid: number
|
|
||||||
order: number
|
|
||||||
type: number
|
|
||||||
code: string
|
|
||||||
leaf?: boolean
|
|
||||||
textEn?: string
|
|
||||||
checked: boolean
|
|
||||||
custom: boolean
|
|
||||||
remark: string
|
|
||||||
}>;
|
|
||||||
|
|
||||||
// 根据 code 将 contentArray 里存在的数据替换到 entries 中
|
|
||||||
const contentMap = new Map<string, string>()
|
|
||||||
if (Array.isArray(contentArray)) {
|
|
||||||
for (const item of contentArray) {
|
|
||||||
if (item?.code != null) {
|
|
||||||
contentMap.set(`${item.code}-${item.content}`, item)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (let entry of entries) {
|
|
||||||
const item = contentMap.get(`${entry.code}-${entry.text}`)
|
|
||||||
if (item != null && `${entry.code}-${entry.text}` == `${item.code}-${item.content}`) {
|
|
||||||
entry.checked = item.checked
|
|
||||||
entry.custom = item.custom
|
|
||||||
entry.remark = item.remark
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 过滤数据
|
|
||||||
let filtered = [...entries]
|
|
||||||
|
|
||||||
// 按 order 排序
|
|
||||||
filtered.sort((a, b) => a.order - b.order)
|
|
||||||
|
|
||||||
// 构建 code -> 父节点信息的映射
|
|
||||||
const parentMap = new Map<string, { serviceGroup: string; content: string }>()
|
|
||||||
|
|
||||||
// 第一次遍历:收集所有 leaf: false 的父节点
|
|
||||||
let i = 1;
|
|
||||||
for (const entry of filtered) {
|
|
||||||
if (entry.leaf === false) {
|
|
||||||
/*const serviceItem = getServiceDictItemById(entry.serviceid) as { code?: string; name?: string } | undefined
|
|
||||||
const serviceGroup = serviceItem
|
|
||||||
? `${String(serviceItem.code || '').trim()} ${String(serviceItem.name || '').trim()}`.trim()
|
|
||||||
: entry.code*/
|
|
||||||
|
|
||||||
parentMap.set(entry.code, {
|
|
||||||
serviceGroup: `${entry.text}`,
|
|
||||||
content: String(entry.text || '').trim()
|
|
||||||
})
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 第二次遍历:处理 leaf: true 的子节点
|
|
||||||
for (const entry of filtered) {
|
|
||||||
// 只处理 leaf: true 的节点(子节点)
|
|
||||||
if (entry.leaf !== true) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
const content = String(entry.text || '').trim()
|
|
||||||
if (!content) continue
|
|
||||||
|
|
||||||
const typeLabel = ((): WorkType => {
|
|
||||||
if (entry.type === 1) return t('workContent.type.optional') as WorkType
|
|
||||||
if (entry.type === 2) return t('workContent.type.daily') as WorkType
|
|
||||||
if (entry.type === 3) return t('workContent.type.special') as WorkType
|
|
||||||
if (entry.type === 4) return t('workContent.type.additional') as WorkType
|
|
||||||
return t('workContent.type.basic') as WorkType
|
|
||||||
})()
|
|
||||||
|
|
||||||
/*const serviceItem = getServiceDictItemById(entry.serviceid) as { code?: string; name?: string } | undefined
|
|
||||||
const serviceGroup = serviceItem
|
|
||||||
? `${String(serviceItem.code || '').trim()} ${String(serviceItem.name || '').trim()}`.trim()
|
|
||||||
: entry.code*/
|
|
||||||
|
|
||||||
// 查找父节点
|
|
||||||
const parent = parentMap.get(entry.code)
|
|
||||||
const parentName = parent?.serviceGroup
|
|
||||||
|
|
||||||
// 构建 path: [父节点名称, 子节点内容]
|
|
||||||
const path = [parentName, content]
|
|
||||||
|
|
||||||
rows.push({
|
|
||||||
id: `dict-${entry.serviceid}-${entry.code}-${entry.order}` ||
|
|
||||||
`dict-${entry.serviceid}-${entry.content.substring(0, 8)}-${entry.order}`,
|
|
||||||
code: entry.code,
|
|
||||||
content,
|
|
||||||
type: typeLabel,
|
|
||||||
dictOrder: entry.order,
|
|
||||||
serviceGroup: parentName, // 保留 serviceGroup 用于显示
|
|
||||||
serviceid: toServiceId(entry.serviceid),
|
|
||||||
remark: entry.remark == null ? '' : entry.remark,
|
|
||||||
checked: entry.checked == null ? true : entry.checked,
|
|
||||||
custom: entry.custom == null ? false : entry.custom,
|
|
||||||
leaf: entry.leaf,
|
|
||||||
path // 使用 [父节点, 子节点] 的路径结构
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return rows
|
|
||||||
}
|
|
||||||
|
|
||||||
const buildDefaultRowsFromDict2 = async (): Promise<WorkContentRow[]> => {
|
|
||||||
const rows: WorkContentRow[] = []
|
const rows: WorkContentRow[] = []
|
||||||
const entries = getWorkListEntries(locale.value) as Array<{ text: string; serviceid: number; order: number; type: number }>
|
const entries = getWorkListEntries(locale.value) as Array<{ text: string; serviceid: number; order: number; type: number }>
|
||||||
const filtered = [...entries]
|
|
||||||
/*let filtered: typeof entries = []
|
let filtered: typeof entries = []
|
||||||
let groupedServiceIds: number[] = []
|
let groupedServiceIds: number[] = []
|
||||||
let groupedBy: 'fid' | 'sid' | null = null
|
let groupedBy: 'fid' | 'sid' | null = null
|
||||||
let matchedWholeProcessGroup: { fid: number; industry: number; sid: number[] } | null = null
|
let matchedWholeProcessGroup: { fid: number; industry: number; sid: number[] } | null = null
|
||||||
isWholeProcessGroupedMode.value = false
|
isWholeProcessGroupedMode.value = false
|
||||||
groupedServiceGroups.value = []
|
groupedServiceGroups.value = []
|
||||||
if (props.dictMode === 'service') {
|
if (props.dictMode === 'service') {
|
||||||
const sid = Number(props.serviceId.split('-').pop())
|
const sid = Number(props.serviceId)
|
||||||
const industryId = await loadProjectIndustryId()
|
const industryId = await loadProjectIndustryId()
|
||||||
const wholeProcessGroupByFid = wholeProcessTasks.find(
|
const wholeProcessGroupByFid = wholeProcessTasks.find(
|
||||||
item => Number(item.fid) === sid && Number(item.industry) === industryId
|
item => Number(item.fid) === sid && Number(item.industry) === industryId
|
||||||
)
|
)
|
||||||
|
|
||||||
const wholeProcessGroup = wholeProcessGroupByFid
|
const wholeProcessGroup = wholeProcessGroupByFid
|
||||||
groupedBy = wholeProcessGroupByFid ? 'fid' : null
|
groupedBy = wholeProcessGroupByFid ? 'fid' : null
|
||||||
if (wholeProcessGroup) {
|
if (wholeProcessGroup) {
|
||||||
@ -320,7 +204,7 @@ const buildDefaultRowsFromDict2 = async (): Promise<WorkContentRow[]> => {
|
|||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
filtered.sort((a, b) => a.order - b.order)
|
filtered.sort((a, b) => a.order - b.order)
|
||||||
}*/
|
}
|
||||||
|
|
||||||
for (const entry of filtered) {
|
for (const entry of filtered) {
|
||||||
const content = String(entry.text || '').trim()
|
const content = String(entry.text || '').trim()
|
||||||
@ -379,67 +263,20 @@ const emitCheckedChange = () => {
|
|||||||
emit('checkedChange', [...checkedIds.value])
|
emit('checkedChange', [...checkedIds.value])
|
||||||
}
|
}
|
||||||
|
|
||||||
const saveToStore = async () => {
|
const saveToStore = () => {
|
||||||
/*const payload: WorkContentState = {
|
const payload: WorkContentState = {
|
||||||
detailRows: getPersistableRows(rowData.value).map(item => ({ ...item }))
|
detailRows: getPersistableRows(rowData.value).map(item => ({ ...item }))
|
||||||
}
|
}
|
||||||
zxFwPricingStore.setKeyState(props.storageKey, payload)
|
zxFwPricingStore.setKeyState(props.storageKey, payload)
|
||||||
emitCheckedChange()*/
|
emitCheckedChange()
|
||||||
const rows = { value: [...rowData.value], type: `${props.contractId}-content`, id: `${props.contractId}-content` }
|
|
||||||
const stats = await useDataStore().upsert(rows)
|
|
||||||
console.log('💾 数据保存成功:', rows)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const loadFromStore = async () => {
|
const loadFromStore = async () => {
|
||||||
// TODO 数据重新加载
|
const defaultRows =
|
||||||
let defaultRows = await buildDefaultRowsFromDict()
|
props.dictMode === 'none'
|
||||||
const zxRows = await useDataStore().query([
|
? []
|
||||||
{ field: 'type', value: `${props.contractId}-zxFw`, operator: 'eq' }
|
: await buildDefaultRowsFromDict()
|
||||||
])
|
const state = await zxFwPricingStore.loadKeyState<WorkContentState>(props.storageKey)
|
||||||
const zxRowCodes = [
|
|
||||||
...new Set(
|
|
||||||
zxRows
|
|
||||||
.map(row => row.code)
|
|
||||||
.filter(Boolean)
|
|
||||||
.flatMap(code => {
|
|
||||||
const mapping = {
|
|
||||||
'D1': ['D3-1', 'D3-2', 'D3-3', 'D3-4', 'D3-6-1', 'D3-7', 'D4-6', 'D5-1'],
|
|
||||||
'D2-1': ['D3-1', 'D3-2', 'D3-3'],
|
|
||||||
'D2-2': ['D3-4', 'D3-5', 'D3-6'],
|
|
||||||
'D3-1': ['D3-1'],
|
|
||||||
'D3-2': ['D3-2'],
|
|
||||||
'D3-3': ['D3-3'],
|
|
||||||
'D3-4': ['D3-4'],
|
|
||||||
'D3-6-1': ['D3-6-1'],
|
|
||||||
'D3-7': ['D3-7'],
|
|
||||||
'D4-6': ['D4-6'],
|
|
||||||
'D5-1': ['D5-1']
|
|
||||||
}
|
|
||||||
return mapping[code] || []
|
|
||||||
})
|
|
||||||
),
|
|
||||||
'D0-1',
|
|
||||||
'D0'
|
|
||||||
]
|
|
||||||
|
|
||||||
// 过滤 defaultRows,只保留 code 在 zxRowCodes 中的行
|
|
||||||
rowData.value = defaultRows.filter(row =>
|
|
||||||
row.code && zxRowCodes.includes(row.code)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
/*const contentRows = await useDataStore().query([
|
|
||||||
{ field: 'type', value: `${props.contractId}-content`, operator: 'eq' }
|
|
||||||
])
|
|
||||||
if (contentRows.length > 0) {
|
|
||||||
rowData.value = contentRows[0].value;
|
|
||||||
} else {
|
|
||||||
|
|
||||||
}*/
|
|
||||||
/*console.log(rowData.value)
|
|
||||||
console.log('zxRowCodes', zxRowCodes)*/
|
|
||||||
|
|
||||||
/*const state = await zxFwPricingStore.loadKeyState<WorkContentState>(props.storageKey)
|
|
||||||
if (Array.isArray(state?.detailRows) && state.detailRows.length > 0) {
|
if (Array.isArray(state?.detailRows) && state.detailRows.length > 0) {
|
||||||
const persistedRows = state.detailRows.map(item => ({
|
const persistedRows = state.detailRows.map(item => ({
|
||||||
...item,
|
...item,
|
||||||
@ -494,9 +331,9 @@ const loadFromStore = async () => {
|
|||||||
} else {
|
} else {
|
||||||
rowData.value = withAddTriggerRows(defaultRows)
|
rowData.value = withAddTriggerRows(defaultRows)
|
||||||
saveToStore()
|
saveToStore()
|
||||||
}*/
|
}
|
||||||
// emitCheckedChange()
|
emitCheckedChange()
|
||||||
// await syncGroupedRowsRender()
|
await syncGroupedRowsRender()
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleCheckedToggle = (id: string, checked: boolean) => {
|
const handleCheckedToggle = (id: string, checked: boolean) => {
|
||||||
@ -539,11 +376,24 @@ const groupRowRendererParams = computed<IGroupCellRendererParams<WorkContentRow>
|
|||||||
innerRenderer: (params: ICellRendererParams<WorkContentRow>) => {
|
innerRenderer: (params: ICellRendererParams<WorkContentRow>) => {
|
||||||
const wrapper = document.createElement('div')
|
const wrapper = document.createElement('div')
|
||||||
wrapper.className = 'work-content-group-row'
|
wrapper.className = 'work-content-group-row'
|
||||||
// 只渲染标签,不渲染复选框
|
const checkbox = document.createElement('input')
|
||||||
|
checkbox.type = 'checkbox'
|
||||||
|
checkbox.className = 'work-content-group-check'
|
||||||
|
const rows = getGroupCheckableRows(params.node)
|
||||||
|
const checkedCount = rows.filter(item => item.checked).length
|
||||||
|
checkbox.checked = rows.length > 0 && checkedCount === rows.length
|
||||||
|
checkbox.indeterminate = checkedCount > 0 && checkedCount < rows.length
|
||||||
|
checkbox.disabled = rows.length === 0
|
||||||
|
checkbox.addEventListener('mousedown', event => event.stopPropagation())
|
||||||
|
checkbox.addEventListener('click', event => event.stopPropagation())
|
||||||
|
checkbox.addEventListener('change', event => {
|
||||||
|
event.stopPropagation()
|
||||||
|
handleGroupCheckedToggle(params.node, checkbox.checked)
|
||||||
|
})
|
||||||
const label = document.createElement('span')
|
const label = document.createElement('span')
|
||||||
label.className = 'work-content-group-label'
|
label.className = 'work-content-group-label'
|
||||||
label.textContent = String(params.valueFormatted || params.value || params.node.key || '')
|
label.textContent = String(params.valueFormatted || params.value || params.node.key || '')
|
||||||
wrapper.appendChild(label)
|
wrapper.append(checkbox, label)
|
||||||
return wrapper
|
return wrapper
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -566,27 +416,30 @@ const contentCellRenderer = (params: ICellRendererParams<WorkContentRow>) => {
|
|||||||
wrapper.appendChild(label)
|
wrapper.appendChild(label)
|
||||||
return wrapper
|
return wrapper
|
||||||
}
|
}
|
||||||
|
// 自定义行不显示 checkbox,直接显示文本;空时显示 placeholder
|
||||||
if (!isAddTriggerRow(data) && !data.custom) {
|
if (data.custom) {
|
||||||
const checkbox = document.createElement('input')
|
const label = document.createElement('span')
|
||||||
checkbox.type = 'checkbox'
|
if (!data.content) {
|
||||||
checkbox.checked = data.checked
|
label.className = 'work-content-placeholder'
|
||||||
checkbox.className = 'work-content-check'
|
label.textContent = t('workContent.clickToInputContent')
|
||||||
checkbox.addEventListener('change', (event: Event) => {
|
} else {
|
||||||
const target = event.target as HTMLInputElement
|
label.className = 'work-content-text'
|
||||||
handleCheckedToggle(data.id, target.checked)
|
label.textContent = data.content
|
||||||
})
|
}
|
||||||
wrapper.appendChild(checkbox)
|
wrapper.appendChild(label)
|
||||||
|
return wrapper
|
||||||
}
|
}
|
||||||
|
const checkbox = document.createElement('input')
|
||||||
|
checkbox.type = 'checkbox'
|
||||||
|
checkbox.className = 'work-content-check'
|
||||||
|
checkbox.checked = Boolean(data.checked)
|
||||||
|
checkbox.addEventListener('change', () => {
|
||||||
|
handleCheckedToggle(data.id, checkbox.checked)
|
||||||
|
})
|
||||||
const label = document.createElement('span')
|
const label = document.createElement('span')
|
||||||
if (!data.content) {
|
label.className = 'work-content-text'
|
||||||
label.className = 'work-content-placeholder'
|
label.textContent = String(data.content || '')
|
||||||
label.textContent = t('workContent.clickToInputContent')
|
wrapper.appendChild(checkbox)
|
||||||
} else {
|
|
||||||
label.className = 'work-content-text'
|
|
||||||
label.textContent = data.content
|
|
||||||
}
|
|
||||||
wrapper.appendChild(label)
|
wrapper.appendChild(label)
|
||||||
return wrapper
|
return wrapper
|
||||||
}
|
}
|
||||||
@ -599,45 +452,13 @@ const columnDefs: ColDef<WorkContentRow>[] = [
|
|||||||
suppressMovable: true,
|
suppressMovable: true,
|
||||||
editable: false,
|
editable: false,
|
||||||
colSpan: params => (isAddTriggerRow(params.data) ? 5 : 1),
|
colSpan: params => (isAddTriggerRow(params.data) ? 5 : 1),
|
||||||
/*valueGetter: params => {
|
valueGetter: params => {
|
||||||
if (!params.node || params.node.group || isAddTriggerRow(params.data)) return ''
|
if (!params.node || params.node.group || isAddTriggerRow(params.data)) return ''
|
||||||
if (!isWholeProcessGroupedMode.value) return (params.node.rowIndex ?? 0) + 1
|
if (!isWholeProcessGroupedMode.value) return (params.node.rowIndex ?? 0) + 1
|
||||||
const siblings = params.node.parent?.childrenAfterSort || []
|
const siblings = params.node.parent?.childrenAfterSort || []
|
||||||
const visibleLeafSiblings = siblings.filter(node => !node.group && !isAddTriggerRow(node.data as WorkContentRow))
|
const visibleLeafSiblings = siblings.filter(node => !node.group && !isAddTriggerRow(node.data as WorkContentRow))
|
||||||
const index = visibleLeafSiblings.findIndex(node => node.id === params.node?.id)
|
const index = visibleLeafSiblings.findIndex(node => node.id === params.node?.id)
|
||||||
return index >= 0 ? index + 1 : ''
|
return index >= 0 ? index + 1 : ''
|
||||||
},*/
|
|
||||||
valueGetter: params => {
|
|
||||||
// 1. 添加触发行不显示序号
|
|
||||||
if (isAddTriggerRow(params.data)) return ''
|
|
||||||
|
|
||||||
// 2. 分组行(一级标题)处理
|
|
||||||
if (params.node.group) {
|
|
||||||
// 获取当前分组在所有分组中的索引
|
|
||||||
const allGroups = params.api.getRowNode(params.node.id)?.allChildren
|
|
||||||
?.filter(node => node.group && !isAddTriggerRow(node.data))
|
|
||||||
const groupIndex = allGroups?.indexOf(params.node) + 1 || 1
|
|
||||||
return String(groupIndex)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. 二级节点处理(叶子节点)
|
|
||||||
const parentNode = params.node.parent
|
|
||||||
if (!parentNode) return ''
|
|
||||||
|
|
||||||
// 获取父分组的序号
|
|
||||||
const parentGroupIndex = parentNode.allChildren
|
|
||||||
?.filter(node => node.group && !isAddTriggerRow(node.data))
|
|
||||||
.indexOf(parentNode) + 1 || 1
|
|
||||||
|
|
||||||
// 获取当前节点在分组内的序号
|
|
||||||
const siblings = parentNode.childrenAfterSort || []
|
|
||||||
const visibleLeafSiblings = siblings.filter(node =>
|
|
||||||
!node.group && !isAddTriggerRow(node.data as WorkContentRow)
|
|
||||||
)
|
|
||||||
const childIndex = visibleLeafSiblings.findIndex(node => node.id === params.node?.id) + 1
|
|
||||||
|
|
||||||
return `${childIndex}`
|
|
||||||
// return `${parentGroupIndex}.${childIndex}`
|
|
||||||
},
|
},
|
||||||
cellRenderer: (params: ICellRendererParams<WorkContentRow>) => {
|
cellRenderer: (params: ICellRendererParams<WorkContentRow>) => {
|
||||||
const row = params.data
|
const row = params.data
|
||||||
@ -670,7 +491,7 @@ const columnDefs: ColDef<WorkContentRow>[] = [
|
|||||||
cellStyle: { whiteSpace: 'normal', lineHeight: '1.5' },
|
cellStyle: { whiteSpace: 'normal', lineHeight: '1.5' },
|
||||||
cellRenderer: contentCellRenderer
|
cellRenderer: contentCellRenderer
|
||||||
},
|
},
|
||||||
/*{
|
{
|
||||||
headerName: t('workContent.columns.type'),
|
headerName: t('workContent.columns.type'),
|
||||||
field: 'type',
|
field: 'type',
|
||||||
minWidth: 100,
|
minWidth: 100,
|
||||||
@ -678,7 +499,7 @@ const columnDefs: ColDef<WorkContentRow>[] = [
|
|||||||
editable: false,
|
editable: false,
|
||||||
valueFormatter: (params: ValueFormatterParams<WorkContentRow>) =>
|
valueFormatter: (params: ValueFormatterParams<WorkContentRow>) =>
|
||||||
isAddTriggerRow(params.data) ? '' : String(params.value || '')
|
isAddTriggerRow(params.data) ? '' : String(params.value || '')
|
||||||
},*/
|
},
|
||||||
{
|
{
|
||||||
headerName: t('workContent.columns.remark'),
|
headerName: t('workContent.columns.remark'),
|
||||||
field: 'remark',
|
field: 'remark',
|
||||||
@ -695,7 +516,7 @@ const columnDefs: ColDef<WorkContentRow>[] = [
|
|||||||
},
|
},
|
||||||
valueFormatter: params => (isAddTriggerRow(params.data) ? '' : (params.value || t('workContent.clickToInput')))
|
valueFormatter: params => (isAddTriggerRow(params.data) ? '' : (params.value || t('workContent.clickToInput')))
|
||||||
},
|
},
|
||||||
/*{
|
{
|
||||||
headerName: t('workContent.columns.actions'),
|
headerName: t('workContent.columns.actions'),
|
||||||
colId: 'actions',
|
colId: 'actions',
|
||||||
minWidth: 92,
|
minWidth: 92,
|
||||||
@ -735,7 +556,7 @@ const columnDefs: ColDef<WorkContentRow>[] = [
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}*/
|
}
|
||||||
]
|
]
|
||||||
const gridColumnDefs = computed(() => withReadonlyAutoHeight(columnDefs))
|
const gridColumnDefs = computed(() => withReadonlyAutoHeight(columnDefs))
|
||||||
|
|
||||||
@ -758,7 +579,7 @@ const createAddTriggerRow = (groupName?: string): WorkContentRow => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const withAddTriggerRows2 = (rows: WorkContentRow[]) => {
|
const withAddTriggerRows = (rows: WorkContentRow[]) => {
|
||||||
const pureRows = getPersistableRows(rows)
|
const pureRows = getPersistableRows(rows)
|
||||||
if (isWholeProcessGroupedMode.value) {
|
if (isWholeProcessGroupedMode.value) {
|
||||||
const groupedMap = new Map<string, WorkContentRow[]>()
|
const groupedMap = new Map<string, WorkContentRow[]>()
|
||||||
@ -768,8 +589,8 @@ const withAddTriggerRows2 = (rows: WorkContentRow[]) => {
|
|||||||
groupedMap.get(groupName)?.push(row)
|
groupedMap.get(groupName)?.push(row)
|
||||||
}
|
}
|
||||||
const groupOrder = groupedServiceGroups.value.length
|
const groupOrder = groupedServiceGroups.value.length
|
||||||
? [...groupedServiceGroups.value]
|
? [...groupedServiceGroups.value]
|
||||||
: [...groupedMap.keys()]
|
: [...groupedMap.keys()]
|
||||||
const result: WorkContentRow[] = []
|
const result: WorkContentRow[] = []
|
||||||
const used = new Set<string>()
|
const used = new Set<string>()
|
||||||
for (const groupName of groupOrder) {
|
for (const groupName of groupOrder) {
|
||||||
@ -787,11 +608,6 @@ const withAddTriggerRows2 = (rows: WorkContentRow[]) => {
|
|||||||
return [...pureRows, createAddTriggerRow()]
|
return [...pureRows, createAddTriggerRow()]
|
||||||
}
|
}
|
||||||
|
|
||||||
const withAddTriggerRows = (rows: WorkContentRow[]) => {
|
|
||||||
const pureRows = getPersistableRows(rows)
|
|
||||||
return pureRows
|
|
||||||
}
|
|
||||||
|
|
||||||
const getDataPath = (data: WorkContentRow) => {
|
const getDataPath = (data: WorkContentRow) => {
|
||||||
const path = Array.isArray(data?.path)
|
const path = Array.isArray(data?.path)
|
||||||
? data.path.map(segment => String(segment || '').trim()).filter(Boolean)
|
? data.path.map(segment => String(segment || '').trim()).filter(Boolean)
|
||||||
@ -869,21 +685,11 @@ watch(isWholeProcessGroupedMode, () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => useDataStore().items,
|
|
||||||
async (newItems) => {
|
|
||||||
if (Object.keys(newItems).length > 0) {
|
|
||||||
loadFromStore();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{ immediate: true, deep: true }
|
|
||||||
)
|
|
||||||
|
|
||||||
/*watch(
|
|
||||||
() => rowData.value.length,
|
() => rowData.value.length,
|
||||||
() => {
|
() => {
|
||||||
void syncGroupedRowsRender()
|
void syncGroupedRowsRender()
|
||||||
}
|
}
|
||||||
)*/
|
)
|
||||||
|
|
||||||
watch(locale, () => {
|
watch(locale, () => {
|
||||||
void loadFromStore()
|
void loadFromStore()
|
||||||
@ -926,10 +732,10 @@ const confirmDeleteRow = () => {
|
|||||||
:columnDefs="gridColumnDefs"
|
:columnDefs="gridColumnDefs"
|
||||||
:theme="myTheme"
|
:theme="myTheme"
|
||||||
:getRowId="(params: { data: WorkContentRow }) => params.data.id"
|
:getRowId="(params: { data: WorkContentRow }) => params.data.id"
|
||||||
:treeData="true"
|
:treeData="isWholeProcessGroupedMode"
|
||||||
:getDataPath="getDataPath"
|
:getDataPath="getDataPath"
|
||||||
:groupDefaultExpanded="true ? -1 : 0"
|
:groupDefaultExpanded="isWholeProcessGroupedMode ? -1 : 0"
|
||||||
:groupDisplayType="true ? 'groupRows' : undefined"
|
:groupDisplayType="isWholeProcessGroupedMode ? 'groupRows' : undefined"
|
||||||
:groupRowRendererParams="groupRowRendererParams"
|
:groupRowRendererParams="groupRowRendererParams"
|
||||||
:animateRows="true"
|
:animateRows="true"
|
||||||
:localeText="AG_GRID_LOCALE_CN"
|
:localeText="AG_GRID_LOCALE_CN"
|
||||||
@ -1018,11 +824,6 @@ const confirmDeleteRow = () => {
|
|||||||
gap: 8px;
|
gap: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 隐藏一级分组行的复选框,但保留折叠箭头 */
|
|
||||||
:deep(.ag-group-cell .ag-cell-value .ag-cell-wrapper > input[type='checkbox']) {
|
|
||||||
display: none !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.work-content-group-check) {
|
:deep(.work-content-group-check) {
|
||||||
width: 14px;
|
width: 14px;
|
||||||
height: 14px;
|
height: 14px;
|
||||||
|
|||||||
@ -138,6 +138,16 @@ const hasMeaningfulFactorValue = (rows: SourceRow[] | undefined) =>
|
|||||||
return hasBudgetValue || hasRemark
|
return hasBudgetValue || hasRemark
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const hasUsablePersistedRows = (state: GridState | null | undefined) =>
|
||||||
|
Array.isArray(state?.detailRows) &&
|
||||||
|
state.detailRows.some(row => {
|
||||||
|
const hasFactor =
|
||||||
|
typeof row?.budgetValue === 'number' ||
|
||||||
|
typeof row?.standardFactor === 'number'
|
||||||
|
const hasRemark = typeof row?.remark === 'string' && row.remark.trim() !== ''
|
||||||
|
return hasFactor || hasRemark || String(row?.id || '').trim() !== ''
|
||||||
|
})
|
||||||
|
|
||||||
const mergeWithDictRows = (rowsFromDb: SourceRow[] | undefined): FactorRow[] => {
|
const mergeWithDictRows = (rowsFromDb: SourceRow[] | undefined): FactorRow[] => {
|
||||||
const dbValueMap = new Map<string, SourceRow>()
|
const dbValueMap = new Map<string, SourceRow>()
|
||||||
for (const row of rowsFromDb || []) {
|
for (const row of rowsFromDb || []) {
|
||||||
@ -308,7 +318,7 @@ const saveFactorChangeState = async (changedRowIds: string[]) => {
|
|||||||
const loadGridState = async (storageKey: string): Promise<GridState | null> => {
|
const loadGridState = async (storageKey: string): Promise<GridState | null> => {
|
||||||
if (!storageKey) return null
|
if (!storageKey) return null
|
||||||
const piniaData = await zxFwPricingStore.loadKeyState<GridState>(storageKey)
|
const piniaData = await zxFwPricingStore.loadKeyState<GridState>(storageKey)
|
||||||
if (piniaData?.detailRows && Array.isArray(piniaData.detailRows)) return piniaData
|
if (hasUsablePersistedRows(piniaData)) return piniaData
|
||||||
|
|
||||||
// 兼容历史 kvStore 数据:命中后迁移到 pinia keyed state。
|
// 兼容历史 kvStore 数据:命中后迁移到 pinia keyed state。
|
||||||
const legacyData = await kvStore.getItem<GridState>(storageKey)
|
const legacyData = await kvStore.getItem<GridState>(storageKey)
|
||||||
|
|||||||
@ -9,7 +9,7 @@ import { withReadonlyAutoHeight } from '@/lib/agGridReadonlyAutoHeight'
|
|||||||
import { decimalAggSum, roundTo, sumByNumber } from '@/lib/decimal'
|
import { decimalAggSum, roundTo, sumByNumber } from '@/lib/decimal'
|
||||||
import { parseNumberOrNull } from '@/lib/number'
|
import { parseNumberOrNull } from '@/lib/number'
|
||||||
import { formatThousandsFlexible } from '@/lib/numberFormat'
|
import { formatThousandsFlexible } from '@/lib/numberFormat'
|
||||||
import { getIndustryDisplayName, getMajorDictEntries, isMajorIdInIndustryScope, xmProjectConfig } from '@/sql'
|
import { getIndustryDisplayName, getMajorDictEntries, isMajorIdInIndustryScope } from '@/sql'
|
||||||
import { syncContractScaleToPricing, type ContractScaleSyncResult } from '@/lib/zxFwPricingSync'
|
import { syncContractScaleToPricing, type ContractScaleSyncResult } from '@/lib/zxFwPricingSync'
|
||||||
import { SwitchRoot, SwitchThumb } from 'reka-ui'
|
import { SwitchRoot, SwitchThumb } from 'reka-ui'
|
||||||
import { useKvStore } from '@/pinia/kv'
|
import { useKvStore } from '@/pinia/kv'
|
||||||
@ -22,6 +22,8 @@ import {
|
|||||||
ToastViewport
|
ToastViewport
|
||||||
} from 'reka-ui'
|
} from 'reka-ui'
|
||||||
import { Button } from '@/components/ui/button'
|
import { Button } from '@/components/ui/button'
|
||||||
|
import { CircleHelp } from 'lucide-vue-next'
|
||||||
|
import { TooltipContent, TooltipProvider, TooltipRoot, TooltipTrigger } from '@/components/ui/tooltip'
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -110,35 +112,14 @@ const buildDetailDict = (entries: Array<[string, MajorLite]>) => {
|
|||||||
hasArea: item.hasArea !== false
|
hasArea: item.hasArea !== false
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
const result = groupOrder.map(code => groupMap.get(code)).filter((group): group is DictGroup => Boolean(group))
|
|
||||||
return result
|
return groupOrder.map(code => groupMap.get(code)).filter((group): group is DictGroup => Boolean(group))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const buildDefaultRows = (): DetailRow[] => {
|
const buildDefaultRows = (): DetailRow[] => {
|
||||||
const rows: DetailRow[] = []
|
const rows: DetailRow[] = []
|
||||||
|
|
||||||
// 1. 首先添加所有分组行(即使没有子项)
|
|
||||||
for (const group of detailDict.value) {
|
|
||||||
if (group.code != 'C0') {
|
|
||||||
rows.push({
|
|
||||||
id: group.id,
|
|
||||||
groupCode: group.code,
|
|
||||||
groupName: group.name,
|
|
||||||
majorCode: group.code,
|
|
||||||
majorName: group.name,
|
|
||||||
hasCost: true,
|
|
||||||
hasArea: true,
|
|
||||||
amount: null,
|
|
||||||
landArea: null,
|
|
||||||
path: [`${group.code} ${group.name}`],
|
|
||||||
isGroupRow: true,
|
|
||||||
hide: false
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const group of detailDict.value) {
|
for (const group of detailDict.value) {
|
||||||
for (const child of group.children) {
|
for (const child of group.children) {
|
||||||
|
|
||||||
@ -345,61 +326,25 @@ interface ContractScaleChangeState {
|
|||||||
updatedAt: number
|
updatedAt: number
|
||||||
}
|
}
|
||||||
|
|
||||||
const XM_PROJECT_PHASE_KEY = 'xm-project-phase-v1'
|
|
||||||
|
|
||||||
interface ProjectPhaseState {
|
|
||||||
feePhase?: string
|
|
||||||
feeStage?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
title: string
|
title: string
|
||||||
dbKey: string
|
dbKey: string
|
||||||
xmInfoKey?: string | null
|
xmInfoKey?: string | null
|
||||||
baseInfoKey?: string
|
baseInfoKey?: string
|
||||||
|
titleHint?: string
|
||||||
|
titleHintAria?: string
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
let persistTimer: ReturnType<typeof setTimeout> | null = null
|
let persistTimer: ReturnType<typeof setTimeout> | null = null
|
||||||
const gridApi = ref<GridApi<DetailRow> | null>(null)
|
const gridApi = ref<GridApi<DetailRow> | null>(null)
|
||||||
const activeIndustryId = ref('')
|
const activeIndustryId = ref('')
|
||||||
const totalLabel = ref(xmProjectConfig.pinnedTotalLabel)
|
const totalLabel = computed(() => {
|
||||||
|
const industryName = getIndustryDisplayName(activeIndustryId.value.trim(), locale.value)
|
||||||
|
return industryName ? t('pricingScale.totalInvestmentByIndustry', { industryName }) : t('pricingScale.totalInvestment')
|
||||||
|
})
|
||||||
const roughCalcEnabled = ref(false)
|
const roughCalcEnabled = ref(false)
|
||||||
const visibleRowData = computed(() => { return detailRows.value.filter(row => !row.hide) })
|
const visibleRowData = computed(() => { return detailRows.value.filter(row => !row.hide) })
|
||||||
|
|
||||||
const feePhaseOptions = xmProjectConfig.feePhaseOptions
|
|
||||||
const feeStageOptions = xmProjectConfig.feeStageOptions
|
|
||||||
const feePhase = ref('')
|
|
||||||
const feeStage = ref('')
|
|
||||||
|
|
||||||
const loadPhaseState = async () => {
|
|
||||||
try {
|
|
||||||
const state = await kvStore.getItem<ProjectPhaseState>(XM_PROJECT_PHASE_KEY)
|
|
||||||
if (state) {
|
|
||||||
feePhase.value = state.feePhase || ''
|
|
||||||
feeStage.value = state.feeStage || ''
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('load phase state failed:', error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const savePhaseState = async () => {
|
|
||||||
try {
|
|
||||||
await kvStore.setItem<ProjectPhaseState>(XM_PROJECT_PHASE_KEY, {
|
|
||||||
feePhase: feePhase.value,
|
|
||||||
feeStage: feeStage.value
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
console.error('save phase state failed:', error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
watch([feePhase, feeStage], () => {
|
|
||||||
void savePhaseState()
|
|
||||||
})
|
|
||||||
|
|
||||||
void loadPhaseState()
|
|
||||||
|
|
||||||
const refreshPinnedTotalLabelCell = () => {
|
const refreshPinnedTotalLabelCell = () => {
|
||||||
if (!gridApi.value) return
|
if (!gridApi.value) return
|
||||||
const pinnedTopNode = gridApi.value.getPinnedTopRow(0)
|
const pinnedTopNode = gridApi.value.getPinnedTopRow(0)
|
||||||
@ -453,26 +398,34 @@ const columnDefs: ColDef<DetailRow>[] = [
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
headerName: '造价金额(元)',
|
headerName: t('pricingScale.columns.landArea'),
|
||||||
field: 'amountYuan',
|
field: 'landArea',
|
||||||
headerClass: 'ag-right-aligned-header',
|
headerClass: 'ag-right-aligned-header',
|
||||||
minWidth: 120,
|
minWidth: 100,
|
||||||
flex: 1,
|
flex: 1,
|
||||||
editable: false,
|
editable: params => !roughCalcEnabled.value && !params.node?.group && !params.node?.rowPinned && Boolean(params.data?.hasArea),
|
||||||
aggFunc: decimalAggSum,
|
cellClass: params =>
|
||||||
valueGetter: params => params.data?.amount,
|
!params.node?.group && !params.node?.rowPinned && params.data?.hasArea
|
||||||
|
? 'editable-cell-line'
|
||||||
|
: '',
|
||||||
cellClassRules: {
|
cellClassRules: {
|
||||||
'ag-right-aligned-cell': () => true
|
'ag-right-aligned-cell': () => true,
|
||||||
|
'editable-cell-empty': params =>
|
||||||
|
!params.node?.group && !params.node?.rowPinned && Boolean(params.data?.hasArea) && (params.value == null || params.value === '')
|
||||||
},
|
},
|
||||||
|
valueParser: params => parseNumberOrNull(params.newValue, { precision: 3 }),
|
||||||
valueFormatter: params => {
|
valueFormatter: params => {
|
||||||
if (roughCalcEnabled.value) {
|
if (roughCalcEnabled.value) {
|
||||||
return ''
|
return ''
|
||||||
}
|
}
|
||||||
const amount = params.value
|
if (!params.node?.group && !params.node?.rowPinned && !params.data?.hasArea) {
|
||||||
if (typeof amount !== 'number' || !Number.isFinite(amount)) {
|
|
||||||
return ''
|
return ''
|
||||||
}
|
}
|
||||||
return formatThousandsFlexible(roundTo(amount * 10000, 0), 0)
|
if (!params.node?.group && !params.node?.rowPinned && (params.value == null || params.value === '')) {
|
||||||
|
return t('pricingScale.clickToInput')
|
||||||
|
}
|
||||||
|
if (params.value == null) return ''
|
||||||
|
return formatThousandsFlexible(params.value, 3)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@ -482,6 +435,9 @@ const autoGroupColumnDef: ColDef = {
|
|||||||
headerName: t('pricingScale.columns.majorGroup'),
|
headerName: t('pricingScale.columns.majorGroup'),
|
||||||
minWidth: 200,
|
minWidth: 200,
|
||||||
flex: 2,
|
flex: 2,
|
||||||
|
cellClassRules: {
|
||||||
|
'ag-summary-label-cell': params => Boolean(params.node?.rowPinned)
|
||||||
|
},
|
||||||
cellRendererParams: {
|
cellRendererParams: {
|
||||||
suppressCount: true
|
suppressCount: true
|
||||||
},
|
},
|
||||||
@ -807,10 +763,24 @@ onMounted(() => {
|
|||||||
<div class="h-full">
|
<div class="h-full">
|
||||||
<div class="rounded-lg border bg-card xmMx scroll-mt-3 flex flex-col overflow-hidden h-full">
|
<div class="rounded-lg border bg-card xmMx scroll-mt-3 flex flex-col overflow-hidden h-full">
|
||||||
<div class="flex items-center justify-between border-b px-4 py-3">
|
<div class="flex items-center justify-between border-b px-4 py-3">
|
||||||
<h3
|
<div class="flex items-center gap-1.5">
|
||||||
class="text-sm font-semibold text-foreground cursor-pointer select-none transition-colors hover:text-primary">
|
<h3
|
||||||
{{ props.title }}
|
class="text-sm font-semibold text-foreground cursor-pointer select-none transition-colors hover:text-primary">
|
||||||
</h3>
|
{{ props.title }}
|
||||||
|
</h3>
|
||||||
|
<TooltipRoot v-if="props.titleHint">
|
||||||
|
<TooltipTrigger as-child>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
:aria-label="props.titleHintAria || props.titleHint"
|
||||||
|
class="inline-flex h-5 w-5 items-center justify-center text-muted-foreground/90 transition hover:text-foreground"
|
||||||
|
>
|
||||||
|
<CircleHelp class="h-4 w-4" />
|
||||||
|
</button>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent side="top">{{ props.titleHint }}</TooltipContent>
|
||||||
|
</TooltipRoot>
|
||||||
|
</div>
|
||||||
<!-- <div class="flex items-center gap-2">
|
<!-- <div class="flex items-center gap-2">
|
||||||
<span class=" text-xs text-muted-foreground">简要计算</span>
|
<span class=" text-xs text-muted-foreground">简要计算</span>
|
||||||
<SwitchRoot
|
<SwitchRoot
|
||||||
@ -822,42 +792,6 @@ onMounted(() => {
|
|||||||
</div> -->
|
</div> -->
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Project Phase Selection -->
|
|
||||||
<div class="border-b px-4 py-3 space-y-3 bg-card">
|
|
||||||
<div class="flex items-center gap-4">
|
|
||||||
<span class="shrink-0 text-sm font-medium text-foreground w-28">项目费用阶段</span>
|
|
||||||
<div class="flex-1 flex flex-wrap gap-2">
|
|
||||||
<button
|
|
||||||
v-for="option in feePhaseOptions"
|
|
||||||
:key="option"
|
|
||||||
class="px-3 py-1.5 text-xs rounded-lg border transition-colors"
|
|
||||||
:class="feePhase === option
|
|
||||||
? 'bg-blue-600 border-blue-600 text-white font-medium'
|
|
||||||
: 'bg-white border-slate-200 text-slate-600 hover:bg-slate-50'"
|
|
||||||
@click="feePhase = option"
|
|
||||||
>
|
|
||||||
{{ option }}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="flex items-center gap-4">
|
|
||||||
<span class="shrink-0 text-sm font-medium text-foreground w-28">项目费用环节</span>
|
|
||||||
<div class="flex-1 flex flex-wrap gap-2">
|
|
||||||
<button
|
|
||||||
v-for="option in feeStageOptions"
|
|
||||||
:key="option"
|
|
||||||
class="px-3 py-1.5 text-xs rounded-lg border transition-colors"
|
|
||||||
:class="feeStage === option
|
|
||||||
? 'bg-blue-600 border-blue-600 text-white font-medium'
|
|
||||||
: 'bg-white border-slate-200 text-slate-600 hover:bg-slate-50'"
|
|
||||||
@click="feeStage = option"
|
|
||||||
>
|
|
||||||
{{ option }}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div :class="agGridWrapClass">
|
<div :class="agGridWrapClass">
|
||||||
<AgGridVue :style="agGridStyle" :rowData="visibleRowData" :pinnedTopRowData="pinnedTopRowData"
|
<AgGridVue :style="agGridStyle" :rowData="visibleRowData" :pinnedTopRowData="pinnedTopRowData"
|
||||||
:columnDefs="gridColumnDefs" :autoGroupColumnDef="autoGroupColumnDef" :gridOptions="detailGridOptions" :theme="myTheme"
|
:columnDefs="gridColumnDefs" :autoGroupColumnDef="autoGroupColumnDef" :gridOptions="detailGridOptions" :theme="myTheme"
|
||||||
|
|||||||
@ -44,24 +44,3 @@
|
|||||||
opacity: 0;
|
opacity: 0;
|
||||||
transform: translateY(-6px) scale(0.98);
|
transform: translateY(-6px) scale(0.98);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Sidebar slide transitions */
|
|
||||||
.slide-in-left-enter-active,
|
|
||||||
.slide-in-left-leave-active {
|
|
||||||
transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.slide-in-left-enter-from,
|
|
||||||
.slide-in-left-leave-to {
|
|
||||||
transform: translateX(100%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.fade-enter-active,
|
|
||||||
.fade-leave-active {
|
|
||||||
transition: opacity 0.3s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.fade-enter-from,
|
|
||||||
.fade-leave-to {
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
|
|||||||
@ -1,25 +1,41 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, onBeforeUnmount, onMounted, ref } from 'vue'
|
import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
import { Button } from '@/components/ui/button'
|
import { Button } from '@/components/ui/button'
|
||||||
import { useTabStore } from '@/pinia/tab'
|
import { useTabStore } from '@/pinia/tab'
|
||||||
import { useKvStore } from '@/pinia/kv'
|
import { useKvStore } from '@/pinia/kv'
|
||||||
import { useUiPrefsStore } from '@/pinia/uiPrefs'
|
import { useUiPrefsStore } from '@/pinia/uiPrefs'
|
||||||
import {
|
import {
|
||||||
BarChart3,
|
|
||||||
Calculator,
|
Calculator,
|
||||||
Check,
|
Check,
|
||||||
|
ChevronDown,
|
||||||
Languages,
|
Languages,
|
||||||
X
|
X
|
||||||
} from 'lucide-vue-next'
|
} from 'lucide-vue-next'
|
||||||
import { getIndustryDisplayName, industryTypeList } from '@/sql'
|
import { getIndustryDisplayName, industryTypeList } from '@/sql'
|
||||||
import { initializeProjectFactorStates, initializeProjectScaleState } from '@/lib/projectWorkspace'
|
import { initializeProjectFactorStates, initializeProjectScaleState } from '@/lib/projectWorkspace'
|
||||||
import {
|
import {
|
||||||
|
SelectContent,
|
||||||
|
SelectIcon,
|
||||||
|
SelectItem,
|
||||||
|
SelectItemIndicator,
|
||||||
|
SelectItemText,
|
||||||
|
SelectPortal,
|
||||||
|
SelectRoot,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectViewport
|
||||||
|
} from 'reka-ui'
|
||||||
|
import {
|
||||||
|
buildDisclaimerUrl,
|
||||||
buildProjectUrl,
|
buildProjectUrl,
|
||||||
|
consumePendingDisclaimerAction,
|
||||||
DEFAULT_PROJECT_ID,
|
DEFAULT_PROJECT_ID,
|
||||||
|
hasAcceptedRestrictedDisclaimer,
|
||||||
FORCE_HOME_QUERY_KEY,
|
FORCE_HOME_QUERY_KEY,
|
||||||
|
isDisclaimerAcceptanceRequired,
|
||||||
NEW_PROJECT_QUERY_KEY,
|
NEW_PROJECT_QUERY_KEY,
|
||||||
OPEN_PROJECT_DIALOG_QUERY_KEY,
|
OPEN_PROJECT_DIALOG_QUERY_KEY,
|
||||||
|
setPendingDisclaimerAction,
|
||||||
PROJECT_TAB_ID,
|
PROJECT_TAB_ID,
|
||||||
QUICK_PROJECT_ID,
|
QUICK_PROJECT_ID,
|
||||||
QUICK_CONSULT_CATEGORY_FACTOR_KEY,
|
QUICK_CONSULT_CATEGORY_FACTOR_KEY,
|
||||||
@ -65,12 +81,13 @@ const PROJECT_INFO_KEY = 'xm-base-info-v1'
|
|||||||
const PROJECT_CONSULT_CATEGORY_FACTOR_KEY = 'xm-consult-category-factor-v1'
|
const PROJECT_CONSULT_CATEGORY_FACTOR_KEY = 'xm-consult-category-factor-v1'
|
||||||
const PROJECT_MAJOR_FACTOR_KEY = 'xm-major-factor-v1'
|
const PROJECT_MAJOR_FACTOR_KEY = 'xm-major-factor-v1'
|
||||||
const PROJECT_SCALE_KEY = 'xm-info-v3'
|
const PROJECT_SCALE_KEY = 'xm-info-v3'
|
||||||
|
const FILE_LEDGER_URL = 'https://www.lianzhong.com.cn/file?fileNo=24'
|
||||||
const getActiveProjectId = () => readCurrentProjectId()
|
const getActiveProjectId = () => readCurrentProjectId()
|
||||||
|
|
||||||
const tabStore = useTabStore()
|
const tabStore = useTabStore()
|
||||||
const kvStore = useKvStore()
|
const kvStore = useKvStore()
|
||||||
const uiPrefsStore = useUiPrefsStore()
|
const uiPrefsStore = useUiPrefsStore()
|
||||||
const { t, locale } = useI18n()
|
const { t, tm, locale } = useI18n()
|
||||||
const projectDialogOpen = ref(false)
|
const projectDialogOpen = ref(false)
|
||||||
const projectIndustry = ref(String(industryTypeList[0]?.id || ''))
|
const projectIndustry = ref(String(industryTypeList[0]?.id || ''))
|
||||||
const projectSubmitting = ref(false)
|
const projectSubmitting = ref(false)
|
||||||
@ -82,21 +99,24 @@ const homeImportConfirmOpen = ref(false)
|
|||||||
const pendingHomeImportFile = ref<File | null>(null)
|
const pendingHomeImportFile = ref<File | null>(null)
|
||||||
const pendingHomeImportFileName = ref('')
|
const pendingHomeImportFileName = ref('')
|
||||||
const existingProjectDialogOpen = ref(false)
|
const existingProjectDialogOpen = ref(false)
|
||||||
|
const disclaimerRequired = ref(false)
|
||||||
const existingProjects = ref<Array<{ id: string; name: string; updatedAt: string }>>([])
|
const existingProjects = ref<Array<{ id: string; name: string; updatedAt: string }>>([])
|
||||||
const existingProjectLoading = ref(false)
|
const existingProjectLoading = ref(false)
|
||||||
const hasExistingProjects = ref(false)
|
const hasExistingProjects = ref(false)
|
||||||
const openedProjectIds = ref<string[]>([])
|
const openedProjectIds = ref<string[]>([])
|
||||||
let existingProjectPollTimer: ReturnType<typeof setInterval> | null = null
|
let existingProjectPollTimer: ReturnType<typeof setInterval> | null = null
|
||||||
const RELATED_FILES_URL = 'https://www.lianzhong.com.cn/file'
|
const heroTitleIndex = ref(0)
|
||||||
|
const heroDescIndex = ref(0)
|
||||||
|
const projectIndustryLabel = computed(() => {
|
||||||
|
const target = String(projectIndustry.value || '').trim()
|
||||||
|
if (!target) return ''
|
||||||
|
return getIndustryDisplayName(target, locale.value) || ''
|
||||||
|
})
|
||||||
const localeBadge = computed(() => (locale.value === 'en-US' ? 'EN' : '中'))
|
const localeBadge = computed(() => (locale.value === 'en-US' ? 'EN' : '中'))
|
||||||
const toggleLocale = () => {
|
const toggleLocale = () => {
|
||||||
const next = locale.value === 'en-US' ? 'zh-CN' : 'en-US'
|
const next = locale.value === 'en-US' ? 'zh-CN' : 'en-US'
|
||||||
uiPrefsStore.setLocale(next as 'zh-CN' | 'en-US')
|
uiPrefsStore.setLocale(next as 'zh-CN' | 'en-US')
|
||||||
}
|
}
|
||||||
|
|
||||||
const openRelatedFiles = () => {
|
|
||||||
window.open(RELATED_FILES_URL, '_blank', 'noopener')
|
|
||||||
}
|
|
||||||
const resolveProjectRegistryName = (projectIdRaw: string) => {
|
const resolveProjectRegistryName = (projectIdRaw: string) => {
|
||||||
const projectId = String(projectIdRaw || '').trim()
|
const projectId = String(projectIdRaw || '').trim()
|
||||||
if (projectId !== DEFAULT_PROJECT_ID) return undefined
|
if (projectId !== DEFAULT_PROJECT_ID) return undefined
|
||||||
@ -162,8 +182,35 @@ const loadProjectDefaults = async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const openProjectCalc = async () => {
|
const openProjectCalc = async () => {
|
||||||
|
await runWithDisclaimerGuard({ type: 'project' }, async () => {
|
||||||
projectDialogOpen.value = true
|
await loadProjectDefaults()
|
||||||
|
projectDialogOpen.value = true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const openRelatedFiles = () => {
|
||||||
|
window.open(FILE_LEDGER_URL, '_blank', 'noopener')
|
||||||
|
}
|
||||||
|
|
||||||
|
const redirectToDisclaimerPage = () => {
|
||||||
|
const returnUrl = window.location.href
|
||||||
|
window.location.href = buildDisclaimerUrl(returnUrl)
|
||||||
|
}
|
||||||
|
|
||||||
|
const runWithDisclaimerGuard = async (
|
||||||
|
pendingAction: { type: 'project' | 'quick' | 'import' | 'existing-project'; projectId?: string },
|
||||||
|
action: () => void | Promise<void>
|
||||||
|
) => {
|
||||||
|
if (!disclaimerRequired.value || hasAcceptedRestrictedDisclaimer()) {
|
||||||
|
await action()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
setPendingDisclaimerAction(pendingAction)
|
||||||
|
redirectToDisclaimerPage()
|
||||||
|
}
|
||||||
|
|
||||||
|
const syncDisclaimerRequirement = () => {
|
||||||
|
disclaimerRequired.value = isDisclaimerAcceptanceRequired()
|
||||||
}
|
}
|
||||||
|
|
||||||
const syncExistingProjectOpenedState = (projectIds: string[]) => {
|
const syncExistingProjectOpenedState = (projectIds: string[]) => {
|
||||||
@ -216,10 +263,11 @@ const startExistingProjectPolling = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const openExistingProjectDialog = async () => {
|
const openExistingProjectDialog = async () => {
|
||||||
projectDialogOpen.value = false
|
await runWithDisclaimerGuard({ type: 'existing-project' }, async () => {
|
||||||
existingProjectDialogOpen.value = true
|
existingProjectDialogOpen.value = true
|
||||||
await refreshExistingProjects()
|
await refreshExistingProjects()
|
||||||
startExistingProjectPolling()
|
startExistingProjectPolling()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const closeExistingProjectDialog = () => {
|
const closeExistingProjectDialog = () => {
|
||||||
@ -310,8 +358,38 @@ const enterQuickCalc = (contractName: string) => {
|
|||||||
tabStore.hasCompletedSetup = true
|
tabStore.hasCompletedSetup = true
|
||||||
}
|
}
|
||||||
|
|
||||||
const openQuickCalc = () => {
|
const openQuickCalc = async () => {
|
||||||
window.alert(t('home.cards.developing'))
|
await runWithDisclaimerGuard({ type: 'quick' }, async () => {
|
||||||
|
await loadQuickDefaults()
|
||||||
|
const contractName = quickContractName.value.trim() || QUICK_CONTRACT_FALLBACK_NAME
|
||||||
|
const industry = quickIndustry.value.trim()
|
||||||
|
|
||||||
|
quickSubmitting.value = true
|
||||||
|
try {
|
||||||
|
const currentInfo = await kvStore.getItem<QuickProjectInfoState>(QUICK_PROJECT_INFO_KEY)
|
||||||
|
await kvStore.setItem(QUICK_PROJECT_INFO_KEY, {
|
||||||
|
...currentInfo,
|
||||||
|
projectIndustry: industry,
|
||||||
|
projectName: t('quickCalc.projectName')
|
||||||
|
})
|
||||||
|
await kvStore.setItem(QUICK_CONTRACT_META_KEY, {
|
||||||
|
id: QUICK_CONTRACT_ID,
|
||||||
|
name: contractName,
|
||||||
|
updatedAt: new Date().toISOString()
|
||||||
|
})
|
||||||
|
if (industry) {
|
||||||
|
await initializeProjectFactorStates(
|
||||||
|
kvStore,
|
||||||
|
industry,
|
||||||
|
QUICK_CONSULT_CATEGORY_FACTOR_KEY,
|
||||||
|
QUICK_MAJOR_FACTOR_KEY
|
||||||
|
)
|
||||||
|
}
|
||||||
|
enterQuickCalc(contractName)
|
||||||
|
} finally {
|
||||||
|
quickSubmitting.value = false
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleHomeImportChange = (event: Event) => {
|
const handleHomeImportChange = (event: Event) => {
|
||||||
@ -325,7 +403,33 @@ const handleHomeImportChange = (event: Event) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const openHomeImport = () => {
|
const openHomeImport = () => {
|
||||||
homeImportInputRef.value?.click()
|
void runWithDisclaimerGuard({ type: 'import' }, () => {
|
||||||
|
homeImportInputRef.value?.click()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const replayPendingDisclaimerAction = async () => {
|
||||||
|
if (!disclaimerRequired.value || !hasAcceptedRestrictedDisclaimer()) return
|
||||||
|
const pendingAction = consumePendingDisclaimerAction()
|
||||||
|
if (!pendingAction) return
|
||||||
|
if (pendingAction.type === 'project') {
|
||||||
|
await loadProjectDefaults()
|
||||||
|
projectDialogOpen.value = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (pendingAction.type === 'quick') {
|
||||||
|
await openQuickCalc()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (pendingAction.type === 'import') {
|
||||||
|
homeImportInputRef.value?.click()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (pendingAction.type === 'existing-project') {
|
||||||
|
existingProjectDialogOpen.value = true
|
||||||
|
await refreshExistingProjects()
|
||||||
|
startExistingProjectPolling()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const cancelHomeImportConfirm = () => {
|
const cancelHomeImportConfirm = () => {
|
||||||
@ -354,10 +458,85 @@ const handleHomeVisibilityChange = () => {
|
|||||||
handleHomeWindowFocus()
|
handleHomeWindowFocus()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const openDisclaimerPage = () => {
|
||||||
|
const href = buildDisclaimerUrl(window.location.href)
|
||||||
|
window.open(href, '_blank', 'noopener')
|
||||||
|
}
|
||||||
|
|
||||||
|
const resolveLocalizedStringArray = (value: unknown) => {
|
||||||
|
if (!Array.isArray(value)) return []
|
||||||
|
return value.filter((item): item is string => typeof item === 'string' && item.trim().length > 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
const heroTitleOptions = computed(() =>
|
||||||
|
resolveLocalizedStringArray(tm('home.cards.heroTitles'))
|
||||||
|
)
|
||||||
|
const heroDescOptions = computed(() =>
|
||||||
|
resolveLocalizedStringArray(tm('home.cards.heroDescs'))
|
||||||
|
)
|
||||||
|
const heroTitleText = computed(() => heroTitleOptions.value[heroTitleIndex.value] || '')
|
||||||
|
const heroDescText = computed(() => heroDescOptions.value[heroDescIndex.value] || '')
|
||||||
|
|
||||||
|
const pickRandomIndex = (length: number) => {
|
||||||
|
if (length <= 1) return 0
|
||||||
|
return Math.floor(Math.random() * length)
|
||||||
|
}
|
||||||
|
|
||||||
|
const refreshHeroCopy = () => {
|
||||||
|
heroTitleIndex.value = pickRandomIndex(heroTitleOptions.value.length)
|
||||||
|
heroDescIndex.value = pickRandomIndex(heroDescOptions.value.length)
|
||||||
|
}
|
||||||
|
|
||||||
|
const homeActionCards = [
|
||||||
|
{
|
||||||
|
key: 'projectBudget',
|
||||||
|
desc: 'projectBudgetDesc',
|
||||||
|
action: 'enter',
|
||||||
|
icon: 'project',
|
||||||
|
iconWrapClass: 'border-blue-100 bg-blue-50/80 text-blue-600',
|
||||||
|
iconClass: 'h-5 w-5',
|
||||||
|
clickFunc: openProjectCalc,
|
||||||
|
showExistingAction: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'quickCalc',
|
||||||
|
desc: 'quickCalcDesc',
|
||||||
|
action: 'enter',
|
||||||
|
icon: 'quick',
|
||||||
|
iconWrapClass: 'border-amber-100 bg-amber-50/80 text-amber-600',
|
||||||
|
iconClass: 'h-10 w-10',
|
||||||
|
clickFunc: openQuickCalc,
|
||||||
|
showExistingAction: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'importData',
|
||||||
|
desc: 'importDataDesc',
|
||||||
|
action: 'pickFile',
|
||||||
|
icon: 'import',
|
||||||
|
iconWrapClass: 'border-emerald-100 bg-emerald-50/80 text-emerald-600',
|
||||||
|
iconClass: 'h-5 w-5',
|
||||||
|
clickFunc: openHomeImport,
|
||||||
|
showExistingAction: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'file',
|
||||||
|
desc: 'fileDataDesc',
|
||||||
|
action: 'openFileSystem',
|
||||||
|
icon: 'files',
|
||||||
|
iconWrapClass: 'border-emerald-100 bg-emerald-50/80 text-emerald-600',
|
||||||
|
iconClass: 'h-5 w-5',
|
||||||
|
clickFunc: openRelatedFiles,
|
||||||
|
showExistingAction: false
|
||||||
|
}
|
||||||
|
] as const
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
syncDisclaimerRequirement()
|
||||||
void refreshExistingProjects()
|
void refreshExistingProjects()
|
||||||
void loadProjectDefaults()
|
void loadProjectDefaults()
|
||||||
void loadQuickDefaults()
|
void loadQuickDefaults()
|
||||||
|
void replayPendingDisclaimerAction()
|
||||||
|
refreshHeroCopy()
|
||||||
window.addEventListener('focus', handleHomeWindowFocus)
|
window.addEventListener('focus', handleHomeWindowFocus)
|
||||||
document.addEventListener('visibilitychange', handleHomeVisibilityChange)
|
document.addEventListener('visibilitychange', handleHomeVisibilityChange)
|
||||||
try {
|
try {
|
||||||
@ -385,127 +564,109 @@ onBeforeUnmount(() => {
|
|||||||
window.removeEventListener('focus', handleHomeWindowFocus)
|
window.removeEventListener('focus', handleHomeWindowFocus)
|
||||||
document.removeEventListener('visibilitychange', handleHomeVisibilityChange)
|
document.removeEventListener('visibilitychange', handleHomeVisibilityChange)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => locale.value,
|
||||||
|
() => {
|
||||||
|
refreshHeroCopy()
|
||||||
|
}
|
||||||
|
)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<input ref="homeImportInputRef" type="file" accept=".zw" class="sr-only" @change="handleHomeImportChange" />
|
<input ref="homeImportInputRef" type="file" accept=".zw" class="sr-only" @change="handleHomeImportChange" />
|
||||||
<div class="home-entry relative flex min-h-full items-center justify-center overflow-hidden px-4 py-8 lg:py-10">
|
<div class="home-entry relative flex min-h-screen items-center justify-center px-4 py-8 lg:py-10">
|
||||||
<div class="pointer-events-none absolute inset-0 bg-[url('/background.png')] bg-cover bg-center bg-no-repeat" />
|
<div class="pointer-events-none absolute inset-0 bg-[radial-gradient(ellipse_80%_60%_at_50%_-10%,rgba(59,130,246,0.06),transparent_70%)]" />
|
||||||
<div class="pointer-events-none absolute inset-0 bg-white/78" />
|
|
||||||
<div
|
|
||||||
class="pointer-events-none absolute inset-0 bg-[radial-gradient(ellipse_80%_60%_at_50%_-10%,rgba(59,130,246,0.08),transparent_70%)]"
|
|
||||||
/>
|
|
||||||
<div class="relative w-full max-w-[1240px]">
|
<div class="relative w-full max-w-[1240px]">
|
||||||
<div class="absolute right-0 top-0 z-10">
|
<div class="absolute right-0 top-0 z-10">
|
||||||
<button
|
<Button
|
||||||
type="button"
|
variant="outline"
|
||||||
class="inline-flex h-8 cursor-pointer items-center justify-center gap-1.5 rounded-full border border-slate-200/80 bg-white/85 px-3 text-xs text-slate-600 shadow-sm backdrop-blur transition hover:bg-white focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-200"
|
size="sm"
|
||||||
|
class="h-8 cursor-pointer gap-1.5 rounded-full border-slate-200/80 bg-white/85 px-3 text-xs text-slate-600 shadow-sm backdrop-blur transition hover:bg-white"
|
||||||
@click="toggleLocale"
|
@click="toggleLocale"
|
||||||
>
|
>
|
||||||
<Languages class="h-3.5 w-3.5" />
|
<Languages class="h-3.5 w-3.5" />
|
||||||
<span>{{ localeBadge }}</span>
|
<span>{{ localeBadge }}</span>
|
||||||
</button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="home-title text-center" :style="{ maxWidth: '800px' }">
|
||||||
<div class="home-title text-center">
|
|
||||||
<h1
|
<h1
|
||||||
class="text-2xl font-semibold tracking-tight text-slate-900 lg:text-3xl"
|
class="text-2xl tracking-tight text-slate-900 lg:text-3xl"
|
||||||
style="font-family: HarmonyOS_Sans_SC, 'Microsoft YaHei', sans-serif; font-weight: 300; white-space: pre-line;"
|
:style="{ whiteSpace: 'pre-line', fontWeight: '200', fontFamily: 'HarmonyOS_Sans_SC' }"
|
||||||
>
|
>
|
||||||
{{ t('home.title') }}
|
{{ t('home.title') }}
|
||||||
</h1>
|
</h1>
|
||||||
<p class="mt-1.5 text-sm text-slate-500">{{ t('home.subtitle') }}</p>
|
<p class="mt-1.5 text-sm text-slate-500">{{ t('home.subtitle') }}</p>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="mt-5 grid items-stretch gap-4 md:grid-cols-2 xl:grid-cols-5">
|
||||||
<div class="mt-5 grid items-stretch gap-4 md:grid-cols-2 xl:grid-cols-4">
|
<div
|
||||||
<section
|
class="home-hero home-card-base home-entry-item home-entry-item--1 relative overflow-hidden rounded-2xl bg-[#dc2626] p-7 text-white shadow-[0_24px_60px_rgba(153,27,27,0.35)]"
|
||||||
class="home-card-base home-entry-item home-entry-item--1 relative overflow-hidden rounded-2xl bg-[#dc2626] p-7 text-white shadow-[0_24px_60px_rgba(153,27,27,0.35)]"
|
|
||||||
>
|
>
|
||||||
<div class="pointer-events-none absolute -right-20 -top-16 h-56 w-56 rounded-full bg-white/12 blur-2xl" />
|
<div class="pointer-events-none absolute -right-20 -top-16 h-56 w-56 rounded-full bg-white/12 blur-2xl" />
|
||||||
<div class="pointer-events-none absolute -bottom-10 -left-10 h-40 w-40 rounded-full bg-white/8 blur-3xl" />
|
<div class="pointer-events-none absolute -left-10 -bottom-10 h-40 w-40 rounded-full bg-white/8 blur-3xl" />
|
||||||
<div v-for="index in 10" :key="index" :class="`home-hero-meteor home-hero-meteor--${index}`" />
|
<div v-for="index in 10" :key="index" :class="`home-hero-meteor home-hero-meteor--${index}`" />
|
||||||
<div class="relative inline-flex h-11 w-11 items-center justify-center rounded-xl bg-white/15 ring-1 ring-white/35">
|
<div class="relative inline-flex h-11 w-11 items-center justify-center rounded-xl bg-white/15 ring-1 ring-white/35">
|
||||||
<BarChart3 class="h-5 w-5" />
|
<Calculator class="h-5 w-5" />
|
||||||
</div>
|
</div>
|
||||||
<h2 class="relative mt-8 whitespace-pre-line text-xl font-semibold leading-tight tracking-tight">
|
<h2 class="relative mt-8 text-2xl font-semibold leading-tight tracking-tight lg:text-3xl" :style="{ whiteSpace: 'pre-line', fontSize: '20px' }">{{ heroTitleText }}</h2>
|
||||||
{{ t('home.cards.heroTitle') }}
|
|
||||||
</h2>
|
|
||||||
<p class="relative mt-2 text-sm text-red-200/90">{{ t('home.cards.heroSubTitle') }}</p>
|
<p class="relative mt-2 text-sm text-red-200/90">{{ t('home.cards.heroSubTitle') }}</p>
|
||||||
<div class="relative mt-6 h-px bg-white/20" />
|
<div class="relative mt-6 h-px bg-white/20" />
|
||||||
<p class="relative mt-4 whitespace-pre-line text-xs leading-5 text-red-200/80">{{ t('home.cards.heroDesc') }}</p>
|
<p class="relative mt-4 whitespace-pre-line text-xs leading-5 text-red-200/60">{{ heroDescText }}</p>
|
||||||
</section>
|
</div>
|
||||||
|
|
||||||
<article
|
<article
|
||||||
|
v-for="(card, index) in homeActionCards"
|
||||||
|
:key="card.key"
|
||||||
role="button"
|
role="button"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
class="home-card-base home-entry-item home-entry-item--2 group flex cursor-pointer flex-col justify-between rounded-xl border border-slate-200/80 bg-white/95 px-5 py-5 shadow-[0_4px_20px_rgba(15,23,42,0.06)] backdrop-blur-sm transition-all duration-200 hover:-translate-y-0.5 hover:shadow-[0_12px_32px_rgba(15,23,42,0.12)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-200"
|
:class="[
|
||||||
@click="openProjectCalc"
|
'home-card home-card-base home-entry-item group flex cursor-pointer flex-col justify-between rounded-xl border border-slate-200/80 bg-white/95 px-5 py-5 shadow-[0_4px_20px_rgba(15,23,42,0.06)] backdrop-blur-sm transition-all duration-200 hover:-translate-y-0.5 hover:shadow-[0_12px_32px_rgba(15,23,42,0.12)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-200',
|
||||||
@keydown.enter.prevent="openProjectCalc"
|
`home-entry-item--${index + 2}`
|
||||||
@keydown.space.prevent="openProjectCalc"
|
]"
|
||||||
|
@click="card.clickFunc"
|
||||||
|
@keydown.enter.prevent="card.clickFunc"
|
||||||
|
@keydown.space.prevent="card.clickFunc"
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<div class="inline-flex h-11 w-11 items-center justify-center rounded-xl border border-blue-100 bg-blue-50/80 text-blue-600 shadow-sm transition-transform duration-200 group-hover:scale-105">
|
<div
|
||||||
<BarChart3 class="h-5 w-5" />
|
:class="[
|
||||||
</div>
|
'inline-flex h-11 w-11 items-center justify-center rounded-xl border shadow-sm transition-transform duration-200 group-hover:scale-105',
|
||||||
<h3 class="mt-4 text-base font-semibold text-slate-900">{{ t('home.cards.projectBudget') }}</h3>
|
card.iconWrapClass
|
||||||
<p class="mt-1.5 text-xs leading-5 text-slate-500">{{ t('home.cards.projectBudgetDesc') }}</p>
|
]"
|
||||||
</div>
|
|
||||||
<div class="mt-4 flex flex-wrap items-center justify-end gap-2">
|
|
||||||
<button
|
|
||||||
v-if="hasExistingProjects"
|
|
||||||
type="button"
|
|
||||||
class="cursor-pointer rounded-md border border-slate-200 px-3 py-1.5 text-xs font-medium text-slate-600 transition hover:border-slate-300 hover:bg-slate-50 hover:text-slate-700"
|
|
||||||
@click.stop="openExistingProjectDialog"
|
|
||||||
>
|
>
|
||||||
{{ t('home.cards.pickExisting') }}
|
<svg v-if="card.icon === 'project'" viewBox="0 0 1024 1024" :class="card.iconClass" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
|
||||||
</button>
|
<path
|
||||||
<button
|
fill="currentColor"
|
||||||
type="button"
|
d="M938.666667 874.666667c0 11.733333-9.6 21.333333-21.333334 21.333333H106.666667c-11.733333 0-21.333333-9.6-21.333334-21.333333s9.6-21.333333 21.333334-21.333334h42.666666V490.666667c0-11.733333 9.6-21.333333 21.333334-21.333334h170.666666c11.733333 0 21.333333 9.6 21.333334 21.333334v362.666666h42.666666V320c0-11.733333 9.6-21.333333 21.333334-21.333333h170.666666c11.733333 0 21.333333 9.6 21.333334 21.333333v533.333333h42.666666V149.333333c0-11.733333 9.6-21.333333 21.333334-21.333333h170.666666c11.733333 0 21.333333 9.6 21.333334 21.333333v704h42.666666c11.733333 0 21.333333 9.6 21.333334 21.333334z"
|
||||||
class="cursor-pointer rounded-md bg-blue-600 px-3 py-1.5 text-xs font-medium text-white transition hover:bg-blue-700"
|
/>
|
||||||
@click.stop="openProjectCalc"
|
</svg>
|
||||||
>
|
<svg v-else-if="card.icon === 'quick'" viewBox="0 0 800 800" :class="card.iconClass" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
|
||||||
<span class="flex items-center gap-1">
|
<path
|
||||||
{{ t('home.cards.enter') }}
|
fill="currentColor"
|
||||||
<svg class="h-3.5 w-3.5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14"/><path d="m12 5 7 7-7 7"/></svg>
|
d="M5245 5891c-11-5-47-38-80-73-33-36-269-281-525-544-256-263-514-530-575-592-90-93-113-123-130-170-20-54-79-170-200-392-70-129-81-164-61-194 22-35 59-40 114-15 26 11 119 47 207 79 88 33 185 69 215 81 30 12 83 31 118 44 58 20 82 40 300 246 130 123 291 276 357 339 66 63 165 158 219 210 55 52 105 100 111 106 57 58 205 194 212 194 4 0 7-520 5-1155l-2-1155-553 0c-343 0-576-4-613-11-86-15-175-46-227-79-25-15-48-26-51-23-3 4-6 154-6 335l0 328-80 0-80 0 0-336 0-336-32 22c-44 29-106 56-176 77-50 15-130 17-647 22l-590 6 0 1165 0 1165 440 0c467 0 528-4 702-50 105-28 200-69 261-114 41-31 42-32 42-91l0-60 80 0 80 0 1 33c3 69 8 82 40 110 19 16 63 43 99 59 36 16 71 33 79 37 13 7 56 169 47 178-6 6-170-49-221-74-27-14-66-38-86-54l-35-28-32 25c-51 40-181 101-274 128-188 54-249 59-840 63l-548 4 0-1330 0-1331 619 0c669 0 715-3 826-54 63-29 138-94 155-136 12-30 13-30 91-30l78 0 17 35c20 44 70 87 137 122 114 58 98 56 807 62l655 6 3 1313c2 1296 2 1314 22 1339 34 43 21 153-29 252-35 67-147 173-224 211-70 34-179 50-222 31z m171-190c72-42 154-148 154-200 0-12-47-64-125-138-69-65-129-119-133-121-4-1-60 51-125 116l-117 118 121 127c136 144 141 146 225 98z m-341-468l110-114-65-62c-36-33-110-104-165-157-385-367-609-575-618-575-7 0-54 44-106 97l-94 97 335 343c184 189 366 376 403 416 38 39 73 71 79 71 6-1 61-53 121-116z m-905-994c0-7-193-79-211-79-5 0 14 46 42 101l51 102 59-59c32-32 59-61 59-65z"
|
||||||
</span>
|
transform="translate(0,800) scale(0.1,-0.1)"
|
||||||
</button>
|
/>
|
||||||
</div>
|
<path
|
||||||
</article>
|
fill="currentColor"
|
||||||
|
d="M1897 4983c-9-8-9-2599-1-2630l6-23 787 0c432 0 791-4 797-8 6-4 18-21 27-38 24-46 102-113 170-144 169-79 482-78 640 1 96 49 141 90 193 177 6 9 97 11 822 11l782 1 0 1330 0 1330-85 0-85 0 0-1250 0-1250-773 0-772 0-18-51c-24-66-84-131-146-158-131-56-369-51-491 11-69 35-121 96-138 162l-8 31-775 5-774 5-2 1150c-2 633-3 1194-3 1248l0 97-73 0c-41 0-77-3-80-7z"
|
||||||
<article
|
transform="translate(0,800) scale(0.1,-0.1)"
|
||||||
role="button"
|
/>
|
||||||
tabindex="0"
|
<path
|
||||||
class="home-card-base home-entry-item home-entry-item--3 group flex cursor-pointer flex-col justify-between rounded-xl border border-slate-200/80 bg-white/95 px-5 py-5 shadow-[0_4px_20px_rgba(15,23,42,0.06)] backdrop-blur-sm transition-all duration-200 hover:-translate-y-0.5 hover:shadow-[0_12px_32px_rgba(15,23,42,0.12)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-200"
|
fill="currentColor"
|
||||||
@click="openQuickCalc"
|
d="M3391 3872c-261-2-347-5-353-15-4-6-8-38-8-69 0-47 4-59 19-68 13-6 214-10 584-10 474 0 566 2 576 14 7 9 11 40 9 78l-3 63-240 5c-132 3-395 4-584 2z"
|
||||||
@keydown.enter.prevent="openQuickCalc"
|
transform="translate(0,800) scale(0.1,-0.1)"
|
||||||
@keydown.space.prevent="openQuickCalc"
|
/>
|
||||||
>
|
</svg>
|
||||||
<div>
|
<svg v-else-if="card.icon === 'import'" viewBox="0 0 1024 1024" :class="card.iconClass" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
|
||||||
<div class="inline-flex h-11 w-11 items-center justify-center rounded-xl border border-amber-100 bg-amber-50/80 text-amber-600 shadow-sm transition-transform duration-200 group-hover:scale-105">
|
<path
|
||||||
<Calculator class="h-5 w-5" />
|
fill="currentColor"
|
||||||
</div>
|
d="M154.579478 1001.73913v-332.844521h89.043479V912.695652H912.695652V369.530435h-234.896695V111.304348H243.890087v349.184h-89.043478V22.26087h585.683478l261.431652 263.924869V1001.73913z m612.173913-721.252173h104.314435l-104.314435-105.293914z m-416.857043 411.469913l79.026087-79.026087H22.26087v-89.043479h406.661565L349.94087 444.861217l41.138087-41.22713 123.592347 123.592348 41.227131 41.182608-41.227131 41.138087-123.592347 123.592348z m123.013565-123.013566l0.489739-0.534261-0.489739-0.489739z"
|
||||||
<h3 class="mt-4 text-base font-semibold text-slate-900">{{ t('home.cards.quickCalc') }}</h3>
|
/>
|
||||||
<p class="mt-1.5 text-xs leading-5 text-slate-500">{{ t('home.cards.quickCalcDesc') }}</p>
|
</svg>
|
||||||
</div>
|
|
||||||
<div class="mt-4 flex items-center text-xs font-medium text-slate-400 transition-colors group-hover:text-slate-600">
|
|
||||||
<span>{{ t('home.cards.developing') }}</span>
|
|
||||||
<svg class="ml-1 h-3.5 w-3.5 transition-transform group-hover:translate-x-0.5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14"/><path d="m12 5 7 7-7 7"/></svg>
|
|
||||||
</div>
|
|
||||||
</article>
|
|
||||||
|
|
||||||
<article
|
|
||||||
role="button"
|
|
||||||
tabindex="0"
|
|
||||||
class="home-card-base home-entry-item home-entry-item--4 group flex cursor-pointer flex-col justify-between rounded-xl border border-slate-200/80 bg-white/95 px-5 py-5 shadow-[0_4px_20px_rgba(15,23,42,0.06)] backdrop-blur-sm transition-all duration-200 hover:-translate-y-0.5 hover:shadow-[0_12px_32px_rgba(15,23,42,0.12)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-200"
|
|
||||||
@click="openRelatedFiles"
|
|
||||||
@keydown.enter.prevent="openRelatedFiles"
|
|
||||||
@keydown.space.prevent="openRelatedFiles"
|
|
||||||
>
|
|
||||||
<div>
|
|
||||||
<div class="inline-flex h-11 w-11 items-center justify-center rounded-xl border border-emerald-100 bg-emerald-50/80 shadow-sm transition-transform duration-200 group-hover:scale-105">
|
|
||||||
<svg
|
<svg
|
||||||
|
v-else
|
||||||
viewBox="0 0 1024 1024"
|
viewBox="0 0 1024 1024"
|
||||||
class="h-6 w-6"
|
:class="card.iconClass"
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
>
|
>
|
||||||
@ -515,15 +676,37 @@ onBeforeUnmount(() => {
|
|||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<h3 class="mt-4 text-base font-semibold text-slate-900">{{ t('home.cards.relatedFiles') }}</h3>
|
<h3 class="mt-4 text-base font-semibold text-slate-900">{{ t(`home.cards.${card.key}`) }}</h3>
|
||||||
<p class="mt-1.5 text-xs leading-5 text-slate-500">{{ t('home.cards.relatedFilesDesc') }}</p>
|
<p class="mt-1.5 text-xs leading-5 text-slate-500">{{ t(`home.cards.${card.desc}`) }}</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-4 flex items-center text-xs font-medium text-slate-400 transition-colors group-hover:text-slate-600">
|
<div class="mt-4 flex items-center justify-between gap-2">
|
||||||
<span>{{ t('home.cards.openRelatedFiles') }}</span>
|
<button
|
||||||
<svg class="ml-1 h-3.5 w-3.5 transition-transform group-hover:translate-x-0.5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14"/><path d="m12 5 7 7-7 7"/></svg>
|
v-if="card.showExistingAction && hasExistingProjects"
|
||||||
|
type="button"
|
||||||
|
class="cursor-pointer rounded-md border border-slate-200 px-2.5 py-1 text-xs font-medium text-slate-500 transition hover:border-slate-300 hover:bg-slate-50 hover:text-slate-700"
|
||||||
|
@click.stop="openExistingProjectDialog"
|
||||||
|
>
|
||||||
|
{{ t('home.cards.pickExisting') }}
|
||||||
|
</button>
|
||||||
|
<div class="flex items-center text-xs font-medium text-slate-400 transition-colors group-hover:text-slate-600">
|
||||||
|
<span>{{ t(`home.cards.${card.action}`) }}</span>
|
||||||
|
<svg class="ml-1 h-3.5 w-3.5 transition-transform group-hover:translate-x-0.5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14"/><path d="m12 5 7 7-7 7"/></svg>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</article>
|
</article>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="home-disclaimer mt-5 text-center">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="inline-flex cursor-pointer items-center justify-center rounded-2xl border border-slate-300/60 bg-white/55 px-5 py-2 text-base font-semibold text-slate-700 shadow-[0_8px_24px_rgba(15,23,42,0.06)] backdrop-blur-sm underline decoration-slate-300 underline-offset-4 transition hover:border-slate-400/70 hover:bg-white/70 hover:text-slate-900 hover:decoration-slate-500"
|
||||||
|
@click="openDisclaimerPage"
|
||||||
|
>
|
||||||
|
{{ t('home.disclaimer.link') }}
|
||||||
|
</button>
|
||||||
|
<p class="mt-2 text-xs leading-5 text-slate-500">
|
||||||
|
{{ t('home.disclaimer.supportText') }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -532,22 +715,18 @@ onBeforeUnmount(() => {
|
|||||||
class="fixed inset-0 z-[90] flex items-center justify-center bg-black/45 p-4"
|
class="fixed inset-0 z-[90] flex items-center justify-center bg-black/45 p-4"
|
||||||
@click.self="closeExistingProjectDialog"
|
@click.self="closeExistingProjectDialog"
|
||||||
>
|
>
|
||||||
<div class="w-full max-w-lg rounded-3xl border border-slate-200/60 bg-white shadow-2xl">
|
<div class="w-full max-w-lg rounded-xl border bg-background shadow-2xl">
|
||||||
<div class="flex items-start justify-between border-b border-slate-100 px-6 pt-6 pb-4">
|
<div class="flex items-center justify-between border-b px-5 py-4">
|
||||||
<div>
|
<div>
|
||||||
<h3 class="text-xl font-bold text-[#1a1a1a]">{{ t('home.dialog.chooseExistingProject') }}</h3>
|
<h3 class="text-base font-semibold text-foreground">{{ t('home.dialog.chooseExistingProject') }}</h3>
|
||||||
<p class="mt-1.5 text-base text-[#666]">{{ t('home.dialog.chooseExistingProjectDesc') }}</p>
|
<p class="mt-1 text-sm text-muted-foreground">{{ t('home.dialog.chooseExistingProjectDesc') }}</p>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<Button variant="ghost" size="icon" class="h-8 w-8" @click="closeExistingProjectDialog">
|
||||||
type="button"
|
<X class="h-4 w-4" />
|
||||||
class="flex h-8 w-8 items-center justify-center rounded-full text-slate-400 transition hover:bg-slate-100 hover:text-slate-600"
|
</Button>
|
||||||
@click="closeExistingProjectDialog"
|
|
||||||
>
|
|
||||||
<X class="h-5 w-5" />
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="max-h-80 space-y-3 overflow-auto px-6 py-5">
|
<div class="max-h-80 space-y-2 overflow-auto px-5 py-4">
|
||||||
<div
|
<div
|
||||||
v-if="!existingProjectLoading && existingProjects.length === 0"
|
v-if="!existingProjectLoading && existingProjects.length === 0"
|
||||||
class="rounded-lg border border-dashed border-slate-200 bg-slate-50 px-3 py-5 text-center text-sm text-slate-500"
|
class="rounded-lg border border-dashed border-slate-200 bg-slate-50 px-3 py-5 text-center text-sm text-slate-500"
|
||||||
@ -559,29 +738,29 @@ onBeforeUnmount(() => {
|
|||||||
:key="project.id"
|
:key="project.id"
|
||||||
type="button"
|
type="button"
|
||||||
:disabled="isExistingProjectOpened(project.id)"
|
:disabled="isExistingProjectOpened(project.id)"
|
||||||
class="flex w-full items-center justify-between rounded-xl border border-slate-200 bg-white px-4 py-3.5 text-left transition hover:border-slate-300 hover:bg-slate-50 disabled:cursor-not-allowed disabled:opacity-60"
|
class="flex w-full items-center justify-between rounded-lg border border-slate-200 px-3 py-2 text-left transition"
|
||||||
|
:class="isExistingProjectOpened(project.id)
|
||||||
|
? 'cursor-not-allowed border-slate-200 bg-slate-100/80 opacity-70'
|
||||||
|
: 'cursor-pointer hover:border-slate-300 hover:bg-slate-50'"
|
||||||
@click="enterExistingProject(project.id)"
|
@click="enterExistingProject(project.id)"
|
||||||
>
|
>
|
||||||
<div class="min-w-0 flex-1">
|
<div class="min-w-0">
|
||||||
<div class="text-base font-medium text-[#1a1a1a]">
|
<div class="truncate text-sm font-medium text-slate-800">
|
||||||
{{ project.name }}
|
{{ project.name }}
|
||||||
|
<span v-if="isExistingProjectOpened(project.id)" class="ml-1 text-xs text-slate-500">
|
||||||
|
{{ t('tab.toolbar.opened') }}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-1 text-sm text-[#888]">{{ project.id }}</div>
|
<div class="mt-0.5 text-xs text-slate-500">{{ project.id }}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="shrink-0 pl-4 text-sm text-[#888]">
|
<div class="shrink-0 pl-2 text-xs text-slate-500">
|
||||||
{{ t('tab.toolbar.lastEdited', { time: formatProjectEditedTime(project.updatedAt) }) }}
|
{{ t('tab.toolbar.lastEdited', { time: formatProjectEditedTime(project.updatedAt) }) }}
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex items-center justify-end gap-3 border-t border-slate-100 px-6 pt-4 pb-6">
|
<div class="flex items-center justify-end gap-2 border-t px-5 py-4">
|
||||||
<button
|
<Button variant="outline" @click="closeExistingProjectDialog">{{ t('common.cancel') }}</Button>
|
||||||
type="button"
|
|
||||||
class="cursor-pointer rounded-lg border border-slate-200 bg-white px-5 py-2.5 text-base font-medium text-[#666] transition hover:border-slate-300 hover:bg-slate-50"
|
|
||||||
@click="closeExistingProjectDialog"
|
|
||||||
>
|
|
||||||
{{ t('common.cancel') }}
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -591,67 +770,61 @@ onBeforeUnmount(() => {
|
|||||||
class="fixed inset-0 z-[90] flex items-center justify-center bg-black/45 p-4"
|
class="fixed inset-0 z-[90] flex items-center justify-center bg-black/45 p-4"
|
||||||
@click.self="closeProjectCalcDialog"
|
@click.self="closeProjectCalcDialog"
|
||||||
>
|
>
|
||||||
<div class="w-full max-w-lg rounded-3xl border border-slate-200/60 bg-white shadow-2xl">
|
<div class="w-full max-w-md rounded-xl border bg-background shadow-2xl">
|
||||||
<div class="flex items-start justify-between border-b border-slate-100 px-6 pt-6 pb-4">
|
<div class="flex items-center justify-between border-b px-5 py-4">
|
||||||
<div>
|
<div>
|
||||||
<h3 class="text-xl font-bold text-[#1a1a1a]">{{ t('home.dialog.newProject') }}</h3>
|
<h3 class="text-base font-semibold text-foreground">{{ t('home.dialog.newProject') }}</h3>
|
||||||
<p class="mt-1.5 text-base text-[#666]">选择工程行业后,进入项目计算页面</p>
|
<p class="mt-1 text-sm text-muted-foreground">{{ t('home.dialog.chooseIndustryDesc') }}</p>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<Button variant="ghost" size="icon" class="h-8 w-8" @click="closeProjectCalcDialog">
|
||||||
type="button"
|
<X class="h-4 w-4" />
|
||||||
class="flex h-8 w-8 items-center justify-center rounded-full text-slate-400 transition hover:bg-slate-100 hover:text-slate-600"
|
</Button>
|
||||||
@click="closeProjectCalcDialog"
|
|
||||||
>
|
|
||||||
<X class="h-5 w-5" />
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="px-6 pt-5 pb-4">
|
<div class="space-y-4 px-5 py-4">
|
||||||
<h4 class="mb-3 text-base font-semibold text-[#1a1a1a]">{{ t('home.dialog.industry') }}</h4>
|
<label class="block space-y-2">
|
||||||
<div class="space-y-3">
|
<span class="text-sm font-medium text-foreground">{{ t('home.dialog.industry') }}</span>
|
||||||
<button
|
<SelectRoot v-model="projectIndustry">
|
||||||
v-for="item in industryTypeList"
|
<SelectTrigger
|
||||||
:key="`project-${item.id}`"
|
class="inline-flex h-11 w-full items-center justify-between rounded-xl border border-border/70 bg-[linear-gradient(180deg,rgba(248,250,252,0.98),rgba(241,245,249,0.92))] px-4 text-sm text-foreground shadow-sm outline-none transition hover:border-border focus-visible:ring-2 focus-visible:ring-slate-300"
|
||||||
type="button"
|
|
||||||
:class="[
|
|
||||||
'flex w-full items-center justify-between rounded-xl border px-4 py-3.5 text-left transition',
|
|
||||||
projectIndustry === String(item.id)
|
|
||||||
? 'border-[#1a1a1a] bg-white text-[#1a1a1a] shadow-sm'
|
|
||||||
: 'border-slate-200 bg-white text-[#1a1a1a] hover:border-slate-300'
|
|
||||||
]"
|
|
||||||
@click="projectIndustry = String(item.id)"
|
|
||||||
>
|
|
||||||
<span class="text-base">{{ getIndustryDisplayName(item.id, locale) }}</span>
|
|
||||||
<div
|
|
||||||
:class="[
|
|
||||||
'flex h-5 w-5 items-center justify-center rounded-md border transition',
|
|
||||||
projectIndustry === String(item.id)
|
|
||||||
? 'border-[#1a1a1a] bg-[#1a1a1a] text-white'
|
|
||||||
: 'border-slate-300 bg-white'
|
|
||||||
]"
|
|
||||||
>
|
>
|
||||||
<Check v-if="projectIndustry === String(item.id)" class="h-3.5 w-3.5" />
|
<span :class="projectIndustryLabel ? 'text-foreground' : 'text-muted-foreground'">
|
||||||
</div>
|
{{ projectIndustryLabel || t('home.dialog.selectIndustry') }}
|
||||||
</button>
|
</span>
|
||||||
</div>
|
<SelectIcon as-child>
|
||||||
|
<ChevronDown class="h-4 w-4 text-muted-foreground" />
|
||||||
|
</SelectIcon>
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectPortal>
|
||||||
|
<SelectContent
|
||||||
|
:side-offset="6"
|
||||||
|
position="popper"
|
||||||
|
class="z-[120] w-[var(--reka-select-trigger-width)] overflow-hidden rounded-xl border border-border bg-popover text-popover-foreground shadow-xl"
|
||||||
|
>
|
||||||
|
<SelectViewport class="p-1">
|
||||||
|
<SelectItem
|
||||||
|
v-for="item in industryTypeList"
|
||||||
|
:key="`project-${item.id}`"
|
||||||
|
:value="String(item.id)"
|
||||||
|
class="relative flex h-9 w-full cursor-default select-none items-center rounded-md pl-3 pr-8 text-sm outline-none data-[highlighted]:bg-muted data-[highlighted]:text-foreground data-[state=checked]:bg-slate-100"
|
||||||
|
>
|
||||||
|
<SelectItemText>{{ getIndustryDisplayName(item.id, locale) }}</SelectItemText>
|
||||||
|
<SelectItemIndicator class="absolute right-2 inline-flex items-center text-slate-700">
|
||||||
|
<Check class="h-4 w-4" />
|
||||||
|
</SelectItemIndicator>
|
||||||
|
</SelectItem>
|
||||||
|
</SelectViewport>
|
||||||
|
</SelectContent>
|
||||||
|
</SelectPortal>
|
||||||
|
</SelectRoot>
|
||||||
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex items-center justify-end gap-3 border-t border-slate-100 px-6 pt-4 pb-6">
|
<div class="flex items-center justify-end gap-2 border-t px-5 py-4">
|
||||||
<button
|
<Button variant="outline" @click="closeProjectCalcDialog">{{ t('common.cancel') }}</Button>
|
||||||
type="button"
|
<Button :disabled="projectSubmitting || !projectIndustry" @click="confirmProjectCalc">
|
||||||
class="cursor-pointer rounded-lg border border-slate-200 bg-white px-5 py-2.5 text-base font-medium text-[#666] transition hover:border-slate-300 hover:bg-slate-50"
|
|
||||||
@click="closeProjectCalcDialog; openExistingProjectDialog()"
|
|
||||||
>
|
|
||||||
{{ t('home.cards.pickExisting') }}
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
:disabled="projectSubmitting || !projectIndustry"
|
|
||||||
class="cursor-pointer rounded-lg bg-[#1a1a1a] px-5 py-2.5 text-base font-medium text-white transition hover:bg-[#2a2a2a] disabled:cursor-not-allowed disabled:opacity-50"
|
|
||||||
@click="confirmProjectCalc"
|
|
||||||
>
|
|
||||||
{{ projectSubmitting ? t('home.dialog.entering') : t('home.dialog.enterProjectCalc') }}
|
{{ projectSubmitting ? t('home.dialog.entering') : t('home.dialog.enterProjectCalc') }}
|
||||||
</button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -697,11 +870,17 @@ onBeforeUnmount(() => {
|
|||||||
.home-entry-item {
|
.home-entry-item {
|
||||||
animation: fade-up 0.45s cubic-bezier(0.22, 1, 0.36, 1) both;
|
animation: fade-up 0.45s cubic-bezier(0.22, 1, 0.36, 1) both;
|
||||||
}
|
}
|
||||||
|
.home-slogan-row {
|
||||||
|
animation: fade-up 0.45s cubic-bezier(0.22, 1, 0.36, 1) 0.6s both;
|
||||||
|
}
|
||||||
.home-entry-item--1 { animation-delay: 0.2s; }
|
.home-entry-item--1 { animation-delay: 0.2s; }
|
||||||
.home-entry-item--2 { animation-delay: 0.3s; }
|
.home-entry-item--2 { animation-delay: 0.3s; }
|
||||||
.home-entry-item--3 { animation-delay: 0.4s; }
|
.home-entry-item--3 { animation-delay: 0.4s; }
|
||||||
.home-entry-item--4 { animation-delay: 0.5s; }
|
.home-entry-item--4 { animation-delay: 0.5s; }
|
||||||
.home-entry-item--5 { animation-delay: 0.6s; }
|
.home-entry-item--5 { animation-delay: 0.6s; }
|
||||||
|
.home-disclaimer {
|
||||||
|
animation: fade-up 0.45s cubic-bezier(0.22, 1, 0.36, 1) 0.6s both;
|
||||||
|
}
|
||||||
|
|
||||||
@keyframes hero-in {
|
@keyframes hero-in {
|
||||||
from { opacity: 0; transform: translateX(-20px) scale(0.97); }
|
from { opacity: 0; transform: translateX(-20px) scale(0.97); }
|
||||||
|
|||||||
@ -17,6 +17,7 @@ import { getIndustryMajorEntry } from '@/lib/pricingScaleCalc'
|
|||||||
import { getBenchmarkBudgetSplitByScale, getScaleBudgetFeeSplit } from '@/lib/pricingScaleFee'
|
import { getBenchmarkBudgetSplitByScale, getScaleBudgetFeeSplit } from '@/lib/pricingScaleFee'
|
||||||
import { QUICK_PROJECT_INFO_KEY } from '@/lib/workspace'
|
import { QUICK_PROJECT_INFO_KEY } from '@/lib/workspace'
|
||||||
import { initializeProjectFactorStates } from '@/lib/projectWorkspace'
|
import { initializeProjectFactorStates } from '@/lib/projectWorkspace'
|
||||||
|
import { resolveServicePricingCapabilities } from '@/lib/servicePricing'
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
contractId: string
|
contractId: string
|
||||||
@ -36,8 +37,9 @@ type DictFactorItem = {
|
|||||||
defCoe: number | null
|
defCoe: number | null
|
||||||
hasCost?: boolean | null
|
hasCost?: boolean | null
|
||||||
hasArea?: boolean | null
|
hasArea?: boolean | null
|
||||||
scale?: boolean | null
|
enableInvestScale?: boolean | null
|
||||||
onlyCostScale?: boolean | null
|
enableLandScale?: boolean | null
|
||||||
|
investScaleSingleTotal?: boolean | null
|
||||||
}
|
}
|
||||||
|
|
||||||
type QuickCalcScaleMode = 'cost' | 'area'
|
type QuickCalcScaleMode = 'cost' | 'area'
|
||||||
@ -69,8 +71,9 @@ const mapDictItemToFactorItem = (id: string, item: Record<string, unknown> | und
|
|||||||
defCoe: typeof item.defCoe === 'number' ? item.defCoe : null,
|
defCoe: typeof item.defCoe === 'number' ? item.defCoe : null,
|
||||||
hasCost: item.hasCost === true,
|
hasCost: item.hasCost === true,
|
||||||
hasArea: item.hasArea === true,
|
hasArea: item.hasArea === true,
|
||||||
scale: item.scale === true,
|
enableInvestScale: item.enableInvestScale === true,
|
||||||
onlyCostScale: item.onlyCostScale === true
|
enableLandScale: item.enableLandScale === true,
|
||||||
|
investScaleSingleTotal: item.investScaleSingleTotal === true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -195,10 +198,22 @@ const preferLandScaleForDualMajor = computed(() => majorSupportsCostScale.value
|
|||||||
const workEnvCoefficient = computed(() =>
|
const workEnvCoefficient = computed(() =>
|
||||||
parseNumberOrNull(workEnvFactor.value, { sanitize: true, precision: 3 })
|
parseNumberOrNull(workEnvFactor.value, { sanitize: true, precision: 3 })
|
||||||
)
|
)
|
||||||
const consultSupportsScale = computed(() => selectedConsultDictItem.value?.scale === true)
|
const consultPricingCapabilities = computed(() =>
|
||||||
const consultOnlySupportsCostScale = computed(() => selectedConsultDictItem.value?.onlyCostScale === true)
|
resolveServicePricingCapabilities(selectedConsultDictItem.value, {
|
||||||
|
investScaleEnabled: false,
|
||||||
|
landScaleEnabled: false,
|
||||||
|
investScaleSingleTotal: false,
|
||||||
|
workloadEnabled: false,
|
||||||
|
hourlyEnabled: false
|
||||||
|
})
|
||||||
|
)
|
||||||
|
const consultSupportsScale = computed(() => consultPricingCapabilities.value.investScaleEnabled || consultPricingCapabilities.value.landScaleEnabled)
|
||||||
|
const consultOnlySupportsCostScale = computed(() =>
|
||||||
|
consultPricingCapabilities.value.investScaleEnabled
|
||||||
|
&& !consultPricingCapabilities.value.landScaleEnabled
|
||||||
|
)
|
||||||
const canUseInvestScale = computed(() =>
|
const canUseInvestScale = computed(() =>
|
||||||
consultSupportsScale.value &&
|
consultPricingCapabilities.value.investScaleEnabled &&
|
||||||
hasResolvedMajor.value &&
|
hasResolvedMajor.value &&
|
||||||
(
|
(
|
||||||
consultOnlySupportsCostScale.value ||
|
consultOnlySupportsCostScale.value ||
|
||||||
@ -206,9 +221,8 @@ const canUseInvestScale = computed(() =>
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
const canUseLandScale = computed(() =>
|
const canUseLandScale = computed(() =>
|
||||||
consultSupportsScale.value &&
|
consultPricingCapabilities.value.landScaleEnabled &&
|
||||||
hasResolvedMajor.value &&
|
hasResolvedMajor.value &&
|
||||||
!consultOnlySupportsCostScale.value &&
|
|
||||||
majorSupportsLandScale.value
|
majorSupportsLandScale.value
|
||||||
)
|
)
|
||||||
const investScalePlaceholder = computed(() => {
|
const investScalePlaceholder = computed(() => {
|
||||||
@ -693,8 +707,8 @@ watch(canUseLandScale, enabled => {
|
|||||||
|
|
||||||
.quick-calc-layout {
|
.quick-calc-layout {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: minmax(0, 1.45fr) minmax(340px, 0.95fr);
|
grid-template-columns: minmax(0, 1.02fr) minmax(420px, 1.34fr);
|
||||||
gap: 10px;
|
gap: 12px;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
}
|
}
|
||||||
@ -710,6 +724,34 @@ watch(canUseLandScale, enabled => {
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.quick-calc-panel--form {
|
||||||
|
position: relative;
|
||||||
|
border-color: var(--qc-border);
|
||||||
|
background:
|
||||||
|
radial-gradient(circle at 84% -12%, color-mix(in srgb, hsl(var(--destructive)) 22%, transparent) 0%, transparent 46%),
|
||||||
|
radial-gradient(circle at -8% 108%, color-mix(in srgb, hsl(var(--destructive)) 14%, transparent) 0%, transparent 44%),
|
||||||
|
linear-gradient(160deg, color-mix(in srgb, var(--card) 95%, white) 0%, color-mix(in srgb, var(--card) 86%, var(--muted)) 100%);
|
||||||
|
box-shadow:
|
||||||
|
0 22px 44px color-mix(in srgb, hsl(var(--destructive)) 12%, transparent),
|
||||||
|
0 12px 28px color-mix(in srgb, var(--foreground) 10%, transparent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.quick-calc-panel--form::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
height: 3px;
|
||||||
|
background: linear-gradient(
|
||||||
|
90deg,
|
||||||
|
color-mix(in srgb, hsl(var(--destructive)) 58%, white) 0%,
|
||||||
|
color-mix(in srgb, hsl(var(--destructive)) 26%, transparent) 35%,
|
||||||
|
transparent 100%
|
||||||
|
);
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
.quick-calc-panel__header {
|
.quick-calc-panel__header {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
@ -1152,7 +1194,7 @@ watch(canUseLandScale, enabled => {
|
|||||||
|
|
||||||
.quick-calc-panel--form .quick-calc-form {
|
.quick-calc-panel--form .quick-calc-form {
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
padding: 8px;
|
padding: 10px;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1179,8 +1221,11 @@ watch(canUseLandScale, enabled => {
|
|||||||
|
|
||||||
.quick-calc-panel--form .quick-calc-form-section {
|
.quick-calc-panel--form .quick-calc-form-section {
|
||||||
gap: 6px;
|
gap: 6px;
|
||||||
padding: 8px 10px;
|
padding: 10px 12px;
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
|
background:
|
||||||
|
linear-gradient(180deg, color-mix(in srgb, white 30%, transparent) 0%, transparent 100%),
|
||||||
|
color-mix(in srgb, var(--card) 94%, var(--muted));
|
||||||
}
|
}
|
||||||
|
|
||||||
.quick-calc-form-section__header {
|
.quick-calc-form-section__header {
|
||||||
@ -1321,7 +1366,11 @@ watch(canUseLandScale, enabled => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.quick-calc-panel--form .quick-calc-field__readonly--emphasis {
|
.quick-calc-panel--form .quick-calc-field__readonly--emphasis {
|
||||||
font-size: 14px;
|
font-size: 18px;
|
||||||
|
font-weight: 800;
|
||||||
|
color: color-mix(in srgb, hsl(var(--destructive)) 88%, var(--foreground));
|
||||||
|
letter-spacing: 0.015em;
|
||||||
|
text-shadow: 0 1px 0 color-mix(in srgb, white 65%, transparent);
|
||||||
}
|
}
|
||||||
|
|
||||||
.quick-calc-form-hint {
|
.quick-calc-form-hint {
|
||||||
|
|||||||
@ -16,10 +16,12 @@ import { useI18n } from 'vue-i18n'
|
|||||||
import TypeLine from '@/layout/typeLine.vue'
|
import TypeLine from '@/layout/typeLine.vue'
|
||||||
import MethodUnavailableNotice from '@/features/shared/components/MethodUnavailableNotice.vue'
|
import MethodUnavailableNotice from '@/features/shared/components/MethodUnavailableNotice.vue'
|
||||||
import ScaleFormulaReadonlyPane from '@/features/pricing/components/ScaleFormulaReadonlyPane.vue'
|
import ScaleFormulaReadonlyPane from '@/features/pricing/components/ScaleFormulaReadonlyPane.vue'
|
||||||
|
import { resolveServicePricingCapabilities } from '@/lib/servicePricing'
|
||||||
|
|
||||||
interface ServiceMethodType {
|
interface ServiceMethodType {
|
||||||
scale?: boolean | null
|
enableInvestScale?: boolean | null
|
||||||
onlyCostScale?: boolean | null
|
enableLandScale?: boolean | null
|
||||||
|
investScaleSingleTotal?: boolean | null
|
||||||
amount?: boolean | null
|
amount?: boolean | null
|
||||||
workDay?: boolean | null
|
workDay?: boolean | null
|
||||||
}
|
}
|
||||||
@ -40,20 +42,19 @@ interface PricingCategoryItem {
|
|||||||
component: Component
|
component: Component
|
||||||
}
|
}
|
||||||
|
|
||||||
const resolveMethodEnabled = (value: boolean | null | undefined, fallback = true) =>
|
|
||||||
typeof value === 'boolean' ? value : fallback
|
|
||||||
|
|
||||||
const methodAvailability = computed(() => {
|
const methodAvailability = computed(() => {
|
||||||
const scale = resolveMethodEnabled(props.type?.scale, true)
|
const capability = resolveServicePricingCapabilities(props.type, {
|
||||||
const onlyCostScale = resolveMethodEnabled(props.type?.onlyCostScale, false)
|
investScaleEnabled: true,
|
||||||
const amount = resolveMethodEnabled(props.type?.amount, true)
|
landScaleEnabled: true,
|
||||||
const workDay = resolveMethodEnabled(props.type?.workDay, true)
|
investScaleSingleTotal: false,
|
||||||
|
workloadEnabled: true,
|
||||||
|
hourlyEnabled: true
|
||||||
|
})
|
||||||
return {
|
return {
|
||||||
investmentScale: scale,
|
investmentScale: capability.investScaleEnabled,
|
||||||
landScale: scale && !onlyCostScale,
|
landScale: capability.landScaleEnabled,
|
||||||
workload: amount,
|
workload: capability.workloadEnabled,
|
||||||
hourly: workDay
|
hourly: capability.hourlyEnabled
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -71,7 +72,6 @@ const createPricingPane = (name: string) =>
|
|||||||
|
|
||||||
return () => h(AsyncPricingView, {
|
return () => h(AsyncPricingView, {
|
||||||
contractId: props.contractId,
|
contractId: props.contractId,
|
||||||
contractName: props.contractName,
|
|
||||||
serviceId: props.serviceId,
|
serviceId: props.serviceId,
|
||||||
projectInfoKey: props.projectInfoKey
|
projectInfoKey: props.projectInfoKey
|
||||||
})
|
})
|
||||||
@ -93,7 +93,6 @@ const investmentScaleView = createPricingPane('InvestmentScalePricingPane')
|
|||||||
const landScaleView = createPricingPane('LandScalePricingPane')
|
const landScaleView = createPricingPane('LandScalePricingPane')
|
||||||
const workloadView = createPricingPane('WorkloadPricingPane')
|
const workloadView = createPricingPane('WorkloadPricingPane')
|
||||||
const hourlyView = createPricingPane('HourlyPricingPane')
|
const hourlyView = createPricingPane('HourlyPricingPane')
|
||||||
const otherService = createPricingPane('OtherService')
|
|
||||||
|
|
||||||
const createScaleFormulaPane = (
|
const createScaleFormulaPane = (
|
||||||
method: 'investScale' | 'landScale',
|
method: 'investScale' | 'landScale',
|
||||||
@ -132,8 +131,7 @@ const workContentPane = markRaw(
|
|||||||
contractId: props.contractId,
|
contractId: props.contractId,
|
||||||
projectInfoKey: props.projectInfoKey,
|
projectInfoKey: props.projectInfoKey,
|
||||||
serviceId: props.serviceId,
|
serviceId: props.serviceId,
|
||||||
dictMode: 'service',
|
dictMode: 'service'
|
||||||
"show-no-column": true
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -163,7 +161,7 @@ const pricingCategories = computed<PricingCategoryItem[]>(() => [
|
|||||||
label: t('zxFwView.categories.investmentScale'),
|
label: t('zxFwView.categories.investmentScale'),
|
||||||
component: methodAvailability.value.investmentScale ? investmentScaleView : investmentScaleUnavailableView
|
component: methodAvailability.value.investmentScale ? investmentScaleView : investmentScaleUnavailableView
|
||||||
},
|
},
|
||||||
/*{
|
{
|
||||||
key: 'investment-scale-formula',
|
key: 'investment-scale-formula',
|
||||||
label: t('zxFwView.categories.investmentScaleFormula'),
|
label: t('zxFwView.categories.investmentScaleFormula'),
|
||||||
component: methodAvailability.value.investmentScale ? investmentScaleFormulaView : investmentScaleUnavailableView
|
component: methodAvailability.value.investmentScale ? investmentScaleFormulaView : investmentScaleUnavailableView
|
||||||
@ -177,7 +175,7 @@ const pricingCategories = computed<PricingCategoryItem[]>(() => [
|
|||||||
key: 'land-scale-formula',
|
key: 'land-scale-formula',
|
||||||
label: t('zxFwView.categories.landScaleFormula'),
|
label: t('zxFwView.categories.landScaleFormula'),
|
||||||
component: methodAvailability.value.landScale ? landScaleFormulaView : landScaleUnavailableView
|
component: methodAvailability.value.landScale ? landScaleFormulaView : landScaleUnavailableView
|
||||||
},*/
|
},
|
||||||
{
|
{
|
||||||
key: 'workload-method',
|
key: 'workload-method',
|
||||||
label: t('zxFwView.categories.workload'),
|
label: t('zxFwView.categories.workload'),
|
||||||
@ -193,11 +191,6 @@ const pricingCategories = computed<PricingCategoryItem[]>(() => [
|
|||||||
label: t('zxFwView.categories.workContent'),
|
label: t('zxFwView.categories.workContent'),
|
||||||
component: workContentPane
|
component: workContentPane
|
||||||
},
|
},
|
||||||
{
|
|
||||||
key: 'other-service',
|
|
||||||
label: t('zxFwView.categories.otherService'),
|
|
||||||
component: otherService
|
|
||||||
},
|
|
||||||
])
|
])
|
||||||
|
|
||||||
const defaultCategory = computed(() => {
|
const defaultCategory = computed(() => {
|
||||||
|
|||||||
@ -53,6 +53,10 @@ const { t, locale } = useI18n()
|
|||||||
const DEFAULT_PROJECT_NAME = t('xmInfo.defaultProjectName')
|
const DEFAULT_PROJECT_NAME = t('xmInfo.defaultProjectName')
|
||||||
const DEFAULT_DESC = t('xmInfo.defaultDesc')
|
const DEFAULT_DESC = t('xmInfo.defaultDesc')
|
||||||
const INDUSTRY_HINT_TEXT = computed(() => t('xmInfo.industryHint'))
|
const INDUSTRY_HINT_TEXT = computed(() => t('xmInfo.industryHint'))
|
||||||
|
const REPORT_CONTENT_HINT_TEXT = computed(() => t('xmInfo.reportContentHint'))
|
||||||
|
const OTHER_DESC_HINT_TEXT = computed(() => t('xmInfo.otherDescHint'))
|
||||||
|
const FIELD_HINT_BUTTON_CLASS = 'inline-flex h-5 w-5 items-center justify-center text-muted-foreground/90 transition hover:text-foreground'
|
||||||
|
const FIELD_HINT_ICON_CLASS = 'h-4 w-4'
|
||||||
const getTodayDateString = () => {
|
const getTodayDateString = () => {
|
||||||
const now = new Date()
|
const now = new Date()
|
||||||
const year = String(now.getFullYear())
|
const year = String(now.getFullYear())
|
||||||
@ -231,7 +235,21 @@ onMounted(async () => {
|
|||||||
<div v-else class="rounded-xl border bg-card p-4 shadow-sm shrink-0 md:p-5">
|
<div v-else class="rounded-xl border bg-card p-4 shadow-sm shrink-0 md:p-5">
|
||||||
<div class="grid grid-cols-1 gap-4 md:grid-cols-2 xl:grid-cols-4">
|
<div class="grid grid-cols-1 gap-4 md:grid-cols-2 xl:grid-cols-4">
|
||||||
<div class="md:col-span-2 xl:col-span-4">
|
<div class="md:col-span-2 xl:col-span-4">
|
||||||
<label class="block text-sm font-medium text-foreground">{{ t('xmInfo.fields.projectName') }}</label>
|
<div class="flex items-center gap-1.5">
|
||||||
|
<label class="block text-sm font-medium text-foreground">{{ t('xmInfo.fields.projectName') }}</label>
|
||||||
|
<TooltipRoot>
|
||||||
|
<TooltipTrigger as-child>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
:aria-label="t('xmInfo.reportContentHintAria')"
|
||||||
|
:class="FIELD_HINT_BUTTON_CLASS"
|
||||||
|
>
|
||||||
|
<CircleHelp :class="FIELD_HINT_ICON_CLASS" />
|
||||||
|
</button>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent side="top">{{ REPORT_CONTENT_HINT_TEXT }}</TooltipContent>
|
||||||
|
</TooltipRoot>
|
||||||
|
</div>
|
||||||
<input
|
<input
|
||||||
v-model="projectName"
|
v-model="projectName"
|
||||||
type="text"
|
type="text"
|
||||||
@ -250,9 +268,9 @@ onMounted(async () => {
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
:aria-label="t('xmInfo.industryHintAria')"
|
:aria-label="t('xmInfo.industryHintAria')"
|
||||||
class="inline-flex h-5 w-5 items-center justify-center text-muted-foreground/90 transition hover:text-foreground"
|
:class="FIELD_HINT_BUTTON_CLASS"
|
||||||
>
|
>
|
||||||
<CircleHelp class="h-5 w-5" />
|
<CircleHelp :class="FIELD_HINT_ICON_CLASS" />
|
||||||
</button>
|
</button>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent side="top">{{ INDUSTRY_HINT_TEXT }}</TooltipContent>
|
<TooltipContent side="top">{{ INDUSTRY_HINT_TEXT }}</TooltipContent>
|
||||||
@ -275,7 +293,7 @@ onMounted(async () => {
|
|||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="md:col-span-2 xl:col-span-4">
|
<div class="md:col-span-2 xl:col-span-4">
|
||||||
<label class="block text-sm font-medium text-foreground">{{ t('xmInfo.fields.overview') }}</label>
|
<label class="block text-sm font-medium text-foreground">{{ t('xmInfo.fields.overview') }}</label>
|
||||||
<textarea
|
<textarea
|
||||||
v-model="overview"
|
v-model="overview"
|
||||||
@ -427,10 +445,25 @@ onMounted(async () => {
|
|||||||
|
|
||||||
|
|
||||||
<div class="md:col-span-2 xl:col-span-4">
|
<div class="md:col-span-2 xl:col-span-4">
|
||||||
<label class="block text-sm font-medium text-foreground">{{ t('xmInfo.fields.desc') }}</label>
|
<div class="flex items-center gap-1.5">
|
||||||
|
<label class="block text-sm font-medium text-foreground">{{ t('xmInfo.fields.desc') }}</label>
|
||||||
|
<TooltipRoot>
|
||||||
|
<TooltipTrigger as-child>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
:aria-label="t('xmInfo.otherDescHintAria')"
|
||||||
|
:class="FIELD_HINT_BUTTON_CLASS"
|
||||||
|
>
|
||||||
|
<CircleHelp :class="FIELD_HINT_ICON_CLASS" />
|
||||||
|
</button>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent side="top">{{ OTHER_DESC_HINT_TEXT }}</TooltipContent>
|
||||||
|
</TooltipRoot>
|
||||||
|
</div>
|
||||||
<textarea
|
<textarea
|
||||||
v-model="desc"
|
v-model="desc"
|
||||||
rows="4"
|
rows="4"
|
||||||
|
:placeholder="t('xmInfo.placeholders.desc')"
|
||||||
class="mt-2 w-full rounded-lg border bg-background px-4 py-2 text-sm outline-none ring-offset-background shadow-sm transition placeholder:text-muted-foreground/70 focus-visible:border-primary/60 focus-visible:ring-2 focus-visible:ring-ring resize-none"
|
class="mt-2 w-full rounded-lg border bg-background px-4 py-2 text-sm outline-none ring-offset-background shadow-sm transition placeholder:text-muted-foreground/70 focus-visible:border-primary/60 focus-visible:ring-2 focus-visible:ring-ring resize-none"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -26,8 +26,8 @@ const majorFactorView = markRaw(
|
|||||||
const xmCategories = computed(() => [
|
const xmCategories = computed(() => [
|
||||||
{ key: 'info', label: t('xmCard.categories.info'), component: infoView },
|
{ key: 'info', label: t('xmCard.categories.info'), component: infoView },
|
||||||
{ key: 'scale-info', label: t('xmCard.categories.scaleInfo'), component: scaleInfoView },
|
{ key: 'scale-info', label: t('xmCard.categories.scaleInfo'), component: scaleInfoView },
|
||||||
// { key: 'consult-category-factor', label: t('xmCard.categories.consultCategoryFactor'), component: consultCategoryFactorView },
|
{ key: 'consult-category-factor', label: t('xmCard.categories.consultCategoryFactor'), component: consultCategoryFactorView },
|
||||||
// { key: 'major-factor', label: t('xmCard.categories.majorFactor'), component: majorFactorView },
|
{ key: 'major-factor', label: t('xmCard.categories.majorFactor'), component: majorFactorView },
|
||||||
{ key: 'contract', label: t('xmCard.categories.contract'), component: htView }
|
{ key: 'contract', label: t('xmCard.categories.contract'), component: htView }
|
||||||
])
|
])
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -10,4 +10,4 @@ const DB_KEY = 'xm-info-v3'
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<CommonAgGrid title="项目明细" :dbKey="DB_KEY" />
|
<CommonAgGrid title="项目明细" :dbKey="DB_KEY" />
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@ -14,32 +14,49 @@ export const enUS = {
|
|||||||
countdown: 'This page will try to close automatically in {seconds} seconds. You can open another project in a new tab first.',
|
countdown: 'This page will try to close automatically in {seconds} seconds. You can open another project in a new tab first.',
|
||||||
opened: '(Opened)',
|
opened: '(Opened)',
|
||||||
lastEdited: 'Last edited: {time}',
|
lastEdited: 'Last edited: {time}',
|
||||||
openDefault: 'Open Default Project',
|
openDefault: 'Back to Home',
|
||||||
createAndOpen: 'Create and Open'
|
createAndOpen: 'Create and Open'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
home: {
|
home: {
|
||||||
title: 'Engineering Calculation Entry',
|
title: 'Yue Gong Xue Biao Zi [2026] No. 5\nSpecification for Budget Preparation of Cost Consulting Services for Transportation Engineering',
|
||||||
subtitle: 'Project Budget · Quick Calc · Import Data',
|
subtitle: 'Project Budget · Quick Calc · Import Data · Related Files',
|
||||||
projectCalcTab: 'Project Calculation',
|
projectCalcTab: 'Project Calculation',
|
||||||
quickCalcTab: 'Quick Calculation',
|
quickCalcTab: 'Quick Calculation',
|
||||||
cards: {
|
cards: {
|
||||||
heroTitle: 'One-Click Smart Budget',
|
heroTitle: 'Zonghuiyi|Billing Made Simple',
|
||||||
heroSubTitle: 'Accelerate standards adoption',
|
heroTitles: [
|
||||||
heroDesc: 'Cost consulting fee calculator for transport construction projects',
|
'Zonghuiyi | Billing Made Simple',
|
||||||
|
'Zonghuiyi | No Late Nights for Billing',
|
||||||
|
'Zonghuiyi | Effortless Billing'
|
||||||
|
],
|
||||||
|
heroSubTitle: '',
|
||||||
|
heroDesc: 'Instant Pricing, Instant Results, Leave Time for Creation',
|
||||||
|
heroDescs: [
|
||||||
|
'Instant Pricing, Instant Results, Leave Time for Creation.',
|
||||||
|
'Smart Pricing, One Click to Results, Leave Time for Creation.',
|
||||||
|
'Smart Pricing, One Click to Generate, Leave Time for Creation.',
|
||||||
|
'Zonghuiyi. Simple, No Late Nights, Effortless.'
|
||||||
|
],
|
||||||
projectBudget: 'Project Budget',
|
projectBudget: 'Project Budget',
|
||||||
projectBudgetDesc: 'For full project-level calculation across multiple contracts with import/export support',
|
projectBudgetDesc: 'For full project-level calculation across multiple contracts with import/export support',
|
||||||
quickCalc: 'Quick Calc',
|
quickCalc: 'Quick Calc',
|
||||||
quickCalcDesc: 'Suitable for single service trial, select industry, consultation type, engineering specialty, input base number and get results in seconds',
|
quickCalcDesc: 'Suitable for single service trial, select industry, consultation type, engineering specialty, input base number and get results in seconds',
|
||||||
importData: 'Import Data',
|
importData: 'Import Data',
|
||||||
importDataDesc: 'Import a ".zw" package and create a new project automatically to restore the data without overriding existing projects',
|
importDataDesc: 'Import a ".zw" package and create a new project automatically to restore the data without overriding existing projects',
|
||||||
|
relatedFiles: 'Related Files',
|
||||||
|
relatedFilesDesc: 'View, print, and download the fee documents, related bidding documents, and contract templates used by this calculator',
|
||||||
|
viewFiles: 'View Files',
|
||||||
enter: 'Enter',
|
enter: 'Enter',
|
||||||
developing: 'In Development',
|
|
||||||
pickFile: 'Choose File',
|
pickFile: 'Choose File',
|
||||||
pickExisting: 'Choose Existing',
|
pickExisting: 'Choose Existing',
|
||||||
relatedFiles: 'Related Files',
|
openFileSystem: 'Open File System',
|
||||||
relatedFilesDesc: 'View related fee documents, tender files, contract files, service content, and work requirements',
|
file: 'File System',
|
||||||
openRelatedFiles: 'Open Page'
|
fileDataDesc: 'The file system provides related fee documents, bidding documents, contract documents, service contents, and work requirements'
|
||||||
|
},
|
||||||
|
disclaimer: {
|
||||||
|
link: 'View Disclaimer',
|
||||||
|
supportText: 'This calculator is provided with free technical support by Zhongwei Engineering Consulting Co., Ltd.'
|
||||||
},
|
},
|
||||||
dialog: {
|
dialog: {
|
||||||
newProject: 'New Project',
|
newProject: 'New Project',
|
||||||
@ -56,6 +73,47 @@ export const enUS = {
|
|||||||
noProjectYet: 'No project available. Create a new project first.'
|
noProjectYet: 'No project available. Create a new project first.'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
disclaimerPage: {
|
||||||
|
documentTitle: 'Budget Tool Disclaimer',
|
||||||
|
eyebrow: 'DISCLAIMER',
|
||||||
|
pageTitle: 'Disclaimer for Budget Preparation Tool under (T/GDHS017-2026) Cost Consulting Services for Transportation Engineering as Specifications for Budget Compilation',
|
||||||
|
lastUpdatedLabel: 'Last updated: ',
|
||||||
|
lastUpdatedValue: 'April 16, 2026',
|
||||||
|
leadText: 'Thank you for using the cost consulting budget preparation tool for the Specification for Budget Preparation of Cost Consulting Services for Transportation Engineering (T/GDHS 017-2026) provided on this website. Before using this tool, please read the following disclaimer carefully. By continuing to use this tool, you acknowledge that you have read, understood, and agreed to all terms of this disclaimer.',
|
||||||
|
sections: {
|
||||||
|
standardBasisTitle: '1. Standard Basis',
|
||||||
|
standardBasisP1: '1.1 This tool is based on the methodology set out in the group standard Specification for Budget Preparation of Cost Consulting Services for Transportation Engineering (T/GDHS 017-2026) issued by the Guangdong Province Highway Society "GDHS". Users must independently determine whether the standard applies to their specific projects and the regulatory requirements of their local authorities.',
|
||||||
|
standardBasisP2: '1.2 The standard version used by this tool is indicated on the interface as T/GDHS 017-2026. Should the standard be subsequently revised, supplemented, or replaced, this tool may not be updated in a timely manner. Users must independently confirm that the referenced version remains the latest valid version before use.',
|
||||||
|
standardBasisP3: '1.3 The calculation results generated by this tool are based on the budgeting methods, cost structure, and preparation rules in specified the standard. Requirements and calculation approaches may still vary across regions and project owners. This tool does not guarantee that its results will meet the review requirements of any specific project or authority.',
|
||||||
|
referenceOnlyTitle: '2. Results Are for Reference Only',
|
||||||
|
referenceOnlyP1: 'All results generated by this tool, including but not limited to values, breakdowns, summaries and preparation instructions, are produced automatically based on the parameters you provide (such as engineering industry, project scale, consulting category, engineering discipline, job responsibilities, adjustment factor, etc.) and the mathematical models and formulas in the standard. They are for reference purposes only and do not constitute professional advice or any official or mandatory basis for budget approval.',
|
||||||
|
accuracyTitle: '3. No Warranties of Accuracy or Completeness',
|
||||||
|
accuracyP1: 'Although we make reasonable efforts to ensure the tool\'s availability, it is provided on an as-is basis without any express or implied warranty. We do not guarantee that the results will always be accurate, error-free, or complete. Differences may arise due to input errors, formula selection, rounding, or system delays.',
|
||||||
|
riskTitle: '4. Users Bear Their Own Risks',
|
||||||
|
riskP1: 'You should independently assess the reliability of the calculation results and bear all risks and responsibilities arising from any decisions made based thereon. The output of this tool should not replace professional calculation or review. Before making important decisions, you should consult professionals holding a registered certificate in transportation engineering cost or perform manual verification based on the specific project context.',
|
||||||
|
liabilityTitle: '5. Limitation of Liability',
|
||||||
|
liabilityP1: 'To the fullest extent permitted by applicable law, the developers, administrators, publishers, and their affiliates shall not be liable for any direct, indirect, accidental, special, or consequential losses arising from the use of or inability to use this tool, even if advised of the possibility of such losses.',
|
||||||
|
liabilityP2: 'In particular, if any cost consulting enterprise or individual issues deliverables from the results generated by this tool, the issuing party bears sole responsibility for their quality. This tool assumes no responsibility for the accuracy, compliance, or disputes arising from any third-party deliverables.',
|
||||||
|
interruptionTitle: '6. Service Interruption and Changes',
|
||||||
|
interruptionP1: 'We reserve the right to modify, suspend, or terminate part or all of this tool at any time, with or without notice. We are not responsible for unavailability, data loss, or changes in calculation results caused by maintenance, network failures, third-party service interruptions, or updates to the standard.',
|
||||||
|
externalTitle: '7. External Links and Third-Party Content',
|
||||||
|
externalP1: 'If this tool references or links to the National Group Standards Information Platform, the Guangdong Highway Society website, or other third-party websites, such links are provided only for convenience and do not imply endorsement of their accuracy, timeliness, or completeness. We assume no responsibility for any information, services, or content provided by third parties.',
|
||||||
|
lawTitle: '8. Governing Law',
|
||||||
|
lawP1: 'This disclaimer shall be governed by the laws of the People\'s Republic of China. If any provision of this disclaimer is held invalid or unenforceable, the remaining provisions shall remain in effect.'
|
||||||
|
},
|
||||||
|
confirm: {
|
||||||
|
title: 'User Confirmation',
|
||||||
|
desc1: 'I have read, understood, and agree to the full contents of this disclaimer.',
|
||||||
|
desc2: 'You must check the box before continuing to use this tool.',
|
||||||
|
checkbox: 'I have read, understood, and agree to the full contents of this disclaimer.',
|
||||||
|
continue: 'Agree and Continue',
|
||||||
|
hint: 'Once checked, your acceptance will be recorded in this browser so the same restricted entry will not prompt you again.'
|
||||||
|
},
|
||||||
|
actions: {
|
||||||
|
back: 'Back to Home',
|
||||||
|
switchLocale: 'Switch Language'
|
||||||
|
}
|
||||||
|
},
|
||||||
tab: {
|
tab: {
|
||||||
toolbar: {
|
toolbar: {
|
||||||
light: 'Light',
|
light: 'Light',
|
||||||
@ -169,9 +227,7 @@ export const enUS = {
|
|||||||
toast: {
|
toast: {
|
||||||
export: 'Export Report',
|
export: 'Export Report',
|
||||||
success: 'Export Success',
|
success: 'Export Success',
|
||||||
failed: 'Export Failed',
|
failed: 'Export Failed'
|
||||||
saveSuccess: 'Save Success',
|
|
||||||
saveFailed: 'Save Failed'
|
|
||||||
},
|
},
|
||||||
messages: {
|
messages: {
|
||||||
defaultProjectLabel: 'Default Project',
|
defaultProjectLabel: 'Default Project',
|
||||||
@ -305,7 +361,9 @@ export const enUS = {
|
|||||||
reserveTitle: 'Reserve Fee'
|
reserveTitle: 'Reserve Fee'
|
||||||
},
|
},
|
||||||
htInfo: {
|
htInfo: {
|
||||||
scaleDetailTitle: 'Contract Scale Details'
|
scaleDetailTitle: 'Contract Scale Details',
|
||||||
|
scaleDetailHint: 'When the scale data in this table differs from the project scale data, pricing will use this table.',
|
||||||
|
scaleDetailHintAria: 'Contract scale details hint'
|
||||||
},
|
},
|
||||||
htFeeRate: {
|
htFeeRate: {
|
||||||
baseLabel: 'Base (total budget of all service fees)',
|
baseLabel: 'Base (total budget of all service fees)',
|
||||||
@ -320,7 +378,7 @@ export const enUS = {
|
|||||||
title: 'Consulting Service Details',
|
title: 'Consulting Service Details',
|
||||||
warning: 'Please review and adjust recommended limits/special values in the specification, then update final fee if needed.',
|
warning: 'Please review and adjust recommended limits/special values in the specification, then update final fee if needed.',
|
||||||
editTabTitle: 'Service Edit-{name}',
|
editTabTitle: 'Service Edit-{name}',
|
||||||
subtotal: 'Subtotal',
|
subtotal: 'Total',
|
||||||
edit: 'Edit',
|
edit: 'Edit',
|
||||||
resetDefault: 'Reset',
|
resetDefault: 'Reset',
|
||||||
delete: 'Remove',
|
delete: 'Remove',
|
||||||
@ -370,7 +428,7 @@ export const enUS = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
htFeeGrid: {
|
htFeeGrid: {
|
||||||
subtotal: 'Subtotal',
|
subtotal: 'Total',
|
||||||
currentRow: 'Current Row',
|
currentRow: 'Current Row',
|
||||||
unnamed: 'Unnamed',
|
unnamed: 'Unnamed',
|
||||||
edit: 'Edit',
|
edit: 'Edit',
|
||||||
@ -402,6 +460,8 @@ export const enUS = {
|
|||||||
},
|
},
|
||||||
serviceSelector: {
|
serviceSelector: {
|
||||||
title: 'Select Services',
|
title: 'Select Services',
|
||||||
|
titleHint: 'Some selectable services have been added beyond those listed in the specification. These additions do not conflict with the specification and are only included to support fee calculation under the specification.',
|
||||||
|
titleHintAria: 'Select services hint',
|
||||||
clear: 'Clear',
|
clear: 'Clear',
|
||||||
empty: 'No services'
|
empty: 'No services'
|
||||||
},
|
},
|
||||||
@ -437,7 +497,7 @@ export const enUS = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
htFeeDetail: {
|
htFeeDetail: {
|
||||||
subtotal: 'Subtotal',
|
subtotal: 'Total',
|
||||||
currentRow: 'Current Row',
|
currentRow: 'Current Row',
|
||||||
clickToInput: 'Click to input',
|
clickToInput: 'Click to input',
|
||||||
addRow: 'Add Row',
|
addRow: 'Add Row',
|
||||||
@ -576,7 +636,7 @@ export const enUS = {
|
|||||||
pricingScale: {
|
pricingScale: {
|
||||||
totalInvestmentByIndustry: '{industryName} Total Investment',
|
totalInvestmentByIndustry: '{industryName} Total Investment',
|
||||||
totalInvestment: 'Total Investment',
|
totalInvestment: 'Total Investment',
|
||||||
clickToInput: 'Optional, enter manually, numeric, 4 decimals',
|
clickToInput: 'Click to input',
|
||||||
projectLabel: 'Project {index}',
|
projectLabel: 'Project {index}',
|
||||||
columns: {
|
columns: {
|
||||||
investAmount: 'Cost Amount (10k CNY)',
|
investAmount: 'Cost Amount (10k CNY)',
|
||||||
@ -589,7 +649,7 @@ export const enUS = {
|
|||||||
consultCategoryFactor: 'Consult Category Factor',
|
consultCategoryFactor: 'Consult Category Factor',
|
||||||
majorFactor: 'Major Factor',
|
majorFactor: 'Major Factor',
|
||||||
workStageFactor: 'Work Stage Factor (Draft/Review)',
|
workStageFactor: 'Work Stage Factor (Draft/Review)',
|
||||||
workRatio: 'Work Ratio (%)',
|
workRatio: 'Service Budget Composition Ratio and Quantity Ratio',
|
||||||
total: 'Total',
|
total: 'Total',
|
||||||
remark: 'Remark',
|
remark: 'Remark',
|
||||||
majorGroup: 'Major Code and Major Name'
|
majorGroup: 'Major Code and Major Name'
|
||||||
@ -598,7 +658,8 @@ export const enUS = {
|
|||||||
resetInvestAmount: 'Click ↻ to restore default cost amount for this column',
|
resetInvestAmount: 'Click ↻ to restore default cost amount for this column',
|
||||||
resetLandArea: 'Click ↻ to restore default land area for this column',
|
resetLandArea: 'Click ↻ to restore default land area for this column',
|
||||||
resetConsultCategoryFactor: 'Click ↻ to restore default consult category factor for this column',
|
resetConsultCategoryFactor: 'Click ↻ to restore default consult category factor for this column',
|
||||||
resetMajorFactor: 'Click ↻ to restore default major factor for this column'
|
resetMajorFactor: 'Click ↻ to restore default major factor for this column',
|
||||||
|
workRatio: 'This coefficient applies in two cases: service budget composition ratio, for cases where only part of the entrusted work in Appendix D Tables D.2 to D.7 of the specification is included; and quantity ratio, for cases where the calculation base uses the scale of each deliverable, batched task, single project, or unit project rather than the total amount, with quantity representing multiple items, copies, or units.'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
pricingPane: {
|
pricingPane: {
|
||||||
@ -610,6 +671,8 @@ export const enUS = {
|
|||||||
confirmOverride: 'Confirm Override',
|
confirmOverride: 'Confirm Override',
|
||||||
investment: {
|
investment: {
|
||||||
title: 'Investment Scale Details',
|
title: 'Investment Scale Details',
|
||||||
|
titleHint: 'Budget amount values in this pane follow whichever was operated later between the contract scale table and this pane.',
|
||||||
|
titleHintAria: 'Investment scale details hint',
|
||||||
clearDesc: 'This will clear current investment scale details. Continue?',
|
clearDesc: 'This will clear current investment scale details. Continue?',
|
||||||
overrideDesc: 'Use contract default data to override current investment scale details. Continue?'
|
overrideDesc: 'Use contract default data to override current investment scale details. Continue?'
|
||||||
},
|
},
|
||||||
@ -644,16 +707,14 @@ export const enUS = {
|
|||||||
total: 'Grand Total',
|
total: 'Grand Total',
|
||||||
columns: {
|
columns: {
|
||||||
code: 'Code',
|
code: 'Code',
|
||||||
name: 'Name',
|
name: 'Personnel Name',
|
||||||
technician: 'Technician',
|
referenceUnitPrice: 'Budget Reference Unit Price',
|
||||||
assistantEngineer: 'Assistant Engineer',
|
laborBudgetUnitPrice: 'Labor Budget Unit Price (CNY/workday)',
|
||||||
midEngineer: 'Intermediate Engineer (or Level 2 Cost Engineer)',
|
compositeBudgetUnitPrice: 'Composite Budget Unit Price (CNY/workday)',
|
||||||
seniorEngineer: 'Senior Engineer (or Level 1 Cost Engineer)',
|
adoptedBudgetUnitPrice: 'Adopted Budget Unit Price (CNY/workday)',
|
||||||
profSeniorEngineer: 'Professor-level Senior Engineer',
|
personnelCount: 'Personnel Count',
|
||||||
unitPrice: 'Unit Price (CNY/workday)',
|
|
||||||
workdayCount: 'Workday Count',
|
workdayCount: 'Workday Count',
|
||||||
subtotal: 'Subtotal (CNY)',
|
serviceBudget: 'Service Budget (CNY)',
|
||||||
avgUnitPrice: 'Average Unit Price (CNY/workday)',
|
|
||||||
remark: 'Remark'
|
remark: 'Remark'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -666,6 +727,10 @@ export const enUS = {
|
|||||||
defaultDesc: 'When providing cost consulting services, penalties should be graded by service quality. For scores >=85 and <90, penalty is 10% of budget fee; >=80 and <85: 20%; >=75 and <80: 30%; >=70 and <75: 40%; <70: 50% or above.',
|
defaultDesc: 'When providing cost consulting services, penalties should be graded by service quality. For scores >=85 and <90, penalty is 10% of budget fee; >=80 and <85: 20%; >=75 and <80: 30%; >=70 and <75: 40%; <70: 50% or above.',
|
||||||
industryHint: 'Changing industry requires reset and re-selection',
|
industryHint: 'Changing industry requires reset and re-selection',
|
||||||
industryHintAria: 'Industry hint',
|
industryHintAria: 'Industry hint',
|
||||||
|
reportContentHint: 'This field is optional and is only used to auto-generate report content.',
|
||||||
|
reportContentHintAria: 'Report content hint',
|
||||||
|
otherDescHint: 'This field is optional. The current content is only a sample. The preparer may fill it in according to actual needs or leave it blank.',
|
||||||
|
otherDescHintAria: 'Other notes hint',
|
||||||
createFromHomeFirst: 'Please create a project from Home before entering this page.',
|
createFromHomeFirst: 'Please create a project from Home before entering this page.',
|
||||||
fields: {
|
fields: {
|
||||||
projectName: 'Project Name',
|
projectName: 'Project Name',
|
||||||
@ -679,9 +744,10 @@ export const enUS = {
|
|||||||
},
|
},
|
||||||
placeholders: {
|
placeholders: {
|
||||||
overview: 'Enter project overview',
|
overview: 'Enter project overview',
|
||||||
preparedBy: 'Enter preparer',
|
desc: 'Other Notes',
|
||||||
reviewedBy: 'Enter reviewer',
|
preparedBy: 'XXX',
|
||||||
preparedCompany: 'Enter prepared company'
|
reviewedBy: 'XXX',
|
||||||
|
preparedCompany: 'XXX'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} as const
|
} as const
|
||||||
|
|||||||
@ -14,32 +14,49 @@ export const zhCN = {
|
|||||||
countdown: '本页将在 {seconds} 秒后自动尝试关闭。你也可以先在新标签页打开其他项目。',
|
countdown: '本页将在 {seconds} 秒后自动尝试关闭。你也可以先在新标签页打开其他项目。',
|
||||||
opened: '(已打开)',
|
opened: '(已打开)',
|
||||||
lastEdited: '最后编辑:{time}',
|
lastEdited: '最后编辑:{time}',
|
||||||
openDefault: '打开默认项目',
|
openDefault: '返回首页',
|
||||||
createAndOpen: '新建项目并打开'
|
createAndOpen: '新建项目并打开'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
home: {
|
home: {
|
||||||
title: '粤价函〔2008〕929号\n《广东省交通工程造价技术中介服务收费项目及标准表》',
|
title: '粤公学标字〔2026〕5号\n交通运输工程造价咨询服务预算编制规范',
|
||||||
subtitle: '项目计算 · 单项速算 · 导入数据',
|
subtitle: '项目计算 · 单项速算 · 导入数据 · 相关文件',
|
||||||
projectCalcTab: '项目计算',
|
projectCalcTab: '项目计算',
|
||||||
quickCalcTab: '快速计算',
|
quickCalcTab: '快速计算',
|
||||||
cards: {
|
cards: {
|
||||||
heroTitle: '智能预算一键生成',
|
heroTitle: '众会易|算费真容易',
|
||||||
heroSubTitle: '助力《规范》高效落地',
|
heroTitles: [
|
||||||
heroDesc: '交通建设项目工程造价咨询服务费计算',
|
'众会易 | 算费真容易',
|
||||||
|
'众会易 | 算费不熬夜',
|
||||||
|
'众会易 | 算费不费力'
|
||||||
|
],
|
||||||
|
heroSubTitle: '',
|
||||||
|
heroDesc: '智算费用 即点即出 您的时间留给创造',
|
||||||
|
heroDescs: [
|
||||||
|
'智算费用 即点即出 您的时间留给创造。',
|
||||||
|
'智算费用 一键即出 您的时间留给创造。',
|
||||||
|
'智算费用 一键生成 您的时间留给创造。',
|
||||||
|
'众会易 真容易 不熬夜 不费力'
|
||||||
|
],
|
||||||
projectBudget: '项目预算',
|
projectBudget: '项目预算',
|
||||||
projectBudgetDesc: '适用于多合同段、项目级整体计算,支持导出/导入完整项目数据',
|
projectBudgetDesc: '适用于多合同段、项目级整体计算,支持导出/导入完整项目数据',
|
||||||
quickCalc: '单项速算',
|
quickCalc: '单项速算',
|
||||||
quickCalcDesc: '适用于单项服务试算,选择行业、咨询类型、工程专业,输入基数秒出结果',
|
quickCalcDesc: '适用于单项服务试算,选择行业、咨询类型、工程专业,输入基数秒出结果',
|
||||||
importData: '导入数据',
|
importData: '导入数据',
|
||||||
importDataDesc: '导入".zw"数据包,并自动新建一个项目用于恢复数据,不会覆盖现有项目',
|
importDataDesc: '导入".zw"数据包,并自动新建一个项目用于恢复数据,不会覆盖现有项目',
|
||||||
|
relatedFiles: '相关文件',
|
||||||
|
relatedFilesDesc: '在线查看、打印和下载与该计算器依据的收费文件、相关招标文件与合同文件范本',
|
||||||
|
viewFiles: '查看文件',
|
||||||
enter: '进入计算',
|
enter: '进入计算',
|
||||||
developing: '正在开发',
|
|
||||||
pickFile: '选择文件',
|
pickFile: '选择文件',
|
||||||
pickExisting: '选择已有项目',
|
pickExisting: '选择已有项目',
|
||||||
relatedFiles: '相关文件',
|
openFileSystem: '打开文件系统',
|
||||||
relatedFilesDesc: '可查看相关收费文件、招标文件、合同文件、服务内容、工作要求',
|
file: '文件系统',
|
||||||
openRelatedFiles: '打开页面'
|
fileDataDesc: '文件系统可查看相关收费文件、招标文件、合同文件、服务内容、工作要求'
|
||||||
|
},
|
||||||
|
disclaimer: {
|
||||||
|
link: '查看免责声明',
|
||||||
|
supportText: '本计算工具由众为工程咨询有限公司提供免费技术支持'
|
||||||
},
|
},
|
||||||
dialog: {
|
dialog: {
|
||||||
newProject: '新建项目',
|
newProject: '新建项目',
|
||||||
@ -56,6 +73,47 @@ export const zhCN = {
|
|||||||
noProjectYet: '当前暂无可进入的项目,请先新建项目。'
|
noProjectYet: '当前暂无可进入的项目,请先新建项目。'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
disclaimerPage: {
|
||||||
|
documentTitle: '预算编制工具免责声明',
|
||||||
|
eyebrow: 'DISCLAIMER',
|
||||||
|
pageTitle: '《交通运输工程造价咨询服务预算编制规范》(T/GDHS 017-2026)预算编制工具免责声明',
|
||||||
|
lastUpdatedLabel: '最后更新日期:',
|
||||||
|
lastUpdatedValue: '2026年04月16日',
|
||||||
|
leadText: '感谢您使用本网站提供的《交通运输工程造价咨询服务预算编制规范》(T/GDHS 017-2026)造价咨询服务预算编制工具。在您使用本工具前,请仔细阅读以下免责声明条款。您继续使用本工具,即视为您已阅读、理解并同意接受本声明的全部内容。',
|
||||||
|
sections: {
|
||||||
|
standardBasisTitle: '1. 标准依据说明',
|
||||||
|
standardBasisP1: '1.1 本工具依据广东省公路学会发布的团体标准《交通运输工程造价咨询服务预算编制规范》(T/GDHS 017-2026)设定编制方法。使用者应自行判断该标准是否适用于其具体项目及所在地主管部门要求。',
|
||||||
|
standardBasisP2: '1.2 本工具所依据的规范版本已在工具界面标注为 T/GDHS 017-2026。如该规范后续发布修订内容、补充规定或被新版本替代,本工具可能无法及时同步更新。使用者有责任在使用前确认所依据规范是否仍为最新有效版本。',
|
||||||
|
standardBasisP3: '1.3 本工具的计算结果基于本规范中的预算编制方法、费用组成及编制规则,但不同地区、不同项目法人对造价咨询服务预算编制的具体要求和计算方法可能存在差异。本工具不保证其计算结果符合任何特定项目或特定主管部门的审核要求。',
|
||||||
|
referenceOnlyTitle: '2. 计算结果仅供参考',
|
||||||
|
referenceOnlyP1: '本工具所提供的所有计算结果,包括但不限于数值、明细表、汇总报表及编制说明,均基于您输入的参数(如工程行业、项目规模、咨询类别、工程专业、工作内容、调整系数等)以及本规范中的数学模型与公式自动生成,仅供参考使用。这些结果不构成任何形式的专业建议,也不代表任何官方或强制性的预算审批依据。',
|
||||||
|
accuracyTitle: '3. 不保证准确性与完整性',
|
||||||
|
accuracyP1: '尽管我们尽力确保本工具可用,但本工具按现状提供,不附带任何明示或暗示的保证。我们无法保证计算结果在任何情况下均准确、无误或完整。由于数据输入错误、公式取舍、四舍五入或系统延迟等原因,结果可能与实际情况存在偏差。',
|
||||||
|
riskTitle: '4. 用户自行承担风险',
|
||||||
|
riskP1: '您应独立判断计算结果的可信性,并承担将其用于任何决策所产生的全部风险与责任。您不应依赖本工具替代专业人士的具体计算或复核。在作出重大决定前,建议咨询持有交通运输工程造价工程师注册证书的专业人员,或结合项目具体情况进行人工验证与复核。',
|
||||||
|
liabilityTitle: '5. 责任限制',
|
||||||
|
liabilityP1: '在适用法律允许的最大范围内,本工具的开发方、管理方、发布方及其关联方,不对因使用或无法使用本工具而导致的任何直接、间接、偶然、特殊或后果性损失承担法律责任,即使已被告知可能发生此类损失。',
|
||||||
|
liabilityP2: '特别声明:任何造价咨询企业或个人依据本工具计算结果出具的成果文件,其质量责任由出具方自行承担。本工具不对任何第三方成果文件的准确性、合规性或由此引发的争议承担责任。',
|
||||||
|
interruptionTitle: '6. 服务中断与修改',
|
||||||
|
interruptionP1: '我们保留随时修改、暂停或终止本工具部分或全部功能的权利,且可能不另行通知。对于因技术维护、网络故障、第三方服务中断、规范版本变更等原因导致的工具不可用、数据丢失或计算结果变化,我们不承担责任。',
|
||||||
|
externalTitle: '7. 外部链接与第三方内容',
|
||||||
|
externalP1: '如果本工具引用或链接至全国团体标准信息平台、广东省公路学会官网或其他第三方网站,该等链接仅为方便用户查阅规范原文而提供,不代表我们认可其内容的准确性、时效性或完整性。对于任何第三方网站或工具的信息、服务或内容,我们不承担责任。',
|
||||||
|
lawTitle: '8. 适用法律',
|
||||||
|
lawP1: '本声明的解释、效力及争议解决均适用中华人民共和国法律。若本声明任何条款被认定为无效或不可执行,不影响其余条款的效力。'
|
||||||
|
},
|
||||||
|
confirm: {
|
||||||
|
title: '用户确认',
|
||||||
|
desc1: '我已阅读、理解并同意本免责声明的全部内容。',
|
||||||
|
desc2: '勾选后方可继续使用本工具。',
|
||||||
|
checkbox: '我已阅读、理解并同意本免责声明的全部内容。',
|
||||||
|
continue: '同意并继续',
|
||||||
|
hint: '勾选后将记录当前浏览器的同意状态,后续从同一受限入口访问时不再重复提示。'
|
||||||
|
},
|
||||||
|
actions: {
|
||||||
|
back: '返回入口',
|
||||||
|
switchLocale: '切换语言'
|
||||||
|
}
|
||||||
|
},
|
||||||
tab: {
|
tab: {
|
||||||
toolbar: {
|
toolbar: {
|
||||||
light: '浅色',
|
light: '浅色',
|
||||||
@ -69,7 +127,7 @@ export const zhCN = {
|
|||||||
reset: '重置',
|
reset: '重置',
|
||||||
resetting: '重置中...',
|
resetting: '重置中...',
|
||||||
projectList: '项目列表',
|
projectList: '项目列表',
|
||||||
projectCount: '项目数量:{count}/{max}',
|
projectCount: '项目数量:{count}',
|
||||||
createProject: '新建项目',
|
createProject: '新建项目',
|
||||||
backHome: '返回入口',
|
backHome: '返回入口',
|
||||||
resetAll: '清除全部项目',
|
resetAll: '清除全部项目',
|
||||||
@ -169,9 +227,7 @@ export const zhCN = {
|
|||||||
toast: {
|
toast: {
|
||||||
export: '导出报表',
|
export: '导出报表',
|
||||||
success: '导出成功',
|
success: '导出成功',
|
||||||
failed: '导出失败',
|
failed: '导出失败'
|
||||||
saveSuccess: '保存成功',
|
|
||||||
saveFailed: '保存失败'
|
|
||||||
},
|
},
|
||||||
messages: {
|
messages: {
|
||||||
defaultProjectLabel: '默认项目',
|
defaultProjectLabel: '默认项目',
|
||||||
@ -196,7 +252,7 @@ export const zhCN = {
|
|||||||
copied: '已复制',
|
copied: '已复制',
|
||||||
copyFailed: '复制失败',
|
copyFailed: '复制失败',
|
||||||
brandAlt: '众为咨询',
|
brandAlt: '众为咨询',
|
||||||
supportText: '本网站由众为工程咨询有限公司提供免费技术支持',
|
supportText: '本计算工具由众为工程咨询有限公司提供免费技术支持',
|
||||||
aboutTitle: '关于我们',
|
aboutTitle: '关于我们',
|
||||||
companyName: '众为工程咨询有限公司',
|
companyName: '众为工程咨询有限公司',
|
||||||
openOfficialSiteAria: '跳转到官网首页',
|
openOfficialSiteAria: '跳转到官网首页',
|
||||||
@ -278,9 +334,9 @@ export const zhCN = {
|
|||||||
metaBudget: '合同段预算金额:{amount}',
|
metaBudget: '合同段预算金额:{amount}',
|
||||||
currencySuffix: '元',
|
currencySuffix: '元',
|
||||||
categories: {
|
categories: {
|
||||||
baseInfo: '合同基础信息',
|
baseInfo: '基础信息',
|
||||||
scaleInfo: '合同规模',
|
scaleInfo: '规模信息',
|
||||||
services: '合同费用汇总表',
|
services: '咨询服务',
|
||||||
consultFactor: '咨询分类系数',
|
consultFactor: '咨询分类系数',
|
||||||
majorFactor: '工程专业系数',
|
majorFactor: '工程专业系数',
|
||||||
additionalFee: '附加工作费',
|
additionalFee: '附加工作费',
|
||||||
@ -305,7 +361,9 @@ export const zhCN = {
|
|||||||
reserveTitle: '预备费'
|
reserveTitle: '预备费'
|
||||||
},
|
},
|
||||||
htInfo: {
|
htInfo: {
|
||||||
scaleDetailTitle: '合同规模明细'
|
scaleDetailTitle: '合同规模明细',
|
||||||
|
scaleDetailHint: '当本表规模与项目规模数据不一致时,计费以本表规模为准',
|
||||||
|
scaleDetailHintAria: '合同规模明细提示'
|
||||||
},
|
},
|
||||||
htFeeRate: {
|
htFeeRate: {
|
||||||
baseLabel: '基数(所有服务费预算合计)',
|
baseLabel: '基数(所有服务费预算合计)',
|
||||||
@ -320,7 +378,7 @@ export const zhCN = {
|
|||||||
title: '咨询服务明细',
|
title: '咨询服务明细',
|
||||||
warning: '※ 请注意检查并修改《规范》建议的限值或特殊值,并在确认金额栏修改',
|
warning: '※ 请注意检查并修改《规范》建议的限值或特殊值,并在确认金额栏修改',
|
||||||
editTabTitle: '服务编辑-{name}',
|
editTabTitle: '服务编辑-{name}',
|
||||||
subtotal: '小计',
|
subtotal: '总计',
|
||||||
edit: '编辑',
|
edit: '编辑',
|
||||||
resetDefault: '恢复默认',
|
resetDefault: '恢复默认',
|
||||||
delete: '删除',
|
delete: '删除',
|
||||||
@ -350,7 +408,7 @@ export const zhCN = {
|
|||||||
},
|
},
|
||||||
htSummary: {
|
htSummary: {
|
||||||
title: '合同段汇总',
|
title: '合同段汇总',
|
||||||
total: '合计',
|
total: '总计',
|
||||||
remark: '说明',
|
remark: '说明',
|
||||||
placeholder: '请先填咨询服务/附加工作费/预备费的数据',
|
placeholder: '请先填咨询服务/附加工作费/预备费的数据',
|
||||||
additionalPrefix: '附加工作费',
|
additionalPrefix: '附加工作费',
|
||||||
@ -370,7 +428,7 @@ export const zhCN = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
htFeeGrid: {
|
htFeeGrid: {
|
||||||
subtotal: '小计',
|
subtotal: '总计',
|
||||||
currentRow: '当前行',
|
currentRow: '当前行',
|
||||||
unnamed: '未命名',
|
unnamed: '未命名',
|
||||||
edit: '编辑',
|
edit: '编辑',
|
||||||
@ -402,6 +460,8 @@ export const zhCN = {
|
|||||||
},
|
},
|
||||||
serviceSelector: {
|
serviceSelector: {
|
||||||
title: '选择服务',
|
title: '选择服务',
|
||||||
|
titleHint: '本选择项较《规范》列明的服务项有所增加。此增加与《规范》无冲突,只为满足《规范》费用计算之需',
|
||||||
|
titleHintAria: '选择服务提示',
|
||||||
clear: '清空',
|
clear: '清空',
|
||||||
empty: '暂无服务'
|
empty: '暂无服务'
|
||||||
},
|
},
|
||||||
@ -417,8 +477,7 @@ export const zhCN = {
|
|||||||
landScaleFormula: '用地规模法计算公式',
|
landScaleFormula: '用地规模法计算公式',
|
||||||
workload: '工作量法',
|
workload: '工作量法',
|
||||||
hourly: '工时法',
|
hourly: '工时法',
|
||||||
workContent: '工作内容',
|
workContent: '工作内容'
|
||||||
otherService: '其他服务计算'
|
|
||||||
},
|
},
|
||||||
formulaColumns: {
|
formulaColumns: {
|
||||||
subtitle: '直接展示当前计价法 store 的最新明细,随数据变更自动同步。',
|
subtitle: '直接展示当前计价法 store 的最新明细,随数据变更自动同步。',
|
||||||
@ -438,7 +497,7 @@ export const zhCN = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
htFeeDetail: {
|
htFeeDetail: {
|
||||||
subtotal: '小计',
|
subtotal: '总计',
|
||||||
currentRow: '当前行',
|
currentRow: '当前行',
|
||||||
clickToInput: '点击输入',
|
clickToInput: '点击输入',
|
||||||
addRow: '添加行',
|
addRow: '添加行',
|
||||||
@ -559,8 +618,8 @@ export const zhCN = {
|
|||||||
},
|
},
|
||||||
xmCard: {
|
xmCard: {
|
||||||
categories: {
|
categories: {
|
||||||
info: '项目基础信息',
|
info: '基础信息',
|
||||||
scaleInfo: '项目规模',
|
scaleInfo: '规模信息',
|
||||||
consultCategoryFactor: '咨询分类系数',
|
consultCategoryFactor: '咨询分类系数',
|
||||||
majorFactor: '工程专业系数',
|
majorFactor: '工程专业系数',
|
||||||
contract: '合同段管理'
|
contract: '合同段管理'
|
||||||
@ -576,12 +635,11 @@ export const zhCN = {
|
|||||||
pricingScale: {
|
pricingScale: {
|
||||||
totalInvestmentByIndustry: '{industryName}总投资',
|
totalInvestmentByIndustry: '{industryName}总投资',
|
||||||
totalInvestment: '总投资',
|
totalInvestment: '总投资',
|
||||||
clickToInput: '非必填,手动录入,数字,4位',
|
clickToInput: '点击输入',
|
||||||
projectLabel: '项目{index}',
|
projectLabel: '项目{index}',
|
||||||
columns: {
|
columns: {
|
||||||
code: '编码',
|
|
||||||
investAmount: '造价金额(万元)',
|
investAmount: '造价金额(万元)',
|
||||||
landArea: '造价金额(元)',
|
landArea: '用地面积(亩)',
|
||||||
benchmarkBudget: '基准预算(元)',
|
benchmarkBudget: '基准预算(元)',
|
||||||
basicWork: '基本工作',
|
basicWork: '基本工作',
|
||||||
optionalWork: '可选工作',
|
optionalWork: '可选工作',
|
||||||
@ -589,25 +647,18 @@ export const zhCN = {
|
|||||||
budgetFee: '预算费用',
|
budgetFee: '预算费用',
|
||||||
consultCategoryFactor: '咨询分类系数',
|
consultCategoryFactor: '咨询分类系数',
|
||||||
majorFactor: '专业系数',
|
majorFactor: '专业系数',
|
||||||
workStageFactor: '工作环节系数',
|
workStageFactor: '工作环节系数(编审系数)',
|
||||||
workRatio: '工作占比',
|
workRatio: '服务预算构成比率与数量比',
|
||||||
total: '合计',
|
total: '合计',
|
||||||
remark: '说明',
|
remark: '说明',
|
||||||
majorGroup: '项目明细费用',
|
majorGroup: '专业编码以及工程专业名称'
|
||||||
name: '名称',
|
|
||||||
number: '编码',
|
|
||||||
base: '计算基础',
|
|
||||||
base2: '计算基数F(万元)',
|
|
||||||
formula: '计算公式',
|
|
||||||
calculationAmount: '计算金额(元)',
|
|
||||||
calculationGroup: '计算公式',
|
|
||||||
serviceFee: '服务费用(元)'
|
|
||||||
},
|
},
|
||||||
tooltip: {
|
tooltip: {
|
||||||
resetInvestAmount: '点击右侧↻恢复本列默认造价金额',
|
resetInvestAmount: '点击右侧↻本列造价金额数值恢复为本合同规模数值',
|
||||||
resetLandArea: '点击右侧↻恢复本列默认用地面积',
|
resetLandArea: '点击右侧↻本列用地面积数值恢复为本合同规模数值',
|
||||||
resetConsultCategoryFactor: '点击右侧↻恢复本列默认咨询分类系数',
|
resetConsultCategoryFactor: '点击右侧↻本列咨询分类系数值恢复为本项目的相应的咨询分类系数值',
|
||||||
resetMajorFactor: '点击右侧↻恢复本列默认专业系数'
|
resetMajorFactor: '点击右侧↻本列专业系数值恢复为本项目的相应的专业系数值',
|
||||||
|
workRatio: '本列系数适用于以下两种情形:服务预算构成比率:适用于《规范》附录D表D.2~D.7中委托工作内容仅为部分工作时的情形;数量比,适用于计算基数按每份成果、分批次任务、单项工程或单位工程的规模(非总额)的情形,数量表示有多个个、份或项'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
pricingPane: {
|
pricingPane: {
|
||||||
@ -619,6 +670,8 @@ export const zhCN = {
|
|||||||
confirmOverride: '确认覆盖',
|
confirmOverride: '确认覆盖',
|
||||||
investment: {
|
investment: {
|
||||||
title: '投资规模明细',
|
title: '投资规模明细',
|
||||||
|
titleHint: '本表造价金额取值规则:以本合同规模表与本表造价金额中操作时间较晚的值为准',
|
||||||
|
titleHintAria: '投资规模明细提示',
|
||||||
clearDesc: '将清空当前投资规模明细,是否继续?',
|
clearDesc: '将清空当前投资规模明细,是否继续?',
|
||||||
overrideDesc: '将使用合同默认数据覆盖当前投资规模明细,是否继续?'
|
overrideDesc: '将使用合同默认数据覆盖当前投资规模明细,是否继续?'
|
||||||
},
|
},
|
||||||
@ -634,78 +687,34 @@ export const zhCN = {
|
|||||||
unavailableMessage: '当前服务没有关联工作量法任务,无需填写此部分内容。',
|
unavailableMessage: '当前服务没有关联工作量法任务,无需填写此部分内容。',
|
||||||
clickToInput: '点击输入',
|
clickToInput: '点击输入',
|
||||||
none: '无',
|
none: '无',
|
||||||
total: '总合计',
|
total: '总计',
|
||||||
columns: {
|
columns: {
|
||||||
code: '编码',
|
code: '编码',
|
||||||
name: '名称',
|
name: '名称',
|
||||||
budgetBase: '计算基础',
|
budgetBase: '预算基数',
|
||||||
budgetReferenceUnitPrice: '计算基数(份)',
|
budgetReferenceUnitPrice: '预算参考单价',
|
||||||
budgetAdoptedUnitPrice: '最低单价(万元/份)',
|
budgetAdoptedUnitPrice: '预算采用单价',
|
||||||
workload: '中值单价(万元/份)',
|
workload: '工作量',
|
||||||
consultCategoryFactor: '最高单价(万元/份)',
|
consultCategoryFactor: '咨询分类系数',
|
||||||
cLow: '计算最低值(元)',
|
serviceFee: '服务费用(元)',
|
||||||
cMid: '计算中值(元)',
|
|
||||||
cHigh: '计算最高值(元)',
|
|
||||||
serviceFee: '本计算取值(元)',
|
|
||||||
remark: '说明'
|
remark: '说明'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
hourlyFeeGrid: {
|
hourlyFeeGrid: {
|
||||||
title: '工时法明细',
|
title: '工时法明细',
|
||||||
clickToInput: '点击输入',
|
clickToInput: '点击输入',
|
||||||
total: '总合计',
|
total: '总计',
|
||||||
columns: {
|
columns: {
|
||||||
code: '编码',
|
code: '编码',
|
||||||
name: '名称',
|
name: '人员名称',
|
||||||
technician: '技术员',
|
referenceUnitPrice: '预算参考单价',
|
||||||
assistantEngineer: '助理工程师',
|
laborBudgetUnitPrice: '人工预算单价(元/工日)',
|
||||||
midEngineer: '中级工程师(或二级造价工程师)',
|
|
||||||
seniorEngineer: '高级工程师(或一级造价工程师)',
|
|
||||||
profSeniorEngineer: '正高级工程师',
|
|
||||||
unitPrice: '单价(元/工日)',
|
|
||||||
workdayCount: '工日数量(工日)',
|
|
||||||
subtotal: '费用小计(元)',
|
|
||||||
unitPrice2: '单价(元/工日)',
|
|
||||||
workdayCount2: '工日数量(工日)',
|
|
||||||
subtotal2: '费用小计(元)',
|
|
||||||
unitPrice3: '单价(元/工日)',
|
|
||||||
workdayCount3: '工日数量(工日)',
|
|
||||||
subtotal3: '费用小计(元)',
|
|
||||||
unitPrice4: '单价(元/工日)',
|
|
||||||
workdayCount4: '工日数量(工日)',
|
|
||||||
subtotal4: '费用小计(元)',
|
|
||||||
unitPrice5: '单价(元/工日)',
|
|
||||||
workdayCount5: '工日数量(工日)',
|
|
||||||
subtotal5: '费用小计(元)',
|
|
||||||
unitPrice6: '单价(元/工日)',
|
|
||||||
workdayCount6: '工日数量(工日)',
|
|
||||||
subtotal6: '费用小计(元)',
|
|
||||||
avgUnitPrice: '折算单价(元/工日)',
|
|
||||||
remark: '说明',
|
|
||||||
total: '合计',
|
|
||||||
referenceUnitPrice: '参考单价(元/工日)',
|
|
||||||
laborBudgetUnitPrice: '劳动预算单价(元/工日)',
|
|
||||||
compositeBudgetUnitPrice: '综合预算单价(元/工日)',
|
compositeBudgetUnitPrice: '综合预算单价(元/工日)',
|
||||||
adoptedBudgetUnitPrice: '采用单价(元/工日)',
|
adoptedBudgetUnitPrice: '预算采用单价(元/工日)',
|
||||||
personnelCount: '人员数量(人)',
|
personnelCount: '人员数量(人)',
|
||||||
serviceBudget: '服务费用(元)',
|
workdayCount: '工日数量(工日)',
|
||||||
}
|
serviceBudget: '服务预算(元)',
|
||||||
},
|
remark: '说明'
|
||||||
otherService: {
|
|
||||||
title: '其他服务计算',
|
|
||||||
clickToInput: '点击输入',
|
|
||||||
total: '小计',
|
|
||||||
columns: {
|
|
||||||
num: '序号',
|
|
||||||
code: '编码',
|
|
||||||
name: '名称',
|
|
||||||
feeItem: '费用项',
|
|
||||||
unit: '单位',
|
|
||||||
quantity: '数量',
|
|
||||||
unitPrice: '单价',
|
|
||||||
serviceFee: '服务费用(元)',
|
|
||||||
remark: '说明',
|
|
||||||
actions: '操作'
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
xmScaleGrid: {
|
xmScaleGrid: {
|
||||||
@ -717,6 +726,10 @@ export const zhCN = {
|
|||||||
defaultDesc: '在履行造价咨询服务时,宜根据咨询服务质量情况分级确定相应的处罚金额。其中考评得分在大于及等于85和小于90分时,处罚金额为预算费用的10%;其中考评得分在大于及等于80和小于85分时,处罚金额为预算费用的20%;其中考评得分在大于及等于75和小于80分时,处罚金额为预算费用的30%;其中考评得分在大于及等于70和小于75分时,处罚金额为预算费用的40%;其中考评得分小于70分时,处罚金额为预算费用的50%以上。',
|
defaultDesc: '在履行造价咨询服务时,宜根据咨询服务质量情况分级确定相应的处罚金额。其中考评得分在大于及等于85和小于90分时,处罚金额为预算费用的10%;其中考评得分在大于及等于80和小于85分时,处罚金额为预算费用的20%;其中考评得分在大于及等于75和小于80分时,处罚金额为预算费用的30%;其中考评得分在大于及等于70和小于75分时,处罚金额为预算费用的40%;其中考评得分小于70分时,处罚金额为预算费用的50%以上。',
|
||||||
industryHint: '变更需要重置后重新选择',
|
industryHint: '变更需要重置后重新选择',
|
||||||
industryHintAria: '工程行业提示',
|
industryHintAria: '工程行业提示',
|
||||||
|
reportContentHint: '本内容为选择性填写,填写内容仅用于自动生成编制报告内容',
|
||||||
|
reportContentHintAria: '说明内容提示',
|
||||||
|
otherDescHint: '本内容为选择性填写。当前显示内容仅为示意,编制人可根据实际情况填写,亦可不填。',
|
||||||
|
otherDescHintAria: '其他说明提示',
|
||||||
createFromHomeFirst: '请从首页先新建项目后再进入此页面。',
|
createFromHomeFirst: '请从首页先新建项目后再进入此页面。',
|
||||||
fields: {
|
fields: {
|
||||||
projectName: '项目名称',
|
projectName: '项目名称',
|
||||||
@ -730,9 +743,10 @@ export const zhCN = {
|
|||||||
},
|
},
|
||||||
placeholders: {
|
placeholders: {
|
||||||
overview: '请输入项目概况',
|
overview: '请输入项目概况',
|
||||||
preparedBy: '请输入编制人',
|
desc: '其他说明',
|
||||||
reviewedBy: '请输入复核人',
|
preparedBy: 'XXX',
|
||||||
preparedCompany: '请输入编制单位'
|
reviewedBy: 'XXX',
|
||||||
|
preparedCompany: 'XXX'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} as const
|
} as const
|
||||||
|
|||||||
4031
src/layout/tab.vue
4031
src/layout/tab.vue
File diff suppressed because it is too large
Load Diff
@ -96,10 +96,14 @@ const activeComponent = computed(() => {
|
|||||||
|
|
||||||
const sideWidthStyle = computed(() => ({ width: 'var(--app-typeline-side-w)' }))
|
const sideWidthStyle = computed(() => ({ width: 'var(--app-typeline-side-w)' }))
|
||||||
const itemGapStyle = computed(() => ({ gap: 'var(--app-typeline-gap)' }))
|
const itemGapStyle = computed(() => ({ gap: 'var(--app-typeline-gap)' }))
|
||||||
|
const axisColStyle = computed(() => ({ width: 'var(--app-typeline-dot)' }))
|
||||||
const dotStyle = computed(() => ({ width: 'var(--app-typeline-dot)', height: 'var(--app-typeline-dot)' }))
|
const dotStyle = computed(() => ({ width: 'var(--app-typeline-dot)', height: 'var(--app-typeline-dot)' }))
|
||||||
const dotInnerStyle = computed(() => ({ width: 'var(--app-typeline-dot-inner)', height: 'var(--app-typeline-dot-inner)' }))
|
const dotInnerStyle = computed(() => ({ width: 'var(--app-typeline-dot-inner)', height: 'var(--app-typeline-dot-inner)' }))
|
||||||
const labelStyle = computed(() => ({ fontSize: 'var(--app-typeline-label-font)', lineHeight: 'var(--app-typeline-label-line)' }))
|
const labelStyle = computed(() => ({ fontSize: 'var(--app-typeline-label-font)', lineHeight: 'var(--app-typeline-label-line)' }))
|
||||||
const lineStyle = computed(() => ({ left: 'var(--app-typeline-line-left)' }))
|
const connectorStyle = computed(() => ({
|
||||||
|
height: 'var(--app-typeline-arrow-shaft-h)',
|
||||||
|
width: 'var(--app-typeline-arrow-line-w)'
|
||||||
|
}))
|
||||||
|
|
||||||
const copyBtnText = ref(t('typeLine.copy'))
|
const copyBtnText = ref(t('typeLine.copy'))
|
||||||
const sheetOpen = ref(false)
|
const sheetOpen = ref(false)
|
||||||
@ -107,6 +111,34 @@ const sheetOpen = ref(false)
|
|||||||
let copyBtnTimer: ReturnType<typeof setTimeout> | null = null
|
let copyBtnTimer: ReturnType<typeof setTimeout> | null = null
|
||||||
let titleOverflowRafId: number | null = null
|
let titleOverflowRafId: number | null = null
|
||||||
|
|
||||||
|
const copyTextWithFallback = async (text: string) => {
|
||||||
|
if (!text) return false
|
||||||
|
|
||||||
|
if (typeof navigator !== 'undefined' && navigator.clipboard?.writeText) {
|
||||||
|
await navigator.clipboard.writeText(text)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof document === 'undefined') return false
|
||||||
|
|
||||||
|
const textarea = document.createElement('textarea')
|
||||||
|
textarea.value = text
|
||||||
|
textarea.setAttribute('readonly', 'true')
|
||||||
|
textarea.style.position = 'fixed'
|
||||||
|
textarea.style.top = '-9999px'
|
||||||
|
textarea.style.left = '-9999px'
|
||||||
|
textarea.style.opacity = '0'
|
||||||
|
document.body.appendChild(textarea)
|
||||||
|
|
||||||
|
try {
|
||||||
|
textarea.focus()
|
||||||
|
textarea.select()
|
||||||
|
textarea.setSelectionRange(0, textarea.value.length)
|
||||||
|
return document.execCommand('copy')
|
||||||
|
} finally {
|
||||||
|
document.body.removeChild(textarea)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -118,8 +150,8 @@ const handleCopySubtitle = async () => {
|
|||||||
if (!text) return
|
if (!text) return
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await navigator.clipboard.writeText(text)
|
const copied = await copyTextWithFallback(text)
|
||||||
copyBtnText.value = t('typeLine.copied')
|
copyBtnText.value = copied ? t('typeLine.copied') : t('typeLine.copyFailed')
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('copy failed:', error)
|
console.error('copy failed:', error)
|
||||||
copyBtnText.value = t('typeLine.copyFailed')
|
copyBtnText.value = t('typeLine.copyFailed')
|
||||||
@ -256,24 +288,32 @@ useMotionValueEvent(
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div :class="['flex flex-col gap-6 relative ', (props.title || props.subtitle || props.metaText) ? 'mt-3' : 'mt-6']">
|
<div :class="['flex flex-col gap-2 relative ', (props.title || props.subtitle || props.metaText) ? 'mt-3' : 'mt-6']">
|
||||||
<div :style="lineStyle" class="absolute top-3 bottom-3 w-[1.5px] bg-border/60"></div>
|
<div v-for="(item, index) in props.categories" :key="item.key"
|
||||||
|
:style="itemGapStyle" class="flex items-start cursor-pointer group" @click="switchCategory(item.key)">
|
||||||
<div v-for="item in props.categories" :key="item.key"
|
<div :style="axisColStyle" class="flex shrink-0 flex-col items-center">
|
||||||
:style="itemGapStyle" class="relative flex items-center cursor-pointer group" @click="switchCategory(item.key)">
|
<div :class="[
|
||||||
<div :class="[
|
'z-10 rounded-full border-2 flex shrink-0 items-center justify-center transition-all duration-200',
|
||||||
'z-10 rounded-full border-2 flex items-center justify-center transition-all duration-200',
|
activeCategory === item.key
|
||||||
activeCategory === item.key
|
? 'bg-primary border-primary shadow-[0_0_0_3px_rgba(var(--primary),0.15)]'
|
||||||
? 'bg-blue-600 border-blue-600'
|
: 'bg-background border-muted-foreground/40 group-hover:border-muted-foreground/70'
|
||||||
: 'bg-background border-slate-300 group-hover:border-slate-400'
|
]" :style="dotStyle">
|
||||||
]" :style="dotStyle">
|
<div v-if="activeCategory === item.key" class="bg-background rounded-full" :style="dotInnerStyle"></div>
|
||||||
<div v-if="activeCategory === item.key" class="bg-white rounded-full" :style="dotInnerStyle"></div>
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="index < props.categories.length - 1"
|
||||||
|
class="flex flex-col items-center"
|
||||||
|
:style="{ paddingTop: 'var(--app-typeline-arrow-offset)' }"
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
|
<div :style="connectorStyle" class="typeline-arrow-connector"></div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<span :class="[
|
<span :class="[
|
||||||
'transition-colors duration-200',
|
'pt-px transition-colors duration-200',
|
||||||
activeCategory === item.key
|
activeCategory === item.key
|
||||||
? 'font-semibold text-blue-600'
|
? 'font-semibold text-primary'
|
||||||
: 'text-slate-500 group-hover:text-slate-700'
|
: 'text-muted-foreground group-hover:text-foreground'
|
||||||
]" :style="labelStyle">
|
]" :style="labelStyle">
|
||||||
{{ item.label }}
|
{{ item.label }}
|
||||||
</span>
|
</span>
|
||||||
@ -391,4 +431,23 @@ useMotionValueEvent(
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
word-break: break-word;
|
word-break: break-word;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.typeline-arrow-connector {
|
||||||
|
position: relative;
|
||||||
|
background: color-mix(in oklab, var(--foreground) 22%, var(--border));
|
||||||
|
border-radius: 9999px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.typeline-arrow-connector::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
left: 50%;
|
||||||
|
top: 100%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
border-left: calc(var(--app-typeline-arrow-head-w) / 2) solid transparent;
|
||||||
|
border-right: calc(var(--app-typeline-arrow-head-w) / 2) solid transparent;
|
||||||
|
border-top: var(--app-typeline-arrow-head-h) solid color-mix(in oklab, var(--foreground) 22%, var(--border));
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@ -45,7 +45,7 @@ export class AgGridResetHeader implements IHeaderComp {
|
|||||||
eButton.style.height = '18px'
|
eButton.style.height = '18px'
|
||||||
eButton.style.border = '1px solid #d1d5db'
|
eButton.style.border = '1px solid #d1d5db'
|
||||||
eButton.style.borderRadius = '999px'
|
eButton.style.borderRadius = '999px'
|
||||||
eButton.style.background = '#fff'
|
eButton.style.background = '#edff87'
|
||||||
eButton.style.color = '#4b5563'
|
eButton.style.color = '#4b5563'
|
||||||
eButton.style.cursor = 'pointer'
|
eButton.style.cursor = 'pointer'
|
||||||
eButton.style.fontSize = '12px'
|
eButton.style.fontSize = '12px'
|
||||||
@ -69,8 +69,38 @@ export class AgGridResetHeader implements IHeaderComp {
|
|||||||
this.params = params
|
this.params = params
|
||||||
this.eLabel.textContent = params.displayName || params.column?.getColDef().headerName || ''
|
this.eLabel.textContent = params.displayName || params.column?.getColDef().headerName || ''
|
||||||
const fallbackResetTitle = i18n.global.t('agGrid.resetDefault')
|
const fallbackResetTitle = i18n.global.t('agGrid.resetDefault')
|
||||||
|
const hintText = String(params.column?.getColDef().headerTooltip || '').trim()
|
||||||
|
const hasReset = Boolean(params.onReset)
|
||||||
|
const hasHint = Boolean(hintText)
|
||||||
|
|
||||||
|
if (hasReset) {
|
||||||
|
this.eButton.textContent = '↻'
|
||||||
|
this.eButton.title = ''
|
||||||
|
this.eButton.setAttribute('aria-label', params.resetTitle || fallbackResetTitle)
|
||||||
|
this.eButton.style.visibility = 'visible'
|
||||||
|
this.eButton.style.border = '1px solid #d1d5db'
|
||||||
|
this.eButton.style.background = '#edff87'
|
||||||
|
this.eButton.style.color = '#4b5563'
|
||||||
|
this.eButton.style.cursor = 'pointer'
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasHint) {
|
||||||
|
this.eButton.textContent = '?'
|
||||||
|
this.eButton.title = hintText
|
||||||
|
this.eButton.setAttribute('aria-label', hintText)
|
||||||
|
this.eButton.style.visibility = 'visible'
|
||||||
|
this.eButton.style.border = '1px solid #cbd5e1'
|
||||||
|
this.eButton.style.background = '#ffffff'
|
||||||
|
this.eButton.style.color = '#64748b'
|
||||||
|
this.eButton.style.cursor = 'help'
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
this.eButton.textContent = ''
|
||||||
|
this.eButton.title = ''
|
||||||
this.eButton.setAttribute('aria-label', params.resetTitle || fallbackResetTitle)
|
this.eButton.setAttribute('aria-label', params.resetTitle || fallbackResetTitle)
|
||||||
this.eButton.style.visibility = params.onReset ? 'visible' : 'hidden'
|
this.eButton.style.visibility = 'hidden'
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -8,6 +8,7 @@ import {
|
|||||||
import { roundTo, sumByNumber, toDecimal, toFiniteNumberOrNull } from '@/lib/decimal'
|
import { roundTo, sumByNumber, toDecimal, toFiniteNumberOrNull } from '@/lib/decimal'
|
||||||
import { getScaleBudgetFee } from '@/lib/pricingScaleFee'
|
import { getScaleBudgetFee } from '@/lib/pricingScaleFee'
|
||||||
import { getScaleBudgetFeeByRow } from '@/lib/pricingScaleDetail'
|
import { getScaleBudgetFeeByRow } from '@/lib/pricingScaleDetail'
|
||||||
|
import { isInvestScaleSingleTotalService, resolveServicePricingCapabilities } from '@/lib/servicePricing'
|
||||||
import { useZxFwPricingStore, type ServicePricingMethod } from '@/pinia/zxFwPricing'
|
import { useZxFwPricingStore, type ServicePricingMethod } from '@/pinia/zxFwPricing'
|
||||||
import { useKvStore } from '@/pinia/kv'
|
import { useKvStore } from '@/pinia/kv'
|
||||||
|
|
||||||
@ -49,6 +50,8 @@ const getOnlyCostScaleSummaryAmount = (
|
|||||||
|
|
||||||
interface ScaleRow {
|
interface ScaleRow {
|
||||||
id: string
|
id: string
|
||||||
|
hasCost?: boolean
|
||||||
|
hasArea?: boolean
|
||||||
amount: number | null
|
amount: number | null
|
||||||
landArea: number | null
|
landArea: number | null
|
||||||
benchmarkBudgetBasicChecked: boolean
|
benchmarkBudgetBasicChecked: boolean
|
||||||
@ -85,6 +88,10 @@ interface MajorLite {
|
|||||||
|
|
||||||
interface ServiceLite {
|
interface ServiceLite {
|
||||||
defCoe: number | null
|
defCoe: number | null
|
||||||
|
enableInvestScale?: boolean | null
|
||||||
|
enableLandScale?: boolean | null
|
||||||
|
investScaleSingleTotal?: boolean | null
|
||||||
|
scale?: boolean | null
|
||||||
onlyCostScale?: boolean | null
|
onlyCostScale?: boolean | null
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -221,9 +228,14 @@ const getDefaultConsultCategoryFactor = (serviceId: string | number) => {
|
|||||||
return toFiniteNumberOrNull(service?.defCoe)
|
return toFiniteNumberOrNull(service?.defCoe)
|
||||||
}
|
}
|
||||||
|
|
||||||
const isOnlyCostScaleService = (serviceId: string | number) => {
|
const usesInvestScaleSingleTotal = (serviceId: string | number) => {
|
||||||
const service = (getServiceDictById() as Record<string, ServiceLite | undefined>)[String(serviceId)]
|
const service = (getServiceDictById() as Record<string, ServiceLite | undefined>)[String(serviceId)]
|
||||||
return service?.onlyCostScale === true
|
return isInvestScaleSingleTotalService(service)
|
||||||
|
}
|
||||||
|
|
||||||
|
const getScaleMethodCapabilities = (serviceId: string | number) => {
|
||||||
|
const service = (getServiceDictById() as Record<string, ServiceLite | undefined>)[String(serviceId)]
|
||||||
|
return resolveServicePricingCapabilities(service)
|
||||||
}
|
}
|
||||||
|
|
||||||
const majorById = new Map(getMajorDictEntries().map(({ id, item }) => [id, item as MajorLite]))
|
const majorById = new Map(getMajorDictEntries().map(({ id, item }) => [id, item as MajorLite]))
|
||||||
@ -326,6 +338,8 @@ const buildDefaultScaleRows = (
|
|||||||
consultCategoryFactorMap?.get(String(serviceId)) ?? getDefaultConsultCategoryFactor(serviceId)
|
consultCategoryFactorMap?.get(String(serviceId)) ?? getDefaultConsultCategoryFactor(serviceId)
|
||||||
return getMajorLeafIds().map(id => ({
|
return getMajorLeafIds().map(id => ({
|
||||||
id,
|
id,
|
||||||
|
hasCost: isCostMajorById(id),
|
||||||
|
hasArea: isAreaMajorById(id),
|
||||||
amount: null,
|
amount: null,
|
||||||
landArea: null,
|
landArea: null,
|
||||||
benchmarkBudgetBasicChecked: true,
|
benchmarkBudgetBasicChecked: true,
|
||||||
@ -333,7 +347,7 @@ const buildDefaultScaleRows = (
|
|||||||
consultCategoryFactor: defaultConsultCategoryFactor,
|
consultCategoryFactor: defaultConsultCategoryFactor,
|
||||||
majorFactor: majorFactorMap?.get(id) ?? getDefaultMajorFactorById(id),
|
majorFactor: majorFactorMap?.get(id) ?? getDefaultMajorFactorById(id),
|
||||||
workStageFactor: 1,
|
workStageFactor: 1,
|
||||||
workRatio: 100
|
workRatio: 1
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -366,6 +380,14 @@ const mergeScaleRows = (
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
...row,
|
...row,
|
||||||
|
hasCost:
|
||||||
|
typeof (fromDb as { hasCost?: unknown }).hasCost === 'boolean'
|
||||||
|
? Boolean((fromDb as { hasCost?: unknown }).hasCost)
|
||||||
|
: row.hasCost,
|
||||||
|
hasArea:
|
||||||
|
typeof (fromDb as { hasArea?: unknown }).hasArea === 'boolean'
|
||||||
|
? Boolean((fromDb as { hasArea?: unknown }).hasArea)
|
||||||
|
: row.hasArea,
|
||||||
amount: toFiniteNumberOrNull(fromDb.amount),
|
amount: toFiniteNumberOrNull(fromDb.amount),
|
||||||
landArea: toFiniteNumberOrNull(fromDb.landArea),
|
landArea: toFiniteNumberOrNull(fromDb.landArea),
|
||||||
benchmarkBudgetBasicChecked:
|
benchmarkBudgetBasicChecked:
|
||||||
@ -394,7 +416,7 @@ const mergeScaleRows = (
|
|||||||
|
|
||||||
const getInvestmentBudgetFee = (row: ScaleRow) => getScaleBudgetFeeByRow(row, 'cost')
|
const getInvestmentBudgetFee = (row: ScaleRow) => getScaleBudgetFeeByRow(row, 'cost')
|
||||||
|
|
||||||
const getOnlyCostScaleBudgetFee = (
|
const getInvestScaleSingleTotalBudgetFee = (
|
||||||
serviceId: string,
|
serviceId: string,
|
||||||
rowsFromDb: Array<Record<string, unknown>> | undefined,
|
rowsFromDb: Array<Record<string, unknown>> | undefined,
|
||||||
consultCategoryFactorMap?: Map<string, number | null>,
|
consultCategoryFactorMap?: Map<string, number | null>,
|
||||||
@ -412,7 +434,7 @@ const getOnlyCostScaleBudgetFee = (
|
|||||||
toFiniteNumberOrNull(industryMajorEntry?.item?.defCoe) ??
|
toFiniteNumberOrNull(industryMajorEntry?.item?.defCoe) ??
|
||||||
1
|
1
|
||||||
|
|
||||||
// 新版 onlyCostScale 支持“按项目行”存储(如 1::majorId、2::majorId),每行需独立计费后求和。
|
// 单行总投资模式支持“按项目行”存储(如 1::majorId、2::majorId),每行需独立计费后求和。
|
||||||
const usePerRowCalculation = sourceRows.some(row => {
|
const usePerRowCalculation = sourceRows.some(row => {
|
||||||
if (typeof row?.projectIndex === 'number' && Number.isFinite(row.projectIndex)) return true
|
if (typeof row?.projectIndex === 'number' && Number.isFinite(row.projectIndex)) return true
|
||||||
const id = String(row?.id || '')
|
const id = String(row?.id || '')
|
||||||
@ -429,7 +451,7 @@ const getOnlyCostScaleBudgetFee = (
|
|||||||
majorFactor: getRowNumberOrFallback(row, 'majorFactor', defaultMajorFactor),
|
majorFactor: getRowNumberOrFallback(row, 'majorFactor', defaultMajorFactor),
|
||||||
consultCategoryFactor: getRowNumberOrFallback(row, 'consultCategoryFactor', defaultConsultCategoryFactor),
|
consultCategoryFactor: getRowNumberOrFallback(row, 'consultCategoryFactor', defaultConsultCategoryFactor),
|
||||||
workStageFactor: getRowNumberOrFallback(row, 'workStageFactor', 1),
|
workStageFactor: getRowNumberOrFallback(row, 'workStageFactor', 1),
|
||||||
workRatio: getRowNumberOrFallback(row, 'workRatio', 100)
|
workRatio: getRowNumberOrFallback(row, 'workRatio', 1)
|
||||||
}, 'cost')
|
}, 'cost')
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -443,7 +465,7 @@ const getOnlyCostScaleBudgetFee = (
|
|||||||
const consultCategoryFactor = getRowNumberOrFallback(onlyRow, 'consultCategoryFactor', defaultConsultCategoryFactor)
|
const consultCategoryFactor = getRowNumberOrFallback(onlyRow, 'consultCategoryFactor', defaultConsultCategoryFactor)
|
||||||
const majorFactor = getRowNumberOrFallback(onlyRow, 'majorFactor', defaultMajorFactor)
|
const majorFactor = getRowNumberOrFallback(onlyRow, 'majorFactor', defaultMajorFactor)
|
||||||
const workStageFactor = getRowNumberOrFallback(onlyRow, 'workStageFactor', 1)
|
const workStageFactor = getRowNumberOrFallback(onlyRow, 'workStageFactor', 1)
|
||||||
const workRatio = getRowNumberOrFallback(onlyRow, 'workRatio', 100)
|
const workRatio = getRowNumberOrFallback(onlyRow, 'workRatio', 1)
|
||||||
return getScaleBudgetFeeByRow({
|
return getScaleBudgetFeeByRow({
|
||||||
amount: resolvedTotalAmount,
|
amount: resolvedTotalAmount,
|
||||||
benchmarkBudgetBasicChecked: typeof onlyRow?.benchmarkBudgetBasicChecked === 'boolean' ? onlyRow.benchmarkBudgetBasicChecked : true,
|
benchmarkBudgetBasicChecked: typeof onlyRow?.benchmarkBudgetBasicChecked === 'boolean' ? onlyRow.benchmarkBudgetBasicChecked : true,
|
||||||
@ -455,7 +477,7 @@ const getOnlyCostScaleBudgetFee = (
|
|||||||
}, 'cost')
|
}, 'cost')
|
||||||
}
|
}
|
||||||
|
|
||||||
const buildOnlyCostScaleDetailRows = (
|
const buildInvestScaleSingleTotalDetailRows = (
|
||||||
serviceId: string,
|
serviceId: string,
|
||||||
rowsFromDb: Array<Record<string, unknown>> | undefined,
|
rowsFromDb: Array<Record<string, unknown>> | undefined,
|
||||||
consultCategoryFactorMap?: Map<string, number | null>,
|
consultCategoryFactorMap?: Map<string, number | null>,
|
||||||
@ -484,11 +506,13 @@ const buildOnlyCostScaleDetailRows = (
|
|||||||
1
|
1
|
||||||
)
|
)
|
||||||
const workStageFactor = getRowNumberOrFallback(onlyRow, 'workStageFactor', 1)
|
const workStageFactor = getRowNumberOrFallback(onlyRow, 'workStageFactor', 1)
|
||||||
const workRatio = getRowNumberOrFallback(onlyRow, 'workRatio', 100)
|
const workRatio = getRowNumberOrFallback(onlyRow, 'workRatio', 1)
|
||||||
|
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
id: onlyCostRowId,
|
id: onlyCostRowId,
|
||||||
|
hasCost: true,
|
||||||
|
hasArea: false,
|
||||||
amount: resolvedTotalAmount,
|
amount: resolvedTotalAmount,
|
||||||
landArea: null,
|
landArea: null,
|
||||||
consultCategoryFactor,
|
consultCategoryFactor,
|
||||||
@ -656,6 +680,8 @@ const normalizeScopedScaleRows = (
|
|||||||
const hasWorkRatio = hasOwn(row, 'workRatio')
|
const hasWorkRatio = hasOwn(row, 'workRatio')
|
||||||
return {
|
return {
|
||||||
id: resolvedMajorId,
|
id: resolvedMajorId,
|
||||||
|
hasCost: isCostMajorById(resolvedMajorId),
|
||||||
|
hasArea: isAreaMajorById(resolvedMajorId),
|
||||||
amount: toFiniteNumberOrNull(row.amount),
|
amount: toFiniteNumberOrNull(row.amount),
|
||||||
landArea: toFiniteNumberOrNull(row.landArea),
|
landArea: toFiniteNumberOrNull(row.landArea),
|
||||||
benchmarkBudgetBasicChecked:
|
benchmarkBudgetBasicChecked:
|
||||||
@ -673,7 +699,7 @@ const normalizeScopedScaleRows = (
|
|||||||
(hasWorkStageFactor ? null : 1),
|
(hasWorkStageFactor ? null : 1),
|
||||||
workRatio:
|
workRatio:
|
||||||
toFiniteNumberOrNull(row.workRatio) ??
|
toFiniteNumberOrNull(row.workRatio) ??
|
||||||
(hasWorkRatio ? null : 100)
|
(hasWorkRatio ? null : 1)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -731,7 +757,8 @@ const buildDefaultPricingMethodDetailRows = (
|
|||||||
serviceId: string,
|
serviceId: string,
|
||||||
context: PricingMethodDefaultBuildContext
|
context: PricingMethodDefaultBuildContext
|
||||||
): PricingMethodDefaultDetailRows => {
|
): PricingMethodDefaultDetailRows => {
|
||||||
const onlyCostScale = isOnlyCostScaleService(serviceId)
|
const capabilities = getScaleMethodCapabilities(serviceId)
|
||||||
|
const investScaleSingleTotal = usesInvestScaleSingleTotal(serviceId)
|
||||||
const scaleRows = resolveScaleRows(
|
const scaleRows = resolveScaleRows(
|
||||||
serviceId,
|
serviceId,
|
||||||
null,
|
null,
|
||||||
@ -740,21 +767,25 @@ const buildDefaultPricingMethodDetailRows = (
|
|||||||
context.majorFactorMap
|
context.majorFactorMap
|
||||||
)
|
)
|
||||||
|
|
||||||
const investScale = onlyCostScale
|
const investScale = capabilities.investScaleEnabled
|
||||||
? buildOnlyCostScaleDetailRows(
|
? (investScaleSingleTotal
|
||||||
serviceId,
|
? buildInvestScaleSingleTotalDetailRows(
|
||||||
context.htData?.detailRows as Array<Record<string, unknown>> | undefined,
|
serviceId,
|
||||||
context.consultCategoryFactorMap,
|
context.htData?.detailRows as Array<Record<string, unknown>> | undefined,
|
||||||
context.majorFactorMap,
|
context.consultCategoryFactorMap,
|
||||||
context.industryId,
|
context.majorFactorMap,
|
||||||
context.htData?.totalAmount ?? null
|
context.industryId,
|
||||||
)
|
context.htData?.totalAmount ?? null
|
||||||
: scaleRows.filter(row => {
|
)
|
||||||
if (!isCostMajorById(row.id)) return false
|
: scaleRows.filter(row => {
|
||||||
if (context.excludeInvestmentCostAndAreaRows && isDualScaleMajorById(row.id)) return false
|
if (!isCostMajorById(row.id)) return false
|
||||||
return true
|
if (context.excludeInvestmentCostAndAreaRows && isDualScaleMajorById(row.id)) return false
|
||||||
})
|
return true
|
||||||
const landScale = scaleRows.filter(row => isAreaMajorById(row.id))
|
}))
|
||||||
|
: []
|
||||||
|
const landScale = capabilities.landScaleEnabled
|
||||||
|
? scaleRows.filter(row => isAreaMajorById(row.id))
|
||||||
|
: []
|
||||||
|
|
||||||
return {
|
return {
|
||||||
investScale,
|
investScale,
|
||||||
@ -838,7 +869,7 @@ export const getPricingMethodTotalsForService = async (params: {
|
|||||||
|
|
||||||
const consultCategoryFactorMap = buildConsultCategoryFactorMap(consultFactorData)
|
const consultCategoryFactorMap = buildConsultCategoryFactorMap(consultFactorData)
|
||||||
const majorFactorMap = buildMajorFactorMap(majorFactorData)
|
const majorFactorMap = buildMajorFactorMap(majorFactorData)
|
||||||
const onlyCostScale = isOnlyCostScaleService(serviceId)
|
const investScaleSingleTotal = usesInvestScaleSingleTotal(serviceId)
|
||||||
const industryId = typeof baseInfo?.projectIndustry === 'string' ? baseInfo.projectIndustry.trim() : ''
|
const industryId = typeof baseInfo?.projectIndustry === 'string' ? baseInfo.projectIndustry.trim() : ''
|
||||||
|
|
||||||
// 优先使用对应计费页的数据;不存在时回退合同段规模信息,再回退默认字典行。
|
// 优先使用对应计费页的数据;不存在时回退合同段规模信息,再回退默认字典行。
|
||||||
@ -851,8 +882,8 @@ export const getPricingMethodTotalsForService = async (params: {
|
|||||||
const scopedLandRows = hasScopedScaleRows(landScaleRowsSource)
|
const scopedLandRows = hasScopedScaleRows(landScaleRowsSource)
|
||||||
? normalizeScopedScaleRows(serviceId, landScaleRowsSource, consultCategoryFactorMap, majorFactorMap)
|
? normalizeScopedScaleRows(serviceId, landScaleRowsSource, consultCategoryFactorMap, majorFactorMap)
|
||||||
: null
|
: null
|
||||||
const investScale = onlyCostScale
|
const investScale = investScaleSingleTotal
|
||||||
? getOnlyCostScaleBudgetFee(
|
? getInvestScaleSingleTotalBudgetFee(
|
||||||
serviceId,
|
serviceId,
|
||||||
(investData?.detailRows as Array<Record<string, unknown>> | undefined) ||
|
(investData?.detailRows as Array<Record<string, unknown>> | undefined) ||
|
||||||
(htData?.detailRows as Array<Record<string, unknown>> | undefined),
|
(htData?.detailRows as Array<Record<string, unknown>> | undefined),
|
||||||
@ -943,16 +974,49 @@ export const ensurePricingMethodDetailRowsForServices = async (params: {
|
|||||||
const workloadData = toStoredDetailRowsState(storeWorkloadData) || workloadDataFallback
|
const workloadData = toStoredDetailRowsState(storeWorkloadData) || workloadDataFallback
|
||||||
const hourlyData = toStoredDetailRowsState(storeHourlyData) || hourlyDataFallback
|
const hourlyData = toStoredDetailRowsState(storeHourlyData) || hourlyDataFallback
|
||||||
|
|
||||||
const shouldInitInvest = !Array.isArray(investData?.detailRows) || investData!.detailRows!.length === 0
|
const shouldInitInvest = !Array.isArray(investData?.detailRows)
|
||||||
const shouldInitLand = !Array.isArray(landData?.detailRows) || landData!.detailRows!.length === 0
|
const shouldInitLand = !Array.isArray(landData?.detailRows)
|
||||||
const shouldInitWorkload = !Array.isArray(workloadData?.detailRows) || workloadData!.detailRows!.length === 0
|
const shouldInitWorkload = !Array.isArray(workloadData?.detailRows)
|
||||||
const shouldInitHourly = !Array.isArray(hourlyData?.detailRows) || hourlyData!.detailRows!.length === 0
|
const shouldInitHourly = !Array.isArray(hourlyData?.detailRows)
|
||||||
|
|
||||||
|
console.log('[pricing][ensure-detail-rows][before] ' + JSON.stringify({
|
||||||
|
contractId: params.contractId,
|
||||||
|
serviceId,
|
||||||
|
shouldInit: {
|
||||||
|
invest: shouldInitInvest,
|
||||||
|
land: shouldInitLand,
|
||||||
|
workload: shouldInitWorkload,
|
||||||
|
hourly: shouldInitHourly
|
||||||
|
},
|
||||||
|
existingLengths: {
|
||||||
|
invest: Array.isArray(investData?.detailRows) ? investData.detailRows.length : -1,
|
||||||
|
land: Array.isArray(landData?.detailRows) ? landData.detailRows.length : -1,
|
||||||
|
workload: Array.isArray(workloadData?.detailRows) ? workloadData.detailRows.length : -1,
|
||||||
|
hourly: Array.isArray(hourlyData?.detailRows) ? hourlyData.detailRows.length : -1
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
const writeTasks: Promise<unknown>[] = []
|
const writeTasks: Promise<unknown>[] = []
|
||||||
let defaultRows: PricingMethodDefaultDetailRows | null = null
|
let defaultRows: PricingMethodDefaultDetailRows | null = null
|
||||||
const getDefaultRows = () => {
|
const getDefaultRows = () => {
|
||||||
if (!defaultRows) {
|
if (!defaultRows) {
|
||||||
defaultRows = buildDefaultPricingMethodDetailRows(serviceId, context)
|
defaultRows = buildDefaultPricingMethodDetailRows(serviceId, context)
|
||||||
|
console.log('[pricing][ensure-detail-rows][defaults] ' + JSON.stringify({
|
||||||
|
contractId: params.contractId,
|
||||||
|
serviceId,
|
||||||
|
lengths: {
|
||||||
|
invest: Array.isArray(defaultRows.investScale) ? defaultRows.investScale.length : -1,
|
||||||
|
land: Array.isArray(defaultRows.landScale) ? defaultRows.landScale.length : -1,
|
||||||
|
workload: Array.isArray(defaultRows.workload) ? defaultRows.workload.length : -1,
|
||||||
|
hourly: Array.isArray(defaultRows.hourly) ? defaultRows.hourly.length : -1
|
||||||
|
},
|
||||||
|
sample: {
|
||||||
|
invest: Array.isArray(defaultRows.investScale) ? defaultRows.investScale[0] : null,
|
||||||
|
land: Array.isArray(defaultRows.landScale) ? defaultRows.landScale[0] : null,
|
||||||
|
workload: Array.isArray(defaultRows.workload) ? defaultRows.workload[0] : null,
|
||||||
|
hourly: Array.isArray(defaultRows.hourly) ? defaultRows.hourly[0] : null
|
||||||
|
}
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
return defaultRows
|
return defaultRows
|
||||||
}
|
}
|
||||||
@ -996,6 +1060,13 @@ export const ensurePricingMethodDetailRowsForServices = async (params: {
|
|||||||
if (writeTasks.length > 0) {
|
if (writeTasks.length > 0) {
|
||||||
await Promise.all(writeTasks)
|
await Promise.all(writeTasks)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log('[pricing][ensure-detail-rows][after] ' + JSON.stringify({
|
||||||
|
contractId: params.contractId,
|
||||||
|
serviceId,
|
||||||
|
wroteAny: writeTasks.length > 0,
|
||||||
|
writeCount: writeTasks.length
|
||||||
|
}))
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -1019,3 +1090,4 @@ export const getPricingMethodTotalsForServices = async (params: {
|
|||||||
)
|
)
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -8,6 +8,7 @@
|
|||||||
import { getMajorDictEntries, getMajorIdAliasMap, getServiceDictById } from '@/sql'
|
import { getMajorDictEntries, getMajorIdAliasMap, getServiceDictById } from '@/sql'
|
||||||
import { toFiniteNumberOrNull } from '@/lib/decimal'
|
import { toFiniteNumberOrNull } from '@/lib/decimal'
|
||||||
import { getBenchmarkBudgetByScale, getScaleBudgetFee } from '@/lib/pricingScaleFee'
|
import { getBenchmarkBudgetByScale, getScaleBudgetFee } from '@/lib/pricingScaleFee'
|
||||||
|
import { isInvestScaleSingleTotalService } from '@/lib/servicePricing'
|
||||||
import type {
|
import type {
|
||||||
ScaleCalcRow,
|
ScaleCalcRow,
|
||||||
ScaleType,
|
ScaleType,
|
||||||
@ -80,9 +81,9 @@ export const getDefaultConsultCategoryFactor = (serviceId: string | number): num
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** 判断是否为仅投资规模服务 */
|
/** 判断是否为仅投资规模服务 */
|
||||||
export const isOnlyCostScaleService = (serviceId: string | number): boolean => {
|
export const isInvestScaleSingleTotalByService = (serviceId: string | number): boolean => {
|
||||||
const service = (getServiceDictById() as Record<string, ServiceLite | undefined>)[String(serviceId)]
|
const service = (getServiceDictById() as Record<string, ServiceLite | undefined>)[String(serviceId)]
|
||||||
return service?.onlyCostScale === true
|
return isInvestScaleSingleTotalService(service)
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ----------------------------------------------------------------
|
/* ----------------------------------------------------------------
|
||||||
@ -112,7 +113,7 @@ export const buildDefaultScaleRows = (
|
|||||||
consultCategoryFactor: defaultFactor,
|
consultCategoryFactor: defaultFactor,
|
||||||
majorFactor: majorFactorMap?.get(id) ?? getDefaultMajorFactor(id),
|
majorFactor: majorFactorMap?.get(id) ?? getDefaultMajorFactor(id),
|
||||||
workStageFactor: 1,
|
workStageFactor: 1,
|
||||||
workRatio: 100
|
workRatio: 1
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -216,3 +217,4 @@ export const sumNullableBy = <T>(list: T[], pick: (item: T) => number | null | u
|
|||||||
}
|
}
|
||||||
return hasValid ? total : null
|
return hasValid ? total : null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import {
|
|||||||
formatScaleReadonlyMoney,
|
formatScaleReadonlyMoney,
|
||||||
getScaleMergeColSpanBeforeTotal
|
getScaleMergeColSpanBeforeTotal
|
||||||
} from '@/lib/pricingScaleGrid'
|
} from '@/lib/pricingScaleGrid'
|
||||||
|
import { AgGridResetHeader } from '@/lib/agGridResetHeader'
|
||||||
import { i18n } from '@/i18n'
|
import { i18n } from '@/i18n'
|
||||||
|
|
||||||
type ScaleColumnField<TRow> = Extract<keyof TRow, string> | string
|
type ScaleColumnField<TRow> = Extract<keyof TRow, string> | string
|
||||||
@ -199,6 +200,8 @@ export const createScaleBudgetFeeColumnGroup = <TRow>(options: {
|
|||||||
headerName: scaleT('columns.workRatio'),
|
headerName: scaleT('columns.workRatio'),
|
||||||
field: 'workRatio' as any,
|
field: 'workRatio' as any,
|
||||||
colId: 'workRatio',
|
colId: 'workRatio',
|
||||||
|
headerTooltip: scaleT('tooltip.workRatio'),
|
||||||
|
headerComponent: AgGridResetHeader,
|
||||||
headerClass: 'ag-right-aligned-header',
|
headerClass: 'ag-right-aligned-header',
|
||||||
minWidth: 80,
|
minWidth: 80,
|
||||||
flex: 1,
|
flex: 1,
|
||||||
@ -258,11 +261,14 @@ export const createScaleAutoGroupColumn = <TRow>(options: {
|
|||||||
idLabelMap: Map<string, string>
|
idLabelMap: Map<string, string>
|
||||||
parseProjectIndexFromPathKey: (key: string) => number | null
|
parseProjectIndexFromPathKey: (key: string) => number | null
|
||||||
}) : ColDef<TRow> => ({
|
}) : ColDef<TRow> => ({
|
||||||
headerName: scaleT('columns.number'),
|
headerName: scaleT('columns.majorGroup'),
|
||||||
minWidth: 250,
|
minWidth: 250,
|
||||||
flex: 2,
|
flex: 2,
|
||||||
wrapText: true,
|
wrapText: true,
|
||||||
autoHeight: true,
|
autoHeight: true,
|
||||||
|
cellClassRules: {
|
||||||
|
'ag-summary-label-cell': params => Boolean(params.node?.rowPinned)
|
||||||
|
},
|
||||||
cellStyle: {
|
cellStyle: {
|
||||||
whiteSpace: 'normal',
|
whiteSpace: 'normal',
|
||||||
lineHeight: '1.4'
|
lineHeight: '1.4'
|
||||||
@ -276,8 +282,8 @@ export const createScaleAutoGroupColumn = <TRow>(options: {
|
|||||||
return options.totalLabel
|
return options.totalLabel
|
||||||
}
|
}
|
||||||
const rowData = params.data as any
|
const rowData = params.data as any
|
||||||
if (!params.node?.group && rowData?.majorCode) {
|
if (!params.node?.group && rowData?.majorCode && rowData?.majorName) {
|
||||||
return rowData.majorCode
|
return `${rowData.majorCode} ${rowData.majorName}`
|
||||||
}
|
}
|
||||||
const nodeId = String(params.value || '')
|
const nodeId = String(params.value || '')
|
||||||
const projectIndex = options.parseProjectIndexFromPathKey(nodeId)
|
const projectIndex = options.parseProjectIndexFromPathKey(nodeId)
|
||||||
@ -287,8 +293,8 @@ export const createScaleAutoGroupColumn = <TRow>(options: {
|
|||||||
tooltipValueGetter: params => {
|
tooltipValueGetter: params => {
|
||||||
if (params.node?.rowPinned) return options.totalLabel
|
if (params.node?.rowPinned) return options.totalLabel
|
||||||
const rowData = params.data as any
|
const rowData = params.data as any
|
||||||
if (!params.node?.group && rowData?.majorCode) {
|
if (!params.node?.group && rowData?.majorCode && rowData?.majorName) {
|
||||||
return rowData.majorCode
|
return `${rowData.majorCode} ${rowData.majorName}`
|
||||||
}
|
}
|
||||||
const nodeId = String(params.value || '')
|
const nodeId = String(params.value || '')
|
||||||
const projectIndex = options.parseProjectIndexFromPathKey(nodeId)
|
const projectIndex = options.parseProjectIndexFromPathKey(nodeId)
|
||||||
@ -296,92 +302,3 @@ export const createScaleAutoGroupColumn = <TRow>(options: {
|
|||||||
return options.idLabelMap.get(nodeId) || nodeId
|
return options.idLabelMap.get(nodeId) || nodeId
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
export const createScaleCalculationFormulaColumnGroup = <TRow>(options: {
|
|
||||||
getCalculationBase?: (row: TRow | undefined) => string | null
|
|
||||||
getCalculationBaseValue?: (row: TRow | undefined) => number | null
|
|
||||||
getCalculationFormula?: (row: TRow | undefined) => string | null
|
|
||||||
getCalculationAmount?: (row: TRow | undefined) => number | null
|
|
||||||
isBaseEditable?: (row: TRow | undefined) => boolean
|
|
||||||
parseNumberOrNull: (value: any, options?: any) => any
|
|
||||||
}) : ColGroupDef<TRow> => ({
|
|
||||||
headerName: scaleT('columns.calculationGroup'),
|
|
||||||
marryChildren: true,
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
headerName: scaleT('columns.base'),
|
|
||||||
field: 'calculationBase' as any,
|
|
||||||
colId: 'calculationBase',
|
|
||||||
minWidth: 120,
|
|
||||||
flex: 1,
|
|
||||||
editable: params => !params.node?.group && !params.node?.rowPinned && (options.isBaseEditable?.(params.data) ?? true),
|
|
||||||
cellClass: params =>
|
|
||||||
!params.node?.group && !params.node?.rowPinned && (options.isBaseEditable?.(params.data) ?? true)
|
|
||||||
? 'editable-cell-line'
|
|
||||||
: '',
|
|
||||||
cellClassRules: {
|
|
||||||
'ag-right-aligned-cell': () => true,
|
|
||||||
'editable-cell-empty': params =>
|
|
||||||
!params.node?.group && !params.node?.rowPinned && (options.isBaseEditable?.(params.data) ?? true) && (params.value == null || params.value === '')
|
|
||||||
},
|
|
||||||
valueGetter: params => (params.node?.rowPinned ? null : options.getCalculationBase?.(params.data) ?? null),
|
|
||||||
valueParser: params => options.parseNumberOrNull(params.newValue, { precision: 3 }),
|
|
||||||
valueFormatter: params => {
|
|
||||||
if (params.node?.rowPinned) return ''
|
|
||||||
const value = params.value
|
|
||||||
return value == null || value === '' ? '' : String(value)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
headerName: scaleT('columns.base2'),
|
|
||||||
field: 'calculationBaseValue' as any,
|
|
||||||
colId: 'calculationBaseValue',
|
|
||||||
headerClass: 'ag-right-aligned-header',
|
|
||||||
minWidth: 130,
|
|
||||||
flex: 1,
|
|
||||||
cellClassRules: {
|
|
||||||
'ag-right-aligned-cell': () => true
|
|
||||||
},
|
|
||||||
valueGetter: params => (params.node?.rowPinned ? null : options.getCalculationBaseValue?.(params.data) ?? null),
|
|
||||||
valueFormatter: params => {
|
|
||||||
if (params.node?.rowPinned) return ''
|
|
||||||
const value = params.value
|
|
||||||
if (value == null) return ''
|
|
||||||
return typeof value === 'number' ? value.toLocaleString() : String(value)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
headerName: scaleT('columns.formula'),
|
|
||||||
field: 'calculationFormula' as any,
|
|
||||||
colId: 'calculationFormula',
|
|
||||||
minWidth: 200,
|
|
||||||
flex: 1.5,
|
|
||||||
wrapText: true,
|
|
||||||
autoHeight: true,
|
|
||||||
cellStyle: { whiteSpace: 'normal', lineHeight: '1.4' },
|
|
||||||
valueGetter: params => (params.node?.rowPinned ? null : options.getCalculationFormula?.(params.data) ?? null),
|
|
||||||
valueFormatter: params => {
|
|
||||||
if (params.node?.rowPinned) return ''
|
|
||||||
return params.value || ''
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
headerName: scaleT('columns.calculationAmount'),
|
|
||||||
field: 'calculationAmount' as any,
|
|
||||||
colId: 'calculationAmount',
|
|
||||||
headerClass: 'ag-right-aligned-header',
|
|
||||||
minWidth: 120,
|
|
||||||
flex: 1,
|
|
||||||
cellClassRules: {
|
|
||||||
'ag-right-aligned-cell': () => true
|
|
||||||
},
|
|
||||||
valueGetter: params => (params.node?.rowPinned ? null : options.getCalculationAmount?.(params.data) ?? null),
|
|
||||||
valueFormatter: params => {
|
|
||||||
if (params.node?.rowPinned) return ''
|
|
||||||
const value = params.value
|
|
||||||
if (value == null) return ''
|
|
||||||
return typeof value === 'number' ? value.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 }) : String(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
})
|
|
||||||
|
|||||||
@ -54,7 +54,7 @@ export const getBenchmarkBudgetByScale = (value: unknown, mode: ScaleMode) => {
|
|||||||
*
|
*
|
||||||
* 这个方法负责“乘系数”这一层:
|
* 这个方法负责“乘系数”这一层:
|
||||||
* 1. 输入的是已经拆好的基准预算 basic / optional
|
* 1. 输入的是已经拆好的基准预算 basic / optional
|
||||||
* 2. 再乘咨询分类系数、专业系数、阶段系数、工作占比
|
* 2. 再乘咨询分类系数、专业系数、阶段系数、服务预算构成比率与数量比
|
||||||
* 3. 返回服务费的基本部分 / 附加部分 / 合计
|
* 3. 返回服务费的基本部分 / 附加部分 / 合计
|
||||||
*
|
*
|
||||||
* 和 `getBenchmarkBudgetSplitByScale` 的区别是:
|
* 和 `getBenchmarkBudgetSplitByScale` 的区别是:
|
||||||
@ -78,7 +78,7 @@ export const getScaleBudgetFeeSplit = (params: {
|
|||||||
const majorFactor = toFiniteNumberOrNull(params.majorFactor)
|
const majorFactor = toFiniteNumberOrNull(params.majorFactor)
|
||||||
const consultCategoryFactor = toFiniteNumberOrNull(params.consultCategoryFactor)
|
const consultCategoryFactor = toFiniteNumberOrNull(params.consultCategoryFactor)
|
||||||
const workStageFactor = hasWorkStageFactor ? toFiniteNumberOrNull(params.workStageFactor) : 1
|
const workStageFactor = hasWorkStageFactor ? toFiniteNumberOrNull(params.workStageFactor) : 1
|
||||||
const workRatio = hasWorkRatio ? toFiniteNumberOrNull(params.workRatio) : 100
|
const workRatio = hasWorkRatio ? toFiniteNumberOrNull(params.workRatio) : 1
|
||||||
|
|
||||||
if (
|
if (
|
||||||
benchmarkBudgetBasic == null ||
|
benchmarkBudgetBasic == null ||
|
||||||
@ -95,7 +95,6 @@ export const getScaleBudgetFeeSplit = (params: {
|
|||||||
.mul(majorFactor)
|
.mul(majorFactor)
|
||||||
.mul(workStageFactor)
|
.mul(workStageFactor)
|
||||||
.mul(workRatio)
|
.mul(workRatio)
|
||||||
.div(100)
|
|
||||||
const roundedBenchmarkBudget = roundTo(addNumbers(benchmarkBudgetBasic, benchmarkBudgetOptional), 2)
|
const roundedBenchmarkBudget = roundTo(addNumbers(benchmarkBudgetBasic, benchmarkBudgetOptional), 2)
|
||||||
const basic = roundTo(toDecimal(benchmarkBudgetBasic).mul(multiplier), 2)
|
const basic = roundTo(toDecimal(benchmarkBudgetBasic).mul(multiplier), 2)
|
||||||
const optional = roundTo(toDecimal(benchmarkBudgetOptional).mul(multiplier), 2)
|
const optional = roundTo(toDecimal(benchmarkBudgetOptional).mul(multiplier), 2)
|
||||||
@ -121,7 +120,7 @@ export const getScaleBudgetFee = (params: {
|
|||||||
const majorFactor = toFiniteNumberOrNull(params.majorFactor)
|
const majorFactor = toFiniteNumberOrNull(params.majorFactor)
|
||||||
const consultCategoryFactor = toFiniteNumberOrNull(params.consultCategoryFactor)
|
const consultCategoryFactor = toFiniteNumberOrNull(params.consultCategoryFactor)
|
||||||
const workStageFactor = hasWorkStageFactor ? toFiniteNumberOrNull(params.workStageFactor) : 1
|
const workStageFactor = hasWorkStageFactor ? toFiniteNumberOrNull(params.workStageFactor) : 1
|
||||||
const workRatio = hasWorkRatio ? toFiniteNumberOrNull(params.workRatio) : 100
|
const workRatio = hasWorkRatio ? toFiniteNumberOrNull(params.workRatio) : 1
|
||||||
|
|
||||||
if (
|
if (
|
||||||
benchmarkBudget == null ||
|
benchmarkBudget == null ||
|
||||||
@ -138,6 +137,5 @@ export const getScaleBudgetFee = (params: {
|
|||||||
.mul(majorFactor)
|
.mul(majorFactor)
|
||||||
.mul(workStageFactor)
|
.mul(workStageFactor)
|
||||||
.mul(workRatio)
|
.mul(workRatio)
|
||||||
.div(100)
|
|
||||||
return roundTo(toDecimal(roundedBenchmarkBudget).mul(multiplier), 2)
|
return roundTo(toDecimal(roundedBenchmarkBudget).mul(multiplier), 2)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -294,10 +294,10 @@ export const initializeProjectFactorStates = async (
|
|||||||
detailRows: buildFactorRowsFromEntries(majorEntries)
|
detailRows: buildFactorRowsFromEntries(majorEntries)
|
||||||
}
|
}
|
||||||
|
|
||||||
await Promise.all([
|
// 新项目初始化走 createProjectKvAdapter 时,setItem 是整包读改写,不是原子更新。
|
||||||
kvStore.setItem(consultCategoryFactorKey, consultPayload),
|
// 这里并发写两个 key 会互相覆盖,导致咨询系数或专业系数其中一个丢失。
|
||||||
kvStore.setItem(majorFactorKey, majorPayload)
|
await kvStore.setItem(consultCategoryFactorKey, consultPayload)
|
||||||
])
|
await kvStore.setItem(majorFactorKey, majorPayload)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const initializeProjectScaleState = async (
|
export const initializeProjectScaleState = async (
|
||||||
@ -307,3 +307,4 @@ export const initializeProjectScaleState = async (
|
|||||||
) => {
|
) => {
|
||||||
await kvStore.setItem(projectScaleKey, buildDefaultProjectScaleState(industry))
|
await kvStore.setItem(projectScaleKey, buildDefaultProjectScaleState(industry))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { serviceList } from '@/sql'
|
import { getMajorDictById, getMajorIdAliasMap, serviceList } from '@/sql'
|
||||||
import { roundTo, toFiniteNumber, toFiniteNumberOrZero } from '@/lib/decimal'
|
import { roundTo, toFiniteNumber, toFiniteNumberOrZero } from '@/lib/decimal'
|
||||||
import { getBenchmarkBudgetSplitByScale, getScaleBudgetFeeSplit } from '@/lib/pricingScaleFee'
|
import { getBenchmarkBudgetSplitByScale, getScaleBudgetFeeSplit } from '@/lib/pricingScaleFee'
|
||||||
export { toFiniteNumber, toFiniteNumberOrZero }
|
export { toFiniteNumber, toFiniteNumberOrZero }
|
||||||
@ -52,6 +52,7 @@ interface ScaleRowLike {
|
|||||||
|
|
||||||
interface WorkloadMethodRowLike {
|
interface WorkloadMethodRowLike {
|
||||||
id: string
|
id: string
|
||||||
|
conversion?: unknown
|
||||||
budgetAdoptedUnitPrice?: unknown
|
budgetAdoptedUnitPrice?: unknown
|
||||||
workload?: unknown
|
workload?: unknown
|
||||||
basicFee?: unknown
|
basicFee?: unknown
|
||||||
@ -246,6 +247,18 @@ export const toScaleMajorId = (row: ScaleMethodRowLike): number | null => {
|
|||||||
return toSafeInteger(parsed.majorPart)
|
return toSafeInteger(parsed.majorPart)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const majorDictById = getMajorDictById() as Record<string, { hasCost?: unknown; hasArea?: unknown } | undefined>
|
||||||
|
const majorIdAliasMap = getMajorIdAliasMap()
|
||||||
|
|
||||||
|
const resolveMajorCapability = (majorId: number | null) => {
|
||||||
|
if (majorId == null) return null
|
||||||
|
const key = String(majorId)
|
||||||
|
const resolvedKey = Object.prototype.hasOwnProperty.call(majorDictById, key)
|
||||||
|
? key
|
||||||
|
: (majorIdAliasMap.get(key) || key)
|
||||||
|
return majorDictById[resolvedKey] || null
|
||||||
|
}
|
||||||
|
|
||||||
export const toScaleProNum = (row: ScaleMethodRowLike): number => {
|
export const toScaleProNum = (row: ScaleMethodRowLike): number => {
|
||||||
const parsed = parseScaleScopedRowId(row.id)
|
const parsed = parseScaleScopedRowId(row.id)
|
||||||
return parsed.proNum > 0 ? parsed.proNum : 1
|
return parsed.proNum > 0 ? parsed.proNum : 1
|
||||||
@ -276,7 +289,16 @@ const isExportableScaleMethodRow = (
|
|||||||
mode: 'cost' | 'area'
|
mode: 'cost' | 'area'
|
||||||
) => {
|
) => {
|
||||||
if (!isScaleLeafRow(row)) return false
|
if (!isScaleLeafRow(row)) return false
|
||||||
return mode === 'cost' ? row?.hasCost === true : row?.hasArea === true
|
if (mode === 'cost') {
|
||||||
|
if (row?.hasCost === true) return true
|
||||||
|
if (row?.hasCost === false) return false
|
||||||
|
} else {
|
||||||
|
if (row?.hasArea === true) return true
|
||||||
|
if (row?.hasArea === false) return false
|
||||||
|
}
|
||||||
|
const major = row ? resolveMajorCapability(toScaleMajorId(row)) : null
|
||||||
|
if (!major) return false
|
||||||
|
return mode === 'cost' ? major.hasCost !== false : major.hasArea !== false
|
||||||
}
|
}
|
||||||
|
|
||||||
export const normalizeTaskText = (value: unknown): string => String(value || '').trim()
|
export const normalizeTaskText = (value: unknown): string => String(value || '').trim()
|
||||||
@ -302,9 +324,14 @@ export const resolveScaleMethodFee = (row: ScaleMethodRowLike, mode: 'cost' | 'a
|
|||||||
workRatio: row.workRatio
|
workRatio: row.workRatio
|
||||||
})
|
})
|
||||||
: null
|
: null
|
||||||
const basicFee = allUnchecked ? null : (toFiniteNumber(row.budgetFee) ?? computedSplit?.total ?? null)
|
const basicFee = allUnchecked
|
||||||
const basicFeeBasic = allUnchecked ? null : (toFiniteNumber(row.budgetFeeBasic) ?? computedSplit?.basic ?? null)
|
? null
|
||||||
const basicFeeOptional = allUnchecked ? null : (toFiniteNumber(row.budgetFeeOptional) ?? computedSplit?.optional ?? null)
|
: (benchmarkBudgetBasic != null && benchmarkBudgetOptional != null
|
||||||
|
? roundTo(benchmarkBudgetBasic + benchmarkBudgetOptional, 2)
|
||||||
|
: null)
|
||||||
|
const basicFeeBasic = allUnchecked ? null : benchmarkBudgetBasic
|
||||||
|
const basicFeeOptional = allUnchecked ? null : benchmarkBudgetOptional
|
||||||
|
const serviceFee = allUnchecked ? null : (toFiniteNumber(row.budgetFee) ?? computedSplit?.total ?? null)
|
||||||
const basicFormula = typeof row.basicFormula === 'string' && row.basicFormula.trim()
|
const basicFormula = typeof row.basicFormula === 'string' && row.basicFormula.trim()
|
||||||
? row.basicFormula
|
? row.basicFormula
|
||||||
: (basicChecked ? (benchmarkSplit?.basicFormula ?? '') : '')
|
: (basicChecked ? (benchmarkSplit?.basicFormula ?? '') : '')
|
||||||
@ -315,6 +342,7 @@ export const resolveScaleMethodFee = (row: ScaleMethodRowLike, mode: 'cost' | 'a
|
|||||||
basicFee,
|
basicFee,
|
||||||
basicFeeBasic,
|
basicFeeBasic,
|
||||||
basicFeeOptional,
|
basicFeeOptional,
|
||||||
|
serviceFee,
|
||||||
basicFormula,
|
basicFormula,
|
||||||
optionalFormula
|
optionalFormula
|
||||||
}
|
}
|
||||||
@ -440,11 +468,12 @@ export const buildMethod1 = (rows: ScaleMethodRowLike[] | undefined) => {
|
|||||||
const cost = toFiniteNumber(row.amount)
|
const cost = toFiniteNumber(row.amount)
|
||||||
const feeResolved = resolveScaleMethodFee(row, 'cost')
|
const feeResolved = resolveScaleMethodFee(row, 'cost')
|
||||||
const basicFee = feeResolved.basicFee
|
const basicFee = feeResolved.basicFee
|
||||||
if (basicFee != null) hasTotalValue = true
|
const serviceFee = feeResolved.serviceFee
|
||||||
|
if (serviceFee != null) hasTotalValue = true
|
||||||
const basicFeeBasic = feeResolved.basicFeeBasic
|
const basicFeeBasic = feeResolved.basicFeeBasic
|
||||||
const basicFeeOptional = feeResolved.basicFeeOptional
|
const basicFeeOptional = feeResolved.basicFeeOptional
|
||||||
const remark = typeof row.remark === 'string' ? row.remark : ''
|
const remark = typeof row.remark === 'string' ? row.remark : ''
|
||||||
if (basicFee == null) return null
|
if (basicFee == null && serviceFee == null) return null
|
||||||
return {
|
return {
|
||||||
proNum,
|
proNum,
|
||||||
major,
|
major,
|
||||||
@ -458,7 +487,7 @@ export const buildMethod1 = (rows: ScaleMethodRowLike[] | undefined) => {
|
|||||||
majorCoe: toFiniteNumberOrZero(row.majorFactor),
|
majorCoe: toFiniteNumberOrZero(row.majorFactor),
|
||||||
processCoe: toFiniteNumber(row.workStageFactor) ?? 1,
|
processCoe: toFiniteNumber(row.workStageFactor) ?? 1,
|
||||||
proportion: toFiniteNumber(row.workRatio) ?? 1,
|
proportion: toFiniteNumber(row.workRatio) ?? 1,
|
||||||
fee: toMoney(basicFee),
|
fee: toMoney(serviceFee),
|
||||||
remark
|
remark
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -491,11 +520,12 @@ export const buildMethod2 = (rows: ScaleMethodRowLike[] | undefined) => {
|
|||||||
const area = toFiniteNumber(row.landArea)
|
const area = toFiniteNumber(row.landArea)
|
||||||
const feeResolved = resolveScaleMethodFee(row, 'area')
|
const feeResolved = resolveScaleMethodFee(row, 'area')
|
||||||
const basicFee = feeResolved.basicFee
|
const basicFee = feeResolved.basicFee
|
||||||
if (basicFee != null) hasTotalValue = true
|
const serviceFee = feeResolved.serviceFee
|
||||||
|
if (serviceFee != null) hasTotalValue = true
|
||||||
const basicFeeBasic = feeResolved.basicFeeBasic
|
const basicFeeBasic = feeResolved.basicFeeBasic
|
||||||
const basicFeeOptional = feeResolved.basicFeeOptional
|
const basicFeeOptional = feeResolved.basicFeeOptional
|
||||||
const remark = typeof row.remark === 'string' ? row.remark : ''
|
const remark = typeof row.remark === 'string' ? row.remark : ''
|
||||||
if (basicFee == null) return null
|
if (basicFee == null && serviceFee == null) return null
|
||||||
return {
|
return {
|
||||||
proNum,
|
proNum,
|
||||||
major,
|
major,
|
||||||
@ -509,7 +539,7 @@ export const buildMethod2 = (rows: ScaleMethodRowLike[] | undefined) => {
|
|||||||
majorCoe: toFiniteNumberOrZero(row.majorFactor),
|
majorCoe: toFiniteNumberOrZero(row.majorFactor),
|
||||||
processCoe: toFiniteNumber(row.workStageFactor) ?? 1,
|
processCoe: toFiniteNumber(row.workStageFactor) ?? 1,
|
||||||
proportion: toFiniteNumber(row.workRatio) ?? 1,
|
proportion: toFiniteNumber(row.workRatio) ?? 1,
|
||||||
fee: toMoney(basicFee),
|
fee: toMoney(serviceFee),
|
||||||
remark
|
remark
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -527,16 +557,34 @@ export const buildMethod2 = (rows: ScaleMethodRowLike[] | undefined) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const resolveWorkloadBasicFee = (row: WorkloadMethodRowLike) => {
|
||||||
|
const basicFee = toFiniteNumber(row.basicFee)
|
||||||
|
if (basicFee != null) return basicFee
|
||||||
|
const price = toFiniteNumber(row.budgetAdoptedUnitPrice)
|
||||||
|
const conversion = toFiniteNumber(row.conversion)
|
||||||
|
const amount = toFiniteNumber(row.workload)
|
||||||
|
if (price == null || conversion == null || amount == null) return null
|
||||||
|
return roundTo(price * conversion * amount, 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
const resolveWorkloadServiceFee = (row: WorkloadMethodRowLike, basicFee: number | null) => {
|
||||||
|
const fee = toFiniteNumber(row.serviceFee)
|
||||||
|
if (fee != null) return fee
|
||||||
|
const factor = toFiniteNumber(row.consultCategoryFactor)
|
||||||
|
if (basicFee == null || factor == null) return null
|
||||||
|
return roundTo(basicFee * factor, 2)
|
||||||
|
}
|
||||||
|
|
||||||
export const buildMethod3 = (rows: WorkloadMethodRowLike[] | undefined) => {
|
export const buildMethod3 = (rows: WorkloadMethodRowLike[] | undefined) => {
|
||||||
if (!Array.isArray(rows)) return null
|
if (!Array.isArray(rows)) return null
|
||||||
let hasTotalValue = false
|
let hasTotalValue = false
|
||||||
const det = rows
|
const det = rows
|
||||||
.map(row => {
|
.map(row => {
|
||||||
const task = getTaskIdFromRowId(row.id)
|
const task = getTaskIdFromRowId(row.id)
|
||||||
if (task == null || row.basicFee == null) return null
|
if (task == null) return null
|
||||||
const amount = toFiniteNumber(row.workload)
|
const amount = toFiniteNumber(row.workload)
|
||||||
const basicFee = toFiniteNumber(row.basicFee)
|
const basicFee = resolveWorkloadBasicFee(row)
|
||||||
const fee = toFiniteNumber(row.serviceFee)
|
const fee = resolveWorkloadServiceFee(row, basicFee)
|
||||||
if (fee != null) hasTotalValue = true
|
if (fee != null) hasTotalValue = true
|
||||||
const remark = typeof row.remark === 'string' ? row.remark : ''
|
const remark = typeof row.remark === 'string' ? row.remark : ''
|
||||||
const hasValue = amount != null || basicFee != null || fee != null || isNonEmptyString(remark)
|
const hasValue = amount != null || basicFee != null || fee != null || isNonEmptyString(remark)
|
||||||
@ -561,16 +609,26 @@ export const buildMethod3 = (rows: WorkloadMethodRowLike[] | undefined) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const resolveHourlyServiceFee = (row: HourlyMethodRowLike) => {
|
||||||
|
const fee = toFiniteNumber(row.serviceBudget)
|
||||||
|
if (fee != null) return fee
|
||||||
|
const price = toFiniteNumber(row.adoptedBudgetUnitPrice)
|
||||||
|
const personNum = toFiniteNumber(row.personnelCount)
|
||||||
|
const workDay = toFiniteNumber(row.workdayCount)
|
||||||
|
if (price == null || personNum == null || workDay == null) return null
|
||||||
|
return roundTo(price * personNum * workDay, 2)
|
||||||
|
}
|
||||||
|
|
||||||
export const buildMethod4 = (rows: HourlyMethodRowLike[] | undefined) => {
|
export const buildMethod4 = (rows: HourlyMethodRowLike[] | undefined) => {
|
||||||
if (!Array.isArray(rows)) return null
|
if (!Array.isArray(rows)) return null
|
||||||
let hasTotalValue = false
|
let hasTotalValue = false
|
||||||
const det = rows
|
const det = rows
|
||||||
.map(row => {
|
.map(row => {
|
||||||
const expert = getExpertIdFromRowId(row.id)
|
const expert = getExpertIdFromRowId(row.id)
|
||||||
if (expert == null || row.serviceBudget == null) return null
|
if (expert == null) return null
|
||||||
const personNum = toFiniteNumber(row.personnelCount)
|
const personNum = toFiniteNumber(row.personnelCount)
|
||||||
const workDay = toFiniteNumber(row.workdayCount)
|
const workDay = toFiniteNumber(row.workdayCount)
|
||||||
const fee = toFiniteNumber(row.serviceBudget)
|
const fee = resolveHourlyServiceFee(row)
|
||||||
if (fee != null) hasTotalValue = true
|
if (fee != null) hasTotalValue = true
|
||||||
const remark = typeof row.remark === 'string' ? row.remark : ''
|
const remark = typeof row.remark === 'string' ? row.remark : ''
|
||||||
const hasValue = personNum != null || workDay != null || fee != null || isNonEmptyString(remark)
|
const hasValue = personNum != null || workDay != null || fee != null || isNonEmptyString(remark)
|
||||||
|
|||||||
63
src/lib/servicePricing.ts
Normal file
63
src/lib/servicePricing.ts
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
import type { ServiceLite } from '@/types/pricing'
|
||||||
|
|
||||||
|
type ServicePricingCapabilitySource = Partial<Pick<
|
||||||
|
ServiceLite,
|
||||||
|
| 'enableInvestScale'
|
||||||
|
| 'enableLandScale'
|
||||||
|
| 'investScaleSingleTotal'
|
||||||
|
| 'scale'
|
||||||
|
| 'onlyCostScale'
|
||||||
|
| 'amount'
|
||||||
|
| 'workDay'
|
||||||
|
>>
|
||||||
|
|
||||||
|
export interface ServicePricingCapabilities {
|
||||||
|
investScaleEnabled: boolean
|
||||||
|
landScaleEnabled: boolean
|
||||||
|
investScaleSingleTotal: boolean
|
||||||
|
workloadEnabled: boolean
|
||||||
|
hourlyEnabled: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export const resolveMethodEnabled = (value: unknown, fallback: boolean) => {
|
||||||
|
if (typeof value === 'boolean') return value
|
||||||
|
if (typeof value === 'number') return value === 1
|
||||||
|
if (typeof value === 'string') {
|
||||||
|
const normalized = value.trim().toLowerCase()
|
||||||
|
if (normalized === 'true' || normalized === '1') return true
|
||||||
|
if (normalized === 'false' || normalized === '0') return false
|
||||||
|
}
|
||||||
|
return fallback
|
||||||
|
}
|
||||||
|
|
||||||
|
export const resolveServicePricingCapabilities = (
|
||||||
|
service: ServicePricingCapabilitySource | null | undefined,
|
||||||
|
defaults: Partial<ServicePricingCapabilities> = {}
|
||||||
|
): ServicePricingCapabilities => {
|
||||||
|
const legacyScaleEnabled = resolveMethodEnabled(service?.scale, defaults.investScaleEnabled ?? false)
|
||||||
|
const legacyOnlyCostScale = resolveMethodEnabled(service?.onlyCostScale, defaults.investScaleSingleTotal ?? false)
|
||||||
|
|
||||||
|
const investScaleEnabled = resolveMethodEnabled(
|
||||||
|
service?.enableInvestScale,
|
||||||
|
legacyScaleEnabled
|
||||||
|
)
|
||||||
|
const landScaleEnabled = resolveMethodEnabled(
|
||||||
|
service?.enableLandScale,
|
||||||
|
legacyScaleEnabled && !legacyOnlyCostScale
|
||||||
|
)
|
||||||
|
const investScaleSingleTotal = resolveMethodEnabled(
|
||||||
|
service?.investScaleSingleTotal,
|
||||||
|
legacyOnlyCostScale
|
||||||
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
investScaleEnabled,
|
||||||
|
landScaleEnabled,
|
||||||
|
investScaleSingleTotal,
|
||||||
|
workloadEnabled: resolveMethodEnabled(service?.amount, defaults.workloadEnabled ?? false),
|
||||||
|
hourlyEnabled: resolveMethodEnabled(service?.workDay, defaults.hourlyEnabled ?? false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const isInvestScaleSingleTotalService = (service: ServicePricingCapabilitySource | null | undefined) =>
|
||||||
|
resolveServicePricingCapabilities(service).investScaleSingleTotal
|
||||||
@ -13,9 +13,15 @@ export const PROJECT_ID_QUERY_KEY = 'projectId'
|
|||||||
export const NEW_PROJECT_QUERY_KEY = 'newProject'
|
export const NEW_PROJECT_QUERY_KEY = 'newProject'
|
||||||
export const OPEN_PROJECT_DIALOG_QUERY_KEY = 'openProjectDialog'
|
export const OPEN_PROJECT_DIALOG_QUERY_KEY = 'openProjectDialog'
|
||||||
export const FORCE_HOME_QUERY_KEY = 'forceHome'
|
export const FORCE_HOME_QUERY_KEY = 'forceHome'
|
||||||
|
export const DISCLAIMER_ENTRY_QUERY_KEY = 'from'
|
||||||
|
export const DISCLAIMER_ENTRY_QUERY_VALUE = 'gov'
|
||||||
export const DEFAULT_PROJECT_ID = 'default'
|
export const DEFAULT_PROJECT_ID = 'default'
|
||||||
export const QUICK_PROJECT_ID = 'quick'
|
export const QUICK_PROJECT_ID = 'quick'
|
||||||
export const PROJECT_DB_NAME_PREFIX = 'DB'
|
export const PROJECT_DB_NAME_PREFIX = 'DB'
|
||||||
|
export const DISCLAIMER_ACCEPTANCE_STORAGE_KEY = 'jgjs-disclaimer-accepted-v1'
|
||||||
|
export const DISCLAIMER_ACCEPTED_EVENT = 'jgjs-disclaimer-accepted'
|
||||||
|
export const DISCLAIMER_PENDING_ACTION_STORAGE_KEY = 'jgjs-disclaimer-pending-action-v1'
|
||||||
|
export const DISCLAIMER_RETURN_URL_QUERY_KEY = 'returnUrl'
|
||||||
|
|
||||||
export const QUICK_CONTRACT_ID = 'quick-contract-default'
|
export const QUICK_CONTRACT_ID = 'quick-contract-default'
|
||||||
export const QUICK_CONTRACT_META_KEY = 'quick-contract-meta-v1'
|
export const QUICK_CONTRACT_META_KEY = 'quick-contract-meta-v1'
|
||||||
@ -42,6 +48,11 @@ export interface QuickContractMeta {
|
|||||||
updatedAt: string
|
updatedAt: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface DisclaimerPendingAction {
|
||||||
|
type: 'project' | 'quick' | 'import' | 'existing-project'
|
||||||
|
projectId?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export const readWorkspaceMode = (): WorkspaceMode => {
|
export const readWorkspaceMode = (): WorkspaceMode => {
|
||||||
@ -160,7 +171,7 @@ export const buildProjectUrl = (
|
|||||||
projectIdRaw: string,
|
projectIdRaw: string,
|
||||||
options?: { newProject?: boolean; openProjectDialog?: boolean; forceHome?: boolean }
|
options?: { newProject?: boolean; openProjectDialog?: boolean; forceHome?: boolean }
|
||||||
) => {
|
) => {
|
||||||
try {
|
try {
|
||||||
const url = new URL(window.location.href)
|
const url = new URL(window.location.href)
|
||||||
url.searchParams.set(PROJECT_ID_QUERY_KEY, normalizeProjectId(projectIdRaw))
|
url.searchParams.set(PROJECT_ID_QUERY_KEY, normalizeProjectId(projectIdRaw))
|
||||||
if (options?.newProject) {
|
if (options?.newProject) {
|
||||||
@ -185,6 +196,107 @@ export const buildProjectUrl = (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const readDisclaimerAcceptedEntries = () => {
|
||||||
|
try {
|
||||||
|
const raw = window.localStorage.getItem(DISCLAIMER_ACCEPTANCE_STORAGE_KEY)
|
||||||
|
if (!raw) return {}
|
||||||
|
const parsed = JSON.parse(raw) as Record<string, unknown>
|
||||||
|
if (!parsed || typeof parsed !== 'object') return {}
|
||||||
|
return parsed
|
||||||
|
} catch {
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const readRestrictedEntryCodeFromUrl = (href?: string | URL | null) => {
|
||||||
|
try {
|
||||||
|
const url = href instanceof URL ? href : new URL(href || window.location.href, window.location.href)
|
||||||
|
const entry = String(url.searchParams.get(DISCLAIMER_ENTRY_QUERY_KEY) || '').trim()
|
||||||
|
return entry
|
||||||
|
} catch {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const isRestrictedDisclaimerEntry = (entryRaw: string) =>
|
||||||
|
String(entryRaw || '').trim() === DISCLAIMER_ENTRY_QUERY_VALUE
|
||||||
|
|
||||||
|
export const isDisclaimerAcceptanceRequired = (href?: string | URL | null) =>
|
||||||
|
isRestrictedDisclaimerEntry(readRestrictedEntryCodeFromUrl(href))
|
||||||
|
|
||||||
|
export const hasAcceptedRestrictedDisclaimer = (entryRaw?: string) => {
|
||||||
|
const entry = String(entryRaw || readRestrictedEntryCodeFromUrl() || '').trim()
|
||||||
|
if (!isRestrictedDisclaimerEntry(entry)) return false
|
||||||
|
const acceptedMap = readDisclaimerAcceptedEntries()
|
||||||
|
return acceptedMap[entry] === true
|
||||||
|
}
|
||||||
|
|
||||||
|
export const persistRestrictedDisclaimerAcceptance = (entryRaw?: string) => {
|
||||||
|
const entry = String(entryRaw || readRestrictedEntryCodeFromUrl() || '').trim()
|
||||||
|
if (!isRestrictedDisclaimerEntry(entry)) return false
|
||||||
|
try {
|
||||||
|
const acceptedMap = readDisclaimerAcceptedEntries()
|
||||||
|
acceptedMap[entry] = true
|
||||||
|
window.localStorage.setItem(DISCLAIMER_ACCEPTANCE_STORAGE_KEY, JSON.stringify(acceptedMap))
|
||||||
|
window.dispatchEvent(new CustomEvent(DISCLAIMER_ACCEPTED_EVENT, {
|
||||||
|
detail: {
|
||||||
|
entry
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
return true
|
||||||
|
} catch {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const setPendingDisclaimerAction = (action: DisclaimerPendingAction | null) => {
|
||||||
|
try {
|
||||||
|
if (!action) {
|
||||||
|
window.sessionStorage.removeItem(DISCLAIMER_PENDING_ACTION_STORAGE_KEY)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
window.sessionStorage.setItem(DISCLAIMER_PENDING_ACTION_STORAGE_KEY, JSON.stringify(action))
|
||||||
|
} catch {
|
||||||
|
// ignore session storage errors
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const consumePendingDisclaimerAction = () => {
|
||||||
|
try {
|
||||||
|
const raw = window.sessionStorage.getItem(DISCLAIMER_PENDING_ACTION_STORAGE_KEY)
|
||||||
|
window.sessionStorage.removeItem(DISCLAIMER_PENDING_ACTION_STORAGE_KEY)
|
||||||
|
if (!raw) return null
|
||||||
|
const parsed = JSON.parse(raw) as DisclaimerPendingAction
|
||||||
|
if (!parsed || typeof parsed !== 'object') return null
|
||||||
|
const type = String(parsed.type || '').trim()
|
||||||
|
if (!type) return null
|
||||||
|
if (!['project', 'quick', 'import', 'existing-project'].includes(type)) return null
|
||||||
|
return {
|
||||||
|
type: type as DisclaimerPendingAction['type'],
|
||||||
|
projectId: typeof parsed.projectId === 'string' ? parsed.projectId.trim() : undefined
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const buildDisclaimerUrl = (returnUrl?: string) => {
|
||||||
|
try {
|
||||||
|
const url = new URL(window.location.href)
|
||||||
|
const target = new URL(`${url.pathname}${url.search}`, url.origin)
|
||||||
|
if (returnUrl) {
|
||||||
|
target.hash = `#/disclaimer?${new URLSearchParams({
|
||||||
|
[DISCLAIMER_RETURN_URL_QUERY_KEY]: returnUrl
|
||||||
|
}).toString()}`
|
||||||
|
} else {
|
||||||
|
target.hash = '#/disclaimer'
|
||||||
|
}
|
||||||
|
return target.toString()
|
||||||
|
} catch {
|
||||||
|
return './#/disclaimer'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export const getProjectDbName = (projectIdRaw: string) => {
|
export const getProjectDbName = (projectIdRaw: string) => {
|
||||||
const projectId = normalizeProjectId(projectIdRaw)
|
const projectId = normalizeProjectId(projectIdRaw)
|
||||||
return `${PROJECT_DB_NAME_PREFIX}-${projectId}`
|
return `${PROJECT_DB_NAME_PREFIX}-${projectId}`
|
||||||
|
|||||||
@ -22,6 +22,15 @@ type FactorDictItem = {
|
|||||||
|
|
||||||
type FactorDict = Record<string, FactorDictItem>
|
type FactorDict = Record<string, FactorDictItem>
|
||||||
|
|
||||||
|
const hasUsableFactorRows = (state: XmFactorState | null | undefined) =>
|
||||||
|
Array.isArray(state?.detailRows) &&
|
||||||
|
state.detailRows.some(row => {
|
||||||
|
const hasFactor =
|
||||||
|
toFiniteNumberOrNull(row?.budgetValue) != null ||
|
||||||
|
toFiniteNumberOrNull(row?.standardFactor) != null
|
||||||
|
return hasFactor || String(row?.id || '').trim() !== ''
|
||||||
|
})
|
||||||
|
|
||||||
const buildStandardFactorMap = (dict: FactorDict): Map<string, number | null> => {
|
const buildStandardFactorMap = (dict: FactorDict): Map<string, number | null> => {
|
||||||
const map = new Map<string, number | null>()
|
const map = new Map<string, number | null>()
|
||||||
for (const [id, item] of Object.entries(dict)) {
|
for (const [id, item] of Object.entries(dict)) {
|
||||||
@ -68,7 +77,12 @@ const loadFactorMap = async (
|
|||||||
const zxFwPricingStore = getZxFwPricingStoreSafely()
|
const zxFwPricingStore = getZxFwPricingStoreSafely()
|
||||||
const kvStore = getKvStoreSafely()
|
const kvStore = getKvStoreSafely()
|
||||||
const piniaData = zxFwPricingStore ? await zxFwPricingStore.loadKeyState<XmFactorState>(storageKey) : null
|
const piniaData = zxFwPricingStore ? await zxFwPricingStore.loadKeyState<XmFactorState>(storageKey) : null
|
||||||
const data = piniaData ?? (kvStore ? await kvStore.getItem<XmFactorState>(storageKey) : null)
|
const kvData = kvStore ? await kvStore.getItem<XmFactorState>(storageKey) : null
|
||||||
|
const data = hasUsableFactorRows(piniaData)
|
||||||
|
? piniaData
|
||||||
|
: hasUsableFactorRows(kvData)
|
||||||
|
? kvData
|
||||||
|
: (piniaData ?? kvData)
|
||||||
const map = buildStandardFactorMap(dict)
|
const map = buildStandardFactorMap(dict)
|
||||||
for (const row of data?.detailRows || []) {
|
for (const row of data?.detailRows || []) {
|
||||||
if (!row?.id) continue
|
if (!row?.id) continue
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import { roundTo, sumByNumber, toDecimal } from '@/lib/decimal'
|
import { roundTo, sumByNumber, toDecimal } from '@/lib/decimal'
|
||||||
import { ensurePricingMethodDetailRowsForServices } from '@/lib/pricingMethodTotals'
|
import { ensurePricingMethodDetailRowsForServices } from '@/lib/pricingMethodTotals'
|
||||||
|
import { isInvestScaleSingleTotalService, resolveServicePricingCapabilities } from '@/lib/servicePricing'
|
||||||
import { getServiceDictItemById } from '@/sql'
|
import { getServiceDictItemById } from '@/sql'
|
||||||
import {
|
import {
|
||||||
isSameNullableNumber,
|
isSameNullableNumber,
|
||||||
@ -64,6 +65,10 @@ type WorkloadDetailRow = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type ServiceLite = {
|
type ServiceLite = {
|
||||||
|
enableInvestScale?: boolean | null
|
||||||
|
enableLandScale?: boolean | null
|
||||||
|
investScaleSingleTotal?: boolean | null
|
||||||
|
scale?: boolean | null
|
||||||
onlyCostScale?: boolean | null
|
onlyCostScale?: boolean | null
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -108,9 +113,14 @@ const getWorkloadMethodTotalServiceFee = (rows: WorkloadDetailRow[]) => {
|
|||||||
return roundTo(sumByNumber(rows, row => row.serviceFee ?? null), 2)
|
return roundTo(sumByNumber(rows, row => row.serviceFee ?? null), 2)
|
||||||
}
|
}
|
||||||
|
|
||||||
const isOnlyCostScaleService = (serviceId: string) => {
|
const usesInvestScaleSingleTotal = (serviceId: string) => {
|
||||||
const service = getServiceDictItemById(serviceId) as ServiceLite | undefined
|
const service = getServiceDictItemById(serviceId) as ServiceLite | undefined
|
||||||
return service?.onlyCostScale === true
|
return isInvestScaleSingleTotalService(service)
|
||||||
|
}
|
||||||
|
|
||||||
|
const getScaleMethodCapabilities = (serviceId: string) => {
|
||||||
|
const service = getServiceDictItemById(serviceId) as ServiceLite | undefined
|
||||||
|
return resolveServicePricingCapabilities(service)
|
||||||
}
|
}
|
||||||
|
|
||||||
const matchesChangedScaleRow = (
|
const matchesChangedScaleRow = (
|
||||||
@ -145,7 +155,7 @@ const syncScaleMethodRows = async (params: {
|
|||||||
let changedRowCount = 0
|
let changedRowCount = 0
|
||||||
const useSummaryScaleValues =
|
const useSummaryScaleValues =
|
||||||
methodState.detailRows.length === 1 ||
|
methodState.detailRows.length === 1 ||
|
||||||
(params.method === 'investScale' && isOnlyCostScaleService(params.serviceId))
|
(params.method === 'investScale' && usesInvestScaleSingleTotal(params.serviceId))
|
||||||
const nextRows = methodState.detailRows.map(rawRow => {
|
const nextRows = methodState.detailRows.map(rawRow => {
|
||||||
const mode = params.method === 'investScale' ? 'cost' : 'area'
|
const mode = params.method === 'investScale' ? 'cost' : 'area'
|
||||||
if (!matchesChangedScaleRow(rawRow, params.changedRowIds, { bypassFilter: useSummaryScaleValues })) return rawRow
|
if (!matchesChangedScaleRow(rawRow, params.changedRowIds, { bypassFilter: useSummaryScaleValues })) return rawRow
|
||||||
@ -243,33 +253,40 @@ export const syncContractScaleToPricing = async (
|
|||||||
let updatedRowCount = 0
|
let updatedRowCount = 0
|
||||||
|
|
||||||
for (const serviceId of selectedIds) {
|
for (const serviceId of selectedIds) {
|
||||||
const investChangedCount = await syncScaleMethodRows({
|
const capabilities = getScaleMethodCapabilities(serviceId)
|
||||||
contractId,
|
|
||||||
serviceId,
|
if (capabilities.investScaleEnabled) {
|
||||||
method: 'investScale',
|
const investChangedCount = await syncScaleMethodRows({
|
||||||
sourceRowMap,
|
contractId,
|
||||||
sourceRowIdMap,
|
serviceId,
|
||||||
projectTotals,
|
method: 'investScale',
|
||||||
changedRowIds: changedRowIdSet
|
sourceRowMap,
|
||||||
})
|
sourceRowIdMap,
|
||||||
if (investChangedCount > 0) {
|
projectTotals,
|
||||||
updatedServiceIdSet.add(serviceId)
|
changedRowIds: changedRowIdSet
|
||||||
updatedMethodCount += 1
|
})
|
||||||
updatedRowCount += investChangedCount
|
if (investChangedCount > 0) {
|
||||||
|
updatedServiceIdSet.add(serviceId)
|
||||||
|
updatedMethodCount += 1
|
||||||
|
updatedRowCount += investChangedCount
|
||||||
|
}
|
||||||
}
|
}
|
||||||
const landChangedCount = await syncScaleMethodRows({
|
|
||||||
contractId,
|
if (capabilities.landScaleEnabled) {
|
||||||
serviceId,
|
const landChangedCount = await syncScaleMethodRows({
|
||||||
method: 'landScale',
|
contractId,
|
||||||
sourceRowMap,
|
serviceId,
|
||||||
sourceRowIdMap,
|
method: 'landScale',
|
||||||
projectTotals,
|
sourceRowMap,
|
||||||
changedRowIds: changedRowIdSet
|
sourceRowIdMap,
|
||||||
})
|
projectTotals,
|
||||||
if (landChangedCount > 0) {
|
changedRowIds: changedRowIdSet
|
||||||
updatedServiceIdSet.add(serviceId)
|
})
|
||||||
updatedMethodCount += 1
|
if (landChangedCount > 0) {
|
||||||
updatedRowCount += landChangedCount
|
updatedServiceIdSet.add(serviceId)
|
||||||
|
updatedMethodCount += 1
|
||||||
|
updatedRowCount += landChangedCount
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -516,3 +533,4 @@ export const syncPricingTotalToZxFw = async (params: {
|
|||||||
const store = useZxFwPricingStore()
|
const store = useZxFwPricingStore()
|
||||||
return store.updatePricingField(params)
|
return store.updatePricingField(params)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
14
src/main.ts
14
src/main.ts
@ -27,6 +27,8 @@ import './style.css'
|
|||||||
import { i18n } from '@/i18n'
|
import { i18n } from '@/i18n'
|
||||||
import { ensureProjectIdInUrl, getProjectDbName } from '@/lib/workspace'
|
import { ensureProjectIdInUrl, getProjectDbName } from '@/lib/workspace'
|
||||||
import { listProjects } from '@/lib/projectRegistry'
|
import { listProjects } from '@/lib/projectRegistry'
|
||||||
|
import { collectActiveProjectSessionLocks } from '@/lib/projectSessionLock'
|
||||||
|
import { router } from '@/router'
|
||||||
|
|
||||||
LicenseManager.setLicenseKey(
|
LicenseManager.setLicenseKey(
|
||||||
'[v3][RELEASE][0102]_NDg2Njc4MzY3MDgzNw==16d78ca762fb5d2ff740aed081e2af7b'
|
'[v3][RELEASE][0102]_NDg2Njc4MzY3MDgzNw==16d78ca762fb5d2ff740aed081e2af7b'
|
||||||
@ -51,14 +53,20 @@ const AG_GRID_MODULES = [
|
|||||||
LocaleModule,ValidationModule ,CellSpanModule ,RowStyleModule ,RowSelectionModule ,ServerSideRowModelApiModule
|
LocaleModule,ValidationModule ,CellSpanModule ,RowStyleModule ,RowSelectionModule ,ServerSideRowModelApiModule
|
||||||
]
|
]
|
||||||
|
|
||||||
|
const isDisclaimerRoute = () => String(window.location.hash || '').startsWith('#/disclaimer')
|
||||||
|
|
||||||
const pickBootstrapProjectId = () => {
|
const pickBootstrapProjectId = () => {
|
||||||
|
if (isDisclaimerRoute()) return 'default'
|
||||||
try {
|
try {
|
||||||
const url = new URL(window.location.href)
|
const url = new URL(window.location.href)
|
||||||
|
|
||||||
const explicit = String(url.searchParams.get('projectId') || '').trim()
|
const explicit = String(url.searchParams.get('projectId') || '').trim()
|
||||||
if (explicit) return ensureProjectIdInUrl()
|
if (explicit) return ensureProjectIdInUrl()
|
||||||
const projects = listProjects()
|
const projects = listProjects()
|
||||||
if (projects.length > 0) {
|
const openedProjectIds = collectActiveProjectSessionLocks(projects.map(project => project.id))
|
||||||
const lastEdited = projects[0]
|
const availableProjects = projects.filter(project => !openedProjectIds.has(project.id))
|
||||||
|
if (availableProjects.length > 0) {
|
||||||
|
const lastEdited = availableProjects[0]
|
||||||
url.searchParams.set('projectId', lastEdited.id)
|
url.searchParams.set('projectId', lastEdited.id)
|
||||||
window.history.replaceState({}, '', `${url.pathname}${url.search}${url.hash}`)
|
window.history.replaceState({}, '', `${url.pathname}${url.search}${url.hash}`)
|
||||||
}
|
}
|
||||||
@ -83,4 +91,4 @@ uiPrefsStore.initFromStorage()
|
|||||||
// 在应用启动时一次性注册 AG Grid 运行所需模块。
|
// 在应用启动时一次性注册 AG Grid 运行所需模块。
|
||||||
ModuleRegistry.registerModules(AG_GRID_MODULES)
|
ModuleRegistry.registerModules(AG_GRID_MODULES)
|
||||||
|
|
||||||
createApp(App).use(pinia).use(i18n).mount('#app')
|
createApp(App).use(pinia).use(i18n).use(router).mount('#app')
|
||||||
|
|||||||
522
src/pinia/zx.ts
522
src/pinia/zx.ts
@ -1,522 +0,0 @@
|
|||||||
import { defineStore } from 'pinia'
|
|
||||||
import { ref, computed } from 'vue'
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 通用数据项接口
|
|
||||||
* 可根据实际需求扩展字段
|
|
||||||
*/
|
|
||||||
export interface DataItem {
|
|
||||||
id: string
|
|
||||||
[key: string]: any
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 查询条件接口
|
|
||||||
*/
|
|
||||||
export interface QueryCondition {
|
|
||||||
field?: string
|
|
||||||
value?: any
|
|
||||||
operator?: 'eq' | 'neq' | 'gt' | 'lt' | 'gte' | 'lte' | 'includes'
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 通用对象存储 Store
|
|
||||||
* 提供增删改查功能,state 中保存的是对象结构
|
|
||||||
*/
|
|
||||||
export const useDataStore = defineStore('zx', () => {
|
|
||||||
// ==================== State ====================
|
|
||||||
/**
|
|
||||||
* 数据存储对象
|
|
||||||
* 结构:{ [id: string]: DataItem }
|
|
||||||
*/
|
|
||||||
const items = ref<Record<string, DataItem>>({})
|
|
||||||
|
|
||||||
// ==================== Getters ====================
|
|
||||||
/**
|
|
||||||
* 获取所有数据项(数组形式)
|
|
||||||
*/
|
|
||||||
const allItems = computed<DataItem[]>(() => {
|
|
||||||
return Object.values(items.value)
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取数据项数量
|
|
||||||
*/
|
|
||||||
const count = computed<number>(() => {
|
|
||||||
return Object.keys(items.value).length
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取所有 ID 列表
|
|
||||||
*/
|
|
||||||
const allIds = computed<string[]>(() => {
|
|
||||||
return Object.keys(items.value)
|
|
||||||
})
|
|
||||||
|
|
||||||
// ==================== Actions ====================
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 创建/新增数据项
|
|
||||||
* @param item 要添加的数据项(必须包含 id 字段)
|
|
||||||
* @returns 是否成功
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* ```ts
|
|
||||||
* const store = useDataStore()
|
|
||||||
* await store.create({ id: '1', name: '测试', value: 100 })
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
const create = async (item: DataItem): Promise<boolean> => {
|
|
||||||
if (!item || !item.id) {
|
|
||||||
console.warn('[DataStore] 创建失败:缺少 id 字段')
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
const id = String(item.id)
|
|
||||||
|
|
||||||
// 检查是否已存在
|
|
||||||
if (items.value[id]) {
|
|
||||||
console.warn(`[DataStore] 创建失败:ID "${id}" 已存在`)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// 深拷贝后存储
|
|
||||||
items.value[id] = JSON.parse(JSON.stringify(item))
|
|
||||||
console.log(`[DataStore] 创建成功:${id}`)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 批量创建数据项
|
|
||||||
* @param itemList 数据项数组
|
|
||||||
* @returns 成功创建的数量
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* ```ts
|
|
||||||
* await store.createBatch([
|
|
||||||
* { id: '1', name: '项目1' },
|
|
||||||
* { id: '2', name: '项目2' }
|
|
||||||
* ])
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
const createBatch = async (itemList: DataItem[]): Promise<number> => {
|
|
||||||
let successCount = 0
|
|
||||||
|
|
||||||
for (const item of itemList) {
|
|
||||||
const result = await create(item)
|
|
||||||
if (result) successCount++
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(`[DataStore] 批量创建完成:成功 ${successCount}/${itemList.length}`)
|
|
||||||
return successCount
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 更新或插入数据项(Upsert)
|
|
||||||
* 如果 ID 存在则更新,不存在则创建
|
|
||||||
* @param item 数据项(必须包含 id 字段)
|
|
||||||
* @returns 操作类型:'created' | 'updated'
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* ```ts
|
|
||||||
* const result = await store.upsert({ id: '1', name: '测试', value: 100 })
|
|
||||||
* console.log(result) // 'created' 或 'updated'
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
const upsert = async (item: DataItem): Promise<'created' | 'updated'> => {
|
|
||||||
if (!item || !item.id) {
|
|
||||||
console.warn('[DataStore] Upsert 失败:缺少 id 字段')
|
|
||||||
throw new Error('数据项必须包含 id 字段')
|
|
||||||
}
|
|
||||||
|
|
||||||
const id = String(item.id)
|
|
||||||
const exists = Object.prototype.hasOwnProperty.call(items.value, id)
|
|
||||||
|
|
||||||
// 深拷贝后存储
|
|
||||||
items.value[id] = JSON.parse(JSON.stringify(item))
|
|
||||||
|
|
||||||
if (exists) {
|
|
||||||
console.log(`[DataStore] Upsert 更新:${id}`)
|
|
||||||
return 'updated'
|
|
||||||
} else {
|
|
||||||
console.log(`[DataStore] Upsert 创建:${id}`)
|
|
||||||
return 'created'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 批量更新或插入数据项(Batch Upsert)
|
|
||||||
* @param itemList 数据项数组
|
|
||||||
* @returns 统计信息:{ created: number, updated: number }
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* ```ts
|
|
||||||
* const stats = await store.upsertBatch([
|
|
||||||
* { id: '1', name: '项目1' }, // 如果存在则更新,否则创建
|
|
||||||
* { id: '2', name: '项目2' }
|
|
||||||
* ])
|
|
||||||
* console.log(stats) // { created: 1, updated: 1 }
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
const upsertBatch = async (itemList: DataItem[]): Promise<{ created: number; updated: number }> => {
|
|
||||||
let createdCount = 0
|
|
||||||
let updatedCount = 0
|
|
||||||
|
|
||||||
for (const item of itemList) {
|
|
||||||
const result = await upsert(item)
|
|
||||||
if (result === 'created') {
|
|
||||||
createdCount++
|
|
||||||
} else {
|
|
||||||
updatedCount++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(`[DataStore] 批量 Upsert 完成:创建 ${createdCount} 条,更新 ${updatedCount} 条,共 ${itemList.length} 条`)
|
|
||||||
return { created: createdCount, updated: updatedCount }
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 读取单个数据项
|
|
||||||
* @param id 数据项 ID
|
|
||||||
* @returns 数据项副本,不存在则返回 null
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* ```ts
|
|
||||||
* const item = await store.read('1')
|
|
||||||
* if (item) {
|
|
||||||
* console.log(item.name)
|
|
||||||
* }
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
const read = async (id: string): Promise<DataItem | null> => {
|
|
||||||
const key = String(id)
|
|
||||||
|
|
||||||
if (!items.value[key]) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
// 返回深拷贝,避免外部修改影响 store
|
|
||||||
return JSON.parse(JSON.stringify(items.value[key]))
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 批量读取数据项
|
|
||||||
* @param ids ID 数组
|
|
||||||
* @returns 数据项数组(按传入顺序)
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* ```ts
|
|
||||||
* const items = await store.readBatch(['1', '2', '3'])
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
const readBatch = async (ids: string[]): Promise<DataItem[]> => {
|
|
||||||
const results: DataItem[] = []
|
|
||||||
|
|
||||||
for (const id of ids) {
|
|
||||||
const item = await read(id)
|
|
||||||
if (item) {
|
|
||||||
results.push(item)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return results
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 读取所有数据项
|
|
||||||
* @returns 所有数据项的数组副本
|
|
||||||
*/
|
|
||||||
const readAll = async (): Promise<DataItem[]> => {
|
|
||||||
return JSON.parse(JSON.stringify(allItems.value))
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 更新数据项(全量替换)
|
|
||||||
* @param id 数据项 ID
|
|
||||||
* @param newItem 新的数据项(必须包含 id 字段)
|
|
||||||
* @returns 是否成功
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* ```ts
|
|
||||||
* await store.update('1', { id: '1', name: '新名称', value: 200 })
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
const update = async (id: string, newItem: DataItem): Promise<boolean> => {
|
|
||||||
const key = String(id)
|
|
||||||
|
|
||||||
if (!items.value[key]) {
|
|
||||||
console.warn(`[DataStore] 更新失败:ID "${key}" 不存在`)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!newItem || newItem.id !== id) {
|
|
||||||
console.warn('[DataStore] 更新失败:ID 不匹配')
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// 全量替换
|
|
||||||
items.value[key] = JSON.parse(JSON.stringify(newItem))
|
|
||||||
console.log(`[DataStore] 更新成功:${key}`)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 部分更新数据项(合并更新)
|
|
||||||
* @param id 数据项 ID
|
|
||||||
* @param partialData 要更新的字段(不需要包含 id)
|
|
||||||
* @returns 是否成功
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* ```ts
|
|
||||||
* await store.patch('1', { name: '新名称' }) // 只更新 name 字段
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
const patch = async (id: string, partialData: Partial<DataItem>): Promise<boolean> => {
|
|
||||||
const key = String(id)
|
|
||||||
|
|
||||||
if (!items.value[key]) {
|
|
||||||
console.warn(`[DataStore] 部分更新失败:ID "${key}" 不存在`)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// 合并更新
|
|
||||||
items.value[key] = {
|
|
||||||
...items.value[key],
|
|
||||||
...partialData,
|
|
||||||
id: key // 确保 id 不被修改
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(`[DataStore] 部分更新成功:${key}`)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 批量更新数据项
|
|
||||||
* @param updates 更新操作数组 [{ id, data }]
|
|
||||||
* @returns 成功更新的数量
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* ```ts
|
|
||||||
* await store.updateBatch([
|
|
||||||
* { id: '1', data: { name: '新名称1' } },
|
|
||||||
* { id: '2', data: { value: 200 } }
|
|
||||||
* ])
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
const updateBatch = async (updates: Array<{ id: string; data: Partial<DataItem> }>): Promise<number> => {
|
|
||||||
let successCount = 0
|
|
||||||
|
|
||||||
for (const { id, data } of updates) {
|
|
||||||
const result = await patch(id, data)
|
|
||||||
if (result) successCount++
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(`[DataStore] 批量更新完成:成功 ${successCount}/${updates.length}`)
|
|
||||||
return successCount
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 删除单个数据项
|
|
||||||
* @param id 数据项 ID
|
|
||||||
* @returns 是否成功
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* ```ts
|
|
||||||
* await store.delete('1')
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
const deleteItem = async (id: string): Promise<boolean> => {
|
|
||||||
const key = String(id)
|
|
||||||
|
|
||||||
if (!items.value[key]) {
|
|
||||||
console.warn(`[DataStore] 删除失败:ID "${key}" 不存在`)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
delete items.value[key]
|
|
||||||
console.log(`[DataStore] 删除成功:${key}`)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 批量删除数据项
|
|
||||||
* @param ids ID 数组
|
|
||||||
* @returns 成功删除的数量
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* ```ts
|
|
||||||
* await store.deleteBatch(['1', '2', '3'])
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
const deleteBatch = async (ids: string[]): Promise<number> => {
|
|
||||||
let successCount = 0
|
|
||||||
|
|
||||||
for (const id of ids) {
|
|
||||||
const result = await deleteItem(id)
|
|
||||||
if (result) successCount++
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(`[DataStore] 批量删除完成:成功 ${successCount}/${ids.length}`)
|
|
||||||
return successCount
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 清空所有数据
|
|
||||||
*/
|
|
||||||
const clear = async (): Promise<void> => {
|
|
||||||
items.value = {}
|
|
||||||
console.log('[DataStore] 已清空所有数据')
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 根据条件查询数据项
|
|
||||||
* @param conditions 查询条件数组
|
|
||||||
* @returns 符合条件的数据项数组
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* ```ts
|
|
||||||
* // 查询 name 为 "测试" 且 value > 50 的数据
|
|
||||||
* const results = await store.query([
|
|
||||||
* { field: 'name', value: '测试', operator: 'eq' },
|
|
||||||
* { field: 'value', value: 50, operator: 'gt' }
|
|
||||||
* ])
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
const query = async (conditions: QueryCondition[]): Promise<DataItem[]> => {
|
|
||||||
const results = allItems.value.filter(item => {
|
|
||||||
return conditions.every(condition => {
|
|
||||||
if (!condition.field) return true
|
|
||||||
|
|
||||||
const fieldValue = item[condition.field]
|
|
||||||
const targetValue = condition.value
|
|
||||||
const operator = condition.operator || 'eq'
|
|
||||||
|
|
||||||
switch (operator) {
|
|
||||||
case 'eq':
|
|
||||||
return fieldValue === targetValue
|
|
||||||
case 'neq':
|
|
||||||
return fieldValue !== targetValue
|
|
||||||
case 'gt':
|
|
||||||
return fieldValue > targetValue
|
|
||||||
case 'lt':
|
|
||||||
return fieldValue < targetValue
|
|
||||||
case 'gte':
|
|
||||||
return fieldValue >= targetValue
|
|
||||||
case 'lte':
|
|
||||||
return fieldValue <= targetValue
|
|
||||||
case 'includes':
|
|
||||||
return Array.isArray(fieldValue)
|
|
||||||
? fieldValue.includes(targetValue)
|
|
||||||
: String(fieldValue).includes(String(targetValue))
|
|
||||||
default:
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
return JSON.parse(JSON.stringify(results))
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 根据单个字段值查询
|
|
||||||
* @param field 字段名
|
|
||||||
* @param value 字段值
|
|
||||||
* @returns 符合条件的第一个数据项,不存在则返回 null
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* ```ts
|
|
||||||
* const item = await store.findByField('name', '测试')
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
const findByField = async (field: string, value: any): Promise<DataItem | null> => {
|
|
||||||
const results = await query([{ field, value, operator: 'eq' }])
|
|
||||||
return results.length > 0 ? results[0] : null
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 检查数据项是否存在
|
|
||||||
* @param id 数据项 ID
|
|
||||||
* @returns 是否存在
|
|
||||||
*/
|
|
||||||
const exists = async (id: string): Promise<boolean> => {
|
|
||||||
const key = String(id)
|
|
||||||
return Object.prototype.hasOwnProperty.call(items.value, key)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 导出数据(用于备份或迁移)
|
|
||||||
* @returns 所有数据的数组形式
|
|
||||||
*/
|
|
||||||
const exportData = async (): Promise<DataItem[]> => {
|
|
||||||
return JSON.parse(JSON.stringify(allItems.value))
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 导入数据(用于恢复或迁移)
|
|
||||||
* @param dataList 数据数组
|
|
||||||
* @param options 导入选项
|
|
||||||
* - replace: 是否替换现有数据(默认 false,即合并)
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* ```ts
|
|
||||||
* // 合并导入
|
|
||||||
* await store.importData(dataArray)
|
|
||||||
*
|
|
||||||
* // 替换导入
|
|
||||||
* await store.importData(dataArray, { replace: true })
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
const importData = async (
|
|
||||||
dataList: DataItem[],
|
|
||||||
options?: { replace?: boolean }
|
|
||||||
): Promise<void> => {
|
|
||||||
if (options?.replace) {
|
|
||||||
await clear()
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const item of dataList) {
|
|
||||||
if (item && item.id) {
|
|
||||||
items.value[String(item.id)] = JSON.parse(JSON.stringify(item))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(`[DataStore] 导入完成:${dataList.length} 条数据`)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ==================== Return ====================
|
|
||||||
return {
|
|
||||||
// State
|
|
||||||
items,
|
|
||||||
|
|
||||||
// Getters
|
|
||||||
allItems,
|
|
||||||
count,
|
|
||||||
allIds,
|
|
||||||
|
|
||||||
// CRUD Actions
|
|
||||||
create,
|
|
||||||
createBatch,
|
|
||||||
upsert,
|
|
||||||
upsertBatch,
|
|
||||||
read,
|
|
||||||
readBatch,
|
|
||||||
readAll,
|
|
||||||
update,
|
|
||||||
patch,
|
|
||||||
updateBatch,
|
|
||||||
deleteItem,
|
|
||||||
deleteBatch,
|
|
||||||
clear,
|
|
||||||
|
|
||||||
// Query Actions
|
|
||||||
query,
|
|
||||||
findByField,
|
|
||||||
exists,
|
|
||||||
|
|
||||||
// Import/Export
|
|
||||||
exportData,
|
|
||||||
importData
|
|
||||||
}
|
|
||||||
}, {
|
|
||||||
persist: true // 启用持久化
|
|
||||||
})
|
|
||||||
@ -15,7 +15,7 @@ import {
|
|||||||
} from '@/pinia/zxFwPricingHtFee'
|
} from '@/pinia/zxFwPricingHtFee'
|
||||||
import { useZxFwPricingKeysStore } from '@/pinia/zxFwPricingKeys'
|
import { useZxFwPricingKeysStore } from '@/pinia/zxFwPricingKeys'
|
||||||
|
|
||||||
export type ZxFwPricingField = 'investScale' | 'landScale' | 'serviceFee' | 'hourly'
|
export type ZxFwPricingField = 'investScale' | 'landScale' | 'workload' | 'hourly'
|
||||||
export type ServicePricingMethod = ZxFwPricingField
|
export type ServicePricingMethod = ZxFwPricingField
|
||||||
|
|
||||||
export interface ZxFwDetailRow {
|
export interface ZxFwDetailRow {
|
||||||
@ -26,7 +26,7 @@ export interface ZxFwDetailRow {
|
|||||||
process?: number | null
|
process?: number | null
|
||||||
investScale: number | null
|
investScale: number | null
|
||||||
landScale: number | null
|
landScale: number | null
|
||||||
serviceFee: number | null
|
workload: number | null
|
||||||
hourly: number | null
|
hourly: number | null
|
||||||
subtotal?: number | null
|
subtotal?: number | null
|
||||||
finalFee?: number | null
|
finalFee?: number | null
|
||||||
@ -47,7 +47,7 @@ export interface ServicePricingMethodState<TRow = unknown> {
|
|||||||
export interface ServicePricingState {
|
export interface ServicePricingState {
|
||||||
investScale?: ServicePricingMethodState
|
investScale?: ServicePricingMethodState
|
||||||
landScale?: ServicePricingMethodState
|
landScale?: ServicePricingMethodState
|
||||||
serviceFee?: ServicePricingMethodState
|
workload?: ServicePricingMethodState
|
||||||
hourly?: ServicePricingMethodState
|
hourly?: ServicePricingMethodState
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -63,7 +63,7 @@ const FIXED_ROW_ID = 'fixed-budget-c'
|
|||||||
const METHOD_STORAGE_PREFIX_MAP: Record<ServicePricingMethod, string> = {
|
const METHOD_STORAGE_PREFIX_MAP: Record<ServicePricingMethod, string> = {
|
||||||
investScale: 'tzGMF',
|
investScale: 'tzGMF',
|
||||||
landScale: 'ydGMF',
|
landScale: 'ydGMF',
|
||||||
serviceFee: 'gzlF',
|
workload: 'gzlF',
|
||||||
hourly: 'hourlyPricing'
|
hourly: 'hourlyPricing'
|
||||||
}
|
}
|
||||||
const STORAGE_PREFIX_METHOD_MAP = new Map<string, ServicePricingMethod>(
|
const STORAGE_PREFIX_METHOD_MAP = new Map<string, ServicePricingMethod>(
|
||||||
@ -131,7 +131,7 @@ const normalizeRows = (rows: unknown): ZxFwDetailRow[] =>
|
|||||||
process: normalizeProcessValue(row.process, rowId),
|
process: normalizeProcessValue(row.process, rowId),
|
||||||
investScale: toFiniteNumberOrNull(row.investScale),
|
investScale: toFiniteNumberOrNull(row.investScale),
|
||||||
landScale: toFiniteNumberOrNull(row.landScale),
|
landScale: toFiniteNumberOrNull(row.landScale),
|
||||||
serviceFee: toFiniteNumberOrNull(row.serviceFee),
|
workload: toFiniteNumberOrNull(row.workload),
|
||||||
hourly: toFiniteNumberOrNull(row.hourly),
|
hourly: toFiniteNumberOrNull(row.hourly),
|
||||||
subtotal: toFiniteNumberOrNull(row.subtotal),
|
subtotal: toFiniteNumberOrNull(row.subtotal),
|
||||||
finalFee: toFiniteNumberOrNull(row.finalFee),
|
finalFee: toFiniteNumberOrNull(row.finalFee),
|
||||||
@ -802,7 +802,7 @@ export const useZxFwPricingStore = defineStore('zxFwPricing', () => {
|
|||||||
if (!targetServiceId) return false
|
if (!targetServiceId) return false
|
||||||
|
|
||||||
const nextValue = toFiniteNumberOrNull(params.value)
|
const nextValue = toFiniteNumberOrNull(params.value)
|
||||||
|
|
||||||
let changed = false
|
let changed = false
|
||||||
const updatedRows = current.detailRows.map(row => {
|
const updatedRows = current.detailRows.map(row => {
|
||||||
if (String(row.id || '') !== targetServiceId) return row
|
if (String(row.id || '') !== targetServiceId) return row
|
||||||
@ -824,7 +824,7 @@ export const useZxFwPricingStore = defineStore('zxFwPricing', () => {
|
|||||||
const rowSubtotal = sumNullableNumbers([
|
const rowSubtotal = sumNullableNumbers([
|
||||||
toFiniteNumberOrNull(row.investScale),
|
toFiniteNumberOrNull(row.investScale),
|
||||||
toFiniteNumberOrNull(row.landScale),
|
toFiniteNumberOrNull(row.landScale),
|
||||||
toFiniteNumberOrNull(row.serviceFee),
|
toFiniteNumberOrNull(row.workload),
|
||||||
toFiniteNumberOrNull(row.hourly)
|
toFiniteNumberOrNull(row.hourly)
|
||||||
])
|
])
|
||||||
return {
|
return {
|
||||||
@ -945,7 +945,8 @@ export const useZxFwPricingStore = defineStore('zxFwPricing', () => {
|
|||||||
getHtFeeMethodState,
|
getHtFeeMethodState,
|
||||||
setHtFeeMethodState,
|
setHtFeeMethodState,
|
||||||
loadHtFeeMethodState,
|
loadHtFeeMethodState,
|
||||||
removeHtFeeMethodState
|
removeHtFeeMethodState,
|
||||||
|
syncHtExtraFeeByContractBase
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
persist: true
|
persist: true
|
||||||
|
|||||||
19
src/router.ts
Normal file
19
src/router.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import { createRouter, createWebHashHistory } from 'vue-router'
|
||||||
|
import DisclaimerPage from '@/features/disclaimer/components/DisclaimerPage.vue'
|
||||||
|
import WorkspaceShell from '@/features/app/components/WorkspaceShell.vue'
|
||||||
|
|
||||||
|
export const router = createRouter({
|
||||||
|
history: createWebHashHistory(),
|
||||||
|
routes: [
|
||||||
|
{
|
||||||
|
path: '/',
|
||||||
|
name: 'workspace',
|
||||||
|
component: WorkspaceShell
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/disclaimer',
|
||||||
|
name: 'disclaimer',
|
||||||
|
component: DisclaimerPage
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
1040
src/sql.ts
1040
src/sql.ts
File diff suppressed because it is too large
Load Diff
@ -57,12 +57,17 @@ html {
|
|||||||
--app-grid-row-h: 2.25rem;
|
--app-grid-row-h: 2.25rem;
|
||||||
--app-grid-font-size: 0.875rem;
|
--app-grid-font-size: 0.875rem;
|
||||||
--app-typeline-side-w: 12.5rem;
|
--app-typeline-side-w: 12.5rem;
|
||||||
--app-typeline-gap: 0.75rem;
|
--app-typeline-gap: 0.625rem;
|
||||||
--app-typeline-label-font: 0.8125rem;
|
--app-typeline-label-font: 0.8125rem;
|
||||||
--app-typeline-label-line: 1rem;
|
--app-typeline-label-line: 1rem;
|
||||||
--app-typeline-dot: 1.25rem;
|
--app-typeline-dot: 1.125rem;
|
||||||
--app-typeline-dot-inner: 0.375rem;
|
--app-typeline-dot-inner: 0.3125rem;
|
||||||
--app-typeline-line-left: 0.5625rem;
|
--app-typeline-line-left: 0.5625rem;
|
||||||
|
--app-typeline-arrow-offset: 0.125rem;
|
||||||
|
--app-typeline-arrow-shaft-h: 0.9375rem;
|
||||||
|
--app-typeline-arrow-line-w: 0.09375rem;
|
||||||
|
--app-typeline-arrow-head-w: 0.5rem;
|
||||||
|
--app-typeline-arrow-head-h: 0.375rem;
|
||||||
--radius: 0.625rem;
|
--radius: 0.625rem;
|
||||||
--background: oklch(1 0 0);
|
--background: oklch(1 0 0);
|
||||||
--foreground: oklch(0.145 0 0);
|
--foreground: oklch(0.145 0 0);
|
||||||
@ -181,6 +186,23 @@ input[inputmode='numeric'] {
|
|||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ag-theme-quartz .ag-cell.ag-summary-label-cell {
|
||||||
|
justify-content: center;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ag-theme-quartz .ag-cell.ag-summary-label-cell .ag-cell-wrapper {
|
||||||
|
width: 100%;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ag-theme-quartz .ag-cell.ag-summary-label-cell .ag-cell-value {
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
justify-content: center;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
/* Global AG Grid header alignment: center all header text. */
|
/* Global AG Grid header alignment: center all header text. */
|
||||||
.ag-theme-quartz .ag-header-cell-label,
|
.ag-theme-quartz .ag-header-cell-label,
|
||||||
.ag-theme-quartz .ag-header-group-cell-label {
|
.ag-theme-quartz .ag-header-group-cell-label {
|
||||||
@ -418,12 +440,17 @@ input[inputmode='numeric'] {
|
|||||||
--app-grid-row-h: 2.125rem;
|
--app-grid-row-h: 2.125rem;
|
||||||
--app-grid-font-size: 0.8125rem;
|
--app-grid-font-size: 0.8125rem;
|
||||||
--app-typeline-side-w: 11.5rem;
|
--app-typeline-side-w: 11.5rem;
|
||||||
--app-typeline-gap: 0.625rem;
|
--app-typeline-gap: 0.5625rem;
|
||||||
--app-typeline-label-font: 0.75rem;
|
--app-typeline-label-font: 0.75rem;
|
||||||
--app-typeline-label-line: 0.95rem;
|
--app-typeline-label-line: 0.95rem;
|
||||||
--app-typeline-dot: 1.125rem;
|
--app-typeline-dot: 1rem;
|
||||||
--app-typeline-dot-inner: 0.3125rem;
|
--app-typeline-dot-inner: 0.25rem;
|
||||||
--app-typeline-line-left: 0.5rem;
|
--app-typeline-line-left: 0.5rem;
|
||||||
|
--app-typeline-arrow-offset: 0.125rem;
|
||||||
|
--app-typeline-arrow-shaft-h: 0.8125rem;
|
||||||
|
--app-typeline-arrow-line-w: 0.09375rem;
|
||||||
|
--app-typeline-arrow-head-w: 0.4375rem;
|
||||||
|
--app-typeline-arrow-head-h: 0.3125rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -439,12 +466,17 @@ input[inputmode='numeric'] {
|
|||||||
--app-grid-row-h: 2.25rem;
|
--app-grid-row-h: 2.25rem;
|
||||||
--app-grid-font-size: 0.875rem;
|
--app-grid-font-size: 0.875rem;
|
||||||
--app-typeline-side-w: 12rem;
|
--app-typeline-side-w: 12rem;
|
||||||
--app-typeline-gap: 0.6875rem;
|
--app-typeline-gap: 0.59375rem;
|
||||||
--app-typeline-label-font: 0.8125rem;
|
--app-typeline-label-font: 0.8125rem;
|
||||||
--app-typeline-label-line: 1rem;
|
--app-typeline-label-line: 1rem;
|
||||||
--app-typeline-dot: 1.1875rem;
|
--app-typeline-dot: 1.0625rem;
|
||||||
--app-typeline-dot-inner: 0.375rem;
|
--app-typeline-dot-inner: 0.3125rem;
|
||||||
--app-typeline-line-left: 0.5625rem;
|
--app-typeline-line-left: 0.5625rem;
|
||||||
|
--app-typeline-arrow-offset: 0.125rem;
|
||||||
|
--app-typeline-arrow-shaft-h: 0.875rem;
|
||||||
|
--app-typeline-arrow-line-w: 0.09375rem;
|
||||||
|
--app-typeline-arrow-head-w: 0.46875rem;
|
||||||
|
--app-typeline-arrow-head-h: 0.34375rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -460,12 +492,17 @@ input[inputmode='numeric'] {
|
|||||||
--app-grid-row-h: 2.25rem;
|
--app-grid-row-h: 2.25rem;
|
||||||
--app-grid-font-size: 0.875rem;
|
--app-grid-font-size: 0.875rem;
|
||||||
--app-typeline-side-w: 12.5rem;
|
--app-typeline-side-w: 12.5rem;
|
||||||
--app-typeline-gap: 0.75rem;
|
--app-typeline-gap: 0.625rem;
|
||||||
--app-typeline-label-font: 0.875rem;
|
--app-typeline-label-font: 0.875rem;
|
||||||
--app-typeline-label-line: 1.1rem;
|
--app-typeline-label-line: 1.1rem;
|
||||||
--app-typeline-dot: 1.25rem;
|
--app-typeline-dot: 1.125rem;
|
||||||
--app-typeline-dot-inner: 0.4375rem;
|
--app-typeline-dot-inner: 0.375rem;
|
||||||
--app-typeline-line-left: 0.625rem;
|
--app-typeline-line-left: 0.625rem;
|
||||||
|
--app-typeline-arrow-offset: 0.125rem;
|
||||||
|
--app-typeline-arrow-shaft-h: 0.9375rem;
|
||||||
|
--app-typeline-arrow-line-w: 0.09375rem;
|
||||||
|
--app-typeline-arrow-head-w: 0.5rem;
|
||||||
|
--app-typeline-arrow-head-h: 0.375rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -481,12 +518,17 @@ input[inputmode='numeric'] {
|
|||||||
--app-grid-row-h: 2.5rem;
|
--app-grid-row-h: 2.5rem;
|
||||||
--app-grid-font-size: 0.9375rem;
|
--app-grid-font-size: 0.9375rem;
|
||||||
--app-typeline-side-w: 13rem;
|
--app-typeline-side-w: 13rem;
|
||||||
--app-typeline-gap: 0.8125rem;
|
--app-typeline-gap: 0.6875rem;
|
||||||
--app-typeline-label-font: 0.9375rem;
|
--app-typeline-label-font: 0.9375rem;
|
||||||
--app-typeline-label-line: 1.2rem;
|
--app-typeline-label-line: 1.2rem;
|
||||||
--app-typeline-dot: 1.375rem;
|
--app-typeline-dot: 1.25rem;
|
||||||
--app-typeline-dot-inner: 0.5rem;
|
--app-typeline-dot-inner: 0.4375rem;
|
||||||
--app-typeline-line-left: 0.6875rem;
|
--app-typeline-line-left: 0.6875rem;
|
||||||
|
--app-typeline-arrow-offset: 0.15625rem;
|
||||||
|
--app-typeline-arrow-shaft-h: 1rem;
|
||||||
|
--app-typeline-arrow-line-w: 0.125rem;
|
||||||
|
--app-typeline-arrow-head-w: 0.5625rem;
|
||||||
|
--app-typeline-arrow-head-h: 0.40625rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -502,12 +544,17 @@ input[inputmode='numeric'] {
|
|||||||
--app-grid-row-h: 2.625rem;
|
--app-grid-row-h: 2.625rem;
|
||||||
--app-grid-font-size: 1rem;
|
--app-grid-font-size: 1rem;
|
||||||
--app-typeline-side-w: 13.5rem;
|
--app-typeline-side-w: 13.5rem;
|
||||||
--app-typeline-gap: 0.875rem;
|
--app-typeline-gap: 0.75rem;
|
||||||
--app-typeline-label-font: 1rem;
|
--app-typeline-label-font: 1rem;
|
||||||
--app-typeline-label-line: 1.25rem;
|
--app-typeline-label-line: 1.25rem;
|
||||||
--app-typeline-dot: 1.5rem;
|
--app-typeline-dot: 1.375rem;
|
||||||
--app-typeline-dot-inner: 0.5625rem;
|
--app-typeline-dot-inner: 0.5rem;
|
||||||
--app-typeline-line-left: 0.75rem;
|
--app-typeline-line-left: 0.75rem;
|
||||||
|
--app-typeline-arrow-offset: 0.1875rem;
|
||||||
|
--app-typeline-arrow-shaft-h: 1.125rem;
|
||||||
|
--app-typeline-arrow-line-w: 0.125rem;
|
||||||
|
--app-typeline-arrow-head-w: 0.625rem;
|
||||||
|
--app-typeline-arrow-head-h: 0.4375rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -523,11 +570,16 @@ input[inputmode='numeric'] {
|
|||||||
--app-grid-row-h: 2.875rem;
|
--app-grid-row-h: 2.875rem;
|
||||||
--app-grid-font-size: 1.0625rem;
|
--app-grid-font-size: 1.0625rem;
|
||||||
--app-typeline-side-w: 14.5rem;
|
--app-typeline-side-w: 14.5rem;
|
||||||
--app-typeline-gap: 1rem;
|
--app-typeline-gap: 0.875rem;
|
||||||
--app-typeline-label-font: 1.0625rem;
|
--app-typeline-label-font: 1.0625rem;
|
||||||
--app-typeline-label-line: 1.35rem;
|
--app-typeline-label-line: 1.35rem;
|
||||||
--app-typeline-dot: 1.625rem;
|
--app-typeline-dot: 1.5rem;
|
||||||
--app-typeline-dot-inner: 0.625rem;
|
--app-typeline-dot-inner: 0.5625rem;
|
||||||
--app-typeline-line-left: 0.8125rem;
|
--app-typeline-line-left: 0.8125rem;
|
||||||
|
--app-typeline-arrow-offset: 0.21875rem;
|
||||||
|
--app-typeline-arrow-shaft-h: 1.25rem;
|
||||||
|
--app-typeline-arrow-line-w: 0.125rem;
|
||||||
|
--app-typeline-arrow-head-w: 0.6875rem;
|
||||||
|
--app-typeline-arrow-head-h: 0.5rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -213,8 +213,15 @@ export interface MajorLite {
|
|||||||
/** 咨询服务字典精简信息 */
|
/** 咨询服务字典精简信息 */
|
||||||
export interface ServiceLite {
|
export interface ServiceLite {
|
||||||
defCoe: number | null
|
defCoe: number | null
|
||||||
|
enableInvestScale?: boolean | null
|
||||||
|
enableLandScale?: boolean | null
|
||||||
|
investScaleSingleTotal?: boolean | null
|
||||||
|
scale?: boolean | null
|
||||||
|
/** legacy fallback; new code should use investScaleSingleTotal */
|
||||||
onlyCostScale?: boolean | null
|
onlyCostScale?: boolean | null
|
||||||
mutiple?: boolean | null
|
mutiple?: boolean | null
|
||||||
|
amount?: boolean | null
|
||||||
|
workDay?: boolean | null
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 工作量法任务字典 */
|
/** 工作量法任务字典 */
|
||||||
|
|||||||
@ -1 +1 @@
|
|||||||
{"root":["./src/main.ts","./src/sql.ts","./src/components/ui/button/index.ts","./src/components/ui/card/index.ts","./src/components/ui/scroll-area/index.ts","./src/components/ui/tooltip/index.ts","./src/features/ht/contracts.ts","./src/features/ht/importexport.ts","./src/features/ht/types.ts","./src/features/tab/importexport.ts","./src/features/tab/types.ts","./src/i18n/dictionary-en.ts","./src/i18n/index.ts","./src/i18n/locales/en-us.ts","./src/i18n/locales/zh-cn.ts","./src/lib/aggridreadonlyautoheight.ts","./src/lib/aggridresetheader.ts","./src/lib/contractsegment.ts","./src/lib/decimal.ts","./src/lib/diyaggridoptions.ts","./src/lib/number.ts","./src/lib/numberformat.ts","./src/lib/pricinghourlycalc.ts","./src/lib/pricingmethodtotals.ts","./src/lib/pricingpersistcontrol.ts","./src/lib/pricingpinnedrows.ts","./src/lib/pricingscalecalc.ts","./src/lib/pricingscalecolumns.ts","./src/lib/pricingscaledetail.ts","./src/lib/pricingscaledict.ts","./src/lib/pricingscalefee.ts","./src/lib/pricingscalegrid.ts","./src/lib/pricingscalelink.ts","./src/lib/pricingscalepanedata.ts","./src/lib/pricingscalepanelifecycle.ts","./src/lib/pricingscaleproject.ts","./src/lib/pricingscalerowmap.ts","./src/lib/pricingworkloadcalc.ts","./src/lib/projectevents.ts","./src/lib/projectkvstore.ts","./src/lib/projectregistry.ts","./src/lib/projectsessionlock.ts","./src/lib/projectworkspace.ts","./src/lib/reportexportbuilders.ts","./src/lib/utils.ts","./src/lib/workspace.ts","./src/lib/xmfactordefaults.ts","./src/lib/zwarchive.ts","./src/lib/zxfwpricingsync.ts","./src/pinia/kv.ts","./src/pinia/tab.ts","./src/pinia/uiprefs.ts","./src/pinia/zx.ts","./src/pinia/zxfwpricing.ts","./src/pinia/zxfwpricinghtfee.ts","./src/pinia/zxfwpricingkeys.ts","./src/pinia/plugin/indexdb.ts","./src/pinia/plugin/types.d.ts","./src/types/pricing.ts","./src/app.vue","./src/components/ui/button/button.vue","./src/components/ui/card/card.vue","./src/components/ui/card/cardaction.vue","./src/components/ui/card/cardcontent.vue","./src/components/ui/card/carddescription.vue","./src/components/ui/card/cardfooter.vue","./src/components/ui/card/cardheader.vue","./src/components/ui/card/cardtitle.vue","./src/components/ui/scroll-area/scrollarea.vue","./src/components/ui/scroll-area/scrollbar.vue","./src/components/ui/tooltip/tooltipcontent.vue","./src/features/ht/components/ht.vue","./src/features/ht/components/htadditionalworkfee.vue","./src/features/ht/components/htbaseinfo.vue","./src/features/ht/components/htconsultcategoryfactor.vue","./src/features/ht/components/htcontractsummary.vue","./src/features/ht/components/htfeeratemethodform.vue","./src/features/ht/components/htmajorfactor.vue","./src/features/ht/components/htreservefee.vue","./src/features/ht/components/htcard.vue","./src/features/ht/components/htinfo.vue","./src/features/ht/components/zxfw.vue","./src/features/pricing/components/hourlypricingpane.vue","./src/features/pricing/components/investmentscalepricingpane.vue","./src/features/pricing/components/landscalepricingpane.vue","./src/features/pricing/components/otherservice.vue","./src/features/pricing/components/scaleformulareadonlypane.vue","./src/features/pricing/components/workloadpricingpane.vue","./src/features/shared/components/hourlyfeegrid.vue","./src/features/shared/components/htfeegrid.vue","./src/features/shared/components/htfeemethodgrid.vue","./src/features/shared/components/methodunavailablenotice.vue","./src/features/shared/components/servicecheckboxselector.vue","./src/features/shared/components/workcontentgrid.vue","./src/features/shared/components/xmfactorgrid.vue","./src/features/shared/components/xmcommonaggrid.vue","./src/features/workbench/components/homeentryview.vue","./src/features/workbench/components/htfeemethodtypelineview.vue","./src/features/workbench/components/quickcalcworkbenchview.vue","./src/features/workbench/components/zxfwview.vue","./src/features/xm/components/xmconsultcategoryfactor.vue","./src/features/xm/components/xmmajorfactor.vue","./src/features/xm/components/info.vue","./src/features/xm/components/xmcard.vue","./src/features/xm/components/xminfo.vue","./src/layout/tab.vue","./src/layout/typeline.vue"],"errors":true,"version":"5.9.3"}
|
{"root":["./src/main.ts","./src/router.ts","./src/sql.ts","./src/components/ui/button/index.ts","./src/components/ui/card/index.ts","./src/components/ui/scroll-area/index.ts","./src/components/ui/tooltip/index.ts","./src/features/ht/contracts.ts","./src/features/ht/importexport.ts","./src/features/ht/types.ts","./src/features/tab/importexport.ts","./src/features/tab/types.ts","./src/i18n/dictionary-en.ts","./src/i18n/index.ts","./src/i18n/locales/en-us.ts","./src/i18n/locales/zh-cn.ts","./src/lib/aggridreadonlyautoheight.ts","./src/lib/aggridresetheader.ts","./src/lib/contractsegment.ts","./src/lib/decimal.ts","./src/lib/diyaggridoptions.ts","./src/lib/number.ts","./src/lib/numberformat.ts","./src/lib/pricinghourlycalc.ts","./src/lib/pricingmethodtotals.ts","./src/lib/pricingpersistcontrol.ts","./src/lib/pricingpinnedrows.ts","./src/lib/pricingscalecalc.ts","./src/lib/pricingscalecolumns.ts","./src/lib/pricingscaledetail.ts","./src/lib/pricingscaledict.ts","./src/lib/pricingscalefee.ts","./src/lib/pricingscalegrid.ts","./src/lib/pricingscalelink.ts","./src/lib/pricingscalepanedata.ts","./src/lib/pricingscalepanelifecycle.ts","./src/lib/pricingscaleproject.ts","./src/lib/pricingscalerowmap.ts","./src/lib/pricingworkloadcalc.ts","./src/lib/projectevents.ts","./src/lib/projectkvstore.ts","./src/lib/projectregistry.ts","./src/lib/projectsessionlock.ts","./src/lib/projectworkspace.ts","./src/lib/reportexportbuilders.ts","./src/lib/servicepricing.ts","./src/lib/utils.ts","./src/lib/workspace.ts","./src/lib/xmfactordefaults.ts","./src/lib/zwarchive.ts","./src/lib/zxfwpricingsync.ts","./src/pinia/kv.ts","./src/pinia/tab.ts","./src/pinia/uiprefs.ts","./src/pinia/zxfwpricing.ts","./src/pinia/zxfwpricinghtfee.ts","./src/pinia/zxfwpricingkeys.ts","./src/pinia/plugin/indexdb.ts","./src/pinia/plugin/types.d.ts","./src/types/pricing.ts","./src/app.vue","./src/components/ui/button/button.vue","./src/components/ui/card/card.vue","./src/components/ui/card/cardaction.vue","./src/components/ui/card/cardcontent.vue","./src/components/ui/card/carddescription.vue","./src/components/ui/card/cardfooter.vue","./src/components/ui/card/cardheader.vue","./src/components/ui/card/cardtitle.vue","./src/components/ui/scroll-area/scrollarea.vue","./src/components/ui/scroll-area/scrollbar.vue","./src/components/ui/tooltip/tooltipcontent.vue","./src/features/app/components/workspaceshell.vue","./src/features/disclaimer/components/disclaimerpage.vue","./src/features/ht/components/ht.vue","./src/features/ht/components/htadditionalworkfee.vue","./src/features/ht/components/htbaseinfo.vue","./src/features/ht/components/htconsultcategoryfactor.vue","./src/features/ht/components/htcontractsummary.vue","./src/features/ht/components/htfeeratemethodform.vue","./src/features/ht/components/htmajorfactor.vue","./src/features/ht/components/htreservefee.vue","./src/features/ht/components/htcard.vue","./src/features/ht/components/htinfo.vue","./src/features/ht/components/zxfw.vue","./src/features/pricing/components/hourlypricingpane.vue","./src/features/pricing/components/investmentscalepricingpane.vue","./src/features/pricing/components/landscalepricingpane.vue","./src/features/pricing/components/scaleformulareadonlypane.vue","./src/features/pricing/components/workloadpricingpane.vue","./src/features/shared/components/hourlyfeegrid.vue","./src/features/shared/components/htfeegrid.vue","./src/features/shared/components/htfeemethodgrid.vue","./src/features/shared/components/methodunavailablenotice.vue","./src/features/shared/components/servicecheckboxselector.vue","./src/features/shared/components/workcontentgrid.vue","./src/features/shared/components/xmfactorgrid.vue","./src/features/shared/components/xmcommonaggrid.vue","./src/features/workbench/components/homeentryview.vue","./src/features/workbench/components/htfeemethodtypelineview.vue","./src/features/workbench/components/quickcalcworkbenchview.vue","./src/features/workbench/components/zxfwview.vue","./src/features/xm/components/xmconsultcategoryfactor.vue","./src/features/xm/components/xmmajorfactor.vue","./src/features/xm/components/info.vue","./src/features/xm/components/xmcard.vue","./src/features/xm/components/xminfo.vue","./src/layout/tab.vue","./src/layout/typeline.vue"],"version":"5.9.3"}
|
||||||
Loading…
x
Reference in New Issue
Block a user