diff --git a/index.html b/index.html index d3cfca5..7267ac8 100644 --- a/index.html +++ b/index.html @@ -1,14 +1,17 @@ - - - - - - 六个羽友赛事运营平台 - - -
- - - + + + + + + + 六羽赛事运营平台 + + + +
+ + + + \ No newline at end of file diff --git a/package.json b/package.json index 85b4782..b30813c 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,10 @@ }, "dependencies": { "@ant-design/icons-vue": "^7.0.1", + "@yp-component/root": "^0.0.48", "ant-design-vue": "^4.0.0", + "china-area-data": "^5.0.1", + "dayjs": "^1.11.21", "express": "^5.2.1", "html2canvas": "^1.4.1", "qs": "^6.15.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 0000000..d937aa4 --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,4461 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@ant-design/icons-vue': + specifier: ^7.0.1 + version: 7.0.1(vue@3.5.40(typescript@5.9.3)) + '@yp-component/root': + specifier: ^0.0.48 + version: 0.0.48(typescript@5.9.3) + ant-design-vue: + specifier: ^4.0.0 + version: 4.2.6(vue@3.5.40(typescript@5.9.3)) + china-area-data: + specifier: ^5.0.1 + version: 5.0.1 + cropperjs: + specifier: ^1.6.2 + version: 1.6.2 + dayjs: + specifier: ^1.11.21 + version: 1.11.21 + express: + specifier: ^5.2.1 + version: 5.2.1 + html2canvas: + specifier: ^1.4.1 + version: 1.4.1 + qs: + specifier: ^6.15.0 + version: 6.15.3 + vue: + specifier: ^3.3.0 + version: 3.5.40(typescript@5.9.3) + vue-router: + specifier: ^4.2.0 + version: 4.6.4(vue@3.5.40(typescript@5.9.3)) + devDependencies: + '@commitlint/cli': + specifier: ^20.4.3 + version: 20.5.3(@types/node@20.19.43)(conventional-commits-parser@6.4.0)(typescript@5.9.3) + '@commitlint/config-conventional': + specifier: ^20.4.3 + version: 20.5.3 + '@rollup/plugin-strip': + specifier: ^3.0.4 + version: 3.0.4(rollup@4.62.2) + '@types/node': + specifier: ^20.10.0 + version: 20.19.43 + '@types/qs': + specifier: ^6.15.0 + version: 6.15.1 + '@typescript-eslint/eslint-plugin': + specifier: ^6.0.0 + version: 6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1)(typescript@5.9.3) + '@typescript-eslint/parser': + specifier: ^6.0.0 + version: 6.21.0(eslint@8.57.1)(typescript@5.9.3) + '@vitejs/plugin-vue': + specifier: ^4.5.0 + version: 4.6.2(vite@5.4.21(@types/node@20.19.43)(less@4.8.0))(vue@3.5.40(typescript@5.9.3)) + '@vitejs/plugin-vue-jsx': + specifier: ^4.0.0 + version: 4.2.0(vite@5.4.21(@types/node@20.19.43)(less@4.8.0))(vue@3.5.40(typescript@5.9.3)) + eslint: + specifier: ^8.0.0 + version: 8.57.1 + eslint-config-prettier: + specifier: ^9.0.0 + version: 9.1.2(eslint@8.57.1) + eslint-plugin-prettier: + specifier: ^5.0.0 + version: 5.5.6(eslint-config-prettier@9.1.2(eslint@8.57.1))(eslint@8.57.1)(prettier@3.9.6) + eslint-plugin-vue: + specifier: ^9.0.0 + version: 9.33.0(eslint@8.57.1) + husky: + specifier: ^9.1.7 + version: 9.1.7 + less: + specifier: ^4.6.4 + version: 4.8.0 + lint-staged: + specifier: ^15.0.0 + version: 15.5.2 + prettier: + specifier: ^3.0.0 + version: 3.9.6 + typescript: + specifier: ^5.3.0 + version: 5.9.3 + vite: + specifier: ^5.0.0 + version: 5.4.21(@types/node@20.19.43)(less@4.8.0) + vite-plugin-remove-console: + specifier: ^2.2.0 + version: 2.2.0 + vue-tsc: + specifier: ^2.0.0 + version: 2.2.12(typescript@5.9.3) + +packages: + + '@ant-design/colors@6.0.0': + resolution: {integrity: sha512-qAZRvPzfdWHtfameEGP2Qvuf838NhergR35o+EuVyB5XvSA98xod5r4utvi4TJ3ywmevm290g9nsCG5MryrdWQ==} + + '@ant-design/icons-svg@4.5.0': + resolution: {integrity: sha512-1BTUFyKPTBZ53MuTP8s0k5SFEXL7o3VHEOwLgzaoWKwnBeqIcqUtVshc4SKzhI6uACfqhJqBwBUE9FsWR3uULA==} + + '@ant-design/icons-vue@7.0.1': + resolution: {integrity: sha512-eCqY2unfZK6Fe02AwFlDHLfoyEFreP6rBwAZMIJ1LugmfMiVgwWDYlp1YsRugaPtICYOabV1iWxXdP12u9U43Q==} + peerDependencies: + vue: '>=3.0.3' + + '@babel/code-frame@7.29.7': + resolution: {integrity: sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==} + engines: {node: '>=6.9.0'} + + '@babel/compat-data@7.29.7': + resolution: {integrity: sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==} + engines: {node: '>=6.9.0'} + + '@babel/core@7.29.7': + resolution: {integrity: sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==} + engines: {node: '>=6.9.0'} + + '@babel/generator@7.29.7': + resolution: {integrity: sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==} + engines: {node: '>=6.9.0'} + + '@babel/helper-annotate-as-pure@7.29.7': + resolution: {integrity: sha512-OoK6239jHPuSQOoS0kfTVKn0b/rVTk0seKq4Gd2UMLtmOVLjDC0ki3e+c90Trqv2gMfvJFqkiljrr568+qddiw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-compilation-targets@7.29.7': + resolution: {integrity: sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==} + engines: {node: '>=6.9.0'} + + '@babel/helper-create-class-features-plugin@7.29.7': + resolution: {integrity: sha512-IY3ZD9Tmooqr3TUhc3DUWxiuo8xx1DWLhd5M7hQ+ZWJamqM2BbalrBJb2MisSLoYorOj75U03qULCxQTY9r3hg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-globals@7.29.7': + resolution: {integrity: sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-member-expression-to-functions@7.29.7': + resolution: {integrity: sha512-j+7JYmk1JYDtACIGj0QJqqWZjoUpMoEikQGADMaHgCMCSDqd2+P32rfcibUNrGOMWrlzK1WJBdxrB3JJQZwWtg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-imports@7.29.7': + resolution: {integrity: sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-transforms@7.29.7': + resolution: {integrity: sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-optimise-call-expression@7.29.7': + resolution: {integrity: sha512-+kmGVjcT9RGYzoDwdwEqEvGgKe3BYq+O1iGzjFubaNgZHwYHP6lsF2Yghf4kEuv9BV7tYDZ913aBW9am6YKong==} + engines: {node: '>=6.9.0'} + + '@babel/helper-plugin-utils@7.29.7': + resolution: {integrity: sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-replace-supers@7.29.7': + resolution: {integrity: sha512-atfGXWSeCiF4DnKZIfmJfQRkSw9b9gNNXR1kqKjbhG4pGYCOnkp8OcTB8E3NXjBu8NpheSnOeNKz8KT7UNFTmQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-skip-transparent-expression-wrappers@7.29.7': + resolution: {integrity: sha512-brcMGQaVzIeUb+6/bs1Av0f8YuNNjKY2JyvfRCsFuFsdKccEQ5Ges2y74D74NZ1Rz8lKJ9ksJkfqwQFJ/iNEyQ==} + engines: {node: '>=6.9.0'} + + '@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/helper-validator-option@7.29.7': + resolution: {integrity: sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==} + engines: {node: '>=6.9.0'} + + '@babel/helpers@7.29.7': + resolution: {integrity: sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.29.7': + resolution: {integrity: sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/plugin-syntax-jsx@7.29.7': + resolution: {integrity: sha512-TSu8+mHCoEaaCDEZ0I3+6mvTBYR4PCxQwf2z9/r5Tbztv6NaLR3B9thGTTxX2WGuGHJqRiAbKPeGTJ5XWXVg6A==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-typescript@7.29.7': + resolution: {integrity: sha512-ngr+82Sh0xMz25TPCZi+nC2iTzjfCdWS2ONXTp/PtSCHCgaCNBpdMqgvJ2ccdLlClVZ7sisIgB914j/JFe+RZA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-typescript@7.29.7': + resolution: {integrity: sha512-jK52h8LaLc7JarhQV2ofeFMts4H7vnOXnqZNA6fYglBTZewRBE51KWt3BUltW1P+KoPsYkHoJeXePuz4zo2LMw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/runtime@7.29.7': + resolution: {integrity: sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==} + engines: {node: '>=6.9.0'} + + '@babel/template@7.29.7': + resolution: {integrity: sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==} + engines: {node: '>=6.9.0'} + + '@babel/traverse@7.29.7': + resolution: {integrity: sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==} + engines: {node: '>=6.9.0'} + + '@babel/types@7.29.7': + resolution: {integrity: sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==} + engines: {node: '>=6.9.0'} + + '@commitlint/cli@20.5.3': + resolution: {integrity: sha512-OJdL0EXWD5y9LPa0nr/geOwzaS8BsdaybKkcloB0JgsguGxNv2R+hC2FTPqrAcprg35zF33KOQerY0x8W1aesA==} + engines: {node: '>=v18'} + hasBin: true + + '@commitlint/config-conventional@20.5.3': + resolution: {integrity: sha512-j34Qqeaa152chJgz2ysyk0BCpHenJn1lV0Rx0VXf8k3ccQcED+48EZrzMvo9jLmJUyBrrBwvu89I+2er4gW7QQ==} + engines: {node: '>=v18'} + + '@commitlint/config-validator@20.5.0': + resolution: {integrity: sha512-T/Uh6iJUzyx7j35GmHWdIiGRQB+ouZDk0pwAaYq4SXgB54KZhFdJ0vYmxiW6AMYICTIWuyMxDBl1jK74oFp/Gw==} + engines: {node: '>=v18'} + + '@commitlint/ensure@20.5.3': + resolution: {integrity: sha512-4i4AgNvH62owG9MwSiWKrle7HGNpBHHdLnWFIp5fTsHUYe5kRuh15t08L/0pdbbrRk8JKXQxxN4hZQcn+szkrw==} + engines: {node: '>=v18'} + + '@commitlint/execute-rule@20.0.0': + resolution: {integrity: sha512-xyCoOShoPuPL44gVa+5EdZsBVao/pNzpQhkzq3RdtlFdKZtjWcLlUFQHSWBuhk5utKYykeJPSz2i8ABHQA+ZZw==} + engines: {node: '>=v18'} + + '@commitlint/format@20.5.0': + resolution: {integrity: sha512-TI9EwFU/qZWSK7a5qyXMpKPPv3qta7FO4tKW+Wt2al7sgMbLWTsAcDpX1cU8k16TRdsiiet9aOw0zpvRXNJu7Q==} + engines: {node: '>=v18'} + + '@commitlint/is-ignored@20.5.0': + resolution: {integrity: sha512-JWLarAsurHJhPozbuAH6GbP4p/hdOCoqS9zJMfqwswne+/GPs5V0+rrsfOkP68Y8PSLphwtFXV0EzJ+GTXTTGg==} + engines: {node: '>=v18'} + + '@commitlint/lint@20.5.3': + resolution: {integrity: sha512-M7JbWBNr2gXKaPc4i/KipsuW1gkDHpj35KPjWtKy3Z+2AQw5wu1gBi1LIO0uoaij67CqY4K8PxPZSGens4evCw==} + engines: {node: '>=v18'} + + '@commitlint/load@20.5.3': + resolution: {integrity: sha512-1FDZWuKyu98Myb8i7Tp31jPU2rZpOwAdYRyJcy2KoGg7Xk2A+bgHN8smhMaaNSNkmE8fwt53BokywZq8Gv/5XQ==} + engines: {node: '>=v18'} + + '@commitlint/message@20.4.3': + resolution: {integrity: sha512-6akwCYrzcrFcTYz9GyUaWlhisY4lmQ3KvrnabmhoeAV8nRH4dXJAh4+EUQ3uArtxxKQkvxJS78hNX2EU3USgxQ==} + engines: {node: '>=v18'} + + '@commitlint/parse@20.5.0': + resolution: {integrity: sha512-SeKWHBMk7YOTnnEWUhx+d1a9vHsjjuo6Uo1xRfPNfeY4bdYFasCH1dDpAv13Lyn+dDPOels+jP6D2GRZqzc5fA==} + engines: {node: '>=v18'} + + '@commitlint/read@20.5.0': + resolution: {integrity: sha512-JDEIJ2+GnWpK8QqwfmW7O42h0aycJEWNqcdkJnyzLD11nf9dW2dWLTVEa8Wtlo4IZFGLPATjR5neA5QlOvIH1w==} + engines: {node: '>=v18'} + + '@commitlint/resolve-extends@20.5.3': + resolution: {integrity: sha512-+ogW9v/u9JqpvAgTrLra/YTFo0KkjU6iNblF89pPsj4NebNc+DAWctsludwezI8YnsjBmfHpApSwcXprN/f/ew==} + engines: {node: '>=v18'} + + '@commitlint/rules@20.5.3': + resolution: {integrity: sha512-MPlMnb9D3wbszYMp+1hPtuhtPJndRo6I6yfkZVA4+jR8w7Kqp0u2u/Y+gzbaItx5Lltq5rw7FSZQWJMoXUC4NQ==} + engines: {node: '>=v18'} + + '@commitlint/to-lines@20.0.0': + resolution: {integrity: sha512-2l9gmwiCRqZNWgV+pX1X7z4yP0b3ex/86UmUFgoRt672Ez6cAM2lOQeHFRUTuE6sPpi8XBCGnd8Kh3bMoyHwJw==} + engines: {node: '>=v18'} + + '@commitlint/top-level@20.4.3': + resolution: {integrity: sha512-qD9xfP6dFg5jQ3NMrOhG0/w5y3bBUsVGyJvXxdWEwBm8hyx4WOk3kKXw28T5czBYvyeCVJgJJ6aoJZUWDpaacQ==} + engines: {node: '>=v18'} + + '@commitlint/types@20.5.0': + resolution: {integrity: sha512-ZJoS8oSq2CAZEpc/YI9SulLrdiIyXeHb/OGqGrkUP6Q7YV+0ouNAa7GjqRdXeQPncHQIDz/jbCTlHScvYvO/gA==} + engines: {node: '>=v18'} + + '@conventional-changelog/git-client@2.7.0': + resolution: {integrity: sha512-j7A8/LBEQ+3rugMzPXoKYzyUPpw/0CBQCyvtTR7Lmu4olG4yRC/Tfkq79Mr3yuPs0SUitlO2HwGP3gitMJnRFw==} + engines: {node: '>=18'} + peerDependencies: + conventional-commits-filter: ^5.0.0 + conventional-commits-parser: ^6.4.0 + peerDependenciesMeta: + conventional-commits-filter: + optional: true + conventional-commits-parser: + optional: true + + '@ctrl/tinycolor@3.6.1': + resolution: {integrity: sha512-SITSV6aIXsuVNV3f3O0f2n/cgyEDWoSqtZMYiAmcsYHydcKrOz3gUxB/iXd/Qf08+IZX4KpgNbvUdMBmWz+kcA==} + engines: {node: '>=10'} + + '@emotion/hash@0.9.2': + resolution: {integrity: sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==} + + '@emotion/unitless@0.8.1': + resolution: {integrity: sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ==} + + '@esbuild/aix-ppc64@0.21.5': + resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.21.5': + resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.21.5': + resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==} + engines: {node: '>=12'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.21.5': + resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.21.5': + resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.21.5': + resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.21.5': + resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.21.5': + resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.21.5': + resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.21.5': + resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.21.5': + resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.21.5': + resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==} + engines: {node: '>=12'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.21.5': + resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.21.5': + resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.21.5': + resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.21.5': + resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.21.5': + resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-x64@0.21.5': + resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-x64@0.21.5': + resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + + '@esbuild/sunos-x64@0.21.5': + resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.21.5': + resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.21.5': + resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.21.5': + resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + + '@eslint-community/eslint-utils@4.10.1': + resolution: {integrity: sha512-cuadcxVFE8sDK6iWJbs8Sn0av2Nrh2QSGQhVlBW9AaAHqHwjWsZHT8LJ4hFGPh7ASBV2deFdM7H/DPjulmh8rg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + + '@eslint-community/regexpp@4.12.2': + resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + + '@eslint/eslintrc@2.1.4': + resolution: {integrity: sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + '@eslint/js@8.57.1': + resolution: {integrity: sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + '@humanwhocodes/config-array@0.13.0': + resolution: {integrity: sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==} + engines: {node: '>=10.10.0'} + deprecated: Use @eslint/config-array instead + + '@humanwhocodes/module-importer@1.0.1': + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} + engines: {node: '>=12.22'} + + '@humanwhocodes/object-schema@2.0.3': + resolution: {integrity: sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==} + deprecated: Use @eslint/object-schema instead + + '@jridgewell/gen-mapping@0.3.13': + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} + + '@jridgewell/remapping@2.3.5': + resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + + '@jridgewell/trace-mapping@0.3.31': + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + + '@nodelib/fs.scandir@2.1.5': + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} + + '@nodelib/fs.stat@2.0.5': + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} + + '@nodelib/fs.walk@1.2.8': + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} + + '@pkgr/core@0.3.6': + resolution: {integrity: sha512-SEeaJLb3qBNF/OaXnaR1NmmBbFYk1zC0ZH/52fATcRPLFg/p791YrcyFFy44Bo9sLaGuSuLp5Q6axbb/O+v/RA==} + engines: {node: ^14.18.0 || >=16.0.0} + + '@rolldown/pluginutils@1.0.1': + resolution: {integrity: sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==} + + '@rollup/plugin-strip@3.0.4': + resolution: {integrity: sha512-LDRV49ZaavxUo2YoKKMQjCxzCxugu1rCPQa0lDYBOWLj6vtzBMr8DcoJjsmg+s450RbKbe3qI9ZLaSO+O1oNbg==} + engines: {node: '>=14.0.0'} + peerDependencies: + rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true + + '@rollup/pluginutils@5.4.0': + resolution: {integrity: sha512-MfPp06CjRLfXQ3wY0R8vJDYBy/MvVcc9OulEfR0B8Iv9ko+GCNaRZ+EpJYFl27LhKsZK0o420sYCRHCjfCgeUg==} + engines: {node: '>=14.0.0'} + peerDependencies: + rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true + + '@rollup/rollup-android-arm-eabi@4.62.2': + resolution: {integrity: sha512-6o7ZLZK+BeenkZCFNDXqpbjw9bD6nuWonvS/lwQJp7NoVVxm6p3qE7qQ5jGuBjiFsgvqjD8mZAU5oWxTmbOeOg==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.62.2': + resolution: {integrity: sha512-BaH7BllCACHoH1LguOU56UItGfUWjujlO65kS9LAodViaN4bwIKd7oeW/ZHJ/4ljr/7MIiENnNy3HJ0zXv8Zkw==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.62.2': + resolution: {integrity: sha512-v39RCCvj4He82I9sFmk+M1VZ0PLM9sfsLVikjfx2hYBNALhrrOR2D3JjQA6AhlaSOgcR+RzrKY7e1+bT6SUO/A==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.62.2': + resolution: {integrity: sha512-yl0y2vq3S3lHeuXhEdss6TWfKW8vkujImO12tn4ZkG/4oghr09LvdYm2RElVjokTQiUvDUGXLGsYeLqUMCKpGA==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.62.2': + resolution: {integrity: sha512-tT4pvt4qXD+vEoezupCWi+a1F0vvDiksiHc+PxRlYTOH1I6/X4id9jPxTP+Fg+545euaFT1jJVs4CEdHZAU1vw==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.62.2': + resolution: {integrity: sha512-6nU5F2wCW+qvCBhTn1pdIU3bzsIoF7EUwsCDRxilWGprQR6yd508YnH9+OKFCwpfS8pjZqDUmnCAr7exax0XCg==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.62.2': + resolution: {integrity: sha512-n1GJHPOvpIfhi3TmrCeh6S6URt9BFCt0KQE3qvexyGCTAKpR4Lg+eWvNZEqu7epxwus/8ElT3hacYEucm49SZg==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm-musleabihf@4.62.2': + resolution: {integrity: sha512-JqgflS8wEB+UXV/vS1RpRbifGBeN4D5lz8D8oOFbFZw4vedvdOgCFAjfBmIMdW3yL10XpQQ0Ambepw6MXrhOnA==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm64-gnu@4.62.2': + resolution: {integrity: sha512-wnFJkogWvN4jm/hQRF2UBaeUmk20j5+DmHvoyWii2b8HJDyvz1MF2OU/6ynXt2KR63rbZLWkFpoytpdc/yBuSA==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-arm64-musl@4.62.2': + resolution: {integrity: sha512-HVu2bp0zhvJ8xHEV9+UUs7S90VadmBSY3LcIMvozbPo4AuMGDWlz3ymHLHZPX4hR67TKTt8Qp5PJ5RBg/i+RMQ==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-loong64-gnu@4.62.2': + resolution: {integrity: sha512-mQqqAV8QaoSgr9I2fKDLY2BAVvmKjWoGiu/cSYQonsLvtqwEn1E4QYfnCOcp5zoEqNhsDYin1s6jx/VJmrxlZg==} + cpu: [loong64] + os: [linux] + + '@rollup/rollup-linux-loong64-musl@4.62.2': + resolution: {integrity: sha512-IxKLoxCQ2IWi6bT2akyDUBGsOImDKB+sPp4EsTmwFQ/fMwpCKm8uLSSgP/Kx/QYUgKis6SEZ5/Nlhup0DIA0PQ==} + cpu: [loong64] + os: [linux] + + '@rollup/rollup-linux-ppc64-gnu@4.62.2': + resolution: {integrity: sha512-Mk5ha2RQSgyFfmYYLkBpPnUk8D8FriBxesO1u9O75X0mHgXL1UQcH5Itl2lurWL2tj0RxV9b9tJgipac0hRY9A==} + cpu: [ppc64] + os: [linux] + + '@rollup/rollup-linux-ppc64-musl@4.62.2': + resolution: {integrity: sha512-CjvEnqJL/0/TQ3TXX3OPIJ/kmBellrWd4heXUmHeJlTnmwjKpSJzoehLaL6Xk0ZnMHBu9dZuFADNOrtjF4v+2w==} + cpu: [ppc64] + os: [linux] + + '@rollup/rollup-linux-riscv64-gnu@4.62.2': + resolution: {integrity: sha512-1SiZbzwdkaDURsew/tSOrooKiYy7EQGT6m8ufavAi9NEyQb/6VuIxFXAL1fqa4iZe3g4NbNk4P7J32z2tw5Mgg==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-riscv64-musl@4.62.2': + resolution: {integrity: sha512-nQts12zJ3NQRoE6uYljOH89v7szzLDvG2JD/vsX+vGXU8w/At1GowTZ5/7qeFQ8m7L55rpR8Okugnuo5bgjy2Q==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-s390x-gnu@4.62.2': + resolution: {integrity: sha512-E9/ll019jhPIJgpzfZoIkBGhcz+kKNgVWYRY0zr9srBdPPFVpvOKW8VaJKUbeK+eZXyQF9ltME+Kk6affeaPgg==} + cpu: [s390x] + os: [linux] + + '@rollup/rollup-linux-x64-gnu@4.62.2': + resolution: {integrity: sha512-5BqxR/pshjey51iliyzTD5Xi3EN0aLmQ2lZ3lvefVV9c82BvrLo2/6OT55iifpWBufs6kdwWbuOKS841DrmK9A==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-linux-x64-musl@4.62.2': + resolution: {integrity: sha512-uNN83XxQrRAh/w0/pmAfibcwyb6YWt4gP+dpnQKPVJshAloQ785ii8CT8ZCIxkGg9opVsvAlGhFitSm6D1Jjpg==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-openbsd-x64@4.62.2': + resolution: {integrity: sha512-srjEIxSH3LRnJN6THczDHWQplqEMFiAJrTab0msUryh9kwNpkICf3Ea6q6MN/2cZwRFUNx5w+h6Hpi4QuHS6Zg==} + cpu: [x64] + os: [openbsd] + + '@rollup/rollup-openharmony-arm64@4.62.2': + resolution: {integrity: sha512-8hOJnxgbyObnCm5AlRA3A931xX19xq80RjVTKgJOvEKWqJruP/Uf12IbAOaDjjEXYRewwHLfmF0YRIdK3OwKWA==} + cpu: [arm64] + os: [openharmony] + + '@rollup/rollup-win32-arm64-msvc@4.62.2': + resolution: {integrity: sha512-mmF4AY1i0hG/bLWUctUq59gtmgaSIRa3cu/A3JFRp/sCNEme2bgDEiDS22P9FbnJB8NJNF4jPJiSP5RHQpUTDg==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.62.2': + resolution: {integrity: sha512-DZgkknc6jhHrk46V25vbAM0zZkyP0nSDkJB8/dRkLTxv470dOmWDqGoEJl/9A0dFfS7yE3REOwNDxpHwSLSt0Q==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-gnu@4.62.2': + resolution: {integrity: sha512-T6xr6ucWSFto+VGajA8YH26LdpHRuP4YLHEKAtCWvJDOlnmWcDZVCI2Jmjr+IFHDlt2zRaTAKE4tfjTaWLgJBg==} + cpu: [x64] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.62.2': + resolution: {integrity: sha512-BfzEnDJOt9T8M989/lA37EcJgat01wLRnoi5dQf3QzOH7jzpqTAzdDbVfRljVr5r+jzKqpbHeyOfAaXxAd0PAA==} + cpu: [x64] + os: [win32] + + '@simonwep/pickr@1.8.2': + resolution: {integrity: sha512-/l5w8BIkrpP6n1xsetx9MWPWlU6OblN5YgZZphxan0Tq4BByTCETL6lyIeY8lagalS2Nbt4F2W034KHLIiunKA==} + + '@simple-libs/child-process-utils@1.0.2': + resolution: {integrity: sha512-/4R8QKnd/8agJynkNdJmNw2MBxuFTRcNFnE5Sg/G+jkSsV8/UBgULMzhizWWW42p8L5H7flImV2ATi79Ove2Tw==} + engines: {node: '>=18'} + + '@simple-libs/stream-utils@1.2.0': + resolution: {integrity: sha512-KxXvfapcixpz6rVEB6HPjOUZT22yN6v0vI0urQSk1L8MlEWPDFCZkhw2xmkyoTGYeFw7tWTZd7e3lVzRZRN/EA==} + engines: {node: '>=18'} + + '@types/estree@1.0.9': + resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==} + + '@types/json-schema@7.0.15': + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + + '@types/node@20.19.43': + resolution: {integrity: sha512-6oYBAi5ikg4Pl+kGsoYtawUMBT2zZMCvPNF7pVLnHZfd1zf38DRiWn/gT01RYCdUqkv7Fhr+C9ot4/tb+2sVvA==} + + '@types/qs@6.15.1': + resolution: {integrity: sha512-GZHUBZR9hckSUhrxmp1nG6NwdpM9fCunJwyThLW1X3AyHgd9IlHb6VANpQQqDr2o/qQp6McZ3y/IA2rVzKzSbw==} + + '@types/semver@7.7.1': + resolution: {integrity: sha512-FmgJfu+MOcQ370SD0ev7EI8TlCAfKYU+B4m5T3yXc1CiRN94g/SZPtsCkk506aUDtlMnFZvasDwHHUcZUEaYuA==} + + '@typescript-eslint/eslint-plugin@6.21.0': + resolution: {integrity: sha512-oy9+hTPCUFpngkEZUSzbf9MxI65wbKFoQYsgPdILTfbUldp5ovUuphZVe4i30emU9M/kP+T64Di0mxl7dSw3MA==} + engines: {node: ^16.0.0 || >=18.0.0} + peerDependencies: + '@typescript-eslint/parser': ^6.0.0 || ^6.0.0-alpha + eslint: ^7.0.0 || ^8.0.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@typescript-eslint/parser@6.21.0': + resolution: {integrity: sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ==} + engines: {node: ^16.0.0 || >=18.0.0} + peerDependencies: + eslint: ^7.0.0 || ^8.0.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@typescript-eslint/scope-manager@6.21.0': + resolution: {integrity: sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg==} + engines: {node: ^16.0.0 || >=18.0.0} + + '@typescript-eslint/type-utils@6.21.0': + resolution: {integrity: sha512-rZQI7wHfao8qMX3Rd3xqeYSMCL3SoiSQLBATSiVKARdFGCYSRvmViieZjqc58jKgs8Y8i9YvVVhRbHSTA4VBag==} + engines: {node: ^16.0.0 || >=18.0.0} + peerDependencies: + eslint: ^7.0.0 || ^8.0.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@typescript-eslint/types@6.21.0': + resolution: {integrity: sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==} + engines: {node: ^16.0.0 || >=18.0.0} + + '@typescript-eslint/typescript-estree@6.21.0': + resolution: {integrity: sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==} + engines: {node: ^16.0.0 || >=18.0.0} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@typescript-eslint/utils@6.21.0': + resolution: {integrity: sha512-NfWVaC8HP9T8cbKQxHcsJBY5YE1O33+jpMwN45qzWWaPDZgLIbo12toGMWnmhvCpd3sIxkpDw3Wv1B3dYrbDQQ==} + engines: {node: ^16.0.0 || >=18.0.0} + peerDependencies: + eslint: ^7.0.0 || ^8.0.0 + + '@typescript-eslint/visitor-keys@6.21.0': + resolution: {integrity: sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==} + engines: {node: ^16.0.0 || >=18.0.0} + + '@ungap/structured-clone@1.3.3': + resolution: {integrity: sha512-60YRaenCQcVjYEKOcG824+DRGGIQ3VKErcBoAEDJZz5bKIs2ZG+X/H9Nk+Q6EVkwJk5QNApxbrc5QtBSwtrXAg==} + + '@vitejs/plugin-vue-jsx@4.2.0': + resolution: {integrity: sha512-DSTrmrdLp+0LDNF77fqrKfx7X0ErRbOcUAgJL/HbSesqQwoUvUQ4uYQqaex+rovqgGcoPqVk+AwUh3v9CuiYIw==} + engines: {node: ^18.0.0 || >=20.0.0} + peerDependencies: + vite: ^5.0.0 || ^6.0.0 + vue: ^3.0.0 + + '@vitejs/plugin-vue@4.6.2': + resolution: {integrity: sha512-kqf7SGFoG+80aZG6Pf+gsZIVvGSCKE98JbiWqcCV9cThtg91Jav0yvYFC9Zb+jKetNGF6ZKeoaxgZfND21fWKw==} + engines: {node: ^14.18.0 || >=16.0.0} + peerDependencies: + vite: ^4.0.0 || ^5.0.0 + vue: ^3.2.25 + + '@volar/language-core@2.4.15': + resolution: {integrity: sha512-3VHw+QZU0ZG9IuQmzT68IyN4hZNd9GchGPhbD9+pa8CVv7rnoOZwo7T8weIbrRmihqy3ATpdfXFnqRrfPVK6CA==} + + '@volar/source-map@2.4.15': + resolution: {integrity: sha512-CPbMWlUN6hVZJYGcU/GSoHu4EnCHiLaXI9n8c9la6RaI9W5JHX+NqG+GSQcB0JdC2FIBLdZJwGsfKyBB71VlTg==} + + '@volar/typescript@2.4.15': + resolution: {integrity: sha512-2aZ8i0cqPGjXb4BhkMsPYDkkuc2ZQ6yOpqwAuNwUoncELqoy5fRgOQtLR9gB0g902iS0NAkvpIzs27geVyVdPg==} + + '@vue/babel-helper-vue-transform-on@1.5.0': + resolution: {integrity: sha512-0dAYkerNhhHutHZ34JtTl2czVQHUNWv6xEbkdF5W+Yrv5pCWsqjeORdOgbtW2I9gWlt+wBmVn+ttqN9ZxR5tzA==} + + '@vue/babel-plugin-jsx@1.5.0': + resolution: {integrity: sha512-mneBhw1oOqCd2247O0Yw/mRwC9jIGACAJUlawkmMBiNmL4dGA2eMzuNZVNqOUfYTa6vqmND4CtOPzmEEEqLKFw==} + peerDependencies: + '@babel/core': ^7.0.0-0 + peerDependenciesMeta: + '@babel/core': + optional: true + + '@vue/babel-plugin-resolve-type@1.5.0': + resolution: {integrity: sha512-Wm/60o+53JwJODm4Knz47dxJnLDJ9FnKnGZJbUUf8nQRAtt6P+undLUAVU3Ha33LxOJe6IPoifRQ6F/0RrU31w==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@vue/compiler-core@3.5.40': + resolution: {integrity: sha512-39E8IgOhTbVDnoJFMKc2DvYnypcZwUqgUhQkccva/0m6FUwtIKSGV7n1hpVmYcFaoRAwf9pBcwnKlCEsN63ZEQ==} + + '@vue/compiler-dom@3.5.40': + resolution: {integrity: sha512-pwkx4vqlqOspFstrcmzwkKLePVMD3PT65imRzLhanU2V1Fj4K13g6OXjanOyzw3aTAuRk84BOmY8f3rEHqPaVA==} + + '@vue/compiler-sfc@3.5.40': + resolution: {integrity: sha512-gIf497P4kpuALcvs5n3AEg1Vdn0pSY4XbjASIfHNYF1/MP3T2Mf2STERTubysBxCRxzJGJYtF/O7vwJrxFB3Vw==} + + '@vue/compiler-ssr@3.5.40': + resolution: {integrity: sha512-rrE5xiXG663+vHCHa3J9p2z5OcBRjXmoqenprJxAFQxg5pSshzeBiCE6pu46axapRJ2Adk0YDA2BRZVjiHXnhg==} + + '@vue/compiler-vue2@2.7.16': + resolution: {integrity: sha512-qYC3Psj9S/mfu9uVi5WvNZIzq+xnXMhOwbTFKKDD7b1lhpnn71jXSFdTQ+WsIEk0ONCd7VV2IMm7ONl6tbQ86A==} + + '@vue/devtools-api@6.6.4': + resolution: {integrity: sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==} + + '@vue/language-core@2.2.12': + resolution: {integrity: sha512-IsGljWbKGU1MZpBPN+BvPAdr55YPkj2nB/TBNGNC32Vy2qLG25DYu/NBN2vNtZqdRbTRjaoYrahLrToim2NanA==} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@vue/reactivity@3.5.40': + resolution: {integrity: sha512-B7ot9UlUZOi1zbq61/LvE88ZLTV8IlajTdiZTAEiDQgrnIMIZoPr9kGw0Zw46ObW62O9+H/Be3kMbfb7kYPQZA==} + + '@vue/runtime-core@3.5.40': + resolution: {integrity: sha512-KAZLweuZ6uUJPK1PMSQPgBU5gCjgrrfjUhSglmU9NhH+Zjepa8cnwSydPWDWHDwOgY4g3VcZ+PljbiHlURNCbw==} + + '@vue/runtime-dom@3.5.40': + resolution: {integrity: sha512-ZfrX8ssZQds900L9pr8AuK05ddnMsR4MPMZr8cPN9GoqoPWcXLhjvvbIA2SMv+7a97sJ1vv9pj/zxK0Cq/eEFQ==} + + '@vue/server-renderer@3.5.40': + resolution: {integrity: sha512-XNJym9WpevhTVt1HuwOrCRJ5Q+9z4BjTMrDtjTrvx74SmUll8spNTw6whWJa9mEkO4PKn5TihI/bm/8ds2QVJw==} + + '@vue/shared@3.5.40': + resolution: {integrity: sha512-WxnBtruIqOoV3rA4jeKDWzrYI5h7Cp4+pjwDi8kWGHz+IslhiN+wguLVVhtv2l8VoU02rzDCVfDjgCl1lNpZVg==} + + '@yp-component/root@0.0.48': + resolution: {integrity: sha512-TOp8adAob3pMSfecQjEC4uVNCGqNWcShDjSQysd4pYY34i5ldFLRGSiJdpLsFG1xKkcSudiqEc/Si/ZfYoDSqA==} + + accepts@2.0.0: + resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==} + engines: {node: '>= 0.6'} + + acorn-jsx@5.3.2: + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + + acorn@8.17.0: + resolution: {integrity: sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg==} + engines: {node: '>=0.4.0'} + hasBin: true + + ajv@6.15.0: + resolution: {integrity: sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==} + + ajv@8.20.0: + resolution: {integrity: sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==} + + alien-signals@1.0.13: + resolution: {integrity: sha512-OGj9yyTnJEttvzhTUWuscOvtqxq5vrhF7vL9oS0xJ2mK0ItPYP1/y+vCFebfxoEyAz0++1AIwJ5CMr+Fk3nDmg==} + + ansi-escapes@7.3.0: + resolution: {integrity: sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg==} + engines: {node: '>=18'} + + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + ansi-regex@6.2.2: + resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==} + engines: {node: '>=12'} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + ansi-styles@6.2.3: + resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} + engines: {node: '>=12'} + + ant-design-vue@4.2.6: + resolution: {integrity: sha512-t7eX13Yj3i9+i5g9lqFyYneoIb3OzTvQjq9Tts1i+eiOd3Eva/6GagxBSXM1fOCjqemIu0FYVE1ByZ/38epR3Q==} + engines: {node: '>=12.22.0'} + peerDependencies: + vue: '>=3.2.0' + + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + + array-ify@1.0.0: + resolution: {integrity: sha512-c5AMf34bKdvPhQ7tBGhqkgKNUzMr4WUs+WDtC2ZUGOUncbxKMTvqxYctiseW3+L4bA8ec+GcZ6/A/FW4m8ukng==} + + array-tree-filter@2.1.0: + resolution: {integrity: sha512-4ROwICNlNw/Hqa9v+rk5h22KjmzB1JGTMVKP2AKJBOCgb0yL0ASf0+YvCcLNNwquOHNX48jkeZIJ3a+oOQqKcw==} + + array-union@2.1.0: + resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} + engines: {node: '>=8'} + + async-validator@4.2.5: + resolution: {integrity: sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg==} + + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + base64-arraybuffer@1.0.2: + resolution: {integrity: sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==} + engines: {node: '>= 0.6.0'} + + baseline-browser-mapping@2.11.1: + resolution: {integrity: sha512-HYXq73DDpCtNzOmrFsm9eSwCvWCql0RzqjpDzXN9EadiLJ4DNat0nsZ/Bzmy+Ud12mb4/zKDY0cQ805ZzN+i0A==} + engines: {node: '>=6.0.0'} + hasBin: true + + body-parser@2.3.0: + resolution: {integrity: sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==} + engines: {node: '>=18'} + + boolbase@1.0.0: + resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} + + brace-expansion@1.1.16: + resolution: {integrity: sha512-IDw48K2/2kRkg9LdJxurvq3lV3aBgq0REY89duEqFRthjlPdXHKMj7EnQOXVckxzgisinf3nHfrcE2FufFLXMw==} + + brace-expansion@2.1.2: + resolution: {integrity: sha512-w5JZcKgdhDOgOwm8H+KgbosopHMuGcl6qbulwjtz3SM7I7P3yW1eAjzMPLrIE+NQ9vjgANKHWeMHnrT0OXW1oA==} + + braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} + + browserslist@4.28.7: + resolution: {integrity: sha512-JxV13hNrFxqjOc8alRbq9dK1MM79NEXYpma2B2J4wAtpWS5zIEIKqWPGCl7N4o7Uc7B7itylh7SuDujATRyyTw==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + + bytes@3.1.2: + resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} + engines: {node: '>= 0.8'} + + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} + + call-bound@1.0.4: + resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} + engines: {node: '>= 0.4'} + + callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + + caniuse-lite@1.0.30001806: + resolution: {integrity: sha512-72Cuvd95zbSYPKq6Fhg8eDJRlzgWDf7/mtoZv6Qe/DYNCEBdNxoA3+rZAU2ZhGCpZlns3EssFavaZomckT5Uuw==} + + chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + + chalk@5.6.2: + resolution: {integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==} + engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} + + china-area-data@5.0.1: + resolution: {integrity: sha512-BQDPpiv5Nn+018ekcJK2oSD9PAD+E1bvXB0wgabc//dFVS/KvRqCgg0QOEUt3vBkx9XzB5a9BmkJCEZDBxVjVw==} + + cli-cursor@5.0.0: + resolution: {integrity: sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==} + engines: {node: '>=18'} + + cli-truncate@4.0.0: + resolution: {integrity: sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA==} + engines: {node: '>=18'} + + cliui@8.0.1: + resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} + engines: {node: '>=12'} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + colorette@2.0.20: + resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==} + + commander@13.1.0: + resolution: {integrity: sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==} + engines: {node: '>=18'} + + compare-func@2.0.0: + resolution: {integrity: sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA==} + + compute-scroll-into-view@1.0.20: + resolution: {integrity: sha512-UCB0ioiyj8CRjtrvaceBLqqhZCVP+1B8+NWQhmdsm0VXOJtobBCf1dBQmebCCo34qZmUwZfIH2MZLqNHazrfjg==} + + concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + + content-disposition@1.1.0: + resolution: {integrity: sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==} + engines: {node: '>=18'} + + content-type@1.0.5: + resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} + engines: {node: '>= 0.6'} + + content-type@2.0.0: + resolution: {integrity: sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==} + engines: {node: '>=18'} + + conventional-changelog-angular@8.3.1: + resolution: {integrity: sha512-6gfI3otXK5Ph5DfCOI1dblr+kN3FAm5a97hYoQkqNZxOaYa5WKfXH+AnpsmS+iUH2mgVC2Cg2Qw9m5OKcmNrIg==} + engines: {node: '>=18'} + + conventional-changelog-conventionalcommits@9.3.1: + resolution: {integrity: sha512-dTYtpIacRpcZgrvBYvBfArMmK2xvIpv2TaxM0/ZI5CBtNUzvF2x0t15HsbRABWprS6UPmvj+PzHVjSx4qAVKyw==} + engines: {node: '>=18'} + + conventional-commits-parser@6.4.0: + resolution: {integrity: sha512-tvRg7FIBNlyPzjdG8wWRlPHQJJHI7DylhtRGeU9Lq+JuoPh5BKpPRX83ZdLrvXuOSu5Eo/e7SzOQhU4Hd2Miuw==} + engines: {node: '>=18'} + hasBin: true + + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + + cookie-signature@1.2.2: + resolution: {integrity: sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==} + engines: {node: '>=6.6.0'} + + cookie@0.7.2: + resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} + engines: {node: '>= 0.6'} + + copy-anything@3.0.5: + resolution: {integrity: sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w==} + engines: {node: '>=12.13'} + + core-js@3.49.0: + resolution: {integrity: sha512-es1U2+YTtzpwkxVLwAFdSpaIMyQaq0PBgm3YD1W3Qpsn1NAmO3KSgZfu+oGSWVu6NvLHoHCV/aYcsE5wiB7ALg==} + + cosmiconfig-typescript-loader@6.3.0: + resolution: {integrity: sha512-Akr82WH1Wfqatyiqpj8HDkO2o2KmJRu1FhKfSNJP3K4IdXwHfEyL7MOb62i1AGQVLtIQM+iCE9CGOtrfhR+mmA==} + engines: {node: '>=v18'} + peerDependencies: + '@types/node': '*' + cosmiconfig: '>=9' + typescript: '>=5' + + cosmiconfig@9.0.2: + resolution: {integrity: sha512-gtTZxTDau1wL7Y7zifc2dd8jHSK/k6BTx/2Xp/BpdlAdnlYWFVt7qhJqgwi7637yRwRQ3qL4ZidbB4I8tA5VOg==} + engines: {node: '>=14'} + peerDependencies: + typescript: '>=4.9.5' + peerDependenciesMeta: + typescript: + optional: true + + cropperjs@1.6.2: + resolution: {integrity: sha512-nhymn9GdnV3CqiEHJVai54TULFAE3VshJTXSqSJKa8yXAKyBKDWdhHarnlIPrshJ0WMFTGuFvG02YjLXfPiuOA==} + + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + + css-line-break@2.1.0: + resolution: {integrity: sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==} + + cssesc@3.0.0: + resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} + engines: {node: '>=4'} + hasBin: true + + csstype@3.2.3: + resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + + dayjs@1.11.21: + resolution: {integrity: sha512-98IT+HOahAisibz/yjKbzuOBwYcjJ7BCLPzARyHiyEBmRz4fatF+KPJszEHXsGYjUG234aH/cOjW1wwTbKUZlA==} + + de-indent@1.0.2: + resolution: {integrity: sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==} + + 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 + + deep-is@0.1.4: + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + + depd@2.0.0: + resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} + engines: {node: '>= 0.8'} + + dir-glob@3.0.1: + resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} + engines: {node: '>=8'} + + doctrine@3.0.0: + resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} + engines: {node: '>=6.0.0'} + + dom-align@1.12.4: + resolution: {integrity: sha512-R8LUSEay/68zE5c8/3BDxiTEvgb4xZTF0RKmAHfiEVN3klfIpXfi2/QCoiWPccVQ0J/ZGdz9OjzL4uJEP/MRAw==} + + dom-scroll-into-view@2.0.1: + resolution: {integrity: sha512-bvVTQe1lfaUr1oFzZX80ce9KLDlZ3iU+XGNE/bz9HnGdklTieqsbmsLHe+rT2XWqopvL0PckkYqN7ksmm5pe3w==} + + dot-prop@5.3.0: + resolution: {integrity: sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==} + engines: {node: '>=8'} + + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + + ee-first@1.1.1: + resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} + + electron-to-chromium@1.5.395: + resolution: {integrity: sha512-7zt9Aw+SrmxLWLN0zhaTWZQiCdryLVrYTq5R7iZakLvi2UQPYMMsROYV/2qVCzMeCiSXHwKOU+sZ4zOVVlrtKA==} + + emoji-regex@10.6.0: + resolution: {integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==} + + emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + + encodeurl@2.0.0: + resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} + engines: {node: '>= 0.8'} + + entities@7.0.1: + resolution: {integrity: sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==} + engines: {node: '>=0.12'} + + env-paths@2.2.1: + resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==} + engines: {node: '>=6'} + + environment@1.1.0: + resolution: {integrity: sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==} + engines: {node: '>=18'} + + errno@0.1.8: + resolution: {integrity: sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==} + hasBin: true + + error-ex@1.3.4: + resolution: {integrity: sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==} + + 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-toolkit@1.49.0: + resolution: {integrity: sha512-G5iZ6Pc/FNRY/soKZHC+TxGDD83rHUDXxzaWhGCX44vAv/tMs56WMusnm/KMNK+luUPsgA9U28cGr4RDlSzL2g==} + + esbuild@0.21.5: + resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==} + engines: {node: '>=12'} + hasBin: true + + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + + escape-html@1.0.3: + resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} + + escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + + eslint-config-prettier@9.1.2: + resolution: {integrity: sha512-iI1f+D2ViGn+uvv5HuHVUamg8ll4tN+JRHGc6IJi4TP9Kl976C57fzPXgseXNs8v0iA8aSJpHsTWjDb9QJamGQ==} + hasBin: true + peerDependencies: + eslint: '>=7.0.0' + + eslint-plugin-prettier@5.5.6: + resolution: {integrity: sha512-ifetmTcxWfz+4qRW3pH/ujdTq2jQIj59AxJMIN26K5avYgU8dxycUETQonWiW+wPrYXA0j3Try0l1CnwVQtDqQ==} + engines: {node: ^14.18.0 || >=16.0.0} + peerDependencies: + '@types/eslint': '>=8.0.0' + eslint: '>=8.0.0' + eslint-config-prettier: '>= 7.0.0 <10.0.0 || >=10.1.0' + prettier: '>=3.0.0' + peerDependenciesMeta: + '@types/eslint': + optional: true + eslint-config-prettier: + optional: true + + eslint-plugin-vue@9.33.0: + resolution: {integrity: sha512-174lJKuNsuDIlLpjeXc5E2Tss8P44uIimAfGD0b90k0NoirJqpG7stLuU9Vp/9ioTOrQdWVREc4mRd1BD+CvGw==} + engines: {node: ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.2.0 || ^7.0.0 || ^8.0.0 || ^9.0.0 + + eslint-scope@7.2.2: + resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + eslint-visitor-keys@3.4.3: + resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + eslint@8.57.1: + resolution: {integrity: sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + deprecated: This version is no longer supported. Please see https://eslint.org/version-support for other options. + hasBin: true + + espree@9.6.1: + resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + esquery@1.7.0: + resolution: {integrity: sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==} + engines: {node: '>=0.10'} + + esrecurse@4.3.0: + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} + + estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + + estree-walker@2.0.2: + resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} + + esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + + etag@1.8.1: + resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} + engines: {node: '>= 0.6'} + + eventemitter3@5.0.4: + resolution: {integrity: sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==} + + execa@8.0.1: + resolution: {integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==} + engines: {node: '>=16.17'} + + express@5.2.1: + resolution: {integrity: sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==} + engines: {node: '>= 18'} + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-diff@1.3.0: + resolution: {integrity: sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==} + + fast-glob@3.3.3: + resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} + engines: {node: '>=8.6.0'} + + fast-json-stable-stringify@2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + + fast-levenshtein@2.0.6: + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + + fast-uri@3.1.4: + resolution: {integrity: sha512-8JnbkQ4juDyvYs4mgFGQqg4yCYtFDtUtmp2QIQq11ZZe5CFQ5wcqm1rqDgAh/QdMySuBnPzMUiJUNZG5N/AiQw==} + + fastq@1.20.1: + resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==} + + file-entry-cache@6.0.1: + resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} + engines: {node: ^10.12.0 || >=12.0.0} + + fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} + + finalhandler@2.1.1: + resolution: {integrity: sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==} + engines: {node: '>= 18.0.0'} + + find-up@5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} + + flat-cache@3.2.0: + resolution: {integrity: sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==} + engines: {node: ^10.12.0 || >=12.0.0} + + flatted@3.4.3: + resolution: {integrity: sha512-/zipXxyO6rGvuNGDiULY9MvEGSkb2gaG4GGH4ygMi0ZZzyMHdUZBmntJmx5x1G2VuPytCwGN4xsJP6cw+sK+vQ==} + + forwarded@0.2.0: + resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} + engines: {node: '>= 0.6'} + + fresh@2.0.0: + resolution: {integrity: sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==} + engines: {node: '>= 0.8'} + + fs.realpath@1.0.0: + resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + + 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==} + + gensync@1.0.0-beta.2: + resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} + engines: {node: '>=6.9.0'} + + get-caller-file@2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} + + get-east-asian-width@1.6.0: + resolution: {integrity: sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==} + engines: {node: '>=18'} + + 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'} + + get-stream@8.0.1: + resolution: {integrity: sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==} + engines: {node: '>=16'} + + git-raw-commits@5.0.1: + resolution: {integrity: sha512-Y+csSm2GD/PCSh6Isd/WiMjNAydu0VBiG9J7EdQsNA5P9uXvLayqjmTsNlK5Gs9IhblFZqOU0yid5Il5JPoLiQ==} + engines: {node: '>=18'} + hasBin: true + + glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + + glob-parent@6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + + glob@7.2.3: + resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} + deprecated: Glob versions prior to v9 are no longer supported + + global-directory@5.0.0: + resolution: {integrity: sha512-1pgFdhK3J2LeM+dVf2Pd424yHx2ou338lC0ErNP2hPx4j8eW1Sp0XqSjNxtk6Tc4Kr5wlWtSvz8cn2yb7/SG/w==} + engines: {node: '>=20'} + + globals@13.24.0: + resolution: {integrity: sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==} + engines: {node: '>=8'} + + globby@11.1.0: + resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} + engines: {node: '>=10'} + + 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==} + + graphemer@1.4.0: + resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} + + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + + has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} + + hasown@2.0.4: + resolution: {integrity: sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==} + engines: {node: '>= 0.4'} + + he@1.2.0: + resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==} + hasBin: true + + html2canvas@1.4.1: + resolution: {integrity: sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==} + engines: {node: '>=8.0.0'} + + http-errors@2.0.1: + resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==} + engines: {node: '>= 0.8'} + + human-signals@5.0.0: + resolution: {integrity: sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==} + engines: {node: '>=16.17.0'} + + husky@9.1.7: + resolution: {integrity: sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==} + engines: {node: '>=18'} + hasBin: true + + 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'} + + iconv-lite@0.7.3: + resolution: {integrity: sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==} + engines: {node: '>=0.10.0'} + + ignore@5.3.2: + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} + engines: {node: '>= 4'} + + import-fresh@3.3.1: + resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} + engines: {node: '>=6'} + + import-meta-resolve@4.2.0: + resolution: {integrity: sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==} + + imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + + inflight@1.0.6: + resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} + deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. + + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + ini@6.0.0: + resolution: {integrity: sha512-IBTdIkzZNOpqm7q3dRqJvMaldXjDHWkEDfrwGEQTs5eaQMWV+djAhR+wahyNNMAa+qpbDUhBMVt4ZKNwpPm7xQ==} + engines: {node: ^20.17.0 || >=22.9.0} + + ipaddr.js@1.9.1: + resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} + engines: {node: '>= 0.10'} + + is-arrayish@0.2.1: + resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + + is-fullwidth-code-point@4.0.0: + resolution: {integrity: sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==} + engines: {node: '>=12'} + + is-fullwidth-code-point@5.1.0: + resolution: {integrity: sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==} + engines: {node: '>=18'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + + is-obj@2.0.0: + resolution: {integrity: sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==} + engines: {node: '>=8'} + + is-path-inside@3.0.3: + resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} + engines: {node: '>=8'} + + is-plain-obj@4.1.0: + resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==} + engines: {node: '>=12'} + + is-plain-object@3.0.1: + resolution: {integrity: sha512-Xnpx182SBMrr/aBik8y+GuR4U1L9FqMSojwDQwPMmxyC6bvEqly9UBCxhauBF5vNh2gwWJNX6oDV7O+OM4z34g==} + engines: {node: '>=0.10.0'} + + is-promise@4.0.0: + resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==} + + is-stream@3.0.0: + resolution: {integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + is-what@4.1.16: + resolution: {integrity: sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A==} + engines: {node: '>=12.13'} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + jiti@2.6.1: + resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==} + hasBin: true + + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + js-yaml@4.3.0: + resolution: {integrity: sha512-1td788aAnnZ5qs7V2QIRl1owjtYpbKt749Y3xauqQgwIIGF/xXWz1wMTEBx5O3LK3lXLVuqXPdPxj2BoFHaW9Q==} + hasBin: true + + jsesc@3.1.0: + resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} + engines: {node: '>=6'} + hasBin: true + + json-buffer@3.0.1: + resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + + json-parse-even-better-errors@2.3.1: + resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} + + json-schema-traverse@0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + + json-schema-traverse@1.0.0: + resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} + + json-stable-stringify-without-jsonify@1.0.1: + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + + json5@2.2.3: + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} + hasBin: true + + keyv@4.5.4: + resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + + less@4.8.0: + resolution: {integrity: sha512-7Y7DJBMbsW29UGjOG6NGvxQEx71AaDcrryBwYCMaFwn0kj8FkSueKN9r9WexVOetKEhXh38kL++WJncfkEpS+g==} + engines: {node: '>=18'} + hasBin: true + + levn@0.4.1: + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} + + lilconfig@3.1.3: + resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==} + engines: {node: '>=14'} + + lines-and-columns@1.2.4: + resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + + lint-staged@15.5.2: + resolution: {integrity: sha512-YUSOLq9VeRNAo/CTaVmhGDKG+LBtA8KF1X4K5+ykMSwWST1vDxJRB2kv2COgLb1fvpCo+A/y9A0G0znNVmdx4w==} + engines: {node: '>=18.12.0'} + hasBin: true + + listr2@8.3.3: + resolution: {integrity: sha512-LWzX2KsqcB1wqQ4AHgYb4RsDXauQiqhjLk+6hjbaeHG4zpjjVAB6wC/gz6X0l+Du1cN3pUB5ZlrvTbhGSNnUQQ==} + engines: {node: '>=18.0.0'} + + locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} + + lodash-es@4.18.1: + resolution: {integrity: sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A==} + + lodash.merge@4.6.2: + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + + lodash@4.18.1: + resolution: {integrity: sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==} + + log-update@6.1.0: + resolution: {integrity: sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==} + engines: {node: '>=18'} + + loose-envify@1.4.0: + resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} + hasBin: true + + lru-cache@5.1.1: + resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + + 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'} + + media-typer@1.1.0: + resolution: {integrity: sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==} + engines: {node: '>= 0.8'} + + meow@13.2.0: + resolution: {integrity: sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA==} + engines: {node: '>=18'} + + merge-descriptors@2.0.0: + resolution: {integrity: sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==} + engines: {node: '>=18'} + + merge-stream@2.0.0: + resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} + + merge2@1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} + + micromatch@4.0.8: + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} + engines: {node: '>=8.6'} + + mime-db@1.54.0: + resolution: {integrity: sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==} + engines: {node: '>= 0.6'} + + mime-types@3.0.2: + resolution: {integrity: sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==} + engines: {node: '>=18'} + + mime@1.6.0: + resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==} + engines: {node: '>=4'} + hasBin: true + + mimic-fn@4.0.0: + resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==} + engines: {node: '>=12'} + + mimic-function@5.0.1: + resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==} + engines: {node: '>=18'} + + minimatch@3.1.5: + resolution: {integrity: sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==} + + minimatch@9.0.3: + resolution: {integrity: sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==} + engines: {node: '>=16 || 14 >=14.17'} + + minimatch@9.0.9: + resolution: {integrity: sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==} + engines: {node: '>=16 || 14 >=14.17'} + + minimist@1.2.8: + resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + + 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.16: + resolution: {integrity: sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + nanopop@2.4.2: + resolution: {integrity: sha512-NzOgmMQ+elxxHeIha+OG/Pv3Oc3p4RU2aBhwWwAqDpXrdTbtRylbRLQztLy8dMMwfl6pclznBdfUhccEn9ZIzw==} + + natural-compare@1.4.0: + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + + 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 + + negotiator@1.0.0: + resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==} + engines: {node: '>= 0.6'} + + node-releases@2.0.51: + resolution: {integrity: sha512-wRNIrw4DmVLKQlbgOMdkMx27Wrpzes2hh5Jtbi2bjPd+4wJstWIqP5A+lscnqbm0xxmT5Bpg8Lec5ItEBwx6BQ==} + engines: {node: '>=18'} + + npm-run-path@5.3.0: + resolution: {integrity: sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + nth-check@2.1.1: + resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} + + object-inspect@1.13.4: + resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} + engines: {node: '>= 0.4'} + + on-finished@2.4.1: + resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} + engines: {node: '>= 0.8'} + + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + + onetime@6.0.0: + resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==} + engines: {node: '>=12'} + + onetime@7.0.0: + resolution: {integrity: sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==} + engines: {node: '>=18'} + + optionator@0.9.4: + resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} + engines: {node: '>= 0.8.0'} + + p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + + p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + + parent-module@1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} + + parse-json@5.2.0: + resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} + engines: {node: '>=8'} + + parse-node-version@1.0.1: + resolution: {integrity: sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==} + engines: {node: '>= 0.10'} + + parseurl@1.3.3: + resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} + engines: {node: '>= 0.8'} + + path-browserify@1.0.1: + resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==} + + path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + + path-is-absolute@1.0.1: + resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} + engines: {node: '>=0.10.0'} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + path-key@4.0.0: + resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==} + engines: {node: '>=12'} + + path-to-regexp@8.4.2: + resolution: {integrity: sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==} + + path-type@4.0.0: + resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} + engines: {node: '>=8'} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@2.3.2: + resolution: {integrity: sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==} + engines: {node: '>=8.6'} + + picomatch@4.0.5: + resolution: {integrity: sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==} + engines: {node: '>=12'} + + pidtree@0.6.1: + resolution: {integrity: sha512-e0F9AOF1JMrCfBsyJOwU9lNvQ0WtXTq0j/4jk0BQ5JSI9VAybPXmDpPRw/2FQ3e5d3ZFN1mLh7jW99m/jjaptw==} + engines: {node: '>=0.10'} + hasBin: true + + postcss-selector-parser@6.1.4: + resolution: {integrity: sha512-bIoJLOmjCO1S9XdY/DcnR5hJxvrDir1PbGChrzXG3vw0/FOliy/fA3dmdhQ441kah4gKv+TwckGzex6wNS5cnQ==} + engines: {node: '>=4'} + + postcss@8.5.21: + resolution: {integrity: sha512-v4sDNP3fdNiWMfabO7OwOQdOX8TiQSztKyT1Wj0w+j7LDallJThJRBBBmzVGyYj0crMh7jlV4zepPkiNu9UwDQ==} + engines: {node: ^10 || ^12 || >=14} + + prelude-ls@1.2.1: + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} + + prettier-linter-helpers@1.0.1: + resolution: {integrity: sha512-SxToR7P8Y2lWmv/kTzVLC1t/GDI2WGjMwNhLLE9qtH8Q13C+aEmuRlzDst4Up4s0Wc8sF2M+J57iB3cMLqftfg==} + engines: {node: '>=6.0.0'} + + prettier@3.9.6: + resolution: {integrity: sha512-OpN0zzVdiaiAhxpuuj5efpIS4sY9j7bY6uR5mnj5yPzGkdkjNKSJeUThPb60Jw29QuAZgA4o+/iB49kFiaBX6g==} + engines: {node: '>=14'} + hasBin: true + + probe-image-size@7.3.0: + resolution: {integrity: sha512-7CaDeBwiAbh6ohXsvLbAZhO7wzsZAmaevfxe39qvCwRh8LyaZfDlBGGLU1CCTgrTLtCOdwBBhjOrIHaIIimHfQ==} + + proxy-addr@2.0.7: + resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} + engines: {node: '>= 0.10'} + + prr@1.0.1: + resolution: {integrity: sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==} + + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + + qs@6.15.3: + resolution: {integrity: sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A==} + engines: {node: '>=0.6'} + + queue-microtask@1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + + range-parser@1.3.0: + resolution: {integrity: sha512-hek2mFQpPuI4E1BBKrSto+BU3e3x4xuarsbiwr3+lf7p44juvFMV0XFWQAP3xUyqXA4RrXLIoaSUGbSt056ZMw==} + engines: {node: '>= 0.6'} + + raw-body@3.0.2: + resolution: {integrity: sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==} + engines: {node: '>= 0.10'} + + require-directory@2.1.1: + resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} + engines: {node: '>=0.10.0'} + + require-from-string@2.0.2: + resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} + engines: {node: '>=0.10.0'} + + resize-observer-polyfill@1.5.1: + resolution: {integrity: sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==} + + resolve-from@4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} + + resolve-from@5.0.0: + resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} + engines: {node: '>=8'} + + restore-cursor@5.1.0: + resolution: {integrity: sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==} + engines: {node: '>=18'} + + reusify@1.1.0: + resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + + rfdc@1.4.1: + resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==} + + rimraf@3.0.2: + resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} + deprecated: Rimraf versions prior to v4 are no longer supported + hasBin: true + + rollup@4.62.2: + resolution: {integrity: sha512-RFnrW4lhXA3s3eqHDZvN654g8OTjzRfqpIRJYczCGB6HzphckVAi/Qh4tbPUbRuDi7s1Llv8g/NspLkttY3gTA==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + + router@2.2.0: + resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==} + engines: {node: '>= 18'} + + run-parallel@1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + + sax@1.6.0: + resolution: {integrity: sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA==} + engines: {node: '>=11.0.0'} + + scroll-into-view-if-needed@2.2.31: + resolution: {integrity: sha512-dGCXy99wZQivjmjIqihaBQNjryrz5rueJY7eHfTdyWEiR4ttYpsajb14rn9s5d4DY4EcY6+4+U/maARBXJedkA==} + + semver@6.3.1: + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} + hasBin: true + + semver@7.8.5: + resolution: {integrity: sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==} + engines: {node: '>=10'} + hasBin: true + + send@1.2.1: + resolution: {integrity: sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==} + engines: {node: '>= 18'} + + serve-static@2.2.1: + resolution: {integrity: sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==} + engines: {node: '>= 18'} + + setprototypeof@1.2.0: + resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} + + shallow-equal@1.2.1: + resolution: {integrity: sha512-S4vJDjHHMBaiZuT9NPb616CSmLf618jawtv3sufLl6ivK8WocjAo58cXwbRV1cgqxH0Qbv+iUt6m05eqEa2IRA==} + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + side-channel-list@1.0.1: + resolution: {integrity: sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==} + engines: {node: '>= 0.4'} + + side-channel-map@1.0.1: + resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==} + engines: {node: '>= 0.4'} + + side-channel-weakmap@1.0.2: + resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} + engines: {node: '>= 0.4'} + + side-channel@1.1.1: + resolution: {integrity: sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==} + engines: {node: '>= 0.4'} + + signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + + slash@3.0.0: + resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} + engines: {node: '>=8'} + + slice-ansi@5.0.0: + resolution: {integrity: sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==} + engines: {node: '>=12'} + + slice-ansi@7.1.2: + resolution: {integrity: sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==} + engines: {node: '>=18'} + + 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'} + + statuses@2.0.2: + resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} + engines: {node: '>= 0.8'} + + stream-parser@0.3.1: + resolution: {integrity: sha512-bJ/HgKq41nlKvlhccD5kaCr/P+Hu0wPNKPJOH7en+YrJu/9EgqUF+88w5Jb6KNcjOFMhfX4B2asfeAtIGuHObQ==} + + string-argv@0.3.2: + resolution: {integrity: sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==} + engines: {node: '>=0.6.19'} + + string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + + string-width@7.2.0: + resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==} + engines: {node: '>=18'} + + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + + strip-ansi@7.2.0: + resolution: {integrity: sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==} + engines: {node: '>=12'} + + strip-final-newline@3.0.0: + resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==} + engines: {node: '>=12'} + + strip-json-comments@3.1.1: + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} + + stylis@4.4.0: + resolution: {integrity: sha512-5Z9ZpRzfuH6l/UAvCPAPUo3665Nk2wLaZU3x+TLHKVzIz33+sbJqbtrYoC3KD4/uVOr2Zp+L0LySezP9OHV9yA==} + + supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + + synckit@0.11.13: + resolution: {integrity: sha512-eNRKgb3z66Yp3D2CixVujOUvXLFUTij/zVnV8KRyvFdQwpz7I5DS8UfRkTeLzb64u+dkzDSdelE24izu+zSSUg==} + engines: {node: ^14.18.0 || >=16.0.0} + + text-segmentation@1.0.3: + resolution: {integrity: sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==} + + text-table@0.2.0: + resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} + + throttle-debounce@5.0.2: + resolution: {integrity: sha512-B71/4oyj61iNH0KeCamLuE2rmKuTO5byTOSVwECM5FA7TiAiAW+UqTKZ9ERueC4qvgSttUhdmq1mXC3kJqGX7A==} + engines: {node: '>=12.22'} + + tinyexec@1.2.4: + resolution: {integrity: sha512-SHf/r48b7vOrjve9PxJo3MN5v5yuyjHvdUcrQffT3WXMUfnGmHDVbC4k3sHJaJTgZCwpUplIaAo5ANtMyp3YHg==} + engines: {node: '>=18'} + + to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + + toidentifier@1.0.1: + resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} + engines: {node: '>=0.6'} + + ts-api-utils@1.4.3: + resolution: {integrity: sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==} + engines: {node: '>=16'} + peerDependencies: + typescript: '>=4.2.0' + + type-check@0.4.0: + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} + + type-fest@0.20.2: + resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} + engines: {node: '>=10'} + + type-is@2.1.0: + resolution: {integrity: sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==} + engines: {node: '>= 18'} + + typescript@5.9.3: + resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} + engines: {node: '>=14.17'} + hasBin: true + + undici-types@6.21.0: + resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + + unpipe@1.0.0: + resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} + engines: {node: '>= 0.8'} + + update-browserslist-db@1.2.3: + resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + + uri-js@4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + + util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + + utrie@1.0.2: + resolution: {integrity: sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==} + + vary@1.1.2: + resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} + engines: {node: '>= 0.8'} + + vite-plugin-remove-console@2.2.0: + resolution: {integrity: sha512-qgjh5pz75MdE9Kzs8J0kBwaCfifHV0ezRbB9rpGsIOxam+ilcGV7WOk91vFJXquzRmiKrFh3Hxlh0JJWAmXTbQ==} + + vite@5.4.21: + resolution: {integrity: sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@types/node': ^18.0.0 || >=20.0.0 + less: '*' + lightningcss: ^1.21.0 + sass: '*' + sass-embedded: '*' + stylus: '*' + sugarss: '*' + terser: ^5.4.0 + peerDependenciesMeta: + '@types/node': + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + + vscode-uri@3.1.0: + resolution: {integrity: sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==} + + vue-eslint-parser@9.4.3: + resolution: {integrity: sha512-2rYRLWlIpaiN8xbPiDyXZXRgLGOtWxERV7ND5fFAv5qo1D2N9Fu9MNajBNc6o13lZ+24DAWCkQCvj4klgmcITg==} + engines: {node: ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: '>=6.0.0' + + vue-router@4.6.4: + resolution: {integrity: sha512-Hz9q5sa33Yhduglwz6g9skT8OBPii+4bFn88w6J+J4MfEo4KRRpmiNG/hHHkdbRFlLBOqxN8y8gf2Fb0MTUgVg==} + peerDependencies: + vue: ^3.5.0 + + vue-tsc@2.2.12: + resolution: {integrity: sha512-P7OP77b2h/Pmk+lZdJ0YWs+5tJ6J2+uOQPo7tlBnY44QqQSPYvS0qVT4wqDJgwrZaLe47etJLLQRFia71GYITw==} + hasBin: true + peerDependencies: + typescript: '>=5.0.0' + + vue-types@3.0.2: + resolution: {integrity: sha512-IwUC0Aq2zwaXqy74h4WCvFCUtoV0iSWr0snWnE9TnU18S66GAQyqQbRf2qfJtUuiFsBf6qp0MEwdonlwznlcrw==} + engines: {node: '>=10.15.0'} + peerDependencies: + vue: ^3.0.0 + + vue@3.5.40: + resolution: {integrity: sha512-+8PJ4SJXdn/cHGImF4CKdxlWHIN5Dkt7DoufRREM6h6uVCx2m7QxgcEQmmzyOK8A9mcafg7sFbJFYsdFVubTig==} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + warning@4.0.3: + resolution: {integrity: sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==} + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + word-wrap@1.2.5: + resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} + engines: {node: '>=0.10.0'} + + wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + + wrap-ansi@9.0.2: + resolution: {integrity: sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==} + engines: {node: '>=18'} + + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + + xml-name-validator@4.0.0: + resolution: {integrity: sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==} + engines: {node: '>=12'} + + y18n@5.0.8: + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} + engines: {node: '>=10'} + + yallist@3.1.1: + resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + + yaml@2.9.0: + resolution: {integrity: sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==} + engines: {node: '>= 14.6'} + hasBin: true + + yargs-parser@21.1.1: + resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} + engines: {node: '>=12'} + + yargs@17.7.3: + resolution: {integrity: sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g==} + engines: {node: '>=12'} + + yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + +snapshots: + + '@ant-design/colors@6.0.0': + dependencies: + '@ctrl/tinycolor': 3.6.1 + + '@ant-design/icons-svg@4.5.0': {} + + '@ant-design/icons-vue@7.0.1(vue@3.5.40(typescript@5.9.3))': + dependencies: + '@ant-design/colors': 6.0.0 + '@ant-design/icons-svg': 4.5.0 + vue: 3.5.40(typescript@5.9.3) + + '@babel/code-frame@7.29.7': + dependencies: + '@babel/helper-validator-identifier': 7.29.7 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/compat-data@7.29.7': {} + + '@babel/core@7.29.7': + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/generator': 7.29.7 + '@babel/helper-compilation-targets': 7.29.7 + '@babel/helper-module-transforms': 7.29.7(@babel/core@7.29.7) + '@babel/helpers': 7.29.7 + '@babel/parser': 7.29.7 + '@babel/template': 7.29.7 + '@babel/traverse': 7.29.7 + '@babel/types': 7.29.7 + '@jridgewell/remapping': 2.3.5 + convert-source-map: 2.0.0 + debug: 4.4.3 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/generator@7.29.7': + dependencies: + '@babel/parser': 7.29.7 + '@babel/types': 7.29.7 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + jsesc: 3.1.0 + + '@babel/helper-annotate-as-pure@7.29.7': + dependencies: + '@babel/types': 7.29.7 + + '@babel/helper-compilation-targets@7.29.7': + dependencies: + '@babel/compat-data': 7.29.7 + '@babel/helper-validator-option': 7.29.7 + browserslist: 4.28.7 + lru-cache: 5.1.1 + semver: 6.3.1 + + '@babel/helper-create-class-features-plugin@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-annotate-as-pure': 7.29.7 + '@babel/helper-member-expression-to-functions': 7.29.7 + '@babel/helper-optimise-call-expression': 7.29.7 + '@babel/helper-replace-supers': 7.29.7(@babel/core@7.29.7) + '@babel/helper-skip-transparent-expression-wrappers': 7.29.7 + '@babel/traverse': 7.29.7 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/helper-globals@7.29.7': {} + + '@babel/helper-member-expression-to-functions@7.29.7': + dependencies: + '@babel/traverse': 7.29.7 + '@babel/types': 7.29.7 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-imports@7.29.7': + dependencies: + '@babel/traverse': 7.29.7 + '@babel/types': 7.29.7 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-transforms@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-module-imports': 7.29.7 + '@babel/helper-validator-identifier': 7.29.7 + '@babel/traverse': 7.29.7 + transitivePeerDependencies: + - supports-color + + '@babel/helper-optimise-call-expression@7.29.7': + dependencies: + '@babel/types': 7.29.7 + + '@babel/helper-plugin-utils@7.29.7': {} + + '@babel/helper-replace-supers@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-member-expression-to-functions': 7.29.7 + '@babel/helper-optimise-call-expression': 7.29.7 + '@babel/traverse': 7.29.7 + transitivePeerDependencies: + - supports-color + + '@babel/helper-skip-transparent-expression-wrappers@7.29.7': + dependencies: + '@babel/traverse': 7.29.7 + '@babel/types': 7.29.7 + transitivePeerDependencies: + - supports-color + + '@babel/helper-string-parser@7.29.7': {} + + '@babel/helper-validator-identifier@7.29.7': {} + + '@babel/helper-validator-option@7.29.7': {} + + '@babel/helpers@7.29.7': + dependencies: + '@babel/template': 7.29.7 + '@babel/types': 7.29.7 + + '@babel/parser@7.29.7': + dependencies: + '@babel/types': 7.29.7 + + '@babel/plugin-syntax-jsx@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-syntax-typescript@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-typescript@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-annotate-as-pure': 7.29.7 + '@babel/helper-create-class-features-plugin': 7.29.7(@babel/core@7.29.7) + '@babel/helper-plugin-utils': 7.29.7 + '@babel/helper-skip-transparent-expression-wrappers': 7.29.7 + '@babel/plugin-syntax-typescript': 7.29.7(@babel/core@7.29.7) + transitivePeerDependencies: + - supports-color + + '@babel/runtime@7.29.7': {} + + '@babel/template@7.29.7': + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/parser': 7.29.7 + '@babel/types': 7.29.7 + + '@babel/traverse@7.29.7': + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/generator': 7.29.7 + '@babel/helper-globals': 7.29.7 + '@babel/parser': 7.29.7 + '@babel/template': 7.29.7 + '@babel/types': 7.29.7 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + '@babel/types@7.29.7': + dependencies: + '@babel/helper-string-parser': 7.29.7 + '@babel/helper-validator-identifier': 7.29.7 + + '@commitlint/cli@20.5.3(@types/node@20.19.43)(conventional-commits-parser@6.4.0)(typescript@5.9.3)': + dependencies: + '@commitlint/format': 20.5.0 + '@commitlint/lint': 20.5.3 + '@commitlint/load': 20.5.3(@types/node@20.19.43)(typescript@5.9.3) + '@commitlint/read': 20.5.0(conventional-commits-parser@6.4.0) + '@commitlint/types': 20.5.0 + tinyexec: 1.2.4 + yargs: 17.7.3 + transitivePeerDependencies: + - '@types/node' + - conventional-commits-filter + - conventional-commits-parser + - typescript + + '@commitlint/config-conventional@20.5.3': + dependencies: + '@commitlint/types': 20.5.0 + conventional-changelog-conventionalcommits: 9.3.1 + + '@commitlint/config-validator@20.5.0': + dependencies: + '@commitlint/types': 20.5.0 + ajv: 8.20.0 + + '@commitlint/ensure@20.5.3': + dependencies: + '@commitlint/types': 20.5.0 + es-toolkit: 1.49.0 + + '@commitlint/execute-rule@20.0.0': {} + + '@commitlint/format@20.5.0': + dependencies: + '@commitlint/types': 20.5.0 + picocolors: 1.1.1 + + '@commitlint/is-ignored@20.5.0': + dependencies: + '@commitlint/types': 20.5.0 + semver: 7.8.5 + + '@commitlint/lint@20.5.3': + dependencies: + '@commitlint/is-ignored': 20.5.0 + '@commitlint/parse': 20.5.0 + '@commitlint/rules': 20.5.3 + '@commitlint/types': 20.5.0 + + '@commitlint/load@20.5.3(@types/node@20.19.43)(typescript@5.9.3)': + dependencies: + '@commitlint/config-validator': 20.5.0 + '@commitlint/execute-rule': 20.0.0 + '@commitlint/resolve-extends': 20.5.3 + '@commitlint/types': 20.5.0 + cosmiconfig: 9.0.2(typescript@5.9.3) + cosmiconfig-typescript-loader: 6.3.0(@types/node@20.19.43)(cosmiconfig@9.0.2(typescript@5.9.3))(typescript@5.9.3) + es-toolkit: 1.49.0 + is-plain-obj: 4.1.0 + picocolors: 1.1.1 + transitivePeerDependencies: + - '@types/node' + - typescript + + '@commitlint/message@20.4.3': {} + + '@commitlint/parse@20.5.0': + dependencies: + '@commitlint/types': 20.5.0 + conventional-changelog-angular: 8.3.1 + conventional-commits-parser: 6.4.0 + + '@commitlint/read@20.5.0(conventional-commits-parser@6.4.0)': + dependencies: + '@commitlint/top-level': 20.4.3 + '@commitlint/types': 20.5.0 + git-raw-commits: 5.0.1(conventional-commits-parser@6.4.0) + minimist: 1.2.8 + tinyexec: 1.2.4 + transitivePeerDependencies: + - conventional-commits-filter + - conventional-commits-parser + + '@commitlint/resolve-extends@20.5.3': + dependencies: + '@commitlint/config-validator': 20.5.0 + '@commitlint/types': 20.5.0 + es-toolkit: 1.49.0 + global-directory: 5.0.0 + import-meta-resolve: 4.2.0 + resolve-from: 5.0.0 + + '@commitlint/rules@20.5.3': + dependencies: + '@commitlint/ensure': 20.5.3 + '@commitlint/message': 20.4.3 + '@commitlint/to-lines': 20.0.0 + '@commitlint/types': 20.5.0 + + '@commitlint/to-lines@20.0.0': {} + + '@commitlint/top-level@20.4.3': + dependencies: + escalade: 3.2.0 + + '@commitlint/types@20.5.0': + dependencies: + conventional-commits-parser: 6.4.0 + picocolors: 1.1.1 + + '@conventional-changelog/git-client@2.7.0(conventional-commits-parser@6.4.0)': + dependencies: + '@simple-libs/child-process-utils': 1.0.2 + '@simple-libs/stream-utils': 1.2.0 + semver: 7.8.5 + optionalDependencies: + conventional-commits-parser: 6.4.0 + + '@ctrl/tinycolor@3.6.1': {} + + '@emotion/hash@0.9.2': {} + + '@emotion/unitless@0.8.1': {} + + '@esbuild/aix-ppc64@0.21.5': + optional: true + + '@esbuild/android-arm64@0.21.5': + optional: true + + '@esbuild/android-arm@0.21.5': + optional: true + + '@esbuild/android-x64@0.21.5': + optional: true + + '@esbuild/darwin-arm64@0.21.5': + optional: true + + '@esbuild/darwin-x64@0.21.5': + optional: true + + '@esbuild/freebsd-arm64@0.21.5': + optional: true + + '@esbuild/freebsd-x64@0.21.5': + optional: true + + '@esbuild/linux-arm64@0.21.5': + optional: true + + '@esbuild/linux-arm@0.21.5': + optional: true + + '@esbuild/linux-ia32@0.21.5': + optional: true + + '@esbuild/linux-loong64@0.21.5': + optional: true + + '@esbuild/linux-mips64el@0.21.5': + optional: true + + '@esbuild/linux-ppc64@0.21.5': + optional: true + + '@esbuild/linux-riscv64@0.21.5': + optional: true + + '@esbuild/linux-s390x@0.21.5': + optional: true + + '@esbuild/linux-x64@0.21.5': + optional: true + + '@esbuild/netbsd-x64@0.21.5': + optional: true + + '@esbuild/openbsd-x64@0.21.5': + optional: true + + '@esbuild/sunos-x64@0.21.5': + optional: true + + '@esbuild/win32-arm64@0.21.5': + optional: true + + '@esbuild/win32-ia32@0.21.5': + optional: true + + '@esbuild/win32-x64@0.21.5': + optional: true + + '@eslint-community/eslint-utils@4.10.1(eslint@8.57.1)': + dependencies: + eslint: 8.57.1 + eslint-visitor-keys: 3.4.3 + + '@eslint-community/regexpp@4.12.2': {} + + '@eslint/eslintrc@2.1.4': + dependencies: + ajv: 6.15.0 + debug: 4.4.3 + espree: 9.6.1 + globals: 13.24.0 + ignore: 5.3.2 + import-fresh: 3.3.1 + js-yaml: 4.3.0 + minimatch: 3.1.5 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + + '@eslint/js@8.57.1': {} + + '@humanwhocodes/config-array@0.13.0': + dependencies: + '@humanwhocodes/object-schema': 2.0.3 + debug: 4.4.3 + minimatch: 3.1.5 + transitivePeerDependencies: + - supports-color + + '@humanwhocodes/module-importer@1.0.1': {} + + '@humanwhocodes/object-schema@2.0.3': {} + + '@jridgewell/gen-mapping@0.3.13': + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/remapping@2.3.5': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/sourcemap-codec@1.5.5': {} + + '@jridgewell/trace-mapping@0.3.31': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + + '@nodelib/fs.scandir@2.1.5': + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + + '@nodelib/fs.stat@2.0.5': {} + + '@nodelib/fs.walk@1.2.8': + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.20.1 + + '@pkgr/core@0.3.6': {} + + '@rolldown/pluginutils@1.0.1': {} + + '@rollup/plugin-strip@3.0.4(rollup@4.62.2)': + dependencies: + '@rollup/pluginutils': 5.4.0(rollup@4.62.2) + estree-walker: 2.0.2 + magic-string: 0.30.21 + optionalDependencies: + rollup: 4.62.2 + + '@rollup/pluginutils@5.4.0(rollup@4.62.2)': + dependencies: + '@types/estree': 1.0.9 + estree-walker: 2.0.2 + picomatch: 4.0.5 + optionalDependencies: + rollup: 4.62.2 + + '@rollup/rollup-android-arm-eabi@4.62.2': + optional: true + + '@rollup/rollup-android-arm64@4.62.2': + optional: true + + '@rollup/rollup-darwin-arm64@4.62.2': + optional: true + + '@rollup/rollup-darwin-x64@4.62.2': + optional: true + + '@rollup/rollup-freebsd-arm64@4.62.2': + optional: true + + '@rollup/rollup-freebsd-x64@4.62.2': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.62.2': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.62.2': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.62.2': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.62.2': + optional: true + + '@rollup/rollup-linux-loong64-gnu@4.62.2': + optional: true + + '@rollup/rollup-linux-loong64-musl@4.62.2': + optional: true + + '@rollup/rollup-linux-ppc64-gnu@4.62.2': + optional: true + + '@rollup/rollup-linux-ppc64-musl@4.62.2': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.62.2': + optional: true + + '@rollup/rollup-linux-riscv64-musl@4.62.2': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.62.2': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.62.2': + optional: true + + '@rollup/rollup-linux-x64-musl@4.62.2': + optional: true + + '@rollup/rollup-openbsd-x64@4.62.2': + optional: true + + '@rollup/rollup-openharmony-arm64@4.62.2': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.62.2': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.62.2': + optional: true + + '@rollup/rollup-win32-x64-gnu@4.62.2': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.62.2': + optional: true + + '@simonwep/pickr@1.8.2': + dependencies: + core-js: 3.49.0 + nanopop: 2.4.2 + + '@simple-libs/child-process-utils@1.0.2': + dependencies: + '@simple-libs/stream-utils': 1.2.0 + + '@simple-libs/stream-utils@1.2.0': {} + + '@types/estree@1.0.9': {} + + '@types/json-schema@7.0.15': {} + + '@types/node@20.19.43': + dependencies: + undici-types: 6.21.0 + + '@types/qs@6.15.1': {} + + '@types/semver@7.7.1': {} + + '@typescript-eslint/eslint-plugin@6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1)(typescript@5.9.3)': + dependencies: + '@eslint-community/regexpp': 4.12.2 + '@typescript-eslint/parser': 6.21.0(eslint@8.57.1)(typescript@5.9.3) + '@typescript-eslint/scope-manager': 6.21.0 + '@typescript-eslint/type-utils': 6.21.0(eslint@8.57.1)(typescript@5.9.3) + '@typescript-eslint/utils': 6.21.0(eslint@8.57.1)(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 6.21.0 + debug: 4.4.3 + eslint: 8.57.1 + graphemer: 1.4.0 + ignore: 5.3.2 + natural-compare: 1.4.0 + semver: 7.8.5 + ts-api-utils: 1.4.3(typescript@5.9.3) + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.9.3)': + dependencies: + '@typescript-eslint/scope-manager': 6.21.0 + '@typescript-eslint/types': 6.21.0 + '@typescript-eslint/typescript-estree': 6.21.0(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 6.21.0 + debug: 4.4.3 + eslint: 8.57.1 + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/scope-manager@6.21.0': + dependencies: + '@typescript-eslint/types': 6.21.0 + '@typescript-eslint/visitor-keys': 6.21.0 + + '@typescript-eslint/type-utils@6.21.0(eslint@8.57.1)(typescript@5.9.3)': + dependencies: + '@typescript-eslint/typescript-estree': 6.21.0(typescript@5.9.3) + '@typescript-eslint/utils': 6.21.0(eslint@8.57.1)(typescript@5.9.3) + debug: 4.4.3 + eslint: 8.57.1 + ts-api-utils: 1.4.3(typescript@5.9.3) + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/types@6.21.0': {} + + '@typescript-eslint/typescript-estree@6.21.0(typescript@5.9.3)': + dependencies: + '@typescript-eslint/types': 6.21.0 + '@typescript-eslint/visitor-keys': 6.21.0 + debug: 4.4.3 + globby: 11.1.0 + is-glob: 4.0.3 + minimatch: 9.0.3 + semver: 7.8.5 + ts-api-utils: 1.4.3(typescript@5.9.3) + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/utils@6.21.0(eslint@8.57.1)(typescript@5.9.3)': + dependencies: + '@eslint-community/eslint-utils': 4.10.1(eslint@8.57.1) + '@types/json-schema': 7.0.15 + '@types/semver': 7.7.1 + '@typescript-eslint/scope-manager': 6.21.0 + '@typescript-eslint/types': 6.21.0 + '@typescript-eslint/typescript-estree': 6.21.0(typescript@5.9.3) + eslint: 8.57.1 + semver: 7.8.5 + transitivePeerDependencies: + - supports-color + - typescript + + '@typescript-eslint/visitor-keys@6.21.0': + dependencies: + '@typescript-eslint/types': 6.21.0 + eslint-visitor-keys: 3.4.3 + + '@ungap/structured-clone@1.3.3': {} + + '@vitejs/plugin-vue-jsx@4.2.0(vite@5.4.21(@types/node@20.19.43)(less@4.8.0))(vue@3.5.40(typescript@5.9.3))': + dependencies: + '@babel/core': 7.29.7 + '@babel/plugin-transform-typescript': 7.29.7(@babel/core@7.29.7) + '@rolldown/pluginutils': 1.0.1 + '@vue/babel-plugin-jsx': 1.5.0(@babel/core@7.29.7) + vite: 5.4.21(@types/node@20.19.43)(less@4.8.0) + vue: 3.5.40(typescript@5.9.3) + transitivePeerDependencies: + - supports-color + + '@vitejs/plugin-vue@4.6.2(vite@5.4.21(@types/node@20.19.43)(less@4.8.0))(vue@3.5.40(typescript@5.9.3))': + dependencies: + vite: 5.4.21(@types/node@20.19.43)(less@4.8.0) + vue: 3.5.40(typescript@5.9.3) + + '@volar/language-core@2.4.15': + dependencies: + '@volar/source-map': 2.4.15 + + '@volar/source-map@2.4.15': {} + + '@volar/typescript@2.4.15': + dependencies: + '@volar/language-core': 2.4.15 + path-browserify: 1.0.1 + vscode-uri: 3.1.0 + + '@vue/babel-helper-vue-transform-on@1.5.0': {} + + '@vue/babel-plugin-jsx@1.5.0(@babel/core@7.29.7)': + dependencies: + '@babel/helper-module-imports': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + '@babel/plugin-syntax-jsx': 7.29.7(@babel/core@7.29.7) + '@babel/template': 7.29.7 + '@babel/traverse': 7.29.7 + '@babel/types': 7.29.7 + '@vue/babel-helper-vue-transform-on': 1.5.0 + '@vue/babel-plugin-resolve-type': 1.5.0(@babel/core@7.29.7) + '@vue/shared': 3.5.40 + optionalDependencies: + '@babel/core': 7.29.7 + transitivePeerDependencies: + - supports-color + + '@vue/babel-plugin-resolve-type@1.5.0(@babel/core@7.29.7)': + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/core': 7.29.7 + '@babel/helper-module-imports': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + '@babel/parser': 7.29.7 + '@vue/compiler-sfc': 3.5.40 + transitivePeerDependencies: + - supports-color + + '@vue/compiler-core@3.5.40': + dependencies: + '@babel/parser': 7.29.7 + '@vue/shared': 3.5.40 + entities: 7.0.1 + estree-walker: 2.0.2 + source-map-js: 1.2.1 + + '@vue/compiler-dom@3.5.40': + dependencies: + '@vue/compiler-core': 3.5.40 + '@vue/shared': 3.5.40 + + '@vue/compiler-sfc@3.5.40': + dependencies: + '@babel/parser': 7.29.7 + '@vue/compiler-core': 3.5.40 + '@vue/compiler-dom': 3.5.40 + '@vue/compiler-ssr': 3.5.40 + '@vue/shared': 3.5.40 + estree-walker: 2.0.2 + magic-string: 0.30.21 + postcss: 8.5.21 + source-map-js: 1.2.1 + + '@vue/compiler-ssr@3.5.40': + dependencies: + '@vue/compiler-dom': 3.5.40 + '@vue/shared': 3.5.40 + + '@vue/compiler-vue2@2.7.16': + dependencies: + de-indent: 1.0.2 + he: 1.2.0 + + '@vue/devtools-api@6.6.4': {} + + '@vue/language-core@2.2.12(typescript@5.9.3)': + dependencies: + '@volar/language-core': 2.4.15 + '@vue/compiler-dom': 3.5.40 + '@vue/compiler-vue2': 2.7.16 + '@vue/shared': 3.5.40 + alien-signals: 1.0.13 + minimatch: 9.0.9 + muggle-string: 0.4.1 + path-browserify: 1.0.1 + optionalDependencies: + typescript: 5.9.3 + + '@vue/reactivity@3.5.40': + dependencies: + '@vue/shared': 3.5.40 + + '@vue/runtime-core@3.5.40': + dependencies: + '@vue/reactivity': 3.5.40 + '@vue/shared': 3.5.40 + + '@vue/runtime-dom@3.5.40': + dependencies: + '@vue/reactivity': 3.5.40 + '@vue/runtime-core': 3.5.40 + '@vue/shared': 3.5.40 + csstype: 3.2.3 + + '@vue/server-renderer@3.5.40': + dependencies: + '@vue/compiler-ssr': 3.5.40 + '@vue/runtime-dom': 3.5.40 + '@vue/shared': 3.5.40 + + '@vue/shared@3.5.40': {} + + '@yp-component/root@0.0.48(typescript@5.9.3)': + dependencies: + cropperjs: 1.6.2 + vue: 3.5.40(typescript@5.9.3) + transitivePeerDependencies: + - typescript + + accepts@2.0.0: + dependencies: + mime-types: 3.0.2 + negotiator: 1.0.0 + + acorn-jsx@5.3.2(acorn@8.17.0): + dependencies: + acorn: 8.17.0 + + acorn@8.17.0: {} + + ajv@6.15.0: + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + + ajv@8.20.0: + dependencies: + fast-deep-equal: 3.1.3 + fast-uri: 3.1.4 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + + alien-signals@1.0.13: {} + + ansi-escapes@7.3.0: + dependencies: + environment: 1.1.0 + + ansi-regex@5.0.1: {} + + ansi-regex@6.2.2: {} + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + ansi-styles@6.2.3: {} + + ant-design-vue@4.2.6(vue@3.5.40(typescript@5.9.3)): + dependencies: + '@ant-design/colors': 6.0.0 + '@ant-design/icons-vue': 7.0.1(vue@3.5.40(typescript@5.9.3)) + '@babel/runtime': 7.29.7 + '@ctrl/tinycolor': 3.6.1 + '@emotion/hash': 0.9.2 + '@emotion/unitless': 0.8.1 + '@simonwep/pickr': 1.8.2 + array-tree-filter: 2.1.0 + async-validator: 4.2.5 + csstype: 3.2.3 + dayjs: 1.11.21 + dom-align: 1.12.4 + dom-scroll-into-view: 2.0.1 + lodash: 4.18.1 + lodash-es: 4.18.1 + resize-observer-polyfill: 1.5.1 + scroll-into-view-if-needed: 2.2.31 + shallow-equal: 1.2.1 + stylis: 4.4.0 + throttle-debounce: 5.0.2 + vue: 3.5.40(typescript@5.9.3) + vue-types: 3.0.2(vue@3.5.40(typescript@5.9.3)) + warning: 4.0.3 + + argparse@2.0.1: {} + + array-ify@1.0.0: {} + + array-tree-filter@2.1.0: {} + + array-union@2.1.0: {} + + async-validator@4.2.5: {} + + balanced-match@1.0.2: {} + + base64-arraybuffer@1.0.2: {} + + baseline-browser-mapping@2.11.1: {} + + body-parser@2.3.0: + dependencies: + bytes: 3.1.2 + content-type: 2.0.0 + debug: 4.4.3 + http-errors: 2.0.1 + iconv-lite: 0.7.3 + on-finished: 2.4.1 + qs: 6.15.3 + raw-body: 3.0.2 + type-is: 2.1.0 + transitivePeerDependencies: + - supports-color + + boolbase@1.0.0: {} + + brace-expansion@1.1.16: + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + + brace-expansion@2.1.2: + dependencies: + balanced-match: 1.0.2 + + braces@3.0.3: + dependencies: + fill-range: 7.1.1 + + browserslist@4.28.7: + dependencies: + baseline-browser-mapping: 2.11.1 + caniuse-lite: 1.0.30001806 + electron-to-chromium: 1.5.395 + node-releases: 2.0.51 + update-browserslist-db: 1.2.3(browserslist@4.28.7) + + bytes@3.1.2: {} + + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + + call-bound@1.0.4: + dependencies: + call-bind-apply-helpers: 1.0.2 + get-intrinsic: 1.3.0 + + callsites@3.1.0: {} + + caniuse-lite@1.0.30001806: {} + + chalk@4.1.2: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + + chalk@5.6.2: {} + + china-area-data@5.0.1: {} + + cli-cursor@5.0.0: + dependencies: + restore-cursor: 5.1.0 + + cli-truncate@4.0.0: + dependencies: + slice-ansi: 5.0.0 + string-width: 7.2.0 + + cliui@8.0.1: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + + colorette@2.0.20: {} + + commander@13.1.0: {} + + compare-func@2.0.0: + dependencies: + array-ify: 1.0.0 + dot-prop: 5.3.0 + + compute-scroll-into-view@1.0.20: {} + + concat-map@0.0.1: {} + + content-disposition@1.1.0: {} + + content-type@1.0.5: {} + + content-type@2.0.0: {} + + conventional-changelog-angular@8.3.1: + dependencies: + compare-func: 2.0.0 + + conventional-changelog-conventionalcommits@9.3.1: + dependencies: + compare-func: 2.0.0 + + conventional-commits-parser@6.4.0: + dependencies: + '@simple-libs/stream-utils': 1.2.0 + meow: 13.2.0 + + convert-source-map@2.0.0: {} + + cookie-signature@1.2.2: {} + + cookie@0.7.2: {} + + copy-anything@3.0.5: + dependencies: + is-what: 4.1.16 + + core-js@3.49.0: {} + + cosmiconfig-typescript-loader@6.3.0(@types/node@20.19.43)(cosmiconfig@9.0.2(typescript@5.9.3))(typescript@5.9.3): + dependencies: + '@types/node': 20.19.43 + cosmiconfig: 9.0.2(typescript@5.9.3) + jiti: 2.6.1 + typescript: 5.9.3 + + cosmiconfig@9.0.2(typescript@5.9.3): + dependencies: + env-paths: 2.2.1 + import-fresh: 3.3.1 + js-yaml: 4.3.0 + parse-json: 5.2.0 + optionalDependencies: + typescript: 5.9.3 + + cropperjs@1.6.2: {} + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + css-line-break@2.1.0: + dependencies: + utrie: 1.0.2 + + cssesc@3.0.0: {} + + csstype@3.2.3: {} + + dayjs@1.11.21: {} + + de-indent@1.0.2: {} + + 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 + + deep-is@0.1.4: {} + + depd@2.0.0: {} + + dir-glob@3.0.1: + dependencies: + path-type: 4.0.0 + + doctrine@3.0.0: + dependencies: + esutils: 2.0.3 + + dom-align@1.12.4: {} + + dom-scroll-into-view@2.0.1: {} + + dot-prop@5.3.0: + dependencies: + is-obj: 2.0.0 + + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + + ee-first@1.1.1: {} + + electron-to-chromium@1.5.395: {} + + emoji-regex@10.6.0: {} + + emoji-regex@8.0.0: {} + + encodeurl@2.0.0: {} + + entities@7.0.1: {} + + env-paths@2.2.1: {} + + environment@1.1.0: {} + + errno@0.1.8: + dependencies: + prr: 1.0.1 + optional: true + + error-ex@1.3.4: + dependencies: + is-arrayish: 0.2.1 + + es-define-property@1.0.1: {} + + es-errors@1.3.0: {} + + es-object-atoms@1.1.2: + dependencies: + es-errors: 1.3.0 + + es-toolkit@1.49.0: {} + + esbuild@0.21.5: + optionalDependencies: + '@esbuild/aix-ppc64': 0.21.5 + '@esbuild/android-arm': 0.21.5 + '@esbuild/android-arm64': 0.21.5 + '@esbuild/android-x64': 0.21.5 + '@esbuild/darwin-arm64': 0.21.5 + '@esbuild/darwin-x64': 0.21.5 + '@esbuild/freebsd-arm64': 0.21.5 + '@esbuild/freebsd-x64': 0.21.5 + '@esbuild/linux-arm': 0.21.5 + '@esbuild/linux-arm64': 0.21.5 + '@esbuild/linux-ia32': 0.21.5 + '@esbuild/linux-loong64': 0.21.5 + '@esbuild/linux-mips64el': 0.21.5 + '@esbuild/linux-ppc64': 0.21.5 + '@esbuild/linux-riscv64': 0.21.5 + '@esbuild/linux-s390x': 0.21.5 + '@esbuild/linux-x64': 0.21.5 + '@esbuild/netbsd-x64': 0.21.5 + '@esbuild/openbsd-x64': 0.21.5 + '@esbuild/sunos-x64': 0.21.5 + '@esbuild/win32-arm64': 0.21.5 + '@esbuild/win32-ia32': 0.21.5 + '@esbuild/win32-x64': 0.21.5 + + escalade@3.2.0: {} + + escape-html@1.0.3: {} + + escape-string-regexp@4.0.0: {} + + eslint-config-prettier@9.1.2(eslint@8.57.1): + dependencies: + eslint: 8.57.1 + + eslint-plugin-prettier@5.5.6(eslint-config-prettier@9.1.2(eslint@8.57.1))(eslint@8.57.1)(prettier@3.9.6): + dependencies: + eslint: 8.57.1 + prettier: 3.9.6 + prettier-linter-helpers: 1.0.1 + synckit: 0.11.13 + optionalDependencies: + eslint-config-prettier: 9.1.2(eslint@8.57.1) + + eslint-plugin-vue@9.33.0(eslint@8.57.1): + dependencies: + '@eslint-community/eslint-utils': 4.10.1(eslint@8.57.1) + eslint: 8.57.1 + globals: 13.24.0 + natural-compare: 1.4.0 + nth-check: 2.1.1 + postcss-selector-parser: 6.1.4 + semver: 7.8.5 + vue-eslint-parser: 9.4.3(eslint@8.57.1) + xml-name-validator: 4.0.0 + transitivePeerDependencies: + - supports-color + + eslint-scope@7.2.2: + dependencies: + esrecurse: 4.3.0 + estraverse: 5.3.0 + + eslint-visitor-keys@3.4.3: {} + + eslint@8.57.1: + dependencies: + '@eslint-community/eslint-utils': 4.10.1(eslint@8.57.1) + '@eslint-community/regexpp': 4.12.2 + '@eslint/eslintrc': 2.1.4 + '@eslint/js': 8.57.1 + '@humanwhocodes/config-array': 0.13.0 + '@humanwhocodes/module-importer': 1.0.1 + '@nodelib/fs.walk': 1.2.8 + '@ungap/structured-clone': 1.3.3 + ajv: 6.15.0 + chalk: 4.1.2 + cross-spawn: 7.0.6 + debug: 4.4.3 + doctrine: 3.0.0 + escape-string-regexp: 4.0.0 + eslint-scope: 7.2.2 + eslint-visitor-keys: 3.4.3 + espree: 9.6.1 + esquery: 1.7.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 6.0.1 + find-up: 5.0.0 + glob-parent: 6.0.2 + globals: 13.24.0 + graphemer: 1.4.0 + ignore: 5.3.2 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + is-path-inside: 3.0.3 + js-yaml: 4.3.0 + json-stable-stringify-without-jsonify: 1.0.1 + levn: 0.4.1 + lodash.merge: 4.6.2 + minimatch: 3.1.5 + natural-compare: 1.4.0 + optionator: 0.9.4 + strip-ansi: 6.0.1 + text-table: 0.2.0 + transitivePeerDependencies: + - supports-color + + espree@9.6.1: + dependencies: + acorn: 8.17.0 + acorn-jsx: 5.3.2(acorn@8.17.0) + eslint-visitor-keys: 3.4.3 + + esquery@1.7.0: + dependencies: + estraverse: 5.3.0 + + esrecurse@4.3.0: + dependencies: + estraverse: 5.3.0 + + estraverse@5.3.0: {} + + estree-walker@2.0.2: {} + + esutils@2.0.3: {} + + etag@1.8.1: {} + + eventemitter3@5.0.4: {} + + execa@8.0.1: + dependencies: + cross-spawn: 7.0.6 + get-stream: 8.0.1 + human-signals: 5.0.0 + is-stream: 3.0.0 + merge-stream: 2.0.0 + npm-run-path: 5.3.0 + onetime: 6.0.0 + signal-exit: 4.1.0 + strip-final-newline: 3.0.0 + + express@5.2.1: + dependencies: + accepts: 2.0.0 + body-parser: 2.3.0 + content-disposition: 1.1.0 + content-type: 1.0.5 + cookie: 0.7.2 + cookie-signature: 1.2.2 + debug: 4.4.3 + depd: 2.0.0 + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + finalhandler: 2.1.1 + fresh: 2.0.0 + http-errors: 2.0.1 + merge-descriptors: 2.0.0 + mime-types: 3.0.2 + on-finished: 2.4.1 + once: 1.4.0 + parseurl: 1.3.3 + proxy-addr: 2.0.7 + qs: 6.15.3 + range-parser: 1.3.0 + router: 2.2.0 + send: 1.2.1 + serve-static: 2.2.1 + statuses: 2.0.2 + type-is: 2.1.0 + vary: 1.1.2 + transitivePeerDependencies: + - supports-color + + fast-deep-equal@3.1.3: {} + + fast-diff@1.3.0: {} + + fast-glob@3.3.3: + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.8 + + fast-json-stable-stringify@2.1.0: {} + + fast-levenshtein@2.0.6: {} + + fast-uri@3.1.4: {} + + fastq@1.20.1: + dependencies: + reusify: 1.1.0 + + file-entry-cache@6.0.1: + dependencies: + flat-cache: 3.2.0 + + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 + + finalhandler@2.1.1: + dependencies: + debug: 4.4.3 + encodeurl: 2.0.0 + escape-html: 1.0.3 + on-finished: 2.4.1 + parseurl: 1.3.3 + statuses: 2.0.2 + transitivePeerDependencies: + - supports-color + + find-up@5.0.0: + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + + flat-cache@3.2.0: + dependencies: + flatted: 3.4.3 + keyv: 4.5.4 + rimraf: 3.0.2 + + flatted@3.4.3: {} + + forwarded@0.2.0: {} + + fresh@2.0.0: {} + + fs.realpath@1.0.0: {} + + fsevents@2.3.3: + optional: true + + function-bind@1.1.2: {} + + gensync@1.0.0-beta.2: {} + + get-caller-file@2.0.5: {} + + get-east-asian-width@1.6.0: {} + + 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 + + get-stream@8.0.1: {} + + git-raw-commits@5.0.1(conventional-commits-parser@6.4.0): + dependencies: + '@conventional-changelog/git-client': 2.7.0(conventional-commits-parser@6.4.0) + meow: 13.2.0 + transitivePeerDependencies: + - conventional-commits-filter + - conventional-commits-parser + + glob-parent@5.1.2: + dependencies: + is-glob: 4.0.3 + + glob-parent@6.0.2: + dependencies: + is-glob: 4.0.3 + + glob@7.2.3: + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 3.1.5 + once: 1.4.0 + path-is-absolute: 1.0.1 + + global-directory@5.0.0: + dependencies: + ini: 6.0.0 + + globals@13.24.0: + dependencies: + type-fest: 0.20.2 + + globby@11.1.0: + dependencies: + array-union: 2.1.0 + dir-glob: 3.0.1 + fast-glob: 3.3.3 + ignore: 5.3.2 + merge2: 1.4.1 + slash: 3.0.0 + + gopd@1.2.0: {} + + graceful-fs@4.2.11: + optional: true + + graphemer@1.4.0: {} + + has-flag@4.0.0: {} + + has-symbols@1.1.0: {} + + hasown@2.0.4: + dependencies: + function-bind: 1.1.2 + + he@1.2.0: {} + + html2canvas@1.4.1: + dependencies: + css-line-break: 2.1.0 + text-segmentation: 1.0.3 + + http-errors@2.0.1: + dependencies: + depd: 2.0.0 + inherits: 2.0.4 + setprototypeof: 1.2.0 + statuses: 2.0.2 + toidentifier: 1.0.1 + + human-signals@5.0.0: {} + + husky@9.1.7: {} + + 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 + + iconv-lite@0.7.3: + dependencies: + safer-buffer: 2.1.2 + + ignore@5.3.2: {} + + import-fresh@3.3.1: + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + + import-meta-resolve@4.2.0: {} + + imurmurhash@0.1.4: {} + + inflight@1.0.6: + dependencies: + once: 1.4.0 + wrappy: 1.0.2 + + inherits@2.0.4: {} + + ini@6.0.0: {} + + ipaddr.js@1.9.1: {} + + is-arrayish@0.2.1: {} + + is-extglob@2.1.1: {} + + is-fullwidth-code-point@3.0.0: {} + + is-fullwidth-code-point@4.0.0: {} + + is-fullwidth-code-point@5.1.0: + dependencies: + get-east-asian-width: 1.6.0 + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-number@7.0.0: {} + + is-obj@2.0.0: {} + + is-path-inside@3.0.3: {} + + is-plain-obj@4.1.0: {} + + is-plain-object@3.0.1: {} + + is-promise@4.0.0: {} + + is-stream@3.0.0: {} + + is-what@4.1.16: {} + + isexe@2.0.0: {} + + jiti@2.6.1: {} + + js-tokens@4.0.0: {} + + js-yaml@4.3.0: + dependencies: + argparse: 2.0.1 + + jsesc@3.1.0: {} + + json-buffer@3.0.1: {} + + json-parse-even-better-errors@2.3.1: {} + + json-schema-traverse@0.4.1: {} + + json-schema-traverse@1.0.0: {} + + json-stable-stringify-without-jsonify@1.0.1: {} + + json5@2.2.3: {} + + keyv@4.5.4: + dependencies: + json-buffer: 3.0.1 + + less@4.8.0: + 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.3.0 + source-map: 0.6.1 + transitivePeerDependencies: + - supports-color + + levn@0.4.1: + dependencies: + prelude-ls: 1.2.1 + type-check: 0.4.0 + + lilconfig@3.1.3: {} + + lines-and-columns@1.2.4: {} + + lint-staged@15.5.2: + dependencies: + chalk: 5.6.2 + commander: 13.1.0 + debug: 4.4.3 + execa: 8.0.1 + lilconfig: 3.1.3 + listr2: 8.3.3 + micromatch: 4.0.8 + pidtree: 0.6.1 + string-argv: 0.3.2 + yaml: 2.9.0 + transitivePeerDependencies: + - supports-color + + listr2@8.3.3: + dependencies: + cli-truncate: 4.0.0 + colorette: 2.0.20 + eventemitter3: 5.0.4 + log-update: 6.1.0 + rfdc: 1.4.1 + wrap-ansi: 9.0.2 + + locate-path@6.0.0: + dependencies: + p-locate: 5.0.0 + + lodash-es@4.18.1: {} + + lodash.merge@4.6.2: {} + + lodash@4.18.1: {} + + log-update@6.1.0: + dependencies: + ansi-escapes: 7.3.0 + cli-cursor: 5.0.0 + slice-ansi: 7.1.2 + strip-ansi: 7.2.0 + wrap-ansi: 9.0.2 + + loose-envify@1.4.0: + dependencies: + js-tokens: 4.0.0 + + lru-cache@5.1.1: + dependencies: + yallist: 3.1.1 + + magic-string@0.30.21: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + + make-dir@5.1.0: + optional: true + + math-intrinsics@1.1.0: {} + + media-typer@1.1.0: {} + + meow@13.2.0: {} + + merge-descriptors@2.0.0: {} + + merge-stream@2.0.0: {} + + merge2@1.4.1: {} + + micromatch@4.0.8: + dependencies: + braces: 3.0.3 + picomatch: 2.3.2 + + mime-db@1.54.0: {} + + mime-types@3.0.2: + dependencies: + mime-db: 1.54.0 + + mime@1.6.0: + optional: true + + mimic-fn@4.0.0: {} + + mimic-function@5.0.1: {} + + minimatch@3.1.5: + dependencies: + brace-expansion: 1.1.16 + + minimatch@9.0.3: + dependencies: + brace-expansion: 2.1.2 + + minimatch@9.0.9: + dependencies: + brace-expansion: 2.1.2 + + minimist@1.2.8: {} + + ms@2.0.0: + optional: true + + ms@2.1.3: {} + + muggle-string@0.4.1: {} + + nanoid@3.3.16: {} + + nanopop@2.4.2: {} + + natural-compare@1.4.0: {} + + needle@2.9.1: + dependencies: + debug: 3.2.7 + iconv-lite: 0.4.24 + sax: 1.6.0 + transitivePeerDependencies: + - supports-color + optional: true + + needle@3.5.0: + dependencies: + iconv-lite: 0.6.3 + sax: 1.6.0 + optional: true + + negotiator@1.0.0: {} + + node-releases@2.0.51: {} + + npm-run-path@5.3.0: + dependencies: + path-key: 4.0.0 + + nth-check@2.1.1: + dependencies: + boolbase: 1.0.0 + + object-inspect@1.13.4: {} + + on-finished@2.4.1: + dependencies: + ee-first: 1.1.1 + + once@1.4.0: + dependencies: + wrappy: 1.0.2 + + onetime@6.0.0: + dependencies: + mimic-fn: 4.0.0 + + onetime@7.0.0: + dependencies: + mimic-function: 5.0.1 + + optionator@0.9.4: + dependencies: + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.4.1 + prelude-ls: 1.2.1 + type-check: 0.4.0 + word-wrap: 1.2.5 + + p-limit@3.1.0: + dependencies: + yocto-queue: 0.1.0 + + p-locate@5.0.0: + dependencies: + p-limit: 3.1.0 + + parent-module@1.0.1: + dependencies: + callsites: 3.1.0 + + parse-json@5.2.0: + dependencies: + '@babel/code-frame': 7.29.7 + error-ex: 1.3.4 + json-parse-even-better-errors: 2.3.1 + lines-and-columns: 1.2.4 + + parse-node-version@1.0.1: {} + + parseurl@1.3.3: {} + + path-browserify@1.0.1: {} + + path-exists@4.0.0: {} + + path-is-absolute@1.0.1: {} + + path-key@3.1.1: {} + + path-key@4.0.0: {} + + path-to-regexp@8.4.2: {} + + path-type@4.0.0: {} + + picocolors@1.1.1: {} + + picomatch@2.3.2: {} + + picomatch@4.0.5: {} + + pidtree@0.6.1: {} + + postcss-selector-parser@6.1.4: + dependencies: + cssesc: 3.0.0 + util-deprecate: 1.0.2 + + postcss@8.5.21: + dependencies: + nanoid: 3.3.16 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + prelude-ls@1.2.1: {} + + prettier-linter-helpers@1.0.1: + dependencies: + fast-diff: 1.3.0 + + prettier@3.9.6: {} + + probe-image-size@7.3.0: + dependencies: + lodash.merge: 4.6.2 + needle: 2.9.1 + stream-parser: 0.3.1 + transitivePeerDependencies: + - supports-color + optional: true + + proxy-addr@2.0.7: + dependencies: + forwarded: 0.2.0 + ipaddr.js: 1.9.1 + + prr@1.0.1: + optional: true + + punycode@2.3.1: {} + + qs@6.15.3: + dependencies: + es-define-property: 1.0.1 + side-channel: 1.1.1 + + queue-microtask@1.2.3: {} + + range-parser@1.3.0: {} + + raw-body@3.0.2: + dependencies: + bytes: 3.1.2 + http-errors: 2.0.1 + iconv-lite: 0.7.3 + unpipe: 1.0.0 + + require-directory@2.1.1: {} + + require-from-string@2.0.2: {} + + resize-observer-polyfill@1.5.1: {} + + resolve-from@4.0.0: {} + + resolve-from@5.0.0: {} + + restore-cursor@5.1.0: + dependencies: + onetime: 7.0.0 + signal-exit: 4.1.0 + + reusify@1.1.0: {} + + rfdc@1.4.1: {} + + rimraf@3.0.2: + dependencies: + glob: 7.2.3 + + rollup@4.62.2: + dependencies: + '@types/estree': 1.0.9 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.62.2 + '@rollup/rollup-android-arm64': 4.62.2 + '@rollup/rollup-darwin-arm64': 4.62.2 + '@rollup/rollup-darwin-x64': 4.62.2 + '@rollup/rollup-freebsd-arm64': 4.62.2 + '@rollup/rollup-freebsd-x64': 4.62.2 + '@rollup/rollup-linux-arm-gnueabihf': 4.62.2 + '@rollup/rollup-linux-arm-musleabihf': 4.62.2 + '@rollup/rollup-linux-arm64-gnu': 4.62.2 + '@rollup/rollup-linux-arm64-musl': 4.62.2 + '@rollup/rollup-linux-loong64-gnu': 4.62.2 + '@rollup/rollup-linux-loong64-musl': 4.62.2 + '@rollup/rollup-linux-ppc64-gnu': 4.62.2 + '@rollup/rollup-linux-ppc64-musl': 4.62.2 + '@rollup/rollup-linux-riscv64-gnu': 4.62.2 + '@rollup/rollup-linux-riscv64-musl': 4.62.2 + '@rollup/rollup-linux-s390x-gnu': 4.62.2 + '@rollup/rollup-linux-x64-gnu': 4.62.2 + '@rollup/rollup-linux-x64-musl': 4.62.2 + '@rollup/rollup-openbsd-x64': 4.62.2 + '@rollup/rollup-openharmony-arm64': 4.62.2 + '@rollup/rollup-win32-arm64-msvc': 4.62.2 + '@rollup/rollup-win32-ia32-msvc': 4.62.2 + '@rollup/rollup-win32-x64-gnu': 4.62.2 + '@rollup/rollup-win32-x64-msvc': 4.62.2 + fsevents: 2.3.3 + + router@2.2.0: + dependencies: + debug: 4.4.3 + depd: 2.0.0 + is-promise: 4.0.0 + parseurl: 1.3.3 + path-to-regexp: 8.4.2 + transitivePeerDependencies: + - supports-color + + run-parallel@1.2.0: + dependencies: + queue-microtask: 1.2.3 + + safer-buffer@2.1.2: {} + + sax@1.6.0: + optional: true + + scroll-into-view-if-needed@2.2.31: + dependencies: + compute-scroll-into-view: 1.0.20 + + semver@6.3.1: {} + + semver@7.8.5: {} + + send@1.2.1: + dependencies: + debug: 4.4.3 + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + fresh: 2.0.0 + http-errors: 2.0.1 + mime-types: 3.0.2 + ms: 2.1.3 + on-finished: 2.4.1 + range-parser: 1.3.0 + statuses: 2.0.2 + transitivePeerDependencies: + - supports-color + + serve-static@2.2.1: + dependencies: + encodeurl: 2.0.0 + escape-html: 1.0.3 + parseurl: 1.3.3 + send: 1.2.1 + transitivePeerDependencies: + - supports-color + + setprototypeof@1.2.0: {} + + shallow-equal@1.2.1: {} + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + side-channel-list@1.0.1: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + + side-channel-map@1.0.1: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + + side-channel-weakmap@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + side-channel-map: 1.0.1 + + side-channel@1.1.1: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + side-channel-list: 1.0.1 + side-channel-map: 1.0.1 + side-channel-weakmap: 1.0.2 + + signal-exit@4.1.0: {} + + slash@3.0.0: {} + + slice-ansi@5.0.0: + dependencies: + ansi-styles: 6.2.3 + is-fullwidth-code-point: 4.0.0 + + slice-ansi@7.1.2: + dependencies: + ansi-styles: 6.2.3 + is-fullwidth-code-point: 5.1.0 + + source-map-js@1.2.1: {} + + source-map@0.6.1: + optional: true + + statuses@2.0.2: {} + + stream-parser@0.3.1: + dependencies: + debug: 2.6.9 + transitivePeerDependencies: + - supports-color + optional: true + + string-argv@0.3.2: {} + + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + string-width@7.2.0: + dependencies: + emoji-regex: 10.6.0 + get-east-asian-width: 1.6.0 + strip-ansi: 7.2.0 + + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + + strip-ansi@7.2.0: + dependencies: + ansi-regex: 6.2.2 + + strip-final-newline@3.0.0: {} + + strip-json-comments@3.1.1: {} + + stylis@4.4.0: {} + + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + + synckit@0.11.13: + dependencies: + '@pkgr/core': 0.3.6 + + text-segmentation@1.0.3: + dependencies: + utrie: 1.0.2 + + text-table@0.2.0: {} + + throttle-debounce@5.0.2: {} + + tinyexec@1.2.4: {} + + to-regex-range@5.0.1: + dependencies: + is-number: 7.0.0 + + toidentifier@1.0.1: {} + + ts-api-utils@1.4.3(typescript@5.9.3): + dependencies: + typescript: 5.9.3 + + type-check@0.4.0: + dependencies: + prelude-ls: 1.2.1 + + type-fest@0.20.2: {} + + type-is@2.1.0: + dependencies: + content-type: 2.0.0 + media-typer: 1.1.0 + mime-types: 3.0.2 + + typescript@5.9.3: {} + + undici-types@6.21.0: {} + + unpipe@1.0.0: {} + + update-browserslist-db@1.2.3(browserslist@4.28.7): + dependencies: + browserslist: 4.28.7 + escalade: 3.2.0 + picocolors: 1.1.1 + + uri-js@4.4.1: + dependencies: + punycode: 2.3.1 + + util-deprecate@1.0.2: {} + + utrie@1.0.2: + dependencies: + base64-arraybuffer: 1.0.2 + + vary@1.1.2: {} + + vite-plugin-remove-console@2.2.0: {} + + vite@5.4.21(@types/node@20.19.43)(less@4.8.0): + dependencies: + esbuild: 0.21.5 + postcss: 8.5.21 + rollup: 4.62.2 + optionalDependencies: + '@types/node': 20.19.43 + fsevents: 2.3.3 + less: 4.8.0 + + vscode-uri@3.1.0: {} + + vue-eslint-parser@9.4.3(eslint@8.57.1): + dependencies: + debug: 4.4.3 + eslint: 8.57.1 + eslint-scope: 7.2.2 + eslint-visitor-keys: 3.4.3 + espree: 9.6.1 + esquery: 1.7.0 + lodash: 4.18.1 + semver: 7.8.5 + transitivePeerDependencies: + - supports-color + + vue-router@4.6.4(vue@3.5.40(typescript@5.9.3)): + dependencies: + '@vue/devtools-api': 6.6.4 + vue: 3.5.40(typescript@5.9.3) + + vue-tsc@2.2.12(typescript@5.9.3): + dependencies: + '@volar/typescript': 2.4.15 + '@vue/language-core': 2.2.12(typescript@5.9.3) + typescript: 5.9.3 + + vue-types@3.0.2(vue@3.5.40(typescript@5.9.3)): + dependencies: + is-plain-object: 3.0.1 + vue: 3.5.40(typescript@5.9.3) + + vue@3.5.40(typescript@5.9.3): + dependencies: + '@vue/compiler-dom': 3.5.40 + '@vue/compiler-sfc': 3.5.40 + '@vue/runtime-dom': 3.5.40 + '@vue/server-renderer': 3.5.40 + '@vue/shared': 3.5.40 + optionalDependencies: + typescript: 5.9.3 + + warning@4.0.3: + dependencies: + loose-envify: 1.4.0 + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + word-wrap@1.2.5: {} + + wrap-ansi@7.0.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + wrap-ansi@9.0.2: + dependencies: + ansi-styles: 6.2.3 + string-width: 7.2.0 + strip-ansi: 7.2.0 + + wrappy@1.0.2: {} + + xml-name-validator@4.0.0: {} + + y18n@5.0.8: {} + + yallist@3.1.1: {} + + yaml@2.9.0: {} + + yargs-parser@21.1.1: {} + + yargs@17.7.3: + dependencies: + cliui: 8.0.1 + escalade: 3.2.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 21.1.1 + + yocto-queue@0.1.0: {} diff --git a/src/App.tsx b/src/App.tsx index f6f5c6d..e25c41f 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,7 +1,10 @@ import { defineComponent } from 'vue'; import { ConfigProvider } from 'ant-design-vue'; -import zhCN from 'ant-design-vue/es/locale/zh_CN'; +import zhCN from 'ant-design-vue/locale/zh_CN'; import { RouterView } from 'vue-router'; +import dayjs from 'dayjs'; +import 'dayjs/locale/zh-cn'; +dayjs.locale('zh-cn'); /** * 根组件 diff --git a/src/api/upload.ts b/src/api/upload.ts new file mode 100644 index 0000000..e732da7 --- /dev/null +++ b/src/api/upload.ts @@ -0,0 +1,27 @@ +import { get } from '@/utils/request'; +import type { OssUploadTypeValue } from '@/utils/oss'; + +/** OSS 签名数据 */ +export interface OssSignData { + host: string; + dir: string; + policy: string; + signature: string; + x_oss_credential: string; + x_oss_date: string; + security_token: string; +} + +/** + * 获取 OSS 上传签名 + * @param type 上传类型(参见 OssUploadType) + */ +export async function fetchOssSign(type: OssUploadTypeValue): Promise { + // 后端签名接口地址(待对接时调整) + const res: any = await get('/sys/oss/getUploadSign', { type }); + const data = res?.data as OssSignData; + if (!data?.host) { + throw new Error(res?.msg || '获取上传凭证失败'); + } + return data; +} diff --git a/src/assets/img/icon/logout.svg b/src/assets/img/icon/logout.svg new file mode 100644 index 0000000..43a1425 --- /dev/null +++ b/src/assets/img/icon/logout.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/styles/index.less b/src/assets/styles/index.less index b5a78cc..af15fdb 100644 --- a/src/assets/styles/index.less +++ b/src/assets/styles/index.less @@ -55,3 +55,61 @@ a { ::-webkit-scrollbar-track { background: transparent; } + +// ===== 页面布局公共类 ===== + +// 页面容器:纵向 flex,占满高度;筛选区与表格区作为直接子元素 +.page-container { + display: flex; + flex-direction: column; + height: 100%; +} + +// 页面筛选区域:横向 flex,可换行 +.page-filter { + display: flex; + flex-wrap: wrap; + align-items: center; + column-gap: 16px; + row-gap: 12px; + flex-shrink: 0; + padding: 16px; + background-color: #fff; + border-radius: 10px; +} + +// 筛选区域操作按钮组:横向 flex +.page-filter-actions { + display: flex; + align-items: center; + gap: 10px; + margin-left: 0; +} + +// 表格显示区域:纵向 flex,占据剩余高度。 +// overflow: hidden 防止子元素撑开容器,滚动由 Table 的 scroll.x/y 接管。 +// 分页组件作为独立子元素置于 Table 下方,不占用 Table 的 scroll 空间。 +.page-table { + display: flex; + flex-direction: column; + flex: 1; + background-color: #fff; + min-height: 0; + min-width: 0; + margin-top: 16px; + overflow: hidden; + border-radius: 10px; +} + +.page-pagination { + flex-shrink: 0; + display: flex; + justify-content: flex-end; + padding: 14px; +} +// ===== antd 组件覆盖 ===== + +// Tag 默认 margin-right: 8px,会挤开表格列或卡片内间距,统一去掉 +.ant-tag { + margin-right: 0; +} diff --git a/src/components/ChangePasswordModal/controller.ts b/src/components/ChangePasswordModal/controller.ts new file mode 100644 index 0000000..22ccb67 --- /dev/null +++ b/src/components/ChangePasswordModal/controller.ts @@ -0,0 +1,48 @@ +/** + * 修改密码弹窗 - 表单域控制器 + * + * 集中管理弹窗内的所有规则与提示文案。 + */ +import type { Rule } from 'ant-design-vue/es/form'; +import { isValidPassword } from '@/utils/form'; + +// ============================================================ +// 文案 +// ============================================================ + +/** 弹窗底部提示 */ +export const TIPS_TEXT = '修改后立即生效,请妥善保管新密码'; + +// ============================================================ +// 表单规则 +// ============================================================ + +/** 新密码规则:必填 + 至少 8 位 + 字母 + 数字 */ +export const newPasswordRules: Rule[] = [ + { required: true, message: '请输入新密码' }, + { + validator: (_rule: unknown, value: string) => { + if (!value) return Promise.resolve(); + if (!isValidPassword(value)) { + return Promise.reject(new Error('密码至少8位,需包含字母与数字')); + } + return Promise.resolve(); + }, + }, +]; + +/** 确认密码规则:必填 + 必须与新密码一致 */ +export function confirmNewPasswordRules(getPassword: () => string): Rule[] { + return [ + { required: true, message: '请再次输入新密码' }, + { + validator: (_rule: unknown, value: string) => { + if (!value) return Promise.resolve(); + if (value !== getPassword()) { + return Promise.reject(new Error('两次输入的密码不一致')); + } + return Promise.resolve(); + }, + }, + ]; +} diff --git a/src/components/ChangePasswordModal/index.tsx b/src/components/ChangePasswordModal/index.tsx new file mode 100644 index 0000000..4c61711 --- /dev/null +++ b/src/components/ChangePasswordModal/index.tsx @@ -0,0 +1,100 @@ +import { defineComponent, ref, reactive } from 'vue'; +import { useEffect } from '@/hooks'; +import { Modal, Form, Input, Button, message } from 'ant-design-vue'; +import { TIPS_TEXT, newPasswordRules, confirmNewPasswordRules } from './controller'; +import styles from './style.module.less'; + +interface ChangePasswordModalProps { + visible: boolean; + submitting?: boolean; + onClose: () => void; + /** 提交回调:父组件负责调用 API,loading 状态由 submitting 控制 */ + onSave: (payload: { newPassword: string }) => void; +} + +/** 默认表单数据 */ +const getDefaultForm = () => ({ + newPassword: '', + confirmPassword: '', +}); + +export default defineComponent({ + name: 'ChangePasswordModal', + props: { + visible: { type: Boolean, default: false }, + submitting: { type: Boolean, default: false }, + onClose: { type: Function, required: true }, + onSave: { type: Function, required: true }, + }, + setup(props: ChangePasswordModalProps) { + const formRef = ref(); + const formData = reactive(getDefaultForm()); + + /** 监听 visible 重置表单 */ + useEffect(() => { + if (props.visible) { + Object.assign(formData, getDefaultForm()); + setTimeout(() => formRef.value?.clearValidate(), 0); + } + }, [() => props.visible]); + + /** 提交 */ + const handleSubmit = async () => { + try { + await formRef.value?.validate(); + } catch { + return; + } + props.onSave({ newPassword: formData.newPassword }); + }; + + const handleClose = () => { + props.onClose(); + }; + + return () => ( + +
+ + + + + formData.newPassword)} + > + + + +
{TIPS_TEXT}
+
+ + {/* 底部按钮 */} +
+ + +
+
+ ); + }, +}); diff --git a/src/components/ChangePasswordModal/style.module.less b/src/components/ChangePasswordModal/style.module.less new file mode 100644 index 0000000..4d622b2 --- /dev/null +++ b/src/components/ChangePasswordModal/style.module.less @@ -0,0 +1,37 @@ +.form { + :global { + .ant-form-item { + margin-bottom: 16px; + } + .ant-form-item-explain-error { + font-size: 12px; + } + } +} + +/* 底部提示文案 */ +.tip { + margin-top: -4px; + margin-bottom: 24px; + color: rgba(0, 0, 0, 0.45); + font-size: 12px; + line-height: 1.5; +} + +/* 底部按钮 */ +.footer { + display: flex; + justify-content: flex-end; + gap: 8px; + padding-top: 16px; + border-top: 1px solid #f0f0f0; +} + +/* 微调 Modal 内部样式 */ +.modal { + :global { + .ant-modal-body { + padding: 16px 24px 0; + } + } +} diff --git a/src/components/index.ts b/src/components/index.ts index 17c5079..fdb9b81 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -3,3 +3,4 @@ * 在业务中可通过 `import { HelloWorld } from '@/components'` 使用 */ export { default as HelloWorld } from './HelloWorld'; +export { default as ChangePasswordModal } from './ChangePasswordModal'; diff --git a/src/config/fallbackRoutes.ts b/src/config/fallbackRoutes.ts new file mode 100644 index 0000000..9282088 --- /dev/null +++ b/src/config/fallbackRoutes.ts @@ -0,0 +1,142 @@ +import type { MenuNode } from '@/types'; + +/** + * ====== 兜底路由配置 ====== + * + * 当后端菜单接口不可用时,使用此配置作为降级方案。 + * 数据格式与后端 `/menu/tree` 接口返回完全一致, + * 因此可以直接通过 transformMenuToRoutes / transformMenuNode 复用同一套处理逻辑。 + * + * ## 后端应返回的数组格式示例 + * + * ```json + * [ + * { "id": "dashboard", "name": "工作台", "path": "dashboard", "component": "dashboard", "icon": "DashboardOutlined" }, + * { + * "id": "events", "name": "赛事管理", "path": "events", "icon": "TrophyOutlined", + * "children": [ + * { "id": "events_list", "name": "赛事列表", "path": "events/list", "component": "events/list" }, + * { "id": "events_orders", "name": "订单管理", "path": "events/orders", "component": "events/orders" } + * ] + * } + * ] + * ``` + * + * ### 字段说明 + * | 字段 | 类型 | 必填 | 说明 | + * |------|------|------|------| + * | id | number\|string | 是 | 唯一标识 | + * | name | string | 是 | 菜单名称/路由 name | + * | path | string | 是 | 路由相对路径(不带 / 前缀) | + * | component | string | 否 | 组件路径(映射 src/pages/ 下目录,如 "events/list") | + * | icon | string | 否 | antd 图标名(如 DashboardOutlined) | + * | sort | number | 否 | 排序权重(越小越前) | + * | hideInMenu | boolean | 否 | 是否在菜单中隐藏 | + * | externalLink | string | 否 | 外链地址 | + * | activeMenu | string | 否 | 高亮菜单路径 | + * | disabled | boolean | 否 | 禁用标记(禁用后路由不注册、菜单不显示) | + * | children | MenuNode[] | 否 | 子路由 | + * + * ### 关于 routes → menuItems 的转换 + * `menuStore.ts` 中的 `transformMenuNode()` 已完成此功能, + * 它将 MenuNode[] 递归转换为 antd Menu 组件所需的 MenuItemRaw[] 格式。 + */ + +export const FALLBACK_MENU_NODES: MenuNode[] = [ + { + id: 'events', + name: '赛事管理', + path: 'events', + icon: 'TrophyOutlined', + children: [ + { + id: 'events_list', + name: '赛事列表', + path: 'events/list', + component: 'events/list', + }, + { + id: 'events_orders', + name: '订单管理', + path: 'events/orders', + component: 'events/orders', + }, + { + id: 'events_users', + name: '用户列表', + path: 'events/users', + component: 'events/users', + }, + { + id: 'events_banner', + name: 'Banner配置', + path: 'events/banner', + component: 'events/banner', + }, + { + id: 'events_logs', + name: '赛事操作日志', + path: 'events/logs', + component: 'events/logs', + }, + ], + }, + { + id: 'finance', + name: '账务管理', + path: 'finance', + icon: 'TransactionOutlined', + children: [ + { + id: 'finance_withdraw', + name: '提现申请', + path: 'finance/withdraw', + component: 'finance/withdraw', + }, + { + id: 'finance_wallet', + name: '用户钱包', + path: 'finance/wallet', + component: 'finance/wallet', + }, + { + id: 'finance_payments', + name: '支付流水', + path: 'finance/payments', + component: 'finance/payments', + }, + { + id: 'finance_reports', + name: '账务报表', + path: 'finance/reports', + component: 'finance/reports', + }, + ], + }, + { + id: 'system', + name: '系统管理', + path: 'system', + icon: 'SettingOutlined', + children: [ + { + id: 'system_users', + name: '用户管理', + path: 'system/users', + component: 'system/users', + }, + { + id: 'system_roles', + name: '角色权限', + path: 'system/roles', + component: 'system/roles', + }, + { + id: 'system_logs', + name: '操作日志', + path: 'system/logs', + component: 'system/logs', + }, + ], + }, +]; diff --git a/src/hooks/index.ts b/src/hooks/index.ts index 78a5410..11f8541 100644 --- a/src/hooks/index.ts +++ b/src/hooks/index.ts @@ -7,4 +7,6 @@ export * from './useAuth'; export * from './useWebSocket'; export * from './useBasicLayout'; export * from './useBreadcrumb'; +export * from './useThrottleFn'; +export * from './useContainerSize'; export * from './useContext'; diff --git a/src/hooks/useAuth.ts b/src/hooks/useAuth.ts index 5eca581..275228a 100644 --- a/src/hooks/useAuth.ts +++ b/src/hooks/useAuth.ts @@ -1,6 +1,6 @@ // src/hooks/useAuth.ts import { useState } from './useState'; -import { watch } from 'vue'; +import { useEffect } from './useEffect'; import { useMenuStore } from '@/stores/menuStore'; import { usePermissionStore } from '@/stores/permissionStore'; import router from '@/router'; @@ -10,13 +10,14 @@ const TOKEN_KEY = 'MY_APP_AUTH_TOKEN'; function createAuth() { const [token, setToken] = useState(window.localStorage.getItem(TOKEN_KEY)); - watch(token, (newToken) => { + useEffect(() => { + const newToken = token.value; if (newToken) { window.localStorage.setItem(TOKEN_KEY, newToken); } else { window.localStorage.removeItem(TOKEN_KEY); } - }); + }, [token]); const isLoggedIn = () => !!token.value; diff --git a/src/hooks/useContainerSize.ts b/src/hooks/useContainerSize.ts new file mode 100644 index 0000000..8bf68e4 --- /dev/null +++ b/src/hooks/useContainerSize.ts @@ -0,0 +1,54 @@ +import { ref, onMounted, onUnmounted, type Ref } from 'vue'; + +/** + * 监听容器尺寸变化,返回动态 width/height 给 Table 的 scroll 属性使用。 + * + * 基于 ResizeObserver,窗口缩放 / 侧边栏收起展开 / 筛选区折叠 等任何导致 + * 容器尺寸变化的行为都会自动触发重新计算,无需额外处理。 + * + * 用法: + * ```ts + * const { containerRef, width, height } = useContainerSize(); + * //
+ * // + * // + * ``` + * + * @param headerOffset 从容器高度中扣除的表头偏移量,默认 55 + */ +export function useContainerSize(options?: { headerOffset?: number }) { + const { headerOffset = 55 } = options ?? {}; + + const containerRef = ref(null) as Ref; + const width = ref(0); + const height = ref(0); + + let observer: ResizeObserver | null = null; + + const updateSize = () => { + const el = containerRef.value; + if (!el) return; + width.value = el.clientWidth; + height.value = Math.max(el.clientHeight - headerOffset, 200); + }; + + onMounted(() => { + updateSize(); + const el = containerRef.value; + if (!el) return; + + observer = new ResizeObserver((entries) => { + for (const entry of entries) { + const w = entry.contentRect.width; + const h = entry.contentRect.height; + if (w !== width.value) width.value = w; + if (h !== height.value) height.value = Math.max(h - headerOffset, 200); + } + }); + observer.observe(el); + }); + + onUnmounted(() => observer?.disconnect()); + + return { containerRef, width, height }; +} diff --git a/src/hooks/useDebounce.ts b/src/hooks/useDebounce.ts index aebfad1..30393d1 100644 --- a/src/hooks/useDebounce.ts +++ b/src/hooks/useDebounce.ts @@ -1,6 +1,7 @@ // src/hooks/useDebounce.ts -import { watch, onUnmounted } from 'vue'; +import { onUnmounted, Ref } from 'vue'; import { useState } from './useState'; +import { useEffect } from './useEffect'; export interface UseDebounceOptions { delay?: number; @@ -8,14 +9,11 @@ export interface UseDebounceOptions { maxWait?: number; } -export function useDebounce( - source: any, // Ref - options: number | UseDebounceOptions = {}, -) { +export function useDebounce(source: Ref, options: number | UseDebounceOptions = {}) { const config = typeof options === 'number' ? { delay: options } : options; const { delay = 300, immediate = false, maxWait } = config; - const [debouncedValue, setDebouncedValue] = useState(source.value); + const [debouncedValue, setDebouncedValue] = useState(source.value as T); const [isPending, setIsPending] = useState(false); let timer: any = null; @@ -43,7 +41,7 @@ export function useDebounce( isFirstCall = true; }; - watch(source, () => { + useEffect(() => { setIsPending(true); // 处理 maxWait @@ -64,7 +62,9 @@ export function useDebounce( updateValue(); }, delay); } - }); + + return clearTimers; + }, [source]); // 卸载 onUnmounted(() => { diff --git a/src/hooks/usePagination.ts b/src/hooks/usePagination.ts index eeafc3c..d3a0a13 100644 --- a/src/hooks/usePagination.ts +++ b/src/hooks/usePagination.ts @@ -1,6 +1,7 @@ /* eslint-disable @typescript-eslint/ban-ts-comment */ // src/hooks/usePagination.ts -import { computed, ref, watch, Ref, triggerRef } from 'vue'; +import { computed, ref, Ref, triggerRef } from 'vue'; +import { useEffect } from './useEffect'; /** * 分页配置项 @@ -77,24 +78,20 @@ export function usePagination(options: UsePaginationOptions = {}): UsePagination const pageSizeRef = ref(options.pageSize ?? defaultPageSize); const totalRef = ref(options.total ?? 0); - watch( - () => options.current, - (val) => { - if (val !== undefined) currentRef.value = val; - }, - ); - watch( - () => options.pageSize, - (val) => { - if (val !== undefined) pageSizeRef.value = val; - }, - ); - watch( - () => options.total, - (val) => { - if (val !== undefined) totalRef.value = val; - }, - ); + useEffect(() => { + const val = options.current; + if (val !== undefined) currentRef.value = val; + }, [() => options.current]); + + useEffect(() => { + const val = options.pageSize; + if (val !== undefined) pageSizeRef.value = val; + }, [() => options.pageSize]); + + useEffect(() => { + const val = options.total; + if (val !== undefined) totalRef.value = val; + }, [() => options.total]); const totalPages = computed(() => { const total = totalRef.value; diff --git a/src/hooks/useRequest.ts b/src/hooks/useRequest.ts index bd1076c..ce984e7 100644 --- a/src/hooks/useRequest.ts +++ b/src/hooks/useRequest.ts @@ -1,5 +1,6 @@ /* eslint-disable @typescript-eslint/ban-ts-comment */ -import { ref, Ref, watch, WatchSource, onUnmounted, computed } from 'vue'; +import { ref, Ref, WatchSource, onUnmounted, computed } from 'vue'; +import { useEffect } from './useEffect'; export interface UseRequestReturn { /** 纯净的数据,不包含后端外层壳 */ @@ -128,31 +129,29 @@ export function useRequest( const shouldAutoRun = computed(() => !manual && ready.value); let hasAutoRun = false; // 标记是否已自动执行过,防止重复触发 - watch( - shouldAutoRun, - (val) => { - if (val && !hasAutoRun) { - hasAutoRun = true; - run(); - } else if (!val) { - // 当条件不满足时重置 hasAutoRun,使下次满足条件时能再次自动执行 - hasAutoRun = false; - } - }, - { immediate: true }, - ); + useEffect(() => { + const val = shouldAutoRun.value; + if (val && !hasAutoRun) { + hasAutoRun = true; + run(); + } else if (!val) { + // 当条件不满足时重置 hasAutoRun,使下次满足条件时能再次自动执行 + hasAutoRun = false; + } + }, [shouldAutoRun]); // refreshDeps 变化时重新请求(初始化时不再重复触发) if (refreshDeps && refreshDeps.length > 0) { - watch( - refreshDeps, - () => { - if (shouldAutoRun.value) { - run(); - } - }, - { deep: true }, - ); + let skipInitialRefresh = true; + useEffect(() => { + if (skipInitialRefresh) { + skipInitialRefresh = false; + return; + } + if (shouldAutoRun.value) { + run(); + } + }, refreshDeps); } const getSignal = () => abortController?.signal; diff --git a/src/hooks/useThrottleFn.ts b/src/hooks/useThrottleFn.ts new file mode 100644 index 0000000..9b1ce8e --- /dev/null +++ b/src/hooks/useThrottleFn.ts @@ -0,0 +1,46 @@ +import { ref } from 'vue'; + +/** + * 节流函数封装 + * @param fn 需要节流的函数 + * @param delay 节流间隔时间(ms),默认 500 + * @returns 返回节流后的函数,连续调用时只会在每个 delay 周期内执行一次 + */ +export function useThrottleFn any>( + fn: T, + delay: number = 500, +): T & { cancel: () => void } { + const lastTime = ref(0); + let timer: any = null; + + const throttled = function (this: any, ...args: any[]) { + const now = Date.now(); + const remaining = delay - (now - lastTime.value); + + if (remaining <= 0) { + // 冷却时间已过,立即执行 + if (timer) { + clearTimeout(timer); + timer = null; + } + lastTime.value = now; + fn.apply(this, args); + } else if (!timer) { + // 冷却时间未过,延后执行 + timer = setTimeout(() => { + lastTime.value = Date.now(); + timer = null; + fn.apply(this, args); + }, remaining); + } + } as T & { cancel: () => void }; + + throttled.cancel = () => { + if (timer) { + clearTimeout(timer); + timer = null; + } + }; + + return throttled; +} diff --git a/src/hooks/useWebSocket.ts b/src/hooks/useWebSocket.ts index 2952105..4cde23b 100644 --- a/src/hooks/useWebSocket.ts +++ b/src/hooks/useWebSocket.ts @@ -1,5 +1,6 @@ -import { ref, watch, onBeforeUnmount, toValue } from 'vue'; +import { ref, onBeforeUnmount, toValue } from 'vue'; import type { Ref, MaybeRefOrGetter } from 'vue'; +import { useEffect } from './useEffect'; // --- 类型定义 --- export type WebSocketStatus = 'connecting' | 'open' | 'closed' | 'error'; @@ -216,14 +217,16 @@ export function useWebSocket( // --- 监听与副作用 --- // 监听 URL 变化,如果变了则重新连接 - watch( - () => toValue(url), - () => { - if (status.value !== 'closed') { - connect(); - } - }, - ); + let skipInitialUrlWatch = true; + useEffect(() => { + if (skipInitialUrlWatch) { + skipInitialUrlWatch = false; + return; + } + if (status.value !== 'closed') { + connect(); + } + }, [() => toValue(url)]); // 组件卸载时清理资源 onBeforeUnmount(() => { diff --git a/src/layouts/BasicLayout.module.less b/src/layouts/BasicLayout.module.less index a3fc109..3b182a3 100644 --- a/src/layouts/BasicLayout.module.less +++ b/src/layouts/BasicLayout.module.less @@ -1,9 +1,6 @@ .container { min-height: 100vh; -} - -.sider { - box-shadow: 2px 0 8px rgba(0, 0, 0, 0.08); + height: 100vh; } .logo { @@ -12,8 +9,8 @@ align-items: center; justify-content: center; overflow: hidden; - color: #fff; - background: rgba(255, 255, 255, 0.08); + color: #1a1a1a; + background: #fff; } .logoText { @@ -22,14 +19,6 @@ white-space: nowrap; } -.header { - background: #fff; - padding: 0 16px; - display: flex; - align-items: center; - box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08); -} - .trigger { font-size: 18px; cursor: pointer; @@ -43,15 +32,84 @@ } } -.content { - margin: 16px; - padding: 24px; - background: #fff; - border-radius: 8px; - min-height: 280px; +// 面包屑 +.breadcrumb { + flex: 1; + margin-left: 8px; + font-size: 14px; + + :global { + .ant-breadcrumb-link, + .ant-breadcrumb-separator, + .ant-breadcrumb-item a { + color: rgba(0, 0, 0, 0.65); + } + + .ant-breadcrumb-item:last-child { + .ant-breadcrumb-link, + .ant-breadcrumb-separator, + a { + color: rgba(0, 0, 0, 0.88); + font-weight: 500; + cursor: default; + } + } + } } -.footer { - text-align: center; - color: rgba(0, 0, 0, 0.45); +// Header 右侧:账号信息 + 退出图标 +.headerRight { + margin-left: auto; + display: flex; + gap: 10px; + align-items: center; +} + +.userPhone { + font-size: 14px; + color: rgba(0, 0, 0, 0.65); +} + +.logoutIcon { + width: 18px; + height: 18px; + opacity: 0.45; + cursor: pointer; + transition: opacity 0.3s; + + &:hover { + opacity: 1; + } +} + +// 下拉菜单项:图标 + 文字 +.menuItem { + display: inline-flex; + align-items: center; + gap: 8px; + + :global(.anticon) { + font-size: 14px; + } +} + +.menuItemDanger { + color: #ff4d4f; +} + +.content { + margin: 16px; + border-radius: 8px; + flex: 1; + min-height: 0; + display: flex; + flex-direction: column; + overflow: hidden; +} + +.main { + flex: 1; + min-height: 0; + display: flex; + flex-direction: column; } diff --git a/src/layouts/BasicLayout.tsx b/src/layouts/BasicLayout.tsx index 00e4baa..cea6533 100644 --- a/src/layouts/BasicLayout.tsx +++ b/src/layouts/BasicLayout.tsx @@ -1,19 +1,49 @@ import { computed, defineComponent, h, ref } from 'vue'; +import type { CSSProperties } from 'vue'; import { useRouter, useRoute, RouterView } from 'vue-router'; -import { Layout, Menu } from 'ant-design-vue'; +import { Layout, Menu, Modal, Breadcrumb, Dropdown, message } from 'ant-design-vue'; import type { MenuProps } from 'ant-design-vue'; import { DashboardOutlined, + EditOutlined, InfoCircleOutlined, + LogoutOutlined, MenuFoldOutlined, MenuUnfoldOutlined, SettingOutlined, + TransactionOutlined, + TrophyOutlined, } from '@ant-design/icons-vue'; import { useMenuStore } from '@/stores/menuStore'; +import { useUserStore } from '@/stores/userStore'; +import { useState, useBreadcrumb, auth } from '@/hooks'; +import { ChangePasswordModal } from '@/components'; +import logoutSvg from '@/assets/img/icon/logout.svg'; import styles from './BasicLayout.module.less'; const { Header, Sider, Content, Footer } = Layout; +const headerStyle: CSSProperties = { + background: '#fff', + padding: '0 16px', + display: 'flex', + alignItems: 'center', + color: '#1a1a1a', + boxShadow: '0 1px 4px rgba(0, 21, 41, 0.08)', +}; + +const siderStyle: CSSProperties = { + background: '#fff', + boxShadow: '2px 0 8px rgba(0, 21, 41, 0.08)', +}; + +const footerStyle: CSSProperties = { + padding: '0 50px 16px 50px', + textAlign: 'center', + color: '#3a3a3a', + userSelect: 'none', +}; + /** * 图标名 → 组件映射 * 后端下发的 icon 字符串在此映射为 antd icon 组件 @@ -23,6 +53,8 @@ const ICON_MAP: Record = { DashboardOutlined, InfoCircleOutlined, SettingOutlined, + TransactionOutlined, + TrophyOutlined, }; /** @@ -60,6 +92,8 @@ export default defineComponent({ const collapsed = ref(false); const { menuItems } = useMenuStore(); + const { phone } = useUserStore(); + const { breadcrumbs } = useBreadcrumb(); const selectedKeys = computed(() => [route.path]); @@ -73,14 +107,61 @@ export default defineComponent({ collapsed.value = !collapsed.value; }; + /** ===== 退出登录 ===== */ + const handleLogout = () => { + Modal.confirm({ + title: '提示', + content: '是否退出登录?', + okText: '确认', + cancelText: '取消', + onOk: () => { + const { clearUser } = useUserStore(); + clearUser(); + auth.logout(); + }, + }); + }; + + /** ===== 修改密码弹窗 ===== */ + const [changePwdVisible, setChangePwdVisible] = useState(false); + const [changePwdSubmitting, setChangePwdSubmitting] = useState(false); + + const handleChangePassword = () => { + setChangePwdVisible(true); + }; + + const handleChangePwdSave = async (payload: { newPassword: string }) => { + if (changePwdSubmitting.value) return; + setChangePwdSubmitting(true); + try { + // TODO: 替换为真实 API 调用 + console.log('修改密码:', payload); + message.success('修改成功'); + setChangePwdVisible(false); + } catch (err: any) { + message.error(err?.msg || '修改失败'); + } finally { + setChangePwdSubmitting(false); + } + }; + + /** ===== 头像下拉菜单:修改密码 / 退出登录 ===== */ + const handleUserMenuClick: MenuProps['onClick'] = ({ key }) => { + if (key === 'changePassword') { + handleChangePassword(); + } else if (key === 'logout') { + handleLogout(); + } + }; + return () => ( - +
- {collapsed.value ? '6羽' : '六个羽友赛事'} + {collapsed.value ? '6羽' : '六羽赛事运营平台'}
- -
+ +
{collapsed.value ? : } + + {/* 面包屑:紧跟在折叠按钮之后 */} + + {breadcrumbs.value.map((item, index) => { + const isLast = index === breadcrumbs.value.length - 1; + return ( + + {isLast || !item.path ? ( + {item.title} + ) : ( + router.push(item.path as string)}>{item.title} + )} + + ); + })} + + + {/* 右侧: 账号信息 + 退出 */} +
+ {phone.value} + + {{ + default: () => , + overlay: () => ( + + + + + 修改密码 + + + + + + + 退出登录 + + + + ), + }} + +
-
六个羽友赛事运营平台 ©{new Date().getFullYear()}
+
六个羽友赛事运营平台 ©{new Date().getFullYear()}
+ + {/* 修改密码弹窗 */} + {changePwdVisible.value && ( + setChangePwdVisible(false)} + onSave={handleChangePwdSave} + /> + )} ); }, diff --git a/src/main.ts b/src/main.ts index d723be9..2c16dae 100644 --- a/src/main.ts +++ b/src/main.ts @@ -6,10 +6,13 @@ import App from './App'; import router from './router'; import './router/guard'; // 路由守卫:登录鉴权 + 动态菜单加载 import './assets/styles/index.less'; +import ypComponent from '@yp-component/root'; +import '@yp-component/root/style.css'; const app = createApp(App); app.use(router); app.use(Antd); +app.use(ypComponent); app.mount('#app'); diff --git a/src/pages/events/banner/components/BannerFormModal.module.less b/src/pages/events/banner/components/BannerFormModal.module.less new file mode 100644 index 0000000..8d3a844 --- /dev/null +++ b/src/pages/events/banner/components/BannerFormModal.module.less @@ -0,0 +1,133 @@ +.form { + :global { + .ant-form-item { + margin-bottom: 16px; + } + .ant-form-item-explain-error { + font-size: 12px; + } + } +} + +/* ===== 上传 ===== */ +.uploader { + position: relative; +} + +.uploadCard { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + width: 100%; + height: 140px; + border: 1px dashed #d9d9d9; + border-radius: 6px; + background-color: #fafafa; + cursor: pointer; + transition: border-color 0.2s; + gap: 4px; + + &:hover { + border-color: #1677ff; + } +} + +.uploadCardError { + border-color: #ff4d4f; +} + +.uploadIcon { + font-size: 32px; + color: #999; + font-weight: 300; + line-height: 1; +} + +.uploadHint { + color: #666; + font-size: 14px; +} + +.uploadTip { + color: #999; + font-size: 12px; + text-align: center; + line-height: 1.6; +} + +/* ===== 预览 ===== */ +.previewWrapper { + position: relative; + width: 100%; + height: 140px; + border: 1px solid #d9d9d9; + border-radius: 6px; + overflow: hidden; + background-color: #f5f5f5; + + :global { + .ant-spin-nested-loading, + .ant-spin-container { + width: 100%; + height: 100%; + } + } +} + +.previewImg { + width: 100%; + height: 100%; + object-fit: cover; + display: block; +} + +.uploadAgainBtn { + position: absolute; + bottom: 0; + left: 0; + right: 0; + background: rgba(0, 0, 0, 0.5); + color: #fff; + text-align: center; + padding: 4px 0; + cursor: pointer; + font-size: 12px; + user-select: none; + transition: background 0.2s; + + &:hover { + background: rgba(0, 0, 0, 0.65); + } +} + +/* ===== 提示 ===== */ +.tip { + padding: 8px 12px; + margin-bottom: 16px; + background-color: #f0f5ff; + border: 1px solid #d6e4ff; + border-radius: 4px; + color: #1677ff; + font-size: 13px; +} + +/* ===== 时间行(双列)===== */ +.timeRow { + display: flex; + gap: 16px; + + .timeItem { + flex: 1; + } +} + +/* ===== 底部按钮 ===== */ +.footer { + display: flex; + justify-content: flex-end; + gap: 8px; + padding-top: 16px; + border-top: 1px solid #f0f0f0; + margin-top: 8px; +} diff --git a/src/pages/events/banner/components/BannerFormModal.tsx b/src/pages/events/banner/components/BannerFormModal.tsx new file mode 100644 index 0000000..d4f3feb --- /dev/null +++ b/src/pages/events/banner/components/BannerFormModal.tsx @@ -0,0 +1,549 @@ +import { defineComponent, ref, reactive } from 'vue'; +import { useEffect } from '@/hooks'; +import { + Modal, + Form, + Input, + Select, + DatePicker, + InputNumber, + Button, + message, + Spin, +} from 'ant-design-vue'; +import { JUMP_TYPE_OPTIONS, EVENT_OPTIONS, REGION_OPTIONS } from '../model/useBannerModel'; +import { ImageCropper } from '@yp-component/root'; +import { uploadFile, OssUploadType } from '@/utils/oss'; +import dayjs from 'dayjs'; +import styles from './BannerFormModal.module.less'; + +/** 默认表单数据 */ +const getDefaultForm = () => ({ + title: '', + imageUrl: '', + imageStatus: 'empty' as 'empty' | 'uploading' | 'success' | 'error', + jumpType: 'event_detail' as 'event_detail' | 'miniprogram_page' | 'custom_link' | 'none', + relatedEventId: '', + pagePath: '', + customUrl: '', + region: 'home', + displayTimeRange: null as [string, string] | null, + sort: 1, +}); + +/** 表单字段最大长度限制 */ +const TITLE_MAX = 50; +const PATH_MAX = 200; +const URL_MAX = 500; + +/** 图片上传限制:最大 10MB */ +const IMAGE_MAX_SIZE = 10 * 1024 * 1024; +/** 裁剪比例:1053:351 = 3:1 */ +const CROP_ASPECT_RATIO = 1053 / 351; + +interface BannerFormModalProps { + visible: boolean; + record: any; + submitting?: boolean; + onClose: () => void; + onSave: (formData: any) => void; +} + +/** + * 将 base64 字符串转换为 File 对象 + */ +function base64ToFile(dataUrl: string, fileName: string): File { + const arr = dataUrl.split(','); + const mime = arr[0].match(/:(.*?);/)?.[1] || 'image/png'; + const bstr = atob(arr[1]); + const u8arr = new Uint8Array(bstr.length); + for (let i = 0; i < bstr.length; i++) { + u8arr[i] = bstr.charCodeAt(i); + } + return new File([u8arr], fileName, { type: mime }); +} + +/** + * Banner 新增/编辑表单弹窗 + */ +export default defineComponent({ + name: 'BannerFormModal', + props: { + visible: { type: Boolean, default: false }, + record: { type: Object, default: null }, + submitting: { type: Boolean, default: false }, + onClose: { type: Function, required: true }, + onSave: { type: Function, required: true }, + }, + setup(props: BannerFormModalProps) { + const formRef = ref(); + const fileInputRef = ref(null); + const formData = reactive(getDefaultForm()); + + /** 裁剪弹窗状态 */ + const cropperVisible = ref(false); + /** 待裁剪图片的本地预览 URL */ + const uploadImageUrl = ref(''); + + /** 根据 record 初始化表单 */ + const initFormFromRecord = (record: any) => { + const fresh = getDefaultForm(); + if (!record) { + Object.assign(formData, fresh); + return; + } + + let pagePath = ''; + let customUrl = ''; + if (record.jumpType === 'miniprogram_page') { + pagePath = record.jumpUrl || ''; + } else if (record.jumpType === 'custom_link') { + customUrl = record.jumpUrl || ''; + } + + Object.assign(formData, { + ...fresh, + title: record.title || '', + imageUrl: record.imageUrl || '', + imageStatus: record.imageUrl ? ('success' as const) : ('empty' as const), + jumpType: record.jumpType || 'event_detail', + relatedEventId: record.relatedEventId || '', + pagePath, + customUrl, + region: record.region && record.region !== record.city ? record.region : 'home', + displayTimeRange: + record.displayStartTime && record.displayEndTime + ? ([record.displayStartTime, record.displayEndTime] as [string, string]) + : null, + sort: record.sort ?? 1, + }); + }; + + /** 监听 visible 变化重置表单 */ + useEffect(() => { + if (props.visible) { + initFormFromRecord(props.record); + cropperVisible.value = false; + uploadImageUrl.value = ''; + setTimeout(() => formRef.value?.clearValidate(), 0); + } + }, [() => props.visible]); + + /** 监听 jumpType 变化时清理联动字段 */ + useEffect(() => { + const newType = formData.jumpType; + if (newType !== 'event_detail') formData.relatedEventId = ''; + if (newType !== 'miniprogram_page') formData.pagePath = ''; + if (newType !== 'custom_link') formData.customUrl = ''; + const fieldsToValidate: string[] = []; + if (newType === 'event_detail') fieldsToValidate.push('relatedEventId'); + if (newType === 'miniprogram_page') fieldsToValidate.push('pagePath'); + if (newType === 'custom_link') fieldsToValidate.push('customUrl'); + if (fieldsToValidate.length) formRef.value?.clearValidate(fieldsToValidate); + }, [() => formData.jumpType]); + + /** 触发隐藏的文件选择器 */ + const triggerFilePicker = () => { + if (formData.imageStatus === 'uploading') return; + fileInputRef.value?.click(); + }; + + /** 处理文件选择 → 打开裁剪弹窗 */ + const handleFileChange = (e: Event) => { + const target = e.target as HTMLInputElement; + const file = target.files?.[0]; + // 重置 input 以便同一文件可再次选择 + target.value = ''; + + if (!file) return; + if (!/^image\//.test(file.type)) { + message.error('请选择图片文件'); + return; + } + if (file.size > IMAGE_MAX_SIZE) { + message.error(`图片大小不能超过 ${IMAGE_MAX_SIZE / 1024 / 1024}MB`); + return; + } + + // 生成本地预览 URL,打开裁剪弹窗 + uploadImageUrl.value = URL.createObjectURL(file); + cropperVisible.value = true; + }; + + /** 裁剪确认回调:将裁剪后的图片上传到 OSS */ + const handleCropConfirm = async (dataUrl: string) => { + cropperVisible.value = false; + + // 释放旧的本地预览 + if (formData.imageUrl && formData.imageUrl.startsWith('blob:')) { + URL.revokeObjectURL(formData.imageUrl); + } + + // 先用 base64 做本地预览 + const localUrl = dataUrl; + formData.imageUrl = localUrl; + formData.imageStatus = 'uploading'; + + try { + const file = base64ToFile(dataUrl, `banner_${Date.now()}.jpg`); + const result = await uploadFile(file, OssUploadType.Banner as any); + + // 释放本地预览 + URL.revokeObjectURL(localUrl); + formData.imageUrl = result.url; + formData.imageStatus = 'success'; + message.success('图片上传成功'); + } catch (err: any) { + console.error('[Banner] 上传失败:', err); + URL.revokeObjectURL(localUrl); + formData.imageUrl = ''; + formData.imageStatus = 'error'; + message.error(err?.message || '图片上传失败'); + } + + // 清理裁剪资源 + if (uploadImageUrl.value) { + URL.revokeObjectURL(uploadImageUrl.value); + } + uploadImageUrl.value = ''; + }; + + /** 裁剪弹窗取消 */ + const onCropperCancel = () => { + cropperVisible.value = false; + if (uploadImageUrl.value) { + URL.revokeObjectURL(uploadImageUrl.value); + } + uploadImageUrl.value = ''; + }; + + /** 删除已上传图片 */ + const handleRemoveImage = () => { + if (formData.imageUrl && formData.imageUrl.startsWith('blob:')) { + URL.revokeObjectURL(formData.imageUrl); + } + formData.imageUrl = ''; + formData.imageStatus = 'empty'; + }; + + /** 提交表单 */ + const handleSubmit = async () => { + try { + await formRef.value?.validate(); + } catch { + return; + } + + // 防御:上传中时禁止提交 + if (formData.imageStatus === 'uploading') { + message.warning('图片仍在上传中,请稍候'); + return; + } + + // 构造提交数据:根据 jumpType 整理 jumpUrl + const payload: any = { ...formData }; + if (payload.jumpType === 'event_detail') { + payload.jumpUrl = payload.relatedEventId; + } else if (payload.jumpType === 'miniprogram_page') { + payload.jumpUrl = payload.pagePath; + } else if (payload.jumpType === 'custom_link') { + payload.jumpUrl = payload.customUrl; + } else { + payload.jumpUrl = ''; + } + payload.displayStartTime = payload.displayTimeRange?.[0] || ''; + payload.displayEndTime = payload.displayTimeRange?.[1] || ''; + props.onSave(payload); + }; + + return () => ( + <> + +
+ {/* ===== Banner 标题 ===== */} + + + + + {/* ===== 上传图片(必填,带裁剪) ===== */} + { + if (!formData.imageUrl || formData.imageUrl.startsWith('blob:')) { + return Promise.reject(new Error('请上传 Banner 图片')); + } + return Promise.resolve(); + }, + }, + ]} + > +
+ + {formData.imageStatus === 'empty' || formData.imageStatus === 'error' ? ( +
+ + + 点击上传 + + 建议尺寸 1053*351 像素 +
+ 支持 JPG/PNG,最大 10MB +
+
+ ) : ( +
+ + banner preview + +
+ 重新上传 +
+
+ )} +
+
+ + {/* ===== 跳转类型 ===== */} + + + + )} + + {formData.jumpType === 'miniprogram_page' && ( + + + + )} + + {formData.jumpType === 'custom_link' && ( + + + + )} + + {formData.jumpType === 'none' && ( +
选择"无链接"时,点击 Banner 不会跳转。
+ )} + + {/* ===== 所属区域 ===== */} + + + + +
+ {{ + bodyCell: (args: any) => + renderBodyCell({ + ...args, + renderJumpType, + renderStatus, + onEdit: handleEdit, + onToggleStatus: confirmToggleStatus, + onDelete: confirmDelete, + }), + }} +
+
+ + {/* 独立分页,右下方 */} +
+ `共 ${total} 条`} + onChange={handlePageChange} + onShowSizeChange={handlePageChange} + /> +
+ + + {/* ===== 新增/编辑弹窗 ===== */} + {modalVisible.value && ( + + )} + + ); + }, +}); diff --git a/src/pages/events/banner/model/useBannerModel.ts b/src/pages/events/banner/model/useBannerModel.ts new file mode 100644 index 0000000..1cbab9f --- /dev/null +++ b/src/pages/events/banner/model/useBannerModel.ts @@ -0,0 +1,323 @@ +import { computed, reactive, toRef, Ref, h } from 'vue'; +import { message, Tag } from 'ant-design-vue'; +import { useState, useDebounce, useThrottleFn } from '@/hooks'; + +// ============================================================ +// 常量 +// ============================================================ + +/** Banner 状态选项(筛选) */ +export const BANNER_STATUS_OPTIONS = [ + { value: '', label: '全部' }, + { value: 'enabled', label: '启用' }, + { value: 'disabled', label: '禁用' }, +] as const; + +/** 跳转类型选项(表单下拉) */ +export const JUMP_TYPE_OPTIONS = [ + { value: 'event_detail', label: '赛事详情页' }, + { value: 'miniprogram_page', label: '小程序页面' }, + { value: 'custom_link', label: '自定义链接' }, + { value: 'none', label: '无链接' }, +] as const; + +/** 跳转类型映射(用于表格 Tag 渲染) */ +const JUMP_TYPE_MAP: Record = { + event_detail: { label: '赛事详情页', color: 'blue' }, + miniprogram_page: { label: '小程序页面', color: 'cyan' }, + custom_link: { label: '自定义链接', color: 'purple' }, + none: { label: '无链接', color: 'default' }, +}; + +/** Banner 状态映射 */ +const STATUS_MAP: Record = { + enabled: { label: '启用中', color: 'green' }, + disabled: { label: '已禁用', color: 'default' }, +}; + +/** 关联赛事选项(mock) */ +export const EVENT_OPTIONS = [ + { value: '', label: '不关联' }, + { value: 'EV20260701001', label: '2026全国青少年羽毛球锦标赛暨体育文化交流大会' }, + { value: 'EV20260701002', label: '国际马拉松城市联赛' }, + { value: 'EV20260701003', label: '全民健身运动会' }, + { value: 'EV20260701004', label: '上海国际马拉松公开赛' }, + { value: 'EV20260701005', label: '北京城市定向越野挑战赛' }, +]; + +/** 所属区域选项(mock) */ +export const REGION_OPTIONS = [ + { value: 'home', label: '首页' }, + { value: 'guangdong_shenzhen', label: '广东省深圳市' }, + { value: 'zhejiang_hangzhou', label: '浙江省杭州市' }, + { value: 'beijing', label: '北京市' }, + { value: 'shanghai', label: '上海市' }, +]; + +// ============================================================ +// 假数据(表格) +// ============================================================ + +const MOCK_DATA = Array.from({ length: 12 }, (_, i) => { + const jumpType = (['event_detail', 'miniprogram_page', 'custom_link', 'none'] as const)[i % 4]; + const provinces = ['广东省', '浙江省', '北京市', '上海市']; + const cities = ['深圳市', '杭州市', '北京市', '上海市']; + + return { + key: `${i + 1}`, + id: `BANNER${String(i + 1).padStart(4, '0')}`, + sort: i + 1, + imageUrl: `https://picsum.photos/seed/banner${i + 1}/80/44`, + title: + i % 4 === 0 + ? '2026全国青少年羽毛球锦标赛火热报名中' + : i % 4 === 1 + ? '马拉松城市联赛限时优惠' + : i % 4 === 2 + ? '健身运动会专属福利' + : '平台品牌宣传活动', + jumpType, + jumpUrl: + jumpType === 'custom_link' + ? 'https://example.com/activity' + : jumpType === 'miniprogram_page' + ? '/pages/activity/detail' + : '', + relatedEventId: + jumpType === 'event_detail' + ? (['EV20260701001', 'EV20260701002', 'EV20260701003'] as const)[i % 3] + : '', + relatedEvent: + jumpType === 'event_detail' + ? (() => { + const map: Record = { + EV20260701001: '2026全国青少年羽毛球锦标赛暨体育文化交流大会', + EV20260701002: '国际马拉松城市联赛', + EV20260701003: '全民健身运动会', + }; + return map[(['EV20260701001', 'EV20260701002', 'EV20260701003'] as const)[i % 3]] || ''; + })() + : '', + region: provinces[i % 4], + city: cities[i % 4], + displayStartTime: '2026-08-01 00:00', + displayEndTime: '2026-08-31 23:59', + createTime: `2026-07-${String((i % 28) + 1).padStart(2, '0')} ${String((i * 2 + 8) % 24).padStart(2, '0')}:${String((i * 7) % 60).padStart(2, '0')}`, + status: (i % 4 === 0 ? 'disabled' : 'enabled') as 'enabled' | 'disabled', + }; +}); + +// ============================================================ +// Model +// ============================================================ + +/** + * Banner 配置页数据模型 + */ +export function useBannerModel() { + // ===== 筛选条件 ===== + const filterForm = reactive({ + searchTitle: '', + status: '' as '' | 'enabled' | 'disabled', + }); + + // 防抖 300ms + const { debouncedValue: debouncedTitle } = useDebounce( + toRef(filterForm, 'searchTitle') as Ref, + { delay: 300 }, + ); + + // ===== 表格状态 ===== + const [loading, setLoading] = useState(false); + const [dataSource, setDataSource] = useState(MOCK_DATA); + const [pagination, setPagination] = useState({ + current: 1, + pageSize: 10, + total: MOCK_DATA.length, + }); + + // ===== 弹窗状态 ===== + const [modalVisible, setModalVisible] = useState(false); + const [editingRecord, setEditingRecord] = useState(null); + + // ===== 提交态(用于表单弹窗的确认按钮 loading)===== + const [formSubmitting, setFormSubmitting] = useState(false); + + // ===== 表格列配置(纯数据列,复杂渲染在页面 bodyCell 中处理)===== + const columns = [ + { title: '排序', dataIndex: 'sort', key: 'sort', width: 70, align: 'center' as const }, + { title: '图片', key: 'image', width: 100, align: 'center' as const }, + { title: '标题', dataIndex: 'title', key: 'title', width: 220 }, + { + title: '跳转类型', + dataIndex: 'jumpType', + key: 'jumpType', + width: 120, + align: 'center' as const, + }, + { title: '关联赛事', dataIndex: 'relatedEvent', key: 'relatedEvent', width: 200 }, + { title: '所属区域', key: 'region', width: 140 }, + { title: '展示时间', key: 'displayTime', width: 260 }, + { title: '创建时间', dataIndex: 'createTime', key: 'createTime', width: 160 }, + { title: '状态', dataIndex: 'status', key: 'status', width: 100, align: 'center' as const }, + ]; + + // ===== 计算属性 ===== + const hasFilter = computed(() => { + return debouncedTitle.value.trim() !== '' || filterForm.status !== ''; + }); + + const isEdit = computed(() => editingRecord.value !== null); + + // ===== 方法 ===== + + /** 查询(节流 500ms) */ + const handleSearch = useThrottleFn(async () => { + setLoading(true); + try { + console.log('搜索条件:', { + title: debouncedTitle.value, + status: filterForm.status, + }); + // TODO: 替换为真实 API 调用 + setDataSource(MOCK_DATA); + setPagination({ ...pagination.value, total: MOCK_DATA.length }); + message.success('查询成功'); + } catch (error: any) { + message.error(error.msg || '查询失败'); + } finally { + setLoading(false); + } + }, 500); + + /** 重置(节流 500ms) */ + const handleReset = useThrottleFn(() => { + filterForm.searchTitle = ''; + filterForm.status = ''; + setPagination({ current: 1, pageSize: 10, total: MOCK_DATA.length }); + setDataSource(MOCK_DATA); + }, 500); + + const handlePageChange = (page: number, pageSize: number) => { + setPagination({ ...pagination.value, current: page, pageSize }); + handleSearch(); + }; + + /** 打开新增弹窗 */ + const handleAdd = () => { + setEditingRecord(null); + setModalVisible(true); + }; + + /** 打开编辑弹窗 */ + const handleEdit = (record: any) => { + setEditingRecord(record); + setModalVisible(true); + }; + + /** 关闭弹窗 */ + const handleCloseModal = () => { + setModalVisible(false); + setEditingRecord(null); + }; + + /** 提交表单(新增/编辑) */ + const handleSave = useThrottleFn(async (formPayload: any) => { + if (formSubmitting.value) return; + setFormSubmitting(true); + try { + const payload = { + ...formPayload, + displayStartTime: formPayload.displayTimeRange?.[0] || '', + displayEndTime: formPayload.displayTimeRange?.[1] || '', + }; + console.log(isEdit.value ? '编辑 Banner' : '新增 Banner', payload); + // TODO: 替换为真实 API 调用 + message.success(isEdit.value ? '编辑成功' : '新增成功'); + handleCloseModal(); + handleSearch(); + } catch (error: any) { + message.error(error.msg || '操作失败'); + } finally { + setFormSubmitting(false); + } + }, 500); + + /** 删除(节流 500ms) */ + const handleDelete = useThrottleFn(async (record: any) => { + setLoading(true); + try { + console.log('删除 Banner:', record.id); + // TODO: 替换为真实 API 调用 + message.success('删除成功'); + handleSearch(); + } catch (error: any) { + message.error(error.msg || '删除失败'); + } finally { + setLoading(false); + } + }, 500); + + /** 启用/禁用切换(节流 500ms) */ + const handleToggleStatus = useThrottleFn(async (record: any) => { + setLoading(true); + try { + const newStatus = record.status === 'enabled' ? 'disabled' : 'enabled'; + const actionText = newStatus === 'enabled' ? '启用' : '禁用'; + console.log(`${actionText} Banner:`, record.id); + // TODO: 替换为真实 API 调用 + // 更新本地数据 + setDataSource( + dataSource.value.map((item: any) => + item.key === record.key ? { ...item, status: newStatus } : item, + ), + ); + message.success(`${actionText}成功`); + } catch (error: any) { + message.error(error.msg || '操作失败'); + } finally { + setLoading(false); + } + }, 500); + + // ===== 供页面 bodyCell 使用的渲染工具 ===== + const renderJumpType = (type: string) => { + const info = JUMP_TYPE_MAP[type] || { label: type || '-', color: 'default' }; + return h(Tag, { color: info.color }, () => info.label); + }; + + const renderStatus = (status: string) => { + const info = STATUS_MAP[status] || { label: status || '-', color: 'default' }; + const tagProps: any = { color: info.color }; + // 已禁用状态添加半透明样式 + if (status === 'disabled') { + tagProps.style = { opacity: 0.5 }; + } + return h(Tag, tagProps, () => info.label); + }; + + return { + filterForm, + loading, + dataSource, + columns, + pagination, + hasFilter, + modalVisible, + setModalVisible, + editingRecord, + isEdit, + formSubmitting, + renderJumpType, + renderStatus, + handleSearch, + handleReset, + handlePageChange, + handleAdd, + handleEdit, + handleCloseModal, + handleSave, + handleDelete, + handleToggleStatus, + }; +} diff --git a/src/pages/events/list/components/EventDetailModal.module.less b/src/pages/events/list/components/EventDetailModal.module.less new file mode 100644 index 0000000..d5e26b7 --- /dev/null +++ b/src/pages/events/list/components/EventDetailModal.module.less @@ -0,0 +1,67 @@ +.section { + margin-bottom: 24px; +} + +.section:last-child { + margin-bottom: 0; +} + +.sectionTitle { + font-size: 14px; + font-weight: 600; + color: rgba(0, 0, 0, 0.85); + margin-bottom: 12px; + padding-left: 8px; + border-left: 3px solid #1677ff; +} + +.textBlock { + font-size: 13px; + line-height: 1.8; + color: rgba(0, 0, 0, 0.65); + white-space: pre-wrap; + background: #fafafa; + padding: 12px 16px; + border-radius: 4px; + border: 1px solid #f0f0f0; +} + +// Descriptions 列宽稍微压缩一些,避免单标签被挤 +.desc :global(.ant-descriptions-item-label) { + width: 110px; + color: rgba(0, 0, 0, 0.65); +} + +// 组别信息下的子表格 +.group { + margin-bottom: 16px; +} + +.group:last-child { + margin-bottom: 0; +} + +.groupTitle { + font-size: 13px; + color: rgba(0, 0, 0, 0.85); + margin-bottom: 8px; + font-weight: 500; +} + +.paginationRow { + display: flex; + justify-content: flex-end; + margin-top: 8px; +} + +// 分组信息列表 +.divisionList { + display: flex; + flex-direction: column; + gap: 6px; +} + +.divisionItem { + font-size: 13px; + color: rgba(0, 0, 0, 0.65); +} diff --git a/src/pages/events/list/components/EventDetailModal.tsx b/src/pages/events/list/components/EventDetailModal.tsx new file mode 100644 index 0000000..279a8a6 --- /dev/null +++ b/src/pages/events/list/components/EventDetailModal.tsx @@ -0,0 +1,208 @@ +import { defineComponent } from 'vue'; +import { Modal, Descriptions, Image, Table, Pagination, Space } from 'ant-design-vue'; +import styles from './EventDetailModal.module.less'; + +interface EventDetailModalProps { + visible: boolean; + record: any; + onClose: () => void; +} + +// ===== Mock 数据 ===== +const MOCK_DETAIL = { + name: '第二届全国地学羽毛球邀请赛', + eventTime: '2026.05.26 08:00 ~ 2026.05.31 22:00', + descr: + '赛事介绍赛事介绍赛事介绍赛事介绍赛事介绍赛事介绍赛事介绍赛事介绍赛事介绍赛事介绍赛事介绍赛事介绍赛事介绍赛事介绍赛事介绍赛事介绍赛事介绍赛事介绍赛事介绍赛事介绍赛事介绍赛事介绍赛事介绍。', + registerTime: '2026.05.24 08:00 ~ 2026.05.26 22:00', + cancelDeadline: '2026.05.25 22:00', + region: '北京市 北京市 东城区', + address: '李宁体育馆', + venueNo: '李宁体育馆', + publicEvent: '在首页展示赛事', + needIdCard: '无需提供', + needIdCardImage: '无需提供', + createTime: '2026.05.26 08:00', + creator: '张三', + status: '报名中', + enrolledCount: 100, + cancelledCount: 1, + maxCount: 1000, + crossedCount: 50, + completedCount: 50, + cover: 'https://picsum.photos/seed/eventcover/600/300', + announcement: + '赛事公告赛事公告赛事公告赛事公告赛事公告赛事公告赛事公告赛事公告赛事公告赛事公告赛事公告赛事公告赛事公告赛事公告赛事公告赛事公告赛事公告赛事公告赛事公告赛事公告赛事公告赛事公告赛事公告赛事公告赛事公告赛事公告赛事公告赛事公告赛事公告赛事公告。', +}; + +// 假组别数据 +const MOCK_GROUPS = [ + { + key: 'group1', + title: '组别1:男子单打(单打)', + players: [ + { + key: '1', + name: '张三', + gender: '男', + phone: '12345678995', + idNo: 'xxxxxxxxxxxxxxxx', + idImg: '', + }, + { key: '2', name: '李四', gender: '男', phone: '', idNo: '', idImg: '' }, + ], + }, + { + key: 'group2', + title: '组别2:女子双打(双打)', + players: [ + { + key: '1', + name: '张三', + gender: '女', + phone: '12345678995', + idNo: 'xxxxxxxxxxxxxxxx', + idImg: '', + }, + { key: '2', name: '李四', gender: '女', phone: '', idNo: '', idImg: '' }, + ], + }, +]; + +// 假分组数据 +const MOCK_DIVISIONS = ['分组1:男子小组1(循环赛)', '分组2:女子小组1(淘汰赛)']; + +const PLAYER_COLUMNS = [ + { title: '选手', dataIndex: 'name', key: 'name', width: 100 }, + { title: '性别', dataIndex: 'gender', key: 'gender', width: 80 }, + { title: '手机号', dataIndex: 'phone', key: 'phone', width: 150 }, + { title: '证件号', dataIndex: 'idNo', key: 'idNo', width: 180 }, + { + title: '证件图片', + dataIndex: 'idImg', + key: 'idImg', + width: 180, + customRender: () => ( + + + + + ), + }, +]; + +export default defineComponent({ + name: 'EventDetailModal', + props: { + visible: { type: Boolean, default: false }, + record: { type: Object, default: () => ({}) }, + onClose: { type: Function, required: true }, + }, + setup(props: EventDetailModalProps) { + const detail = MOCK_DETAIL; + + return () => ( + + {/* ===== 基础信息 ===== */} +
+
基础信息
+ + {/* 第一行:赛事名称(1 列) + 赛事时间(占 2 列) */} + {detail.name} + + {detail.eventTime} + + + {/* 第二行:赛事说明(占 3 列,独占整行) */} + + {detail.descr} + + + {detail.registerTime} + + {detail.cancelDeadline} + + + {detail.region} + {detail.address} + {detail.venueNo} + + {detail.publicEvent} + {detail.needIdCard} + {detail.needIdCardImage} + + {detail.createTime} + {detail.creator} + {detail.status} + {detail.enrolledCount} + + {detail.cancelledCount} + + + {detail.maxCount} + {detail.crossedCount} + {detail.completedCount} + +
+ + {/* ===== 封面 ===== */} +
+
封面
+ +
+ + {/* ===== 赛事公告 ===== */} +
+
赛事公告
+
{detail.announcement}
+
+ + {/* ===== 组别信息 ===== */} +
+
组别信息
+ {MOCK_GROUPS.map((group) => ( +
+
{group.title}
+ +
+ +
+ + ))} + + + {/* ===== 分组信息 ===== */} +
+
分组信息
+
+ {MOCK_DIVISIONS.map((d) => ( +
+ {d} +
+ ))} +
+
+ + ); + }, +}); diff --git a/src/pages/events/list/components/EventRegulationModal.module.less b/src/pages/events/list/components/EventRegulationModal.module.less new file mode 100644 index 0000000..c86aad7 --- /dev/null +++ b/src/pages/events/list/components/EventRegulationModal.module.less @@ -0,0 +1,51 @@ +.section { + margin-bottom: 20px; +} + +.section:last-child { + margin-bottom: 0; +} + +.sectionTitle { + font-size: 14px; + font-weight: 600; + color: rgba(0, 0, 0, 0.85); + margin-bottom: 10px; + padding-left: 8px; + border-left: 3px solid #1677ff; +} + +.textBlock { + font-size: 13px; + line-height: 1.5; + color: rgba(0, 0, 0, 0.65); + white-space: pre-wrap; + background: #fafafa; + padding: 12px 16px; + border-radius: 4px; + border: 1px solid #f0f0f0; +} + +.imageGrid { + display: grid; + grid-template-columns: repeat(4, 1fr); + gap: 12px; +} + +.imageCell { + width: 100%; + aspect-ratio: 3 / 2; + background: #f5f5f5; + border-radius: 4px; + overflow: hidden; + border: 1px solid #f0f0f0; + display: flex; + align-items: center; + justify-content: center; +} + +.imageCell :global(.ant-image) { + width: 100%; + height: 100%; + object-fit: cover; +} diff --git a/src/pages/events/list/components/EventRegulationModal.tsx b/src/pages/events/list/components/EventRegulationModal.tsx new file mode 100644 index 0000000..aa15084 --- /dev/null +++ b/src/pages/events/list/components/EventRegulationModal.tsx @@ -0,0 +1,66 @@ +import { defineComponent } from 'vue'; +import { Modal, Image } from 'ant-design-vue'; +import styles from './EventRegulationModal.module.less'; + +interface EventRegulationModalProps { + visible: boolean; + onClose: () => void; +} + +/** 规程文字 */ +const REGULATION_TEXT = [ + '一、赛事目的:推动羽毛球运动发展,提高运动员技术水平,增进体育文化交流。', + '二、参赛资格:凡身体健康、年龄符合要求的羽毛球爱好者均可报名参加。', + '三、比赛规则:采用中国羽毛球协会审定的最新《羽毛球竞赛规则》。', + '四、赛制安排:比赛分小组赛和淘汰赛两个阶段,小组赛采用单循环,淘汰赛采用单败淘汰。', + '五、器材要求:参赛运动员自备球拍,比赛用球由组委会统一提供。', + '六、裁判设置:每场比赛设主裁判1名、副裁判2名,确保比赛公平公正。', +].join('\n\n'); + +/** 规程图片(mock:8 张示例图,4 列 grid) */ +const REGULATION_IMAGES = Array.from({ length: 8 }, (_, i) => ({ + id: i + 1, + src: `https://picsum.photos/seed/reg${i + 1}/300/200`, + alt: `规程图片 ${i + 1}`, +})); + +export default defineComponent({ + name: 'EventRegulationModal', + props: { + visible: { type: Boolean, default: false }, + onClose: { type: Function, required: true }, + }, + setup(props: EventRegulationModalProps) { + return () => ( + + {/* 规程文字 */} +
+
规程文字
+
{REGULATION_TEXT}
+
+ + {/* 规程图片:4 列 grid,点击可浏览 */} +
+
规程图片
+ +
+ {REGULATION_IMAGES.map((img) => ( +
+ +
+ ))} +
+
+
+
+ ); + }, +}); diff --git a/src/pages/events/list/index.module.less b/src/pages/events/list/index.module.less new file mode 100644 index 0000000..9974dc5 --- /dev/null +++ b/src/pages/events/list/index.module.less @@ -0,0 +1,18 @@ +/** + * 表格 body 容器:占满 flex 剩余空间,并确保 antd 嵌套 div 逐层传递 height: 100%, + * 使得 Table scroll.y = 'calc(100% - 55px)' 中的 100% 能正确解析。 + */ +.tableBody { + flex: 1; + min-height: 0; + overflow: hidden; + + :global { + .ant-spin-nested-loading, + .ant-spin-container, + .ant-table, + .ant-table-container { + height: 100%; + } + } +} diff --git a/src/pages/events/list/index.tsx b/src/pages/events/list/index.tsx new file mode 100644 index 0000000..974b069 --- /dev/null +++ b/src/pages/events/list/index.tsx @@ -0,0 +1,304 @@ +import { defineComponent } from 'vue'; +import { + Button, + Input, + Table, + Form, + Space, + Select, + DatePicker, + Cascader, + Tooltip, + Pagination, + Modal, +} from 'ant-design-vue'; +import { + useEventListModel, + EVENT_STATUS_OPTIONS, + PUBLIC_EVENT_OPTIONS, + ID_CARD_OPTIONS, + ID_CARD_IMAGE_OPTIONS, +} from './model/useEventListModel'; +import { regionOptions } from '@/utils/areaData'; +import { useContainerSize, useState } from '@/hooks'; +import EventRegulationModal from './components/EventRegulationModal'; +import EventDetailModal from './components/EventDetailModal'; +import styles from './index.module.less'; + +const { RangePicker } = DatePicker; + +/** + * bodyCell 渲染函数 + */ +function renderBodyCell({ + column, + text, + record, + onView, + onRemove, + onViewRegulation, +}: { + column: any; + text: any; + record: any; + onView: (record: any) => void; + onRemove: (record: any) => void; + onViewRegulation: (record: any) => void; +}) { + if (column.dataIndex === 'eventName') { + const maxLen = 15; + const raw = text || ''; + const display = raw.length > maxLen ? raw.slice(0, maxLen) + '...' : raw; + return raw.length > maxLen ? ( + + {display} + + ) : ( + {display} + ); + } + + if (column.key === 'regulation') { + return ( + onViewRegulation(record)}> + 查看 + + ); + } + + if (column.key === 'action') { + const isRemoved = record.status === 'removed'; + return ( + + + + + ); + } + + return; +} + +/** + * 赛事列表 + */ +export default defineComponent({ + name: 'EventList', + setup() { + const { + filterForm, + loading, + dataSource, + columns, + pagination, + handleSearch, + handleReset, + handlePageChange, + } = useEventListModel(); + + const { containerRef, height } = useContainerSize(); + + // 弹窗状态 + const [regulationModalVisible, setRegulationModalVisible] = useState(false); + const [detailModalVisible, setDetailModalVisible] = useState(false); + const [currentRecord, setCurrentRecord] = useState({}); + + const handleView = (record: any) => { + console.log('查看赛事:', record.eventId); + setCurrentRecord(record); + setDetailModalVisible(true); + }; + + const handleRemove = (record: any) => { + const isRemoved = record.status === 'removed'; + const actionText = isRemoved ? '上架' : '下架'; + Modal.confirm({ + title: `${actionText}确认`, + content: `确认${actionText}"${record.eventName}"该赛事吗,下架后用户将无法搜索和报名该赛事,已有报名和订单不受影响。`, + okText: '确认', + cancelText: '取消', + onOk: () => { + console.log(`${actionText}赛事:`, record.eventId); + // TODO: 替换为真实 API 调用 + }, + }); + }; + + const handleViewRegulation = (record: any) => { + console.log('查看规程:', record.eventId); + setCurrentRecord(record); + setRegulationModalVisible(true); + }; + + /** 最终表格列:模型列 + 赛事规程 + 操作 */ + const tableColumns = [ + ...columns, + { title: '赛事规程', key: 'regulation', width: 100, align: 'center' as const }, + { + title: '操作', + key: 'action', + width: 160, + fixed: 'right' as const, + align: 'center' as const, + }, + ]; + + return () => ( +
+ {/* ===== 筛选区 ===== */} +
+ + + (filterForm.createTimeRange = val)} + /> + + + (filterForm.eventTimeRange = val)} + /> + + + + + + + + + (filterForm.publicEvent = val || '')} + /> + + + (filterForm.needIdCardImage = val || '')} + /> + + + (filterForm.region = val || [])} + /> + + + + + + + + +
+ + {/* ===== 表格区 ===== */} +
+
+
+ {{ + bodyCell: (args: any) => + renderBodyCell({ + ...args, + onView: handleView, + onRemove: handleRemove, + onViewRegulation: handleViewRegulation, + }), + }} +
+
+ + {/* 独立分页,右下方 */} +
+ `共 ${total} 条`} + onChange={handlePageChange} + onShowSizeChange={handlePageChange} + /> +
+
+ + {/* ===== 弹窗区 ===== */} + {regulationModalVisible.value && ( + setRegulationModalVisible(false)} + /> + )} + {detailModalVisible.value && ( + setDetailModalVisible(false)} + /> + )} + + ); + }, +}); diff --git a/src/pages/events/list/model/useEventListModel.ts b/src/pages/events/list/model/useEventListModel.ts new file mode 100644 index 0000000..2eedb0e --- /dev/null +++ b/src/pages/events/list/model/useEventListModel.ts @@ -0,0 +1,255 @@ +import { computed, reactive, toRef, Ref, h } from 'vue'; +import { message, Tag } from 'ant-design-vue'; +import { useState, useDebounce, useThrottleFn } from '@/hooks'; + +// ============================================================ +// 常量 +// ============================================================ + +/** 赛事状态选项 */ +export const EVENT_STATUS_OPTIONS = [ + { value: '', label: '全部' }, + { value: 'enrolling', label: '报名中' }, + { value: 'ongoing', label: '进行中' }, + { value: 'finished', label: '已结束' }, + { value: 'deleted', label: '已删除' }, + { value: 'removed', label: '已下架' }, +] as const; + +/** 公开活动选项 */ +export const PUBLIC_EVENT_OPTIONS = [ + { value: '', label: '全部' }, + { value: 'show', label: '在首页展示赛事' }, + { value: 'hide', label: '不在首页展示赛事' }, +] as const; + +/** 证件号选项 */ +export const ID_CARD_OPTIONS = [ + { value: '', label: '全部' }, + { value: 'no', label: '无需提供' }, + { value: 'yes', label: '需提供' }, +] as const; + +/** 证件图片选项 */ +export const ID_CARD_IMAGE_OPTIONS = [ + { value: '', label: '全部' }, + { value: 'no', label: '无需提供' }, + { value: 'yes', label: '需提供' }, +] as const; + +// ============================================================ +// 假数据 +// ============================================================ + +const MOCK_DATA = Array.from({ length: 12 }, (_, i) => ({ + key: `${i + 1}`, + eventId: `EV2026070100${String(i + 1).padStart(2, '0')}`, + eventName: + i % 3 === 0 + ? '2026全国青少年羽毛球锦标赛暨体育文化交流大会' + : i % 3 === 1 + ? '国际马拉松城市联赛' + : '全民健身运动会', + eventTimeStart: '2026-08-12 09:00', + eventTimeEnd: '2026-08-15 18:00', + province: '广东省', + city: '深圳市', + district: '南山区', + address: '深圳湾体育中心', + needIdCard: i % 2 === 0 ? '是' : '否', + needIdCardImage: i % 2 === 0 ? '是' : '否', + creatorNickname: ['张运营', '李管理', '王策划', '赵赛事'][i % 4], + creatorPhone: ['13800138001', '13900139002', '13700137003', '13600136004'][i % 4], + createTime: '2026-07-01 14:30:00', + status: (['enrolling', 'ongoing', 'finished', 'deleted', 'removed'] as const)[i % 5], + enrolledCount: [128, 256, 512, 64, 0][i % 5], + cancelledCount: [5, 12, 8, 3, 0][i % 5], +})); + +/** 状态标签映射 */ +const STATUS_MAP: Record = { + enrolling: { label: '报名中', color: 'blue' }, + ongoing: { label: '进行中', color: 'green' }, + finished: { label: '已结束', color: 'default' }, + deleted: { label: '已删除', color: 'red' }, + removed: { label: '已下架', color: 'orange' }, +}; + +// ============================================================ +// Model +// ============================================================ + +/** + * 赛事列表页数据模型 + */ +export function useEventListModel() { + // ===== 筛选条件 ===== + const filterForm = reactive({ + createTimeRange: null as [string, string] | null, + eventTimeRange: null as [string, string] | null, + searchName: '', + searchCreator: '', + status: '', + region: [] as string[], + publicEvent: '', + needIdCard: '', + needIdCardImage: '', + }); + + // 可搜索字段防抖 + const { debouncedValue: debouncedName } = useDebounce( + toRef(filterForm, 'searchName') as Ref, + { delay: 300 }, + ); + const { debouncedValue: debouncedCreator } = useDebounce( + toRef(filterForm, 'searchCreator') as Ref, + { delay: 300 }, + ); + + // ===== 表格状态 ===== + const [loading, setLoading] = useState(false); + const [dataSource, setDataSource] = useState(MOCK_DATA); + const [pagination, setPagination] = useState({ + current: 1, + pageSize: 10, + total: MOCK_DATA.length, + }); + + // ===== 表格列配置 ===== + const columns = [ + { title: '赛事ID', dataIndex: 'eventId', key: 'eventId', width: 180 }, + { + title: '赛事名称', + dataIndex: 'eventName', + key: 'eventName', + width: 160, + }, + { + title: '赛事时间', + key: 'eventTime', + width: 260, + customRender: ({ record }: { record: any }) => + `${record.eventTimeStart} 至 ${record.eventTimeEnd}`, + }, + { + title: '省市区', + key: 'region', + width: 200, + customRender: ({ record }: { record: any }) => + `${record.province} ${record.city} ${record.district}`, + }, + { title: '地点', dataIndex: 'address', key: 'address', width: 180 }, + { + title: '是否提供证件号', + dataIndex: 'needIdCard', + key: 'needIdCard', + width: 160, + }, + { + title: '是否提供证件图片', + dataIndex: 'needIdCardImage', + key: 'needIdCardImage', + width: 160, + }, + { + title: '创建人昵称', + dataIndex: 'creatorNickname', + key: 'creatorNickname', + width: 110, + }, + { + title: '创建人手机号', + dataIndex: 'creatorPhone', + key: 'creatorPhone', + width: 130, + }, + { title: '创建时间', dataIndex: 'createTime', key: 'createTime', width: 170 }, + { + title: '状态', + dataIndex: 'status', + key: 'status', + width: 90, + customRender: ({ text }: { text: string }) => { + const info = STATUS_MAP[text] || { label: text, color: 'default' }; + return h(Tag, { color: info.color }, () => info.label); + }, + }, + { title: '报名人数', dataIndex: 'enrolledCount', key: 'enrolledCount', width: 90 }, + { title: '取消报名人数', dataIndex: 'cancelledCount', key: 'cancelledCount', width: 140 }, + ]; + + // ===== 计算属性 ===== + const hasFilter = computed(() => { + return ( + filterForm.createTimeRange !== null || + filterForm.eventTimeRange !== null || + debouncedName.value.trim() !== '' || + debouncedCreator.value.trim() !== '' || + filterForm.status !== '' || + filterForm.region.length > 0 || + filterForm.publicEvent !== '' || + filterForm.needIdCard !== '' || + filterForm.needIdCardImage !== '' + ); + }); + + // ===== 方法 ===== + + /** 查询 */ + const handleSearch = useThrottleFn(async () => { + setLoading(true); + try { + console.log('搜索条件:', { + createTimeRange: filterForm.createTimeRange, + eventTimeRange: filterForm.eventTimeRange, + name: debouncedName.value, + creator: debouncedCreator.value, + status: filterForm.status, + region: filterForm.region, + publicEvent: filterForm.publicEvent, + needIdCard: filterForm.needIdCard, + needIdCardImage: filterForm.needIdCardImage, + }); + // TODO: 替换为真实 API 调用 + setDataSource(MOCK_DATA); + setPagination({ ...pagination.value, total: MOCK_DATA.length }); + message.success('查询成功'); + } catch (error: any) { + message.error(error.msg || '查询失败'); + } finally { + setLoading(false); + } + }, 500); + + /** 重置 */ + const handleReset = useThrottleFn(() => { + filterForm.createTimeRange = null; + filterForm.eventTimeRange = null; + filterForm.searchName = ''; + filterForm.searchCreator = ''; + filterForm.status = ''; + filterForm.region = []; + filterForm.publicEvent = ''; + filterForm.needIdCard = ''; + filterForm.needIdCardImage = ''; + setPagination({ current: 1, pageSize: 10, total: MOCK_DATA.length }); + setDataSource(MOCK_DATA); + }, 500); + + const handlePageChange = (page: number, pageSize: number) => { + setPagination({ ...pagination.value, current: page, pageSize }); + handleSearch(); + }; + + return { + filterForm, + loading, + dataSource, + columns, + pagination, + hasFilter, + handleSearch, + handleReset, + handlePageChange, + }; +} diff --git a/src/pages/events/orders/components/OrderDetailModal.module.less b/src/pages/events/orders/components/OrderDetailModal.module.less new file mode 100644 index 0000000..02e1d7f --- /dev/null +++ b/src/pages/events/orders/components/OrderDetailModal.module.less @@ -0,0 +1,196 @@ +// ===== 自定义标题栏 ===== +.modalHeader { + display: flex; + justify-content: space-between; + align-items: center; + padding: 0 0 16px; + margin-bottom: 16px; + border-bottom: 1px solid #f0f0f0; +} + +.modalTitle { + font-size: 16px; + font-weight: 600; + color: rgba(0, 0, 0, 0.85); +} + +.closeBtn { + width: 28px; + height: 28px; + border: none; + background: transparent; + font-size: 22px; + line-height: 1; + color: rgba(0, 0, 0, 0.45); + cursor: pointer; + border-radius: 4px; + transition: all 0.2s; + + &:hover { + background: rgba(0, 0, 0, 0.04); + color: rgba(0, 0, 0, 0.85); + } +} + +// ===== 内容区 ===== +.modalBody { + padding-top: 0; +} + +// ===== 订单信息 grid ===== +.infoGrid { + display: grid; + grid-template-columns: repeat(3, 1fr); + row-gap: 12px; + column-gap: 24px; + font-size: 13px; + margin-bottom: 24px; + padding-bottom: 16px; + border-bottom: 1px solid #f0f0f0; +} + +.infoItem { + display: flex; + align-items: baseline; + min-width: 0; +} + +.infoLabel { + color: rgba(0, 0, 0, 0.65); + flex-shrink: 0; +} + +// ===== 通用 section ===== +.section { + margin-bottom: 24px; +} + +.section:last-child { + margin-bottom: 0; +} + +.sectionTitle { + font-size: 14px; + font-weight: 600; + color: rgba(0, 0, 0, 0.85); + margin-bottom: 12px; + padding-left: 8px; + border-left: 3px solid #1677ff; +} + +// ===== 退款记录 ===== +.refundHeader { + margin-bottom: 12px; +} + +.refundSummary { + display: flex; + gap: 32px; + font-size: 13px; + color: rgba(0, 0, 0, 0.85); + margin-bottom: 12px; +} + +/** 固定高度 + 滚动 */ +.refundList { + max-height: 320px; + overflow-y: auto; + padding-right: 4px; + + // 自定义滚动条样式 + &::-webkit-scrollbar { + width: 6px; + } + &::-webkit-scrollbar-thumb { + background: #d9d9d9; + border-radius: 3px; + } + &::-webkit-scrollbar-track { + background: transparent; + } +} + +.refundCard { + padding: 14px 16px; + margin-bottom: 12px; + background: #fafafa; + border: 1px solid #f0f0f0; + border-radius: 6px; +} + +.refundCard:last-child { + margin-bottom: 0; +} + +.refundCardHeader { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 12px; + padding-bottom: 10px; + border-bottom: 1px dashed #e0e0e0; +} + +.refundIndex { + font-size: 13px; + font-weight: 500; + color: rgba(0, 0, 0, 0.85); +} + +.refundCardBody { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 6px 24px; + font-size: 13px; + color: rgba(0, 0, 0, 0.65); + line-height: 1.8; +} + +.refundCol { + min-width: 0; +} + +.refundAmount { + color: rgba(0, 0, 0, 0.85); + font-weight: 500; +} + +.partialMark { + margin-left: 4px; + color: #fa8c16; + font-size: 12px; +} + +.refundEmpty { + padding: 32px 0; + text-align: center; + font-size: 13px; + color: rgba(0, 0, 0, 0.45); + background: #fafafa; + border-radius: 6px; + border: 1px dashed #e0e0e0; +} + +// 分页:右下角 +.refundPagination { + display: flex; + justify-content: flex-end; + margin-top: 12px; +} + +// ===== 底部按钮区 ===== +.modalFooter { + display: flex; + justify-content: flex-end; + gap: 12px; + padding-top: 16px; + margin-top: 8px; + border-top: 1px solid #f0f0f0; +} + +// ===== Modal 外层包裹:去除 antd 默认内边距,让自定义标题贴合 ===== +.orderModalWrap { + :global(.ant-modal-body) { + padding-top: 16px; + } +} diff --git a/src/pages/events/orders/components/OrderDetailModal.tsx b/src/pages/events/orders/components/OrderDetailModal.tsx new file mode 100644 index 0000000..40df2ae --- /dev/null +++ b/src/pages/events/orders/components/OrderDetailModal.tsx @@ -0,0 +1,270 @@ +import { defineComponent, ref, computed } from 'vue'; +import { Modal, Table, Button, Tag, Pagination, Image, Space } from 'ant-design-vue'; +import styles from './OrderDetailModal.module.less'; + +interface OrderDetailModalProps { + visible: boolean; + record: any; + onClose: () => void; + /** 触发"重新退款"二次确认对话框 */ + onReRefund: (record: any) => void; +} + +const PLAYER_COLUMNS = [ + { title: '真实姓名', dataIndex: 'name', key: 'name', width: 100, align: 'center' as const }, + { title: '性别', dataIndex: 'gender', key: 'gender', width: 80, align: 'center' as const }, + { title: '手机号', dataIndex: 'phone', key: 'phone', width: 130 }, + { title: '证件号', dataIndex: 'idNo', key: 'idNo', width: 180 }, + { + title: '证件图片', + dataIndex: 'idImg', + key: 'idImg', + width: 170, + customRender: ({ text }: { text: string[] }) => ( + + {(text || []).map((src) => ( + + ))} + + ), + }, + { + title: '比赛组别', + dataIndex: 'groupName', + key: 'groupName', + width: 110, + align: 'center' as const, + }, + { + title: '订单金额', + dataIndex: 'orderAmount', + key: 'orderAmount', + width: 100, + align: 'right' as const, + customRender: ({ text }: { text: number }) => `${(text || 0).toFixed(2)}`, + }, + { + title: '优惠金额', + dataIndex: 'discountAmount', + key: 'discountAmount', + width: 100, + align: 'right' as const, + customRender: ({ text }: { text: number }) => `${(text || 0).toFixed(2)}`, + }, + { + title: '实付金额', + dataIndex: 'paidAmount', + key: 'paidAmount', + width: 100, + align: 'right' as const, + customRender: ({ text }: { text: number }) => `${(text || 0).toFixed(2)}`, + }, + { + title: '退款金额', + dataIndex: 'refundAmount', + key: 'refundAmount', + width: 100, + align: 'right' as const, + customRender: ({ text }: { text: number }) => (text ? text.toFixed(2) : '-'), + }, +]; + +const REFUND_STATUS_MAP: Record = { + success: { label: '退款成功', color: 'green' }, + failed: { label: '退款失败', color: 'red' }, +}; + +export default defineComponent({ + name: 'OrderDetailModal', + props: { + visible: { type: Boolean, default: false }, + record: { type: Object, default: () => ({}) }, + onClose: { type: Function, required: true }, + onReRefund: { type: Function, required: true }, + }, + setup(props: OrderDetailModalProps) { + const refundPage = ref(1); + const refundPageSize = ref(2); + + /** 退款记录 */ + const refundRecords = computed(() => props.record?.refundRecords || []); + const hasRefund = computed(() => refundRecords.value.length > 0); + + /** 当前页展示的退款记录 */ + const pagedRefunds = computed(() => { + const start = (refundPage.value - 1) * refundPageSize.value; + return refundRecords.value.slice(start, start + refundPageSize.value); + }); + + /** 已退金额合计 */ + const totalRefunded = computed(() => + refundRecords.value + .filter((r) => r.status === 'success') + .reduce((sum, r) => sum + (r.amount || 0), 0), + ); + + const handleRefundPageChange = (page: number, pageSize: number) => { + refundPage.value = page; + refundPageSize.value = pageSize; + }; + + const handleConfirm = () => { + // 无退款信息时:仅关闭弹窗 + props.onClose(); + }; + + const handleReRefund = () => { + props.onReRefund(props.record); + }; + + return () => ( + + {/* ===== 自定义标题栏 ===== */} +
+ 订单信息 + +
+ +
+ {/* ===== 订单信息 ===== */} +
+
+ 赛事名称: + {props.record?.eventName} +
+
+ 订单编号: + {props.record?.orderId} +
+
+ 订单时间: + {props.record?.orderTime} +
+
+ 支付时间: + {props.record?.payTime} +
+
+ 订单金额: + {props.record?.totalAmount?.toFixed(2)}元 +
+
+ 优惠金额: + {(props.record?.discountAmount || 0).toFixed(2)}元 +
+
+ 实付金额: + {props.record?.paidAmount?.toFixed(2)}元 +
+
+ 退款金额: + {props.record?.refundAmount?.toFixed(2)}元 +
+
+ + {/* ===== 选手信息 ===== */} +
+
选手信息
+ + + + {/* ===== 退款记录 ===== */} +
+
+ 退款记录 +
+ {hasRefund.value ? ( + <> +
+ 已退金额:{totalRefunded.value.toFixed(2)}元 + 退款次数:{refundRecords.value.length}次 +
+ {/* 固定高度 + 内部滚动 */} +
+ {pagedRefunds.value.map((r, idx) => { + const statusInfo = REFUND_STATUS_MAP[r.status] || { + label: '-', + color: 'default', + }; + const realIdx = (refundPage.value - 1) * refundPageSize.value + idx + 1; + return ( +
+
+ 第{realIdx}次退款 + {statusInfo.label} +
+
+
+
+ 退款金额: + + ¥{r.amount.toFixed(2)} + {r.isPartial && ( + (部分退款) + )} + +
+
申请时间:{r.applyTime}
+
完款时间:{r.completeTime || '-'}
+
+
+
退款方式:{r.method}
+
第三方退款单号:{r.thirdPartyNo || '-'}
+
+
+
+ ); + })} +
+
+ +
+ + ) : ( +
暂无退款记录
+ )} +
+ + + {/* ===== 底部按钮 ===== */} +
+ + {hasRefund.value ? ( + + ) : ( + + )} +
+ + ); + }, +}); diff --git a/src/pages/events/orders/index.module.less b/src/pages/events/orders/index.module.less new file mode 100644 index 0000000..4c665ac --- /dev/null +++ b/src/pages/events/orders/index.module.less @@ -0,0 +1,37 @@ +/** + * 表格 body 容器:占满 flex 剩余空间,并确保 antd 嵌套 div 逐层传递 height: 100%, + * 使得 Table scroll.y = 'calc(100% - 55px)' 中的 100% 能正确解析。 + */ +.tableBody { + flex: 1; + min-height: 0; + overflow: hidden; + + :global { + .ant-spin-nested-loading, + .ant-spin-container, + .ant-table, + .ant-table-container { + height: 100%; + } + } +} + +.summaryBar { + flex-shrink: 0; + display: flex; + align-items: center; + gap: 20px; + padding: 12px 16px; + font-size: 14px; + color: #1a1a1a; + + .summaryItem { + font-weight: 500; + + .summaryLabel { + color: #666; + font-weight: 400; + } + } +} diff --git a/src/pages/events/orders/index.tsx b/src/pages/events/orders/index.tsx new file mode 100644 index 0000000..d860ff3 --- /dev/null +++ b/src/pages/events/orders/index.tsx @@ -0,0 +1,270 @@ +import { defineComponent } from 'vue'; +import { + Button, + Input, + Table, + Form, + Space, + Select, + DatePicker, + Tooltip, + Pagination, + Modal, +} from 'ant-design-vue'; +import { useOrderModel, ORDER_STATUS_OPTIONS, REFUND_STATUS_OPTIONS } from './model/useOrderModel'; +import { useContainerSize, useState } from '@/hooks'; +import OrderDetailModal from './components/OrderDetailModal'; +import styles from './index.module.less'; + +const { RangePicker } = DatePicker; + +/** + * 触发「重新退款」二次确认对话框 + * 表格中的重新退款按钮和详情弹窗中的重新退款按钮均调用此函数。 + */ +function promptReRefund(record: any) { + Modal.confirm({ + title: '确认要重新退款吗?', + content: ( +
+
退款金额:¥{(record.refundAmount || 0).toFixed(2)}
+
退款方式:原路退回(微信支付)
+
+ ), + okText: '确认', + cancelText: '取消', + onOk: () => { + console.log('重新退款:', record.orderId); + // TODO: 替换为真实 API 调用 + }, + }); +} + +/** + * bodyCell 渲染函数 + */ +function renderBodyCell({ + column, + text, + record, + onView, + onReRefund, +}: { + column: any; + text: any; + record: any; + onView: (record: any) => void; + onReRefund: (record: any) => void; +}) { + // 赛事名称:最多15字符,超出显示 Tooltip + if (column.dataIndex === 'eventName') { + const maxLen = 15; + const raw = text || ''; + const display = raw.length > maxLen ? raw.slice(0, maxLen) + '...' : raw; + return raw.length > maxLen ? ( + + {display} + + ) : ( + {display} + ); + } + + if (column.key === 'action') { + const showReRefund = record.refundStatus === 'refund_failed'; + return ( + + + {showReRefund && ( + + )} + + ); + } + + return; +} + +/** + * 订单管理 + */ +export default defineComponent({ + name: 'EventOrders', + setup() { + const { + filterForm, + loading, + dataSource, + columns, + summary, + pagination, + handleSearch, + handleReset, + handlePageChange, + } = useOrderModel(); + + const { containerRef, height } = useContainerSize(); + + // 详情弹窗状态 + const [detailVisible, setDetailVisible] = useState(false); + const [currentRecord, setCurrentRecord] = useState({}); + + const handleView = (record: any) => { + setCurrentRecord(record); + setDetailVisible(true); + }; + + const handleReRefund = (record: any) => { + promptReRefund(record); + }; + + /** 最终表格列:模型列 + 操作 */ + const tableColumns = [ + ...columns, + { + title: '操作', + key: 'action', + width: 160, + fixed: 'right' as const, + align: 'center' as const, + }, + ]; + + return () => ( +
+ {/* ===== 筛选区 ===== */} +
+
+ + (filterForm.orderTimeRange = val)} + /> + + + + + + + + + + + + (filterForm.refundStatus = val || '')} + /> + + + + + + + + +
+ + {/* ===== 表格区 ===== */} +
+ {/* 金额汇总 */} +
+ + 总订单金额(已支付): + {summary.value.totalOrderAmount.toFixed(2)}元 + + + 总实付金额: + {summary.value.totalPaidAmount.toFixed(2)}元 + + + 总退款金额: + {summary.value.totalRefundAmount.toFixed(2)}元 + +
+ +
+
+ {{ + bodyCell: (args: any) => + renderBodyCell({ + ...args, + onView: handleView, + onReRefund: handleReRefund, + }), + }} +
+
+ + {/* 独立分页,右下方 */} +
+ `共 ${total} 条`} + onChange={handlePageChange} + onShowSizeChange={handlePageChange} + /> +
+
+ + {/* ===== 详情弹窗 ===== */} + {detailVisible.value && ( + setDetailVisible(false)} + onReRefund={(record) => { + // 关闭详情弹窗,再弹出二次确认 + setDetailVisible(false); + promptReRefund(record); + }} + /> + )} + + ); + }, +}); diff --git a/src/pages/events/orders/model/useOrderModel.ts b/src/pages/events/orders/model/useOrderModel.ts new file mode 100644 index 0000000..8145fa7 --- /dev/null +++ b/src/pages/events/orders/model/useOrderModel.ts @@ -0,0 +1,343 @@ +import { computed, reactive, toRef, Ref, h } from 'vue'; +import { message, Tag } from 'ant-design-vue'; +import { useState, useDebounce, useThrottleFn } from '@/hooks'; + +// ============================================================ +// 常量 +// ============================================================ + +/** 订单状态选项 */ +export const ORDER_STATUS_OPTIONS = [ + { value: '', label: '全部' }, + { value: 'paid', label: '已支付' }, + { value: 'partial_refund', label: '部分退款' }, + { value: 'full_refund', label: '全部退款' }, + { value: 'closed', label: '已关闭' }, +] as const; + +/** 退款状态选项 */ +export const REFUND_STATUS_OPTIONS = [ + { value: '', label: '全部' }, + { value: 'refund_success', label: '退款成功' }, + { value: 'refund_failed', label: '退款失败' }, +] as const; + +/** 订单状态 Tag 映射 */ +const ORDER_STATUS_MAP: Record = { + paid: { label: '已支付', color: 'green' }, + partial_refund: { label: '部分退款', color: 'orange' }, + full_refund: { label: '全部退款', color: 'blue' }, + closed: { label: '已关闭', color: 'default' }, +}; + +/** 退款状态 Tag 映射 */ +const REFUND_STATUS_MAP: Record = { + refund_success: { label: '退款成功', color: 'green' }, + refund_failed: { label: '退款失败', color: 'red' }, +}; + +// ============================================================ +// 假数据 +// ============================================================ + +const MOCK_DATA = Array.from({ length: 12 }, (_, i) => { + const orderStatus = (['paid', 'paid', 'partial_refund', 'full_refund', 'closed'] as const)[i % 5]; + const refundStatus: string = + orderStatus === 'partial_refund' || orderStatus === 'full_refund' + ? (['refund_success', 'refund_success', 'refund_failed'] as const)[i % 3] + : ''; + + const totalAmount = [188.0, 256.5, 399.0, 128.0, 520.0][i % 5]; + const discountAmount = i % 3 === 0 ? 20.0 : i % 3 === 1 ? 10.0 : 0; + const refundAmount = + orderStatus === 'partial_refund' + ? 50.0 + : orderStatus === 'full_refund' + ? totalAmount - discountAmount + : 0; + + // ===== 选手信息(详情弹窗用) ===== + const playerNames = ['张三', '李四', '王五']; + const players = playerNames.slice(0, (i % 3) + 1).map((name, idx) => ({ + key: `${i + 1}-${idx}`, + name, + gender: idx === 1 ? '女' : '男', + phone: '12345678956', + idNo: 'xxxxxxxxxxxxxxxx', + idImg: [ + 'https://picsum.photos/seed/idimg-a/120/80', + 'https://picsum.photos/seed/idimg-b/120/80', + ], + groupName: ['男子单打', '女子双打', '混合双打'][idx], + orderAmount: [100.0, 50.0, 38.0][idx], + discountAmount: 0, + paidAmount: [100.0, 50.0, 38.0][idx], + refundAmount: idx === 1 && orderStatus === 'full_refund' ? 50.0 : 0, + })); + + // ===== 退款记录(详情弹窗用) ===== + const refundRecords: any[] = []; + if (orderStatus === 'partial_refund' || orderStatus === 'full_refund') { + if (i % 4 === 2) { + refundRecords.push( + { + key: `${i + 1}-r1`, + amount: 50.0, + isPartial: true, + applyTime: '2026-06-07 16:20', + completeTime: '2026-06-07 16:25', + method: '原路退回(微信支付)', + thirdPartyNo: '5030000789202606071625102846', + status: 'success', + }, + { + key: `${i + 1}-r2`, + amount: 49.0, + isPartial: false, + applyTime: '2026-06-08 10:00', + completeTime: '', + method: '原路退回(微信支付)', + thirdPartyNo: '', + status: 'failed', + }, + ); + } else if (i % 4 === 0) { + refundRecords.push({ + key: `${i + 1}-r1`, + amount: 30.0, + isPartial: true, + applyTime: '2026-06-05 09:30', + completeTime: '2026-06-05 09:35', + method: '原路退回(微信支付)', + thirdPartyNo: '5030000789202606050935000123', + status: 'success', + }); + } + } + + return { + key: `${i + 1}`, + orderId: `ORD202607${String(i + 1).padStart(5, '0')}`, + eventName: + i % 3 === 0 + ? '2026全国青少年羽毛球锦标赛暨体育文化交流大会' + : i % 3 === 1 + ? '国际马拉松城市联赛' + : '全民健身运动会', + playerName: ['张选手', '李选手', '王选手', '赵选手'][i % 4], + phone: ['13800138001', '13900139002', '13700137003', '13600136004'][i % 4], + groupName: ['男子青年组', '女子青年组', '男子中年组', '女子中年组'][i % 4], + registrant: ['张报名', '李报名', '王报名', '赵报名'][i % 4], + registrantPhone: ['13800138001', '13900139002', '13700137003', '13600136004'][i % 4], + registrantCount: [1, 2, 1, 3][i % 4], + orderTime: '2026-07-15 10:30:00', + payTime: '2026-07-15 10:32:15', + totalAmount, + discountAmount, + paidAmount: totalAmount - discountAmount, + refundAmount, + orderStatus, + refundStatus, + players, + refundRecords, + }; +}); + +// ============================================================ +// 金额汇总假数据 +// ============================================================ + +const MOCK_SUMMARY = { + totalOrderAmount: 113255.0, + totalPaidAmount: 113255.0, + totalRefundAmount: 1255.0, +}; + +// ============================================================ +// Model +// ============================================================ + +/** + * 订单列表页数据模型 + */ +export function useOrderModel() { + // ===== 筛选条件 ===== + const filterForm = reactive({ + orderTimeRange: null as [string, string] | null, + searchOrderId: '', + searchEventName: '', + searchPlayerName: '', + orderStatus: '', + refundStatus: '', + }); + + // 可搜索字段防抖 + const { debouncedValue: debouncedOrderId } = useDebounce( + toRef(filterForm, 'searchOrderId') as Ref, + { delay: 300 }, + ); + const { debouncedValue: debouncedEventName } = useDebounce( + toRef(filterForm, 'searchEventName') as Ref, + { delay: 300 }, + ); + const { debouncedValue: debouncedPlayerName } = useDebounce( + toRef(filterForm, 'searchPlayerName') as Ref, + { delay: 300 }, + ); + + // ===== 表格状态 ===== + const [loading, setLoading] = useState(false); + const [dataSource, setDataSource] = useState(MOCK_DATA); + const [pagination, setPagination] = useState({ + current: 1, + pageSize: 10, + total: MOCK_DATA.length, + }); + + // ===== 金额汇总 ===== + const [summary, setSummary] = useState(MOCK_SUMMARY); + + // ===== 表格列配置 ===== + const columns = [ + { title: '订单编号', dataIndex: 'orderId', key: 'orderId', width: 200 }, + { title: '赛事名称', dataIndex: 'eventName', key: 'eventName', width: 180 }, + { title: '选手名称', dataIndex: 'playerName', key: 'playerName', width: 100 }, + { title: '手机号', dataIndex: 'phone', key: 'phone', width: 130 }, + { title: '比赛组别', dataIndex: 'groupName', key: 'groupName', width: 120 }, + { title: '报名人', dataIndex: 'registrant', key: 'registrant', width: 100 }, + { title: '报名人手机号', dataIndex: 'registrantPhone', key: 'registrantPhone', width: 130 }, + { + title: '报名人数', + dataIndex: 'registrantCount', + key: 'registrantCount', + width: 90, + align: 'center' as const, + }, + { title: '订单时间', dataIndex: 'orderTime', key: 'orderTime', width: 170 }, + { title: '支付时间', dataIndex: 'payTime', key: 'payTime', width: 170 }, + { + title: '订单总金额', + dataIndex: 'totalAmount', + key: 'totalAmount', + width: 120, + align: 'right' as const, + customRender: ({ text }: { text: number }) => `${(text || 0).toFixed(2)}元`, + }, + { + title: '总优惠金额', + dataIndex: 'discountAmount', + key: 'discountAmount', + width: 120, + align: 'right' as const, + customRender: ({ text }: { text: number }) => `${(text || 0).toFixed(2)}元`, + }, + { + title: '实付总金额', + dataIndex: 'paidAmount', + key: 'paidAmount', + width: 120, + align: 'right' as const, + customRender: ({ text }: { text: number }) => `${(text || 0).toFixed(2)}元`, + }, + { + title: '退款总金额', + dataIndex: 'refundAmount', + key: 'refundAmount', + width: 120, + align: 'right' as const, + customRender: ({ text }: { text: number }) => `${(text || 0).toFixed(2)}元`, + }, + { + title: '订单状态', + dataIndex: 'orderStatus', + key: 'orderStatus', + width: 100, + align: 'center' as const, + customRender: ({ text }: { text: string }) => { + const info = ORDER_STATUS_MAP[text] || { label: text || '-', color: 'default' }; + return h(Tag, { color: info.color }, () => info.label); + }, + }, + { + title: '退款状态', + dataIndex: 'refundStatus', + key: 'refundStatus', + width: 100, + align: 'center' as const, + customRender: ({ text }: { text: string }) => { + if (!text) return h('span', '-'); + const info = REFUND_STATUS_MAP[text] || { label: text, color: 'default' }; + return h(Tag, { color: info.color }, () => info.label); + }, + }, + ]; + + // ===== 计算属性 ===== + const hasFilter = computed(() => { + return ( + filterForm.orderTimeRange !== null || + debouncedOrderId.value.trim() !== '' || + debouncedEventName.value.trim() !== '' || + debouncedPlayerName.value.trim() !== '' || + filterForm.orderStatus !== '' || + filterForm.refundStatus !== '' + ); + }); + + // ===== 方法 ===== + + /** 查询 */ + const handleSearch = useThrottleFn(async () => { + setLoading(true); + try { + console.log('搜索条件:', { + orderTimeRange: filterForm.orderTimeRange, + orderId: debouncedOrderId.value, + eventName: debouncedEventName.value, + playerName: debouncedPlayerName.value, + orderStatus: filterForm.orderStatus, + refundStatus: filterForm.refundStatus, + }); + // TODO: 替换为真实 API 调用 + setDataSource(MOCK_DATA); + setPagination({ ...pagination.value, total: MOCK_DATA.length }); + setSummary(MOCK_SUMMARY); + message.success('查询成功'); + } catch (error: any) { + message.error(error.msg || '查询失败'); + } finally { + setLoading(false); + } + }, 500); + + /** 重置 */ + const handleReset = useThrottleFn(() => { + filterForm.orderTimeRange = null; + filterForm.searchOrderId = ''; + filterForm.searchEventName = ''; + filterForm.searchPlayerName = ''; + filterForm.orderStatus = ''; + filterForm.refundStatus = ''; + setPagination({ current: 1, pageSize: 10, total: MOCK_DATA.length }); + setDataSource(MOCK_DATA); + setSummary(MOCK_SUMMARY); + }, 500); + + const handlePageChange = (page: number, pageSize: number) => { + setPagination({ ...pagination.value, current: page, pageSize }); + handleSearch(); + }; + + return { + filterForm, + loading, + dataSource, + columns, + summary, + pagination, + hasFilter, + handleSearch, + handleReset, + handlePageChange, + }; +} diff --git a/src/pages/events/users/index.module.less b/src/pages/events/users/index.module.less new file mode 100644 index 0000000..9974dc5 --- /dev/null +++ b/src/pages/events/users/index.module.less @@ -0,0 +1,18 @@ +/** + * 表格 body 容器:占满 flex 剩余空间,并确保 antd 嵌套 div 逐层传递 height: 100%, + * 使得 Table scroll.y = 'calc(100% - 55px)' 中的 100% 能正确解析。 + */ +.tableBody { + flex: 1; + min-height: 0; + overflow: hidden; + + :global { + .ant-spin-nested-loading, + .ant-spin-container, + .ant-table, + .ant-table-container { + height: 100%; + } + } +} diff --git a/src/pages/events/users/index.tsx b/src/pages/events/users/index.tsx new file mode 100644 index 0000000..86621d7 --- /dev/null +++ b/src/pages/events/users/index.tsx @@ -0,0 +1,181 @@ +import { defineComponent } from 'vue'; +import { + Button, + Input, + Table, + Form, + Space, + Select, + Image, + Pagination, + Modal, +} from 'ant-design-vue'; +import { useUserModel, USER_STATUS_OPTIONS } from './model/useUserModel'; +import { useContainerSize } from '@/hooks'; +import styles from './index.module.less'; + +/** + * bodyCell 渲染函数 + */ +function renderBodyCell({ + column, + text, + record, + onToggleStatus, +}: { + column: any; + text: any; + record: any; + onToggleStatus: (record: any) => void; +}) { + // 证件图片:有则显示,无则不显示 + if (column.dataIndex === 'idImg') { + const images = record.idImg || []; + if (images.length === 0) { + return '-'; + } + return ( + + {images.map((src: string) => ( + + ))} + + ); + } + + if (column.key === 'action') { + const isActive = record.status === 'active'; + return ( + + ); + } + + return; +} + +/** + * 用户列表 + */ +export default defineComponent({ + name: 'EventUsers', + setup() { + const { + filterForm, + loading, + dataSource, + columns, + pagination, + handleSearch, + handleReset, + handlePageChange, + } = useUserModel(); + + const { containerRef, height } = useContainerSize(); + + const handleToggleStatus = (record: any) => { + const isActive = record.status === 'active'; + const actionText = isActive ? '禁用' : '启用'; + Modal.confirm({ + title: `${actionText}确认`, + content: `确认${actionText}用户"${record.nickName}"吗?`, + okText: '确认', + cancelText: '取消', + onOk: () => { + console.log(`${actionText}用户:`, record.userId); + // TODO: 替换为真实 API 调用 + }, + }); + }; + + /** 最终表格列:模型列 + 操作 */ + const tableColumns = [ + ...columns, + { + title: '操作', + key: 'action', + width: 100, + fixed: 'right' as const, + align: 'center' as const, + }, + ]; + + return () => ( +
+ {/* ===== 筛选区 ===== */} +
+
+ + + + + + + + + + + {/* 菜单权限配置 */} + +
+ +
+
+
+ + {/* 底部按钮 */} +
+ + +
+ + ); + }; + }, +}); diff --git a/src/pages/system/roles/components/UserListModal.module.less b/src/pages/system/roles/components/UserListModal.module.less new file mode 100644 index 0000000..47bf537 --- /dev/null +++ b/src/pages/system/roles/components/UserListModal.module.less @@ -0,0 +1,23 @@ +.filter { + margin-bottom: 16px; +} + +.bottom { + display: flex; + justify-content: space-between; + align-items: center; + margin-top: 16px; + padding-top: 12px; + border-top: 1px solid #f0f0f0; +} + +.total { + color: rgba(0, 0, 0, 0.65); + font-size: 13px; +} + +.paginationRow { + display: flex; + justify-content: flex-end; + margin-top: 12px; +} diff --git a/src/pages/system/roles/components/UserListModal.tsx b/src/pages/system/roles/components/UserListModal.tsx new file mode 100644 index 0000000..7f5af7e --- /dev/null +++ b/src/pages/system/roles/components/UserListModal.tsx @@ -0,0 +1,136 @@ +import { defineComponent, ref, reactive, computed } from 'vue'; +import { Modal, Form, Input, Table, Button, Space, Pagination } from 'ant-design-vue'; +import { useEffect, useState } from '@/hooks'; +import styles from './UserListModal.module.less'; + +interface UserListModalProps { + visible: boolean; + role: any; + users: any[]; + renderStatus: (status: string) => any; + onClose: () => void; +} + +export default defineComponent({ + name: 'RoleUserListModal', + props: { + visible: { type: Boolean, default: false }, + role: { type: Object, default: null }, + users: { type: Array, default: () => [] }, + renderStatus: { type: Function, required: true }, + onClose: { type: Function, required: true }, + }, + setup(props: UserListModalProps) { + const filterForm = reactive({ searchKeyword: '' }); + + const [pagination, setPagination] = useState({ current: 1, pageSize: 10, total: 0 }); + + /** 过滤后的用户列表 */ + const filteredUsers = computed(() => { + const kw = filterForm.searchKeyword.trim(); + if (!kw) return props.users; + return props.users.filter( + (u: any) => u.realName.includes(kw) || (u.phone && u.phone.includes(kw)), + ); + }); + + /** 监听 visible/users 变化重置分页 */ + useEffect(() => { + setPagination({ current: 1, pageSize: 10, total: filteredUsers.value.length }); + }, [() => props.visible, () => props.users.length]); + + const handleSearch = () => { + setPagination({ ...pagination.value, total: filteredUsers.value.length, current: 1 }); + }; + + const handleReset = () => { + filterForm.searchKeyword = ''; + setPagination({ current: 1, pageSize: 10, total: props.users.length }); + }; + + const handlePageChange = (page: number, pageSize: number) => { + setPagination({ ...pagination.value, current: page, pageSize }); + }; + + return () => { + const roleName = props.role?.roleName || ''; + + const columns = [ + { title: '姓名', dataIndex: 'realName', key: 'realName', width: 120 }, + { title: '手机号', dataIndex: 'phone', key: 'phone', width: 160 }, + { title: '创建时间', dataIndex: 'createTime', key: 'createTime', width: 200 }, + { + title: '状态', + dataIndex: 'status', + key: 'status', + width: 100, + align: 'center' as const, + customRender: ({ text }: { text: string }) => props.renderStatus(text), + }, + ]; + + return ( + + {/* 筛选 */} +
+
+ + + + + + + + + +
+
+ + {/* 表格 */} + + + {/* 底部:共 X 人 + 关闭按钮 */} +
+
共 {filteredUsers.value.length} 人
+ +
+ + {/* 分页(右侧) */} + {filteredUsers.value.length > pagination.value.pageSize && ( +
+ +
+ )} + + ); + }; + }, +}); diff --git a/src/pages/system/roles/controller.ts b/src/pages/system/roles/controller.ts new file mode 100644 index 0000000..fe1dba0 --- /dev/null +++ b/src/pages/system/roles/controller.ts @@ -0,0 +1,65 @@ +/** + * 角色权限 - 表单域控制器 + * + * 集中管理角色表单的规则、菜单权限树 mock 数据与提示文案。 + */ +import type { Rule } from 'ant-design-vue/es/form'; + +// ============================================================ +// 规则 +// ============================================================ + +/** 角色名称规则:必填 + 长度限制 */ +export const roleNameRules: Rule[] = [ + { required: true, message: '请输入角色名称' }, + { max: 20, message: '角色名称最多 20 个字符' }, +]; + +// ============================================================ +// 菜单权限树(mock) +// ============================================================ + +export interface MenuPermNode { + /** 唯一 key,作为 Tree 节点的 key */ + key: string; + /** 显示标题 */ + title: string; + /** 子节点 */ + children?: MenuPermNode[]; +} + +/** + * 完整菜单权限树。 + * 后续可改为从后端接口拉取(接口应返回该结构)。 + */ +export const PERMISSION_TREE: MenuPermNode[] = [ + { + key: 'events', + title: '赛事管理', + children: [ + { + key: 'events.list', + title: '赛事列表', + children: [ + { key: 'events.list.view', title: '赛事列表-查看' }, + { key: 'events.list.on', title: '赛事列表-上架' }, + { key: 'events.list.off', title: '赛事列表-下架' }, + { key: 'events.rules.view', title: '赛事规程-查看' }, + ], + }, + { + key: 'orders', + title: '订单管理', + children: [ + { key: 'orders.view', title: '订单管理-查看' }, + { key: 'orders.refund', title: '订单管理-重新退款' }, + ], + }, + { key: 'users', title: '用户列表' }, + ], + }, + { + key: 'events_logs', + title: '赛事操作日志', + }, +]; diff --git a/src/pages/system/roles/index.module.less b/src/pages/system/roles/index.module.less new file mode 100644 index 0000000..e693719 --- /dev/null +++ b/src/pages/system/roles/index.module.less @@ -0,0 +1,47 @@ +/** + * 表格 body 容器:占满 flex 剩余空间 + */ +.tableBody { + flex: 1; + min-height: 0; + overflow: hidden; + + :global { + .ant-spin-nested-loading, + .ant-spin-container, + .ant-table, + .ant-table-container { + height: 100%; + } + } +} + +/* 用户数链接 */ +.userCountLink { + font-variant-numeric: tabular-nums; +} + +/* ===== 删除确认弹窗 ===== */ +.confirmContent { + padding: 4px 24px; + font-size: 14px; + color: rgba(0, 0, 0, 0.85); + + strong { + margin: 0 4px; + font-weight: 600; + } +} + +.confirmTip { + margin-top: 8px; + font-size: 12px; + color: rgba(0, 0, 0, 0.45); + line-height: 1.5; +} + +:global(.deleteConfirmModal) { + .ant-modal-confirm-body { + padding: 24px !important; + } +} diff --git a/src/pages/system/roles/index.tsx b/src/pages/system/roles/index.tsx new file mode 100644 index 0000000..a26f542 --- /dev/null +++ b/src/pages/system/roles/index.tsx @@ -0,0 +1,208 @@ +import { defineComponent } from 'vue'; +import { Button, Input, Table, Form, Space, Select, Modal, Pagination } from 'ant-design-vue'; +import type { ModalProps } from 'ant-design-vue'; +import { useRoleModel } from './model/useRoleModel'; +import { useContainerSize } from '@/hooks'; +import RoleFormModal from './components/RoleFormModal'; +import UserListModal from './components/UserListModal'; +import styles from './index.module.less'; + +/** + * bodyCell 渲染 + */ +function renderBodyCell({ + column, + record, + onViewUsers, + onEdit, + onDelete, +}: { + column: any; + text: any; + record: any; + onViewUsers: (record: any) => void; + onEdit: (record: any) => void; + onDelete: (record: any) => void; +}) { + if (column.dataIndex === 'userCount') { + return ( + onViewUsers(record)}> + {record.userCount}人 + + ); + } + + if (column.key === 'action') { + return ( + + + + + ); + } + return; +} + +/** + * 删除角色二次确认 + */ +function confirmDelete({ record, onOk }: { record: any; onOk: (record: any) => void }) { + Modal.confirm({ + title: '确认删除', + icon: null, + class: styles.deleteConfirmModal, + content: ( +
+
+ 确定要删除角色 + {record.roleName} + 吗? +
+
删除后该角色下的用户将失去对应的权限,请谨慎操作。
+
+ ), + okText: '确认', + cancelText: '取消', + okButtonProps: { danger: true } as ModalProps['okButtonProps'], + onOk: () => onOk(record), + }); +} + +/** + * 角色权限 + */ +export default defineComponent({ + name: 'SystemRoles', + setup() { + const { + filterForm, + loading, + dataSource, + columns, + pagination, + formVisible, + editingRecord, + formSubmitting, + userListVisible, + currentRole, + handleSearch, + handleReset, + handlePageChange, + handleAdd, + handleEdit, + handleCloseForm, + handleSave, + handleDelete, + handleViewUsers, + handleCloseUserList, + getRoleUsers, + renderStatus, + } = useRoleModel(); + + const { containerRef, height } = useContainerSize(); + + const triggerDelete = (record: any) => confirmDelete({ record, onOk: handleDelete }); + + const tableColumns = [ + ...columns, + { + title: '操作', + key: 'action', + width: 140, + fixed: 'right' as const, + align: 'center' as const, + }, + ]; + + return () => ( +
+ {/* ===== 筛选区 ===== */} +
+
+ + + + + + + + + + + +
+ + {/* ===== 表格区 ===== */} +
+
+
+ {{ + bodyCell: (args: any) => + renderBodyCell({ + ...args, + onViewUsers: handleViewUsers, + onEdit: handleEdit, + onDelete: triggerDelete, + }), + }} +
+
+ + {/* 分页 */} +
+ `共 ${total} 条`} + onChange={handlePageChange} + onShowSizeChange={handlePageChange} + /> +
+
+ + {/* ===== 角色表单弹窗 ===== */} + {formVisible.value && ( + + )} + + {/* ===== 角色用户列表弹窗 ===== */} + {userListVisible.value && ( + + )} + + ); + }, +}); diff --git a/src/pages/system/roles/model/useRoleModel.ts b/src/pages/system/roles/model/useRoleModel.ts new file mode 100644 index 0000000..d5ff605 --- /dev/null +++ b/src/pages/system/roles/model/useRoleModel.ts @@ -0,0 +1,323 @@ +import { computed, reactive, toRef, Ref, h } from 'vue'; +import { message, Tag } from 'ant-design-vue'; +import { useState, useDebounce, useThrottleFn } from '@/hooks'; + +// ============================================================ +// 假数据 +// ============================================================ + +const MOCK_DATA = [ + { + key: '1', + roleId: 'R20250601001', + roleName: '超级管理员', + userCount: 1, + createTime: '2025-06-01 09:00:00', + }, + { + key: '2', + roleId: 'R20250815001', + roleName: '运营管理员', + userCount: 3, + createTime: '2025-08-15 14:30:00', + }, + { + key: '3', + roleId: 'R20251010001', + roleName: '财务', + userCount: 2, + createTime: '2025-10-10 10:15:00', + }, + { + key: '4', + roleId: 'R20260120001', + roleName: '客服', + userCount: 4, + createTime: '2026-01-20 16:00:00', + }, +]; + +/** 角色下用户列表 mock */ +const MOCK_USERS: Record = { + R20250601001: [ + { + key: '1', + userId: 'U001', + realName: '张超管', + phone: '13900000001', + createTime: '2025-06-01 09:00:00', + status: 'active', + }, + ], + R20250815001: [ + { + key: '1', + userId: 'U002', + realName: '李运营', + phone: '15900000002', + createTime: '2026-01-10 16:00:00', + status: 'disabled', + }, + { + key: '2', + userId: 'U003', + realName: '王运营', + phone: '15900000001', + createTime: '2025-10-20 10:00:00', + status: 'active', + }, + { + key: '3', + userId: 'U004', + realName: '张运营', + phone: '13900000002', + createTime: '2025-08-15 14:30:00', + status: 'active', + }, + ], + R20251010001: [ + { + key: '1', + userId: 'U005', + realName: '赵财务', + phone: '13800000005', + createTime: '2025-10-10 10:15:00', + status: 'active', + }, + { + key: '2', + userId: 'U006', + realName: '钱财务', + phone: '13800000006', + createTime: '2025-10-12 11:00:00', + status: 'active', + }, + ], + R20260120001: [ + { + key: '1', + userId: 'U007', + realName: '孙客服', + phone: '13700000007', + createTime: '2026-01-20 16:00:00', + status: 'active', + }, + { + key: '2', + userId: 'U008', + realName: '周客服', + phone: '13700000008', + createTime: '2026-01-21 09:30:00', + status: 'active', + }, + { + key: '3', + userId: 'U009', + realName: '吴客服', + phone: '13700000009', + createTime: '2026-01-22 10:00:00', + status: 'active', + }, + { + key: '4', + userId: 'U010', + realName: '郑客服', + phone: '13700000010', + createTime: '2026-01-23 14:00:00', + status: 'active', + }, + ], +}; + +// ============================================================ +// 状态文本/颜色映射 +// ============================================================ +const STATUS_MAP: Record = { + active: { label: '正常', color: 'green' }, + disabled: { label: '禁用', color: 'red' }, +}; + +// ============================================================ +// Model +// ============================================================ + +/** + * 角色权限页数据模型 + */ +export function useRoleModel() { + // ===== 筛选条件 ===== + const filterForm = reactive({ + searchKeyword: '', + }); + + // 防抖 300ms + const { debouncedValue: debouncedKeyword } = useDebounce( + toRef(filterForm, 'searchKeyword') as Ref, + { delay: 300 }, + ); + + // ===== 表格状态 ===== + const [loading, setLoading] = useState(false); + const [dataSource, setDataSource] = useState(MOCK_DATA); + const [pagination, setPagination] = useState({ + current: 1, + pageSize: 10, + total: MOCK_DATA.length, + }); + + // ===== 弹窗状态 ===== + const [formVisible, setFormVisible] = useState(false); + const [editingRecord, setEditingRecord] = useState(null); + const [userListVisible, setUserListVisible] = useState(false); + const [currentRole, setCurrentRole] = useState(null); + + // ===== 提交态 ===== + const [formSubmitting, setFormSubmitting] = useState(false); + + // ===== 表格列配置 ===== + const columns = [ + { title: '角色名称', dataIndex: 'roleName', key: 'roleName', width: 200 }, + { + title: '用户数', + dataIndex: 'userCount', + key: 'userCount', + width: 120, + align: 'center' as const, + customRender: ({ text }: { text: number }) => `${text}人`, + }, + { title: '创建时间', dataIndex: 'createTime', key: 'createTime', width: 200 }, + ]; + + // ===== 计算属性 ===== + const hasFilter = computed(() => debouncedKeyword.value.trim() !== ''); + + const isEdit = computed(() => editingRecord.value !== null); + + // ===== 方法 ===== + + /** 查询 */ + const handleSearch = useThrottleFn(async () => { + setLoading(true); + try { + console.log('搜索条件:', { keyword: debouncedKeyword.value }); + // TODO: 替换为真实 API 调用 + setDataSource(MOCK_DATA); + setPagination({ ...pagination.value, total: MOCK_DATA.length }); + message.success('查询成功'); + } catch (error: any) { + message.error(error.msg || '查询失败'); + } finally { + setLoading(false); + } + }, 500); + + /** 重置 */ + const handleReset = useThrottleFn(() => { + filterForm.searchKeyword = ''; + setPagination({ current: 1, pageSize: 10, total: MOCK_DATA.length }); + setDataSource(MOCK_DATA); + }, 500); + + const handlePageChange = (page: number, pageSize: number) => { + setPagination({ ...pagination.value, current: page, pageSize }); + handleSearch(); + }; + + /** 新增 */ + const handleAdd = () => { + setEditingRecord(null); + setFormVisible(true); + }; + + /** 编辑 */ + const handleEdit = (record: any) => { + setEditingRecord(record); + setFormVisible(true); + }; + + /** 关闭表单弹窗 */ + const handleCloseForm = () => { + setFormVisible(false); + setEditingRecord(null); + }; + + /** 提交(新增/编辑) */ + const handleSave = useThrottleFn(async (formPayload: any) => { + if (formSubmitting.value) return; + setFormSubmitting(true); + try { + console.log(isEdit.value ? '编辑角色' : '新增角色', formPayload); + // TODO: 替换为真实 API 调用 + message.success(isEdit.value ? '编辑成功' : '新增成功'); + handleCloseForm(); + handleSearch(); + } catch (error: any) { + message.error(error.msg || '操作失败'); + } finally { + setFormSubmitting(false); + } + }, 500); + + /** 删除 */ + const handleDelete = useThrottleFn(async (record: any) => { + setLoading(true); + try { + console.log('删除角色:', record.roleId); + // TODO: 替换为真实 API 调用 + setDataSource(dataSource.value.filter((item: any) => item.key !== record.key)); + message.success('删除成功'); + } catch (error: any) { + message.error(error.msg || '删除失败'); + } finally { + setLoading(false); + } + }, 500); + + /** 打开用户列表弹窗 */ + const handleViewUsers = (record: any) => { + setCurrentRole(record); + setUserListVisible(true); + }; + + /** 关闭用户列表弹窗 */ + const handleCloseUserList = () => { + setUserListVisible(false); + setCurrentRole(null); + }; + + /** 根据 roleId 获取该角色下的用户列表 */ + const getRoleUsers = (roleId: string) => MOCK_USERS[roleId] || []; + + /** 状态 Tag 渲染 */ + const renderStatus = (status: string) => { + const info = STATUS_MAP[status] || { label: status || '-', color: 'default' }; + return h(Tag, { color: info.color }, () => info.label); + }; + + return { + filterForm, + loading, + dataSource, + columns, + pagination, + hasFilter, + formVisible, + editingRecord, + isEdit, + formSubmitting, + userListVisible, + currentRole, + handleSearch, + handleReset, + handlePageChange, + handleAdd, + handleEdit, + handleCloseForm, + handleSave, + handleDelete, + handleViewUsers, + handleCloseUserList, + getRoleUsers, + renderStatus, + }; +} diff --git a/src/pages/system/users/components/UserFormModal.module.less b/src/pages/system/users/components/UserFormModal.module.less new file mode 100644 index 0000000..3791736 --- /dev/null +++ b/src/pages/system/users/components/UserFormModal.module.less @@ -0,0 +1,52 @@ +/* ===== 表单行/列布局 ===== */ +.row { + display: flex; + gap: 16px; + flex-wrap: wrap; +} + +.col { + flex: 1; + min-width: 0; +} + +/* 让 antd Form.Item 在 col 里能正确控制宽度 */ +.form { + :global { + .ant-form-item { + flex: 1; + min-width: 0; + } + } +} + +/* ===== 密码区重置链接 ===== */ +.resetLink { + font-size: 14px; + color: #1677ff; + cursor: pointer; + user-select: none; + + &:hover { + color: #4096ff; + } +} + +/* ===== 取消重置行 ===== */ +.cancelRow { + display: flex; + justify-content: flex-end; + margin-top: -8px; + margin-bottom: 16px; + font-size: 12px; +} + +/* ===== 底部按钮 ===== */ +.footer { + display: flex; + justify-content: flex-end; + gap: 8px; + padding-top: 16px; + border-top: 1px solid #f0f0f0; + margin-top: 8px; +} diff --git a/src/pages/system/users/components/UserFormModal.tsx b/src/pages/system/users/components/UserFormModal.tsx new file mode 100644 index 0000000..5ca74fa --- /dev/null +++ b/src/pages/system/users/components/UserFormModal.tsx @@ -0,0 +1,222 @@ +import { defineComponent, ref, reactive } from 'vue'; +import { useEffect } from '@/hooks'; +import { Modal, Form, Input, Select, Button } from 'ant-design-vue'; +import { + ROLE_FORM_OPTIONS, + PASSWORD_PLACEHOLDER, + realNameRules, + phoneRules, + passwordRules, + confirmPasswordRules, + roleRules, +} from '../controller'; +import styles from './UserFormModal.module.less'; + +interface UserFormModalProps { + visible: boolean; + record: any; + submitting?: boolean; + onClose: () => void; + onSave: (formData: any) => void; +} + +/** 默认表单数据 */ +const getDefaultForm = () => ({ + realName: '', + phone: '', + password: '', + confirmPassword: '', + role: '', +}); + +export default defineComponent({ + name: 'UserFormModal', + props: { + visible: { type: Boolean, default: false }, + record: { type: Object, default: null }, + submitting: { type: Boolean, default: false }, + onClose: { type: Function, required: true }, + onSave: { type: Function, required: true }, + }, + setup(props: UserFormModalProps) { + const formRef = ref(); + const formData = reactive(getDefaultForm()); + + /** 编辑模式下是否处于"重置密码"状态 */ + const isResetting = ref(false); + + /** 根据 record 初始化表单 */ + const initFormFromRecord = (record: any) => { + const fresh = getDefaultForm(); + if (!record) { + Object.assign(formData, fresh); + isResetting.value = false; + return; + } + Object.assign(formData, { + ...fresh, + realName: record.realName || '', + phone: record.phone || '', + role: record.role || '', + }); + isResetting.value = false; + }; + + /** 监听 visible 变化重置表单 */ + useEffect(() => { + if (props.visible) { + initFormFromRecord(props.record); + isResetting.value = false; + setTimeout(() => formRef.value?.clearValidate(), 0); + } + }, [() => props.visible]); + + /** 点击"重置密码" */ + const handleResetPassword = () => { + isResetting.value = true; + formData.password = ''; + formData.confirmPassword = ''; + }; + + /** 提交 */ + const handleSubmit = async () => { + try { + await formRef.value?.validate(); + } catch { + return; + } + + // 编辑模式下,未触发重置密码时,不提交密码字段 + const payload: any = { + realName: formData.realName, + phone: formData.phone, + role: formData.role, + }; + if (props.record) { + if (isResetting.value) { + payload.password = formData.password; + } + } else { + // 新增:密码必传 + payload.password = formData.password; + } + + props.onSave(payload); + }; + + /** 关闭 */ + const handleClose = () => { + props.onClose(); + }; + + return () => { + const isEdit = !!props.record; + + return ( + +
+ {/* 姓名 / 手机号:同一行 */} +
+ + + + + + +
+ + {/* ====== 密码区 + 角色 ====== */} + {isEdit && !isResetting.value ? ( + /* 编辑模式未重置:密码占位 + 角色 同一行 */ +
+ + + 重置密码 + + } + /> + + + + + + )} + + + {/* 底部按钮 */} +
+ + +
+ + ); + }; + }, +}); diff --git a/src/pages/system/users/controller.ts b/src/pages/system/users/controller.ts new file mode 100644 index 0000000..d87b4f6 --- /dev/null +++ b/src/pages/system/users/controller.ts @@ -0,0 +1,96 @@ +/** + * 用户管理 - 表单域控制器 + * + * 集中管理用户新增/编辑弹窗的所有规则校验、下拉选项与常量。 + * 各规则数组可直接传给 antd Form.Item 的 rules prop。 + */ +import type { Rule } from 'ant-design-vue/es/form'; +import { isChinaPhone, isValidPassword, isValidRealName } from '@/utils/form'; + +// ============================================================ +// 常量 +// ============================================================ + +/** 密码占位符(写死长度,避免暴露真实密码长度) */ +export const PASSWORD_PLACEHOLDER = '********'; + +/** 角色下拉选项(表单使用) */ +export const ROLE_FORM_OPTIONS = [ + { value: 'admin', label: '超级管理员' }, + { value: 'operator', label: '运营人员' }, + { value: 'auditor', label: '审核人员' }, +] as const; + +// ============================================================ +// 真实姓名规则 +// ============================================================ + +export const realNameRules: Rule[] = [ + { required: true, message: '请输入姓名' }, + { max: 8, message: '姓名最多 8 个字符' }, + { + validator: (_rule: unknown, value: string) => { + if (!value) return Promise.resolve(); + if (!isValidRealName(value)) { + return Promise.reject(new Error('姓名仅支持中英文或数字,1-8 个字符')); + } + return Promise.resolve(); + }, + }, +]; + +// ============================================================ +// 手机号规则 +// ============================================================ + +export const phoneRules: Rule[] = [ + { required: true, message: '请输入手机号' }, + { + validator: (_rule: unknown, value: string) => { + if (!value) return Promise.resolve(); + if (!isChinaPhone(value)) { + return Promise.reject(new Error('请输入正确的11位中国大陆手机号')); + } + return Promise.resolve(); + }, + }, +]; + +// ============================================================ +// 密码规则 +// ============================================================ + +export const passwordRules: Rule[] = [ + { required: true, message: '请输入密码' }, + { + validator: (_rule: unknown, value: string) => { + if (!value) return Promise.resolve(); + if (!isValidPassword(value)) { + return Promise.reject(new Error('密码至少8位,需包含字母与数字')); + } + return Promise.resolve(); + }, + }, +]; + +/** 确认密码规则:必须与 password 字段一致 */ +export function confirmPasswordRules(getPassword: () => string): Rule[] { + return [ + { required: true, message: '请再次输入密码' }, + { + validator: (_rule: unknown, value: string) => { + if (!value) return Promise.resolve(); + if (value !== getPassword()) { + return Promise.reject(new Error('两次输入的密码不一致')); + } + return Promise.resolve(); + }, + }, + ]; +} + +// ============================================================ +// 角色规则 +// ============================================================ + +export const roleRules: Rule[] = [{ required: true, message: '请选择角色' }]; diff --git a/src/pages/system/users/index.module.less b/src/pages/system/users/index.module.less new file mode 100644 index 0000000..d411680 --- /dev/null +++ b/src/pages/system/users/index.module.less @@ -0,0 +1,43 @@ +/** + * 表格 body 容器:占满 flex 剩余空间, + * 确保 antd 嵌套 div 逐层传递 height: 100%, + * 使得 Table scroll.y 能正确解析。 + */ +.tableBody { + flex: 1; + min-height: 0; + overflow: hidden; + + :global { + .ant-spin-nested-loading, + .ant-spin-container, + .ant-table, + .ant-table-container { + height: 100%; + } + } +} + +/* ===== 禁用/启用 二次确认弹窗 ===== */ + +/* 内容块:左右加 padding,按用户要求 */ +.confirmContent { + display: flex; + align-items: center; + flex-wrap: wrap; + padding: 4px 0; + font-size: 14px; + color: rgba(0, 0, 0, 0.85); + + strong { + margin: 0 4px; + font-weight: 600; + } +} + +/* antd Modal.confirm 默认 body 已经有 padding,这里额外覆盖一下确保一致 */ +:global(.toggleConfirmModal) { + .ant-modal-confirm-body { + padding: 24px !important; + } +} diff --git a/src/pages/system/users/index.tsx b/src/pages/system/users/index.tsx new file mode 100644 index 0000000..edc3fcc --- /dev/null +++ b/src/pages/system/users/index.tsx @@ -0,0 +1,212 @@ +import { defineComponent } from 'vue'; +import { Button, Input, Table, Form, Space, Select, Modal, Pagination } from 'ant-design-vue'; +import { useUserModel, USER_ROLE_OPTIONS, USER_STATUS_OPTIONS } from './model/useUserModel'; +import { useContainerSize } from '@/hooks'; +import UserFormModal from './components/UserFormModal'; +import styles from './index.module.less'; + +/** + * bodyCell 渲染函数 + */ +function renderBodyCell({ + column, + record, + onEdit, + onToggleStatus, +}: { + column: any; + text: any; + record: any; + onEdit: (record: any) => void; + onToggleStatus: (record: any) => void; +}) { + // 操作列 + if (column.key === 'action') { + const isActive = record.status === 'active'; + return ( + + + + + ); + } + return; +} + +/** + * 禁用/启用 二次确认弹窗 + */ +function confirmToggleStatus({ record, onOk }: { record: any; onOk: (record: any) => void }) { + const isActive = record.status === 'active'; + const actionText = isActive ? '禁用' : '启用'; + Modal.confirm({ + title: `确认${actionText}`, + class: styles.toggleConfirmModal, + content: ( +
+ 确定要{actionText}用户 + {record.realName} + 吗? +
+ ), + okText: '确认', + cancelText: '取消', + okButtonProps: { danger: isActive }, + onOk: () => onOk(record), + }); +} + +/** + * 系统管理 - 用户管理 + */ +export default defineComponent({ + name: 'SystemUsers', + setup() { + const { + filterForm, + loading, + dataSource, + columns, + pagination, + modalVisible, + editingRecord, + isEdit, + formSubmitting, + handleSearch, + handleReset, + handlePageChange, + handleAdd, + handleEdit, + handleCloseModal, + handleSave, + handleToggleStatus, + } = useUserModel(); + + const { containerRef, height } = useContainerSize(); + + /** 触发禁用/启用的二次确认 */ + const triggerToggle = (record: any) => { + confirmToggleStatus({ record, onOk: handleToggleStatus }); + }; + + /** 触发编辑 */ + const triggerEdit = (record: any) => { + handleEdit(record); + }; + + /** 表格最终列:模型列 + 操作 */ + const tableColumns = [ + ...columns, + { + title: '操作', + key: 'action', + width: 140, + fixed: 'right' as const, + align: 'center' as const, + }, + ]; + + return () => ( +
+ {/* ===== 筛选区 ===== */} +
+
+ + + + + (filterForm.status = val || '')} + /> + + + + + + + + +
+
+ + {/* ===== 表格区 ===== */} +
+
+ + {{ + bodyCell: (args: any) => + renderBodyCell({ + ...args, + onEdit: triggerEdit, + onToggleStatus: triggerToggle, + }), + }} +
+
+ + {/* 独立分页 */} +
+ `共 ${total} 条`} + onChange={handlePageChange} + onShowSizeChange={handlePageChange} + /> +
+
+ + {/* ===== 新增/编辑弹窗 ===== */} + {modalVisible.value && ( + + )} +
+ ); + }, +}); diff --git a/src/pages/system/users/model/useUserModel.ts b/src/pages/system/users/model/useUserModel.ts new file mode 100644 index 0000000..4dde2cb --- /dev/null +++ b/src/pages/system/users/model/useUserModel.ts @@ -0,0 +1,248 @@ +import { computed, reactive, toRef, Ref, h } from 'vue'; +import { message, Tag } from 'ant-design-vue'; +import { useState, useDebounce, useThrottleFn } from '@/hooks'; + +// ============================================================ +// 常量 +// ============================================================ + +/** 角色选项(筛选) */ +export const USER_ROLE_OPTIONS = [ + { value: '', label: '全部' }, + { value: 'admin', label: '管理员' }, +] as const; + +/** 状态选项(筛选) */ +export const USER_STATUS_OPTIONS = [ + { value: '', label: '全部' }, + { value: 'active', label: '正常' }, + { value: 'disabled', label: '禁用' }, +] as const; + +/** 角色文本/颜色映射(表格渲染) */ +const ROLE_MAP: Record = { + admin: { label: '管理员', color: 'blue' }, + operator: { label: '运营人员', color: 'cyan' }, + auditor: { label: '审核人员', color: 'purple' }, +}; + +/** 状态文本/颜色映射 */ +const STATUS_MAP: Record = { + active: { label: '正常', color: 'green' }, + disabled: { label: '禁用', color: 'red' }, +}; + +// ============================================================ +// 假数据 +// ============================================================ + +const MOCK_DATA = [ + { + key: '1', + userId: 'U20260526001', + realName: '张三', + phone: '15585658975', + role: 'admin', + status: 'active' as 'active' | 'disabled', + createTime: '2026.05.26 08:00:00', + }, + { + key: '2', + userId: 'U20260526002', + realName: '王五', + phone: '13800138000', + role: 'admin', + status: 'disabled' as 'active' | 'disabled', + createTime: '2026.05.26 14:00:00', + }, +]; + +// ============================================================ +// Model +// ============================================================ + +/** + * 系统管理 - 用户列表页数据模型 + */ +export function useUserModel() { + // ===== 筛选条件 ===== + const filterForm = reactive({ + searchKeyword: '', // 用户名/手机号 + role: '' as '' | 'admin', + status: '' as '' | 'active' | 'disabled', + }); + + // 防抖 300ms + const { debouncedValue: debouncedKeyword } = useDebounce( + toRef(filterForm, 'searchKeyword') as Ref, + { delay: 300 }, + ); + + // ===== 表格状态 ===== + const [loading, setLoading] = useState(false); + const [dataSource, setDataSource] = useState(MOCK_DATA); + const [pagination, setPagination] = useState({ + current: 1, + pageSize: 10, + total: MOCK_DATA.length, + }); + + // ===== 弹窗状态 ===== + const [modalVisible, setModalVisible] = useState(false); + const [editingRecord, setEditingRecord] = useState(null); + + // ===== 提交态 ===== + const [formSubmitting, setFormSubmitting] = useState(false); + + // ===== 表格列配置 ===== + const columns = [ + { title: '姓名', dataIndex: 'realName', key: 'realName', width: 120 }, + { title: '手机号', dataIndex: 'phone', key: 'phone', width: 140 }, + { + title: '角色', + dataIndex: 'role', + key: 'role', + width: 120, + align: 'center' as const, + customRender: ({ text }: { text: string }) => { + const info = ROLE_MAP[text] || { label: text || '-', color: 'default' }; + return h(Tag, { color: info.color }, () => info.label); + }, + }, + { + title: '状态', + dataIndex: 'status', + key: 'status', + width: 100, + align: 'center' as const, + customRender: ({ text }: { text: string }) => { + const info = STATUS_MAP[text] || { label: text || '-', color: 'default' }; + return h(Tag, { color: info.color }, () => info.label); + }, + }, + { title: '创建时间', dataIndex: 'createTime', key: 'createTime', width: 180 }, + ]; + + // ===== 计算属性 ===== + const hasFilter = computed(() => { + return ( + debouncedKeyword.value.trim() !== '' || filterForm.role !== '' || filterForm.status !== '' + ); + }); + + const isEdit = computed(() => editingRecord.value !== null); + + // ===== 方法 ===== + + /** 查询(节流 500ms) */ + const handleSearch = useThrottleFn(async () => { + setLoading(true); + try { + console.log('搜索条件:', { + keyword: debouncedKeyword.value, + role: filterForm.role, + status: filterForm.status, + }); + // TODO: 替换为真实 API 调用 + setDataSource(MOCK_DATA); + setPagination({ ...pagination.value, total: MOCK_DATA.length }); + message.success('查询成功'); + } catch (error: any) { + message.error(error.msg || '查询失败'); + } finally { + setLoading(false); + } + }, 500); + + /** 重置(节流 500ms) */ + const handleReset = useThrottleFn(() => { + filterForm.searchKeyword = ''; + filterForm.role = ''; + filterForm.status = ''; + setPagination({ current: 1, pageSize: 10, total: MOCK_DATA.length }); + setDataSource(MOCK_DATA); + }, 500); + + const handlePageChange = (page: number, pageSize: number) => { + setPagination({ ...pagination.value, current: page, pageSize }); + handleSearch(); + }; + + /** 打开新增弹窗 */ + const handleAdd = () => { + setEditingRecord(null); + setModalVisible(true); + }; + + /** 打开编辑弹窗 */ + const handleEdit = (record: any) => { + setEditingRecord(record); + setModalVisible(true); + }; + + /** 关闭弹窗 */ + const handleCloseModal = () => { + setModalVisible(false); + setEditingRecord(null); + }; + + /** 提交表单(新增/编辑) */ + const handleSave = useThrottleFn(async (formPayload: any) => { + if (formSubmitting.value) return; + setFormSubmitting(true); + try { + console.log(isEdit.value ? '编辑用户' : '新增用户', formPayload); + // TODO: 替换为真实 API 调用 + message.success(isEdit.value ? '编辑成功' : '新增成功'); + handleCloseModal(); + handleSearch(); + } catch (error: any) { + message.error(error.msg || '操作失败'); + } finally { + setFormSubmitting(false); + } + }, 500); + + /** 启用/禁用切换(节流 500ms) */ + const handleToggleStatus = useThrottleFn(async (record: any) => { + setLoading(true); + try { + const newStatus = record.status === 'active' ? 'disabled' : 'active'; + const actionText = newStatus === 'active' ? '启用' : '禁用'; + console.log(`${actionText}用户:`, record.userId); + // TODO: 替换为真实 API 调用 + setDataSource( + dataSource.value.map((item: any) => + item.key === record.key ? { ...item, status: newStatus } : item, + ), + ); + message.success(`${actionText}成功`); + } catch (error: any) { + message.error(error.msg || '操作失败'); + } finally { + setLoading(false); + } + }, 500); + + return { + filterForm, + loading, + dataSource, + columns, + pagination, + hasFilter, + modalVisible, + setModalVisible, + editingRecord, + isEdit, + formSubmitting, + handleSearch, + handleReset, + handlePageChange, + handleAdd, + handleEdit, + handleCloseModal, + handleSave, + handleToggleStatus, + }; +} diff --git a/src/router/guard.ts b/src/router/guard.ts index 20893c9..b2beeae 100644 --- a/src/router/guard.ts +++ b/src/router/guard.ts @@ -1,6 +1,13 @@ import router from '@/router'; import { auth } from '@/hooks/useAuth'; +const appTitle = import.meta.env.VITE_APP_TITLE || 'CPMS 运营平台'; + +/** 导航完成后设置页面标题,避免动态路由加载前误匹配 404 导致 title 闪烁 */ +function updateDocumentTitle(title?: string) { + document.title = title ? `${title} - ${appTitle}` : appTitle; +} + /** * 全局路由守卫 * 核心流程: @@ -15,10 +22,6 @@ import { auth } from '@/hooks/useAuth'; * matched 信息带过去,导致仍然匹配到 /:pathMatch(.*)*。 */ router.beforeEach(async (to, _from, next) => { - // 设置页面标题 - const appTitle = import.meta.env.VITE_APP_TITLE || 'CPMS 运营平台'; - document.title = to.meta.title ? `${to.meta.title} - ${appTitle}` : appTitle; - // ── /login 特殊处理 ── if (to.path === '/login') { if (auth.isLoggedIn()) { @@ -51,3 +54,7 @@ router.beforeEach(async (to, _from, next) => { next(); }); + +router.afterEach((to) => { + updateDocumentTitle(to.meta.title as string | undefined); +}); diff --git a/src/stores/menuStore.ts b/src/stores/menuStore.ts index 97a4380..4d62fa6 100644 --- a/src/stores/menuStore.ts +++ b/src/stores/menuStore.ts @@ -4,6 +4,7 @@ import type { MenuNode, MenuItemRaw } from '@/types'; import router, { resetRouter } from '@/router'; import { fetchMenuTree } from '@/api/menu'; import { pageModules } from '@/router/glob'; +import { FALLBACK_MENU_NODES } from '@/config/fallbackRoutes'; /** * 使用 import.meta.glob 动态收集 src/pages/ 下所有页面组件 @@ -24,15 +25,11 @@ import { pageModules } from '@/router/glob'; function resolveComponent(component?: string): RouteRecordRaw['component'] | undefined { if (!component) return undefined; - // 构建 glob key const globKey = `/src/pages/${component}/index.tsx`; - - // 直接匹配 if (pageModules[globKey]) { return pageModules[globKey]; } - // 兜底:尝试带 @ 前缀匹配(Vite alias) const aliasKey = `@/pages/${component}/index.tsx`; if (pageModules[aliasKey]) { return pageModules[aliasKey]; @@ -60,43 +57,94 @@ const state = reactive({ homePath: '/dashboard', }); +// ============================================================ +// 辅助函数 +// ============================================================ + +/** + * 获取节点的可用子节点(排除 disabled 和 externalLink) + */ +function getEnabledChildren(node: MenuNode): MenuNode[] { + if (!node.children?.length) return []; + return node.children.filter((child) => !child.disabled && !child.externalLink); +} + /** * 将后端 MenuNode 递归转为 antd Menu 的 items 格式 + * + * 规则: + * - 过滤 disabled / hideInMenu / externalLink 节点 + * - 父节点有 enabledChildren 时递归转换;若所有子节点都被过滤且父节点无 component,则父节点也不显示 + * - key 统一加 '/' 前缀,与 route.path 匹配(如 '/dashboard'、'/events/list') */ function transformMenuNode(nodes: MenuNode[]): MenuItemRaw[] { - return nodes - .filter((node) => !node.hideInMenu && !node.externalLink) - .sort((a, b) => (a.sort ?? 0) - (b.sort ?? 0)) - .map((node) => { - const item: MenuItemRaw = { - key: node.path, - label: node.name, - iconName: node.icon, - }; + const result: MenuItemRaw[] = []; - if (node.children?.length) { - item.children = transformMenuNode(node.children); + for (const node of nodes) { + if (node.disabled || node.hideInMenu || node.externalLink) continue; + + const enabledChildren = getEnabledChildren(node); + const item: MenuItemRaw = { + key: '/' + node.path, + label: node.name, + iconName: node.icon, + }; + + if (enabledChildren.length > 0) { + const childItems = transformMenuNode(enabledChildren); + if (childItems.length > 0) { + item.children = childItems; } + // 子节点全部被过滤 → 父节点也不显示 + else { + continue; + } + } - return item; - }); + result.push(item); + } + + // 排序 + result.sort((a, b) => { + const aNode = nodes.find((n) => '/' + n.path === a.key); + const bNode = nodes.find((n) => '/' + n.path === b.key); + return (aNode?.sort ?? 0) - (bNode?.sort ?? 0); + }); + + return result; } /** * 将后端 MenuNode 递归转为 vue-router RouteRecordRaw - * component 字段通过 resolveComponent() 动态解析 + * + * node.path 存储完整路径(如 'events/list'), + * 但 Vue Router 嵌套路由下子节点的 path 必须相对于父节点(如 'list')。 + * 通过 parentPath 参数实现自动剥离前缀。 + * + * 规则: + * - 跳过 disabled / externalLink 节点 + * - 父节点无 component 且所有子节点被过滤 → 跳过 + * - 父节点无 component 时,redirect 自动指向第一个可用子路由(相对路径) */ -function transformMenuToRoutes(nodes: MenuNode[]): RouteRecordRaw[] { +function transformMenuToRoutes(nodes: MenuNode[], parentPath = ''): RouteRecordRaw[] { const routes: RouteRecordRaw[] = []; for (const node of nodes) { - // 外链节点不注册路由 - if (node.externalLink) continue; + if (node.disabled || node.externalLink) continue; + const enabledChildren = getEnabledChildren(node); const componentLoader = resolveComponent(node.component); + // 无组件且无可用子节点 → 跳过 + if (!componentLoader && enabledChildren.length === 0) continue; + + // 计算 Vue Router 路由 path: 根节点用完整路径,子节点剥离父级前缀 + const routePath = parentPath + ? node.path.slice(parentPath.length + 1) // 'events/list' → 'list' + : node.path; + const route: RouteRecordRaw = { - path: node.path, + path: routePath, name: node.name, meta: { title: node.name, @@ -111,11 +159,13 @@ function transformMenuToRoutes(nodes: MenuNode[]): RouteRecordRaw[] { route.component = componentLoader; } - if (node.children?.length) { - route.children = transformMenuToRoutes(node.children); - // 没有组件的父节点自动重定向到第一个子节点 - if (!componentLoader && node.children[0]) { - route.redirect = node.children[0].path; + if (enabledChildren.length > 0) { + // 递归时传入当前节点的完整路径作为父级前缀 + route.children = transformMenuToRoutes(enabledChildren, node.path); + // 无组件的父节点 → redirect 指向第一个可用子路由(相对路径) + if (!componentLoader) { + const firstChildRelativePath = enabledChildren[0].path.slice(node.path.length + 1); + route.redirect = firstChildRelativePath; } } @@ -127,14 +177,19 @@ function transformMenuToRoutes(nodes: MenuNode[]): RouteRecordRaw[] { /** * 获取第一个可见菜单的路径(用作默认首页) + * 跳过 disabled / hideInMenu / externalLink 节点 + * 返回带 '/' 前缀的完整路径 */ function getFirstMenuPath(nodes: MenuNode[]): string { for (const node of nodes) { - if (!node.hideInMenu && !node.externalLink && !node.children?.length) { - return node.path; + if (node.disabled || node.externalLink) continue; + + const enabledChildren = getEnabledChildren(node); + if (!node.hideInMenu && enabledChildren.length === 0) { + return '/' + node.path; } - if (node.children?.length) { - const childPath = getFirstMenuPath(node.children); + if (enabledChildren.length > 0) { + const childPath = getFirstMenuPath(enabledChildren); if (childPath) return childPath; } } @@ -146,8 +201,7 @@ function getFirstMenuPath(nodes: MenuNode[]): string { * 动态路由作为其子路由挂载 */ function ensureLayoutRoute() { - const existing = router.hasRoute('BasicLayout'); - if (!existing) { + if (!router.hasRoute('BasicLayout')) { router.addRoute({ path: '/', name: 'BasicLayout', @@ -159,61 +213,41 @@ function ensureLayoutRoute() { } /** - * 注册兜底菜单和路由(后端接口不可用时使用) + * 通用"注入路由 + 菜单"流程 + * 无论是后端数据还是兜底数据,都走同一套 logic */ -function registerFallbackRoutes() { +function applyMenuTree(menuTree: MenuNode[]) { + state.menuTree = menuTree; + state.menuItems = transformMenuNode(menuTree); + state.homePath = getFirstMenuPath(menuTree); + ensureLayoutRoute(); - router.addRoute('BasicLayout', { - path: 'dashboard', - name: 'Dashboard', - component: () => import('@/pages/dashboard'), - meta: { title: '工作台', icon: 'DashboardOutlined' }, + const dynamicRoutes = transformMenuToRoutes(menuTree); + dynamicRoutes.forEach((route) => { + router.addRoute('BasicLayout', route); }); - router.addRoute('BasicLayout', { - path: 'about', - name: 'About', - component: () => import('@/pages/about'), - meta: { title: '关于', icon: 'InfoCircleOutlined' }, - }); - router.addRoute('BasicLayout', { path: '', redirect: '/dashboard' }); + + // 默认重定向到首页 + router.addRoute('BasicLayout', { path: '', redirect: state.homePath }); } +// ============================================================ +// 暴露给组件使用的 composable +// ============================================================ + export function useMenuStore() { const loadMenu = async () => { if (state.loaded) return; try { const menuTree = await fetchMenuTree(); - state.menuTree = menuTree; - state.menuItems = transformMenuNode(menuTree); - state.homePath = getFirstMenuPath(menuTree); - - // 确保 BasicLayout 布局路由已注册 - ensureLayoutRoute(); - - // 动态注册路由:将后端路由配置注入到 BasicLayout 布局下 - const dynamicRoutes = transformMenuToRoutes(menuTree); - dynamicRoutes.forEach((route) => { - router.addRoute('BasicLayout', route); - }); - - // 默认重定向到首页(BasicLayout 的根路径) - router.addRoute('BasicLayout', { path: '', redirect: state.homePath }); - + applyMenuTree(menuTree); state.loaded = true; } catch (err) { console.error('加载菜单失败:', err); - // 降级:使用兜底菜单 - state.menuItems = [ - { key: '/dashboard', label: '工作台', iconName: 'DashboardOutlined' }, - { key: '/about', label: '关于', iconName: 'InfoCircleOutlined' }, - ]; - state.homePath = '/dashboard'; - - // 兜底路由 - registerFallbackRoutes(); - + // 降级:使用本地兜底菜单 + applyMenuTree(FALLBACK_MENU_NODES); state.loaded = true; } }; diff --git a/src/stores/userStore.ts b/src/stores/userStore.ts new file mode 100644 index 0000000..e524f82 --- /dev/null +++ b/src/stores/userStore.ts @@ -0,0 +1,39 @@ +import { reactive, computed, toRefs } from 'vue'; + +interface UserState { + /** 手机号 */ + phone: string; + /** 昵称 */ + nickname: string; + /** 登录 token */ + token: string; +} + +const state = reactive({ + phone: '13377981125', + nickname: '', + token: '', +}); + +export function useUserStore() { + const setUser = (user: Partial) => { + if (user.phone !== undefined) state.phone = user.phone; + if (user.nickname !== undefined) state.nickname = user.nickname; + if (user.token !== undefined) state.token = user.token; + }; + + const clearUser = () => { + state.phone = ''; + state.nickname = ''; + state.token = ''; + }; + + return { + ...toRefs(state), + phone: computed(() => state.phone), + nickname: computed(() => state.nickname), + token: computed(() => state.token), + setUser, + clearUser, + }; +} diff --git a/src/types/china-area-data.d.ts b/src/types/china-area-data.d.ts new file mode 100644 index 0000000..3e65f0a --- /dev/null +++ b/src/types/china-area-data.d.ts @@ -0,0 +1,4 @@ +declare module 'china-area-data' { + const data: Record>; + export default data; +} diff --git a/src/types/index.ts b/src/types/index.ts index 7c314c0..2140445 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -55,6 +55,8 @@ export interface MenuNode { externalLink?: string; /** 高亮的菜单路径(用于详情页高亮父级菜单) */ activeMenu?: string; + /** 是否禁用(禁用后路由不注册、菜单不显示) */ + disabled?: boolean; /** 子菜单/子路由 */ children?: MenuNode[]; } diff --git a/src/utils/areaData.ts b/src/utils/areaData.ts new file mode 100644 index 0000000..76baa6a --- /dev/null +++ b/src/utils/areaData.ts @@ -0,0 +1,33 @@ +import areaData from 'china-area-data'; + +export interface CascadeOption { + value: string; + label: string; + children?: CascadeOption[]; +} + +/** + * 将 china-area-data 原始数据转换为 antd Cascader 所需的 options 格式 + * + * 原始数据格式: { '86': { '110000': '北京' }, '110000': { '110100': '市辖区' }, ... } + * 目标格式: [{ value: '110000', label: '北京', children: [...] }] + */ +function buildTree(parentCode: string): CascadeOption[] { + const nodes = areaData[parentCode]; + if (!nodes) return []; + + return Object.entries(nodes).map(([code, name]) => { + const node: CascadeOption = { + value: code, + label: name as string, + }; + const children = buildTree(code); + if (children.length > 0) { + node.children = children; + } + return node; + }); +} + +/** 省市区三级联动数据 */ +export const regionOptions: CascadeOption[] = buildTree('86'); diff --git a/src/utils/form.ts b/src/utils/form.ts new file mode 100644 index 0000000..27b11ea --- /dev/null +++ b/src/utils/form.ts @@ -0,0 +1,47 @@ +/** + * 表单正则校验工具 + * + * 只导出纯正则常量与独立的校验函数,不做任何 antd/framework 耦合。 + * 各页面的 Form.Item rules 应在对应页面目录的 controller.ts 中组装。 + */ + +// ============================================================ +// 正则常量 +// ============================================================ + +/** + * 中国大陆手机号 + * - 11 位数字,第二位 3-9 + */ +export const CHINA_PHONE_REGEX = /^1[3-9]\d{9}$/; + +/** + * 登录密码 + * - 至少 8 位,同时包含字母与数字 + * - 允许特殊字符 !@#$%^&*()_+-= + */ +export const LOGIN_PASSWORD_REGEX = /^(?=.*[A-Za-z])(?=.*\d)[\w!@#$%^&*()_+\-=]{8,}$/; + +/** + * 真实姓名(中文/英文/数字,1-8 位) + */ +export const REAL_NAME_REGEX = /^[\u4e00-\u9fa5A-Za-z0-9·]{1,8}$/; + +// ============================================================ +// 校验函数 +// ============================================================ + +/** 校验中国大陆手机号 */ +export function isChinaPhone(value: unknown): boolean { + return typeof value === 'string' && CHINA_PHONE_REGEX.test(value); +} + +/** 校验密码:至少 8 位 + 同时含字母与数字 */ +export function isValidPassword(value: unknown): boolean { + return typeof value === 'string' && LOGIN_PASSWORD_REGEX.test(value); +} + +/** 校验真实姓名:1-8 个字符 */ +export function isValidRealName(value: unknown): boolean { + return typeof value === 'string' && value.length > 0 && REAL_NAME_REGEX.test(value); +} diff --git a/src/utils/oss.ts b/src/utils/oss.ts new file mode 100644 index 0000000..3ceaa4a --- /dev/null +++ b/src/utils/oss.ts @@ -0,0 +1,121 @@ +import type { OssSignData } from '@/api/upload'; +import { fetchOssSign } from '@/api/upload'; + +/** + * OSS 上传类型 + * 1=证件照 2=赛事封面 3=头像 4=富文本 5=Banner + */ +export const OssUploadType = { + IdCard: '1', + EventCover: '2', + Avatar: '3', + RichText: '4', + Banner: '5', +} as const; + +export type OssUploadTypeValue = (typeof OssUploadType)[keyof typeof OssUploadType]; + +/** + * 生成唯一文件名:16 位大小写字母+数字随机串.ext + */ +export function genFileName(filePath: string, fileName?: string): string { + let suffix = ''; + if (fileName) { + const dot = fileName.lastIndexOf('.'); + suffix = dot >= 0 ? fileName.slice(dot).toLowerCase() : ''; + } + if (!suffix && filePath) { + const dot = filePath.lastIndexOf('.'); + suffix = dot >= 0 ? filePath.slice(dot).toLowerCase() : ''; + } + if (!suffix) suffix = '.jpg'; + + const random = Array.from({ length: 16 }, () => { + const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; + return chars.charAt(Math.floor(Math.random() * chars.length)); + }).join(''); + + return `${random}${suffix}`; +} + +/** 根据签名和文件名构建 OSS 上传 FormData 字段 */ +export function buildOssFormData(sign: OssSignData, fileName: string): Record { + return { + key: `${sign.dir}${fileName}`, + policy: sign.policy, + 'x-oss-signature': sign.signature, + 'x-oss-signature-version': 'OSS4-HMAC-SHA256', + 'x-oss-credential': sign.x_oss_credential, + 'x-oss-date': sign.x_oss_date, + 'x-oss-security-token': sign.security_token, + success_action_status: '200', + }; +} + +/** + * 使用已有签名上传单个文件到 OSS(Web 版,fetch + FormData) + */ +export async function uploadFileWithSign(file: File, sign: OssSignData): Promise { + const fileName = genFileName(file.name, file.name); + const key = `${sign.dir}${fileName}`; + + const formData = new FormData(); + Object.entries(buildOssFormData(sign, fileName)).forEach(([k, v]) => { + formData.append(k, v); + }); + formData.append('file', file); + + const res = await fetch(sign.host, { + method: 'POST', + body: formData, + }); + + if (!res.ok) { + throw new Error(`OSS 上传失败:${res.status} ${res.statusText}`); + } + + return `${sign.host}/${key}`; +} + +/** + * 上传单个文件到 OSS + * @returns 文件访问 URL + */ +export async function uploadFile(file: File, type: OssUploadTypeValue): Promise<{ url: string }> { + const sign = await fetchOssSign(type); + const url = await uploadFileWithSign(file, sign); + return { url }; +} + +/** + * 逐张上传文件到 OSS(同一批复用同一签名,单张失败不影响后续) + */ +export async function uploadFilesOneByOne( + files: File[], + type: OssUploadTypeValue, + options?: { + onSuccess?: (url: string, index: number) => void | Promise; + onError?: (error: unknown, index: number) => void; + }, +): Promise<{ successCount: number; failCount: number }> { + if (files.length === 0) { + return { successCount: 0, failCount: 0 }; + } + + const sign = await fetchOssSign(type); + let successCount = 0; + let failCount = 0; + + for (let i = 0; i < files.length; i++) { + try { + const url = await uploadFileWithSign(files[i], sign); + successCount++; + await options?.onSuccess?.(url, i); + } catch (err) { + failCount++; + options?.onError?.(err, i); + } + } + + return { successCount, failCount }; +}