添加了postsPanel作为通用帖子显示板,增加了对jest测试的配置,添加了论坛主页,设定了论坛全局框架,设定了论坛基础主题色及主题切换、字号切换逻辑
Change-Id: I9fad0cf577088adb00c9850d405ccd23e6072413
diff --git a/assets/ad1.png b/assets/ad1.png
new file mode 100644
index 0000000..fb4f3dc
--- /dev/null
+++ b/assets/ad1.png
Binary files differ
diff --git a/assets/ad2.png b/assets/ad2.png
new file mode 100644
index 0000000..ec345e6
--- /dev/null
+++ b/assets/ad2.png
Binary files differ
diff --git a/asserts/logo.png b/assets/logo.png
similarity index 100%
rename from asserts/logo.png
rename to assets/logo.png
Binary files differ
diff --git a/package-lock.json b/package-lock.json
index 9094e24..9d6fbc7 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -9,12 +9,14 @@
"version": "0.0.1",
"license": "ISC",
"dependencies": {
+ "@ant-design/icons": "^5.6.1",
"@babel/core": "^7.26.9",
"@babel/preset-typescript": "^7.26.0",
"@jest/globals": "^29.7.0",
"@reduxjs/toolkit": "^2.6.1",
"@types/react": "^19.0.10",
"@types/react-dom": "^19.0.4",
+ "antd": "^5.24.7",
"axios": "^1.8.4",
"axios-mock-adapter": "^2.1.0",
"babel-loader": "^9.2.1",
@@ -24,6 +26,8 @@
"css-minimizer-webpack-plugin": "^7.0.0",
"html-webpack-plugin": "^5.6.3",
"jest-environment-jsdom": "^29.7.0",
+ "lodash": "^4.17.21",
+ "lodash.debounce": "^4.0.8",
"mini-css-extract-plugin": "^2.9.2",
"mock": "^0.1.1",
"mockjs": "^1.1.0",
@@ -45,6 +49,7 @@
"@testing-library/react": "^16.3.0",
"@testing-library/user-event": "^14.6.1",
"@types/jest": "^29.5.14",
+ "@types/lodash": "^4.17.16",
"babel-jest": "^29.7.0",
"css-loader": "^7.1.2",
"identity-obj-proxy": "^3.0.0",
@@ -80,6 +85,103 @@
"node": ">=6.0.0"
}
},
+ "node_modules/@ant-design/colors": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmmirror.com/@ant-design/colors/-/colors-7.2.0.tgz",
+ "integrity": "sha512-bjTObSnZ9C/O8MB/B4OUtd/q9COomuJAR2SYfhxLyHvCKn4EKwCN3e+fWGMo7H5InAyV0wL17jdE9ALrdOW/6A==",
+ "license": "MIT",
+ "dependencies": {
+ "@ant-design/fast-color": "^2.0.6"
+ }
+ },
+ "node_modules/@ant-design/cssinjs": {
+ "version": "1.23.0",
+ "resolved": "https://registry.npmmirror.com/@ant-design/cssinjs/-/cssinjs-1.23.0.tgz",
+ "integrity": "sha512-7GAg9bD/iC9ikWatU9ym+P9ugJhi/WbsTWzcKN6T4gU0aehsprtke1UAaaSxxkjjmkJb3llet/rbUSLPgwlY4w==",
+ "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.npmmirror.com/@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": "2.0.6",
+ "resolved": "https://registry.npmmirror.com/@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/@ant-design/icons": {
+ "version": "5.6.1",
+ "resolved": "https://registry.npmmirror.com/@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/@ant-design/icons-svg": {
+ "version": "4.4.2",
+ "resolved": "https://registry.npmmirror.com/@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.npmmirror.com/@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/code-frame": {
"version": "7.26.2",
"resolved": "https://registry.npmmirror.com/@babel/code-frame/-/code-frame-7.26.2.tgz",
@@ -1834,7 +1936,6 @@
"version": "7.27.0",
"resolved": "https://registry.npmmirror.com/@babel/runtime/-/runtime-7.27.0.tgz",
"integrity": "sha512-VtPOkrdPHZsKc/clNqyi9WUA8TINkZ4cGk63UUE3u4pmB2k+ZMQRDuIOagv8UVd6j7k0T3+RRIb7beKTebNbcw==",
- "dev": true,
"license": "MIT",
"dependencies": {
"regenerator-runtime": "^0.14.0"
@@ -3008,6 +3109,18 @@
"node": ">=14.17.0"
}
},
+ "node_modules/@emotion/hash": {
+ "version": "0.8.0",
+ "resolved": "https://registry.npmmirror.com/@emotion/hash/-/hash-0.8.0.tgz",
+ "integrity": "sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==",
+ "license": "MIT"
+ },
+ "node_modules/@emotion/unitless": {
+ "version": "0.7.5",
+ "resolved": "https://registry.npmmirror.com/@emotion/unitless/-/unitless-0.7.5.tgz",
+ "integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==",
+ "license": "MIT"
+ },
"node_modules/@istanbuljs/load-nyc-config": {
"version": "1.1.0",
"resolved": "https://registry.npmmirror.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz",
@@ -3531,6 +3644,155 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/@rc-component/async-validator": {
+ "version": "5.0.4",
+ "resolved": "https://registry.npmmirror.com/@rc-component/async-validator/-/async-validator-5.0.4.tgz",
+ "integrity": "sha512-qgGdcVIF604M9EqjNF0hbUTz42bz/RDtxWdWuU5EQe3hi7M8ob54B6B35rOsvX5eSvIHIzT9iH1R3n+hk3CGfg==",
+ "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.npmmirror.com/@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/context": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmmirror.com/@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.0",
+ "resolved": "https://registry.npmmirror.com/@rc-component/mini-decimal/-/mini-decimal-1.1.0.tgz",
+ "integrity": "sha512-jS4E7T9Li2GuYwI6PyiVXmxTiM6b07rlD9Ge8uGZSCz3WlzcG5ZK7g5bbuKNeZ9pgUuPK/5guV781ujdVpm4HQ==",
+ "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.npmmirror.com/@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.npmmirror.com/@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.0.0",
+ "resolved": "https://registry.npmmirror.com/@rc-component/qrcode/-/qrcode-1.0.0.tgz",
+ "integrity": "sha512-L+rZ4HXP2sJ1gHMGHjsg9jlYBX/SLN2D6OxP9Zn3qgtpMWtO2vUfxVFwiogHpAIqs54FnALxraUy/BCO1yRIgg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.24.7",
+ "classnames": "^2.3.2",
+ "rc-util": "^5.38.0"
+ },
+ "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.npmmirror.com/@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.2.6",
+ "resolved": "https://registry.npmmirror.com/@rc-component/trigger/-/trigger-2.2.6.tgz",
+ "integrity": "sha512-/9zuTnWwhQ3S3WT1T8BubuFTT46kvnXgaERR9f4BTKyn61/wpf/BvbImzYBubzJibU707FxwbKszLlHjcLiv1Q==",
+ "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/@reduxjs/toolkit": {
"version": "2.6.1",
"resolved": "https://registry.npmmirror.com/@reduxjs/toolkit/-/toolkit-2.6.1.tgz",
@@ -4013,6 +4275,13 @@
"integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
"license": "MIT"
},
+ "node_modules/@types/lodash": {
+ "version": "4.17.16",
+ "resolved": "https://registry.npmmirror.com/@types/lodash/-/lodash-4.17.16.tgz",
+ "integrity": "sha512-HX7Em5NYQAXKW+1T+FiuG27NGwzJfCX3s1GjOa7ujxZa52kjJLOr4FUxT+giF6Tgxv1e+/czV/iTtBw27WTU9g==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/@types/mime": {
"version": "1.3.5",
"resolved": "https://registry.npmmirror.com/@types/mime/-/mime-1.3.5.tgz",
@@ -4541,6 +4810,71 @@
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
+ "node_modules/antd": {
+ "version": "5.24.7",
+ "resolved": "https://registry.npmmirror.com/antd/-/antd-5.24.7.tgz",
+ "integrity": "sha512-xROWsw0yYFGiNFpVSUZ9/Gs43q0qIM9BkfjugeqgePlZBpLZzLjtOpf4UGM+5aijelHqMi8864KZCX5BbcZYfA==",
+ "license": "MIT",
+ "dependencies": {
+ "@ant-design/colors": "^7.2.0",
+ "@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.0.0",
+ "@rc-component/tour": "~1.15.1",
+ "@rc-component/trigger": "^2.2.6",
+ "classnames": "^2.5.1",
+ "copy-to-clipboard": "^3.3.3",
+ "dayjs": "^1.11.11",
+ "rc-cascader": "~3.33.1",
+ "rc-checkbox": "~3.5.0",
+ "rc-collapse": "~3.9.0",
+ "rc-dialog": "~9.6.0",
+ "rc-drawer": "~7.2.0",
+ "rc-dropdown": "~4.2.1",
+ "rc-field-form": "~2.7.0",
+ "rc-image": "~7.11.1",
+ "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.3",
+ "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.6",
+ "rc-slider": "~11.1.8",
+ "rc-steps": "~6.0.1",
+ "rc-switch": "~4.1.0",
+ "rc-table": "~7.50.4",
+ "rc-tabs": "~15.5.2",
+ "rc-textarea": "~1.10.0",
+ "rc-tooltip": "~6.4.0",
+ "rc-tree": "~5.13.1",
+ "rc-tree-select": "~5.27.0",
+ "rc-upload": "~4.8.1",
+ "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/anymatch": {
"version": "3.1.3",
"resolved": "https://registry.npmmirror.com/anymatch/-/anymatch-3.1.3.tgz",
@@ -5228,6 +5562,12 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/classnames": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmmirror.com/classnames/-/classnames-2.5.1.tgz",
+ "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==",
+ "license": "MIT"
+ },
"node_modules/clean-css": {
"version": "5.3.3",
"resolved": "https://registry.npmmirror.com/clean-css/-/clean-css-5.3.3.tgz",
@@ -5393,6 +5733,12 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/compute-scroll-into-view": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmmirror.com/compute-scroll-into-view/-/compute-scroll-into-view-3.1.1.tgz",
+ "integrity": "sha512-VRhuHOLoKYOy4UbilLbUzbYg93XLjv2PncJC50EuTWPA3gaja1UjBsUP/D/9/juV3vQFr6XBEzn9KCAHdUvOHw==",
+ "license": "MIT"
+ },
"node_modules/concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmmirror.com/concat-map/-/concat-map-0.0.1.tgz",
@@ -5467,6 +5813,15 @@
"url": "https://github.com/sponsors/mesqueeb"
}
},
+ "node_modules/copy-to-clipboard": {
+ "version": "3.3.3",
+ "resolved": "https://registry.npmmirror.com/copy-to-clipboard/-/copy-to-clipboard-3.3.3.tgz",
+ "integrity": "sha512-2KV8NhB5JqC3ky0r9PMCAZKbUHSwtEo4CwCs0KXgruG43gX5PMqDEBbVU4OUzw2MuAWUfsuFmWvEKG5QRfSnJA==",
+ "license": "MIT",
+ "dependencies": {
+ "toggle-selection": "^1.0.6"
+ }
+ },
"node_modules/core-js": {
"version": "3.41.0",
"resolved": "https://registry.npmmirror.com/core-js/-/core-js-3.41.0.tgz",
@@ -5992,6 +6347,12 @@
"node": ">=12"
}
},
+ "node_modules/dayjs": {
+ "version": "1.11.13",
+ "resolved": "https://registry.npmmirror.com/dayjs/-/dayjs-1.11.13.tgz",
+ "integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==",
+ "license": "MIT"
+ },
"node_modules/debug": {
"version": "4.4.0",
"resolved": "https://registry.npmmirror.com/debug/-/debug-4.4.0.tgz",
@@ -8892,6 +9253,15 @@
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
"license": "MIT"
},
+ "node_modules/json2mq": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmmirror.com/json2mq/-/json2mq-0.2.0.tgz",
+ "integrity": "sha512-SzoRg7ux5DWTII9J2qkrZrqV1gt+rTaoufMxEzXbS26Uid0NwaJd123HcoB80TgubEppxxIGdNxCx50fEoEWQA==",
+ "license": "MIT",
+ "dependencies": {
+ "string-convert": "^0.2.0"
+ }
+ },
"node_modules/json5": {
"version": "2.2.3",
"resolved": "https://registry.npmmirror.com/json5/-/json5-2.2.3.tgz",
@@ -9051,7 +9421,6 @@
"version": "4.0.8",
"resolved": "https://registry.npmmirror.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
"integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==",
- "dev": true,
"license": "MIT"
},
"node_modules/lodash.memoize": {
@@ -11343,6 +11712,612 @@
"node": ">=0.10.0"
}
},
+ "node_modules/rc-cascader": {
+ "version": "3.33.1",
+ "resolved": "https://registry.npmmirror.com/rc-cascader/-/rc-cascader-3.33.1.tgz",
+ "integrity": "sha512-Kyl4EJ7ZfCBuidmZVieegcbFw0RcU5bHHSbtEdmuLYd0fYHCAiYKZ6zon7fWAVyC6rWWOOib0XKdTSf7ElC9rg==",
+ "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.npmmirror.com/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.npmmirror.com/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.npmmirror.com/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.2.0",
+ "resolved": "https://registry.npmmirror.com/rc-drawer/-/rc-drawer-7.2.0.tgz",
+ "integrity": "sha512-9lOQ7kBekEJRdEpScHvtmEtXnAsy+NGDXiRWc2ZVC7QXAazNVbeT4EraQKYwCME8BJLa8Bxqxvs5swwyOepRwg==",
+ "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.npmmirror.com/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.0",
+ "resolved": "https://registry.npmmirror.com/rc-field-form/-/rc-field-form-2.7.0.tgz",
+ "integrity": "sha512-hgKsCay2taxzVnBPZl+1n4ZondsV78G++XVsMIJCAoioMjlMQR9YwAp7JZDIECzIu2Z66R+f4SFIRrO2DjDNAA==",
+ "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.11.1",
+ "resolved": "https://registry.npmmirror.com/rc-image/-/rc-image-7.11.1.tgz",
+ "integrity": "sha512-XuoWx4KUXg7hNy5mRTy1i8c8p3K8boWg6UajbHpDXS5AlRVucNfTi5YxTtPBTBzegxAZpvuLfh3emXFt6ybUdA==",
+ "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.npmmirror.com/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.npmmirror.com/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.npmmirror.com/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.npmmirror.com/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.npmmirror.com/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.3",
+ "resolved": "https://registry.npmmirror.com/rc-notification/-/rc-notification-5.6.3.tgz",
+ "integrity": "sha512-42szwnn8VYQoT6GnjO00i1iwqV9D1TTMvxObWsuLwgl0TsOokzhkYiufdtQBsJMFjJravS1hfDKVMHLKLcPE4g==",
+ "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.4.1",
+ "resolved": "https://registry.npmmirror.com/rc-overflow/-/rc-overflow-1.4.1.tgz",
+ "integrity": "sha512-3MoPQQPV1uKyOMVNd6SZfONi+f3st0r8PksexIdBTeIYbMX0Jr+k7pHEDvsXtR4BpCv90/Pv2MovVNhktKrwvw==",
+ "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.npmmirror.com/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.npmmirror.com/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.npmmirror.com/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.npmmirror.com/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.npmmirror.com/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.0",
+ "resolved": "https://registry.npmmirror.com/rc-segmented/-/rc-segmented-2.7.0.tgz",
+ "integrity": "sha512-liijAjXz+KnTRVnxxXG2sYDGd6iLL7VpGGdR8gwoxAXy2KglviKCxLWZdjKYJzYzGSUwKDSTdYk8brj54Bn5BA==",
+ "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.6",
+ "resolved": "https://registry.npmmirror.com/rc-select/-/rc-select-14.16.6.tgz",
+ "integrity": "sha512-YPMtRPqfZWOm2XGTbx5/YVr1HT0vn//8QS77At0Gjb3Lv+Lbut0IORJPKLWu1hQ3u4GsA0SrDzs7nI8JG7Zmyg==",
+ "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.8",
+ "resolved": "https://registry.npmmirror.com/rc-slider/-/rc-slider-11.1.8.tgz",
+ "integrity": "sha512-2gg/72YFSpKP+Ja5AjC5DPL1YnV8DEITDQrcc1eASrUYjl0esptaBVJBh5nLTXCCp15eD8EuGjwezVGSHhs9tQ==",
+ "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.npmmirror.com/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.npmmirror.com/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.50.4",
+ "resolved": "https://registry.npmmirror.com/rc-table/-/rc-table-7.50.4.tgz",
+ "integrity": "sha512-Y+YuncnQqoS5e7yHvfvlv8BmCvwDYDX/2VixTBEhkMDk9itS9aBINp4nhzXFKiBP/frG4w0pS9d9Rgisl0T1Bw==",
+ "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.5.2",
+ "resolved": "https://registry.npmmirror.com/rc-tabs/-/rc-tabs-15.5.2.tgz",
+ "integrity": "sha512-Hbqf2IV6k/jPgfMjPtIDmPV0D0C9c/fN4B/fYcoh9qqaUzUZQoK0PYzsV3UaV+3UsmyoYt48p74m/HkLhGTw+w==",
+ "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.0",
+ "resolved": "https://registry.npmmirror.com/rc-textarea/-/rc-textarea-1.10.0.tgz",
+ "integrity": "sha512-ai9IkanNuyBS4x6sOL8qu/Ld40e6cEs6pgk93R+XLYg0mDSjNBGey6/ZpDs5+gNLD7urQ14po3V6Ck2dJLt9SA==",
+ "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.npmmirror.com/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.npmmirror.com/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.npmmirror.com/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.8.1",
+ "resolved": "https://registry.npmmirror.com/rc-upload/-/rc-upload-4.8.1.tgz",
+ "integrity": "sha512-toEAhwl4hjLAI1u8/CgKWt30BR06ulPa4iGQSMvSXoHzO88gPCslxqV/mnn4gJU7PDoltGIC9Eh+wkeudqgHyw==",
+ "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.npmmirror.com/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.18.5",
+ "resolved": "https://registry.npmmirror.com/rc-virtual-list/-/rc-virtual-list-3.18.5.tgz",
+ "integrity": "sha512-1FuxVSxhzTj3y8k5xMPbhXCB0t2TOiI3Tq+qE2Bu+GGV7f+ECVuQl4OUg6lZ2qT5fordTW7CBpr9czdzXCI7Pg==",
+ "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": "19.1.0",
"resolved": "https://registry.npmmirror.com/react/-/react-19.1.0.tgz",
@@ -11561,7 +12536,6 @@
"version": "0.14.1",
"resolved": "https://registry.npmmirror.com/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",
"integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==",
- "dev": true,
"license": "MIT"
},
"node_modules/regenerator-transform": {
@@ -11678,6 +12652,12 @@
"integrity": "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==",
"license": "MIT"
},
+ "node_modules/resize-observer-polyfill": {
+ "version": "1.5.1",
+ "resolved": "https://registry.npmmirror.com/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz",
+ "integrity": "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==",
+ "license": "MIT"
+ },
"node_modules/resolve": {
"version": "1.22.10",
"resolved": "https://registry.npmmirror.com/resolve/-/resolve-1.22.10.tgz",
@@ -11833,6 +12813,15 @@
"url": "https://opencollective.com/webpack"
}
},
+ "node_modules/scroll-into-view-if-needed": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmmirror.com/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/select-hose": {
"version": "2.0.0",
"resolved": "https://registry.npmmirror.com/select-hose/-/select-hose-2.0.0.tgz",
@@ -12293,6 +13282,12 @@
"safe-buffer": "~5.2.0"
}
},
+ "node_modules/string-convert": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmmirror.com/string-convert/-/string-convert-0.2.1.tgz",
+ "integrity": "sha512-u/1tdPl4yQnPBjnVrmdLo9gtuLvELKsAoRapekWggdiQNvvvum+jYF329d84NAa660KQw7pB2n36KrIKVoXa3A==",
+ "license": "MIT"
+ },
"node_modules/string-length": {
"version": "4.0.2",
"resolved": "https://registry.npmmirror.com/string-length/-/string-length-4.0.2.tgz",
@@ -12426,6 +13421,12 @@
"node": ">=4"
}
},
+ "node_modules/stylis": {
+ "version": "4.3.6",
+ "resolved": "https://registry.npmmirror.com/stylis/-/stylis-4.3.6.tgz",
+ "integrity": "sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ==",
+ "license": "MIT"
+ },
"node_modules/supports-color": {
"version": "8.1.1",
"resolved": "https://registry.npmmirror.com/supports-color/-/supports-color-8.1.1.tgz",
@@ -12672,6 +13673,15 @@
"tslib": "^2"
}
},
+ "node_modules/throttle-debounce": {
+ "version": "5.0.2",
+ "resolved": "https://registry.npmmirror.com/throttle-debounce/-/throttle-debounce-5.0.2.tgz",
+ "integrity": "sha512-B71/4oyj61iNH0KeCamLuE2rmKuTO5byTOSVwECM5FA7TiAiAW+UqTKZ9ERueC4qvgSttUhdmq1mXC3kJqGX7A==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.22"
+ }
+ },
"node_modules/thunky": {
"version": "1.1.0",
"resolved": "https://registry.npmmirror.com/thunky/-/thunky-1.1.0.tgz",
@@ -12697,6 +13707,12 @@
"node": ">=8.0"
}
},
+ "node_modules/toggle-selection": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmmirror.com/toggle-selection/-/toggle-selection-1.0.6.tgz",
+ "integrity": "sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ==",
+ "license": "MIT"
+ },
"node_modules/toidentifier": {
"version": "1.0.1",
"resolved": "https://registry.npmmirror.com/toidentifier/-/toidentifier-1.0.1.tgz",
diff --git a/package.json b/package.json
index 1550be5..43abfdd 100644
--- a/package.json
+++ b/package.json
@@ -11,12 +11,14 @@
"author": "san3yuan",
"license": "ISC",
"dependencies": {
+ "@ant-design/icons": "^5.6.1",
"@babel/core": "^7.26.9",
"@babel/preset-typescript": "^7.26.0",
"@jest/globals": "^29.7.0",
"@reduxjs/toolkit": "^2.6.1",
"@types/react": "^19.0.10",
"@types/react-dom": "^19.0.4",
+ "antd": "^5.24.7",
"axios": "^1.8.4",
"axios-mock-adapter": "^2.1.0",
"babel-loader": "^9.2.1",
@@ -26,6 +28,8 @@
"css-minimizer-webpack-plugin": "^7.0.0",
"html-webpack-plugin": "^5.6.3",
"jest-environment-jsdom": "^29.7.0",
+ "lodash": "^4.17.21",
+ "lodash.debounce": "^4.0.8",
"mini-css-extract-plugin": "^2.9.2",
"mock": "^0.1.1",
"mockjs": "^1.1.0",
@@ -47,6 +51,7 @@
"@testing-library/react": "^16.3.0",
"@testing-library/user-event": "^14.6.1",
"@types/jest": "^29.5.14",
+ "@types/lodash": "^4.17.16",
"babel-jest": "^29.7.0",
"css-loader": "^7.1.2",
"identity-obj-proxy": "^3.0.0",
diff --git a/scripts/webpack.dev.js b/scripts/webpack.dev.js
index bd4ebac..c5feb8d 100644
--- a/scripts/webpack.dev.js
+++ b/scripts/webpack.dev.js
@@ -14,7 +14,8 @@
'process.env': JSON.stringify({
NODE_ENV: 'development', // 等价于 mode 设置
PUBLIC_URL: './', // 建议使用相对路径
- API_BASE_URL: 'http://localhost:3030/api' // 添加API路径
+ API_BASE_URL: 'http://localhost:3030/api', // 添加API路径
+ WEB_BASE_URL: 'http://localhost:8080', // 添加WEB路径
})
}),
],
diff --git a/src/api/post.ts b/src/api/post.ts
new file mode 100644
index 0000000..99dc8fa
--- /dev/null
+++ b/src/api/post.ts
@@ -0,0 +1 @@
+export const hotPosts='/post/hot'
\ No newline at end of file
diff --git a/src/components/navbar/navbar.module.css b/src/components/navbar/navbar.module.css
new file mode 100644
index 0000000..b531f39
--- /dev/null
+++ b/src/components/navbar/navbar.module.css
@@ -0,0 +1,12 @@
+.menu{
+ background-color: var(--card-bg);
+ font-size: 1rem;
+
+ display:flex;
+ justify-content:space-between;
+}
+
+.menu *{
+ color:var(--text-color);
+ transition:none;
+}
\ No newline at end of file
diff --git a/src/components/navbar/navbar.tsx b/src/components/navbar/navbar.tsx
new file mode 100644
index 0000000..a6c1fe7
--- /dev/null
+++ b/src/components/navbar/navbar.tsx
@@ -0,0 +1,139 @@
+import React, { use } from "react";
+import Icon, {
+ HomeOutlined,
+} from "@ant-design/icons";
+import { Space } from 'antd';
+import type { GetProps } from 'antd';
+import type { MenuProps } from 'antd';
+import { Menu } from 'antd';
+import { useState } from "react";
+import style from './navbar.module.css'
+
+
+type CustomIconComponentProps = GetProps<typeof Icon>;
+type MenuItem = Required<MenuProps>['items'][number];
+const web_base_url = process.env.WEB_BASE_URL || 'http://localhost:3000';
+
+const VideoSvg = () => (
+ <svg width="1em" height="1em" fill="currentColor" xmlns="http://www.w3.org/2000/svg" p-id="7331" viewBox="0 0 1024 1024">
+ <title>video icon</title>
+ <path d="M85.333333 348.091733V764.586667a153.6 153.6 0 0 0 153.6 153.6h561.578667a153.6 153.6 0 0 0 153.6-153.6V348.091733a153.6 153.6 0 0 0-153.6-153.6H238.933333a153.6 153.6 0 0 0-153.6 153.6z m68.266667 0a85.333333 85.333333 0 0 1 85.333333-85.333333h561.578667a85.333333 85.333333 0 0 1 85.333333 85.333333V764.586667a85.333333 85.333333 0 0 1-85.333333 85.333333H238.933333a85.333333 85.333333 0 0 1-85.333333-85.333333V348.091733z" fill="#000000" p-id="7332"></path><path d="M401.885867 182.254933l-65.467734-69.188266a34.133333 34.133333 0 1 0-49.578666 46.933333l65.450666 69.1712a34.133333 34.133333 0 0 0 49.595734-46.916267zM635.989333 182.254933l65.467734-69.188266a34.133333 34.133333 0 1 1 49.578666 46.933333l-65.467733 69.1712a34.133333 34.133333 0 0 1-49.578667-46.916267zM383.556267 472.405333v167.168a85.333333 85.333333 0 0 0 128.733866 73.454934l141.482667-83.575467a85.333333 85.333333 0 0 0 0-146.944l-141.482667-83.575467a85.333333 85.333333 0 0 0-128.733866 73.454934z m68.266666 0a17.066667 17.066667 0 0 1 25.7536-14.711466l141.4656 83.592533a17.066667 17.066667 0 0 1 0 29.3888l-141.482666 83.575467a17.066667 17.066667 0 0 1-25.736534-14.677334v-167.185066z" fill="#000000" p-id="7333"></path>
+ </svg>
+);
+const MusicSvg = () => (
+ <svg width="1em" height="1em" fill="currentColor" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2776" viewBox="0 0 1024 1024">
+ <title>music icon</title>
+ <path d="M895.456 770.56C895.552 769.696 896 768.896 896 768L896 160c0-1.056-0.512-1.984-0.608-3.008-0.032-1.664 0.448-3.232 0.16-4.928-2.88-17.408-19.328-29.184-36.8-26.304l-480 80c-17.408 2.912-29.216 19.392-26.304 36.832 0.256 1.472 1.024 2.656 1.44 4.032C352.96 249.664 352 252.672 352 256l0 429.6c-20.128-9.376-43.648-14.784-69.408-14.784-21.312 0-42.816 3.456-63.968 10.336-39.616 12.8-73.536 36.224-95.584 65.984-24.064 32.512-31.68 70.24-20.864 103.648 15.648 48.288 66.656 79.456 129.92 79.456 21.248 0 42.72-3.456 63.904-10.304 58.656-19.04 100.288-59.2 115.04-103.808C413.888 811.328 416 806.016 416 800l0-5.312c1.056-8.48 1.056-16.96 0-25.472L416 264.448l416-69.344 0 490.88c-20.32-9.632-44.096-15.2-70.176-15.2-21.28 0-42.816 3.456-63.968 10.336-39.584 12.8-73.568 36.224-95.584 65.984-24.096 32.512-31.68 70.24-20.864 103.648 15.648 48.288 66.656 79.456 129.92 79.456 21.248 0 42.752-3.456 63.904-10.304C853.472 894.56 902.176 831.68 895.456 770.56z" fill="#5D646F" p-id="2777"></path>
+
+ </svg>
+);
+const GameSvg = () => (
+ <svg width="1em" height="1em" fill="currentColor" xmlns="http://www.w3.org/2000/svg" p-id="8400" viewBox="0 0 1024 1024">
+ <title>game icon</title>
+ <path d="M683.2 588.8m-43.2 0a43.2 43.2 0 1 0 86.4 0 43.2 43.2 0 1 0-86.4 0Z" p-id="8401"></path><path d="M942.4 358.4c-30.4-17.6-64-25.6-97.6-25.6H555.2v-256c0-25.6-17.6-43.2-43.2-43.2s-43.2 17.6-43.2 43.2v256H174.4C81.6 332.8 0 414.4 0 512v299.2c0 97.6 81.6 179.2 174.4 179.2 84.8 0 158.4-64 174.4-145.6h324.8c17.6 81.6 84.8 145.6 174.4 145.6 97.6 0 174.4-81.6 174.4-179.2V512c1.6-59.2-28.8-120-80-153.6z m-4.8 456c0 51.2-36.8 94.4-88 94.4s-89.6-43.2-89.6-94.4c0-30.4-20.8-56-56-56H353.6c-64 0-84.8 25.6-84.8 56 0 51.2-43.2 89.6-89.6 89.6-51.2 0-89.6-43.2-89.6-94.4V507.2c-4.8-46.4 38.4-89.6 84.8-89.6H848c25.6 0 46.4 8 64 30.4 17.6 17.6 25.6 38.4 25.6 64v302.4z" p-id="8402"></path><path d="M340.8 545.6h-43.2v-43.2c0-25.6-17.6-43.2-43.2-43.2s-43.2 17.6-43.2 43.2v43.2h-43.2c-25.6 0-43.2 17.6-43.2 43.2s17.6 43.2 43.2 43.2h43.2v43.2c0 25.6 17.6 43.2 43.2 43.2s43.2-17.6 43.2-43.2v-43.2h43.2c25.6 0 43.2-17.6 43.2-43.2s-17.6-43.2-43.2-43.2z" p-id="8403"></path><path d="M852.8 588.8m-43.2 0a43.2 43.2 0 1 0 86.4 0 43.2 43.2 0 1 0-86.4 0Z" p-id="8404"></path><path d="M768 504m-43.2 0a43.2 43.2 0 1 0 86.4 0 43.2 43.2 0 1 0-86.4 0Z" p-id="8405"></path><path d="M768 673.6m-43.2 0a43.2 43.2 0 1 0 86.4 0 43.2 43.2 0 1 0-86.4 0Z" p-id="8406"></path>
+ </svg>
+);
+const SoftwareSvg = () => (
+ <svg width="1em" height="1em" fill="currentColor" xmlns="http://www.w3.org/2000/svg" p-id="10283" viewBox="0 0 1024 1024">
+ <title>software icon</title>
+ <path d="M398.208 113.792v284.448H113.76V113.792h284.448z m0-56.896H113.76c-31.296 0-56.896 25.6-56.896 56.896v284.448c0 31.296 25.6 56.896 56.896 56.896h284.448c31.296 0 56.896-25.6 56.896-56.896V113.792c0-31.296-25.6-56.896-56.896-56.896z m0 568.896v284.448H113.76v-284.448h284.448z m0-56.896H113.76c-31.296 0-56.896 25.6-56.896 56.896v284.448c0 31.296 25.6 56.896 56.896 56.896h284.448c31.296 0 56.896-25.6 56.896-56.896v-284.448c0-31.296-25.6-56.896-56.896-56.896z m512-455.104v284.448h-284.448V113.792h284.448z m0-56.896h-284.448c-31.296 0-56.896 25.6-56.896 56.896v284.448c0 31.296 25.6 56.896 56.896 56.896h284.448c31.296 0 56.896-25.6 56.896-56.896V113.792c0-31.296-25.6-56.896-56.896-56.896z m0 568.896v284.448h-284.448v-284.448h284.448z m0-56.896h-284.448c-31.296 0-56.896 25.6-56.896 56.896v284.448c0 31.296 25.6 56.896 56.896 56.896h284.448c31.296 0 56.896-25.6 56.896-56.896v-284.448c0-31.296-25.6-56.896-56.896-56.896z" p-id="10284"></path>
+ </svg>
+);
+const ChatSvg = () => (
+ <svg width="1em" height="1em" fill="currentColor" xmlns="http://www.w3.org/2000/svg" p-id="15809" viewBox="0 0 1024 1024">
+ <title>chat icon</title>
+ <path d="M920.642991 1.684336h-583.775701c-48.08972 0-87.327103 39.428785-87.327103 87.738617v88.217122H103.596262c-48.328972 0-87.566355 39.419215-87.566355 87.977869V675.935701c0 48.558654 39.237383 87.977869 87.566355 87.977869H133.024299v229.31858a28.901682 28.901682 0 0 0 18.42243 27.159925c3.588785 1.435514 7.17757 2.162841 10.766355 2.162841a29.284486 29.284486 0 0 0 21.293458-9.129869L418.691589 763.674318h268.201869c23.685981 0 44.740187-10.335701 60.770093-26.202916l93.069159 98.552822c5.742056 6.010019 13.398131 9.139439 21.293458 9.13944 3.588785 0 7.17757-0.727327 10.766355-2.162842a29.265346 29.265346 0 0 0 18.42243-27.169495V587.718579H920.642991c48.08972 0 87.327103-39.428785 87.327102-87.738616v-410.55701C1007.730841 41.103551 968.73271 1.684336 920.642991 1.684336zM686.893458 705.019215h-281.839252c-9.809346 0-18.183178 5.292262-23.446729 12.737794L191.401869 919.437159V735.547813c0-0.239252-0.239252-0.478505-0.239252-0.717757 0-0.239252 0.239252-0.478505 0.239252-0.727327 0-16.096897-13.158879-29.322766-29.188785-29.322766H103.596262c-16.029907 0-29.188785-13.216299-29.188785-29.322767V265.617944c0-16.106467 13.158879-29.332336 29.188785-29.332337h145.943925v263.943178c0 48.309832 39.237383 87.729047 87.327103 87.729047h269.876635l101.442991 107.453009c-5.502804 5.761196-12.919626 9.608374-21.293458 9.608374z m262.699065-204.8c0 16.106467-12.919626 29.093084-28.949532 29.093084h-58.616823c-16.029907 0-29.188785 13.206729-29.188785 29.322766v183.889346l-192.358878-204.082243-0.239253-0.239252c-1.914019-1.923589-4.06729-3.129421-6.459813-4.564935-0.957009-0.727327-1.914019-1.684336-3.11028-1.923588-0.957009-0.478505-1.914019-0.239252-2.871028-0.727328a24.757832 24.757832 0 0 0-8.373832-1.684336H336.86729a28.968673 28.968673 0 0 1-28.949533-29.083514V89.422953c0-16.106467 12.919626-29.093084 28.949533-29.093084h583.775701a28.968673 28.968673 0 0 1 28.949532 29.093084v410.796262z" fill="#2E323F" p-id="15810"></path>
+ </svg>
+);
+
+
+const MusicIcon = (props: Partial<CustomIconComponentProps>) => (
+ <Icon component={MusicSvg} {...props} />
+);
+const VideoIcon = (props: Partial<CustomIconComponentProps>) => (
+ <Icon component={VideoSvg} {...props} />
+);
+const GameIcon = (props: Partial<CustomIconComponentProps>) => (
+ <Icon component={GameSvg} {...props} />
+);
+const SoftwareIcon = (props: Partial<CustomIconComponentProps>) => (
+ <Icon component={SoftwareSvg} {...props} />
+);
+const ChatIcon = (props: Partial<CustomIconComponentProps>) => (
+ <Icon component={ChatSvg} {...props} />
+);
+
+
+const items: MenuItem[] = [
+{
+ key: 'home',
+ icon: <HomeOutlined />,
+ label: (
+ <a href={{web_base_url}+'/'}>
+ 首页
+ </a>
+ ),
+},
+{
+ key: 'video',
+ icon: <VideoIcon />,
+ label: (
+ <a href={{web_base_url}+'/posts?type=video'}>
+ 影视
+ </a>
+ ),
+},
+{
+ key: 'music',
+ icon: <MusicIcon />,
+ label: (
+ <a href={{web_base_url}+'/posts?type=music'}>
+ 音乐
+ </a>
+ ),
+},
+{
+ key: 'game',
+ icon: <GameIcon />,
+ label: (
+ <a href={{web_base_url}+'/posts?type=game'}>
+ 游戏
+ </a>
+ ),
+},
+{
+ key: 'software',
+ icon: <SoftwareIcon />,
+ label: (
+ <a href={{web_base_url}+'/posts?type=software'}>
+ 软件
+ </a>
+ ),
+},
+{
+ key: 'chat',
+ icon: <ChatIcon />,
+ label: (
+ <a href={{web_base_url}+'/posts?type=chat'}>
+ 聊天
+ </a>
+ ),
+},
+];
+
+
+
+
+
+const Navbar: React.FC = () => {
+ const [current, setCurrent] = useState('home');
+
+ const onClick: MenuProps['onClick'] = (e) => {
+ console.log('click ', e);
+ setCurrent(e.key);
+ };
+
+ return <Menu className={style.menu} onClick={onClick} selectedKeys={[current]} mode="horizontal" items={items} />;
+}
+
+export default Navbar;
diff --git a/src/components/postsPanel/postsPanel.module.css b/src/components/postsPanel/postsPanel.module.css
new file mode 100644
index 0000000..dc10683
--- /dev/null
+++ b/src/components/postsPanel/postsPanel.module.css
@@ -0,0 +1,38 @@
+.panel{
+ background-color: var(--card-bg);
+ height:100%;
+ width:100%;
+}
+
+.header{
+ border-bottom:1px solid var(--border-color);
+ box-shadow: 1px;
+ height:10%;
+ width:100%;
+ display:flex;
+ justify-content: space-between;
+}
+
+.header .title{
+ font:bold ;
+ color:var(--text-color);
+
+}
+
+.header .more{
+ font:bold ;
+ color:var(--text-color);
+}
+
+.content .item{
+ display:flex;
+ justify-content:space-between;
+
+}
+.content .item .text{
+ width:70%;
+ overflow-x:hidden;
+ color:var(--text-color);
+}
+
+
diff --git a/src/components/postsPanel/postsPanel.tsx b/src/components/postsPanel/postsPanel.tsx
new file mode 100644
index 0000000..e4f87c0
--- /dev/null
+++ b/src/components/postsPanel/postsPanel.tsx
@@ -0,0 +1,40 @@
+import { useApi } from '@/hooks/request';
+import React, { useCallback } from 'react';
+import request from '@/utils/request'
+import style from './postsPanel.module.css'
+
+
+interface panelProps{
+ name:string,
+ url:string,
+ limit:number
+}
+
+const PostsPanel:React.FC<panelProps> = (props) => {
+ const fenchData = useCallback(() => request.get(props.url), [props.url])
+ const {data} = useApi(fenchData, true);
+
+
+
+ return (
+ <div className={style.panel}>
+ <div className={style.header}>
+ <span className={style.title}>{props.name}</span>
+ <span className={style.more}>更多</span>
+ </div>
+ <div className={style.content}>
+ {data && data.length > 0 ?
+ data?.map((item: { title: string; date: string }, index: number) => (
+ <div key={index} className={style.item}>
+ <span className={style.text}>{item.title}</span>
+ <span>{item.date}</span>
+ </div>
+ )) :(
+ <div>未查询到相关记录</div>
+ )}
+ </div>
+ </div>
+ )
+}
+
+export default PostsPanel;
\ No newline at end of file
diff --git a/src/components/selfStatus/selfStatus.tsx b/src/components/selfStatus/selfStatus.tsx
index be2a3bc..a5156f4 100644
--- a/src/components/selfStatus/selfStatus.tsx
+++ b/src/components/selfStatus/selfStatus.tsx
@@ -13,7 +13,6 @@
const downloadTraffic = useAppSelector(state => state.user.downloadTraffic);
const downloadPoints = useAppSelector(state => state.user.downloadPoints);
const avatar = useAppSelector(state => state.user.avatar);
- console.log(avatar)
return (
<div className={style.container}>
@@ -23,14 +22,14 @@
<div className={style.right}>
<div className={style.info}>
<p className={style.userName}>{userName}</p>
- <p className={style.role}>角色: {role}</p>
- <p className={style.uploadTraffic}>上传量: {uploadTraffic}</p>
- <p className={style.downloadTraffic}>下载量: {downloadTraffic}</p>
+ <p className={style.role}>用户组: {role && role.trim().length? role:'N/A'}</p>
+ <p className={style.uploadTraffic}>上传量: {uploadTraffic ? uploadTraffic : 0}</p>
+ <p className={style.downloadTraffic}>下载量: {downloadTraffic ? downloadTraffic : 0}</p>
<p className={style.shareRatio}>
分享率: {uploadTraffic && downloadTraffic ? (uploadTraffic / downloadTraffic).toFixed(2) : "N/A"}
</p>
- <p className={style.downloadPoints}>下载积分: {downloadPoints}</p>
+ <p className={style.downloadPoints}>下载积分: {downloadPoints ? downloadPoints : 0}</p>
</div>
<button className={style.signInButton}>签到</button>
</div>
diff --git a/src/components/selfStatus/style.module.css b/src/components/selfStatus/style.module.css
index bcf883c..8ed86a7 100644
--- a/src/components/selfStatus/style.module.css
+++ b/src/components/selfStatus/style.module.css
@@ -6,7 +6,7 @@
padding: 20px;
border: 1px solid #ccc;
border-radius: 10px;
- background-color: #f9f9f9;
+ background-color: var(--card-bg);
width: 100%;
height: 100%; /* Adjust height as needed */
box-sizing: border-box;
@@ -48,6 +48,7 @@
.userName {
font-size: 18px;
+ color:var(--text-color);
font-weight: bold;
margin-bottom: 5px;
}
@@ -56,6 +57,8 @@
.downloadTraffic,
.downloadPoints,
.shareRatio {
+ color:var(--text-color);
+ margin-top:5px;
font-size: 14px;
margin-bottom: 5px;
}
diff --git a/src/global.css b/src/global.css
index 8d1922e..b49490a 100644
--- a/src/global.css
+++ b/src/global.css
@@ -4,7 +4,7 @@
height: 100vh;
width: 100vw;
background-color: #e6f7ff; /* Light blue background */
- display: flex;
+ /* display: flex; */
justify-content: center;
align-items: center;
font-family: Arial, sans-serif; /* Optional: Set a global font */
@@ -16,9 +16,24 @@
height: 100vh;
width: 100vw;
background-color: #e6f7ff; /* Light blue background */
- display: flex;
+ /* display: flex; */
justify-content: center;
align-items: center;
font-family: Arial, sans-serif; /* Optional: Set a global font */
box-sizing: border-box;
-}
\ No newline at end of file
+}
+
+body.light {
+ --bg-color: #f9f9f9;
+ --text-color: #000000;
+ --card-bg: #ffffff;
+ --border-color: #e0e0e0;
+ }
+
+body.dark {
+ --bg-color: #2b2b2b;
+ --text-color: #f1f1f1;
+ --card-bg: #1e1e1e;
+ --border-color: #444444;
+ }
+
\ No newline at end of file
diff --git a/src/index.tsx b/src/index.tsx
index 4bd29bd..69441df 100644
--- a/src/index.tsx
+++ b/src/index.tsx
@@ -6,6 +6,13 @@
import { RouterProvider } from "react-router";
import './global.css';
+if(localStorage.getItem("theme") === null) {
+ localStorage.setItem("theme", "light");
+ document.body.className="light";
+}else{
+ document.body.className=localStorage.getItem("theme")!;
+}
+
const root = createRoot(document.getElementById('root')!)
root.render(
<Provider store={store}>
diff --git a/src/mock/auth.d.ts b/src/mock/auth.d.ts
index 70d0d39..0f7494a 100644
--- a/src/mock/auth.d.ts
+++ b/src/mock/auth.d.ts
@@ -1,3 +1,3 @@
import type MockAdapter from 'axios-mock-adapter';
-export declare function setupAuthMock(mock: MockAdapter): void;
\ No newline at end of file
+export declare function setupAuthMock(mock: MockAdapter): void;
diff --git a/src/mock/index.ts b/src/mock/index.ts
index b07a1be..fabb34e 100644
--- a/src/mock/index.ts
+++ b/src/mock/index.ts
@@ -1,6 +1,8 @@
import MockAdapter from 'axios-mock-adapter';
import instance from '@/utils/axios'
import {setupAuthMock} from './auth'
+import { setupUserMock } from './user';
+import { setupPostMock } from './post';
// 创建 Mock 实例
export const mock = new MockAdapter(instance, {
@@ -14,6 +16,8 @@
// 加载各模块 Mock
setupAuthMock(mock)
+ setupUserMock(mock)
+ setupPostMock(mock)
console.log('Mock 模块已加载')
}
diff --git a/src/mock/post.d.ts b/src/mock/post.d.ts
new file mode 100644
index 0000000..9d00ff3
--- /dev/null
+++ b/src/mock/post.d.ts
@@ -0,0 +1,3 @@
+
+import type MockAdapter from 'axios-mock-adapter';
+export declare function setupPostMock(mock: MockAdapter): void;
\ No newline at end of file
diff --git a/src/mock/post.js b/src/mock/post.js
new file mode 100644
index 0000000..abdfc6a
--- /dev/null
+++ b/src/mock/post.js
@@ -0,0 +1,17 @@
+import Mock from 'mockjs';
+import MockAdapter from 'axios-mock-adapter';
+import {hotPosts} from '@/api/post'
+
+/**
+ * 设置用户相关的 Mock 接口
+ * @param {MockAdapter} mock
+ */
+export function setupPostMock(mock){
+ mock.onGet(hotPosts).reply(config => {
+ let data = Mock.mock([{
+ 'title':'test title',
+ 'date':'2025-4-20'
+ }]);
+ return [200, data];
+ });
+}
diff --git a/src/mock/user.d.ts b/src/mock/user.d.ts
index 0f12476..9d72eb0 100644
--- a/src/mock/user.d.ts
+++ b/src/mock/user.d.ts
@@ -1,3 +1,2 @@
import type MockAdapter from 'axios-mock-adapter';
-
export declare function setupUserMock(mock: MockAdapter): void;
\ No newline at end of file
diff --git a/src/mock/user.js b/src/mock/user.js
index e3ae653..c0e1321 100644
--- a/src/mock/user.js
+++ b/src/mock/user.js
@@ -15,7 +15,7 @@
'uploadTraffic' : 0,
'downloadTraffic': 0,
'downloadPoints' : 0,
- 'avatar' : 0,
+ 'avatar' : 'https://www.w3school.com.cn/i/photo/tulip.jpg',
});
return [200, data];
});
diff --git a/src/route/index.tsx b/src/route/index.tsx
index d857a36..c56ada3 100644
--- a/src/route/index.tsx
+++ b/src/route/index.tsx
@@ -2,6 +2,7 @@
import PrivateRoute from './privateRoute'
import { useSelector } from 'react-redux'
import Login from '../views/login/login'
+import Frame from '../views/frame/frame'
import React from 'react'
import Forum from '../views/forum'
import { RootState } from '@/store'
@@ -15,8 +16,15 @@
redirectPath="/login"/>,
children: [
{
- index: true,
- element: <Forum /> // 论坛主页面
+ path:'/',
+ element: <Frame/>,
+ children: [
+ {
+ index: true,
+ element:<Forum/>
+
+ },
+ ]
},
]
},
diff --git a/src/store/index.ts b/src/store/index.ts
index 5501ef3..57b060a 100644
--- a/src/store/index.ts
+++ b/src/store/index.ts
@@ -1,8 +1,10 @@
import {configureStore} from '@reduxjs/toolkit';
-import userReducer from './userReducer';
+import userReducer from './userReducer';
+import settingReducer from './settingReducer';
const store = configureStore({
reducer: {
user: userReducer,
+ setting:settingReducer,
}
});
diff --git a/src/store/settingReducer.ts b/src/store/settingReducer.ts
new file mode 100644
index 0000000..ef519b2
--- /dev/null
+++ b/src/store/settingReducer.ts
@@ -0,0 +1,50 @@
+import { createSlice } from "@reduxjs/toolkit";
+
+interface SettingState {
+ theme: string;
+ fontSize: string;
+ showSearch: boolean;
+}
+
+const initialState: SettingState = {
+ theme: localStorage.getItem("theme") || "light",
+ fontSize: localStorage.getItem("font") || "medium",
+ showSearch: false,
+};
+
+const fontSizeMap: Record<string, string> = {
+ small: "12px",
+ medium: "16px",
+ large: "20px",
+};
+document.body.className = initialState.theme; // 设置初始主题
+document.documentElement.style.fontSize = fontSizeMap[initialState.fontSize];
+export const settingSlice = createSlice({
+ name:"setting",
+ initialState,
+ reducers:{
+ toggleSearch: (state) => {
+ state.showSearch = !state.showSearch;
+ },
+ toggleFontSize: (state) => {
+ state.fontSize =
+ state.fontSize === "small"
+ ? "medium"
+ : state.fontSize === "medium"
+ ? "large"
+ : "small";
+ document.documentElement.style.fontSize = fontSizeMap[state.fontSize]; // 切换字体大小
+ localStorage.setItem("font", state.fontSize); // 保存到 localStorage
+ },
+ toggleTheme: (state) => {
+ const nextTheme = state.theme === "light" ? "dark" : "light";
+ state.theme = nextTheme;
+ document.body.className = nextTheme; // 切换 body 类名用于全局主题
+ localStorage.setItem("theme", nextTheme); // 保存到 localStorage
+ console.log("theme:", nextTheme)
+ },
+ },
+})
+
+export default settingSlice.reducer;
+export const { toggleSearch, toggleFontSize, toggleTheme } = settingSlice.actions;
\ No newline at end of file
diff --git a/src/store/userReducer.ts b/src/store/userReducer.ts
index 61cacb8..bbed95e 100644
--- a/src/store/userReducer.ts
+++ b/src/store/userReducer.ts
@@ -32,7 +32,6 @@
state.isLogin = true;
},
getUserInfo: (state, action) => {
-
state.userId = action.payload.userId;
state.userName = action.payload.userName;
state.role = action.payload.role;
@@ -40,6 +39,7 @@
state.downloadTraffic = action.payload.downloadTraffic;
state.downloadPoints = action.payload.downloadPoints;
state.avatar = action.payload.avatar;
+ console.log(state);
},
logout: (state) => {
state.userId = '';
diff --git a/src/views/forum/index.module.css b/src/views/forum/index.module.css
index 53cf91c..5cd586a 100644
--- a/src/views/forum/index.module.css
+++ b/src/views/forum/index.module.css
@@ -1,12 +1,80 @@
-.selfStatus{
- width:25%;
- height:45%;
- position: absolute;
- right:0%;
- top:0%;
-
+/* index.module.css */
+.container {
+ width: 100%;
+ height: 100%;
+ position: relative; /* 新增 */
}
-.container{
+.up{
+ width: 100%;
+ height: 50%;
+ position: relative; /* 新增 */
+ top: 0%;
+ display: flex;
+ flex-direction: row;
+ border-radius: 8px;
+}
+
+.down{
+ height:48%;
+ width:100%;
+ bottom:0%;
+ display:flex;
+ flex-direction:row;
+}
+
+
+.upright{
+ width:25%;
+}
+.upleft{
+ height:100%;
+ width:75%;
+ margin:5px;
+ display:flex;
+ flex-direction:column;
+}
+
+.upcontent{
+ display:flex;
+ flex-direction: row;
width:100%;
height:100%;
+}
+
+.advertisements {
+ width:40%;
+ margin: 5px;
+ border-radius: 8px;
+ overflow: hidden;
+}
+
+.adImage {
+ width: 100%;
+ height: 99%;
+ object-fit: fill;
+ border-radius: 8px;
+}
+
+
+.hotPosts{
+ width:60%;
+ margin:5px;
+}
+
+
+.selfStatus {
+ width: 100%;
+ height: 100%;
+ position: relative; /* 改为绝对定位 */
+
+ border-radius: 8px;
+}
+
+.newPost,
+.likePost,
+.forsalePost {
+
+ flex:1;
+ margin:5px;
+ border-radius: 8px;
}
\ No newline at end of file
diff --git a/src/views/forum/index.tsx b/src/views/forum/index.tsx
index dbceffd..345f880 100644
--- a/src/views/forum/index.tsx
+++ b/src/views/forum/index.tsx
@@ -3,14 +3,65 @@
import SelfStatus from "@/components/selfStatus/selfStatus";
import style from "./index.module.css";
+import Navbar from "@/components/navbar/navbar";
+import PostsPanel from "@/components/postsPanel/postsPanel";
+import { hotPosts } from "@/api/post";
+import { Carousel } from 'antd';
+import ad1 from '&/assets/ad1.png'
+import ad2 from '&/assets/ad2.png'
+import { useEffect } from "react";
export default function Forum() {
+ useEffect(() => {
+ // 禁止滚动
+ document.body.style.overflow = 'hidden';
-
+ // 组件卸载时恢复滚动
+ return () => {
+ document.body.style.overflow = 'auto';
+ };
+ }, []);
+
+
return (
<div className={style.container}>
- <div className={style.selfStatus}>
- <SelfStatus />
+ <div className={style.up}>
+ <div className={style.upleft}>
+ <div className={style.navbar}>
+ <Navbar/>
+ </div>
+ <div className={style.upcontent}>
+ <div className={style.advertisements}>
+ <Carousel arrows infinite={false}>
+ <div>
+ <img src={ad1} alt="广告1" className={style.adImage} />
+ </div>
+ <div>
+ <img src={ad2} alt="广告2" className={style.adImage} />
+ </div>
+ </Carousel>
+ </div>
+ <div className={style.hotPosts}>
+ <PostsPanel name='热门种子' url={hotPosts} limit={5}/>
+ </div>
+ </div>
+ </div>
+ <div className={style.upright}>
+ <div className={style.selfStatus}>
+ <SelfStatus />
+ </div>
+ </div>
+ </div>
+ <div className={style.down}>
+ <div className={style.newPost}>
+ <PostsPanel name='最新发布' url={hotPosts} limit={5}/>
+ </div>
+ <div className={style.likePost}>
+ <PostsPanel name='猜你喜欢' url={hotPosts} limit={5}/>
+ </div>
+ <div className={style.forsalePost}>
+ <PostsPanel name='促销种子' url={hotPosts} limit={5}/>
+ </div>
</div>
</div>
);
diff --git a/src/views/frame/frame.module.css b/src/views/frame/frame.module.css
new file mode 100644
index 0000000..91967f8
--- /dev/null
+++ b/src/views/frame/frame.module.css
@@ -0,0 +1,63 @@
+
+
+.header{
+ display:flex;
+ justify-content: space-between;
+ align-items: center;
+ height:8%;
+ background-color: var(--bg-color);
+ width: 100%;
+ overflow:hidden;
+}
+
+.header .logo{
+ width: 100px; /* Set a fixed width for the logo */
+ height: auto; /* Maintain aspect ratio */
+ left:0;
+ margin:10px;
+}
+
+.header .searchInput{
+ border:1px solid rgb(197, 193, 194);
+ width: 60%;
+ height:50%;
+ padding: 5px 10px;
+ border-radius: 5px;
+ font-size: 1rem;
+ color: var(--text-color);
+ background-color: var(--bg-color);
+ transition: all 0.1s ease-in-out;
+}
+
+.header .toollist{
+ align-content: center;
+ right:0;
+ gap:10px;
+ margin-right:10px;
+}
+
+.header .toollist > *{
+ padding: 5px 10px;
+ width:auto;
+ font-size:150%;
+ user-select: none;
+ color: var(--text-color);
+}
+
+.header .toollist > *:hover{
+ background-color: rgb(197, 193, 194);
+ border-radius: 5px;
+ cursor: pointer;
+ font-size:160%;
+ transition: all 0.1s ease-in-out;
+ color: var(--text-color);
+}
+
+.container{
+ height:92vh;
+ background-color:var(--bg-color);
+ display:flex;
+ flex-direction:column;
+ overflow: auto;
+ width:100%;
+}
\ No newline at end of file
diff --git a/src/views/frame/frame.tsx b/src/views/frame/frame.tsx
new file mode 100644
index 0000000..c0c2e0e
--- /dev/null
+++ b/src/views/frame/frame.tsx
@@ -0,0 +1,55 @@
+import React from "react";
+import { Outlet } from "react-router";
+import { useEffect, useState } from "react";
+import {
+ SearchOutlined,
+ FontSizeOutlined,
+ MessageOutlined,
+ SunOutlined,
+ MoonOutlined
+ } from "@ant-design/icons";
+import style from "./frame.module.css";
+import logo from "&/assets/logo.png";
+import { useAppDispatch } from "@/hooks/store";
+import { useSelector } from "react-redux";
+const Frame:React.FC = () => {
+
+ const dispatch = useAppDispatch();
+
+ const showSearch = useSelector((state: any) => state.setting.showSearch);
+ const theme= useSelector((state: any) => state.setting.theme);
+ const toggleSearch = () => {
+ dispatch({ type: "setting/toggleSearch" });
+ }
+
+ const toggleFontSize = () => {
+ dispatch({ type: "setting/toggleFontSize" });
+ };
+
+ const toggleTheme = () => {
+ dispatch({ type: "setting/toggleTheme" });
+ };
+
+
+ return (
+ <div style={{ display: 'block', height: '100vh' }}>
+ <header className={style.header}>
+ <img className={style.logo} src={logo} alt="website logo"></img>
+ {showSearch && (<input className={style.searchInput} placeholder="输入关键词进行搜索"/>)}
+ <div className={style.toollist}>
+ <SearchOutlined onClick={toggleSearch}/>
+ <FontSizeOutlined onClick={toggleFontSize}/>
+ <MessageOutlined />
+ {theme === 'dark' ? <MoonOutlined onClick={toggleTheme}/> : <SunOutlined onClick={toggleTheme}/>}
+ </div>
+ </header>
+ <div className={style.container}>
+ <Outlet/>
+ </div>
+
+
+ </div>
+ );
+}
+
+export default Frame;
\ No newline at end of file
diff --git a/src/views/login/login.tsx b/src/views/login/login.tsx
index 26b7842..bd429c7 100644
--- a/src/views/login/login.tsx
+++ b/src/views/login/login.tsx
@@ -8,21 +8,21 @@
import { useState } from 'react';
import { useSelector } from 'react-redux';
import { useNavigate } from 'react-router';
-import logo from '&/asserts/logo.png';
+import logo from '&/assets/logo.png';
import { getUserInfo } from '@/api/user';
-
+import debounce from 'lodash/debounce';
const Login: React.FC = () => {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const dispatch = useAppDispatch();
const { refresh: postUserLoginRefresh } = useApi(() => request.post(postUserLogin, {}), false);
- const { refresh: getUserInfoRefresh } = useApi(async () => await request.get(getUserInfo), false);
+ const { refresh: getUserInfoRefresh } = useApi(() => request.get(getUserInfo), false);
const nav = useNavigate();
- const handleLogin = async () => {
+ const handleLogin = debounce(async () => {
try {
- const res = await postUserLoginRefresh();
+ const res =await postUserLoginRefresh();
if (res==null ||(res as any).error) {
alert('Login failed. Please check your credentials.');
return;
@@ -44,14 +44,17 @@
console.error('Unknown error occurred');
}
}
- };
+ }, 1000) as () => void;
+ const handleLogoClick = () => {
+ nav('/');
+ }
return (
<div className={style.form}>
- <img className={style.logo} src={logo} alt="logo"></img>
+ <img className={style.logo} src={logo} alt="logo" onClick={handleLogoClick}></img>
<input type="email" value={email} onChange={(e) => setEmail(e.target.value)} className={style.email} placeholder="Enter your email" />
<input type="password" value={password} onChange={(e) => setPassword(e.target.value)} className={style.password} placeholder="Enter your password" />
- <button className={style.submit} onClick={handleLogin}>登录</button>
+ <button className={style.submit} onClick={() => handleLogin()}>登录</button>
<button className={style.register}>注册</button>
<button className={style.forget}> 忘记密码</button>
</div>
diff --git a/test/login.test.tsx b/test/login.test.tsx
index 936d9fb..b342387 100644
--- a/test/login.test.tsx
+++ b/test/login.test.tsx
@@ -33,8 +33,9 @@
describe('Login Component', () => {
const mockDispatch = jest.fn();
const mockNavigate = jest.fn();
- const mockRefresh = jest.fn();
-
+ const mockPostRefresh = jest.fn();
+ const mockGetRefresh = jest.fn();
+
beforeEach(() => {
// 初始化模拟函数返回值
mockUseAppDispatch.mockReturnValue(mockDispatch);
@@ -42,12 +43,28 @@
mockUseSelector.mockImplementation((selector) => selector({ user: { userName: '' } }));
// 默认模拟 useApi 返回正常状态
- mockUseApi.mockReturnValue({
+ mockUseApi
+ .mockReturnValueOnce({
data: { token: 'mock-token' },
loading: false,
error: null,
- refresh: mockRefresh,
- });
+ refresh: mockPostRefresh,
+ })
+ .mockReturnValueOnce({
+ data:{
+ 'userId' : '001',
+ 'userName' : 'san3yuan',
+ 'role' : 'manager',
+ 'uploadTraffic' : 0,
+ 'downloadTraffic': 0,
+ 'downloadPoints' : 0,
+ 'avatar' : 'https://www.w3school.com.cn/i/photo/tulip.jpg',
+ },
+ loading: false,
+ error: null,
+ refresh: mockGetRefresh,
+ })
+ ;
});
afterEach(() => {
@@ -67,26 +84,47 @@
// 测试 2: 登录成功流程
it('点击登录按钮触发API请求、Redux更新和导航', async () => {
// 模拟 API 返回有效数据
- mockRefresh.mockResolvedValue({ token: 'mock-token' }); // 关键:必须返回 Promise
- mockUseApi.mockReturnValue({
- data: { token: 'mock-token' },
- loading: false,
- error: null,
- refresh: mockRefresh,
- });
+ mockPostRefresh.mockResolvedValue({ token: 'mock-token' });
+ mockGetRefresh.mockResolvedValue({
+ 'userId' : '001',
+ 'userName' : 'san3yuan',
+ 'role' : 'manager',
+ 'uploadTraffic' : 0,
+ 'downloadTraffic': 0,
+ 'downloadPoints' : 0,
+ 'avatar' : 'https://www.w3school.com.cn/i/photo/tulip.jpg',
+ });
render(<Login />);
-
+ jest.useFakeTimers();
fireEvent.click(screen.getByText('登录'));
-
+ jest.runAllTimers();
await waitFor(() => {
// 验证 dispatch 调用
- expect(mockDispatch).toHaveBeenCalledWith({
+ expect(mockPostRefresh).toHaveBeenCalled();
+
+ // 验证Redux更新
+ expect(mockDispatch).toHaveBeenNthCalledWith(1, {
type: 'user/login',
- payload: { token: 'mock-token' },
+ payload: { token: 'mock-token' }
+ });
+
+ // 验证第二次API调用(用户信息)
+ expect(mockGetRefresh).toHaveBeenCalled();
+
+ // 验证用户信息更新
+ expect(mockDispatch).toHaveBeenNthCalledWith(2, {
+ type: 'user/getUserInfo',
+ payload: {
+ 'userId' : '001',
+ 'userName' : 'san3yuan',
+ 'role' : 'manager',
+ 'uploadTraffic' : 0,
+ 'downloadTraffic': 0,
+ 'downloadPoints' : 0,
+ 'avatar' : 'https://www.w3school.com.cn/i/photo/tulip.jpg',
+ }
});
});
- });
-
-
+ });
});
\ No newline at end of file
diff --git a/test/postsPanel.test.tsx b/test/postsPanel.test.tsx
new file mode 100644
index 0000000..cd753fc
--- /dev/null
+++ b/test/postsPanel.test.tsx
@@ -0,0 +1,75 @@
+import React from 'react';
+import { render, screen, waitFor } from '@testing-library/react';
+import '@testing-library/jest-dom';
+import { useApi } from '@/hooks/request';
+import PostsPanel from '@/components/postsPanel/postsPanel';
+
+// 模拟 useApi
+jest.mock('@/hooks/request', () => ({
+ useApi: jest.fn(() => ({
+ data: [], // 默认返回空数组
+ loading: false,
+ error: null,
+ })),
+}));
+describe('PostsPanel Component', () => {
+ const mockUseApi = useApi as jest.MockedFunction<typeof useApi>;
+
+ it('renders the component with a title', () => {
+ // 渲染组件
+ render(<PostsPanel name="热门帖子" url="/api/posts" limit={5} />);
+
+ // 验证标题是否正确渲染
+ expect(screen.getByText('热门帖子')).toBeInTheDocument();
+ expect(screen.getByText('更多')).toBeInTheDocument();
+ });
+
+ it('renders posts when data is available', async () => {
+ // 模拟 API 返回数据
+ mockUseApi.mockReturnValue({
+ data: [
+ { title: 'Post 1', date: '2025-04-01' },
+ { title: 'Post 2', date: '2025-04-02' },
+ ],
+ });
+
+ // 渲染组件
+ render(<PostsPanel name="热门帖子" url="/api/posts" limit={5} />);
+
+ // 验证数据是否正确渲染
+ await waitFor(() => {
+ expect(screen.getByText('Post 1')).toBeInTheDocument();
+ expect(screen.getByText('2025-04-01')).toBeInTheDocument();
+ expect(screen.getByText('Post 2')).toBeInTheDocument();
+ expect(screen.getByText('2025-04-02')).toBeInTheDocument();
+ });
+ });
+
+ it('renders a message when no data is available', async () => {
+ // 模拟 API 返回空数据
+ mockUseApi.mockReturnValue({
+ data: [],
+ });
+
+ // 渲染组件
+ render(<PostsPanel name="热门帖子" url="/api/posts" limit={5} />);
+
+ // 验证无数据时的提示信息
+ await waitFor(() => {
+ expect(screen.getByText('未查询到相关记录')).toBeInTheDocument();
+ });
+ });
+
+ it('handles loading state', () => {
+ // 模拟加载状态
+ mockUseApi.mockReturnValue({
+ data: null,
+ });
+
+ // 渲染组件
+ render(<PostsPanel name="热门帖子" url="/api/posts" limit={5} />);
+
+ // 验证组件是否正确渲染(可以根据需求添加加载状态的测试)
+ expect(screen.getByText('未查询到相关记录')).toBeInTheDocument();
+ });
+});
\ No newline at end of file
diff --git a/test/selfStatus.test.tsx b/test/selfStatus.test.tsx
new file mode 100644
index 0000000..4bd690e
--- /dev/null
+++ b/test/selfStatus.test.tsx
@@ -0,0 +1,77 @@
+import SelfStatus from '@/components/selfStatus/selfStatus';
+import { render, screen } from '@testing-library/react';
+import { useSelector } from 'react-redux';
+import React from 'react';
+import '@testing-library/jest-dom';
+import { useAppSelector } from '@/hooks/store';
+
+jest.mock('@/hooks/request', () => ({
+ useApi: jest.fn(),
+ }));
+ // 模拟所有外部依赖
+ jest.mock('@/hooks/store', () => ({
+ useAppDispatch: jest.fn(),
+ useAppSelector: jest.fn(),
+ }));
+
+ jest.mock('react-router', () => ({
+ useNavigate: jest.fn(),
+ }));
+
+ jest.mock('react-redux', () => ({
+ useSelector: jest.fn(),
+ }));
+
+
+describe('SelfStatus Component', () => {
+ it('renders correctly', () => {
+ (useAppSelector as jest.Mock).mockImplementation((selector) => selector({
+ user: {
+ userId: '001',
+ userName: 'san3yuan',
+ role: 'manager',
+ uploadTraffic: 0,
+ downloadTraffic: 0,
+ downloadPoints: 0,
+ },
+ setting: {
+ theme: 'light',
+ },
+ }));
+
+ render(<SelfStatus />);
+
+ expect(screen.getByText('san3yuan')).toBeInTheDocument();
+ expect(screen.getByText('用户组: manager')).toBeInTheDocument();
+ expect(screen.getByText('上传量: 0')).toBeInTheDocument();
+ expect(screen.getByText('下载量: 0')).toBeInTheDocument();
+ expect(screen.getByText('下载积分: 0')).toBeInTheDocument();
+ })
+ it('calculates and displays share ratio correctly', () => {
+ (useAppSelector as jest.Mock).mockImplementation((selector) => selector({
+ user: {
+ uploadTraffic: 100,
+ downloadTraffic: 50,
+ },
+ }));
+ render(<SelfStatus />);
+ expect(screen.getByText('分享率: 2.00')).toBeInTheDocument();
+ });
+ it('handles empty data gracefully', () => {
+ (useAppSelector as jest.Mock).mockImplementation((selector) => selector({
+ user: {
+ userName: '',
+ role: '',
+ uploadTraffic: null,
+ downloadTraffic: null,
+ downloadPoints: null,
+ },
+ }));
+ render(<SelfStatus />);
+ expect(screen.getByText('用户组: N/A')).toBeInTheDocument();
+ expect(screen.getByText('上传量: 0')).toBeInTheDocument();
+ expect(screen.getByText('下载量: 0')).toBeInTheDocument();
+ expect(screen.getByText('分享率: N/A')).toBeInTheDocument();
+ expect(screen.getByText('下载积分: 0')).toBeInTheDocument();
+ });
+});
\ No newline at end of file