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",
|
||||
"clsx": "^2.1.1",
|
||||
"decimal.js": "^10.6.0",
|
||||
"docxtemplater": "^3.68.5",
|
||||
"exceljs": "^4.4.0",
|
||||
"file-saver": "^2.0.5",
|
||||
"localforage": "^1.10.0",
|
||||
"lucide-vue-next": "^0.563.0",
|
||||
"motion-v": "^2.0.0",
|
||||
"pinia": "^3.0.4",
|
||||
"pinia-plugin-persistedstate": "^4.7.1",
|
||||
"pizzip": "^3.2.0",
|
||||
"reka-ui": "^2.8.0",
|
||||
"tailwind-merge": "^3.4.0",
|
||||
"tailwindcss": "^4.1.18",
|
||||
"vue": "^3.5.25",
|
||||
"vue-i18n": "^11.3.0",
|
||||
"vue-router": "^4.6.4",
|
||||
"vuedraggable": "^4.1.0",
|
||||
},
|
||||
"devDependencies": {
|
||||
@ -47,554 +45,548 @@
|
||||
},
|
||||
},
|
||||
"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=="],
|
||||
|
||||
"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=="],
|
||||
"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=="],
|
||||
}
|
||||
}
|
||||
|
||||
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" />
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.ico" />
|
||||
<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>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
||||
67
package-lock.json
generated
67
package-lock.json
generated
@ -21,20 +21,18 @@
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"decimal.js": "^10.6.0",
|
||||
"docxtemplater": "^3.68.5",
|
||||
"exceljs": "^4.4.0",
|
||||
"file-saver": "^2.0.5",
|
||||
"localforage": "^1.10.0",
|
||||
"lucide-vue-next": "^0.563.0",
|
||||
"motion-v": "^2.0.0",
|
||||
"pinia": "^3.0.4",
|
||||
"pinia-plugin-persistedstate": "^4.7.1",
|
||||
"pizzip": "^3.2.0",
|
||||
"reka-ui": "^2.8.0",
|
||||
"tailwind-merge": "^3.4.0",
|
||||
"tailwindcss": "^4.1.18",
|
||||
"vue": "^3.5.25",
|
||||
"vue-i18n": "^11.3.0",
|
||||
"vue-router": "^4.6.4",
|
||||
"vuedraggable": "^4.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
@ -659,15 +657,6 @@
|
||||
"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": {
|
||||
"version": "13.1.0",
|
||||
"license": "MIT",
|
||||
@ -1024,18 +1013,6 @@
|
||||
"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": {
|
||||
"version": "0.1.4",
|
||||
"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": {
|
||||
"version": "12.34.3",
|
||||
"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": {
|
||||
"version": "8.5.6",
|
||||
"funding": [
|
||||
@ -2172,6 +2128,27 @@
|
||||
"integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==",
|
||||
"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": {
|
||||
"version": "3.2.5",
|
||||
"dev": true,
|
||||
|
||||
13
package.json
13
package.json
@ -1,13 +1,14 @@
|
||||
{
|
||||
"name": "my-vue-app",
|
||||
"name": "ZWJJ2026",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"version": "1.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "bunx --bun vite",
|
||||
"build": " bunx --bun vite build",
|
||||
"build": "bunx vue-tsc -b && bunx --bun vite build",
|
||||
"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": {
|
||||
"@ag-grid-community/locale": "^35.1.0",
|
||||
@ -23,20 +24,18 @@
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"decimal.js": "^10.6.0",
|
||||
"docxtemplater": "^3.68.5",
|
||||
"exceljs": "^4.4.0",
|
||||
"file-saver": "^2.0.5",
|
||||
"localforage": "^1.10.0",
|
||||
"lucide-vue-next": "^0.563.0",
|
||||
"motion-v": "^2.0.0",
|
||||
"pinia": "^3.0.4",
|
||||
"pinia-plugin-persistedstate": "^4.7.1",
|
||||
"pizzip": "^3.2.0",
|
||||
"reka-ui": "^2.8.0",
|
||||
"tailwind-merge": "^3.4.0",
|
||||
"tailwindcss": "^4.1.18",
|
||||
"vue": "^3.5.25",
|
||||
"vue-i18n": "^11.3.0",
|
||||
"vue-router": "^4.6.4",
|
||||
"vuedraggable": "^4.1.0"
|
||||
},
|
||||
"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 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="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>
|
||||
<RouterView />
|
||||
</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,
|
||||
ToastViewport
|
||||
} from 'reka-ui'
|
||||
import {useDataStore} from '@/pinia/zx'
|
||||
|
||||
const STORAGE_KEY = 'ht-card-v1'
|
||||
const tabStore = useTabStore()
|
||||
@ -334,18 +333,8 @@ const loadContractBudgetFee = async (contractId: string) => {
|
||||
loadHtMainTotalFee(`htExtraFee-${contractId}-additional-work`),
|
||||
loadHtMainTotalFee(`htExtraFee-${contractId}-reserve`)
|
||||
])
|
||||
|
||||
const zxRows = await useDataStore().query([
|
||||
{ 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)*/
|
||||
const parts = [serviceFee, additionalFee, reserveFee]
|
||||
const total = sumNullableNumbers(parts)
|
||||
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 { useI18n } from 'vue-i18n'
|
||||
import { AgGridVue } from 'ag-grid-vue3'
|
||||
@ -60,23 +60,12 @@ const rowData = ref<SummaryRow[]>([])
|
||||
const explanationText = ref('')
|
||||
let reloadTimer: ReturnType<typeof setTimeout> | null = null
|
||||
|
||||
/**
|
||||
* 对数值数组求和(保留3位小数)
|
||||
* @param values 数值数组(可包含 null/undefined)
|
||||
* @returns 求和结果(无有效值时返回 null)
|
||||
*/
|
||||
const sum3 = (values: Array<number | null | undefined>) => {
|
||||
const valid = values.filter((v): v is number => toFiniteNumberOrNull(v) != null)
|
||||
if (valid.length === 0) return null
|
||||
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 rows = Array.isArray(state?.detailRows) ? state.detailRows : []
|
||||
if (rows.length === 0) return null
|
||||
@ -99,13 +88,6 @@ const sumHourlyMethodFee = (state: HourlyMethodStateLike | null): number | 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 rows = Array.isArray(state?.detailRows) ? state.detailRows : []
|
||||
if (rows.length === 0) return null
|
||||
@ -128,12 +110,6 @@ const sumQuantityMethodFee = (state: QuantityMethodStateLike | null): number | n
|
||||
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<{
|
||||
subtotal: 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 (
|
||||
rowType: 'additional' | 'reserve',
|
||||
list: Array<{ id: string | number; name: string; code: unknown }>
|
||||
@ -212,11 +182,6 @@ const buildFeeRows = async (
|
||||
return { rows, explainLines }
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建咨询服务汇总行数据
|
||||
* 从合同状态中筛选已选中的服务项,转换为 SummaryRow 格式
|
||||
* @returns 咨询服务汇总行数组
|
||||
*/
|
||||
const buildServiceRows = (): SummaryRow[] => {
|
||||
const contractState = zxFwPricingStore.getContractState(props.contractId)
|
||||
const selectedSet = new Set((contractState?.selectedIds || []).map(id => String(id)))
|
||||
@ -237,11 +202,6 @@ const buildServiceRows = (): SummaryRow[] => {
|
||||
}))
|
||||
}
|
||||
|
||||
/**
|
||||
* 重新加载所有汇总行数据
|
||||
* 包括:咨询服务、附加工作、预备费
|
||||
* 同时生成说明文本
|
||||
*/
|
||||
const reloadRows = async () => {
|
||||
await zxFwPricingStore.loadContract(props.contractId)
|
||||
const [additionalResult, reserveResult] = await Promise.all([
|
||||
@ -259,10 +219,6 @@ const reloadRows = async () => {
|
||||
explanationText.value = lines.join('\n')
|
||||
}
|
||||
|
||||
/**
|
||||
* 延迟重新加载汇总数据(防抖 80ms)
|
||||
* 避免频繁触发数据加载
|
||||
*/
|
||||
const scheduleReload = () => {
|
||||
if (reloadTimer) clearTimeout(reloadTimer)
|
||||
reloadTimer = setTimeout(() => {
|
||||
@ -270,10 +226,6 @@ const scheduleReload = () => {
|
||||
}, 80)
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新签名(用于 watch 监听数据变化)
|
||||
* 监听合同数据、附加工作、预备费的主状态和方法状态变化
|
||||
*/
|
||||
const refreshSignature = computed(() => {
|
||||
const additionalKey = `htExtraFee-${props.contractId}-additional-work`
|
||||
const reserveKey = `htExtraFee-${props.contractId}-reserve`
|
||||
@ -286,10 +238,6 @@ const refreshSignature = computed(() => {
|
||||
})
|
||||
})
|
||||
|
||||
/**
|
||||
* 计算总计行数据
|
||||
* 对所有汇总行的各字段进行求和
|
||||
*/
|
||||
const totalRow = computed<SummaryRow>(() => {
|
||||
const sumField = (pick: (row: SummaryRow) => number | null | undefined) =>
|
||||
sum3(rowData.value.map(pick))
|
||||
@ -354,6 +302,9 @@ const columnDefs: ColDef<SummaryRow>[] = [
|
||||
minWidth: 90,
|
||||
maxWidth: 140,
|
||||
colSpan: params => (params.data?.rowType === 'total' ? 2 : 1),
|
||||
cellClassRules: {
|
||||
'ag-summary-label-cell': params => params.data?.rowType === 'total'
|
||||
},
|
||||
valueFormatter: params => {
|
||||
if (params.data?.rowType === 'total') return params.data.name || t('htSummary.total')
|
||||
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' : '')
|
||||
}
|
||||
|
||||
/**
|
||||
* AG Grid 准备就绪回调
|
||||
* 初始化 gridApi 并同步自动行高
|
||||
* @param event Grid 准备就绪事件
|
||||
*/
|
||||
const onGridReady = (event: GridReadyEvent<SummaryRow>) => {
|
||||
gridApi.value = event.api
|
||||
void syncAutoRowHeights()
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查 Grid API 是否可用
|
||||
* @param api Grid API 实例
|
||||
* @returns API 是否有效
|
||||
*/
|
||||
const isGridApiAlive = (api: GridApi<SummaryRow> | null | undefined): api is GridApi<SummaryRow> =>
|
||||
Boolean(api && !api.isDestroyed?.())
|
||||
|
||||
/**
|
||||
* 同步自动行高
|
||||
* 触发 AG Grid 重新计算行高并刷新单元格
|
||||
*/
|
||||
const syncAutoRowHeights = async () => {
|
||||
await nextTick()
|
||||
const api = gridApi.value
|
||||
@ -501,20 +438,10 @@ const syncAutoRowHeights = async () => {
|
||||
api.refreshCells({ force: true })
|
||||
}
|
||||
|
||||
/**
|
||||
* 首次数据渲染完成回调
|
||||
* 同步自动行高以确保显示正确
|
||||
* @param _event 首次数据渲染事件
|
||||
*/
|
||||
const onFirstDataRendered = (_event: FirstDataRenderedEvent<SummaryRow>) => {
|
||||
void syncAutoRowHeights()
|
||||
}
|
||||
|
||||
/**
|
||||
* 行数据更新回调
|
||||
* 同步自动行高以适应新数据
|
||||
* @param _event 行数据更新事件
|
||||
*/
|
||||
const onRowDataUpdated = (_event: RowDataUpdatedEvent<SummaryRow>) => {
|
||||
void syncAutoRowHeights()
|
||||
}
|
||||
|
||||
@ -19,7 +19,6 @@ import TypeLine from '@/layout/typeLine.vue';
|
||||
import { useZxFwPricingStore } from '@/pinia/zxFwPricing'
|
||||
import { roundTo, sumNullableNumbers, toFiniteNumber } from '@/lib/decimal'
|
||||
import { formatThousands } from '@/lib/numberFormat'
|
||||
import {useDataStore} from '@/pinia/zx'
|
||||
|
||||
// 1. 完善 Props 类型 + 添加校验(可选但推荐)
|
||||
const props = defineProps<{
|
||||
@ -153,15 +152,7 @@ const refreshContractBudget = async () => {
|
||||
])
|
||||
const parts = [serviceFee, additionalFee, reserveFee]
|
||||
const total = sumNullableNumbers(parts)
|
||||
|
||||
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)
|
||||
contractBudget.value = total == null ? null : roundTo(total, 2)
|
||||
}
|
||||
|
||||
const budgetRefreshSignature = computed(() => {
|
||||
@ -343,11 +334,11 @@ const xmCategories = computed<XmCategoryItem[]>(() => [
|
||||
{ key: 'base-info', label: t('htCard.categories.baseInfo'), component: htBaseInfoView },
|
||||
{ key: 'info', label: t('htCard.categories.scaleInfo'), component: htView },
|
||||
{ key: 'contract', label: t('htCard.categories.services'), component: zxfwView },
|
||||
// { key: 'consult-category-factor', label: t('htCard.categories.consultFactor'), component: consultCategoryFactorView },
|
||||
// { key: 'major-factor', label: t('htCard.categories.majorFactor'), component: majorFactorView },
|
||||
// { key: 'additional-work-fee', label: t('htCard.categories.additionalFee'), component: additionalWorkFeeView },
|
||||
// { key: 'reserve-fee', label: t('htCard.categories.reserveFee'), component: reserveFeeView },
|
||||
// { key: 'all', label: t('htCard.categories.summary'), component: summaryView },
|
||||
{ key: 'consult-category-factor', label: t('htCard.categories.consultFactor'), component: consultCategoryFactorView },
|
||||
{ key: 'major-factor', label: t('htCard.categories.majorFactor'), component: majorFactorView },
|
||||
{ key: 'additional-work-fee', label: t('htCard.categories.additionalFee'), component: additionalWorkFeeView },
|
||||
{ key: 'reserve-fee', label: t('htCard.categories.reserveFee'), component: reserveFeeView },
|
||||
{ key: 'all', label: t('htCard.categories.summary'), component: summaryView },
|
||||
]);
|
||||
|
||||
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 { t } = useI18n()
|
||||
const titleText = computed(() => `${t('htInfo.scaleDetailTitle')}:${t('htInfo.scaleDetailHint')}`)
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<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>
|
||||
|
||||
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,
|
||||
majorFactor: null,
|
||||
workStageFactor: 1,
|
||||
workRatio: 100,
|
||||
workRatio: 1,
|
||||
budgetFee: null,
|
||||
budgetFeeBasic: 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 { AG_GRID_LOCALE_CN } from '@ag-grid-community/locale';
|
||||
import {useDataStore} from "@/pinia/zx";
|
||||
|
||||
interface DetailRow {
|
||||
id: string
|
||||
@ -31,12 +30,9 @@ interface DetailRow {
|
||||
workload: number | null
|
||||
basicFee: number | null
|
||||
budgetBase: string
|
||||
budgetReferenceUnitPrice: string | number | null
|
||||
budgetReferenceUnitPrice: string
|
||||
budgetAdoptedUnitPrice: number | null
|
||||
consultCategoryFactor: number | null
|
||||
cLow: number | null
|
||||
cMid: number | null
|
||||
cHigh: number | null
|
||||
serviceFee: number | null
|
||||
remark: string
|
||||
path: string[]
|
||||
@ -49,7 +45,7 @@ interface XmInfoState {
|
||||
|
||||
const props = defineProps<{
|
||||
contractId: string,
|
||||
contractName: string,
|
||||
|
||||
serviceId: string | number
|
||||
}>()
|
||||
const zxFwPricingStore = useZxFwPricingStore()
|
||||
@ -64,28 +60,15 @@ let factorDefaultsLoaded = false
|
||||
const paneInstanceCreatedAt = Date.now()
|
||||
const gridApi = ref<GridApi<DetailRow> | null>(null)
|
||||
|
||||
/**
|
||||
* 获取服务对应的咨询服务分类因子
|
||||
* @returns 咨询服务分类因子值,如果没有则返回 null
|
||||
*/
|
||||
const getDefaultConsultCategoryFactor = () =>
|
||||
consultCategoryFactorMap.value.get(String(props.serviceId)) ?? null
|
||||
|
||||
/**
|
||||
* 确保咨询服务分类因子默认值已加载
|
||||
* 从合同段的咨询因子配置中加载,避免重复加载
|
||||
*/
|
||||
const ensureFactorDefaultsLoaded = async () => {
|
||||
if (factorDefaultsLoaded) return
|
||||
consultCategoryFactorMap.value = await loadConsultCategoryFactorMap(HT_CONSULT_FACTOR_KEY.value)
|
||||
factorDefaultsLoaded = true
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否应该跳过数据持久化
|
||||
* 用于在清空操作后的短时间内避免旧数据回填
|
||||
* @returns 是否应该跳过持久化
|
||||
*/
|
||||
const shouldSkipPersist = () => {
|
||||
const storageKey = buildProjectScopedSessionKey(PRICING_CLEAR_SKIP_PREFIX, DB_KEY.value)
|
||||
const raw = sessionStorage.getItem(storageKey)
|
||||
@ -109,11 +92,6 @@ const shouldSkipPersist = () => {
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否应该强制加载默认数据
|
||||
* 用于在恢复默认操作后强制使用初始值
|
||||
* @returns 是否应该强制加载默认数据
|
||||
*/
|
||||
const shouldForceDefaultLoad = () => {
|
||||
const storageKey = buildProjectScopedSessionKey(PRICING_FORCE_DEFAULT_PREFIX, DB_KEY.value)
|
||||
const raw = sessionStorage.getItem(storageKey)
|
||||
@ -123,12 +101,8 @@ const shouldForceDefaultLoad = () => {
|
||||
return Number.isFinite(forceUntil) && Date.now() <= forceUntil
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前服务工作量法的状态
|
||||
* @returns 工作量法状态对象,包含明细行数据
|
||||
*/
|
||||
const getMethodState = () =>
|
||||
zxFwPricingStore.getServicePricingMethodState<DetailRow>(props.contractId, props.serviceId, 'serviceFee')
|
||||
zxFwPricingStore.getServicePricingMethodState<DetailRow>(props.contractId, props.serviceId, 'workload')
|
||||
|
||||
const detailRows = computed<DetailRow[]>({
|
||||
get: () => {
|
||||
@ -136,7 +110,7 @@ const detailRows = computed<DetailRow[]>({
|
||||
return Array.isArray(rows) ? rows : []
|
||||
},
|
||||
set: rows => {
|
||||
zxFwPricingStore.setServicePricingMethodState(props.contractId, props.serviceId, 'serviceFee', {
|
||||
zxFwPricingStore.setServicePricingMethodState(props.contractId, props.serviceId, 'workload', {
|
||||
detailRows: rows
|
||||
})
|
||||
}
|
||||
@ -153,17 +127,9 @@ type taskLite = {
|
||||
maxPrice: number | null
|
||||
minPrice: number | null
|
||||
defPrice: number | null
|
||||
midPrice: number | null
|
||||
highPrice: number | null
|
||||
desc: string | null
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取任务显示名称
|
||||
* 根据当前语言环境返回中文或英文名称
|
||||
* @param task 任务对象
|
||||
* @returns 任务显示名称
|
||||
*/
|
||||
const getTaskDisplayName = (task: taskLite | undefined) => {
|
||||
if (!task) return ''
|
||||
return String(locale.value).toLowerCase().startsWith('en')
|
||||
@ -171,12 +137,6 @@ const getTaskDisplayName = (task: taskLite | undefined) => {
|
||||
: task.name
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化任务参考单价
|
||||
* 根据任务的最小/最大价格生成价格区间字符串
|
||||
* @param task 任务对象
|
||||
* @returns 格式化后的价格区间字符串(如 "100元-200元")
|
||||
*/
|
||||
const formatTaskReferenceUnitPrice = (task: taskLite) => {
|
||||
const unit = task.unit || ''
|
||||
const hasMin = typeof task.minPrice === 'number' && Number.isFinite(task.minPrice)
|
||||
@ -187,16 +147,8 @@ const formatTaskReferenceUnitPrice = (task: taskLite) => {
|
||||
return ''
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前服务对应的工作任务 ID 列表
|
||||
* 从 taskList 中筛选出属于当前服务的任务,并按 ID 排序
|
||||
* @returns 任务 ID 数组
|
||||
*/
|
||||
const getSourceTaskIds = () => {
|
||||
// 从 serviceId 中提取最后的数字部分,例如:ct-1776069559338-f621-zx-9 -> 9
|
||||
const serviceIdStr = String(props.serviceId)
|
||||
const match = serviceIdStr.match(/-(\d+)$/)
|
||||
const currentServiceId = match ? Number(match[1]) : NaN
|
||||
const currentServiceId = Number(props.serviceId)
|
||||
return Object.entries(taskList as Record<string, taskLite>)
|
||||
.filter(([, task]) => Number(task.serviceID) === currentServiceId)
|
||||
.map(([key]) => Number(key))
|
||||
@ -206,11 +158,6 @@ const getSourceTaskIds = () => {
|
||||
|
||||
const isWorkloadMethodApplicable = computed(() => getSourceTaskIds().length > 0)
|
||||
|
||||
/**
|
||||
* 构建默认明细行数据
|
||||
* 根据当前服务的任务列表生成初始行数据
|
||||
* @returns 默认明细行数组
|
||||
*/
|
||||
const buildDefaultRows = (): DetailRow[] => {
|
||||
const rows: DetailRow[] = []
|
||||
const sourceTaskIds = getSourceTaskIds()
|
||||
@ -219,24 +166,22 @@ const buildDefaultRows = (): DetailRow[] => {
|
||||
const task = (taskList as Record<string, taskLite | undefined>)[String(taskId)]
|
||||
const taskCode = task?.code || task?.ref || ''
|
||||
if (!taskCode || !task?.name) continue
|
||||
const contractId = props.contractId;
|
||||
const rowId = `${contractId}-task-${taskId}`
|
||||
const rowId = `task-${taskId}-${order}`
|
||||
rows.push({
|
||||
id: rowId,
|
||||
taskCode,
|
||||
taskName: getTaskDisplayName(task),
|
||||
unit: task.unit || '',
|
||||
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,
|
||||
budgetBase: task.basicParam || '',
|
||||
budgetReferenceUnitPrice: null,
|
||||
budgetReferenceUnitPrice: formatTaskReferenceUnitPrice(task),
|
||||
budgetAdoptedUnitPrice:
|
||||
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,
|
||||
remark: task.desc|| '',
|
||||
type: 'task',
|
||||
path: [rowId]
|
||||
})
|
||||
}
|
||||
@ -246,12 +191,6 @@ const buildDefaultRows = (): DetailRow[] => {
|
||||
|
||||
const isNoTaskRow = (row: DetailRow | undefined) => row?.id?.startsWith('task-none-') ?? false
|
||||
|
||||
/**
|
||||
* 合并数据库中的行数据与默认行数据
|
||||
* 保留用户编辑的值(工作量、单价、因子等),缺失时使用默认值
|
||||
* @param rowsFromDb 从数据库加载的行数据
|
||||
* @returns 合并后的明细行数组
|
||||
*/
|
||||
const mergeWithDictRows = (rowsFromDb: DetailRow[] | undefined): DetailRow[] => {
|
||||
const dbValueMap = new Map<string, DetailRow>()
|
||||
for (const row of rowsFromDb || []) {
|
||||
@ -263,38 +202,20 @@ const mergeWithDictRows = (rowsFromDb: DetailRow[] | undefined): DetailRow[] =>
|
||||
if (!fromDb) return row
|
||||
const hasRemark = Object.prototype.hasOwnProperty.call(fromDb, 'remark')
|
||||
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 {
|
||||
...row,
|
||||
workload: typeof fromDb.workload === 'number' ? fromDb.workload : null,
|
||||
basicFee: typeof fromDb.basicFee === 'number' ? fromDb.basicFee : null,
|
||||
budgetReferenceUnitPrice: fromDb.budgetReferenceUnitPrice ?? row.budgetReferenceUnitPrice,
|
||||
budgetAdoptedUnitPrice: 1, // 固定为 1
|
||||
cLow: cLowValue,
|
||||
cMid: cMidValue,
|
||||
cHigh: cHighValue,
|
||||
budgetAdoptedUnitPrice:
|
||||
typeof fromDb.budgetAdoptedUnitPrice === 'number' ? fromDb.budgetAdoptedUnitPrice : null,
|
||||
consultCategoryFactor:
|
||||
typeof fromDb.consultCategoryFactor === 'number'
|
||||
? fromDb.consultCategoryFactor
|
||||
: hasConsultCategoryFactor
|
||||
? null
|
||||
: getDefaultConsultCategoryFactor(),
|
||||
serviceFee: serviceFee,
|
||||
serviceFee: typeof fromDb.serviceFee === 'number' ? fromDb.serviceFee : null,
|
||||
remark: typeof fromDb.remark === 'string' ? fromDb.remark : hasRemark ? '' : row.remark
|
||||
}
|
||||
})
|
||||
@ -306,12 +227,6 @@ const parseSanitizedNumberOrNull = (value: unknown) =>
|
||||
const parseSanitizedAdoptedPriceOrNull = (value: unknown) =>
|
||||
parseNumberOrNull(value, { sanitize: true, precision: 6 })
|
||||
|
||||
/**
|
||||
* 计算基础费用
|
||||
* 公式:基础费用 = 采用单价 × 换算系数 × 工作量
|
||||
* @param row 明细行数据
|
||||
* @returns 基础费用(保留2位小数),如果数据不完整则返回 null
|
||||
*/
|
||||
const calcBasicFee = (row: DetailRow | undefined) => {
|
||||
if (!row || isNoTaskRow(row)) return null
|
||||
const price = row.budgetAdoptedUnitPrice
|
||||
@ -330,12 +245,6 @@ const calcBasicFee = (row: DetailRow | undefined) => {
|
||||
return roundTo(toDecimal(price).mul(conversion).mul(workload), 2)
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算服务费用
|
||||
* 公式:服务费用 = 基础费用 × 咨询服务分类因子
|
||||
* @param row 明细行数据
|
||||
* @returns 服务费用(保留2位小数),如果数据不完整则返回 null
|
||||
*/
|
||||
const calcServiceFee = (row: DetailRow | undefined) => {
|
||||
if (!row || isNoTaskRow(row)) return null
|
||||
const factor = row.consultCategoryFactor
|
||||
@ -350,12 +259,6 @@ const calcServiceFee = (row: DetailRow | undefined) => {
|
||||
return roundTo(toDecimal(basicFee).mul(factor), 2)
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化可编辑数字单元格
|
||||
* 根据行类型和值状态返回相应的显示文本
|
||||
* @param params AG Grid 单元格参数
|
||||
* @returns 格式化后的显示文本
|
||||
*/
|
||||
const formatEditableNumber = (params: any) => {
|
||||
if (isNoTaskRow(params.data)) return t('workloadPricing.none')
|
||||
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)
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断两行是否应该合并显示(按任务名称和预算基数)
|
||||
* 用于 AG Grid 的 spanRows 功能,相同任务和预算基数的行合并显示
|
||||
* @param params AG Grid 行比较参数
|
||||
* @returns 是否应该合并
|
||||
*/
|
||||
const spanRowsByTaskName = (params: any) => {
|
||||
const rowA = params?.nodeA?.data as DetailRow | undefined
|
||||
const rowB = params?.nodeB?.data as DetailRow | undefined
|
||||
@ -388,6 +285,9 @@ const columnDefs: ColDef<DetailRow>[] = [
|
||||
width: 120,
|
||||
pinned: 'left',
|
||||
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 || '')
|
||||
},
|
||||
{
|
||||
@ -408,6 +308,7 @@ const columnDefs: ColDef<DetailRow>[] = [
|
||||
field: 'budgetBase',
|
||||
minWidth: 150,
|
||||
autoHeight: true,
|
||||
|
||||
width: 180,
|
||||
colSpan: params => (params.node?.rowPinned ? 3 : 1),
|
||||
spanRows: spanRowsByTaskName,
|
||||
@ -418,16 +319,15 @@ const columnDefs: ColDef<DetailRow>[] = [
|
||||
field: 'budgetReferenceUnitPrice',
|
||||
minWidth: 170,
|
||||
flex: 1,
|
||||
editable: params => !params.node?.group && !params.node?.rowPinned && !isNoTaskRow(params.data),
|
||||
valueFormatter: formatEditableNumber
|
||||
valueFormatter: params => params.value || ''
|
||||
},
|
||||
/*{
|
||||
{
|
||||
headerName: t('workloadPricing.columns.budgetAdoptedUnitPrice'),
|
||||
field: 'budgetAdoptedUnitPrice',
|
||||
headerClass: 'ag-right-aligned-header',
|
||||
minWidth: 170,
|
||||
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' : ''),
|
||||
cellClassRules: {
|
||||
'ag-right-aligned-cell': () => true,
|
||||
@ -447,58 +347,8 @@ const columnDefs: ColDef<DetailRow>[] = [
|
||||
const unit = params.data?.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'),
|
||||
field: 'workload',
|
||||
minWidth: 140,
|
||||
@ -517,33 +367,8 @@ const columnDefs: ColDef<DetailRow>[] = [
|
||||
aggFunc: decimalAggSum,
|
||||
valueParser: params => parseSanitizedNumberOrNull(params.newValue),
|
||||
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'),
|
||||
field: 'consultCategoryFactor',
|
||||
width: 80,
|
||||
@ -562,54 +387,6 @@ const columnDefs: ColDef<DetailRow>[] = [
|
||||
},
|
||||
valueParser: params => parseSanitizedNumberOrNull(params.newValue),
|
||||
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'),
|
||||
@ -617,19 +394,17 @@ const columnDefs: ColDef<DetailRow>[] = [
|
||||
headerClass: 'ag-right-aligned-header',
|
||||
minWidth: 150,
|
||||
flex: 1,
|
||||
editable: params => !params.node?.group && !params.node?.rowPinned && !isNoTaskRow(params.data),
|
||||
cellClass: params => (!params.node?.group && !params.node?.rowPinned ? 'editable-cell-line' : ''),
|
||||
editable: false,
|
||||
cellClassRules: {
|
||||
'ag-right-aligned-cell': () => true,
|
||||
|
||||
'editable-cell-empty': params =>
|
||||
!params.node?.group &&
|
||||
!params.node?.rowPinned &&
|
||||
!isNoTaskRow(params.data) &&
|
||||
(params.value == null || params.value === '')
|
||||
'ag-right-aligned-cell': () => true
|
||||
},
|
||||
valueParser: params => parseSanitizedNumberOrNull(params.newValue),
|
||||
valueFormatter: formatEditableNumber
|
||||
valueGetter: params => (params.node?.rowPinned ? params.data?.serviceFee ?? null : calcServiceFee(params.data)),
|
||||
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'),
|
||||
@ -658,9 +433,7 @@ const columnDefs: ColDef<DetailRow>[] = [
|
||||
]
|
||||
const gridColumnDefs = computed(() => withReadonlyAutoHeight(columnDefs))
|
||||
|
||||
const totalWorkload = computed(() => {
|
||||
return 1;
|
||||
})
|
||||
const totalWorkload = computed(() => sumByNumber(detailRows.value, row => row.workload))
|
||||
const totalBasicFee = computed(() => sumByNumber(detailRows.value, row => calcBasicFee(row)))
|
||||
const totalServiceFee = computed(() => sumNullableBy(detailRows.value, row => calcServiceFee(row)))
|
||||
const pinnedTopRowData = computed(() =>
|
||||
@ -684,63 +457,44 @@ const pinnedTopRowData = computed(() =>
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 构建用于持久化的明细行数据
|
||||
* 计算并附加基础费用和服务费用字段
|
||||
* @returns 包含计算字段的明细行数组
|
||||
*/
|
||||
const buildPersistDetailRows = () =>
|
||||
detailRows.value.map(row => ({
|
||||
...row,
|
||||
basicFee: calcBasicFee(row),
|
||||
serviceFee: row.serviceFee
|
||||
serviceFee: calcServiceFee(row)
|
||||
}))
|
||||
|
||||
/**
|
||||
* 保存数据到 IndexedDB 并同步到 zxFw store
|
||||
* 保存时机:单元格编辑、批量操作结束、组件失活/卸载时
|
||||
* 同步工作量法的总服务费用到咨询服务汇总表
|
||||
*/
|
||||
const saveToIndexedDB = async () => {
|
||||
if (!isWorkloadMethodApplicable.value) return
|
||||
if (shouldSkipPersist()) return
|
||||
try {
|
||||
const rows = detailRows.value.map(row => ({ ...row, type: `${props.contractId}-task`}))
|
||||
const stats = await useDataStore().upsertBatch(rows)
|
||||
console.log('💾 数据保存成功:', stats)
|
||||
/*zxFwPricingStore.setServicePricingMethodState(
|
||||
const payload = {
|
||||
detailRows: JSON.parse(JSON.stringify(buildPersistDetailRows()))
|
||||
}
|
||||
zxFwPricingStore.setServicePricingMethodState(
|
||||
props.contractId,
|
||||
props.serviceId,
|
||||
'serviceFee',
|
||||
'workload',
|
||||
payload,
|
||||
{ force: true }
|
||||
)*/
|
||||
// const synced = await syncPricingTotalToZxFw({
|
||||
// contractId: props.contractId,
|
||||
// serviceId: props.serviceId,
|
||||
// field: 'serviceFee',
|
||||
// value: totalWorkload.value
|
||||
// })
|
||||
// if (!synced) return
|
||||
)
|
||||
const synced = await syncPricingTotalToZxFw({
|
||||
contractId: props.contractId,
|
||||
serviceId: props.serviceId,
|
||||
field: 'workload',
|
||||
value: totalServiceFee.value
|
||||
})
|
||||
if (!synced) return
|
||||
} catch (error) {
|
||||
console.error('saveToIndexedDB failed:', error)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断两个可空数值是否相等(考虑精度)
|
||||
* @param left 第一个数值
|
||||
* @param right 第二个数值
|
||||
* @returns 是否相等
|
||||
*/
|
||||
const isSameNullableNumber = (left: number | null | undefined, right: number | null | undefined) => {
|
||||
if (left == null && right == null) return true
|
||||
if (left == null || right == null) return false
|
||||
return roundTo(left, 6) === roundTo(right, 6)
|
||||
}
|
||||
|
||||
/**
|
||||
* 从合同段同步咨询服务分类因子
|
||||
* 当合同段的咨询因子配置变化时,更新当前工作量的因子默认值
|
||||
*/
|
||||
const syncLinkedConsultFactorFromHt = async () => {
|
||||
if (!isWorkloadMethodApplicable.value || detailRows.value.length === 0) return
|
||||
consultCategoryFactorMap.value = await loadConsultCategoryFactorMap(HT_CONSULT_FACTOR_KEY.value)
|
||||
@ -766,30 +520,23 @@ const linkedConsultFactorSignature = computed(() => JSON.stringify({
|
||||
?? null
|
||||
}))
|
||||
|
||||
/**
|
||||
* 从 IndexedDB 加载工作量法数据
|
||||
* 加载时机:组件挂载、激活、storageKey 变化时
|
||||
* 优先加载历史数据,没有则使用默认数据
|
||||
*/
|
||||
const loadFromIndexedDB = async () => {
|
||||
try {
|
||||
if (!isWorkloadMethodApplicable.value) {
|
||||
detailRows.value = []
|
||||
return
|
||||
}
|
||||
const taskRows = await useDataStore().query([
|
||||
{ field: 'type', value: `${props.contractId}-task`, operator: 'eq' }
|
||||
])
|
||||
/*await ensureFactorDefaultsLoaded()
|
||||
|
||||
await ensureFactorDefaultsLoaded()
|
||||
|
||||
if (shouldForceDefaultLoad()) {
|
||||
detailRows.value = buildDefaultRows()
|
||||
return
|
||||
}*/
|
||||
}
|
||||
|
||||
// const data = await zxFwPricingStore.loadServicePricingMethodState<DetailRow>(props.contractId, props.serviceId, 'serviceFee')
|
||||
if (taskRows) {
|
||||
detailRows.value = mergeWithDictRows(taskRows)
|
||||
const data = await zxFwPricingStore.loadServicePricingMethodState<DetailRow>(props.contractId, props.serviceId, 'workload')
|
||||
if (data) {
|
||||
detailRows.value = mergeWithDictRows(data.detailRows)
|
||||
return
|
||||
}
|
||||
|
||||
@ -800,10 +547,6 @@ const loadFromIndexedDB = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据任务词典更新行标签
|
||||
* 当语言切换或任务词典更新时,同步更新任务名称、单位等信息
|
||||
*/
|
||||
const relabelRowsFromTaskDict = async () => {
|
||||
if (!isWorkloadMethodApplicable.value || detailRows.value.length === 0) return
|
||||
let changed = false
|
||||
@ -841,46 +584,24 @@ const relabelRowsFromTaskDict = async () => {
|
||||
|
||||
let isBulkClipboardMutation = false
|
||||
|
||||
/**
|
||||
* 提交网格变更并保存
|
||||
* 在单元格编辑完成后调用,触发数据持久化
|
||||
*/
|
||||
const commitGridChanges = () => {
|
||||
void saveToIndexedDB()
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理单元格值变化事件
|
||||
* 触发时机:用户编辑完单元格后
|
||||
* 批量操作期间跳过,避免重复保存
|
||||
*/
|
||||
const handleCellValueChanged = () => {
|
||||
if (isBulkClipboardMutation) return
|
||||
commitGridChanges()
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理批量粘贴/填充操作开始
|
||||
* 设置标志位,避免在批量操作过程中频繁保存
|
||||
*/
|
||||
const handleBulkMutationStart = () => {
|
||||
isBulkClipboardMutation = true
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理批量粘贴/填充操作结束
|
||||
* 清除标志位并保存所有变更
|
||||
*/
|
||||
const handleBulkMutationEnd = () => {
|
||||
isBulkClipboardMutation = false
|
||||
commitGridChanges()
|
||||
}
|
||||
|
||||
/**
|
||||
* AG Grid 初始化完成回调
|
||||
* 记录 gridApi 实例,用于后续刷新单元格等操作
|
||||
* @param event AG Grid 初始化事件
|
||||
*/
|
||||
const handleGridReady = (event: GridReadyEvent<DetailRow>) => {
|
||||
gridApi.value = event.api
|
||||
}
|
||||
@ -898,21 +619,6 @@ watch(
|
||||
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) => {
|
||||
if (Array.isArray(params.value)) {
|
||||
return JSON.stringify(params.value); // 数组转字符串复制
|
||||
@ -920,12 +626,6 @@ const processCellForClipboard = (params: any) => {
|
||||
return params.value;
|
||||
};
|
||||
|
||||
/**
|
||||
* 处理从剪贴板粘贴的单元格数据
|
||||
* 根据字段类型解析数值(单价、工作量、因子等)
|
||||
* @param params AG Grid 剪贴板参数
|
||||
* @returns 解析后的单元格值
|
||||
*/
|
||||
const processCellFromClipboard = (params: any) => {
|
||||
const field = String(params.column?.getColDef?.().field || '')
|
||||
if (field === 'budgetAdoptedUnitPrice') {
|
||||
@ -968,7 +668,7 @@ const mydiyTheme = myTheme.withParams({
|
||||
</div>
|
||||
|
||||
<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"
|
||||
:animateRows="true"
|
||||
:enableCellSpan="true"
|
||||
|
||||
@ -20,14 +20,7 @@ import {useZxFwPricingStore, type HtFeeMethodType, type ServicePricingMethod} fr
|
||||
import { AG_GRID_LOCALE_CN } from '@ag-grid-community/locale'
|
||||
import { buildProjectScopedSessionKey } from '@/lib/pricingPersistControl'
|
||||
import { withReadonlyAutoHeight } from '@/lib/agGridReadonlyAutoHeight'
|
||||
import {formatScaleEditableNumber, formatScaleReadonlyMoney} from "@/lib/pricingScaleGrid";
|
||||
import {ValueParserParams, ValueFormatterParams} from 'ag-grid-community';
|
||||
import {useKvStore} from "@/pinia/kv";
|
||||
import {useDataStore} from '@/pinia/zx'
|
||||
/**
|
||||
* 数据行接口定义
|
||||
* 注意:此接口需与 columnDefs 中的字段保持一致
|
||||
*/
|
||||
|
||||
interface DetailRow {
|
||||
id: string
|
||||
expertCode: string
|
||||
@ -36,35 +29,15 @@ interface DetailRow {
|
||||
compositeBudgetUnitPrice: string
|
||||
adoptedBudgetUnitPrice: number | null
|
||||
personnelCount: number | null
|
||||
workdayCount: number | string | null
|
||||
serviceBudget: number | string | null
|
||||
workdayCount: number | null
|
||||
serviceBudget: number | null
|
||||
remark: string
|
||||
path: string[]
|
||||
// 新增:按职称分类的工时字段(与 columnDefs 对应)
|
||||
unitPrice?: number | string | null
|
||||
workdayCount1?: number | string | null
|
||||
feeSubtotal?: number | string | null
|
||||
unitPrice2?: number | string | null
|
||||
workdayCount2?: number | string | null
|
||||
feeSubtotal2?: number | string | null
|
||||
unitPrice3?: number | string | null
|
||||
workdayCount3?: number | string | null
|
||||
feeSubtotal3?: number | string | null
|
||||
unitPrice4?: number | string | null
|
||||
workdayCount4?: number | string | null
|
||||
feeSubtotal4?: number | string | null
|
||||
unitPrice5?: number | string | null
|
||||
workdayCount5?: number | string | null
|
||||
feeSubtotal5?: number | string | null
|
||||
workdayCount6?: number | string | null
|
||||
feeSubtotal6?: number | string | null,
|
||||
avgUnitPrice: number | string | null
|
||||
}
|
||||
|
||||
interface GridState {
|
||||
detailRows: DetailRow[]
|
||||
}
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
storageKey: string
|
||||
@ -151,9 +124,34 @@ const getHtMethodState = () => {
|
||||
}
|
||||
const detailRows = computed<DetailRow[]>({
|
||||
get: () => {
|
||||
if (useServicePricingState.value) {
|
||||
const rows = getServiceMethodState()?.detailRows
|
||||
return Array.isArray(rows) ? rows : []
|
||||
}
|
||||
if (useHtMethodState.value) {
|
||||
const rows = getHtMethodState()?.detailRows
|
||||
return Array.isArray(rows) ? rows : []
|
||||
}
|
||||
return fallbackDetailRows.value
|
||||
},
|
||||
set: rows => {
|
||||
if (useServicePricingState.value && serviceMethod.value) {
|
||||
const currentState = getServiceMethodState()
|
||||
zxFwPricingStore.setServicePricingMethodState(props.contractId!, props.serviceId!, serviceMethod.value, {
|
||||
detailRows: rows,
|
||||
projectCount: currentState?.projectCount ?? null
|
||||
})
|
||||
return
|
||||
}
|
||||
if (useHtMethodState.value) {
|
||||
zxFwPricingStore.setHtFeeMethodState(
|
||||
props.htMainStorageKey!,
|
||||
props.htRowId!,
|
||||
props.htMethodType!,
|
||||
{ detailRows: rows }
|
||||
)
|
||||
return
|
||||
}
|
||||
fallbackDetailRows.value = rows
|
||||
}
|
||||
})
|
||||
@ -215,18 +213,12 @@ const getDefaultAdoptedBudgetUnitPrice = (expert: ExpertLite) => {
|
||||
return roundTo(toDecimal(expert.defPrice).mul(expert.manageCoe), 2)
|
||||
}
|
||||
|
||||
const buildDefaultRows = async (): Promise<DetailRow[]> => {
|
||||
const buildDefaultRows = (): DetailRow[] => {
|
||||
const rows: DetailRow[] = []
|
||||
const rowsToMap = await useDataStore().query([
|
||||
{ field: 'type', value: `${props.contractId}-zxFw`, operator: 'eq' }
|
||||
])
|
||||
for (const expert of rowsToMap) {
|
||||
const expertId = expert.id || expert._id // 根据实际字段调整
|
||||
const contractId = props.contractId
|
||||
const rowId = `${contractId}-hourly-${expertId}`
|
||||
for (const [expertId, expert] of expertEntries) {
|
||||
const rowId = `expert-${expertId}`
|
||||
rows.push({
|
||||
id: rowId,
|
||||
type: `${props.contractId}-hourly`,
|
||||
expertCode: expert.code,
|
||||
expertName: getExpertDisplayName(expert),
|
||||
laborBudgetUnitPrice: formatPriceRange(expert.minPrice, expert.maxPrice),
|
||||
@ -242,48 +234,23 @@ const buildDefaultRows = async (): Promise<DetailRow[]> => {
|
||||
return rows
|
||||
}
|
||||
|
||||
/**
|
||||
* 合并数据库中的行数据与默认行数据
|
||||
* 保留用户编辑的所有字段值(包括新增的职称分类字段)
|
||||
*/
|
||||
const mergeWithDictRows = async (rowsFromDb: DetailRow[] | undefined): Promise<DetailRow[]> => {
|
||||
const mergeWithDictRows = (rowsFromDb: DetailRow[] | undefined): DetailRow[] => {
|
||||
const dbValueMap = new Map<string, DetailRow>()
|
||||
for (const row of rowsFromDb || []) {
|
||||
dbValueMap.set(row.id, row)
|
||||
}
|
||||
|
||||
const defaultRows = await buildDefaultRows()
|
||||
return defaultRows.map(row => {
|
||||
return buildDefaultRows().map(row => {
|
||||
const fromDb = dbValueMap.get(row.id)
|
||||
if (!fromDb) return row
|
||||
|
||||
// 使用展开运算符合并所有字段,确保新增字段也能正确合并
|
||||
return {
|
||||
...row,
|
||||
// 保留数据库中的所有可编辑字段
|
||||
adoptedBudgetUnitPrice: typeof fromDb.adoptedBudgetUnitPrice === 'number' ? fromDb.adoptedBudgetUnitPrice : null,
|
||||
adoptedBudgetUnitPrice:
|
||||
typeof fromDb.adoptedBudgetUnitPrice === 'number' ? fromDb.adoptedBudgetUnitPrice : null,
|
||||
personnelCount: typeof fromDb.personnelCount === 'number' ? fromDb.personnelCount : null,
|
||||
workdayCount: typeof fromDb.workdayCount === 'number' ? fromDb.workdayCount : null,
|
||||
serviceBudget: typeof fromDb.serviceBudget === 'number' ? fromDb.serviceBudget : null,
|
||||
remark: typeof fromDb.remark === 'string' ? fromDb.remark : '',
|
||||
// 合并新增的职称分类字段
|
||||
unitPrice: typeof fromDb.unitPrice === 'number' ? fromDb.unitPrice : null,
|
||||
workdayCount1: typeof fromDb.workdayCount1 === 'number' ? fromDb.workdayCount1 : null,
|
||||
feeSubtotal: typeof fromDb.feeSubtotal === 'number' ? fromDb.feeSubtotal : null,
|
||||
unitPrice2: typeof fromDb.unitPrice2 === 'number' ? fromDb.unitPrice2 : null,
|
||||
workdayCount2: typeof fromDb.workdayCount2 === 'number' ? fromDb.workdayCount2 : null,
|
||||
feeSubtotal2: typeof fromDb.feeSubtotal2 === 'number' ? fromDb.feeSubtotal2 : null,
|
||||
unitPrice3: typeof fromDb.unitPrice3 === 'number' ? fromDb.unitPrice3 : null,
|
||||
workdayCount3: typeof fromDb.workdayCount3 === 'number' ? fromDb.workdayCount3 : null,
|
||||
feeSubtotal3: typeof fromDb.feeSubtotal3 === 'number' ? fromDb.feeSubtotal3 : null,
|
||||
unitPrice4: typeof fromDb.unitPrice4 === 'number' ? fromDb.unitPrice4 : null,
|
||||
workdayCount4: typeof fromDb.workdayCount4 === 'number' ? fromDb.workdayCount4 : null,
|
||||
feeSubtotal4: typeof fromDb.feeSubtotal4 === 'number' ? fromDb.feeSubtotal4 : null,
|
||||
unitPrice5: typeof fromDb.unitPrice5 === 'number' ? fromDb.unitPrice5 : null,
|
||||
workdayCount5: typeof fromDb.workdayCount5 === 'number' ? fromDb.workdayCount5 : null,
|
||||
feeSubtotal5: typeof fromDb.feeSubtotal5 === 'number' ? fromDb.feeSubtotal5 : null,
|
||||
feeSubtotal6: typeof fromDb.feeSubtotal6 === 'number' ? fromDb.feeSubtotal6 : null,
|
||||
avgUnitPrice: typeof fromDb.avgUnitPrice === 'number' ? fromDb.avgUnitPrice : null,
|
||||
remark: typeof fromDb.remark === 'string' ? fromDb.remark : ''
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -331,258 +298,7 @@ const calcServiceBudget = (row: DetailRow | undefined) => {
|
||||
|
||||
const syncServiceBudgetToRows = () => {
|
||||
for (const row of detailRows.value) {
|
||||
// row.serviceBudget = calcServiceBudget(row)
|
||||
|
||||
// 计算各职称的费用小计(单价 × 工日)
|
||||
row.feeSubtotal = Math.round((row.unitPrice ?? 0) * (row.workdayCount ?? 0))
|
||||
row.feeSubtotal2 = Math.round((row.unitPrice2 ?? 0) * (row.workdayCount2 ?? 0))
|
||||
row.feeSubtotal3 = Math.round((row.unitPrice3 ?? 0) * (row.workdayCount3 ?? 0))
|
||||
row.feeSubtotal4 = Math.round((row.unitPrice4 ?? 0) * (row.workdayCount4 ?? 0))
|
||||
row.feeSubtotal5 = Math.round((row.unitPrice5 ?? 0) * (row.workdayCount5 ?? 0))
|
||||
|
||||
// 计算总工日
|
||||
row.workdayCount6 = (row.workdayCount ?? 0) +
|
||||
(row.workdayCount2 ?? 0) +
|
||||
(row.workdayCount3 ?? 0) +
|
||||
(row.workdayCount4 ?? 0) +
|
||||
(row.workdayCount5 ?? 0)
|
||||
|
||||
// 计算总费用
|
||||
row.feeSubtotal6 = Math.round(
|
||||
(row.workdayCount ?? 0) * (row.unitPrice ?? 0) +
|
||||
(row.workdayCount2 ?? 0) * (row.unitPrice2 ?? 0) +
|
||||
(row.workdayCount3 ?? 0) * (row.unitPrice3 ?? 0) +
|
||||
(row.workdayCount4 ?? 0) * (row.unitPrice4 ?? 0) +
|
||||
(row.workdayCount5 ?? 0) * (row.unitPrice5 ?? 0)
|
||||
)
|
||||
|
||||
// 计算平均单价
|
||||
const totalWorkday = row.workdayCount6 ?? 0
|
||||
const totalFee = row.feeSubtotal6 ?? 0
|
||||
row.avgUnitPrice = Math.round(totalWorkday * totalFee)
|
||||
}
|
||||
}
|
||||
|
||||
function editableNumberCol2(
|
||||
field: string,
|
||||
headerName: string,
|
||||
options: {
|
||||
decimals?: 0 | 1; // 精度:0=整数,1=1位小数
|
||||
aggFunc?: string;
|
||||
} = {}
|
||||
): ColDef {
|
||||
const {decimals = 0, aggFunc = 'sum'} = options;
|
||||
|
||||
/*const valueParser = (params: ValueParserParams) => {
|
||||
const val = params.newValue?.trim();
|
||||
if (!val) return null;
|
||||
const num = parseFloat(val);
|
||||
return isNaN(num) || num < 0 ? null : num;
|
||||
};*/
|
||||
|
||||
const valueFormatter = (params: ValueFormatterParams) => {
|
||||
if (params.node?.rowPinned === 'bottom') {
|
||||
return '/';
|
||||
}
|
||||
const val = params.value;
|
||||
if (val === null || val === undefined) return 0;
|
||||
if (decimals === 0) {
|
||||
return Math.round(val).toString(); // 强制整数显示
|
||||
} else {
|
||||
return Number(val).toFixed(1); // 保留1位小数
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
field,
|
||||
headerName,
|
||||
editable: (params) => {
|
||||
if (params.node?.rowPinned === 'bottom') {
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
},
|
||||
cellDataType: 'number',
|
||||
valueFormatter,
|
||||
aggFunc,
|
||||
};
|
||||
}
|
||||
|
||||
function editableNumberCol3(
|
||||
field: string,
|
||||
headerName: string,
|
||||
options: {
|
||||
decimals?: 0 | 1; // 精度:0=整数,1=1位小数
|
||||
aggFunc?: string;
|
||||
} = {}
|
||||
): ColDef {
|
||||
const {decimals = 0, aggFunc = 'sum'} = options;
|
||||
|
||||
const valueParser = (params: ValueParserParams) => {
|
||||
const val = params.newValue?.trim();
|
||||
if (!val) return null;
|
||||
const num = parseFloat(val);
|
||||
return isNaN(num) || num < 0 ? null : num;
|
||||
};
|
||||
|
||||
const valueFormatter = (params: ValueFormatterParams) => {
|
||||
const val = params.value;
|
||||
if (val === null || val === undefined) return 0;
|
||||
if (decimals === 0) {
|
||||
return Math.round(val).toString(); // 强制整数显示
|
||||
} else {
|
||||
return Number(val).toFixed(1); // 保留1位小数
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
field,
|
||||
headerName,
|
||||
editable: (params) => {
|
||||
if (params.node?.rowPinned === 'bottom') {
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
},
|
||||
cellDataType: 'number',
|
||||
valueParser,
|
||||
valueFormatter,
|
||||
aggFunc,
|
||||
};
|
||||
}
|
||||
|
||||
// 费用小计列(计算列,不可编辑)
|
||||
function calculatedFeeSubtotalCol(
|
||||
field: string,
|
||||
headerName: string,
|
||||
unitPriceField: string = 'unitPrice',
|
||||
workdayCountField: string = 'workdayCount'
|
||||
): ColDef {
|
||||
return {
|
||||
field,
|
||||
headerName,
|
||||
editable: false,
|
||||
cellDataType: 'number',
|
||||
valueGetter: (params) => {
|
||||
const unitPrice = params.data?.[unitPriceField] ?? 0;
|
||||
const workdayCount = params.data?.[workdayCountField] ?? 0;
|
||||
const subtotal = unitPrice * workdayCount;
|
||||
if (params.node?.rowPinned === 'bottom') {
|
||||
// 总计行的特殊计算逻辑
|
||||
// 例如:使用不同的计算方式或直接返回预计算的值
|
||||
const allRows = fallbackDetailRows.value;
|
||||
const total = allRows.reduce((sum, row) => {
|
||||
return sum + ((row[unitPriceField as keyof DetailRow] as number || 0) *
|
||||
(row[workdayCountField as keyof DetailRow] as number || 0));
|
||||
}, 0);
|
||||
return Math.round(total); // 显示为整数
|
||||
}
|
||||
return Math.round(subtotal); // 费用小计为整数
|
||||
},
|
||||
/*valueFormatter: (params: ValueFormatterParams) => {
|
||||
const unitPrice = params.data?.[unitPriceField] ?? 0;
|
||||
const workdayCount = params.data?.[workdayCountField] ?? 0;
|
||||
const subtotal = unitPrice * workdayCount;
|
||||
return Math.round(subtotal); // 显示为整数
|
||||
},*/
|
||||
aggFunc: 'sum'
|
||||
};
|
||||
}
|
||||
|
||||
function calculatedFeeSubtotalCol2(
|
||||
field: string,
|
||||
headerName: string,
|
||||
unitPriceField: string = 'unitPrice',
|
||||
workdayCountField: string = 'workdayCount'
|
||||
): ColDef {
|
||||
return {
|
||||
field,
|
||||
headerName,
|
||||
editable: false,
|
||||
cellDataType: 'number',
|
||||
valueGetter: (params) => {
|
||||
const unitPrice = params.data?.[unitPriceField] ?? 0;
|
||||
const workdayCount = params.data?.[workdayCountField] ?? 0;
|
||||
const subtotal = unitPrice * workdayCount;
|
||||
return Math.round(subtotal); // 费用小计为整数
|
||||
},
|
||||
valueFormatter: (params: ValueFormatterParams) => {
|
||||
const allRows = fallbackDetailRows.value;
|
||||
const total = allRows.reduce((sum, row) => {
|
||||
return sum + ((row[unitPriceField as keyof DetailRow] as number || 0) *
|
||||
(row[workdayCountField as keyof DetailRow] as number || 0));
|
||||
}, 0);
|
||||
return Math.round(total); // 显示为整数
|
||||
},
|
||||
aggFunc: 'sum'
|
||||
};
|
||||
}
|
||||
|
||||
function calculatedWorkdayTotalCol(
|
||||
field: string,
|
||||
headerName: string,
|
||||
options: {
|
||||
decimals?: 0 | 1; // 精度:0=整数,1=1位小数
|
||||
aggFunc?: string;
|
||||
}): ColDef {
|
||||
const {decimals = 0, aggFunc = 'sum'} = options;
|
||||
return {
|
||||
field,
|
||||
headerName,
|
||||
editable: false,
|
||||
cellDataType: 'number',
|
||||
valueGetter: (params) => {
|
||||
const workdayCount1 = params.data?.['workdayCount'] ?? 0;
|
||||
const workdayCount2 = params.data?.['workdayCount2'] ?? 0;
|
||||
const workdayCount3 = params.data?.['workdayCount3'] ?? 0;
|
||||
const workdayCount4 = params.data?.['workdayCount4'] ?? 0;
|
||||
const workdayCount5 = params.data?.['workdayCount5'] ?? 0;
|
||||
const count = workdayCount1 + workdayCount2 + workdayCount3 + workdayCount4 + workdayCount5;
|
||||
return count; // 费用小计为整数
|
||||
},
|
||||
valueFormatter: (params: ValueFormatterParams) => {
|
||||
const val = params.value;
|
||||
if (val === null || val === undefined) return '';
|
||||
if (decimals === 0) {
|
||||
return Math.round(val).toString(); // 强制整数显示
|
||||
} else {
|
||||
return Number(val).toFixed(1); // 保留1位小数
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function calculatedAvgUnitPriceCol(
|
||||
field: string,
|
||||
headerName: string,
|
||||
options: {
|
||||
decimals?: 0 | 1; // 精度:0=整数,1=1位小数
|
||||
aggFunc?: string;
|
||||
}): ColDef {
|
||||
const {decimals = 0, aggFunc = 'sum'} = options;
|
||||
return {
|
||||
field,
|
||||
headerName,
|
||||
editable: false,
|
||||
cellDataType: 'number'
|
||||
}
|
||||
}
|
||||
|
||||
function calculatedSubTotalCol(
|
||||
field: string,
|
||||
headerName: string,
|
||||
options: {
|
||||
decimals?: 0 | 1; // 精度:0=整数,1=1位小数
|
||||
aggFunc?: string;
|
||||
}): ColDef {
|
||||
const {decimals = 0, aggFunc = 'sum'} = options;
|
||||
return {
|
||||
field,
|
||||
headerName,
|
||||
editable: false,
|
||||
cellDataType: 'number'
|
||||
row.serviceBudget = calcServiceBudget(row)
|
||||
}
|
||||
}
|
||||
|
||||
@ -660,7 +376,10 @@ const columnDefs: (ColDef<DetailRow> | ColGroupDef<DetailRow>)[] = [
|
||||
width: 100,
|
||||
pinned: 'left',
|
||||
colSpan: params => (params.node?.rowPinned ? 2 : 1),
|
||||
valueFormatter: params => (params.node?.rowPinned ? t('hourlyFeeGrid.total') : params.value || 0)
|
||||
cellClassRules: {
|
||||
'ag-summary-label-cell': params => Boolean(params.node?.rowPinned)
|
||||
},
|
||||
valueFormatter: params => (params.node?.rowPinned ? t('hourlyFeeGrid.total') : params.value || '')
|
||||
},
|
||||
{
|
||||
headerName: t('hourlyFeeGrid.columns.name'),
|
||||
@ -676,77 +395,17 @@ const columnDefs: (ColDef<DetailRow> | ColGroupDef<DetailRow>)[] = [
|
||||
valueFormatter: params => (params.node?.rowPinned ? '' : params.value || '')
|
||||
},
|
||||
{
|
||||
headerName: t('hourlyFeeGrid.columns.technician'),
|
||||
headerName: t('hourlyFeeGrid.columns.referenceUnitPrice'),
|
||||
marryChildren: true,
|
||||
children: [
|
||||
// 单价(元/工日)→ 整数,非必填
|
||||
editableNumberCol2('unitPrice', t('hourlyFeeGrid.columns.unitPrice'), {decimals: 0}),
|
||||
// 工日数量(工日)→ 1位小数,非必填
|
||||
editableNumberCol3('workdayCount', t('hourlyFeeGrid.columns.workdayCount'), {decimals: 1}),
|
||||
// 费用小计(元)→ 计算列,整数
|
||||
calculatedFeeSubtotalCol('feeSubtotal', '费用小计(元)', 'unitPrice', 'workdayCount')
|
||||
readonlyTextCol('laborBudgetUnitPrice', t('hourlyFeeGrid.columns.laborBudgetUnitPrice'), {
|
||||
colSpan: params => (params.node?.rowPinned ? 3 : 1),
|
||||
valueFormatter: params => (params.node?.rowPinned ? '' : params.value || '')
|
||||
}),
|
||||
readonlyTextCol('compositeBudgetUnitPrice', t('hourlyFeeGrid.columns.compositeBudgetUnitPrice'))
|
||||
]
|
||||
},
|
||||
{
|
||||
headerName: t('hourlyFeeGrid.columns.assistantEngineer'),
|
||||
marryChildren: true,
|
||||
children: [
|
||||
// 单价(元/工日)→ 整数,非必填
|
||||
editableNumberCol2('unitPrice2', t('hourlyFeeGrid.columns.unitPrice'), {decimals: 0}),
|
||||
// 工日数量(工日)→ 1位小数,非必填
|
||||
editableNumberCol3('workdayCount2', t('hourlyFeeGrid.columns.workdayCount'), {decimals: 1}),
|
||||
// 费用小计(元)→ 计算列,整数
|
||||
calculatedFeeSubtotalCol('feeSubtotal2', '费用小计(元)', 'unitPrice2', 'workdayCount2')
|
||||
]
|
||||
},
|
||||
{
|
||||
headerName: t('hourlyFeeGrid.columns.midEngineer'),
|
||||
marryChildren: true,
|
||||
children: [
|
||||
// 单价(元/工日)→ 整数,非必填
|
||||
editableNumberCol2('unitPrice3', t('hourlyFeeGrid.columns.unitPrice'), {decimals: 0}),
|
||||
// 工日数量(工日)→ 1位小数,非必填
|
||||
editableNumberCol3('workdayCount3', t('hourlyFeeGrid.columns.workdayCount'), {decimals: 1}),
|
||||
// 费用小计(元)→ 计算列,整数
|
||||
calculatedFeeSubtotalCol('feeSubtotal3', '费用小计(元)', 'unitPrice3', 'workdayCount3')
|
||||
]
|
||||
},
|
||||
{
|
||||
headerName: t('hourlyFeeGrid.columns.seniorEngineer'),
|
||||
marryChildren: true,
|
||||
children: [
|
||||
// 单价(元/工日)→ 整数,非必填
|
||||
editableNumberCol2('unitPrice4', t('hourlyFeeGrid.columns.unitPrice'), {decimals: 0}),
|
||||
// 工日数量(工日)→ 1位小数,非必填
|
||||
editableNumberCol3('workdayCount4', t('hourlyFeeGrid.columns.workdayCount'), {decimals: 1}),
|
||||
// 费用小计(元)→ 计算列,整数
|
||||
calculatedFeeSubtotalCol('feeSubtotal4', '费用小计(元)', 'unitPrice4', 'workdayCount4')
|
||||
]
|
||||
},
|
||||
{
|
||||
headerName: t('hourlyFeeGrid.columns.profSeniorEngineer'),
|
||||
marryChildren: true,
|
||||
children: [
|
||||
// 单价(元/工日)→ 整数,非必填
|
||||
editableNumberCol2('unitPrice5', t('hourlyFeeGrid.columns.unitPrice'), {decimals: 0}),
|
||||
// 工日数量(工日)→ 1位小数,非必填
|
||||
editableNumberCol3('workdayCount5', t('hourlyFeeGrid.columns.workdayCount'), {decimals: 1}),
|
||||
// 费用小计(元)→ 计算列,整数
|
||||
calculatedFeeSubtotalCol('feeSubtotal5', '费用小计(元)', 'unitPrice5', 'workdayCount5')
|
||||
]
|
||||
},
|
||||
{
|
||||
headerName: t('hourlyFeeGrid.columns.total'),
|
||||
marryChildren: true,
|
||||
children: [
|
||||
/*editableNumberCol2('workdayCount6', t('hourlyFeeGrid.columns.workdayCount'), {decimals: 1}),
|
||||
editableNumberCol2('subtotal6', t('hourlyFeeGrid.columns.subtotal'), {decimals: 0}),*/
|
||||
calculatedWorkdayTotalCol('workdayCount6', t('hourlyFeeGrid.columns.workdayCount'), {decimals: 1}),
|
||||
calculatedSubTotalCol('feeSubtotal6', t('hourlyFeeGrid.columns.subtotal'), {decimals: 0}),
|
||||
calculatedAvgUnitPriceCol('avgUnitPrice', t('hourlyFeeGrid.columns.avgUnitPrice'), {decimals: 0})
|
||||
]
|
||||
},
|
||||
/*editableMoneyCol('adoptedBudgetUnitPrice', t('hourlyFeeGrid.columns.adoptedBudgetUnitPrice')),
|
||||
editableMoneyCol('adoptedBudgetUnitPrice', t('hourlyFeeGrid.columns.adoptedBudgetUnitPrice')),
|
||||
editableNumberCol('personnelCount', t('hourlyFeeGrid.columns.personnelCount'), {
|
||||
aggFunc: decimalAggSum,
|
||||
valueParser: params => parseNonNegativeIntegerOrNull(params.newValue),
|
||||
@ -769,7 +428,7 @@ const columnDefs: (ColDef<DetailRow> | ColGroupDef<DetailRow>)[] = [
|
||||
if (params.value == null || params.value === '') return ''
|
||||
return formatThousandsFlexible(params.value, 3)
|
||||
}
|
||||
},*/
|
||||
},
|
||||
{
|
||||
headerName: t('hourlyFeeGrid.columns.remark'),
|
||||
field: 'remark',
|
||||
@ -793,30 +452,10 @@ const columnDefs: (ColDef<DetailRow> | ColGroupDef<DetailRow>)[] = [
|
||||
]
|
||||
const gridColumnDefs = computed(() => withReadonlyAutoHeight(columnDefs))
|
||||
|
||||
const totalWorkdayCount = computed(() => sumByNumber(detailRows.value, row => row.workdayCount))
|
||||
const totalWorkdayCount2 = computed(() => sumByNumber(detailRows.value, row => row.workdayCount2))
|
||||
const totalWorkdayCount3 = computed(() => sumByNumber(detailRows.value, row => row.workdayCount3))
|
||||
const totalWorkdayCount4 = computed(() => sumByNumber(detailRows.value, row => row.workdayCount4))
|
||||
const totalWorkdayCount5 = computed(() => sumByNumber(detailRows.value, row => row.workdayCount5))
|
||||
const totalFeeSubtotal = computed(() => {
|
||||
return sumByNumber(detailRows.value, row => row.feeSubtotal)
|
||||
})
|
||||
const totalFeeSubtotal2 = computed(() => sumByNumber(detailRows.value, row => row.feeSubtotal2))
|
||||
const totalFeeSubtotal3 = computed(() => sumByNumber(detailRows.value, row => row.feeSubtotal3))
|
||||
const totalFeeSubtotal4 = computed(() => sumByNumber(detailRows.value, row => row.feeSubtotal4))
|
||||
const totalFeeSubtotal5 = computed(() => sumByNumber(detailRows.value, row => row.feeSubtotal5))
|
||||
const totalFeeSubtotal6 = computed(() => {
|
||||
const allRows = fallbackDetailRows.value;
|
||||
return allRows.reduce((sum, row) => {
|
||||
const value = Number(row?.feeSubtotal6) || 0
|
||||
return sum + value
|
||||
}, 0);
|
||||
})
|
||||
const totalAvgUnitPrice = computed(() => sumByNumber(detailRows.value, row => row.avgUnitPrice))
|
||||
const totalPersonnelCount = computed(() => sumByNumber(detailRows.value, row => row.personnelCount))
|
||||
const totalWorkdayCount = computed(() => sumByNumber(detailRows.value, row => row.workdayCount))
|
||||
const totalServiceBudget = computed(() => sumNullableNumbers(detailRows.value.map(row => calcServiceBudget(row))))
|
||||
const pinnedTopRowData = computed(() => {
|
||||
const result = [
|
||||
const pinnedTopRowData = computed(() => [
|
||||
{
|
||||
id: 'pinned-total-row',
|
||||
expertCode: t('hourlyFeeGrid.total'),
|
||||
@ -824,110 +463,84 @@ const pinnedTopRowData = computed(() => {
|
||||
laborBudgetUnitPrice: '',
|
||||
compositeBudgetUnitPrice: '',
|
||||
adoptedBudgetUnitPrice: null,
|
||||
// personnelCount: totalPersonnelCount.value,
|
||||
unitPrice: '/',
|
||||
personnelCount: totalPersonnelCount.value,
|
||||
workdayCount: totalWorkdayCount.value,
|
||||
workdayCount2: totalWorkdayCount2.value,
|
||||
workdayCount3: totalWorkdayCount3.value,
|
||||
workdayCount4: totalWorkdayCount4.value,
|
||||
workdayCount5: totalWorkdayCount5.value,
|
||||
avgUnitPrice: totalAvgUnitPrice.value,
|
||||
feeSubtotal2: totalFeeSubtotal2.value,
|
||||
feeSubtotal3: totalFeeSubtotal3.value,
|
||||
feeSubtotal4: totalFeeSubtotal4.value,
|
||||
feeSubtotal5: totalFeeSubtotal5.value,
|
||||
feeSubtotal6: totalFeeSubtotal6.value,
|
||||
serviceBudget: totalServiceBudget.value,
|
||||
remark: '',
|
||||
path: ['TOTAL']
|
||||
}
|
||||
]
|
||||
|
||||
return result
|
||||
})
|
||||
|
||||
/**
|
||||
* 保存数据到 IndexedDB
|
||||
* 保存时机:
|
||||
* 1. 单元格值变化时(handleCellValueChanged)
|
||||
* 2. 组件失活时(onDeactivated)- 切换页面
|
||||
* 3. 组件卸载前(onBeforeUnmount)- 关闭页面
|
||||
*/
|
||||
|
||||
const recalculateServiceFees = () => {
|
||||
for (const row of fallbackDetailRows.value) {
|
||||
row.workdayCount6 = row.workdayCount + row.workdayCount2 + row.workdayCount3 + row.workdayCount4 + row.workdayCount5
|
||||
row.feeSubtotal6 = row.feeSubtotal + row.feeSubtotal2 + row.feeSubtotal3 + row.feeSubtotal4 + row.feeSubtotal5
|
||||
row.avgUnitPrice = row.feeSubtotal6 * row.workdayCount6
|
||||
}
|
||||
}
|
||||
|
||||
])
|
||||
|
||||
const saveToIndexedDB = async () => {
|
||||
if (shouldSkipPersist()) return
|
||||
try {
|
||||
// syncServiceBudgetToRows()
|
||||
syncServiceBudgetToRows();
|
||||
// ✅ 使用 upsertBatch:存在则更新,不存在则新增
|
||||
const rows = detailRows.value.map(row => ({ ...row }))
|
||||
const stats = await useDataStore().upsertBatch(rows)
|
||||
syncServiceBudgetToRows()
|
||||
const payload: GridState = {
|
||||
detailRows: JSON.parse(JSON.stringify(detailRows.value))
|
||||
}
|
||||
|
||||
if (useServicePricingState.value && serviceMethod.value) {
|
||||
zxFwPricingStore.setServicePricingMethodState(
|
||||
props.contractId!,
|
||||
props.serviceId!,
|
||||
serviceMethod.value,
|
||||
payload,
|
||||
{ force: true }
|
||||
)
|
||||
} else if (useHtMethodState.value) {
|
||||
zxFwPricingStore.setHtFeeMethodState(
|
||||
props.htMainStorageKey!,
|
||||
props.htRowId!,
|
||||
props.htMethodType!,
|
||||
payload,
|
||||
{ force: true }
|
||||
)
|
||||
} else {
|
||||
zxFwPricingStore.setKeyState(props.storageKey, payload)
|
||||
}
|
||||
|
||||
if (props.enableZxFwSync && props.contractId && props.serviceId != null) {
|
||||
const synced = await syncPricingTotalToZxFw({
|
||||
contractId: props.contractId,
|
||||
serviceId: props.serviceId,
|
||||
field: props.syncField,
|
||||
value: totalServiceBudget.value
|
||||
})
|
||||
if (!synced) return
|
||||
}
|
||||
|
||||
console.log('💾 数据保存成功:', stats)
|
||||
} catch (error) {
|
||||
console.error('❌ saveToIndexedDB 失败:', error)
|
||||
console.error('saveToIndexedDB failed:', error)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从 IndexedDB 加载数据
|
||||
* 加载时机:
|
||||
* 1. 组件挂载时(onMounted)
|
||||
* 2. 组件激活时(onActivated)- 切换回页面
|
||||
* 3. storageKey 变化时(watch)
|
||||
*/
|
||||
const loadFromIndexedDB = async () => {
|
||||
try {
|
||||
|
||||
// ✅ 查询 type 等于 'hourly' 的数据
|
||||
const hourlyRows = await useDataStore().query([
|
||||
{ field: 'type', value: `${props.contractId}-hourly`, operator: 'eq' }
|
||||
])
|
||||
// ✅ 将 DataItem[] 转换为 DetailRow[]
|
||||
const detailRowsFromStore: DetailRow[] = hourlyRows.map(row => ({
|
||||
id: String(row.id || ''),
|
||||
expertCode: String(row.expertCode || ''),
|
||||
expertName: String(row.expertName || ''),
|
||||
laborBudgetUnitPrice: String(row.laborBudgetUnitPrice || ''),
|
||||
compositeBudgetUnitPrice: String(row.compositeBudgetUnitPrice || ''),
|
||||
adoptedBudgetUnitPrice: row.adoptedBudgetUnitPrice != null ? Number(row.adoptedBudgetUnitPrice) : null,
|
||||
personnelCount: row.personnelCount != null ? Number(row.personnelCount) : null,
|
||||
workdayCount: row.workdayCount != null ? Number(row.workdayCount) : null,
|
||||
serviceBudget: row.serviceBudget != null ? Number(row.serviceBudget) : null,
|
||||
remark: String(row.remark || ''),
|
||||
path: Array.isArray(row.path) ? row.path : [],
|
||||
unitPrice: row.unitPrice != null ? Number(row.unitPrice) : null,
|
||||
workdayCount1: row.workdayCount1 != null ? Number(row.workdayCount1) : null,
|
||||
feeSubtotal: row.feeSubtotal != null ? Number(row.feeSubtotal) : null,
|
||||
unitPrice2: row.unitPrice2 != null ? Number(row.unitPrice2) : null,
|
||||
workdayCount2: row.workdayCount2 != null ? Number(row.workdayCount2) : null,
|
||||
feeSubtotal2: row.feeSubtotal2 != null ? Number(row.feeSubtotal2) : null,
|
||||
unitPrice3: row.unitPrice3 != null ? Number(row.unitPrice3) : null,
|
||||
workdayCount3: row.workdayCount3 != null ? Number(row.workdayCount3) : null,
|
||||
feeSubtotal3: row.feeSubtotal3 != null ? Number(row.feeSubtotal3) : null,
|
||||
unitPrice4: row.unitPrice4 != null ? Number(row.unitPrice4) : null,
|
||||
workdayCount4: row.workdayCount4 != null ? Number(row.workdayCount4) : null,
|
||||
feeSubtotal4: row.feeSubtotal4 != null ? Number(row.feeSubtotal4) : null,
|
||||
unitPrice5: row.unitPrice5 != null ? Number(row.unitPrice5) : null,
|
||||
workdayCount5: row.workdayCount5 != null ? Number(row.workdayCount5) : null,
|
||||
feeSubtotal5: row.feeSubtotal5 != null ? Number(row.feeSubtotal5) : null,
|
||||
workdayCount6: row.workdayCount6 != null ? Number(row.workdayCount6) : null,
|
||||
feeSubtotal6: row.feeSubtotal6 != null ? Number(row.feeSubtotal6) : null,
|
||||
avgUnitPrice: row.avgUnitPrice != null ? Number(row.avgUnitPrice) : null
|
||||
}))
|
||||
fallbackDetailRows.value = await mergeWithDictRows(detailRowsFromStore)
|
||||
console.log('✅ 转换后的 DetailRow 数据:', detailRowsFromStore)
|
||||
|
||||
if (shouldForceDefaultLoad()) {
|
||||
detailRows.value = buildDefaultRows()
|
||||
syncServiceBudgetToRows()
|
||||
return
|
||||
}
|
||||
const data = useServicePricingState.value && serviceMethod.value
|
||||
? await zxFwPricingStore.loadServicePricingMethodState<DetailRow>(props.contractId!, props.serviceId!, serviceMethod.value)
|
||||
: useHtMethodState.value
|
||||
? await zxFwPricingStore.loadHtFeeMethodState<GridState>(
|
||||
props.htMainStorageKey!,
|
||||
props.htRowId!,
|
||||
props.htMethodType!
|
||||
)
|
||||
: await zxFwPricingStore.loadKeyState<GridState>(props.storageKey)
|
||||
if (data) {
|
||||
detailRows.value = mergeWithDictRows(data.detailRows)
|
||||
syncServiceBudgetToRows()
|
||||
return
|
||||
}
|
||||
detailRows.value = buildDefaultRows()
|
||||
syncServiceBudgetToRows()
|
||||
} catch (error) {
|
||||
detailRows.value = await buildDefaultRows()
|
||||
console.error('loadFromIndexedDB failed:', error)
|
||||
detailRows.value = buildDefaultRows()
|
||||
syncServiceBudgetToRows()
|
||||
}
|
||||
}
|
||||
|
||||
@ -954,29 +567,16 @@ const relabelRowsFromExpertDict = async () => {
|
||||
|
||||
let isBulkClipboardMutation = false
|
||||
|
||||
/**
|
||||
* 提交网格变更:同步计算值、刷新UI、保存到数据库
|
||||
* @param source 变更来源(用于日志追踪)
|
||||
*/
|
||||
const commitGridChanges = (source: string) => {
|
||||
console.log('🔄 提交网格变更:', source)
|
||||
syncServiceBudgetToRows()
|
||||
gridApi.value?.refreshCells({ force: true })
|
||||
scheduleAutoRowHeights()
|
||||
void saveToIndexedDB()
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理单元格值变化事件
|
||||
* 触发时机:用户编辑完单元格后
|
||||
*/
|
||||
const handleCellValueChanged = (event?: any) => {
|
||||
/*if (isBulkClipboardMutation) {
|
||||
return
|
||||
}
|
||||
|
||||
commitGridChanges('cell-value-changed')*/
|
||||
saveToIndexedDB()
|
||||
if (isBulkClipboardMutation) return
|
||||
commitGridChanges('cell-value-changed')
|
||||
}
|
||||
|
||||
const handleBulkMutationStart = () => {
|
||||
@ -984,8 +584,8 @@ const handleBulkMutationStart = () => {
|
||||
}
|
||||
|
||||
const handleBulkMutationEnd = (event?: any) => {
|
||||
// isBulkClipboardMutation = false
|
||||
// commitGridChanges(event?.type || 'bulk-end')
|
||||
isBulkClipboardMutation = false
|
||||
commitGridChanges(event?.type || 'bulk-end')
|
||||
}
|
||||
|
||||
const handleGridReady = (event: GridReadyEvent<DetailRow>) => {
|
||||
@ -1063,34 +663,16 @@ const processCellFromClipboard = (params: any) => {
|
||||
return params.value
|
||||
}
|
||||
|
||||
/**
|
||||
* 组件挂载时加载数据
|
||||
*/
|
||||
onMounted(async () => {
|
||||
console.log('🚀 组件挂载')
|
||||
await loadFromIndexedDB()
|
||||
scheduleAutoRowHeights()
|
||||
})
|
||||
|
||||
/**
|
||||
* 组件激活时重新加载数据(从其他页面切换回来时)
|
||||
*/
|
||||
onActivated(async () => {
|
||||
console.log('🔙 组件激活')
|
||||
await loadFromIndexedDB()
|
||||
scheduleAutoRowHeights()
|
||||
})
|
||||
|
||||
watch(
|
||||
() => useDataStore().items,
|
||||
async (newItems) => {
|
||||
if (Object.keys(newItems).length > 0) {
|
||||
loadFromIndexedDB();
|
||||
}
|
||||
},
|
||||
{ immediate: true, deep: true }
|
||||
)
|
||||
|
||||
watch(
|
||||
() => props.storageKey,
|
||||
() => {
|
||||
@ -1113,21 +695,12 @@ watch(
|
||||
}
|
||||
)
|
||||
|
||||
/**
|
||||
* 组件失活时保存数据(切换页面时触发)
|
||||
* 配合 Vue 的 <keep-alive> 使用
|
||||
*/
|
||||
onDeactivated(() => {
|
||||
console.log('🔄 组件失活,保存数据')
|
||||
gridApi.value?.stopEditing()
|
||||
void saveToIndexedDB()
|
||||
})
|
||||
|
||||
/**
|
||||
* 组件卸载前保存数据(关闭页面时触发)
|
||||
*/
|
||||
onBeforeUnmount(() => {
|
||||
console.log('💔 组件卸载,保存数据')
|
||||
gridApi.value?.stopEditing()
|
||||
gridApi.value = null
|
||||
if (autoHeightSyncTimer) {
|
||||
@ -1150,7 +723,7 @@ onBeforeUnmount(() => {
|
||||
<AgGridVue
|
||||
:style="agGridStyle"
|
||||
:rowData="detailRows"
|
||||
:pinnedBottomRowData="pinnedTopRowData"
|
||||
:pinnedTopRowData="pinnedTopRowData"
|
||||
:columnDefs="gridColumnDefs"
|
||||
:gridOptions="gridOptions"
|
||||
:theme="myTheme"
|
||||
|
||||
@ -279,6 +279,9 @@ const columnDefs: ColDef<FeeRow>[] = [
|
||||
: typeof params.node?.rowIndex === 'number'
|
||||
? params.node.rowIndex + 1
|
||||
: '',
|
||||
cellClassRules: {
|
||||
'ag-summary-label-cell': params => isSubtotalRow(params.data)
|
||||
},
|
||||
colSpan: params => (isSubtotalRow(params.data) ? 2 : 1)
|
||||
},
|
||||
{
|
||||
|
||||
@ -375,6 +375,12 @@ const saveToIndexedDB = async (force = false) => {
|
||||
const snapshot = JSON.stringify(payload.detailRows)
|
||||
if (!force && snapshot === lastSavedSnapshot.value) return
|
||||
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
|
||||
} catch (error) {
|
||||
console.error('saveToIndexedDB failed:', error)
|
||||
@ -498,6 +504,7 @@ const columnDefs: ColDef<FeeMethodRow>[] = [
|
||||
? ''
|
||||
: 'editable-cell-line',
|
||||
cellClassRules: {
|
||||
'ag-summary-label-cell': params => isSummaryRow(params.data),
|
||||
'editable-cell-empty': params => params.value == null || params.value === ''
|
||||
}
|
||||
},
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
<!--
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { computed, watch } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
interface ServiceItem {
|
||||
@ -11,6 +10,7 @@ interface ServiceItem {
|
||||
|
||||
const props = defineProps<{
|
||||
services: ServiceItem[]
|
||||
serviceRows?: string[][]
|
||||
modelValue: string[]
|
||||
}>()
|
||||
|
||||
@ -19,68 +19,67 @@ const emit = defineEmits<{
|
||||
}>()
|
||||
const { t } = useI18n()
|
||||
|
||||
/** 互斥规则配置 */
|
||||
const MUTUAL_EXCLUSION_RULES: Record<string, string[]> = {
|
||||
'D1': ['D2-1', 'D2-2', 'D3-1', 'D3-2', 'D3-3', 'D3-4', 'D3-6-1', 'D3-7'],
|
||||
'D2-1': ['D1', 'D2-2', 'D3-4', 'D3-6-1', 'D3-7'],
|
||||
'D2-2': ['D2-1', 'D3-1', 'D3-2', 'D3-3']
|
||||
}
|
||||
|
||||
/** 当前已选服务的 code 集合 */
|
||||
const selectedCodes = computed(() => {
|
||||
return props.services
|
||||
.filter(s => selectedSet.value.has(s.id))
|
||||
.map(s => s.code)
|
||||
})
|
||||
|
||||
/** 获取某服务被哪些服务互斥 */
|
||||
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 serviceById = computed(() => new Map(props.services.map(item => [item.id, item])))
|
||||
const firstRowIds = computed(() => {
|
||||
const rows = Array.isArray(props.serviceRows) ? props.serviceRows : []
|
||||
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[][]
|
||||
|
||||
const toggleService = (item: ServiceItem, checked: boolean) => {
|
||||
const { disabled } = isServiceDisabled(item)
|
||||
// 如果尝试勾选被禁用的服务,直接忽略
|
||||
if (checked && disabled) return
|
||||
const used = new Set<string>()
|
||||
const grouped = rows
|
||||
.map(row => row.map(id => serviceById.value.get(id)).filter((item): item is ServiceItem => Boolean(item)))
|
||||
.map(row => row.filter(item => {
|
||||
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 next = new Set(props.modelValue)
|
||||
if (checked) {
|
||||
next.add(item.id)
|
||||
// 勾选时,自动取消互斥的服务
|
||||
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)
|
||||
}
|
||||
if (firstRowIdSet.value.has(id)) {
|
||||
firstRowIds.value.forEach(firstId => next.delete(firstId))
|
||||
}
|
||||
next.add(id)
|
||||
} 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 = () => {
|
||||
emit('update:modelValue', [])
|
||||
}
|
||||
@ -89,7 +88,10 @@ const clearAll = () => {
|
||||
<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>
|
||||
<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
|
||||
type="button"
|
||||
class="cursor-pointer h-6 rounded-md border px-2 text-[13px] text-muted-foreground transition hover:bg-accent"
|
||||
@ -99,102 +101,37 @@ const clearAll = () => {
|
||||
</button>
|
||||
</div>
|
||||
<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">
|
||||
<div
|
||||
v-for="(row, rowIndex) in groupedRows"
|
||||
:key="`service-row-${rowIndex}`"
|
||||
class="flex flex-wrap items-start gap-1 border-b border-slate-200 pb-1.5 last:border-b-0 last:pb-0"
|
||||
>
|
||||
<label
|
||||
v-for="item in props.services"
|
||||
v-for="item in row"
|
||||
: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',
|
||||
isServiceDisabled(item).disabled
|
||||
? 'opacity-50 cursor-not-allowed hover:bg-transparent'
|
||||
: 'hover:bg-muted/60'
|
||||
'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'
|
||||
]"
|
||||
:title="isServiceDisabled(item).reason"
|
||||
>
|
||||
<input
|
||||
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)"
|
||||
: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
|
||||
type="checkbox"
|
||||
class="mt-0.5"
|
||||
: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>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="props.services.length === 0" class="px-2 py-4 text-center text-xs text-muted-foreground">
|
||||
{{ t('serviceSelector.empty') }}
|
||||
</div>
|
||||
|
||||
@ -31,7 +31,6 @@ import { getServiceDictItemById, getWorkListEntries, wholeProcessTasks } from '@
|
||||
import { WorkType } from '@/sql'
|
||||
import { useZxFwPricingStore } from '@/pinia/zxFwPricing'
|
||||
import { useKvStore } from '@/pinia/kv'
|
||||
import { useDataStore } from '@/pinia/zx'
|
||||
import { Trash2 } from 'lucide-vue-next'
|
||||
|
||||
interface WorkContentRow {
|
||||
@ -149,132 +148,17 @@ const loadProjectIndustryId = async () => {
|
||||
}
|
||||
|
||||
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 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 groupedBy: 'fid' | 'sid' | null = null
|
||||
let matchedWholeProcessGroup: { fid: number; industry: number; sid: number[] } | null = null
|
||||
isWholeProcessGroupedMode.value = false
|
||||
groupedServiceGroups.value = []
|
||||
if (props.dictMode === 'service') {
|
||||
const sid = Number(props.serviceId.split('-').pop())
|
||||
const sid = Number(props.serviceId)
|
||||
const industryId = await loadProjectIndustryId()
|
||||
const wholeProcessGroupByFid = wholeProcessTasks.find(
|
||||
item => Number(item.fid) === sid && Number(item.industry) === industryId
|
||||
@ -320,7 +204,7 @@ const buildDefaultRowsFromDict2 = async (): Promise<WorkContentRow[]> => {
|
||||
})
|
||||
} else {
|
||||
filtered.sort((a, b) => a.order - b.order)
|
||||
}*/
|
||||
}
|
||||
|
||||
for (const entry of filtered) {
|
||||
const content = String(entry.text || '').trim()
|
||||
@ -379,67 +263,20 @@ const emitCheckedChange = () => {
|
||||
emit('checkedChange', [...checkedIds.value])
|
||||
}
|
||||
|
||||
const saveToStore = async () => {
|
||||
/*const payload: WorkContentState = {
|
||||
const saveToStore = () => {
|
||||
const payload: WorkContentState = {
|
||||
detailRows: getPersistableRows(rowData.value).map(item => ({ ...item }))
|
||||
}
|
||||
zxFwPricingStore.setKeyState(props.storageKey, payload)
|
||||
emitCheckedChange()*/
|
||||
const rows = { value: [...rowData.value], type: `${props.contractId}-content`, id: `${props.contractId}-content` }
|
||||
const stats = await useDataStore().upsert(rows)
|
||||
console.log('💾 数据保存成功:', rows)
|
||||
emitCheckedChange()
|
||||
}
|
||||
|
||||
const loadFromStore = async () => {
|
||||
// TODO 数据重新加载
|
||||
let defaultRows = await buildDefaultRowsFromDict()
|
||||
const zxRows = await useDataStore().query([
|
||||
{ field: 'type', value: `${props.contractId}-zxFw`, operator: 'eq' }
|
||||
])
|
||||
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)
|
||||
const defaultRows =
|
||||
props.dictMode === 'none'
|
||||
? []
|
||||
: await buildDefaultRowsFromDict()
|
||||
const state = await zxFwPricingStore.loadKeyState<WorkContentState>(props.storageKey)
|
||||
if (Array.isArray(state?.detailRows) && state.detailRows.length > 0) {
|
||||
const persistedRows = state.detailRows.map(item => ({
|
||||
...item,
|
||||
@ -494,9 +331,9 @@ const loadFromStore = async () => {
|
||||
} else {
|
||||
rowData.value = withAddTriggerRows(defaultRows)
|
||||
saveToStore()
|
||||
}*/
|
||||
// emitCheckedChange()
|
||||
// await syncGroupedRowsRender()
|
||||
}
|
||||
emitCheckedChange()
|
||||
await syncGroupedRowsRender()
|
||||
}
|
||||
|
||||
const handleCheckedToggle = (id: string, checked: boolean) => {
|
||||
@ -539,11 +376,24 @@ const groupRowRendererParams = computed<IGroupCellRendererParams<WorkContentRow>
|
||||
innerRenderer: (params: ICellRendererParams<WorkContentRow>) => {
|
||||
const wrapper = document.createElement('div')
|
||||
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')
|
||||
label.className = 'work-content-group-label'
|
||||
label.textContent = String(params.valueFormatted || params.value || params.node.key || '')
|
||||
wrapper.appendChild(label)
|
||||
wrapper.append(checkbox, label)
|
||||
return wrapper
|
||||
}
|
||||
}
|
||||
@ -566,19 +416,8 @@ const contentCellRenderer = (params: ICellRendererParams<WorkContentRow>) => {
|
||||
wrapper.appendChild(label)
|
||||
return wrapper
|
||||
}
|
||||
|
||||
if (!isAddTriggerRow(data) && !data.custom) {
|
||||
const checkbox = document.createElement('input')
|
||||
checkbox.type = 'checkbox'
|
||||
checkbox.checked = data.checked
|
||||
checkbox.className = 'work-content-check'
|
||||
checkbox.addEventListener('change', (event: Event) => {
|
||||
const target = event.target as HTMLInputElement
|
||||
handleCheckedToggle(data.id, target.checked)
|
||||
})
|
||||
wrapper.appendChild(checkbox)
|
||||
}
|
||||
|
||||
// 自定义行不显示 checkbox,直接显示文本;空时显示 placeholder
|
||||
if (data.custom) {
|
||||
const label = document.createElement('span')
|
||||
if (!data.content) {
|
||||
label.className = 'work-content-placeholder'
|
||||
@ -590,6 +429,20 @@ const contentCellRenderer = (params: ICellRendererParams<WorkContentRow>) => {
|
||||
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')
|
||||
label.className = 'work-content-text'
|
||||
label.textContent = String(data.content || '')
|
||||
wrapper.appendChild(checkbox)
|
||||
wrapper.appendChild(label)
|
||||
return wrapper
|
||||
}
|
||||
|
||||
const columnDefs: ColDef<WorkContentRow>[] = [
|
||||
{
|
||||
@ -599,45 +452,13 @@ const columnDefs: ColDef<WorkContentRow>[] = [
|
||||
suppressMovable: true,
|
||||
editable: false,
|
||||
colSpan: params => (isAddTriggerRow(params.data) ? 5 : 1),
|
||||
/*valueGetter: params => {
|
||||
valueGetter: params => {
|
||||
if (!params.node || params.node.group || isAddTriggerRow(params.data)) return ''
|
||||
if (!isWholeProcessGroupedMode.value) return (params.node.rowIndex ?? 0) + 1
|
||||
const siblings = params.node.parent?.childrenAfterSort || []
|
||||
const visibleLeafSiblings = siblings.filter(node => !node.group && !isAddTriggerRow(node.data as WorkContentRow))
|
||||
const index = visibleLeafSiblings.findIndex(node => node.id === params.node?.id)
|
||||
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>) => {
|
||||
const row = params.data
|
||||
@ -670,7 +491,7 @@ const columnDefs: ColDef<WorkContentRow>[] = [
|
||||
cellStyle: { whiteSpace: 'normal', lineHeight: '1.5' },
|
||||
cellRenderer: contentCellRenderer
|
||||
},
|
||||
/*{
|
||||
{
|
||||
headerName: t('workContent.columns.type'),
|
||||
field: 'type',
|
||||
minWidth: 100,
|
||||
@ -678,7 +499,7 @@ const columnDefs: ColDef<WorkContentRow>[] = [
|
||||
editable: false,
|
||||
valueFormatter: (params: ValueFormatterParams<WorkContentRow>) =>
|
||||
isAddTriggerRow(params.data) ? '' : String(params.value || '')
|
||||
},*/
|
||||
},
|
||||
{
|
||||
headerName: t('workContent.columns.remark'),
|
||||
field: 'remark',
|
||||
@ -695,7 +516,7 @@ const columnDefs: ColDef<WorkContentRow>[] = [
|
||||
},
|
||||
valueFormatter: params => (isAddTriggerRow(params.data) ? '' : (params.value || t('workContent.clickToInput')))
|
||||
},
|
||||
/*{
|
||||
{
|
||||
headerName: t('workContent.columns.actions'),
|
||||
colId: 'actions',
|
||||
minWidth: 92,
|
||||
@ -735,7 +556,7 @@ const columnDefs: ColDef<WorkContentRow>[] = [
|
||||
}
|
||||
}
|
||||
})
|
||||
}*/
|
||||
}
|
||||
]
|
||||
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)
|
||||
if (isWholeProcessGroupedMode.value) {
|
||||
const groupedMap = new Map<string, WorkContentRow[]>()
|
||||
@ -787,11 +608,6 @@ const withAddTriggerRows2 = (rows: WorkContentRow[]) => {
|
||||
return [...pureRows, createAddTriggerRow()]
|
||||
}
|
||||
|
||||
const withAddTriggerRows = (rows: WorkContentRow[]) => {
|
||||
const pureRows = getPersistableRows(rows)
|
||||
return pureRows
|
||||
}
|
||||
|
||||
const getDataPath = (data: WorkContentRow) => {
|
||||
const path = Array.isArray(data?.path)
|
||||
? data.path.map(segment => String(segment || '').trim()).filter(Boolean)
|
||||
@ -869,21 +685,11 @@ watch(isWholeProcessGroupedMode, () => {
|
||||
})
|
||||
|
||||
watch(
|
||||
() => useDataStore().items,
|
||||
async (newItems) => {
|
||||
if (Object.keys(newItems).length > 0) {
|
||||
loadFromStore();
|
||||
}
|
||||
},
|
||||
{ immediate: true, deep: true }
|
||||
)
|
||||
|
||||
/*watch(
|
||||
() => rowData.value.length,
|
||||
() => {
|
||||
void syncGroupedRowsRender()
|
||||
}
|
||||
)*/
|
||||
)
|
||||
|
||||
watch(locale, () => {
|
||||
void loadFromStore()
|
||||
@ -926,10 +732,10 @@ const confirmDeleteRow = () => {
|
||||
:columnDefs="gridColumnDefs"
|
||||
:theme="myTheme"
|
||||
:getRowId="(params: { data: WorkContentRow }) => params.data.id"
|
||||
:treeData="true"
|
||||
:treeData="isWholeProcessGroupedMode"
|
||||
:getDataPath="getDataPath"
|
||||
:groupDefaultExpanded="true ? -1 : 0"
|
||||
:groupDisplayType="true ? 'groupRows' : undefined"
|
||||
:groupDefaultExpanded="isWholeProcessGroupedMode ? -1 : 0"
|
||||
:groupDisplayType="isWholeProcessGroupedMode ? 'groupRows' : undefined"
|
||||
:groupRowRendererParams="groupRowRendererParams"
|
||||
:animateRows="true"
|
||||
:localeText="AG_GRID_LOCALE_CN"
|
||||
@ -1018,11 +824,6 @@ const confirmDeleteRow = () => {
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
/* 隐藏一级分组行的复选框,但保留折叠箭头 */
|
||||
:deep(.ag-group-cell .ag-cell-value .ag-cell-wrapper > input[type='checkbox']) {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
:deep(.work-content-group-check) {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
|
||||
@ -138,6 +138,16 @@ const hasMeaningfulFactorValue = (rows: SourceRow[] | undefined) =>
|
||||
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 dbValueMap = new Map<string, SourceRow>()
|
||||
for (const row of rowsFromDb || []) {
|
||||
@ -308,7 +318,7 @@ const saveFactorChangeState = async (changedRowIds: string[]) => {
|
||||
const loadGridState = async (storageKey: string): Promise<GridState | null> => {
|
||||
if (!storageKey) return null
|
||||
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。
|
||||
const legacyData = await kvStore.getItem<GridState>(storageKey)
|
||||
|
||||
@ -9,7 +9,7 @@ import { withReadonlyAutoHeight } from '@/lib/agGridReadonlyAutoHeight'
|
||||
import { decimalAggSum, roundTo, sumByNumber } from '@/lib/decimal'
|
||||
import { parseNumberOrNull } from '@/lib/number'
|
||||
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 { SwitchRoot, SwitchThumb } from 'reka-ui'
|
||||
import { useKvStore } from '@/pinia/kv'
|
||||
@ -22,6 +22,8 @@ import {
|
||||
ToastViewport
|
||||
} from 'reka-ui'
|
||||
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
|
||||
})
|
||||
}
|
||||
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 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 child of group.children) {
|
||||
|
||||
@ -345,61 +326,25 @@ interface ContractScaleChangeState {
|
||||
updatedAt: number
|
||||
}
|
||||
|
||||
const XM_PROJECT_PHASE_KEY = 'xm-project-phase-v1'
|
||||
|
||||
interface ProjectPhaseState {
|
||||
feePhase?: string
|
||||
feeStage?: string
|
||||
}
|
||||
|
||||
const props = defineProps<{
|
||||
title: string
|
||||
dbKey: string
|
||||
xmInfoKey?: string | null
|
||||
baseInfoKey?: string
|
||||
titleHint?: string
|
||||
titleHintAria?: string
|
||||
}>()
|
||||
|
||||
let persistTimer: ReturnType<typeof setTimeout> | null = null
|
||||
const gridApi = ref<GridApi<DetailRow> | null>(null)
|
||||
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 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 = () => {
|
||||
if (!gridApi.value) return
|
||||
const pinnedTopNode = gridApi.value.getPinnedTopRow(0)
|
||||
@ -453,26 +398,34 @@ const columnDefs: ColDef<DetailRow>[] = [
|
||||
}
|
||||
},
|
||||
{
|
||||
headerName: '造价金额(元)',
|
||||
field: 'amountYuan',
|
||||
headerName: t('pricingScale.columns.landArea'),
|
||||
field: 'landArea',
|
||||
headerClass: 'ag-right-aligned-header',
|
||||
minWidth: 120,
|
||||
minWidth: 100,
|
||||
flex: 1,
|
||||
editable: false,
|
||||
aggFunc: decimalAggSum,
|
||||
valueGetter: params => params.data?.amount,
|
||||
editable: params => !roughCalcEnabled.value && !params.node?.group && !params.node?.rowPinned && Boolean(params.data?.hasArea),
|
||||
cellClass: params =>
|
||||
!params.node?.group && !params.node?.rowPinned && params.data?.hasArea
|
||||
? 'editable-cell-line'
|
||||
: '',
|
||||
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 => {
|
||||
if (roughCalcEnabled.value) {
|
||||
return ''
|
||||
}
|
||||
const amount = params.value
|
||||
if (typeof amount !== 'number' || !Number.isFinite(amount)) {
|
||||
if (!params.node?.group && !params.node?.rowPinned && !params.data?.hasArea) {
|
||||
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'),
|
||||
minWidth: 200,
|
||||
flex: 2,
|
||||
cellClassRules: {
|
||||
'ag-summary-label-cell': params => Boolean(params.node?.rowPinned)
|
||||
},
|
||||
cellRendererParams: {
|
||||
suppressCount: true
|
||||
},
|
||||
@ -807,10 +763,24 @@ onMounted(() => {
|
||||
<div class="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 gap-1.5">
|
||||
<h3
|
||||
class="text-sm font-semibold text-foreground cursor-pointer select-none transition-colors hover:text-primary">
|
||||
{{ 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">
|
||||
<span class=" text-xs text-muted-foreground">简要计算</span>
|
||||
<SwitchRoot
|
||||
@ -822,42 +792,6 @@ onMounted(() => {
|
||||
</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">
|
||||
<AgGridVue :style="agGridStyle" :rowData="visibleRowData" :pinnedTopRowData="pinnedTopRowData"
|
||||
:columnDefs="gridColumnDefs" :autoGroupColumnDef="autoGroupColumnDef" :gridOptions="detailGridOptions" :theme="myTheme"
|
||||
|
||||
@ -44,24 +44,3 @@
|
||||
opacity: 0;
|
||||
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">
|
||||
import { computed, onBeforeUnmount, onMounted, ref } from 'vue'
|
||||
import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { useTabStore } from '@/pinia/tab'
|
||||
import { useKvStore } from '@/pinia/kv'
|
||||
import { useUiPrefsStore } from '@/pinia/uiPrefs'
|
||||
import {
|
||||
BarChart3,
|
||||
Calculator,
|
||||
Check,
|
||||
ChevronDown,
|
||||
Languages,
|
||||
X
|
||||
} from 'lucide-vue-next'
|
||||
import { getIndustryDisplayName, industryTypeList } from '@/sql'
|
||||
import { initializeProjectFactorStates, initializeProjectScaleState } from '@/lib/projectWorkspace'
|
||||
import {
|
||||
SelectContent,
|
||||
SelectIcon,
|
||||
SelectItem,
|
||||
SelectItemIndicator,
|
||||
SelectItemText,
|
||||
SelectPortal,
|
||||
SelectRoot,
|
||||
SelectTrigger,
|
||||
SelectViewport
|
||||
} from 'reka-ui'
|
||||
import {
|
||||
buildDisclaimerUrl,
|
||||
buildProjectUrl,
|
||||
consumePendingDisclaimerAction,
|
||||
DEFAULT_PROJECT_ID,
|
||||
hasAcceptedRestrictedDisclaimer,
|
||||
FORCE_HOME_QUERY_KEY,
|
||||
isDisclaimerAcceptanceRequired,
|
||||
NEW_PROJECT_QUERY_KEY,
|
||||
OPEN_PROJECT_DIALOG_QUERY_KEY,
|
||||
setPendingDisclaimerAction,
|
||||
PROJECT_TAB_ID,
|
||||
QUICK_PROJECT_ID,
|
||||
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_MAJOR_FACTOR_KEY = 'xm-major-factor-v1'
|
||||
const PROJECT_SCALE_KEY = 'xm-info-v3'
|
||||
const FILE_LEDGER_URL = 'https://www.lianzhong.com.cn/file?fileNo=24'
|
||||
const getActiveProjectId = () => readCurrentProjectId()
|
||||
|
||||
const tabStore = useTabStore()
|
||||
const kvStore = useKvStore()
|
||||
const uiPrefsStore = useUiPrefsStore()
|
||||
const { t, locale } = useI18n()
|
||||
const { t, tm, locale } = useI18n()
|
||||
const projectDialogOpen = ref(false)
|
||||
const projectIndustry = ref(String(industryTypeList[0]?.id || ''))
|
||||
const projectSubmitting = ref(false)
|
||||
@ -82,21 +99,24 @@ const homeImportConfirmOpen = ref(false)
|
||||
const pendingHomeImportFile = ref<File | null>(null)
|
||||
const pendingHomeImportFileName = ref('')
|
||||
const existingProjectDialogOpen = ref(false)
|
||||
const disclaimerRequired = ref(false)
|
||||
const existingProjects = ref<Array<{ id: string; name: string; updatedAt: string }>>([])
|
||||
const existingProjectLoading = ref(false)
|
||||
const hasExistingProjects = ref(false)
|
||||
const openedProjectIds = ref<string[]>([])
|
||||
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 toggleLocale = () => {
|
||||
const next = locale.value === 'en-US' ? '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 projectId = String(projectIdRaw || '').trim()
|
||||
if (projectId !== DEFAULT_PROJECT_ID) return undefined
|
||||
@ -162,8 +182,35 @@ const loadProjectDefaults = async () => {
|
||||
}
|
||||
|
||||
const openProjectCalc = async () => {
|
||||
|
||||
await runWithDisclaimerGuard({ type: 'project' }, async () => {
|
||||
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[]) => {
|
||||
@ -216,10 +263,11 @@ const startExistingProjectPolling = () => {
|
||||
}
|
||||
|
||||
const openExistingProjectDialog = async () => {
|
||||
projectDialogOpen.value = false
|
||||
await runWithDisclaimerGuard({ type: 'existing-project' }, async () => {
|
||||
existingProjectDialogOpen.value = true
|
||||
await refreshExistingProjects()
|
||||
startExistingProjectPolling()
|
||||
})
|
||||
}
|
||||
|
||||
const closeExistingProjectDialog = () => {
|
||||
@ -310,8 +358,38 @@ const enterQuickCalc = (contractName: string) => {
|
||||
tabStore.hasCompletedSetup = true
|
||||
}
|
||||
|
||||
const openQuickCalc = () => {
|
||||
window.alert(t('home.cards.developing'))
|
||||
const openQuickCalc = async () => {
|
||||
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) => {
|
||||
@ -325,7 +403,33 @@ const handleHomeImportChange = (event: Event) => {
|
||||
}
|
||||
|
||||
const openHomeImport = () => {
|
||||
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 = () => {
|
||||
@ -354,10 +458,85 @@ const handleHomeVisibilityChange = () => {
|
||||
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(() => {
|
||||
syncDisclaimerRequirement()
|
||||
void refreshExistingProjects()
|
||||
void loadProjectDefaults()
|
||||
void loadQuickDefaults()
|
||||
void replayPendingDisclaimerAction()
|
||||
refreshHeroCopy()
|
||||
window.addEventListener('focus', handleHomeWindowFocus)
|
||||
document.addEventListener('visibilitychange', handleHomeVisibilityChange)
|
||||
try {
|
||||
@ -385,127 +564,109 @@ onBeforeUnmount(() => {
|
||||
window.removeEventListener('focus', handleHomeWindowFocus)
|
||||
document.removeEventListener('visibilitychange', handleHomeVisibilityChange)
|
||||
})
|
||||
|
||||
watch(
|
||||
() => locale.value,
|
||||
() => {
|
||||
refreshHeroCopy()
|
||||
}
|
||||
)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<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="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-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="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-[radial-gradient(ellipse_80%_60%_at_50%_-10%,rgba(59,130,246,0.06),transparent_70%)]" />
|
||||
<div class="relative w-full max-w-[1240px]">
|
||||
<div class="absolute right-0 top-0 z-10">
|
||||
<button
|
||||
type="button"
|
||||
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"
|
||||
<Button
|
||||
variant="outline"
|
||||
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"
|
||||
>
|
||||
<Languages class="h-3.5 w-3.5" />
|
||||
<span>{{ localeBadge }}</span>
|
||||
</button>
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div class="home-title text-center">
|
||||
<div class="home-title text-center" :style="{ maxWidth: '800px' }">
|
||||
<h1
|
||||
class="text-2xl font-semibold 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;"
|
||||
class="text-2xl tracking-tight text-slate-900 lg:text-3xl"
|
||||
:style="{ whiteSpace: 'pre-line', fontWeight: '200', fontFamily: 'HarmonyOS_Sans_SC' }"
|
||||
>
|
||||
{{ t('home.title') }}
|
||||
</h1>
|
||||
<p class="mt-1.5 text-sm text-slate-500">{{ t('home.subtitle') }}</p>
|
||||
</div>
|
||||
|
||||
<div class="mt-5 grid items-stretch gap-4 md:grid-cols-2 xl:grid-cols-4">
|
||||
<section
|
||||
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="mt-5 grid items-stretch gap-4 md:grid-cols-2 xl:grid-cols-5">
|
||||
<div
|
||||
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)]"
|
||||
>
|
||||
<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 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" />
|
||||
</div>
|
||||
<h2 class="relative mt-8 whitespace-pre-line text-xl font-semibold leading-tight tracking-tight">
|
||||
{{ t('home.cards.heroTitle') }}
|
||||
</h2>
|
||||
<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" />
|
||||
<p class="relative mt-4 whitespace-pre-line text-xs leading-5 text-red-200/80">{{ t('home.cards.heroDesc') }}</p>
|
||||
</section>
|
||||
|
||||
<article
|
||||
role="button"
|
||||
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"
|
||||
@click="openProjectCalc"
|
||||
@keydown.enter.prevent="openProjectCalc"
|
||||
@keydown.space.prevent="openProjectCalc"
|
||||
>
|
||||
<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">
|
||||
<BarChart3 class="h-5 w-5" />
|
||||
</div>
|
||||
<h3 class="mt-4 text-base font-semibold text-slate-900">{{ t('home.cards.projectBudget') }}</h3>
|
||||
<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') }}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
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"
|
||||
>
|
||||
<span class="flex items-center gap-1">
|
||||
{{ t('home.cards.enter') }}
|
||||
<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>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<article
|
||||
role="button"
|
||||
tabindex="0"
|
||||
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"
|
||||
@click="openQuickCalc"
|
||||
@keydown.enter.prevent="openQuickCalc"
|
||||
@keydown.space.prevent="openQuickCalc"
|
||||
>
|
||||
<div>
|
||||
<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">
|
||||
<Calculator class="h-5 w-5" />
|
||||
</div>
|
||||
<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>
|
||||
<h2 class="relative mt-8 text-2xl font-semibold leading-tight tracking-tight lg:text-3xl" :style="{ whiteSpace: 'pre-line', fontSize: '20px' }">{{ heroTitleText }}</h2>
|
||||
<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" />
|
||||
<p class="relative mt-4 whitespace-pre-line text-xs leading-5 text-red-200/60">{{ heroDescText }}</p>
|
||||
</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
|
||||
v-for="(card, index) in homeActionCards"
|
||||
:key="card.key"
|
||||
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"
|
||||
:class="[
|
||||
'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',
|
||||
`home-entry-item--${index + 2}`
|
||||
]"
|
||||
@click="card.clickFunc"
|
||||
@keydown.enter.prevent="card.clickFunc"
|
||||
@keydown.space.prevent="card.clickFunc"
|
||||
>
|
||||
<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">
|
||||
<div
|
||||
:class="[
|
||||
'inline-flex h-11 w-11 items-center justify-center rounded-xl border shadow-sm transition-transform duration-200 group-hover:scale-105',
|
||||
card.iconWrapClass
|
||||
]"
|
||||
>
|
||||
<svg v-if="card.icon === 'project'" viewBox="0 0 1024 1024" :class="card.iconClass" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
|
||||
<path
|
||||
fill="currentColor"
|
||||
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"
|
||||
/>
|
||||
</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">
|
||||
<path
|
||||
fill="currentColor"
|
||||
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"
|
||||
transform="translate(0,800) scale(0.1,-0.1)"
|
||||
/>
|
||||
<path
|
||||
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"
|
||||
transform="translate(0,800) scale(0.1,-0.1)"
|
||||
/>
|
||||
<path
|
||||
fill="currentColor"
|
||||
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"
|
||||
transform="translate(0,800) scale(0.1,-0.1)"
|
||||
/>
|
||||
</svg>
|
||||
<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">
|
||||
<path
|
||||
fill="currentColor"
|
||||
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"
|
||||
/>
|
||||
</svg>
|
||||
<svg
|
||||
v-else
|
||||
viewBox="0 0 1024 1024"
|
||||
class="h-6 w-6"
|
||||
:class="card.iconClass"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
aria-hidden="true"
|
||||
>
|
||||
@ -515,15 +676,37 @@ onBeforeUnmount(() => {
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<h3 class="mt-4 text-base font-semibold text-slate-900">{{ t('home.cards.relatedFiles') }}</h3>
|
||||
<p class="mt-1.5 text-xs leading-5 text-slate-500">{{ t('home.cards.relatedFilesDesc') }}</p>
|
||||
<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.${card.desc}`) }}</p>
|
||||
</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.openRelatedFiles') }}</span>
|
||||
<div class="mt-4 flex items-center justify-between gap-2">
|
||||
<button
|
||||
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>
|
||||
</article>
|
||||
</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>
|
||||
|
||||
@ -532,22 +715,18 @@ onBeforeUnmount(() => {
|
||||
class="fixed inset-0 z-[90] flex items-center justify-center bg-black/45 p-4"
|
||||
@click.self="closeExistingProjectDialog"
|
||||
>
|
||||
<div class="w-full max-w-lg rounded-3xl border border-slate-200/60 bg-white shadow-2xl">
|
||||
<div class="flex items-start justify-between border-b border-slate-100 px-6 pt-6 pb-4">
|
||||
<div class="w-full max-w-lg rounded-xl border bg-background shadow-2xl">
|
||||
<div class="flex items-center justify-between border-b px-5 py-4">
|
||||
<div>
|
||||
<h3 class="text-xl font-bold text-[#1a1a1a]">{{ t('home.dialog.chooseExistingProject') }}</h3>
|
||||
<p class="mt-1.5 text-base text-[#666]">{{ t('home.dialog.chooseExistingProjectDesc') }}</p>
|
||||
<h3 class="text-base font-semibold text-foreground">{{ t('home.dialog.chooseExistingProject') }}</h3>
|
||||
<p class="mt-1 text-sm text-muted-foreground">{{ t('home.dialog.chooseExistingProjectDesc') }}</p>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
class="flex h-8 w-8 items-center justify-center rounded-full text-slate-400 transition hover:bg-slate-100 hover:text-slate-600"
|
||||
@click="closeExistingProjectDialog"
|
||||
>
|
||||
<X class="h-5 w-5" />
|
||||
</button>
|
||||
<Button variant="ghost" size="icon" class="h-8 w-8" @click="closeExistingProjectDialog">
|
||||
<X class="h-4 w-4" />
|
||||
</Button>
|
||||
</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
|
||||
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"
|
||||
@ -559,29 +738,29 @@ onBeforeUnmount(() => {
|
||||
:key="project.id"
|
||||
type="button"
|
||||
: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)"
|
||||
>
|
||||
<div class="min-w-0 flex-1">
|
||||
<div class="text-base font-medium text-[#1a1a1a]">
|
||||
<div class="min-w-0">
|
||||
<div class="truncate text-sm font-medium text-slate-800">
|
||||
{{ project.name }}
|
||||
<span v-if="isExistingProjectOpened(project.id)" class="ml-1 text-xs text-slate-500">
|
||||
{{ t('tab.toolbar.opened') }}
|
||||
</span>
|
||||
</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 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) }) }}
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-end gap-3 border-t border-slate-100 px-6 pt-4 pb-6">
|
||||
<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 class="flex items-center justify-end gap-2 border-t px-5 py-4">
|
||||
<Button variant="outline" @click="closeExistingProjectDialog">{{ t('common.cancel') }}</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -591,67 +770,61 @@ onBeforeUnmount(() => {
|
||||
class="fixed inset-0 z-[90] flex items-center justify-center bg-black/45 p-4"
|
||||
@click.self="closeProjectCalcDialog"
|
||||
>
|
||||
<div class="w-full max-w-lg rounded-3xl border border-slate-200/60 bg-white shadow-2xl">
|
||||
<div class="flex items-start justify-between border-b border-slate-100 px-6 pt-6 pb-4">
|
||||
<div class="w-full max-w-md rounded-xl border bg-background shadow-2xl">
|
||||
<div class="flex items-center justify-between border-b px-5 py-4">
|
||||
<div>
|
||||
<h3 class="text-xl font-bold text-[#1a1a1a]">{{ t('home.dialog.newProject') }}</h3>
|
||||
<p class="mt-1.5 text-base text-[#666]">选择工程行业后,进入项目计算页面</p>
|
||||
<h3 class="text-base font-semibold text-foreground">{{ t('home.dialog.newProject') }}</h3>
|
||||
<p class="mt-1 text-sm text-muted-foreground">{{ t('home.dialog.chooseIndustryDesc') }}</p>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
class="flex h-8 w-8 items-center justify-center rounded-full text-slate-400 transition hover:bg-slate-100 hover:text-slate-600"
|
||||
@click="closeProjectCalcDialog"
|
||||
>
|
||||
<X class="h-5 w-5" />
|
||||
</button>
|
||||
<Button variant="ghost" size="icon" class="h-8 w-8" @click="closeProjectCalcDialog">
|
||||
<X class="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div class="px-6 pt-5 pb-4">
|
||||
<h4 class="mb-3 text-base font-semibold text-[#1a1a1a]">{{ t('home.dialog.industry') }}</h4>
|
||||
<div class="space-y-3">
|
||||
<button
|
||||
<div class="space-y-4 px-5 py-4">
|
||||
<label class="block space-y-2">
|
||||
<span class="text-sm font-medium text-foreground">{{ t('home.dialog.industry') }}</span>
|
||||
<SelectRoot v-model="projectIndustry">
|
||||
<SelectTrigger
|
||||
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"
|
||||
>
|
||||
<span :class="projectIndustryLabel ? 'text-foreground' : 'text-muted-foreground'">
|
||||
{{ projectIndustryLabel || t('home.dialog.selectIndustry') }}
|
||||
</span>
|
||||
<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}`"
|
||||
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)"
|
||||
: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"
|
||||
>
|
||||
<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" />
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
<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 class="flex items-center justify-end gap-3 border-t border-slate-100 px-6 pt-4 pb-6">
|
||||
<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="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"
|
||||
>
|
||||
<div class="flex items-center justify-end gap-2 border-t px-5 py-4">
|
||||
<Button variant="outline" @click="closeProjectCalcDialog">{{ t('common.cancel') }}</Button>
|
||||
<Button :disabled="projectSubmitting || !projectIndustry" @click="confirmProjectCalc">
|
||||
{{ projectSubmitting ? t('home.dialog.entering') : t('home.dialog.enterProjectCalc') }}
|
||||
</button>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -697,11 +870,17 @@ onBeforeUnmount(() => {
|
||||
.home-entry-item {
|
||||
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--2 { animation-delay: 0.3s; }
|
||||
.home-entry-item--3 { animation-delay: 0.4s; }
|
||||
.home-entry-item--4 { animation-delay: 0.5s; }
|
||||
.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 {
|
||||
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 { QUICK_PROJECT_INFO_KEY } from '@/lib/workspace'
|
||||
import { initializeProjectFactorStates } from '@/lib/projectWorkspace'
|
||||
import { resolveServicePricingCapabilities } from '@/lib/servicePricing'
|
||||
|
||||
const props = defineProps<{
|
||||
contractId: string
|
||||
@ -36,8 +37,9 @@ type DictFactorItem = {
|
||||
defCoe: number | null
|
||||
hasCost?: boolean | null
|
||||
hasArea?: boolean | null
|
||||
scale?: boolean | null
|
||||
onlyCostScale?: boolean | null
|
||||
enableInvestScale?: boolean | null
|
||||
enableLandScale?: boolean | null
|
||||
investScaleSingleTotal?: boolean | null
|
||||
}
|
||||
|
||||
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,
|
||||
hasCost: item.hasCost === true,
|
||||
hasArea: item.hasArea === true,
|
||||
scale: item.scale === true,
|
||||
onlyCostScale: item.onlyCostScale === true
|
||||
enableInvestScale: item.enableInvestScale === true,
|
||||
enableLandScale: item.enableLandScale === true,
|
||||
investScaleSingleTotal: item.investScaleSingleTotal === true
|
||||
}
|
||||
}
|
||||
|
||||
@ -195,10 +198,22 @@ const preferLandScaleForDualMajor = computed(() => majorSupportsCostScale.value
|
||||
const workEnvCoefficient = computed(() =>
|
||||
parseNumberOrNull(workEnvFactor.value, { sanitize: true, precision: 3 })
|
||||
)
|
||||
const consultSupportsScale = computed(() => selectedConsultDictItem.value?.scale === true)
|
||||
const consultOnlySupportsCostScale = computed(() => selectedConsultDictItem.value?.onlyCostScale === true)
|
||||
const consultPricingCapabilities = computed(() =>
|
||||
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(() =>
|
||||
consultSupportsScale.value &&
|
||||
consultPricingCapabilities.value.investScaleEnabled &&
|
||||
hasResolvedMajor.value &&
|
||||
(
|
||||
consultOnlySupportsCostScale.value ||
|
||||
@ -206,9 +221,8 @@ const canUseInvestScale = computed(() =>
|
||||
)
|
||||
)
|
||||
const canUseLandScale = computed(() =>
|
||||
consultSupportsScale.value &&
|
||||
consultPricingCapabilities.value.landScaleEnabled &&
|
||||
hasResolvedMajor.value &&
|
||||
!consultOnlySupportsCostScale.value &&
|
||||
majorSupportsLandScale.value
|
||||
)
|
||||
const investScalePlaceholder = computed(() => {
|
||||
@ -693,8 +707,8 @@ watch(canUseLandScale, enabled => {
|
||||
|
||||
.quick-calc-layout {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1.45fr) minmax(340px, 0.95fr);
|
||||
gap: 10px;
|
||||
grid-template-columns: minmax(0, 1.02fr) minmax(420px, 1.34fr);
|
||||
gap: 12px;
|
||||
height: 100%;
|
||||
min-height: 0;
|
||||
}
|
||||
@ -710,6 +724,34 @@ watch(canUseLandScale, enabled => {
|
||||
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 {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
@ -1152,7 +1194,7 @@ watch(canUseLandScale, enabled => {
|
||||
|
||||
.quick-calc-panel--form .quick-calc-form {
|
||||
overflow: auto;
|
||||
padding: 8px;
|
||||
padding: 10px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
@ -1179,8 +1221,11 @@ watch(canUseLandScale, enabled => {
|
||||
|
||||
.quick-calc-panel--form .quick-calc-form-section {
|
||||
gap: 6px;
|
||||
padding: 8px 10px;
|
||||
padding: 10px 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 {
|
||||
@ -1321,7 +1366,11 @@ watch(canUseLandScale, enabled => {
|
||||
}
|
||||
|
||||
.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 {
|
||||
|
||||
@ -16,10 +16,12 @@ import { useI18n } from 'vue-i18n'
|
||||
import TypeLine from '@/layout/typeLine.vue'
|
||||
import MethodUnavailableNotice from '@/features/shared/components/MethodUnavailableNotice.vue'
|
||||
import ScaleFormulaReadonlyPane from '@/features/pricing/components/ScaleFormulaReadonlyPane.vue'
|
||||
import { resolveServicePricingCapabilities } from '@/lib/servicePricing'
|
||||
|
||||
interface ServiceMethodType {
|
||||
scale?: boolean | null
|
||||
onlyCostScale?: boolean | null
|
||||
enableInvestScale?: boolean | null
|
||||
enableLandScale?: boolean | null
|
||||
investScaleSingleTotal?: boolean | null
|
||||
amount?: boolean | null
|
||||
workDay?: boolean | null
|
||||
}
|
||||
@ -40,20 +42,19 @@ interface PricingCategoryItem {
|
||||
component: Component
|
||||
}
|
||||
|
||||
const resolveMethodEnabled = (value: boolean | null | undefined, fallback = true) =>
|
||||
typeof value === 'boolean' ? value : fallback
|
||||
|
||||
const methodAvailability = computed(() => {
|
||||
const scale = resolveMethodEnabled(props.type?.scale, true)
|
||||
const onlyCostScale = resolveMethodEnabled(props.type?.onlyCostScale, false)
|
||||
const amount = resolveMethodEnabled(props.type?.amount, true)
|
||||
const workDay = resolveMethodEnabled(props.type?.workDay, true)
|
||||
|
||||
const capability = resolveServicePricingCapabilities(props.type, {
|
||||
investScaleEnabled: true,
|
||||
landScaleEnabled: true,
|
||||
investScaleSingleTotal: false,
|
||||
workloadEnabled: true,
|
||||
hourlyEnabled: true
|
||||
})
|
||||
return {
|
||||
investmentScale: scale,
|
||||
landScale: scale && !onlyCostScale,
|
||||
workload: amount,
|
||||
hourly: workDay
|
||||
investmentScale: capability.investScaleEnabled,
|
||||
landScale: capability.landScaleEnabled,
|
||||
workload: capability.workloadEnabled,
|
||||
hourly: capability.hourlyEnabled
|
||||
}
|
||||
})
|
||||
|
||||
@ -71,7 +72,6 @@ const createPricingPane = (name: string) =>
|
||||
|
||||
return () => h(AsyncPricingView, {
|
||||
contractId: props.contractId,
|
||||
contractName: props.contractName,
|
||||
serviceId: props.serviceId,
|
||||
projectInfoKey: props.projectInfoKey
|
||||
})
|
||||
@ -93,7 +93,6 @@ const investmentScaleView = createPricingPane('InvestmentScalePricingPane')
|
||||
const landScaleView = createPricingPane('LandScalePricingPane')
|
||||
const workloadView = createPricingPane('WorkloadPricingPane')
|
||||
const hourlyView = createPricingPane('HourlyPricingPane')
|
||||
const otherService = createPricingPane('OtherService')
|
||||
|
||||
const createScaleFormulaPane = (
|
||||
method: 'investScale' | 'landScale',
|
||||
@ -132,8 +131,7 @@ const workContentPane = markRaw(
|
||||
contractId: props.contractId,
|
||||
projectInfoKey: props.projectInfoKey,
|
||||
serviceId: props.serviceId,
|
||||
dictMode: 'service',
|
||||
"show-no-column": true
|
||||
dictMode: 'service'
|
||||
})
|
||||
}
|
||||
})
|
||||
@ -163,7 +161,7 @@ const pricingCategories = computed<PricingCategoryItem[]>(() => [
|
||||
label: t('zxFwView.categories.investmentScale'),
|
||||
component: methodAvailability.value.investmentScale ? investmentScaleView : investmentScaleUnavailableView
|
||||
},
|
||||
/*{
|
||||
{
|
||||
key: 'investment-scale-formula',
|
||||
label: t('zxFwView.categories.investmentScaleFormula'),
|
||||
component: methodAvailability.value.investmentScale ? investmentScaleFormulaView : investmentScaleUnavailableView
|
||||
@ -177,7 +175,7 @@ const pricingCategories = computed<PricingCategoryItem[]>(() => [
|
||||
key: 'land-scale-formula',
|
||||
label: t('zxFwView.categories.landScaleFormula'),
|
||||
component: methodAvailability.value.landScale ? landScaleFormulaView : landScaleUnavailableView
|
||||
},*/
|
||||
},
|
||||
{
|
||||
key: 'workload-method',
|
||||
label: t('zxFwView.categories.workload'),
|
||||
@ -193,11 +191,6 @@ const pricingCategories = computed<PricingCategoryItem[]>(() => [
|
||||
label: t('zxFwView.categories.workContent'),
|
||||
component: workContentPane
|
||||
},
|
||||
{
|
||||
key: 'other-service',
|
||||
label: t('zxFwView.categories.otherService'),
|
||||
component: otherService
|
||||
},
|
||||
])
|
||||
|
||||
const defaultCategory = computed(() => {
|
||||
|
||||
@ -53,6 +53,10 @@ const { t, locale } = useI18n()
|
||||
const DEFAULT_PROJECT_NAME = t('xmInfo.defaultProjectName')
|
||||
const DEFAULT_DESC = t('xmInfo.defaultDesc')
|
||||
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 now = new Date()
|
||||
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 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="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
|
||||
v-model="projectName"
|
||||
type="text"
|
||||
@ -250,9 +268,9 @@ onMounted(async () => {
|
||||
<button
|
||||
type="button"
|
||||
: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>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="top">{{ INDUSTRY_HINT_TEXT }}</TooltipContent>
|
||||
@ -427,10 +445,25 @@ onMounted(async () => {
|
||||
|
||||
|
||||
<div class="md:col-span-2 xl:col-span-4">
|
||||
<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
|
||||
v-model="desc"
|
||||
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"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@ -26,8 +26,8 @@ const majorFactorView = markRaw(
|
||||
const xmCategories = computed(() => [
|
||||
{ key: 'info', label: t('xmCard.categories.info'), component: infoView },
|
||||
{ key: 'scale-info', label: t('xmCard.categories.scaleInfo'), component: scaleInfoView },
|
||||
// { key: 'consult-category-factor', label: t('xmCard.categories.consultCategoryFactor'), component: consultCategoryFactorView },
|
||||
// { key: 'major-factor', label: t('xmCard.categories.majorFactor'), component: majorFactorView },
|
||||
{ key: 'consult-category-factor', label: t('xmCard.categories.consultCategoryFactor'), component: consultCategoryFactorView },
|
||||
{ key: 'major-factor', label: t('xmCard.categories.majorFactor'), component: majorFactorView },
|
||||
{ key: 'contract', label: t('xmCard.categories.contract'), component: htView }
|
||||
])
|
||||
</script>
|
||||
|
||||
@ -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.',
|
||||
opened: '(Opened)',
|
||||
lastEdited: 'Last edited: {time}',
|
||||
openDefault: 'Open Default Project',
|
||||
openDefault: 'Back to Home',
|
||||
createAndOpen: 'Create and Open'
|
||||
}
|
||||
},
|
||||
home: {
|
||||
title: 'Engineering Calculation Entry',
|
||||
subtitle: 'Project Budget · Quick Calc · Import Data',
|
||||
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 · Related Files',
|
||||
projectCalcTab: 'Project Calculation',
|
||||
quickCalcTab: 'Quick Calculation',
|
||||
cards: {
|
||||
heroTitle: 'One-Click Smart Budget',
|
||||
heroSubTitle: 'Accelerate standards adoption',
|
||||
heroDesc: 'Cost consulting fee calculator for transport construction projects',
|
||||
heroTitle: 'Zonghuiyi|Billing Made Simple',
|
||||
heroTitles: [
|
||||
'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',
|
||||
projectBudgetDesc: 'For full project-level calculation across multiple contracts with import/export support',
|
||||
quickCalc: 'Quick Calc',
|
||||
quickCalcDesc: 'Suitable for single service trial, select industry, consultation type, engineering specialty, input base number and get results in seconds',
|
||||
importData: 'Import Data',
|
||||
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',
|
||||
developing: 'In Development',
|
||||
pickFile: 'Choose File',
|
||||
pickExisting: 'Choose Existing',
|
||||
relatedFiles: 'Related Files',
|
||||
relatedFilesDesc: 'View related fee documents, tender files, contract files, service content, and work requirements',
|
||||
openRelatedFiles: 'Open Page'
|
||||
openFileSystem: 'Open File System',
|
||||
file: 'File System',
|
||||
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: {
|
||||
newProject: 'New Project',
|
||||
@ -56,6 +73,47 @@ export const enUS = {
|
||||
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: {
|
||||
toolbar: {
|
||||
light: 'Light',
|
||||
@ -169,9 +227,7 @@ export const enUS = {
|
||||
toast: {
|
||||
export: 'Export Report',
|
||||
success: 'Export Success',
|
||||
failed: 'Export Failed',
|
||||
saveSuccess: 'Save Success',
|
||||
saveFailed: 'Save Failed'
|
||||
failed: 'Export Failed'
|
||||
},
|
||||
messages: {
|
||||
defaultProjectLabel: 'Default Project',
|
||||
@ -305,7 +361,9 @@ export const enUS = {
|
||||
reserveTitle: 'Reserve Fee'
|
||||
},
|
||||
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: {
|
||||
baseLabel: 'Base (total budget of all service fees)',
|
||||
@ -320,7 +378,7 @@ export const enUS = {
|
||||
title: 'Consulting Service Details',
|
||||
warning: 'Please review and adjust recommended limits/special values in the specification, then update final fee if needed.',
|
||||
editTabTitle: 'Service Edit-{name}',
|
||||
subtotal: 'Subtotal',
|
||||
subtotal: 'Total',
|
||||
edit: 'Edit',
|
||||
resetDefault: 'Reset',
|
||||
delete: 'Remove',
|
||||
@ -370,7 +428,7 @@ export const enUS = {
|
||||
}
|
||||
},
|
||||
htFeeGrid: {
|
||||
subtotal: 'Subtotal',
|
||||
subtotal: 'Total',
|
||||
currentRow: 'Current Row',
|
||||
unnamed: 'Unnamed',
|
||||
edit: 'Edit',
|
||||
@ -402,6 +460,8 @@ export const enUS = {
|
||||
},
|
||||
serviceSelector: {
|
||||
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',
|
||||
empty: 'No services'
|
||||
},
|
||||
@ -437,7 +497,7 @@ export const enUS = {
|
||||
}
|
||||
},
|
||||
htFeeDetail: {
|
||||
subtotal: 'Subtotal',
|
||||
subtotal: 'Total',
|
||||
currentRow: 'Current Row',
|
||||
clickToInput: 'Click to input',
|
||||
addRow: 'Add Row',
|
||||
@ -576,7 +636,7 @@ export const enUS = {
|
||||
pricingScale: {
|
||||
totalInvestmentByIndustry: '{industryName} Total Investment',
|
||||
totalInvestment: 'Total Investment',
|
||||
clickToInput: 'Optional, enter manually, numeric, 4 decimals',
|
||||
clickToInput: 'Click to input',
|
||||
projectLabel: 'Project {index}',
|
||||
columns: {
|
||||
investAmount: 'Cost Amount (10k CNY)',
|
||||
@ -589,7 +649,7 @@ export const enUS = {
|
||||
consultCategoryFactor: 'Consult Category Factor',
|
||||
majorFactor: 'Major Factor',
|
||||
workStageFactor: 'Work Stage Factor (Draft/Review)',
|
||||
workRatio: 'Work Ratio (%)',
|
||||
workRatio: 'Service Budget Composition Ratio and Quantity Ratio',
|
||||
total: 'Total',
|
||||
remark: 'Remark',
|
||||
majorGroup: 'Major Code and Major Name'
|
||||
@ -598,7 +658,8 @@ export const enUS = {
|
||||
resetInvestAmount: 'Click ↻ to restore default cost amount for this column',
|
||||
resetLandArea: 'Click ↻ to restore default land area 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: {
|
||||
@ -610,6 +671,8 @@ export const enUS = {
|
||||
confirmOverride: 'Confirm Override',
|
||||
investment: {
|
||||
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?',
|
||||
overrideDesc: 'Use contract default data to override current investment scale details. Continue?'
|
||||
},
|
||||
@ -644,16 +707,14 @@ export const enUS = {
|
||||
total: 'Grand Total',
|
||||
columns: {
|
||||
code: 'Code',
|
||||
name: 'Name',
|
||||
technician: 'Technician',
|
||||
assistantEngineer: 'Assistant Engineer',
|
||||
midEngineer: 'Intermediate Engineer (or Level 2 Cost Engineer)',
|
||||
seniorEngineer: 'Senior Engineer (or Level 1 Cost Engineer)',
|
||||
profSeniorEngineer: 'Professor-level Senior Engineer',
|
||||
unitPrice: 'Unit Price (CNY/workday)',
|
||||
name: 'Personnel Name',
|
||||
referenceUnitPrice: 'Budget Reference Unit Price',
|
||||
laborBudgetUnitPrice: 'Labor Budget Unit Price (CNY/workday)',
|
||||
compositeBudgetUnitPrice: 'Composite Budget Unit Price (CNY/workday)',
|
||||
adoptedBudgetUnitPrice: 'Adopted Budget Unit Price (CNY/workday)',
|
||||
personnelCount: 'Personnel Count',
|
||||
workdayCount: 'Workday Count',
|
||||
subtotal: 'Subtotal (CNY)',
|
||||
avgUnitPrice: 'Average Unit Price (CNY/workday)',
|
||||
serviceBudget: 'Service Budget (CNY)',
|
||||
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.',
|
||||
industryHint: 'Changing industry requires reset and re-selection',
|
||||
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.',
|
||||
fields: {
|
||||
projectName: 'Project Name',
|
||||
@ -679,9 +744,10 @@ export const enUS = {
|
||||
},
|
||||
placeholders: {
|
||||
overview: 'Enter project overview',
|
||||
preparedBy: 'Enter preparer',
|
||||
reviewedBy: 'Enter reviewer',
|
||||
preparedCompany: 'Enter prepared company'
|
||||
desc: 'Other Notes',
|
||||
preparedBy: 'XXX',
|
||||
reviewedBy: 'XXX',
|
||||
preparedCompany: 'XXX'
|
||||
}
|
||||
}
|
||||
} as const
|
||||
|
||||
@ -14,32 +14,49 @@ export const zhCN = {
|
||||
countdown: '本页将在 {seconds} 秒后自动尝试关闭。你也可以先在新标签页打开其他项目。',
|
||||
opened: '(已打开)',
|
||||
lastEdited: '最后编辑:{time}',
|
||||
openDefault: '打开默认项目',
|
||||
openDefault: '返回首页',
|
||||
createAndOpen: '新建项目并打开'
|
||||
}
|
||||
},
|
||||
home: {
|
||||
title: '粤价函〔2008〕929号\n《广东省交通工程造价技术中介服务收费项目及标准表》',
|
||||
subtitle: '项目计算 · 单项速算 · 导入数据',
|
||||
title: '粤公学标字〔2026〕5号\n交通运输工程造价咨询服务预算编制规范',
|
||||
subtitle: '项目计算 · 单项速算 · 导入数据 · 相关文件',
|
||||
projectCalcTab: '项目计算',
|
||||
quickCalcTab: '快速计算',
|
||||
cards: {
|
||||
heroTitle: '智能预算一键生成',
|
||||
heroSubTitle: '助力《规范》高效落地',
|
||||
heroDesc: '交通建设项目工程造价咨询服务费计算',
|
||||
heroTitle: '众会易|算费真容易',
|
||||
heroTitles: [
|
||||
'众会易 | 算费真容易',
|
||||
'众会易 | 算费不熬夜',
|
||||
'众会易 | 算费不费力'
|
||||
],
|
||||
heroSubTitle: '',
|
||||
heroDesc: '智算费用 即点即出 您的时间留给创造',
|
||||
heroDescs: [
|
||||
'智算费用 即点即出 您的时间留给创造。',
|
||||
'智算费用 一键即出 您的时间留给创造。',
|
||||
'智算费用 一键生成 您的时间留给创造。',
|
||||
'众会易 真容易 不熬夜 不费力'
|
||||
],
|
||||
projectBudget: '项目预算',
|
||||
projectBudgetDesc: '适用于多合同段、项目级整体计算,支持导出/导入完整项目数据',
|
||||
quickCalc: '单项速算',
|
||||
quickCalcDesc: '适用于单项服务试算,选择行业、咨询类型、工程专业,输入基数秒出结果',
|
||||
importData: '导入数据',
|
||||
importDataDesc: '导入".zw"数据包,并自动新建一个项目用于恢复数据,不会覆盖现有项目',
|
||||
relatedFiles: '相关文件',
|
||||
relatedFilesDesc: '在线查看、打印和下载与该计算器依据的收费文件、相关招标文件与合同文件范本',
|
||||
viewFiles: '查看文件',
|
||||
enter: '进入计算',
|
||||
developing: '正在开发',
|
||||
pickFile: '选择文件',
|
||||
pickExisting: '选择已有项目',
|
||||
relatedFiles: '相关文件',
|
||||
relatedFilesDesc: '可查看相关收费文件、招标文件、合同文件、服务内容、工作要求',
|
||||
openRelatedFiles: '打开页面'
|
||||
openFileSystem: '打开文件系统',
|
||||
file: '文件系统',
|
||||
fileDataDesc: '文件系统可查看相关收费文件、招标文件、合同文件、服务内容、工作要求'
|
||||
},
|
||||
disclaimer: {
|
||||
link: '查看免责声明',
|
||||
supportText: '本计算工具由众为工程咨询有限公司提供免费技术支持'
|
||||
},
|
||||
dialog: {
|
||||
newProject: '新建项目',
|
||||
@ -56,6 +73,47 @@ export const zhCN = {
|
||||
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: {
|
||||
toolbar: {
|
||||
light: '浅色',
|
||||
@ -69,7 +127,7 @@ export const zhCN = {
|
||||
reset: '重置',
|
||||
resetting: '重置中...',
|
||||
projectList: '项目列表',
|
||||
projectCount: '项目数量:{count}/{max}',
|
||||
projectCount: '项目数量:{count}',
|
||||
createProject: '新建项目',
|
||||
backHome: '返回入口',
|
||||
resetAll: '清除全部项目',
|
||||
@ -169,9 +227,7 @@ export const zhCN = {
|
||||
toast: {
|
||||
export: '导出报表',
|
||||
success: '导出成功',
|
||||
failed: '导出失败',
|
||||
saveSuccess: '保存成功',
|
||||
saveFailed: '保存失败'
|
||||
failed: '导出失败'
|
||||
},
|
||||
messages: {
|
||||
defaultProjectLabel: '默认项目',
|
||||
@ -196,7 +252,7 @@ export const zhCN = {
|
||||
copied: '已复制',
|
||||
copyFailed: '复制失败',
|
||||
brandAlt: '众为咨询',
|
||||
supportText: '本网站由众为工程咨询有限公司提供免费技术支持',
|
||||
supportText: '本计算工具由众为工程咨询有限公司提供免费技术支持',
|
||||
aboutTitle: '关于我们',
|
||||
companyName: '众为工程咨询有限公司',
|
||||
openOfficialSiteAria: '跳转到官网首页',
|
||||
@ -278,9 +334,9 @@ export const zhCN = {
|
||||
metaBudget: '合同段预算金额:{amount}',
|
||||
currencySuffix: '元',
|
||||
categories: {
|
||||
baseInfo: '合同基础信息',
|
||||
scaleInfo: '合同规模',
|
||||
services: '合同费用汇总表',
|
||||
baseInfo: '基础信息',
|
||||
scaleInfo: '规模信息',
|
||||
services: '咨询服务',
|
||||
consultFactor: '咨询分类系数',
|
||||
majorFactor: '工程专业系数',
|
||||
additionalFee: '附加工作费',
|
||||
@ -305,7 +361,9 @@ export const zhCN = {
|
||||
reserveTitle: '预备费'
|
||||
},
|
||||
htInfo: {
|
||||
scaleDetailTitle: '合同规模明细'
|
||||
scaleDetailTitle: '合同规模明细',
|
||||
scaleDetailHint: '当本表规模与项目规模数据不一致时,计费以本表规模为准',
|
||||
scaleDetailHintAria: '合同规模明细提示'
|
||||
},
|
||||
htFeeRate: {
|
||||
baseLabel: '基数(所有服务费预算合计)',
|
||||
@ -320,7 +378,7 @@ export const zhCN = {
|
||||
title: '咨询服务明细',
|
||||
warning: '※ 请注意检查并修改《规范》建议的限值或特殊值,并在确认金额栏修改',
|
||||
editTabTitle: '服务编辑-{name}',
|
||||
subtotal: '小计',
|
||||
subtotal: '总计',
|
||||
edit: '编辑',
|
||||
resetDefault: '恢复默认',
|
||||
delete: '删除',
|
||||
@ -350,7 +408,7 @@ export const zhCN = {
|
||||
},
|
||||
htSummary: {
|
||||
title: '合同段汇总',
|
||||
total: '合计',
|
||||
total: '总计',
|
||||
remark: '说明',
|
||||
placeholder: '请先填咨询服务/附加工作费/预备费的数据',
|
||||
additionalPrefix: '附加工作费',
|
||||
@ -370,7 +428,7 @@ export const zhCN = {
|
||||
}
|
||||
},
|
||||
htFeeGrid: {
|
||||
subtotal: '小计',
|
||||
subtotal: '总计',
|
||||
currentRow: '当前行',
|
||||
unnamed: '未命名',
|
||||
edit: '编辑',
|
||||
@ -402,6 +460,8 @@ export const zhCN = {
|
||||
},
|
||||
serviceSelector: {
|
||||
title: '选择服务',
|
||||
titleHint: '本选择项较《规范》列明的服务项有所增加。此增加与《规范》无冲突,只为满足《规范》费用计算之需',
|
||||
titleHintAria: '选择服务提示',
|
||||
clear: '清空',
|
||||
empty: '暂无服务'
|
||||
},
|
||||
@ -417,8 +477,7 @@ export const zhCN = {
|
||||
landScaleFormula: '用地规模法计算公式',
|
||||
workload: '工作量法',
|
||||
hourly: '工时法',
|
||||
workContent: '工作内容',
|
||||
otherService: '其他服务计算'
|
||||
workContent: '工作内容'
|
||||
},
|
||||
formulaColumns: {
|
||||
subtitle: '直接展示当前计价法 store 的最新明细,随数据变更自动同步。',
|
||||
@ -438,7 +497,7 @@ export const zhCN = {
|
||||
}
|
||||
},
|
||||
htFeeDetail: {
|
||||
subtotal: '小计',
|
||||
subtotal: '总计',
|
||||
currentRow: '当前行',
|
||||
clickToInput: '点击输入',
|
||||
addRow: '添加行',
|
||||
@ -559,8 +618,8 @@ export const zhCN = {
|
||||
},
|
||||
xmCard: {
|
||||
categories: {
|
||||
info: '项目基础信息',
|
||||
scaleInfo: '项目规模',
|
||||
info: '基础信息',
|
||||
scaleInfo: '规模信息',
|
||||
consultCategoryFactor: '咨询分类系数',
|
||||
majorFactor: '工程专业系数',
|
||||
contract: '合同段管理'
|
||||
@ -576,12 +635,11 @@ export const zhCN = {
|
||||
pricingScale: {
|
||||
totalInvestmentByIndustry: '{industryName}总投资',
|
||||
totalInvestment: '总投资',
|
||||
clickToInput: '非必填,手动录入,数字,4位',
|
||||
clickToInput: '点击输入',
|
||||
projectLabel: '项目{index}',
|
||||
columns: {
|
||||
code: '编码',
|
||||
investAmount: '造价金额(万元)',
|
||||
landArea: '造价金额(元)',
|
||||
landArea: '用地面积(亩)',
|
||||
benchmarkBudget: '基准预算(元)',
|
||||
basicWork: '基本工作',
|
||||
optionalWork: '可选工作',
|
||||
@ -589,25 +647,18 @@ export const zhCN = {
|
||||
budgetFee: '预算费用',
|
||||
consultCategoryFactor: '咨询分类系数',
|
||||
majorFactor: '专业系数',
|
||||
workStageFactor: '工作环节系数',
|
||||
workRatio: '工作占比',
|
||||
workStageFactor: '工作环节系数(编审系数)',
|
||||
workRatio: '服务预算构成比率与数量比',
|
||||
total: '合计',
|
||||
remark: '说明',
|
||||
majorGroup: '项目明细费用',
|
||||
name: '名称',
|
||||
number: '编码',
|
||||
base: '计算基础',
|
||||
base2: '计算基数F(万元)',
|
||||
formula: '计算公式',
|
||||
calculationAmount: '计算金额(元)',
|
||||
calculationGroup: '计算公式',
|
||||
serviceFee: '服务费用(元)'
|
||||
majorGroup: '专业编码以及工程专业名称'
|
||||
},
|
||||
tooltip: {
|
||||
resetInvestAmount: '点击右侧↻恢复本列默认造价金额',
|
||||
resetLandArea: '点击右侧↻恢复本列默认用地面积',
|
||||
resetConsultCategoryFactor: '点击右侧↻恢复本列默认咨询分类系数',
|
||||
resetMajorFactor: '点击右侧↻恢复本列默认专业系数'
|
||||
resetInvestAmount: '点击右侧↻本列造价金额数值恢复为本合同规模数值',
|
||||
resetLandArea: '点击右侧↻本列用地面积数值恢复为本合同规模数值',
|
||||
resetConsultCategoryFactor: '点击右侧↻本列咨询分类系数值恢复为本项目的相应的咨询分类系数值',
|
||||
resetMajorFactor: '点击右侧↻本列专业系数值恢复为本项目的相应的专业系数值',
|
||||
workRatio: '本列系数适用于以下两种情形:服务预算构成比率:适用于《规范》附录D表D.2~D.7中委托工作内容仅为部分工作时的情形;数量比,适用于计算基数按每份成果、分批次任务、单项工程或单位工程的规模(非总额)的情形,数量表示有多个个、份或项'
|
||||
}
|
||||
},
|
||||
pricingPane: {
|
||||
@ -619,6 +670,8 @@ export const zhCN = {
|
||||
confirmOverride: '确认覆盖',
|
||||
investment: {
|
||||
title: '投资规模明细',
|
||||
titleHint: '本表造价金额取值规则:以本合同规模表与本表造价金额中操作时间较晚的值为准',
|
||||
titleHintAria: '投资规模明细提示',
|
||||
clearDesc: '将清空当前投资规模明细,是否继续?',
|
||||
overrideDesc: '将使用合同默认数据覆盖当前投资规模明细,是否继续?'
|
||||
},
|
||||
@ -634,78 +687,34 @@ export const zhCN = {
|
||||
unavailableMessage: '当前服务没有关联工作量法任务,无需填写此部分内容。',
|
||||
clickToInput: '点击输入',
|
||||
none: '无',
|
||||
total: '总合计',
|
||||
total: '总计',
|
||||
columns: {
|
||||
code: '编码',
|
||||
name: '名称',
|
||||
budgetBase: '计算基础',
|
||||
budgetReferenceUnitPrice: '计算基数(份)',
|
||||
budgetAdoptedUnitPrice: '最低单价(万元/份)',
|
||||
workload: '中值单价(万元/份)',
|
||||
consultCategoryFactor: '最高单价(万元/份)',
|
||||
cLow: '计算最低值(元)',
|
||||
cMid: '计算中值(元)',
|
||||
cHigh: '计算最高值(元)',
|
||||
serviceFee: '本计算取值(元)',
|
||||
budgetBase: '预算基数',
|
||||
budgetReferenceUnitPrice: '预算参考单价',
|
||||
budgetAdoptedUnitPrice: '预算采用单价',
|
||||
workload: '工作量',
|
||||
consultCategoryFactor: '咨询分类系数',
|
||||
serviceFee: '服务费用(元)',
|
||||
remark: '说明'
|
||||
}
|
||||
},
|
||||
hourlyFeeGrid: {
|
||||
title: '工时法明细',
|
||||
clickToInput: '点击输入',
|
||||
total: '总合计',
|
||||
total: '总计',
|
||||
columns: {
|
||||
code: '编码',
|
||||
name: '名称',
|
||||
technician: '技术员',
|
||||
assistantEngineer: '助理工程师',
|
||||
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: '劳动预算单价(元/工日)',
|
||||
name: '人员名称',
|
||||
referenceUnitPrice: '预算参考单价',
|
||||
laborBudgetUnitPrice: '人工预算单价(元/工日)',
|
||||
compositeBudgetUnitPrice: '综合预算单价(元/工日)',
|
||||
adoptedBudgetUnitPrice: '采用单价(元/工日)',
|
||||
adoptedBudgetUnitPrice: '预算采用单价(元/工日)',
|
||||
personnelCount: '人员数量(人)',
|
||||
serviceBudget: '服务费用(元)',
|
||||
}
|
||||
},
|
||||
otherService: {
|
||||
title: '其他服务计算',
|
||||
clickToInput: '点击输入',
|
||||
total: '小计',
|
||||
columns: {
|
||||
num: '序号',
|
||||
code: '编码',
|
||||
name: '名称',
|
||||
feeItem: '费用项',
|
||||
unit: '单位',
|
||||
quantity: '数量',
|
||||
unitPrice: '单价',
|
||||
serviceFee: '服务费用(元)',
|
||||
remark: '说明',
|
||||
actions: '操作'
|
||||
workdayCount: '工日数量(工日)',
|
||||
serviceBudget: '服务预算(元)',
|
||||
remark: '说明'
|
||||
}
|
||||
},
|
||||
xmScaleGrid: {
|
||||
@ -717,6 +726,10 @@ export const zhCN = {
|
||||
defaultDesc: '在履行造价咨询服务时,宜根据咨询服务质量情况分级确定相应的处罚金额。其中考评得分在大于及等于85和小于90分时,处罚金额为预算费用的10%;其中考评得分在大于及等于80和小于85分时,处罚金额为预算费用的20%;其中考评得分在大于及等于75和小于80分时,处罚金额为预算费用的30%;其中考评得分在大于及等于70和小于75分时,处罚金额为预算费用的40%;其中考评得分小于70分时,处罚金额为预算费用的50%以上。',
|
||||
industryHint: '变更需要重置后重新选择',
|
||||
industryHintAria: '工程行业提示',
|
||||
reportContentHint: '本内容为选择性填写,填写内容仅用于自动生成编制报告内容',
|
||||
reportContentHintAria: '说明内容提示',
|
||||
otherDescHint: '本内容为选择性填写。当前显示内容仅为示意,编制人可根据实际情况填写,亦可不填。',
|
||||
otherDescHintAria: '其他说明提示',
|
||||
createFromHomeFirst: '请从首页先新建项目后再进入此页面。',
|
||||
fields: {
|
||||
projectName: '项目名称',
|
||||
@ -730,9 +743,10 @@ export const zhCN = {
|
||||
},
|
||||
placeholders: {
|
||||
overview: '请输入项目概况',
|
||||
preparedBy: '请输入编制人',
|
||||
reviewedBy: '请输入复核人',
|
||||
preparedCompany: '请输入编制单位'
|
||||
desc: '其他说明',
|
||||
preparedBy: 'XXX',
|
||||
reviewedBy: 'XXX',
|
||||
preparedCompany: 'XXX'
|
||||
}
|
||||
}
|
||||
} as const
|
||||
|
||||
2797
src/layout/tab.vue
2797
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 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 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 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 sheetOpen = ref(false)
|
||||
@ -107,6 +111,34 @@ const sheetOpen = ref(false)
|
||||
let copyBtnTimer: ReturnType<typeof setTimeout> | 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
|
||||
|
||||
try {
|
||||
await navigator.clipboard.writeText(text)
|
||||
copyBtnText.value = t('typeLine.copied')
|
||||
const copied = await copyTextWithFallback(text)
|
||||
copyBtnText.value = copied ? t('typeLine.copied') : t('typeLine.copyFailed')
|
||||
} catch (error) {
|
||||
console.error('copy failed:', error)
|
||||
copyBtnText.value = t('typeLine.copyFailed')
|
||||
@ -256,24 +288,32 @@ useMotionValueEvent(
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div :class="['flex flex-col gap-6 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 in props.categories" :key="item.key"
|
||||
:style="itemGapStyle" class="relative flex items-center cursor-pointer group" @click="switchCategory(item.key)">
|
||||
<div :class="['flex flex-col gap-2 relative ', (props.title || props.subtitle || props.metaText) ? 'mt-3' : 'mt-6']">
|
||||
<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 :style="axisColStyle" class="flex shrink-0 flex-col items-center">
|
||||
<div :class="[
|
||||
'z-10 rounded-full border-2 flex items-center justify-center transition-all duration-200',
|
||||
'z-10 rounded-full border-2 flex shrink-0 items-center justify-center transition-all duration-200',
|
||||
activeCategory === item.key
|
||||
? 'bg-blue-600 border-blue-600'
|
||||
: 'bg-background border-slate-300 group-hover:border-slate-400'
|
||||
? 'bg-primary border-primary shadow-[0_0_0_3px_rgba(var(--primary),0.15)]'
|
||||
: 'bg-background border-muted-foreground/40 group-hover:border-muted-foreground/70'
|
||||
]" :style="dotStyle">
|
||||
<div v-if="activeCategory === item.key" class="bg-white rounded-full" :style="dotInnerStyle"></div>
|
||||
<div v-if="activeCategory === item.key" class="bg-background 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>
|
||||
<span :class="[
|
||||
'transition-colors duration-200',
|
||||
'pt-px transition-colors duration-200',
|
||||
activeCategory === item.key
|
||||
? 'font-semibold text-blue-600'
|
||||
: 'text-slate-500 group-hover:text-slate-700'
|
||||
? 'font-semibold text-primary'
|
||||
: 'text-muted-foreground group-hover:text-foreground'
|
||||
]" :style="labelStyle">
|
||||
{{ item.label }}
|
||||
</span>
|
||||
@ -391,4 +431,23 @@ useMotionValueEvent(
|
||||
overflow: hidden;
|
||||
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>
|
||||
|
||||
@ -45,7 +45,7 @@ export class AgGridResetHeader implements IHeaderComp {
|
||||
eButton.style.height = '18px'
|
||||
eButton.style.border = '1px solid #d1d5db'
|
||||
eButton.style.borderRadius = '999px'
|
||||
eButton.style.background = '#fff'
|
||||
eButton.style.background = '#edff87'
|
||||
eButton.style.color = '#4b5563'
|
||||
eButton.style.cursor = 'pointer'
|
||||
eButton.style.fontSize = '12px'
|
||||
@ -69,8 +69,38 @@ export class AgGridResetHeader implements IHeaderComp {
|
||||
this.params = params
|
||||
this.eLabel.textContent = params.displayName || params.column?.getColDef().headerName || ''
|
||||
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 = params.onReset ? 'visible' : 'hidden'
|
||||
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.style.visibility = 'hidden'
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
@ -8,6 +8,7 @@ import {
|
||||
import { roundTo, sumByNumber, toDecimal, toFiniteNumberOrNull } from '@/lib/decimal'
|
||||
import { getScaleBudgetFee } from '@/lib/pricingScaleFee'
|
||||
import { getScaleBudgetFeeByRow } from '@/lib/pricingScaleDetail'
|
||||
import { isInvestScaleSingleTotalService, resolveServicePricingCapabilities } from '@/lib/servicePricing'
|
||||
import { useZxFwPricingStore, type ServicePricingMethod } from '@/pinia/zxFwPricing'
|
||||
import { useKvStore } from '@/pinia/kv'
|
||||
|
||||
@ -49,6 +50,8 @@ const getOnlyCostScaleSummaryAmount = (
|
||||
|
||||
interface ScaleRow {
|
||||
id: string
|
||||
hasCost?: boolean
|
||||
hasArea?: boolean
|
||||
amount: number | null
|
||||
landArea: number | null
|
||||
benchmarkBudgetBasicChecked: boolean
|
||||
@ -85,6 +88,10 @@ interface MajorLite {
|
||||
|
||||
interface ServiceLite {
|
||||
defCoe: number | null
|
||||
enableInvestScale?: boolean | null
|
||||
enableLandScale?: boolean | null
|
||||
investScaleSingleTotal?: boolean | null
|
||||
scale?: boolean | null
|
||||
onlyCostScale?: boolean | null
|
||||
}
|
||||
|
||||
@ -221,9 +228,14 @@ const getDefaultConsultCategoryFactor = (serviceId: string | number) => {
|
||||
return toFiniteNumberOrNull(service?.defCoe)
|
||||
}
|
||||
|
||||
const isOnlyCostScaleService = (serviceId: string | number) => {
|
||||
const usesInvestScaleSingleTotal = (serviceId: string | number) => {
|
||||
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]))
|
||||
@ -326,6 +338,8 @@ const buildDefaultScaleRows = (
|
||||
consultCategoryFactorMap?.get(String(serviceId)) ?? getDefaultConsultCategoryFactor(serviceId)
|
||||
return getMajorLeafIds().map(id => ({
|
||||
id,
|
||||
hasCost: isCostMajorById(id),
|
||||
hasArea: isAreaMajorById(id),
|
||||
amount: null,
|
||||
landArea: null,
|
||||
benchmarkBudgetBasicChecked: true,
|
||||
@ -333,7 +347,7 @@ const buildDefaultScaleRows = (
|
||||
consultCategoryFactor: defaultConsultCategoryFactor,
|
||||
majorFactor: majorFactorMap?.get(id) ?? getDefaultMajorFactorById(id),
|
||||
workStageFactor: 1,
|
||||
workRatio: 100
|
||||
workRatio: 1
|
||||
}))
|
||||
}
|
||||
|
||||
@ -366,6 +380,14 @@ const mergeScaleRows = (
|
||||
|
||||
return {
|
||||
...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),
|
||||
landArea: toFiniteNumberOrNull(fromDb.landArea),
|
||||
benchmarkBudgetBasicChecked:
|
||||
@ -394,7 +416,7 @@ const mergeScaleRows = (
|
||||
|
||||
const getInvestmentBudgetFee = (row: ScaleRow) => getScaleBudgetFeeByRow(row, 'cost')
|
||||
|
||||
const getOnlyCostScaleBudgetFee = (
|
||||
const getInvestScaleSingleTotalBudgetFee = (
|
||||
serviceId: string,
|
||||
rowsFromDb: Array<Record<string, unknown>> | undefined,
|
||||
consultCategoryFactorMap?: Map<string, number | null>,
|
||||
@ -412,7 +434,7 @@ const getOnlyCostScaleBudgetFee = (
|
||||
toFiniteNumberOrNull(industryMajorEntry?.item?.defCoe) ??
|
||||
1
|
||||
|
||||
// 新版 onlyCostScale 支持“按项目行”存储(如 1::majorId、2::majorId),每行需独立计费后求和。
|
||||
// 单行总投资模式支持“按项目行”存储(如 1::majorId、2::majorId),每行需独立计费后求和。
|
||||
const usePerRowCalculation = sourceRows.some(row => {
|
||||
if (typeof row?.projectIndex === 'number' && Number.isFinite(row.projectIndex)) return true
|
||||
const id = String(row?.id || '')
|
||||
@ -429,7 +451,7 @@ const getOnlyCostScaleBudgetFee = (
|
||||
majorFactor: getRowNumberOrFallback(row, 'majorFactor', defaultMajorFactor),
|
||||
consultCategoryFactor: getRowNumberOrFallback(row, 'consultCategoryFactor', defaultConsultCategoryFactor),
|
||||
workStageFactor: getRowNumberOrFallback(row, 'workStageFactor', 1),
|
||||
workRatio: getRowNumberOrFallback(row, 'workRatio', 100)
|
||||
workRatio: getRowNumberOrFallback(row, 'workRatio', 1)
|
||||
}, 'cost')
|
||||
})
|
||||
}
|
||||
@ -443,7 +465,7 @@ const getOnlyCostScaleBudgetFee = (
|
||||
const consultCategoryFactor = getRowNumberOrFallback(onlyRow, 'consultCategoryFactor', defaultConsultCategoryFactor)
|
||||
const majorFactor = getRowNumberOrFallback(onlyRow, 'majorFactor', defaultMajorFactor)
|
||||
const workStageFactor = getRowNumberOrFallback(onlyRow, 'workStageFactor', 1)
|
||||
const workRatio = getRowNumberOrFallback(onlyRow, 'workRatio', 100)
|
||||
const workRatio = getRowNumberOrFallback(onlyRow, 'workRatio', 1)
|
||||
return getScaleBudgetFeeByRow({
|
||||
amount: resolvedTotalAmount,
|
||||
benchmarkBudgetBasicChecked: typeof onlyRow?.benchmarkBudgetBasicChecked === 'boolean' ? onlyRow.benchmarkBudgetBasicChecked : true,
|
||||
@ -455,7 +477,7 @@ const getOnlyCostScaleBudgetFee = (
|
||||
}, 'cost')
|
||||
}
|
||||
|
||||
const buildOnlyCostScaleDetailRows = (
|
||||
const buildInvestScaleSingleTotalDetailRows = (
|
||||
serviceId: string,
|
||||
rowsFromDb: Array<Record<string, unknown>> | undefined,
|
||||
consultCategoryFactorMap?: Map<string, number | null>,
|
||||
@ -484,11 +506,13 @@ const buildOnlyCostScaleDetailRows = (
|
||||
1
|
||||
)
|
||||
const workStageFactor = getRowNumberOrFallback(onlyRow, 'workStageFactor', 1)
|
||||
const workRatio = getRowNumberOrFallback(onlyRow, 'workRatio', 100)
|
||||
const workRatio = getRowNumberOrFallback(onlyRow, 'workRatio', 1)
|
||||
|
||||
return [
|
||||
{
|
||||
id: onlyCostRowId,
|
||||
hasCost: true,
|
||||
hasArea: false,
|
||||
amount: resolvedTotalAmount,
|
||||
landArea: null,
|
||||
consultCategoryFactor,
|
||||
@ -656,6 +680,8 @@ const normalizeScopedScaleRows = (
|
||||
const hasWorkRatio = hasOwn(row, 'workRatio')
|
||||
return {
|
||||
id: resolvedMajorId,
|
||||
hasCost: isCostMajorById(resolvedMajorId),
|
||||
hasArea: isAreaMajorById(resolvedMajorId),
|
||||
amount: toFiniteNumberOrNull(row.amount),
|
||||
landArea: toFiniteNumberOrNull(row.landArea),
|
||||
benchmarkBudgetBasicChecked:
|
||||
@ -673,7 +699,7 @@ const normalizeScopedScaleRows = (
|
||||
(hasWorkStageFactor ? null : 1),
|
||||
workRatio:
|
||||
toFiniteNumberOrNull(row.workRatio) ??
|
||||
(hasWorkRatio ? null : 100)
|
||||
(hasWorkRatio ? null : 1)
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -731,7 +757,8 @@ const buildDefaultPricingMethodDetailRows = (
|
||||
serviceId: string,
|
||||
context: PricingMethodDefaultBuildContext
|
||||
): PricingMethodDefaultDetailRows => {
|
||||
const onlyCostScale = isOnlyCostScaleService(serviceId)
|
||||
const capabilities = getScaleMethodCapabilities(serviceId)
|
||||
const investScaleSingleTotal = usesInvestScaleSingleTotal(serviceId)
|
||||
const scaleRows = resolveScaleRows(
|
||||
serviceId,
|
||||
null,
|
||||
@ -740,8 +767,9 @@ const buildDefaultPricingMethodDetailRows = (
|
||||
context.majorFactorMap
|
||||
)
|
||||
|
||||
const investScale = onlyCostScale
|
||||
? buildOnlyCostScaleDetailRows(
|
||||
const investScale = capabilities.investScaleEnabled
|
||||
? (investScaleSingleTotal
|
||||
? buildInvestScaleSingleTotalDetailRows(
|
||||
serviceId,
|
||||
context.htData?.detailRows as Array<Record<string, unknown>> | undefined,
|
||||
context.consultCategoryFactorMap,
|
||||
@ -753,8 +781,11 @@ const buildDefaultPricingMethodDetailRows = (
|
||||
if (!isCostMajorById(row.id)) return false
|
||||
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 {
|
||||
investScale,
|
||||
@ -838,7 +869,7 @@ export const getPricingMethodTotalsForService = async (params: {
|
||||
|
||||
const consultCategoryFactorMap = buildConsultCategoryFactorMap(consultFactorData)
|
||||
const majorFactorMap = buildMajorFactorMap(majorFactorData)
|
||||
const onlyCostScale = isOnlyCostScaleService(serviceId)
|
||||
const investScaleSingleTotal = usesInvestScaleSingleTotal(serviceId)
|
||||
const industryId = typeof baseInfo?.projectIndustry === 'string' ? baseInfo.projectIndustry.trim() : ''
|
||||
|
||||
// 优先使用对应计费页的数据;不存在时回退合同段规模信息,再回退默认字典行。
|
||||
@ -851,8 +882,8 @@ export const getPricingMethodTotalsForService = async (params: {
|
||||
const scopedLandRows = hasScopedScaleRows(landScaleRowsSource)
|
||||
? normalizeScopedScaleRows(serviceId, landScaleRowsSource, consultCategoryFactorMap, majorFactorMap)
|
||||
: null
|
||||
const investScale = onlyCostScale
|
||||
? getOnlyCostScaleBudgetFee(
|
||||
const investScale = investScaleSingleTotal
|
||||
? getInvestScaleSingleTotalBudgetFee(
|
||||
serviceId,
|
||||
(investData?.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 hourlyData = toStoredDetailRowsState(storeHourlyData) || hourlyDataFallback
|
||||
|
||||
const shouldInitInvest = !Array.isArray(investData?.detailRows) || investData!.detailRows!.length === 0
|
||||
const shouldInitLand = !Array.isArray(landData?.detailRows) || landData!.detailRows!.length === 0
|
||||
const shouldInitWorkload = !Array.isArray(workloadData?.detailRows) || workloadData!.detailRows!.length === 0
|
||||
const shouldInitHourly = !Array.isArray(hourlyData?.detailRows) || hourlyData!.detailRows!.length === 0
|
||||
const shouldInitInvest = !Array.isArray(investData?.detailRows)
|
||||
const shouldInitLand = !Array.isArray(landData?.detailRows)
|
||||
const shouldInitWorkload = !Array.isArray(workloadData?.detailRows)
|
||||
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>[] = []
|
||||
let defaultRows: PricingMethodDefaultDetailRows | null = null
|
||||
const getDefaultRows = () => {
|
||||
if (!defaultRows) {
|
||||
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
|
||||
}
|
||||
@ -996,6 +1060,13 @@ export const ensurePricingMethodDetailRowsForServices = async (params: {
|
||||
if (writeTasks.length > 0) {
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
@ -8,6 +8,7 @@
|
||||
import { getMajorDictEntries, getMajorIdAliasMap, getServiceDictById } from '@/sql'
|
||||
import { toFiniteNumberOrNull } from '@/lib/decimal'
|
||||
import { getBenchmarkBudgetByScale, getScaleBudgetFee } from '@/lib/pricingScaleFee'
|
||||
import { isInvestScaleSingleTotalService } from '@/lib/servicePricing'
|
||||
import type {
|
||||
ScaleCalcRow,
|
||||
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)]
|
||||
return service?.onlyCostScale === true
|
||||
return isInvestScaleSingleTotalService(service)
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------------
|
||||
@ -112,7 +113,7 @@ export const buildDefaultScaleRows = (
|
||||
consultCategoryFactor: defaultFactor,
|
||||
majorFactor: majorFactorMap?.get(id) ?? getDefaultMajorFactor(id),
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
@ -4,6 +4,7 @@ import {
|
||||
formatScaleReadonlyMoney,
|
||||
getScaleMergeColSpanBeforeTotal
|
||||
} from '@/lib/pricingScaleGrid'
|
||||
import { AgGridResetHeader } from '@/lib/agGridResetHeader'
|
||||
import { i18n } from '@/i18n'
|
||||
|
||||
type ScaleColumnField<TRow> = Extract<keyof TRow, string> | string
|
||||
@ -199,6 +200,8 @@ export const createScaleBudgetFeeColumnGroup = <TRow>(options: {
|
||||
headerName: scaleT('columns.workRatio'),
|
||||
field: 'workRatio' as any,
|
||||
colId: 'workRatio',
|
||||
headerTooltip: scaleT('tooltip.workRatio'),
|
||||
headerComponent: AgGridResetHeader,
|
||||
headerClass: 'ag-right-aligned-header',
|
||||
minWidth: 80,
|
||||
flex: 1,
|
||||
@ -258,11 +261,14 @@ export const createScaleAutoGroupColumn = <TRow>(options: {
|
||||
idLabelMap: Map<string, string>
|
||||
parseProjectIndexFromPathKey: (key: string) => number | null
|
||||
}) : ColDef<TRow> => ({
|
||||
headerName: scaleT('columns.number'),
|
||||
headerName: scaleT('columns.majorGroup'),
|
||||
minWidth: 250,
|
||||
flex: 2,
|
||||
wrapText: true,
|
||||
autoHeight: true,
|
||||
cellClassRules: {
|
||||
'ag-summary-label-cell': params => Boolean(params.node?.rowPinned)
|
||||
},
|
||||
cellStyle: {
|
||||
whiteSpace: 'normal',
|
||||
lineHeight: '1.4'
|
||||
@ -276,8 +282,8 @@ export const createScaleAutoGroupColumn = <TRow>(options: {
|
||||
return options.totalLabel
|
||||
}
|
||||
const rowData = params.data as any
|
||||
if (!params.node?.group && rowData?.majorCode) {
|
||||
return rowData.majorCode
|
||||
if (!params.node?.group && rowData?.majorCode && rowData?.majorName) {
|
||||
return `${rowData.majorCode} ${rowData.majorName}`
|
||||
}
|
||||
const nodeId = String(params.value || '')
|
||||
const projectIndex = options.parseProjectIndexFromPathKey(nodeId)
|
||||
@ -287,8 +293,8 @@ export const createScaleAutoGroupColumn = <TRow>(options: {
|
||||
tooltipValueGetter: params => {
|
||||
if (params.node?.rowPinned) return options.totalLabel
|
||||
const rowData = params.data as any
|
||||
if (!params.node?.group && rowData?.majorCode) {
|
||||
return rowData.majorCode
|
||||
if (!params.node?.group && rowData?.majorCode && rowData?.majorName) {
|
||||
return `${rowData.majorCode} ${rowData.majorName}`
|
||||
}
|
||||
const nodeId = String(params.value || '')
|
||||
const projectIndex = options.parseProjectIndexFromPathKey(nodeId)
|
||||
@ -296,92 +302,3 @@ export const createScaleAutoGroupColumn = <TRow>(options: {
|
||||
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
|
||||
* 2. 再乘咨询分类系数、专业系数、阶段系数、工作占比
|
||||
* 2. 再乘咨询分类系数、专业系数、阶段系数、服务预算构成比率与数量比
|
||||
* 3. 返回服务费的基本部分 / 附加部分 / 合计
|
||||
*
|
||||
* 和 `getBenchmarkBudgetSplitByScale` 的区别是:
|
||||
@ -78,7 +78,7 @@ export const getScaleBudgetFeeSplit = (params: {
|
||||
const majorFactor = toFiniteNumberOrNull(params.majorFactor)
|
||||
const consultCategoryFactor = toFiniteNumberOrNull(params.consultCategoryFactor)
|
||||
const workStageFactor = hasWorkStageFactor ? toFiniteNumberOrNull(params.workStageFactor) : 1
|
||||
const workRatio = hasWorkRatio ? toFiniteNumberOrNull(params.workRatio) : 100
|
||||
const workRatio = hasWorkRatio ? toFiniteNumberOrNull(params.workRatio) : 1
|
||||
|
||||
if (
|
||||
benchmarkBudgetBasic == null ||
|
||||
@ -95,7 +95,6 @@ export const getScaleBudgetFeeSplit = (params: {
|
||||
.mul(majorFactor)
|
||||
.mul(workStageFactor)
|
||||
.mul(workRatio)
|
||||
.div(100)
|
||||
const roundedBenchmarkBudget = roundTo(addNumbers(benchmarkBudgetBasic, benchmarkBudgetOptional), 2)
|
||||
const basic = roundTo(toDecimal(benchmarkBudgetBasic).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 consultCategoryFactor = toFiniteNumberOrNull(params.consultCategoryFactor)
|
||||
const workStageFactor = hasWorkStageFactor ? toFiniteNumberOrNull(params.workStageFactor) : 1
|
||||
const workRatio = hasWorkRatio ? toFiniteNumberOrNull(params.workRatio) : 100
|
||||
const workRatio = hasWorkRatio ? toFiniteNumberOrNull(params.workRatio) : 1
|
||||
|
||||
if (
|
||||
benchmarkBudget == null ||
|
||||
@ -138,6 +137,5 @@ export const getScaleBudgetFee = (params: {
|
||||
.mul(majorFactor)
|
||||
.mul(workStageFactor)
|
||||
.mul(workRatio)
|
||||
.div(100)
|
||||
return roundTo(toDecimal(roundedBenchmarkBudget).mul(multiplier), 2)
|
||||
}
|
||||
|
||||
@ -294,10 +294,10 @@ export const initializeProjectFactorStates = async (
|
||||
detailRows: buildFactorRowsFromEntries(majorEntries)
|
||||
}
|
||||
|
||||
await Promise.all([
|
||||
kvStore.setItem(consultCategoryFactorKey, consultPayload),
|
||||
kvStore.setItem(majorFactorKey, majorPayload)
|
||||
])
|
||||
// 新项目初始化走 createProjectKvAdapter 时,setItem 是整包读改写,不是原子更新。
|
||||
// 这里并发写两个 key 会互相覆盖,导致咨询系数或专业系数其中一个丢失。
|
||||
await kvStore.setItem(consultCategoryFactorKey, consultPayload)
|
||||
await kvStore.setItem(majorFactorKey, majorPayload)
|
||||
}
|
||||
|
||||
export const initializeProjectScaleState = async (
|
||||
@ -307,3 +307,4 @@ export const initializeProjectScaleState = async (
|
||||
) => {
|
||||
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 { getBenchmarkBudgetSplitByScale, getScaleBudgetFeeSplit } from '@/lib/pricingScaleFee'
|
||||
export { toFiniteNumber, toFiniteNumberOrZero }
|
||||
@ -52,6 +52,7 @@ interface ScaleRowLike {
|
||||
|
||||
interface WorkloadMethodRowLike {
|
||||
id: string
|
||||
conversion?: unknown
|
||||
budgetAdoptedUnitPrice?: unknown
|
||||
workload?: unknown
|
||||
basicFee?: unknown
|
||||
@ -246,6 +247,18 @@ export const toScaleMajorId = (row: ScaleMethodRowLike): number | null => {
|
||||
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 => {
|
||||
const parsed = parseScaleScopedRowId(row.id)
|
||||
return parsed.proNum > 0 ? parsed.proNum : 1
|
||||
@ -276,7 +289,16 @@ const isExportableScaleMethodRow = (
|
||||
mode: 'cost' | 'area'
|
||||
) => {
|
||||
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()
|
||||
@ -302,9 +324,14 @@ export const resolveScaleMethodFee = (row: ScaleMethodRowLike, mode: 'cost' | 'a
|
||||
workRatio: row.workRatio
|
||||
})
|
||||
: null
|
||||
const basicFee = allUnchecked ? null : (toFiniteNumber(row.budgetFee) ?? computedSplit?.total ?? null)
|
||||
const basicFeeBasic = allUnchecked ? null : (toFiniteNumber(row.budgetFeeBasic) ?? computedSplit?.basic ?? null)
|
||||
const basicFeeOptional = allUnchecked ? null : (toFiniteNumber(row.budgetFeeOptional) ?? computedSplit?.optional ?? null)
|
||||
const basicFee = allUnchecked
|
||||
? 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()
|
||||
? row.basicFormula
|
||||
: (basicChecked ? (benchmarkSplit?.basicFormula ?? '') : '')
|
||||
@ -315,6 +342,7 @@ export const resolveScaleMethodFee = (row: ScaleMethodRowLike, mode: 'cost' | 'a
|
||||
basicFee,
|
||||
basicFeeBasic,
|
||||
basicFeeOptional,
|
||||
serviceFee,
|
||||
basicFormula,
|
||||
optionalFormula
|
||||
}
|
||||
@ -440,11 +468,12 @@ export const buildMethod1 = (rows: ScaleMethodRowLike[] | undefined) => {
|
||||
const cost = toFiniteNumber(row.amount)
|
||||
const feeResolved = resolveScaleMethodFee(row, 'cost')
|
||||
const basicFee = feeResolved.basicFee
|
||||
if (basicFee != null) hasTotalValue = true
|
||||
const serviceFee = feeResolved.serviceFee
|
||||
if (serviceFee != null) hasTotalValue = true
|
||||
const basicFeeBasic = feeResolved.basicFeeBasic
|
||||
const basicFeeOptional = feeResolved.basicFeeOptional
|
||||
const remark = typeof row.remark === 'string' ? row.remark : ''
|
||||
if (basicFee == null) return null
|
||||
if (basicFee == null && serviceFee == null) return null
|
||||
return {
|
||||
proNum,
|
||||
major,
|
||||
@ -458,7 +487,7 @@ export const buildMethod1 = (rows: ScaleMethodRowLike[] | undefined) => {
|
||||
majorCoe: toFiniteNumberOrZero(row.majorFactor),
|
||||
processCoe: toFiniteNumber(row.workStageFactor) ?? 1,
|
||||
proportion: toFiniteNumber(row.workRatio) ?? 1,
|
||||
fee: toMoney(basicFee),
|
||||
fee: toMoney(serviceFee),
|
||||
remark
|
||||
}
|
||||
})
|
||||
@ -491,11 +520,12 @@ export const buildMethod2 = (rows: ScaleMethodRowLike[] | undefined) => {
|
||||
const area = toFiniteNumber(row.landArea)
|
||||
const feeResolved = resolveScaleMethodFee(row, 'area')
|
||||
const basicFee = feeResolved.basicFee
|
||||
if (basicFee != null) hasTotalValue = true
|
||||
const serviceFee = feeResolved.serviceFee
|
||||
if (serviceFee != null) hasTotalValue = true
|
||||
const basicFeeBasic = feeResolved.basicFeeBasic
|
||||
const basicFeeOptional = feeResolved.basicFeeOptional
|
||||
const remark = typeof row.remark === 'string' ? row.remark : ''
|
||||
if (basicFee == null) return null
|
||||
if (basicFee == null && serviceFee == null) return null
|
||||
return {
|
||||
proNum,
|
||||
major,
|
||||
@ -509,7 +539,7 @@ export const buildMethod2 = (rows: ScaleMethodRowLike[] | undefined) => {
|
||||
majorCoe: toFiniteNumberOrZero(row.majorFactor),
|
||||
processCoe: toFiniteNumber(row.workStageFactor) ?? 1,
|
||||
proportion: toFiniteNumber(row.workRatio) ?? 1,
|
||||
fee: toMoney(basicFee),
|
||||
fee: toMoney(serviceFee),
|
||||
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) => {
|
||||
if (!Array.isArray(rows)) return null
|
||||
let hasTotalValue = false
|
||||
const det = rows
|
||||
.map(row => {
|
||||
const task = getTaskIdFromRowId(row.id)
|
||||
if (task == null || row.basicFee == null) return null
|
||||
if (task == null) return null
|
||||
const amount = toFiniteNumber(row.workload)
|
||||
const basicFee = toFiniteNumber(row.basicFee)
|
||||
const fee = toFiniteNumber(row.serviceFee)
|
||||
const basicFee = resolveWorkloadBasicFee(row)
|
||||
const fee = resolveWorkloadServiceFee(row, basicFee)
|
||||
if (fee != null) hasTotalValue = true
|
||||
const remark = typeof row.remark === 'string' ? row.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) => {
|
||||
if (!Array.isArray(rows)) return null
|
||||
let hasTotalValue = false
|
||||
const det = rows
|
||||
.map(row => {
|
||||
const expert = getExpertIdFromRowId(row.id)
|
||||
if (expert == null || row.serviceBudget == null) return null
|
||||
if (expert == null) return null
|
||||
const personNum = toFiniteNumber(row.personnelCount)
|
||||
const workDay = toFiniteNumber(row.workdayCount)
|
||||
const fee = toFiniteNumber(row.serviceBudget)
|
||||
const fee = resolveHourlyServiceFee(row)
|
||||
if (fee != null) hasTotalValue = true
|
||||
const remark = typeof row.remark === 'string' ? row.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 OPEN_PROJECT_DIALOG_QUERY_KEY = 'openProjectDialog'
|
||||
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 QUICK_PROJECT_ID = 'quick'
|
||||
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_META_KEY = 'quick-contract-meta-v1'
|
||||
@ -42,6 +48,11 @@ export interface QuickContractMeta {
|
||||
updatedAt: string
|
||||
}
|
||||
|
||||
export interface DisclaimerPendingAction {
|
||||
type: 'project' | 'quick' | 'import' | 'existing-project'
|
||||
projectId?: string
|
||||
}
|
||||
|
||||
|
||||
|
||||
export const readWorkspaceMode = (): WorkspaceMode => {
|
||||
@ -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) => {
|
||||
const projectId = normalizeProjectId(projectIdRaw)
|
||||
return `${PROJECT_DB_NAME_PREFIX}-${projectId}`
|
||||
|
||||
@ -22,6 +22,15 @@ type 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 map = new Map<string, number | null>()
|
||||
for (const [id, item] of Object.entries(dict)) {
|
||||
@ -68,7 +77,12 @@ const loadFactorMap = async (
|
||||
const zxFwPricingStore = getZxFwPricingStoreSafely()
|
||||
const kvStore = getKvStoreSafely()
|
||||
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)
|
||||
for (const row of data?.detailRows || []) {
|
||||
if (!row?.id) continue
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { roundTo, sumByNumber, toDecimal } from '@/lib/decimal'
|
||||
import { ensurePricingMethodDetailRowsForServices } from '@/lib/pricingMethodTotals'
|
||||
import { isInvestScaleSingleTotalService, resolveServicePricingCapabilities } from '@/lib/servicePricing'
|
||||
import { getServiceDictItemById } from '@/sql'
|
||||
import {
|
||||
isSameNullableNumber,
|
||||
@ -64,6 +65,10 @@ type WorkloadDetailRow = {
|
||||
}
|
||||
|
||||
type ServiceLite = {
|
||||
enableInvestScale?: boolean | null
|
||||
enableLandScale?: boolean | null
|
||||
investScaleSingleTotal?: boolean | null
|
||||
scale?: boolean | null
|
||||
onlyCostScale?: boolean | null
|
||||
}
|
||||
|
||||
@ -108,9 +113,14 @@ const getWorkloadMethodTotalServiceFee = (rows: WorkloadDetailRow[]) => {
|
||||
return roundTo(sumByNumber(rows, row => row.serviceFee ?? null), 2)
|
||||
}
|
||||
|
||||
const isOnlyCostScaleService = (serviceId: string) => {
|
||||
const usesInvestScaleSingleTotal = (serviceId: string) => {
|
||||
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 = (
|
||||
@ -145,7 +155,7 @@ const syncScaleMethodRows = async (params: {
|
||||
let changedRowCount = 0
|
||||
const useSummaryScaleValues =
|
||||
methodState.detailRows.length === 1 ||
|
||||
(params.method === 'investScale' && isOnlyCostScaleService(params.serviceId))
|
||||
(params.method === 'investScale' && usesInvestScaleSingleTotal(params.serviceId))
|
||||
const nextRows = methodState.detailRows.map(rawRow => {
|
||||
const mode = params.method === 'investScale' ? 'cost' : 'area'
|
||||
if (!matchesChangedScaleRow(rawRow, params.changedRowIds, { bypassFilter: useSummaryScaleValues })) return rawRow
|
||||
@ -243,6 +253,9 @@ export const syncContractScaleToPricing = async (
|
||||
let updatedRowCount = 0
|
||||
|
||||
for (const serviceId of selectedIds) {
|
||||
const capabilities = getScaleMethodCapabilities(serviceId)
|
||||
|
||||
if (capabilities.investScaleEnabled) {
|
||||
const investChangedCount = await syncScaleMethodRows({
|
||||
contractId,
|
||||
serviceId,
|
||||
@ -257,6 +270,9 @@ export const syncContractScaleToPricing = async (
|
||||
updatedMethodCount += 1
|
||||
updatedRowCount += investChangedCount
|
||||
}
|
||||
}
|
||||
|
||||
if (capabilities.landScaleEnabled) {
|
||||
const landChangedCount = await syncScaleMethodRows({
|
||||
contractId,
|
||||
serviceId,
|
||||
@ -272,6 +288,7 @@ export const syncContractScaleToPricing = async (
|
||||
updatedRowCount += landChangedCount
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
updatedServiceCount: updatedServiceIdSet.size,
|
||||
@ -516,3 +533,4 @@ export const syncPricingTotalToZxFw = async (params: {
|
||||
const store = useZxFwPricingStore()
|
||||
return store.updatePricingField(params)
|
||||
}
|
||||
|
||||
|
||||
14
src/main.ts
14
src/main.ts
@ -27,6 +27,8 @@ import './style.css'
|
||||
import { i18n } from '@/i18n'
|
||||
import { ensureProjectIdInUrl, getProjectDbName } from '@/lib/workspace'
|
||||
import { listProjects } from '@/lib/projectRegistry'
|
||||
import { collectActiveProjectSessionLocks } from '@/lib/projectSessionLock'
|
||||
import { router } from '@/router'
|
||||
|
||||
LicenseManager.setLicenseKey(
|
||||
'[v3][RELEASE][0102]_NDg2Njc4MzY3MDgzNw==16d78ca762fb5d2ff740aed081e2af7b'
|
||||
@ -51,14 +53,20 @@ const AG_GRID_MODULES = [
|
||||
LocaleModule,ValidationModule ,CellSpanModule ,RowStyleModule ,RowSelectionModule ,ServerSideRowModelApiModule
|
||||
]
|
||||
|
||||
const isDisclaimerRoute = () => String(window.location.hash || '').startsWith('#/disclaimer')
|
||||
|
||||
const pickBootstrapProjectId = () => {
|
||||
if (isDisclaimerRoute()) return 'default'
|
||||
try {
|
||||
const url = new URL(window.location.href)
|
||||
|
||||
const explicit = String(url.searchParams.get('projectId') || '').trim()
|
||||
if (explicit) return ensureProjectIdInUrl()
|
||||
const projects = listProjects()
|
||||
if (projects.length > 0) {
|
||||
const lastEdited = projects[0]
|
||||
const openedProjectIds = collectActiveProjectSessionLocks(projects.map(project => project.id))
|
||||
const availableProjects = projects.filter(project => !openedProjectIds.has(project.id))
|
||||
if (availableProjects.length > 0) {
|
||||
const lastEdited = availableProjects[0]
|
||||
url.searchParams.set('projectId', lastEdited.id)
|
||||
window.history.replaceState({}, '', `${url.pathname}${url.search}${url.hash}`)
|
||||
}
|
||||
@ -83,4 +91,4 @@ uiPrefsStore.initFromStorage()
|
||||
// 在应用启动时一次性注册 AG Grid 运行所需模块。
|
||||
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'
|
||||
import { useZxFwPricingKeysStore } from '@/pinia/zxFwPricingKeys'
|
||||
|
||||
export type ZxFwPricingField = 'investScale' | 'landScale' | 'serviceFee' | 'hourly'
|
||||
export type ZxFwPricingField = 'investScale' | 'landScale' | 'workload' | 'hourly'
|
||||
export type ServicePricingMethod = ZxFwPricingField
|
||||
|
||||
export interface ZxFwDetailRow {
|
||||
@ -26,7 +26,7 @@ export interface ZxFwDetailRow {
|
||||
process?: number | null
|
||||
investScale: number | null
|
||||
landScale: number | null
|
||||
serviceFee: number | null
|
||||
workload: number | null
|
||||
hourly: number | null
|
||||
subtotal?: number | null
|
||||
finalFee?: number | null
|
||||
@ -47,7 +47,7 @@ export interface ServicePricingMethodState<TRow = unknown> {
|
||||
export interface ServicePricingState {
|
||||
investScale?: ServicePricingMethodState
|
||||
landScale?: ServicePricingMethodState
|
||||
serviceFee?: ServicePricingMethodState
|
||||
workload?: ServicePricingMethodState
|
||||
hourly?: ServicePricingMethodState
|
||||
}
|
||||
|
||||
@ -63,7 +63,7 @@ const FIXED_ROW_ID = 'fixed-budget-c'
|
||||
const METHOD_STORAGE_PREFIX_MAP: Record<ServicePricingMethod, string> = {
|
||||
investScale: 'tzGMF',
|
||||
landScale: 'ydGMF',
|
||||
serviceFee: 'gzlF',
|
||||
workload: 'gzlF',
|
||||
hourly: 'hourlyPricing'
|
||||
}
|
||||
const STORAGE_PREFIX_METHOD_MAP = new Map<string, ServicePricingMethod>(
|
||||
@ -131,7 +131,7 @@ const normalizeRows = (rows: unknown): ZxFwDetailRow[] =>
|
||||
process: normalizeProcessValue(row.process, rowId),
|
||||
investScale: toFiniteNumberOrNull(row.investScale),
|
||||
landScale: toFiniteNumberOrNull(row.landScale),
|
||||
serviceFee: toFiniteNumberOrNull(row.serviceFee),
|
||||
workload: toFiniteNumberOrNull(row.workload),
|
||||
hourly: toFiniteNumberOrNull(row.hourly),
|
||||
subtotal: toFiniteNumberOrNull(row.subtotal),
|
||||
finalFee: toFiniteNumberOrNull(row.finalFee),
|
||||
@ -824,7 +824,7 @@ export const useZxFwPricingStore = defineStore('zxFwPricing', () => {
|
||||
const rowSubtotal = sumNullableNumbers([
|
||||
toFiniteNumberOrNull(row.investScale),
|
||||
toFiniteNumberOrNull(row.landScale),
|
||||
toFiniteNumberOrNull(row.serviceFee),
|
||||
toFiniteNumberOrNull(row.workload),
|
||||
toFiniteNumberOrNull(row.hourly)
|
||||
])
|
||||
return {
|
||||
@ -945,7 +945,8 @@ export const useZxFwPricingStore = defineStore('zxFwPricing', () => {
|
||||
getHtFeeMethodState,
|
||||
setHtFeeMethodState,
|
||||
loadHtFeeMethodState,
|
||||
removeHtFeeMethodState
|
||||
removeHtFeeMethodState,
|
||||
syncHtExtraFeeByContractBase
|
||||
}
|
||||
}, {
|
||||
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-font-size: 0.875rem;
|
||||
--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-line: 1rem;
|
||||
--app-typeline-dot: 1.25rem;
|
||||
--app-typeline-dot-inner: 0.375rem;
|
||||
--app-typeline-dot: 1.125rem;
|
||||
--app-typeline-dot-inner: 0.3125rem;
|
||||
--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;
|
||||
--background: oklch(1 0 0);
|
||||
--foreground: oklch(0.145 0 0);
|
||||
@ -181,6 +186,23 @@ input[inputmode='numeric'] {
|
||||
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. */
|
||||
.ag-theme-quartz .ag-header-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-font-size: 0.8125rem;
|
||||
--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-line: 0.95rem;
|
||||
--app-typeline-dot: 1.125rem;
|
||||
--app-typeline-dot-inner: 0.3125rem;
|
||||
--app-typeline-dot: 1rem;
|
||||
--app-typeline-dot-inner: 0.25rem;
|
||||
--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-font-size: 0.875rem;
|
||||
--app-typeline-side-w: 12rem;
|
||||
--app-typeline-gap: 0.6875rem;
|
||||
--app-typeline-gap: 0.59375rem;
|
||||
--app-typeline-label-font: 0.8125rem;
|
||||
--app-typeline-label-line: 1rem;
|
||||
--app-typeline-dot: 1.1875rem;
|
||||
--app-typeline-dot-inner: 0.375rem;
|
||||
--app-typeline-dot: 1.0625rem;
|
||||
--app-typeline-dot-inner: 0.3125rem;
|
||||
--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-font-size: 0.875rem;
|
||||
--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-line: 1.1rem;
|
||||
--app-typeline-dot: 1.25rem;
|
||||
--app-typeline-dot-inner: 0.4375rem;
|
||||
--app-typeline-dot: 1.125rem;
|
||||
--app-typeline-dot-inner: 0.375rem;
|
||||
--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-font-size: 0.9375rem;
|
||||
--app-typeline-side-w: 13rem;
|
||||
--app-typeline-gap: 0.8125rem;
|
||||
--app-typeline-gap: 0.6875rem;
|
||||
--app-typeline-label-font: 0.9375rem;
|
||||
--app-typeline-label-line: 1.2rem;
|
||||
--app-typeline-dot: 1.375rem;
|
||||
--app-typeline-dot-inner: 0.5rem;
|
||||
--app-typeline-dot: 1.25rem;
|
||||
--app-typeline-dot-inner: 0.4375rem;
|
||||
--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-font-size: 1rem;
|
||||
--app-typeline-side-w: 13.5rem;
|
||||
--app-typeline-gap: 0.875rem;
|
||||
--app-typeline-gap: 0.75rem;
|
||||
--app-typeline-label-font: 1rem;
|
||||
--app-typeline-label-line: 1.25rem;
|
||||
--app-typeline-dot: 1.5rem;
|
||||
--app-typeline-dot-inner: 0.5625rem;
|
||||
--app-typeline-dot: 1.375rem;
|
||||
--app-typeline-dot-inner: 0.5rem;
|
||||
--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-font-size: 1.0625rem;
|
||||
--app-typeline-side-w: 14.5rem;
|
||||
--app-typeline-gap: 1rem;
|
||||
--app-typeline-gap: 0.875rem;
|
||||
--app-typeline-label-font: 1.0625rem;
|
||||
--app-typeline-label-line: 1.35rem;
|
||||
--app-typeline-dot: 1.625rem;
|
||||
--app-typeline-dot-inner: 0.625rem;
|
||||
--app-typeline-dot: 1.5rem;
|
||||
--app-typeline-dot-inner: 0.5625rem;
|
||||
--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 {
|
||||
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
|
||||
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