chore: snapshot before P0 security phase (backup point)

This commit is contained in:
SpecialX
2026-07-07 19:12:33 +08:00
parent 3f68f3eb09
commit 747344bfe3
130 changed files with 18879 additions and 6615 deletions

View File

@@ -194,7 +194,11 @@ export function UserCard({ user, onSelect, children }: UserCardProps): JSX.Eleme
### 4.1 配置tsconfig.json
当前项目配置需升级以符合规范。**目标配置**
当前项目配置已基本符合规范(`target: ES2022``noImplicitReturns: true``noFallthroughCasesInSwitch: true`)。
**待办**`noUncheckedIndexedAccess` 当前为 `false`(数组/对象索引不返回 `T | undefined`),逐步迁移后启用。具体配置见 `tsconfig.json`
**目标配置**
```json
{
@@ -221,12 +225,6 @@ export function UserCard({ user, onSelect, children }: UserCardProps): JSX.Eleme
}
```
**当前差异**(需逐步升级):
- `target`: `ES2017``ES2022`
- 缺失 `noUncheckedIndexedAccess`(数组/对象索引返回 `T | undefined`
- 缺失 `noImplicitReturns`(函数所有分支必须返回)
- 缺失 `noFallthroughCasesInSwitch`
### 4.2 类型规则
1. **禁止 `any`**:未知类型用 `unknown` 并做类型守卫。若极特殊情况必须使用,需 `// eslint-disable-next-line @typescript-eslint/no-explicit-any` 并注释原因
@@ -366,23 +364,22 @@ import { cn } from "@/shared/lib/utils";
### 6.3 设计令牌配置
本项目在 `src/app/globals.css` 中使用 CSS 变量定义设计令牌:
本项目在 `src/app/styles/tokens/` 中使用 CSS 变量定义设计令牌(分层架构)
```css
:root {
--background: 0 0% 100%;
--foreground: 240 10% 3.9%;
--primary: 240 5.9% 10%;
--primary-foreground: 0 0% 98%;
--destructive: 0 84.2% 60.2%;
--border: 240 5.9% 90%;
--radius: 0.5rem;
/* ... */
}
```
- `primitive.css`: 原始色板/字号/间距/阴影Layer 1业务代码不直接引用
- `semantic-light.css` + `semantic-dark.css`: 语义令牌Layer 2业务代码唯一引用入口
- `lesson-preparation.css`: `--lp-*` 令牌(明暗双份,模块命名空间)
- `tailwind-theme.css`: `@theme inline` 将 Semantic 令牌暴露为 Tailwind 类
- `index.css`: 入口文件(汇总 @import
**所有视觉设计决策**(颜色、字号、间距)必须体现在设计令牌中,组件中不使用硬编码值。
**ESLint 强制约束**
- `no-restricted-syntax`: 禁止 `#hex` 颜色字面量
- `design-tokens/no-hardcoded-fonts`: 禁止 `'Inter'`/`'Fraunces'`/`'JetBrains Mono'` 字面量(单词边界匹配,不影响 `Interval`/`Interactive` 等标识符)
- 白名单:`primitive.css`(令牌定义)、`email-channel.ts`(邮件 HTML`manifest.ts`PWA
- 任意值豁免需注释:`// eslint-disable-next-line no-restricted-syntax -- <reason>`
---
## 七、数据获取与状态管理
@@ -400,7 +397,7 @@ import { cn } from "@/shared/lib/utils";
**规则**
- 服务端数据获取通过模块的 `data-access.ts` 函数
- 客户端动态数据统一使用 **TanStack Query v5+****禁止在 `useEffect` 中手写 fetch**
- 缓存策略:服务端请求必须显式设置 `next.revalidate`使用 `unstable_cache`,并注释缓存时长理由
- 缓存策略:服务端请求使用 `cacheFn`(封装 React `cache()` + 自定义缓存层),详见 `shared/lib/cache/`;不使用 `unstable_cache`(权限数据易变,跨请求缓存风险高于收益)
### 7.2 Server Actions
@@ -470,18 +467,23 @@ export async function createExamAction(
### 7.3 状态管理
| 场景 | 方案 |
|------|------|
| 局部 UI 状态 | `useState` / `useReducer` |
| 跨组件共享(小范围) | React Context |
| 跨组件共享(大范围) | Zustand轻量、可选择订阅 |
| 全局状态 | 仅存放真正全局必要数据(认证信息、主题、通知列表) |
| URL 状态 | `nuqs`(已集成) |
本项目采用 **5 层状态模型**
| 层级 | 场景 | 方案 |
|------|------|------|
| L1 URL | 可分享、可刷新的状态(分页、筛选、排序) | `nuqs` |
| L2 Server | 服务端数据 | TanStack Query |
| L3 Client Business | 客户端业务状态 | Zustand slice |
| L4 Global UI | 全局 UI 状态(弹窗、主题) | Zustand ui-store + ModalRoot |
| L5 Form | 表单状态 | react-hook-form + zodResolver |
**规则**
- Context 拆分:一个 Context 只负责一类数据,避免无关状态变化引发不必要的渲染
- 业务数据一律通过路由参数或 TanStack Query 获取,**不存入全局状态**
- Zustand 的 `persist` 中间件必须处理版本迁移和敏感数据加密
- 优先使用细粒度 Zustand selectors单字段 selector而非 `useShallow` 多字段包装
- 乐观更新使用 React 19 `useOptimistic` + `useTransition`,替代手动 `isPending` 状态
- `useOptimistic``addOptimistic` 必须在 transition 或 action 内调用form action 也算 action
---
@@ -700,52 +702,19 @@ test(checkout): cover discount edge cases
## 十五、统一工具配置
### 15.1 ESLint(当前配置 + 建议增强)
### 15.1 ESLint
**当前配置**`eslint.config.mjs`
**当前配置**`eslint.config.mjs`已实现以下规则
```javascript
import { defineConfig, globalIgnores } from "eslint/config";
import nextVitals from "eslint-config-next/core-web-vitals";
import nextTs from "eslint-config-next/typescript";
- `no-restricted-syntax`: 禁止 `#hex` 颜色字面量
- `design-tokens/no-hardcoded-fonts`: 禁止 `'Inter'`/`'Fraunces'`/`'JetBrains Mono'` 字面量(单词边界匹配,不影响 `Interval`/`Interactive` 等标识符)
- 白名单:`primitive.css`(令牌定义)、`email-channel.ts`(邮件 HTML`manifest.ts`PWA
- `@typescript-eslint/no-explicit-any`: error
- `react/react-in-jsx-scope`: off
- `react/function-component-definition`: function-declaration
- `import/order`: 强制分组排序
const eslintConfig = defineConfig([
...nextVitals,
...nextTs,
{
rules: {
"react-hooks/incompatible-library": "off",
},
},
// ...
]);
export default eslintConfig;
```
**建议增强**(待逐步集成):
```javascript
{
extends: [
"next/core-web-vitals",
"plugin:@typescript-eslint/recommended",
"plugin:react/recommended",
"plugin:react-hooks/recommended",
"plugin:jsx-a11y/recommended",
"prettier"
],
rules: {
"@typescript-eslint/no-explicit-any": "error",
"react/react-in-jsx-scope": "off",
"react/function-component-definition": [2, { "namedComponents": "function-declaration" }],
"import/order": ["error", {
"groups": ["builtin", "external", "internal", "parent", "sibling", "index", "type"],
"newlines-between": "always"
}]
}
}
```
(具体配置见 `eslint.config.mjs`
### 15.2 Prettier