返回 AiToEarn
AGENTS.md
根目录 / project / aitoearn-web / AGENTS.md
1 # AGENTS.md
2
3 本文件定义 Codex 在 `aitoearn-web` 仓库内的默认工作规则。
4
5 ## Communication
6
7 - 默认用中文回答问题。
8 - 生成 git commit message 或提交说明时,不要加入 `Co-Authored-By` 或 `<noreply@anthropic.com>`。
9 - git commit message 必须符合 commitlint / Conventional Commits 规范,使用 `feat`、`fix`、`docs`、`style`、`refactor`、`perf`、`test`、`build`、`ci`、`chore`、`revert` 等类型,格式为 `type(scope): 中文描述` 或 `type: 中文描述`。
10 - 如果需要用 git 提交代码,必须生成完整的 git 提交记录,必须生成中文提交记录。
11
12 ## Skill Usage
13
14 - 默认遵循 `karpathy-guidelines`:先明确假设与边界,再编码;保持实现简单;只做与需求直接相关的最小改动;完成后用可验证结果确认修改生效。
15 - 编写、审查、重构 React 或 Next.js 代码时,优先遵循 `vercel-react-best-practices`。
16 - UI 设计类 skill 职责边界:
17 - `PRODUCT.md` / `DESIGN.md` 是项目设计宪法;任何 UI 设计输出、参考图、代码实现和审查结论都必须服从它们。
18 - `taste-skill`(安装目录;skill name 为 `design-taste-frontend`):用于从零生成设计方案,例如全新页面、全新模块、全新组件或全新视觉方向的初始设计构思。
19 - `impeccable`:用于审查、打磨、修复或优化现有设计,例如已有页面、组件、截图或代码的视觉/体验改进。
20 - `taste-skill` / `design-taste-frontend` 只能在现有品牌 token、字体、圆角、组件体系、主题兼容、国际化和工程硬约束内发散,不得重新定义项目设计系统。
21 - 当任务是“从零设计 + 落地实现”时,先使用 `taste-skill` / `design-taste-frontend` 产出方向,再按项目现有设计系统与工程约束实现;最终落地前必须使用 `impeccable` 做一致性、可访问性、主题兼容和反模板化审查。
22 - 当两者看似都适用时,以任务对象为准:全新设计方案用 `taste-skill` / `design-taste-frontend`,现有设计审查/修复用 `impeccable`,不要让两个 skill 同时争夺最终设计权。
23
24 ## Before Coding
25
26 编写新逻辑前,必须先检查是否已有可复用实现,按顺序检索:
27
28 1. `src/utils/README.md`
29 2. 如涉及 `src/utils/<目录>/`,继续检查目标子目录 `README.md`
30 3. `src/hooks/README.md`
31 4. `src/components/README.md`
32 5. `src/store/README.md`
33 6. `src/app/config/`
34 7. 当前页面目录下的 `store`、`hooks`、`utils`
35
36 如果找到相似实现,必须优先复用或扩展,不要重复造轮子。
37
38 - 新增可复用代码时,必须同步更新对应 README。
39 - 可复用的全局方法写到 `src/utils/`,并同步更新 `src/utils/README.md` 和目标子目录 README。
40 - `src/utils` 是唯一全局工具目录,禁止新增或恢复 `src/lib` 作为工具目录。
41 - 添加工具方法前,先检查 `src/utils/README.md` 和目标子目录 README,避免重复实现。
42 - 涉及 UI、交互、滚动、弹窗、按钮等能力时,组件选择优先级必须为:
43 1. 先搜索并复用全局组件或现有公共组件
44 2. 其次使用 `shadcn/ui` 组件或基于其现有封装扩展
45 3. 如果以上都没有,禁止直接在当前页面/局部临时实现,必须先与用户讨论方案,再决定是否新增组件或局部实现
46
47 ## Hard Constraints
48
49 - 不要使用 `npm run build` 做类型检查;改用 `npx tsc --noEmit`。
50 - 不要使用 `as any` 绕过 TypeScript 错误;必须从类型源头修复。
51 - 不要使用硬编码货币符号或货币代码,如 `$`、`USD`、`¥`、`CNY`;使用 `appCurrencySymbol` / `appCurrency`,见 `src/utils/currency.ts`。
52 - 用户可见金额默认只展示货币符号与金额,不额外展示 `appCurrency`、接口返回的 `currency` 或 `currencyCode` 后缀;国内版和国际版不会同时存在,`CNY` / `USD` 仅用于数据、API、计算和必要的配置逻辑。
53 - 不要使用 `<Input type="number">` 或 `<input type="number">`;统一使用 `NumberInput`,见 `src/components/ui/number-input.tsx`。
54 - 不要使用硬编码颜色,如 `text-gray-900`。
55 - 如果页面或组件中渲染 OSS/R2 图片,必须使用全局组件 `OssImage`(`@/components/common/OssImage`),不要直接使用 `next/image` 或 `<img>` 渲染 OSS 图片;`OssImage` 会根据 `sizes`、`width`、`height` 或显式缩略图参数生成阿里云 OSS / Cloudflare R2 缩略图 URL。
56 - 不要使用纯黑色,如 `#000`、`black`、`text-black`;优先使用语义化颜色变量,如 `text-foreground`、`text-muted-foreground`。
57 - 修改中文、日文、韩文等非 ASCII 文本文件时,必须确保读写链路为 UTF-8,禁止通过未设置 UTF-8 的 PowerShell 管道传递非 ASCII 文本到 `python -`、`node -e` 等命令。
58 - 如必须在 PowerShell 中用管道/Here-String 传递非 ASCII 文本,命令开头必须设置 `$OutputEncoding = [System.Text.UTF8Encoding]::new($false); [Console]::InputEncoding = [System.Text.UTF8Encoding]::new($false); [Console]::OutputEncoding = [System.Text.UTF8Encoding]::new($false);`。
59
60 ## Reuse Rules
61
62 以下能力优先复用现有实现,不要手写重复逻辑:
63
64 - 数字、日期、时间、秒数格式化
65 - 货币显示与换算
66 - OSS URL 与代理 URL 拼接
67 - 移动端判断
68 - 视频信息与视频时长获取
69 - 倒计时逻辑
70 - VIP 状态判断
71 - 中文语言判断
72 - 相对时间格式化
73 - 确认弹窗
74 - 图片/视频预览
75
76 决策规则:
77
78 - 功能完全一致:直接复用
79 - 功能 80% 相似:扩展现有实现
80 - 功能 50% 相似:抽取公共部分后复用
81 - 功能全新且无类似实现:允许新建,但必须放到合适的公共目录并更新 README
82
83 如果开发过程中发现已有重复代码,主动提示是否需要合并重构。
84
85 ## Project Utilities
86
87 - OSS 资源使用 `getOssUrl`,见 `src/utils/oss.ts`
88 - Canvas 跨域代理使用 `getOssProxyPath`,见 `src/utils/oss.ts`
89 - 货币显示使用 `appCurrencySymbol` + `appCurrency`,见 `src/utils/currency.ts`
90 - SEO 元数据使用 `getMetadata`,见 `src/utils/metadata.ts`
91 - 持久化 store 使用 `createPersistStore`,见 `src/utils/storage/createPersistStore.ts`
92 - 数字输入统一使用 `NumberInput`
93 - 语言配置在 `src/app/i18n/languageConfig.ts`
94 - 国际化目录在 `src/app/i18n`
95
96 ## Directory Conventions
97
98 - 页面私有组件放在 `pages/xxx/components/`
99 - 公共组件放在 `src/components/`
100 - 跨组件状态放在 `xxxStore/`
101 - API 方法放在 `src/api/`
102 - API 类型放在 `src/api/types/`
103 - 子模块目录结构保持一致,例如 `src/api/tasks/task.api.ts` 对应 `src/api/tasks/task.types.ts`
104 - 仅在当前页面、当前布局或当前模块内使用的 `components`、`hooks`、`utils`、`store`,必须放在对应局部目录,不要提前放到全局目录。
105 - 只有在确认被多个页面、布局或模块复用后,才允许提升到 `src/components/`、`src/hooks/`、`src/utils/`、`src/store/` 等全局目录。
106 - 不要为了“可能以后会复用”而提前做全局抽取;先局部沉淀,复用场景明确后再抽象。
107
108 ### Global Hooks Directory Boundary
109
110 - `src/hooks` 只放项目级通用 Hook,必须被多个页面、布局、全局组件或跨业务域场景真实复用。
111 - 只有一个页面或组件引用的 Hook 必须放到调用方局部 `hooks/` 目录,不允许通过 `@/hooks` 暴露。
112 - 仅某个全局业务组件域复用的 Hook 放到 `src/components/<业务域>/hooks/`,不要提升到 `src/hooks`。
113 - 新增全局 Hook 前必须先用 `rg` 检查调用方与类似实现;单引用或未被复用的 Hook 不满足全局准入。
114 - 新增、迁移、删除 `src/hooks` 下任意文件时,必须同步更新 `src/hooks/README.md` 和 `src/hooks/index.ts`。
115
116 ### Global Utils Directory Boundary
117
118 - `src/utils` 是唯一全局工具目录,只放跨页面、跨业务复用的纯工具函数、稳定基础设施封装和低业务耦合工具。
119 - 禁止新增或恢复 `src/lib` 作为工具目录;历史 `lib` 能力必须迁移到 `src/utils`、`src/api`、`src/app/i18n` 或调用方局部目录。
120 - `src/utils/README.md` 维护一级目录边界、根目录文件说明、准入规则和 README 更新规则。
121 - `src/utils` 下每个一级子目录都必须维护自己的 `README.md`,说明目录职责、覆盖范围、文件清单、导出方法和新增规则。
122 - 新增、迁移、删除 `src/utils` 下任意文件时,必须同步更新 `src/utils/README.md` 和对应子目录 README。
123 - 单页面、单布局、单 store、单 hook 使用的工具必须下沉到对应局部目录,不允许放入 `src/utils`。
124 - 功能模块相似的工具必须归入同一业务目录,例如任务相关工具归入 `src/utils/task/`,请求基础设施归入 `src/utils/request/`,命令式 UI 工具归入 `src/utils/ui/`。
125
126 ### Global Components Directory Boundary
127
128 - `src/components/README.md` 只维护一级目录边界、放置规则和索引,不维护二级目录内部组件说明。
129 - `src/components` 根目录禁止新增孤立业务组件;新增全局组件必须归入 `common`、`ui` 或明确业务域目录,例如 `task`、`social`、`analytics`、`Chat`、`Plugin`。
130 - `src/components/common` 只放跨页面、跨业务域复用的小型通用组件,例如头像、图片、SEO、通用弹窗入口;不要放页面流程或业务域专属组件。
131 - 明确属于某个业务域的复用组件必须放入对应业务域目录,例如任务类型标签放 `src/components/task/TaskTypeBadge`,社交平台组件放 `src/components/social/<platform>/`,数据图表放 `src/components/analytics/`。
132 - 每个全局二级目录必须维护自己的 `README.md`,记录该目录组件清单、职责边界和新增规则;新增或迁移组件时同步更新对应二级目录 README。
133 - 如果组件只被单个页面、布局或模块使用,优先放到该页面、布局或模块的局部 `components/` 目录,不要提升到 `src/components`。
134 - `src/app/layout` 只放 App Shell、侧边栏、移动导航、全局 Provider、布局私有工具和只由布局挂载的全局弹窗 UI;普通页面如需控制 layout 弹窗,必须通过 `src/store` 中立状态或事件协作,不要直接 import layout 内部组件。
135
136 ### Cross-Page Reuse Boundary
137
138 - `src/app/[lng]/xxx/` 页面目录下的 `components`、`hooks`、`utils`、`store`、`xxxStore` 默认都是该页面的局部实现,其他页面禁止直接引用。
139 - 如果已经有 A 页面,B 页面需要复用 A 页面中的局部组件、store、hook、utils 或类型,必须先把被复用部分拆到中立公共位置,再由 A / B 页面共同引用,不能让 B 页面直接依赖 A 页面内部文件。
140 - 跨页面复用组件放到 `src/components/<业务域>/`,跨页面复用状态放到 `src/store/<业务域>/`,跨页面复用 hook 放到 `src/hooks/` 或 `src/components/<业务域>/hooks/`,跨页面复用纯工具放到 `src/utils/` 或 `src/components/<业务域>/utils/`。
141 - API 数据模型、请求/响应类型等跨页面类型必须放到 `src/api/types/` 或对应 API 类型文件中,不要放在某个页面的 store/types 里供其他页面引用。
142 - 如果两个页面都需要访问同一份 zustand 状态,必须拆成共享 store;页面级局部 store 只能服务当前页面,不允许通过 `@/app/[lng]/其他页面/...Store` 形成跨页面耦合。
143 - 如果共享组件里需要接入某个页面专属 UI(例如品牌信息卡、二维码区),共享组件必须通过 props / render props / slot 注入,不允许共享组件反向 import 页面专属组件。
144 - 允许页面之间通过路由跳转、URL 参数、事件或公共 store 协作;不允许页面之间直接 import 对方局部目录中的实现细节。
145 - 发现已有跨页面直接引用时,新增功能前必须先重构解耦;至少要把本次会继续依赖的部分提取成公共模块,并同步更新对应 README。
146
147 禁止示例:
148
149 ```tsx
150 import PlanTabBar from '@/app/[lng]/brand-promotion/components/PlanTabBar'
151 import { usePlanDetailStore } from '@/app/[lng]/brand-promotion/[planId]/planDetailStore'
152 import DraftContentModule from '@/app/[lng]/draft-box/components/DraftContentModule'
153 ```
154
155 推荐示例:
156
157 ```tsx
158 import PlanTabBar from '@/components/draft-box/components/PlanTabBar'
159 import DraftContentModule from '@/components/draft-box/components/DraftContentModule'
160 import { usePlanDetailStore } from '@/store/draft-box/planDetailStore'
161 ```
162
163 ### Component Folder Structure
164
165 每个组件使用独立文件夹,通过 `index.tsx` 导出:
166
167 ```text
168 components/
169 DraftDetailDialog/
170 index.tsx
171 DraftDetailDialog.module.scss
172 ```
173
174 - 文件夹名使用 PascalCase,并与组件名一致
175 - 主文件命名为 `index.tsx`
176 - 样式文件命名为 `[文件夹名].module.scss`
177 - 组件内部子组件、hooks、工具函数也放在同一文件夹内
178
179 ### Local Large Component Directory Structure
180
181 当局部页面或组件目录继续扩展,已经拆出多个私有子组件、hooks、工具函数或样式文件时,必须使用标准化子目录,避免把所有文件平铺在组件根目录。
182
183 推荐结构:
184
185 ```text
186 ComponentName/
187 index.tsx
188 README.md
189 components/
190 ChildComponent/
191 index.tsx
192 ChildComponent.module.scss
193 components/
194 hooks/
195 utils/
196 types/
197 hooks/
198 useXxx.ts
199 store/
200 xxxStore.ts
201 types/
202 index.ts
203 utils/
204 xxx.ts
205 styles/
206 ComponentName.module.scss
207 ```
208
209 - `index.tsx` 只保留组件入口、组合编排和必要的 props 连接,不继续堆积大段业务逻辑。
210 - `components/` 只放当前组件私有 UI 子组件;确认跨组件复用后再提升到上层公共 `components/`。
211 - `components/<ChildComponent>/` 如果继续变大,也必须按同样规则继续拆出自己的 `components/`、`hooks/`、`utils/`、`types/`,必要时在上层组件目录使用 `store/` 承接共享状态。
212 - `hooks/` 只放当前组件私有 Hook;确认被多个组件真实复用后再提升到业务域 hooks 或全局 `src/hooks`。
213 - `utils/` 只放当前组件私有纯函数、常量和低副作用计算;跨业务复用再提升到合适公共目录。
214 - `types/` 只放当前组件私有类型、props 类型、视图模型类型和类型辅助;不要把类型定义混在组件、hooks 或 utils 文件里。
215 - `store/` 只放当前组件私有局部状态;跨页面或跨业务复用后再提升到合适公共 store。
216 - `styles/` 放当前组件入口样式或多个内部区块共享的样式;单个子组件私有样式仍放在对应子组件文件夹内。
217 - 使用 `components/`、`hooks/`、`utils/`、`types/`、`store/`、`styles/` 的复杂组件目录必须维护本目录 `README.md`,说明目录职责、内部文件清单和提升复用规则。
218 - 不要在一个文件里同时堆放组件、hooks、业务方法、纯工具函数和类型;必须按职责放到对应子目录。
219 - 不要为了“可能以后复用”提前把局部 hooks、utils、types、store 或子组件提升到全局;先在局部标准目录内沉淀。
220
221 ### File Scalability
222
223 - 单个文件必须保持职责单一,不要在一个文件内堆积过多 UI、请求、状态、事件处理、格式化逻辑。
224 - 页面或组件文件如果已经同时承载大段 JSX、数据请求、复杂状态编排、类型定义和多个交互区域,必须按职责拆分到同目录的 `components/`、`hooks/`、`utils/`、`types/`、`store/` 中。
225 - 出现明显独立的区块时必须及时提取,不要持续向单文件追加代码;例如:弹窗、表单区、筛选区、列表区、表格列定义、步骤流、卡片区、独立异步流程。
226 - 不要在同一个文件中放多个职责无关的组件或工具函数;可复用逻辑优先抽到公共目录,并同步更新对应 README。
227 - 对已有大文件新增功能时,若继续追加会明显降低可读性或扩展性,必须先拆分再开发,不要把重构成本继续后移。
228
229 ## State Management
230
231 当页面内或大型局部组件内多个组件需要共享状态时,必须使用局部 zustand store,不要通过 props 层层传递大量字段。
232
233 - 在页面或组件目录下创建 `store/xxxStore.ts`
234 - 使用 `zustand` + `combine`
235 - 页面或组件顶层负责初始化数据,子组件直接从 store 读取
236 - 2 个及以上字段联合取值时,必须配合 `useShallow` 使用
237 - 页面级或组件级共享状态放局部 store;全局状态仍放 `src/store/`
238 - 父子组件 props 入参或回调明显过多、出现多层透传、兄弟组件需要读写同一组状态时,必须优先评估局部 store,不要继续扩大 props 面。
239 - 不要把服务单个页面或单个大型组件的状态提升到全局 store;先在局部 `store/` 内沉淀。
240
241 示例:
242
243 ```tsx
244 const { foo, bar } = useXxxStore(
245 useShallow((state) => ({
246 foo: state.foo,
247 bar: state.bar,
248 }))
249 )
250 ```
251
252 ## API Rules
253
254 - 新增或修改 API 前必须先阅读 `src/api/README.md` 和目标子目录 `README.md`,按模块边界放置接口。
255 - API 目录使用业务子目录结构:请求方法写入同目录 `*.api.ts`,请求/响应类型与枚举写入 `*.types.ts`,模块常量写入 `*.constants.ts`。
256 - 禁止新增或恢复 `src/api/types` 全局类型目录;类型必须与对应 API 模块同目录,确认为跨模块复用后才允许放入 `src/api/_shared`。
257 - 新增接口前必须用后端路径、HTTP 方法和函数名搜索 `src/api`,已有同功能接口必须复用或扩展,不要重复封装。
258 - 删除或迁移 API 后必须同步更新对应子目录 README,并运行 `npx tsc --noEmit`。
259 - API 方法和类型定义必须分离,不能放在同一文件中。
260 - 创建类接口必须判断返回值 `code === 0` 才视为成功;其他 `code` 都视为失败,并提示 `message`。
261
262 接口响应示例:
263
264 ```json
265 { "data": {}, "code": 0, "message": "success", "timestamp": 1772099056662 }
266 ```
267
268 ## Code Quality
269
270 - 优先复用已有类型和组件。
271 - 保持 lint、prettier、TypeScript 通过。
272 - 页面顶部注释写明组件名称和功能描述。
273 - API 操作需要 loading 状态;UI 需要骨架屏。
274 - 按钮 loading 状态使用 `Loader2` 转圈图标,并设置 `disabled`,不要改变按钮文案。
275 - 按钮加 `cursor-pointer`,兼容移动端。
276
277 ## UI Style
278
279 - 技术栈优先使用 Tailwind CSS + shadcn/ui
280 - 页面主视觉、CTA、强调状态优先使用当前品牌渐变:`gradient-back`;辅助强调可使用 `primary`、`brand-purple`、`brand-cyan`
281 - 主要按钮默认使用 `bg-gradient-back text-gradient-foreground` 的渐变视觉,参考品牌紫到品牌青的圆角胶囊按钮效果
282 - 中性内容、边框、背景仍优先使用语义化 token,如 `bg-background`、`text-foreground`、`text-muted-foreground`、`border-border`
283 - 视觉风格保持简约、现代、留白充足、层次分明
284 - 组件优先使用 shadcn/ui,保持设计语言一致
285
286 ## Tailwind CSS v4
287
288 优先使用 v4 写法:
289
290 ```tsx
291 <div className="bg-(--primary-color)" />
292 <div className="bg-gradient-back text-gradient-foreground" />
293 ```
294
295 不要写:
296
297 ```tsx
298 <div className="bg-[var(--primary-color)]" />
299 ```
300
301 ### Theme Compatibility
302
303 - 所有样式必须兼容 Tailwind CSS 的浅色 / 深色主题切换,不允许只适配单一主题。
304 - 优先使用语义化颜色 token 或主题变量,如 `bg-background`、`text-foreground`、`border-border`、`text-muted-foreground`,避免直接写死仅适用于浅色或深色的颜色值。
305 - 品牌色统一使用 `bg-gradient-back`、`text-gradient-foreground`、`text-primary`、`bg-primary`、`border-primary`、`text-brand-purple`、`text-brand-cyan` 等 Tailwind 主题类,不要直接写 `oklch(...)` 或重复定义品牌渐变。
306 - `--primary` / `bg-primary` 作为背景时,不允许搭配黑色或深色文字;必须使用 `text-primary-foreground`、`text-gradient-foreground` 或其它在当前主题下可读的前景色 token。
307 - 新增或修改 UI 时,必须同时检查文字、背景、边框、分割线、阴影,以及 hover、active、disabled 等状态在浅色 / 深色下的可读性与层级关系。
308 - 如果现有页面或组件已经基于 shadcn/ui 的主题 token 实现,必须延续该方案,不要额外维护一套局部明暗主题样式。
309
310 ## Internationalization
311
312 - 所有用户可见文本必须国际化。
313 - 不要给 `t()` 添加类型断言。
314 - 新增 key 时同步更新翻译文件。
315
316 ### Route Rule
317
318 路由链接不要手动拼接语言前缀:
319
320 ```tsx
321 <Link href="/pricing">定价</Link>
322 ```
323
324 不要写:
325
326 ```tsx
327 <Link href={`/${lng}/pricing`}>定价</Link>
328 ```
329
330 ## Next.js Rules
331
332 - 默认使用服务端组件,仅在需要交互时使用客户端组件
333 - 仅在客户端组件文件中写 `'use client'`
334 - 使用 `error.tsx` 处理错误,使用 `loading.tsx` 管理加载状态
335 - 使用 Next.js `Image` 组件优化图像
336
337 ### Metadata
338
339 - 静态元数据直接使用 `metadata`
340 - 动态元数据优先使用 `generateMetadata`
341 - 页面 SEO 元数据优先复用 `getMetadata`
342
343 ## TypeScript Rules
344
345 - 让 TypeScript 自动推断返回类型,避免冗余标注
346 - 避免使用 `JSX.Element` 作为常规返回类型;需要描述可渲染内容时使用 `React.ReactNode`
347 - 页面组件可以默认导出,普通组件优先命名导出
348
349 ## SCSS Rules
350
351 ### `:global()` Syntax
352
353 不要在 `:global()` 内使用 `&-suffix` 追加类名;单独声明完整类名。
354
355 ### BEM Naming
356
357 - Block: `.block`
358 - Element: `.block_element`
359 - Modifier: `.block_element-modifier`
360
361 ## Project Config References
362
363 开发前务必查阅 `src/app/config/`,避免硬编码和重复定义。
364
365 重点配置:
366
367 - `platConfig.ts`:平台配置与 `PlatType`
368 - `publishConfig.ts`:发布类型 `PubType`
369 - `accountConfig.ts`:账号状态与异常状态
370 - `appDownloadConfig.ts`:应用下载地址与缓存逻辑
371 - `promotionConfig.ts`:推广配置预留文件
372
373 ## Known Pitfalls
374
375 ### `useTransClient` namespace 首次动态加载闪烁
376
377 原因:
378
379 - `settings.ts` 只预加载 `common` 和 `route`
380 - 其他 namespace 首次使用时会异步加载
381 - 首帧可能显示 key,随后才显示真实翻译
382
383 规则:
384
385 - 条件渲染组件可拆成外层控制渲染、内层使用翻译的两层结构
386 - 对已有 loading/skeleton 的组件,外层必须同时等待数据 ready 和 i18n ready,再渲染子组件
387
388 ### 新增路由要检查 `src/middleware.ts`
389
390 新增不需要语言前缀的路由时,要把路径加入 `src/middleware.ts` 白名单,否则会被自动重定向到带语言前缀的路径。
391
392 ### 页面主滚动容器
393
394 页面主滚动元素是 `id="main-content"`,定义在 `src/app/layout/MainContent/index.tsx`。回到顶部等滚动操作应基于该元素,不要基于 `window`。
395
396 ### Radix UI Popover 内部滚动失效
397
398 在 `PopoverContent` 上添加 `allowInnerScroll`。
399
400 ### Canvas 跨域问题
401
402 跨域图片会污染 Canvas;使用同域代理方案,优先复用 `getOssProxyPath`。
403
404 ### Radix UI Tooltip 受控/非受控切换警告
405
406 始终保持受控模式,用独立 state 管理 `open`,不要在 `false` 和 `undefined` 之间切换。
407
408 ### `confirm()` 内需要 loading 时
409
410 将异步逻辑放进 `onOk`,让弹窗内部自己管理确认按钮 loading,不要在 `confirm()` 外部再维护一层 loading。
411
411 lines MARKDOWN