From c7481718e4ddd5d8f910a1d257ab1c6256905f8e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E9=AD=94=E6=96=B9=E7=86=8A=E6=88=90=E7=A5=A5?=
<3615096616@qq.com>
Date: Wed, 29 Apr 2026 21:38:16 +0800
Subject: [PATCH] 1
---
.gitignore | 4 +
index.html | 13 +
package-lock.json | 2996 +++++++++++++++++
package.json | 31 +
src/App.tsx | 12 +
src/apis/apiMallAdmin.ts | 382 +++
src/apis/apisCommon.ts | 23 +
src/apis/apisUser.ts | 36 +
src/apis/base/axiosInstance.ts | 113 +
src/apis/base/types.ts | 47 +
src/components/AuditModal.tsx | 87 +
src/components/JsonPreviewPanel.tsx | 22 +
src/components/StatusTag.tsx | 11 +
src/config/source/config.ts | 29 +
src/index.css | 127 +
src/layouts/RootLayout.tsx | 18 +
src/layouts/auth/AuthLayout.tsx | 32 +
src/layouts/main/MainLayout.tsx | 131 +
src/main.tsx | 121 +
src/pages/admin/banners/index.tsx | 462 +++
src/pages/admin/categories/index.tsx | 385 +++
src/pages/admin/goods/index.tsx | 397 +++
src/pages/admin/homeChannels/index.tsx | 396 +++
.../components/MerchantCreateModal.tsx | 190 ++
src/pages/admin/merchants/index.tsx | 353 ++
src/pages/admin/orders/DetailPage.tsx | 261 ++
src/pages/admin/orders/index.tsx | 328 ++
src/pages/admin/recommendStores/index.tsx | 540 +++
src/pages/admin/searchKeywords/index.tsx | 346 ++
src/pages/admin/stores/index.tsx | 319 ++
src/pages/login/index.tsx | 227 ++
src/routes/components/ProtectedRoute.tsx | 22 +
src/routes/config.tsx | 87 +
src/routes/index.tsx | 7 +
src/store/user.ts | 93 +
src/types/ITokenData.ts | 10 +
src/types/IUserInfo.ts | 6 +
src/types/mall.ts | 513 +++
src/utils/format.ts | 65 +
src/utils/mall.ts | 186 +
src/vite-env.d.ts | 2 +
tsconfig.app.json | 24 +
tsconfig.json | 14 +
tsconfig.node.json | 17 +
vite.config.ts | 45 +
45 files changed, 9530 insertions(+)
create mode 100644 .gitignore
create mode 100644 index.html
create mode 100644 package-lock.json
create mode 100644 package.json
create mode 100644 src/App.tsx
create mode 100644 src/apis/apiMallAdmin.ts
create mode 100644 src/apis/apisCommon.ts
create mode 100644 src/apis/apisUser.ts
create mode 100644 src/apis/base/axiosInstance.ts
create mode 100644 src/apis/base/types.ts
create mode 100644 src/components/AuditModal.tsx
create mode 100644 src/components/JsonPreviewPanel.tsx
create mode 100644 src/components/StatusTag.tsx
create mode 100644 src/config/source/config.ts
create mode 100644 src/index.css
create mode 100644 src/layouts/RootLayout.tsx
create mode 100644 src/layouts/auth/AuthLayout.tsx
create mode 100644 src/layouts/main/MainLayout.tsx
create mode 100644 src/main.tsx
create mode 100644 src/pages/admin/banners/index.tsx
create mode 100644 src/pages/admin/categories/index.tsx
create mode 100644 src/pages/admin/goods/index.tsx
create mode 100644 src/pages/admin/homeChannels/index.tsx
create mode 100644 src/pages/admin/merchants/components/MerchantCreateModal.tsx
create mode 100644 src/pages/admin/merchants/index.tsx
create mode 100644 src/pages/admin/orders/DetailPage.tsx
create mode 100644 src/pages/admin/orders/index.tsx
create mode 100644 src/pages/admin/recommendStores/index.tsx
create mode 100644 src/pages/admin/searchKeywords/index.tsx
create mode 100644 src/pages/admin/stores/index.tsx
create mode 100644 src/pages/login/index.tsx
create mode 100644 src/routes/components/ProtectedRoute.tsx
create mode 100644 src/routes/config.tsx
create mode 100644 src/routes/index.tsx
create mode 100644 src/store/user.ts
create mode 100644 src/types/ITokenData.ts
create mode 100644 src/types/IUserInfo.ts
create mode 100644 src/types/mall.ts
create mode 100644 src/utils/format.ts
create mode 100644 src/utils/mall.ts
create mode 100644 src/vite-env.d.ts
create mode 100644 tsconfig.app.json
create mode 100644 tsconfig.json
create mode 100644 tsconfig.node.json
create mode 100644 vite.config.ts
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..4f3a35a
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,4 @@
+node_modules
+dist
+.DS_Store
+
diff --git a/index.html b/index.html
new file mode 100644
index 0000000..d4fc75b
--- /dev/null
+++ b/index.html
@@ -0,0 +1,13 @@
+
+
+
+
+
+ 商城管理端
+
+
+
+
+
+
+
diff --git a/package-lock.json b/package-lock.json
new file mode 100644
index 0000000..83a4058
--- /dev/null
+++ b/package-lock.json
@@ -0,0 +1,2996 @@
+{
+ "name": "mall-admin",
+ "version": "0.1.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "mall-admin",
+ "version": "0.1.0",
+ "dependencies": {
+ "@ant-design/icons": "^6.0.0",
+ "antd": "^5.19.3",
+ "axios": "^1.9.0",
+ "dayjs": "^1.11.13",
+ "react": "18.2.0",
+ "react-dom": "18.2.0",
+ "react-router-dom": "^7.6.3",
+ "sonner": "^2.0.6",
+ "zustand": "^5.0.6"
+ },
+ "devDependencies": {
+ "@types/node": "^24.0.13",
+ "@types/react": "^18.2.48",
+ "@types/react-dom": "^18.2.18",
+ "@vitejs/plugin-react-swc": "^3.10.2",
+ "typescript": "~5.8.3",
+ "vite": "^7.0.4"
+ }
+ },
+ "node_modules/@ant-design/colors": {
+ "version": "8.0.1",
+ "resolved": "https://registry.npmjs.org/@ant-design/colors/-/colors-8.0.1.tgz",
+ "integrity": "sha512-foPVl0+SWIslGUtD/xBr1p9U4AKzPhNYEseXYRRo5QSzGACYZrQbe11AYJbYfAWnWSpGBx6JjBmSeugUsD9vqQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@ant-design/fast-color": "^3.0.0"
+ }
+ },
+ "node_modules/@ant-design/cssinjs": {
+ "version": "1.24.0",
+ "resolved": "https://registry.npmjs.org/@ant-design/cssinjs/-/cssinjs-1.24.0.tgz",
+ "integrity": "sha512-K4cYrJBsgvL+IoozUXYjbT6LHHNt+19a9zkvpBPxLjFHas1UpPM2A5MlhROb0BT8N8WoavM5VsP9MeSeNK/3mg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.11.1",
+ "@emotion/hash": "^0.8.0",
+ "@emotion/unitless": "^0.7.5",
+ "classnames": "^2.3.1",
+ "csstype": "^3.1.3",
+ "rc-util": "^5.35.0",
+ "stylis": "^4.3.4"
+ },
+ "peerDependencies": {
+ "react": ">=16.0.0",
+ "react-dom": ">=16.0.0"
+ }
+ },
+ "node_modules/@ant-design/cssinjs-utils": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/@ant-design/cssinjs-utils/-/cssinjs-utils-1.1.3.tgz",
+ "integrity": "sha512-nOoQMLW1l+xR1Co8NFVYiP8pZp3VjIIzqV6D6ShYF2ljtdwWJn5WSsH+7kvCktXL/yhEtWURKOfH5Xz/gzlwsg==",
+ "license": "MIT",
+ "dependencies": {
+ "@ant-design/cssinjs": "^1.21.0",
+ "@babel/runtime": "^7.23.2",
+ "rc-util": "^5.38.0"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ }
+ },
+ "node_modules/@ant-design/fast-color": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/@ant-design/fast-color/-/fast-color-3.0.1.tgz",
+ "integrity": "sha512-esKJegpW4nckh0o6kV3Tkb7NPIZYbPnnFxmQDUmL08ukXZAvV85TZBr70eGuke/CIArLaP6aw8lt9KILjnWuOw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8.x"
+ }
+ },
+ "node_modules/@ant-design/icons": {
+ "version": "6.1.1",
+ "resolved": "https://registry.npmjs.org/@ant-design/icons/-/icons-6.1.1.tgz",
+ "integrity": "sha512-AMT4N2y++TZETNHiM77fs4a0uPVCJGuL5MTonk13Pvv7UN7sID1cNEZOc1qNqx6zLKAOilTEFAdAoAFKa0U//Q==",
+ "license": "MIT",
+ "dependencies": {
+ "@ant-design/colors": "^8.0.0",
+ "@ant-design/icons-svg": "^4.4.0",
+ "@rc-component/util": "^1.3.0",
+ "clsx": "^2.1.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "peerDependencies": {
+ "react": ">=16.0.0",
+ "react-dom": ">=16.0.0"
+ }
+ },
+ "node_modules/@ant-design/icons-svg": {
+ "version": "4.4.2",
+ "resolved": "https://registry.npmjs.org/@ant-design/icons-svg/-/icons-svg-4.4.2.tgz",
+ "integrity": "sha512-vHbT+zJEVzllwP+CM+ul7reTEfBR0vgxFe7+lREAsAA7YGsYpboiq2sQNeQeRvh09GfQgs/GyFEvZpJ9cLXpXA==",
+ "license": "MIT"
+ },
+ "node_modules/@ant-design/react-slick": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@ant-design/react-slick/-/react-slick-1.1.2.tgz",
+ "integrity": "sha512-EzlvzE6xQUBrZuuhSAFTdsr4P2bBBHGZwKFemEfq8gIGyIQCxalYfZW/T2ORbtQx5rU69o+WycP3exY/7T1hGA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.10.4",
+ "classnames": "^2.2.5",
+ "json2mq": "^0.2.0",
+ "resize-observer-polyfill": "^1.5.1",
+ "throttle-debounce": "^5.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0"
+ }
+ },
+ "node_modules/@babel/runtime": {
+ "version": "7.29.2",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz",
+ "integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@emotion/hash": {
+ "version": "0.8.0",
+ "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.8.0.tgz",
+ "integrity": "sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==",
+ "license": "MIT"
+ },
+ "node_modules/@emotion/unitless": {
+ "version": "0.7.5",
+ "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz",
+ "integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==",
+ "license": "MIT"
+ },
+ "node_modules/@esbuild/aix-ppc64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.7.tgz",
+ "integrity": "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "aix"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-arm": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.7.tgz",
+ "integrity": "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-arm64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.7.tgz",
+ "integrity": "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-x64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.7.tgz",
+ "integrity": "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/darwin-arm64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.7.tgz",
+ "integrity": "sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/darwin-x64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.7.tgz",
+ "integrity": "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.7.tgz",
+ "integrity": "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/freebsd-x64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.7.tgz",
+ "integrity": "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-arm": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.7.tgz",
+ "integrity": "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-arm64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.7.tgz",
+ "integrity": "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-ia32": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.7.tgz",
+ "integrity": "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-loong64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.7.tgz",
+ "integrity": "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-mips64el": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.7.tgz",
+ "integrity": "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-ppc64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.7.tgz",
+ "integrity": "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-riscv64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.7.tgz",
+ "integrity": "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-s390x": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.7.tgz",
+ "integrity": "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-x64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.7.tgz",
+ "integrity": "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/netbsd-arm64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.7.tgz",
+ "integrity": "sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/netbsd-x64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.7.tgz",
+ "integrity": "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-arm64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.7.tgz",
+ "integrity": "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-x64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.7.tgz",
+ "integrity": "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openharmony-arm64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.7.tgz",
+ "integrity": "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/sunos-x64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.7.tgz",
+ "integrity": "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-arm64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.7.tgz",
+ "integrity": "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-ia32": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.7.tgz",
+ "integrity": "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-x64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.7.tgz",
+ "integrity": "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@rc-component/async-validator": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/@rc-component/async-validator/-/async-validator-5.1.0.tgz",
+ "integrity": "sha512-n4HcR5siNUXRX23nDizbZBQPO0ZM/5oTtmKZ6/eqL0L2bo747cklFdZGRN2f+c9qWGICwDzrhW0H7tE9PptdcA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.24.4"
+ },
+ "engines": {
+ "node": ">=14.x"
+ }
+ },
+ "node_modules/@rc-component/color-picker": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/@rc-component/color-picker/-/color-picker-2.0.1.tgz",
+ "integrity": "sha512-WcZYwAThV/b2GISQ8F+7650r5ZZJ043E57aVBFkQ+kSY4C6wdofXgB0hBx+GPGpIU0Z81eETNoDUJMr7oy/P8Q==",
+ "license": "MIT",
+ "dependencies": {
+ "@ant-design/fast-color": "^2.0.6",
+ "@babel/runtime": "^7.23.6",
+ "classnames": "^2.2.6",
+ "rc-util": "^5.38.1"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ }
+ },
+ "node_modules/@rc-component/color-picker/node_modules/@ant-design/fast-color": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/@ant-design/fast-color/-/fast-color-2.0.6.tgz",
+ "integrity": "sha512-y2217gk4NqL35giHl72o6Zzqji9O7vHh9YmhUVkPtAOpoTCH4uWxo/pr4VE8t0+ChEPs0qo4eJRC5Q1eXWo3vA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.24.7"
+ },
+ "engines": {
+ "node": ">=8.x"
+ }
+ },
+ "node_modules/@rc-component/context": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/@rc-component/context/-/context-1.4.0.tgz",
+ "integrity": "sha512-kFcNxg9oLRMoL3qki0OMxK+7g5mypjgaaJp/pkOis/6rVxma9nJBF/8kCIuTYHUQNr0ii7MxqE33wirPZLJQ2w==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.10.1",
+ "rc-util": "^5.27.0"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ }
+ },
+ "node_modules/@rc-component/mini-decimal": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/@rc-component/mini-decimal/-/mini-decimal-1.1.3.tgz",
+ "integrity": "sha512-bk/FJ09fLf+NLODMAFll6CfYrHPBioTedhW6lxDBuuWucJEqFUd4l/D/5JgIi3dina6sYahB8iuPAZTNz2pMxw==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.18.0"
+ },
+ "engines": {
+ "node": ">=8.x"
+ }
+ },
+ "node_modules/@rc-component/mutate-observer": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@rc-component/mutate-observer/-/mutate-observer-1.1.0.tgz",
+ "integrity": "sha512-QjrOsDXQusNwGZPf4/qRQasg7UFEj06XiCJ8iuiq/Io7CrHrgVi6Uuetw60WAMG1799v+aM8kyc+1L/GBbHSlw==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.18.0",
+ "classnames": "^2.3.2",
+ "rc-util": "^5.24.4"
+ },
+ "engines": {
+ "node": ">=8.x"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ }
+ },
+ "node_modules/@rc-component/portal": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@rc-component/portal/-/portal-1.1.2.tgz",
+ "integrity": "sha512-6f813C0IsasTZms08kfA8kPAGxbbkYToa8ALaiDIGGECU4i9hj8Plgbx0sNJDrey3EtHO30hmdaxtT0138xZcg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.18.0",
+ "classnames": "^2.3.2",
+ "rc-util": "^5.24.4"
+ },
+ "engines": {
+ "node": ">=8.x"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ }
+ },
+ "node_modules/@rc-component/qrcode": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@rc-component/qrcode/-/qrcode-1.1.1.tgz",
+ "integrity": "sha512-LfLGNymzKdUPjXUbRP+xOhIWY4jQ+YMj5MmWAcgcAq1Ij8XP7tRmAXqyuv96XvLUBE/5cA8hLFl9eO1JQMujrA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.24.7"
+ },
+ "engines": {
+ "node": ">=8.x"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ }
+ },
+ "node_modules/@rc-component/tour": {
+ "version": "1.15.1",
+ "resolved": "https://registry.npmjs.org/@rc-component/tour/-/tour-1.15.1.tgz",
+ "integrity": "sha512-Tr2t7J1DKZUpfJuDZWHxyxWpfmj8EZrqSgyMZ+BCdvKZ6r1UDsfU46M/iWAAFBy961Ssfom2kv5f3UcjIL2CmQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.18.0",
+ "@rc-component/portal": "^1.0.0-9",
+ "@rc-component/trigger": "^2.0.0",
+ "classnames": "^2.3.2",
+ "rc-util": "^5.24.4"
+ },
+ "engines": {
+ "node": ">=8.x"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ }
+ },
+ "node_modules/@rc-component/trigger": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/@rc-component/trigger/-/trigger-2.3.1.tgz",
+ "integrity": "sha512-ORENF39PeXTzM+gQEshuk460Z8N4+6DkjpxlpE7Q3gYy1iBpLrx0FOJz3h62ryrJZ/3zCAUIkT1Pb/8hHWpb3A==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.23.2",
+ "@rc-component/portal": "^1.1.0",
+ "classnames": "^2.3.2",
+ "rc-motion": "^2.0.0",
+ "rc-resize-observer": "^1.3.1",
+ "rc-util": "^5.44.0"
+ },
+ "engines": {
+ "node": ">=8.x"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ }
+ },
+ "node_modules/@rc-component/util": {
+ "version": "1.10.1",
+ "resolved": "https://registry.npmjs.org/@rc-component/util/-/util-1.10.1.tgz",
+ "integrity": "sha512-q++9S6rUa5Idb/xIBNz6jtvumw5+O5YV5V0g4iK9mn9jWs4oGJheE3ZN1kAnE723AXyaD8v95yeOASmdk8Jnng==",
+ "license": "MIT",
+ "dependencies": {
+ "is-mobile": "^5.0.0",
+ "react-is": "^18.2.0"
+ },
+ "peerDependencies": {
+ "react": ">=18.0.0",
+ "react-dom": ">=18.0.0"
+ }
+ },
+ "node_modules/@rolldown/pluginutils": {
+ "version": "1.0.0-beta.27",
+ "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz",
+ "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@rollup/rollup-android-arm-eabi": {
+ "version": "4.60.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.2.tgz",
+ "integrity": "sha512-dnlp69efPPg6Uaw2dVqzWRfAWRnYVb1XJ8CyyhIbZeaq4CA5/mLeZ1IEt9QqQxmbdvagjLIm2ZL8BxXv5lH4Yw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-android-arm64": {
+ "version": "4.60.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.2.tgz",
+ "integrity": "sha512-OqZTwDRDchGRHHm/hwLOL7uVPB9aUvI0am/eQuWMNyFHf5PSEQmyEeYYheA0EPPKUO/l0uigCp+iaTjoLjVoHg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-arm64": {
+ "version": "4.60.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.2.tgz",
+ "integrity": "sha512-UwRE7CGpvSVEQS8gUMBe1uADWjNnVgP3Iusyda1nSRwNDCsRjnGc7w6El6WLQsXmZTbLZx9cecegumcitNfpmA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-x64": {
+ "version": "4.60.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.2.tgz",
+ "integrity": "sha512-gjEtURKLCC5VXm1I+2i1u9OhxFsKAQJKTVB8WvDAHF+oZlq0GTVFOlTlO1q3AlCTE/DF32c16ESvfgqR7343/g==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-arm64": {
+ "version": "4.60.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.2.tgz",
+ "integrity": "sha512-Bcl6CYDeAgE70cqZaMojOi/eK63h5Me97ZqAQoh77VPjMysA/4ORQBRGo3rRy45x4MzVlU9uZxs8Uwy7ZaKnBw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-x64": {
+ "version": "4.60.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.2.tgz",
+ "integrity": "sha512-LU+TPda3mAE2QB0/Hp5VyeKJivpC6+tlOXd1VMoXV/YFMvk/MNk5iXeBfB4MQGRWyOYVJ01625vjkr0Az98OJQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
+ "version": "4.60.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.2.tgz",
+ "integrity": "sha512-2QxQrM+KQ7DAW4o22j+XZ6RKdxjLD7BOWTP0Bv0tmjdyhXSsr2Ul1oJDQqh9Zf5qOwTuTc7Ek83mOFaKnodPjg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-musleabihf": {
+ "version": "4.60.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.2.tgz",
+ "integrity": "sha512-TbziEu2DVsTEOPif2mKWkMeDMLoYjx95oESa9fkQQK7r/Orta0gnkcDpzwufEcAO2BLBsD7mZkXGFqEdMRRwfw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-gnu": {
+ "version": "4.60.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.2.tgz",
+ "integrity": "sha512-bO/rVDiDUuM2YfuCUwZ1t1cP+/yqjqz+Xf2VtkdppefuOFS2OSeAfgafaHNkFn0t02hEyXngZkxtGqXcXwO8Rg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-musl": {
+ "version": "4.60.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.2.tgz",
+ "integrity": "sha512-hr26p7e93Rl0Za+JwW7EAnwAvKkehh12BU1Llm9Ykiibg4uIr2rbpxG9WCf56GuvidlTG9KiiQT/TXT1yAWxTA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-loong64-gnu": {
+ "version": "4.60.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.2.tgz",
+ "integrity": "sha512-pOjB/uSIyDt+ow3k/RcLvUAOGpysT2phDn7TTUB3n75SlIgZzM6NKAqlErPhoFU+npgY3/n+2HYIQVbF70P9/A==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-loong64-musl": {
+ "version": "4.60.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.2.tgz",
+ "integrity": "sha512-2/w+q8jszv9Ww1c+6uJT3OwqhdmGP2/4T17cu8WuwyUuuaCDDJ2ojdyYwZzCxx0GcsZBhzi3HmH+J5pZNXnd+Q==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-ppc64-gnu": {
+ "version": "4.60.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.2.tgz",
+ "integrity": "sha512-11+aL5vKheYgczxtPVVRhdptAM2H7fcDR5Gw4/bTcteuZBlH4oP9f5s9zYO9aGZvoGeBpqXI/9TZZihZ609wKw==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-ppc64-musl": {
+ "version": "4.60.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.2.tgz",
+ "integrity": "sha512-i16fokAGK46IVZuV8LIIwMdtqhin9hfYkCh8pf8iC3QU3LpwL+1FSFGej+O7l3E/AoknL6Dclh2oTdnRMpTzFQ==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-gnu": {
+ "version": "4.60.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.2.tgz",
+ "integrity": "sha512-49FkKS6RGQoriDSK/6E2GkAsAuU5kETFCh7pG4yD/ylj9rKhTmO3elsnmBvRD4PgJPds5W2PkhC82aVwmUcJ7A==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-musl": {
+ "version": "4.60.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.2.tgz",
+ "integrity": "sha512-mjYNkHPfGpUR00DuM1ZZIgs64Hpf4bWcz9Z41+4Q+pgDx73UwWdAYyf6EG/lRFldmdHHzgrYyge5akFUW0D3mQ==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-s390x-gnu": {
+ "version": "4.60.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.2.tgz",
+ "integrity": "sha512-ALyvJz965BQk8E9Al/JDKKDLH2kfKFLTGMlgkAbbYtZuJt9LU8DW3ZoDMCtQpXAltZxwBHevXz5u+gf0yA0YoA==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-gnu": {
+ "version": "4.60.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.2.tgz",
+ "integrity": "sha512-UQjrkIdWrKI626Du8lCQ6MJp/6V1LAo2bOK9OTu4mSn8GGXIkPXk/Vsp4bLHCd9Z9Iz2OTEaokUE90VweJgIYQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-musl": {
+ "version": "4.60.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.2.tgz",
+ "integrity": "sha512-bTsRGj6VlSdn/XD4CGyzMnzaBs9bsRxy79eTqTCBsA8TMIEky7qg48aPkvJvFe1HyzQ5oMZdg7AnVlWQSKLTnw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-openbsd-x64": {
+ "version": "4.60.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.2.tgz",
+ "integrity": "sha512-6d4Z3534xitaA1FcMWP7mQPq5zGwBmGbhphh2DwaA1aNIXUu3KTOfwrWpbwI4/Gr0uANo7NTtaykFyO2hPuFLg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-openharmony-arm64": {
+ "version": "4.60.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.2.tgz",
+ "integrity": "sha512-NetAg5iO2uN7eB8zE5qrZ3CSil+7IJt4WDFLcC75Ymywq1VZVD6qJ6EvNLjZ3rEm6gB7XW5JdT60c6MN35Z85Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-arm64-msvc": {
+ "version": "4.60.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.2.tgz",
+ "integrity": "sha512-NCYhOotpgWZ5kdxCZsv6Iudx0wX8980Q/oW4pNFNihpBKsDbEA1zpkfxJGC0yugsUuyDZ7gL37dbzwhR0VI7pQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-ia32-msvc": {
+ "version": "4.60.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.2.tgz",
+ "integrity": "sha512-RXsaOqXxfoUBQoOgvmmijVxJnW2IGB0eoMO7F8FAjaj0UTywUO/luSqimWBJn04WNgUkeNhh7fs7pESXajWmkg==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-gnu": {
+ "version": "4.60.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.2.tgz",
+ "integrity": "sha512-qdAzEULD+/hzObedtmV6iBpdL5TIbKVztGiK7O3/KYSf+HIzU257+MX1EXJcyIiDbMAqmbwaufcYPvyRryeZtA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-msvc": {
+ "version": "4.60.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.2.tgz",
+ "integrity": "sha512-Nd/SgG27WoA9e+/TdK74KnHz852TLa94ovOYySo/yMPuTmpckK/jIF2jSwS3g7ELSKXK13/cVdmg1Z/DaCWKxA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@swc/core": {
+ "version": "1.15.30",
+ "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.15.30.tgz",
+ "integrity": "sha512-R8VQbQY1BZcbIF2p3gjlTCwAQzx1A194ugWfwld5y+WgVVWqVKm7eURGGOVbQVubgKWzidP2agomBbg96rZilQ==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@swc/counter": "^0.1.3",
+ "@swc/types": "^0.1.26"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/swc"
+ },
+ "optionalDependencies": {
+ "@swc/core-darwin-arm64": "1.15.30",
+ "@swc/core-darwin-x64": "1.15.30",
+ "@swc/core-linux-arm-gnueabihf": "1.15.30",
+ "@swc/core-linux-arm64-gnu": "1.15.30",
+ "@swc/core-linux-arm64-musl": "1.15.30",
+ "@swc/core-linux-ppc64-gnu": "1.15.30",
+ "@swc/core-linux-s390x-gnu": "1.15.30",
+ "@swc/core-linux-x64-gnu": "1.15.30",
+ "@swc/core-linux-x64-musl": "1.15.30",
+ "@swc/core-win32-arm64-msvc": "1.15.30",
+ "@swc/core-win32-ia32-msvc": "1.15.30",
+ "@swc/core-win32-x64-msvc": "1.15.30"
+ },
+ "peerDependencies": {
+ "@swc/helpers": ">=0.5.17"
+ },
+ "peerDependenciesMeta": {
+ "@swc/helpers": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@swc/core-darwin-arm64": {
+ "version": "1.15.30",
+ "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.15.30.tgz",
+ "integrity": "sha512-VvpP+vq08HmGYewMWvrdsxh9s2lthz/808zXm8Yu5kaqeR8Yia2b0eYXleHQ3VAjoStUDk6LzTheBW9KXYQdMA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0 AND MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@swc/core-darwin-x64": {
+ "version": "1.15.30",
+ "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.15.30.tgz",
+ "integrity": "sha512-WiJA0hiZI3nwQAO6mu5RqigtWGDtth4Hiq6rbZxAaQyhIcqKIg5IoMRc1Y071lrNJn29eEDMC86Rq58xgUxlDg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0 AND MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@swc/core-linux-arm-gnueabihf": {
+ "version": "1.15.30",
+ "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.15.30.tgz",
+ "integrity": "sha512-YANuFUo48kIT6plJgCD0keae9HFXfjxsbvsgevqc0hr/07X/p7sAWTFOGYEc2SXcASaK7UvuQqzlbW8pr7R79g==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@swc/core-linux-arm64-gnu": {
+ "version": "1.15.30",
+ "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.15.30.tgz",
+ "integrity": "sha512-VndG8jaR4ugY6u+iVOT0Q+d2fZd7sLgjPgN8W/Le+3EbZKl+cRfFxV7Eoz4gfLqhmneZPdcIzf9T3LkgkmqNLg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0 AND MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@swc/core-linux-arm64-musl": {
+ "version": "1.15.30",
+ "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.15.30.tgz",
+ "integrity": "sha512-1SYGs2l0Yyyi0pR/P/NKz/x0kqxkoiw+BXeJjLUdecSk/KasncWlJrc6hOvFSgKHOBrzgM5jwuluKtlT8dnrcA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0 AND MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@swc/core-linux-ppc64-gnu": {
+ "version": "1.15.30",
+ "resolved": "https://registry.npmjs.org/@swc/core-linux-ppc64-gnu/-/core-linux-ppc64-gnu-1.15.30.tgz",
+ "integrity": "sha512-TXREtiXeRhbfDFbmhnkIsXpKfzbfT73YkV2ZF6w0sfxgjC5zI2ZAbaCOq25qxvegofj2K93DtOpm9RLaBgqR2g==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0 AND MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@swc/core-linux-s390x-gnu": {
+ "version": "1.15.30",
+ "resolved": "https://registry.npmjs.org/@swc/core-linux-s390x-gnu/-/core-linux-s390x-gnu-1.15.30.tgz",
+ "integrity": "sha512-DCR2YYeyd6DQE4OuDhImouuNcjXEiEdnn1Y0DyGteugPEDvVuvYk8Xddi+4o2SgWH6jiW8/I+3emZvbep1NC+g==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "Apache-2.0 AND MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@swc/core-linux-x64-gnu": {
+ "version": "1.15.30",
+ "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.15.30.tgz",
+ "integrity": "sha512-5Pizw3NgfOJ5BJOBK8TIRa59xFW2avESTOBDPTAYwZYa1JNDs+KMF9lUfjJiJLM5HiMs/wPheA9eiT0q9m2AoA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0 AND MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@swc/core-linux-x64-musl": {
+ "version": "1.15.30",
+ "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.15.30.tgz",
+ "integrity": "sha512-qyqydP/wyH8alcIP4a2hnGSjHLJjm9H7yDFup+CPy9oTahFgLLwnNcv5UHXqO2Qs3AIND+cls5f/Bb6hqpxdgA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0 AND MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@swc/core-win32-arm64-msvc": {
+ "version": "1.15.30",
+ "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.15.30.tgz",
+ "integrity": "sha512-CaQENgDHVGOg1mSF5sQVgvfFHG9kjMor2rkLMLeLOkfZYNj13ppnJ9+lfaBZLZUMMbnlGQnavCJb8PVBUOso7Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0 AND MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@swc/core-win32-ia32-msvc": {
+ "version": "1.15.30",
+ "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.15.30.tgz",
+ "integrity": "sha512-30VdLeGk6fugiUs/kUdJ/pAg7z/zpvVbR11RH60jZ0Z42WIeIniYx0rLEWN7h/pKJ3CopqsQ3RsogCAkRKiA2g==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "Apache-2.0 AND MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@swc/core-win32-x64-msvc": {
+ "version": "1.15.30",
+ "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.15.30.tgz",
+ "integrity": "sha512-4iObHPR+Q4oDY110EF5SF5eIaaVJNpMdG9C0q3Q92BsJ5y467uHz7sYQhP60WYlLFsLQ1el2YrIPUItUAQGOKg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0 AND MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@swc/counter": {
+ "version": "0.1.3",
+ "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz",
+ "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==",
+ "dev": true,
+ "license": "Apache-2.0"
+ },
+ "node_modules/@swc/types": {
+ "version": "0.1.26",
+ "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.26.tgz",
+ "integrity": "sha512-lyMwd7WGgG79RS7EERZV3T8wMdmPq3xwyg+1nmAM64kIhx5yl+juO2PYIHb7vTiPgPCj8LYjsNV2T5wiQHUEaw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@swc/counter": "^0.1.3"
+ }
+ },
+ "node_modules/@types/estree": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
+ "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/node": {
+ "version": "24.12.2",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-24.12.2.tgz",
+ "integrity": "sha512-A1sre26ke7HDIuY/M23nd9gfB+nrmhtYyMINbjI1zHJxYteKR6qSMX56FsmjMcDb3SMcjJg5BiRRgOCC/yBD0g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "undici-types": "~7.16.0"
+ }
+ },
+ "node_modules/@types/prop-types": {
+ "version": "15.7.15",
+ "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz",
+ "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==",
+ "devOptional": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/react": {
+ "version": "18.3.28",
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.28.tgz",
+ "integrity": "sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/prop-types": "*",
+ "csstype": "^3.2.2"
+ }
+ },
+ "node_modules/@types/react-dom": {
+ "version": "18.3.7",
+ "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz",
+ "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "^18.0.0"
+ }
+ },
+ "node_modules/@vitejs/plugin-react-swc": {
+ "version": "3.11.0",
+ "resolved": "https://registry.npmjs.org/@vitejs/plugin-react-swc/-/plugin-react-swc-3.11.0.tgz",
+ "integrity": "sha512-YTJCGFdNMHCMfjODYtxRNVAYmTWQ1Lb8PulP/2/f/oEEtglw8oKxKIZmmRkyXrVrHfsKOaVkAc3NT9/dMutO5w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@rolldown/pluginutils": "1.0.0-beta.27",
+ "@swc/core": "^1.12.11"
+ },
+ "peerDependencies": {
+ "vite": "^4 || ^5 || ^6 || ^7"
+ }
+ },
+ "node_modules/antd": {
+ "version": "5.29.3",
+ "resolved": "https://registry.npmjs.org/antd/-/antd-5.29.3.tgz",
+ "integrity": "sha512-3DdbGCa9tWAJGcCJ6rzR8EJFsv2CtyEbkVabZE14pfgUHfCicWCj0/QzQVLDYg8CPfQk9BH7fHCoTXHTy7MP/A==",
+ "license": "MIT",
+ "dependencies": {
+ "@ant-design/colors": "^7.2.1",
+ "@ant-design/cssinjs": "^1.23.0",
+ "@ant-design/cssinjs-utils": "^1.1.3",
+ "@ant-design/fast-color": "^2.0.6",
+ "@ant-design/icons": "^5.6.1",
+ "@ant-design/react-slick": "~1.1.2",
+ "@babel/runtime": "^7.26.0",
+ "@rc-component/color-picker": "~2.0.1",
+ "@rc-component/mutate-observer": "^1.1.0",
+ "@rc-component/qrcode": "~1.1.0",
+ "@rc-component/tour": "~1.15.1",
+ "@rc-component/trigger": "^2.3.0",
+ "classnames": "^2.5.1",
+ "copy-to-clipboard": "^3.3.3",
+ "dayjs": "^1.11.11",
+ "rc-cascader": "~3.34.0",
+ "rc-checkbox": "~3.5.0",
+ "rc-collapse": "~3.9.0",
+ "rc-dialog": "~9.6.0",
+ "rc-drawer": "~7.3.0",
+ "rc-dropdown": "~4.2.1",
+ "rc-field-form": "~2.7.1",
+ "rc-image": "~7.12.0",
+ "rc-input": "~1.8.0",
+ "rc-input-number": "~9.5.0",
+ "rc-mentions": "~2.20.0",
+ "rc-menu": "~9.16.1",
+ "rc-motion": "^2.9.5",
+ "rc-notification": "~5.6.4",
+ "rc-pagination": "~5.1.0",
+ "rc-picker": "~4.11.3",
+ "rc-progress": "~4.0.0",
+ "rc-rate": "~2.13.1",
+ "rc-resize-observer": "^1.4.3",
+ "rc-segmented": "~2.7.0",
+ "rc-select": "~14.16.8",
+ "rc-slider": "~11.1.9",
+ "rc-steps": "~6.0.1",
+ "rc-switch": "~4.1.0",
+ "rc-table": "~7.54.0",
+ "rc-tabs": "~15.7.0",
+ "rc-textarea": "~1.10.2",
+ "rc-tooltip": "~6.4.0",
+ "rc-tree": "~5.13.1",
+ "rc-tree-select": "~5.27.0",
+ "rc-upload": "~4.11.0",
+ "rc-util": "^5.44.4",
+ "scroll-into-view-if-needed": "^3.1.0",
+ "throttle-debounce": "^5.0.2"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/ant-design"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ }
+ },
+ "node_modules/antd/node_modules/@ant-design/colors": {
+ "version": "7.2.1",
+ "resolved": "https://registry.npmjs.org/@ant-design/colors/-/colors-7.2.1.tgz",
+ "integrity": "sha512-lCHDcEzieu4GA3n8ELeZ5VQ8pKQAWcGGLRTQ50aQM2iqPpq2evTxER84jfdPvsPAtEcZ7m44NI45edFMo8oOYQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@ant-design/fast-color": "^2.0.6"
+ }
+ },
+ "node_modules/antd/node_modules/@ant-design/fast-color": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/@ant-design/fast-color/-/fast-color-2.0.6.tgz",
+ "integrity": "sha512-y2217gk4NqL35giHl72o6Zzqji9O7vHh9YmhUVkPtAOpoTCH4uWxo/pr4VE8t0+ChEPs0qo4eJRC5Q1eXWo3vA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.24.7"
+ },
+ "engines": {
+ "node": ">=8.x"
+ }
+ },
+ "node_modules/antd/node_modules/@ant-design/icons": {
+ "version": "5.6.1",
+ "resolved": "https://registry.npmjs.org/@ant-design/icons/-/icons-5.6.1.tgz",
+ "integrity": "sha512-0/xS39c91WjPAZOWsvi1//zjx6kAp4kxWwctR6kuU6p133w8RU0D2dSCvZC19uQyharg/sAvYxGYWl01BbZZfg==",
+ "license": "MIT",
+ "dependencies": {
+ "@ant-design/colors": "^7.0.0",
+ "@ant-design/icons-svg": "^4.4.0",
+ "@babel/runtime": "^7.24.8",
+ "classnames": "^2.2.6",
+ "rc-util": "^5.31.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "peerDependencies": {
+ "react": ">=16.0.0",
+ "react-dom": ">=16.0.0"
+ }
+ },
+ "node_modules/asynckit": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
+ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
+ "license": "MIT"
+ },
+ "node_modules/axios": {
+ "version": "1.15.2",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-1.15.2.tgz",
+ "integrity": "sha512-wLrXxPtcrPTsNlJmKjkPnNPK2Ihe0hn0wGSaTEiHRPxwjvJwT3hKmXF4dpqxmPO9SoNb2FsYXj/xEo0gHN+D5A==",
+ "license": "MIT",
+ "dependencies": {
+ "follow-redirects": "^1.15.11",
+ "form-data": "^4.0.5",
+ "proxy-from-env": "^2.1.0"
+ }
+ },
+ "node_modules/call-bind-apply-helpers": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
+ "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/classnames": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz",
+ "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==",
+ "license": "MIT"
+ },
+ "node_modules/clsx": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
+ "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/combined-stream": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
+ "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
+ "license": "MIT",
+ "dependencies": {
+ "delayed-stream": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/compute-scroll-into-view": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-3.1.1.tgz",
+ "integrity": "sha512-VRhuHOLoKYOy4UbilLbUzbYg93XLjv2PncJC50EuTWPA3gaja1UjBsUP/D/9/juV3vQFr6XBEzn9KCAHdUvOHw==",
+ "license": "MIT"
+ },
+ "node_modules/cookie": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz",
+ "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/copy-to-clipboard": {
+ "version": "3.3.3",
+ "resolved": "https://registry.npmjs.org/copy-to-clipboard/-/copy-to-clipboard-3.3.3.tgz",
+ "integrity": "sha512-2KV8NhB5JqC3ky0r9PMCAZKbUHSwtEo4CwCs0KXgruG43gX5PMqDEBbVU4OUzw2MuAWUfsuFmWvEKG5QRfSnJA==",
+ "license": "MIT",
+ "dependencies": {
+ "toggle-selection": "^1.0.6"
+ }
+ },
+ "node_modules/csstype": {
+ "version": "3.2.3",
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
+ "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
+ "license": "MIT"
+ },
+ "node_modules/dayjs": {
+ "version": "1.11.20",
+ "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.20.tgz",
+ "integrity": "sha512-YbwwqR/uYpeoP4pu043q+LTDLFBLApUP6VxRihdfNTqu4ubqMlGDLd6ErXhEgsyvY0K6nCs7nggYumAN+9uEuQ==",
+ "license": "MIT"
+ },
+ "node_modules/delayed-stream": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
+ "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/dunder-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
+ "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "gopd": "^1.2.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-define-property": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
+ "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-errors": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
+ "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-object-atoms": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
+ "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-set-tostringtag": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
+ "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.6",
+ "has-tostringtag": "^1.0.2",
+ "hasown": "^2.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/esbuild": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.7.tgz",
+ "integrity": "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "optionalDependencies": {
+ "@esbuild/aix-ppc64": "0.27.7",
+ "@esbuild/android-arm": "0.27.7",
+ "@esbuild/android-arm64": "0.27.7",
+ "@esbuild/android-x64": "0.27.7",
+ "@esbuild/darwin-arm64": "0.27.7",
+ "@esbuild/darwin-x64": "0.27.7",
+ "@esbuild/freebsd-arm64": "0.27.7",
+ "@esbuild/freebsd-x64": "0.27.7",
+ "@esbuild/linux-arm": "0.27.7",
+ "@esbuild/linux-arm64": "0.27.7",
+ "@esbuild/linux-ia32": "0.27.7",
+ "@esbuild/linux-loong64": "0.27.7",
+ "@esbuild/linux-mips64el": "0.27.7",
+ "@esbuild/linux-ppc64": "0.27.7",
+ "@esbuild/linux-riscv64": "0.27.7",
+ "@esbuild/linux-s390x": "0.27.7",
+ "@esbuild/linux-x64": "0.27.7",
+ "@esbuild/netbsd-arm64": "0.27.7",
+ "@esbuild/netbsd-x64": "0.27.7",
+ "@esbuild/openbsd-arm64": "0.27.7",
+ "@esbuild/openbsd-x64": "0.27.7",
+ "@esbuild/openharmony-arm64": "0.27.7",
+ "@esbuild/sunos-x64": "0.27.7",
+ "@esbuild/win32-arm64": "0.27.7",
+ "@esbuild/win32-ia32": "0.27.7",
+ "@esbuild/win32-x64": "0.27.7"
+ }
+ },
+ "node_modules/fdir": {
+ "version": "6.5.0",
+ "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
+ "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "peerDependencies": {
+ "picomatch": "^3 || ^4"
+ },
+ "peerDependenciesMeta": {
+ "picomatch": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/follow-redirects": {
+ "version": "1.16.0",
+ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz",
+ "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==",
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://github.com/sponsors/RubenVerborgh"
+ }
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">=4.0"
+ },
+ "peerDependenciesMeta": {
+ "debug": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/form-data": {
+ "version": "4.0.5",
+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz",
+ "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==",
+ "license": "MIT",
+ "dependencies": {
+ "asynckit": "^0.4.0",
+ "combined-stream": "^1.0.8",
+ "es-set-tostringtag": "^2.1.0",
+ "hasown": "^2.0.2",
+ "mime-types": "^2.1.12"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/fsevents": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/function-bind": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
+ "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/get-intrinsic": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
+ "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.2",
+ "es-define-property": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "es-object-atoms": "^1.1.1",
+ "function-bind": "^1.1.2",
+ "get-proto": "^1.0.1",
+ "gopd": "^1.2.0",
+ "has-symbols": "^1.1.0",
+ "hasown": "^2.0.2",
+ "math-intrinsics": "^1.1.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/get-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
+ "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
+ "license": "MIT",
+ "dependencies": {
+ "dunder-proto": "^1.0.1",
+ "es-object-atoms": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/gopd": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
+ "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has-symbols": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
+ "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has-tostringtag": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
+ "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
+ "license": "MIT",
+ "dependencies": {
+ "has-symbols": "^1.0.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/hasown": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.3.tgz",
+ "integrity": "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==",
+ "license": "MIT",
+ "dependencies": {
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/is-mobile": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/is-mobile/-/is-mobile-5.0.0.tgz",
+ "integrity": "sha512-Tz/yndySvLAEXh+Uk8liFCxOwVH6YutuR74utvOcu7I9Di+DwM0mtdPVZNaVvvBUM2OXxne/NhOs1zAO7riusQ==",
+ "license": "MIT"
+ },
+ "node_modules/js-tokens": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
+ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
+ "license": "MIT"
+ },
+ "node_modules/json2mq": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/json2mq/-/json2mq-0.2.0.tgz",
+ "integrity": "sha512-SzoRg7ux5DWTII9J2qkrZrqV1gt+rTaoufMxEzXbS26Uid0NwaJd123HcoB80TgubEppxxIGdNxCx50fEoEWQA==",
+ "license": "MIT",
+ "dependencies": {
+ "string-convert": "^0.2.0"
+ }
+ },
+ "node_modules/loose-envify": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
+ "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
+ "license": "MIT",
+ "dependencies": {
+ "js-tokens": "^3.0.0 || ^4.0.0"
+ },
+ "bin": {
+ "loose-envify": "cli.js"
+ }
+ },
+ "node_modules/math-intrinsics": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
+ "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/mime-db": {
+ "version": "1.52.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
+ "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mime-types": {
+ "version": "2.1.35",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
+ "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
+ "license": "MIT",
+ "dependencies": {
+ "mime-db": "1.52.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/nanoid": {
+ "version": "3.3.11",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
+ "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "bin": {
+ "nanoid": "bin/nanoid.cjs"
+ },
+ "engines": {
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+ }
+ },
+ "node_modules/picocolors": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/picomatch": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
+ "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/postcss": {
+ "version": "8.5.10",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.10.tgz",
+ "integrity": "sha512-pMMHxBOZKFU6HgAZ4eyGnwXF/EvPGGqUr0MnZ5+99485wwW41kW91A4LOGxSHhgugZmSChL5AlElNdwlNgcnLQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "nanoid": "^3.3.11",
+ "picocolors": "^1.1.1",
+ "source-map-js": "^1.2.1"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/proxy-from-env": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz",
+ "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/rc-cascader": {
+ "version": "3.34.0",
+ "resolved": "https://registry.npmjs.org/rc-cascader/-/rc-cascader-3.34.0.tgz",
+ "integrity": "sha512-KpXypcvju9ptjW9FaN2NFcA2QH9E9LHKq169Y0eWtH4e/wHQ5Wh5qZakAgvb8EKZ736WZ3B0zLLOBsrsja5Dag==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.25.7",
+ "classnames": "^2.3.1",
+ "rc-select": "~14.16.2",
+ "rc-tree": "~5.13.0",
+ "rc-util": "^5.43.0"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ }
+ },
+ "node_modules/rc-checkbox": {
+ "version": "3.5.0",
+ "resolved": "https://registry.npmjs.org/rc-checkbox/-/rc-checkbox-3.5.0.tgz",
+ "integrity": "sha512-aOAQc3E98HteIIsSqm6Xk2FPKIER6+5vyEFMZfo73TqM+VVAIqOkHoPjgKLqSNtVLWScoaM7vY2ZrGEheI79yg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.10.1",
+ "classnames": "^2.3.2",
+ "rc-util": "^5.25.2"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ }
+ },
+ "node_modules/rc-collapse": {
+ "version": "3.9.0",
+ "resolved": "https://registry.npmjs.org/rc-collapse/-/rc-collapse-3.9.0.tgz",
+ "integrity": "sha512-swDdz4QZ4dFTo4RAUMLL50qP0EY62N2kvmk2We5xYdRwcRn8WcYtuetCJpwpaCbUfUt5+huLpVxhvmnK+PHrkA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.10.1",
+ "classnames": "2.x",
+ "rc-motion": "^2.3.4",
+ "rc-util": "^5.27.0"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ }
+ },
+ "node_modules/rc-dialog": {
+ "version": "9.6.0",
+ "resolved": "https://registry.npmjs.org/rc-dialog/-/rc-dialog-9.6.0.tgz",
+ "integrity": "sha512-ApoVi9Z8PaCQg6FsUzS8yvBEQy0ZL2PkuvAgrmohPkN3okps5WZ5WQWPc1RNuiOKaAYv8B97ACdsFU5LizzCqg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.10.1",
+ "@rc-component/portal": "^1.0.0-8",
+ "classnames": "^2.2.6",
+ "rc-motion": "^2.3.0",
+ "rc-util": "^5.21.0"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ }
+ },
+ "node_modules/rc-drawer": {
+ "version": "7.3.0",
+ "resolved": "https://registry.npmjs.org/rc-drawer/-/rc-drawer-7.3.0.tgz",
+ "integrity": "sha512-DX6CIgiBWNpJIMGFO8BAISFkxiuKitoizooj4BDyee8/SnBn0zwO2FHrNDpqqepj0E/TFTDpmEBCyFuTgC7MOg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.23.9",
+ "@rc-component/portal": "^1.1.1",
+ "classnames": "^2.2.6",
+ "rc-motion": "^2.6.1",
+ "rc-util": "^5.38.1"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ }
+ },
+ "node_modules/rc-dropdown": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/rc-dropdown/-/rc-dropdown-4.2.1.tgz",
+ "integrity": "sha512-YDAlXsPv3I1n42dv1JpdM7wJ+gSUBfeyPK59ZpBD9jQhK9jVuxpjj3NmWQHOBceA1zEPVX84T2wbdb2SD0UjmA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.18.3",
+ "@rc-component/trigger": "^2.0.0",
+ "classnames": "^2.2.6",
+ "rc-util": "^5.44.1"
+ },
+ "peerDependencies": {
+ "react": ">=16.11.0",
+ "react-dom": ">=16.11.0"
+ }
+ },
+ "node_modules/rc-field-form": {
+ "version": "2.7.1",
+ "resolved": "https://registry.npmjs.org/rc-field-form/-/rc-field-form-2.7.1.tgz",
+ "integrity": "sha512-vKeSifSJ6HoLaAB+B8aq/Qgm8a3dyxROzCtKNCsBQgiverpc4kWDQihoUwzUj+zNWJOykwSY4dNX3QrGwtVb9A==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.18.0",
+ "@rc-component/async-validator": "^5.0.3",
+ "rc-util": "^5.32.2"
+ },
+ "engines": {
+ "node": ">=8.x"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ }
+ },
+ "node_modules/rc-image": {
+ "version": "7.12.0",
+ "resolved": "https://registry.npmjs.org/rc-image/-/rc-image-7.12.0.tgz",
+ "integrity": "sha512-cZ3HTyyckPnNnUb9/DRqduqzLfrQRyi+CdHjdqgsyDpI3Ln5UX1kXnAhPBSJj9pVRzwRFgqkN7p9b6HBDjmu/Q==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.11.2",
+ "@rc-component/portal": "^1.0.2",
+ "classnames": "^2.2.6",
+ "rc-dialog": "~9.6.0",
+ "rc-motion": "^2.6.2",
+ "rc-util": "^5.34.1"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ }
+ },
+ "node_modules/rc-input": {
+ "version": "1.8.0",
+ "resolved": "https://registry.npmjs.org/rc-input/-/rc-input-1.8.0.tgz",
+ "integrity": "sha512-KXvaTbX+7ha8a/k+eg6SYRVERK0NddX8QX7a7AnRvUa/rEH0CNMlpcBzBkhI0wp2C8C4HlMoYl8TImSN+fuHKA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.11.1",
+ "classnames": "^2.2.1",
+ "rc-util": "^5.18.1"
+ },
+ "peerDependencies": {
+ "react": ">=16.0.0",
+ "react-dom": ">=16.0.0"
+ }
+ },
+ "node_modules/rc-input-number": {
+ "version": "9.5.0",
+ "resolved": "https://registry.npmjs.org/rc-input-number/-/rc-input-number-9.5.0.tgz",
+ "integrity": "sha512-bKaEvB5tHebUURAEXw35LDcnRZLq3x1k7GxfAqBMzmpHkDGzjAtnUL8y4y5N15rIFIg5IJgwr211jInl3cipag==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.10.1",
+ "@rc-component/mini-decimal": "^1.0.1",
+ "classnames": "^2.2.5",
+ "rc-input": "~1.8.0",
+ "rc-util": "^5.40.1"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ }
+ },
+ "node_modules/rc-mentions": {
+ "version": "2.20.0",
+ "resolved": "https://registry.npmjs.org/rc-mentions/-/rc-mentions-2.20.0.tgz",
+ "integrity": "sha512-w8HCMZEh3f0nR8ZEd466ATqmXFCMGMN5UFCzEUL0bM/nGw/wOS2GgRzKBcm19K++jDyuWCOJOdgcKGXU3fXfbQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.22.5",
+ "@rc-component/trigger": "^2.0.0",
+ "classnames": "^2.2.6",
+ "rc-input": "~1.8.0",
+ "rc-menu": "~9.16.0",
+ "rc-textarea": "~1.10.0",
+ "rc-util": "^5.34.1"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ }
+ },
+ "node_modules/rc-menu": {
+ "version": "9.16.1",
+ "resolved": "https://registry.npmjs.org/rc-menu/-/rc-menu-9.16.1.tgz",
+ "integrity": "sha512-ghHx6/6Dvp+fw8CJhDUHFHDJ84hJE3BXNCzSgLdmNiFErWSOaZNsihDAsKq9ByTALo/xkNIwtDFGIl6r+RPXBg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.10.1",
+ "@rc-component/trigger": "^2.0.0",
+ "classnames": "2.x",
+ "rc-motion": "^2.4.3",
+ "rc-overflow": "^1.3.1",
+ "rc-util": "^5.27.0"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ }
+ },
+ "node_modules/rc-motion": {
+ "version": "2.9.5",
+ "resolved": "https://registry.npmjs.org/rc-motion/-/rc-motion-2.9.5.tgz",
+ "integrity": "sha512-w+XTUrfh7ArbYEd2582uDrEhmBHwK1ZENJiSJVb7uRxdE7qJSYjbO2eksRXmndqyKqKoYPc9ClpPh5242mV1vA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.11.1",
+ "classnames": "^2.2.1",
+ "rc-util": "^5.44.0"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ }
+ },
+ "node_modules/rc-notification": {
+ "version": "5.6.4",
+ "resolved": "https://registry.npmjs.org/rc-notification/-/rc-notification-5.6.4.tgz",
+ "integrity": "sha512-KcS4O6B4qzM3KH7lkwOB7ooLPZ4b6J+VMmQgT51VZCeEcmghdeR4IrMcFq0LG+RPdnbe/ArT086tGM8Snimgiw==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.10.1",
+ "classnames": "2.x",
+ "rc-motion": "^2.9.0",
+ "rc-util": "^5.20.1"
+ },
+ "engines": {
+ "node": ">=8.x"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ }
+ },
+ "node_modules/rc-overflow": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/rc-overflow/-/rc-overflow-1.5.0.tgz",
+ "integrity": "sha512-Lm/v9h0LymeUYJf0x39OveU52InkdRXqnn2aYXfWmo8WdOonIKB2kfau+GF0fWq6jPgtdO9yMqveGcK6aIhJmg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.11.1",
+ "classnames": "^2.2.1",
+ "rc-resize-observer": "^1.0.0",
+ "rc-util": "^5.37.0"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ }
+ },
+ "node_modules/rc-pagination": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/rc-pagination/-/rc-pagination-5.1.0.tgz",
+ "integrity": "sha512-8416Yip/+eclTFdHXLKTxZvn70duYVGTvUUWbckCCZoIl3jagqke3GLsFrMs0bsQBikiYpZLD9206Ej4SOdOXQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.10.1",
+ "classnames": "^2.3.2",
+ "rc-util": "^5.38.0"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ }
+ },
+ "node_modules/rc-picker": {
+ "version": "4.11.3",
+ "resolved": "https://registry.npmjs.org/rc-picker/-/rc-picker-4.11.3.tgz",
+ "integrity": "sha512-MJ5teb7FlNE0NFHTncxXQ62Y5lytq6sh5nUw0iH8OkHL/TjARSEvSHpr940pWgjGANpjCwyMdvsEV55l5tYNSg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.24.7",
+ "@rc-component/trigger": "^2.0.0",
+ "classnames": "^2.2.1",
+ "rc-overflow": "^1.3.2",
+ "rc-resize-observer": "^1.4.0",
+ "rc-util": "^5.43.0"
+ },
+ "engines": {
+ "node": ">=8.x"
+ },
+ "peerDependencies": {
+ "date-fns": ">= 2.x",
+ "dayjs": ">= 1.x",
+ "luxon": ">= 3.x",
+ "moment": ">= 2.x",
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ },
+ "peerDependenciesMeta": {
+ "date-fns": {
+ "optional": true
+ },
+ "dayjs": {
+ "optional": true
+ },
+ "luxon": {
+ "optional": true
+ },
+ "moment": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/rc-progress": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/rc-progress/-/rc-progress-4.0.0.tgz",
+ "integrity": "sha512-oofVMMafOCokIUIBnZLNcOZFsABaUw8PPrf1/y0ZBvKZNpOiu5h4AO9vv11Sw0p4Hb3D0yGWuEattcQGtNJ/aw==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.10.1",
+ "classnames": "^2.2.6",
+ "rc-util": "^5.16.1"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ }
+ },
+ "node_modules/rc-rate": {
+ "version": "2.13.1",
+ "resolved": "https://registry.npmjs.org/rc-rate/-/rc-rate-2.13.1.tgz",
+ "integrity": "sha512-QUhQ9ivQ8Gy7mtMZPAjLbxBt5y9GRp65VcUyGUMF3N3fhiftivPHdpuDIaWIMOTEprAjZPC08bls1dQB+I1F2Q==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.10.1",
+ "classnames": "^2.2.5",
+ "rc-util": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8.x"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ }
+ },
+ "node_modules/rc-resize-observer": {
+ "version": "1.4.3",
+ "resolved": "https://registry.npmjs.org/rc-resize-observer/-/rc-resize-observer-1.4.3.tgz",
+ "integrity": "sha512-YZLjUbyIWox8E9i9C3Tm7ia+W7euPItNWSPX5sCcQTYbnwDb5uNpnLHQCG1f22oZWUhLw4Mv2tFmeWe68CDQRQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.20.7",
+ "classnames": "^2.2.1",
+ "rc-util": "^5.44.1",
+ "resize-observer-polyfill": "^1.5.1"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ }
+ },
+ "node_modules/rc-segmented": {
+ "version": "2.7.1",
+ "resolved": "https://registry.npmjs.org/rc-segmented/-/rc-segmented-2.7.1.tgz",
+ "integrity": "sha512-izj1Nw/Dw2Vb7EVr+D/E9lUTkBe+kKC+SAFSU9zqr7WV2W5Ktaa9Gc7cB2jTqgk8GROJayltaec+DBlYKc6d+g==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.11.1",
+ "classnames": "^2.2.1",
+ "rc-motion": "^2.4.4",
+ "rc-util": "^5.17.0"
+ },
+ "peerDependencies": {
+ "react": ">=16.0.0",
+ "react-dom": ">=16.0.0"
+ }
+ },
+ "node_modules/rc-select": {
+ "version": "14.16.8",
+ "resolved": "https://registry.npmjs.org/rc-select/-/rc-select-14.16.8.tgz",
+ "integrity": "sha512-NOV5BZa1wZrsdkKaiK7LHRuo5ZjZYMDxPP6/1+09+FB4KoNi8jcG1ZqLE3AVCxEsYMBe65OBx71wFoHRTP3LRg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.10.1",
+ "@rc-component/trigger": "^2.1.1",
+ "classnames": "2.x",
+ "rc-motion": "^2.0.1",
+ "rc-overflow": "^1.3.1",
+ "rc-util": "^5.16.1",
+ "rc-virtual-list": "^3.5.2"
+ },
+ "engines": {
+ "node": ">=8.x"
+ },
+ "peerDependencies": {
+ "react": "*",
+ "react-dom": "*"
+ }
+ },
+ "node_modules/rc-slider": {
+ "version": "11.1.9",
+ "resolved": "https://registry.npmjs.org/rc-slider/-/rc-slider-11.1.9.tgz",
+ "integrity": "sha512-h8IknhzSh3FEM9u8ivkskh+Ef4Yo4JRIY2nj7MrH6GQmrwV6mcpJf5/4KgH5JaVI1H3E52yCdpOlVyGZIeph5A==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.10.1",
+ "classnames": "^2.2.5",
+ "rc-util": "^5.36.0"
+ },
+ "engines": {
+ "node": ">=8.x"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ }
+ },
+ "node_modules/rc-steps": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/rc-steps/-/rc-steps-6.0.1.tgz",
+ "integrity": "sha512-lKHL+Sny0SeHkQKKDJlAjV5oZ8DwCdS2hFhAkIjuQt1/pB81M0cA0ErVFdHq9+jmPmFw1vJB2F5NBzFXLJxV+g==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.16.7",
+ "classnames": "^2.2.3",
+ "rc-util": "^5.16.1"
+ },
+ "engines": {
+ "node": ">=8.x"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ }
+ },
+ "node_modules/rc-switch": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/rc-switch/-/rc-switch-4.1.0.tgz",
+ "integrity": "sha512-TI8ufP2Az9oEbvyCeVE4+90PDSljGyuwix3fV58p7HV2o4wBnVToEyomJRVyTaZeqNPAp+vqeo4Wnj5u0ZZQBg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.21.0",
+ "classnames": "^2.2.1",
+ "rc-util": "^5.30.0"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ }
+ },
+ "node_modules/rc-table": {
+ "version": "7.54.0",
+ "resolved": "https://registry.npmjs.org/rc-table/-/rc-table-7.54.0.tgz",
+ "integrity": "sha512-/wDTkki6wBTjwylwAGjpLKYklKo9YgjZwAU77+7ME5mBoS32Q4nAwoqhA2lSge6fobLW3Tap6uc5xfwaL2p0Sw==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.10.1",
+ "@rc-component/context": "^1.4.0",
+ "classnames": "^2.2.5",
+ "rc-resize-observer": "^1.1.0",
+ "rc-util": "^5.44.3",
+ "rc-virtual-list": "^3.14.2"
+ },
+ "engines": {
+ "node": ">=8.x"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ }
+ },
+ "node_modules/rc-tabs": {
+ "version": "15.7.0",
+ "resolved": "https://registry.npmjs.org/rc-tabs/-/rc-tabs-15.7.0.tgz",
+ "integrity": "sha512-ZepiE+6fmozYdWf/9gVp7k56PKHB1YYoDsKeQA1CBlJ/POIhjkcYiv0AGP0w2Jhzftd3AVvZP/K+V+Lpi2ankA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.11.2",
+ "classnames": "2.x",
+ "rc-dropdown": "~4.2.0",
+ "rc-menu": "~9.16.0",
+ "rc-motion": "^2.6.2",
+ "rc-resize-observer": "^1.0.0",
+ "rc-util": "^5.34.1"
+ },
+ "engines": {
+ "node": ">=8.x"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ }
+ },
+ "node_modules/rc-textarea": {
+ "version": "1.10.2",
+ "resolved": "https://registry.npmjs.org/rc-textarea/-/rc-textarea-1.10.2.tgz",
+ "integrity": "sha512-HfaeXiaSlpiSp0I/pvWpecFEHpVysZ9tpDLNkxQbMvMz6gsr7aVZ7FpWP9kt4t7DB+jJXesYS0us1uPZnlRnwQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.10.1",
+ "classnames": "^2.2.1",
+ "rc-input": "~1.8.0",
+ "rc-resize-observer": "^1.0.0",
+ "rc-util": "^5.27.0"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ }
+ },
+ "node_modules/rc-tooltip": {
+ "version": "6.4.0",
+ "resolved": "https://registry.npmjs.org/rc-tooltip/-/rc-tooltip-6.4.0.tgz",
+ "integrity": "sha512-kqyivim5cp8I5RkHmpsp1Nn/Wk+1oeloMv9c7LXNgDxUpGm+RbXJGL+OPvDlcRnx9DBeOe4wyOIl4OKUERyH1g==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.11.2",
+ "@rc-component/trigger": "^2.0.0",
+ "classnames": "^2.3.1",
+ "rc-util": "^5.44.3"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ }
+ },
+ "node_modules/rc-tree": {
+ "version": "5.13.1",
+ "resolved": "https://registry.npmjs.org/rc-tree/-/rc-tree-5.13.1.tgz",
+ "integrity": "sha512-FNhIefhftobCdUJshO7M8uZTA9F4OPGVXqGfZkkD/5soDeOhwO06T/aKTrg0WD8gRg/pyfq+ql3aMymLHCTC4A==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.10.1",
+ "classnames": "2.x",
+ "rc-motion": "^2.0.1",
+ "rc-util": "^5.16.1",
+ "rc-virtual-list": "^3.5.1"
+ },
+ "engines": {
+ "node": ">=10.x"
+ },
+ "peerDependencies": {
+ "react": "*",
+ "react-dom": "*"
+ }
+ },
+ "node_modules/rc-tree-select": {
+ "version": "5.27.0",
+ "resolved": "https://registry.npmjs.org/rc-tree-select/-/rc-tree-select-5.27.0.tgz",
+ "integrity": "sha512-2qTBTzwIT7LRI1o7zLyrCzmo5tQanmyGbSaGTIf7sYimCklAToVVfpMC6OAldSKolcnjorBYPNSKQqJmN3TCww==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.25.7",
+ "classnames": "2.x",
+ "rc-select": "~14.16.2",
+ "rc-tree": "~5.13.0",
+ "rc-util": "^5.43.0"
+ },
+ "peerDependencies": {
+ "react": "*",
+ "react-dom": "*"
+ }
+ },
+ "node_modules/rc-upload": {
+ "version": "4.11.0",
+ "resolved": "https://registry.npmjs.org/rc-upload/-/rc-upload-4.11.0.tgz",
+ "integrity": "sha512-ZUyT//2JAehfHzjWowqROcwYJKnZkIUGWaTE/VogVrepSl7AFNbQf4+zGfX4zl9Vrj/Jm8scLO0R6UlPDKK4wA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.18.3",
+ "classnames": "^2.2.5",
+ "rc-util": "^5.2.0"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ }
+ },
+ "node_modules/rc-util": {
+ "version": "5.44.4",
+ "resolved": "https://registry.npmjs.org/rc-util/-/rc-util-5.44.4.tgz",
+ "integrity": "sha512-resueRJzmHG9Q6rI/DfK6Kdv9/Lfls05vzMs1Sk3M2P+3cJa+MakaZyWY8IPfehVuhPJFKrIY1IK4GqbiaiY5w==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.18.3",
+ "react-is": "^18.2.0"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ }
+ },
+ "node_modules/rc-virtual-list": {
+ "version": "3.19.2",
+ "resolved": "https://registry.npmjs.org/rc-virtual-list/-/rc-virtual-list-3.19.2.tgz",
+ "integrity": "sha512-Ys6NcjwGkuwkeaWBDqfI3xWuZ7rDiQXlH1o2zLfFzATfEgXcqpk8CkgMfbJD81McqjcJVez25a3kPxCR807evA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.20.0",
+ "classnames": "^2.2.6",
+ "rc-resize-observer": "^1.0.0",
+ "rc-util": "^5.36.0"
+ },
+ "engines": {
+ "node": ">=8.x"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ }
+ },
+ "node_modules/react": {
+ "version": "18.2.0",
+ "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz",
+ "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==",
+ "license": "MIT",
+ "dependencies": {
+ "loose-envify": "^1.1.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/react-dom": {
+ "version": "18.2.0",
+ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz",
+ "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==",
+ "license": "MIT",
+ "dependencies": {
+ "loose-envify": "^1.1.0",
+ "scheduler": "^0.23.0"
+ },
+ "peerDependencies": {
+ "react": "^18.2.0"
+ }
+ },
+ "node_modules/react-is": {
+ "version": "18.3.1",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
+ "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==",
+ "license": "MIT"
+ },
+ "node_modules/react-router": {
+ "version": "7.14.2",
+ "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.14.2.tgz",
+ "integrity": "sha512-yCqNne6I8IB6rVCH7XUvlBK7/QKyqypBFGv+8dj4QBFJiiRX+FG7/nkdAvGElyvVZ/HQP5N19wzteuTARXi5Gw==",
+ "license": "MIT",
+ "dependencies": {
+ "cookie": "^1.0.1",
+ "set-cookie-parser": "^2.6.0"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=18",
+ "react-dom": ">=18"
+ },
+ "peerDependenciesMeta": {
+ "react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/react-router-dom": {
+ "version": "7.14.2",
+ "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.14.2.tgz",
+ "integrity": "sha512-YZcM5ES8jJSM+KrJ9BdvHHqlnGTg5tH3sC5ChFRj4inosKctdyzBDhOyyHdGk597q2OT6NTrCA1OvB/YDwfekQ==",
+ "license": "MIT",
+ "dependencies": {
+ "react-router": "7.14.2"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=18",
+ "react-dom": ">=18"
+ }
+ },
+ "node_modules/resize-observer-polyfill": {
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz",
+ "integrity": "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==",
+ "license": "MIT"
+ },
+ "node_modules/rollup": {
+ "version": "4.60.2",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.2.tgz",
+ "integrity": "sha512-J9qZyW++QK/09NyN/zeO0dG/1GdGfyp9lV8ajHnRVLfo/uFsbji5mHnDgn/qYdUHyCkM2N+8VyspgZclfAh0eQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "1.0.8"
+ },
+ "bin": {
+ "rollup": "dist/bin/rollup"
+ },
+ "engines": {
+ "node": ">=18.0.0",
+ "npm": ">=8.0.0"
+ },
+ "optionalDependencies": {
+ "@rollup/rollup-android-arm-eabi": "4.60.2",
+ "@rollup/rollup-android-arm64": "4.60.2",
+ "@rollup/rollup-darwin-arm64": "4.60.2",
+ "@rollup/rollup-darwin-x64": "4.60.2",
+ "@rollup/rollup-freebsd-arm64": "4.60.2",
+ "@rollup/rollup-freebsd-x64": "4.60.2",
+ "@rollup/rollup-linux-arm-gnueabihf": "4.60.2",
+ "@rollup/rollup-linux-arm-musleabihf": "4.60.2",
+ "@rollup/rollup-linux-arm64-gnu": "4.60.2",
+ "@rollup/rollup-linux-arm64-musl": "4.60.2",
+ "@rollup/rollup-linux-loong64-gnu": "4.60.2",
+ "@rollup/rollup-linux-loong64-musl": "4.60.2",
+ "@rollup/rollup-linux-ppc64-gnu": "4.60.2",
+ "@rollup/rollup-linux-ppc64-musl": "4.60.2",
+ "@rollup/rollup-linux-riscv64-gnu": "4.60.2",
+ "@rollup/rollup-linux-riscv64-musl": "4.60.2",
+ "@rollup/rollup-linux-s390x-gnu": "4.60.2",
+ "@rollup/rollup-linux-x64-gnu": "4.60.2",
+ "@rollup/rollup-linux-x64-musl": "4.60.2",
+ "@rollup/rollup-openbsd-x64": "4.60.2",
+ "@rollup/rollup-openharmony-arm64": "4.60.2",
+ "@rollup/rollup-win32-arm64-msvc": "4.60.2",
+ "@rollup/rollup-win32-ia32-msvc": "4.60.2",
+ "@rollup/rollup-win32-x64-gnu": "4.60.2",
+ "@rollup/rollup-win32-x64-msvc": "4.60.2",
+ "fsevents": "~2.3.2"
+ }
+ },
+ "node_modules/scheduler": {
+ "version": "0.23.2",
+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz",
+ "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==",
+ "license": "MIT",
+ "dependencies": {
+ "loose-envify": "^1.1.0"
+ }
+ },
+ "node_modules/scroll-into-view-if-needed": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/scroll-into-view-if-needed/-/scroll-into-view-if-needed-3.1.0.tgz",
+ "integrity": "sha512-49oNpRjWRvnU8NyGVmUaYG4jtTkNonFZI86MmGRDqBphEK2EXT9gdEUoQPZhuBM8yWHxCWbobltqYO5M4XrUvQ==",
+ "license": "MIT",
+ "dependencies": {
+ "compute-scroll-into-view": "^3.0.2"
+ }
+ },
+ "node_modules/set-cookie-parser": {
+ "version": "2.7.2",
+ "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz",
+ "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==",
+ "license": "MIT"
+ },
+ "node_modules/sonner": {
+ "version": "2.0.7",
+ "resolved": "https://registry.npmjs.org/sonner/-/sonner-2.0.7.tgz",
+ "integrity": "sha512-W6ZN4p58k8aDKA4XPcx2hpIQXBRAgyiWVkYhT7CvK6D3iAu7xjvVyhQHg2/iaKJZ1XVJ4r7XuwGL+WGEK37i9w==",
+ "license": "MIT",
+ "peerDependencies": {
+ "react": "^18.0.0 || ^19.0.0 || ^19.0.0-rc",
+ "react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-rc"
+ }
+ },
+ "node_modules/source-map-js": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
+ "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/string-convert": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/string-convert/-/string-convert-0.2.1.tgz",
+ "integrity": "sha512-u/1tdPl4yQnPBjnVrmdLo9gtuLvELKsAoRapekWggdiQNvvvum+jYF329d84NAa660KQw7pB2n36KrIKVoXa3A==",
+ "license": "MIT"
+ },
+ "node_modules/stylis": {
+ "version": "4.4.0",
+ "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.4.0.tgz",
+ "integrity": "sha512-5Z9ZpRzfuH6l/UAvCPAPUo3665Nk2wLaZU3x+TLHKVzIz33+sbJqbtrYoC3KD4/uVOr2Zp+L0LySezP9OHV9yA==",
+ "license": "MIT"
+ },
+ "node_modules/throttle-debounce": {
+ "version": "5.0.2",
+ "resolved": "https://registry.npmjs.org/throttle-debounce/-/throttle-debounce-5.0.2.tgz",
+ "integrity": "sha512-B71/4oyj61iNH0KeCamLuE2rmKuTO5byTOSVwECM5FA7TiAiAW+UqTKZ9ERueC4qvgSttUhdmq1mXC3kJqGX7A==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.22"
+ }
+ },
+ "node_modules/tinyglobby": {
+ "version": "0.2.16",
+ "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz",
+ "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fdir": "^6.5.0",
+ "picomatch": "^4.0.4"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/SuperchupuDev"
+ }
+ },
+ "node_modules/toggle-selection": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/toggle-selection/-/toggle-selection-1.0.6.tgz",
+ "integrity": "sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ==",
+ "license": "MIT"
+ },
+ "node_modules/typescript": {
+ "version": "5.8.3",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz",
+ "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=14.17"
+ }
+ },
+ "node_modules/undici-types": {
+ "version": "7.16.0",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz",
+ "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/vite": {
+ "version": "7.3.2",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.2.tgz",
+ "integrity": "sha512-Bby3NOsna2jsjfLVOHKes8sGwgl4TT0E6vvpYgnAYDIF/tie7MRaFthmKuHx1NSXjiTueXH3do80FMQgvEktRg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "esbuild": "^0.27.0",
+ "fdir": "^6.5.0",
+ "picomatch": "^4.0.3",
+ "postcss": "^8.5.6",
+ "rollup": "^4.43.0",
+ "tinyglobby": "^0.2.15"
+ },
+ "bin": {
+ "vite": "bin/vite.js"
+ },
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ },
+ "funding": {
+ "url": "https://github.com/vitejs/vite?sponsor=1"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.3"
+ },
+ "peerDependencies": {
+ "@types/node": "^20.19.0 || >=22.12.0",
+ "jiti": ">=1.21.0",
+ "less": "^4.0.0",
+ "lightningcss": "^1.21.0",
+ "sass": "^1.70.0",
+ "sass-embedded": "^1.70.0",
+ "stylus": ">=0.54.8",
+ "sugarss": "^5.0.0",
+ "terser": "^5.16.0",
+ "tsx": "^4.8.1",
+ "yaml": "^2.4.2"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ },
+ "jiti": {
+ "optional": true
+ },
+ "less": {
+ "optional": true
+ },
+ "lightningcss": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ },
+ "sass-embedded": {
+ "optional": true
+ },
+ "stylus": {
+ "optional": true
+ },
+ "sugarss": {
+ "optional": true
+ },
+ "terser": {
+ "optional": true
+ },
+ "tsx": {
+ "optional": true
+ },
+ "yaml": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/zustand": {
+ "version": "5.0.12",
+ "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.12.tgz",
+ "integrity": "sha512-i77ae3aZq4dhMlRhJVCYgMLKuSiZAaUPAct2AksxQ+gOtimhGMdXljRT21P5BNpeT4kXlLIckvkPM029OljD7g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.20.0"
+ },
+ "peerDependencies": {
+ "@types/react": ">=18.0.0",
+ "immer": ">=9.0.6",
+ "react": ">=18.0.0",
+ "use-sync-external-store": ">=1.2.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "immer": {
+ "optional": true
+ },
+ "react": {
+ "optional": true
+ },
+ "use-sync-external-store": {
+ "optional": true
+ }
+ }
+ }
+ }
+}
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..355c063
--- /dev/null
+++ b/package.json
@@ -0,0 +1,31 @@
+{
+ "name": "mall-admin",
+ "private": true,
+ "version": "0.1.0",
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "tsc -b && vite build",
+ "preview": "vite preview"
+ },
+ "dependencies": {
+ "@ant-design/icons": "^6.0.0",
+ "antd": "^5.19.3",
+ "axios": "^1.9.0",
+ "dayjs": "^1.11.13",
+ "react": "18.2.0",
+ "react-dom": "18.2.0",
+ "react-router-dom": "^7.6.3",
+ "sonner": "^2.0.6",
+ "zustand": "^5.0.6"
+ },
+ "devDependencies": {
+ "@types/node": "^24.0.13",
+ "@types/react": "^18.2.48",
+ "@types/react-dom": "^18.2.18",
+ "@vitejs/plugin-react-swc": "^3.10.2",
+ "typescript": "~5.8.3",
+ "vite": "^7.0.4"
+ }
+}
+
diff --git a/src/App.tsx b/src/App.tsx
new file mode 100644
index 0000000..3945c36
--- /dev/null
+++ b/src/App.tsx
@@ -0,0 +1,12 @@
+import { Toaster } from 'sonner'
+import AppRoutes from '@/routes'
+
+export default function App() {
+ return (
+ <>
+
+
+ >
+ )
+}
+
diff --git a/src/apis/apiMallAdmin.ts b/src/apis/apiMallAdmin.ts
new file mode 100644
index 0000000..10594cd
--- /dev/null
+++ b/src/apis/apiMallAdmin.ts
@@ -0,0 +1,382 @@
+import axiosInstance from '@/apis/base/axiosInstance'
+import type { IBaseResponse } from '@/apis/base/types'
+import type {
+ AdminBannerDeleteParams,
+ AdminBannerItem,
+ AdminBannerPageParams,
+ AdminBannerSaveParams,
+ AdminCategoryDeleteParams,
+ AdminCategoryItem,
+ AdminCategorySaveParams,
+ AdminCategoryTreeParams,
+ AdminGoodsAuditParams,
+ AdminGoodsPageParams,
+ AdminHomeChannelDeleteParams,
+ AdminHomeChannelItem,
+ AdminHomeChannelPageParams,
+ AdminHomeChannelSaveParams,
+ AdminMerchantCreateParams,
+ AdminMerchantAuditParams,
+ AdminMerchantPageParams,
+ AdminOrderDetailParams,
+ AdminOrderPageParams,
+ AdminRecommendMerchantDeleteParams,
+ AdminRecommendMerchantItem,
+ AdminRecommendMerchantPageParams,
+ AdminRecommendMerchantSaveParams,
+ AdminRecommendStoreDeleteParams,
+ AdminRecommendStoreItem,
+ AdminRecommendStorePageParams,
+ AdminRecommendStoreSaveParams,
+ AdminSearchKeywordDeleteParams,
+ AdminSearchKeywordItem,
+ AdminSearchKeywordPageParams,
+ AdminSearchKeywordSaveParams,
+ AdminStoreAuditParams,
+ AdminStorePageParams,
+ GoodsSummary,
+ MerchantInfo,
+ OrderDetailInfo,
+ OrderSummary,
+ PageResult,
+ StoreInfo,
+} from '@/types/mall'
+
+const normalizePageResult = (value?: Partial> | null): PageResult => ({
+ total: Number(value?.total ?? 0),
+ list: Array.isArray(value?.list) ? value.list : [],
+})
+
+const normalizeCategoryItems = (items?: AdminCategoryItem[] | null): AdminCategoryItem[] => {
+ if (!Array.isArray(items)) {
+ return []
+ }
+
+ return items.map(item => ({
+ ...item,
+ children: normalizeCategoryItems(item.children),
+ }))
+}
+
+export const apiAdminMerchantPage = async (params: AdminMerchantPageParams, signal?: AbortSignal) => {
+ const response = await axiosInstance.post>>(
+ '/api/v1/mall/admin/merchant_page',
+ params,
+ { signal }
+ )
+
+ return {
+ ...response.data,
+ data: normalizePageResult(response.data.data),
+ }
+}
+
+export const apiAdminMerchantCreate = async (params: AdminMerchantCreateParams, signal?: AbortSignal) => {
+ const response = await axiosInstance.post>(
+ '/api/v1/mall/admin/merchant_create',
+ params,
+ { signal }
+ )
+ return response.data
+}
+
+export const apiAdminMerchantAudit = async (params: AdminMerchantAuditParams, signal?: AbortSignal) => {
+ const response = await axiosInstance.post>(
+ '/api/v1/mall/admin/merchant_audit',
+ params,
+ { signal }
+ )
+ return response.data
+}
+
+export const apiAdminStorePage = async (params: AdminStorePageParams, signal?: AbortSignal) => {
+ const response = await axiosInstance.post>>(
+ '/api/v1/mall/admin/store_page',
+ params,
+ { signal }
+ )
+
+ return {
+ ...response.data,
+ data: normalizePageResult(response.data.data),
+ }
+}
+
+export const apiAdminStoreAudit = async (params: AdminStoreAuditParams, signal?: AbortSignal) => {
+ const response = await axiosInstance.post>(
+ '/api/v1/mall/admin/store_audit',
+ params,
+ { signal }
+ )
+ return response.data
+}
+
+export const apiAdminGoodsPage = async (params: AdminGoodsPageParams, signal?: AbortSignal) => {
+ const response = await axiosInstance.post>>(
+ '/api/v1/mall/admin/goods_page',
+ params,
+ { signal }
+ )
+
+ return {
+ ...response.data,
+ data: normalizePageResult(response.data.data),
+ }
+}
+
+export const apiAdminGoodsAudit = async (params: AdminGoodsAuditParams, signal?: AbortSignal) => {
+ const response = await axiosInstance.post>(
+ '/api/v1/mall/admin/goods_audit',
+ params,
+ { signal }
+ )
+ return response.data
+}
+
+export const apiAdminOrderPage = async (params: AdminOrderPageParams, signal?: AbortSignal) => {
+ const response = await axiosInstance.post>>(
+ '/api/v1/mall/admin/order_page',
+ params,
+ { signal }
+ )
+
+ return {
+ ...response.data,
+ data: normalizePageResult(response.data.data),
+ }
+}
+
+export const apiAdminOrderDetail = async (params: AdminOrderDetailParams, signal?: AbortSignal) => {
+ const response = await axiosInstance.post>(
+ '/api/v1/mall/admin/order_detail',
+ params,
+ { signal }
+ )
+
+ return {
+ ...response.data,
+ data: response.data.data?.data ?? null,
+ }
+}
+
+export const apiAdminCategoryTree = async (params: AdminCategoryTreeParams, signal?: AbortSignal) => {
+ const response = await axiosInstance.post>(
+ '/api/v1/mall/admin/category_tree',
+ params,
+ { signal }
+ )
+
+ return {
+ ...response.data,
+ data: normalizeCategoryItems(response.data.data?.list),
+ }
+}
+
+export const apiAdminCategorySave = async (params: AdminCategorySaveParams, signal?: AbortSignal) => {
+ const response = await axiosInstance.post>(
+ '/api/v1/mall/admin/category_save',
+ params,
+ { signal }
+ )
+ return response.data
+}
+
+export const apiAdminCategoryDelete = async (params: AdminCategoryDeleteParams, signal?: AbortSignal) => {
+ const response = await axiosInstance.post>(
+ '/api/v1/mall/admin/category_delete',
+ params,
+ { signal }
+ )
+ return response.data
+}
+
+export const apiAdminBannerPage = async (params: AdminBannerPageParams, signal?: AbortSignal) => {
+ const response = await axiosInstance.post>>(
+ '/api/v1/mall/admin/banner_page',
+ params,
+ { signal }
+ )
+
+ return {
+ ...response.data,
+ data: normalizePageResult(response.data.data),
+ }
+}
+
+export const apiAdminBannerSave = async (params: AdminBannerSaveParams, signal?: AbortSignal) => {
+ const response = await axiosInstance.post>(
+ '/api/v1/mall/admin/banner_save',
+ params,
+ { signal }
+ )
+ return response.data
+}
+
+export const apiAdminBannerDelete = async (params: AdminBannerDeleteParams, signal?: AbortSignal) => {
+ const response = await axiosInstance.post>(
+ '/api/v1/mall/admin/banner_delete',
+ params,
+ { signal }
+ )
+ return response.data
+}
+
+export const apiAdminRecommendStorePage = async (
+ params: AdminRecommendStorePageParams,
+ signal?: AbortSignal
+) => {
+ const response = await axiosInstance.post>>(
+ '/api/v1/mall/admin/recommend_store_page',
+ params,
+ { signal }
+ )
+
+ return {
+ ...response.data,
+ data: normalizePageResult(response.data.data),
+ }
+}
+
+export const apiAdminRecommendStoreSave = async (
+ params: AdminRecommendStoreSaveParams,
+ signal?: AbortSignal
+) => {
+ const response = await axiosInstance.post>(
+ '/api/v1/mall/admin/recommend_store_save',
+ params,
+ { signal }
+ )
+ return response.data
+}
+
+export const apiAdminRecommendMerchantPage = async (
+ params: AdminRecommendMerchantPageParams,
+ signal?: AbortSignal
+) => {
+ const response = await axiosInstance.post>>(
+ '/api/v1/mall/admin/recommend_merchant_page',
+ params,
+ { signal }
+ )
+
+ return {
+ ...response.data,
+ data: normalizePageResult(response.data.data),
+ }
+}
+
+export const apiAdminRecommendMerchantSave = async (
+ params: AdminRecommendMerchantSaveParams,
+ signal?: AbortSignal
+) => {
+ const response = await axiosInstance.post>(
+ '/api/v1/mall/admin/recommend_merchant_save',
+ params,
+ { signal }
+ )
+ return response.data
+}
+
+export const apiAdminRecommendMerchantDelete = async (
+ params: AdminRecommendMerchantDeleteParams,
+ signal?: AbortSignal
+) => {
+ const response = await axiosInstance.post>(
+ '/api/v1/mall/admin/recommend_merchant_delete',
+ params,
+ { signal }
+ )
+ return response.data
+}
+
+export const apiAdminSearchKeywordPage = async (
+ params: AdminSearchKeywordPageParams,
+ signal?: AbortSignal
+) => {
+ const response = await axiosInstance.post>>(
+ '/api/v1/mall/admin/search_keyword_page',
+ params,
+ { signal }
+ )
+
+ return {
+ ...response.data,
+ data: normalizePageResult(response.data.data),
+ }
+}
+
+export const apiAdminSearchKeywordSave = async (
+ params: AdminSearchKeywordSaveParams,
+ signal?: AbortSignal
+) => {
+ const response = await axiosInstance.post>(
+ '/api/v1/mall/admin/search_keyword_save',
+ params,
+ { signal }
+ )
+ return response.data
+}
+
+export const apiAdminSearchKeywordDelete = async (
+ params: AdminSearchKeywordDeleteParams,
+ signal?: AbortSignal
+) => {
+ const response = await axiosInstance.post>(
+ '/api/v1/mall/admin/search_keyword_delete',
+ params,
+ { signal }
+ )
+ return response.data
+}
+
+export const apiAdminHomeChannelPage = async (
+ params: AdminHomeChannelPageParams,
+ signal?: AbortSignal
+) => {
+ const response = await axiosInstance.post>>(
+ '/api/v1/mall/admin/home_channel_page',
+ params,
+ { signal }
+ )
+
+ return {
+ ...response.data,
+ data: normalizePageResult(response.data.data),
+ }
+}
+
+export const apiAdminHomeChannelSave = async (
+ params: AdminHomeChannelSaveParams,
+ signal?: AbortSignal
+) => {
+ const response = await axiosInstance.post>(
+ '/api/v1/mall/admin/home_channel_save',
+ params,
+ { signal }
+ )
+ return response.data
+}
+
+export const apiAdminHomeChannelDelete = async (
+ params: AdminHomeChannelDeleteParams,
+ signal?: AbortSignal
+) => {
+ const response = await axiosInstance.post>(
+ '/api/v1/mall/admin/home_channel_delete',
+ params,
+ { signal }
+ )
+ return response.data
+}
+
+export const apiAdminRecommendStoreDelete = async (
+ params: AdminRecommendStoreDeleteParams,
+ signal?: AbortSignal
+) => {
+ const response = await axiosInstance.post>(
+ '/api/v1/mall/admin/recommend_store_delete',
+ params,
+ { signal }
+ )
+ return response.data
+}
diff --git a/src/apis/apisCommon.ts b/src/apis/apisCommon.ts
new file mode 100644
index 0000000..6ecca59
--- /dev/null
+++ b/src/apis/apisCommon.ts
@@ -0,0 +1,23 @@
+import axiosInstance from '@/apis/base/axiosInstance'
+import type { IBaseResponse } from '@/apis/base/types'
+
+export const apiGetSmsCode = async (
+ params: {
+ account: string
+ type: '0' | '1' | '2' | '8'
+ },
+ signal?: AbortSignal
+) => {
+ const response = await axiosInstance.post>(
+ '/api/v1/user/captcha',
+ {
+ ...params,
+ loginType: '0',
+ },
+ {
+ signal,
+ }
+ )
+ return response.data
+}
+
diff --git a/src/apis/apisUser.ts b/src/apis/apisUser.ts
new file mode 100644
index 0000000..cc366fa
--- /dev/null
+++ b/src/apis/apisUser.ts
@@ -0,0 +1,36 @@
+import axiosInstance from '@/apis/base/axiosInstance'
+import type { IBaseResponse } from '@/apis/base/types'
+import type { ITokenData } from '@/types/ITokenData'
+import type { IUserInfo } from '@/types/IUserInfo'
+
+export interface UserLoginParams {
+ account: string
+ password: string
+ captcha: string
+ captchaId?: string
+ loginType: '0'
+ type: '0'
+ remember: boolean
+}
+
+export const apiUserLogin = async (params: UserLoginParams, signal?: AbortSignal) => {
+ const response = await axiosInstance.post>(
+ '/api/v1/user/super_login',
+ params,
+ {
+ signal,
+ }
+ )
+ return response.data
+}
+
+export const apiGetUserInfo = async (signal?: AbortSignal) => {
+ const response = await axiosInstance.post>(
+ '/api/v1/user/pur_detail',
+ {},
+ {
+ signal,
+ }
+ )
+ return response.data
+}
diff --git a/src/apis/base/axiosInstance.ts b/src/apis/base/axiosInstance.ts
new file mode 100644
index 0000000..66edff6
--- /dev/null
+++ b/src/apis/base/axiosInstance.ts
@@ -0,0 +1,113 @@
+import axios, { AxiosHeaders } from 'axios'
+import type { AxiosError, InternalAxiosRequestConfig } from 'axios'
+import { getBaseUrl, getVersion } from '@/config/source/config'
+import { useUserStore } from '@/store/user'
+import { API_ERROR_CODES, API_ERROR_TYPES, ApiError } from './types'
+
+const axiosInstance = axios.create({
+ baseURL: getBaseUrl(),
+ timeout: 30 * 1000,
+})
+
+const handleHttpError = (status: number, data: unknown, message: string, error: unknown): never => {
+ switch (status) {
+ case 401:
+ useUserStore.getState().clearAuth()
+ throw new ApiError(API_ERROR_CODES.UNAUTHORIZED, '登录已失效', API_ERROR_TYPES.HTTP, data, error)
+ case 403:
+ throw new ApiError(API_ERROR_CODES.FORBIDDEN, '无权限访问', API_ERROR_TYPES.HTTP, data, error)
+ case 500:
+ case 503:
+ throw new ApiError(API_ERROR_CODES.SERVER_ERROR, '服务器错误', API_ERROR_TYPES.HTTP, data, error)
+ default:
+ throw new ApiError(status, message, API_ERROR_TYPES.HTTP, data, error)
+ }
+}
+
+const handleAxiosError = (error: AxiosError): never => {
+ if (error.code === 'ECONNABORTED') {
+ throw new ApiError(API_ERROR_CODES.TIMEOUT_ERROR, '请求超时', API_ERROR_TYPES.NETWORK, undefined, error)
+ }
+
+ if (error.response) {
+ const { status, data } = error.response
+ const message = (data as { message?: string; msg?: string } | undefined)?.message
+ || (data as { message?: string; msg?: string } | undefined)?.msg
+ || error.message
+ return handleHttpError(status, data, message, error)
+ }
+
+ if (error.request) {
+ throw new ApiError(
+ API_ERROR_CODES.NETWORK_ERROR,
+ '网络错误,请检查网关地址与当前网络',
+ API_ERROR_TYPES.NETWORK,
+ undefined,
+ error
+ )
+ }
+
+ throw new ApiError(API_ERROR_CODES.SERVER_ERROR, error.message, API_ERROR_TYPES.UNKNOWN, undefined, error)
+}
+
+axiosInstance.interceptors.request.use(
+ (config: InternalAxiosRequestConfig) => {
+ const token = useUserStore.getState().token?.accessToken ?? ''
+
+ config.baseURL = getBaseUrl()
+ config.headers = new AxiosHeaders({
+ 'Content-Type': 'application/json;charset=utf-8',
+ authorization: token,
+ channel: 'admin',
+ version: getVersion(),
+ ...config.headers,
+ })
+
+ return config
+ },
+ error => {
+ throw new ApiError(API_ERROR_CODES.SERVER_ERROR, '请求初始化失败', API_ERROR_TYPES.UNKNOWN, undefined, error)
+ }
+)
+
+axiosInstance.interceptors.response.use(
+ response => {
+ const payload = (response.data ?? {}) as {
+ code?: number | string
+ message?: string
+ msg?: string
+ data?: unknown
+ }
+ const code = Number(payload.code ?? API_ERROR_CODES.SERVER_ERROR)
+ const message = payload.message || payload.msg || '请求失败'
+
+ if (code === API_ERROR_CODES.SUCCESS) {
+ response.data = {
+ code,
+ message,
+ data: payload.data,
+ }
+ return response
+ }
+
+ if (code === API_ERROR_CODES.UNAUTHORIZED) {
+ useUserStore.getState().clearAuth()
+ }
+
+ throw new ApiError(code, message, API_ERROR_TYPES.BUSINESS, payload.data)
+ },
+ (error: unknown): never => {
+ if (axios.isCancel(error)) {
+ throw new ApiError(API_ERROR_CODES.CANCEL_ERROR, '请求已取消', API_ERROR_TYPES.UNKNOWN, undefined, error)
+ }
+
+ if (error instanceof Error && 'isAxiosError' in error) {
+ return handleAxiosError(error as AxiosError)
+ }
+
+ const message = error instanceof Error ? error.message : '未知错误'
+ throw new ApiError(API_ERROR_CODES.SERVER_ERROR, message, API_ERROR_TYPES.UNKNOWN, undefined, error)
+ }
+)
+
+export default axiosInstance
diff --git a/src/apis/base/types.ts b/src/apis/base/types.ts
new file mode 100644
index 0000000..a50f6bd
--- /dev/null
+++ b/src/apis/base/types.ts
@@ -0,0 +1,47 @@
+export const API_ERROR_TYPES = {
+ HTTP: 'HTTP',
+ BUSINESS: 'BUSINESS',
+ NETWORK: 'NETWORK',
+ UNKNOWN: 'UNKNOWN',
+} as const
+
+export type ApiErrorType = (typeof API_ERROR_TYPES)[keyof typeof API_ERROR_TYPES]
+
+export const API_ERROR_CODES = {
+ SUCCESS: 200,
+ UNAUTHORIZED: 600,
+ FORBIDDEN: 403,
+ SERVER_ERROR: 500,
+ NETWORK_ERROR: 1000,
+ TIMEOUT_ERROR: 1001,
+ CANCEL_ERROR: 1002,
+} as const
+
+export class ApiError extends Error {
+ code: number
+ type: ApiErrorType
+ data?: unknown
+ originalError?: unknown
+
+ constructor(
+ code: number,
+ message: string,
+ type: ApiErrorType,
+ data?: unknown,
+ originalError?: unknown
+ ) {
+ super(message)
+ this.name = 'ApiError'
+ this.code = code
+ this.type = type
+ this.data = data
+ this.originalError = originalError
+ }
+}
+
+export interface IBaseResponse {
+ code: number
+ message: string
+ data: T
+}
+
diff --git a/src/components/AuditModal.tsx b/src/components/AuditModal.tsx
new file mode 100644
index 0000000..4186ed4
--- /dev/null
+++ b/src/components/AuditModal.tsx
@@ -0,0 +1,87 @@
+import type { ReactNode } from 'react'
+import { Form, Input, Modal, Radio, Space, Typography } from 'antd'
+import { auditActionOptions } from '@/utils/mall'
+
+export interface AuditModalValues {
+ auditStatus: 'approved' | 'rejected'
+ remark?: string
+}
+
+interface AuditModalProps {
+ open: boolean
+ title: string
+ loading?: boolean
+ summary?: ReactNode
+ initialValues?: Partial
+ onCancel: () => void
+ onSubmit: (values: AuditModalValues) => Promise | void
+}
+
+export default function AuditModal({
+ open,
+ title,
+ loading,
+ summary,
+ initialValues,
+ onCancel,
+ onSubmit,
+}: AuditModalProps) {
+ const [form] = Form.useForm()
+
+ return (
+ {
+ void form.submit()
+ }}
+ >
+ {summary ? (
+
+ 审核对象
+ {summary}
+
+ ) : null}
+
+
+
+
+ {auditActionOptions.map(option => (
+
+ {option.label}
+
+ ))}
+
+
+
+
+
+
+
+
+
+ )
+}
diff --git a/src/components/JsonPreviewPanel.tsx b/src/components/JsonPreviewPanel.tsx
new file mode 100644
index 0000000..387b8cc
--- /dev/null
+++ b/src/components/JsonPreviewPanel.tsx
@@ -0,0 +1,22 @@
+import { Card } from 'antd'
+import { safeJsonPretty } from '@/utils/format'
+
+interface JsonPreviewPanelProps {
+ title: string
+ value?: string | null
+}
+
+export default function JsonPreviewPanel({ title, value }: JsonPreviewPanelProps) {
+ const content = value?.trim()
+
+ if (!content) {
+ return null
+ }
+
+ return (
+
+ {safeJsonPretty(content)}
+
+ )
+}
+
diff --git a/src/components/StatusTag.tsx b/src/components/StatusTag.tsx
new file mode 100644
index 0000000..b4bd1c8
--- /dev/null
+++ b/src/components/StatusTag.tsx
@@ -0,0 +1,11 @@
+import { Tag } from 'antd'
+import { getStatusColor, getStatusLabel } from '@/utils/mall'
+
+interface StatusTagProps {
+ value?: string | null
+}
+
+export default function StatusTag({ value }: StatusTagProps) {
+ return {getStatusLabel(value)}
+}
+
diff --git a/src/config/source/config.ts b/src/config/source/config.ts
new file mode 100644
index 0000000..589e404
--- /dev/null
+++ b/src/config/source/config.ts
@@ -0,0 +1,29 @@
+interface WindowConfig {
+ VERSION?: string
+}
+
+const DEFAULT_VERSION = '0.1.0'
+const FIXED_BASE_URL = 'https://test-gateway.yuntuo.hk'
+
+const getWindowConfig = (): WindowConfig | undefined => {
+ if (typeof window === 'undefined') {
+ return undefined
+ }
+ return (window as typeof window & { config?: WindowConfig }).config
+}
+
+export const getStoredBaseUrl = (): string => {
+ return FIXED_BASE_URL
+}
+
+export const setStoredBaseUrl = (value: string) => {
+ void value
+}
+
+export const getBaseUrl = (): string => {
+ return FIXED_BASE_URL
+}
+
+export const getVersion = (): string => {
+ return getWindowConfig()?.VERSION?.trim() || DEFAULT_VERSION
+}
diff --git a/src/index.css b/src/index.css
new file mode 100644
index 0000000..c81b554
--- /dev/null
+++ b/src/index.css
@@ -0,0 +1,127 @@
+:root {
+ color: #162233;
+ background: #f5f7f6;
+ font-family: "PingFang SC", "SF Pro Display", "Helvetica Neue", "Microsoft YaHei", sans-serif;
+ font-synthesis: none;
+ text-rendering: optimizeLegibility;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+}
+
+* {
+ box-sizing: border-box;
+}
+
+html,
+body,
+#root {
+ margin: 0;
+ min-height: 100%;
+}
+
+body {
+ background:
+ radial-gradient(circle at top left, rgba(21, 111, 78, 0.08), transparent 28%),
+ radial-gradient(circle at top right, rgba(5, 170, 129, 0.05), transparent 22%),
+ #f5f7f6;
+}
+
+a {
+ color: inherit;
+}
+
+.page-toolbar {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ justify-content: space-between;
+ gap: 10px;
+}
+
+.search-form.ant-form-inline {
+ row-gap: 14px;
+ column-gap: 18px;
+}
+
+.search-form.ant-form-inline .ant-form-item {
+ margin-right: 0;
+ margin-bottom: 0;
+}
+
+.search-form.ant-form-inline .ant-form-item-label {
+ padding-inline-end: 10px;
+}
+
+.table-top-align .ant-table-cell {
+ vertical-align: top;
+}
+
+.ant-typography,
+.ant-typography.ant-typography-paragraph {
+ margin-bottom: 0 !important;
+}
+
+.ant-btn {
+ font-weight: 400;
+}
+
+.ant-card .ant-card-head {
+ min-height: 46px;
+}
+
+.ant-card .ant-card-head-title {
+ font-size: 14px;
+ font-weight: 600;
+}
+
+.merchant-create-modal .ant-modal-content {
+ border-radius: 16px;
+}
+
+.merchant-create-modal .ant-modal-header {
+ margin-bottom: 4px;
+}
+
+.merchant-create-form .ant-form-item {
+ margin-bottom: 14px;
+}
+
+.merchant-create-form .ant-radio-group {
+ display: inline-flex;
+ padding: 4px;
+ border-radius: 12px;
+ background: #f3f7f5;
+}
+
+.merchant-create-form .ant-radio-button-wrapper {
+ min-width: 92px;
+ text-align: center;
+ border-radius: 8px;
+}
+
+.merchant-create-form .ant-radio-button-wrapper:not(:first-child)::before {
+ display: none;
+}
+
+.merchant-create-section-title {
+ display: block;
+ margin-bottom: 12px;
+ color: #156f4e;
+ font-size: 13px;
+ font-weight: 600;
+}
+
+.mono {
+ font-family: "SFMono-Regular", "JetBrains Mono", "Consolas", monospace;
+}
+
+pre.json-block {
+ margin: 0;
+ padding: 12px 14px;
+ border-radius: 10px;
+ background: #0f172a;
+ color: #dbeafe;
+ overflow: auto;
+ font-size: 11.5px;
+ line-height: 1.6;
+}
diff --git a/src/layouts/RootLayout.tsx b/src/layouts/RootLayout.tsx
new file mode 100644
index 0000000..a31879d
--- /dev/null
+++ b/src/layouts/RootLayout.tsx
@@ -0,0 +1,18 @@
+import { useEffect } from 'react'
+import { Outlet, useLocation, useNavigate } from 'react-router-dom'
+import { useUserStore } from '@/store/user'
+
+export default function RootLayout() {
+ const navigate = useNavigate()
+ const location = useLocation()
+ const token = useUserStore(state => state.token)
+
+ useEffect(() => {
+ if (location.pathname === '/') {
+ navigate(token?.accessToken ? '/merchant-review' : '/login', { replace: true })
+ }
+ }, [location.pathname, navigate, token?.accessToken])
+
+ return
+}
+
diff --git a/src/layouts/auth/AuthLayout.tsx b/src/layouts/auth/AuthLayout.tsx
new file mode 100644
index 0000000..dc71842
--- /dev/null
+++ b/src/layouts/auth/AuthLayout.tsx
@@ -0,0 +1,32 @@
+import { Layout, theme } from 'antd'
+import { Outlet } from 'react-router-dom'
+
+export default function AuthLayout() {
+ const { token } = theme.useToken()
+
+ return (
+
+
+
+
+
+
+
+ )
+}
diff --git a/src/layouts/main/MainLayout.tsx b/src/layouts/main/MainLayout.tsx
new file mode 100644
index 0000000..2a88fe7
--- /dev/null
+++ b/src/layouts/main/MainLayout.tsx
@@ -0,0 +1,131 @@
+import { useMemo } from 'react'
+import {
+ ApartmentOutlined,
+ AppstoreOutlined,
+ LogoutOutlined,
+ NotificationOutlined,
+ PictureOutlined,
+ ShoppingCartOutlined,
+ StarOutlined,
+ SolutionOutlined,
+ TagsOutlined,
+} from '@ant-design/icons'
+import { Avatar, Button, Layout, Menu, Space, Typography } from 'antd'
+import { Outlet, useLocation, useNavigate } from 'react-router-dom'
+import { useUserStore } from '@/store/user'
+
+const menuItems = [
+ {
+ key: '/merchant-review',
+ icon: ,
+ label: '商家审核',
+ },
+ {
+ key: '/categories',
+ icon: ,
+ label: '商品类目',
+ },
+ {
+ key: '/banners',
+ icon: ,
+ label: '首页 Banner',
+ },
+ {
+ key: '/goods-review',
+ icon: ,
+ label: '商品审核',
+ },
+ {
+ key: '/recommend-stores',
+ icon: ,
+ label: '首页商家',
+ },
+ {
+ key: '/search-keywords',
+ icon: ,
+ label: '首页热词',
+ },
+ {
+ key: '/home-channels',
+ icon: ,
+ label: '频道卡片',
+ },
+ {
+ key: '/orders',
+ icon: ,
+ label: '订单总览',
+ },
+]
+
+const resolveSelectedKey = (pathname: string) => {
+ if (pathname.startsWith('/orders')) {
+ return '/orders'
+ }
+ return pathname
+}
+
+export default function MainLayout() {
+ const navigate = useNavigate()
+ const location = useLocation()
+ const logout = useUserStore(state => state.logout)
+ const userInfo = useUserStore(state => state.userInfo)
+ const token = useUserStore(state => state.token)
+
+ const displayName = useMemo(() => {
+ return userInfo?.name || userInfo?.account || token?.account || '商城管理员'
+ }, [token?.account, userInfo?.account, userInfo?.name])
+
+ return (
+
+
+
+
+ 商城管理端
+
+
+
+
+
+
+
+
+ {displayName.slice(0, 1)}
+ {displayName}
+ }
+ onClick={() => {
+ logout()
+ navigate('/login', { replace: true })
+ }}
+ >
+ 退出登录
+
+
+
+
+
+
+
+
+
+ )
+}
diff --git a/src/main.tsx b/src/main.tsx
new file mode 100644
index 0000000..1ffed42
--- /dev/null
+++ b/src/main.tsx
@@ -0,0 +1,121 @@
+import { createRoot } from 'react-dom/client'
+import { BrowserRouter } from 'react-router-dom'
+import { App as AntdApp, ConfigProvider, theme as antdTheme, type ThemeConfig } from 'antd'
+import zhCN from 'antd/locale/zh_CN'
+import dayjs from 'dayjs'
+import 'dayjs/locale/zh-cn'
+import App from './App'
+import './index.css'
+
+dayjs.locale('zh-cn')
+
+const appTheme: ThemeConfig = {
+ algorithm: antdTheme.defaultAlgorithm,
+ token: {
+ colorPrimaryBg: '#e5f2ee',
+ colorPrimaryBgHover: '#cde7de',
+ colorPrimaryBorder: '#86c2ae',
+ colorPrimaryBorderHover: '#61ae94',
+ colorPrimaryActive: '#054e38',
+ colorPrimaryTextHover: '#349172',
+ colorPrimaryTextActive: '#054e38',
+ colorPrimaryHover: '#349172',
+ colorPrimary: '#156f4e',
+ colorInfo: '#156f4e',
+ colorTextBase: '#0e1513',
+ colorError: '#ef485e',
+ colorWarning: '#eda617',
+ colorSuccess: '#05aa81',
+ colorSuccessBg: '#dff4eb',
+ colorSuccessBgHover: '#a9e9d0',
+ colorTextTertiary: 'rgba(14,21,19,0.45)',
+ colorTextQuaternary: 'rgba(14,21,19,0.25)',
+ fontSize: 14,
+ sizeStep: 4,
+ sizeUnit: 4,
+ lineHeight: 1.25,
+ borderRadiusXS: 4,
+ borderRadiusSM: 6,
+ borderRadius: 8,
+ borderRadiusLG: 12,
+ controlHeight: 32,
+ controlHeightSM: 28,
+ controlHeightLG: 36,
+ fontSizeHeading1: 30,
+ fontSizeHeading2: 24,
+ fontSizeHeading3: 20,
+ fontSizeHeading4: 16,
+ fontSizeHeading5: 14,
+ wireframe: false,
+ fontFamily:
+ '"PingFang SC", "SF Pro Display", "Helvetica Neue", "Microsoft YaHei", sans-serif',
+ },
+ components: {
+ Button: {
+ defaultActiveColor: 'rgb(21,111,78)',
+ defaultHoverBorderColor: 'rgb(52,145,114)',
+ defaultHoverColor: 'rgb(52,145,114)',
+ groupBorderColor: 'rgb(52,145,114)',
+ defaultColor: 'rgb(21,111,78)',
+ primaryShadow: '0 0px 0 rgba(5,145,255,0.1)',
+ defaultShadow: '0 0px 0 rgba(0,0,0,0.02)',
+ dangerShadow: '0 0px 0 rgba(255,38,5,0.06)',
+ defaultActiveBorderColor: 'rgb(21,111,78)',
+ },
+ Pagination: {
+ colorPrimary: 'rgb(21,111,78)',
+ },
+ DatePicker: {
+ activeBorderColor: 'rgb(21,111,78)',
+ },
+ Segmented: {
+ lineWidth: 1,
+ },
+ Cascader: {
+ colorHighlight: 'rgb(5,170,129)',
+ optionSelectedBg: 'rgb(248,248,248)',
+ },
+ Alert: {
+ colorInfoBg: 'rgb(240,244,242)',
+ colorInfoBorder: 'rgb(200,215,208)',
+ },
+ Layout: {
+ bodyBg: '#f5f7f6',
+ headerBg: '#0f1720',
+ siderBg: '#0f1720',
+ triggerBg: '#122031',
+ },
+ Menu: {
+ darkItemSelectedBg: '#156f4e',
+ itemBorderRadius: 8,
+ },
+ Card: {
+ borderRadiusLG: 12,
+ bodyPadding: 18,
+ },
+ Table: {
+ headerBg: '#f6f8f7',
+ headerSplitColor: '#e3ebe7',
+ cellPaddingBlock: 10,
+ cellPaddingInline: 12,
+ fontSize: 13,
+ },
+ Descriptions: {
+ itemPaddingBottom: 10,
+ labelBg: '#fafcfa',
+ },
+ Form: {
+ itemMarginBottom: 14,
+ },
+ },
+}
+
+createRoot(document.getElementById('root')!).render(
+
+
+
+
+
+
+
+)
diff --git a/src/pages/admin/banners/index.tsx b/src/pages/admin/banners/index.tsx
new file mode 100644
index 0000000..cef4c83
--- /dev/null
+++ b/src/pages/admin/banners/index.tsx
@@ -0,0 +1,462 @@
+import { useEffect, useState } from 'react'
+import dayjs, { type Dayjs } from 'dayjs'
+import {
+ Button,
+ Card,
+ DatePicker,
+ Form,
+ Image,
+ Input,
+ InputNumber,
+ Modal,
+ Popconfirm,
+ Select,
+ Space,
+ Table,
+ Typography,
+} from 'antd'
+import type { ColumnsType, TablePaginationConfig } from 'antd/es/table'
+import { toast } from 'sonner'
+import {
+ apiAdminBannerDelete,
+ apiAdminBannerPage,
+ apiAdminBannerSave,
+} from '@/apis/apiMallAdmin'
+import StatusTag from '@/components/StatusTag'
+import type {
+ AdminBannerItem,
+ AdminBannerPageParams,
+ AdminBannerSaveParams,
+ EnabledStatus,
+} from '@/types/mall'
+import { compactText, formatTimestamp } from '@/utils/format'
+import {
+ bannerPositionOptions,
+ enabledStatusOptions,
+ getLinkTypeLabel,
+ getRecommendStorePositionLabel,
+ linkTypeOptions,
+ withAllOption,
+} from '@/utils/mall'
+
+const { TextArea } = Input
+const DEFAULT_PAGE_SIZE = 20
+const DEFAULT_POSITION = 'home_top'
+const DEFAULT_STATUS: EnabledStatus = 'enabled'
+
+interface BannerSearchValues {
+ keyword?: string
+ positionCode?: 'home_top' | ''
+ status?: EnabledStatus | ''
+ page: number
+ size: number
+}
+
+interface BannerFormValues {
+ title: string
+ subTitle?: string
+ description?: string
+ buttonText?: string
+ positionCode: 'home_top'
+ imageUrl: string
+ linkType?: string
+ linkValue?: string
+ sort?: number
+ status: EnabledStatus
+ startTime?: Dayjs | null
+ endTime?: Dayjs | null
+}
+
+const normalizeSearchValues = (values: BannerSearchValues): AdminBannerPageParams => ({
+ keyword: values.keyword?.trim() || undefined,
+ positionCode: values.positionCode || undefined,
+ status: values.status || undefined,
+ page: values.page,
+ size: values.size,
+})
+
+export default function BannerManagementPage() {
+ const [form] = Form.useForm()
+ const [modalForm] = Form.useForm()
+ const [loading, setLoading] = useState(false)
+ const [saving, setSaving] = useState(false)
+ const [list, setList] = useState([])
+ const [total, setTotal] = useState(0)
+ const [pagination, setPagination] = useState({ current: 1, pageSize: DEFAULT_PAGE_SIZE })
+ const [modalOpen, setModalOpen] = useState(false)
+ const [editingRecord, setEditingRecord] = useState(null)
+
+ const loadData = async (page = pagination.current, size = pagination.pageSize) => {
+ try {
+ setLoading(true)
+ const response = await apiAdminBannerPage(
+ normalizeSearchValues({
+ ...form.getFieldsValue(),
+ page,
+ size,
+ })
+ )
+ setList(response.data.list)
+ setTotal(response.data.total)
+ setPagination({ current: page, pageSize: size })
+ } catch (error) {
+ toast.error(error instanceof Error ? error.message : 'Banner 列表加载失败')
+ } finally {
+ setLoading(false)
+ }
+ }
+
+ useEffect(() => {
+ form.setFieldsValue({
+ page: 1,
+ size: DEFAULT_PAGE_SIZE,
+ })
+ void loadData(1, DEFAULT_PAGE_SIZE)
+ }, [])
+
+ const resetModal = () => {
+ setModalOpen(false)
+ setEditingRecord(null)
+ modalForm.resetFields()
+ }
+
+ const openCreateModal = () => {
+ setEditingRecord(null)
+ modalForm.setFieldsValue({
+ positionCode: DEFAULT_POSITION,
+ status: DEFAULT_STATUS,
+ sort: 0,
+ startTime: null,
+ endTime: null,
+ })
+ setModalOpen(true)
+ }
+
+ const openEditModal = (record: AdminBannerItem) => {
+ setEditingRecord(record)
+ modalForm.setFieldsValue({
+ title: record.title,
+ subTitle: record.subTitle || '',
+ description: record.description || '',
+ buttonText: record.buttonText || '',
+ positionCode: record.positionCode,
+ imageUrl: record.imageUrl,
+ linkType: record.linkType || undefined,
+ linkValue: record.linkValue || '',
+ sort: record.sort ?? 0,
+ status: record.status,
+ startTime: record.startTime ? dayjs.unix(record.startTime) : null,
+ endTime: record.endTime ? dayjs.unix(record.endTime) : null,
+ })
+ setModalOpen(true)
+ }
+
+ const handleDelete = async (record: AdminBannerItem) => {
+ try {
+ await apiAdminBannerDelete({ id: record.id })
+ toast.success('Banner 已删除')
+ await loadData()
+ } catch (error) {
+ toast.error(error instanceof Error ? error.message : 'Banner 删除失败')
+ }
+ }
+
+ const handleSave = async () => {
+ try {
+ const values = await modalForm.validateFields()
+ setSaving(true)
+ const payload: AdminBannerSaveParams = {
+ id: editingRecord?.id,
+ title: values.title.trim(),
+ subTitle: values.subTitle?.trim() || undefined,
+ description: values.description?.trim() || undefined,
+ buttonText: values.buttonText?.trim() || undefined,
+ positionCode: values.positionCode,
+ imageUrl: values.imageUrl.trim(),
+ linkType: values.linkType || undefined,
+ linkValue: values.linkValue?.trim() || undefined,
+ sort: values.sort ?? 0,
+ status: values.status,
+ startTime: values.startTime?.unix(),
+ endTime: values.endTime?.unix(),
+ }
+ await apiAdminBannerSave(payload)
+ toast.success(editingRecord ? 'Banner 已更新' : 'Banner 已新增')
+ resetModal()
+ await loadData()
+ } catch (error) {
+ if (error instanceof Error && error.message) {
+ toast.error(error.message)
+ }
+ } finally {
+ setSaving(false)
+ }
+ }
+
+ const handleTableChange = (next: TablePaginationConfig) => {
+ void loadData(next.current || 1, next.pageSize || DEFAULT_PAGE_SIZE)
+ }
+
+ const columns: ColumnsType = [
+ {
+ title: 'Banner',
+ key: 'banner',
+ width: 260,
+ render: (_, record) => (
+
+ {record.imageUrl ? (
+
+ ) : null}
+
+ {compactText(record.title)}
+ {compactText(record.subTitle)}
+ {compactText(record.description)}
+
+
+ ),
+ },
+ {
+ title: '按钮文案',
+ dataIndex: 'buttonText',
+ width: 120,
+ render: value => compactText(value),
+ },
+ {
+ title: '位置',
+ dataIndex: 'positionCode',
+ width: 130,
+ render: value => getRecommendStorePositionLabel(value),
+ },
+ {
+ title: '跳转信息',
+ key: 'link',
+ width: 220,
+ render: (_, record) => (
+
+ {getLinkTypeLabel(record.linkType)}
+
+ {compactText(record.linkValue)}
+
+
+ ),
+ },
+ {
+ title: '排序',
+ dataIndex: 'sort',
+ width: 76,
+ },
+ {
+ title: '状态',
+ dataIndex: 'status',
+ width: 96,
+ render: value => ,
+ },
+ {
+ title: '开始时间',
+ dataIndex: 'startTime',
+ width: 170,
+ render: value => formatTimestamp(value),
+ },
+ {
+ title: '结束时间',
+ dataIndex: 'endTime',
+ width: 170,
+ render: value => formatTimestamp(value),
+ },
+ {
+ title: '更新时间',
+ dataIndex: 'updatedAt',
+ width: 170,
+ render: value => formatTimestamp(value),
+ },
+ {
+ title: '操作',
+ key: 'action',
+ fixed: 'right',
+ width: 144,
+ render: (_, record) => (
+
+
+ void handleDelete(record)}
+ >
+
+
+
+ ),
+ },
+ ]
+
+ return (
+
+
+
+ 首页 Banner
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ rowKey="id"
+ loading={loading}
+ columns={columns}
+ dataSource={list}
+ className="table-top-align"
+ scroll={{ x: 1700 }}
+ pagination={{
+ current: pagination.current,
+ pageSize: pagination.pageSize,
+ total,
+ showSizeChanger: true,
+ showTotal: count => `共 ${count} 条`,
+ }}
+ onChange={handleTableChange}
+ />
+
+
+ void handleSave()}
+ >
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ({
+ validator(_, value?: Dayjs | null) {
+ const startTime = getFieldValue('startTime') as Dayjs | null | undefined
+ if (!startTime || !value || value.valueOf() >= startTime.valueOf()) {
+ return Promise.resolve()
+ }
+ return Promise.reject(new Error('结束时间不能早于开始时间'))
+ },
+ }),
+ ]}
+ >
+
+
+
+
+
+
+ )
+}
diff --git a/src/pages/admin/categories/index.tsx b/src/pages/admin/categories/index.tsx
new file mode 100644
index 0000000..ded0222
--- /dev/null
+++ b/src/pages/admin/categories/index.tsx
@@ -0,0 +1,385 @@
+import { useEffect, useMemo, useState } from 'react'
+import { Avatar, Button, Card, Form, Input, InputNumber, Modal, Popconfirm, Select, Space, Table, TreeSelect, Typography } from 'antd'
+import type { ColumnsType } from 'antd/es/table'
+import { toast } from 'sonner'
+import {
+ apiAdminCategoryDelete,
+ apiAdminCategorySave,
+ apiAdminCategoryTree,
+} from '@/apis/apiMallAdmin'
+import StatusTag from '@/components/StatusTag'
+import type {
+ AdminCategoryItem,
+ AdminCategorySaveParams,
+ AdminCategoryTreeParams,
+ EnabledStatus,
+} from '@/types/mall'
+import { compactText, formatTimestamp } from '@/utils/format'
+import {
+ enabledStatusOptions,
+ getBooleanLabel,
+ getCategoryLevelLabel,
+ withAllOption,
+} from '@/utils/mall'
+
+const DEFAULT_STATUS: EnabledStatus = 'enabled'
+const ROOT_PARENT_ID = 0
+
+interface CategorySearchValues {
+ status?: EnabledStatus | ''
+}
+
+interface CategoryFormValues {
+ parentId: number
+ categoryName: string
+ categoryIcon?: string
+ sort?: number
+ status: EnabledStatus
+}
+
+interface ParentTreeNode {
+ title: string
+ value: number
+ disabled?: boolean
+ children: ParentTreeNode[]
+}
+
+const collectDisabledParentIds = (node?: AdminCategoryItem | null): Set => {
+ const ids = new Set()
+
+ const walk = (current?: AdminCategoryItem | null) => {
+ if (!current) {
+ return
+ }
+ ids.add(current.id)
+ current.children?.forEach(child => walk(child))
+ }
+
+ walk(node)
+ return ids
+}
+
+const buildParentTreeChildren = (
+ items: AdminCategoryItem[],
+ disabledIds: Set
+): ParentTreeNode[] =>
+ items.map(item => ({
+ title: `${compactText(item.categoryName)} (ID: ${item.id})`,
+ value: item.id,
+ disabled: disabledIds.has(item.id),
+ children: buildParentTreeChildren(item.children, disabledIds),
+ }))
+
+const buildParentTreeData = (items: AdminCategoryItem[], disabledIds: Set): ParentTreeNode[] => [
+ {
+ title: '顶级类目',
+ value: ROOT_PARENT_ID,
+ children: buildParentTreeChildren(items, disabledIds),
+ },
+]
+
+const normalizeSearchValues = (values: CategorySearchValues): AdminCategoryTreeParams => ({
+ status: values.status || undefined,
+})
+
+export default function CategoryManagementPage() {
+ const [form] = Form.useForm()
+ const [modalForm] = Form.useForm()
+ const [loading, setLoading] = useState(false)
+ const [saving, setSaving] = useState(false)
+ const [list, setList] = useState([])
+ const [editingRecord, setEditingRecord] = useState(null)
+ const [modalOpen, setModalOpen] = useState(false)
+
+ const loadData = async () => {
+ try {
+ setLoading(true)
+ const response = await apiAdminCategoryTree(normalizeSearchValues(form.getFieldsValue()))
+ setList(response.data)
+ } catch (error) {
+ toast.error(error instanceof Error ? error.message : '类目列表加载失败')
+ } finally {
+ setLoading(false)
+ }
+ }
+
+ useEffect(() => {
+ void loadData()
+ }, [])
+
+ const disabledParentIds = useMemo(() => collectDisabledParentIds(editingRecord), [editingRecord])
+ const parentTreeData = useMemo(
+ () => buildParentTreeData(list, disabledParentIds),
+ [disabledParentIds, list]
+ )
+
+ const resetModal = () => {
+ setModalOpen(false)
+ setEditingRecord(null)
+ modalForm.resetFields()
+ }
+
+ const openCreateRootModal = () => {
+ setEditingRecord(null)
+ modalForm.setFieldsValue({
+ parentId: ROOT_PARENT_ID,
+ categoryName: '',
+ categoryIcon: '',
+ sort: 0,
+ status: DEFAULT_STATUS,
+ })
+ setModalOpen(true)
+ }
+
+ const openCreateChildModal = (record: AdminCategoryItem) => {
+ setEditingRecord(null)
+ modalForm.setFieldsValue({
+ parentId: record.id,
+ categoryName: '',
+ categoryIcon: '',
+ sort: 0,
+ status: DEFAULT_STATUS,
+ })
+ setModalOpen(true)
+ }
+
+ const openEditModal = (record: AdminCategoryItem) => {
+ setEditingRecord(record)
+ modalForm.setFieldsValue({
+ parentId: record.parentId || ROOT_PARENT_ID,
+ categoryName: record.categoryName,
+ categoryIcon: record.categoryIcon || '',
+ sort: record.sort ?? 0,
+ status: record.status,
+ })
+ setModalOpen(true)
+ }
+
+ const handleDelete = async (record: AdminCategoryItem) => {
+ try {
+ await apiAdminCategoryDelete({ id: record.id })
+ toast.success('类目已删除')
+ await loadData()
+ } catch (error) {
+ toast.error(error instanceof Error ? error.message : '类目删除失败')
+ }
+ }
+
+ const handleSave = async () => {
+ try {
+ const values = await modalForm.validateFields()
+ setSaving(true)
+ const payload: AdminCategorySaveParams = {
+ id: editingRecord?.id,
+ parentId: Number(values.parentId ?? ROOT_PARENT_ID),
+ categoryName: values.categoryName.trim(),
+ categoryIcon: values.categoryIcon?.trim() || undefined,
+ sort: values.sort ?? 0,
+ status: values.status,
+ }
+ await apiAdminCategorySave(payload)
+ toast.success(editingRecord ? '类目已更新' : '类目已新增')
+ resetModal()
+ await loadData()
+ } catch (error) {
+ if (error instanceof Error && error.message) {
+ toast.error(error.message)
+ }
+ } finally {
+ setSaving(false)
+ }
+ }
+
+ const columns: ColumnsType = [
+ {
+ title: '类目名称',
+ dataIndex: 'categoryName',
+ width: 240,
+ render: (_, record) => (
+
+
+ {record.categoryName?.slice(0, 1)}
+
+ {compactText(record.categoryName)}
+
+ ),
+ },
+ {
+ title: '类目 ID',
+ dataIndex: 'id',
+ width: 92,
+ },
+ {
+ title: '父类目 ID',
+ dataIndex: 'parentId',
+ width: 106,
+ render: value => Number(value || 0),
+ },
+ {
+ title: '层级',
+ dataIndex: 'categoryLevel',
+ width: 88,
+ render: value => getCategoryLevelLabel(value),
+ },
+ {
+ title: '类目路径',
+ dataIndex: 'categoryPath',
+ width: 220,
+ render: value => (
+
+ {compactText(value)}
+
+ ),
+ },
+ {
+ title: '类目图标',
+ dataIndex: 'categoryIcon',
+ width: 220,
+ render: value => compactText(value),
+ },
+ {
+ title: '叶子类目',
+ dataIndex: 'isLeaf',
+ width: 92,
+ render: value => getBooleanLabel(value),
+ },
+ {
+ title: '商品数量',
+ dataIndex: 'goodsCount',
+ width: 96,
+ },
+ {
+ title: '排序',
+ dataIndex: 'sort',
+ width: 76,
+ },
+ {
+ title: '状态',
+ dataIndex: 'status',
+ width: 92,
+ render: value => ,
+ },
+ {
+ title: '更新时间',
+ dataIndex: 'updatedAt',
+ width: 170,
+ render: value => formatTimestamp(value),
+ },
+ {
+ title: '操作',
+ key: 'action',
+ fixed: 'right',
+ width: 200,
+ render: (_, record) => (
+
+
+
+ void handleDelete(record)}
+ >
+
+
+
+ ),
+ },
+ ]
+
+ return (
+
+
+
+ 商品类目管理
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ rowKey="id"
+ loading={loading}
+ columns={columns}
+ dataSource={list}
+ className="table-top-align"
+ pagination={false}
+ defaultExpandAllRows
+ scroll={{ x: 1680 }}
+ />
+
+
+ void handleSave()}
+ >
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ )
+}
diff --git a/src/pages/admin/goods/index.tsx b/src/pages/admin/goods/index.tsx
new file mode 100644
index 0000000..6d2c9f6
--- /dev/null
+++ b/src/pages/admin/goods/index.tsx
@@ -0,0 +1,397 @@
+import { useEffect, useMemo, useState } from 'react'
+import {
+ Avatar,
+ Button,
+ Card,
+ Descriptions,
+ Drawer,
+ Form,
+ Image,
+ Input,
+ Select,
+ Space,
+ Table,
+ Typography,
+} from 'antd'
+import type { ColumnsType, TablePaginationConfig } from 'antd/es/table'
+import { toast } from 'sonner'
+import AuditModal, { type AuditModalValues } from '@/components/AuditModal'
+import JsonPreviewPanel from '@/components/JsonPreviewPanel'
+import StatusTag from '@/components/StatusTag'
+import { apiAdminGoodsAudit, apiAdminGoodsPage, apiAdminMerchantPage } from '@/apis/apiMallAdmin'
+import { useUserStore } from '@/store/user'
+import type { AdminGoodsPageParams, GoodsSummary, MerchantInfo } from '@/types/mall'
+import { compactText, formatAmount, formatTimestamp } from '@/utils/format'
+import { auditStatusOptions, saleStatusOptions, withAllOption } from '@/utils/mall'
+
+const DEFAULT_PAGE_SIZE = 20
+
+interface MerchantSelectOption {
+ label: string
+ value: string
+}
+
+interface GoodsSearchValues {
+ merchantId?: string
+ keyword?: string
+ auditStatus?: string
+ saleStatus?: string
+ page: number
+ size: number
+}
+
+const normalizeFilterValues = (values: GoodsSearchValues): AdminGoodsPageParams => ({
+ merchantId: values.merchantId || undefined,
+ keyword: values.keyword?.trim() || undefined,
+ auditStatus: values.auditStatus as AdminGoodsPageParams['auditStatus'],
+ saleStatus: values.saleStatus as AdminGoodsPageParams['saleStatus'],
+ page: values.page,
+ size: values.size,
+})
+
+const buildMerchantOption = (merchant: Pick): MerchantSelectOption => ({
+ value: String(merchant.id),
+ label: compactText(merchant.merchantName),
+})
+
+const mergeMerchantOptions = (current: MerchantSelectOption[], next: MerchantSelectOption[]) => {
+ const merged = new Map()
+ current.forEach(item => merged.set(item.value, item))
+ next.forEach(item => merged.set(item.value, item))
+ return Array.from(merged.values())
+}
+
+export default function GoodsReviewPage() {
+ const [form] = Form.useForm()
+ const operatorId = useUserStore(state => state.token?.uid ?? 0)
+ const [loading, setLoading] = useState(false)
+ const [auditLoading, setAuditLoading] = useState(false)
+ const [merchantLoading, setMerchantLoading] = useState(false)
+ const [merchantOptions, setMerchantOptions] = useState([])
+ const [list, setList] = useState([])
+ const [total, setTotal] = useState(0)
+ const [selectedRecord, setSelectedRecord] = useState(null)
+ const [viewRecord, setViewRecord] = useState(null)
+ const [pagination, setPagination] = useState({ current: 1, pageSize: DEFAULT_PAGE_SIZE })
+
+ const loadData = async (page = pagination.current, size = pagination.pageSize) => {
+ try {
+ setLoading(true)
+ const values = form.getFieldsValue()
+ const response = await apiAdminGoodsPage(
+ normalizeFilterValues({
+ ...values,
+ page,
+ size,
+ })
+ )
+ setList(response.data.list)
+ setTotal(response.data.total)
+ setPagination({ current: page, pageSize: size })
+ } catch (error) {
+ toast.error(error instanceof Error ? error.message : '商品列表加载失败')
+ } finally {
+ setLoading(false)
+ }
+ }
+
+ const loadMerchantOptions = async (keyword?: string) => {
+ try {
+ setMerchantLoading(true)
+ const response = await apiAdminMerchantPage({
+ keyword: keyword?.trim() || undefined,
+ page: 1,
+ size: 20,
+ })
+ const nextOptions = response.data.list.map(item => buildMerchantOption(item))
+ setMerchantOptions(current => mergeMerchantOptions(current, nextOptions))
+ } catch (error) {
+ toast.error(error instanceof Error ? error.message : '商家筛选项加载失败')
+ } finally {
+ setMerchantLoading(false)
+ }
+ }
+
+ useEffect(() => {
+ form.setFieldsValue({
+ page: 1,
+ size: DEFAULT_PAGE_SIZE,
+ })
+ void loadData(1, DEFAULT_PAGE_SIZE)
+ }, [])
+
+ const columns = useMemo>(
+ () => [
+ {
+ title: '商品信息',
+ key: 'goods',
+ width: 320,
+ render: (_, record) => (
+
+ {record.coverUrl ? (
+
+ ) : (
+
+ {record.spuName?.slice(0, 1)}
+
+ )}
+
+ {compactText(record.spuName)}
+ {compactText(record.subTitle)}
+
+ SPU: {compactText(record.spuNo)}
+
+
+
+ ),
+ },
+ {
+ title: '商家信息',
+ key: 'owner',
+ width: 160,
+ render: (_, record) => (
+
+ 商家 ID: {record.merchantId || '--'}
+
+ 类目: {compactText(record.categoryName) !== '--' ? compactText(record.categoryName) : record.categoryId || '--'}
+
+
+ ),
+ },
+ {
+ title: '审核状态',
+ dataIndex: 'auditStatus',
+ width: 120,
+ render: value => ,
+ },
+ {
+ title: '上下架',
+ dataIndex: 'saleStatus',
+ width: 100,
+ render: value => ,
+ },
+ {
+ title: '价格区间',
+ key: 'price',
+ width: 160,
+ render: (_, record) => `${formatAmount(record.minPrice)} - ${formatAmount(record.maxPrice)}`,
+ },
+ {
+ title: '库存',
+ key: 'stock',
+ width: 130,
+ render: (_, record) => (
+
+ 可售: {record.totalStock || 0}
+ 锁定: {record.lockedStock || 0}
+
+ ),
+ },
+ {
+ title: '创建时间',
+ dataIndex: 'createdAt',
+ width: 170,
+ render: value => formatTimestamp(value),
+ },
+ {
+ title: '操作',
+ key: 'action',
+ fixed: 'right',
+ width: 180,
+ render: (_, record) => (
+
+
+
+
+ ),
+ },
+ ],
+ []
+ )
+
+ const handleAuditSubmit = async (values: AuditModalValues) => {
+ if (!selectedRecord) {
+ return
+ }
+
+ try {
+ setAuditLoading(true)
+ await apiAdminGoodsAudit({
+ spuId: selectedRecord.id,
+ auditStatus: values.auditStatus,
+ remark: values.remark?.trim() || undefined,
+ operatorId,
+ })
+ toast.success('商品审核已提交')
+ setSelectedRecord(null)
+ await loadData()
+ } catch (error) {
+ toast.error(error instanceof Error ? error.message : '商品审核失败')
+ } finally {
+ setAuditLoading(false)
+ }
+ }
+
+ const handleTableChange = (next: TablePaginationConfig) => {
+ void loadData(next.current || 1, next.pageSize || DEFAULT_PAGE_SIZE)
+ }
+
+ return (
+
+
+
+ 商品审核列表
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ rowKey="id"
+ loading={loading}
+ columns={columns}
+ dataSource={list}
+ className="table-top-align"
+ scroll={{ x: 1480 }}
+ pagination={{
+ current: pagination.current,
+ pageSize: pagination.pageSize,
+ total,
+ showSizeChanger: true,
+ showTotal: count => `共 ${count} 条`,
+ }}
+ onChange={handleTableChange}
+ />
+
+
+ setSelectedRecord(null)}
+ onSubmit={handleAuditSubmit}
+ summary={
+ selectedRecord ? (
+
+ {compactText(selectedRecord.spuName)}
+ {compactText(selectedRecord.spuNo)}
+ {selectedRecord.merchantId}
+
+ {compactText(selectedRecord.categoryName) !== '--'
+ ? compactText(selectedRecord.categoryName)
+ : selectedRecord.categoryId}
+
+
+ {formatAmount(selectedRecord.minPrice)} - {formatAmount(selectedRecord.maxPrice)}
+
+
+ ) : null
+ }
+ />
+
+ setViewRecord(null)}
+ >
+ {viewRecord ? (
+
+
+ {viewRecord.id}
+ {viewRecord.merchantId}
+ {viewRecord.categoryId}
+ {compactText(viewRecord.categoryName)}
+ {compactText(viewRecord.spuNo)}
+ {compactText(viewRecord.spuName)}
+
+ {compactText(viewRecord.subTitle)}
+
+
+ {compactText(viewRecord.coverUrl)}
+
+
+
+
+
+
+
+
+
+
+ {viewRecord.sort}
+ {viewRecord.salesVolume}
+ {viewRecord.virtualSales}
+ {viewRecord.viewCount}
+ {compactText(viewRecord.minPrice)}
+ {compactText(viewRecord.maxPrice)}
+ {viewRecord.totalStock}
+ {viewRecord.lockedStock}
+ {formatTimestamp(viewRecord.createdAt)}
+ {formatTimestamp(viewRecord.updatedAt)}
+
+
+
+
+ ) : null}
+
+
+ )
+}
diff --git a/src/pages/admin/homeChannels/index.tsx b/src/pages/admin/homeChannels/index.tsx
new file mode 100644
index 0000000..a5019ca
--- /dev/null
+++ b/src/pages/admin/homeChannels/index.tsx
@@ -0,0 +1,396 @@
+import { useEffect, useState } from 'react'
+import dayjs, { type Dayjs } from 'dayjs'
+import {
+ Avatar,
+ Button,
+ Card,
+ DatePicker,
+ Form,
+ Input,
+ InputNumber,
+ Modal,
+ Popconfirm,
+ Select,
+ Space,
+ Table,
+ Typography,
+} from 'antd'
+import type { ColumnsType, TablePaginationConfig } from 'antd/es/table'
+import { toast } from 'sonner'
+import {
+ apiAdminHomeChannelDelete,
+ apiAdminHomeChannelPage,
+ apiAdminHomeChannelSave,
+} from '@/apis/apiMallAdmin'
+import StatusTag from '@/components/StatusTag'
+import type {
+ AdminHomeChannelItem,
+ AdminHomeChannelPageParams,
+ AdminHomeChannelSaveParams,
+ EnabledStatus,
+} from '@/types/mall'
+import { compactText, formatTimestamp } from '@/utils/format'
+import { enabledStatusOptions, getLinkTypeLabel, linkTypeOptions, withAllOption } from '@/utils/mall'
+
+const DEFAULT_PAGE_SIZE = 20
+const DEFAULT_STATUS: EnabledStatus = 'enabled'
+
+interface HomeChannelFilterValues {
+ keyword?: string
+ status?: EnabledStatus | ''
+ page: number
+ size: number
+}
+
+interface HomeChannelFormValues {
+ title: string
+ subTitle?: string
+ iconUrl?: string
+ linkType?: string
+ linkValue?: string
+ sort?: number
+ status: EnabledStatus
+ startTime?: Dayjs | null
+ endTime?: Dayjs | null
+}
+
+const normalizeSearchValues = (values: HomeChannelFilterValues): AdminHomeChannelPageParams => ({
+ keyword: values.keyword?.trim() || undefined,
+ status: values.status || undefined,
+ page: values.page,
+ size: values.size,
+})
+
+export default function HomeChannelManagementPage() {
+ const [form] = Form.useForm()
+ const [modalForm] = Form.useForm()
+ const [loading, setLoading] = useState(false)
+ const [saving, setSaving] = useState(false)
+ const [list, setList] = useState([])
+ const [total, setTotal] = useState(0)
+ const [pagination, setPagination] = useState({ current: 1, pageSize: DEFAULT_PAGE_SIZE })
+ const [modalOpen, setModalOpen] = useState(false)
+ const [editingRecord, setEditingRecord] = useState(null)
+
+ const loadData = async (page = pagination.current, size = pagination.pageSize) => {
+ try {
+ setLoading(true)
+ const response = await apiAdminHomeChannelPage(
+ normalizeSearchValues({
+ ...form.getFieldsValue(),
+ page,
+ size,
+ })
+ )
+ setList(response.data.list)
+ setTotal(response.data.total)
+ setPagination({ current: page, pageSize: size })
+ } catch (error) {
+ toast.error(error instanceof Error ? error.message : '频道卡片列表加载失败')
+ } finally {
+ setLoading(false)
+ }
+ }
+
+ useEffect(() => {
+ form.setFieldsValue({
+ page: 1,
+ size: DEFAULT_PAGE_SIZE,
+ })
+ void loadData(1, DEFAULT_PAGE_SIZE)
+ }, [])
+
+ const resetModal = () => {
+ setModalOpen(false)
+ setEditingRecord(null)
+ modalForm.resetFields()
+ }
+
+ const openCreateModal = () => {
+ setEditingRecord(null)
+ modalForm.setFieldsValue({
+ sort: 0,
+ status: DEFAULT_STATUS,
+ startTime: null,
+ endTime: null,
+ })
+ setModalOpen(true)
+ }
+
+ const openEditModal = (record: AdminHomeChannelItem) => {
+ setEditingRecord(record)
+ modalForm.setFieldsValue({
+ title: record.title,
+ subTitle: record.subTitle || '',
+ iconUrl: record.iconUrl || '',
+ linkType: record.linkType || undefined,
+ linkValue: record.linkValue || '',
+ sort: record.sort ?? 0,
+ status: record.status,
+ startTime: record.startTime ? dayjs.unix(record.startTime) : null,
+ endTime: record.endTime ? dayjs.unix(record.endTime) : null,
+ })
+ setModalOpen(true)
+ }
+
+ const handleDelete = async (record: AdminHomeChannelItem) => {
+ try {
+ await apiAdminHomeChannelDelete({ id: record.id })
+ toast.success('频道卡片已删除')
+ await loadData()
+ } catch (error) {
+ toast.error(error instanceof Error ? error.message : '频道卡片删除失败')
+ }
+ }
+
+ const handleSave = async () => {
+ try {
+ const values = await modalForm.validateFields()
+ setSaving(true)
+ const payload: AdminHomeChannelSaveParams = {
+ id: editingRecord?.id,
+ title: values.title.trim(),
+ subTitle: values.subTitle?.trim() || undefined,
+ iconUrl: values.iconUrl?.trim() || undefined,
+ linkType: values.linkType || undefined,
+ linkValue: values.linkValue?.trim() || undefined,
+ sort: values.sort ?? 0,
+ status: values.status,
+ startTime: values.startTime?.unix(),
+ endTime: values.endTime?.unix(),
+ }
+ await apiAdminHomeChannelSave(payload)
+ toast.success(editingRecord ? '频道卡片已更新' : '频道卡片已新增')
+ resetModal()
+ await loadData()
+ } catch (error) {
+ if (error instanceof Error && error.message) {
+ toast.error(error.message)
+ }
+ } finally {
+ setSaving(false)
+ }
+ }
+
+ const handleTableChange = (next: TablePaginationConfig) => {
+ void loadData(next.current || 1, next.pageSize || DEFAULT_PAGE_SIZE)
+ }
+
+ const columns: ColumnsType = [
+ {
+ title: '频道卡片',
+ key: 'channel',
+ width: 240,
+ render: (_, record) => (
+
+
+ {record.title?.slice(0, 1)}
+
+
+ {compactText(record.title)}
+ {compactText(record.subTitle)}
+
+
+ ),
+ },
+ {
+ title: '跳转信息',
+ key: 'link',
+ width: 220,
+ render: (_, record) => (
+
+ {getLinkTypeLabel(record.linkType)}
+
+ {compactText(record.linkValue)}
+
+
+ ),
+ },
+ {
+ title: '排序',
+ dataIndex: 'sort',
+ width: 76,
+ },
+ {
+ title: '状态',
+ dataIndex: 'status',
+ width: 96,
+ render: value => ,
+ },
+ {
+ title: '开始时间',
+ dataIndex: 'startTime',
+ width: 170,
+ render: value => formatTimestamp(value),
+ },
+ {
+ title: '结束时间',
+ dataIndex: 'endTime',
+ width: 170,
+ render: value => formatTimestamp(value),
+ },
+ {
+ title: '更新时间',
+ dataIndex: 'updatedAt',
+ width: 170,
+ render: value => formatTimestamp(value),
+ },
+ {
+ title: '操作',
+ key: 'action',
+ fixed: 'right',
+ width: 144,
+ render: (_, record) => (
+
+
+ void handleDelete(record)}
+ >
+
+
+
+ ),
+ },
+ ]
+
+ return (
+
+
+
+ 首页频道卡片
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ rowKey="id"
+ loading={loading}
+ columns={columns}
+ dataSource={list}
+ className="table-top-align"
+ scroll={{ x: 1240 }}
+ pagination={{
+ current: pagination.current,
+ pageSize: pagination.pageSize,
+ total,
+ showSizeChanger: true,
+ showTotal: count => `共 ${count} 条`,
+ }}
+ onChange={handleTableChange}
+ />
+
+
+ void handleSave()}
+ >
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ({
+ validator(_, value?: Dayjs | null) {
+ const startTime = getFieldValue('startTime') as Dayjs | null | undefined
+ if (!startTime || !value || value.valueOf() >= startTime.valueOf()) {
+ return Promise.resolve()
+ }
+ return Promise.reject(new Error('结束时间不能早于开始时间'))
+ },
+ }),
+ ]}
+ >
+
+
+
+
+
+
+ )
+}
diff --git a/src/pages/admin/merchants/components/MerchantCreateModal.tsx b/src/pages/admin/merchants/components/MerchantCreateModal.tsx
new file mode 100644
index 0000000..5bc343e
--- /dev/null
+++ b/src/pages/admin/merchants/components/MerchantCreateModal.tsx
@@ -0,0 +1,190 @@
+import { Col, Form, Input, Modal, Radio, Row, Typography } from 'antd'
+import type { AdminMerchantCreateParams, MerchantLoginType } from '@/types/mall'
+
+const { TextArea } = Input
+
+const PHONE_PATTERN = /^\+?[0-9][0-9\s-]{5,19}$/
+
+export interface MerchantCreateFormValues extends AdminMerchantCreateParams {}
+
+interface MerchantCreateModalProps {
+ open: boolean
+ loading?: boolean
+ onCancel: () => void
+ onSubmit: (values: MerchantCreateFormValues) => void | Promise
+}
+
+const loginTypeOptions: Array<{ label: string; value: MerchantLoginType }> = [
+ { label: '手机号', value: '0' },
+ { label: '邮箱', value: '1' },
+]
+
+export default function MerchantCreateModal({
+ open,
+ loading,
+ onCancel,
+ onSubmit,
+}: MerchantCreateModalProps) {
+ const [form] = Form.useForm()
+ const loginType = Form.useWatch('loginType', form) ?? '0'
+
+ const handleOk = async () => {
+ const values = await form.validateFields()
+ await onSubmit(values)
+ }
+
+ return (
+ void handleOk()}
+ >
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 商家信息
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ )
+}
diff --git a/src/pages/admin/merchants/index.tsx b/src/pages/admin/merchants/index.tsx
new file mode 100644
index 0000000..da148a9
--- /dev/null
+++ b/src/pages/admin/merchants/index.tsx
@@ -0,0 +1,353 @@
+import { useEffect, useMemo, useState } from 'react'
+import {
+ Button,
+ Card,
+ Descriptions,
+ Drawer,
+ Form,
+ Input,
+ Select,
+ Space,
+ Table,
+ Typography,
+} from 'antd'
+import type { ColumnsType, TablePaginationConfig } from 'antd/es/table'
+import { toast } from 'sonner'
+import AuditModal, { type AuditModalValues } from '@/components/AuditModal'
+import JsonPreviewPanel from '@/components/JsonPreviewPanel'
+import StatusTag from '@/components/StatusTag'
+import { apiAdminMerchantAudit, apiAdminMerchantCreate, apiAdminMerchantPage } from '@/apis/apiMallAdmin'
+import MerchantCreateModal, {
+ type MerchantCreateFormValues,
+} from '@/pages/admin/merchants/components/MerchantCreateModal'
+import { useUserStore } from '@/store/user'
+import type { AdminMerchantPageParams, MerchantInfo } from '@/types/mall'
+import { compactText, formatTimestamp } from '@/utils/format'
+import { auditStatusOptions, enabledStatusOptions, withAllOption } from '@/utils/mall'
+
+const DEFAULT_PAGE_SIZE = 20
+
+const normalizeFilterValues = (values: MerchantSearchValues): AdminMerchantPageParams => ({
+ keyword: values.keyword?.trim() || undefined,
+ status: values.status as AdminMerchantPageParams['status'],
+ auditStatus: values.auditStatus as AdminMerchantPageParams['auditStatus'],
+ page: values.page,
+ size: values.size,
+})
+
+interface MerchantSearchValues {
+ keyword?: string
+ status?: string
+ auditStatus?: string
+ page: number
+ size: number
+}
+
+export default function MerchantReviewPage() {
+ const [form] = Form.useForm()
+ const operatorId = useUserStore(state => state.token?.uid ?? 0)
+ const [loading, setLoading] = useState(false)
+ const [auditLoading, setAuditLoading] = useState(false)
+ const [createLoading, setCreateLoading] = useState(false)
+ const [list, setList] = useState([])
+ const [total, setTotal] = useState(0)
+ const [selectedRecord, setSelectedRecord] = useState(null)
+ const [viewRecord, setViewRecord] = useState(null)
+ const [createOpen, setCreateOpen] = useState(false)
+ const [pagination, setPagination] = useState({ current: 1, pageSize: DEFAULT_PAGE_SIZE })
+
+ const loadData = async (page = pagination.current, size = pagination.pageSize) => {
+ try {
+ setLoading(true)
+ const values = form.getFieldsValue()
+ const response = await apiAdminMerchantPage(
+ normalizeFilterValues({
+ ...values,
+ page,
+ size,
+ })
+ )
+
+ setList(response.data.list)
+ setTotal(response.data.total)
+ setPagination({ current: page, pageSize: size })
+ } catch (error) {
+ toast.error(error instanceof Error ? error.message : '商家列表加载失败')
+ } finally {
+ setLoading(false)
+ }
+ }
+
+ useEffect(() => {
+ form.setFieldsValue({
+ page: 1,
+ size: DEFAULT_PAGE_SIZE,
+ })
+ void loadData(1, DEFAULT_PAGE_SIZE)
+ }, [])
+
+ const columns = useMemo>(
+ () => [
+ {
+ title: '商家信息',
+ key: 'merchant',
+ width: 250,
+ render: (_, record) => (
+
+ {compactText(record.merchantName)}
+
+ 编号: {compactText(record.merchantNo)}
+
+ UID: {record.uid || '--'}
+
+ ),
+ },
+ {
+ title: '联系人',
+ key: 'contact',
+ width: 180,
+ render: (_, record) => (
+
+ {compactText(record.contactName)}
+ {compactText(record.contactMobile)}
+ {compactText(record.contactEmail)}
+
+ ),
+ },
+ {
+ title: '营业执照号',
+ dataIndex: 'licenseNo',
+ width: 180,
+ render: value => {compactText(value)},
+ },
+ {
+ title: '启停状态',
+ dataIndex: 'status',
+ width: 110,
+ render: value => ,
+ },
+ {
+ title: '审核状态',
+ dataIndex: 'auditStatus',
+ width: 120,
+ render: value => ,
+ },
+ {
+ title: '结算状态',
+ dataIndex: 'settleStatus',
+ width: 120,
+ render: value => ,
+ },
+ {
+ title: '备注',
+ dataIndex: 'remark',
+ width: 180,
+ render: value => compactText(value),
+ },
+ {
+ title: '申请时间',
+ dataIndex: 'createdAt',
+ width: 170,
+ render: value => formatTimestamp(value),
+ },
+ {
+ title: '操作',
+ key: 'action',
+ fixed: 'right',
+ width: 180,
+ render: (_, record) => (
+
+
+
+
+ ),
+ },
+ ],
+ []
+ )
+
+ const handleTableChange = (next: TablePaginationConfig) => {
+ void loadData(next.current || 1, next.pageSize || DEFAULT_PAGE_SIZE)
+ }
+
+ const handleAuditSubmit = async (values: AuditModalValues) => {
+ if (!selectedRecord) {
+ return
+ }
+
+ try {
+ setAuditLoading(true)
+ await apiAdminMerchantAudit({
+ merchantId: String(selectedRecord.id),
+ auditStatus: values.auditStatus,
+ remark: values.remark?.trim() || undefined,
+ operatorId,
+ })
+ toast.success('商家审核已提交')
+ setSelectedRecord(null)
+ await loadData()
+ } catch (error) {
+ toast.error(error instanceof Error ? error.message : '商家审核失败')
+ } finally {
+ setAuditLoading(false)
+ }
+ }
+
+ const handleCreateSubmit = async (values: MerchantCreateFormValues) => {
+ try {
+ setCreateLoading(true)
+ const response = await apiAdminMerchantCreate({
+ loginType: values.loginType,
+ account: values.account.trim(),
+ password: values.password,
+ merchantName: values.merchantName.trim(),
+ contactName: values.contactName.trim(),
+ contactMobile: values.contactMobile?.trim() || '',
+ contactEmail: values.contactEmail?.trim() || '',
+ licenseNo: values.licenseNo?.trim() || '',
+ licenseImagesJson: '',
+ extraInfoJson: '',
+ remark: values.remark?.trim() || '',
+ createIp: '',
+ })
+ toast.success(`商家账号已创建:${response.data.merchantNo} / UID ${response.data.uid}`)
+ setCreateOpen(false)
+ await loadData()
+ } catch (error) {
+ toast.error(error instanceof Error ? error.message : '商家账号创建失败')
+ } finally {
+ setCreateLoading(false)
+ }
+ }
+
+ return (
+
+
+
+ 商家审核列表
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ rowKey="id"
+ loading={loading}
+ columns={columns}
+ dataSource={list}
+ className="table-top-align"
+ scroll={{ x: 1400 }}
+ pagination={{
+ current: pagination.current,
+ pageSize: pagination.pageSize,
+ total,
+ showSizeChanger: true,
+ showTotal: count => `共 ${count} 条`,
+ }}
+ onChange={handleTableChange}
+ />
+
+
+ setSelectedRecord(null)}
+ onSubmit={handleAuditSubmit}
+ summary={
+ selectedRecord ? (
+
+ {compactText(selectedRecord.merchantName)}
+ {compactText(selectedRecord.merchantNo)}
+ {compactText(selectedRecord.contactName)}
+ {compactText(selectedRecord.contactMobile)}
+
+
+
+
+ ) : null
+ }
+ />
+
+ setViewRecord(null)}
+ >
+ {viewRecord ? (
+
+
+ {viewRecord.id}
+ {viewRecord.uid}
+ {compactText(viewRecord.merchantNo)}
+ {compactText(viewRecord.merchantName)}
+
+
+
+
+
+
+
+
+
+ {compactText(viewRecord.remark)}
+ {compactText(viewRecord.contactName)}
+ {compactText(viewRecord.contactMobile)}
+ {compactText(viewRecord.contactEmail)}
+ {compactText(viewRecord.licenseNo)}
+ {formatTimestamp(viewRecord.approvedAt)}
+ {formatTimestamp(viewRecord.createdAt)}
+ {formatTimestamp(viewRecord.updatedAt)}
+
+
+
+
+
+ ) : null}
+
+
+ setCreateOpen(false)}
+ onSubmit={handleCreateSubmit}
+ />
+
+ )
+}
diff --git a/src/pages/admin/orders/DetailPage.tsx b/src/pages/admin/orders/DetailPage.tsx
new file mode 100644
index 0000000..91822de
--- /dev/null
+++ b/src/pages/admin/orders/DetailPage.tsx
@@ -0,0 +1,261 @@
+import { useEffect, useMemo, useState } from 'react'
+import {
+ Alert,
+ Button,
+ Card,
+ Descriptions,
+ Empty,
+ Image,
+ Space,
+ Spin,
+ Table,
+ Typography,
+} from 'antd'
+import type { ColumnsType } from 'antd/es/table'
+import { ArrowLeftOutlined } from '@ant-design/icons'
+import { useNavigate, useParams } from 'react-router-dom'
+import { toast } from 'sonner'
+import StatusTag from '@/components/StatusTag'
+import { apiAdminOrderDetail } from '@/apis/apiMallAdmin'
+import type { OrderDetailInfo, OrderItemInfo } from '@/types/mall'
+import {
+ buildAddress,
+ compactText,
+ formatAmount,
+ formatTimestamp,
+ parseSpecSummary,
+ safeJsonPretty,
+} from '@/utils/format'
+
+export default function OrderDetailPage() {
+ const navigate = useNavigate()
+ const { orderId } = useParams()
+ const [loading, setLoading] = useState(false)
+ const [detail, setDetail] = useState(null)
+
+ useEffect(() => {
+ const id = Number(orderId)
+ if (!id) {
+ toast.error('订单 ID 无效')
+ return
+ }
+
+ const loadDetail = async () => {
+ try {
+ setLoading(true)
+ const response = await apiAdminOrderDetail({ orderId: id })
+ setDetail(response.data)
+ } catch (error) {
+ toast.error(error instanceof Error ? error.message : '订单详情加载失败')
+ } finally {
+ setLoading(false)
+ }
+ }
+
+ void loadDetail()
+ }, [orderId])
+
+ const itemColumns = useMemo>(
+ () => [
+ {
+ title: '商品',
+ key: 'goods',
+ width: 340,
+ render: (_, record) => (
+
+ {record.coverUrl ? (
+
+ ) : null}
+
+ {compactText(record.spuName)}
+ {compactText(record.skuName)}
+ {parseSpecSummary(record.skuSpecJson)}
+
+
+ ),
+ },
+ {
+ title: '数量',
+ dataIndex: 'buyNum',
+ width: 90,
+ },
+ {
+ title: '成交价',
+ dataIndex: 'salePrice',
+ width: 120,
+ render: value => formatAmount(value, detail?.currency),
+ },
+ {
+ title: '商品金额',
+ dataIndex: 'itemAmount',
+ width: 140,
+ render: value => formatAmount(value, detail?.currency),
+ },
+ {
+ title: '退款金额',
+ dataIndex: 'refundAmount',
+ width: 140,
+ render: value => formatAmount(value, detail?.currency),
+ },
+ {
+ title: '售后状态',
+ dataIndex: 'afterSaleStatus',
+ width: 120,
+ render: value => ,
+ },
+ ],
+ [detail?.currency]
+ )
+
+ if (loading) {
+ return (
+
+
+
+ )
+ }
+
+ if (!detail) {
+ return (
+
+
+
+ )
+ }
+
+ return (
+
+
+
+ } style={{ paddingInline: 0 }} onClick={() => navigate('/orders')}>
+ 返回订单列表
+
+
+ 订单详情
+
+
+
+
+
+
+ {detail.id}
+ {detail.tradeId}
+ {compactText(detail.orderNo)}
+ {compactText(detail.tradeNo)}
+ {compactText(detail.merchantName || detail.storeName)}
+
+
+
+
+
+
+ {compactText(detail.payChannel)}
+ {detail.merchantId}
+ {detail.uid ?? detail.memberId}
+ {formatTimestamp(detail.createdAt)}
+ {formatTimestamp(detail.payTime)}
+ {formatTimestamp(detail.shipTime)}
+ {formatTimestamp(detail.finishTime)}
+ {formatTimestamp(detail.cancelTime)}
+ {formatTimestamp(detail.autoConfirmAt)}
+ {formatTimestamp(detail.updatedAt)}
+
+
+
+
+
+ {compactText(detail.receiverName)}
+ {compactText(detail.receiverMobile)}
+ {compactText(detail.receiverCountryCode)}
+ {compactText(detail.receiverZipCode)}
+
+ {buildAddress(
+ detail.receiverProvince,
+ detail.receiverCity,
+ detail.receiverDistrict,
+ detail.receiverAddress
+ )}
+
+ {compactText(detail.deliveryCompany)}
+ {compactText(detail.deliveryNo)}
+
+ {compactText(detail.buyerRemark)}
+
+
+ {compactText(detail.sellerRemark)}
+
+
+
+
+
+
+ {formatAmount(detail.goodsAmount, detail.currency)}
+ {formatAmount(detail.freightAmount, detail.currency)}
+ {formatAmount(detail.discountAmount, detail.currency)}
+ {formatAmount(detail.payAmount, detail.currency)}
+
+
+
+
+
+ rowKey="id"
+ columns={itemColumns}
+ dataSource={detail.items || []}
+ pagination={false}
+ scroll={{ x: 960 }}
+ expandable={{
+ expandedRowRender: record => (
+
+ {record.id}
+ {record.orderId}
+ {record.tradeId}
+ {compactText(record.orderNo)}
+ {record.spuId}
+ {record.skuId}
+ {compactText(record.spuName)}
+ {compactText(record.skuName)}
+
+ {compactText(record.coverUrl)}
+
+
+ {parseSpecSummary(record.skuSpecJson)}
+
+ {record.buyNum}
+ {formatAmount(record.salePrice, detail.currency)}
+ {formatAmount(record.originPrice, detail.currency)}
+
+ {formatAmount(record.discountAmount, detail.currency)}
+
+ {formatAmount(record.itemAmount, detail.currency)}
+
+ {formatAmount(record.refundAmount, detail.currency)}
+
+
+
+
+
+ ),
+ }}
+ />
+
+
+ {detail.extraInfoJson ? (
+
+
+ {safeJsonPretty(detail.extraInfoJson)}
+
+ ) : null}
+
+ )
+}
diff --git a/src/pages/admin/orders/index.tsx b/src/pages/admin/orders/index.tsx
new file mode 100644
index 0000000..f6e6497
--- /dev/null
+++ b/src/pages/admin/orders/index.tsx
@@ -0,0 +1,328 @@
+import { useEffect, useMemo, useState } from 'react'
+import {
+ Button,
+ Card,
+ Descriptions,
+ Drawer,
+ Form,
+ Input,
+ Select,
+ Space,
+ Table,
+ Typography,
+} from 'antd'
+import type { ColumnsType, TablePaginationConfig } from 'antd/es/table'
+import { useNavigate } from 'react-router-dom'
+import { toast } from 'sonner'
+import StatusTag from '@/components/StatusTag'
+import { apiAdminMerchantPage, apiAdminOrderPage } from '@/apis/apiMallAdmin'
+import type { AdminOrderPageParams, MerchantInfo, OrderSummary } from '@/types/mall'
+import { compactText, formatAmount, formatTimestamp } from '@/utils/format'
+import { afterSaleStatusOptions, orderStatusOptions, withAllOption } from '@/utils/mall'
+
+const DEFAULT_PAGE_SIZE = 20
+
+interface MerchantSelectOption {
+ label: string
+ value: string
+}
+
+interface OrderSearchValues {
+ merchantId?: string
+ tradeNo?: string
+ orderNo?: string
+ orderStatus?: string
+ afterSaleStatus?: string
+ page: number
+ size: number
+}
+
+const normalizeFilterValues = (values: OrderSearchValues): AdminOrderPageParams => ({
+ merchantId: values.merchantId || undefined,
+ tradeNo: values.tradeNo?.trim() || undefined,
+ orderNo: values.orderNo?.trim() || undefined,
+ orderStatus: values.orderStatus as AdminOrderPageParams['orderStatus'],
+ afterSaleStatus: values.afterSaleStatus as AdminOrderPageParams['afterSaleStatus'],
+ page: values.page,
+ size: values.size,
+})
+
+const buildMerchantOption = (merchant: Pick): MerchantSelectOption => ({
+ value: String(merchant.id),
+ label: compactText(merchant.merchantName),
+})
+
+const mergeMerchantOptions = (current: MerchantSelectOption[], next: MerchantSelectOption[]) => {
+ const merged = new Map()
+ current.forEach(item => merged.set(item.value, item))
+ next.forEach(item => merged.set(item.value, item))
+ return Array.from(merged.values())
+}
+
+export default function OrderOverviewPage() {
+ const navigate = useNavigate()
+ const [form] = Form.useForm()
+ const [loading, setLoading] = useState(false)
+ const [merchantLoading, setMerchantLoading] = useState(false)
+ const [merchantOptions, setMerchantOptions] = useState([])
+ const [list, setList] = useState([])
+ const [total, setTotal] = useState(0)
+ const [viewRecord, setViewRecord] = useState(null)
+ const [pagination, setPagination] = useState({ current: 1, pageSize: DEFAULT_PAGE_SIZE })
+
+ const loadData = async (page = pagination.current, size = pagination.pageSize) => {
+ try {
+ setLoading(true)
+ const values = form.getFieldsValue()
+ const response = await apiAdminOrderPage(
+ normalizeFilterValues({
+ ...values,
+ page,
+ size,
+ })
+ )
+ setList(response.data.list)
+ setTotal(response.data.total)
+ setPagination({ current: page, pageSize: size })
+ } catch (error) {
+ toast.error(error instanceof Error ? error.message : '订单列表加载失败')
+ } finally {
+ setLoading(false)
+ }
+ }
+
+ const loadMerchantOptions = async (keyword?: string) => {
+ try {
+ setMerchantLoading(true)
+ const response = await apiAdminMerchantPage({
+ keyword: keyword?.trim() || undefined,
+ page: 1,
+ size: 20,
+ })
+ const nextOptions = response.data.list.map(item => buildMerchantOption(item))
+ setMerchantOptions(current => mergeMerchantOptions(current, nextOptions))
+ } catch (error) {
+ toast.error(error instanceof Error ? error.message : '商家筛选项加载失败')
+ } finally {
+ setMerchantLoading(false)
+ }
+ }
+
+ useEffect(() => {
+ form.setFieldsValue({
+ page: 1,
+ size: DEFAULT_PAGE_SIZE,
+ })
+ void loadData(1, DEFAULT_PAGE_SIZE)
+ }, [])
+
+ const columns = useMemo>(
+ () => [
+ {
+ title: '订单编号',
+ key: 'numbers',
+ width: 240,
+ render: (_, record) => (
+
+
+ {compactText(record.orderNo)}
+
+
+ 交易单: {compactText(record.tradeNo)}
+
+
+ ),
+ },
+ {
+ title: '商家信息',
+ key: 'merchant',
+ width: 180,
+ render: (_, record) => (
+
+ {compactText(record.merchantName || record.storeName)}
+ 商家 ID: {record.merchantId || '--'}
+
+ ),
+ },
+ {
+ title: '订单状态',
+ dataIndex: 'orderStatus',
+ width: 120,
+ render: value => ,
+ },
+ {
+ title: '售后状态',
+ dataIndex: 'afterSaleStatus',
+ width: 120,
+ render: value => ,
+ },
+ {
+ title: '金额',
+ key: 'amount',
+ width: 180,
+ render: (_, record) => (
+
+ 应付: {formatAmount(record.payAmount, record.currency)}
+
+ 商品: {formatAmount(record.goodsAmount, record.currency)}
+
+
+ 运费: {formatAmount(record.freightAmount, record.currency)}
+
+
+ ),
+ },
+ {
+ title: '关键时间',
+ key: 'time',
+ width: 200,
+ render: (_, record) => (
+
+ 下单: {formatTimestamp(record.createdAt)}
+ 支付: {formatTimestamp(record.payTime)}
+ 发货: {formatTimestamp(record.shipTime)}
+
+ ),
+ },
+ {
+ title: '操作',
+ key: 'action',
+ fixed: 'right',
+ width: 180,
+ render: (_, record) => (
+
+
+
+
+ ),
+ },
+ ],
+ [navigate]
+ )
+
+ const handleTableChange = (next: TablePaginationConfig) => {
+ void loadData(next.current || 1, next.pageSize || DEFAULT_PAGE_SIZE)
+ }
+
+ return (
+
+
+
+ 平台订单总览
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ rowKey="id"
+ loading={loading}
+ columns={columns}
+ dataSource={list}
+ className="table-top-align"
+ scroll={{ x: 1320 }}
+ pagination={{
+ current: pagination.current,
+ pageSize: pagination.pageSize,
+ total,
+ showSizeChanger: true,
+ showTotal: count => `共 ${count} 条`,
+ }}
+ onChange={handleTableChange}
+ />
+
+
+ setViewRecord(null)}
+ >
+ {viewRecord ? (
+
+ {viewRecord.id}
+ {viewRecord.tradeId}
+ {compactText(viewRecord.tradeNo)}
+ {compactText(viewRecord.orderNo)}
+ {viewRecord.uid ?? viewRecord.memberId}
+ {viewRecord.merchantId}
+
+ {compactText(viewRecord.merchantName || viewRecord.storeName)}
+
+
+
+
+
+
+
+ {compactText(viewRecord.currency)}
+ {compactText(viewRecord.goodsAmount)}
+ {compactText(viewRecord.freightAmount)}
+ {compactText(viewRecord.discountAmount)}
+ {compactText(viewRecord.payAmount)}
+ {formatTimestamp(viewRecord.payTime)}
+ {formatTimestamp(viewRecord.shipTime)}
+ {formatTimestamp(viewRecord.finishTime)}
+ {formatTimestamp(viewRecord.cancelTime)}
+ {formatTimestamp(viewRecord.createdAt)}
+ {formatTimestamp(viewRecord.updatedAt)}
+
+ ) : null}
+
+
+ )
+}
diff --git a/src/pages/admin/recommendStores/index.tsx b/src/pages/admin/recommendStores/index.tsx
new file mode 100644
index 0000000..fa409fb
--- /dev/null
+++ b/src/pages/admin/recommendStores/index.tsx
@@ -0,0 +1,540 @@
+import { useEffect, useState } from 'react'
+import dayjs, { type Dayjs } from 'dayjs'
+import {
+ Avatar,
+ Button,
+ Card,
+ DatePicker,
+ Form,
+ Input,
+ InputNumber,
+ Modal,
+ Popconfirm,
+ Select,
+ Space,
+ Table,
+ Typography,
+} from 'antd'
+import type { ColumnsType, TablePaginationConfig } from 'antd/es/table'
+import { toast } from 'sonner'
+import {
+ apiAdminMerchantPage,
+ apiAdminRecommendMerchantDelete,
+ apiAdminRecommendMerchantPage,
+ apiAdminRecommendMerchantSave,
+} from '@/apis/apiMallAdmin'
+import StatusTag from '@/components/StatusTag'
+import type {
+ AdminRecommendMerchantItem,
+ AdminRecommendMerchantPageParams,
+ AdminRecommendMerchantSaveParams,
+ EnabledStatus,
+ MerchantInfo,
+ RecommendMerchantPositionCode,
+} from '@/types/mall'
+import { compactText, formatTimestamp } from '@/utils/format'
+import {
+ enabledStatusOptions,
+ getRecommendStorePositionLabel,
+ recommendStorePositionOptions,
+ withAllOption,
+} from '@/utils/mall'
+
+const { TextArea } = Input
+const DEFAULT_PAGE_SIZE = 20
+const DEFAULT_STATUS: EnabledStatus = 'enabled'
+const DEFAULT_POSITION_CODE: RecommendMerchantPositionCode = 'home_featured_store'
+
+interface RecommendStoreSearchValues {
+ keyword?: string
+ positionCode?: RecommendMerchantPositionCode | ''
+ status?: EnabledStatus | ''
+ merchantId?: string
+ page: number
+ size: number
+}
+
+interface RecommendStoreFormValues {
+ positionCode: RecommendMerchantPositionCode
+ merchantId?: string
+ mainCategoryName?: string
+ recommendReason?: string
+ rankScore?: number
+ sort?: number
+ status: EnabledStatus
+ startTime?: Dayjs | null
+ endTime?: Dayjs | null
+}
+
+interface MerchantSelectOption {
+ label: string
+ value: string
+}
+
+const normalizeSearchValues = (values: RecommendStoreSearchValues): AdminRecommendMerchantPageParams => ({
+ keyword: values.keyword?.trim() || undefined,
+ positionCode: values.positionCode || undefined,
+ status: values.status || undefined,
+ merchantId: values.merchantId || undefined,
+ page: values.page,
+ size: values.size,
+})
+
+const buildMerchantOption = (merchant: Pick): MerchantSelectOption => ({
+ value: String(merchant.id),
+ label: compactText(merchant.merchantName),
+})
+
+const mergeMerchantOptions = (current: MerchantSelectOption[], next: MerchantSelectOption[]) => {
+ const merged = new Map()
+ current.forEach(item => merged.set(item.value, item))
+ next.forEach(item => merged.set(item.value, item))
+ return Array.from(merged.values())
+}
+
+export default function RecommendStoreManagementPage() {
+ const [form] = Form.useForm()
+ const [modalForm] = Form.useForm()
+ const [loading, setLoading] = useState(false)
+ const [saving, setSaving] = useState(false)
+ const [merchantLoading, setMerchantLoading] = useState(false)
+ const [list, setList] = useState([])
+ const [total, setTotal] = useState(0)
+ const [pagination, setPagination] = useState({ current: 1, pageSize: DEFAULT_PAGE_SIZE })
+ const [modalOpen, setModalOpen] = useState(false)
+ const [editingRecord, setEditingRecord] = useState(null)
+ const [merchantOptions, setMerchantOptions] = useState([])
+
+ const loadData = async (page = pagination.current, size = pagination.pageSize) => {
+ try {
+ setLoading(true)
+ const response = await apiAdminRecommendMerchantPage(
+ normalizeSearchValues({
+ ...form.getFieldsValue(),
+ page,
+ size,
+ })
+ )
+ setList(response.data.list)
+ setTotal(response.data.total)
+ setPagination({ current: page, pageSize: size })
+ } catch (error) {
+ toast.error(error instanceof Error ? error.message : '首页推荐商家列表加载失败')
+ } finally {
+ setLoading(false)
+ }
+ }
+
+ const loadMerchantOptions = async (keyword?: string) => {
+ try {
+ setMerchantLoading(true)
+ const response = await apiAdminMerchantPage({
+ keyword: keyword?.trim() || undefined,
+ page: 1,
+ size: 20,
+ })
+ const nextOptions = response.data.list.map(item => buildMerchantOption(item))
+ setMerchantOptions(current => mergeMerchantOptions(current, nextOptions))
+ } catch (error) {
+ toast.error(error instanceof Error ? error.message : '商家选项加载失败')
+ } finally {
+ setMerchantLoading(false)
+ }
+ }
+
+ useEffect(() => {
+ form.setFieldsValue({
+ page: 1,
+ size: DEFAULT_PAGE_SIZE,
+ })
+ void loadData(1, DEFAULT_PAGE_SIZE)
+ }, [])
+
+ const resetModal = () => {
+ setModalOpen(false)
+ setEditingRecord(null)
+ modalForm.resetFields()
+ }
+
+ const openCreateModal = () => {
+ setEditingRecord(null)
+ modalForm.setFieldsValue({
+ positionCode: DEFAULT_POSITION_CODE,
+ rankScore: 0,
+ sort: 0,
+ status: DEFAULT_STATUS,
+ startTime: null,
+ endTime: null,
+ })
+ setModalOpen(true)
+ if (!merchantOptions.length) {
+ void loadMerchantOptions()
+ }
+ }
+
+ const openEditModal = (record: AdminRecommendMerchantItem) => {
+ setEditingRecord(record)
+ setMerchantOptions(current =>
+ mergeMerchantOptions(current, [
+ {
+ value: record.merchantId,
+ label: compactText(record.merchantName),
+ },
+ ])
+ )
+ modalForm.setFieldsValue({
+ positionCode: record.positionCode,
+ merchantId: record.merchantId,
+ mainCategoryName: record.mainCategoryName || '',
+ recommendReason: record.recommendReason || '',
+ rankScore: Number(record.rankScore ?? 0),
+ sort: record.sort ?? 0,
+ status: record.configStatus,
+ startTime: record.startTime ? dayjs.unix(record.startTime) : null,
+ endTime: record.endTime ? dayjs.unix(record.endTime) : null,
+ })
+ setModalOpen(true)
+ }
+
+ const handleDelete = async (record: AdminRecommendMerchantItem) => {
+ try {
+ await apiAdminRecommendMerchantDelete({ id: record.id })
+ toast.success('首页推荐商家已删除')
+ await loadData()
+ } catch (error) {
+ toast.error(error instanceof Error ? error.message : '首页推荐商家删除失败')
+ }
+ }
+
+ const handleSave = async () => {
+ try {
+ const values = await modalForm.validateFields()
+ setSaving(true)
+ const payload: AdminRecommendMerchantSaveParams = {
+ id: editingRecord?.id,
+ positionCode: values.positionCode,
+ merchantId: values.merchantId || '',
+ mainCategoryName: values.mainCategoryName?.trim() || undefined,
+ recommendReason: values.recommendReason?.trim() || undefined,
+ rankScore: values.rankScore ?? 0,
+ sort: values.sort ?? 0,
+ status: values.status,
+ startTime: values.startTime?.unix(),
+ endTime: values.endTime?.unix(),
+ }
+ await apiAdminRecommendMerchantSave(payload)
+ toast.success(editingRecord ? '首页推荐商家已更新' : '首页推荐商家已新增')
+ resetModal()
+ await loadData()
+ } catch (error) {
+ if (error instanceof Error && error.message) {
+ toast.error(error.message)
+ }
+ } finally {
+ setSaving(false)
+ }
+ }
+
+ const handleTableChange = (next: TablePaginationConfig) => {
+ void loadData(next.current || 1, next.pageSize || DEFAULT_PAGE_SIZE)
+ }
+
+ const columns: ColumnsType = [
+ {
+ title: '配置 ID',
+ dataIndex: 'id',
+ width: 92,
+ },
+ {
+ title: '位置编码',
+ dataIndex: 'positionCode',
+ width: 140,
+ render: value => getRecommendStorePositionLabel(value),
+ },
+ {
+ title: '商家 ID',
+ dataIndex: 'merchantId',
+ width: 92,
+ },
+ {
+ title: '商家名称',
+ dataIndex: 'merchantName',
+ width: 200,
+ render: value => compactText(value),
+ },
+ {
+ title: '商家 Logo',
+ dataIndex: 'logo',
+ width: 100,
+ render: (_, record) => (
+
+ {record.merchantName?.slice(0, 1)}
+
+ ),
+ },
+ {
+ title: '商家公告',
+ dataIndex: 'notice',
+ width: 220,
+ render: value => (
+
+ {compactText(value)}
+
+ ),
+ },
+ {
+ title: '商家状态',
+ dataIndex: 'merchantStatus',
+ width: 96,
+ render: value => ,
+ },
+ {
+ title: '审核状态',
+ dataIndex: 'auditStatus',
+ width: 96,
+ render: value => ,
+ },
+ {
+ title: '主推类目',
+ dataIndex: 'mainCategoryName',
+ width: 160,
+ render: value => compactText(value),
+ },
+ {
+ title: '推荐理由',
+ dataIndex: 'recommendReason',
+ width: 220,
+ render: value => (
+
+ {compactText(value)}
+
+ ),
+ },
+ {
+ title: '排行分值',
+ dataIndex: 'rankScore',
+ width: 96,
+ },
+ {
+ title: '排序',
+ dataIndex: 'sort',
+ width: 76,
+ },
+ {
+ title: '配置状态',
+ dataIndex: 'configStatus',
+ width: 96,
+ render: value => ,
+ },
+ {
+ title: '开始时间',
+ dataIndex: 'startTime',
+ width: 170,
+ render: value => formatTimestamp(value),
+ },
+ {
+ title: '结束时间',
+ dataIndex: 'endTime',
+ width: 170,
+ render: value => formatTimestamp(value),
+ },
+ {
+ title: '更新时间',
+ dataIndex: 'updatedAt',
+ width: 170,
+ render: value => formatTimestamp(value),
+ },
+ {
+ title: '操作',
+ key: 'action',
+ fixed: 'right',
+ width: 144,
+ render: (_, record) => (
+
+
+ void handleDelete(record)}
+ >
+
+
+
+ ),
+ },
+ ]
+
+ return (
+
+
+
+ 首页推荐商家
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ rowKey="id"
+ loading={loading}
+ columns={columns}
+ dataSource={list}
+ className="table-top-align"
+ scroll={{ x: 2200 }}
+ pagination={{
+ current: pagination.current,
+ pageSize: pagination.pageSize,
+ total,
+ showSizeChanger: true,
+ showTotal: count => `共 ${count} 条`,
+ }}
+ onChange={handleTableChange}
+ />
+
+
+ void handleSave()}
+ >
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ({
+ validator(_, value?: Dayjs | null) {
+ const startTime = getFieldValue('startTime') as Dayjs | null | undefined
+ if (!startTime || !value || value.valueOf() >= startTime.valueOf()) {
+ return Promise.resolve()
+ }
+ return Promise.reject(new Error('结束时间不能早于开始时间'))
+ },
+ }),
+ ]}
+ >
+
+
+
+
+
+
+ )
+}
diff --git a/src/pages/admin/searchKeywords/index.tsx b/src/pages/admin/searchKeywords/index.tsx
new file mode 100644
index 0000000..01fc24b
--- /dev/null
+++ b/src/pages/admin/searchKeywords/index.tsx
@@ -0,0 +1,346 @@
+import { useEffect, useState } from 'react'
+import dayjs, { type Dayjs } from 'dayjs'
+import {
+ Button,
+ Card,
+ DatePicker,
+ Form,
+ Input,
+ InputNumber,
+ Modal,
+ Popconfirm,
+ Select,
+ Space,
+ Table,
+ Typography,
+} from 'antd'
+import type { ColumnsType, TablePaginationConfig } from 'antd/es/table'
+import { toast } from 'sonner'
+import {
+ apiAdminSearchKeywordDelete,
+ apiAdminSearchKeywordPage,
+ apiAdminSearchKeywordSave,
+} from '@/apis/apiMallAdmin'
+import StatusTag from '@/components/StatusTag'
+import type {
+ AdminSearchKeywordItem,
+ AdminSearchKeywordPageParams,
+ AdminSearchKeywordSaveParams,
+ EnabledStatus,
+} from '@/types/mall'
+import { compactText, formatTimestamp } from '@/utils/format'
+import { enabledStatusOptions, withAllOption } from '@/utils/mall'
+
+const DEFAULT_PAGE_SIZE = 20
+const DEFAULT_STATUS: EnabledStatus = 'enabled'
+
+interface SearchKeywordFilterValues {
+ keyword?: string
+ status?: EnabledStatus | ''
+ page: number
+ size: number
+}
+
+interface SearchKeywordFormValues {
+ keyword: string
+ sort?: number
+ status: EnabledStatus
+ startTime?: Dayjs | null
+ endTime?: Dayjs | null
+}
+
+const normalizeSearchValues = (values: SearchKeywordFilterValues): AdminSearchKeywordPageParams => ({
+ keyword: values.keyword?.trim() || undefined,
+ status: values.status || undefined,
+ page: values.page,
+ size: values.size,
+})
+
+export default function SearchKeywordManagementPage() {
+ const [form] = Form.useForm()
+ const [modalForm] = Form.useForm()
+ const [loading, setLoading] = useState(false)
+ const [saving, setSaving] = useState(false)
+ const [list, setList] = useState([])
+ const [total, setTotal] = useState(0)
+ const [pagination, setPagination] = useState({ current: 1, pageSize: DEFAULT_PAGE_SIZE })
+ const [modalOpen, setModalOpen] = useState(false)
+ const [editingRecord, setEditingRecord] = useState(null)
+
+ const loadData = async (page = pagination.current, size = pagination.pageSize) => {
+ try {
+ setLoading(true)
+ const response = await apiAdminSearchKeywordPage(
+ normalizeSearchValues({
+ ...form.getFieldsValue(),
+ page,
+ size,
+ })
+ )
+ setList(response.data.list)
+ setTotal(response.data.total)
+ setPagination({ current: page, pageSize: size })
+ } catch (error) {
+ toast.error(error instanceof Error ? error.message : '热词列表加载失败')
+ } finally {
+ setLoading(false)
+ }
+ }
+
+ useEffect(() => {
+ form.setFieldsValue({
+ page: 1,
+ size: DEFAULT_PAGE_SIZE,
+ })
+ void loadData(1, DEFAULT_PAGE_SIZE)
+ }, [])
+
+ const resetModal = () => {
+ setModalOpen(false)
+ setEditingRecord(null)
+ modalForm.resetFields()
+ }
+
+ const openCreateModal = () => {
+ setEditingRecord(null)
+ modalForm.setFieldsValue({
+ sort: 0,
+ status: DEFAULT_STATUS,
+ startTime: null,
+ endTime: null,
+ })
+ setModalOpen(true)
+ }
+
+ const openEditModal = (record: AdminSearchKeywordItem) => {
+ setEditingRecord(record)
+ modalForm.setFieldsValue({
+ keyword: record.keyword,
+ sort: record.sort ?? 0,
+ status: record.status,
+ startTime: record.startTime ? dayjs.unix(record.startTime) : null,
+ endTime: record.endTime ? dayjs.unix(record.endTime) : null,
+ })
+ setModalOpen(true)
+ }
+
+ const handleDelete = async (record: AdminSearchKeywordItem) => {
+ try {
+ await apiAdminSearchKeywordDelete({ id: record.id })
+ toast.success('热词已删除')
+ await loadData()
+ } catch (error) {
+ toast.error(error instanceof Error ? error.message : '热词删除失败')
+ }
+ }
+
+ const handleSave = async () => {
+ try {
+ const values = await modalForm.validateFields()
+ setSaving(true)
+ const payload: AdminSearchKeywordSaveParams = {
+ id: editingRecord?.id,
+ keyword: values.keyword.trim(),
+ sort: values.sort ?? 0,
+ status: values.status,
+ startTime: values.startTime?.unix(),
+ endTime: values.endTime?.unix(),
+ }
+ await apiAdminSearchKeywordSave(payload)
+ toast.success(editingRecord ? '热词已更新' : '热词已新增')
+ resetModal()
+ await loadData()
+ } catch (error) {
+ if (error instanceof Error && error.message) {
+ toast.error(error.message)
+ }
+ } finally {
+ setSaving(false)
+ }
+ }
+
+ const handleTableChange = (next: TablePaginationConfig) => {
+ void loadData(next.current || 1, next.pageSize || DEFAULT_PAGE_SIZE)
+ }
+
+ const columns: ColumnsType = [
+ {
+ title: '热词内容',
+ dataIndex: 'keyword',
+ width: 220,
+ render: value => compactText(value),
+ },
+ {
+ title: '排序',
+ dataIndex: 'sort',
+ width: 80,
+ },
+ {
+ title: '状态',
+ dataIndex: 'status',
+ width: 96,
+ render: value => ,
+ },
+ {
+ title: '开始时间',
+ dataIndex: 'startTime',
+ width: 170,
+ render: value => formatTimestamp(value),
+ },
+ {
+ title: '结束时间',
+ dataIndex: 'endTime',
+ width: 170,
+ render: value => formatTimestamp(value),
+ },
+ {
+ title: '更新时间',
+ dataIndex: 'updatedAt',
+ width: 170,
+ render: value => formatTimestamp(value),
+ },
+ {
+ title: '操作',
+ key: 'action',
+ fixed: 'right',
+ width: 144,
+ render: (_, record) => (
+
+
+ void handleDelete(record)}
+ >
+
+
+
+ ),
+ },
+ ]
+
+ return (
+
+
+
+ 首页热词
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ rowKey="id"
+ loading={loading}
+ columns={columns}
+ dataSource={list}
+ className="table-top-align"
+ scroll={{ x: 1100 }}
+ pagination={{
+ current: pagination.current,
+ pageSize: pagination.pageSize,
+ total,
+ showSizeChanger: true,
+ showTotal: count => `共 ${count} 条`,
+ }}
+ onChange={handleTableChange}
+ />
+
+
+ void handleSave()}
+ >
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ({
+ validator(_, value?: Dayjs | null) {
+ const startTime = getFieldValue('startTime') as Dayjs | null | undefined
+ if (!startTime || !value || value.valueOf() >= startTime.valueOf()) {
+ return Promise.resolve()
+ }
+ return Promise.reject(new Error('结束时间不能早于开始时间'))
+ },
+ }),
+ ]}
+ >
+
+
+
+
+
+
+ )
+}
diff --git a/src/pages/admin/stores/index.tsx b/src/pages/admin/stores/index.tsx
new file mode 100644
index 0000000..5259cf7
--- /dev/null
+++ b/src/pages/admin/stores/index.tsx
@@ -0,0 +1,319 @@
+import { useEffect, useMemo, useState } from 'react'
+import {
+ Avatar,
+ Button,
+ Card,
+ Descriptions,
+ Drawer,
+ Form,
+ Input,
+ Select,
+ Space,
+ Table,
+ Typography,
+} from 'antd'
+import type { ColumnsType, TablePaginationConfig } from 'antd/es/table'
+import { toast } from 'sonner'
+import AuditModal, { type AuditModalValues } from '@/components/AuditModal'
+import JsonPreviewPanel from '@/components/JsonPreviewPanel'
+import StatusTag from '@/components/StatusTag'
+import { apiAdminStoreAudit, apiAdminStorePage } from '@/apis/apiMallAdmin'
+import { useUserStore } from '@/store/user'
+import type { AdminStorePageParams, StoreInfo } from '@/types/mall'
+import { buildAddress, compactText, formatTimestamp } from '@/utils/format'
+import { auditStatusOptions, enabledStatusOptions, withAllOption } from '@/utils/mall'
+
+const DEFAULT_PAGE_SIZE = 20
+
+interface StoreSearchValues {
+ merchantId?: string
+ keyword?: string
+ storeStatus?: string
+ auditStatus?: string
+ page: number
+ size: number
+}
+
+const normalizeFilterValues = (values: StoreSearchValues): AdminStorePageParams => ({
+ merchantId: values.merchantId || undefined,
+ keyword: values.keyword?.trim() || undefined,
+ storeStatus: values.storeStatus as AdminStorePageParams['storeStatus'],
+ auditStatus: values.auditStatus as AdminStorePageParams['auditStatus'],
+ page: values.page,
+ size: values.size,
+})
+
+export default function StoreReviewPage() {
+ const [form] = Form.useForm()
+ const operatorId = useUserStore(state => state.token?.uid ?? 0)
+ const [loading, setLoading] = useState(false)
+ const [auditLoading, setAuditLoading] = useState(false)
+ const [list, setList] = useState([])
+ const [total, setTotal] = useState(0)
+ const [selectedRecord, setSelectedRecord] = useState(null)
+ const [viewRecord, setViewRecord] = useState(null)
+ const [pagination, setPagination] = useState({ current: 1, pageSize: DEFAULT_PAGE_SIZE })
+
+ const loadData = async (page = pagination.current, size = pagination.pageSize) => {
+ try {
+ setLoading(true)
+ const values = form.getFieldsValue()
+ const response = await apiAdminStorePage(
+ normalizeFilterValues({
+ ...values,
+ page,
+ size,
+ })
+ )
+ setList(response.data.list)
+ setTotal(response.data.total)
+ setPagination({ current: page, pageSize: size })
+ } catch (error) {
+ toast.error(error instanceof Error ? error.message : '店铺列表加载失败')
+ } finally {
+ setLoading(false)
+ }
+ }
+
+ useEffect(() => {
+ form.setFieldsValue({
+ page: 1,
+ size: DEFAULT_PAGE_SIZE,
+ })
+ void loadData(1, DEFAULT_PAGE_SIZE)
+ }, [])
+
+ const columns = useMemo>(
+ () => [
+ {
+ title: '店铺信息',
+ key: 'store',
+ width: 260,
+ render: (_, record) => (
+
+
+ {record.storeName?.slice(0, 1)}
+
+
+ {compactText(record.storeName)}
+
+ 店铺编号: {compactText(record.storeNo)}
+
+ 商家 ID: {record.merchantId || '--'}
+
+
+ ),
+ },
+ {
+ title: '服务信息',
+ key: 'service',
+ width: 180,
+ render: (_, record) => (
+
+ {compactText(record.servicePhone)}
+ {compactText(record.serviceEmail)}
+ {compactText(record.contactName)}
+
+ ),
+ },
+ {
+ title: '店铺地址',
+ key: 'address',
+ width: 240,
+ render: (_, record) => buildAddress(record.province, record.city, record.district, record.addressDetail),
+ },
+ {
+ title: '启停状态',
+ dataIndex: 'storeStatus',
+ width: 110,
+ render: value => ,
+ },
+ {
+ title: '审核状态',
+ dataIndex: 'auditStatus',
+ width: 110,
+ render: value => ,
+ },
+ {
+ title: '创建时间',
+ dataIndex: 'createdAt',
+ width: 170,
+ render: value => formatTimestamp(value),
+ },
+ {
+ title: '操作',
+ key: 'action',
+ fixed: 'right',
+ width: 180,
+ render: (_, record) => (
+
+
+
+
+ ),
+ },
+ ],
+ []
+ )
+
+ const handleAuditSubmit = async (values: AuditModalValues) => {
+ if (!selectedRecord) {
+ return
+ }
+
+ try {
+ setAuditLoading(true)
+ await apiAdminStoreAudit({
+ storeId: selectedRecord.id,
+ auditStatus: values.auditStatus,
+ remark: values.remark?.trim() || undefined,
+ operatorId,
+ })
+ toast.success('店铺审核已提交')
+ setSelectedRecord(null)
+ await loadData()
+ } catch (error) {
+ toast.error(error instanceof Error ? error.message : '店铺审核失败')
+ } finally {
+ setAuditLoading(false)
+ }
+ }
+
+ const handleTableChange = (next: TablePaginationConfig) => {
+ void loadData(next.current || 1, next.pageSize || DEFAULT_PAGE_SIZE)
+ }
+
+ return (
+
+
+
+ 店铺审核列表
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ rowKey="id"
+ loading={loading}
+ columns={columns}
+ dataSource={list}
+ className="table-top-align"
+ scroll={{ x: 1320 }}
+ pagination={{
+ current: pagination.current,
+ pageSize: pagination.pageSize,
+ total,
+ showSizeChanger: true,
+ showTotal: count => `共 ${count} 条`,
+ }}
+ onChange={handleTableChange}
+ />
+
+
+ setSelectedRecord(null)}
+ onSubmit={handleAuditSubmit}
+ summary={
+ selectedRecord ? (
+
+ {compactText(selectedRecord.storeName)}
+ {compactText(selectedRecord.storeNo)}
+ {selectedRecord.merchantId}
+ {compactText(selectedRecord.servicePhone)}
+
+ {buildAddress(
+ selectedRecord.province,
+ selectedRecord.city,
+ selectedRecord.district,
+ selectedRecord.addressDetail
+ )}
+
+
+ ) : null
+ }
+ />
+
+ setViewRecord(null)}
+ >
+ {viewRecord ? (
+
+
+ {viewRecord.id}
+ {viewRecord.merchantId}
+ {compactText(viewRecord.storeNo)}
+ {compactText(viewRecord.storeName)}
+
+
+
+
+
+
+ {compactText(viewRecord.storeLogo)}
+ {compactText(viewRecord.storeBanner)}
+ {compactText(viewRecord.storeNotice)}
+ {compactText(viewRecord.servicePhone)}
+ {compactText(viewRecord.serviceEmail)}
+ {compactText(viewRecord.contactName)}
+ {compactText(viewRecord.contactMobile)}
+ {compactText(viewRecord.province)}
+ {compactText(viewRecord.city)}
+ {compactText(viewRecord.district)}
+
+ {compactText(viewRecord.addressDetail)}
+
+ {compactText(viewRecord.zipCode)}
+ {viewRecord.sort}
+ {formatTimestamp(viewRecord.createdAt)}
+ {formatTimestamp(viewRecord.updatedAt)}
+
+
+
+
+ ) : null}
+
+
+ )
+}
diff --git a/src/pages/login/index.tsx b/src/pages/login/index.tsx
new file mode 100644
index 0000000..e260553
--- /dev/null
+++ b/src/pages/login/index.tsx
@@ -0,0 +1,227 @@
+import { useEffect, useState } from 'react'
+import {
+ Button,
+ Card,
+ Checkbox,
+ Form,
+ Input,
+ Typography,
+ theme,
+} from 'antd'
+import {
+ LockOutlined,
+ LoginOutlined,
+ SafetyCertificateOutlined,
+ UserOutlined,
+} from '@ant-design/icons'
+import { useNavigate } from 'react-router-dom'
+import { toast } from 'sonner'
+import { apiUserLogin } from '@/apis/apisUser'
+import { useUserStore } from '@/store/user'
+
+const LOGIN_REMEMBER_KEY = 'mall-admin:remember-login'
+
+interface LoginFormValues {
+ account: string
+ password: string
+ captcha: string
+ remember: boolean
+}
+
+interface RememberLoginData {
+ account: string
+ password: string
+ remember: boolean
+}
+
+const readRememberLogin = (): RememberLoginData => {
+ if (typeof window === 'undefined') {
+ return {
+ account: '',
+ password: '',
+ remember: true,
+ }
+ }
+
+ try {
+ const raw = window.localStorage.getItem(LOGIN_REMEMBER_KEY)
+ if (!raw) {
+ return {
+ account: '',
+ password: '',
+ remember: true,
+ }
+ }
+
+ const parsed = JSON.parse(raw) as Partial
+ return {
+ account: parsed.account ?? '',
+ password: parsed.password ?? '',
+ remember: parsed.remember ?? true,
+ }
+ } catch {
+ return {
+ account: '',
+ password: '',
+ remember: true,
+ }
+ }
+}
+
+const persistRememberLogin = (values: RememberLoginData) => {
+ if (typeof window === 'undefined') {
+ return
+ }
+
+ if (!values.remember) {
+ window.localStorage.removeItem(LOGIN_REMEMBER_KEY)
+ return
+ }
+
+ window.localStorage.setItem(LOGIN_REMEMBER_KEY, JSON.stringify(values))
+}
+
+export default function LoginPage() {
+ const navigate = useNavigate()
+ const [form] = Form.useForm()
+ const [loading, setLoading] = useState(false)
+ const token = useUserStore(state => state.token)
+ const setToken = useUserStore(state => state.setToken)
+ const { token: themeToken } = theme.useToken()
+
+ useEffect(() => {
+ if (token?.accessToken) {
+ navigate('/merchant-review', { replace: true })
+ return
+ }
+
+ form.setFieldsValue(readRememberLogin())
+ }, [form, navigate, token?.accessToken])
+
+ const onFinish = async (values: LoginFormValues) => {
+ try {
+ setLoading(true)
+
+ const res = await apiUserLogin({
+ account: values.account.trim(),
+ password: values.password,
+ captcha: values.captcha.trim(),
+ captchaId: '',
+ loginType: '0',
+ type: '0',
+ remember: values.remember,
+ })
+
+ persistRememberLogin({
+ account: values.account.trim(),
+ password: values.password,
+ remember: values.remember,
+ })
+
+ setToken(res.data)
+ toast.success('登录成功')
+ navigate('/merchant-review', { replace: true })
+ } catch (error) {
+ toast.error(error instanceof Error ? error.message : '登录失败')
+ } finally {
+ setLoading(false)
+ }
+ }
+
+ return (
+
+
+
+ 商城平台管理端
+
+
+
+
+ } />
+
+
+
+ }
+ />
+
+
+
+ }
+ />
+
+
+
+
+ 记住密码
+
+
+
+
+
+
+ } loading={loading}>
+ 登录
+
+
+
+
+ )
+}
diff --git a/src/routes/components/ProtectedRoute.tsx b/src/routes/components/ProtectedRoute.tsx
new file mode 100644
index 0000000..49f9058
--- /dev/null
+++ b/src/routes/components/ProtectedRoute.tsx
@@ -0,0 +1,22 @@
+import type { ReactNode } from 'react'
+import { Navigate } from 'react-router-dom'
+import { useUserStore } from '@/store/user'
+
+interface ProtectedRouteProps {
+ children: ReactNode
+ redirectTo?: string
+}
+
+export default function ProtectedRoute({
+ children,
+ redirectTo = '/login',
+}: ProtectedRouteProps) {
+ const token = useUserStore(state => state.token)
+
+ if (!token?.accessToken) {
+ return
+ }
+
+ return <>{children}>
+}
+
diff --git a/src/routes/config.tsx b/src/routes/config.tsx
new file mode 100644
index 0000000..e754c0a
--- /dev/null
+++ b/src/routes/config.tsx
@@ -0,0 +1,87 @@
+import type { RouteObject } from 'react-router-dom'
+import { Navigate } from 'react-router-dom'
+import RootLayout from '@/layouts/RootLayout'
+import AuthLayout from '@/layouts/auth/AuthLayout'
+import MainLayout from '@/layouts/main/MainLayout'
+import ProtectedRoute from '@/routes/components/ProtectedRoute'
+import LoginPage from '@/pages/login'
+import MerchantReviewPage from '@/pages/admin/merchants'
+import GoodsReviewPage from '@/pages/admin/goods'
+import OrderOverviewPage from '@/pages/admin/orders'
+import OrderDetailPage from '@/pages/admin/orders/DetailPage'
+import BannerManagementPage from '@/pages/admin/banners'
+import CategoryManagementPage from '@/pages/admin/categories'
+import HomeChannelManagementPage from '@/pages/admin/homeChannels'
+import RecommendStoreManagementPage from '@/pages/admin/recommendStores'
+import SearchKeywordManagementPage from '@/pages/admin/searchKeywords'
+
+export const routeConfig: RouteObject[] = [
+ {
+ path: '/',
+ element: ,
+ children: [
+ {
+ element: ,
+ children: [
+ {
+ path: 'login',
+ element: ,
+ },
+ ],
+ },
+ {
+ element: (
+
+
+
+ ),
+ children: [
+ {
+ index: true,
+ element: ,
+ },
+ {
+ path: 'merchant-review',
+ element: ,
+ },
+ {
+ path: 'categories',
+ element: ,
+ },
+ {
+ path: 'banners',
+ element: ,
+ },
+ {
+ path: 'goods-review',
+ element: ,
+ },
+ {
+ path: 'recommend-stores',
+ element: ,
+ },
+ {
+ path: 'search-keywords',
+ element: ,
+ },
+ {
+ path: 'home-channels',
+ element: ,
+ },
+ {
+ path: 'orders',
+ element: ,
+ },
+ {
+ path: 'orders/:orderId',
+ element: ,
+ },
+ ],
+ },
+ {
+ path: '*',
+ element: ,
+ },
+ ],
+ },
+]
diff --git a/src/routes/index.tsx b/src/routes/index.tsx
new file mode 100644
index 0000000..7b19c41
--- /dev/null
+++ b/src/routes/index.tsx
@@ -0,0 +1,7 @@
+import { useRoutes } from 'react-router-dom'
+import { routeConfig } from './config'
+
+export default function AppRoutes() {
+ return useRoutes(routeConfig)
+}
+
diff --git a/src/store/user.ts b/src/store/user.ts
new file mode 100644
index 0000000..e9a5451
--- /dev/null
+++ b/src/store/user.ts
@@ -0,0 +1,93 @@
+import { create } from 'zustand'
+import { createJSONStorage, persist } from 'zustand/middleware'
+import { apiGetUserInfo } from '@/apis/apisUser'
+import type { ITokenData } from '@/types/ITokenData'
+import type { IUserInfo } from '@/types/IUserInfo'
+
+const STORAGE_KEY = 'mall-admin:user'
+
+interface UserState {
+ token: ITokenData | null
+ role: string[] | null
+ refreshUsering: boolean
+ userInfo: IUserInfo | null
+ setToken: (token: ITokenData) => void
+ setRole: (role: string[] | null) => void
+ setUserInfo: (userInfo: IUserInfo) => void
+ refreshUser: () => Promise
+ refreshAll: () => Promise
+ logout: () => void
+ clearStorage: () => void
+ clearAuth: () => void
+}
+
+export const useUserStore = create()(
+ persist(
+ (set, get) => ({
+ token: null,
+ role: [],
+ refreshUsering: false,
+ userInfo: null,
+ setToken: token => {
+ set({
+ token,
+ role: token.role ?? [],
+ })
+ void get().refreshAll()
+ },
+ setRole: role => {
+ set({ role })
+ },
+ setUserInfo: userInfo => {
+ set({ userInfo })
+ },
+ refreshUser: async () => {
+ if (!get().token?.accessToken) {
+ return
+ }
+
+ try {
+ set({ refreshUsering: true })
+ const res = await apiGetUserInfo()
+ set({ userInfo: res.data })
+ } catch {
+ // 用户信息接口失败时保留 token,避免影响已登录联调
+ } finally {
+ set({ refreshUsering: false })
+ }
+ },
+ refreshAll: async () => {
+ await get().refreshUser()
+ },
+ logout: () => {
+ get().clearStorage()
+ },
+ clearStorage: () => {
+ set({
+ token: null,
+ role: [],
+ refreshUsering: false,
+ userInfo: null,
+ })
+ localStorage.removeItem(STORAGE_KEY)
+ },
+ clearAuth: () => {
+ get().clearStorage()
+ },
+ }),
+ {
+ name: STORAGE_KEY,
+ storage: createJSONStorage(() => localStorage),
+ partialize: state => ({
+ token: state.token,
+ role: state.role,
+ userInfo: state.userInfo,
+ }),
+ onRehydrateStorage: () => state => {
+ if (state) {
+ void state.refreshAll()
+ }
+ },
+ }
+ )
+)
diff --git a/src/types/ITokenData.ts b/src/types/ITokenData.ts
new file mode 100644
index 0000000..060a3e5
--- /dev/null
+++ b/src/types/ITokenData.ts
@@ -0,0 +1,10 @@
+export interface ITokenData {
+ accessToken: string
+ accessExpire?: number
+ refreshAfter?: number
+ role?: string[]
+ utype?: string
+ uid?: number
+ account?: string
+}
+
diff --git a/src/types/IUserInfo.ts b/src/types/IUserInfo.ts
new file mode 100644
index 0000000..1046cdb
--- /dev/null
+++ b/src/types/IUserInfo.ts
@@ -0,0 +1,6 @@
+export interface IUserInfo {
+ uid: number
+ name: string
+ account: string
+}
+
diff --git a/src/types/mall.ts b/src/types/mall.ts
new file mode 100644
index 0000000..160e632
--- /dev/null
+++ b/src/types/mall.ts
@@ -0,0 +1,513 @@
+export type EnabledStatus = 'enabled' | 'disabled'
+export type AuditStatus = 'pending' | 'approved' | 'rejected'
+export type SettleStatus = 'pending' | 'active' | 'frozen'
+export type SaleStatus = 'off' | 'on'
+export type HomePositionCode =
+ | 'home_top'
+ | 'home_rank_store'
+ | 'home_featured_store'
+ | 'home_recommend_goods'
+export type RecommendMerchantPositionCode = 'home_rank_store' | 'home_featured_store'
+export type RecommendStorePositionCode = RecommendMerchantPositionCode
+export type MerchantLoginType = '0' | '1'
+export type LinkType = 'goods' | 'store' | 'topic' | 'h5'
+export type OrderStatus =
+ | 'pending_pay'
+ | 'paid_wait_ship'
+ | 'shipped'
+ | 'finished'
+ | 'canceled'
+ | 'after_sale'
+ | 'refund_finished'
+export type AfterSaleStatus =
+ | 'none'
+ | 'pending'
+ | 'approved'
+ | 'rejected'
+ | 'refunding'
+ | 'finished'
+ | 'closed'
+
+export interface PageResult {
+ total: number
+ list: T[]
+}
+
+export interface MerchantInfo {
+ id: number
+ uid: number
+ merchantNo: string
+ merchantName: string
+ status: EnabledStatus
+ auditStatus: AuditStatus
+ settleStatus: SettleStatus
+ contactName: string
+ contactMobile: string
+ contactEmail: string
+ licenseNo: string
+ licenseImagesJson: string
+ extraInfoJson: string
+ remark: string
+ logo?: string
+ notice?: string
+ approvedAt: number
+ createdAt: number
+ updatedAt: number
+}
+
+export interface StoreInfo {
+ id: number
+ merchantId: string
+ storeNo: string
+ storeName: string
+ storeLogo: string
+ storeBanner: string
+ storeNotice: string
+ storeStatus: EnabledStatus
+ auditStatus: AuditStatus
+ servicePhone: string
+ serviceEmail: string
+ contactName: string
+ contactMobile: string
+ province: string
+ city: string
+ district: string
+ addressDetail: string
+ zipCode: string
+ sort: number
+ createdAt: number
+ updatedAt: number
+}
+
+export interface GoodsSummary {
+ id: number
+ merchantId: string
+ storeId: number
+ categoryId: number
+ categoryName?: string
+ spuNo: string
+ spuName: string
+ subTitle: string
+ coverUrl: string
+ status: EnabledStatus
+ auditStatus: AuditStatus
+ saleStatus: SaleStatus
+ sort: number
+ salesVolume: number
+ virtualSales: number
+ viewCount: number
+ minPrice: string
+ maxPrice: string
+ totalStock: number
+ lockedStock: number
+ createdAt: number
+ updatedAt: number
+}
+
+export interface OrderItemInfo {
+ id: number
+ orderId: number
+ tradeId: number
+ orderNo: string
+ spuId: number
+ skuId: number
+ spuName: string
+ skuName: string
+ coverUrl: string
+ skuSpecJson: string
+ buyNum: number
+ salePrice: string
+ originPrice: string
+ discountAmount: string
+ itemAmount: string
+ refundAmount: string
+ afterSaleStatus: AfterSaleStatus
+}
+
+export interface OrderSummary {
+ id: number
+ tradeId: number
+ tradeNo: string
+ orderNo: string
+ memberId: number
+ uid?: number
+ merchantId: string
+ storeId: number
+ merchantName?: string
+ storeName: string
+ orderStatus: OrderStatus
+ afterSaleStatus: AfterSaleStatus
+ currency: string
+ goodsAmount: string
+ freightAmount: string
+ discountAmount: string
+ payAmount: string
+ payTime: number
+ shipTime: number
+ finishTime: number
+ cancelTime: number
+ createdAt: number
+ updatedAt: number
+}
+
+export interface OrderDetailInfo {
+ id: number
+ tradeId: number
+ tradeNo: string
+ orderNo: string
+ memberId: number
+ uid?: number
+ merchantId: string
+ storeId: number
+ merchantName?: string
+ storeName: string
+ orderStatus: OrderStatus
+ afterSaleStatus: AfterSaleStatus
+ currency: string
+ goodsAmount: string
+ freightAmount: string
+ discountAmount: string
+ payAmount: string
+ buyerRemark: string
+ sellerRemark: string
+ receiverName: string
+ receiverMobile: string
+ receiverCountryCode: string
+ receiverProvince: string
+ receiverCity: string
+ receiverDistrict: string
+ receiverAddress: string
+ receiverZipCode: string
+ deliveryCompany: string
+ deliveryNo: string
+ payChannel: string
+ extraInfoJson: string
+ payTime: number
+ shipTime: number
+ finishTime: number
+ cancelTime: number
+ autoConfirmAt: number
+ items: OrderItemInfo[]
+ createdAt: number
+ updatedAt: number
+}
+
+export interface AdminMerchantPageParams {
+ keyword?: string
+ status?: EnabledStatus
+ auditStatus?: AuditStatus
+ page: number
+ size: number
+}
+
+export interface AdminMerchantCreateParams {
+ loginType: MerchantLoginType
+ account: string
+ password: string
+ merchantName: string
+ contactName: string
+ contactMobile?: string
+ contactEmail?: string
+ licenseNo?: string
+ licenseImagesJson?: string
+ extraInfoJson?: string
+ remark?: string
+ createIp?: string
+}
+
+export interface AdminMerchantAuditParams {
+ merchantId: string
+ auditStatus: Extract
+ remark?: string
+ operatorId: number
+}
+
+export interface AdminStorePageParams {
+ merchantId?: string
+ keyword?: string
+ storeStatus?: EnabledStatus
+ auditStatus?: AuditStatus
+ page: number
+ size: number
+}
+
+export interface AdminStoreAuditParams {
+ storeId: number
+ auditStatus: Extract
+ remark?: string
+ operatorId: number
+}
+
+export interface AdminGoodsPageParams {
+ merchantId?: string
+ storeId?: number
+ keyword?: string
+ auditStatus?: AuditStatus
+ saleStatus?: SaleStatus
+ page: number
+ size: number
+}
+
+export interface AdminGoodsAuditParams {
+ spuId: number
+ auditStatus: Extract
+ remark?: string
+ operatorId: number
+}
+
+export interface AdminOrderPageParams {
+ merchantId?: string
+ storeId?: number
+ tradeNo?: string
+ orderNo?: string
+ orderStatus?: OrderStatus
+ afterSaleStatus?: AfterSaleStatus
+ page: number
+ size: number
+}
+
+export interface AdminOrderDetailParams {
+ orderId: number
+}
+
+export interface AdminCategoryItem {
+ id: number
+ parentId: number
+ categoryName: string
+ categoryLevel: number
+ categoryPath: string
+ categoryIcon: string
+ isLeaf: boolean
+ sort: number
+ status: EnabledStatus
+ goodsCount: number
+ createdAt: number
+ updatedAt: number
+ children: AdminCategoryItem[]
+}
+
+export interface AdminCategoryTreeParams {
+ status?: EnabledStatus
+}
+
+export interface AdminCategorySaveParams {
+ id?: number
+ parentId?: number
+ categoryName: string
+ categoryIcon?: string
+ sort?: number
+ status: EnabledStatus
+}
+
+export interface AdminCategoryDeleteParams {
+ id: number
+}
+
+export interface AdminRecommendStoreItem {
+ id: number
+ positionCode: RecommendStorePositionCode
+ storeId: number
+ merchantId: string
+ storeName: string
+ storeLogo: string
+ storeNotice: string
+ storeStatus: EnabledStatus
+ auditStatus: AuditStatus
+ mainCategoryName: string
+ recommendReason: string
+ rankScore: number
+ sort: number
+ configStatus: EnabledStatus
+ startTime: number
+ endTime: number
+ createdAt: number
+ updatedAt: number
+}
+
+export interface AdminRecommendStorePageParams {
+ keyword?: string
+ positionCode?: RecommendStorePositionCode
+ status?: EnabledStatus
+ merchantId?: string
+ page: number
+ size: number
+}
+
+export interface AdminRecommendStoreSaveParams {
+ id?: number
+ positionCode: RecommendStorePositionCode
+ storeId: number
+ mainCategoryName?: string
+ recommendReason?: string
+ rankScore?: number
+ sort?: number
+ status: EnabledStatus
+ startTime?: number
+ endTime?: number
+}
+
+export interface AdminRecommendStoreDeleteParams {
+ id: number
+}
+
+export interface AdminBannerItem {
+ id: number
+ title: string
+ subTitle: string
+ description: string
+ buttonText: string
+ positionCode: Extract
+ imageUrl: string
+ linkType: LinkType | string
+ linkValue: string
+ sort: number
+ status: EnabledStatus
+ startTime: number
+ endTime: number
+ createdAt: number
+ updatedAt: number
+}
+
+export interface AdminBannerPageParams {
+ keyword?: string
+ positionCode?: Extract
+ status?: EnabledStatus
+ page: number
+ size: number
+}
+
+export interface AdminBannerSaveParams {
+ id?: number
+ title: string
+ subTitle?: string
+ description?: string
+ buttonText?: string
+ positionCode: Extract
+ imageUrl: string
+ linkType?: LinkType | string
+ linkValue?: string
+ sort?: number
+ status?: EnabledStatus
+ startTime?: number
+ endTime?: number
+}
+
+export interface AdminBannerDeleteParams {
+ id: number
+}
+
+export interface AdminRecommendMerchantItem {
+ id: number
+ positionCode: RecommendMerchantPositionCode
+ merchantId: string
+ merchantName: string
+ logo: string
+ notice: string
+ merchantStatus: EnabledStatus
+ auditStatus: AuditStatus
+ mainCategoryName: string
+ recommendReason: string
+ rankScore: number
+ sort: number
+ configStatus: EnabledStatus
+ startTime: number
+ endTime: number
+ createdAt: number
+ updatedAt: number
+}
+
+export interface AdminRecommendMerchantPageParams {
+ keyword?: string
+ positionCode?: RecommendMerchantPositionCode
+ status?: EnabledStatus
+ merchantId?: string
+ page: number
+ size: number
+}
+
+export interface AdminRecommendMerchantSaveParams {
+ id?: number
+ positionCode: RecommendMerchantPositionCode
+ merchantId: string
+ mainCategoryName?: string
+ recommendReason?: string
+ rankScore?: number
+ sort?: number
+ status?: EnabledStatus
+ startTime?: number
+ endTime?: number
+}
+
+export interface AdminRecommendMerchantDeleteParams {
+ id: number
+}
+
+export interface AdminSearchKeywordItem {
+ id: number
+ keyword: string
+ sort: number
+ status: EnabledStatus
+ startTime: number
+ endTime: number
+ createdAt: number
+ updatedAt: number
+}
+
+export interface AdminSearchKeywordPageParams {
+ keyword?: string
+ status?: EnabledStatus
+ page: number
+ size: number
+}
+
+export interface AdminSearchKeywordSaveParams {
+ id?: number
+ keyword: string
+ sort?: number
+ status?: EnabledStatus
+ startTime?: number
+ endTime?: number
+}
+
+export interface AdminSearchKeywordDeleteParams {
+ id: number
+}
+
+export interface AdminHomeChannelItem {
+ id: number
+ title: string
+ subTitle: string
+ iconUrl: string
+ linkType: LinkType | string
+ linkValue: string
+ sort: number
+ status: EnabledStatus
+ startTime: number
+ endTime: number
+ createdAt: number
+ updatedAt: number
+}
+
+export interface AdminHomeChannelPageParams {
+ keyword?: string
+ status?: EnabledStatus
+ page: number
+ size: number
+}
+
+export interface AdminHomeChannelSaveParams {
+ id?: number
+ title: string
+ subTitle?: string
+ iconUrl?: string
+ linkType?: LinkType | string
+ linkValue?: string
+ sort?: number
+ status?: EnabledStatus
+ startTime?: number
+ endTime?: number
+}
+
+export interface AdminHomeChannelDeleteParams {
+ id: number
+}
diff --git a/src/utils/format.ts b/src/utils/format.ts
new file mode 100644
index 0000000..c31c6e9
--- /dev/null
+++ b/src/utils/format.ts
@@ -0,0 +1,65 @@
+import dayjs from 'dayjs'
+
+export const formatTimestamp = (value?: number | null) => {
+ if (!value) {
+ return '--'
+ }
+ return dayjs.unix(value).format('YYYY-MM-DD HH:mm:ss')
+}
+
+export const formatAmount = (value?: string | null, currency?: string | null) => {
+ if (!value) {
+ return '--'
+ }
+ const prefix = currency || 'CNY'
+ return `${prefix} ${value}`
+}
+
+export const compactText = (value?: string | null) => {
+ const text = value?.trim()
+ return text || '--'
+}
+
+export const safeJsonPretty = (value?: string | null) => {
+ const text = value?.trim()
+ if (!text) {
+ return ''
+ }
+
+ try {
+ return JSON.stringify(JSON.parse(text), null, 2)
+ } catch {
+ return text
+ }
+}
+
+export const buildAddress = (...segments: Array) => {
+ const text = segments.filter(Boolean).join('')
+ return text || '--'
+}
+
+export const parseSpecSummary = (value?: string | null) => {
+ const text = value?.trim()
+ if (!text) {
+ return '--'
+ }
+
+ try {
+ const parsed = JSON.parse(text) as Array<{ name?: string; value?: string }>
+ if (!Array.isArray(parsed)) {
+ return text
+ }
+ const summary = parsed
+ .map(item => {
+ const name = item?.name?.trim()
+ const specValue = item?.value?.trim()
+ return name && specValue ? `${name}: ${specValue}` : undefined
+ })
+ .filter(Boolean)
+ .join(' / ')
+ return summary || text
+ } catch {
+ return text
+ }
+}
+
diff --git a/src/utils/mall.ts b/src/utils/mall.ts
new file mode 100644
index 0000000..c6d399a
--- /dev/null
+++ b/src/utils/mall.ts
@@ -0,0 +1,186 @@
+import type {
+ AfterSaleStatus,
+ AuditStatus,
+ EnabledStatus,
+ HomePositionCode,
+ LinkType,
+ OrderStatus,
+ RecommendStorePositionCode,
+ SaleStatus,
+ SettleStatus,
+} from '@/types/mall'
+
+type SelectValue = string
+
+export interface LabelOption {
+ label: string
+ value: T
+}
+
+export const enabledStatusOptions: LabelOption[] = [
+ { label: '启用', value: 'enabled' },
+ { label: '停用', value: 'disabled' },
+]
+
+export const auditStatusOptions: LabelOption[] = [
+ { label: '待审核', value: 'pending' },
+ { label: '审核通过', value: 'approved' },
+ { label: '审核驳回', value: 'rejected' },
+]
+
+export const auditActionOptions: LabelOption<'approved' | 'rejected'>[] = [
+ { label: '审核通过', value: 'approved' },
+ { label: '审核驳回', value: 'rejected' },
+]
+
+export const settleStatusOptions: LabelOption[] = [
+ { label: '待开通', value: 'pending' },
+ { label: '正常结算', value: 'active' },
+ { label: '结算冻结', value: 'frozen' },
+]
+
+export const saleStatusOptions: LabelOption[] = [
+ { label: '下架', value: 'off' },
+ { label: '上架', value: 'on' },
+]
+
+export const orderStatusOptions: LabelOption[] = [
+ { label: '待支付', value: 'pending_pay' },
+ { label: '待发货', value: 'paid_wait_ship' },
+ { label: '已发货', value: 'shipped' },
+ { label: '已完成', value: 'finished' },
+ { label: '已取消', value: 'canceled' },
+ { label: '售后中', value: 'after_sale' },
+ { label: '退款完成', value: 'refund_finished' },
+]
+
+export const afterSaleStatusOptions: LabelOption[] = [
+ { label: '无售后', value: 'none' },
+ { label: '待审核', value: 'pending' },
+ { label: '审核通过', value: 'approved' },
+ { label: '审核驳回', value: 'rejected' },
+ { label: '退款中', value: 'refunding' },
+ { label: '已完成', value: 'finished' },
+ { label: '已关闭', value: 'closed' },
+]
+
+export const recommendStorePositionOptions: LabelOption[] = [
+ { label: '首页排行榜', value: 'home_rank_store' },
+ { label: '首页优质商家', value: 'home_featured_store' },
+]
+
+export const bannerPositionOptions: LabelOption>[] = [
+ { label: '首页顶部 Banner', value: 'home_top' },
+]
+
+export const linkTypeOptions: LabelOption[] = [
+ { label: '商品', value: 'goods' },
+ { label: '商家', value: 'store' },
+ { label: '专题', value: 'topic' },
+ { label: 'H5', value: 'h5' },
+]
+
+const STATUS_LABEL_MAP: Record = {
+ enabled: '启用',
+ disabled: '停用',
+ pending: '待审核',
+ approved: '审核通过',
+ rejected: '审核驳回',
+ active: '正常结算',
+ frozen: '结算冻结',
+ off: '下架',
+ on: '上架',
+ pending_pay: '待支付',
+ paid_wait_ship: '待发货',
+ shipped: '已发货',
+ finished: '已完成',
+ canceled: '已取消',
+ after_sale: '售后中',
+ refund_finished: '退款完成',
+ none: '无售后',
+ refunding: '退款中',
+ closed: '已关闭',
+}
+
+const STATUS_COLOR_MAP: Record = {
+ enabled: 'green',
+ disabled: 'default',
+ pending: 'gold',
+ approved: 'green',
+ rejected: 'red',
+ active: 'blue',
+ frozen: 'volcano',
+ off: 'default',
+ on: 'cyan',
+ pending_pay: 'gold',
+ paid_wait_ship: 'processing',
+ shipped: 'blue',
+ finished: 'green',
+ canceled: 'default',
+ after_sale: 'orange',
+ refund_finished: 'purple',
+ none: 'default',
+ refunding: 'processing',
+ closed: 'default',
+}
+
+const RECOMMEND_STORE_POSITION_LABEL_MAP: Record = {
+ home_rank_store: '首页排行榜',
+ home_featured_store: '首页优质商家',
+}
+
+const LINK_TYPE_LABEL_MAP: Record = {
+ goods: '商品',
+ store: '商家',
+ topic: '专题',
+ h5: 'H5',
+}
+
+export const getStatusLabel = (value?: string | null) => {
+ if (!value) {
+ return '--'
+ }
+ return STATUS_LABEL_MAP[value] || value
+}
+
+export const getStatusColor = (value?: string | null) => {
+ if (!value) {
+ return 'default'
+ }
+ return STATUS_COLOR_MAP[value] || 'default'
+}
+
+export const getRecommendStorePositionLabel = (value?: string | null) => {
+ if (!value) {
+ return '--'
+ }
+ if (value === 'home_top') {
+ return '首页顶部 Banner'
+ }
+ return RECOMMEND_STORE_POSITION_LABEL_MAP[value as RecommendStorePositionCode] || value
+}
+
+export const getLinkTypeLabel = (value?: string | null) => {
+ if (!value) {
+ return '--'
+ }
+ return LINK_TYPE_LABEL_MAP[value as LinkType] || value
+}
+
+export const getCategoryLevelLabel = (value?: number | null) => {
+ if (!value) {
+ return '--'
+ }
+ return `L${value}`
+}
+
+export const getBooleanLabel = (value?: boolean | null) => {
+ if (value === undefined || value === null) {
+ return '--'
+ }
+ return value ? '是' : '否'
+}
+
+export const withAllOption = (options: LabelOption[]) => {
+ return [{ label: '全部', value: '' }, ...options]
+}
diff --git a/src/vite-env.d.ts b/src/vite-env.d.ts
new file mode 100644
index 0000000..ed77210
--- /dev/null
+++ b/src/vite-env.d.ts
@@ -0,0 +1,2 @@
+///
+
diff --git a/tsconfig.app.json b/tsconfig.app.json
new file mode 100644
index 0000000..197bd0f
--- /dev/null
+++ b/tsconfig.app.json
@@ -0,0 +1,24 @@
+{
+ "extends": "./tsconfig.json",
+ "compilerOptions": {
+ "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
+ "target": "ES2022",
+ "useDefineForClassFields": true,
+ "lib": ["ES2022", "DOM", "DOM.Iterable"],
+ "module": "ESNext",
+ "skipLibCheck": true,
+ "moduleResolution": "bundler",
+ "allowImportingTsExtensions": true,
+ "verbatimModuleSyntax": true,
+ "moduleDetection": "force",
+ "noEmit": true,
+ "jsx": "react-jsx",
+ "strict": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "noFallthroughCasesInSwitch": true,
+ "allowSyntheticDefaultImports": true
+ },
+ "include": ["src"]
+}
+
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
index 0000000..4e91a73
--- /dev/null
+++ b/tsconfig.json
@@ -0,0 +1,14 @@
+{
+ "compilerOptions": {
+ "baseUrl": "./",
+ "paths": {
+ "@/*": ["src/*"]
+ }
+ },
+ "files": [],
+ "references": [
+ { "path": "./tsconfig.app.json" },
+ { "path": "./tsconfig.node.json" }
+ ]
+}
+
diff --git a/tsconfig.node.json b/tsconfig.node.json
new file mode 100644
index 0000000..4979eff
--- /dev/null
+++ b/tsconfig.node.json
@@ -0,0 +1,17 @@
+{
+ "compilerOptions": {
+ "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
+ "target": "ES2023",
+ "lib": ["ES2023"],
+ "module": "ESNext",
+ "skipLibCheck": true,
+ "moduleResolution": "bundler",
+ "allowSyntheticDefaultImports": true,
+ "verbatimModuleSyntax": true,
+ "moduleDetection": "force",
+ "noEmit": true,
+ "strict": true
+ },
+ "include": ["vite.config.ts"]
+}
+
diff --git a/vite.config.ts b/vite.config.ts
new file mode 100644
index 0000000..d6014f7
--- /dev/null
+++ b/vite.config.ts
@@ -0,0 +1,45 @@
+import { defineConfig } from 'vite'
+import react from '@vitejs/plugin-react-swc'
+import { dirname, resolve } from 'node:path'
+import { fileURLToPath } from 'node:url'
+
+const __dirname = dirname(fileURLToPath(import.meta.url))
+
+export default defineConfig({
+ plugins: [react()],
+ resolve: {
+ alias: {
+ '@': resolve(__dirname, 'src'),
+ },
+ },
+ server: {
+ port: 5200,
+ },
+ build: {
+ chunkSizeWarningLimit: 1200,
+ rollupOptions: {
+ output: {
+ manualChunks(id) {
+ if (id.includes('node_modules/antd') || id.includes('node_modules/@ant-design')) {
+ return 'antd-vendor'
+ }
+
+ if (id.includes('node_modules/react-router-dom') || id.includes('node_modules/react-router')) {
+ return 'router-vendor'
+ }
+
+ if (
+ id.includes('node_modules/axios')
+ || id.includes('node_modules/dayjs')
+ || id.includes('node_modules/sonner')
+ || id.includes('node_modules/zustand')
+ ) {
+ return 'app-vendor'
+ }
+
+ return undefined
+ },
+ },
+ },
+ },
+})