commit 7bc84bbe5ead319a40cc9e272514d5a371b18798 Author: wangzzpeng <1032675359@qq.com> Date: Thu Sep 24 10:10:43 2026 +0800 first diff --git a/.env.development b/.env.development new file mode 100644 index 0000000..1ea6f86 --- /dev/null +++ b/.env.development @@ -0,0 +1,2 @@ +# 开发期走 vite 代理:/api 前缀由 vite.config.ts 转发到后端 8080 并去掉前缀 +VITE_API_BASE_URL=/api diff --git a/.env.production b/.env.production new file mode 100644 index 0000000..6ff2a49 --- /dev/null +++ b/.env.production @@ -0,0 +1,2 @@ +# TODO(api): 待接口文档提供后填写 +VITE_API_BASE_URL= diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bf341f2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,31 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local +docs +.claude +read_prototype.js +RTK.md +.playwright-mcp +.omo +.opencode + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..77673d0 --- /dev/null +++ b/.npmrc @@ -0,0 +1,3 @@ +use-node-version=20.19.0 +package-manager-strict=true +engine-strict=true \ No newline at end of file diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..a7cea0b --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,3 @@ +{ + "recommendations": ["Vue.volar"] +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..33895ab --- /dev/null +++ b/README.md @@ -0,0 +1,5 @@ +# Vue 3 + TypeScript + Vite + +This template should help get you started developing with Vue 3 and TypeScript in Vite. The template uses Vue 3 ` + + diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..89839f5 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,2131 @@ +{ + "name": "cz-web", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "cz-web", + "version": "0.0.0", + "hasInstallScript": true, + "dependencies": { + "axios": "^1.20.0", + "element-plus": "^2.14.5", + "pinia": "^4.0.3", + "vue": "^3.5.42", + "vue-router": "^4.6.4" + }, + "devDependencies": { + "@types/node": "^24.13.3", + "@vitejs/plugin-vue": "^6.0.8", + "@vue/tsconfig": "^0.9.1", + "less": "^4.9.1", + "typescript": "~6.0.2", + "vite": "^8.3.0", + "vue-tsc": "^3.3.11" + }, + "engines": { + "node": ">=20.19.0", + "pnpm": ">=10.0.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmmirror.com/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmmirror.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.9", + "resolved": "https://registry.npmmirror.com/@babel/parser/-/parser-7.29.9.tgz", + "integrity": "sha512-CjXrNHTnvqBVqHgdBysY3vk2T8tpJHb5/RMeHJBTyVa9xgugCB0CJTx/3oO8RV2QRQP391RWpB7D6hLjm8V9uA==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.8" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.8", + "resolved": "https://registry.npmmirror.com/@babel/types/-/types-7.29.8.tgz", + "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@ctrl/tinycolor": { + "version": "4.2.1", + "resolved": "https://registry.npmmirror.com/@ctrl/tinycolor/-/tinycolor-4.2.1.tgz", + "integrity": "sha512-Vh5uy9Y1JBMId6J1f68TMXnZrTCvNdo9Fu2k0d1YnJngR+sM5kWTKl441c/3TFs+cG0NWOjRWgQ/iaWhbYx+7A==", + "license": "MIT", + "engines": { + "node": ">=14" + } + }, + "node_modules/@element-plus/icons-vue": { + "version": "2.3.2", + "resolved": "https://registry.npmmirror.com/@element-plus/icons-vue/-/icons-vue-2.3.2.tgz", + "integrity": "sha512-OzIuTaIfC8QXEPmJvB4Y4kw34rSXdCJzxcD1kFStBvr8bK6X1zQAYDo0CNMjojnfTqRQCJ0I7prlErcoRiET2A==", + "license": "MIT", + "peerDependencies": { + "vue": "^3.2.0" + } + }, + "node_modules/@floating-ui/core": { + "version": "1.8.0", + "resolved": "https://registry.npmmirror.com/@floating-ui/core/-/core-1.8.0.tgz", + "integrity": "sha512-0CIZ5itps/8x7BG8dEIhs53BvCUH2PCoogtakwRTut+Arm58sJooJ0AuZhLw2HJYIR5cMLNPBSS728sPho2khQ==", + "license": "MIT", + "dependencies": { + "@floating-ui/utils": "^0.2.12" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.8.0", + "resolved": "https://registry.npmmirror.com/@floating-ui/dom/-/dom-1.8.0.tgz", + "integrity": "sha512-yXSrzeHZBTZadLOlfyhCkJHNeLJnHRnRInwdZ40L7ZiaAtrBwoYlsDrX3v5zB1Utk7CLfzcOVnVVWoXEky7Ceg==", + "license": "MIT", + "dependencies": { + "@floating-ui/core": "^1.8.0", + "@floating-ui/utils": "^0.2.12" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.12", + "resolved": "https://registry.npmmirror.com/@floating-ui/utils/-/utils-0.2.12.tgz", + "integrity": "sha512-HpCo8tmWzLVad5s2d19EhAz5zqrrQ6s69qd6moPMQvkOuSwDT1YgRfWSVuc4ennqrgv3OHppiOGMQ7oC13yIww==", + "license": "MIT" + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.6.0", + "resolved": "https://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.6.0.tgz", + "integrity": "sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==", + "license": "MIT" + }, + "node_modules/@oxc-project/types": { + "version": "0.151.0", + "resolved": "https://registry.npmmirror.com/@oxc-project/types/-/types-0.151.0.tgz", + "integrity": "sha512-J1yXrIlNDZVzE3ada310xeAw7nH8yCAyLPuUIsjKatFPmfn5bS1oW+cM+QsGOtVWd5nhSpbwZWx/rue+r5Z+PA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/oxc-project" + } + }, + "node_modules/@popperjs/core": { + "name": "@sxzz/popperjs-es", + "version": "2.11.8", + "resolved": "https://registry.npmmirror.com/@sxzz/popperjs-es/-/popperjs-es-2.11.8.tgz", + "integrity": "sha512-wOwESXvvED3S8xBmcPWHs2dUuzrE4XiZeFu7e1hROIJkm02a49N120pmOXxY33sBb6hArItm5W5tcg1cBtV+HQ==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, + "node_modules/@rolldown/binding-android-arm-eabi": { + "version": "1.2.10", + "resolved": "https://registry.npmmirror.com/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.10.tgz", + "integrity": "sha512-bp9svZb+QurZeh+8H4BhrZkifEB0YBNvTVzNSJnJQkj4NrRwmQoDUCGP0vSN7PbvLeM7l1tK6GXL8mrTiH2myg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.2.10", + "resolved": "https://registry.npmmirror.com/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.10.tgz", + "integrity": "sha512-wm6Dld3RXUAZ/gRWKyUy+4W1B5CB5UeFaOzsSWJWEdxZXHH8rCYiZ5dGe6oJmhsunAPWzL7FZV+VtvmN5Ye2eA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.2.10", + "resolved": "https://registry.npmmirror.com/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.10.tgz", + "integrity": "sha512-UbEfXq/AqGNgRTV3ik+X/iR6mUxu2QdYAadwRxJWquUGnW6gDqdP1FtLtFXRow7RJx0ssRwi80XAPr4r+4DtsA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.2.10", + "resolved": "https://registry.npmmirror.com/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.10.tgz", + "integrity": "sha512-7f5h17q5KZVx/ji1vb8OTq31ch1O2I7K8NPIr44GkyWTApXMIsmhWqZfgpOH10xeauqghDAvGlZktasCkcF6Eg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.2.10", + "resolved": "https://registry.npmmirror.com/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.10.tgz", + "integrity": "sha512-ynOk/eEYhC6ZB2xCGvKrEOwE58oBy9LnrAqtkrDF9Fz1VTaNdGZTsV0VarJdhPwb+sOJTGjCLwcuyRJZ1dnMcQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.2.10", + "resolved": "https://registry.npmmirror.com/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.10.tgz", + "integrity": "sha512-ERrAs185meZZhGan7a4l3RiiJK1ArSDlHdST++uvSxe+FDbR4TwUPahT/cbZJvaG6fIpDpF78surN+tX708Y4Q==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.2.10", + "resolved": "https://registry.npmmirror.com/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.10.tgz", + "integrity": "sha512-KN7OHKD0J3jy1UzBwZWPxpwhODf9IARUIJcrH+yLYKOcmegZ8luEUM38lDP1bDVj40yP6PsSzCqOJF76vljFnQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.2.10", + "resolved": "https://registry.npmmirror.com/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.10.tgz", + "integrity": "sha512-8l9wP8O+wa8zD6iw6egSfzVtu7oZVfH3hlUsMM4MwbLMhxleqeoXbZzjddyK3YyNlwLhqznq3tF7PkNJ8T/V2w==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.2.10", + "resolved": "https://registry.npmmirror.com/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.10.tgz", + "integrity": "sha512-SeXNKeQzA5kLhz/J0CH6ZP0/HJ3v1xm/0YbiYpE0kK7emfRC2OIGGIaE14xzkISEGv2aYuUSpiLiU5Gbq+OI0A==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.2.10", + "resolved": "https://registry.npmmirror.com/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.10.tgz", + "integrity": "sha512-mtht0nR+y8/hart4175Ll15w7lY8dg7CtQ+j2FDNTsDRspOWTK/2V3l0aj9sIj7XmvqxT8Yli/wq22e7feTTWg==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.2.10", + "resolved": "https://registry.npmmirror.com/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.10.tgz", + "integrity": "sha512-FSM94nGd55NYo48usCyM/nHfUKRnqc9+b0vJNuKV0oCCpIp/OGims7rO1Nv/DkFkt0S/s2rxsJ2kkS8J3HcpeA==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.2.10", + "resolved": "https://registry.npmmirror.com/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.10.tgz", + "integrity": "sha512-C3YxNB16myRLs7o+B+6PnQ6jBsdIS4+AE4Ah8glVGhDpEv9AOvxhZ/1duAb4B0UGczEK/lBbccksd8VI+p6zfw==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.2.10", + "resolved": "https://registry.npmmirror.com/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.10.tgz", + "integrity": "sha512-571TlE/F1eeTjjdjYAMMMPs1Mfv3MtX6s3+ZKVU6HiUjZ5Njc6c/qzNy/8K3zALTZnaw3JQVYrHxvNfjm43KAg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.2.10", + "resolved": "https://registry.npmmirror.com/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.10.tgz", + "integrity": "sha512-QXW+ZWaiqs2c7Fi++D/SsW07LTPcUrncxcskJGfGNBoaLik1IU6fJymz4HsqwEO0u5Iq11yTO0B/mc4cPk7jrQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.2.10", + "resolved": "https://registry.npmmirror.com/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.10.tgz", + "integrity": "sha512-5FQFGgah17YeMtG1Yd5a+rMxQpTksyNXxRtKz06FVTaQw3RKYUJQbUoKk0/5jrXBpDo+7makNP7UHA2LQyH64A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", + "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/lodash": { + "version": "4.17.25", + "resolved": "https://registry.npmmirror.com/@types/lodash/-/lodash-4.17.25.tgz", + "integrity": "sha512-+K1NIO8I+F9/wNulfVvu23QYd0Pe9/OCqRrim4NoYIf1VoEDL90Ve4ClzpyqBLc7NpGGWRvYNCKZ1BE/Jpf8dQ==", + "license": "MIT" + }, + "node_modules/@types/lodash-es": { + "version": "4.17.12", + "resolved": "https://registry.npmmirror.com/@types/lodash-es/-/lodash-es-4.17.12.tgz", + "integrity": "sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==", + "license": "MIT", + "dependencies": { + "@types/lodash": "*" + } + }, + "node_modules/@types/node": { + "version": "24.13.6", + "resolved": "https://registry.npmmirror.com/@types/node/-/node-24.13.6.tgz", + "integrity": "sha512-SGrw/h3KPFshy3OE6ZL53LMBG5vGQQ8/gIpiqz/kRZhPJ7HgwCEs8LBuNtWLa8dvGZVpSF7+Bf+c11HUrCb/yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.18.0" + } + }, + "node_modules/@types/web-bluetooth": { + "version": "0.0.21", + "resolved": "https://registry.npmmirror.com/@types/web-bluetooth/-/web-bluetooth-0.0.21.tgz", + "integrity": "sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA==", + "license": "MIT" + }, + "node_modules/@vitejs/plugin-vue": { + "version": "6.0.9", + "resolved": "https://registry.npmmirror.com/@vitejs/plugin-vue/-/plugin-vue-6.0.9.tgz", + "integrity": "sha512-rD/MORlhaZMlXWW0rEn4FB4wMinWC0z/D7Ye160S5+Rs1mC8ZDAdcDp4SjKfUhG5t8q1ktcPVw4xuTkPRlzrSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rolldown/pluginutils": "^1.0.1" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0", + "vue": "^3.2.25" + } + }, + "node_modules/@volar/language-core": { + "version": "2.4.28", + "resolved": "https://registry.npmmirror.com/@volar/language-core/-/language-core-2.4.28.tgz", + "integrity": "sha512-w4qhIJ8ZSitgLAkVay6AbcnC7gP3glYM3fYwKV3srj8m494E3xtrCv6E+bWviiK/8hs6e6t1ij1s2Endql7vzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/source-map": "2.4.28" + } + }, + "node_modules/@volar/source-map": { + "version": "2.4.28", + "resolved": "https://registry.npmmirror.com/@volar/source-map/-/source-map-2.4.28.tgz", + "integrity": "sha512-yX2BDBqJkRXfKw8my8VarTyjv48QwxdJtvRgUpNE5erCsgEUdI2DsLbpa+rOQVAJYshY99szEcRDmyHbF10ggQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@volar/typescript": { + "version": "2.4.28", + "resolved": "https://registry.npmmirror.com/@volar/typescript/-/typescript-2.4.28.tgz", + "integrity": "sha512-Ja6yvWrbis2QtN4ClAKreeUZPVYMARDYZl9LMEv1iQ1QdepB6wn0jTRxA9MftYmYa4DQ4k/DaSZpFPUfxl8giw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/language-core": "2.4.28", + "path-browserify": "^1.0.1", + "vscode-uri": "^3.0.8" + } + }, + "node_modules/@vue/compiler-core": { + "version": "3.5.43", + "resolved": "https://registry.npmmirror.com/@vue/compiler-core/-/compiler-core-3.5.43.tgz", + "integrity": "sha512-zdiLhnbe1QQqgDT8xZMpNmyqZ3qlI+/Q/FHQco57Kwl/b05HhCzN6eVGN9QU9rbga4CrS0H5SYY8VGHZCt/1Hg==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.8", + "@vue/shared": "3.5.43", + "entities": "^7.0.1", + "estree-walker": "^2.0.2", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-dom": { + "version": "3.5.43", + "resolved": "https://registry.npmmirror.com/@vue/compiler-dom/-/compiler-dom-3.5.43.tgz", + "integrity": "sha512-PEZoAk3NQmsn/ejMzSOCyTYqwGqczrWm70PuhBKjjv1+TCoQAaO/zOqNwjV+honlNstT5ILxtc+8r8UUfj+iEQ==", + "license": "MIT", + "dependencies": { + "@vue/compiler-core": "3.5.43", + "@vue/shared": "3.5.43" + } + }, + "node_modules/@vue/compiler-sfc": { + "version": "3.5.43", + "resolved": "https://registry.npmmirror.com/@vue/compiler-sfc/-/compiler-sfc-3.5.43.tgz", + "integrity": "sha512-FCbrG3XNCRl+js3huuKx4IVHBLTvMkJhVepjbxSPu1gn4yWLaYtGNQdjJGZaMytXB6qb76qQDDmDSLy/vkmleQ==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.8", + "@vue/compiler-core": "3.5.43", + "@vue/compiler-dom": "3.5.43", + "@vue/compiler-ssr": "3.5.43", + "@vue/shared": "3.5.43", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.21", + "postcss": "^8.5.28", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-ssr": { + "version": "3.5.43", + "resolved": "https://registry.npmmirror.com/@vue/compiler-ssr/-/compiler-ssr-3.5.43.tgz", + "integrity": "sha512-GF62orf7KiJX9RqrHNGrYBudsQGD0OhJ5nDs90O8UiDuS40+YMYomiXu6w6EuvtXuRDc3MSNis3EaaSKAVSWpg==", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.43", + "@vue/shared": "3.5.43" + } + }, + "node_modules/@vue/devtools-api": { + "version": "8.2.1", + "resolved": "https://registry.npmmirror.com/@vue/devtools-api/-/devtools-api-8.2.1.tgz", + "integrity": "sha512-6u4vXBlIBAC1wMplIZgpyPn7uh/s4Bf6F5bMzvLv+EdJ0aHs/+4B7Ygv864EStQSjRbsRzTko/kUG1A1IejQ3A==", + "license": "MIT", + "peer": true, + "dependencies": { + "@vue/devtools-kit": "^8.2.1" + } + }, + "node_modules/@vue/devtools-kit": { + "version": "8.2.1", + "resolved": "https://registry.npmmirror.com/@vue/devtools-kit/-/devtools-kit-8.2.1.tgz", + "integrity": "sha512-FIGIuq3AWReEpbAHY/cRGeHDfI0qOb8OCQ3YjbEAX04uaxIDbGc9rhkbVcG7rnfHPXE3RsU5KrWOu9V/okd8AQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@vue/devtools-shared": "^8.2.1", + "birpc": "^2.6.1", + "hookable": "^5.5.3", + "perfect-debounce": "^2.0.0" + } + }, + "node_modules/@vue/devtools-shared": { + "version": "8.2.1", + "resolved": "https://registry.npmmirror.com/@vue/devtools-shared/-/devtools-shared-8.2.1.tgz", + "integrity": "sha512-Fkac7lUdGReh6pVOi3AYPRGe82LQqRmAfThW7RRligOAP0ZA/Z1z9XLHDM9dv34pV2HRc79DK8uKPeG2fLnA/g==", + "license": "MIT", + "peer": true + }, + "node_modules/@vue/language-core": { + "version": "3.3.11", + "resolved": "https://registry.npmmirror.com/@vue/language-core/-/language-core-3.3.11.tgz", + "integrity": "sha512-QJmpliwAVpC/OxubIByPAhNzsQPRc8/gxlN2qnVzVfIMjMDz/9RnXRFoetjz5yEgXVXyp4LqhXq3V53PjmNzFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/language-core": "2.4.28", + "@vue/compiler-dom": "^3.5.0", + "@vue/shared": "^3.5.0", + "alien-signals": "^3.2.1", + "muggle-string": "^0.4.1", + "path-browserify": "^1.0.1", + "picomatch": "^4.0.4" + } + }, + "node_modules/@vue/reactivity": { + "version": "3.5.43", + "resolved": "https://registry.npmmirror.com/@vue/reactivity/-/reactivity-3.5.43.tgz", + "integrity": "sha512-G/c9GyOZNI2jVaaS6OX1EF1SSFSv7H0ERqNTl4+DTFMlZmB5eVAB53aLQNam/7NL2NPtaDD7RdVrzf8uJzMuOA==", + "license": "MIT", + "dependencies": { + "@vue/shared": "3.5.43" + } + }, + "node_modules/@vue/runtime-core": { + "version": "3.5.43", + "resolved": "https://registry.npmmirror.com/@vue/runtime-core/-/runtime-core-3.5.43.tgz", + "integrity": "sha512-hU6U6VnVhBGQDpvlnnDlIB8ZGJBiOcgk2lh/0InltHiz3D8oSkluvuvY+do1G2H3+udeKFsmaBlgVYP7gXQzEw==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.43", + "@vue/shared": "3.5.43" + } + }, + "node_modules/@vue/runtime-dom": { + "version": "3.5.43", + "resolved": "https://registry.npmmirror.com/@vue/runtime-dom/-/runtime-dom-3.5.43.tgz", + "integrity": "sha512-Bb2Jc0YjjJdMt1SJmb9b2L/IWd3I8lIT9x9eS/xvvP9CiVgna0ffua74xKRmt4/uSJ+0r4iN8ex1jrqkhQGWQw==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.43", + "@vue/runtime-core": "3.5.43", + "@vue/shared": "3.5.43", + "csstype": "^3.2.3" + } + }, + "node_modules/@vue/server-renderer": { + "version": "3.5.43", + "resolved": "https://registry.npmmirror.com/@vue/server-renderer/-/server-renderer-3.5.43.tgz", + "integrity": "sha512-l2Ygjv9NehV94PSBxNWsAHC0j/eIIKbn92mBuWXAPGLnn6HfJ6MH5ubsd+Nk0YoZ5FRuxWI1P2VSoh+dbPQhCQ==", + "license": "MIT", + "dependencies": { + "@vue/compiler-ssr": "3.5.43", + "@vue/runtime-dom": "3.5.43", + "@vue/shared": "3.5.43" + } + }, + "node_modules/@vue/shared": { + "version": "3.5.43", + "resolved": "https://registry.npmmirror.com/@vue/shared/-/shared-3.5.43.tgz", + "integrity": "sha512-uksS7YGMR5NZyr4JNq0Rp+QyLns0ueaz20KwzIPW9R0LH1Vnt4E+XUM29PNseEbf1www2gOuhuDi5AKOIXag9Q==", + "license": "MIT" + }, + "node_modules/@vue/tsconfig": { + "version": "0.9.1", + "resolved": "https://registry.npmmirror.com/@vue/tsconfig/-/tsconfig-0.9.1.tgz", + "integrity": "sha512-buvjm+9NzLCJL29KY1j1991YYJ5e6275OiK+G4jtmfIb+z4POywbdm0wXusT9adVWqe0xqg70TbI7+mRx4uU9w==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "typescript": ">= 5.8", + "vue": "^3.4.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + }, + "vue": { + "optional": true + } + } + }, + "node_modules/@vueuse/core": { + "version": "15.0.0", + "resolved": "https://registry.npmmirror.com/@vueuse/core/-/core-15.0.0.tgz", + "integrity": "sha512-Zv67h5mrk7YHDJsRObx+DXaCBz6RKBUpxqLesJ+3xZNPG6yuQs7ja7kg3veFrrouYLWAqydQ7CwGEbLVxB43Ng==", + "license": "MIT", + "dependencies": { + "@types/web-bluetooth": "^0.0.21", + "@vueuse/metadata": "15.0.0", + "@vueuse/shared": "15.0.0" + }, + "engines": { + "node": ">=22" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "vue": "^3.5.0" + } + }, + "node_modules/@vueuse/metadata": { + "version": "15.0.0", + "resolved": "https://registry.npmmirror.com/@vueuse/metadata/-/metadata-15.0.0.tgz", + "integrity": "sha512-sfWE5LADAKJ0KnjtGliMdSN8Ij/L7uP6rXIZlgPloAI4zvvefbw6H5Lt7KJA6RqQOYgVqWF5Guj50HHMIzygBg==", + "license": "MIT", + "engines": { + "node": ">=22" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/shared": { + "version": "15.0.0", + "resolved": "https://registry.npmmirror.com/@vueuse/shared/-/shared-15.0.0.tgz", + "integrity": "sha512-vbNXhGrbV53xPOMSyOzMHQRvhMgZf7oNDw2RChUetVxwTIEkInQ1joZ9JbT0G3z2n2AJNjvmCWeM9ySYrnvogA==", + "license": "MIT", + "engines": { + "node": ">=22" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "vue": "^3.5.0" + } + }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmmirror.com/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "license": "MIT", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/alien-signals": { + "version": "3.2.1", + "resolved": "https://registry.npmmirror.com/alien-signals/-/alien-signals-3.2.1.tgz", + "integrity": "sha512-I8FjmltrfnDFoZedi5CG8DghVYNhzb/Ijluz7tCSJH0xpd0484Kowhbb1XDYOxfJpU1p5wnM2X54dA+IfGyD1g==", + "dev": true, + "license": "MIT" + }, + "node_modules/async-validator": { + "version": "4.2.5", + "resolved": "https://registry.npmmirror.com/async-validator/-/async-validator-4.2.5.tgz", + "integrity": "sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg==", + "license": "MIT" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmmirror.com/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/axios": { + "version": "1.20.0", + "resolved": "https://registry.npmmirror.com/axios/-/axios-1.20.0.tgz", + "integrity": "sha512-r8aOh8j9cGKpgQAqpzrUHnSIc6a59Y3Xf/cv8sy1DrHCkZHzQGEuoq1tARk6qSyDdtQGSDgpb9kFlruzPvrgwg==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.16.0", + "form-data": "^4.0.6", + "https-proxy-agent": "^5.0.1", + "proxy-from-env": "^2.1.0" + } + }, + "node_modules/birpc": { + "version": "2.9.0", + "resolved": "https://registry.npmmirror.com/birpc/-/birpc-2.9.0.tgz", + "integrity": "sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw==", + "license": "MIT", + "peer": true, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmmirror.com/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/copy-anything": { + "version": "3.0.5", + "resolved": "https://registry.npmmirror.com/copy-anything/-/copy-anything-3.0.5.tgz", + "integrity": "sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-what": "^4.1.8" + }, + "engines": { + "node": ">=12.13" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmmirror.com/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "license": "MIT" + }, + "node_modules/dayjs": { + "version": "1.11.23", + "resolved": "https://registry.npmmirror.com/dayjs/-/dayjs-1.11.23.tgz", + "integrity": "sha512-QDTCU0M0MxR3hQfnlDJfwekQiaanm1ubOD231u73WBckQ/fsamwRLiE2GBz6D3a/xF1NgfiDLJjXBa1hYOYTtQ==", + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmmirror.com/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmmirror.com/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/element-plus": { + "version": "2.14.6", + "resolved": "https://registry.npmmirror.com/element-plus/-/element-plus-2.14.6.tgz", + "integrity": "sha512-GH0fMoMgVEKkHimoaKvg5B00NmfPbQ+qxX6/JrO7F+dLAC3atrZr2Y2MoccHMxS22M7QE12/s0DG0Zs49iwJ/A==", + "license": "MIT", + "dependencies": { + "@ctrl/tinycolor": "^4.2.1", + "@element-plus/icons-vue": "^2.3.2", + "@floating-ui/dom": "^1.8.0", + "@popperjs/core": "npm:@sxzz/popperjs-es@^2.11.8", + "@types/lodash": "^4.17.24", + "@types/lodash-es": "^4.17.12", + "@vueuse/core": "15.0.0", + "async-validator": "^4.2.5", + "dayjs": "^1.11.20", + "lodash": "^4.18.1", + "lodash-es": "^4.18.1", + "lodash-unified": "^1.0.3", + "memoize-one": "^6.0.0", + "normalize-wheel-es": "^1.2.0", + "vue-component-type-helpers": "^3.3.11" + }, + "peerDependencies": { + "vue": "^3.3.7" + } + }, + "node_modules/entities": { + "version": "7.0.1", + "resolved": "https://registry.npmmirror.com/entities/-/entities-7.0.1.tgz", + "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/errno": { + "version": "0.1.8", + "resolved": "https://registry.npmmirror.com/errno/-/errno-0.1.8.tgz", + "integrity": "sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "prr": "~1.0.1" + }, + "bin": { + "errno": "cli.js" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "license": "MIT" + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmmirror.com/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/follow-redirects": { + "version": "1.16.0", + "resolved": "https://registry.npmmirror.com/follow-redirects/-/follow-redirects-1.16.0.tgz", + "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.6", + "resolved": "https://registry.npmmirror.com/form-data/-/form-data-4.0.6.tgz", + "integrity": "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.4", + "mime-types": "^2.1.35" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmmirror.com/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC", + "optional": true + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.4", + "resolved": "https://registry.npmmirror.com/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hookable": { + "version": "5.5.3", + "resolved": "https://registry.npmmirror.com/hookable/-/hookable-5.5.3.tgz", + "integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==", + "license": "MIT", + "peer": true + }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmmirror.com/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "license": "MIT", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-what": { + "version": "4.1.16", + "resolved": "https://registry.npmmirror.com/is-what/-/is-what-4.1.16.tgz", + "integrity": "sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.13" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, + "node_modules/less": { + "version": "4.9.1", + "resolved": "https://registry.npmmirror.com/less/-/less-4.9.1.tgz", + "integrity": "sha512-orp15PfJvvNDIqJdVWzMI9Sjpjp3VTiw3sfvbB+67LlISTEn8uVT2EdYSuyl02BLvaftv6sdk9Umnxmm5rckmg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "copy-anything": "^3.0.5", + "parse-node-version": "^1.0.1" + }, + "bin": { + "lessc": "bin/lessc" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "errno": "^0.1.1", + "graceful-fs": "^4.1.2", + "make-dir": "^5.1.0", + "mime": "^1.4.1", + "needle": "^3.1.0", + "probe-image-size": "^7.2.3", + "source-map": "~0.6.0" + } + }, + "node_modules/lightningcss": { + "version": "1.33.0", + "resolved": "https://registry.npmmirror.com/lightningcss/-/lightningcss-1.33.0.tgz", + "integrity": "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.33.0", + "lightningcss-darwin-arm64": "1.33.0", + "lightningcss-darwin-x64": "1.33.0", + "lightningcss-freebsd-x64": "1.33.0", + "lightningcss-linux-arm-gnueabihf": "1.33.0", + "lightningcss-linux-arm64-gnu": "1.33.0", + "lightningcss-linux-arm64-musl": "1.33.0", + "lightningcss-linux-x64-gnu": "1.33.0", + "lightningcss-linux-x64-musl": "1.33.0", + "lightningcss-win32-arm64-msvc": "1.33.0", + "lightningcss-win32-x64-msvc": "1.33.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.33.0", + "resolved": "https://registry.npmmirror.com/lightningcss-android-arm64/-/lightningcss-android-arm64-1.33.0.tgz", + "integrity": "sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.33.0", + "resolved": "https://registry.npmmirror.com/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.33.0.tgz", + "integrity": "sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.33.0", + "resolved": "https://registry.npmmirror.com/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.33.0.tgz", + "integrity": "sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.33.0", + "resolved": "https://registry.npmmirror.com/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.33.0.tgz", + "integrity": "sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.33.0", + "resolved": "https://registry.npmmirror.com/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.33.0.tgz", + "integrity": "sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.33.0", + "resolved": "https://registry.npmmirror.com/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.33.0.tgz", + "integrity": "sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.33.0", + "resolved": "https://registry.npmmirror.com/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.33.0.tgz", + "integrity": "sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.33.0", + "resolved": "https://registry.npmmirror.com/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.33.0.tgz", + "integrity": "sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.33.0", + "resolved": "https://registry.npmmirror.com/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.33.0.tgz", + "integrity": "sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.33.0", + "resolved": "https://registry.npmmirror.com/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.33.0.tgz", + "integrity": "sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.33.0", + "resolved": "https://registry.npmmirror.com/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.33.0.tgz", + "integrity": "sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lodash": { + "version": "4.18.1", + "resolved": "https://registry.npmmirror.com/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", + "license": "MIT" + }, + "node_modules/lodash-es": { + "version": "4.18.1", + "resolved": "https://registry.npmmirror.com/lodash-es/-/lodash-es-4.18.1.tgz", + "integrity": "sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A==", + "license": "MIT" + }, + "node_modules/lodash-unified": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/lodash-unified/-/lodash-unified-1.0.3.tgz", + "integrity": "sha512-WK9qSozxXOD7ZJQlpSqOT+om2ZfcT4yO+03FuzAHD0wF6S0l0090LRPDx3vhTTLZ8cFKpBn+IOcVXK6qOcIlfQ==", + "license": "MIT", + "peerDependencies": { + "@types/lodash-es": "*", + "lodash": "*", + "lodash-es": "*" + } + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmmirror.com/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmmirror.com/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/make-dir": { + "version": "5.1.0", + "resolved": "https://registry.npmmirror.com/make-dir/-/make-dir-5.1.0.tgz", + "integrity": "sha512-IfpFq6UM39dUNiphpA6uDezNx/AvWyhwfICWPR3t1VspkgkMZrL+Rk1RbN1bx+aeNYwOrqGJgEgV3yotk+ZUVw==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/memoize-one": { + "version": "6.0.0", + "resolved": "https://registry.npmmirror.com/memoize-one/-/memoize-one-6.0.0.tgz", + "integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==", + "license": "MIT" + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmmirror.com/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true, + "license": "MIT", + "optional": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmmirror.com/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmmirror.com/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/muggle-string": { + "version": "0.4.1", + "resolved": "https://registry.npmmirror.com/muggle-string/-/muggle-string-0.4.1.tgz", + "integrity": "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.19", + "resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.19.tgz", + "integrity": "sha512-Y2tUNy4ouw6tq5oDSKeQYGOyhkUBhNOcGV/02KC+6kd9eDGqdZd++mjMiIDilrBYvjEnCYvVtsuHCuP+okSfug==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/needle": { + "version": "3.5.0", + "resolved": "https://registry.npmmirror.com/needle/-/needle-3.5.0.tgz", + "integrity": "sha512-jaQyPKKk2YokHrEg+vFDYxXIHTCBgiZwSHOoVx/8V3GIBS8/VN6NdVRmg8q1ERtPkMvmOvebsgga4sAj5hls/w==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "iconv-lite": "^0.6.3", + "sax": "^1.2.4" + }, + "bin": { + "needle": "bin/needle" + }, + "engines": { + "node": ">= 4.4.x" + } + }, + "node_modules/normalize-wheel-es": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/normalize-wheel-es/-/normalize-wheel-es-1.2.0.tgz", + "integrity": "sha512-Wj7+EJQ8mSuXr2iWfnujrimU35R2W4FAErEyTmJoJ7ucwTn2hOUSsRehMb5RSYkxXGTM7Y9QpvPmp++w5ftoJw==", + "license": "BSD-3-Clause" + }, + "node_modules/nostics": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/nostics/-/nostics-1.3.0.tgz", + "integrity": "sha512-wzRsrJwNlFbqIRQt8aCeL2WE8USip/uuvGSOZV1D02CxX8ABTMbw8IywKjH7yWHETMXbWJI4OBpihg9rmz2Vyg==", + "license": "MIT" + }, + "node_modules/parse-node-version": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/parse-node-version/-/parse-node-version-1.0.1.tgz", + "integrity": "sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/path-browserify": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/path-browserify/-/path-browserify-1.0.1.tgz", + "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", + "dev": true, + "license": "MIT" + }, + "node_modules/perfect-debounce": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/perfect-debounce/-/perfect-debounce-2.1.0.tgz", + "integrity": "sha512-LjgdTytVFXeUgtHZr9WYViYSM/g8MkcTPYDlPa3cDqMirHjKiSZPYd6DoL7pK8AJQr+uWkQvCjHNdiMqsrJs+g==", + "license": "MIT", + "peer": true + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.7", + "resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-4.0.7.tgz", + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pinia": { + "version": "4.0.3", + "resolved": "https://registry.npmmirror.com/pinia/-/pinia-4.0.3.tgz", + "integrity": "sha512-XMQqpvjgG7LMqVhhFUzKLT4KEbsYbfZZ0CZU9PgdFm1O2VKBmIkykL8+SfNhOaT7sR0wT6BEgKT0YNDYWgmVRA==", + "license": "MIT", + "dependencies": { + "nostics": "^1.1.4" + }, + "funding": { + "url": "https://github.com/sponsors/posva" + }, + "peerDependencies": { + "@vue/devtools-api": "^8.1.5", + "typescript": ">=5.6.0", + "vue": "^3.5.11" + }, + "peerDependenciesMeta": { + "@vue/devtools-api": { + "optional": false + }, + "typescript": { + "optional": true + } + } + }, + "node_modules/postcss": { + "version": "8.5.28", + "resolved": "https://registry.npmmirror.com/postcss/-/postcss-8.5.28.tgz", + "integrity": "sha512-RRuzqDtt5Y9h3quz5hWhK+TPnsmVs6WwSU6LkJMeY4HstUEDuYTG8UJSdawMRzmzAtV+KEoG8N3Qg2qLy5vM/A==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.18", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/probe-image-size": { + "version": "7.4.0", + "resolved": "https://registry.npmmirror.com/probe-image-size/-/probe-image-size-7.4.0.tgz", + "integrity": "sha512-cdEprVtZxV+awMde9X+4jILBFYh4CARxVrQaMl4wY4YcPWbul9jntXrIW95NInBDyJwcVUP3U0T6yukN8rMBaQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], + "license": "MIT", + "optional": true, + "dependencies": { + "lodash.merge": "^4.6.2", + "needle": "^2.5.2", + "stream-parser": "~0.3.1" + } + }, + "node_modules/probe-image-size/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmmirror.com/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/probe-image-size/node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/probe-image-size/node_modules/needle": { + "version": "2.9.1", + "resolved": "https://registry.npmmirror.com/needle/-/needle-2.9.1.tgz", + "integrity": "sha512-6R9fqJ5Zcmf+uYaFgdIHmLwNldn5HbK8L5ybn7Uz+ylX/rnOsSp1AHcvQSrCaFN+qNM1wpymHqD7mVasEOlHGQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "debug": "^3.2.6", + "iconv-lite": "^0.4.4", + "sax": "^1.2.4" + }, + "bin": { + "needle": "bin/needle" + }, + "engines": { + "node": ">= 4.4.x" + } + }, + "node_modules/proxy-from-env": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/proxy-from-env/-/proxy-from-env-2.1.0.tgz", + "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/prr": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/prr/-/prr-1.0.1.tgz", + "integrity": "sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/rolldown": { + "version": "1.2.10", + "resolved": "https://registry.npmmirror.com/rolldown/-/rolldown-1.2.10.tgz", + "integrity": "sha512-OxkA08pSryMK7B3XiFA09B4OJ1xJMPgIYCBMY2xchzpqgBGsV1o0DetPAE+Sl3N3L4oCPiEzmHVSOj7iR04Zog==", + "dev": true, + "license": "MIT", + "dependencies": { + "@oxc-project/types": "=0.151.0", + "@rolldown/pluginutils": "^1.0.0" + }, + "bin": { + "rolldown": "bin/cli.mjs" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm-eabi": "1.2.10", + "@rolldown/binding-android-arm64": "1.2.10", + "@rolldown/binding-darwin-arm64": "1.2.10", + "@rolldown/binding-darwin-x64": "1.2.10", + "@rolldown/binding-freebsd-x64": "1.2.10", + "@rolldown/binding-linux-arm-gnueabihf": "1.2.10", + "@rolldown/binding-linux-arm64-gnu": "1.2.10", + "@rolldown/binding-linux-arm64-musl": "1.2.10", + "@rolldown/binding-linux-ppc64-gnu": "1.2.10", + "@rolldown/binding-linux-s390x-gnu": "1.2.10", + "@rolldown/binding-linux-x64-gnu": "1.2.10", + "@rolldown/binding-linux-x64-musl": "1.2.10", + "@rolldown/binding-openharmony-arm64": "1.2.10", + "@rolldown/binding-win32-arm64-msvc": "1.2.10", + "@rolldown/binding-win32-x64-msvc": "1.2.10" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmmirror.com/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/sax": { + "version": "1.6.1", + "resolved": "https://registry.npmmirror.com/sax/-/sax-1.6.1.tgz", + "integrity": "sha512-42tBVwLWnaQvW5zc4HbZrTuWccECCZfBi92FDuwtqxasH+JbPB3/FOKb1m222K42R4WxuxzzMsTswfzgtSu64Q==", + "dev": true, + "license": "BlueOak-1.0.0", + "optional": true, + "engines": { + "node": ">=11.0.0" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmmirror.com/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stream-parser": { + "version": "0.3.1", + "resolved": "https://registry.npmmirror.com/stream-parser/-/stream-parser-0.3.1.tgz", + "integrity": "sha512-bJ/HgKq41nlKvlhccD5kaCr/P+Hu0wPNKPJOH7en+YrJu/9EgqUF+88w5Jb6KNcjOFMhfX4B2asfeAtIGuHObQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "debug": "2" + } + }, + "node_modules/stream-parser/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmmirror.com/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/stream-parser/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmmirror.com/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/typescript": { + "version": "6.0.3", + "resolved": "https://registry.npmmirror.com/typescript/-/typescript-6.0.3.tgz", + "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", + "devOptional": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "7.18.2", + "resolved": "https://registry.npmmirror.com/undici-types/-/undici-types-7.18.2.tgz", + "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", + "dev": true, + "license": "MIT" + }, + "node_modules/vite": { + "version": "8.3.0", + "resolved": "https://registry.npmmirror.com/vite/-/vite-8.3.0.tgz", + "integrity": "sha512-lhZBVvEHefgE+HQZC9O7EBJgCU/nVzFNl7vkS4RE0APtWLP02/8QVIkQtzBxPquh7lq5/78NHipTj7ODQ6XuyQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "lightningcss": "^1.33.0", + "picomatch": "^4.0.7", + "postcss": "^8.5.28", + "rolldown": "~1.2.6", + "tinyglobby": "^0.2.17" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "@vitejs/devtools": "^0.7.1", + "esbuild": "^0.27.0 || ^0.28.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" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "@vitejs/devtools": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vscode-uri": { + "version": "3.2.0", + "resolved": "https://registry.npmmirror.com/vscode-uri/-/vscode-uri-3.2.0.tgz", + "integrity": "sha512-m2gXo3bn0G1kT9InzMf07fTbqMbGtyckj3bH5ktLO+1Ssv+yiATZ4dhwaQv9UZWxJh6E9IFGnQyjgWVDWVBDrg==", + "dev": true, + "license": "MIT" + }, + "node_modules/vue": { + "version": "3.5.43", + "resolved": "https://registry.npmmirror.com/vue/-/vue-3.5.43.tgz", + "integrity": "sha512-o5qZoksdnjIKvW1srZ3ab7pcDNYAerBjRe54D0LBLfRdCYFrSgBHVXokMas35czQc0//lmx4/tuY4ZNQ+Rf2Ng==", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.43", + "@vue/compiler-sfc": "3.5.43", + "@vue/runtime-dom": "3.5.43", + "@vue/server-renderer": "3.5.43", + "@vue/shared": "3.5.43" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/vue-component-type-helpers": { + "version": "3.3.11", + "resolved": "https://registry.npmmirror.com/vue-component-type-helpers/-/vue-component-type-helpers-3.3.11.tgz", + "integrity": "sha512-LwcxzeliO9fkQcpJG0PoX8X5kmAhKmH9wkpDLxNabwzkQ9Zeib2YVHwFV4pcWmMLfXVfjr/dSV+DaJ3cIPgSNA==", + "license": "MIT" + }, + "node_modules/vue-router": { + "version": "4.6.4", + "resolved": "https://registry.npmmirror.com/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.npmmirror.com/@vue/devtools-api/-/devtools-api-6.6.4.tgz", + "integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==", + "license": "MIT" + }, + "node_modules/vue-tsc": { + "version": "3.3.11", + "resolved": "https://registry.npmmirror.com/vue-tsc/-/vue-tsc-3.3.11.tgz", + "integrity": "sha512-gOb0B9rtU2+f1dszwPqSH5kAieIF9ReeLhD3kSRNHv5WZZUQz/JdVXW0RTdqhNTMlQkqKzrTTviqKr/4FYZraQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/typescript": "2.4.28", + "@vue/language-core": "3.3.11" + }, + "bin": { + "vue-tsc": "bin/vue-tsc.js" + }, + "peerDependencies": { + "typescript": ">=5.0.0" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..3d775b5 --- /dev/null +++ b/package.json @@ -0,0 +1,32 @@ +{ + "name": "cz-web", + "private": true, + "version": "0.0.0", + "type": "module", + "engines": { + "node": ">=20.19.0", + "pnpm": ">=10.0.0" + }, + "scripts": { + "dev": "vite", + "build": "vue-tsc -b && vite build", + "preview": "vite preview", + "preinstall": "npx only-allow pnpm" + }, + "dependencies": { + "axios": "^1.20.0", + "element-plus": "^2.14.5", + "pinia": "^4.0.3", + "vue": "^3.5.42", + "vue-router": "^4.6.4" + }, + "devDependencies": { + "@types/node": "^24.13.3", + "@vitejs/plugin-vue": "^6.0.8", + "@vue/tsconfig": "^0.9.1", + "less": "^4.9.1", + "typescript": "~6.0.2", + "vite": "^8.3.0", + "vue-tsc": "^3.3.11" + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 0000000..8de4b33 --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,1368 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + axios: + specifier: ^1.20.0 + version: 1.20.0 + element-plus: + specifier: ^2.14.5 + version: 2.14.5(vue@3.5.42(typescript@6.0.3)) + pinia: + specifier: ^4.0.3 + version: 4.0.3(@vue/devtools-api@6.6.4)(typescript@6.0.3)(vue@3.5.42(typescript@6.0.3)) + vue: + specifier: ^3.5.42 + version: 3.5.42(typescript@6.0.3) + vue-router: + specifier: ^4.6.4 + version: 4.6.4(vue@3.5.42(typescript@6.0.3)) + devDependencies: + '@types/node': + specifier: ^24.13.3 + version: 24.13.5 + '@vitejs/plugin-vue': + specifier: ^6.0.8 + version: 6.0.9(vite@8.3.0(@types/node@24.13.5)(less@4.9.1))(vue@3.5.42(typescript@6.0.3)) + '@vue/tsconfig': + specifier: ^0.9.1 + version: 0.9.1(typescript@6.0.3)(vue@3.5.42(typescript@6.0.3)) + less: + specifier: ^4.9.1 + version: 4.9.1 + typescript: + specifier: ~6.0.2 + version: 6.0.3 + vite: + specifier: ^8.3.0 + version: 8.3.0(@types/node@24.13.5)(less@4.9.1) + vue-tsc: + specifier: ^3.3.11 + version: 3.3.11(typescript@6.0.3) + +packages: + + '@babel/helper-string-parser@7.29.7': + resolution: {integrity: sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.29.7': + resolution: {integrity: sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.29.8': + resolution: {integrity: sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/types@7.29.8': + resolution: {integrity: sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==} + engines: {node: '>=6.9.0'} + + '@ctrl/tinycolor@4.2.1': + resolution: {integrity: sha512-Vh5uy9Y1JBMId6J1f68TMXnZrTCvNdo9Fu2k0d1YnJngR+sM5kWTKl441c/3TFs+cG0NWOjRWgQ/iaWhbYx+7A==} + engines: {node: '>=14'} + + '@element-plus/icons-vue@2.3.2': + resolution: {integrity: sha512-OzIuTaIfC8QXEPmJvB4Y4kw34rSXdCJzxcD1kFStBvr8bK6X1zQAYDo0CNMjojnfTqRQCJ0I7prlErcoRiET2A==} + peerDependencies: + vue: ^3.2.0 + + '@floating-ui/core@1.8.0': + resolution: {integrity: sha512-0CIZ5itps/8x7BG8dEIhs53BvCUH2PCoogtakwRTut+Arm58sJooJ0AuZhLw2HJYIR5cMLNPBSS728sPho2khQ==} + + '@floating-ui/dom@1.8.0': + resolution: {integrity: sha512-yXSrzeHZBTZadLOlfyhCkJHNeLJnHRnRInwdZ40L7ZiaAtrBwoYlsDrX3v5zB1Utk7CLfzcOVnVVWoXEky7Ceg==} + + '@floating-ui/utils@0.2.12': + resolution: {integrity: sha512-HpCo8tmWzLVad5s2d19EhAz5zqrrQ6s69qd6moPMQvkOuSwDT1YgRfWSVuc4ennqrgv3OHppiOGMQ7oC13yIww==} + + '@jridgewell/sourcemap-codec@1.6.0': + resolution: {integrity: sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==} + + '@oxc-project/types@0.149.0': + resolution: {integrity: sha512-Efcc+iF0j3Bf67YjEqIqWXbX5XddXoK/Mw4K1/JuXwRCZ8N16VR7iT23nlCc9XrveFVh/E5Rqs2StT0V8v9LdA==} + + '@rolldown/binding-android-arm-eabi@1.2.8': + resolution: {integrity: sha512-tN5aztYkKCte4i5SIrrz5yK/HMjEuCqCSCJa418jOV8tZ1cBY3YF2otxB1ktPxzsLA1BeTqwapK0bfjxNvHJVw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [android] + + '@rolldown/binding-android-arm64@1.2.8': + resolution: {integrity: sha512-dIYTWl9XprMUiQFoc55KUyk/oS8SKYH3zFl0LTR7RT0Xj4hgSVyuJcroH8JUu8RcpF8fTB6E0aOwCkZoYPcDSQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + + '@rolldown/binding-darwin-arm64@1.2.8': + resolution: {integrity: sha512-PCSDQGXD2IyTEFrcgPyBM8jJuGmrbCMuoIOXdbEGVemruKACXoLQJrb+A45Z0L5t1RQkdfJprAYPkikbh7dzdA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + + '@rolldown/binding-darwin-x64@1.2.8': + resolution: {integrity: sha512-Uk7lRsGhPFHVX/sAUC6D5H9Ol30dFHd6iquokll2th3LpdJ3F5CzQB+7DHn0Ri2mG+U7k2zXiPHDrwZenXhwSA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + + '@rolldown/binding-freebsd-x64@1.2.8': + resolution: {integrity: sha512-DjszaTEVogPqA5bYzsEeqDCQxbcp2fexQwKcRspYji2yzR68fCf+e4fx6kBSRDwX5/brZaHw/hWS9+A/+/w9sQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + + '@rolldown/binding-linux-arm-gnueabihf@1.2.8': + resolution: {integrity: sha512-zmwa7FTmdzB6aaEEuuls18H6Ap5JmJPSoPTuXixeJZV6tG40SyLkApQtz1g8ptZtiEKqj9OM0oNLPh1AgvE31Q==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@rolldown/binding-linux-arm64-gnu@1.2.8': + resolution: {integrity: sha512-KdYQDPHwJVnbFwdTGMgxsI9SqblBlz6STGM+w1We/d5B8OWWidYH0MwkU/uA1wM5fIpO2MkOVxXrNzzuZhw9ew==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-arm64-musl@1.2.8': + resolution: {integrity: sha512-jFJTifHnNPY+yzOoNZQfSIysrVyXzEQPhPnOUjmD1bcQGHH6s7c8cViKWar8YplQImE5N9JRqMCLrM2CdxOrZA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@rolldown/binding-linux-ppc64-gnu@1.2.8': + resolution: {integrity: sha512-FhiOziBDWPBjbcmRzfLyIJnaP7AVMFXT7YCXPjXxj7wKU3vx24RjrCNN/zjvVa+N2vVoHJwCoUBvsrN/DG3zIA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-s390x-gnu@1.2.8': + resolution: {integrity: sha512-WnHfADMzOV2Y55wlx1hzzQnar/wDt/VdvWSD99r18Mz9ylNieIGOkRx3UV21h7m/eJvjySYJkO26VvGNFkwsIQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-x64-gnu@1.2.8': + resolution: {integrity: sha512-H9tRr5ibfXFVLxbPOseVewewFpl28zcEdjRDt2FTUZU7odxP0gEv1ki4/kGmcGOh78oRwZuuQllGLZ9zTJp84g==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-x64-musl@1.2.8': + resolution: {integrity: sha512-UefiqfM3D6IVNlZ8tSGs9+Ejjud2T+oxO0IHADU45Y+lyEjD2dVFyZHbkfX0LUb5Zugo/oIv1eCO/KVYhgYJYA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [musl] + + '@rolldown/binding-openharmony-arm64@1.2.8': + resolution: {integrity: sha512-637Ke4kWSy6rp9cxQ9gMOXlxPgIw/c1beASV4M//3+9I4uwBVOOl74G+e3zyU3u19U7RkRl/HuewixZ/Z6+Rjg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + + '@rolldown/binding-win32-arm64-msvc@1.2.8': + resolution: {integrity: sha512-xWBkPOF1Q9k/Gv1nQXnVdLxKu74jXppuOM4Z3mnypVUJJJwLsMl7hNJGRAUJoG8A5MgOI1ACKM+wBFxSJzKy4A==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + + '@rolldown/binding-win32-x64-msvc@1.2.8': + resolution: {integrity: sha512-uz2ZvfgXbxqNwijjjbxrnvALwpyODDcgc1T1N8N3rf/DXKQmaFwmB4LX4yyjggpwN2obdQLb2rgirX5ffCWYng==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + + '@rolldown/pluginutils@1.0.1': + resolution: {integrity: sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==} + + '@sxzz/popperjs-es@2.11.8': + resolution: {integrity: sha512-wOwESXvvED3S8xBmcPWHs2dUuzrE4XiZeFu7e1hROIJkm02a49N120pmOXxY33sBb6hArItm5W5tcg1cBtV+HQ==} + + '@types/lodash-es@4.17.12': + resolution: {integrity: sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==} + + '@types/lodash@4.17.25': + resolution: {integrity: sha512-+K1NIO8I+F9/wNulfVvu23QYd0Pe9/OCqRrim4NoYIf1VoEDL90Ve4ClzpyqBLc7NpGGWRvYNCKZ1BE/Jpf8dQ==} + + '@types/node@24.13.5': + resolution: {integrity: sha512-TXyindR+lBr22aJIdMQzCFHPHR6cR4js838mRDCSz5hOKWZvZwsXSSiXDmjRj4iJmgl+sR9O+1mkoVBSMadNug==} + + '@types/web-bluetooth@0.0.21': + resolution: {integrity: sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA==} + + '@vitejs/plugin-vue@6.0.9': + resolution: {integrity: sha512-rD/MORlhaZMlXWW0rEn4FB4wMinWC0z/D7Ye160S5+Rs1mC8ZDAdcDp4SjKfUhG5t8q1ktcPVw4xuTkPRlzrSA==} + engines: {node: ^20.19.0 || >=22.12.0} + peerDependencies: + vite: ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 + vue: ^3.2.25 + + '@volar/language-core@2.4.28': + resolution: {integrity: sha512-w4qhIJ8ZSitgLAkVay6AbcnC7gP3glYM3fYwKV3srj8m494E3xtrCv6E+bWviiK/8hs6e6t1ij1s2Endql7vzQ==} + + '@volar/source-map@2.4.28': + resolution: {integrity: sha512-yX2BDBqJkRXfKw8my8VarTyjv48QwxdJtvRgUpNE5erCsgEUdI2DsLbpa+rOQVAJYshY99szEcRDmyHbF10ggQ==} + + '@volar/typescript@2.4.28': + resolution: {integrity: sha512-Ja6yvWrbis2QtN4ClAKreeUZPVYMARDYZl9LMEv1iQ1QdepB6wn0jTRxA9MftYmYa4DQ4k/DaSZpFPUfxl8giw==} + + '@vue/compiler-core@3.5.42': + resolution: {integrity: sha512-2Ye1ilMtKXxl8qZUrQ5j0CdgenFp/HFQmta6rfRyfEsTG69L6Wk+tWuNoHYHMx9E8tF2Slvdg1FuwDvAXdy1LQ==} + + '@vue/compiler-dom@3.5.42': + resolution: {integrity: sha512-qbhQZEFmycr+ni/qyuccS4sucNN7VAbDfbkvNxWOX2VfgFm90MNs3/UhRNKoPMEIVn0F8gdlYjLPvqxHwHeQOA==} + + '@vue/compiler-sfc@3.5.42': + resolution: {integrity: sha512-fkCAFB4okcAANGMThboWnScp/gzWjU0ZSkVnjTIiplmMDq2uq0tIB3j+xVu4rhv5rvOgBySCysudmbMd6xRRqw==} + + '@vue/compiler-ssr@3.5.42': + resolution: {integrity: sha512-xmLk3wLkbizPAiLyomjgFFosf2ys9b5Ghb+oh/k2tnvipNz8OFrQOiTcWCzyK7MpBp9KkyGtfvgfLUivbmuGYA==} + + '@vue/devtools-api@6.6.4': + resolution: {integrity: sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==} + + '@vue/language-core@3.3.11': + resolution: {integrity: sha512-QJmpliwAVpC/OxubIByPAhNzsQPRc8/gxlN2qnVzVfIMjMDz/9RnXRFoetjz5yEgXVXyp4LqhXq3V53PjmNzFw==} + + '@vue/reactivity@3.5.42': + resolution: {integrity: sha512-TzNNfKpb7hDxbQltwAut8VDQA5YP+BuRlxntHUuRjyKwlMvmAPbs3unhCvieijifY6vFfVBwsS7wG/C7uq+bEQ==} + + '@vue/runtime-core@3.5.42': + resolution: {integrity: sha512-9uACtuHs7vJGkm5Bp3xu4xRDLFTIYy5DgxpToVjqGIAhAEKwQfsaLvKINhM6nFVp6bZPRFGdDqd1g52MqKsotA==} + + '@vue/runtime-dom@3.5.42': + resolution: {integrity: sha512-rsCmhiWLaRxGltLwhlCWyYkFn7WAbKRh0q17eZ1A6Dq6eqc2ACQ61IIryxz0LrsvCzHSilLA9JHovVwM8CNE2g==} + + '@vue/server-renderer@3.5.42': + resolution: {integrity: sha512-2++5dUyYS4gvo7xQXSECUDhB7TS0aOl5SeVfC5qSq1Jgfhjvegw1zqhwTIR3imZ+QYPJQw9gfcFvXGAjGZ7ajQ==} + + '@vue/shared@3.5.42': + resolution: {integrity: sha512-2rPxex1jQf4jvl9MOHl6YaXCPcrNqz/FstMOEh3QWY+/OME9nQTvl9WYeCwhW7AFjaR0SnngZGlp/wkR6rkI6g==} + + '@vue/tsconfig@0.9.1': + resolution: {integrity: sha512-buvjm+9NzLCJL29KY1j1991YYJ5e6275OiK+G4jtmfIb+z4POywbdm0wXusT9adVWqe0xqg70TbI7+mRx4uU9w==} + peerDependencies: + typescript: '>= 5.8' + vue: ^3.4.0 + peerDependenciesMeta: + typescript: + optional: true + vue: + optional: true + + '@vueuse/core@14.4.0': + resolution: {integrity: sha512-X4WHz1HlCzCBoYXesUkifzzWBAcZgXG8Fi5iNPQg/epdzOB3gu8Fawj3hvuwYR1nGcXGnvxwYYcUC/71++svtQ==} + peerDependencies: + vue: ^3.5.0 + + '@vueuse/metadata@14.4.0': + resolution: {integrity: sha512-swx/255R6JyHZFJhx845iz5CRWDZdCfvkZOpACWc5+c5WHcG24mv8gUT1WIdFQaHt6dq79rvILd9QnCWiyVm9g==} + + '@vueuse/shared@14.4.0': + resolution: {integrity: sha512-JRgY90Sz8DDtPMsaDflvPMp9xYk69JZAmbuDvAquUVXKr2gEjqtzGNTTthLfckH0BzBqvnu31gb4a8TGLRe79g==} + peerDependencies: + vue: ^3.5.0 + + agent-base@6.0.2: + resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} + engines: {node: '>= 6.0.0'} + + alien-signals@3.2.1: + resolution: {integrity: sha512-I8FjmltrfnDFoZedi5CG8DghVYNhzb/Ijluz7tCSJH0xpd0484Kowhbb1XDYOxfJpU1p5wnM2X54dA+IfGyD1g==} + + async-validator@4.2.5: + resolution: {integrity: sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg==} + + asynckit@0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + + axios@1.20.0: + resolution: {integrity: sha512-r8aOh8j9cGKpgQAqpzrUHnSIc6a59Y3Xf/cv8sy1DrHCkZHzQGEuoq1tARk6qSyDdtQGSDgpb9kFlruzPvrgwg==} + + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} + + combined-stream@1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} + + copy-anything@3.0.5: + resolution: {integrity: sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w==} + engines: {node: '>=12.13'} + + csstype@3.2.3: + resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + + dayjs@1.11.23: + resolution: {integrity: sha512-QDTCU0M0MxR3hQfnlDJfwekQiaanm1ubOD231u73WBckQ/fsamwRLiE2GBz6D3a/xF1NgfiDLJjXBa1hYOYTtQ==} + + debug@2.6.9: + resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + debug@3.2.7: + resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + delayed-stream@1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} + + detect-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} + engines: {node: '>=8'} + + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + + element-plus@2.14.5: + resolution: {integrity: sha512-bghYy/S+qg87enHPXELirhEdDqsVAUGcGpbGIeG8dz0kwpIkGz7gYsifulBshXX74iRtHib85XWQj0uSH2A1Yg==} + peerDependencies: + vue: ^3.3.7 + + entities@7.0.1: + resolution: {integrity: sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==} + engines: {node: '>=0.12'} + + errno@0.1.8: + resolution: {integrity: sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==} + hasBin: true + + es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + + es-object-atoms@1.1.2: + resolution: {integrity: sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==} + engines: {node: '>= 0.4'} + + es-set-tostringtag@2.1.0: + resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} + engines: {node: '>= 0.4'} + + estree-walker@2.0.2: + resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + follow-redirects@1.16.0: + resolution: {integrity: sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==} + engines: {node: '>=4.0'} + peerDependencies: + debug: '*' + peerDependenciesMeta: + debug: + optional: true + + form-data@4.0.6: + resolution: {integrity: sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==} + engines: {node: '>= 6'} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} + + get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} + + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} + + graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + + has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} + + has-tostringtag@1.0.2: + resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} + engines: {node: '>= 0.4'} + + hasown@2.0.4: + resolution: {integrity: sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==} + engines: {node: '>= 0.4'} + + https-proxy-agent@5.0.1: + resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==} + engines: {node: '>= 6'} + + iconv-lite@0.4.24: + resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} + engines: {node: '>=0.10.0'} + + iconv-lite@0.6.3: + resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} + engines: {node: '>=0.10.0'} + + is-what@4.1.16: + resolution: {integrity: sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A==} + engines: {node: '>=12.13'} + + less@4.9.1: + resolution: {integrity: sha512-orp15PfJvvNDIqJdVWzMI9Sjpjp3VTiw3sfvbB+67LlISTEn8uVT2EdYSuyl02BLvaftv6sdk9Umnxmm5rckmg==} + engines: {node: '>=18'} + hasBin: true + + lightningcss-android-arm64@1.33.0: + resolution: {integrity: sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [android] + + lightningcss-darwin-arm64@1.33.0: + resolution: {integrity: sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [darwin] + + lightningcss-darwin-x64@1.33.0: + resolution: {integrity: sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [darwin] + + lightningcss-freebsd-x64@1.33.0: + resolution: {integrity: sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [freebsd] + + lightningcss-linux-arm-gnueabihf@1.33.0: + resolution: {integrity: sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm] + os: [linux] + + lightningcss-linux-arm64-gnu@1.33.0: + resolution: {integrity: sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + lightningcss-linux-arm64-musl@1.33.0: + resolution: {integrity: sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + libc: [musl] + + lightningcss-linux-x64-gnu@1.33.0: + resolution: {integrity: sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + libc: [glibc] + + lightningcss-linux-x64-musl@1.33.0: + resolution: {integrity: sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + libc: [musl] + + lightningcss-win32-arm64-msvc@1.33.0: + resolution: {integrity: sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [win32] + + lightningcss-win32-x64-msvc@1.33.0: + resolution: {integrity: sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [win32] + + lightningcss@1.33.0: + resolution: {integrity: sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==} + engines: {node: '>= 12.0.0'} + + lodash-es@4.18.1: + resolution: {integrity: sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A==} + + lodash-unified@1.0.3: + resolution: {integrity: sha512-WK9qSozxXOD7ZJQlpSqOT+om2ZfcT4yO+03FuzAHD0wF6S0l0090LRPDx3vhTTLZ8cFKpBn+IOcVXK6qOcIlfQ==} + peerDependencies: + '@types/lodash-es': '*' + lodash: '*' + lodash-es: '*' + + lodash.merge@4.6.2: + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + + lodash@4.18.1: + resolution: {integrity: sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==} + + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + + make-dir@5.1.0: + resolution: {integrity: sha512-IfpFq6UM39dUNiphpA6uDezNx/AvWyhwfICWPR3t1VspkgkMZrL+Rk1RbN1bx+aeNYwOrqGJgEgV3yotk+ZUVw==} + engines: {node: '>=18'} + + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + + memoize-one@6.0.0: + resolution: {integrity: sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==} + + mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + + mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + + mime@1.6.0: + resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==} + engines: {node: '>=4'} + hasBin: true + + ms@2.0.0: + resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + muggle-string@0.4.1: + resolution: {integrity: sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==} + + nanoid@3.3.19: + resolution: {integrity: sha512-Y2tUNy4ouw6tq5oDSKeQYGOyhkUBhNOcGV/02KC+6kd9eDGqdZd++mjMiIDilrBYvjEnCYvVtsuHCuP+okSfug==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + needle@2.9.1: + resolution: {integrity: sha512-6R9fqJ5Zcmf+uYaFgdIHmLwNldn5HbK8L5ybn7Uz+ylX/rnOsSp1AHcvQSrCaFN+qNM1wpymHqD7mVasEOlHGQ==} + engines: {node: '>= 4.4.x'} + hasBin: true + + needle@3.5.0: + resolution: {integrity: sha512-jaQyPKKk2YokHrEg+vFDYxXIHTCBgiZwSHOoVx/8V3GIBS8/VN6NdVRmg8q1ERtPkMvmOvebsgga4sAj5hls/w==} + engines: {node: '>= 4.4.x'} + hasBin: true + + normalize-wheel-es@1.2.0: + resolution: {integrity: sha512-Wj7+EJQ8mSuXr2iWfnujrimU35R2W4FAErEyTmJoJ7ucwTn2hOUSsRehMb5RSYkxXGTM7Y9QpvPmp++w5ftoJw==} + + nostics@1.2.0: + resolution: {integrity: sha512-FGqEfhQjrvo1lL8KFifdTQiNwwQHJxC1jtYE1Rc54qF/jxONUNL+kC9gS1krX8Q65PgrQ5fCqH/I4NhWBvdSqg==} + + parse-node-version@1.0.1: + resolution: {integrity: sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==} + engines: {node: '>= 0.10'} + + path-browserify@1.0.1: + resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@4.0.7: + resolution: {integrity: sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==} + engines: {node: '>=12'} + + pinia@4.0.3: + resolution: {integrity: sha512-XMQqpvjgG7LMqVhhFUzKLT4KEbsYbfZZ0CZU9PgdFm1O2VKBmIkykL8+SfNhOaT7sR0wT6BEgKT0YNDYWgmVRA==} + peerDependencies: + '@vue/devtools-api': ^8.1.5 + typescript: '>=5.6.0' + vue: ^3.5.11 + peerDependenciesMeta: + typescript: + optional: true + + postcss@8.5.28: + resolution: {integrity: sha512-RRuzqDtt5Y9h3quz5hWhK+TPnsmVs6WwSU6LkJMeY4HstUEDuYTG8UJSdawMRzmzAtV+KEoG8N3Qg2qLy5vM/A==} + engines: {node: ^10 || ^12 || >=14} + + probe-image-size@7.4.0: + resolution: {integrity: sha512-cdEprVtZxV+awMde9X+4jILBFYh4CARxVrQaMl4wY4YcPWbul9jntXrIW95NInBDyJwcVUP3U0T6yukN8rMBaQ==} + + proxy-from-env@2.1.0: + resolution: {integrity: sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==} + engines: {node: '>=10'} + + prr@1.0.1: + resolution: {integrity: sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==} + + rolldown@1.2.8: + resolution: {integrity: sha512-Z67nTmhZe7anqnM/EjI392w5i/ANUinjip7QYsOyN37oayduxt3ksdX0hf5OOamkAd53BiIHfbfSzfUmzKFQqQ==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + + sax@1.6.1: + resolution: {integrity: sha512-42tBVwLWnaQvW5zc4HbZrTuWccECCZfBi92FDuwtqxasH+JbPB3/FOKb1m222K42R4WxuxzzMsTswfzgtSu64Q==} + engines: {node: '>=11.0.0'} + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + source-map@0.6.1: + resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} + engines: {node: '>=0.10.0'} + + stream-parser@0.3.1: + resolution: {integrity: sha512-bJ/HgKq41nlKvlhccD5kaCr/P+Hu0wPNKPJOH7en+YrJu/9EgqUF+88w5Jb6KNcjOFMhfX4B2asfeAtIGuHObQ==} + + tinyglobby@0.2.17: + resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} + engines: {node: '>=12.0.0'} + + typescript@6.0.3: + resolution: {integrity: sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==} + engines: {node: '>=14.17'} + hasBin: true + + undici-types@7.18.2: + resolution: {integrity: sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==} + + vite@8.3.0: + resolution: {integrity: sha512-lhZBVvEHefgE+HQZC9O7EBJgCU/nVzFNl7vkS4RE0APtWLP02/8QVIkQtzBxPquh7lq5/78NHipTj7ODQ6XuyQ==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + '@types/node': ^20.19.0 || >=22.12.0 + '@vitejs/devtools': ^0.7.1 + esbuild: ^0.27.0 || ^0.28.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 + peerDependenciesMeta: + '@types/node': + optional: true + '@vitejs/devtools': + optional: true + esbuild: + optional: true + jiti: + optional: true + less: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + vscode-uri@3.2.0: + resolution: {integrity: sha512-m2gXo3bn0G1kT9InzMf07fTbqMbGtyckj3bH5ktLO+1Ssv+yiATZ4dhwaQv9UZWxJh6E9IFGnQyjgWVDWVBDrg==} + + vue-component-type-helpers@3.3.11: + resolution: {integrity: sha512-LwcxzeliO9fkQcpJG0PoX8X5kmAhKmH9wkpDLxNabwzkQ9Zeib2YVHwFV4pcWmMLfXVfjr/dSV+DaJ3cIPgSNA==} + + vue-router@4.6.4: + resolution: {integrity: sha512-Hz9q5sa33Yhduglwz6g9skT8OBPii+4bFn88w6J+J4MfEo4KRRpmiNG/hHHkdbRFlLBOqxN8y8gf2Fb0MTUgVg==} + peerDependencies: + vue: ^3.5.0 + + vue-tsc@3.3.11: + resolution: {integrity: sha512-gOb0B9rtU2+f1dszwPqSH5kAieIF9ReeLhD3kSRNHv5WZZUQz/JdVXW0RTdqhNTMlQkqKzrTTviqKr/4FYZraQ==} + hasBin: true + peerDependencies: + typescript: '>=5.0.0' + + vue@3.5.42: + resolution: {integrity: sha512-4RyHQTbQvOPs3MfvUO1Sg0YRrKNnA0mAVtvpd12Tg1fKDN7OHBUl1IqSn8zGJjK9nI3NkNp8cgTpVrSZC5TTcA==} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + +snapshots: + + '@babel/helper-string-parser@7.29.7': {} + + '@babel/helper-validator-identifier@7.29.7': {} + + '@babel/parser@7.29.8': + dependencies: + '@babel/types': 7.29.8 + + '@babel/types@7.29.8': + dependencies: + '@babel/helper-string-parser': 7.29.7 + '@babel/helper-validator-identifier': 7.29.7 + + '@ctrl/tinycolor@4.2.1': {} + + '@element-plus/icons-vue@2.3.2(vue@3.5.42(typescript@6.0.3))': + dependencies: + vue: 3.5.42(typescript@6.0.3) + + '@floating-ui/core@1.8.0': + dependencies: + '@floating-ui/utils': 0.2.12 + + '@floating-ui/dom@1.8.0': + dependencies: + '@floating-ui/core': 1.8.0 + '@floating-ui/utils': 0.2.12 + + '@floating-ui/utils@0.2.12': {} + + '@jridgewell/sourcemap-codec@1.6.0': {} + + '@oxc-project/types@0.149.0': {} + + '@rolldown/binding-android-arm-eabi@1.2.8': + optional: true + + '@rolldown/binding-android-arm64@1.2.8': + optional: true + + '@rolldown/binding-darwin-arm64@1.2.8': + optional: true + + '@rolldown/binding-darwin-x64@1.2.8': + optional: true + + '@rolldown/binding-freebsd-x64@1.2.8': + optional: true + + '@rolldown/binding-linux-arm-gnueabihf@1.2.8': + optional: true + + '@rolldown/binding-linux-arm64-gnu@1.2.8': + optional: true + + '@rolldown/binding-linux-arm64-musl@1.2.8': + optional: true + + '@rolldown/binding-linux-ppc64-gnu@1.2.8': + optional: true + + '@rolldown/binding-linux-s390x-gnu@1.2.8': + optional: true + + '@rolldown/binding-linux-x64-gnu@1.2.8': + optional: true + + '@rolldown/binding-linux-x64-musl@1.2.8': + optional: true + + '@rolldown/binding-openharmony-arm64@1.2.8': + optional: true + + '@rolldown/binding-win32-arm64-msvc@1.2.8': + optional: true + + '@rolldown/binding-win32-x64-msvc@1.2.8': + optional: true + + '@rolldown/pluginutils@1.0.1': {} + + '@sxzz/popperjs-es@2.11.8': {} + + '@types/lodash-es@4.17.12': + dependencies: + '@types/lodash': 4.17.25 + + '@types/lodash@4.17.25': {} + + '@types/node@24.13.5': + dependencies: + undici-types: 7.18.2 + + '@types/web-bluetooth@0.0.21': {} + + '@vitejs/plugin-vue@6.0.9(vite@8.3.0(@types/node@24.13.5)(less@4.9.1))(vue@3.5.42(typescript@6.0.3))': + dependencies: + '@rolldown/pluginutils': 1.0.1 + vite: 8.3.0(@types/node@24.13.5)(less@4.9.1) + vue: 3.5.42(typescript@6.0.3) + + '@volar/language-core@2.4.28': + dependencies: + '@volar/source-map': 2.4.28 + + '@volar/source-map@2.4.28': {} + + '@volar/typescript@2.4.28': + dependencies: + '@volar/language-core': 2.4.28 + path-browserify: 1.0.1 + vscode-uri: 3.2.0 + + '@vue/compiler-core@3.5.42': + dependencies: + '@babel/parser': 7.29.8 + '@vue/shared': 3.5.42 + entities: 7.0.1 + estree-walker: 2.0.2 + source-map-js: 1.2.1 + + '@vue/compiler-dom@3.5.42': + dependencies: + '@vue/compiler-core': 3.5.42 + '@vue/shared': 3.5.42 + + '@vue/compiler-sfc@3.5.42': + dependencies: + '@babel/parser': 7.29.8 + '@vue/compiler-core': 3.5.42 + '@vue/compiler-dom': 3.5.42 + '@vue/compiler-ssr': 3.5.42 + '@vue/shared': 3.5.42 + estree-walker: 2.0.2 + magic-string: 0.30.21 + postcss: 8.5.28 + source-map-js: 1.2.1 + + '@vue/compiler-ssr@3.5.42': + dependencies: + '@vue/compiler-dom': 3.5.42 + '@vue/shared': 3.5.42 + + '@vue/devtools-api@6.6.4': {} + + '@vue/language-core@3.3.11': + dependencies: + '@volar/language-core': 2.4.28 + '@vue/compiler-dom': 3.5.42 + '@vue/shared': 3.5.42 + alien-signals: 3.2.1 + muggle-string: 0.4.1 + path-browserify: 1.0.1 + picomatch: 4.0.7 + + '@vue/reactivity@3.5.42': + dependencies: + '@vue/shared': 3.5.42 + + '@vue/runtime-core@3.5.42': + dependencies: + '@vue/reactivity': 3.5.42 + '@vue/shared': 3.5.42 + + '@vue/runtime-dom@3.5.42': + dependencies: + '@vue/reactivity': 3.5.42 + '@vue/runtime-core': 3.5.42 + '@vue/shared': 3.5.42 + csstype: 3.2.3 + + '@vue/server-renderer@3.5.42': + dependencies: + '@vue/compiler-ssr': 3.5.42 + '@vue/runtime-dom': 3.5.42 + '@vue/shared': 3.5.42 + + '@vue/shared@3.5.42': {} + + '@vue/tsconfig@0.9.1(typescript@6.0.3)(vue@3.5.42(typescript@6.0.3))': + optionalDependencies: + typescript: 6.0.3 + vue: 3.5.42(typescript@6.0.3) + + '@vueuse/core@14.4.0(vue@3.5.42(typescript@6.0.3))': + dependencies: + '@types/web-bluetooth': 0.0.21 + '@vueuse/metadata': 14.4.0 + '@vueuse/shared': 14.4.0(vue@3.5.42(typescript@6.0.3)) + vue: 3.5.42(typescript@6.0.3) + + '@vueuse/metadata@14.4.0': {} + + '@vueuse/shared@14.4.0(vue@3.5.42(typescript@6.0.3))': + dependencies: + vue: 3.5.42(typescript@6.0.3) + + agent-base@6.0.2: + dependencies: + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + alien-signals@3.2.1: {} + + async-validator@4.2.5: {} + + asynckit@0.4.0: {} + + axios@1.20.0: + dependencies: + follow-redirects: 1.16.0 + form-data: 4.0.6 + https-proxy-agent: 5.0.1 + proxy-from-env: 2.1.0 + transitivePeerDependencies: + - debug + - supports-color + + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + + combined-stream@1.0.8: + dependencies: + delayed-stream: 1.0.0 + + copy-anything@3.0.5: + dependencies: + is-what: 4.1.16 + + csstype@3.2.3: {} + + dayjs@1.11.23: {} + + debug@2.6.9: + dependencies: + ms: 2.0.0 + optional: true + + debug@3.2.7: + dependencies: + ms: 2.1.3 + optional: true + + debug@4.4.3: + dependencies: + ms: 2.1.3 + + delayed-stream@1.0.0: {} + + detect-libc@2.1.2: {} + + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + + element-plus@2.14.5(vue@3.5.42(typescript@6.0.3)): + dependencies: + '@ctrl/tinycolor': 4.2.1 + '@element-plus/icons-vue': 2.3.2(vue@3.5.42(typescript@6.0.3)) + '@floating-ui/dom': 1.8.0 + '@popperjs/core': '@sxzz/popperjs-es@2.11.8' + '@types/lodash': 4.17.25 + '@types/lodash-es': 4.17.12 + '@vueuse/core': 14.4.0(vue@3.5.42(typescript@6.0.3)) + async-validator: 4.2.5 + dayjs: 1.11.23 + lodash: 4.18.1 + lodash-es: 4.18.1 + lodash-unified: 1.0.3(@types/lodash-es@4.17.12)(lodash-es@4.18.1)(lodash@4.18.1) + memoize-one: 6.0.0 + normalize-wheel-es: 1.2.0 + vue: 3.5.42(typescript@6.0.3) + vue-component-type-helpers: 3.3.11 + + entities@7.0.1: {} + + errno@0.1.8: + dependencies: + prr: 1.0.1 + optional: true + + es-define-property@1.0.1: {} + + es-errors@1.3.0: {} + + es-object-atoms@1.1.2: + dependencies: + es-errors: 1.3.0 + + es-set-tostringtag@2.1.0: + dependencies: + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + has-tostringtag: 1.0.2 + hasown: 2.0.4 + + estree-walker@2.0.2: {} + + fdir@6.5.0(picomatch@4.0.7): + optionalDependencies: + picomatch: 4.0.7 + + follow-redirects@1.16.0: {} + + form-data@4.0.6: + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + es-set-tostringtag: 2.1.0 + hasown: 2.0.4 + mime-types: 2.1.35 + + fsevents@2.3.3: + optional: true + + function-bind@1.1.2: {} + + get-intrinsic@1.3.0: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.2 + function-bind: 1.1.2 + get-proto: 1.0.1 + gopd: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.4 + math-intrinsics: 1.1.0 + + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.2 + + gopd@1.2.0: {} + + graceful-fs@4.2.11: + optional: true + + has-symbols@1.1.0: {} + + has-tostringtag@1.0.2: + dependencies: + has-symbols: 1.1.0 + + hasown@2.0.4: + dependencies: + function-bind: 1.1.2 + + https-proxy-agent@5.0.1: + dependencies: + agent-base: 6.0.2 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + iconv-lite@0.4.24: + dependencies: + safer-buffer: 2.1.2 + optional: true + + iconv-lite@0.6.3: + dependencies: + safer-buffer: 2.1.2 + optional: true + + is-what@4.1.16: {} + + less@4.9.1: + dependencies: + copy-anything: 3.0.5 + parse-node-version: 1.0.1 + optionalDependencies: + errno: 0.1.8 + graceful-fs: 4.2.11 + make-dir: 5.1.0 + mime: 1.6.0 + needle: 3.5.0 + probe-image-size: 7.4.0 + source-map: 0.6.1 + transitivePeerDependencies: + - supports-color + + lightningcss-android-arm64@1.33.0: + optional: true + + lightningcss-darwin-arm64@1.33.0: + optional: true + + lightningcss-darwin-x64@1.33.0: + optional: true + + lightningcss-freebsd-x64@1.33.0: + optional: true + + lightningcss-linux-arm-gnueabihf@1.33.0: + optional: true + + lightningcss-linux-arm64-gnu@1.33.0: + optional: true + + lightningcss-linux-arm64-musl@1.33.0: + optional: true + + lightningcss-linux-x64-gnu@1.33.0: + optional: true + + lightningcss-linux-x64-musl@1.33.0: + optional: true + + lightningcss-win32-arm64-msvc@1.33.0: + optional: true + + lightningcss-win32-x64-msvc@1.33.0: + optional: true + + lightningcss@1.33.0: + dependencies: + detect-libc: 2.1.2 + optionalDependencies: + lightningcss-android-arm64: 1.33.0 + lightningcss-darwin-arm64: 1.33.0 + lightningcss-darwin-x64: 1.33.0 + lightningcss-freebsd-x64: 1.33.0 + lightningcss-linux-arm-gnueabihf: 1.33.0 + lightningcss-linux-arm64-gnu: 1.33.0 + lightningcss-linux-arm64-musl: 1.33.0 + lightningcss-linux-x64-gnu: 1.33.0 + lightningcss-linux-x64-musl: 1.33.0 + lightningcss-win32-arm64-msvc: 1.33.0 + lightningcss-win32-x64-msvc: 1.33.0 + + lodash-es@4.18.1: {} + + lodash-unified@1.0.3(@types/lodash-es@4.17.12)(lodash-es@4.18.1)(lodash@4.18.1): + dependencies: + '@types/lodash-es': 4.17.12 + lodash: 4.18.1 + lodash-es: 4.18.1 + + lodash.merge@4.6.2: + optional: true + + lodash@4.18.1: {} + + magic-string@0.30.21: + dependencies: + '@jridgewell/sourcemap-codec': 1.6.0 + + make-dir@5.1.0: + optional: true + + math-intrinsics@1.1.0: {} + + memoize-one@6.0.0: {} + + mime-db@1.52.0: {} + + mime-types@2.1.35: + dependencies: + mime-db: 1.52.0 + + mime@1.6.0: + optional: true + + ms@2.0.0: + optional: true + + ms@2.1.3: {} + + muggle-string@0.4.1: {} + + nanoid@3.3.19: {} + + needle@2.9.1: + dependencies: + debug: 3.2.7 + iconv-lite: 0.4.24 + sax: 1.6.1 + transitivePeerDependencies: + - supports-color + optional: true + + needle@3.5.0: + dependencies: + iconv-lite: 0.6.3 + sax: 1.6.1 + optional: true + + normalize-wheel-es@1.2.0: {} + + nostics@1.2.0: {} + + parse-node-version@1.0.1: {} + + path-browserify@1.0.1: {} + + picocolors@1.1.1: {} + + picomatch@4.0.7: {} + + pinia@4.0.3(@vue/devtools-api@6.6.4)(typescript@6.0.3)(vue@3.5.42(typescript@6.0.3)): + dependencies: + '@vue/devtools-api': 6.6.4 + nostics: 1.2.0 + vue: 3.5.42(typescript@6.0.3) + optionalDependencies: + typescript: 6.0.3 + + postcss@8.5.28: + dependencies: + nanoid: 3.3.19 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + probe-image-size@7.4.0: + dependencies: + lodash.merge: 4.6.2 + needle: 2.9.1 + stream-parser: 0.3.1 + transitivePeerDependencies: + - supports-color + optional: true + + proxy-from-env@2.1.0: {} + + prr@1.0.1: + optional: true + + rolldown@1.2.8: + dependencies: + '@oxc-project/types': 0.149.0 + '@rolldown/pluginutils': 1.0.1 + optionalDependencies: + '@rolldown/binding-android-arm-eabi': 1.2.8 + '@rolldown/binding-android-arm64': 1.2.8 + '@rolldown/binding-darwin-arm64': 1.2.8 + '@rolldown/binding-darwin-x64': 1.2.8 + '@rolldown/binding-freebsd-x64': 1.2.8 + '@rolldown/binding-linux-arm-gnueabihf': 1.2.8 + '@rolldown/binding-linux-arm64-gnu': 1.2.8 + '@rolldown/binding-linux-arm64-musl': 1.2.8 + '@rolldown/binding-linux-ppc64-gnu': 1.2.8 + '@rolldown/binding-linux-s390x-gnu': 1.2.8 + '@rolldown/binding-linux-x64-gnu': 1.2.8 + '@rolldown/binding-linux-x64-musl': 1.2.8 + '@rolldown/binding-openharmony-arm64': 1.2.8 + '@rolldown/binding-win32-arm64-msvc': 1.2.8 + '@rolldown/binding-win32-x64-msvc': 1.2.8 + + safer-buffer@2.1.2: + optional: true + + sax@1.6.1: + optional: true + + source-map-js@1.2.1: {} + + source-map@0.6.1: + optional: true + + stream-parser@0.3.1: + dependencies: + debug: 2.6.9 + transitivePeerDependencies: + - supports-color + optional: true + + tinyglobby@0.2.17: + dependencies: + fdir: 6.5.0(picomatch@4.0.7) + picomatch: 4.0.7 + + typescript@6.0.3: {} + + undici-types@7.18.2: {} + + vite@8.3.0(@types/node@24.13.5)(less@4.9.1): + dependencies: + lightningcss: 1.33.0 + picomatch: 4.0.7 + postcss: 8.5.28 + rolldown: 1.2.8 + tinyglobby: 0.2.17 + optionalDependencies: + '@types/node': 24.13.5 + fsevents: 2.3.3 + less: 4.9.1 + + vscode-uri@3.2.0: {} + + vue-component-type-helpers@3.3.11: {} + + vue-router@4.6.4(vue@3.5.42(typescript@6.0.3)): + dependencies: + '@vue/devtools-api': 6.6.4 + vue: 3.5.42(typescript@6.0.3) + + vue-tsc@3.3.11(typescript@6.0.3): + dependencies: + '@volar/typescript': 2.4.28 + '@vue/language-core': 3.3.11 + typescript: 6.0.3 + + vue@3.5.42(typescript@6.0.3): + dependencies: + '@vue/compiler-dom': 3.5.42 + '@vue/compiler-sfc': 3.5.42 + '@vue/runtime-dom': 3.5.42 + '@vue/server-renderer': 3.5.42 + '@vue/shared': 3.5.42 + optionalDependencies: + typescript: 6.0.3 diff --git a/public/favicon.svg b/public/favicon.svg new file mode 100644 index 0000000..6893eb1 --- /dev/null +++ b/public/favicon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/icons.svg b/public/icons.svg new file mode 100644 index 0000000..e952219 --- /dev/null +++ b/public/icons.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/App.vue b/src/App.vue new file mode 100644 index 0000000..98240ae --- /dev/null +++ b/src/App.vue @@ -0,0 +1,3 @@ + diff --git a/src/api/auth.ts b/src/api/auth.ts new file mode 100644 index 0000000..b7b1267 --- /dev/null +++ b/src/api/auth.ts @@ -0,0 +1,96 @@ +import type { + LoginByAccountParams, + LoginBySmsParams, + LoginResult, + RegisterParams, + ResetPasswordParams, + User, +} from '@/types/business/user' +import { isPhone, isValidPassword } from '@/utils/validate' + +// TODO(api): 待接口文档到位后,将以下 mock 替换为 request 调用(页面 → store → api 数据流不变) + +const SIX_DIGIT_CODE = /^\d{6}$/ + +/** 发送短信验证码 */ +export function sendSmsCode(phone: string): Promise { + if (!isPhone(phone)) return Promise.reject(new Error('手机号格式不正确')) + return Promise.resolve() +} + +/** 账号密码登录(mock:demo 账号可登录,其余提示先注册) */ +export function loginByAccount(params: LoginByAccountParams): Promise { + const { account, password } = params + if (!account.trim() || !password) { + return Promise.reject(new Error('请输入账号和密码')) + } + if (account === 'demo') { + const user: User = { + id: 'u1', + nickname: '王永志', + avatar: '', + phone: '186****618', + email: 'yongzh******4@163.com', + passwordModifiedAt: '', + } + return Promise.resolve({ token: `mock-token-${Date.now()}`, user }) + } + return Promise.reject(new Error('登录失败,请先注册账号')) +} + +/** 手机验证码登录(mock:验证码须为 6 位数字) */ +export function loginBySms(params: LoginBySmsParams): Promise { + const { phone, code } = params + if (!isPhone(phone)) return Promise.reject(new Error('手机号格式不正确')) + if (!SIX_DIGIT_CODE.test(code)) return Promise.reject(new Error('验证码格式不正确,应为6位数字')) + const user: User = { + id: 'u1', + nickname: '王永志', + avatar: '', + phone: '186****618', + email: 'yongzh******4@163.com', + passwordModifiedAt: '', + } + return Promise.resolve({ token: `mock-token-${Date.now()}`, user }) +} + +function validateAuthForm( + phone: string, + code: string, + password: string, + confirmPassword: string, + agree: boolean, +): string | null { + if (!isPhone(phone)) return '手机号格式不正确' + if (!SIX_DIGIT_CODE.test(code)) return '验证码格式不正确,应为6位数字' + if (!isValidPassword(password)) return '密码长度应为6-20位' + if (password !== confirmPassword) return '两次输入的密码不一致' + if (!agree) return '请先阅读并同意《用户协议》和《隐私政策》' + return null +} + +/** 注册 */ +export function register(params: RegisterParams): Promise { + const message = validateAuthForm( + params.phone, + params.code, + params.password, + params.confirmPassword, + params.agree, + ) + if (message) return Promise.reject(new Error(message)) + return Promise.resolve() +} + +/** 重置密码 */ +export function resetPassword(params: ResetPasswordParams): Promise { + const message = validateAuthForm( + params.phone, + params.code, + params.password, + params.confirmPassword, + params.agree, + ) + if (message) return Promise.reject(new Error(message)) + return Promise.resolve() +} diff --git a/src/api/cases.ts b/src/api/cases.ts new file mode 100644 index 0000000..a38f337 --- /dev/null +++ b/src/api/cases.ts @@ -0,0 +1,227 @@ +import type { BusinessType, PageResult } from '@/types/api' +import type { Case, CaseQuery, ScaleRange } from '@/types/business/cases' + +// TODO(api): 待接口文档到位后,将以下 mock 替换为 request 调用 + +/** 案例 mock(可变,收藏状态在前端内存中翻转演示) */ +const MOCK_CASES: Case[] = [ + { + id: 'case-001', + title: '自动化生产线升级改造项目', + category: 'manufacturing', + cover: '', + finishDate: '2026/8/10', + teamSize: 50, + scale: '300万', + industry: '自动化', + duration: '150个工作日', + clientType: '企业', + background: '传统产线效率低、人工依赖高,客户需要通过自动化升级提升产能与品控水平。', + painPoints: '1、产线依赖人工巡检,异常响应慢;2、设备数据孤岛,缺乏统一监控;3、工艺参数无法追溯。', + requirements: '建设产线数据采集与监控平台,完成关键工位自动化改造,打通 MES 系统接口。', + deliverables: '1、产线自动化改造方案;2、PLC 控制程序与上位机软件;3、数据看板与运维手册;4、培训与质保服务。', + video: '', + isFavorite: false, + }, + { + id: 'case-002', + title: '智能工厂物联网整体解决方案', + category: 'iot', + cover: '', + finishDate: '2026/8/10', + teamSize: 30, + scale: '500万', + industry: '工厂服务', + duration: '200个工作日', + clientType: '企业', + background: '园区工厂多系统独立运行,能耗与设备管理粗放,需要整体数字化升级。', + painPoints: '1、门禁/监控/能耗子系统数据不互通;2、能源浪费缺乏精细监测;3、决策缺少实时数据支撑。', + requirements: '构建 IoT 平台接入设备数据,建设综合驾驶舱,实现能耗监测与智能告警。', + deliverables: '1、IoT 接入平台;2、综合驾驶舱大屏;3、能耗分析系统;4、集成联调与培训服务。', + video: '', + isFavorite: false, + }, + { + id: 'case-003', + title: '智能穿戴设备PCB设计与量产', + category: 'hardware', + cover: '', + finishDate: '2026/8/10', + teamSize: 50, + scale: '10-50万', + industry: '智能设备', + duration: '30个工作日', + clientType: '企业', + background: '客户推出新一代智能穿戴产品,需要高集成度低功耗 PCB 设计并快速量产。', + painPoints: '1、板载空间受限,器件布局密集;2、功耗要求严格;3、量产良率需爬坡。', + requirements: '完成原理图与 PCB 设计、打样验证、SMT 贴片与整机装配量产。', + deliverables: '1、原理图与 PCB 设计文件;2、样板与测试报告;3、量产良率报告;4、BOM 与工艺文件。', + video: '', + isFavorite: false, + }, + { + id: 'case-004', + title: '智能园区综合管理平台开发', + category: 'internet', + cover: '', + finishDate: '2026/8/10', + teamSize: 20, + scale: '200万', + industry: '园区服务', + duration: '200个工作日', + clientType: '企业', + background: '传统园区管理普遍"数据孤岛",安防、能耗、物业、招商等系统独立运行,缺乏联动,管理手段依赖人工巡检和纸质流程。', + painPoints: '1、运营效率低下:安保依赖人工巡逻;2、系统孤岛严重:门禁、监控、停车、能耗等子系统独立建设;3、能源浪费突出;4、服务体验欠佳;5、决策缺乏依据;6、安全隐患犹存。', + requirements: '打造集"感知、预警、分析、决策、服务"于一体的智能园区综合管理平台,打通园区各子系统壁垒,实现全场景数字化覆盖。', + deliverables: '1、Web 端管理平台(驾驶舱/安防/能源/物业);2、移动端服务门户;3、IoT 平台与数据大屏;4、全套技术文档;5、培训与不少于1年免费质保。', + video: '', + isFavorite: false, + }, + { + id: 'case-005', + title: '电商中台系统定制开发', + category: 'internet', + cover: '', + finishDate: '2026/7/20', + teamSize: 15, + scale: '150万', + industry: '电商零售', + duration: '120个工作日', + clientType: '企业', + background: '品牌方多渠道订单分散,库存与会员数据不通,需要统一电商中台。', + painPoints: '1、多平台库存不同步;2、会员体系割裂;3、营销活动上线周期长。', + requirements: '建设商品/库存/订单/会员中台,开放 API 对接主流电商平台。', + deliverables: '1、中台系统与管理后台;2、开放 API 与对接文档;3、数据迁移方案;4、上线陪跑服务。', + video: '', + isFavorite: false, + }, + { + id: 'case-006', + title: '教培机构在线学习平台', + category: 'internet', + cover: '', + finishDate: '2026/6/30', + teamSize: 12, + scale: '80万', + industry: '教育培训', + duration: '90个工作日', + clientType: '企业', + background: '教培机构需要线上授课、作业批改与学员管理一体化能力。', + painPoints: '1、授课依赖第三方工具,数据不沉淀;2、续费转化缺少数据支撑。', + requirements: '交付直播授课、题库作业、班级管理、数据分析完整平台(Web + 小程序)。', + deliverables: '1、Web 学习平台;2、学员端小程序;3、题库与直播模块;4、运维部署手册。', + video: '', + isFavorite: false, + }, + { + id: 'case-007', + title: '医院智能随访系统开发', + category: 'internet', + cover: '', + finishDate: '2026/5/18', + teamSize: 18, + scale: '250万', + industry: '医疗健康', + duration: '180个工作日', + clientType: '企业', + background: '三甲医院需要对出院患者进行结构化随访,提升复诊率与科研数据质量。', + painPoints: '1、电话随访效率低;2、随访数据非结构化,科研难复用。', + requirements: '建设随访计划引擎、患者端触达与随访数据仓,对接 HIS 系统。', + deliverables: '1、随访管理平台;2、患者端小程序;3、HIS 对接中间件;4、随访报表与数据字典。', + video: '', + isFavorite: false, + }, + { + id: 'case-008', + title: '园区光伏能耗管理系统', + category: 'iot', + cover: '', + finishDate: '2026/4/25', + teamSize: 10, + scale: '120万', + industry: '能源环保', + duration: '60个工作日', + clientType: '企业', + background: '园区部署屋顶光伏后需要实时监控发电与用电,实现削峰填谷。', + painPoints: '1、发电数据分散在多品牌逆变器;2、能耗缺少分项计量。', + requirements: '接入逆变器/电表数据,建设发电与能耗看板,输出节能策略。', + deliverables: '1、能耗采集网关适配;2、监控看板系统;3、告警与报表;4、验收测试报告。', + video: '', + isFavorite: false, + }, +] + +const SCALE_BOUNDS: Record = { + lt50w: [0, 50], + '50to100w': [50, 100], + '100to300w': [100, 300], + '300to500w': [300, 500], + gt500w: [500, Number.POSITIVE_INFINITY], +} + +/** 从展示值解析规模数值(万),如 '300万' → 300,'10-50万' → 10 */ +function parseScaleValue(scale: string): number { + const value = Number.parseFloat(scale) + return Number.isNaN(value) ? 0 : value +} + +function matchScale(item: Case, selected: ScaleRange[]): boolean { + if (selected.length === 0) return true + const value = parseScaleValue(item.scale) + return selected.some((key) => { + const [min, max] = SCALE_BOUNDS[key] + return value >= min && value < max + }) +} + +function matchDate(item: Case, range: [string, string] | null): boolean { + if (!range) return true + const time = new Date(item.finishDate).getTime() + const start = new Date(range[0]).getTime() + const end = new Date(range[1]).getTime() + return time >= start && time <= end +} + +function applyFilters(query: CaseQuery): Case[] { + return MOCK_CASES.filter((item) => { + if (query.category !== 'all' && item.category !== query.category) return false + if (query.industries.length > 0 && !query.industries.includes(item.industry)) return false + if (!matchScale(item, query.scales)) return false + if (!matchDate(item, query.dateRange)) return false + if (query.keyword && !item.title.includes(query.keyword)) return false + return true + }) +} + +/** 案例列表(筛选 + 分页) */ +export function getCaseList(query: CaseQuery): Promise> { + const filtered = applyFilters(query) + const start = (query.page - 1) * query.pageSize + return Promise.resolve({ + list: filtered.slice(start, start + query.pageSize), + total: filtered.length, + }) +} + +/** 案例详情(不存在返回 null) */ +export function getCaseDetail(id: string): Promise { + const found = MOCK_CASES.find((item) => item.id === id) ?? null + return Promise.resolve(found) +} + +/** 收藏/取消收藏,返回最新案例数据 */ +export function toggleFavorite(id: string): Promise { + const found = MOCK_CASES.find((item) => item.id === id) + if (!found) return Promise.reject(new Error('案例不存在')) + found.isFavorite = !found.isFavorite + return Promise.resolve({ ...found }) +} + +/** 业务类型计数(筛选面板角标) */ +export function getCaseCounts(): Promise>> { + const counts: Partial> = {} + for (const item of MOCK_CASES) { + counts[item.category] = (counts[item.category] ?? 0) + 1 + } + return Promise.resolve(counts) +} diff --git a/src/api/contract.ts b/src/api/contract.ts new file mode 100644 index 0000000..8ba4472 --- /dev/null +++ b/src/api/contract.ts @@ -0,0 +1,110 @@ +import type { FileInfo, PageResult } from '@/types/api' +import type { Contract, ContractQuery } from '@/types/business/contract' + +// TODO(api): 待接口文档到位后,将以下 mock 替换为 request 调用 + +/** 构造 mock 文件信息 */ +function mockFile(name: string, size: number, uploadTime: string): FileInfo { + return { name, size, uploadTime, url: '' } +} + +/** 详情 mock 所属合同 id */ +const DETAIL_ID = 'contract-001' + +/** 合同列表 mock(3 条,覆盖全部签署状态) */ +const MOCK_CONTRACTS: Contract[] = [ + { + id: 'contract-001', + contractNo: 'C8K2M202408001', + contractName: '智慧城市建设项目技术服务合同', + contractType: '互联网软件通用/app开发', + contractAmount: 201000, + relatedProjectName: '智慧城市大数据平台建设项目', + projectNo: 'P8K2M202408001', + signStatus: '履行中', + signDate: '2026-8-17 14:00', + effectiveDate: '2026-8-17 14:00', + expiryDate: '2026-8-17 14:00', + files: [mockFile('项目需求规格说明书.pdf', 2516582, '2026-8-17 14:00')], + }, + { + id: 'contract-002', + contractNo: 'C8K2M202408002', + contractName: '智慧城市建设项目硬件电路设计合同', + contractType: '硬件电路通用/电路版设计', + contractAmount: 180000, + relatedProjectName: '三甲医院指挥医疗信息化系统', + projectNo: 'P8K2M202408001', + signStatus: '已过期', + signDate: '2026-8-17 14:00', + effectiveDate: '2026-8-17 14:00', + expiryDate: '2026-8-17 14:00', + files: [mockFile('硬件电路设计合同.pdf', 1468006, '2026-8-17 14:00')], + }, + { + id: 'contract-003', + contractNo: 'C8K2M202408003', + contractName: '智慧城市建设项目物联网全案合同', + contractType: '物联网全案通用/一套门禁卡', + contractAmount: 96000, + relatedProjectName: '社区康养智能服务系统', + projectNo: 'P8K2M202408001', + signStatus: '待签署', + effectiveDate: '2026-8-17 14:00', + expiryDate: '2026-8-17 14:00', + files: [mockFile('物联网全案合同.pdf', 1258291, '2026-8-17 14:00')], + }, +] + +/** 合同详情 mock */ +const CONTRACT_DETAIL: Contract = { + id: DETAIL_ID, + contractNo: 'C8K2M202408001', + contractName: '社区康养智能服务系统开发合同', + contractType: '互联网软件开发', + contractAmount: 201000, + relatedProjectName: '社区康养智能服务系统', + projectNo: '20260813ZZ251245', + signStatus: '履行中', + signDate: '2024-10-15', + effectiveDate: '2024-10-15', + expiryDate: '2024-10-15', + files: [mockFile('项目需求规格说明书.pdf', 2516582, '2024-10-15')], +} + +/** 合同列表(keyword 筛选 + 分页) */ +export function getContractList(query: ContractQuery): Promise> { + const filtered = MOCK_CONTRACTS.filter((item) => { + if ( + query.keyword + && !item.contractName.includes(query.keyword) + && !item.contractNo.includes(query.keyword) + ) { + return false + } + return true + }) + const pageSize = query.pageSize > 0 ? query.pageSize : 8 + const start = (query.page - 1) * pageSize + return Promise.resolve({ + list: filtered.slice(start, start + pageSize), + total: filtered.length, + }) +} + +/** 合同详情(不存在返回 null) */ +export function getContractDetail(id: string): Promise { + if (id === DETAIL_ID) return Promise.resolve({ ...CONTRACT_DETAIL, files: [...CONTRACT_DETAIL.files] }) + const found = MOCK_CONTRACTS.find((item) => item.id === id) ?? null + return Promise.resolve(found ? { ...found, files: [...found.files] } : null) +} + +/** 获取签署二维码(返回有效期) */ +export function getSignQr(_id: string): Promise<{ expiresInSeconds: number }> { + return Promise.resolve({ expiresInSeconds: 272 }) +} + +/** 刷新签署二维码(返回有效期) */ +export function refreshSignQr(_id: string): Promise<{ expiresInSeconds: number }> { + return Promise.resolve({ expiresInSeconds: 272 }) +} diff --git a/src/api/demand.ts b/src/api/demand.ts new file mode 100644 index 0000000..f35d9f2 --- /dev/null +++ b/src/api/demand.ts @@ -0,0 +1,25 @@ +import type { Demand } from '@/types/business/demand' +import { storage } from '@/utils/storage' + +// TODO(api): 待接口文档到位后,将以下 mock 替换为 request 调用(页面 → store → api 数据流不变) + +const DRAFT_KEY = 'demand-draft' + +/** 提交需求(mock:返回提报单 id) */ +export function submitDemand(demand: Demand): Promise<{ id: string }> { + if (!demand.businessType || !demand.projectName || !demand.coreDescription) { + return Promise.reject(new Error('需求信息不完整,无法提交')) + } + return Promise.resolve({ id: `demand-${Date.now()}` }) +} + +/** 读取本地草稿(无草稿返回 null) */ +export function getDraftDemand(): Promise { + return Promise.resolve(storage.sessionGet(DRAFT_KEY)) +} + +/** 保存草稿到本地 */ +export function saveDraft(demand: Demand): Promise { + storage.sessionSet(DRAFT_KEY, { ...demand, status: 'draft' }) + return Promise.resolve() +} diff --git a/src/api/finance.ts b/src/api/finance.ts new file mode 100644 index 0000000..2c15a84 --- /dev/null +++ b/src/api/finance.ts @@ -0,0 +1,316 @@ +import type { PageResult } from '@/types/api' +import type { + Bill, + BillQuery, + FinanceStats, + Invoice, + InvoiceQuery, + InvoiceTitle, + InvoiceTitlePayload, + PayMethod, + Payment, + PaymentResult, +} from '@/types/business/finance' + +// TODO(api): 待接口文档到位后,将以下 mock 替换为 request 调用 + +/** 账单 mock(可变:支付后状态在前端内存中翻转演示) */ +const MOCK_BILLS: Bill[] = [ + { + id: 'bill-001', + planName: '第一期·预付款 比例33%', + billNo: '20260813ZZ251245', + tradeNo: 'T20260819153035678K2M001', + projectName: '某大型银行数字化转型方案', + amount: 20000, + status: '已支付', + createTime: '2026-8-17 14:00', + }, + { + id: 'bill-002', + planName: '第二期·进度款 比例30%', + billNo: '20260813ZZ251246', + projectName: '智慧城市大数据平台建设项目', + amount: 20100, + status: '待支付', + createTime: '2026-8-17 14:00', + }, + { + id: 'bill-003', + planName: '第三期·验收款 比例27%', + billNo: '20260813ZZ251247', + projectName: '三甲医院指挥医疗信息化系统', + amount: 20000, + status: '未到期', + createTime: '2026-8-17 14:00', + }, + { + id: 'bill-004', + planName: '第四期·尾款 比例10%', + billNo: '20260813ZZ251248', + projectName: '智慧城市大数据平台建设项目', + amount: 20100, + status: '未到期', + createTime: '2026-8-17 14:00', + }, + { + id: 'bill-005', + planName: '第二期·进度款 比例30%', + billNo: '20260813ZZ251249', + projectName: '三甲医院指挥医疗信息化系统', + amount: 20100, + status: '待支付', + createTime: '2026-8-16 10:30', + }, + { + id: 'bill-006', + planName: '第一期·预付款 比例33%', + billNo: '20260813ZZ251250', + tradeNo: 'T20260819153035678K2M002', + projectName: '某大型银行数字化转型方案', + amount: 20000, + status: '已支付', + createTime: '2026-8-15 09:12', + }, +] + +/** 发票 mock(可变:申请开票后状态在前端内存中翻转演示) */ +const MOCK_INVOICES: Invoice[] = [ + { + id: 'invoice-001', + planName: '第一期·预付款 比例33%', + billNo: '20260813ZZ251245', + tradeNo: 'T20260819153035678K2M001', + projectName: '某大型银行数字化转型方案', + amount: 20000, + status: '已开票', + applyTime: '2026-8-17 14:00', + }, + { + id: 'invoice-002', + planName: '第一期·预付款 比例33%', + billNo: '20260813ZZ251250', + tradeNo: 'T20260819153035678K2M002', + projectName: '某大型银行数字化转型方案', + amount: 20000, + status: '开票中', + applyTime: '2026-8-17 14:00', + }, + { + id: 'invoice-003', + planName: '第二期·进度款 比例30%', + billNo: '20260813ZZ251249', + projectName: '三甲医院指挥医疗信息化系统', + amount: 20100, + status: '未开票', + applyTime: '2026-8-17 14:00', + }, +] + +/** 发票抬头 mock(可变,CRUD 在前端内存中演示) */ +const MOCK_TITLES: InvoiceTitle[] = [ + { + id: 'title-001', + type: '企业单位', + title: '山东日照有限有限公司', + taxNo: '344683434513543543545535', + isDefault: true, + }, + { + id: 'title-002', + type: '企业单位', + title: '山东省一周练七次有限公司', + taxNo: '6856754615216565476846', + isDefault: false, + }, +] + +/** 支付进行单(billId → 当前二维码) */ +const MOCK_PAYMENTS = new Map() + +/** 已产生支付结果的账单(billId → 结果),预置已支付账单 */ +const MOCK_RESULTS = new Map() +MOCK_RESULTS.set('bill-001', { + method: 'wechat', + amount: 20000, + payTime: '2026-01-21 15:00:00', + tradeNo: 'T20260819153035678K2M001', + bills: [{ ...MOCK_BILLS[0] }], +}) +MOCK_RESULTS.set('bill-006', { + method: 'wechat', + amount: 20000, + payTime: '2026-01-21 15:00:00', + tradeNo: 'T20260819153035678K2M002', + bills: [{ ...MOCK_BILLS[5] }], +}) + +const DEFAULT_STATS: FinanceStats = { + totalExpense: 1726823900, + month: '2026年1', + pendingCount: 3, + distribution: [ + { name: '生产智造', value: 25, color: '#3370ff' }, + { name: '物联网全案', value: 25, color: '#34c724' }, + { name: '硬件电路', value: 25, color: '#ff8f1f' }, + { name: '互联网软件', value: 25, color: '#f54a45' }, + ], +} + +/** 财务统计(period 为展示周期参数,mock 恒返回原型数据) */ +export function getFinanceStats(period?: string): Promise { + void period + return Promise.resolve(structuredClone(DEFAULT_STATS)) +} + +function matchBillKeyword(item: Bill, keyword: string): boolean { + if (!keyword) return true + return ( + item.planName.includes(keyword) || + item.billNo.includes(keyword) || + item.projectName.includes(keyword) || + (item.tradeNo?.includes(keyword) ?? false) + ) +} + +/** 账单列表(筛选 + 分页) */ +export function getBillList(query: BillQuery): Promise> { + const filtered = MOCK_BILLS.filter((item) => { + if (query.status !== 'all' && item.status !== query.status) return false + return matchBillKeyword(item, query.keyword) + }) + const start = (query.page - 1) * query.pageSize + return Promise.resolve({ + list: filtered.slice(start, start + query.pageSize).map((item) => ({ ...item })), + total: filtered.length, + }) +} + +/** 账单详情(不存在返回 null) */ +export function getBillDetail(id: string): Promise { + const found = MOCK_BILLS.find((item) => item.id === id) ?? null + return Promise.resolve(found ? { ...found } : null) +} + +/** 创建支付单(生成二维码支付信息) */ +export function createPayment(billId: string, method: PayMethod): Promise { + const bill = MOCK_BILLS.find((item) => item.id === billId) + if (!bill) return Promise.reject(new Error('账单不存在')) + const payment: Payment = { + billId, + method, + amount: bill.amount, + qrExpiresInSeconds: 300, + } + MOCK_PAYMENTS.set(billId, payment) + return Promise.resolve({ ...payment }) +} + +/** 切换支付方式(刷新二维码) */ +export function switchPaymentMethod(billId: string, method: PayMethod): Promise { + return createPayment(billId, method) +} + +/** 查询支付结果(无支付记录且未支付返回 null;演示数据 payTime/tradeNo 固定) */ +export function getPayResult(billId: string): Promise { + const existing = MOCK_RESULTS.get(billId) + if (existing) return Promise.resolve(structuredClone(existing)) + + const payment = MOCK_PAYMENTS.get(billId) + const bill = MOCK_BILLS.find((item) => item.id === billId) + if (!payment || !bill) return Promise.resolve(null) + + // 演示:存在支付单即视为完成支付(createPayment 由「完成支付」触发) + const tradeNo = bill.tradeNo ?? 'T20260819153035678K2M001' + if (bill.status !== '已支付') { + bill.status = '已支付' + bill.tradeNo = tradeNo + } + const result: PaymentResult = { + method: payment.method, + amount: payment.amount, + payTime: '2026-01-21 15:00:00', + tradeNo, + bills: [{ ...bill }], + } + MOCK_RESULTS.set(billId, result) + return Promise.resolve(structuredClone(result)) +} + +function matchInvoiceKeyword(item: Invoice, keyword: string): boolean { + if (!keyword) return true + return ( + item.planName.includes(keyword) || + item.billNo.includes(keyword) || + item.projectName.includes(keyword) || + (item.tradeNo?.includes(keyword) ?? false) + ) +} + +/** 发票列表(筛选 + 分页) */ +export function getInvoiceList(query: InvoiceQuery): Promise> { + const filtered = MOCK_INVOICES.filter((item) => { + if (query.status !== 'all' && item.status !== query.status) return false + return matchInvoiceKeyword(item, query.keyword) + }) + const start = (query.page - 1) * query.pageSize + return Promise.resolve({ + list: filtered.slice(start, start + query.pageSize).map((item) => ({ ...item })), + total: filtered.length, + }) +} + +/** 申请开票(演示:状态置为开票中),返回最新发票 */ +export function applyInvoice(id: string): Promise { + const found = MOCK_INVOICES.find((item) => item.id === id) + if (!found) return Promise.reject(new Error('发票记录不存在')) + found.status = '开票中' + return Promise.resolve({ ...found }) +} + +/** 发票抬头列表 */ +export function getInvoiceTitles(): Promise { + return Promise.resolve(MOCK_TITLES.map((item) => ({ ...item }))) +} + +/** 新增发票抬头 */ +export function createInvoiceTitle(payload: InvoiceTitlePayload): Promise { + const created: InvoiceTitle = { + id: `title-${Date.now()}`, + ...payload, + isDefault: false, + } + MOCK_TITLES.push(created) + return Promise.resolve({ ...created }) +} + +/** 编辑发票抬头 */ +export function updateInvoiceTitle(id: string, payload: InvoiceTitlePayload): Promise { + const found = MOCK_TITLES.find((item) => item.id === id) + if (!found) return Promise.reject(new Error('抬头不存在')) + found.type = payload.type + found.title = payload.title + found.taxNo = payload.taxNo + return Promise.resolve({ ...found }) +} + +/** 删除发票抬头 */ +export function deleteInvoiceTitle(id: string): Promise { + const index = MOCK_TITLES.findIndex((item) => item.id === id) + if (index < 0) return Promise.reject(new Error('抬头不存在')) + MOCK_TITLES.splice(index, 1) + return Promise.resolve() +} + +/** 设为默认抬头(唯一默认) */ +export function setDefaultTitle(id: string): Promise { + for (const item of MOCK_TITLES) { + item.isDefault = item.id === id + } + return Promise.resolve(MOCK_TITLES.map((item) => ({ ...item }))) +} + +/** 导出报表(演示:稍后由页面提示成功) */ +export function exportReport(): Promise { + return Promise.resolve() +} diff --git a/src/api/home.ts b/src/api/home.ts new file mode 100644 index 0000000..5adde7d --- /dev/null +++ b/src/api/home.ts @@ -0,0 +1,180 @@ +import type { BusinessType } from '@/types/api' +import type { Case } from '@/types/business/cases' +import type { BusinessLine, HomeStats, ServiceType } from '@/types/business/home' + +// TODO(api): 待接口文档到位后,将以下 mock 返回替换为 request 调用(页面 → store → api 数据流不变) + +const SERVICE_TYPES: ServiceType[] = [ + { label: 'Web应用', desc: '响应式 Web 与后台管理系统定制开发' }, + { label: '小程序', desc: '微信/支付宝小程序开发与上架' }, + { label: 'APP', desc: 'iOS/Android 移动应用定制开发' }, + { label: '二次开发', desc: '基于现有系统的功能扩展与重构' }, + { label: '桌面工具', desc: '跨平台桌面端工具与自动化软件' }, +] + +const SERVICE_CONTENT = `一、产品咨询与规划服务 +1、业务诊断与梳理:深入调研客户现有业务流程,识别数字化痛点与改进机会。 +2、竞品分析与市场调研:分析行业标杆产品,提炼差异化竞争优势。 +3、产品定位与MVP规划:明确核心用户群体,规划最小可行性产品(MVP)功能集,制定迭代路线图。 +4、技术可行性评估:评估所选技术栈的成熟度、扩展性及与现有系统的兼容性。 +二、UI/UX 设计服务 +1、用户研究与体验地图:构建用户画像,梳理用户旅程,优化关键触点体验。 +2、交互原型设计:制作高保真可交互原型,直观展示产品逻辑与操作流程,支持早期验证。 +3、视觉UI设计:遵循最新设计趋势与品牌规范,输出图标、界面、动效等视觉元素。 +4、设计规范与组件库:建立统一的设计系统(Design System),确保多端体验一致性,提升后续迭代效率。 +三、技术开发服务 +5、前端开发:基于 React/Vue/Flutter 等框架,实现响应式Web、H5、小程序及跨平台移动端应用。 +6、后端开发:采用 Spring Cloud、Node.js、Go 等微服务架构,构建高并发、高可用的业务接口与逻辑。 +7、数据库设计:根据业务特性设计关系型(MySQL)与非关系型(Redis、MongoDB)数据库集群方案。 +8、DevOps 与云服务部署:搭建 CI/CD 自动化流水线,基于 Docker/K8s 进行容器化部署,保障系统弹性伸缩。 +四、质量保障(QA)服务 +1、功能测试:覆盖单元测试、集成测试、系统测试及UAT(用户验收测试)支持。 +2、性能测试:进行压力测试、负载测试,定位系统瓶颈,优化响应速度。 +3、安全测试:实施渗透测试、漏洞扫描,保障数据安全与隐私合规。 +4、兼容性测试:覆盖主流浏览器、操作系统及移动设备型号。 +五、运维与持续运营服务 +1、7x24小时监控:实时监控系统健康度、流量及异常日志。 +2、数据运营分析:接入埋点系统,输出用户行为分析报告,指导产品优化。 +3、迭代升级:基于用户反馈与业务增长需求,提供持续的功能迭代与版本更新。` + +const BUSINESS_LINES: BusinessLine[] = [ + { + id: 'internet', + name: '互联网软件', + desc: 'Web/小程序/APP/二次开发/桌面工具等', + slogan: '专业的软件定制开发服务,从需求到上线全流程保障', + icon: '', + serviceContent: SERVICE_CONTENT, + serviceTypes: SERVICE_TYPES, + video: '', + }, + { + id: 'hardware', + name: '硬件电路', + desc: '原理图设计/PCB设计/电气效验等', + slogan: '专业的硬件电路设计服务,从方案到量产全流程保障', + icon: '', + serviceContent: SERVICE_CONTENT, + serviceTypes: SERVICE_TYPES, + video: '', + }, + { + id: 'iot', + name: '物联网全案', + desc: '软硬件开发+云端一体化/定制化开发等', + slogan: '专业的物联网软硬一体化服务,从感知到云端全流程保障', + icon: '', + serviceContent: SERVICE_CONTENT, + serviceTypes: SERVICE_TYPES, + video: '', + }, + { + id: 'manufacturing', + name: '生产制造', + desc: 'PCB制版/SMT/整机装配/定制生产等', + slogan: '专业的研发智造生产服务,从制版到交付全流程保障', + icon: '', + serviceContent: SERVICE_CONTENT, + serviceTypes: SERVICE_TYPES, + video: '', + }, +] + +const FEATURED_CASES: Case[] = [ + { + id: 'case-001', + title: '自动化生产线升级改造项目', + category: 'manufacturing', + cover: '', + finishDate: '2026/8/10', + teamSize: 50, + scale: '300万', + industry: '自动化', + duration: '150个工作日', + clientType: '企业', + background: '传统产线效率低、人工依赖高,客户需要通过自动化升级提升产能与品控水平。', + painPoints: '1、产线依赖人工巡检,异常响应慢;2、设备数据孤岛,缺乏统一监控;3、工艺参数无法追溯。', + requirements: '建设产线数据采集与监控平台,完成关键工位自动化改造,打通 MES 系统接口。', + deliverables: '1、产线自动化改造方案;2、PLC 控制程序与上位机软件;3、数据看板与运维手册;4、培训与质保服务。', + video: '', + isFavorite: false, + }, + { + id: 'case-002', + title: '智能工厂物联网整体解决方案', + category: 'iot', + cover: '', + finishDate: '2026/8/10', + teamSize: 30, + scale: '500万', + industry: '工厂服务', + duration: '200个工作日', + clientType: '企业', + background: '园区工厂多系统独立运行,能耗与设备管理粗放,需要整体数字化升级。', + painPoints: '1、门禁/监控/能耗子系统数据不互通;2、能源浪费缺乏精细监测;3、决策缺少实时数据支撑。', + requirements: '构建 IoT 平台接入设备数据,建设综合驾驶舱,实现能耗监测与智能告警。', + deliverables: '1、IoT 接入平台;2、综合驾驶舱大屏;3、能耗分析系统;4、集成联调与培训服务。', + video: '', + isFavorite: false, + }, + { + id: 'case-003', + title: '智能穿戴设备PCB设计与量产', + category: 'hardware', + cover: '', + finishDate: '2026/8/10', + teamSize: 50, + scale: '10-50万', + industry: '智能设备', + duration: '30个工作日', + clientType: '企业', + background: '客户推出新一代智能穿戴产品,需要高集成度低功耗 PCB 设计并快速量产。', + painPoints: '1、板载空间受限,器件布局密集;2、功耗要求严格;3、量产良率需爬坡。', + requirements: '完成原理图与 PCB 设计、打样验证、SMT 贴片与整机装配量产。', + deliverables: '1、原理图与 PCB 设计文件;2、样板与测试报告;3、量产良率报告;4、BOM 与工艺文件。', + video: '', + isFavorite: false, + }, + { + id: 'case-004', + title: '智能园区综合管理平台开发', + category: 'internet', + cover: '', + finishDate: '2026/8/10', + teamSize: 20, + scale: '200万', + industry: '园区服务', + duration: '200个工作日', + clientType: '企业', + background: '随着新型城镇化建设的加速和"数字中国"战略的深入推进,传统园区管理模式正面临严峻挑战,普遍存在"数据孤岛"现象。', + painPoints: '1、运营效率低下:安保依赖人工巡逻;2、系统孤岛严重:门禁、监控、停车等子系统独立建设;3、能源浪费突出;4、服务体验欠佳;5、决策缺乏依据;6、安全隐患犹存。', + requirements: '打造集"感知、预警、分析、决策、服务"于一体的智能园区综合管理平台,打通园区各子系统壁垒。', + deliverables: '1、Web 端管理平台(驾驶舱/安防/能源/物业);2、移动端服务门户;3、IoT 平台与数据大屏;4、全套技术文档;5、培训与不少于1年免费质保。', + video: '', + isFavorite: false, + }, +] + +/** 首页数据看板 */ +export function getHomeStats(): Promise { + return Promise.resolve({ pending: 3, inProgress: 3, total: 6 }) +} + +/** 业务线列表(首页 4 卡片) */ +export function getBusinessLines(): Promise { + return Promise.resolve(BUSINESS_LINES) +} + +/** 业务详情(业务详情页) */ +export function getBusinessDetail(type: BusinessType): Promise { + const found = BUSINESS_LINES.find((line) => line.id === type) + return Promise.resolve(found ?? BUSINESS_LINES[0]!) +} + +/** 首页案例精选(category=all 时返回全部) */ +export function getFeaturedCases(category: BusinessType | 'all' = 'all'): Promise { + const list = category === 'all' + ? FEATURED_CASES + : FEATURED_CASES.filter((item) => item.category === category) + return Promise.resolve(list) +} diff --git a/src/api/message.ts b/src/api/message.ts new file mode 100644 index 0000000..29ef993 --- /dev/null +++ b/src/api/message.ts @@ -0,0 +1,153 @@ +import type { PageResult } from '@/types/api' +import type { Message, MessageQuery } from '@/types/business/message' + +// TODO(api): 待接口文档到位后,将以下 mock 替换为 request 调用 + +/** 消息 mock(可变,已读状态在前端内存中翻转演示) */ +const MOCK_MESSAGES: Message[] = [ + { + id: 'msg-001', + type: '系统通知', + title: '【新功能】物联网全案"整机联调"看板正式上线', + summary: '整机联调看板已上线,支持联调进度、问题闭环与交付物一览。', + content: '尊敬的用户:\n\n为了提升物联网全案项目的交付透明度,平台已正式上线"整机联调"看板功能。\n\n一、功能亮点\n1、联调进度实时同步:设备接入、协议联调、场景验证各阶段进度一目了然;\n2、问题闭环跟踪:联调过程中产生的缺陷与阻塞项统一登记、分派、闭环;\n3、交付物一览:联调报告、测试记录、验收文档集中展示,随时查阅下载。\n\n二、使用方式\n进入「项目中心 → 项目详情 → 整机联调」即可查看对应看板。已有进行中的物联网全案项目将自动迁移历史数据,无需重新配置。\n\n三、温馨提示\n如在使用过程中遇到问题,可通过「在线客服」联系我们,或联系专属项目经理协助处理。\n\n智联智造平台产品团队\n2026年8月', + publishTime: '2026-08-06 10:00', + isRead: false, + attachments: [ + { name: '物联网全案服务介绍.pdf', size: 2.5 * 1024 * 1024, uploadTime: '2026-08-06 10:00', url: '' }, + ], + }, + { + id: 'msg-002', + type: '系统通知', + title: '平台 v2.6 版本更新公告', + summary: '新增需求提报模板、案例库对比功能,并优化了消息中心体验。', + content: '尊敬的用户:\n\n平台 v2.6 版本已于近日发布,本次更新内容如下:\n\n一、新增功能\n1、需求提报模板:常用需求类型支持保存为模板,一键复用;\n2、案例库对比:最多选择 4 个案例进行横向对比,辅助决策;\n3、消息中心改版:分类标签、关键词搜索、全部已读一站式处理。\n\n二、体验优化\n1、列表页加载性能提升约 40%;\n2、移动端适配细节修复;\n3、附件上传支持 1GB 大文件。\n\n感谢您一直以来的支持,祝使用愉快!', + publishTime: '2026-07-15 14:30', + isRead: false, + attachments: [], + }, + { + id: 'msg-003', + type: '系统通知', + title: '关于平台服务时间调整的通知', + summary: '自 2026-09-01 起,在线客服服务时间调整为 9:00-21:00。', + content: '尊敬的用户:\n\n为进一步提升服务质量,自 2026 年 9 月 1 日起,在线客服服务时间调整如下:\n\n服务时间:每日 9:00 - 21:00(法定节假日照常服务)\n非服务时段提交的留言,我们将在下一个服务时段内尽快回复。\n\n紧急事务可通过专属项目经理电话联系。\n\n感谢您的理解与支持!', + publishTime: '2026-08-28 09:00', + isRead: true, + attachments: [], + }, + { + id: 'msg-004', + type: '状态通知', + title: '合同生效通知', + summary: '您的合同已正式生效,合同编号 CZ-2026-0815 已开始生效。', + content: '您好:\n\n您的合同已正式生效。\n\n合同编号:CZ-2026-0815\n合同名称:智能园区综合管理平台开发合同\n生效日期:2026-08-15\n\n合同已开始生效,可随时在「合同中心」查看详情及履约进度。\n\n如有疑问,请联系您的专属项目经理。', + publishTime: '2026-08-15 16:42', + isRead: false, + attachments: [], + }, + { + id: 'msg-005', + type: '状态通知', + title: '项目进度更新:需求确认阶段已完成', + summary: '「智能工厂物联网整体解决方案」需求确认已完成,即将进入方案设计。', + content: '您好:\n\n您的项目进度已更新:\n\n项目名称:智能工厂物联网整体解决方案\n当前阶段:需求确认 ✅ 已完成\n下一阶段:方案设计\n\n项目组将于 3 个工作日内提交初步方案,届时会再次通知您。您也可以前往「项目中心」查看详细进度与里程碑。\n\n感谢您的配合!', + publishTime: '2026-08-20 11:05', + isRead: false, + attachments: [], + }, + { + id: 'msg-006', + type: '状态通知', + title: '账单待办提醒', + summary: '您有一笔待付款账单,账单编号 BILL-202608-023,请及时处理。', + content: '您好:\n\n您有一笔待付款账单,请及时处理:\n\n账单编号:BILL-202608-023\n关联项目:电商中台系统定制开发\n账单金额:150,000.00 元\n到期日期:2026-09-10\n\n请前往「财务中心 → 账单管理」完成支付。如对账单有疑问,请通过在线客服联系我们。\n\n温馨提示:逾期支付可能影响项目排期,请留意处理时间。', + publishTime: '2026-08-25 10:00', + isRead: true, + attachments: [], + }, + { + id: 'msg-007', + type: '在线客服', + title: '您好,请问有什么可以帮您的?', + summary: '专属客服小助手已收到您的咨询,将尽快为您答复。', + content: '您好,欢迎咨询智联智造平台!\n\n我是您的专属客服小助手,请问有什么可以帮您的?\n\n您可以咨询:\n1、物联网全案开发的流程与周期\n2、项目进度查询\n3、合同与账单相关问题\n4、平台操作指引\n\n我们将在服务时间内尽快回复您。', + publishTime: '2026-08-21 10:25', + isRead: false, + attachments: [], + }, + { + id: 'msg-008', + type: '在线客服', + title: '您的咨询已有回复', + summary: '关于"整机联调周期"的咨询已回复,点击查看详细内容。', + content: '您好:\n\n关于您咨询的"整机联调周期"问题,客服回复如下:\n\n一般物联网全案项目的整机联调周期为 2-4 周,具体取决于设备数量、协议复杂度与场景验证范围。常规智能家居类项目约 2 周,工业场景多设备联调约 3-4 周。\n\n详细排期以项目计划为准,您可在「项目中心」查看最新里程碑。\n\n如还有其他问题,欢迎继续咨询!', + publishTime: '2026-08-19 15:40', + isRead: true, + attachments: [], + }, + { + id: 'msg-009', + type: '在线客服', + title: '会话满意度回访', + summary: '您上次的会话已结束,欢迎对本次服务进行评价。', + content: '您好:\n\n您于 2026-08-18 的会话已结束,感谢您的咨询!\n\n欢迎对本次服务进行评价,您的反馈将帮助我们持续改进。如问题未得到解决,可直接回复本条消息,我们将重新为您跟进。\n\n祝您使用愉快!', + publishTime: '2026-08-18 17:00', + isRead: false, + attachments: [], + }, +] + +function applyFilters(query: MessageQuery): Message[] { + return MOCK_MESSAGES.filter((item) => { + if (query.type !== 'all' && item.type !== query.type) return false + if (query.keyword && !item.title.includes(query.keyword) && !item.summary.includes(query.keyword)) return false + return true + }) +} + +/** 消息列表(筛选 + 分页) */ +export function getMessageList(query: MessageQuery): Promise> { + const filtered = applyFilters(query) + const start = (query.page - 1) * query.pageSize + return Promise.resolve({ + list: filtered.slice(start, start + query.pageSize), + total: filtered.length, + }) +} + +/** 消息详情(不存在返回 null) */ +export function getMessageDetail(id: string): Promise { + const found = MOCK_MESSAGES.find((item) => item.id === id) ?? null + return Promise.resolve(found ? { ...found } : null) +} + +/** 全部标记已读 */ +export function markAllRead(): Promise { + for (const item of MOCK_MESSAGES) { + item.isRead = true + } + return Promise.resolve() +} + +/** 单条标记已读 */ +export function markRead(id: string): Promise { + const found = MOCK_MESSAGES.find((item) => item.id === id) + if (found) found.isRead = true + return Promise.resolve() +} + +/** 各分类消息总数(tab 角标) */ +export function getTabCounts(): Promise<{ all: number; 在线客服: number; 状态通知: number; 系统通知: number }> { + const counts = { all: MOCK_MESSAGES.length, 在线客服: 0, 状态通知: 0, 系统通知: 0 } + for (const item of MOCK_MESSAGES) { + counts[item.type] += 1 + } + return Promise.resolve(counts) +} + +/** 未读消息总数(头部角标) */ +export function getUnreadCount(): Promise { + return Promise.resolve(MOCK_MESSAGES.filter((item) => !item.isRead).length) +} diff --git a/src/api/profile.ts b/src/api/profile.ts new file mode 100644 index 0000000..06b5017 --- /dev/null +++ b/src/api/profile.ts @@ -0,0 +1,418 @@ +import type { BusinessType, PageResult } from '@/types/api' +import type { Case } from '@/types/business/cases' +import type { + Address, + ChangePasswordParams, + Feedback, + FavoritesQuery, + HelpFaqGroup, + ListQuery, + Profile, + Qualification, + QualificationInput, +} from '@/types/business/profile' + +// TODO(api): 待接口文档到位后,将以下 mock 替换为 request 调用(页面 → store → api 数据流不变) + +/** 账号资料 mock(可变,昵称/绑定等操作在前端内存中演示) */ +const MOCK_PROFILE: Profile = { + nickname: '王永志', + phone: '186****618', + email: 'yongzh******4@163.com', + passwordModifiedAt: '15天前', +} + +/** 获取账号资料 */ +export function getProfile(): Promise { + return Promise.resolve({ ...MOCK_PROFILE }) +} + +/** 修改昵称 */ +export function updateNickname(name: string): Promise { + MOCK_PROFILE.nickname = name + return Promise.resolve({ ...MOCK_PROFILE }) +} + +/** 修改密码(验证码 + 新密码) */ +export function changePassword(params: ChangePasswordParams): Promise { + MOCK_PROFILE.passwordModifiedAt = '刚刚' + void params + return Promise.resolve() +} + +/** 绑定/换绑手机号 */ +export function bindPhone(phone: string, code: string): Promise { + void code + MOCK_PROFILE.phone = phone + return Promise.resolve({ ...MOCK_PROFILE }) +} + +/** 绑定/换绑邮箱 */ +export function bindEmail(email: string, code: string): Promise { + void code + MOCK_PROFILE.email = email + return Promise.resolve({ ...MOCK_PROFILE }) +} + +/** 取消绑定邮箱 */ +export function unbindEmail(): Promise { + MOCK_PROFILE.email = '' + return Promise.resolve({ ...MOCK_PROFILE }) +} + +/** 注销前安全身份验证 */ +export function verifyIdentity(phone: string, code: string): Promise { + void phone + void code + return Promise.resolve() +} + +/** 注销账号 */ +export function deleteAccount(): Promise { + return Promise.resolve() +} + +/** 收货地址 mock(isDefault 混合) */ +const MOCK_ADDRESSES: Address[] = [ + { + id: 'addr-1', + receiverName: '王永志', + phone: '18556669516', + region: '山东省日照市东港区', + detailAddress: '凌云购物广场三楼', + isDefault: true, + }, + { + id: 'addr-2', + receiverName: '王永志', + phone: '18556669516', + region: '山东省日照市东港区', + detailAddress: '海曲东路 12 号研发中心 A 座 8 层', + isDefault: false, + }, + { + id: 'addr-3', + receiverName: '李晓婷', + phone: '13863331234', + region: '山东省青岛市市南区', + detailAddress: '香港中路 76 号 A 座 18 层', + isDefault: false, + }, + { + id: 'addr-4', + receiverName: '王永志', + phone: '18556669516', + region: '山东省济南市历下区', + detailAddress: '经十路 9999 号舜耕大厦 12 层', + isDefault: false, + }, +] + +function paginate(list: T[], query: ListQuery): PageResult { + const start = (query.page - 1) * query.pageSize + return { + list: list.slice(start, start + query.pageSize), + total: list.length, + } +} + +/** 收货地址列表(分页) */ +export function getAddresses(query: ListQuery): Promise> { + return Promise.resolve(paginate(MOCK_ADDRESSES, query)) +} + +/** 新增收货地址 */ +export function createAddress(input: Omit): Promise
{ + const created: Address = { + ...input, + id: `addr-${Date.now()}`, + isDefault: MOCK_ADDRESSES.length === 0, + } + MOCK_ADDRESSES.push(created) + return Promise.resolve({ ...created }) +} + +/** 编辑收货地址 */ +export function updateAddress(id: string, input: Omit): Promise
{ + const found = MOCK_ADDRESSES.find((item) => item.id === id) + if (!found) return Promise.reject(new Error('地址不存在')) + Object.assign(found, input) + return Promise.resolve({ ...found }) +} + +/** 删除收货地址 */ +export function deleteAddress(id: string): Promise { + const index = MOCK_ADDRESSES.findIndex((item) => item.id === id) + if (index >= 0) MOCK_ADDRESSES.splice(index, 1) + return Promise.resolve() +} + +/** 设为默认地址 */ +export function setDefaultAddress(id: string): Promise { + for (const item of MOCK_ADDRESSES) { + item.isDefault = item.id === id + } + return Promise.resolve(MOCK_ADDRESSES.map((item) => ({ ...item }))) +} + +/** 我的收藏 mock(4 条已收藏案例,字段与案例库原型一致) */ +const MOCK_FAVORITES: Case[] = [ + { + id: 'fav-1', + title: '自动化生产线升级改造项目', + category: 'manufacturing', + cover: '', + finishDate: '2026/8/10', + teamSize: 50, + scale: '300万', + industry: '自动化', + duration: '150个工作日', + clientType: '企业', + background: '传统产线效率低、人工依赖高,客户需要通过自动化升级提升产能与品控水平。', + painPoints: '1、产线依赖人工巡检,异常响应慢;2、设备数据孤岛,缺乏统一监控;3、工艺参数无法追溯。', + requirements: '建设产线数据采集与监控平台,完成关键工位自动化改造,打通 MES 系统接口。', + deliverables: '1、产线自动化改造方案;2、PLC 控制程序与上位机软件;3、数据看板与运维手册;4、培训与质保服务。', + video: '', + isFavorite: true, + }, + { + id: 'fav-2', + title: '智能工厂物联网整体解决方案', + category: 'iot', + cover: '', + finishDate: '2026/8/10', + teamSize: 30, + scale: '500万', + industry: '工厂服务', + duration: '200个工作日', + clientType: '企业', + background: '园区工厂多系统独立运行,能耗与设备管理粗放,需要整体数字化升级。', + painPoints: '1、门禁/监控/能耗子系统数据不互通;2、能源浪费缺乏精细监测;3、决策缺少实时数据支撑。', + requirements: '构建 IoT 平台接入设备数据,建设综合驾驶舱,实现能耗监测与智能告警。', + deliverables: '1、IoT 接入平台;2、综合驾驶舱大屏;3、能耗分析系统;4、集成联调与培训服务。', + video: '', + isFavorite: true, + }, + { + id: 'fav-3', + title: '智能穿戴设备PCB设计与量产', + category: 'hardware', + cover: '', + finishDate: '2026/8/10', + teamSize: 50, + scale: '10-50万', + industry: '智能设备', + duration: '30个工作日', + clientType: '企业', + background: '客户推出新一代智能穿戴产品,需要高集成度低功耗 PCB 设计并快速量产。', + painPoints: '1、板载空间受限,器件布局密集;2、功耗要求严格;3、量产良率需爬坡。', + requirements: '完成原理图与 PCB 设计、打样验证、SMT 贴片与整机装配量产。', + deliverables: '1、原理图与 PCB 设计文件;2、样板与测试报告;3、量产良率报告;4、BOM 与工艺文件。', + video: '', + isFavorite: true, + }, + { + id: 'fav-4', + title: '智能园区综合管理平台开发', + category: 'internet', + cover: '', + finishDate: '2026/8/10', + teamSize: 20, + scale: '200万', + industry: '园区服务', + duration: '200个工作日', + clientType: '企业', + background: '传统园区管理普遍"数据孤岛",安防、能耗、物业、招商等系统独立运行,缺乏联动,管理手段依赖人工巡检和纸质流程。', + painPoints: '1、运营效率低下:安保依赖人工巡逻;2、系统孤岛严重:门禁、监控、停车、能耗等子系统独立建设;3、能源浪费突出;4、服务体验欠佳;5、决策缺乏依据;6、安全隐患犹存。', + requirements: '打造集"感知、预警、分析、决策、服务"于一体的智能园区综合管理平台,打通园区各子系统壁垒,实现全场景数字化覆盖。', + deliverables: '1、Web 端管理平台(驾驶舱/安防/能源/物业);2、移动端服务门户;3、IoT 平台与数据大屏;4、全套技术文档;5、培训与不少于1年免费质保。', + video: '', + isFavorite: true, + }, +] + +/** 我的收藏(关键字/分类筛选 + 分页) */ +export function getFavorites(query: FavoritesQuery): Promise> { + const filtered = MOCK_FAVORITES.filter((item) => { + if (query.category !== 'all' && item.category !== query.category) return false + if (query.keyword && !item.title.includes(query.keyword)) return false + return true + }) + const start = (query.page - 1) * query.pageSize + return Promise.resolve({ + list: filtered.slice(start, start + query.pageSize).map((item) => ({ ...item })), + total: filtered.length, + }) +} + +/** 收藏分类计数(分类快捷按钮角标) */ +export function getCaseCounts(): Promise>> { + const counts: Partial> = { + internet: 1, + hardware: 1, + iot: 1, + manufacturing: 1, + } + return Promise.resolve(counts) +} + +/** 资质 mock(状态混合:已认证/审核中/未通过) */ +const MOCK_QUALIFICATIONS: Qualification[] = [ + { + id: 'qual-1', + type: '体系认证', + certName: 'ISO9001 质量管理体系认证证书', + certNo: 'ISO202609080075', + issuer: '中国质量认证中心', + validFrom: '2026-9-10', + validTo: '2027-9-10', + status: '已认证', + attachments: [ + { name: '体系认证证书', size: 1_048_576, uploadTime: '2026-09-10', url: '' }, + ], + }, + { + id: 'qual-2', + type: '营业执照', + certName: '营业执照', + certNo: '91371102MA3XX8876Q', + issuer: '日照市东港区市场监督管理局', + validFrom: '2020-3-15', + validTo: '2030-3-14', + status: '审核中', + attachments: [ + { name: '营业执照', size: 820_000, uploadTime: '2026-09-12', url: '' }, + ], + }, + { + id: 'qual-3', + type: '检测报告', + certName: '智能终端产品第三方检测报告', + certNo: 'WT20260512001', + issuer: '山东省产品质量检验研究院', + validFrom: '2026-5-12', + validTo: '2027-5-12', + status: '未通过', + attachments: [ + { name: '检测报告', size: 2_097_152, uploadTime: '2026-05-15', url: '' }, + ], + }, + { + id: 'qual-4', + type: '生产许可证', + certName: '全国工业产品生产许可证', + certNo: 'XK06-123-45678', + issuer: '山东省市场监督管理局', + validFrom: '2024-1-8', + validTo: '2029-1-7', + status: '已认证', + attachments: [ + { name: '生产许可证', size: 1_258_291, uploadTime: '2024-01-10', url: '' }, + ], + }, +] + +/** 资质列表(分页) */ +export function getQualifications(query: ListQuery): Promise> { + return Promise.resolve(paginate(MOCK_QUALIFICATIONS, query)) +} + +/** 新增资质(提交后进入审核中) */ +export function createQualification(input: QualificationInput): Promise { + const created: Qualification = { + ...input, + id: `qual-${Date.now()}`, + status: '审核中', + } + MOCK_QUALIFICATIONS.unshift(created) + return Promise.resolve({ ...created }) +} + +/** 编辑资质(未通过重新提交后回到审核中) */ +export function updateQualification(id: string, input: QualificationInput): Promise { + const found = MOCK_QUALIFICATIONS.find((item) => item.id === id) + if (!found) return Promise.reject(new Error('资质不存在')) + found.type = input.type + found.certName = input.certName + found.certNo = input.certNo + found.issuer = input.issuer + found.validFrom = input.validFrom + found.validTo = input.validTo + found.attachments = input.attachments + if (found.status === '未通过') found.status = '审核中' + return Promise.resolve({ ...found }) +} + +/** 删除资质 */ +export function deleteQualification(id: string): Promise { + const index = MOCK_QUALIFICATIONS.findIndex((item) => item.id === id) + if (index >= 0) MOCK_QUALIFICATIONS.splice(index, 1) + return Promise.resolve() +} + +/** 提交意见反馈 */ +export function submitFeedback(feedback: Feedback): Promise { + void feedback + return Promise.resolve() +} + +/** 帮助中心 FAQ */ +export function getHelpFaqs(): Promise { + return Promise.resolve([ + { + title: '1. 账户与登录', + items: [ + { + q: '如何注册账号?', + a: '点击页面右上角"注册",输入手机号并获取验证码,设置 6-20 位登录密码即可完成注册。', + }, + { + q: '忘记密码怎么办?', + a: '在登录页点击"忘记密码",通过绑定手机号验证码验证身份后即可重新设置密码。', + }, + { + q: '一个账号可以多地登录吗?', + a: '同一账号同时仅允许在 1 台设备上保持在线,超出将自动踢出最早登录的设备。', + }, + { + q: '如何修改绑定手机号?', + a: '进入 个人中心 → 账号安全 → 绑定手机,验证原手机号后即可绑定新号码。', + }, + ], + }, + { + title: '2. 功能使用指南', + items: [ + { + q: '如何提交需求?', + a: '进入"发布需求"页面,填写需求描述、预算、周期等信息并提交,专业顾问将在一个工作日内与您联系。', + }, + { + q: '如何查看我的消息?', + a: '登录后点击右上角消息图标,或进入 个人中心 → 消息通知,即可查看系统通知与顾问消息。', + }, + { + q: '如何申请售后?', + a: '在"我的订单"中找到对应项目,点击"申请售后"填写问题描述并上传附件,平台将尽快为您处理。', + }, + ], + }, + { + title: '3. 系统与权限', + items: [ + { + q: '无权限怎么办?', + a: '请确认当前账号角色是否具备该模块权限;如仍无法访问,请联系专属客服或平台管理员为您开通。', + }, + { + q: '收不到通知怎么办?', + a: '请检查浏览器通知权限与消息设置是否开启;若仍未收到,请联系客服核实账号通知配置。', + }, + { + q: '支持哪些浏览器?', + a: '推荐使用 Chrome、Edge、Firefox、Safari 等主流浏览器的最新版本访问本平台。', + }, + ], + }, + ]) +} diff --git a/src/api/project.ts b/src/api/project.ts new file mode 100644 index 0000000..fdccd00 --- /dev/null +++ b/src/api/project.ts @@ -0,0 +1,270 @@ +import type { FileInfo, PageResult } from '@/types/api' +import type { + AfterSales, + AfterSalesPayload, + Project, + ProjectQuery, +} from '@/types/business/project' +import { formatDate } from '@/utils/format' + +// TODO(api): 待接口文档到位后,将以下 mock 替换为 request 调用 + +/** 构造 mock 文件信息 */ +function mockFile(name: string, size: number, uploadTime: string): FileInfo { + return { name, size, uploadTime, url: '' } +} + +/** 详情 mock 所属项目 id */ +const DETAIL_ID = 'project-004' + +/** 项目列表 mock(6 条,覆盖全部状态,internet/iot 混合) */ +const MOCK_PROJECTS: Project[] = [ + { + id: 'project-001', + projectNo: 'P8K2M202408001', + projectName: '某大型银行数字化转型方案', + businessType: 'internet', + progress: 64, + status: '待立项', + submitTime: '2026-8-17 14:00', + milestones: [], + team: [], + files: [], + paymentPlans: [], + invoiceRecords: [], + }, + { + id: 'project-002', + projectNo: 'P8K2M202408002', + projectName: '智慧城市大数据平台建设项目', + businessType: 'iot', + progress: 64, + status: '待验收', + submitTime: '2026-8-17 14:00', + milestones: [], + team: [], + files: [], + paymentPlans: [], + invoiceRecords: [], + }, + { + id: 'project-003', + projectNo: 'P8K2M202408003', + projectName: '三甲医院指挥医疗信息化系统', + businessType: 'internet', + progress: 64, + status: '已完成', + submitTime: '2026-8-17 14:00', + milestones: [], + team: [], + files: [], + paymentPlans: [], + invoiceRecords: [], + }, + { + id: DETAIL_ID, + projectNo: '20260813RJ251545', + projectName: '社区康养智能服务系统', + businessType: 'internet', + progress: 64, + status: '进行中', + submitTime: '2026-8-17 14:00', + milestones: [], + team: [], + files: [], + paymentPlans: [], + invoiceRecords: [], + }, + { + id: 'project-005', + projectNo: 'P8K2M202408005', + projectName: '智能仓储物流分拣系统', + businessType: 'iot', + progress: 64, + status: '售后中', + submitTime: '2026-8-17 14:00', + milestones: [], + team: [], + files: [], + paymentPlans: [], + invoiceRecords: [], + afterSales: { + status: '处理中', + type: 'BUG问题', + description: '分拣识别偶发误判,需要排查', + remark: '', + attachments: [], + createTime: '2026-8-19 17:00', + }, + }, + { + id: 'project-006', + projectNo: 'P8K2M202408006', + projectName: '新能源电站运维监控平台', + businessType: 'internet', + progress: 64, + status: '已取消', + submitTime: '2026-8-17 14:00', + milestones: [], + team: [], + files: [], + paymentPlans: [], + invoiceRecords: [], + }, +] + +/** 项目详情 mock(社区康养智能服务系统) */ +const PROJECT_DETAIL: Project = { + ...MOCK_PROJECTS[3], + progress: 65, + startTime: '2026-8-13 11:04:12', + endTime: '2026-10-30 18:00:00', + milestones: [ + { name: '需求确认', status: '已完成', time: '2026-8-13 11:04:12' }, + { name: '原型设计', status: '已完成', time: '2026-8-13 11:04:12' }, + { name: '开发实现', status: '进行中' }, + { name: '测试验收', status: '待开始' }, + { name: '项目交付', status: '待开始' }, + ], + team: [ + { role: '项目经理', name: '张三', avatarChar: '张' }, + { role: '项目经理', name: '张三', avatarChar: '张' }, + ], + files: [ + mockFile('项目需求规格说明书.pdf', 2516582, '2026-08-19 17:00'), + mockFile('项目需求说明书.word', 1887437, '2026-08-19 17:00'), + ], + paymentPlans: [ + { + id: 'bill-001', + planName: '第一期·预付款(比例33%)', + billNo: '20260813ZZ251245', + amount: 20100, + status: '已支付', + tradeNo: 'T20260819153035678K2M001', + projectName: '社区康养智能服务系统', + }, + { + id: 'bill-002', + planName: '第二期·付款(比例33%)', + billNo: '20260813ZZ251245', + amount: 20100, + status: '待支付', + projectName: '社区康养智能服务系统', + }, + { + id: 'bill-003', + planName: '第三期·尾款(比例33%)', + billNo: '20260813ZZ251245', + amount: 20100, + status: '未到期', + projectName: '社区康养智能服务系统', + }, + ], + invoiceRecords: [ + mockFile('增值税专用发票.pdf', 325120, '2026-08-19 17:00'), + mockFile('电子回单.jpg', 186368, '2026-08-19 17:00'), + ], + contractFile: mockFile('社区康养智能服务系统开发合同.pdf', 1039360, '2026-08-19 17:00'), + afterSales: { + status: '处理中', + type: 'BUG问题', + description: '具体请看清单', + remark: '', + attachments: [], + createTime: '2026-8-19 17:00', + }, +} + +/** 提交时间快捷区间匹配 */ +function matchTimeShortcut(submitTime: string, shortcut: ProjectQuery['timeShortcut']): boolean { + if (shortcut === 'all') return true + // mock 时间为 '2026-8-17 14:00',替换为 / 保证各环境可解析 + const time = new Date(submitTime.replaceAll('-', '/')).getTime() + const start = new Date() + if (shortcut === '3m') start.setMonth(start.getMonth() - 3) + if (shortcut === '6m') start.setMonth(start.getMonth() - 6) + if (shortcut === '1y') start.setFullYear(start.getFullYear() - 1) + return time >= start.getTime() +} + +/** 项目列表(筛选 + 分页,pageSize 固定 8) */ +export function getProjectList(query: ProjectQuery): Promise> { + const filtered = MOCK_PROJECTS.filter((item) => { + if ( + query.keyword + && !item.projectName.includes(query.keyword) + && !item.projectNo.includes(query.keyword) + ) { + return false + } + if (query.businessType !== 'all' && item.businessType !== query.businessType) return false + if (query.status !== 'all' && item.status !== query.status) return false + if (!matchTimeShortcut(item.submitTime, query.timeShortcut)) return false + return true + }) + const pageSize = query.pageSize > 0 ? query.pageSize : 8 + const start = (query.page - 1) * pageSize + return Promise.resolve({ + list: filtered.slice(start, start + pageSize), + total: filtered.length, + }) +} + +/** 项目详情(不存在返回 null;仅详情项目返回完整数据) */ +export function getProjectDetail(id: string): Promise { + if (id === DETAIL_ID) return Promise.resolve({ ...PROJECT_DETAIL }) + const found = MOCK_PROJECTS.find((item) => item.id === id) ?? null + return Promise.resolve(found ? { ...found } : null) +} + +/** 上传项目文件(追加进详情文件列表) */ +export function uploadProjectFile(file: File): Promise { + const info: FileInfo = { + name: file.name, + size: file.size, + uploadTime: formatDate(new Date()), + url: '', + } + PROJECT_DETAIL.files.push(info) + return Promise.resolve({ ...info }) +} + +/** 删除项目文件(按索引从详情文件列表移除) */ +export function deleteProjectFile(index: number): Promise { + if (index >= 0 && index < PROJECT_DETAIL.files.length) { + PROJECT_DETAIL.files.splice(index, 1) + } + return Promise.resolve() +} + +/** 提交售后工单 */ +export function submitAfterSales( + projectId: string, + payload: AfterSalesPayload, +): Promise { + const result: AfterSales = { + status: '处理中', + type: payload.type, + description: payload.description, + remark: payload.remark, + attachments: [...payload.attachments], + createTime: formatDate(new Date()), + } + if (projectId === DETAIL_ID) PROJECT_DETAIL.afterSales = result + return Promise.resolve({ ...result, attachments: [...result.attachments] }) +} + +/** 取消售后工单 */ +export function cancelAfterSales(projectId: string): Promise { + const result: AfterSales = { + status: '无', + type: '', + description: '', + remark: '', + attachments: [], + createTime: '', + } + if (projectId === DETAIL_ID) PROJECT_DETAIL.afterSales = result + return Promise.resolve({ ...result }) +} diff --git a/src/api/service.ts b/src/api/service.ts new file mode 100644 index 0000000..092286b --- /dev/null +++ b/src/api/service.ts @@ -0,0 +1,72 @@ +import type { ChatMessage } from '@/types/business/service' + +// TODO(api): 待接口文档到位后,将以下 mock 替换为 request 调用 + +let seq = 0 + +function nextId(prefix: string): string { + seq += 1 + return `${prefix}-${Date.now()}-${seq}` +} + +function nowTime(): string { + const date = new Date() + const pad = (n: number): string => (n < 10 ? `0${n}` : String(n)) + return `今天 ${pad(date.getHours())}:${pad(date.getMinutes())}` +} + +/** 历史会话(欢迎语 + 用户提问 + 文件资料) */ +export function getChatHistory(): Promise { + return Promise.resolve([ + { + id: 'chat-001', + role: 'assistant', + content: '您好,欢迎咨询智联智造平台!我是您的专属客服小助手,请问有什么可以帮您的?', + time: '今天 10:23', + }, + { + id: 'chat-002', + role: 'user', + content: '我想咨询一下物联网全案开发的流程和周期', + time: '今天 10:23', + }, + { + id: 'chat-003', + role: 'assistant', + content: '', + file: { name: '物联网全案服务介绍.pdf', size: 2.5 * 1024 * 1024 }, + time: '今天 10:24', + }, + ]) +} + +/** 发送文本消息(echo 用户消息,携带当前时间) */ +export function sendChatMessage(content: string): Promise { + return Promise.resolve({ + id: nextId('chat'), + role: 'user', + content, + time: nowTime(), + }) +} + +/** 上传聊天文件(生成文件卡片消息) */ +export function uploadChatFile(file: File): Promise { + return Promise.resolve({ + id: nextId('chat'), + role: 'user', + content: '', + file: { name: file.name, size: file.size }, + time: nowTime(), + }) +} + +/** 客服自动回复 */ +export function mockAutoReply(): Promise { + return Promise.resolve({ + id: nextId('chat'), + role: 'assistant', + content: '收到您的消息,专属客服稍后为您答复~', + time: nowTime(), + }) +} diff --git a/src/api/web/home.ts b/src/api/web/home.ts new file mode 100644 index 0000000..8740904 --- /dev/null +++ b/src/api/web/home.ts @@ -0,0 +1,62 @@ +// 用户web端首页接口(真实后端绑定) +// 后端: vermis-maker com.ruoyi.vermins.controller.pc.web.UserSideWebHomePageController +// 前缀: /web/home-page 鉴权: 公开接口,无需 token +// +// 使用约定(与项目 SKILL.md 一致): +// 页面/组件禁止直接 import axios,一律经 store 调用本模块的 request。 +// 本文件只放「函数签名 + 请求路径」,不写业务逻辑。 +import { request } from '@/utils/request' +import type { + ProjectStatusCount, + ProjectSubmissionVo, + BannerVo, + AgreementFormVo, +} from '@/types/web/home' + +/** 项目状态统计 */ +export function getProjectStatusCount(): Promise { + return request({ + url: '/web/home-page/project-status-count', + method: 'get', + }) +} + +/** 业务服务类型列表 */ +export function getServiceTypeList(): Promise { + return request({ + url: '/web/home-page/service-type-list', + method: 'get', + }) +} + +/** 业务类型详情 */ +export function getServiceTypeDetail(id: number | string): Promise { + return request({ + url: `/web/home-page/service-type-detail/${id}`, + method: 'get', + }) +} + +/** 轮播图列表 */ +export function getCarouselList(): Promise { + return request({ + url: '/web/home-page/carousel-list', + method: 'get', + }) +} + +/** 平台介绍(富文本 HTML) */ +export function getPlatformIntro(): Promise { + return request({ + url: '/web/home-page/platform-intro', + method: 'get', + }) +} + +/** 联系信息(富文本 HTML) */ +export function getContactInfo(): Promise { + return request({ + url: '/web/home-page/contact-info', + method: 'get', + }) +} diff --git a/src/assets/hero.png b/src/assets/hero.png new file mode 100644 index 0000000..02251f4 Binary files /dev/null and b/src/assets/hero.png differ diff --git a/src/assets/vite.svg b/src/assets/vite.svg new file mode 100644 index 0000000..5101b67 --- /dev/null +++ b/src/assets/vite.svg @@ -0,0 +1 @@ +Vite diff --git a/src/assets/vue.svg b/src/assets/vue.svg new file mode 100644 index 0000000..770e9d3 --- /dev/null +++ b/src/assets/vue.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/components/AppButton.vue b/src/components/AppButton.vue new file mode 100644 index 0000000..f73f30e --- /dev/null +++ b/src/components/AppButton.vue @@ -0,0 +1,103 @@ + + + + + diff --git a/src/components/AppFooter.vue b/src/components/AppFooter.vue new file mode 100644 index 0000000..bfc8cc8 --- /dev/null +++ b/src/components/AppFooter.vue @@ -0,0 +1,146 @@ + + + + + diff --git a/src/components/AppHeader.vue b/src/components/AppHeader.vue new file mode 100644 index 0000000..45ce2f4 --- /dev/null +++ b/src/components/AppHeader.vue @@ -0,0 +1,138 @@ + + + + + diff --git a/src/components/CaseCard.vue b/src/components/CaseCard.vue new file mode 100644 index 0000000..5822a97 --- /dev/null +++ b/src/components/CaseCard.vue @@ -0,0 +1,125 @@ + + + + + diff --git a/src/components/ConfirmDialog.vue b/src/components/ConfirmDialog.vue new file mode 100644 index 0000000..1e73db1 --- /dev/null +++ b/src/components/ConfirmDialog.vue @@ -0,0 +1,132 @@ + + + + + diff --git a/src/components/FileList.vue b/src/components/FileList.vue new file mode 100644 index 0000000..680c34d --- /dev/null +++ b/src/components/FileList.vue @@ -0,0 +1,93 @@ + + + + + diff --git a/src/components/FilterPanel.vue b/src/components/FilterPanel.vue new file mode 100644 index 0000000..9cf3028 --- /dev/null +++ b/src/components/FilterPanel.vue @@ -0,0 +1,162 @@ + + + + + diff --git a/src/components/PaginationBar.vue b/src/components/PaginationBar.vue new file mode 100644 index 0000000..620e093 --- /dev/null +++ b/src/components/PaginationBar.vue @@ -0,0 +1,108 @@ + + + + + diff --git a/src/components/PasswordStrength.vue b/src/components/PasswordStrength.vue new file mode 100644 index 0000000..e923ac3 --- /dev/null +++ b/src/components/PasswordStrength.vue @@ -0,0 +1,88 @@ + + + + + diff --git a/src/components/QrCodePanel.vue b/src/components/QrCodePanel.vue new file mode 100644 index 0000000..e9f879a --- /dev/null +++ b/src/components/QrCodePanel.vue @@ -0,0 +1,114 @@ + + + + + diff --git a/src/components/SmsCodeInput.vue b/src/components/SmsCodeInput.vue new file mode 100644 index 0000000..90eae6d --- /dev/null +++ b/src/components/SmsCodeInput.vue @@ -0,0 +1,79 @@ + + + + + diff --git a/src/components/StatusTag.vue b/src/components/StatusTag.vue new file mode 100644 index 0000000..90985e2 --- /dev/null +++ b/src/components/StatusTag.vue @@ -0,0 +1,50 @@ + + + + + diff --git a/src/components/StepForm.vue b/src/components/StepForm.vue new file mode 100644 index 0000000..aac8ed1 --- /dev/null +++ b/src/components/StepForm.vue @@ -0,0 +1,86 @@ + + + + + diff --git a/src/components/UploadArea.vue b/src/components/UploadArea.vue new file mode 100644 index 0000000..e75f86a --- /dev/null +++ b/src/components/UploadArea.vue @@ -0,0 +1,100 @@ + + + + + diff --git a/src/components/index.ts b/src/components/index.ts new file mode 100644 index 0000000..7dff38a --- /dev/null +++ b/src/components/index.ts @@ -0,0 +1,15 @@ +export { default as AppHeader } from './AppHeader.vue' +export { default as AppFooter } from './AppFooter.vue' +export { default as AppButton } from './AppButton.vue' +export { default as CaseCard } from './CaseCard.vue' +export { default as FilterPanel } from './FilterPanel.vue' +export type { FilterChip } from './FilterPanel.vue' +export { default as PaginationBar } from './PaginationBar.vue' +export { default as StatusTag } from './StatusTag.vue' +export { default as FileList } from './FileList.vue' +export { default as UploadArea } from './UploadArea.vue' +export { default as StepForm } from './StepForm.vue' +export { default as SmsCodeInput } from './SmsCodeInput.vue' +export { default as PasswordStrength } from './PasswordStrength.vue' +export { default as ConfirmDialog } from './ConfirmDialog.vue' +export { default as QrCodePanel } from './QrCodePanel.vue' diff --git a/src/composables/useCountDown.ts b/src/composables/useCountDown.ts new file mode 100644 index 0000000..3e1ac66 --- /dev/null +++ b/src/composables/useCountDown.ts @@ -0,0 +1,32 @@ +import { computed, onScopeDispose, ref } from 'vue' + +/** 倒计时(验证码 60s、二维码失效 4:32 等通用) */ +export function useCountDown(defaultSeconds = 60) { + const seconds = ref(0) + let timer: ReturnType | null = null + + const isCounting = computed(() => seconds.value > 0) + + function start(total: number = defaultSeconds): void { + reset() + seconds.value = total + timer = setInterval(() => { + seconds.value -= 1 + if (seconds.value <= 0) { + reset() + } + }, 1000) + } + + function reset(): void { + if (timer !== null) { + clearInterval(timer) + timer = null + } + seconds.value = 0 + } + + onScopeDispose(reset) + + return { seconds, isCounting, start, reset } +} diff --git a/src/composables/useIntersection.ts b/src/composables/useIntersection.ts new file mode 100644 index 0000000..9465ec6 --- /dev/null +++ b/src/composables/useIntersection.ts @@ -0,0 +1,29 @@ +import { onMounted, onUnmounted, ref, type Ref } from 'vue' + +/** 元素可见性(配合图片懒加载/曝光统计) */ +export function useIntersection( + target: Ref, + options?: IntersectionObserverInit, +): { + isVisible: Ref +} { + const isVisible = ref(false) + let observer: IntersectionObserver | null = null + + onMounted(() => { + const el = target.value + if (!el) return + observer = new IntersectionObserver((entries) => { + const entry = entries[0] + if (entry) isVisible.value = entry.isIntersecting + }, options) + observer.observe(el) + }) + + onUnmounted(() => { + observer?.disconnect() + observer = null + }) + + return { isVisible } +} diff --git a/src/composables/useLoading.ts b/src/composables/useLoading.ts new file mode 100644 index 0000000..5b990d9 --- /dev/null +++ b/src/composables/useLoading.ts @@ -0,0 +1,21 @@ +import { ref, type Ref } from 'vue' + +/** 请求 loading 状态管理 */ +export function useLoading(initial = false): { + loading: Ref + withLoading: (fn: () => Promise) => Promise +} { + const loading = ref(initial) + + async function withLoading(fn: () => Promise): Promise { + loading.value = true + try { + return await fn() + } + finally { + loading.value = false + } + } + + return { loading, withLoading } +} diff --git a/src/composables/useScroll.ts b/src/composables/useScroll.ts new file mode 100644 index 0000000..0d04012 --- /dev/null +++ b/src/composables/useScroll.ts @@ -0,0 +1,26 @@ +import { computed, onMounted, onUnmounted, ref, type ComputedRef, type Ref } from 'vue' + +/** 滚动监听/吸顶/回到顶部 */ +export function useScroll(threshold = 200): { + scrollY: Ref + isAtTop: ComputedRef + showBackTop: ComputedRef + scrollToTop: (smooth?: boolean) => void +} { + const scrollY = ref(0) + const isAtTop = computed(() => scrollY.value <= 0) + const showBackTop = computed(() => scrollY.value > threshold) + + function onScroll(): void { + scrollY.value = window.scrollY + } + + function scrollToTop(smooth = true): void { + window.scrollTo({ top: 0, behavior: smooth ? 'smooth' : 'auto' }) + } + + onMounted(() => window.addEventListener('scroll', onScroll, { passive: true })) + onUnmounted(() => window.removeEventListener('scroll', onScroll)) + + return { scrollY, isAtTop, showBackTop, scrollToTop } +} diff --git a/src/directives/debounce.ts b/src/directives/debounce.ts new file mode 100644 index 0000000..bf780e2 --- /dev/null +++ b/src/directives/debounce.ts @@ -0,0 +1,35 @@ +import type { Directive } from 'vue' + +interface DebounceBinding { + handler: (...args: unknown[]) => void + delay?: number +} + +type DebounceValue = DebounceBinding['handler'] | DebounceBinding + +const listenerMap = new WeakMap() + +function normalize(value: DebounceValue): DebounceBinding { + return typeof value === 'function' ? { handler: value } : value +} + +/** v-debounce 防抖点击:v-debounce="fn" 或 v-debounce="{ handler: fn, delay: 500 }" */ +const debounce: Directive = { + mounted(el, binding) { + const { handler, delay = 300 } = normalize(binding.value) + let timer: ReturnType | null = null + const listener: EventListener = () => { + if (timer !== null) clearTimeout(timer) + timer = setTimeout(() => handler(), delay) + } + listenerMap.set(el, listener) + el.addEventListener('click', listener) + }, + unmounted(el) { + const listener = listenerMap.get(el) + if (listener) el.removeEventListener('click', listener) + listenerMap.delete(el) + }, +} + +export default debounce diff --git a/src/directives/index.ts b/src/directives/index.ts new file mode 100644 index 0000000..5f55f82 --- /dev/null +++ b/src/directives/index.ts @@ -0,0 +1,12 @@ +import type { App } from 'vue' +import lazy from './lazy' +import debounce from './debounce' +import throttle from './throttle' + +/** main.ts 调用:统一注册自定义指令 */ +export function setupDirectives(app: App): void { + app + .directive('lazy', lazy) + .directive('debounce', debounce) + .directive('throttle', throttle) +} diff --git a/src/directives/lazy.ts b/src/directives/lazy.ts new file mode 100644 index 0000000..7898922 --- /dev/null +++ b/src/directives/lazy.ts @@ -0,0 +1,27 @@ +import type { Directive } from 'vue' + +const observerMap = new WeakMap() + +/** v-lazy 图片懒加载:v-lazy="图片地址" */ +const lazy: Directive = { + mounted(el, binding) { + if (!(el instanceof HTMLImageElement)) return + const target = el + const observer = new IntersectionObserver((entries) => { + const entry = entries[0] + if (entry?.isIntersecting) { + if (binding.value) target.src = binding.value + observer.disconnect() + observerMap.delete(el) + } + }) + observerMap.set(el, observer) + observer.observe(el) + }, + unmounted(el) { + observerMap.get(el)?.disconnect() + observerMap.delete(el) + }, +} + +export default lazy diff --git a/src/directives/throttle.ts b/src/directives/throttle.ts new file mode 100644 index 0000000..372390f --- /dev/null +++ b/src/directives/throttle.ts @@ -0,0 +1,38 @@ +import type { Directive } from 'vue' + +interface ThrottleBinding { + handler: (...args: unknown[]) => void + interval?: number +} + +type ThrottleValue = ThrottleBinding['handler'] | ThrottleBinding + +const listenerMap = new WeakMap() + +function normalize(value: ThrottleValue): ThrottleBinding { + return typeof value === 'function' ? { handler: value } : value +} + +/** v-throttle 节流点击:v-throttle="fn" 或 v-throttle="{ handler: fn, interval: 500 }" */ +const throttle: Directive = { + mounted(el, binding) { + const { handler, interval = 500 } = normalize(binding.value) + let lastTime = 0 + const listener: EventListener = () => { + const now = Date.now() + if (now - lastTime >= interval) { + lastTime = now + handler() + } + } + listenerMap.set(el, listener) + el.addEventListener('click', listener) + }, + unmounted(el) { + const listener = listenerMap.get(el) + if (listener) el.removeEventListener('click', listener) + listenerMap.delete(el) + }, +} + +export default throttle diff --git a/src/env.d.ts b/src/env.d.ts new file mode 100644 index 0000000..9094bd0 --- /dev/null +++ b/src/env.d.ts @@ -0,0 +1,10 @@ +/// + +interface ImportMetaEnv { + /** API 基础地址 */ + readonly VITE_API_BASE_URL: string +} + +interface ImportMeta { + readonly env: ImportMetaEnv +} diff --git a/src/layout/components/FloatingActions.vue b/src/layout/components/FloatingActions.vue new file mode 100644 index 0000000..f85f850 --- /dev/null +++ b/src/layout/components/FloatingActions.vue @@ -0,0 +1,58 @@ + + + + + diff --git a/src/layout/index.vue b/src/layout/index.vue new file mode 100644 index 0000000..0056906 --- /dev/null +++ b/src/layout/index.vue @@ -0,0 +1,23 @@ + + + + + diff --git a/src/main.ts b/src/main.ts new file mode 100644 index 0000000..60ab2b9 --- /dev/null +++ b/src/main.ts @@ -0,0 +1,15 @@ +import { createApp } from 'vue' +import App from './App.vue' +import pinia from '@/stores' +import router from '@/router' +import { setupDirectives } from '@/directives' +import 'element-plus/dist/index.css' +import '@/styles/index.less' + +const app = createApp(App) + +app.use(pinia) +app.use(router) +setupDirectives(app) + +app.mount('#app') diff --git a/src/router/guard.ts b/src/router/guard.ts new file mode 100644 index 0000000..cebccec --- /dev/null +++ b/src/router/guard.ts @@ -0,0 +1,16 @@ +import type { Router } from 'vue-router' +import { storage } from '@/utils/storage' + +/** 路由守卫:登录态校验 + document.title */ +export function setupGuard(router: Router): void { + router.beforeEach((to) => { + if (to.meta.requiresAuth && !storage.get('token')) { + return { path: '/login', query: { redirect: to.fullPath } } + } + return true + }) + + router.afterEach((to) => { + document.title = to.meta.title ? `${to.meta.title} - 虫子智造` : '虫子智造' + }) +} diff --git a/src/router/index.ts b/src/router/index.ts new file mode 100644 index 0000000..2cd545e --- /dev/null +++ b/src/router/index.ts @@ -0,0 +1,13 @@ +import { createRouter, createWebHistory } from 'vue-router' +import { routes } from './routes' +import { setupGuard } from './guard' + +const router = createRouter({ + history: createWebHistory(), + routes, + scrollBehavior: () => ({ top: 0 }), +}) + +setupGuard(router) + +export default router diff --git a/src/router/routes.ts b/src/router/routes.ts new file mode 100644 index 0000000..6bc1b08 --- /dev/null +++ b/src/router/routes.ts @@ -0,0 +1,65 @@ +import type { RouteRecordRaw } from 'vue-router' + +/** + * 公共布局(AppHeader + router-view + AppFooter + FloatingActions)下的路由 + * 完整规划见 docs/page-architecture.md 第 2 节 + * M3–M10 视图由子代理并行实现中,交付后本表即为终态 + */ +const layoutRoutes: RouteRecordRaw = { + path: '/', + component: () => import('@/layout/index.vue'), + children: [ + // ── 公开 ── + { path: '', name: 'Home', component: () => import('@/views/home'), meta: { title: '首页' } }, + { path: 'business/:type', name: 'BusinessDetail', component: () => import('@/views/home/detail.vue'), meta: { title: '业务详情' } }, + { path: 'cases', name: 'Cases', component: () => import('@/views/cases'), meta: { title: '成功案例库' } }, + { path: 'cases/:id', name: 'CaseDetail', component: () => import('@/views/cases/detail.vue'), meta: { title: '案例详情' } }, + { path: 'demand', name: 'Demand', component: () => import('@/views/demand'), meta: { title: '需求提报' } }, + + // ── 登录区 ── + { path: 'project', name: 'Project', component: () => import('@/views/project'), meta: { title: '项目中心', requiresAuth: true } }, + { path: 'project/:id', name: 'ProjectDetail', component: () => import('@/views/project/detail.vue'), meta: { title: '项目详情', requiresAuth: true } }, + { path: 'project/:id/after-sales', name: 'AfterSales', component: () => import('@/views/project/after-sales.vue'), meta: { title: '申请售后', requiresAuth: true } }, + { path: 'contract', name: 'Contract', component: () => import('@/views/contract'), meta: { title: '合同中心', requiresAuth: true } }, + { path: 'contract/:id', name: 'ContractDetail', component: () => import('@/views/contract/detail.vue'), meta: { title: '合同详情', requiresAuth: true } }, + { path: 'contract/:id/sign', name: 'ContractSign', component: () => import('@/views/contract/sign.vue'), meta: { title: '签署合同', requiresAuth: true } }, + { path: 'finance', name: 'Finance', component: () => import('@/views/finance'), meta: { title: '财务中心', requiresAuth: true } }, + { path: 'finance/bills', name: 'Bills', component: () => import('@/views/finance/bills.vue'), meta: { title: '账单明细', requiresAuth: true } }, + { path: 'finance/bills/:id', name: 'BillDetail', component: () => import('@/views/finance/bill-detail.vue'), meta: { title: '账单详情', requiresAuth: true } }, + { path: 'finance/bills/:id/pay', name: 'BillPay', component: () => import('@/views/finance/pay.vue'), meta: { title: '扫码支付', requiresAuth: true } }, + { path: 'finance/bills/:id/pay/result', name: 'PayResult', component: () => import('@/views/finance/pay-result.vue'), meta: { title: '支付结果', requiresAuth: true } }, + { path: 'finance/invoices', name: 'Invoices', component: () => import('@/views/finance/invoices.vue'), meta: { title: '发票申请', requiresAuth: true } }, + { path: 'finance/invoice-titles', name: 'InvoiceTitles', component: () => import('@/views/finance/invoice-titles.vue'), meta: { title: '发票抬头', requiresAuth: true } }, + { path: 'message', name: 'Message', component: () => import('@/views/message'), meta: { title: '消息中心', requiresAuth: true } }, + { path: 'message/:id', name: 'MessageDetail', component: () => import('@/views/message/detail.vue'), meta: { title: '通知详情', requiresAuth: true } }, + { path: 'service', name: 'Service', component: () => import('@/views/service'), meta: { title: '在线客服', requiresAuth: true } }, + + // ── 个人中心(左菜单嵌套)── + { + path: 'profile', + component: () => import('@/views/profile'), + meta: { title: '个人中心', requiresAuth: true }, + children: [ + { path: '', redirect: '/profile/security' }, + { path: 'security', name: 'ProfileSecurity', component: () => import('@/views/profile/security.vue'), meta: { title: '账号安全', requiresAuth: true } }, + { path: 'address', name: 'ProfileAddress', component: () => import('@/views/profile/address.vue'), meta: { title: '地址管理', requiresAuth: true } }, + { path: 'favorites', name: 'ProfileFavorites', component: () => import('@/views/profile/favorites.vue'), meta: { title: '收藏列表', requiresAuth: true } }, + { path: 'qualifications', name: 'ProfileQualifications', component: () => import('@/views/profile/qualifications.vue'), meta: { title: '资质列表', requiresAuth: true } }, + { path: 'feedback', name: 'ProfileFeedback', component: () => import('@/views/profile/feedback.vue'), meta: { title: '意见反馈', requiresAuth: true } }, + { path: 'help', name: 'ProfileHelp', component: () => import('@/views/profile/help.vue'), meta: { title: '帮助中心', requiresAuth: true } }, + { path: 'about', name: 'ProfileAbout', component: () => import('@/views/profile/about.vue'), meta: { title: '关于我们', requiresAuth: true } }, + { path: 'hotline', name: 'ProfileHotline', component: () => import('@/views/profile/hotline.vue'), meta: { title: '专属客服热线', requiresAuth: true } }, + ], + }, + ], +} + +/** 无全局布局的路由(登录注册独立品牌页 + 404) */ +const plainRoutes: RouteRecordRaw[] = [ + { path: '/login', name: 'Login', component: () => import('@/views/login'), meta: { title: '登录' } }, + { path: '/register', name: 'Register', component: () => import('@/views/login/register.vue'), meta: { title: '注册' } }, + { path: '/reset-password', name: 'ResetPassword', component: () => import('@/views/login/reset.vue'), meta: { title: '找回密码' } }, + { path: '/:pathMatch(.*)*', name: 'NotFound', component: () => import('@/views/404.vue'), meta: { title: '页面不存在' } }, +] + +export const routes: RouteRecordRaw[] = [layoutRoutes, ...plainRoutes] diff --git a/src/stores/index.ts b/src/stores/index.ts new file mode 100644 index 0000000..8a05980 --- /dev/null +++ b/src/stores/index.ts @@ -0,0 +1,5 @@ +import { createPinia } from 'pinia' + +const pinia = createPinia() + +export default pinia diff --git a/src/stores/modules/cases.ts b/src/stores/modules/cases.ts new file mode 100644 index 0000000..b7749c3 --- /dev/null +++ b/src/stores/modules/cases.ts @@ -0,0 +1,84 @@ +import { defineStore } from 'pinia' +import { ref } from 'vue' +import type { BusinessType, PageResult } from '@/types/api' +import type { Case, CaseQuery } from '@/types/business/cases' +import { getCaseCounts, getCaseDetail, getCaseList, toggleFavorite as toggleFavoriteApi } from '@/api/cases' + +function createDefaultQuery(): CaseQuery { + return { + category: 'all', + industries: [], + scales: [], + dateRange: null, + keyword: '', + page: 1, + pageSize: 8, + } +} + +/** 案例库数据(列表 / 筛选 / 分页 / 收藏) */ +export const useCasesStore = defineStore('cases', () => { + const query = ref(createDefaultQuery()) + const list = ref([]) + const total = ref(0) + const counts = ref>>({}) + const currentCase = ref(null) + + async function fetchCases(): Promise { + const result: PageResult = await getCaseList(query.value) + list.value = result.list + total.value = result.total + } + + async function fetchCounts(): Promise { + counts.value = await getCaseCounts() + } + + function setQuery(next: CaseQuery): void { + query.value = next + } + + /** 确定筛选(重置页码后取数) */ + async function search(): Promise { + query.value = { ...query.value, page: 1 } + await fetchCases() + } + + /** 重置全部条件 */ + async function resetQuery(): Promise { + query.value = createDefaultQuery() + await fetchCases() + } + + async function changePage(page: number): Promise { + query.value = { ...query.value, page } + await fetchCases() + } + + async function fetchCaseDetail(id: string): Promise { + currentCase.value = await getCaseDetail(id) + } + + async function toggleFavorite(id: string): Promise { + const updated = await toggleFavoriteApi(id) + const index = list.value.findIndex((item) => item.id === id) + if (index >= 0) list.value[index] = updated + if (currentCase.value?.id === id) currentCase.value = updated + } + + return { + query, + list, + total, + counts, + currentCase, + fetchCases, + fetchCounts, + setQuery, + search, + resetQuery, + changePage, + fetchCaseDetail, + toggleFavorite, + } +}) diff --git a/src/stores/modules/contract.ts b/src/stores/modules/contract.ts new file mode 100644 index 0000000..fa3a5ac --- /dev/null +++ b/src/stores/modules/contract.ts @@ -0,0 +1,79 @@ +import { defineStore } from 'pinia' +import { ref } from 'vue' +import type { PageResult } from '@/types/api' +import type { Contract, ContractQuery } from '@/types/business/contract' +import { + getContractDetail, + getContractList, + getSignQr, + refreshSignQr, +} from '@/api/contract' + +function createDefaultQuery(): ContractQuery { + return { + keyword: '', + page: 1, + pageSize: 8, + } +} + +/** 合同中心数据(列表 / 筛选 / 分页 / 详情 / 签署二维码) */ +export const useContractStore = defineStore('contract', () => { + const query = ref(createDefaultQuery()) + const list = ref([]) + const total = ref(0) + const current = ref(null) + const signExpires = ref(272) + + async function fetchList(): Promise { + const result: PageResult = await getContractList(query.value) + list.value = result.list + total.value = result.total + } + + async function fetchDetail(id: string): Promise { + current.value = await getContractDetail(id) + } + + function setQuery(next: ContractQuery): void { + query.value = next + } + + /** 确定筛选(重置页码后取数) */ + async function search(): Promise { + query.value = { ...query.value, page: 1 } + await fetchList() + } + + /** 重置全部条件 */ + async function resetQuery(): Promise { + query.value = createDefaultQuery() + await fetchList() + } + + async function changePage(page: number): Promise { + query.value = { ...query.value, page } + await fetchList() + } + + /** 获取/刷新签署二维码(refresh=true 时走刷新接口) */ + async function fetchSignQr(id: string, refresh = false): Promise { + const result = refresh ? await refreshSignQr(id) : await getSignQr(id) + signExpires.value = result.expiresInSeconds + } + + return { + query, + list, + total, + current, + signExpires, + fetchList, + fetchDetail, + setQuery, + search, + resetQuery, + changePage, + fetchSignQr, + } +}) diff --git a/src/stores/modules/demand.ts b/src/stores/modules/demand.ts new file mode 100644 index 0000000..6050737 --- /dev/null +++ b/src/stores/modules/demand.ts @@ -0,0 +1,126 @@ +import { defineStore } from 'pinia' +import { ref } from 'vue' +import { ElMessage } from 'element-plus' +import type { Demand } from '@/types/business/demand' +import type { FileInfo } from '@/types/api' +import { formatDate } from '@/utils/format' +import { getDraftDemand, saveDraft as saveDraftApi, submitDemand } from '@/api/demand' + +function createDefaultForm(): Demand { + return { + businessType: 'internet', + projectName: '', + attachments: [], + coreDescription: '', + expectedDuration: '', + budgetRange: '', + userName: '王永志', + userPhone: '1866****9618', + remark: '', + status: 'draft', + } +} + +/** 需求提报数据(M3:表单 / 步骤 / 提交与草稿) */ +export const useDemandStore = defineStore('demand', () => { + const form = ref(createDefaultForm()) + const step = ref(1) + const loading = ref(false) + + function setStep(next: number): void { + step.value = next + } + + function patchForm(partial: Partial): void { + form.value = { ...form.value, ...partial } + } + + /** File → FileInfo(mock:url 为空,uploadTime 取当前时间) */ + function addAttachment(file: File): void { + const info: FileInfo = { + name: file.name, + size: file.size, + uploadTime: formatDate(new Date()), + url: '', + } + form.value.attachments = [...form.value.attachments, info] + } + + function removeAttachment(target: FileInfo): void { + form.value.attachments = form.value.attachments.filter( + (item) => !(item.name === target.name && item.uploadTime === target.uploadTime), + ) + } + + /** 提交需求:必填校验通过后调 api,成功返回 true(页面可据此跳转) */ + async function submit(): Promise { + if (!form.value.businessType) { + ElMessage.warning('请选择业务类型') + return false + } + if (!form.value.projectName.trim()) { + ElMessage.warning('请填写项目名称') + return false + } + if (!form.value.coreDescription.trim()) { + ElMessage.warning('请填写核心需求描述') + return false + } + loading.value = true + try { + await submitDemand(form.value) + form.value.status = 'submitted' + ElMessage.success('提交后我们的专业顾问将在一个工作日内与您联系') + return true + } + catch (error) { + ElMessage.error(error instanceof Error ? error.message : '提交失败,请稍后重试') + return false + } + finally { + loading.value = false + } + } + + /** 保存草稿到 sessionStorage */ + async function saveDraft(): Promise { + loading.value = true + try { + await saveDraftApi(form.value) + form.value.status = 'draft' + ElMessage.success('草稿已保存') + } + catch (error) { + ElMessage.error(error instanceof Error ? error.message : '草稿保存失败') + } + finally { + loading.value = false + } + } + + /** 恢复本地草稿(无草稿则保持默认表单) */ + async function loadDraft(): Promise { + const draft = await getDraftDemand() + if (draft) form.value = { ...createDefaultForm(), ...draft } + } + + /** 重置表单与步骤 */ + function reset(): void { + form.value = createDefaultForm() + step.value = 1 + } + + return { + form, + step, + loading, + setStep, + patchForm, + addAttachment, + removeAttachment, + submit, + saveDraft, + loadDraft, + reset, + } +}) diff --git a/src/stores/modules/finance.ts b/src/stores/modules/finance.ts new file mode 100644 index 0000000..0fdc0fe --- /dev/null +++ b/src/stores/modules/finance.ts @@ -0,0 +1,207 @@ +import { defineStore } from 'pinia' +import { ref } from 'vue' +import type { PageResult } from '@/types/api' +import type { + Bill, + BillQuery, + FinanceStats, + Invoice, + InvoiceQuery, + InvoiceTitle, + InvoiceTitlePayload, + PayMethod, + Payment, + PaymentResult, +} from '@/types/business/finance' +import { + applyInvoice as applyInvoiceApi, + createInvoiceTitle, + createPayment as createPaymentApi, + deleteInvoiceTitle, + exportReport as exportReportApi, + getBillDetail, + getBillList, + getFinanceStats, + getInvoiceList, + getInvoiceTitles, + getPayResult, + setDefaultTitle as setDefaultTitleApi, + switchPaymentMethod as switchPaymentMethodApi, + updateInvoiceTitle, +} from '@/api/finance' + +function createDefaultBillQuery(): BillQuery { + return { keyword: '', status: 'all', page: 1, pageSize: 6 } +} + +function createDefaultInvoiceQuery(): InvoiceQuery { + return { keyword: '', status: 'all', page: 1, pageSize: 6 } +} + +/** 财务中心数据(统计/账单/支付/发票/抬头) */ +export const useFinanceStore = defineStore('finance', () => { + // —— 统计 + const stats = ref(null) + + // —— 账单列表 + const list = ref([]) + const total = ref(0) + const query = ref(createDefaultBillQuery()) + const currentBill = ref(null) + + // —— 支付 + const payment = ref(null) + const result = ref(null) + + // —— 发票 + const invoices = ref([]) + const invoiceTotal = ref(0) + const invoiceQuery = ref(createDefaultInvoiceQuery()) + + // —— 发票抬头 + const titles = ref([]) + + // —— 统计 + async function fetchStats(period?: string): Promise { + stats.value = await getFinanceStats(period) + } + + // —— 账单 + async function fetchList(): Promise { + const pageData: PageResult = await getBillList(query.value) + list.value = pageData.list + total.value = pageData.total + } + + function setQuery(next: BillQuery): void { + query.value = next + } + + /** 确定筛选(重置页码后取数) */ + async function search(): Promise { + query.value = { ...query.value, page: 1 } + await fetchList() + } + + /** 重置全部条件 */ + async function resetQuery(): Promise { + query.value = createDefaultBillQuery() + await fetchList() + } + + async function changePage(page: number): Promise { + query.value = { ...query.value, page } + await fetchList() + } + + async function fetchDetail(id: string): Promise { + currentBill.value = await getBillDetail(id) + } + + // —— 支付 + async function createPayment(billId: string, method: PayMethod): Promise { + payment.value = await createPaymentApi(billId, method) + } + + /** 切换支付方式(刷新二维码) */ + async function switchPaymentMethod(billId: string, method: PayMethod): Promise { + payment.value = await switchPaymentMethodApi(billId, method) + } + + async function fetchResult(billId: string): Promise { + result.value = await getPayResult(billId) + } + + // —— 发票 + async function fetchInvoices(): Promise { + const pageData: PageResult = await getInvoiceList(invoiceQuery.value) + invoices.value = pageData.list + invoiceTotal.value = pageData.total + } + + function setInvoiceQuery(next: InvoiceQuery): void { + invoiceQuery.value = next + } + + /** 发票确定筛选(重置页码后取数) */ + async function searchInvoices(): Promise { + invoiceQuery.value = { ...invoiceQuery.value, page: 1 } + await fetchInvoices() + } + + async function changeInvoicePage(page: number): Promise { + invoiceQuery.value = { ...invoiceQuery.value, page } + await fetchInvoices() + } + + /** 申请开票(状态 → 开票中) */ + async function applyInvoice(id: string): Promise { + const updated = await applyInvoiceApi(id) + const index = invoices.value.findIndex((item) => item.id === id) + if (index >= 0) invoices.value[index] = updated + } + + // —— 发票抬头 + async function fetchTitles(): Promise { + titles.value = await getInvoiceTitles() + } + + async function createTitle(payload: InvoiceTitlePayload): Promise { + await createInvoiceTitle(payload) + await fetchTitles() + } + + async function updateTitle(id: string, payload: InvoiceTitlePayload): Promise { + await updateInvoiceTitle(id, payload) + await fetchTitles() + } + + async function deleteTitle(id: string): Promise { + await deleteInvoiceTitle(id) + await fetchTitles() + } + + async function setDefaultTitle(id: string): Promise { + titles.value = await setDefaultTitleApi(id) + } + + // —— 报表导出 + async function exportReport(): Promise { + await exportReportApi() + } + + return { + stats, + list, + total, + query, + currentBill, + payment, + result, + invoices, + invoiceTotal, + invoiceQuery, + titles, + fetchStats, + fetchList, + setQuery, + search, + resetQuery, + changePage, + fetchDetail, + createPayment, + switchPaymentMethod, + fetchResult, + fetchInvoices, + setInvoiceQuery, + searchInvoices, + changeInvoicePage, + applyInvoice, + fetchTitles, + createTitle, + updateTitle, + deleteTitle, + setDefaultTitle, + export: exportReport, + } +}) diff --git a/src/stores/modules/home.ts b/src/stores/modules/home.ts new file mode 100644 index 0000000..ab3d846 --- /dev/null +++ b/src/stores/modules/home.ts @@ -0,0 +1,46 @@ +import { defineStore } from 'pinia' +import { ref } from 'vue' +import type { BusinessType } from '@/types/api' +import type { Case } from '@/types/business/cases' +import type { BusinessLine, HomeStats } from '@/types/business/home' +import { getBusinessDetail, getBusinessLines, getFeaturedCases, getHomeStats } from '@/api/home' + +/** 首页数据(数据层:取数 + 状态,页面只调本 store) */ +export const useHomeStore = defineStore('home', () => { + const stats = ref({ pending: 0, inProgress: 0, total: 0 }) + const businessLines = ref([]) + const featuredCases = ref([]) + const currentBusiness = ref(null) + + /** 首页初始化:看板 + 业务线 + 案例精选 */ + async function fetchHomeData(): Promise { + const [homeStats, lines, cases] = await Promise.all([ + getHomeStats(), + getBusinessLines(), + getFeaturedCases(), + ]) + stats.value = homeStats + businessLines.value = lines + featuredCases.value = cases + } + + /** 按业务类型切换案例精选 */ + async function fetchFeaturedCases(category: BusinessType | 'all' = 'all'): Promise { + featuredCases.value = await getFeaturedCases(category) + } + + /** 业务详情 */ + async function fetchBusinessDetail(type: BusinessType): Promise { + currentBusiness.value = await getBusinessDetail(type) + } + + return { + stats, + businessLines, + featuredCases, + currentBusiness, + fetchHomeData, + fetchFeaturedCases, + fetchBusinessDetail, + } +}) diff --git a/src/stores/modules/message.ts b/src/stores/modules/message.ts new file mode 100644 index 0000000..9df92ca --- /dev/null +++ b/src/stores/modules/message.ts @@ -0,0 +1,112 @@ +import { defineStore } from 'pinia' +import { ref } from 'vue' +import { ElMessage } from 'element-plus' +import type { PageResult } from '@/types/api' +import type { Message, MessageQuery } from '@/types/business/message' +import { + getMessageDetail, + getMessageList, + getTabCounts, + getUnreadCount, + markAllRead as markAllReadApi, + markRead as markReadApi, +} from '@/api/message' + +/** tab 计数结构 */ +export interface MessageTabCounts { + all: number + 在线客服: number + 状态通知: number + 系统通知: number +} + +function createDefaultQuery(): MessageQuery { + return { + keyword: '', + type: 'all', + page: 1, + pageSize: 10, + } +} + +/** 消息中心数据(列表 / 筛选 / 分页 / 已读) */ +export const useMessageStore = defineStore('message', () => { + const query = ref(createDefaultQuery()) + const list = ref([]) + const total = ref(0) + const current = ref(null) + const unreadCount = ref(0) + const tabCounts = ref({ all: 0, 在线客服: 0, 状态通知: 0, 系统通知: 0 }) + + async function fetchList(): Promise { + const result: PageResult = await getMessageList(query.value) + list.value = result.list + total.value = result.total + } + + async function fetchDetail(id: string): Promise { + const detail = await getMessageDetail(id) + current.value = detail + if (detail && !detail.isRead) { + await markReadApi(id) + detail.isRead = true + if (unreadCount.value > 0) unreadCount.value -= 1 + await fetchTabCounts() + } + } + + function setQuery(next: MessageQuery): void { + query.value = next + } + + /** 确定搜索(重置页码后取数) */ + async function search(): Promise { + query.value = { ...query.value, page: 1 } + await fetchList() + } + + /** 重置全部条件 */ + async function resetQuery(): Promise { + query.value = createDefaultQuery() + await fetchList() + } + + async function changePage(page: number): Promise { + query.value = { ...query.value, page } + await fetchList() + } + + /** 全部标记已读 */ + async function markAllRead(): Promise { + await markAllReadApi() + ElMessage.success('已全部标记为已读') + await Promise.all([fetchList(), fetchUnreadCount(), fetchTabCounts()]) + if (current.value) current.value.isRead = true + } + + async function fetchUnreadCount(): Promise { + unreadCount.value = await getUnreadCount() + } + + async function fetchTabCounts(): Promise { + tabCounts.value = await getTabCounts() + } + + return { + query, + list, + total, + current, + unreadCount, + tabCounts, + fetchList, + fetchDetail, + setQuery, + search, + resetQuery, + changePage, + markAllRead, + fetchUnreadCount, + fetchTabCounts, + } +}) diff --git a/src/stores/modules/profile.ts b/src/stores/modules/profile.ts new file mode 100644 index 0000000..60dbd71 --- /dev/null +++ b/src/stores/modules/profile.ts @@ -0,0 +1,258 @@ +import { defineStore } from 'pinia' +import { ref } from 'vue' +import { ElMessage } from 'element-plus' +import type { BusinessType, PageResult } from '@/types/api' +import type { Case } from '@/types/business/cases' +import type { + Address, + ChangePasswordParams, + Feedback, + FavoritesQuery, + HelpFaqGroup, + ListQuery, + Profile, + Qualification, + QualificationInput, +} from '@/types/business/profile' +import { + bindEmail, + bindPhone, + changePassword, + createAddress, + createQualification, + deleteAccount as deleteAccountApi, + deleteAddress, + deleteQualification, + getAddresses, + getCaseCounts, + getFavorites, + getHelpFaqs, + getProfile, + getQualifications, + setDefaultAddress, + submitFeedback as submitFeedbackApi, + unbindEmail, + updateAddress, + updateNickname, + updateQualification, + verifyIdentity, +} from '@/api/profile' +import { storage } from '@/utils/storage' + +function createDefaultAddressQuery(): ListQuery { + return { page: 1, pageSize: 6 } +} + +function createDefaultQualificationQuery(): ListQuery { + return { page: 1, pageSize: 6 } +} + +function createDefaultFavoritesQuery(): FavoritesQuery { + return { page: 1, pageSize: 8, keyword: '', category: 'all' } +} + +/** 个人中心数据(资料/安全/地址/收藏/资质/反馈/帮助) */ +export const useProfileStore = defineStore('profile', () => { + // ── 账号资料 ── + const profile = ref(null) + + async function fetchProfile(): Promise { + profile.value = await getProfile() + } + + async function saveNickname(name: string): Promise { + profile.value = await updateNickname(name) + ElMessage.success('昵称修改成功') + } + + // ── 账号安全 ── + async function changePasswordAction(params: ChangePasswordParams): Promise { + await changePassword(params) + profile.value = await getProfile() + ElMessage.success('密码修改成功') + } + + async function bindPhoneAction(phone: string, code: string): Promise { + profile.value = await bindPhone(phone, code) + ElMessage.success('手机号绑定成功') + } + + async function bindEmailAction(email: string, code: string): Promise { + profile.value = await bindEmail(email, code) + ElMessage.success('邮箱绑定成功') + } + + async function unbindEmailAction(): Promise { + profile.value = await unbindEmail() + ElMessage.success('已取消邮箱绑定') + } + + async function verifyIdentityAction(phone: string, code: string): Promise { + await verifyIdentity(phone, code) + } + + async function deleteAccountAction(): Promise { + await deleteAccountApi() + storage.remove('token') + ElMessage.success('注销成功') + } + + // ── 收货地址 ── + const addresses = ref([]) + const addressTotal = ref(0) + const addressQuery = ref(createDefaultAddressQuery()) + + async function fetchAddresses(): Promise { + const result: PageResult
= await getAddresses(addressQuery.value) + addresses.value = result.list + addressTotal.value = result.total + } + + async function createAddressAction(input: Omit): Promise { + await createAddress(input) + ElMessage.success('新增地址成功') + addressQuery.value = { ...addressQuery.value, page: 1 } + await fetchAddresses() + } + + async function updateAddressAction(id: string, input: Omit): Promise { + await updateAddress(id, input) + ElMessage.success('地址保存成功') + await fetchAddresses() + } + + async function deleteAddressAction(id: string): Promise { + await deleteAddress(id) + ElMessage.success('地址删除成功') + await fetchAddresses() + } + + async function setDefaultAddressAction(id: string): Promise { + await setDefaultAddress(id) + ElMessage.success('已设为默认地址') + await fetchAddresses() + } + + async function changeAddressPage(page: number): Promise { + addressQuery.value = { ...addressQuery.value, page } + await fetchAddresses() + } + + // ── 我的收藏 ── + const favorites = ref([]) + const favoritesTotal = ref(0) + const favoritesCounts = ref>>({}) + const favoritesQuery = ref(createDefaultFavoritesQuery()) + + async function fetchFavorites(): Promise { + const result: PageResult = await getFavorites(favoritesQuery.value) + favorites.value = result.list + favoritesTotal.value = result.total + } + + async function fetchFavoritesCounts(): Promise { + favoritesCounts.value = await getCaseCounts() + } + + async function searchFavorites(patch: Partial): Promise { + favoritesQuery.value = { ...favoritesQuery.value, ...patch, page: 1 } + await fetchFavorites() + } + + async function resetFavorites(): Promise { + favoritesQuery.value = createDefaultFavoritesQuery() + await fetchFavorites() + } + + async function changeFavoritesPage(page: number): Promise { + favoritesQuery.value = { ...favoritesQuery.value, page } + await fetchFavorites() + } + + // ── 资质管理 ── + const qualifications = ref([]) + const qualificationTotal = ref(0) + const qualificationQuery = ref(createDefaultQualificationQuery()) + + async function fetchQualifications(): Promise { + const result: PageResult = await getQualifications(qualificationQuery.value) + qualifications.value = result.list + qualificationTotal.value = result.total + } + + async function createQualificationAction(input: QualificationInput): Promise { + await createQualification(input) + ElMessage.success('资质提交成功') + qualificationQuery.value = { ...qualificationQuery.value, page: 1 } + await fetchQualifications() + } + + async function updateQualificationAction(id: string, input: QualificationInput): Promise { + await updateQualification(id, input) + ElMessage.success('资质保存成功') + await fetchQualifications() + } + + async function deleteQualificationAction(id: string): Promise { + await deleteQualification(id) + ElMessage.success('资质删除成功') + await fetchQualifications() + } + + async function changeQualificationPage(page: number): Promise { + qualificationQuery.value = { ...qualificationQuery.value, page } + await fetchQualifications() + } + + // ── 意见反馈 / 帮助中心 ── + async function submitFeedbackAction(feedback: Feedback): Promise { + await submitFeedbackApi(feedback) + } + + const helpFaqs = ref([]) + + async function fetchHelpFaqs(): Promise { + helpFaqs.value = await getHelpFaqs() + } + + return { + profile, + fetchProfile, + saveNickname, + changePassword: changePasswordAction, + bindPhone: bindPhoneAction, + bindEmail: bindEmailAction, + unbindEmail: unbindEmailAction, + verifyIdentity: verifyIdentityAction, + deleteAccount: deleteAccountAction, + addresses, + addressTotal, + addressQuery, + fetchAddresses, + createAddress: createAddressAction, + updateAddress: updateAddressAction, + deleteAddress: deleteAddressAction, + setDefaultAddress: setDefaultAddressAction, + changeAddressPage, + favorites, + favoritesTotal, + favoritesCounts, + favoritesQuery, + fetchFavorites, + fetchFavoritesCounts, + searchFavorites, + resetFavorites, + changeFavoritesPage, + qualifications, + qualificationTotal, + qualificationQuery, + fetchQualifications, + createQualification: createQualificationAction, + updateQualification: updateQualificationAction, + deleteQualification: deleteQualificationAction, + changeQualificationPage, + submitFeedback: submitFeedbackAction, + helpFaqs, + fetchHelpFaqs, + } +}) diff --git a/src/stores/modules/project.ts b/src/stores/modules/project.ts new file mode 100644 index 0000000..f23da0e --- /dev/null +++ b/src/stores/modules/project.ts @@ -0,0 +1,106 @@ +import { defineStore } from 'pinia' +import { ref } from 'vue' +import { ElMessage } from 'element-plus' +import type { PageResult } from '@/types/api' +import type { AfterSalesPayload, Project, ProjectQuery } from '@/types/business/project' +import { + cancelAfterSales as cancelAfterSalesApi, + deleteProjectFile, + getProjectDetail, + getProjectList, + submitAfterSales as submitAfterSalesApi, + uploadProjectFile, +} from '@/api/project' + +function createDefaultQuery(): ProjectQuery { + return { + keyword: '', + businessType: 'all', + status: 'all', + timeShortcut: 'all', + page: 1, + pageSize: 8, + } +} + +/** 项目中心数据(列表 / 筛选 / 分页 / 详情 / 文件 / 售后) */ +export const useProjectStore = defineStore('project', () => { + const query = ref(createDefaultQuery()) + const list = ref([]) + const total = ref(0) + const current = ref(null) + + async function fetchList(): Promise { + const result: PageResult = await getProjectList(query.value) + list.value = result.list + total.value = result.total + } + + async function fetchDetail(id: string): Promise { + current.value = await getProjectDetail(id) + } + + function setQuery(next: ProjectQuery): void { + query.value = next + } + + /** 确定筛选(重置页码后取数) */ + async function search(): Promise { + query.value = { ...query.value, page: 1 } + await fetchList() + } + + /** 重置全部条件 */ + async function resetQuery(): Promise { + query.value = createDefaultQuery() + await fetchList() + } + + async function changePage(page: number): Promise { + query.value = { ...query.value, page } + await fetchList() + } + + /** 上传项目文件(追加到当前详情) */ + async function uploadFile(file: File): Promise { + const info = await uploadProjectFile(file) + current.value?.files.push(info) + } + + /** 删除当前详情中的项目文件 */ + async function deleteFile(index: number): Promise { + await deleteProjectFile(index) + current.value?.files.splice(index, 1) + } + + /** 提交售后工单 */ + async function submitAfterSales(payload: AfterSalesPayload): Promise { + if (!current.value) return + current.value.afterSales = await submitAfterSalesApi(current.value.id, payload) + ElMessage.success('已提交售后工单') + } + + /** 取消售后工单 */ + async function cancelAfterSales(): Promise { + if (!current.value) return + current.value.afterSales = await cancelAfterSalesApi(current.value.id) + ElMessage.success('已取消售后工单') + } + + return { + query, + list, + total, + current, + fetchList, + fetchDetail, + setQuery, + search, + resetQuery, + changePage, + uploadFile, + deleteFile, + submitAfterSales, + cancelAfterSales, + } +}) diff --git a/src/stores/modules/service.ts b/src/stores/modules/service.ts new file mode 100644 index 0000000..f89d26b --- /dev/null +++ b/src/stores/modules/service.ts @@ -0,0 +1,60 @@ +import { defineStore } from 'pinia' +import { ref } from 'vue' +import type { ChatMessage } from '@/types/business/service' +import { + getChatHistory, + mockAutoReply, + sendChatMessage, + uploadChatFile, +} from '@/api/service' + +/** 在线客服会话数据(消息列表 / 发送状态) */ +export const useServiceStore = defineStore('service', () => { + const messages = ref([]) + const sending = ref(false) + + async function fetchHistory(): Promise { + messages.value = await getChatHistory() + } + + /** 发送文本:先推入用户消息,600ms 后推入客服自动回复 */ + async function send(content: string): Promise { + const trimmed = content.trim() + if (!trimmed || sending.value) return + sending.value = true + try { + const userMsg = await sendChatMessage(trimmed) + messages.value.push(userMsg) + window.setTimeout(() => { + void mockAutoReply().then((reply) => { + messages.value.push(reply) + sending.value = false + }) + }, 600) + } + catch { + sending.value = false + } + } + + /** 发送文件卡片消息 */ + async function sendFile(file: File): Promise { + if (sending.value) return + sending.value = true + try { + const fileMsg = await uploadChatFile(file) + messages.value.push(fileMsg) + } + finally { + sending.value = false + } + } + + return { + messages, + sending, + fetchHistory, + send, + sendFile, + } +}) diff --git a/src/stores/modules/site.ts b/src/stores/modules/site.ts new file mode 100644 index 0000000..e8e1390 --- /dev/null +++ b/src/stores/modules/site.ts @@ -0,0 +1,24 @@ +import { defineStore } from 'pinia' +import { ref } from 'vue' + +/** 站点公共数据(站点配置、全局 loading),非页面级 */ +export const useSiteStore = defineStore('site', () => { + const globalLoading = ref(false) + + // TODO(api): 站点配置接入后由 fetchSiteConfig 覆盖默认值 + const siteInfo = ref({ + appName: '虫子智造', + hotline: '400-XXX-XXXX', + businessEmail: 'business@chongzizhizao.com', + supportEmail: 'support@chongzizhizao.com', + workTime: '周一-周五 9:00-18:00', + icp: 'ICP 备 XXXXXXXX 号', + police: '公网安备 XXXXXXXXXXXX 号', + }) + + function setGlobalLoading(value: boolean): void { + globalLoading.value = value + } + + return { globalLoading, siteInfo, setGlobalLoading } +}) diff --git a/src/stores/modules/user.ts b/src/stores/modules/user.ts new file mode 100644 index 0000000..0fbceac --- /dev/null +++ b/src/stores/modules/user.ts @@ -0,0 +1,137 @@ +import { defineStore } from 'pinia' +import { computed, ref } from 'vue' +import { ElMessage } from 'element-plus' +import type { + LoginByAccountParams, + LoginBySmsParams, + LoginResult, + RegisterParams, + ResetPasswordParams, + User, +} from '@/types/business/user' +import { + loginByAccount as loginByAccountApi, + loginBySms as loginBySmsApi, + register as registerApi, + resetPassword as resetPasswordApi, + sendSmsCode as sendSmsCodeApi, +} from '@/api/auth' +import { storage } from '@/utils/storage' + +/** 用户登录态(M4:token 持久化 key 'token',路由守卫读取同一 key) */ +export const useUserStore = defineStore('user', () => { + const token = ref(storage.get('token')) + const user = ref(null) + const loading = ref(false) + + const isLoggedIn = computed(() => Boolean(token.value)) + + /** 发送短信验证码 */ + async function sendSmsCode(phone: string): Promise { + try { + await sendSmsCodeApi(phone) + ElMessage.success('验证码已发送') + } + catch (error) { + ElMessage.error(error instanceof Error ? error.message : '验证码发送失败') + throw error + } + } + + /** 登录成功统一落库(token 持久化) */ + function applyLogin(result: LoginResult): void { + token.value = result.token + user.value = result.user + storage.set('token', result.token) + } + + /** 账号密码登录 */ + async function loginByAccount(params: LoginByAccountParams): Promise { + loading.value = true + try { + const result = await loginByAccountApi(params) + applyLogin(result) + ElMessage.success('登录成功') + return true + } + catch (error) { + ElMessage.error(error instanceof Error ? error.message : '登录失败') + return false + } + finally { + loading.value = false + } + } + + /** 手机验证码登录 */ + async function loginBySms(params: LoginBySmsParams): Promise { + loading.value = true + try { + const result = await loginBySmsApi(params) + applyLogin(result) + ElMessage.success('登录成功') + return true + } + catch (error) { + ElMessage.error(error instanceof Error ? error.message : '登录失败') + return false + } + finally { + loading.value = false + } + } + + /** 注册 */ + async function register(params: RegisterParams): Promise { + loading.value = true + try { + await registerApi(params) + ElMessage.success('注册成功,请登录') + return true + } + catch (error) { + ElMessage.error(error instanceof Error ? error.message : '注册失败') + return false + } + finally { + loading.value = false + } + } + + /** 重置密码 */ + async function resetPassword(params: ResetPasswordParams): Promise { + loading.value = true + try { + await resetPasswordApi(params) + ElMessage.success('修改成功,请登录') + return true + } + catch (error) { + ElMessage.error(error instanceof Error ? error.message : '修改失败') + return false + } + finally { + loading.value = false + } + } + + /** 退出登录(清除持久化 token) */ + function logout(): void { + token.value = null + user.value = null + storage.remove('token') + } + + return { + token, + user, + loading, + isLoggedIn, + sendSmsCode, + loginByAccount, + loginBySms, + register, + resetPassword, + logout, + } +}) diff --git a/src/styles/common.less b/src/styles/common.less new file mode 100644 index 0000000..5ea3f1d --- /dev/null +++ b/src/styles/common.less @@ -0,0 +1,25 @@ +// 通用工具类 + +.container { + width: 100%; + max-width: @container-width; + margin: 0 auto; + padding: 0 @spacing-base; +} + +.link { + cursor: pointer; + transition: color 0.2s; + + &:hover { + color: @primary-color; + } +} + +.text-ellipsis { + .ellipsis(); +} + +.text-secondary { + color: @text-color-secondary; +} diff --git a/src/styles/index.less b/src/styles/index.less new file mode 100644 index 0000000..52725ae --- /dev/null +++ b/src/styles/index.less @@ -0,0 +1,3 @@ +// 样式入口(main.ts 引入;variables/mixins 由 vite additionalData 自动注入) +@import './reset.less'; +@import './common.less'; diff --git a/src/styles/mixins.less b/src/styles/mixins.less new file mode 100644 index 0000000..67cca48 --- /dev/null +++ b/src/styles/mixins.less @@ -0,0 +1,37 @@ +// 混合宏(flex、文本省略、滚动条) + +.flex(@direction: row; @justify: flex-start; @align: center) { + display: flex; + flex-direction: @direction; + justify-content: @justify; + align-items: @align; +} + +.ellipsis() { + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; +} + +.multi-ellipsis(@lines: 2) { + display: -webkit-box; + -webkit-box-orient: vertical; + -webkit-line-clamp: @lines; + overflow: hidden; +} + +.scrollbar(@width: 6px) { + &::-webkit-scrollbar { + width: @width; + height: @width; + } + + &::-webkit-scrollbar-thumb { + background: @border-color-dark; + border-radius: @radius-round; + } + + &::-webkit-scrollbar-track { + background: transparent; + } +} diff --git a/src/styles/reset.less b/src/styles/reset.less new file mode 100644 index 0000000..b3e689f --- /dev/null +++ b/src/styles/reset.less @@ -0,0 +1,64 @@ +// 样式重置 + +*, +*::before, +*::after { + box-sizing: border-box; +} + +html, +body { + margin: 0; + padding: 0; +} + +body { + color: @text-color; + font-family: 'PingFang SC', 'Microsoft YaHei', -apple-system, 'Helvetica Neue', Arial, sans-serif; + font-size: @font-size-base; + line-height: 1.5; + background-color: @bg-color; + -webkit-font-smoothing: antialiased; +} + +ul, +ol { + margin: 0; + padding: 0; + list-style: none; +} + +h1, +h2, +h3, +h4, +h5, +h6, +p { + margin: 0; + font-weight: normal; +} + +a { + color: inherit; + text-decoration: none; +} + +img { + display: block; + max-width: 100%; +} + +button, +input, +select, +textarea { + font: inherit; + color: inherit; +} + +button { + cursor: pointer; + border: none; + background: transparent; +} diff --git a/src/styles/variables.less b/src/styles/variables.less new file mode 100644 index 0000000..4f4b914 --- /dev/null +++ b/src/styles/variables.less @@ -0,0 +1,56 @@ +// ★ less 变量唯一来源(颜色/间距/字号/圆角) +// 注:蓝湖设计稿到位前为原型近似值,到位后按标注精确校准 + +// 品牌色 +@primary-color: #3370ff; +@primary-hover: #598bff; +@primary-light: #eaf1ff; + +// 功能色 +@success-color: #34c724; +@warning-color: #ff8f1f; +@danger-color: #f54a45; +@info-color: #8a94a6; + +// 文字色 +@heading-color: #1f2733; +@text-color: #525e71; +@text-color-secondary: #8a94a6; +@text-color-disabled: #b8c0cc; + +// 背景 / 边框 +@bg-color: #f5f6f8; +@bg-color-light: #fafbfc; +@border-color: #e4e7ec; +@border-color-dark: #cdd3dd; +@white: #ffffff; + +// 圆角 +@radius-sm: 4px; +@radius-base: 8px; +@radius-lg: 12px; +@radius-round: 999px; + +// 间距 +@spacing-xs: 4px; +@spacing-sm: 8px; +@spacing-base: 16px; +@spacing-lg: 24px; +@spacing-xl: 32px; +@spacing-xxl: 48px; + +// 字号 +@font-size-sm: 12px; +@font-size-base: 14px; +@font-size-lg: 16px; +@font-size-xl: 20px; +@font-size-xxl: 28px; + +// 布局 +@container-width: 1200px; +@header-height: 64px; +@footer-height: 240px; + +// 阴影 +@shadow-base: 0 2px 8px rgba(31, 39, 51, 0.08); +@shadow-lg: 0 4px 16px rgba(31, 39, 51, 0.12); diff --git a/src/types/api.d.ts b/src/types/api.d.ts new file mode 100644 index 0000000..868edd6 --- /dev/null +++ b/src/types/api.d.ts @@ -0,0 +1,31 @@ +// 接口通用结构(PRD 4.8) + +/** 业务类型(跨模块共享) */ +export type BusinessType = 'internet' | 'hardware' | 'iot' | 'manufacturing' | 'other' + +/** 统一响应结构 */ +export interface ApiResponse { + code: number + msg: string + data: T +} + +/** 分页结果 */ +export interface PageResult { + list: T[] + total: number +} + +/** 通用分页查询参数 */ +export interface PageQuery { + page: number + pageSize: number +} + +/** 附件/文件信息 */ +export interface FileInfo { + name: string + size: number + uploadTime: string + url: string +} diff --git a/src/types/business/cases.d.ts b/src/types/business/cases.d.ts new file mode 100644 index 0000000..d2c28f9 --- /dev/null +++ b/src/types/business/cases.d.ts @@ -0,0 +1,38 @@ +// 案例实体(PRD 4.1,home/cases/profile.favorites 共享) + +import type { BusinessType } from '@/types/api' + +/** 项目规模区间 */ +export type ScaleRange = 'lt50w' | '50to100w' | '100to300w' | '300to500w' | 'gt500w' + +/** 案例 */ +export interface Case { + id: string + title: string + category: BusinessType + cover: string + finishDate: string + teamSize: number + /** 规模展示值(如 '300万'、'10-50万'),筛选区间见 CaseQuery.scales */ + scale: string + industry: string + duration: string + clientType: string + background: string + painPoints: string + requirements: string + deliverables: string + video: string + isFavorite: boolean +} + +/** 案例筛选条件 */ +export interface CaseQuery { + category: BusinessType | 'all' + industries: string[] + scales: ScaleRange[] + dateRange: [string, string] | null + keyword: string + page: number + pageSize: number +} diff --git a/src/types/business/contract.d.ts b/src/types/business/contract.d.ts new file mode 100644 index 0000000..eb48dbe --- /dev/null +++ b/src/types/business/contract.d.ts @@ -0,0 +1,29 @@ +// 合同中心实体(PRD M6) + +import type { FileInfo } from '@/types/api' + +/** 签署状态 */ +export type SignStatus = '履行中' | '已过期' | '待签署' + +/** 合同 */ +export interface Contract { + id: string + contractNo: string + contractName: string + contractType: string + contractAmount: number + relatedProjectName: string + projectNo: string + signStatus: SignStatus + signDate?: string + effectiveDate: string + expiryDate: string + files: FileInfo[] +} + +/** 合同列表查询条件 */ +export interface ContractQuery { + keyword: string + page: number + pageSize: number +} diff --git a/src/types/business/demand.d.ts b/src/types/business/demand.d.ts new file mode 100644 index 0000000..7eaac14 --- /dev/null +++ b/src/types/business/demand.d.ts @@ -0,0 +1,20 @@ +// 需求提报实体(M3,page-architecture) + +import type { BusinessType, FileInfo } from '@/types/api' + +/** 预算范围 */ +export type BudgetRange = 'lt1w' | '1to5w' | '5to10w' | 'gt10w' | '' + +/** 需求提报表单 */ +export interface Demand { + businessType: BusinessType + projectName: string + attachments: FileInfo[] + coreDescription: string + expectedDuration: number | '' + budgetRange: 'lt1w' | '1to5w' | '5to10w' | 'gt10w' | '' + userName: string + userPhone: string + remark: string + status: 'draft' | 'submitted' +} diff --git a/src/types/business/finance.d.ts b/src/types/business/finance.d.ts new file mode 100644 index 0000000..e718dad --- /dev/null +++ b/src/types/business/finance.d.ts @@ -0,0 +1,92 @@ +// 财务中心实体(账单/支付/发票/抬头/统计) + +/** 账单状态 */ +export type BillStatus = '待支付' | '已支付' | '未到期' + +/** 发票状态 */ +export type InvoiceStatus = '已开票' | '开票中' | '未开票' + +/** 支付方式 */ +export type PayMethod = 'wechat' | 'alipay' + +/** 账单 */ +export interface Bill { + id: string + /** 分期计划名,如 '第一期·预付款 比例33%' */ + planName: string + billNo: string + tradeNo?: string + projectName: string + amount: number + status: BillStatus + createTime: string +} + +/** 账单查询条件 */ +export interface BillQuery { + keyword: string + status: BillStatus | 'all' + page: number + pageSize: number +} + +/** 支付单(二维码) */ +export interface Payment { + billId: string + method: PayMethod + amount: number + qrExpiresInSeconds: number +} + +/** 支付结果 */ +export interface PaymentResult { + method: PayMethod + amount: number + payTime: string + tradeNo: string + bills: Bill[] +} + +/** 发票 */ +export interface Invoice { + id: string + planName: string + billNo: string + tradeNo?: string + projectName: string + amount: number + status: InvoiceStatus + applyTime: string +} + +/** 发票查询条件 */ +export interface InvoiceQuery { + keyword: string + status: InvoiceStatus | 'all' + page: number + pageSize: number +} + +/** 发票抬头 */ +export interface InvoiceTitle { + id: string + type: '企业单位' | '非企业单位' + title: string + taxNo: string + isDefault: boolean +} + +/** 新增/编辑发票抬头入参 */ +export interface InvoiceTitlePayload { + type: InvoiceTitle['type'] + title: string + taxNo: string +} + +/** 财务统计 */ +export interface FinanceStats { + totalExpense: number + month: string + pendingCount: number + distribution: Array<{ name: string; value: number; color: string }> +} diff --git a/src/types/business/home.d.ts b/src/types/business/home.d.ts new file mode 100644 index 0000000..99edb5a --- /dev/null +++ b/src/types/business/home.d.ts @@ -0,0 +1,34 @@ +// 首页实体(PRD 3.1/3.2,page-architecture M1) + +import type { BusinessType } from '@/types/api' + +/** 数据看板 */ +export interface HomeStats { + pending: number + inProgress: number + total: number +} + +/** 快捷入口 */ +export interface QuickLink { + label: string + path: string +} + +/** 服务类型子项 */ +export interface ServiceType { + label: string + desc: string +} + +/** 业务线(首页业务卡片 + 业务详情页共用) */ +export interface BusinessLine { + id: BusinessType + name: string + desc: string + slogan: string + icon: string + serviceContent: string + serviceTypes: ServiceType[] + video: string +} diff --git a/src/types/business/message.d.ts b/src/types/business/message.d.ts new file mode 100644 index 0000000..8556c32 --- /dev/null +++ b/src/types/business/message.d.ts @@ -0,0 +1,27 @@ +// 消息实体(PRD M8 消息中心) + +import type { FileInfo } from '@/types/api' + +/** 消息类型 */ +export type MessageType = '在线客服' | '状态通知' | '系统通知' + +/** 站内消息 */ +export interface Message { + id: string + type: MessageType + title: string + summary: string + /** 正文纯文本,详情页 pre-wrap 渲染 */ + content: string + publishTime: string + isRead: boolean + attachments: FileInfo[] +} + +/** 消息查询条件 */ +export interface MessageQuery { + keyword: string + type: MessageType | 'all' + page: number + pageSize: number +} diff --git a/src/types/business/profile.d.ts b/src/types/business/profile.d.ts new file mode 100644 index 0000000..262c50d --- /dev/null +++ b/src/types/business/profile.d.ts @@ -0,0 +1,87 @@ +// 个人中心实体(M10:security/address/favorites/qualifications/feedback/help/about/hotline 共享) + +import type { BusinessType, FileInfo } from '@/types/api' + +/** 账号资料 */ +export interface Profile { + nickname: string + phone: string + email: string + /** 密码上次修改时间(展示值,如 '15天前') */ + passwordModifiedAt: string +} + +/** 修改密码入参 */ +export interface ChangePasswordParams { + code: string + password: string +} + +/** 收货地址 */ +export interface Address { + id: string + receiverName: string + phone: string + region: string + detailAddress: string + isDefault: boolean +} + +/** 资质类型 */ +export type QualificationType = + | '营业执照' + | '生产许可证' + | '体系认证' + | '检测报告' + | '授权书' + | '其他资质' + +/** 资质审核状态 */ +export type QualificationStatus = '已认证' | '审核中' | '未通过' + +/** 资质 */ +export interface Qualification { + id: string + type: QualificationType + certName: string + certNo: string + issuer: string + validFrom: string + validTo: string + status: QualificationStatus + attachments: FileInfo[] +} + +/** 资质新增/编辑提交载荷(id 存在为编辑) */ +export type QualificationInput = Omit & { id?: string } + +/** 意见反馈 */ +export interface Feedback { + title: string + description: string + attachments: FileInfo[] +} + +/** 列表分页查询 */ +export interface ListQuery { + page: number + pageSize: number +} + +/** 我的收藏查询(关键字 + 分类 + 分页) */ +export interface FavoritesQuery extends ListQuery { + keyword: string + category: BusinessType | 'all' +} + +/** 帮助中心 FAQ 条目 */ +export interface HelpFaqItem { + q: string + a: string +} + +/** 帮助中心 FAQ 分组 */ +export interface HelpFaqGroup { + title: string + items: HelpFaqItem[] +} diff --git a/src/types/business/project.d.ts b/src/types/business/project.d.ts new file mode 100644 index 0000000..627b2c3 --- /dev/null +++ b/src/types/business/project.d.ts @@ -0,0 +1,79 @@ +// 项目中心实体(PRD M5) + +import type { BusinessType, FileInfo } from '@/types/api' + +/** 项目状态 */ +export type ProjectStatus = '待立项' | '进行中' | '待验收' | '已完成' | '售后中' | '已取消' + +/** 里程碑节点 */ +export interface Milestone { + name: string + status: '待开始' | '进行中' | '已完成' + time?: string +} + +/** 团队成员 */ +export interface TeamMember { + role: string + name: string + avatarChar: string +} + +/** 项目账单(付款计划) */ +export interface ProjectBill { + id: string + planName: string + billNo: string + amount: number + status: '待支付' | '已支付' | '未到期' + tradeNo?: string + projectName: string +} + +/** 售后工单 */ +export interface AfterSales { + status: '无' | '处理中' + type: string + description: string + remark: string + attachments: FileInfo[] + createTime: string +} + +/** 售后提交入参 */ +export interface AfterSalesPayload { + type: string + description: string + remark: string + attachments: FileInfo[] +} + +/** 项目 */ +export interface Project { + id: string + projectNo: string + projectName: string + businessType: BusinessType + progress: number + status: ProjectStatus + submitTime: string + startTime?: string + endTime?: string + milestones: Milestone[] + team: TeamMember[] + files: FileInfo[] + paymentPlans: ProjectBill[] + invoiceRecords: FileInfo[] + contractFile?: FileInfo + afterSales?: AfterSales +} + +/** 项目列表查询条件 */ +export interface ProjectQuery { + keyword: string + businessType: BusinessType | 'all' + status: ProjectStatus | 'all' + timeShortcut: 'all' | '3m' | '6m' | '1y' + page: number + pageSize: number +} diff --git a/src/types/business/service.d.ts b/src/types/business/service.d.ts new file mode 100644 index 0000000..dfb3050 --- /dev/null +++ b/src/types/business/service.d.ts @@ -0,0 +1,17 @@ +// 在线客服会话实体(PRD M9 在线客服) + +/** 会话角色 */ +export type ChatRole = 'user' | 'assistant' + +/** 会话消息 */ +export interface ChatMessage { + id: string + role: ChatRole + content: string + /** 文件消息卡片(有值时按文件渲染) */ + file?: { + name: string + size: number + } + time: string +} diff --git a/src/types/business/user.d.ts b/src/types/business/user.d.ts new file mode 100644 index 0000000..c2f232f --- /dev/null +++ b/src/types/business/user.d.ts @@ -0,0 +1,47 @@ +// 用户/认证实体(M4,page-architecture) + +/** 用户 */ +export interface User { + id: string + nickname: string + avatar: string + phone: string + email: string + passwordModifiedAt: string +} + +/** 账号密码登录参数 */ +export interface LoginByAccountParams { + account: string + password: string +} + +/** 短信验证码登录参数 */ +export interface LoginBySmsParams { + phone: string + code: string +} + +/** 登录结果 */ +export interface LoginResult { + token: string + user: User +} + +/** 注册参数 */ +export interface RegisterParams { + phone: string + code: string + password: string + confirmPassword: string + agree: boolean +} + +/** 找回/重置密码参数 */ +export interface ResetPasswordParams { + phone: string + code: string + password: string + confirmPassword: string + agree: boolean +} diff --git a/src/types/global.d.ts b/src/types/global.d.ts new file mode 100644 index 0000000..35a9c97 --- /dev/null +++ b/src/types/global.d.ts @@ -0,0 +1,8 @@ +// 全局声明(Window 扩展等,按需添加) +export {} + +declare global { + interface Window { + // 全局挂载扩展位 + } +} diff --git a/src/types/router.d.ts b/src/types/router.d.ts new file mode 100644 index 0000000..0e3ee2d --- /dev/null +++ b/src/types/router.d.ts @@ -0,0 +1,11 @@ +// RouteMeta 扩展 +export {} + +declare module 'vue-router' { + interface RouteMeta { + /** 页面标题(document.title) */ + title?: string + /** 是否需要登录 */ + requiresAuth?: boolean + } +} diff --git a/src/types/web/home.d.ts b/src/types/web/home.d.ts new file mode 100644 index 0000000..64364c4 --- /dev/null +++ b/src/types/web/home.d.ts @@ -0,0 +1,65 @@ +// 后端「用户web端首页」接口真实返回类型 +// 对应 vermis-maker: com.ruoyi.vermins.controller.pc.web.UserSideWebHomePageController +// 统一前缀: /web/home-page (公开接口,无需登录) +// 字段与后端 VO 严格对齐(驼峰、可空用 | null)。 + +/** GET /web/home-page/project-status-count —— 项目状态统计 */ +export interface ProjectStatusCount { + /** 项目数量 */ + projectCount: number + /** 进行中的项目数目 */ + ongoingProjectCount: number + /** 待处理数目 */ + pendingProjectCount: number +} + +/** 服务类型子项(service-type-list / service-type-detail 内含) */ +export interface ProjectServiceTypeVo { + id: number | null + projectId: number | null + serviceName: string | null + iconUrl: string | null + sortOrder: number | null +} + +/** 项目背景(富文本) */ +export interface ProjectBackgroundVo { + id: number | null + projectId: number | null + content: string | null + sortOrder: number | null +} + +/** 项目提报视图对象:service-type-list / service-type-detail/{id} 的返回 */ +export interface ProjectSubmissionVo { + id: number + businessType: string | null + title: string | null + subtitle: string | null + /** 是否启用:1 启用,0 停用 */ + enabled: number | null + coverUrl: string | null + logoUrl: string | null + serviceTypes: ProjectServiceTypeVo[] + backgrounds: ProjectBackgroundVo[] + /** yyyy-MM-dd HH:mm:ss */ + updateTime: string | null +} + +/** GET /web/home-page/carousel-list —— 轮播图 */ +export interface BannerVo { + id: number | null + imageUrl: string | null + sort: number | null + /** 是否启用(true 启用,false 停用) */ + enabled: boolean | null +} + +/** GET /web/home-page/platform-intro、contact-info —— 协议/静态文章富文本 */ +export interface AgreementFormVo { + id: number | null + /** 协议类型(目前只有「关于我们」一条) */ + type: string | null + /** 文章内容(富文本 HTML) */ + content: string | null +} diff --git a/src/utils/format.ts b/src/utils/format.ts new file mode 100644 index 0000000..6eb4de3 --- /dev/null +++ b/src/utils/format.ts @@ -0,0 +1,67 @@ +// 格式化工具(无 Vue 依赖) + +import type { BusinessType } from '@/types/api' +import type { ScaleRange } from '@/types/business/cases' + +const BUSINESS_TYPE_LABELS: Record = { + internet: '互联网软件', + hardware: '硬件电路', + iot: '物联网全案', + manufacturing: '生产制造', + other: '其他', +} + +const SCALE_LABELS: Record = { + lt50w: '50万以下', + '50to100w': '50-100万', + '100to300w': '100-300万', + '300to500w': '300-500万', + gt500w: '500万以上', +} + +/** 业务类型展示名 */ +export function formatBusinessType(type: BusinessType): string { + return BUSINESS_TYPE_LABELS[type] +} + +/** 项目规模展示名 */ +export function formatScale(scale: ScaleRange): string { + return SCALE_LABELS[scale] +} + +/** 金额千分位(默认两位小数) */ +export function formatMoney(value: number, decimals = 2): string { + const fixed = value.toFixed(decimals) + const [int = '0', fraction] = fixed.split('.') + const withSep = int.replace(/\B(?=(\d{3})+(?!\d))/g, ',') + return fraction ? `${withSep}.${fraction}` : withSep +} + +function pad(n: number): string { + return n < 10 ? `0${n}` : String(n) +} + +/** 日期格式化(YYYY-MM-DD HH:mm:ss 可裁剪) */ +export function formatDate(input: Date | string | number, pattern = 'YYYY-MM-DD HH:mm:ss'): string { + const date = input instanceof Date ? input : new Date(input) + if (Number.isNaN(date.getTime())) return '' + const map: Record = { + YYYY: String(date.getFullYear()), + MM: pad(date.getMonth() + 1), + DD: pad(date.getDate()), + HH: pad(date.getHours()), + mm: pad(date.getMinutes()), + ss: pad(date.getSeconds()), + } + return pattern.replace(/YYYY|MM|DD|HH|mm|ss/g, (token) => map[token] ?? token) +} + +/** 文件大小(B/KB/MB/GB) */ +export function formatFileSize(bytes: number): string { + if (bytes < 1024) return `${bytes}B` + const kb = bytes / 1024 + if (kb < 1024) return `${kb.toFixed(1)}KB` + const mb = kb / 1024 + if (mb < 1024) return `${mb.toFixed(1)}MB` + return `${(mb / 1024).toFixed(1)}GB` +} diff --git a/src/utils/request.ts b/src/utils/request.ts new file mode 100644 index 0000000..0b2553d --- /dev/null +++ b/src/utils/request.ts @@ -0,0 +1,43 @@ +import axios, { type AxiosRequestConfig } from 'axios' +import { ElMessage } from 'element-plus' +import type { ApiResponse } from '@/types/api' +import { storage } from '@/utils/storage' + +// 后端 R.ok() 成功码为 200(org.dromara.common.core.constant.HttpStatus.SUCCESS)。 +// 鉴权头 Authorization: Bearer 已与后端 sa-token.token-prefix=Bearer 对齐,无需改动。 +const SUCCESS_CODE = 200 + +/** 全项目唯一 axios 实例 */ +const service = axios.create({ + baseURL: import.meta.env.VITE_API_BASE_URL, + timeout: 15000, +}) + +service.interceptors.request.use((config) => { + const token = storage.get('token') + if (token) { + config.headers.Authorization = `Bearer ${token}` + } + return config +}) + +service.interceptors.response.use(undefined, (error) => { + ElMessage.error(error?.message || '网络异常,请稍后重试') + return Promise.reject(error) +}) + +/** + * 统一请求:自动解包 ApiResponse,失败统一提示并 reject + * 页面/组件禁止直接 import axios,一律经 store 调用本函数 + */ +export async function request(config: AxiosRequestConfig): Promise { + const resp = await service.request>(config) + const payload = resp.data + if (payload.code !== SUCCESS_CODE) { + ElMessage.error(payload.msg || '请求失败') + return Promise.reject(new Error(payload.msg || '请求失败')) + } + return payload.data +} + +export default service diff --git a/src/utils/storage.ts b/src/utils/storage.ts new file mode 100644 index 0000000..ca62452 --- /dev/null +++ b/src/utils/storage.ts @@ -0,0 +1,34 @@ +// localStorage/sessionStorage 泛型封装(无 Vue 依赖) + +const PREFIX = 'cz:' + +function parse(raw: string | null): T | null { + if (raw === null) return null + try { + return JSON.parse(raw) as T + } + catch { + return null + } +} + +export const storage = { + get(key: string): T | null { + return parse(localStorage.getItem(PREFIX + key)) + }, + set(key: string, value: T): void { + localStorage.setItem(PREFIX + key, JSON.stringify(value)) + }, + remove(key: string): void { + localStorage.removeItem(PREFIX + key) + }, + sessionGet(key: string): T | null { + return parse(sessionStorage.getItem(PREFIX + key)) + }, + sessionSet(key: string, value: T): void { + sessionStorage.setItem(PREFIX + key, JSON.stringify(value)) + }, + sessionRemove(key: string): void { + sessionStorage.removeItem(PREFIX + key) + }, +} diff --git a/src/utils/validate.ts b/src/utils/validate.ts new file mode 100644 index 0000000..23025eb --- /dev/null +++ b/src/utils/validate.ts @@ -0,0 +1,36 @@ +// 校验规则(无 Vue 依赖) + +/** 手机号 */ +export function isPhone(value: string): boolean { + return /^1[3-9]\d{9}$/.test(value) +} + +/** 邮箱 */ +export function isEmail(value: string): boolean { + return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value) +} + +/** URL */ +export function isUrl(value: string): boolean { + return /^https?:\/\/[^\s]+$/.test(value) +} + +export type PasswordLevel = 'weak' | 'medium' | 'strong' + +/** + * 密码强度等级(6-20 位) + * weak:仅字母或仅数字;medium:字母+数字;strong:字母+数字+特殊符号 + */ +export function getPasswordLevel(password: string): PasswordLevel { + const hasLetter = /[a-zA-Z]/.test(password) + const hasDigit = /\d/.test(password) + const hasSymbol = /[^\w\s]/.test(password) + if (hasLetter && hasDigit && hasSymbol) return 'strong' + if (hasLetter && hasDigit) return 'medium' + return 'weak' +} + +/** 登录密码合法性(6-20 位) */ +export function isValidPassword(password: string): boolean { + return password.length >= 6 && password.length <= 20 +} diff --git a/src/views/404.vue b/src/views/404.vue new file mode 100644 index 0000000..8c0cd05 --- /dev/null +++ b/src/views/404.vue @@ -0,0 +1,35 @@ + + + + + diff --git a/src/views/cases/cpns/CaseDetailHeader.vue b/src/views/cases/cpns/CaseDetailHeader.vue new file mode 100644 index 0000000..d845a93 --- /dev/null +++ b/src/views/cases/cpns/CaseDetailHeader.vue @@ -0,0 +1,164 @@ + + + + + diff --git a/src/views/cases/cpns/CaseRichText.vue b/src/views/cases/cpns/CaseRichText.vue new file mode 100644 index 0000000..a19adb5 --- /dev/null +++ b/src/views/cases/cpns/CaseRichText.vue @@ -0,0 +1,57 @@ + + + + + diff --git a/src/views/cases/cpns/CasesFilter.vue b/src/views/cases/cpns/CasesFilter.vue new file mode 100644 index 0000000..5f55f88 --- /dev/null +++ b/src/views/cases/cpns/CasesFilter.vue @@ -0,0 +1,324 @@ + + + + + diff --git a/src/views/cases/cpns/CasesGrid.vue b/src/views/cases/cpns/CasesGrid.vue new file mode 100644 index 0000000..a419cb4 --- /dev/null +++ b/src/views/cases/cpns/CasesGrid.vue @@ -0,0 +1,43 @@ + + + + + diff --git a/src/views/cases/detail.vue b/src/views/cases/detail.vue new file mode 100644 index 0000000..d6f4f3a --- /dev/null +++ b/src/views/cases/detail.vue @@ -0,0 +1,90 @@ + + + + + diff --git a/src/views/cases/index.ts b/src/views/cases/index.ts new file mode 100644 index 0000000..9757d40 --- /dev/null +++ b/src/views/cases/index.ts @@ -0,0 +1,5 @@ +export { default } from './index.vue' +export { default as CasesFilter } from './cpns/CasesFilter.vue' +export { default as CasesGrid } from './cpns/CasesGrid.vue' +export { default as CaseDetailHeader } from './cpns/CaseDetailHeader.vue' +export { default as CaseRichText } from './cpns/CaseRichText.vue' diff --git a/src/views/cases/index.vue b/src/views/cases/index.vue new file mode 100644 index 0000000..a6de2b9 --- /dev/null +++ b/src/views/cases/index.vue @@ -0,0 +1,90 @@ + + + + + diff --git a/src/views/contract/cpns/ContractInfoCard.vue b/src/views/contract/cpns/ContractInfoCard.vue new file mode 100644 index 0000000..7a7547f --- /dev/null +++ b/src/views/contract/cpns/ContractInfoCard.vue @@ -0,0 +1,216 @@ + + + + + diff --git a/src/views/contract/cpns/ContractSignPanel.vue b/src/views/contract/cpns/ContractSignPanel.vue new file mode 100644 index 0000000..c3f6980 --- /dev/null +++ b/src/views/contract/cpns/ContractSignPanel.vue @@ -0,0 +1,47 @@ + + + + + diff --git a/src/views/contract/cpns/ContractTable.vue b/src/views/contract/cpns/ContractTable.vue new file mode 100644 index 0000000..7aee3c6 --- /dev/null +++ b/src/views/contract/cpns/ContractTable.vue @@ -0,0 +1,161 @@ + + + + + diff --git a/src/views/contract/cpns/SignSteps.vue b/src/views/contract/cpns/SignSteps.vue new file mode 100644 index 0000000..c055240 --- /dev/null +++ b/src/views/contract/cpns/SignSteps.vue @@ -0,0 +1,76 @@ + + + + + diff --git a/src/views/contract/detail.vue b/src/views/contract/detail.vue new file mode 100644 index 0000000..0413d5d --- /dev/null +++ b/src/views/contract/detail.vue @@ -0,0 +1,98 @@ + + + + + diff --git a/src/views/contract/index.ts b/src/views/contract/index.ts new file mode 100644 index 0000000..f5eeed7 --- /dev/null +++ b/src/views/contract/index.ts @@ -0,0 +1,5 @@ +export { default } from './index.vue' +export { default as ContractTable } from './cpns/ContractTable.vue' +export { default as ContractInfoCard } from './cpns/ContractInfoCard.vue' +export { default as ContractSignPanel } from './cpns/ContractSignPanel.vue' +export { default as SignSteps } from './cpns/SignSteps.vue' diff --git a/src/views/contract/index.vue b/src/views/contract/index.vue new file mode 100644 index 0000000..f44b58b --- /dev/null +++ b/src/views/contract/index.vue @@ -0,0 +1,95 @@ + + + + + diff --git a/src/views/contract/sign.vue b/src/views/contract/sign.vue new file mode 100644 index 0000000..70b591e --- /dev/null +++ b/src/views/contract/sign.vue @@ -0,0 +1,78 @@ + + + + + diff --git a/src/views/demand/cpns/DemandStep1Business.vue b/src/views/demand/cpns/DemandStep1Business.vue new file mode 100644 index 0000000..5d11b3f --- /dev/null +++ b/src/views/demand/cpns/DemandStep1Business.vue @@ -0,0 +1,105 @@ + + + + + diff --git a/src/views/demand/cpns/DemandStep2Info.vue b/src/views/demand/cpns/DemandStep2Info.vue new file mode 100644 index 0000000..1eb2b7f --- /dev/null +++ b/src/views/demand/cpns/DemandStep2Info.vue @@ -0,0 +1,229 @@ + + +