docs: 映射 qy-lty-admin 代码库
This commit is contained in:
parent
7223c52e9f
commit
a85b6a79a8
203
qy-lty-admin/.planning/codebase/ARCHITECTURE.md
Normal file
203
qy-lty-admin/.planning/codebase/ARCHITECTURE.md
Normal file
@ -0,0 +1,203 @@
|
||||
# Architecture
|
||||
|
||||
**Analysis Date:** 2026-05-07
|
||||
|
||||
## Pattern Overview
|
||||
|
||||
**Overall:** Next.js 15 App Router with client-centric admin dashboard, role-based access control (RBAC) via localStorage permission matrix, and layered API abstraction.
|
||||
|
||||
**Key Characteristics:**
|
||||
- Client-side rendering-first pattern with permission checks in layout/shell components
|
||||
- Token-based authentication with Axios interceptors for transparent token injection
|
||||
- Role-based module access controlled by permission matrix in `lib/permissions.ts`
|
||||
- Form handling via React Hook Form + Zod for validation
|
||||
- Shadcn-style UI components copied into `components/ui/` (not npm packages)
|
||||
|
||||
## Layers
|
||||
|
||||
**Page Layer (Presentation/Routing):**
|
||||
- Purpose: App Router page components that mount client-side shells and load data
|
||||
- Location: `app/[module]/page.tsx` (e.g., `app/outfits/page.tsx`, `app/login/page.tsx`)
|
||||
- Contains: "use client" page entries, form pages, list views
|
||||
- Depends on: DashboardShell, custom feature components, API clients
|
||||
- Used by: Next.js App Router, browser navigation
|
||||
|
||||
**Component Layer (UI & Business Logic):**
|
||||
- Purpose: Reusable business components (modals, dialogs, tables, feature-specific UI)
|
||||
- Location: `components/` (feature folders) and `components/ui/` (primitives)
|
||||
- Contains: DashboardShell, Sidebar, feature dialogs (AddOutfitDialog, DeleteConfirmationDialog), stat cards
|
||||
- Depends on: UI primitives, API clients, React Hook Form, icons (lucide-react)
|
||||
- Used by: Page components
|
||||
|
||||
**API Client Layer (HTTP Communication):**
|
||||
- Purpose: Axios-based API communication with interceptors for auth, error handling
|
||||
- Location: `lib/api/` (e.g., `lib/api/client.ts`, `lib/api/auth.ts`, `lib/api/outfits.ts`)
|
||||
- Contains: API singleton instance, interceptors, module-specific request/response adapters
|
||||
- Depends on: Axios, localStorage (for token retrieval), environment variables
|
||||
- Used by: Page components and feature components
|
||||
|
||||
**Permission/Auth Layer (Access Control):**
|
||||
- Purpose: Role-based module visibility and path-level permission checks
|
||||
- Location: `lib/permissions.ts` (RBAC matrix), `middleware.ts` (route protection)
|
||||
- Contains: PERMISSION_MATRIX (role → module[]), utility functions (hasPermission, getUserRole, getModuleFromPath)
|
||||
- Depends on: localStorage.user_role, localStorage.is_superuser
|
||||
- Used by: DashboardShell, Sidebar, middleware
|
||||
|
||||
**Utility Layer:**
|
||||
- Purpose: Type definitions, helpers, environment detection
|
||||
- Location: `lib/utils.ts`, `lib/api/types.ts`, `lib/api/adapters.ts`
|
||||
- Contains: Tailwind utilities (cn()), type mappings, mock data
|
||||
- Depends on: Nothing (standalone)
|
||||
- Used by: All layers
|
||||
|
||||
## Data Flow
|
||||
|
||||
**Authentication Flow (Login → Authenticated State):**
|
||||
|
||||
1. User fills login form on `app/login/page.tsx`
|
||||
2. Form submission calls `emailLogin(email, password)` from `lib/api/auth.ts`
|
||||
3. Axios POSTs to `{NEXT_PUBLIC_API_BASE_URL}/v1/admin/login/`
|
||||
4. Response contains `token`, `role`, `is_superuser`
|
||||
5. `saveAuthToken(token, isSuperUser, role)` stores to localStorage and Cookie
|
||||
6. Page redirects to `/` (dashboard)
|
||||
7. Middleware checks Cookie `auth_token`; on subsequent page loads, Axios request interceptor injects token from localStorage
|
||||
|
||||
**Protected Page Access Flow:**
|
||||
|
||||
1. User navigates to protected route (e.g., `/outfits`)
|
||||
2. Middleware checks cookie; if missing, redirects to `/login`
|
||||
3. Page mounts, DashboardShell component renders
|
||||
4. DashboardShell calls `hasPathPermission(pathname)` at mount
|
||||
5. Function checks localStorage.user_role against PERMISSION_MATRIX
|
||||
6. If denied, renders access-denied UI; if allowed, renders children
|
||||
7. Sidebar calls `hasPermission(module)` for each menu item (mounted only) to filter visible items
|
||||
8. Sidebar also displays current role from localStorage.user_role
|
||||
|
||||
**Data Fetching Flow (Page → Component → API → Backend):**
|
||||
|
||||
1. Page component (e.g., `app/outfits/page.tsx`) mounts as "use client"
|
||||
2. Page renders feature components (e.g., OutfitsList, OutfitsTable)
|
||||
3. Feature component calls `getOutfits(params)` from `lib/api/outfits.ts`
|
||||
4. API function calls `apiClient.get('/card/category/clothing/?...')`
|
||||
5. Request interceptor injects token: `Authorization: Bearer {token}`
|
||||
6. Backend receives request with token in header
|
||||
7. Response returns `{ success, code, data, message }`
|
||||
8. API adapter function maps backend response to frontend type (e.g., `mapBackendOutfit`)
|
||||
9. Component stores result in React state or updates UI
|
||||
10. Response interceptor handles 401 (token expired) → clears localStorage → redirects to `/login`
|
||||
|
||||
**State Management:**
|
||||
|
||||
- **Token/Auth state:** Persisted in `localStorage.auth_token`, `localStorage.user_role`, `localStorage.is_superuser`; synced to Cookie `auth_token`
|
||||
- **Component state:** React useState (shallow, page-scoped)
|
||||
- **Form state:** React Hook Form (form-scoped)
|
||||
- **No global state manager** (Redux/Zustand not used)
|
||||
|
||||
**Authentication Token Lifecycle:**
|
||||
|
||||
1. Stored in localStorage + Cookie (7-day expiry) after login
|
||||
2. On every API request, request interceptor reads from localStorage
|
||||
3. If 401 response, token deleted from localStorage, user redirected to `/login`
|
||||
4. Cookie-based fallback allows middleware to check auth on route entry
|
||||
|
||||
## Client/Server Component Split
|
||||
|
||||
**Server Components:**
|
||||
- `app/layout.tsx` — Root layout with metadata only; does NOT render protected content
|
||||
- Most pages are **"use client"** due to permission checks and hooks
|
||||
|
||||
**Client Components ("use client"):**
|
||||
- All pages under `app/[module]/` — Need React hooks for permission checks, form handling
|
||||
- `components/dashboard-shell.tsx` — Runs permission checks via `hasPathPermission()`
|
||||
- `components/sidebar.tsx` — Uses `usePathname()`, `useRouter()`, `useState()` for role-based filtering
|
||||
- Feature components — Use useState, API calls, form handling
|
||||
|
||||
## Key Abstractions
|
||||
|
||||
**DashboardShell:**
|
||||
- Purpose: Layout wrapper that enforces route-level permissions
|
||||
- Location: `components/dashboard-shell.tsx`
|
||||
- Pattern: Client wrapper checking `hasPathPermission(pathname)` on mount; renders either access-denied UI or children
|
||||
- Used by: All page components (wraps page content)
|
||||
|
||||
**Sidebar:**
|
||||
- Purpose: Dynamic navigation menu filtered by user role
|
||||
- Location: `components/sidebar.tsx`
|
||||
- Pattern: Client component reading `localStorage.user_role` after mount; filters menu items via `hasPermission(module)`
|
||||
- Menu structure: Three sections (AI Admin, Content Admin, System Admin) with conditional rendering
|
||||
- Icon mapping: MenuItem interface + lucide-react icons
|
||||
|
||||
**API Client Singleton:**
|
||||
- Purpose: Centralized HTTP client with reusable interceptors
|
||||
- Location: `lib/api/client.ts` exports `apiClient` (Axios instance)
|
||||
- Pattern: Single instance created at module load; request/response interceptors for auth/error handling
|
||||
- Interceptors: Request injects token; response handles 401 by clearing token + redirecting
|
||||
|
||||
**Permission Matrix:**
|
||||
- Purpose: Role → Module[] mapping
|
||||
- Location: `lib/permissions.ts` PERMISSION_MATRIX constant
|
||||
- Pattern: Record<RoleName, PermissionModule[]> with 5 roles × 13 modules
|
||||
- Functions: getUserRole() → RoleName; hasPermission(module) → boolean; getModuleFromPath(pathname) → module
|
||||
|
||||
**API Adapter Pattern:**
|
||||
- Purpose: Map backend response schema to frontend type
|
||||
- Example: `mapBackendOutfit()` in `lib/api/outfits.ts`
|
||||
- Pattern: Backend returns `{ id, name, image_url, rarity_display, ... }`; adapter extracts and renames to `{ id, name, imageUrl, rarity, ... }`
|
||||
- Why: Frontend type contracts differ from backend schema
|
||||
|
||||
## Entry Points
|
||||
|
||||
**Browser Entry:**
|
||||
- Location: `app/layout.tsx` (Root layout)
|
||||
- Triggers: Initial page load or navigation
|
||||
- Responsibilities: Renders HTML shell; child routes rendered by App Router
|
||||
|
||||
**Auth Entry:**
|
||||
- Location: `app/login/page.tsx`
|
||||
- Triggers: Unauthenticated users accessing protected routes (redirected by middleware)
|
||||
- Responsibilities: Render login form, call `emailLogin()`, save auth token, redirect to dashboard
|
||||
|
||||
**Dashboard Entry:**
|
||||
- Location: `app/page.tsx`
|
||||
- Triggers: Authenticated users navigating to `/` or post-login redirect
|
||||
- Responsibilities: Check auth status, render stats/charts, provide links to submodules
|
||||
|
||||
**Protected Route Entry (Example):**
|
||||
- Location: `app/outfits/page.tsx`
|
||||
- Triggers: User navigates to `/outfits`
|
||||
- Responsibilities: Wrap content in DashboardShell (permission check), render OutfitsList component, fetch data
|
||||
|
||||
## Error Handling
|
||||
|
||||
**Strategy:** Try-catch at API level; Axios interceptor handles 401; UI displays error toast or inline messages.
|
||||
|
||||
**Patterns:**
|
||||
|
||||
- **API Request Errors:** Caught in try-catch, logged, thrown to caller (page/component)
|
||||
- **401 Unauthorized:** Interceptor clears token, redirects to `/login`
|
||||
- **Form Validation Errors:** React Hook Form + Zod pre-submission; validation errors displayed inline
|
||||
- **Permission Denied:** DashboardShell renders access-denied UI (no error thrown)
|
||||
- **Mock Data Fallback:** `lib/api/client.ts` exports mock users/roles; can be used for offline testing
|
||||
|
||||
## Cross-Cutting Concerns
|
||||
|
||||
**Logging:**
|
||||
- Verbose Axios logging in request/response interceptors (console.log with emoji prefixes)
|
||||
- Error logging in catch blocks
|
||||
|
||||
**Validation:**
|
||||
- React Hook Form + Zod for form fields
|
||||
- Backend response type-checked against ApiResponse interface
|
||||
|
||||
**Authentication:**
|
||||
- localStorage.auth_token + Axios Bearer header injection
|
||||
- Middleware checks Cookie auth_token for route protection
|
||||
- 401 response triggers token cleanup + redirect
|
||||
|
||||
**Internationalization:**
|
||||
- Chinese UI text hardcoded in components
|
||||
- No i18n library used
|
||||
|
||||
---
|
||||
|
||||
*Architecture analysis: 2026-05-07*
|
||||
655
qy-lty-admin/.planning/codebase/CONCERNS.md
Normal file
655
qy-lty-admin/.planning/codebase/CONCERNS.md
Normal file
@ -0,0 +1,655 @@
|
||||
# Codebase Concerns
|
||||
|
||||
**Analysis Date:** 2026-05-07
|
||||
|
||||
## Tech Debt
|
||||
|
||||
### Multiple Lock Files Conflict Risk
|
||||
|
||||
**Issue:** Three conflicting lock files coexist: `package-lock.json`, `pnpm-lock.yaml`, and `yarn.lock`.
|
||||
|
||||
**Files:**
|
||||
- `package-lock.json` (177KB)
|
||||
- `pnpm-lock.yaml` (2.3KB)
|
||||
- `yarn.lock` (91KB)
|
||||
|
||||
**Impact:**
|
||||
- Risk of installing different dependency versions across environments
|
||||
- CI/CD, Docker builds, and local development may pull incompatible versions
|
||||
- Lock file merge conflicts during multi-developer collaboration
|
||||
- Dockerfile uses yarn exclusively, but npm/pnpm may be used locally, creating version drift
|
||||
|
||||
**Current State:**
|
||||
- `Dockerfile` (line 12, 34) explicitly uses `yarn.lock*`
|
||||
- `.gitignore` likely checks in all three files
|
||||
- CLAUDE.md explicitly warns "不要混用" but provides no automation to enforce it
|
||||
|
||||
**Fix Approach:**
|
||||
1. Choose single package manager (recommend yarn, per Dockerfile)
|
||||
2. Delete `package-lock.json` and `pnpm-lock.yaml` from git history
|
||||
3. Add enforcement to `.gitignore` and CI/CD (fail if multiple lock files modified)
|
||||
4. Document in CLAUDE.md that ONLY yarn should be used
|
||||
|
||||
**Priority:** High - impacts build reproducibility and team workflow
|
||||
|
||||
---
|
||||
|
||||
### Production Build Ignores TypeScript & ESLint Errors
|
||||
|
||||
**Issue:** Build succeeds despite type/lint errors; production may contain undetected bugs.
|
||||
|
||||
**Files:**
|
||||
- `next.config.mjs:16-21` — Both `ignoreDuringBuilds: true`
|
||||
|
||||
**Current Configuration:**
|
||||
```javascript
|
||||
eslint: {
|
||||
ignoreDuringBuilds: true,
|
||||
},
|
||||
typescript: {
|
||||
ignoreBuildErrors: true,
|
||||
},
|
||||
```
|
||||
|
||||
**Impact:**
|
||||
- TypeScript errors (type mismatches, missing nullchecks) not caught at build time
|
||||
- ESLint warnings (unused imports, unreachable code) ignored
|
||||
- QA burden shifts to runtime discovery
|
||||
- Breaking changes in dependencies not detected early
|
||||
|
||||
**Evidence:** README.md (line 121) acknowledges this: "务必在本地执行 `next lint` 与 `tsc --noEmit` 保证质量" — manual step, not enforced
|
||||
|
||||
**Fix Approach:**
|
||||
1. Set both to `false` in production config
|
||||
2. Fix all current type/lint errors (likely small count given ~27K LOC)
|
||||
3. Add pre-commit hooks to `husky` to block commits with errors
|
||||
4. Make lint/type checks mandatory in CI/CD
|
||||
|
||||
**Priority:** High - gates correctness
|
||||
|
||||
---
|
||||
|
||||
### Console Logging in Client Code (Debug Artifact)
|
||||
|
||||
**Issue:** Production code contains console.log/warn/error calls, exposing debug info and token prefixes.
|
||||
|
||||
**Files & Line Numbers:**
|
||||
- `lib/api/client.ts:23` — `console.log('🔍 Token检查:', ...)`
|
||||
- `lib/api/client.ts:29` — `console.warn('⚠️ 未找到auth_token...')`
|
||||
- `lib/api/client.ts:35` — `console.log('📡 发送请求:', ...)`
|
||||
- `lib/api/client.ts:36` — `console.log('📋 请求头:', ...)`
|
||||
- `lib/api/client.ts:47` — `console.log('✅ 响应成功:', ...)`
|
||||
- `lib/api/client.ts:51` — `console.error('❌ 响应错误:', ...)`
|
||||
- `lib/api/client.ts:55` — `console.warn('🚫 认证失败...')`
|
||||
- `lib/api/client.ts:56` — `console.log('响应详情:', ...)`
|
||||
- `lib/api/upload.ts:33` — `console.log('📤 开始上传文件:', ...)`
|
||||
- `lib/api/upload.ts:43` — `console.log(...上传进度...)`
|
||||
- `lib/api/upload.ts:59` — `console.log('✅ 文件上传成功:', ...)`
|
||||
- `lib/api/upload.ts:62` — `console.error("文件上传失败:", error)`
|
||||
- Similar issues in upload multifile (lines 83, 93)
|
||||
- `components/sidebar.tsx:97` — `console.error("退出登录失败:", error)`
|
||||
- `app/outfits/page.tsx:72, 117, 135, 154` — console.error calls
|
||||
- Many other pages: `app/*/page.tsx` contain error logging
|
||||
|
||||
**Impact:**
|
||||
- Token prefixes exposed in browser console → XSS/developer tools leak risk
|
||||
- Error details (e.g., API paths, status codes) visible to end users
|
||||
- Production debugging should use structured error handling, not console
|
||||
- Could be scraped by malicious actors with browser console access
|
||||
|
||||
**Evidence:** `lib/api/token-debug.ts` is entire debug utility with `console.log` throughout (lines 7-54), showing token prefix extraction logic
|
||||
|
||||
**Fix Approach:**
|
||||
1. Remove all console.log/warn/error in client code, replace with structured error handling
|
||||
2. Create error boundary/toast notification pattern for user-facing errors
|
||||
3. Optional: Create client-side error tracking service (Sentry) for production
|
||||
4. Add ESLint rule: `no-console` (already available in Next.js config, just not enabled)
|
||||
|
||||
**Priority:** High - security and code cleanliness
|
||||
|
||||
---
|
||||
|
||||
## Security Concerns
|
||||
|
||||
### Client-Side Permission Enforcement Only (Trust Boundary Violation)
|
||||
|
||||
**Issue:** Permissions are enforced ONLY on client; backend may not re-validate on API calls.
|
||||
|
||||
**Files:**
|
||||
- `lib/permissions.ts:65-87` — `getUserRole()`, `hasPermission()` read from `localStorage.user_role`
|
||||
- `components/sidebar.tsx:102-104` — Filter menu items client-side only
|
||||
- `middleware.ts:17-44` — Token validation in middleware, but NO permission check against module being accessed
|
||||
|
||||
**Risk Model:**
|
||||
```
|
||||
User (attacker) → Modify localStorage.user_role="超级管理员" →
|
||||
Sidebar shows all menus (useless) BUT
|
||||
Direct API call to DELETE /api/v1/admin/users/123 → Backend accepts if no re-check
|
||||
```
|
||||
|
||||
**Current Evidence:**
|
||||
- `lib/api/users.ts:24-43` — `getUsers()` makes raw API call; no permission guard in client code
|
||||
- No evidence of backend role check in Django API (not inspectable from frontend code, but CRITICAL to verify)
|
||||
|
||||
**Impact:**
|
||||
- **CRITICAL:** If backend doesn't re-validate role on each endpoint, attackers can:
|
||||
- Modify localStorage role → make admin API calls
|
||||
- Bypass middleware token check (SSR can't access localStorage) → direct API calls with stolen token
|
||||
- Perform unauthorized operations (delete users, change settings, etc.)
|
||||
|
||||
**Trust Boundary Rule:** Backend MUST re-check permission for EVERY admin API call, regardless of client claims.
|
||||
|
||||
**Fix Approach:**
|
||||
1. **URGENT:** Audit Django backend API (`qy_lty/lib/api/v1/admin/`) to confirm all endpoints validate user role/permissions
|
||||
2. Document in CLAUDE.md: "Client-side permission filters are UI courtesy only; backend MUST validate on all admin/ endpoints"
|
||||
3. Add explicit checks in frontend error handling for 403 Forbidden responses
|
||||
4. Consider adding request logging/monitoring to catch unauthorized attempts
|
||||
|
||||
**Priority:** CRITICAL - security vulnerability if backend doesn't re-check
|
||||
|
||||
---
|
||||
|
||||
### Token Storage in localStorage (XSS Vulnerability)
|
||||
|
||||
**Issue:** Auth token stored in `localStorage` instead of secure HttpOnly cookie; vulnerable to XSS.
|
||||
|
||||
**Files:**
|
||||
- `lib/api/auth.ts:48-59` — Stores token in localStorage AND sets js-cookie
|
||||
- `lib/api/client.ts:21-22` — Reads token from localStorage in request interceptor
|
||||
- `middleware.ts:32` — Token read from Cookie (correct), but localStorage is fallback
|
||||
|
||||
**Current Implementation:**
|
||||
```typescript
|
||||
// Stored in BOTH localStorage and cookie
|
||||
localStorage.setItem("auth_token", token); // XSS risk
|
||||
Cookies.set("auth_token", token, { expires: 7, path: '/' }); // No HttpOnly flag
|
||||
```
|
||||
|
||||
**Impact:**
|
||||
- XSS attack can steal token from localStorage
|
||||
- js-cookie (js-cookie, no HttpOnly flag) also vulnerable to XSS
|
||||
- 7-day expiry without rotation = long XSS window
|
||||
- `localStorage.user_role` also stored unencrypted (attackable)
|
||||
|
||||
**Proper Secure Pattern:**
|
||||
- HttpOnly, Secure, SameSite cookies (set by backend during login)
|
||||
- Token never exposed to JavaScript
|
||||
- Client reads from Bearer token response, never stores in accessible storage
|
||||
|
||||
**Fix Approach:**
|
||||
1. Backend should set HttpOnly cookie during login response
|
||||
2. Remove localStorage token storage entirely
|
||||
3. Keep only sealed HTTP-only cookie for auth
|
||||
4. If SSR requires token access, use secure session storage (not localStorage)
|
||||
5. Add CSP (Content-Security-Policy) headers to mitigate XSS
|
||||
|
||||
**Priority:** High - significant security risk
|
||||
|
||||
---
|
||||
|
||||
### No Token Rotation / Refresh Strategy
|
||||
|
||||
**Issue:** Token expires in 7 days, no refresh token or rotation mechanism visible.
|
||||
|
||||
**Files:**
|
||||
- `lib/api/auth.ts:63` — `Cookies.set("auth_token", token, { expires: 7, path: '/' })`
|
||||
- `lib/api/client.ts:54-60` — 401 handler just redirects to /login, doesn't attempt refresh
|
||||
|
||||
**Impact:**
|
||||
- User activity > 7 days → auto-logout (UX degradation)
|
||||
- No short-lived access token + long-lived refresh token pattern
|
||||
- Stolen token valid for full 7 days
|
||||
- No token revocation (logout doesn't invalidate server-side if stateless JWT)
|
||||
|
||||
**Fix Approach:**
|
||||
1. Implement short-lived access token (15-30 min) + refresh token (7-14 days)
|
||||
2. Refresh interceptor: if 401, attempt token refresh before redirecting to /login
|
||||
3. Backend session should blacklist tokens on logout
|
||||
4. Document token lifecycle in CLAUDE.md
|
||||
|
||||
**Priority:** Medium - acceptable for internal admin tool, but should improve for production
|
||||
|
||||
---
|
||||
|
||||
### No Input Validation on File Upload
|
||||
|
||||
**Issue:** File uploads accept any mimetype/size without validation.
|
||||
|
||||
**Files:**
|
||||
- `lib/api/upload.ts:25-65` — `uploadFile()` accepts File object, no size/type checks in client
|
||||
- `components/ui/file-upload.tsx` — Likely has upload UI, needs validation
|
||||
|
||||
**Impact:**
|
||||
- Malicious files (executables, oversized) may reach backend
|
||||
- Backend must validate, but client-side check is first defense
|
||||
- No maximum file size enforced client-side
|
||||
|
||||
**Fix Approach:**
|
||||
1. Add file size limits (e.g., max 50MB)
|
||||
2. Whitelist MIME types (images: image/jpeg, image/png, image/webp only)
|
||||
3. Show user error before upload attempt
|
||||
4. Verify backend enforces same limits
|
||||
|
||||
**Priority:** Medium - backend should catch it, but client should be first defense
|
||||
|
||||
---
|
||||
|
||||
## Fragile Areas
|
||||
|
||||
### Large Pages with Mixed Concerns (1000+ line files)
|
||||
|
||||
**Issue:** Several pages handle data fetching, UI rendering, and business logic in single component.
|
||||
|
||||
**Large Files:**
|
||||
- `app/affinity/page.tsx` (1005 lines) — Affinity rules, levels, interactions all in one page
|
||||
- `components/ui/sidebar.tsx` (763 lines) — Navigation logic, permission filtering, logout in one component
|
||||
- `app/songs/[id]/page.tsx` (651 lines) — Song detail page, editing, deletion mixed
|
||||
- `app/songs/page.tsx` (644 lines) — Song list, search, pagination, CRUD operations
|
||||
|
||||
**Fragility Risks:**
|
||||
- Hard to test individual business logic
|
||||
- Changes to one feature (e.g., affinity rules) risk breaking another (levels)
|
||||
- Difficult to reuse UI logic across pages
|
||||
- High cognitive load for maintenance
|
||||
|
||||
**Safe Modification Pattern:**
|
||||
1. Extract data-fetching logic to `lib/api/` hooks or services
|
||||
2. Create smaller sub-components (e.g., `<AffinityRuleSection>`, `<AffinityLevelSection>`)
|
||||
3. Use custom hooks for state management per feature
|
||||
4. Keep pages as composition only
|
||||
|
||||
**Example Refactor:**
|
||||
```typescript
|
||||
// Current: 1005-line page
|
||||
// After:
|
||||
export default function AffinityPage() {
|
||||
return (
|
||||
<DashboardShell>
|
||||
<DashboardHeader />
|
||||
<AffinityRulesSection />
|
||||
<AffinityLevelsSection />
|
||||
<InteractionLogsSection />
|
||||
</DashboardShell>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
**Priority:** Medium - refactor as part of feature changes, not urgent
|
||||
|
||||
---
|
||||
|
||||
### No Error Boundaries (Next.js error.tsx Files Missing)
|
||||
|
||||
**Issue:** No error boundary components for graceful error handling in App Router.
|
||||
|
||||
**Files:**
|
||||
- `app/` has NO `error.tsx` files
|
||||
- Only some leaf routes have `loading.tsx` (e.g., `app/outfits/loading.tsx`)
|
||||
|
||||
**Current Error Handling:**
|
||||
- Pages catch errors in `try/catch`, show toast notifications
|
||||
- Uncaught errors propagate, may show raw error UI
|
||||
- No fallback UI for API failures
|
||||
|
||||
**Impact:**
|
||||
- User sees blank page or browser error on network failure
|
||||
- No recovery mechanism (retry button, fallback content)
|
||||
- Log data lost if error page crashes
|
||||
|
||||
**Next.js Error Boundary Pattern:**
|
||||
```typescript
|
||||
// app/error.tsx (catches errors in all children)
|
||||
'use client'
|
||||
export default function Error({ error, reset }) {
|
||||
return (
|
||||
<div>
|
||||
<h1>Something went wrong</h1>
|
||||
<button onClick={() => reset()}>Try again</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
**Fix Approach:**
|
||||
1. Add `app/error.tsx` for app-level errors
|
||||
2. Add segment-level `error.tsx` for feature-specific handling (e.g., `app/outfits/error.tsx`)
|
||||
3. Keep try/catch for API calls, render fallback UI
|
||||
4. Add `app/not-found.tsx` for 404 routes
|
||||
|
||||
**Priority:** Low - pages have basic error handling via toasts, but UX could improve
|
||||
|
||||
---
|
||||
|
||||
### Hardcoded API Base URL Fallback
|
||||
|
||||
**Issue:** API client falls back to hardcoded URL if env var not set.
|
||||
|
||||
**Files:**
|
||||
- `lib/api/client.ts:9` — `API_BASE_URL = process.env.NEXT_PUBLIC_API_BASE_URL || "http://localhost:8000/api"`
|
||||
|
||||
**Risk:**
|
||||
- If `NEXT_PUBLIC_API_BASE_URL` not set in production, silently uses localhost
|
||||
- Requests fail silently in production, confusing to debug
|
||||
- No warning log when fallback is used
|
||||
|
||||
**Fix Approach:**
|
||||
1. Remove fallback; throw error if env var not set at build time
|
||||
2. Use `if (!process.env.NEXT_PUBLIC_API_BASE_URL) throw new Error("...")`
|
||||
3. Document required env vars in deployment guide
|
||||
|
||||
**Priority:** Low - environmental issue, unlikely in production if properly configured
|
||||
|
||||
---
|
||||
|
||||
## Performance Bottlenecks
|
||||
|
||||
### No Memoization on Permission Checks
|
||||
|
||||
**Issue:** `hasPermission()`, `getUserRole()` called on every render without memoization.
|
||||
|
||||
**Files:**
|
||||
- `components/sidebar.tsx:102-104` — Calls `hasPermission(item.module)` in `.filter()` on every render
|
||||
- `lib/permissions.ts:65-87` — No caching; reads localStorage every time
|
||||
|
||||
**Current Pattern:**
|
||||
```typescript
|
||||
const visibleAiItems = mounted ? aiMenuItems.filter((item) => hasPermission(item.module)) : []
|
||||
```
|
||||
|
||||
**Impact:**
|
||||
- Sidebar re-renders, re-filters items, re-reads localStorage unnecessarily
|
||||
- Minimal impact on small arrays (3-15 items), but scales poorly
|
||||
|
||||
**Fix Approach:**
|
||||
1. Memoize `getUserRole()` result in sidebar:
|
||||
```typescript
|
||||
const userRole = useMemo(() => getUserRole(), [mounted])
|
||||
const visibleItems = useMemo(() =>
|
||||
aiMenuItems.filter(item => hasPermission(item.module)),
|
||||
[userRole]
|
||||
)
|
||||
```
|
||||
2. Or move to custom hook: `const { role, allowedModules } = usePermissions()`
|
||||
|
||||
**Priority:** Low - not a visible performance issue, but good practice
|
||||
|
||||
---
|
||||
|
||||
## Cross-Repo Coupling Risks
|
||||
|
||||
### Strong Runtime Dependency on qy_lty Backend
|
||||
|
||||
**Issue:** Front-end depends on specific API contract from `qy_lty/` Django backend; contract drift risk.
|
||||
|
||||
**Files:**
|
||||
- `lib/api/users.ts:6-21` — Maps backend `ParadiseUser` to `User` type (field mapping)
|
||||
- `lib/api/outfits.ts` (not read, but expected) — Maps backend outfit schema
|
||||
- ALL API modules expect specific response structure from `/api/v1/admin/*`
|
||||
|
||||
**Coupling Points:**
|
||||
- Backend field names: `username`, `email`, `phone_number`, `date_joined`, `last_login`, `is_superuser`, `is_staff`
|
||||
- Response structure: `{ results: [...], count: number }` (Django REST Framework default)
|
||||
- Endpoint paths: `/v1/admin/login/`, `/user/`, `/common/upload/`
|
||||
- Token storage key in Redis: `admin_token:{token}` (per CLAUDE.md)
|
||||
|
||||
**Risk Scenario:**
|
||||
1. Backend dev changes `/v1/admin/users/` response format
|
||||
2. Frontend still sends old field names or expects old response shape
|
||||
3. Silent failures or type mismatches
|
||||
4. Detected late in testing or production
|
||||
|
||||
**Mitigation in Place:**
|
||||
- CLAUDE.md (line 34) requires both repos update `docs/修改记录.md` when contract changes
|
||||
- But no automated contract testing (OpenAPI, TypeScript codegen)
|
||||
|
||||
**Fix Approach:**
|
||||
1. **Shared types/API contract** (OpenAPI/GraphQL schema) versioned in git
|
||||
2. Generate TypeScript types from backend OpenAPI spec (e.g., with Swagger-Codegen)
|
||||
3. Add pre-commit hook to detect schema changes
|
||||
4. Document in CLAUDE.md: "Any API response structure change requires updating both repos' docs"
|
||||
|
||||
**Priority:** Medium - mitigated by manual process, but automatable
|
||||
|
||||
---
|
||||
|
||||
## Dependencies at Risk
|
||||
|
||||
### No Enforcement of Dependency Updates
|
||||
|
||||
**Issue:** Many dependencies pinned to `latest`, creating unpredictable updates.
|
||||
|
||||
**Files:**
|
||||
- `package.json:12, 56, 63` — `"latest"` version specifiers
|
||||
|
||||
**Current Versions Using "latest":**
|
||||
```json
|
||||
"@hookform/resolvers": "latest",
|
||||
"react-hook-form": "latest",
|
||||
"recharts": "latest",
|
||||
"zod": "latest"
|
||||
```
|
||||
|
||||
**Impact:**
|
||||
- `npm install` at different times installs different major versions
|
||||
- Breaking changes in `react-hook-form` or `recharts` not caught by lock file
|
||||
- Tests pass locally, fail in CI if new major version published
|
||||
|
||||
**Fix Approach:**
|
||||
1. Replace all `"latest"` with specific versions (e.g., `"^7.0.0"` for caret range)
|
||||
2. Use Dependabot or renovate to automate safe updates
|
||||
3. Pin devDependencies to exact versions
|
||||
|
||||
**Example:**
|
||||
```json
|
||||
"@hookform/resolvers": "^3.3.4",
|
||||
"react-hook-form": "^7.51.0",
|
||||
"recharts": "^2.12.0",
|
||||
"zod": "^3.22.0"
|
||||
```
|
||||
|
||||
**Priority:** Medium - not urgent, but good practice
|
||||
|
||||
---
|
||||
|
||||
### Unused Dependencies or Overlapping Packages
|
||||
|
||||
**Issue:** Unclear if all installed packages are actively used.
|
||||
|
||||
**Potential Issues:**
|
||||
- Multiple notification libraries: `sonner` + `@radix-ui/react-toast` (which is used?)
|
||||
- Figure out which is preferred and remove duplication
|
||||
|
||||
**Files:**
|
||||
- `package.json:36, 59` — Both toast packages listed
|
||||
|
||||
**Fix Approach:**
|
||||
1. Audit each dependency in `package.json`
|
||||
2. Remove unused packages
|
||||
3. Consolidate duplicate functionality
|
||||
|
||||
**Priority:** Low - cleanup task
|
||||
|
||||
---
|
||||
|
||||
## Test Coverage Gaps
|
||||
|
||||
### No Tests Found
|
||||
|
||||
**Issue:** No Jest/Vitest configuration or test files detected in codebase.
|
||||
|
||||
**Expected Test Files:**
|
||||
- `*.test.ts`, `*.spec.ts`, `*.test.tsx`, `*.spec.tsx` not found
|
||||
- No `jest.config.*`, `vitest.config.*`
|
||||
|
||||
**Impact:**
|
||||
- Complex logic (permission matrix, API adapters) not tested
|
||||
- Refactoring risky (no regression protection)
|
||||
- Onboarding new developers without safety net
|
||||
- No confidence in critical paths (auth, user management)
|
||||
|
||||
**Critical Areas Needing Tests:**
|
||||
- `lib/permissions.ts` — Permission matrix logic (many branches)
|
||||
- `lib/api/client.ts` — Request/response interceptors (error handling paths)
|
||||
- `lib/api/users.ts` — User mapping logic (backend schema mismatches)
|
||||
- Custom hooks in pages (data fetching, state management)
|
||||
|
||||
**Fix Approach:**
|
||||
1. Set up Jest or Vitest (Vitest recommended for ESM + modern tooling)
|
||||
2. Create basic test for `lib/permissions.ts`:
|
||||
```typescript
|
||||
// lib/permissions.test.ts
|
||||
describe('Permission checks', () => {
|
||||
it('should allow super admin all modules', () => {
|
||||
// Mock localStorage with 超级管理员
|
||||
expect(hasPermission('users')).toBe(true)
|
||||
expect(hasPermission('ai-model')).toBe(true)
|
||||
})
|
||||
|
||||
it('should restrict content admin to content modules', () => {
|
||||
// Mock localStorage with 内容管理员
|
||||
expect(hasPermission('users')).toBe(false)
|
||||
expect(hasPermission('outfits')).toBe(true)
|
||||
})
|
||||
})
|
||||
```
|
||||
3. Aim for 80%+ coverage of critical API functions
|
||||
|
||||
**Priority:** Medium - important for long-term maintainability
|
||||
|
||||
---
|
||||
|
||||
## Missing Critical Features
|
||||
|
||||
### No Logout Token Revocation
|
||||
|
||||
**Issue:** `logout()` clears client-side storage but doesn't invalidate server-side token.
|
||||
|
||||
**Files:**
|
||||
- `lib/api/auth.ts:108-131` — `logout()` calls API endpoint, then clears localStorage
|
||||
|
||||
**Problem:**
|
||||
- If using stateless JWT, backend has no record of logout
|
||||
- Token remains valid until expiry (7 days)
|
||||
- Attacker with stolen token can still use it after target user "logs out"
|
||||
|
||||
**Expected Behavior:**
|
||||
- Server should blacklist token on logout (Redis blacklist or DB flag)
|
||||
- Verify endpoint `/v1/admin/logout/` actually invalidates token
|
||||
|
||||
**Fix Approach:**
|
||||
1. Audit backend logout endpoint to confirm token is invalidated
|
||||
2. Add token blacklist to backend (Redis: token → blacklisted, TTL=7 days)
|
||||
3. Check token against blacklist on every request
|
||||
4. Document in CLAUDE.md
|
||||
|
||||
**Priority:** High - security issue if not handled on backend
|
||||
|
||||
---
|
||||
|
||||
### No Rate Limiting on Client (Brute Force Risk)
|
||||
|
||||
**Issue:** Login page may be vulnerable to brute force attacks.
|
||||
|
||||
**Files:**
|
||||
- `app/login/page.tsx` — Likely has login form
|
||||
- `lib/api/auth.ts:25-40` — No rate limiting before API call
|
||||
|
||||
**Impact:**
|
||||
- Attacker can spam login attempts to guess password
|
||||
- Backend must implement rate limiting, but client can't spam if it has throttling
|
||||
|
||||
**Fix Approach:**
|
||||
1. Client-side: Disable login button for 30 seconds after failed attempt
|
||||
2. Backend: Implement account lockout (3 failed attempts = 10 min lockout)
|
||||
3. Monitor login failures in logs
|
||||
|
||||
**Priority:** Medium - backend responsibility, but client should assist
|
||||
|
||||
---
|
||||
|
||||
## Build Configuration Issues
|
||||
|
||||
### Next.js Standalone Output May Have Deployment Pitfalls
|
||||
|
||||
**Issue:** Standalone mode used, but edge case behaviors not fully documented.
|
||||
|
||||
**Files:**
|
||||
- `next.config.mjs:30` — `output: 'standalone'`
|
||||
|
||||
**What Standalone Does:**
|
||||
- Bundles all dependencies into `.next/standalone` folder
|
||||
- Reduces Docker image size (~100MB vs ~500MB)
|
||||
- Requires explicit `COPY .next .next` in Dockerfile (correctly done)
|
||||
|
||||
**Potential Issues:**
|
||||
- Public assets not automatically copied; must COPY separately (done in Dockerfile:44-45)
|
||||
- `next.config.mjs` must be manually copied (done in Dockerfile:46)
|
||||
- Environment variables must be passed at runtime, not build time
|
||||
- Debugging bundle issues harder (bundled deps not in node_modules)
|
||||
|
||||
**Current Dockerfile (lines 44-46) handles this correctly:**
|
||||
```dockerfile
|
||||
COPY --from=builder /app/.next ./.next
|
||||
COPY --from=builder /app/public ./public
|
||||
COPY --from=builder /app/next.config.mjs ./
|
||||
```
|
||||
|
||||
**Risk:**
|
||||
- If dependencies add new output files in future versions, they won't be copied
|
||||
- Node version mismatch between builder (22.10.0) and runner (22.10.0) is correct but fragile
|
||||
|
||||
**Fix Approach:**
|
||||
1. Document standalone output caveats in deployment guide
|
||||
2. Test Docker image monthly to ensure no drift in builder/runner
|
||||
3. Consider base image pinning: `FROM node:22.10.0-alpine@sha256:...`
|
||||
|
||||
**Priority:** Low - current setup is correct, just needs documentation
|
||||
|
||||
---
|
||||
|
||||
### No Pre-commit Hooks or Husky Setup
|
||||
|
||||
**Issue:** No automated checks before commits; developers can commit broken code.
|
||||
|
||||
**Files:**
|
||||
- No `.husky/` directory
|
||||
- No `pre-commit` config
|
||||
- CLAUDE.md doesn't mention hook setup
|
||||
|
||||
**Risk:**
|
||||
- Developer commits console.log, broken types, or lint errors
|
||||
- CI catches them later, but feedback loop is slow
|
||||
- No enforcement of modification record updates (CLAUDE.md requirement)
|
||||
|
||||
**Fix Approach:**
|
||||
1. Install husky: `npm install husky --save-dev`
|
||||
2. Setup hook: `npx husky install`
|
||||
3. Create `.husky/pre-commit`:
|
||||
```bash
|
||||
#!/bin/sh
|
||||
npm run lint && npm run type-check
|
||||
```
|
||||
4. Create `.husky/prepare-commit-msg` to remind user about docs/修改记录.md
|
||||
|
||||
**Priority:** Low - improvement for team workflow
|
||||
|
||||
---
|
||||
|
||||
## Summary Table
|
||||
|
||||
| Category | Issue | Severity | Fix Effort |
|
||||
|----------|-------|----------|-----------|
|
||||
| Security | Client-only permissions (backend trust) | CRITICAL | High |
|
||||
| Security | Token in localStorage (XSS) | High | Medium |
|
||||
| Security | No token revocation on logout | High | High |
|
||||
| Tech Debt | Lock file conflicts | High | Low |
|
||||
| Tech Debt | Build ignores TS/ESLint errors | High | Low |
|
||||
| Tech Debt | Console logging in production | High | Low |
|
||||
| Fragility | Large 1000+ line pages | Medium | Medium |
|
||||
| Performance | No memoization on permissions | Low | Low |
|
||||
| Testing | Zero test coverage | Medium | High |
|
||||
| Build | Standalone output not fully documented | Low | Low |
|
||||
|
||||
---
|
||||
|
||||
*Concerns audit: 2026-05-07*
|
||||
210
qy-lty-admin/.planning/codebase/CONVENTIONS.md
Normal file
210
qy-lty-admin/.planning/codebase/CONVENTIONS.md
Normal file
@ -0,0 +1,210 @@
|
||||
# 代码规范
|
||||
|
||||
**分析日期:** 2026-05-07
|
||||
|
||||
## 命名规范
|
||||
|
||||
**文件命名:**
|
||||
- 组件文件:PascalCase + `.tsx` 扩展名(如 `DashboardShell.tsx`、`AddOutfitDialog.tsx`)
|
||||
- 工具函数/库文件:kebab-case + `.ts` 扩展名(如 `error-handler.ts`、`client.ts`)
|
||||
- 特殊情况:对话框类组件使用 kebab-case(如 `add-outfit-dialog.tsx`、`delete-confirmation-dialog.tsx`)
|
||||
|
||||
**函数命名:**
|
||||
- 使用 camelCase(如 `handleLogin`、`fetchOutfits`、`mapBackendOutfit`)
|
||||
- API 函数:动词 + 名词,camelCase(如 `getOutfits`、`createOutfit`、`updateOutfit`、`deleteOutfit`)
|
||||
- 事件处理函数:`handle` + 事件名(如 `handleSubmit`、`handleChange`、`handleSendVerificationCode`)
|
||||
- 工具函数:动词 + 目标(如 `formatDate`、`toDisplayOutfit`)
|
||||
|
||||
**变量命名:**
|
||||
- 状态变量:camelCase(如 `email`、`password`、`isLoading`、`selectedOutfit`)
|
||||
- 布尔值:`is` 或 `has` 前缀(如 `isLoading`、`isSubmitting`、`hasPermission`)
|
||||
- 常量:UPPER_SNAKE_CASE(如 `TOAST_LIMIT`、`TOAST_REMOVE_DELAY`、`API_BASE_URL`)
|
||||
|
||||
**类型/接口命名:**
|
||||
- PascalCase(如 `EmailLoginResponse`、`DashboardShellProps`、`ApiResponse<T>`)
|
||||
- Props 接口:`组件名Props` 后缀(如 `ButtonProps`、`DashboardShellProps`)
|
||||
- 联合类型:`RoleName`、`PermissionModule`
|
||||
|
||||
## 代码样式
|
||||
|
||||
**格式化:**
|
||||
- 使用 TypeScript strict 模式(`tsconfig.json` 中 `"strict": true`)
|
||||
- 目标:ES6(`target: "ES6"`)
|
||||
- 模块化解析:`bundler` 模式(用于 Next.js)
|
||||
- 无 ESLint/Prettier 配置文件(`next.config.mjs` 中 `eslint.ignoreDuringBuilds: true`)
|
||||
- 代码风格通过 TypeScript 编译器和 Next.js 内置检查
|
||||
|
||||
**缩进和空格:**
|
||||
- 使用 2 空格缩进(根据代码库一致性)
|
||||
- 函数声明和块语句间使用一致空格
|
||||
|
||||
**类型注解:**
|
||||
- 所有函数参数必须有类型注解
|
||||
- 所有变量应有类型注解(特别是导出的接口和公共函数返回值)
|
||||
- 使用 TypeScript 严格模式防止隐式 `any`
|
||||
|
||||
## 导入组织
|
||||
|
||||
**顺序:**
|
||||
1. React 和 Next.js 核心库(`import React from "react"`、`import { useState } from "react"`)
|
||||
2. Next.js 功能(`import { useRouter } from "next/navigation"`、`import Link from "next/link"`)
|
||||
3. 第三方库(`import axios from "axios"`、`import Cookies from "js-cookie"`)
|
||||
4. 本项目组件(`import { Button } from "@/components/ui/button"`)
|
||||
5. 本项目库和工具(`import { cn } from "@/lib/utils"`)
|
||||
6. 本项目 hooks(`import { useToast } from "@/components/ui/use-toast"`)
|
||||
7. 类型导入(`import type { ApiResponse } from "./client"`)
|
||||
|
||||
**路径别名:**
|
||||
- 项目配置 `@/*` 指向项目根目录
|
||||
- 组件:`@/components/...`
|
||||
- 库函数:`@/lib/...`
|
||||
- Hooks:`@/hooks/...`
|
||||
- 类型和接口:通过 `@/lib/api/types` 集中导入
|
||||
- UI 组件:`@/components/ui/...`
|
||||
|
||||
## 错误处理
|
||||
|
||||
**模式:**
|
||||
- 使用 `try-catch` 处理异步操作和 API 调用(见 `auth.ts`、`outfits.ts`)
|
||||
- 创建自定义错误类 `ApiError` 扩展 `Error`,包含 `status` 属性(见 `error-handler.ts` 第 46-54 行)
|
||||
- 统一的错误处理函数 `handleApiException()` 将不同类型的错误转换为用户友好的消息(见 `error-handler.ts` 第 79-90 行)
|
||||
- 错误信息映射表 `errorMessages` 定义特定错误代码(`USER_NOT_FOUND`、`ROLE_EXISTS` 等)的消息
|
||||
|
||||
**API 错误处理:**
|
||||
- 在 Axios 响应拦截器中处理 401 未授权错误:清除 token 并重定向到登录页面(见 `client.ts` 第 54-61 行)
|
||||
- 对 API 失败使用 Radix Toast 组件显示错误提示(见 `error-handler.ts` 第 69-76 行、第 93-100 行)
|
||||
- 统一的 `handleApiRequest()` 包装器函数处理成功/失败情况并自动显示 toast(见 `error-handler.ts` 第 102-144 行)
|
||||
|
||||
**日志:**
|
||||
- 使用 `console.log()`、`console.warn()`、`console.error()` 进行调试和错误追踪
|
||||
- API 请求/响应在拦截器中记录详细日志,包括 token 检查、请求头、响应状态(见 `client.ts` 第 20-66 行)
|
||||
- 使用 emoji 表情增强日志可读性(`🔍 Token检查`、`✅ Token已添加`、`❌ 响应错误`)
|
||||
|
||||
## 日志记录
|
||||
|
||||
**框架:** `console`(浏览器原生)
|
||||
|
||||
**使用规范:**
|
||||
- API 请求/响应日志在 Axios 拦截器中集中管理
|
||||
- 所有认证流程加日志(token 检查、保存、清除)
|
||||
- 错误信息带上上下文(URL、状态码、方法)
|
||||
- 生产环境应考虑减少日志或使用第三方服务(未配置)
|
||||
|
||||
## 注释
|
||||
|
||||
**何时写注释:**
|
||||
- JSDoc 风格注释用于公共 API 函数(见 `auth.ts` 第 5-7 行、第 24-30 行)
|
||||
- 注释解释"为什么"而不是"是什么"
|
||||
- 复杂业务逻辑或算法需要行内注释
|
||||
|
||||
**JSDoc/TSDoc 风格:**
|
||||
```typescript
|
||||
/**
|
||||
* 邮箱登录接口
|
||||
* @param email 邮箱
|
||||
* @param password 密码
|
||||
* @returns 包含token的响应
|
||||
*/
|
||||
export const emailLogin = async (email: string, password: string): Promise<EmailLoginResponse> => {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
**中文注释:** 所有注释使用中文(符合 CLAUDE.md 的语言偏好)
|
||||
|
||||
## 函数设计
|
||||
|
||||
**大小:**
|
||||
- 函数长度通常 20-50 行(见 `handleLogin` 在 `login/page.tsx` 第 28-63 行)
|
||||
- 超过 100 行的函数应考虑拆分(如 `AddOutfitDialog` 组件中的多步表单分离为 Tabs)
|
||||
|
||||
**参数:**
|
||||
- 单个参数优于多个参数
|
||||
- 对象参数用于选项(如 `handleApiRequest()` 的 options 对象,第 104-110 行)
|
||||
- 类型参数用于泛型函数(如 `handleResponse<T>()`、`ApiResponse<T>`)
|
||||
|
||||
**返回值:**
|
||||
- 异步函数返回 `Promise<T>`
|
||||
- 布尔查询函数返回 `boolean`(如 `hasPermission()`、`isAuthenticated()`)
|
||||
- 数据获取返回具体类型或 `null`(见 `getOutfit()` 返回 `Promise<Outfit>`)
|
||||
|
||||
## 模块设计
|
||||
|
||||
**导出:**
|
||||
- 公共 API 函数从 `lib/api/` 模块导出(如 `emailLogin`、`getOutfits`)
|
||||
- 类型从 `lib/api/types.ts` 集中导出
|
||||
- Utility 函数从 `lib/utils.ts` 导出(如 `cn()` 用于样式合并)
|
||||
|
||||
**Barrel 文件:**
|
||||
- `lib/api/index.ts` 作为 barrel 文件,导出所有 API 函数以便统一导入
|
||||
- 组件库采用 Radix UI + shadcn 风格,每个组件文件独立,无 barrel 文件
|
||||
|
||||
## React 和组件规范
|
||||
|
||||
**状态管理:**
|
||||
- 使用 React Hooks(`useState`、`useEffect`、`useCallback`)进行本地状态管理
|
||||
- 在客户端组件中集中管理表单状态(见 `add-outfit-dialog.tsx` 第 24-36 行)
|
||||
|
||||
**组件模式:**
|
||||
- 所有表单和交互组件用 `"use client"` 指令标记
|
||||
- Page 组件(`app/*/page.tsx`)使用 `"use client"` 支持交互
|
||||
- 使用 Radix UI 和 shadcn 风格组件库,组件文件在 `components/ui/`
|
||||
- Props 接口扩展原生 HTML 属性(如 `ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement>`)
|
||||
|
||||
**样式:**
|
||||
- 使用 Tailwind CSS 类名
|
||||
- 使用 `cn()` 工具函数(基于 `clsx` + `tailwind-merge`)合并条件样式(见 `button.tsx` 第 47 行)
|
||||
- 使用 `class-variance-authority` (CVA) 定义组件变体(见 `button.tsx` 第 7-34 行)
|
||||
- 颜色、间距、圆角通过 Tailwind 配置变量(CSS 变量)定义
|
||||
|
||||
## 表单与验证
|
||||
|
||||
**表单状态:**
|
||||
- 使用 `useState` 管理表单字段状态
|
||||
- 表单提交通过 `handleSubmit()` 函数处理(见 `add-outfit-dialog.tsx` 第 38-56 行)
|
||||
|
||||
**验证:**
|
||||
- 基本的客户端验证(如检查空值:`if (!formData.name || !formData.description)`)
|
||||
- React Hook Form + Zod 在 package.json 中定义但代码中未广泛使用
|
||||
- 服务器端验证通过 API 响应处理(返回 400/422 等错误状态)
|
||||
|
||||
**通知:**
|
||||
- 成功/错误提示使用 Sonner toast 或 Radix Toast(通过 `useToast()` hook)
|
||||
- toast 样式变体:`"default"`、`"destructive"`(见 `error-handler.ts` 第 71-75 行)
|
||||
|
||||
## 权限控制
|
||||
|
||||
**权限检查:**
|
||||
- 运行时权限检查通过 `hasPermission(module)` 函数(见 `permissions.ts` 第 85-87 行)
|
||||
- 权限矩阵定义在 `PERMISSION_MATRIX` 对象,映射角色到模块列表
|
||||
- 路由保护:中间件 `middleware.ts` 检查 cookie 中的 token,无 token 重定向到登录
|
||||
- 组件级权限检查:`DashboardShell` 使用 `hasPathPermission()` 处理访问拒绝(见 `dashboard-shell.tsx` 第 23-24 行)
|
||||
|
||||
**角色存储:**
|
||||
- 登录后在 `localStorage` 中存储 `user_role` 字符串(见 `auth.ts` 第 58 行)
|
||||
- 超级管理员标识存储为 `is_superuser`(见 `auth.ts` 第 53 行)
|
||||
- token 同时存储在 `localStorage` 和 Cookie 中(见 `auth.ts` 第 49、63 行)
|
||||
|
||||
## 异步操作
|
||||
|
||||
**模式:**
|
||||
- 使用 `async/await` 处理异步 API 调用
|
||||
- 加载状态通过 `isLoading` 布尔值跟踪(见 `login/page.tsx` 第 19、30 行)
|
||||
- 模拟延迟用于开发和测试(见 `client.ts` `simulateDelay()` 和 `mockResponse()` 函数)
|
||||
|
||||
## API 集成
|
||||
|
||||
**Axios 配置:**
|
||||
- 在 `lib/api/client.ts` 创建 Axios 实例,基础 URL 来自环境变量 `NEXT_PUBLIC_API_BASE_URL`
|
||||
- 请求拦截器自动注入 Authorization 头(Bearer token)
|
||||
- 响应拦截器处理 401 错误并触发重新登录
|
||||
- API 端点前缀:`/api/v1/admin/`
|
||||
|
||||
**适配器模式:**
|
||||
- 后端返回数据结构与前端显示结构不同
|
||||
- 使用 `mapBackendOutfit()` 等适配器函数转换数据(见 `outfits.ts` 第 5-22 行)
|
||||
- 适配器在 `lib/api/adapters.ts` 中集中管理
|
||||
|
||||
---
|
||||
|
||||
*规范分析日期:2026-05-07*
|
||||
182
qy-lty-admin/.planning/codebase/INTEGRATIONS.md
Normal file
182
qy-lty-admin/.planning/codebase/INTEGRATIONS.md
Normal file
@ -0,0 +1,182 @@
|
||||
# External Integrations
|
||||
|
||||
**Analysis Date:** 2026-05-07
|
||||
|
||||
## APIs & External Services
|
||||
|
||||
**qy_lty Backend (Django REST API):**
|
||||
- Primary backend for all business logic
|
||||
- URL: Base configured via `NEXT_PUBLIC_API_BASE_URL` (default: `http://localhost:8000/api`)
|
||||
- SDK/Client: Axios (custom instance with interceptors)
|
||||
- Auth: Bearer token via `Authorization: Bearer {token}` header
|
||||
- Primary modules consumed:
|
||||
- `/api/v1/admin/login/` - User authentication
|
||||
- `/api/v1/admin/logout/` - User logout
|
||||
- `/ai/bots/` - AI model CRUD
|
||||
- `/card/category/clothing/` - Outfit/clothing items
|
||||
- `/card/category/props/` - Props/accessories
|
||||
- `/card/category/home-decor/` - Home decoration items
|
||||
- `/card/category/food/` - Food items
|
||||
- `/music/songs/` - Song management
|
||||
- `/dances/` - Dance content
|
||||
- `/achievements/` - Achievement system
|
||||
- `/affinity/` - Affinity/favorability system
|
||||
- `/common/upload/` - File upload endpoint
|
||||
- `/common/upload/info/` - File metadata retrieval
|
||||
|
||||
**Request/Response Pattern:**
|
||||
```typescript
|
||||
// Axios configuration: lib/api/client.ts
|
||||
const apiClient = axios.create({
|
||||
baseURL: API_BASE_URL,
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
})
|
||||
|
||||
// Request interceptor: auto-injects token
|
||||
apiClient.interceptors.request.use((config) => {
|
||||
const token = localStorage.getItem('auth_token')
|
||||
if (token) {
|
||||
config.headers.Authorization = `Bearer ${token}`
|
||||
}
|
||||
return config
|
||||
})
|
||||
|
||||
// Response interceptor: handles 401 (redirect to /login)
|
||||
apiClient.interceptors.response.use(
|
||||
(response) => response,
|
||||
(error) => {
|
||||
if (error.response?.status === 401) {
|
||||
localStorage.removeItem('auth_token')
|
||||
window.location.href = '/login'
|
||||
}
|
||||
return Promise.reject(error)
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
## Data Storage
|
||||
|
||||
**Databases:**
|
||||
- No direct database connection from frontend
|
||||
- Backend (qy_lty) manages all persistent data (PostgreSQL/MySQL presumed)
|
||||
- Frontend uses in-memory mock data for fallback/demo scenarios only
|
||||
|
||||
**File Storage:**
|
||||
- Backend-managed via `/common/upload/` endpoint
|
||||
- Upload types supported:
|
||||
- **Images** (JPEG, PNG, GIF, WebP) - max 10MB
|
||||
- **Avatars** (JPEG, PNG) - max 2MB
|
||||
- **Audio** (MP3, WAV, OGG, AAC, FLAC, WMA, M4A) - max 20MB
|
||||
- **Animations** (MP4, AVI, MOV, WMV, FLV, GIF, Lottie JSON) - max 50MB
|
||||
- **General files** - multipart/form-data
|
||||
- Upload library: File API (FormData) via Axios
|
||||
- Progress tracking: `onUploadProgress` callback support
|
||||
|
||||
**Caching:**
|
||||
- Browser localStorage for:
|
||||
- `auth_token` - Authentication token (auto-removed on 401)
|
||||
- `is_superuser` - Superuser flag
|
||||
- `user_role` - User role string (used for permission checks)
|
||||
- `isLoggedIn` - Session state flag
|
||||
- No server-side caching configured (Redis presumed in backend qy_lty)
|
||||
|
||||
## Authentication & Identity
|
||||
|
||||
**Auth Provider:**
|
||||
- Custom implementation via qy_lty backend
|
||||
- Backend OAuth/token system: admin token key format `admin_token:{token}` (in Redis)
|
||||
|
||||
**Login Flow:**
|
||||
1. POST `/api/v1/admin/login/` with email + password
|
||||
2. Backend returns: `{ success, code, data: { token, is_superuser?, role? }, message }`
|
||||
3. Frontend stores token in localStorage + cookies (7-day expiry)
|
||||
4. All subsequent requests include `Authorization: Bearer {token}`
|
||||
5. On 401 response: clear tokens, redirect to `/login`
|
||||
|
||||
**Token Storage:**
|
||||
- Primary: `localStorage.auth_token` (checked on every request)
|
||||
- Secondary: `js-cookie` cookie `auth_token` (7-day expiry) for middleware access
|
||||
- Logout clears both storages
|
||||
|
||||
**Role-Based Access:**
|
||||
- Roles stored in localStorage: `user_role`
|
||||
- Permission matrix defined in `lib/permissions.ts`
|
||||
- Supported roles: 超级管理员, 内容管理员, AI模型管理员, 卡牌管理员, 查看者, 管理员
|
||||
- Module-level access control via `hasPermission()` and `hasPathPermission()` functions
|
||||
|
||||
**Protected Routes:**
|
||||
- Middleware: `middleware.ts` checks for token on protected paths
|
||||
- Protected paths: `/`, `/dashboard`, `/users`, `/roles`, `/ai-models`, `/outfits`, `/props`, `/songs`, `/settings`
|
||||
- Public paths: `/login`, `/register`, `/forgot-password` (no token required)
|
||||
|
||||
## Monitoring & Observability
|
||||
|
||||
**Error Tracking:**
|
||||
- Not detected - errors logged to console only
|
||||
- Error messages mapped in `lib/api/error-handler.ts`
|
||||
- Toast notifications via Sonner for user-facing errors
|
||||
|
||||
**Logs:**
|
||||
- Console logging (development-focused)
|
||||
- Request/response logging in Axios interceptors (logs token status, URLs, headers, status codes)
|
||||
- Client-side logging only (no centralized log aggregation)
|
||||
|
||||
## CI/CD & Deployment
|
||||
|
||||
**Hosting:**
|
||||
- Docker containerization: `Dockerfile` (multi-stage build)
|
||||
- Runtime: Node.js 22.10.0 Alpine Linux
|
||||
- Port: 3000
|
||||
- Command: `yarn start` (runs Next.js production server)
|
||||
|
||||
**CI Pipeline:**
|
||||
- Not detected in codebase (likely external to this repo)
|
||||
|
||||
**Build Output:**
|
||||
- Format: Next.js standalone (self-contained, no `node_modules` in runtime image)
|
||||
- Files included: `.next/standalone/`, `public/`
|
||||
- Size optimization: devDependencies not included in runner stage
|
||||
|
||||
## Environment Configuration
|
||||
|
||||
**Required env vars:**
|
||||
- `NEXT_PUBLIC_API_BASE_URL` - Backend API base URL (must be public, prefixed with `NEXT_PUBLIC_`)
|
||||
- Example: `http://localhost:8000/api` (development), `https://api.production.com/api` (production)
|
||||
|
||||
**Optional env vars:**
|
||||
- `NODE_ENV` - Set to `production` in Docker runner stage
|
||||
- `.env.local` - Overrides all other env files (gitignored)
|
||||
- `.env.development` - Dev-specific overrides
|
||||
- `.env.production` - Production-specific overrides
|
||||
|
||||
**Secrets location:**
|
||||
- Authentication tokens: browser localStorage + cookies
|
||||
- No API keys or credentials hardcoded in source
|
||||
- Environment variable `NEXT_PUBLIC_API_BASE_URL` is the sole configuration bridge to backend
|
||||
|
||||
## Webhooks & Callbacks
|
||||
|
||||
**Incoming:**
|
||||
- None detected
|
||||
- Backend (qy_lty) may have webhooks, but frontend is purely client-side consumer
|
||||
|
||||
**Outgoing:**
|
||||
- None detected
|
||||
- All communication is request-response (REST API calls to qy_lty)
|
||||
|
||||
## Cross-Repo Dependencies
|
||||
|
||||
**qy_lty Backend (Sibling Repo):**
|
||||
- Location: `C:\Users\admin\Desktop\Lila-Server\qy_lty\` (Django)
|
||||
- Contract: `/api/v1/admin/` endpoint suite
|
||||
- Shared concerns: Token format (`admin_token:{token}`), role names, permission structure
|
||||
- Change coordination required: Both `docs/修改记录.md` files must be updated when API contracts change
|
||||
|
||||
**Notes:**
|
||||
- Frontend is tightly coupled to backend API schema (no API versioning detected)
|
||||
- Backend controls: authentication, authorization, data persistence, file storage
|
||||
- Frontend is purely a UI/UX layer consuming backend HTTP APIs
|
||||
|
||||
---
|
||||
|
||||
*Integration audit: 2026-05-07*
|
||||
152
qy-lty-admin/.planning/codebase/STACK.md
Normal file
152
qy-lty-admin/.planning/codebase/STACK.md
Normal file
@ -0,0 +1,152 @@
|
||||
# Technology Stack
|
||||
|
||||
**Analysis Date:** 2026-05-07
|
||||
|
||||
## Languages
|
||||
|
||||
**Primary:**
|
||||
- TypeScript 5 - All application code, types, and configuration
|
||||
- React 19 - UI components and application logic (via Next.js)
|
||||
- CSS / Tailwind - Styling
|
||||
|
||||
**Secondary:**
|
||||
- JavaScript - Configuration files, optional runtime
|
||||
|
||||
## Runtime
|
||||
|
||||
**Environment:**
|
||||
- Node.js 22.10.0 (Alpine Linux) - as specified in Dockerfile
|
||||
|
||||
**Package Manager:**
|
||||
- Yarn (primary for Docker builds; npm/pnpm also supported locally)
|
||||
- Lockfiles: `package-lock.json`, `pnpm-lock.yaml`, `yarn.lock` present
|
||||
- Registry: npm registry (Dockerfile uses Taobao mirror `registry.npmmirror.com` for builds)
|
||||
|
||||
## Frameworks
|
||||
|
||||
**Core:**
|
||||
- Next.js 15.2.4 - App Router, React Server Components, standalone output mode
|
||||
- Configuration: `next.config.mjs`
|
||||
- Build output: standalone (optimized for Docker)
|
||||
- Experimental features: webpackBuildWorker, parallelServerBuildTraces, parallelServerCompiles
|
||||
|
||||
**Styling:**
|
||||
- Tailwind CSS 3.4.17 - Utility-first CSS framework
|
||||
- Animation plugin: `tailwindcss-animate` 1.0.7
|
||||
- Config: `tailwind.config.ts`
|
||||
- CSS Variables enabled for theming (light/dark mode)
|
||||
|
||||
**UI Components:**
|
||||
- Radix UI (15+ component libraries) - Headless, accessible primitives
|
||||
- Core: accordion, alert-dialog, avatar, checkbox, dialog, dropdown-menu, label, popover, select, tabs, toast, tooltip, etc.
|
||||
- shadcn/ui style components (local copies, not npm packages)
|
||||
- Config: `components.json`
|
||||
- Aliases: `@/components`, `@/components/ui`, `@/lib`, `@/hooks`
|
||||
|
||||
**Form Management:**
|
||||
- React Hook Form (latest) - Lightweight form state management
|
||||
- Zod (latest) - Schema validation and type-safe form schemas
|
||||
- @hookform/resolvers (latest) - Integration between React Hook Form and validation libraries
|
||||
|
||||
**Data Visualization:**
|
||||
- Recharts (latest) - React charting library built on D3.js
|
||||
- Used for dashboard analytics/metrics visualization
|
||||
|
||||
**Notifications & Toasts:**
|
||||
- Sonner 1.7.1 - Toast notification library
|
||||
- Radix UI Toast 1.2.4 - Low-level toast primitive (wrapped by use-toast)
|
||||
|
||||
**Navigation & Theming:**
|
||||
- next-themes 0.4.4 - Dark/light mode switching
|
||||
- lucide-react 0.454.0 - Icon library (240+ icons)
|
||||
|
||||
**HTTP Client:**
|
||||
- Axios 1.9.0 - Promise-based HTTP client
|
||||
- Configured with request/response interceptors in `lib/api/client.ts`
|
||||
- Automatic token injection via Authorization header
|
||||
- 401 error handling (token expiry redirect to /login)
|
||||
|
||||
**Utilities:**
|
||||
- js-cookie 3.0.5 - Cookie management (auth token persistence)
|
||||
- date-fns 4.1.0 - Date utility library
|
||||
- class-variance-authority 0.7.1 - Component variant utilities
|
||||
- clsx 2.1.1 - Conditional className merging
|
||||
- tailwind-merge 2.5.5 - Tailwind class conflict resolution
|
||||
- cmdk 1.0.4 - Command palette component
|
||||
- embla-carousel-react 8.5.1 - Carousel component
|
||||
- react-day-picker 8.10.1 - Calendar/date picker
|
||||
- input-otp 1.4.1 - OTP input component
|
||||
- react-resizable-panels 2.1.7 - Resizable panel layout
|
||||
- vaul 0.9.6 - Drawer component
|
||||
|
||||
## Testing & Development
|
||||
|
||||
**Type Checking:**
|
||||
- TypeScript 5 (strict mode enabled)
|
||||
- Type checking via `npm run lint`
|
||||
|
||||
**Linting:**
|
||||
- Next.js ESLint (build-time linting)
|
||||
- Config: ignored during builds (`eslint.ignoreDuringBuilds: true`)
|
||||
|
||||
**Development Server:**
|
||||
- Next.js dev server: `npm run dev` (default port 3000)
|
||||
|
||||
## Configuration
|
||||
|
||||
**Environment:**
|
||||
- Location: `.env.local` (local overrides), `.env.development`, `.env.production`, `.env.example`
|
||||
- Example file: `c:\Users\admin\Desktop\Lila-Server\qy-lty-admin\.env.example`
|
||||
- Key env vars:
|
||||
- `NEXT_PUBLIC_API_BASE_URL` - Backend API base URL (e.g., `http://localhost:8000/api`)
|
||||
- `NODE_ENV` - Set to `production` in Docker runtime
|
||||
|
||||
**TypeScript:**
|
||||
- Config: `tsconfig.json`
|
||||
- Strict mode: enabled
|
||||
- Path aliases: `@/*` maps to project root
|
||||
|
||||
**Next.js:**
|
||||
- Config: `next.config.mjs`
|
||||
- Output mode: `standalone` (self-contained app)
|
||||
- Images: unoptimized (external image optimization)
|
||||
- Build optimization: parallel webpack, server build traces, server compilation
|
||||
|
||||
## Platform Requirements
|
||||
|
||||
**Development:**
|
||||
- Node.js 22+ (or compatible version)
|
||||
- Package manager: yarn, npm, or pnpm
|
||||
- Environment variables: `.env.local` with `NEXT_PUBLIC_API_BASE_URL`
|
||||
|
||||
**Production:**
|
||||
- Docker runtime: Node.js 22.10.0 Alpine
|
||||
- Memory: Typical SPA requirements (~500MB minimum)
|
||||
- Port: 3000 (exposed in Dockerfile)
|
||||
- Build artifacts: `.next/standalone`, `public/`
|
||||
|
||||
## Build Process
|
||||
|
||||
**Development Build:**
|
||||
```bash
|
||||
npm install # Install dependencies
|
||||
npm run dev # Start dev server (hot reload)
|
||||
```
|
||||
|
||||
**Production Build:**
|
||||
```bash
|
||||
npm install # Install all dependencies
|
||||
npm run build # Next.js standalone build → .next/ directory
|
||||
npm run start # Start production server (requires .next/ + public/)
|
||||
```
|
||||
|
||||
**Docker Build (CI/CD):**
|
||||
- Multi-stage build (builder + runner)
|
||||
- Builder stage: installs full dependencies, runs `npm run build`
|
||||
- Runner stage: installs production-only dependencies, copies .next/ and public/
|
||||
- Yarn mirror: Taobao registry (`registry.npmmirror.com`)
|
||||
- Final CMD: `yarn start` (port 3000)
|
||||
|
||||
---
|
||||
|
||||
*Stack analysis: 2026-05-07*
|
||||
291
qy-lty-admin/.planning/codebase/STRUCTURE.md
Normal file
291
qy-lty-admin/.planning/codebase/STRUCTURE.md
Normal file
@ -0,0 +1,291 @@
|
||||
# Codebase Structure
|
||||
|
||||
**Analysis Date:** 2026-05-07
|
||||
|
||||
## Directory Layout
|
||||
|
||||
```
|
||||
qy-lty-admin/
|
||||
├── app/ # Next.js App Router pages & layouts
|
||||
│ ├── layout.tsx # Root layout with metadata
|
||||
│ ├── page.tsx # Dashboard homepage
|
||||
│ ├── login/page.tsx # Login page
|
||||
│ ├── register/page.tsx # Registration page
|
||||
│ ├── forgot-password/page.tsx # Password reset page
|
||||
│ ├── ai-model/page.tsx # AI Model management
|
||||
│ ├── outfits/ # Outfit content module
|
||||
│ │ ├── page.tsx # List view
|
||||
│ │ ├── [id]/page.tsx # Detail view
|
||||
│ │ ├── edit/[id]/page.tsx # Edit form
|
||||
│ │ └── loading.tsx # Loading skeleton
|
||||
│ ├── props/ # Props module (same structure as outfits)
|
||||
│ ├── home-decor/ # Home decor module
|
||||
│ ├── food/ # Food module
|
||||
│ ├── songs/ # Songs module
|
||||
│ ├── dances/ # Dances module
|
||||
│ ├── achievements/ # Achievements module
|
||||
│ ├── affinity/ # Affinity/favorability module
|
||||
│ ├── users/ # User management module
|
||||
│ ├── permissions/ # Permissions management module
|
||||
│ ├── settings/ # System settings module
|
||||
│ └── globals.css # Global Tailwind + custom styles
|
||||
├── components/ # Reusable React components
|
||||
│ ├── ui/ # Shadcn-style primitive components (copy-paste)
|
||||
│ │ ├── button.tsx
|
||||
│ │ ├── card.tsx
|
||||
│ │ ├── dialog.tsx
|
||||
│ │ ├── input.tsx
|
||||
│ │ ├── label.tsx
|
||||
│ │ ├── table.tsx
|
||||
│ │ ├── tabs.tsx
|
||||
│ │ ├── select.tsx
|
||||
│ │ ├── form.tsx
|
||||
│ │ ├── alert-dialog.tsx
|
||||
│ │ └── ... (30+ primitive components)
|
||||
│ ├── outfits/ # Outfit feature components
|
||||
│ │ ├── OutfitsList.tsx
|
||||
│ │ ├── OutfitsTable.tsx
|
||||
│ │ ├── AddOutfitDialog.tsx
|
||||
│ │ └── OutfitDetailCard.tsx
|
||||
│ ├── songs/ # Songs feature components
|
||||
│ ├── dances/ # Dances feature components
|
||||
│ ├── food/ # Food feature components
|
||||
│ ├── home-decor/ # Home decor feature components
|
||||
│ ├── props/ # Props feature components
|
||||
│ ├── affinity/ # Affinity feature components
|
||||
│ ├── achievements/ # Achievements feature components
|
||||
│ ├── permissions/ # Permissions feature components
|
||||
│ ├── users/ # Users feature components
|
||||
│ ├── sidebar.tsx # Main navigation sidebar (client)
|
||||
│ ├── dashboard-shell.tsx # Layout wrapper with permission checks (client)
|
||||
│ ├── dashboard-header.tsx # Page header component
|
||||
│ ├── overview.tsx # Dashboard overview chart
|
||||
│ ├── recent-activity.tsx # Recent activity feed
|
||||
│ ├── stat-card.tsx # KPI stat card
|
||||
│ ├── delete-confirmation-dialog.tsx
|
||||
│ ├── publish-confirmation-dialog.tsx
|
||||
│ ├── theme-provider.tsx
|
||||
│ └── add-outfit-dialog.tsx # Shared modal for outfit creation
|
||||
├── lib/ # Utilities, types, API clients
|
||||
│ ├── api/
|
||||
│ │ ├── client.ts # Axios instance + interceptors + interfaces
|
||||
│ │ ├── auth.ts # Login, logout, token management
|
||||
│ │ ├── outfits.ts # Outfit CRUD operations
|
||||
│ │ ├── props.ts # Props CRUD operations
|
||||
│ │ ├── songs.ts # Songs CRUD operations
|
||||
│ │ ├── dances.ts # Dances CRUD operations
|
||||
│ │ ├── food.ts # Food CRUD operations
|
||||
│ │ ├── home-decor.ts # Home decor CRUD operations
|
||||
│ │ ├── achievements.ts # Achievements CRUD operations
|
||||
│ │ ├── affinity.ts # Affinity system API
|
||||
│ │ ├── ai-models.ts # AI model management
|
||||
│ │ ├── users.ts # User management
|
||||
│ │ ├── roles.ts # Role/permission management
|
||||
│ │ ├── error-handler.ts # Centralized error handling
|
||||
│ │ ├── adapters.ts # Response schema mappers
|
||||
│ │ ├── types.ts # Shared API type definitions
|
||||
│ │ ├── token-debug.ts # Token debugging utilities
|
||||
│ │ ├── card.ts # Card-related API
|
||||
│ │ └── index.ts # Export barrel
|
||||
│ ├── permissions.ts # RBAC matrix + permission utilities
|
||||
│ └── utils.ts # Utility functions (Tailwind cn)
|
||||
├── hooks/ # Custom React hooks
|
||||
│ ├── use-mobile.tsx # Mobile breakpoint detection hook
|
||||
│ └── use-toast.ts # Toast notification hook
|
||||
├── middleware.ts # Next.js route protection middleware
|
||||
├── public/ # Static assets
|
||||
├── styles/ # Additional stylesheets (if any)
|
||||
├── package.json # Dependencies & scripts
|
||||
├── tsconfig.json # TypeScript configuration
|
||||
├── tailwind.config.ts # Tailwind CSS configuration
|
||||
├── postcss.config.mjs # PostCSS configuration
|
||||
├── next.config.mjs # Next.js configuration
|
||||
├── Dockerfile # Docker build (yarn + Taobao mirror)
|
||||
├── docker-compose.yml # Local dev Docker Compose
|
||||
├── README.md # Project overview & permissions matrix
|
||||
├── CLAUDE.md # Codebase instructions for Claude
|
||||
└── docs/
|
||||
└── 修改记录.md # Change log (Chinese)
|
||||
```
|
||||
|
||||
## Directory Purposes
|
||||
|
||||
**app/:**
|
||||
- Purpose: Next.js App Router pages and layouts; each module has a folder with page.tsx + optional nested routes
|
||||
- Contains: Page components ("use client"), form pages, dynamic routes with [id]
|
||||
- Key files: `page.tsx` (list), `[id]/page.tsx` (detail), `edit/[id]/page.tsx` (edit form), `loading.tsx` (skeleton)
|
||||
|
||||
**components/:**
|
||||
- Purpose: Reusable React components organized by feature
|
||||
- Contains: UI primitives (components/ui/), feature-specific components (components/[feature]/), layout components (sidebar, shell)
|
||||
- Key pattern: Feature folders mirror app module structure; components within are sub-components (no subfolders unless complex)
|
||||
|
||||
**components/ui/:**
|
||||
- Purpose: Shadcn-style copy-paste UI primitives
|
||||
- Contains: 30+ Base UI components (Button, Card, Dialog, Form, Table, Tabs, Select, etc.)
|
||||
- Note: These are copied into the repo, not npm packages; safe to modify directly
|
||||
|
||||
**lib/api/:**
|
||||
- Purpose: HTTP client and API communication layer
|
||||
- Contains: Axios singleton instance, interceptors, module-specific API functions, response adapters, type definitions
|
||||
- Pattern: One file per backend module (outfits.ts, songs.ts, etc.) + shared client.ts
|
||||
|
||||
**lib/:**
|
||||
- Purpose: Shared utilities, types, configuration
|
||||
- Contains: permissions.ts (RBAC logic), utils.ts (Tailwind helpers), api/ subfolder
|
||||
|
||||
**hooks/:**
|
||||
- Purpose: Custom React hooks
|
||||
- Contains: use-mobile (responsive detection), use-toast (notification system)
|
||||
|
||||
**middleware.ts:**
|
||||
- Purpose: Next.js request middleware for route protection
|
||||
- Functionality: Checks Cookie auth_token; redirects to /login if missing; whitelists public routes
|
||||
|
||||
**public/:**
|
||||
- Purpose: Static assets (images, icons, etc.)
|
||||
|
||||
**styles/:**
|
||||
- Purpose: Additional CSS if needed (most styling via Tailwind in components)
|
||||
|
||||
## Key File Locations
|
||||
|
||||
**Entry Points:**
|
||||
- `app/layout.tsx`: Root layout (metadata only)
|
||||
- `app/page.tsx`: Dashboard homepage (stats, charts, links)
|
||||
- `app/login/page.tsx`: Authentication entry point
|
||||
- `middleware.ts`: Route protection middleware
|
||||
|
||||
**Configuration:**
|
||||
- `next.config.mjs`: Next.js config (standalone output)
|
||||
- `tsconfig.json`: TypeScript strict mode
|
||||
- `tailwind.config.ts`: Tailwind CSS theme + animations
|
||||
- `postcss.config.mjs`: PostCSS with Tailwind + autoprefixer
|
||||
- `.env.local`: Environment variables (API_BASE_URL)
|
||||
|
||||
**Core Logic:**
|
||||
- `lib/permissions.ts`: Role-based access control matrix & utilities
|
||||
- `lib/api/client.ts`: Axios instance with auth/error interceptors
|
||||
- `components/dashboard-shell.tsx`: Permission-checking layout wrapper
|
||||
- `components/sidebar.tsx`: Role-filtered navigation menu
|
||||
|
||||
**Authentication:**
|
||||
- `lib/api/auth.ts`: Login, logout, token persistence
|
||||
- `middleware.ts`: Route-level token validation
|
||||
|
||||
**Testing:**
|
||||
- No test files detected; testing not yet set up
|
||||
|
||||
## Naming Conventions
|
||||
|
||||
**Files:**
|
||||
- **Page components:** `page.tsx` (App Router standard)
|
||||
- **Feature pages:** `/app/[module]/page.tsx` (e.g., app/outfits/page.tsx)
|
||||
- **Dynamic routes:** `/app/[module]/[id]/page.tsx` (e.g., app/outfits/[id]/page.tsx)
|
||||
- **Layout files:** `layout.tsx`
|
||||
- **Loading states:** `loading.tsx`
|
||||
- **API modules:** `/lib/api/[feature].ts` (e.g., lib/api/outfits.ts)
|
||||
- **Component files:** PascalCase (e.g., OutfitsList.tsx, AddOutfitDialog.tsx)
|
||||
- **Hook files:** `use-[name].ts` (e.g., use-mobile.tsx)
|
||||
|
||||
**Directories:**
|
||||
- **App modules:** kebab-case (e.g., `/app/ai-model`, `/app/home-decor`, `/app/forgot-password`)
|
||||
- **Component folders:** kebab-case for feature groups, `ui` for primitives
|
||||
- **API folder:** All in `lib/api/` (no nested folders)
|
||||
|
||||
**Functions:**
|
||||
- **API functions:** camelCase, verb-first (e.g., `getOutfits()`, `createOutfit()`, `updateOutfit()`)
|
||||
- **Utility functions:** camelCase (e.g., `hasPermission()`, `getUserRole()`)
|
||||
- **Components:** PascalCase (e.g., `DashboardShell`, `Sidebar`, `OutfitsList`)
|
||||
- **Hooks:** camelCase with `use` prefix (e.g., `useMobile`, `useToast`)
|
||||
|
||||
**Variables/Constants:**
|
||||
- **Type definitions:** PascalCase (e.g., `Outfit`, `RoleName`, `ApiResponse`)
|
||||
- **Constants:** UPPER_SNAKE_CASE (e.g., `API_BASE_URL`, `PERMISSION_MATRIX`)
|
||||
- **Local variables:** camelCase (e.g., `outfits`, `isLoading`, `userRole`)
|
||||
|
||||
**Routes:**
|
||||
- **Module routes:** kebab-case URLs (e.g., `/ai-model`, `/home-decor`, `/forgot-password`)
|
||||
- **Dynamic routes:** `[paramName]` (e.g., `/outfits/[id]`, `/dances/[id]/edit`)
|
||||
|
||||
## Where to Add New Code
|
||||
|
||||
**New Feature Module (e.g., New Content Type):**
|
||||
|
||||
1. **Page component:** Create `app/[module-name]/page.tsx`
|
||||
- Wrap with DashboardShell
|
||||
- Import feature components from `components/[module-name]/`
|
||||
- Call API functions from `lib/api/[module-name].ts`
|
||||
|
||||
2. **API client:** Create `lib/api/[module-name].ts`
|
||||
- Export `get[Feature]s()`, `get[Feature]()`, `create[Feature]()`, `update[Feature]()`, `delete[Feature]()`
|
||||
- Use `apiClient.get/post/put/delete()` from `lib/api/client.ts`
|
||||
- Include adapter function to map backend response to frontend type
|
||||
- Export types or import from `lib/api/types.ts`
|
||||
|
||||
3. **Components:** Create `components/[module-name]/` folder
|
||||
- List component (e.g., `[Feature]List.tsx`)
|
||||
- Detail/edit component
|
||||
- Dialog/modal for creation
|
||||
- Table component if displaying tabular data
|
||||
|
||||
4. **Permission matrix:** Add to `lib/permissions.ts`
|
||||
- Add module key to `PermissionModule` type
|
||||
- Add module to relevant roles in `PERMISSION_MATRIX`
|
||||
- Add path mapping in `getModuleFromPath()`
|
||||
|
||||
5. **Sidebar:** Update `components/sidebar.tsx`
|
||||
- Add menu item to appropriate section (AI Admin, Content Admin, or System Admin)
|
||||
- Use lucide-react icon
|
||||
|
||||
6. **Middleware:** Update `middleware.ts`
|
||||
- Add route to `protectedPaths` array if requiring auth
|
||||
|
||||
**New Component:**
|
||||
- Location: `components/[feature-name]/[ComponentName].tsx`
|
||||
- Import from UI primitives: `components/ui/button.tsx`, etc.
|
||||
- Use React Hook Form for forms
|
||||
- Use icons from lucide-react
|
||||
- Mark as "use client" if using hooks/state
|
||||
|
||||
**New Utility Function:**
|
||||
- Location: `lib/utils.ts` (for generic utilities) or `lib/api/[module].ts` (for API helpers)
|
||||
- Type everything with TypeScript
|
||||
- Export from `lib/api/index.ts` if API-related
|
||||
|
||||
**New Hook:**
|
||||
- Location: `hooks/use-[name].ts`
|
||||
- Follow React hook naming convention (use- prefix)
|
||||
|
||||
## Special Directories
|
||||
|
||||
**components/ui/:**
|
||||
- Purpose: Shadcn-style primitives (Button, Card, Dialog, Form, Input, etc.)
|
||||
- Generated: No (manually copied from shadcn/ui)
|
||||
- Committed: Yes (safe to modify without breaking npm upgrades)
|
||||
- Note: Each file is a single component; complex components like Dialog may have sub-exports
|
||||
|
||||
**public/:**
|
||||
- Purpose: Static assets served at root level
|
||||
- Generated: No
|
||||
- Committed: Yes
|
||||
|
||||
**node_modules/:**
|
||||
- Purpose: Installed dependencies
|
||||
- Generated: Yes (by npm/pnpm/yarn)
|
||||
- Committed: No (.gitignore)
|
||||
|
||||
**.next/:**
|
||||
- Purpose: Next.js build output
|
||||
- Generated: Yes (by `npm run build`)
|
||||
- Committed: No (.gitignore)
|
||||
|
||||
**lib/api/:**
|
||||
- Purpose: All API communication in one folder for easy discovery
|
||||
- Generated: No (hand-written)
|
||||
- Committed: Yes
|
||||
- Note: No subfolders; keep flat for single-module-per-file pattern
|
||||
|
||||
---
|
||||
|
||||
*Structure analysis: 2026-05-07*
|
||||
279
qy-lty-admin/.planning/codebase/TESTING.md
Normal file
279
qy-lty-admin/.planning/codebase/TESTING.md
Normal file
@ -0,0 +1,279 @@
|
||||
# 测试模式
|
||||
|
||||
**分析日期:** 2026-05-07
|
||||
|
||||
## 测试框架
|
||||
|
||||
**当前状态:** 未检测到测试基础设施
|
||||
|
||||
- **运行器:** 未配置(package.json 中无 Jest、Vitest、Playwright 等依赖)
|
||||
- **断言库:** 未配置
|
||||
- **测试脚本:** 未在 package.json 中定义(仅有 `dev`、`build`、`start`、`lint`)
|
||||
|
||||
## 测试文件组织
|
||||
|
||||
**当前:** 无测试文件
|
||||
|
||||
项目中未发现 `*.test.ts`、`*.test.tsx`、`*.spec.ts`、`*.spec.tsx`、`__tests__/` 目录等标准测试文件结构。
|
||||
|
||||
## 测试覆盖
|
||||
|
||||
**要求:** 未强制
|
||||
|
||||
**影响:**
|
||||
- 新增代码(尤其是 API 集成、表单验证、权限逻辑)缺乏自动化验证
|
||||
- 技术债风险:`next.config.mjs` 中 `typescript.ignoreBuildErrors: true` 和 `eslint.ignoreDuringBuilds: true`,导致构建时不检查类型和 lint 错误
|
||||
- 手动测试依赖性高
|
||||
|
||||
## 关键需要测试的区域
|
||||
|
||||
### API 集成层
|
||||
**位置:** `lib/api/*.ts`
|
||||
|
||||
**建议测试:**
|
||||
- 认证流程(`emailLogin`、`saveAuthToken`、`logout`)
|
||||
- API 请求拦截器(token 注入、过期处理)
|
||||
- 响应拦截器(401 处理、重定向)
|
||||
- 数据适配器(如 `mapBackendOutfit()` 将后端结构映射到前端类型)
|
||||
- 错误处理(`handleApiException`、`handleApiRequest` 的成功/失败路径)
|
||||
|
||||
**示例测试需求:**
|
||||
```typescript
|
||||
// 应测试:token 自动注入
|
||||
test('请求拦截器应在请求头中添加 Bearer token', () => {
|
||||
// 前置条件:localStorage 中有 auth_token
|
||||
// 发送 API 请求
|
||||
// 验证:Authorization 头包含 "Bearer <token>"
|
||||
})
|
||||
|
||||
// 应测试:401 处理
|
||||
test('响应拦截器应在收到 401 时清除 token 并重定向到登录', () => {
|
||||
// 前置条件:发送请求后服务器返回 401
|
||||
// 验证:localStorage 中 auth_token 被移除
|
||||
// 验证:浏览器重定向到 /login
|
||||
})
|
||||
|
||||
// 应测试:数据适配
|
||||
test('mapBackendOutfit 应正确转换后端服装数据', () => {
|
||||
const backendData = { id: 1, name: '测试服装', image_url: '...' }
|
||||
const result = mapBackendOutfit(backendData)
|
||||
expect(result).toHaveProperty('imageUrl') // 字段映射
|
||||
expect(result.id).toBe('1') // 类型转换
|
||||
})
|
||||
```
|
||||
|
||||
### 权限控制
|
||||
**位置:** `lib/permissions.ts`
|
||||
|
||||
**建议测试:**
|
||||
- `hasPermission(module)` 根据用户角色返回正确的权限
|
||||
- `getUserRole()` 从 localStorage 获取当前角色
|
||||
- `getModuleFromPath(pathname)` 正确提取路径中的模块
|
||||
- `hasPathPermission(pathname)` 组合权限检查
|
||||
|
||||
**示例测试需求:**
|
||||
```typescript
|
||||
test('超级管理员应有所有模块的权限', () => {
|
||||
// 前置:localStorage.user_role = '超级管理员'
|
||||
// 验证:hasPermission('users') === true
|
||||
// 验证:hasPermission('ai-model') === true
|
||||
// 等等所有模块
|
||||
})
|
||||
|
||||
test('内容管理员应无权限访问用户管理', () => {
|
||||
// 前置:localStorage.user_role = '内容管理员'
|
||||
// 验证:hasPermission('users') === false
|
||||
// 验证:hasPermission('outfits') === true
|
||||
})
|
||||
```
|
||||
|
||||
### 表单验证与提交
|
||||
**位置:** `components/*/add-*-dialog.tsx`、`app/*/page.tsx`
|
||||
|
||||
**当前模式:**
|
||||
- 使用 `useState` 管理表单状态
|
||||
- 基本的非空检查(如 `if (!formData.name || !formData.description)` 在 `add-achievement-dialog.tsx`)
|
||||
- 客户端验证不完整
|
||||
|
||||
**建议测试:**
|
||||
- 必填字段验证(空值拒绝)
|
||||
- 表单提交成功调用正确的 API
|
||||
- 提交过程中加载状态变化(`isSubmitting`)
|
||||
- 表单重置(关闭后清空状态)
|
||||
- 错误显示(API 失败时显示 toast)
|
||||
|
||||
**示例测试需求:**
|
||||
```typescript
|
||||
test('添加服装对话框应在名称为空时禁用提交', () => {
|
||||
// 渲染 AddOutfitDialog
|
||||
// 验证:提交按钮禁用状态
|
||||
})
|
||||
|
||||
test('提交表单应调用 createOutfit API', () => {
|
||||
// 填写表单字段
|
||||
// 点击提交
|
||||
// 验证:createOutfit 被调用,参数正确
|
||||
})
|
||||
```
|
||||
|
||||
### 组件交互
|
||||
**位置:** `components/`
|
||||
|
||||
**建议测试:**
|
||||
- 对话框打开/关闭状态
|
||||
- 模态框表单的步骤导航(`step` 状态变化)
|
||||
- 确认对话框的确认/取消操作
|
||||
- 列表分页和搜索
|
||||
|
||||
**示例测试需求:**
|
||||
```typescript
|
||||
test('AddOutfitDialog 应在提交后关闭', () => {
|
||||
// 渲染对话框
|
||||
// 填写并提交表单
|
||||
// 验证:对话框关闭(open === false)
|
||||
})
|
||||
|
||||
test('多步表单应在点击"下一步"时更新步骤', () => {
|
||||
// 验证:初始 step === 1
|
||||
// 点击"下一步"按钮
|
||||
// 验证:step === 2
|
||||
})
|
||||
```
|
||||
|
||||
### 中间件和路由保护
|
||||
**位置:** `middleware.ts`
|
||||
|
||||
**建议测试:**
|
||||
- 无 token 访问受保护路由时重定向到 `/login`
|
||||
- 有 token 时允许访问受保护路由
|
||||
- 公共路由(登录、注册)不需要 token
|
||||
|
||||
**示例测试需求:**
|
||||
```typescript
|
||||
test('中间件应将无 token 的请求重定向到登录', () => {
|
||||
// 模拟请求:GET /outfits,无 auth_token cookie
|
||||
// 验证:重定向到 /login?callbackUrl=/outfits
|
||||
})
|
||||
|
||||
test('中间件应允许有 token 的请求', () => {
|
||||
// 模拟请求:GET /users,包含有效 auth_token cookie
|
||||
// 验证:通过,继续处理
|
||||
})
|
||||
```
|
||||
|
||||
## 建议的测试设置
|
||||
|
||||
### 推荐框架组合
|
||||
|
||||
**单元/集成测试:**
|
||||
- **运行器:** Vitest(与 Next.js 15 兼容,开发体验优于 Jest)
|
||||
- **断言:** Vitest 内置(或 @testing-library/jest-dom)
|
||||
- **Mock 库:** vitest 的 `vi` 模块或 `@testing-library/react`
|
||||
|
||||
**E2E 测试(可选):**
|
||||
- **框架:** Playwright 或 Cypress
|
||||
- **用途:** 完整登录流、权限控制、多步表单
|
||||
|
||||
### 安装建议
|
||||
|
||||
```bash
|
||||
# 安装 Vitest 和相关工具
|
||||
npm install -D vitest @vitest/ui @testing-library/react @testing-library/jest-dom
|
||||
|
||||
# 创建配置文件 vitest.config.ts
|
||||
```
|
||||
|
||||
### 最小化配置示例
|
||||
|
||||
```typescript
|
||||
// vitest.config.ts
|
||||
import { defineConfig } from 'vitest/config'
|
||||
import react from '@vitejs/plugin-react'
|
||||
import path from 'path'
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
test: {
|
||||
globals: true,
|
||||
environment: 'jsdom',
|
||||
setupFiles: ['./test/setup.ts'],
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': path.resolve(__dirname, '.'),
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
// test/setup.ts
|
||||
import '@testing-library/jest-dom'
|
||||
```
|
||||
|
||||
### package.json 脚本建议
|
||||
|
||||
```json
|
||||
{
|
||||
"scripts": {
|
||||
"test": "vitest",
|
||||
"test:ui": "vitest --ui",
|
||||
"test:coverage": "vitest --coverage",
|
||||
"test:run": "vitest run"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 测试文件结构建议
|
||||
|
||||
```
|
||||
project-root/
|
||||
├── lib/
|
||||
│ ├── api/
|
||||
│ │ ├── auth.ts
|
||||
│ │ ├── auth.test.ts # API 认证测试
|
||||
│ │ ├── outfits.ts
|
||||
│ │ ├── outfits.test.ts # API 服装测试
|
||||
│ │ └── ...
|
||||
│ ├── permissions.ts
|
||||
│ └── permissions.test.ts # 权限逻辑测试
|
||||
├── components/
|
||||
│ ├── add-outfit-dialog.tsx
|
||||
│ ├── add-outfit-dialog.test.tsx # 组件交互测试
|
||||
│ └── ...
|
||||
├── app/
|
||||
│ ├── login/
|
||||
│ │ ├── page.tsx
|
||||
│ │ └── page.test.tsx # 登录页测试
|
||||
│ └── ...
|
||||
├── test/
|
||||
│ ├── setup.ts # 测试环境初始化
|
||||
│ ├── mocks/
|
||||
│ │ ├── handlers.ts # MSW 请求拦截器
|
||||
│ │ └── server.ts # MSW 服务器配置
|
||||
│ └── fixtures/
|
||||
│ ├── auth.fixture.ts # 认证数据 fixtures
|
||||
│ └── outfits.fixture.ts # 服装数据 fixtures
|
||||
└── vitest.config.ts
|
||||
```
|
||||
|
||||
## 当前缺失的覆盖范围
|
||||
|
||||
**高优先级(生产关键):**
|
||||
- ✗ API 集成和数据适配
|
||||
- ✗ 认证流程(登录、token 管理、登出)
|
||||
- ✗ 权限检查和路由保护
|
||||
- ✗ 表单验证和提交
|
||||
|
||||
**中优先级(功能完整性):**
|
||||
- ✗ 错误处理和用户通知
|
||||
- ✗ 组件交互(对话框、模态框)
|
||||
- ✗ 列表管理(分页、搜索)
|
||||
|
||||
**低优先级(UX 细节):**
|
||||
- ✗ 样式和响应性
|
||||
- ✗ 无障碍访问 (a11y)
|
||||
|
||||
---
|
||||
|
||||
*测试分析日期:2026-05-07*
|
||||
|
||||
**关键建议:** 该项目缺乏测试基础设施,强烈建议在新增核心功能(特别是认证、权限、API 集成)时同步添加测试。考虑从 API 层和权限控制开始,这些是最容易产生 bug 的关键区域。
|
||||
Loading…
x
Reference in New Issue
Block a user