28 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
zihluwang b91855095b refactor: optimised slots 2026-05-10 13:52:22 +08:00
zihluwang 1f30f70f21 refactor: rename app title 2026-05-10 13:43:01 +08:00
zihluwang 13242deb6c refactor: reduce slots 2026-05-10 13:41:37 +08:00
zihluwang 3b5153f386 refactor: add a built-in slot 2026-05-10 13:32:49 +08:00
zihluwang 5502643466 refactor: change the modification display order 2026-05-10 13:30:51 +08:00
zihluwang 5790750124 feat: implemented listening port check by vite-plugin-port-checker 2026-05-10 13:30:25 +08:00
zihluwang 26bca96575 chore: add dependency vite-plugin-port-checker for checking listening ports 2026-05-10 13:29:55 +08:00
fanxingyao 381ccae5fd feat(index): 更新footer-更新header-添加header背景,footer图标 2026-05-08 18:48:38 +08:00
zihluwang 752e64f259 ci: add ci process 2026-05-08 08:23:35 +08:00
zihluwang 84a2a2ffea Merge remote-tracking branch 'origin/main' into develop 2026-05-08 08:11:08 +08:00
dependabot[bot] fd2352b6ad chore: bump the dependency-updates group with 7 updates
Bumps the dependency-updates group with 7 updates:

