@routerhub/agent-rules 1.5.85 → 1.5.86
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.base.md +44 -485
- package/merge.js +52 -0
- package/package.json +1 -1
- package/rules/devops.md +1 -22
- package/rules/frontend.md +2 -156
- package/rules/global.md +37 -290
- package/rules/go-backend.md +2 -17
package/merge.js
CHANGED
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
* .github/copilot-instructions.md 全局规则(VS Code Copilot)
|
|
19
19
|
* .github/instructions/*.instructions.md 按域条件加载(VS Code Copilot applyTo)
|
|
20
20
|
* .github/PULL_REQUEST_TEMPLATE.md 统一 PR 模板(GitHub 新建 PR 时预填)
|
|
21
|
+
* .claude/skills/* 同步 Skills 到项目本地
|
|
21
22
|
*/
|
|
22
23
|
|
|
23
24
|
const fs = require("fs");
|
|
@@ -41,6 +42,8 @@ function getDefaultConfig() {
|
|
|
41
42
|
privateRulesPath: path.join(currentRoot, "AGENTS.private.md"),
|
|
42
43
|
prTemplateSrc: path.join(packageRoot, "PULL_REQUEST_TEMPLATE.md"),
|
|
43
44
|
prTemplateOutput: path.join(currentRoot, ".github", "PULL_REQUEST_TEMPLATE.md"),
|
|
45
|
+
skillsSrc: path.join(packageRoot, "skills"),
|
|
46
|
+
skillsDst: path.join(currentRoot, ".claude", "skills"),
|
|
44
47
|
};
|
|
45
48
|
}
|
|
46
49
|
|
|
@@ -393,6 +396,45 @@ function syncPrTemplate(config) {
|
|
|
393
396
|
console.log(`✅ PR 模板已同步到 ${output}`);
|
|
394
397
|
}
|
|
395
398
|
|
|
399
|
+
/**
|
|
400
|
+
* 同步 skills/ 目录到项目 .claude/skills/
|
|
401
|
+
* 将包内的 skill 文件复制到消费项目,供 Claude Code 识别和调用
|
|
402
|
+
*/
|
|
403
|
+
function syncSkills(config) {
|
|
404
|
+
const defaultConfig = getDefaultConfig();
|
|
405
|
+
const src = config.skillsSrc || defaultConfig.skillsSrc;
|
|
406
|
+
const dst = config.skillsDst || defaultConfig.skillsDst;
|
|
407
|
+
|
|
408
|
+
if (!fs.existsSync(src)) {
|
|
409
|
+
console.log(" ℹ️ skills/ 目录不存在,跳过技能同步");
|
|
410
|
+
return;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
const skillDirs = fs.readdirSync(src).filter((name) => {
|
|
414
|
+
const stat = fs.statSync(path.join(src, name));
|
|
415
|
+
return stat.isDirectory();
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
if (skillDirs.length === 0) {
|
|
419
|
+
console.log(" ℹ️ skills/ 目录为空,跳过技能同步");
|
|
420
|
+
return;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
fs.mkdirSync(dst, { recursive: true });
|
|
424
|
+
|
|
425
|
+
let syncedCount = 0;
|
|
426
|
+
for (const dirName of skillDirs) {
|
|
427
|
+
const srcDir = path.join(src, dirName);
|
|
428
|
+
const dstDir = path.join(dst, dirName);
|
|
429
|
+
|
|
430
|
+
// 递归复制整个 skill 目录(含 SKILL.md + references/ 等子文件)
|
|
431
|
+
fs.cpSync(srcDir, dstDir, { recursive: true });
|
|
432
|
+
syncedCount++;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
console.log(`✅ 已同步 ${syncedCount} 个技能到 ${dst}`);
|
|
436
|
+
}
|
|
437
|
+
|
|
396
438
|
function mergeAgents(config) {
|
|
397
439
|
const defaultConfig = getDefaultConfig();
|
|
398
440
|
const {
|
|
@@ -442,6 +484,9 @@ function mergeAgents(config) {
|
|
|
442
484
|
fs.writeFileSync(outputPath, merged, "utf-8");
|
|
443
485
|
console.log(`✅ 规则已合并到 ${outputPath}`);
|
|
444
486
|
}
|
|
487
|
+
|
|
488
|
+
// 同步 Skills
|
|
489
|
+
syncSkills(config);
|
|
445
490
|
return;
|
|
446
491
|
}
|
|
447
492
|
|
|
@@ -508,6 +553,9 @@ function mergeAgents(config) {
|
|
|
508
553
|
ensureParentDir(claudeOutput);
|
|
509
554
|
fs.writeFileSync(claudeOutput, agentsMerged, "utf-8");
|
|
510
555
|
console.log(`✅ 全量规则已合并到 ${claudeOutput}`);
|
|
556
|
+
|
|
557
|
+
// 6.1 同步 Skills 到项目 .claude/skills/
|
|
558
|
+
syncSkills(config);
|
|
511
559
|
}
|
|
512
560
|
|
|
513
561
|
function initAgents() {
|
|
@@ -530,6 +578,7 @@ function initAgents() {
|
|
|
530
578
|
console.log(" - AGENTS.md(全量,兼容 Cursor/Claude Code)");
|
|
531
579
|
console.log(" - .github/copilot-instructions.md(全局规则)");
|
|
532
580
|
console.log(" - .github/instructions/*.instructions.md(按域条件加载)");
|
|
581
|
+
console.log(" - .claude/skills/*(Claude Code Skills 技能文件)");
|
|
533
582
|
}
|
|
534
583
|
|
|
535
584
|
function runSync(triggerSource) {
|
|
@@ -688,6 +737,9 @@ function main() {
|
|
|
688
737
|
console.log(
|
|
689
738
|
" .github/instructions/*.instructions.md 按域条件加载(VS Code Copilot applyTo)",
|
|
690
739
|
);
|
|
740
|
+
console.log(
|
|
741
|
+
" .claude/skills/* Claude Code Skills(创建PR / 部署 / Figma / TDD / 写文档等)",
|
|
742
|
+
);
|
|
691
743
|
return;
|
|
692
744
|
}
|
|
693
745
|
|
package/package.json
CHANGED
package/rules/devops.md
CHANGED
|
@@ -6,25 +6,4 @@ outputName: "devops"
|
|
|
6
6
|
|
|
7
7
|
## 部署规则
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
- `.env` 文件中仅允许配置端口号(如 `PORT`、`API_PORT` 等),其余所有配置项(API 地址、数据库连接、第三方服务地址等)必须直接写死到代码中。
|
|
11
|
-
- 密钥、Token、密码、证书等敏感信息不得写入 `.env` 或代码中,必须统一配置到 Nacos 配置中心,应用启动时从 Nacos 拉取。
|
|
12
|
-
- 用户说「部署测试环境」时,必须按以下步骤执行:
|
|
13
|
-
1. **记录当前分支**:在切分支之前,先记录当前所在的功能分支名称(`git branch --show-current`),后续步骤需要用它切回来。
|
|
14
|
-
2. **test 分支检查**:如果当前项目的 `test` 分支不存在,从主分支(`main` 或 `master`)创建 `test` 分支并推送到远程。
|
|
15
|
-
3. **合并当前分支到 test**:将当前功能分支合并到 `test` 分支。
|
|
16
|
-
4. ⚠️ **推送 test 分支到远程**:合并完成后,必须立即将 `test` 分支推送到远程仓库(`git push origin test`),确保团队其他成员能看到 test 分支上的最新代码,避免远程 test 分支落后于本地。
|
|
17
|
-
5. **部署 test 分支**:按当前项目自己的部署方式部署 `test` 分支(如 Cloud Run 部署、容器部署、npm publish 等)。
|
|
18
|
-
6. **发新版本**:按当前项目自己的发版规则发布新版本(如 `./release.sh`、自动递增版本号等)。
|
|
19
|
-
7. ⚠️ **切回原功能分支**:部署和发版全部完成后,必须立即切回步骤 1 记录的原功能分支(`git checkout <原分支名>`),确保 VS Code 当前所在的 Git 分支恢复到部署前的功能分支,而不是停留在 `test` 分支。如果忘记切回,用户后续的开发工作会在 `test` 分支上进行,违反「禁止直接在 test 分支上提交代码」的规则。
|
|
20
|
-
- ⚠️ **部署必须自包含,禁止部署后人工补操作**:后端代码写完并部署时,所有依赖该代码的准备工作必须一并完成并通过自动化方式执行,不允许部署完成后再由人工手动执行命令补救。常见必须自动化的事项包括:
|
|
21
|
-
- **数据库 migration**:涉及 schema 变更(新增表、字段、索引、约束等)时,必须同时编写 migration 脚本,并集成到部署流程中自动执行(如应用启动时自动 migrate、CI/CD 中跑 migrate 命令等),禁止部署后人工登数据库手动执行 DDL。
|
|
22
|
-
- **数据迁移/回填脚本**:涉及存量数据清洗、转换、回填时,脚本必须随代码一起提交,并在部署流程中自动执行或在 PR 中明确写出执行计划。
|
|
23
|
-
- **配置变更**:涉及 Nacos 配置中心新增/修改配置项时,配置变更必须与代码部署同步完成,并在部署流程中自动同步或通过配置管理工具批量推送。
|
|
24
|
-
- **依赖更新**:涉及新的系统依赖(如新的中间件、新的外部服务地址、新的环境变量等)时,必须在部署脚本中自动检查依赖可用性,不存在时部署失败并明确报错,禁止静默跳过等人工发现。
|
|
25
|
-
- **缓存/队列/索引重建**:涉及 Redis 缓存结构变更、消息队列 topic 新增、ES 索引 mapping 变更等,必须脚本化并自动执行。
|
|
26
|
-
- 以上所有自动化脚本必须在 PR 的 Test Plan 中明确写出执行时机(部署前/部署中/部署后)、执行方式和验证方法,不得只写"部署后手动执行"。
|
|
27
|
-
|
|
28
|
-
## GCLB 规范
|
|
29
|
-
|
|
30
|
-
- ⚠️ **禁止修改 GCLB(Google Cloud Load Balancer)配置**。任何情况下不得新增、修改或删除 GCLB 相关配置,包括但不限于转发规则、后端服务、健康检查、SSL 证书、URL 映射等。
|
|
9
|
+
<!-- 部署特有规则在此添加 -->
|
package/rules/frontend.md
CHANGED
|
@@ -4,160 +4,6 @@ applyTo: ["**/*.ts","**/*.tsx","**/*.js","**/*.jsx","**/*.vue","**/*.css","**/*.
|
|
|
4
4
|
outputName: "frontend"
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
##
|
|
7
|
+
## 前端规则
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
- Figma 中配置或表达的跳转、链接、页面流、弹窗、hover/click 等交互必须同步实现;原型未明确但影响流程闭环的交互,需先核对现有产品或询问用户,禁止自行脑补。
|
|
11
|
-
- 交付前必须对照 Figma 做视觉和交互验收,发现无法完全还原的素材、字体、数据或平台限制时,必须明确说明差异和原因。
|
|
12
|
-
|
|
13
|
-
### Figma 设计还原规范(MCP 三步验证法)
|
|
14
|
-
|
|
15
|
-
⚠️ Figma 设计还原时,**禁止直接使用 `get_design_context` 生成的 Tailwind 代码**。`get_design_context` 的代码是大模型推导的近似值,数值(px、间距、尺寸)、布局结构(flex 方向、嵌套关系)和定位方式(absolute/flex)都可能与 Figma 真实设计不符,必须通过以下三步验证:
|
|
16
|
-
|
|
17
|
-
**第 1 步:`get_design_context` → 获取设计意图**
|
|
18
|
-
- 用 `get_design_context` 了解组件的整体结构、颜色、字体、视觉效果
|
|
19
|
-
- ⚠️ 仅用于理解设计意图,**数值和结构不可信**
|
|
20
|
-
|
|
21
|
-
**第 2 步:`get_metadata` → 获取精确坐标(唯一事实源)**
|
|
22
|
-
- `get_metadata` 返回的是 Figma 内部每个节点的**真实 x/y/width/height 坐标**,这是所有尺寸、间距、定位的唯一事实源
|
|
23
|
-
- 用 metadata 坐标反推间距:section 内子元素 y 坐标 = section 的 top padding,子元素高度 + 上边距 + 下边距 = 父级高度
|
|
24
|
-
- 用 metadata 验证布局结构:同级兄弟节点的 x/y 关系决定它们是横向还是纵向排列,子节点 x 坐标决定它是 flex 子元素还是 absolute 定位
|
|
25
|
-
|
|
26
|
-
**第 3 步:`get_metadata` 坐标反推规则**
|
|
27
|
-
|
|
28
|
-
| 场景 | 反推方法 |
|
|
29
|
-
|------|----------|
|
|
30
|
-
| Section 上下 padding | 子元素 y 坐标 = 上 padding;父级高度 - 子元素 y - 子元素高度 = 下 padding |
|
|
31
|
-
| 元素间距(gap) | 兄弟元素之间的 y 差值(纵向)或 x 差值(横向) |
|
|
32
|
-
| 元素是 flex 还是 absolute | 子元素 x 超出父元素左边界 → absolute;子元素在父元素范围内 → flex |
|
|
33
|
-
| 固定宽度还是自适应 | metadata 有明确 width 值 → 固定宽度;否则 → 自适应 |
|
|
34
|
-
| 元素是否有旋转/变换 | `get_design_context` 中有 `rotate`、`-translate-` 等类名 |
|
|
35
|
-
|
|
36
|
-
**常见踩坑清单:**
|
|
37
|
-
|
|
38
|
-
1. **`get_design_context` 生成的 padding 不准** → 必须以 metadata 坐标为基准重新计算
|
|
39
|
-
2. **按钮被嵌套在文字 `flex-col` 内部** → metadata 中同级 x 坐标不同、y 坐标相近 = 横向并排,不应嵌套
|
|
40
|
-
3. **固定宽度容器被改成了全宽** → metadata 中有明确 width 的容器不能去掉固定宽度
|
|
41
|
-
4. **装饰图片被当成 flex 子元素** → metadata 中 x 坐标超出父元素范围 = absolute 定位
|
|
42
|
-
5. **`justify-between` + 全宽把元素推到极端两端** → 必须先确认 metadata 中内容行的宽度
|
|
43
|
-
|
|
44
|
-
## 文件约束
|
|
45
|
-
|
|
46
|
-
- 不随意修改核心业务文件和 API 相关代码,不编写或修改 `README.md`。
|
|
47
|
-
- TypeScript 类型优先复用 `typings.d.ts`,不存在时再自定义。
|
|
48
|
-
|
|
49
|
-
## 命名与类型
|
|
50
|
-
|
|
51
|
-
- 驼峰命名(小驼峰/大驼峰),禁止下划线;变量至少两个单词。
|
|
52
|
-
- 禁止 `as` 和 `any`;`props` 类型优先复用,不重复定义。
|
|
53
|
-
|
|
54
|
-
## 代码风格
|
|
55
|
-
|
|
56
|
-
- 函数式编程,不写 `class`,不写 `try/catch`。
|
|
57
|
-
- 禁止重复实现:复用现有函数/组件/配置,发现重复必须提取封装为公共方法。
|
|
58
|
-
- 同一数据/配置只在一处维护,其余通过引用获取,改一处全局生效。
|
|
59
|
-
- 禁止硬编码数字;常量/枚举定义在 `Const.ts` 或 `constants.ts`,`tab` 索引统一使用常量,时间用 `dayjs`。
|
|
60
|
-
- 遵循 SOLID 原则,避免过度设计;数据回显优先用展开运算符(`...`)。
|
|
61
|
-
- 复用组件通过 `fromType`(值为当前页面名)区分来源,内部差异逻辑基于 `fromType` 分支。
|
|
62
|
-
|
|
63
|
-
## 样式(Tailwind CSS)
|
|
64
|
-
|
|
65
|
-
- 前端开发禁止编写原生 CSS,所有样式必须使用 Tailwind CSS 的 utility classes 实现,不写 `style` 标签、不写内联样式、不写独立样式文件。
|
|
66
|
-
- 统一 Tailwind CSS,禁止新增 `*.module.scss`、`*.module.css`、`.css` 文件(历史文件可保留)。
|
|
67
|
-
- 项目未集成 Tailwind 时,先完成安装配置再开发。
|
|
68
|
-
- 新增样式通过 utility classes 在 JSX/TSX 中编写;不满足时优先 `tailwind.config` extend → `@apply` 封装 → 行内 `style`。
|
|
69
|
-
- 复杂/重复样式提取为 Tailwind 组件类或 React 组件,避免工具类堆砌。
|
|
70
|
-
- 类名小驼峰,禁止 `span` 标签选择器。
|
|
71
|
-
|
|
72
|
-
## 前端尺寸单位规范
|
|
73
|
-
|
|
74
|
-
- ⚠️ **前端项目所有尺寸单位必须使用 `rem`,禁止使用 `px`**。包括但不限于:`width`、`height`、`margin`、`padding`、`font-size`、`border-radius`、`gap`、`line-height`、`top`、`left`、`right`、`bottom` 等所有 CSS 属性的数值。
|
|
75
|
-
- 根元素 `html` 的 `font-size` 默认设置为 `16px`(即 `1rem = 16px`),设计稿中的 `px` 值需转换为 `rem`(公式:`rem = px / 16`)。
|
|
76
|
-
- Tailwind CSS 的 spacing 配置需同步改为 `rem` 单位,禁止在 `tailwind.config` 中使用 `px` 值。
|
|
77
|
-
|
|
78
|
-
## 组件复用
|
|
79
|
-
|
|
80
|
-
- 优先复用已有组件,能通过 `props` 定制就不建新组件;新功能尽量封装为可复用组件。
|
|
81
|
-
|
|
82
|
-
## 前端 API 规范
|
|
83
|
-
|
|
84
|
-
- API 请求严格使用 OpenAPI 生成的方法,禁止手写请求或直接拼接路径。
|
|
85
|
-
- 接口变更后先更新 OpenAPI 定义并重新生成 API 代码,再进行业务开发。
|
|
86
|
-
|
|
87
|
-
## 依赖与构建
|
|
88
|
-
|
|
89
|
-
- 依赖安装统一 `pnpm`,禁止 `npm install`/`yarn install`。
|
|
90
|
-
- 写完代码先 `npm run format`,再 `npm run build`,构建通过才可发版。
|
|
91
|
-
- AI 改完代码后禁止自动执行任何格式化命令(包括但不限于 `npm run format`、`pnpm run format`、`prettier`、`eslint --fix` 等),避免产生仅含格式差异的文件变更,防止 PR 中混入大量与业务逻辑无关的格式化 diff;仅在用户明确要求格式化时才执行格式化操作。
|
|
92
|
-
|
|
93
|
-
## 测试
|
|
94
|
-
|
|
95
|
-
- 测试描述、断言使用中文,覆盖中文场景;充值类测试默认用充值 1 的数据。
|
|
96
|
-
- 用户明确要求由代理自行执行自动化测试时,允许使用无头浏览器进行测试与验证。
|
|
97
|
-
- 写完业务后执行 `pnpm run test:e2e:ui`,告知开发者对应测试用例名称;通过后打开 UI 供手动验证。
|
|
98
|
-
- 端口被占用时自动切换新端口。
|
|
99
|
-
- 自动化测试中,若 Mock 数据不影响业务逻辑验证,优先使用 Mock 数据代替真实接口调用,减少外部依赖和测试不稳定性。
|
|
100
|
-
- ⚠️ **测试用例编写顺序:必须先写主流程,再写边界情况**。主流程(happy path)是最重要的,必须优先覆盖并确保通过;边界情况(异常输入、空值、超时、并发竞态等)重要性相对较低,在主流程全部覆盖完毕后再补充。禁止主流程还没写完就去写边界情况测试,也禁止因为花太多时间在边界情况上而遗漏主流程覆盖。
|
|
101
|
-
- ⚠️ **测试用例必须模拟真实用户的操作流程,禁止只写「XX 可见」类表面检查**。测试用例清单中的每一条主流程用例,必须以真实用户的操作路径来编写:打开页面 → 查看初始状态 → 点击按钮/填写表单 → 提交/保存 → 验证结果页面是否正确展示。核心覆盖场景必须包括:
|
|
102
|
-
- **新建**:从入口进入 → 填写表单 → 提交 → 在列表/详情页验证新建数据是否正确显示,截图每步关键状态。
|
|
103
|
-
- **编辑**:从列表/详情进入编辑 → 修改字段 → 保存 → 验证修改后的数据在页面上是否正确回显,截图前后对比。
|
|
104
|
-
- **删除**:触发删除 → 确认弹窗 → 确认后验证列表/页面中该数据已消失,截图删除前列表和删除后列表对比。
|
|
105
|
-
- **查看/搜索**:进入列表页 → 使用搜索/筛选 → 验证搜索结果是否符合筛选条件,截图搜索结果。
|
|
106
|
-
- **状态流转**:如果业务有状态变更(如审核、启用/禁用、支付等),必须覆盖每个状态流转的操作和页面反馈,截图每个状态下的页面展示。
|
|
107
|
-
- **异常操作**:空表单提交、超长文本输入、非法字符、重复提交等用户可能触发的异常场景,截图错误提示和页面状态。
|
|
108
|
-
- ⚠️ **每一条测试用例必须附带对应步骤的截图,缺一不可**。测试用例清单中每条用例必须写出预期要截哪些图(如「截图1:新建表单填写完成」「截图2:提交成功后列表页新数据出现」),实际执行时每张截图都必须产出并嵌入 HTML 报告。禁止只写操作步骤而不产出对应截图,禁止用「已验证通过」等文字描述代替截图。
|
|
109
|
-
|
|
110
|
-
## 错误日志
|
|
111
|
-
|
|
112
|
-
- 用 `console.error` 记录错误(含函数名/模块名上下文),禁止 `console.log` 输出错误。
|
|
113
|
-
- 提交前移除调试日志和临时代码。
|
|
114
|
-
- 调试过程中产生的中间产物(临时文件、测试脚本、调试截图、dump 文件、临时注释、`console.log` 等)禁止加入 Git 提交,`.gitignore` 中应配置忽略常见中间产物。
|
|
115
|
-
|
|
116
|
-
## Modal 内 Tooltip 规范
|
|
117
|
-
|
|
118
|
-
在 Modal/弹窗内实现 tooltip 时,必须遵守以下规则,避免被 Modal 容器裁剪和消失太快两个问题。
|
|
119
|
-
|
|
120
|
-
### 1. Tooltip 必须用 Portal 渲染到 document.body
|
|
121
|
-
|
|
122
|
-
**问题**:Modal 容器通常有 `overflow: hidden` 或 `overflow-y: auto`,用 CSS `position: absolute/fixed` 的 tooltip 会被裁掉。
|
|
123
|
-
|
|
124
|
-
**解决**:用 React Portal(`createPortal`)把 tooltip 气泡渲染到 `document.body`,完全绕开 Modal 的 overflow 裁剪。
|
|
125
|
-
|
|
126
|
-
- 用 `getBoundingClientRect()` 获取触发元素(`?` 图标)的屏幕坐标
|
|
127
|
-
- 气泡用 `position: fixed` + `transform: translate(...)` 精确定位在触发元素上方
|
|
128
|
-
- `z-index` 至少 10000,确保在所有弹窗层之上
|
|
129
|
-
|
|
130
|
-
### 2. 延迟隐藏 + 气泡可 hover
|
|
131
|
-
|
|
132
|
-
**问题**:鼠标离开触发元素时 tooltip 立即消失,用户来不及把鼠标移到气泡上阅读内容。
|
|
133
|
-
|
|
134
|
-
**解决**:
|
|
135
|
-
- 隐藏加 200ms 延迟(`setTimeout`),给用户反应时间
|
|
136
|
-
- 气泡本身绑定 `onMouseEnter`(取消隐藏定时器)和 `onMouseLeave`(触发同样的延迟隐藏)
|
|
137
|
-
- CSS 上气泡容器必须 `pointer-events: auto`(不能是 `none`),否则鼠标事件不触发
|
|
138
|
-
|
|
139
|
-
### 3. 参考实现
|
|
140
|
-
|
|
141
|
-
实现一个可复用的 `FieldTooltip` 组件,核心结构:
|
|
142
|
-
|
|
143
|
-
```tsx
|
|
144
|
-
import { createPortal } from 'react-dom';
|
|
145
|
-
|
|
146
|
-
function FieldTooltip({ text }: { text: string }) {
|
|
147
|
-
const iconRef = useRef<HTMLSpanElement>(null);
|
|
148
|
-
const hideTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
149
|
-
const [visible, setVisible] = useState(false);
|
|
150
|
-
const [pos, setPos] = useState({ top: 0, left: 0 });
|
|
151
|
-
|
|
152
|
-
// show() → 取消定时器 → getBoundingClientRect 更新坐标 → setVisible(true)
|
|
153
|
-
// hide() → 200ms setTimeout → setVisible(false)
|
|
154
|
-
// clearHideTimer() → clearTimeout
|
|
155
|
-
|
|
156
|
-
// ? 图标: onMouseEnter={show} onMouseLeave={hide}
|
|
157
|
-
// Portal 气泡: onMouseEnter={clearHideTimer} onMouseLeave={hide}
|
|
158
|
-
// position: fixed, z-index: 10000, pointer-events: auto
|
|
159
|
-
}
|
|
160
|
-
```
|
|
161
|
-
|
|
162
|
-
- 禁止在 Modal 内用纯 CSS `position: absolute` 的 hover tooltip
|
|
163
|
-
- 写完 tooltip 后必须实际验证:弹窗内滚动时气泡不被裁剪,鼠标能从图标移到气泡上阅读
|
|
9
|
+
<!-- 前端特有规则在此添加 -->
|