2 Commits

Author SHA1 Message Date
zihluwang 1dfe3f7221 refactor: remove unused icon images 2026-05-11 07:02:17 +08:00
zihluwang 86e259a500 feat: add legal tabs and update header assets
Switch the legal page to tabbed EULA/privacy content with URL sync, update header styling, and migrate footer icons to Ant Design.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-11 07:00:05 +08:00
16 changed files with 324 additions and 118 deletions
+3
View File
@@ -12,6 +12,7 @@
},
"dependencies": {
"@ant-design/cssinjs": "^2.1.2",
"@ant-design/icons": "^6.2.2",
"@reduxjs/toolkit": "^2.11.2",
"@tailwindcss/vite": "^4.2.4",
"@tanstack/react-virtual": "^3.13.24",
@@ -27,6 +28,7 @@
"tailwindcss": "^4.2.4"
},
"devDependencies": {
"@tailwindcss/typography": "^0.5.19",
"@types/node": "^22.19.17",
"@types/react": "^19.2.14",
"@types/react-dom": "^19.2.3",
@@ -35,6 +37,7 @@
"prettier": "^3.8.3",
"typescript": "~6.0.3",
"vite": "^8.0.11",
"vite-plugin-markdown": "^2.2.0",
"vite-plugin-port-checker": "^1.0.1"
},
"pnpm": {
+173
View File
@@ -11,6 +11,9 @@ importers:
'@ant-design/cssinjs':
specifier: ^2.1.2
version: 2.1.2(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
'@ant-design/icons':
specifier: ^6.2.2
version: 6.2.2(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
'@reduxjs/toolkit':
specifier: ^2.11.2
version: 2.11.2(react-redux@9.2.0(@types/react@19.2.14)(react@19.2.6)(redux@5.0.1))(react@19.2.6)
@@ -51,6 +54,9 @@ importers:
specifier: ^4.2.4
version: 4.2.4
devDependencies:
'@tailwindcss/typography':
specifier: ^0.5.19
version: 0.5.19(tailwindcss@4.2.4)
'@types/node':
specifier: ^22.19.17
version: 22.19.17
@@ -75,6 +81,9 @@ importers:
vite:
specifier: ^8.0.11
version: 8.0.11(@types/node@22.19.17)(jiti@2.6.1)
vite-plugin-markdown:
specifier: ^2.2.0
version: 2.2.0(vite@8.0.11(@types/node@22.19.17)(jiti@2.6.1))
vite-plugin-port-checker:
specifier: ^1.0.1
version: 1.0.1(vite@8.0.11(@types/node@22.19.17)(jiti@2.6.1))
@@ -656,6 +665,11 @@ packages:
resolution: {integrity: sha512-9El/iI069DKDSXwTvB9J4BwdO5JhRrOweGaK25taBAvBXyXqJAX+Jqdvs8r8gKpsI/1m0LeJLyQYTf/WLrBT1Q==}
engines: {node: '>= 20'}
'@tailwindcss/typography@0.5.19':
resolution: {integrity: sha512-w31dd8HOx3k9vPtcQh5QHP9GwKcgbMp87j58qi6xgiBnFFtKEAgCWnDw4qUT8aHwkCp8bKvb/KGKWWHedP0AAg==}
peerDependencies:
tailwindcss: '>=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1'
'@tailwindcss/vite@4.2.4':
resolution: {integrity: sha512-pCvohwOCspk3ZFn6eJzrrX3g4n2JY73H6MmYC87XfGPyTty4YsCjYTMArRZm/zOI8dIt3+EcrLHAFPe5A4bgtw==}
peerDependencies:
@@ -706,6 +720,12 @@ packages:
react: '>=18.0.0'
react-dom: '>=18.0.0'
argparse@1.0.10:
resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==}
argparse@2.0.1:
resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
asynckit@0.4.0:
resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}
@@ -731,6 +751,11 @@ packages:
resolution: {integrity: sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==}
engines: {node: '>=18'}
cssesc@3.0.0:
resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==}
engines: {node: '>=4'}
hasBin: true
csstype@3.2.3:
resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==}
@@ -745,6 +770,19 @@ packages:
resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==}
engines: {node: '>=8'}
dom-serializer@1.4.1:
resolution: {integrity: sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==}
domelementtype@2.3.0:
resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==}
domhandler@4.3.1:
resolution: {integrity: sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==}
engines: {node: '>= 4'}
domutils@2.8.0:
resolution: {integrity: sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==}
dunder-proto@1.0.1:
resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==}
engines: {node: '>= 0.4'}
@@ -753,6 +791,12 @@ packages:
resolution: {integrity: sha512-Qohcme7V1inbAfvjItgw0EaxVX5q2rdVEZHRBrEQdRZTssLDGsL8Lwrznl8oQ/6kuTJONLaDcGjkNP247XEhcA==}
engines: {node: '>=10.13.0'}
entities@2.1.0:
resolution: {integrity: sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==}
entities@2.2.0:
resolution: {integrity: sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==}
es-define-property@1.0.1:
resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==}
engines: {node: '>= 0.4'}
@@ -769,6 +813,11 @@ packages:
resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==}
engines: {node: '>= 0.4'}
esprima@4.0.1:
resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==}
engines: {node: '>=4'}
hasBin: true
fdir@6.5.0:
resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==}
engines: {node: '>=12.0.0'}
@@ -791,6 +840,9 @@ packages:
resolution: {integrity: sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==}
engines: {node: '>= 6'}
front-matter@4.0.2:
resolution: {integrity: sha512-I8ZuJ/qG92NWX8i5x1Y8qyj3vizhXS31OxjKDu3LKP+7/qBgfIKValiZIEwoVoJKUHlhWtYrktkxV1XsX+pPlg==}
fsevents@2.3.3:
resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
@@ -830,6 +882,9 @@ packages:
resolution: {integrity: sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==}
engines: {node: '>= 0.4'}
htmlparser2@6.1.0:
resolution: {integrity: sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==}
immer@11.1.4:
resolution: {integrity: sha512-XREFCPo6ksxVzP4E0ekD5aMdf8WMwmdNaz6vuvxgI40UaEiu6q3p8X52aU6GdyvLY3XXX/8R7JOTXStz/nBbRw==}
@@ -840,6 +895,10 @@ packages:
resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==}
hasBin: true
js-yaml@3.14.2:
resolution: {integrity: sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==}
hasBin: true
json2mq@0.2.0:
resolution: {integrity: sha512-SzoRg7ux5DWTII9J2qkrZrqV1gt+rTaoufMxEzXbS26Uid0NwaJd123HcoB80TgubEppxxIGdNxCx50fEoEWQA==}
@@ -917,13 +976,23 @@ packages:
resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==}
engines: {node: '>= 12.0.0'}
linkify-it@3.0.3:
resolution: {integrity: sha512-ynTsyrFSdE5oZ/O9GEf00kPngmOfVwazR5GKDq6EYfhlpFug3J2zybX56a2PRRpc9P+FuSoGNAwjlbDs9jJBPQ==}
magic-string@0.30.21:
resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==}
markdown-it@12.3.2:
resolution: {integrity: sha512-TchMembfxfNVpHkbtriWltGWc+m3xszaRD0CZup7GFFhzIgQqxIfn3eGj1yZpfuflzPvfkt611B2Q/Bsk1YnGg==}
hasBin: true
math-intrinsics@1.1.0:
resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==}
engines: {node: '>= 0.4'}
mdurl@1.0.1:
resolution: {integrity: sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==}
mime-db@1.52.0:
resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==}
engines: {node: '>= 0.6'}
@@ -944,6 +1013,10 @@ packages:
resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==}
engines: {node: '>=12'}
postcss-selector-parser@6.0.10:
resolution: {integrity: sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==}
engines: {node: '>=4'}
postcss@8.5.14:
resolution: {integrity: sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg==}
engines: {node: ^10 || ^12 || >=14}
@@ -1036,6 +1109,9 @@ packages:
resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
engines: {node: '>=0.10.0'}
sprintf-js@1.0.3:
resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==}
string-convert@0.2.1:
resolution: {integrity: sha512-u/1tdPl4yQnPBjnVrmdLo9gtuLvELKsAoRapekWggdiQNvvvum+jYF329d84NAa660KQw7pB2n36KrIKVoXa3A==}
@@ -1065,6 +1141,9 @@ packages:
engines: {node: '>=14.17'}
hasBin: true
uc.micro@1.0.6:
resolution: {integrity: sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==}
undici-types@6.21.0:
resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==}
@@ -1073,6 +1152,14 @@ packages:
peerDependencies:
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
util-deprecate@1.0.2:
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
vite-plugin-markdown@2.2.0:
resolution: {integrity: sha512-eH2tXMZcx3EHb5okd+/0VIyoR8Gp9pGe24UXitOOcGkzObbJ1vl48aGOAbakoT88FBdzC8MXNkMfBIB9VK0Ndg==}
peerDependencies:
vite: '>= 2.0.0'
vite-plugin-port-checker@1.0.1:
resolution: {integrity: sha512-Mx/Pj5zyu4oak+SKGwQyRLGAvnp9zx0nqjCLhaJoGJeZk2t4EarZojTcHGG0gPMJWj9X6c0MIfzaZDslg80/GA==}
peerDependencies:
@@ -1702,6 +1789,11 @@ snapshots:
'@tailwindcss/oxide-win32-arm64-msvc': 4.2.4
'@tailwindcss/oxide-win32-x64-msvc': 4.2.4
'@tailwindcss/typography@0.5.19(tailwindcss@4.2.4)':
dependencies:
postcss-selector-parser: 6.0.10
tailwindcss: 4.2.4
'@tailwindcss/vite@4.2.4(vite@8.0.11(@types/node@22.19.17)(jiti@2.6.1))':
dependencies:
'@tailwindcss/node': 4.2.4
@@ -1798,6 +1890,12 @@ snapshots:
- luxon
- moment
argparse@1.0.10:
dependencies:
sprintf-js: 1.0.3
argparse@2.0.1: {}
asynckit@0.4.0: {}
axios@1.16.0:
@@ -1823,6 +1921,8 @@ snapshots:
cookie@1.1.1: {}
cssesc@3.0.0: {}
csstype@3.2.3: {}
dayjs@1.11.20: {}
@@ -1831,6 +1931,24 @@ snapshots:
detect-libc@2.1.2: {}
dom-serializer@1.4.1:
dependencies:
domelementtype: 2.3.0
domhandler: 4.3.1
entities: 2.2.0
domelementtype@2.3.0: {}
domhandler@4.3.1:
dependencies:
domelementtype: 2.3.0
domutils@2.8.0:
dependencies:
dom-serializer: 1.4.1
domelementtype: 2.3.0
domhandler: 4.3.1
dunder-proto@1.0.1:
dependencies:
call-bind-apply-helpers: 1.0.2
@@ -1842,6 +1960,10 @@ snapshots:
graceful-fs: 4.2.11
tapable: 2.3.3
entities@2.1.0: {}
entities@2.2.0: {}
es-define-property@1.0.1: {}
es-errors@1.3.0: {}
@@ -1857,6 +1979,8 @@ snapshots:
has-tostringtag: 1.0.2
hasown: 2.0.3
esprima@4.0.1: {}
fdir@6.5.0(picomatch@4.0.4):
optionalDependencies:
picomatch: 4.0.4
@@ -1871,6 +1995,10 @@ snapshots:
hasown: 2.0.3
mime-types: 2.1.35
front-matter@4.0.2:
dependencies:
js-yaml: 3.14.2
fsevents@2.3.3:
optional: true
@@ -1910,12 +2038,24 @@ snapshots:
dependencies:
function-bind: 1.1.2
htmlparser2@6.1.0:
dependencies:
domelementtype: 2.3.0
domhandler: 4.3.1
domutils: 2.8.0
entities: 2.2.0
immer@11.1.4: {}
is-mobile@5.0.0: {}
jiti@2.6.1: {}
js-yaml@3.14.2:
dependencies:
argparse: 1.0.10
esprima: 4.0.1
json2mq@0.2.0:
dependencies:
string-convert: 0.2.1
@@ -1969,12 +2109,26 @@ snapshots:
lightningcss-win32-arm64-msvc: 1.32.0
lightningcss-win32-x64-msvc: 1.32.0
linkify-it@3.0.3:
dependencies:
uc.micro: 1.0.6
magic-string@0.30.21:
dependencies:
'@jridgewell/sourcemap-codec': 1.5.5
markdown-it@12.3.2:
dependencies:
argparse: 2.0.1
entities: 2.1.0
linkify-it: 3.0.3
mdurl: 1.0.1
uc.micro: 1.0.6
math-intrinsics@1.1.0: {}
mdurl@1.0.1: {}
mime-db@1.52.0: {}
mime-types@2.1.35:
@@ -1987,6 +2141,11 @@ snapshots:
picomatch@4.0.4: {}
postcss-selector-parser@6.0.10:
dependencies:
cssesc: 3.0.0
util-deprecate: 1.0.2
postcss@8.5.14:
dependencies:
nanoid: 3.3.12
@@ -2074,6 +2233,8 @@ snapshots:
source-map-js@1.2.1: {}
sprintf-js@1.0.3: {}
string-convert@0.2.1: {}
stylis@4.3.6: {}
@@ -2094,12 +2255,24 @@ snapshots:
typescript@6.0.3: {}
uc.micro@1.0.6: {}
undici-types@6.21.0: {}
use-sync-external-store@1.6.0(react@19.2.6):
dependencies:
react: 19.2.6
util-deprecate@1.0.2: {}
vite-plugin-markdown@2.2.0(vite@8.0.11(@types/node@22.19.17)(jiti@2.6.1)):
dependencies:
domhandler: 4.3.1
front-matter: 4.0.2
htmlparser2: 6.1.0
markdown-it: 12.3.2
vite: 8.0.11(@types/node@22.19.17)(jiti@2.6.1)
vite-plugin-port-checker@1.0.1(vite@8.0.11(@types/node@22.19.17)(jiti@2.6.1)):
dependencies:
vite: 8.0.11(@types/node@22.19.17)(jiti@2.6.1)
Binary file not shown.