| Package | From | To |
| --- | --- | --- |
| [axios](https://github.com/axios/axios) | `1.15.2` | `1.16.0` |
| [react](https://github.com/facebook/react/tree/HEAD/packages/react) | `19.2.5` | `19.2.6` |
| [react-dom](https://github.com/facebook/react/tree/HEAD/packages/react-dom) | `19.2.5` | `19.2.6` |
| [react-router](https://github.com/remix-run/react-router/tree/HEAD/packages/react-router) | `7.14.2` | `7.15.0` |
| [react-router-dom](https://github.com/remix-run/react-router/tree/HEAD/packages/react-router-dom) | `7.14.2` | `7.15.0` |
| [globals](https://github.com/sindresorhus/globals) | `17.5.0` | `17.6.0` |
| [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) | `8.0.10` | `8.0.11` |


Updates `axios` from 1.15.2 to 1.16.0
- [Release notes](https://github.com/axios/axios/releases)
- [Changelog](https://github.com/axios/axios/blob/v1.x/CHANGELOG.md)
- [Commits](https://github.com/axios/axios/compare/v1.15.2...v1.16.0)

Updates `react` from 19.2.5 to 19.2.6
- [Release notes](https://github.com/facebook/react/releases)
- [Changelog](https://github.com/facebook/react/blob/main/CHANGELOG.md)
- [Commits](https://github.com/facebook/react/commits/v19.2.6/packages/react)

Updates `react-dom` from 19.2.5 to 19.2.6
- [Release notes](https://github.com/facebook/react/releases)
- [Changelog](https://github.com/facebook/react/blob/main/CHANGELOG.md)
- [Commits](https://github.com/facebook/react/commits/v19.2.6/packages/react-dom)

Updates `react-router` from 7.14.2 to 7.15.0
- [Release notes](https://github.com/remix-run/react-router/releases)
- [Changelog](https://github.com/remix-run/react-router/blob/main/packages/react-router/CHANGELOG.md)
- [Commits](https://github.com/remix-run/react-router/commits/react-router@7.15.0/packages/react-router)

Updates `react-router-dom` from 7.14.2 to 7.15.0
- [Release notes](https://github.com/remix-run/react-router/releases)
- [Changelog](https://github.com/remix-run/react-router/blob/main/packages/react-router-dom/CHANGELOG.md)
- [Commits](https://github.com/remix-run/react-router/commits/react-router-dom@7.15.0/packages/react-router-dom)

Updates `globals` from 17.5.0 to 17.6.0
- [Release notes](https://github.com/sindresorhus/globals/releases)
- [Commits](https://github.com/sindresorhus/globals/compare/v17.5.0...v17.6.0)

Updates `vite` from 8.0.10 to 8.0.11
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v8.0.11/packages/vite)

---
updated-dependencies:
- dependency-name: axios
  dependency-version: 1.16.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: dependency-updates
- dependency-name: react
  dependency-version: 19.2.6
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: dependency-updates
- dependency-name: react-dom
  dependency-version: 19.2.6
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: dependency-updates
- dependency-name: react-router
  dependency-version: 7.15.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: dependency-updates
- dependency-name: react-router-dom
  dependency-version: 7.15.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: dependency-updates
- dependency-name: globals
  dependency-version: 17.6.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: dependency-updates
- dependency-name: vite
  dependency-version: 8.0.11
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dependency-updates
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-07 07:58:51 +00:00
siujamo 0efcf75221 chore: remove unused deploy scripts from package.json 2026-05-07 13:35:41 +08:00
siujamo 0479744ce5 perf: optimise packaging 2026-05-07 10:51:29 +08:00
siujamo af58edbafd feat: add custom hooks for typed useDispatch and useSelector 2026-05-07 10:46:06 +08:00
siujamo 1a16199f2f docs: update .env.example with detailed instructions and configuration notes 2026-05-07 09:21:27 +08:00
zihluwang 9b2d527576 docs: add EULA and PrivacyPolicy 2026-05-06 19:14:43 +08:00
zihluwang d663cc5d20 docs: add js docs 2026-05-06 19:01:58 +08:00
zihluwang 5d84cf0589 feat: add placeholder files for EULA and Privacy Policy
Added empty files as placeholders for future legal documentation.
2026-05-06 18:59:17 +08:00
zihluwang 84fa103555 Merge branch 'refs/heads/main' into develop 2026-05-06 18:57:05 +08:00
zihluwang ff967da485 docs: update README file 2026-05-06 18:55:28 +08:00
zihluwang 6271b22708 Merge pull request #14 from zihluwang/dependabot/npm_and_yarn/dependency-updates-9da01cc519
chore: bump antd from 6.3.6 to 6.3.7 in the dependency-updates group
2026-05-06 15:07:20 +08:00
zihluwang 6e18d2efa9 refactor: change default size of modifications to 10 2026-05-06 01:31:05 +08:00
zihluwang 5803d057fd refactor: change the order of the slots 2026-05-05 11:48:30 +08:00
dependabot[bot] 22da81a102 chore: bump antd from 6.3.6 to 6.3.7 in the dependency-updates group
Bumps the dependency-updates group with 1 update: [antd](https://github.com/ant-design/ant-design).


Updates `antd` from 6.3.6 to 6.3.7
- [Release notes](https://github.com/ant-design/ant-design/releases)
- [Changelog](https://github.com/ant-design/ant-design/blob/master/CHANGELOG.en-US.md)
- [Commits](https://github.com/ant-design/ant-design/compare/6.3.6...6.3.7)

---
updated-dependencies:
- dependency-name: antd
  dependency-version: 6.3.7
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: dependency-updates
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-30 07:57:59 +00:00
zihluwang a8959c28ec Merge pull request #13 from zihluwang/develop
v1.2.2
2026-04-26 11:59:11 +08:00
zihluwang e4acb7fd6f Merge pull request #12 from zihluwang/develop
v1.2.1
2026-04-25 15:17:04 +08:00
25 changed files with 3847 additions and 800 deletions
+21
View File
@@ -0,0 +1,21 @@
# ENVIRONMENT CONFIGURATION TEMPLATE
#
# This file serves as a template for environment variables.
# DO NOT include any sensitive data (passwords, API keys, etc.) in this file.
#
# INSTRUCTIONS
# 1. Copy this file to `.env`, `.env.development`, or `.env.production` depending on your
# target environment.
# 2. Replace the placeholder values with your actual configuration.
# 3. Alternatively, ensure these variables are defined within your system's environment
# settings before running the application.
# The base URL fot the backend API service.
# Ensure this matches your Spring Boot server address (e.g., http://localhost:8080).
VITE_API_BASE_URL=/api
# Determines where Redux state is persisted on the client side.
# Available options:
# - `local`: Persists data in LocalStorage (remains after closing the browser).
# - `session`: Persists data in SessionStorage (cleared when the tab is closed).
VITE_REDUX_STORAGE=local
+30
View File
@@ -0,0 +1,30 @@
name: Upload Release Assets
on:
release:
types: [created] # 仅当创建 Release 之后触发
jobs:
build-and-upload:
runs-on: ubuntu-latest
# 必须授予权限以允许 Action 修改 Release
permissions:
contents: write
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Create archive
run: |
TAG_NAME=${{ github.event.release.tag_name }}
tar -czvf "website-dist-${TAG_NAME}.tar.gz" --exclude=".git*" --exclude=".github*" .
- name: Upload Release Asset
uses: softprops/action-gh-release@v2
with:
# 上传刚才生成的 tar.gz 文件
files: dist-${{ github.event.release.tag_name }}.tar.gz
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
-2
View File
@@ -66,8 +66,6 @@ pnpm preview
```text
src/
components/ Shared UI components
data/ Modification code dataset
init/ Application initialisation
layout/ Route layouts
page/ Route pages
router/ Router configuration
+2798
View File
File diff suppressed because it is too large Load Diff
+13 -11
View File
@@ -8,35 +8,37 @@
"build": "tsc -b && vite build",
"build:tar": "pnpm build && tar -czf dist.tar.gz dist",
"lint": "eslint .",
"preview": "vite preview",
"deploy": "pnpm build && gh-pages -d dist",
"predeploy": "pnpm build"
"preview": "vite preview"
},
"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",
"antd": "^6.3.6",
"axios": "^1.15.2",
"antd": "^6.3.7",
"axios": "^1.16.0",
"dayjs": "^1.11.20",
"react": "^19.2.5",
"react-dom": "^19.2.5",
"react": "^19.2.6",
"react-dom": "^19.2.6",
"react-redux": "^9.2.0",
"react-router": "^7.14.2",
"react-router-dom": "^7.14.2",
"react-router": "^7.15.0",
"react-router-dom": "^7.15.0",
"redux-persist": "^6.0.0",
"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",
"@vitejs/plugin-react": "^6.0.1",
"globals": "^17.5.0",
"globals": "^17.6.0",
"prettier": "^3.8.3",
"typescript": "~6.0.3",
"vite": "^8.0.10"
"vite": "^8.0.11",
"vite-plugin-markdown": "^2.2.0",
"vite-plugin-port-checker": "^1.0.1"
},
"pnpm": {
"ignoredBuiltDependencies": [
+624 -695
View File
File diff suppressed because it is too large Load Diff
+2
View File
@@ -0,0 +1,2 @@
allowBuilds:
esbuild: false
+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 }}
/>
)
}
+7 -11
View File
@@ -1,23 +1,19 @@
[
"枪口",
"左导轨",
"右导轨",
"枪管",
"贴片",
"右贴片",
"上导轨",
"上贴片",
"下导轨",
"贴片",
"瞄准镜",
"战术设备",
"增高座瞄具",
"侧瞄具",
"枪托",
"托腮板",
"枪托套件",
"后握把",
"前握把",
"导轨脚架",
"弹匣座",
"前握把",
"后握把",
"后握贴片",
"握把座",
"弹匣",
"托腮板"
"弹匣座"
]
+29
View File
@@ -0,0 +1,29 @@
# 《三角洲行动游戏指南》最终用户许可协议
> 最近更新日期: 2026年5月6日
感谢您访问《三角洲行动游戏指南》(以下简称“本站”)。在访问或使用本站前,请您务必仔细阅读本协议。
## 第一条:项目性质与声明
本站是由《三角洲行动》游戏玩家开发的兴趣爱好项目,与游戏官方(包括但不限于腾讯、琳恩工作室等)不存在任何形式的关联、授权或代理关系。
本站仅旨在为玩家提供游戏资讯、攻略及技术性指南,不保证资讯的绝对实时性与准确性。
## 第二条:内容版权说明
本站部分指南、攻略及图片素材提取、整理自哔哩哔哩、抖音等视频平台的公开视频,本站均会标注原作者信息或来源。相关内容的知识产权归原作者所有。
由本站开发者原创或邀请的高手提供的特约稿件,其版权归本站或原提供者所有。
严禁任何个人或组织在未经本站或原作者授权的情况下,将本站内容用于商业牟利。
## 第三条:免责声明
游戏机制可能随版本更新而变动,因参考本站指南而产生的任何游戏结果(如战绩波动、游戏内损失等),本站概不负责。
来源标注中可能包含指向第三方平台的链接,本站不对第三方平台内容的合法性或安全性负责。
## 第四条:协议变更
本站保留随时修改本协议的权利。重大变更将通过站点公告形式发布。
+33
View File
@@ -0,0 +1,33 @@
# 《三角洲行动游戏指南》隐私政策
本隐私政策旨在向您说明在无需登录的情况下,本站如何处理与您相关的信息。
## 第一条:信息收集情况
1. **个人信息:** 由于本站采用无需登录的模式,我们不会收集您的姓名、手机号、身份证号或精确地理位置等个人敏感数据。
2. **服务器日志:** 本站未集成任何第三方流量统计工具(如 Google Analytics 或百度统计),不会主动记录您的 IP 地址或用户代理(User-agent)数据。
## 第二条:Cookie 的使用
1. **必要性 Cookie** 本站仅使用必要的 Cookie 用于保存与后端交互所需的功能性数据(如临时会话标识或您的本地设置偏好)。
2. **非追踪性:** 这些 Cookie 不用于追踪您的个人跨站行为,也不会用于构建您的个人画像。
## 第三条:第三方广告与赞助
1. **现状说明:** 本站目前不包含任何商业广告,亦未开通打赏或赞助入口。
2. **未来规划:** 考虑到运营成本,本站保留未来接入广告服务或赞助链接的权利。届时,广告商可能会根据其自身的隐私政策使用 Cookie。一旦此类功能上线,我们将同步更新本政策。
## 第四条:数据安全
我们致力于保护站点的运行安全,防止现有数据被非法篡改或泄露。
## 第五条:联系我们
若您对本协议或内容版权有任何疑问,请通过以下方式联系:
开发者: Zihlu Wang、Xingyao Fan
联系渠道: [GitHub](https://github.com/zihluwang/delta-force-guide-web)
+30
View File
@@ -1,6 +1,7 @@
@layer theme, base, antd, components, utilities;
@import 'tailwindcss';
@plugin "@tailwindcss/typography";
html, body {
margin: 0;
@@ -33,4 +34,33 @@ html, body {
.mod-codes-grid {
grid-template-columns: repeat(4, minmax(0, 1fr));
}
}
.nav-item {
position: relative;
overflow: hidden;
}
.nav-item::after {
content: '';
position: absolute;
bottom: 0;
left: 0;
right: 0;
height: 0;
background: linear-gradient(to top, #22ffa7, transparent);
transition: height 0.2s ease-in-out;
opacity: 0.35;
pointer-events: none;
}
.nav-item:hover::after,
.nav-item.active::after {
height: 80%; /* Height of the upward glow; adjustable. */
}
.nav-item:hover,
.nav-item.active {
color: white;
}
+122 -58
View File
@@ -1,10 +1,17 @@
import { Outlet, Link } from "react-router-dom"
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"
import { useAppDispatch, useAppSelector } from "@/store/hooks"
import { clearCurrentUser } from "@/store/auth-slice"
import { useState } from "react"
/**
* Main application component that serves as the root layout.
@@ -14,6 +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)
async function handleLogout() {
try {
@@ -24,63 +32,33 @@ export default function HeroLayout() {
}
return (
<div className="bg-gray-50">
<div className="bg-gray-50 ">
{/* Navigation Header */}
<header className="bg-white shadow-sm border-b">
<div className="max-w-screen-2xl mx-auto px-4 sm:px-6 lg:px-10">
<div className="flex justify-between items-center h-16">
<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">
<h1 className="text-xl font-semibold text-gray-900">
</h1>
<h1 className="text-xl font-semibold text-white"></h1>
</div>
<nav className="flex space-x-8">
<Link
<nav className="flex h-full">
<NavLink
to="/firearms"
className="text-gray-500 hover:text-gray-900 px-3 py-2 rounded-md text-sm font-medium"
>
className={({ isActive }) =>
`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`
}>
</Link>
<Link
</NavLink>
<NavLink
to="/mod-codes"
className="text-gray-500 hover:text-gray-900 px-3 py-2 rounded-md text-sm font-medium"
>
className={({ isActive }) =>
`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`
}>
</Link>
{user ? (
<Dropdown
trigger={["hover"]}
menu={{
items: [
{
key: "logout",
label: "退出登录",
danger: true,
onClick: handleLogout,
},
],
}}
>
<span className="cursor-pointer text-gray-700 px-3 py-2 rounded-md text-sm font-medium">
{user.username}
</span>
</Dropdown>
) : (
<Link
to="/login"
className="text-gray-500 hover:text-gray-900 px-3 py-2 rounded-md text-sm font-medium"
>
</Link>
)}
<a
href="https://github.com/zihluwang/delta-force-firearm-modification-codes"
target="_blank"
rel="noopener noreferrer"
className="text-gray-500 hover:text-gray-900 px-3 py-2 rounded-md text-sm font-medium"
>
GitHub
</a>
</NavLink>
</nav>
</div>
</div>
@@ -94,13 +72,99 @@ export default function HeroLayout() {
</main>
{/* Footer */}
<footer className="bg-white border-t">
<div className="max-w-screen-2xl mx-auto py-4 px-4 sm:px-6 lg:px-10">
<p className="text-center text-sm text-gray-500">
© 2024-{today.year()} Zihlu Wang OnixByte使 React TypeScript
</p>
<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 仓库">
<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="/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="/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>
{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="flex items-center gap-1.5 text-gray-400 hover:text-white transition-colors duration-200">
<LoginOutlined className="text-lg opacity-80" />
</Link>
)}
</div>
<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>
</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 }
}
+1 -1
View File
@@ -3,7 +3,7 @@ import { Link } from "react-router-dom"
import { FirearmApi } from "@/api"
import FirearmCreateModal from "@/components/firearm-create-modal"
import FirearmEditModal from "@/components/firearm-edit-modal"
import { useAppSelector } from "@/store"
import { useAppSelector } from "@/store/hooks"
import { Firearm, FirearmType } from "@/types"
import { Button, Card, Col, Pagination, Popconfirm, Row, Select, Tag, Typography, App } from "antd"
+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>
)
}
+1 -1
View File
@@ -2,7 +2,7 @@ import { useState } from "react"
import { useNavigate } from "react-router-dom"
import { App, Button, Card, Form, Input, Typography } from "antd"
import { AuthApi } from "@/api"
import { useAppDispatch } from "@/store"
import { useAppDispatch } from "@/store/hooks"
import { setCurrentUser } from "@/store/auth-slice"
import { LoginRequest } from "@/types"
+3 -3
View File
@@ -16,10 +16,10 @@ import { Link, useSearchParams } from "react-router-dom"
import { ModificationApi, TagApi } from "@/api"
import ModificationCreateModal from "@/components/modification-create-modal"
import ModificationEditModal from "@/components/modification-edit-modal"
import { useAppSelector } from "@/store"
import { useAppSelector } from "@/store/hooks"
import { Modification } from "@/types"
const pageSize = 12
const pageSize = 10
export default function ModCodesPage() {
const user = useAppSelector((state) => state.auth.user)
@@ -56,7 +56,7 @@ export default function ModCodesPage() {
page: page - 1,
size: pageSize,
sortBy: "id",
direction: "ASC",
direction: "DESC",
firearmId,
tags: selectedTags,
}).then((pagedData) => {
+9
View File
@@ -13,6 +13,8 @@ function lazy<T extends { default: ComponentType<unknown> }>(importer: () => Pro
}
}
const hydrateFallbackElement = <div className="px-4 py-6 text-gray-500">...</div>
/**
* Main application router configuration using React Router v6.
* Defines all routes and their corresponding components.
@@ -22,11 +24,13 @@ const router = createBrowserRouter(
{
path: "/",
element: <HeroLayout />,
hydrateFallbackElement,
errorElement: <ErrorPage />,
children: [
{
index: true,
lazy: lazy(() => import("@/page/firearms")),
},
{
path: "firearms",
@@ -36,10 +40,15 @@ const router = createBrowserRouter(
path: "mod-codes",
lazy: lazy(() => import("@/page/mod-codes")),
},
{
path: "legal",
lazy: lazy(() => import("@/page/legal"))
}
],
},
{
element: <EmptyLayout />,
hydrateFallbackElement,
errorElement: <ErrorPage />,
children: [
{
+6
View File
@@ -0,0 +1,6 @@
import { useDispatch, useSelector } from "react-redux"
import type { AppDispatch, RootState } from "./index"
export const useAppDispatch = useDispatch.withTypes<AppDispatch>()
export const useAppSelector = useSelector.withTypes<RootState>()
-3
View File
@@ -1,5 +1,4 @@
import { configureStore, combineReducers } from "@reduxjs/toolkit"
import { useDispatch, useSelector } from "react-redux"
import {
persistStore,
persistReducer,
@@ -46,5 +45,3 @@ export type RootState = ReturnType<typeof rootReducer>
export type AppDispatch = typeof store.dispatch
export type AppStore = typeof store
export const useAppDispatch = useDispatch.withTypes<AppDispatch>()
export const useAppSelector = useSelector.withTypes<RootState>()
+8
View File
@@ -1,6 +1,14 @@
/// <reference types="vite/client" />
interface ImportMetaEnv {
/**
* Redux persistent storage location, use `local` for local storage and `session` for
* session storage
*/
readonly VITE_REDUX_STORAGE: "local" | "session"
/**
* Backend API Base URL
*/
readonly VITE_API_BASE_URL: string
}
+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
+38 -10
View File
@@ -2,23 +2,51 @@ import { fileURLToPath, URL } from "node:url"
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()],
plugins: [react(), tailwindcss(), portChecker(), markdown({ mode: [Mode.HTML, Mode.TOC] })],
base: "/",
build: {
rolldownOptions: {
output: {
manualChunks(id) {
if (!id.includes("node_modules")) {
return
}
if (id.includes("react-router")) {
return "router-vendor"
}
if (id.includes("redux") || id.includes("immer")) {
return "redux-vendor"
}
if (id.includes("/node_modules/@ant-design/")) {
return "ant-design-vendor"
}
if (id.includes("/node_modules/rc-")) {
return "antd-rc-vendor"
}
if (
id.includes("/node_modules/react/") ||
id.includes("/node_modules/react-dom/") ||
id.includes("/node_modules/scheduler/")
) {
return "react-vendor"
}
},
},
},
},
resolve: {
alias: {
"@": fileURLToPath(new URL("./src", import.meta.url)),
},
},
server: {
proxy: {
'/api': {
target: 'http://localhost:8080',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, '')
}
}
}
})