10 Commits

Author SHA1 Message Date
siujamo 4b9c7d3e0d refactor: move typed Redux hooks from src/store/hooks.ts to src/hooks/store.ts
Also update GitHub repository URLs and labels in hero layout.
2026-05-12 09:08:37 +08:00
siujamo b2fea5df8e docs: extend British English convention to code comments 2026-05-11 17:48:42 +08:00
siujamo a447bffa77 refactor: update GitHub URLs and English labels in hero layout 2026-05-11 17:47:28 +08:00
siujamo 4d009a0195 fix: migrate login page to Ant Design 6 and Tailwind CSS 4 syntax 2026-05-11 17:46:20 +08:00
siujamo 7a0a74bfea refactor: rename application to 《三角洲》指南 2026-05-11 15:11:31 +08:00
siujamo fd537af916 feat: add CLAUDE.md 2026-05-11 15:11:08 +08:00
siujamo e7f4fc3374 Merge remote-tracking branch 'origin/develop' into develop
# Conflicts:
#	package.json
#	vite.config.ts
2026-05-11 11:10:12 +08:00
zihluwang 92703d4985 ci: build and upload release tarball
Use pnpm build:tar during release workflow and upload dist.tar.gz.
2026-05-11 07:10:30 +08:00
siujamo 0113ece426 Merge remote-tracking branch 'origin/develop' into develop
# Conflicts:
#	pnpm-lock.yaml
2026-05-08 17:38:23 +08:00
siujamo 37adfddf3f feat: add @onixbyte/vite-plugin-port-checker to enhance Vite configuration 2026-05-08 17:35:45 +08:00
7 changed files with 107 additions and 44 deletions
+20 -9
View File
@@ -2,13 +2,13 @@ name: Upload Release Assets
on: on:
release: release:
types: [created] # 仅当创建 Release 之后触发 types: [created] # Trigger only after a Release is created
jobs: jobs:
build-and-upload: build-and-upload:
runs-on: ubuntu-latest runs-on: ubuntu-latest
# 必须授予权限以允许 Action 修改 Release # Must grant permission to allow the Action to modify the Release
permissions: permissions:
contents: write contents: write
@@ -16,15 +16,26 @@ jobs:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Create archive - name: Setup Node
run: | uses: actions/setup-node@v4
TAG_NAME=${{ github.event.release.tag_name }} with:
tar -czvf "website-dist-${TAG_NAME}.tar.gz" --exclude=".git*" --exclude=".github*" . node-version: 20
cache: pnpm
- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
version: 11
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Build release archive
run: pnpm build:tar
- name: Upload Release Asset - name: Upload Release Asset
uses: softprops/action-gh-release@v2 uses: softprops/action-gh-release@v2
with: with:
# 上传刚才生成的 tar.gz 文件 files: dist.tar.gz
files: dist-${{ github.event.release.tag_name }}.tar.gz
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+18 -21
View File
@@ -1,22 +1,19 @@
{ {
// Use IntelliSense to learn about possible attributes. "version": "0.2.0",
// Hover to view descriptions of existing attributes. "configurations": [
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 {
"version": "0.2.0", "type": "node-terminal",
"configurations": [ "request": "launch",
{ "name": "pnpm dev",
"type": "node-terminal", "command": "pnpm dev",
"request": "launch", "cwd": "${workspaceFolder}"
"name": "pnpm dev", },
"command": "pnpm dev", {
"cwd": "${workspaceFolder}" "type": "node-terminal",
}, "request": "launch",
{ "name": "build",
"type": "node-terminal", "command": "pnpm build",
"request": "launch", "cwd": "${workspaceFolder}"
"name": "build", }
"command": "pnpm build", ]
"cwd": "${workspaceFolder}" }
}
]
}
+56
View File
@@ -0,0 +1,56 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Commands
```bash
pnpm install # Install dependencies (pnpm required)
pnpm dev # Start Vite dev server
pnpm build # TypeScript check + Vite production build
pnpm build:tar # Build + tar.gz archive (used by CI)
pnpm preview # Preview production build locally
pnpm lint # ESLint
```
No test suite exists in this project.
## Architecture
This is a Chinese-language SPA for browsing and managing Delta Force guide ("《三角洲》指南"). The frontend talks to a Spring Boot backend via REST APIs.
**App shell** (`src/main.tsx`): React 19 + React Router 7 + Redux Toolkit + Ant Design 6 + Tailwind CSS 4. Wraps the router in Redux `Provider``PersistGate` (Redux Persist) → Ant Design `StyleProvider`/`ConfigProvider` (locale `zh_CN`).
**Routing** (`src/router/index.tsx`): Two layout groups:
- `HeroLayout` (nav header + footer) for `/`, `/firearms`, `/mod-codes`
- `EmptyLayout` (minimal) for `/login`
All page components are lazy-loaded via `createBrowserRouter` + `lazy()`.
**State** (`src/store/`): Redux Toolkit with two slices — `auth` (current user) and `firearms` (paginated firearm list). State is persisted to `localStorage` or `sessionStorage` based on the `VITE_REDUX_STORAGE` env var. Use typed hooks from `src/hooks/store.ts` (`useAppDispatch`, `useAppSelector`).
**API layer** (`src/api/`): Axios instance (`src/shared/web-client/`) with base URL from `VITE_API_BASE_URL`, 10s timeout, and credentials. API modules: `FirearmApi`, `ModificationApi`, `TagApi`, `AuthApi`.
**Pages**:
- `FirearmsPage` — paginated card grid with type filter, create/edit modals (admin-only), delete with popconfirm
- `ModCodesPage` — paginated list with tag multi-select and firearmId query param filter, create/edit modals with nested accessory/tuning form lists
- `LoginPage` — simple username/password form, dispatches `setCurrentUser` on success
**Shared form components**: `FirearmForm` and `ModificationForm` are reused by both create and edit modals. `ModificationForm` fetches all firearms for its weapon selector and supports a `lockFirearmId` prop that disables the selector (used when navigating from a specific firearm).
**Type system** (`src/types/`): `Firearm` with weapon stats, `Modification` with nested `Accessory[]``Tuning[]`, `Page<T>` for paginated API responses, `User` for auth.
**Vite config**: Alias `@``./src`. Plugins: React, Tailwind CSS 4, port checker. Build uses rolldown with manual chunk splitting for React, Redux, Ant Design, React Router, and rc-* packages.
**Styling**: Tailwind CSS 4 with CSS layers (`theme, base, antd, components, utilities`). Responsive grid for mod code cards (1→2→3→4 columns). Prettier: 100 print width, no semicolons, double quotes, trailing commas ES5.
## Environment variables
```
VITE_API_BASE_URL=/api # Backend API base URL
VITE_REDUX_STORAGE=local # "local" or "session" for Redux persistence
```
## Contributing conventions
- User-facing copy, documentation, and code comments in British English
- Commit messages use `chore:` prefix for dependency updates (per Dependabot config)
+1 -1
View File
@@ -4,7 +4,7 @@
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/onixbyte.svg" /> <link rel="icon" type="image/svg+xml" href="/onixbyte.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>三角洲行动改枪码库</title> <title>三角洲》指南</title>
</head> </head>
<body> <body>
<div id="root"></div> <div id="root"></div>
+1 -2
View File
@@ -1,6 +1,5 @@
import { useDispatch, useSelector } from "react-redux" import { useDispatch, useSelector } from "react-redux"
import type { AppDispatch, RootState } from "./index" import type { AppDispatch, RootState } from "@/store"
export const useAppDispatch = useDispatch.withTypes<AppDispatch>() export const useAppDispatch = useDispatch.withTypes<AppDispatch>()
export const useAppSelector = useSelector.withTypes<RootState>() export const useAppSelector = useSelector.withTypes<RootState>()
+6 -6
View File
@@ -9,7 +9,7 @@ import {
LoginOutlined, LoginOutlined,
} from "@ant-design/icons" } from "@ant-design/icons"
import { AuthApi } from "@/api" import { AuthApi } from "@/api"
import { useAppDispatch, useAppSelector } from "@/store/hooks" import { useAppDispatch, useAppSelector } from "@/hooks/store"
import { clearCurrentUser } from "@/store/auth-slice" import { clearCurrentUser } from "@/store/auth-slice"
import { useState } from "react" import { useState } from "react"
@@ -100,18 +100,18 @@ export default function HeroLayout() {
{isDropdownOpen && ( {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"> <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 <a
href="https://github.com/zihluwang/frontend-repo" href="https://github.com/zihluwang/delta-force-guide-web"
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className="block px-4 py-2 text-sm text-gray-300 hover:bg-gray-800 hover:text-white transition-colors"> className="block px-4 py-2 text-sm text-gray-300 hover:bg-gray-800 hover:text-white transition-colors">
Web
</a> </a>
<a <a
href="https://github.com/zihluwang/backend-repo" href="https://github.com/zihluwang/delta-force-guide-server"
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className="block px-4 py-2 text-sm text-gray-300 hover:bg-gray-800 hover:text-white transition-colors"> className="block px-4 py-2 text-sm text-gray-300 hover:bg-gray-800 hover:text-white transition-colors">
Server
</a> </a>
</div> </div>
)} )}
@@ -153,7 +153,7 @@ export default function HeroLayout() {
<Link <Link
to="/login" to="/login"
className="flex items-center gap-1.5 text-gray-400 hover:text-white transition-colors duration-200"> className="flex items-center gap-1.5 text-gray-400 hover:text-white transition-colors duration-200">
<LoginOutlined className="text-lg opacity-80" /> <LoginOutlined className="text-lg opacity-80" />
</Link> </Link>
)} )}
+5 -5
View File
@@ -2,7 +2,7 @@ import { useState } from "react"
import { useNavigate } from "react-router-dom" import { useNavigate } from "react-router-dom"
import { App, Button, Card, Form, Input, Typography } from "antd" import { App, Button, Card, Form, Input, Typography } from "antd"
import { AuthApi } from "@/api" import { AuthApi } from "@/api"
import { useAppDispatch } from "@/store/hooks" import { useAppDispatch } from "@/hooks/store"
import { setCurrentUser } from "@/store/auth-slice" import { setCurrentUser } from "@/store/auth-slice"
import { LoginRequest } from "@/types" import { LoginRequest } from "@/types"
@@ -29,11 +29,11 @@ export default function LoginPage() {
return ( return (
<div className="min-h-screen bg-gray-100 px-4 py-10 sm:py-16"> <div className="min-h-screen bg-gray-100 px-4 py-10 sm:py-16">
<div className="mx-auto max-w-md"> <div className="mx-auto max-w-md">
<Card bordered={false} className="shadow-sm"> <Card variant="borderless" className="shadow-sm">
<Typography.Title level={3} className="!mb-2 text-center"> <Typography.Title level={3} className="mb-2! text-center">
</Typography.Title> </Typography.Title>
<Typography.Paragraph className="!mb-6 text-center !text-gray-500"> <Typography.Paragraph className="mb-6! text-center text-gray-500!">
使 使
</Typography.Paragraph> </Typography.Paragraph>
@@ -54,7 +54,7 @@ export default function LoginPage() {
<Input.Password autoComplete="current-password" placeholder="请输入密码" /> <Input.Password autoComplete="current-password" placeholder="请输入密码" />
</Form.Item> </Form.Item>
<Form.Item className="!mb-0"> <Form.Item className="mb-0!">
<Button type="primary" htmlType="submit" loading={loading} block> <Button type="primary" htmlType="submit" loading={loading} block>
</Button> </Button>