Before

Width:  |  Height:  |  Size: 275 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 320 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 238 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 MiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

+5 -5
View File
@@ -7,9 +7,9 @@ interface FirearmParams extends PageQueryParams {
}
/**
* 查询武器列表
* Fetch firearm list
*
* @param params 分页查询参数¬
* @param params Paged query parameters
*/
export async function getFirearms(params?: FirearmParams): Promise<Page<Firearm>> {
let uri = "/firearms"
@@ -28,9 +28,9 @@ export async function getFirearms(params?: FirearmParams): Promise<Page<Firearm>
}
/**
* 根据 ID 查询武器
* Fetch firearm by ID
*
* @param id 武器 ID
* @param id Firearm ID
*/
export async function getFirearm(id: number): Promise<Firearm> {
const { data } = await WebClient.get<Firearm>(`/firearms/${id}`)
@@ -38,7 +38,7 @@ export async function getFirearm(id: number): Promise<Firearm> {
}
/**
* 新建武器
* Create firearm
* @param request
*/
export async function addFirearm(request: AddFirearmRequest): Promise<Firearm> {
@@ -0,0 +1,17 @@
import React from "react"
interface MarkdownRendererProps {
/** HTML string processed by vite-plugin-markdown */
html: string
/** Optional custom class name */
className?: string
}
export default function MarkdownRenderer({ html, className = "" }: MarkdownRendererProps) {
return (
<article
className={`prose prose-slate max-w-none dark:prose-invert ${className}`}
dangerouslySetInnerHTML={{ __html: html }}
/>
)
}
+2 -1
View File
@@ -1,6 +1,7 @@
@layer theme, base, antd, components, utilities;
@import 'tailwindcss';
@plugin "@tailwindcss/typography";
html, body {
margin: 0;
@@ -56,7 +57,7 @@ html, body {
.nav-item:hover::after,
.nav-item.active::after {
height: 80%; /* 向上打光的高度,可以调整 */
height: 80%; /* Height of the upward glow; adjustable. */
}
.nav-item:hover,
+65 -108
View File
@@ -2,10 +2,16 @@ import { Outlet, Link, NavLink } from "react-router-dom"
import { useMemo } from "react"
import dayjs from "dayjs"
import { Dropdown } from "antd"
import {
FileTextOutlined,
GithubOutlined,
LockOutlined,
LoginOutlined,
} from "@ant-design/icons"
import { AuthApi } from "@/api"
import { useAppDispatch, useAppSelector } from "@/store/hooks"
import { clearCurrentUser } from "@/store/auth-slice"
import { useState } from 'react';
import { useState } from "react"
/**
* Main application component that serves as the root layout.
@@ -15,7 +21,7 @@ export default function HeroLayout() {
const today = useMemo(() => dayjs(), [])
const user = useAppSelector((state) => state.auth.user)
const dispatch = useAppDispatch()
const [isDropdownOpen, setIsDropdownOpen] = useState(false);
const [isDropdownOpen, setIsDropdownOpen] = useState(false)
async function handleLogout() {
try {
@@ -28,7 +34,7 @@ export default function HeroLayout() {
return (
<div className="bg-gray-50 ">
{/* Navigation Header */}
<header className="bg-white shadow-sm border-b bg-[url('/nav_bg.png')] bg-cover bg-center">
<header className="bg-[#0b0f14] shadow-sm border-b">
<div className="max-w-screen-2xl mx-auto px-4 sm:px-6 lg:px-10 h-full">
<div className="flex justify-between items-center h-20">
<div className="flex items-center">
@@ -38,55 +44,21 @@ export default function HeroLayout() {
<NavLink
to="/firearms"
className={({ isActive }) =>
`nav-item inline-flex items-center px-10 h-full text-base font-medium transition-all duration-200 ${isActive ? 'active' : ''
`nav-item inline-flex items-center px-10 h-full text-base font-medium transition-all duration-200 ${
isActive ? "active" : ""
} text-gray-500 hover:text-white`
}
>
}>
</NavLink>
<NavLink
to="/mod-codes"
className={({ isActive }) =>
`nav-item inline-flex items-center px-10 h-full text-base font-medium transition-all duration-200 ${isActive ? 'active' : ''
`nav-item inline-flex items-center px-10 h-full text-base font-medium transition-all duration-200 ${
isActive ? "active" : ""
} text-gray-500 hover:text-white`
}
>
}>
</NavLink>
{/* {user ? (
<Dropdown
trigger={["hover"]}
menu={{
items: [
{
key: "logout",
label: "退出登录",
danger: true,
onClick: handleLogout,
},
],
}}
>
<span className="nav-item inline-flex items-center px-10 h-full text-base font-medium text-gray-500 hover:text-white cursor-pointer">
{user.username}
</span>
</Dropdown>
) : (
<Link
to="/login"
className="nav-item inline-flex items-center px-10 h-full text-base font-medium transition-all duration-200 text-gray-500 hover:text-white"
>
登录
</Link>
)}
<a
href="https://github.com/zihluwang/delta-force-firearm-modification-codes"
target="_blank"
rel="noopener noreferrer"
className="nav-item inline-flex items-center px-10 h-full text-lg font-medium transition-all duration-200 text-gray-500 hover:text-white"
>
GitHub
</a> */}
</nav>
</div>
</div>
@@ -102,71 +74,61 @@ export default function HeroLayout() {
{/* Footer */}
<footer className="bg-black border-t border-gray-800">
<div className="max-w-screen-2xl mx-auto px-4 sm:px-6 lg:px-10 py-8">
<div className="flex flex-wrap justify-center items-center gap-x-6 gap-y-10 text-sm">
<div
className="relative"
onMouseEnter={() => setIsDropdownOpen(true)}
onMouseLeave={() => setIsDropdownOpen(false)}
>
<button
className="flex items-center gap-1.5 text-gray-400 hover:text-white transition-colors duration-200 focus:outline-none"
aria-label="GitHub 仓库"
>
<img
src="/github-logo.png"
alt=""
className="w-5 h-5 opacity-80"
/>
<span>GitHub</span>
<svg
className={`w-3 h-3 transition-transform duration-200 ${isDropdownOpen ? 'rotate-180' : ''}`}
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
</svg>
</button>
{isDropdownOpen && (
<div className="absolute top-full left-2/3 -translate-x-1/2 w-18 bg-gray-950 border border-gray-700 rounded-lg shadow-xl py-1 z-20 opacity-60">
<a
href="https://github.com/zihluwang/frontend-repo"
target="_blank"
rel="noopener noreferrer"
className="block px-4 py-2 text-sm text-gray-300 hover:bg-gray-800 hover:text-white transition-colors"
>
</a>
<a
href="https://github.com/zihluwang/backend-repo"
target="_blank"
rel="noopener noreferrer"
className="block px-4 py-2 text-sm text-gray-300 hover:bg-gray-800 hover:text-white transition-colors"
>
</a>
</div>
)}
</div>
className="relative"
onMouseEnter={() => setIsDropdownOpen(true)}
onMouseLeave={() => setIsDropdownOpen(false)}>
<button
className="flex items-center gap-1.5 text-gray-400 hover:text-white transition-colors duration-200 focus:outline-none"
aria-label="GitHub 仓库">
<GithubOutlined className="text-base opacity-80" />
<span>GitHub</span>
<svg
className={`w-3 h-3 transition-transform duration-200 ${isDropdownOpen ? "rotate-180" : ""}`}
fill="none"
stroke="currentColor"
viewBox="0 0 24 24">
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M19 9l-7 7-7-7"
/>
</svg>
</button>
{isDropdownOpen && (
<div className="absolute top-full left-2/3 -translate-x-1/2 w-18 bg-gray-950 border border-gray-700 rounded-lg shadow-xl py-1 z-20 opacity-60">
<a
href="https://github.com/zihluwang/frontend-repo"
target="_blank"
rel="noopener noreferrer"
className="block px-4 py-2 text-sm text-gray-300 hover:bg-gray-800 hover:text-white transition-colors">
</a>
<a
href="https://github.com/zihluwang/backend-repo"
target="_blank"
rel="noopener noreferrer"
className="block px-4 py-2 text-sm text-gray-300 hover:bg-gray-800 hover:text-white transition-colors">
</a>
</div>
)}
</div>
<span className="text-gray-700 select-none"></span>
<Link
to="/"
className="flex items-center gap-1.5 text-gray-400 hover:text-white transition-colors duration-200"
>
<img src="/footer-1.png" alt="" className="w-8 w-8 opacity-80" />
to="/legal?tab=eula"
className="flex items-center gap-1.5 text-gray-400 hover:text-white transition-colors duration-200">
<FileTextOutlined className="text-lg opacity-80" />
EULA
</Link>
<span className="text-gray-700 select-none"></span>
<Link
to="/"
className="flex items-center gap-1.5 text-gray-400 hover:text-white transition-colors duration-200"
>
<img src="/footer-2.png" alt="" className="w-8 w-8 opacity-80" />
to="/legal?tab=privacy"
className="flex items-center gap-1.5 text-gray-400 hover:text-white transition-colors duration-200">
<LockOutlined className="text-lg opacity-80" />
</Link>
<span className="text-gray-700 select-none"></span>
@@ -182,8 +144,7 @@ export default function HeroLayout() {
onClick: handleLogout,
},
],
}}
>
}}>
<span className="nav-item inline-flex items-center px-10 h-full text-base font-medium text-gray-500 hover:text-white cursor-pointer">
{user.username}
</span>
@@ -191,9 +152,8 @@ export default function HeroLayout() {
) : (
<Link
to="/login"
className="flex items-center gap-1.5 text-gray-400 hover:text-white transition-colors duration-200"
>
<img src="../../public/footer-3.png" alt="" className="w-8 w-8 opacity-80" />
className="flex items-center gap-1.5 text-gray-400 hover:text-white transition-colors duration-200">
<LoginOutlined className="text-lg opacity-80" />
</Link>
)}
@@ -201,11 +161,8 @@ export default function HeroLayout() {
<div className="border-t border-gray-800 my-6" />
<div className="text-center text-xs text-gray-500">
<p>
© 2024-{today.year()} Zihlu Wang OnixByte使 React TypeScript
</p>
<p>© 2024-{today.year()} Zihlu Wang OnixByte使 React TypeScript </p>
</div>
</div>
</footer>
</div>
+6
View File
@@ -0,0 +1,6 @@
declare module "*.md" {
const attributes: Record<string, any>
const html: string
const toc: { level: string; content: string; slug: string }[]
export { attributes, html, toc }
}
+34
View File
@@ -0,0 +1,34 @@
import { Tabs } from "antd"
import { useSearchParams } from "react-router-dom"
import MarkdownRenderer from "@/components/markdown-renderer"
import { html as EulaHtml } from "@/docs/EULA.md"
import { html as PrivacyHtml } from "@/docs/PrivacyPolicy.md"
const tabKeys = new Set(["eula", "privacy"])
export default function LegalPage() {
const [searchParams, setSearchParams] = useSearchParams()
const rawTab = searchParams.get("tab")
const activeTab = rawTab && tabKeys.has(rawTab) ? rawTab : "eula"
return (
<div className="mx-auto max-w-4xl">
<Tabs
activeKey={activeTab}
onChange={(key) => setSearchParams({ tab: key })}
items={[
{
key: "eula",
label: "最终用户许可协议",
children: <MarkdownRenderer html={EulaHtml} />,
},
{
key: "privacy",
label: "隐私政策",
children: <MarkdownRenderer html={PrivacyHtml} />,
},
]}
/>
</div>
)
}
+4
View File
@@ -40,6 +40,10 @@ const router = createBrowserRouter(
path: "mod-codes",
lazy: lazy(() => import("@/page/mod-codes")),
},
{
path: "legal",
lazy: lazy(() => import("@/page/legal"))
}
],
},
{
+10
View File
@@ -0,0 +1,10 @@
import type { Config } from "tailwindcss"
import typography from "@tailwindcss/typography"
export default {
content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"],
theme: {
extend: {},
},
plugins: [typography],
} satisfies Config
+2 -1
View File
@@ -3,10 +3,11 @@ import { defineConfig } from "vite"
import react from "@vitejs/plugin-react"
import tailwindcss from "@tailwindcss/vite"
import portChecker from "vite-plugin-port-checker"
import { Mode, plugin as markdown } from "vite-plugin-markdown"
// https://vite.dev/config/
export default defineConfig({
plugins: [react(), tailwindcss(), portChecker()],
plugins: [react(), tailwindcss(), portChecker(), markdown({ mode: [Mode.HTML, Mode.TOC] })],
base: "/",
build: {
rolldownOptions: {