chore: snapshot before P0 security phase (backup point)
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user