diff --git a/.idea/shelf/_2026_6_16_09_56____.xml b/.idea/shelf/_2026_6_16_09_56____.xml deleted file mode 100644 index 5292170b5..000000000 --- a/.idea/shelf/_2026_6_16_09_56____.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/.idea/shelf/_2026_6_16_10_44____.xml b/.idea/shelf/_2026_6_16_10_44____.xml deleted file mode 100644 index 75d3ba524..000000000 --- a/.idea/shelf/_2026_6_16_10_44____.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/.idea/shelf/_2026_6_16_13_36____.xml b/.idea/shelf/_2026_6_16_13_36____.xml deleted file mode 100644 index 2571aab2a..000000000 --- a/.idea/shelf/_2026_6_16_13_36____.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/.idea/shelf/_2026_6_16_13_38____.xml b/.idea/shelf/_2026_6_16_13_38____.xml deleted file mode 100644 index a2eb32e58..000000000 --- a/.idea/shelf/_2026_6_16_13_38____.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/.idea/shelf/_2026_6_16_15_24____.xml b/.idea/shelf/_2026_6_16_15_24____.xml deleted file mode 100644 index c3b210252..000000000 --- a/.idea/shelf/_2026_6_16_15_24____.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/.idea/shelf/_2026_6_16_16_12____.xml b/.idea/shelf/_2026_6_16_16_12____.xml deleted file mode 100644 index e8bd25acc..000000000 --- a/.idea/shelf/_2026_6_16_16_12____.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/.idea/shelf/_2026_6_17_08_41____.xml b/.idea/shelf/_2026_6_17_08_41____.xml deleted file mode 100644 index 733c2dc3f..000000000 --- a/.idea/shelf/_2026_6_17_08_41____.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/.idea/shelf/在进行更新之前于_2026_6_16_09_56_取消提交了更改_[更改]/HEALTHLINK_HIS_PRICING_v0.1.docx b/.idea/shelf/在进行更新之前于_2026_6_16_09_56_取消提交了更改_[更改]/HEALTHLINK_HIS_PRICING_v0.1.docx deleted file mode 100644 index cc4e0c4eb..000000000 Binary files a/.idea/shelf/在进行更新之前于_2026_6_16_09_56_取消提交了更改_[更改]/HEALTHLINK_HIS_PRICING_v0.1.docx and /dev/null differ diff --git a/.idea/shelf/在进行更新之前于_2026_6_16_09_56_取消提交了更改_[更改]/shelved.patch b/.idea/shelf/在进行更新之前于_2026_6_16_09_56_取消提交了更改_[更改]/shelved.patch deleted file mode 100644 index b1c986acb..000000000 --- a/.idea/shelf/在进行更新之前于_2026_6_16_09_56_取消提交了更改_[更改]/shelved.patch +++ /dev/null @@ -1,249 +0,0 @@ -Index: .aider.conf.yml -IDEA additional info: -Subsystem: com.intellij.openapi.diff.impl.patch.BaseRevisionTextPatchEP -<+># Aider configuration for HealthLink-HIS\n# Aider 自动读取此文件获取开发规范\n\ninstructions: |\n # HealthLink-HIS — AI 开发规范(自动加载)\n \n > \uD83E\uDD16 **本文件供所有 AI 编码工具自动读取**。进入本项目后必须遵守以下规范。\n > \n > **模型决定上限,Harness 决定底线。**\n \n ---\n \n ## 一、项目概览\n \n | 属性 | 值 |\n |------|------|\n | 项目名 | HealthLink-HIS(医院信息系统) |\n | 后端路径 | `healthlink-his-server/` |\n | 前端路径 | `healthlink-his-ui/` |\n | 文档路径 | `MD/` |\n | JDK | 25 (OpenJDK) |\n | Spring Boot | 4.0.6 |\n | MyBatis-Plus | 3.5.16 |\n | Vue | 3.x + Vite + Element Plus |\n | 数据库 | PostgreSQL 15+ |\n | 包名 | `com.healthlink.his` |\n | 后端端口 | 18082 |\n | 前端端口 | 81 |\n \n ---\n \n ## 二、铁律(必须遵守,违反即失败)\n \n ### \uD83D\uDD34 P0 铁律 — 不可违反\n \n **铁律1: 修改完必须测试**\n ```\n 后端: mvn clean compile -DskipTests → mvn install -DskipTests → mvn test\n 前端: npm run build:dev → npm run lint\n ```\n - 白盒:编译通过,无 ERROR\n - 黑盒:关键接口返回 `{code:200, data:...}`,验证业务逻辑\n - 冒烟:应用正常启动,核心流程通畅\n \n **铁律2: Flyway 数据库迁移**\n - 凡是新建表、新增字段,必须创建 Flyway 迁移脚本\n - 路径:`healthlink-his-domain/src/main/resources/db/migration/`\n - 命名:`V{版本号}__{描述}.sql`(双下划线)\n \n **铁律3: 测试通过后才提交**\n - 编译 + 测试全部通过后才能 git commit\n - 不提交未完成的功能、调试代码、临时文件\n \n **铁律4: 前后端API路径对齐**\n - 后端前缀:`/healthlink-his/api/v1/`\n - 前端 `request.js` 的 baseURL 必须与后端匹配\n \n **铁律5: 状态值一致性(Bug #574 教训)**\n - 修改任何状态值前,必须先列出完整的状态流转链路\n - 检查项:枚举定义 → Service 设置 → 查询映射 → 前端 STATUS_CLASS_MAP → 前端 v-if → 统计SQL\n - 禁止:只改一端不检查其他端\n \n **铁律6: 禁止删除源文件(Bug #574 教训)**\n - 绝对禁止删除项目中已有的 Java/Vue/SQL 源文件\n - 编译错误 → 修复错误;重复文件 → 重构合并\n - 唯一例外:明确由人类确认删除的文件\n \n **铁律7: 禁止修改已有公开方法签名**\n - 不能删除/重命名已有的 public 方法,不能修改参数列表\n - 需要新功能 → 添加重载方法;需要改行为 → 修改内部实现\n \n **铁律8: 验证后才宣称完成(Verification Before Completion)**\n - **没有跑过验证命令,就不能说\"完成了\"\"通过了\"\"没问题\"**\n - 禁止使用\"应该可以\"\"大概没问题\"\"看起来正确\"\n - 必须:运行命令 → 读取输出 → 确认结果 → 才能宣称\n - 这是诚实原则,不是效率问题\n \n \n **铁律9: 开发前必须审核原有代码(P0 — 铁律)**\n - **任何新功能开发前,必须先搜索项目中是否已有相关代码**\n - 搜索路径:Controller / AppService / Service / Mapper / Entity / 前端页面 / API接口\n - 如果已有部分功能 → 在原有代码基础上**升级优化完善**,禁止另起炉灶\n - 如果已有接口但前端缺失 → 只补前端,不重复建后端\n - 如果已有前端但后端缺失 → 只补后端,不重写前端\n - 搜索命令:`rg -l \"关键词\" healthlink-his-server/ healthlink-his-ui/src/`\n - 禁止:不看代码就新建模块、重复实现已有功能、废弃原有代码另写一套\n \n \n **铁律12: 设计文档确认后自主开发(铁律)**\n - 设计文档(如 `MD/architecture/GRADE3A_GAP_ANALYSIS_AND_DESIGN.md`)一旦确认,后续开发**必须按文档自主执行**\n - **禁止反复询问\"是否继续\"\"下一步做什么\"\"是否开始\"**——直接按计划推进\n - 每完成一个 Sprint,自动提交推送,然后立即开始下一个 Sprint\n - 只在遇到**无法解决的阻塞**(如技术选型冲突、需求不明确、第三方依赖不可用)时才暂停询问\n - 设计文档是\"**已签合同**\",不是\"参考意见\"。铁律执行优先级:设计文档 > 人类临时指令 > AI 自行判断\n \n \n ### \uD83D\uDFE1 P1 铁律 — 强烈建议\n \n **铁律9: 先分解再行动**\n - 修改超过3个文件、涉及多模块、数据库变更,必须先制定计划\n \n **铁律10: 验证后信**\n - 每次修改后必须验证编译通过,不信记忆\n \n **铁律13: 文档统一管理**\n - 所有文档存储在 `MD/` 目录\n - 文件名:大写英文+下划线(如 `BACKEND_CHECKLIST.md`)\n - 文档头部必须包含元数据块(文档类型、版本、日期)\n \n ---\n \n \n **铁律14: 设计文档必须包含UI设计和调用流程**\n - 所有新模块/页面的设计文档必须包含:UI布局描述、交互效果清单、前后端调用流程\n - 没有明确UI设计的模块,禁止直接编码\n - 详见 \n - 设计文档必须写清楚:系统调用关系、方法函数调用关系、完整业务流程\n - 设计文档中每个用户操作必须对应:前端事件 → API调用 → 后端处理链路 → 返回数据 → UI渲染\n \n ---\n \n ## 三、Karpathy 编码准则\n \n > 减少 LLM 常见编码错误。偏向谨慎而非速度。\n \n ### 3.1 先想再写\n - 明确陈述假设,不确定就问\n - 多种解读时都列出来,不要默默选一种\n - 有更简单的方案就说出来,该推回就推回\n - 不清楚的地方停下来,说清楚哪里不清楚\n \n ### 3.2 简洁优先\n - 不做没要求的功能,不做一次性代码的抽象\n - 不加没要求的\"灵活性\"和\"可配置性\"\n - 200 行能 50 行搞定就重写\n - 自问:\"高级工程师会不会觉得这过度设计?\"\n \n ### 3.3 精准修改\n - 只改必须改的,不\"顺手改进\"相邻代码\n - 匹配现有代码风格,即使你有不同的偏好\n - 每行改动都能追溯到用户的请求\n - 只清理你自己改动产生的无用代码\n \n ### 3.4 目标驱动\n - 把任务转化为可验证目标\n - 多步任务声明计划:`[步骤] → 验证: [检查]`\n - 强验收标准让 Agent 能独立循环,弱标准需要持续澄清\n \n ---\n \n ## 四、全链路 6 环分析\n \n > ⚠\uFE0F **涉及数据库字段的 Bug / 需求,必须走完整链路。**\n \n ```\n 前端/页面 → Controller → Service → Mapper → DB/SQL → 关联模块\n ①录入 ②验证 ③业务 ④持久化 ⑤存储 ⑥联动\n ```\n \n | 环 | 检查内容 |\n |----|---------|\n | ① 录入 | 前端有无输入入口(弹窗、表格行编辑、表单) |\n | ② 验证 | Controller 参数校验、@Valid、权限控制 |\n | ③ 业务 | Service 业务逻辑、事务边界、多个 Service 实现类入口 |\n | ④ 持久化 | Mapper XML、DTO 字段映射、类型转换 |\n | ⑤ 存储 | 数据库表结构、索引、NOT NULL 约束 |\n | ⑥ 联动 | 上游(医嘱→护士站)、下游(打印、计费、报表)是否同步 |\n \n **修复后的验证顺序**:\n 1. 数据库:确认状态值已正确写入\n 2. 后端接口:确认返回的状态映射正确\n 3. 前端显示:确认页面显示正确状态文本\n 4. 前端交互:确认按钮/操作基于正确状态启用/禁用\n 5. 统计数据:确认池/报表统计包含新状态\n \n ---\n \n ## 五、Harness Engineering 方法论\n \n > Harness = 约束 + 反馈 + 控制平面 + 持久执行\n \n ### 5.1 四层约束金字塔\n \n | 层级 | 内容 | 落地方式 |\n |------|------|---------|\n | **L1 架构约束** | 接口合约、包结构、命名规范、禁止模式 | 本文件铁律 |\n | **L2 代码质量** | 圈复杂度、代码风格、类型提示 | 编译门禁 + ESLint |\n | **L3 安全约束** | 敏感信息检测、权限检查、输入验证 | 配置不可硬编码 |\n | **L4 业务规则** | 领域逻辑、数据一致性、事务边界 | 全链路 6 环验证 |\n \n **约束设计原则**:\n - **可验证**:每条约束必须能被自动化检查(\"覆盖率>90%\"✅ \"质量要高\"❌)\n - **无歧义**:\"每函数不超过50行\"✅ \"函数不要太长\"❌\n - **优先级**:安全(1) > 架构(2) > 业务(3) > 质量(4) > 性能(5)\n - **渐进增强**:L1编译通过 → L2+命名规范 → L3+测试覆盖 → L4+安全扫描\n \n ### 5.2 三层反馈系统\n \n | 层级 | 速度 | 覆盖范围 | 失败处理 |\n |------|------|---------|---------|\n | **L1 编译检查** | <30秒 | 语法、类型、签名 | 立即阻断,自行修复 |\n | **L2 数据流验证** | <5分钟 | 全链路字段、Mapper XML、DTO | 修复后上报 |\n | **L3 人工审查** | 10-30分钟 | 架构、设计、业务正确性 | 驳回/指导/批准 |\n \n **反馈铁律**:\n - 反馈必须可行动(文件 + 行号 + 错误类型 + 修复方向)\n - 失败后先回滚到最近检查点,再重试\n - 持续失败3次 → 上报人类\n \n ### 5.3 控制平面\n \n ```\n 战略层(人类) → 设定目标、审批决策、异常升级\n 战术层(Agent) → 任务分解、update_plan、依赖协调、检查点保存\n 执行层(Agent) → 代码生成、测试执行、错误恢复、幂等重试\n ```\n \n ### 5.4 持久执行\n \n - 每个关键步骤保存检查点(`update_plan` 进度)\n - 失败后从最新检查点恢复,不从头开始\n - 幂等设计:同一操作重复执行结果一致\n - **三层状态管理**:系统层(工作流ID/超时/重试) → 执行层(当前活动/进度) → 业务层(已完成工作/中间产物)\n \n ---\n \n ## 六、五层质量门禁\n \n | 门禁 | 时间 | 范围 | 失败处理 |\n |------|------|------|---------|\n | **L1 编译检查** | <30秒 | 语法、类型、导入 | Agent 自行修复 |\n | **L2 静态分析** | <2分钟 | 代码风格、复杂度、安全 | Agent 修复 |\n | **L3 单元测试** | <5分钟 | 功能正确性、边界条件 | 自动修复或上报 |\n | **L4 集成测试** | <15分钟 | 模块间交互、数据流 | 上报人工 |\n | **L5 生产验证** | 持续 | 监控、告警、性能 | 自动回滚 |\n \n **提交铁律**:L1-L2 必须通过才能 commit,L3(如有DB变更)必须通过才能 push\n \n ---\n \n ## 七、系统化调试(Systematic Debugging)\n \n > **铁律:没有根因调查,不能提出修复方案。**\n \n ### 四阶段流程\n \n **阶段1:根因调查**(修复前必须完成)\n 1. 仔细阅读错误信息(堆栈、行号、错误码)\n 2. 稳定复现(能否可靠触发?步骤?每次?)\n 3. 检查最近变更(git diff、新依赖、配置变更)\n 4. 多组件系统:在每个组件边界加诊断日志,定位哪一层断裂\n 5. 追踪数据流:坏值从哪里来?谁调用的?一直追溯到源头\n \n **阶段2:模式分析**\n - 找到同代码库中类似的正常工作代码\n - 逐项对比差异\n - 理解依赖关系\n \n **阶段3:假设与测试**\n - 形成单一假设:\"我认为X是根因,因为Y\"\n - 做最小改动测试\n - 有效 → 阶段4;无效 → 新假设\n \n **阶段4:实施**\n - 创建失败测试用例\n - 修复根因(不是症状)\n - 验证修复\n \n ---\n \n ## 八、后端开发规范\n \n ### 分层架构\n ```\n Controller → AppService → Service → Mapper → Entity\n ```\n \n ### 命名规范\n | 类型 | 规则 | 示例 |\n |------|------|------|\n | Controller | `XxxController` | `RegistrationController` |\n | AppService | `IXxxAppService` / `XxxAppServiceImpl` | `IRegistrationAppService` |\n | Service | `IXxxService` / `XxxServiceImpl` | `IRegistrationService` |\n | Mapper | `XxxMapper` | `RegistrationMapper` |\n | Entity | `Xxx` | `Registration` |\n | DTO | `XxxDto` / `XxxQueryDto` | `RegistrationDto` |\n \n ### 包结构\n ```\n com.healthlink.his.web.{module}.controller\n com.healthlink.his.web.{module}.appservice\n com.healthlink.his.web.{module}.service\n com.healthlink.his.web.{module}.mapper\n com.healthlink.his.web.{module}.dto\n com.healthlink.his.domain.{module}\n com.healthlink.his.common.enums\n ```\n \n ### 关键约束\n - 所有查询使用 `LambdaQueryWrapper`,禁止字符串拼接 SQL\n - `@Transactional(rollbackFor = Exception.class)` 管理事务\n - 所有接口标注 `@PreAuthorize` 权限控制\n - 患者敏感信息在日志中脱敏\n - **扩展功能不修改原有函数签名**\n \n ---\n \n ## 九、前端开发规范\n \n ### 技术栈\n - Vue 3 + Vite + Element Plus + Pinia + Axios(基于 RuoYi-Vue3)\n \n ### 目录结构\n ```\n src/api/{module}/ # API接口\n src/views/{module}/ # 页面组件\n src/store/modules/ # Pinia状态管理\n src/components/ # 公共组件\n ```\n \n ### 关键约束\n - API前缀:`/healthlink-his/api/v1/`\n - 路由懒加载:`() => import('@/views/xxx/index.vue')`\n - 页面使用 `\n -Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP -<+>UTF-8 -=================================================================== -diff --git a/healthlink-his-ui/src/views/empienhanced/patient/index.vue b/healthlink-his-ui/src/views/empienhanced/patient/index.vue ---- a/healthlink-his-ui/src/views/empienhanced/patient/index.vue (revision 8eb6feb70dc30c5984fa709873aac1c4c5809fb5) -+++ b/healthlink-his-ui/src/views/empienhanced/patient/index.vue (date 1781586081313) -@@ -1,8 +1,8 @@ - - - -- -- -+ -+ - - - -@@ -18,25 +18,66 @@ - - - 查询 -- 重置 -+ 重置 - - - - {{ patientData.globalId }} -- {{ patientData.patientName }} -+ {{ patientData.name }} - {{ patientData.gender === 'M' ? '男' : '女' }} - {{ patientData.birthDate }} - {{ patientData.idCardNo }} - {{ patientData.phone }} - {{ patientData.address }} -+ {{ patientData.sourceSystem }} -+ -+ -+ {{ patientData.mergeStatus === 'ACTIVE' ? '正常' : '已合并' }} -+ -+ - - - - -+ -+ -+ -+ 关联院内患者记录 ({{ linkedPatients.length }}) -+ -+ -+ -+ -+ -+ -+ {{ row.genderEnum === 1 ? '男' : '女' }} -+ -+ -+ {{ row.birthDate ? row.birthDate.substring(0,10) : '' }} -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ ID映射关系 -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ - - - -- -+ - - - -@@ -57,23 +98,58 @@ - -+ -\ No newline at end of file -Index: .windsurfrules -IDEA additional info: -Subsystem: com.intellij.openapi.diff.impl.patch.BaseRevisionTextPatchEP -<+># HealthLink-HIS — AI 开发规范 (Windsurf)\n\n> \uD83E\uDD16 Windsurf 打开本项目时自动加载此文件。\n\n---\n\n# HealthLink-HIS — AI 开发规范(自动加载)\n\n> \uD83E\uDD16 **本文件供所有 AI 编码工具自动读取**。进入本项目后必须遵守以下规范。\n> \n> **模型决定上限,Harness 决定底线。**\n\n---\n\n## 一、项目概览\n\n| 属性 | 值 |\n|------|------|\n| 项目名 | HealthLink-HIS(医院信息系统) |\n| 后端路径 | `healthlink-his-server/` |\n| 前端路径 | `healthlink-his-ui/` |\n| 文档路径 | `MD/` |\n| JDK | 25 (OpenJDK) |\n| Spring Boot | 4.0.6 |\n| MyBatis-Plus | 3.5.16 |\n| Vue | 3.x + Vite + Element Plus |\n| 数据库 | PostgreSQL 15+ |\n| 包名 | `com.healthlink.his` |\n| 后端端口 | 18082 |\n| 前端端口 | 81 |\n\n---\n\n## 二、铁律(必须遵守,违反即失败)\n\n### \uD83D\uDD34 P0 铁律 — 不可违反\n\n**铁律1: 修改完必须测试**\n```\n后端: mvn clean compile -DskipTests → mvn install -DskipTests → mvn test\n前端: npm run build:dev → npm run lint\n```\n- 白盒:编译通过,无 ERROR\n- 黑盒:关键接口返回 `{code:200, data:...}`,验证业务逻辑\n- 冒烟:应用正常启动,核心流程通畅\n\n**铁律2: Flyway 数据库迁移**\n- 凡是新建表、新增字段,必须创建 Flyway 迁移脚本\n- 路径:`healthlink-his-domain/src/main/resources/db/migration/`\n- 命名:`V{版本号}__{描述}.sql`(双下划线)\n\n**铁律3: 测试通过后才提交**\n- 编译 + 测试全部通过后才能 git commit\n- 不提交未完成的功能、调试代码、临时文件\n\n**铁律4: 前后端API路径对齐**\n- 后端前缀:`/healthlink-his/api/v1/`\n- 前端 `request.js` 的 baseURL 必须与后端匹配\n\n**铁律5: 状态值一致性(Bug #574 教训)**\n- 修改任何状态值前,必须先列出完整的状态流转链路\n- 检查项:枚举定义 → Service 设置 → 查询映射 → 前端 STATUS_CLASS_MAP → 前端 v-if → 统计SQL\n- 禁止:只改一端不检查其他端\n\n**铁律6: 禁止删除源文件(Bug #574 教训)**\n- 绝对禁止删除项目中已有的 Java/Vue/SQL 源文件\n- 编译错误 → 修复错误;重复文件 → 重构合并\n- 唯一例外:明确由人类确认删除的文件\n\n**铁律7: 禁止修改已有公开方法签名**\n- 不能删除/重命名已有的 public 方法,不能修改参数列表\n- 需要新功能 → 添加重载方法;需要改行为 → 修改内部实现\n\n**铁律8: 验证后才宣称完成(Verification Before Completion)**\n- **没有跑过验证命令,就不能说\"完成了\"\"通过了\"\"没问题\"**\n- 禁止使用\"应该可以\"\"大概没问题\"\"看起来正确\"\n- 必须:运行命令 → 读取输出 → 确认结果 → 才能宣称\n- 这是诚实原则,不是效率问题\n\n\n**铁律9: 开发前必须审核原有代码(P0 — 铁律)**\n- **任何新功能开发前,必须先搜索项目中是否已有相关代码**\n- 搜索路径:Controller / AppService / Service / Mapper / Entity / 前端页面 / API接口\n- 如果已有部分功能 → 在原有代码基础上**升级优化完善**,禁止另起炉灶\n- 如果已有接口但前端缺失 → 只补前端,不重复建后端\n- 如果已有前端但后端缺失 → 只补后端,不重写前端\n- 搜索命令:`rg -l \"关键词\" healthlink-his-server/ healthlink-his-ui/src/`\n- 禁止:不看代码就新建模块、重复实现已有功能、废弃原有代码另写一套\n\n\n**铁律12: 设计文档确认后自主开发(铁律)**\n- 设计文档(如 `MD/architecture/GRADE3A_GAP_ANALYSIS_AND_DESIGN.md`)一旦确认,后续开发**必须按文档自主执行**\n- **禁止反复询问\"是否继续\"\"下一步做什么\"\"是否开始\"**——直接按计划推进\n- 每完成一个 Sprint,自动提交推送,然后立即开始下一个 Sprint\n- 只在遇到**无法解决的阻塞**(如技术选型冲突、需求不明确、第三方依赖不可用)时才暂停询问\n- 设计文档是\"**已签合同**\",不是\"参考意见\"。铁律执行优先级:设计文档 > 人类临时指令 > AI 自行判断\n\n**铁律18: 禁止破坏原有功能(P0绝对铁律)**\n- **完善增加功能和流程时,绝对不能破坏或者让原有功能不能用**\n- 修改已有实体前必须对比原始文件(`git show HEAD~N:./file.java`),保留所有原有字段和方法\n- 新增字段只能追加,不能删除或重命名已有字段\n- SQL迁移只允许 `ALTER TABLE ADD COLUMN`,不允许 `DROP COLUMN` 或 `RENAME COLUMN`\n- Controller新端点不能修改已有端点的路径或参数\n- 前端新页面不能修改已有页面的组件结构\n- 每次修改后必须 `mvn clean compile -DskipTests` 验证\n- **违规判定**: 因修改导致原有代码编译失败或运行报错,视为违反铁律18,必须立即回滚修复\n\n\n### \uD83D\uDFE1 P1 铁律 — 强烈建议\n\n**铁律9: 先分解再行动**\n- 修改超过3个文件、涉及多模块、数据库变更,必须先制定计划\n\n**铁律10: 验证后信**\n- 每次修改后必须验证编译通过,不信记忆\n\n**铁律13: 文档统一管理**\n- 所有文档存储在 `MD/` 目录\n- 文件名:大写英文+下划线(如 `BACKEND_CHECKLIST.md`)\n- 文档头部必须包含元数据块(文档类型、版本、日期)\n\n---\n\n\n**铁律14: 设计文档必须包含UI设计和调用流程**\n- 所有新模块/页面的设计文档必须包含:UI布局描述、交互效果清单、前后端调用流程\n- 没有明确UI设计的模块,禁止直接编码\n- 详见 \n- 设计文档必须写清楚:系统调用关系、方法函数调用关系、完整业务流程\n- 设计文档中每个用户操作必须对应:前端事件 → API调用 → 后端处理链路 → 返回数据 → UI渲染\n\n---\n\n## 三、Karpathy 编码准则\n\n> 减少 LLM 常见编码错误。偏向谨慎而非速度。\n\n### 3.1 先想再写\n- 明确陈述假设,不确定就问\n- 多种解读时都列出来,不要默默选一种\n- 有更简单的方案就说出来,该推回就推回\n- 不清楚的地方停下来,说清楚哪里不清楚\n\n### 3.2 简洁优先\n- 不做没要求的功能,不做一次性代码的抽象\n- 不加没要求的\"灵活性\"和\"可配置性\"\n- 200 行能 50 行搞定就重写\n- 自问:\"高级工程师会不会觉得这过度设计?\"\n\n### 3.3 精准修改\n- 只改必须改的,不\"顺手改进\"相邻代码\n- 匹配现有代码风格,即使你有不同的偏好\n- 每行改动都能追溯到用户的请求\n- 只清理你自己改动产生的无用代码\n\n### 3.4 目标驱动\n- 把任务转化为可验证目标\n- 多步任务声明计划:`[步骤] → 验证: [检查]`\n- 强验收标准让 Agent 能独立循环,弱标准需要持续澄清\n\n---\n\n## 四、全链路 6 环分析\n\n> ⚠\uFE0F **涉及数据库字段的 Bug / 需求,必须走完整链路。**\n\n```\n前端/页面 → Controller → Service → Mapper → DB/SQL → 关联模块\n ①录入 ②验证 ③业务 ④持久化 ⑤存储 ⑥联动\n```\n\n| 环 | 检查内容 |\n|----|---------|\n| ① 录入 | 前端有无输入入口(弹窗、表格行编辑、表单) |\n| ② 验证 | Controller 参数校验、@Valid、权限控制 |\n| ③ 业务 | Service 业务逻辑、事务边界、多个 Service 实现类入口 |\n| ④ 持久化 | Mapper XML、DTO 字段映射、类型转换 |\n| ⑤ 存储 | 数据库表结构、索引、NOT NULL 约束 |\n| ⑥ 联动 | 上游(医嘱→护士站)、下游(打印、计费、报表)是否同步 |\n\n**修复后的验证顺序**:\n1. 数据库:确认状态值已正确写入\n2. 后端接口:确认返回的状态映射正确\n3. 前端显示:确认页面显示正确状态文本\n4. 前端交互:确认按钮/操作基于正确状态启用/禁用\n5. 统计数据:确认池/报表统计包含新状态\n\n---\n\n## 五、Harness Engineering 方法论\n\n> Harness = 约束 + 反馈 + 控制平面 + 持久执行\n\n### 5.1 四层约束金字塔\n\n| 层级 | 内容 | 落地方式 |\n|------|------|---------|\n| **L1 架构约束** | 接口合约、包结构、命名规范、禁止模式 | 本文件铁律 |\n| **L2 代码质量** | 圈复杂度、代码风格、类型提示 | 编译门禁 + ESLint |\n| **L3 安全约束** | 敏感信息检测、权限检查、输入验证 | 配置不可硬编码 |\n| **L4 业务规则** | 领域逻辑、数据一致性、事务边界 | 全链路 6 环验证 |\n\n**约束设计原则**:\n- **可验证**:每条约束必须能被自动化检查(\"覆盖率>90%\"✅ \"质量要高\"❌)\n- **无歧义**:\"每函数不超过50行\"✅ \"函数不要太长\"❌\n- **优先级**:安全(1) > 架构(2) > 业务(3) > 质量(4) > 性能(5)\n- **渐进增强**:L1编译通过 → L2+命名规范 → L3+测试覆盖 → L4+安全扫描\n\n### 5.2 三层反馈系统\n\n| 层级 | 速度 | 覆盖范围 | 失败处理 |\n|------|------|---------|---------|\n| **L1 编译检查** | <30秒 | 语法、类型、签名 | 立即阻断,自行修复 |\n| **L2 数据流验证** | <5分钟 | 全链路字段、Mapper XML、DTO | 修复后上报 |\n| **L3 人工审查** | 10-30分钟 | 架构、设计、业务正确性 | 驳回/指导/批准 |\n\n**反馈铁律**:\n- 反馈必须可行动(文件 + 行号 + 错误类型 + 修复方向)\n- 失败后先回滚到最近检查点,再重试\n- 持续失败3次 → 上报人类\n\n### 5.3 控制平面\n\n```\n战略层(人类) → 设定目标、审批决策、异常升级\n战术层(Agent) → 任务分解、update_plan、依赖协调、检查点保存\n执行层(Agent) → 代码生成、测试执行、错误恢复、幂等重试\n```\n\n### 5.4 持久执行\n\n- 每个关键步骤保存检查点(`update_plan` 进度)\n- 失败后从最新检查点恢复,不从头开始\n- 幂等设计:同一操作重复执行结果一致\n- **三层状态管理**:系统层(工作流ID/超时/重试) → 执行层(当前活动/进度) → 业务层(已完成工作/中间产物)\n\n---\n\n## 六、五层质量门禁\n\n| 门禁 | 时间 | 范围 | 失败处理 |\n|------|------|------|---------|\n| **L1 编译检查** | <30秒 | 语法、类型、导入 | Agent 自行修复 |\n| **L2 静态分析** | <2分钟 | 代码风格、复杂度、安全 | Agent 修复 |\n| **L3 单元测试** | <5分钟 | 功能正确性、边界条件 | 自动修复或上报 |\n| **L4 集成测试** | <15分钟 | 模块间交互、数据流 | 上报人工 |\n| **L5 生产验证** | 持续 | 监控、告警、性能 | 自动回滚 |\n\n**提交铁律**:L1-L2 必须通过才能 commit,L3(如有DB变更)必须通过才能 push\n\n---\n\n## 七、系统化调试(Systematic Debugging)\n\n> **铁律:没有根因调查,不能提出修复方案。**\n\n### 四阶段流程\n\n**阶段1:根因调查**(修复前必须完成)\n1. 仔细阅读错误信息(堆栈、行号、错误码)\n2. 稳定复现(能否可靠触发?步骤?每次?)\n3. 检查最近变更(git diff、新依赖、配置变更)\n4. 多组件系统:在每个组件边界加诊断日志,定位哪一层断裂\n5. 追踪数据流:坏值从哪里来?谁调用的?一直追溯到源头\n\n**阶段2:模式分析**\n- 找到同代码库中类似的正常工作代码\n- 逐项对比差异\n- 理解依赖关系\n\n**阶段3:假设与测试**\n- 形成单一假设:\"我认为X是根因,因为Y\"\n- 做最小改动测试\n- 有效 → 阶段4;无效 → 新假设\n\n**阶段4:实施**\n- 创建失败测试用例\n- 修复根因(不是症状)\n- 验证修复\n\n---\n\n## 八、后端开发规范\n\n### 分层架构\n```\nController → AppService → Service → Mapper → Entity\n```\n\n### 命名规范\n| 类型 | 规则 | 示例 |\n|------|------|------|\n| Controller | `XxxController` | `RegistrationController` |\n| AppService | `IXxxAppService` / `XxxAppServiceImpl` | `IRegistrationAppService` |\n| Service | `IXxxService` / `XxxServiceImpl` | `IRegistrationService` |\n| Mapper | `XxxMapper` | `RegistrationMapper` |\n| Entity | `Xxx` | `Registration` |\n| DTO | `XxxDto` / `XxxQueryDto` | `RegistrationDto` |\n\n### 包结构\n```\ncom.healthlink.his.web.{module}.controller\ncom.healthlink.his.web.{module}.appservice\ncom.healthlink.his.web.{module}.service\ncom.healthlink.his.web.{module}.mapper\ncom.healthlink.his.web.{module}.dto\ncom.healthlink.his.domain.{module}\ncom.healthlink.his.common.enums\n```\n\n### 关键约束\n- 所有查询使用 `LambdaQueryWrapper`,禁止字符串拼接 SQL\n- `@Transactional(rollbackFor = Exception.class)` 管理事务\n- 所有接口标注 `@PreAuthorize` 权限控制\n- 患者敏感信息在日志中脱敏\n- **扩展功能不修改原有函数签名**\n\n---\n\n## 九、前端开发规范\n\n### 技术栈\n- Vue 3 + Vite + Element Plus + Pinia + Axios(基于 RuoYi-Vue3)\n\n### 目录结构\n```\nsrc/api/{module}/ # API接口\nsrc/views/{module}/ # 页面组件\nsrc/store/modules/ # Pinia状态管理\nsrc/components/ # 公共组件\n```\n\n### 关键约束\n- API前缀:`/healthlink-his/api/v1/`\n- 路由懒加载:`() => import('@/views/xxx/index.vue')`\n- 页面使用 `\n -Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP -<+>UTF-8 -=================================================================== -diff --git a/healthlink-his-ui/src/views/empienhanced/merge/index.vue b/healthlink-his-ui/src/views/empienhanced/merge/index.vue ---- a/healthlink-his-ui/src/views/empienhanced/merge/index.vue (revision 8eb6feb70dc30c5984fa709873aac1c4c5809fb5) -+++ b/healthlink-his-ui/src/views/empienhanced/merge/index.vue (date 1781588026449) -@@ -1,29 +1,105 @@ - - - -- 患者合并管理 -+ -+ -+ 患者合并管理 -+ -+ 合并选中患者 ({{ selectedRows.length }}) -+ -+ -+ - -- -- -+ -+ -+ -+ -+ -+ - -- 合并 -+ 查询 -+ 重置 - - -+ -+ 先点击"设为主患者"选一个保留的,再勾选要合并的其他患者,最后点右上角合并按钮 -+ -+ -+ -+ -+ -+ -+ -+ -+ {{ row.name }} -+ -+ -+ -+ -+ {{ row.gender === 'M' ? '男' : row.gender === 'F' ? '女' : row.gender }} -+ -+ -+ {{ row.birthDate ? row.birthDate.substring(0,10) : '' }} -+ -+ -+ -+ -+ -+ -+ {{ row.mergeStatus === 'ACTIVE' ? '正常' : row.mergeStatus === 'MERGED' ? '已合并' : '待处理' }} -+ -+ -+ -+ -+ -+ -+ {{ primaryPatient && primaryPatient.id === row.id ? '已选为主' : '设为主患者' }} -+ -+ -+ -+ - -- -+ -+ -+ -+ {{ primaryPatient?.name }} -+ {{ primaryPatient?.globalId }} -+ {{ primaryPatient?.idCardNo }} -+ {{ primaryPatient?.phone }} -+ -+ -+ 将被合并的患者({{ selectedRows.length }}人): -+ -+ -+ -+ -+ -+ -+ 取消 -+ 确认合并 -+ -+ -+ -+ - 合并日志 -- -- -- -- -- -- -- -- {{ s.row.status === 'ACTIVE' ? '有效' : '已撤销' }} -+ -+ -+ {{ getPatientName(row.sourcePatientId) }} - -- -- -- 撤销 -+ -+ {{ getPatientName(row.targetPatientId) }} -+ -+ -+ -+ -+ -+ -+ -+ -+ {{ row.status === 'MERGED' ? '已合并' : '已撤回' }} -+ - - - -@@ -33,22 +109,73 @@ - - -+ -+const handleMerge = () => { -+ if (!primaryPatient.value) { ElMessage.warning('请先选择主患者'); return } -+ if (selectedRows.value.length === 0) { ElMessage.warning('请勾选要合并的患者'); return } -+ confirmVisible.value = true -+} -+ -+const doMerge = async () => { -+ try { -+ await mergePersons(primaryPatient.value.id, selectedRows.value.map(r => r.id)) -+ ElMessage.success('合并成功') -+ confirmVisible.value = false -+ primaryPatient.value = null -+ selectedRows.value = [] -+ loadPatients() -+ loadLogs() -+ } catch (e) { -+ ElMessage.error('合并失败') -+ } -+} -+ -+const getPatientName = (id) => { -+ const p = patientList.value.find(x => x.id === id) -+ return p ? p.name : id -+} -+ -+onMounted(() => { loadPatients(); loadLogs() }) -+ -\ No newline at end of file -Index: healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/patientmanage/appservice/impl/PatientInformationServiceImpl.java -IDEA additional info: -Subsystem: com.intellij.openapi.diff.impl.patch.BaseRevisionTextPatchEP -<+>package com.healthlink.his.web.patientmanage.appservice.impl;\n\nimport com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;\nimport com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;\nimport com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;\nimport com.baomidou.mybatisplus.core.metadata.IPage;\nimport com.baomidou.mybatisplus.extension.plugins.pagination.Page;\nimport com.core.common.core.domain.R;\nimport com.core.common.core.domain.model.LoginUser;\nimport com.core.common.enums.DelFlag;\nimport com.core.common.exception.ServiceException;\nimport com.core.common.utils.AssignSeqUtil;\nimport com.core.common.utils.ChineseConvertUtils;\nimport com.core.common.utils.MessageUtils;\nimport com.core.common.utils.SecurityUtils;\nimport com.healthlink.his.administration.domain.Patient;\nimport com.healthlink.his.administration.domain.PatientIdentifier;\nimport com.healthlink.his.administration.service.IPatientIdentifierService;\nimport com.healthlink.his.administration.service.IPatientService;\nimport com.healthlink.his.administration.service.IPractitionerService;\nimport com.healthlink.his.common.constant.CommonConstants;\nimport com.healthlink.his.common.constant.PromptMsgConstant;\nimport com.healthlink.his.common.enums.*;\nimport com.healthlink.his.common.utils.EnumUtils;\nimport com.healthlink.his.common.utils.HisQueryUtils;\nimport com.healthlink.his.common.utils.IdCardUtil;\nimport com.healthlink.his.web.patientmanage.appservice.IPatientInformationService;\nimport com.healthlink.his.web.patientmanage.dto.PatientBaseInfoDto;\nimport com.healthlink.his.web.patientmanage.dto.PatientIdInfoDto;\nimport com.healthlink.his.web.patientmanage.dto.PatientInfoInitDto;\nimport com.healthlink.his.web.patientmanage.mapper.PatientManageMapper;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.stereotype.Service;\n\nimport jakarta.servlet.http.HttpServletRequest;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.HashSet;\nimport java.text.SimpleDateFormat;\nimport java.util.Date;\nimport java.util.List;\nimport java.util.stream.Collectors;\nimport java.util.stream.Stream;\n\n/**\n * 门诊患者\n *\n * @author liuhr\n * @date 2025/3/15\n */\n@Service\npublic class PatientInformationServiceImpl implements IPatientInformationService {\n\n @Autowired\n PatientManageMapper patientManageMapper;\n\n @Autowired\n private IPatientService patientService;\n\n @Autowired\n private IPractitionerService practitionerService;\n\n @Autowired\n private IPatientIdentifierService patientIdentifierService;\n\n @Autowired\n private AssignSeqUtil assignSeqUtil;\n\n /**\n * 获取病人信息记录初期数据列表\n *\n * @return 病人信息记录初期数据列表\n */\n @Override\n public PatientInfoInitDto getPatientInfoInit() {\n PatientInfoInitDto initDto = new PatientInfoInitDto();\n // 获取婚姻状态列表\n List maritalStatusOptions = Stream.of(MaritalStatus.values())\n .map(status -> new PatientInfoInitDto.statusEnumOption(status.getValue(), status.getInfo()))\n .collect(Collectors.toList());\n initDto.setMaritalStatus(maritalStatusOptions);\n\n // 获取职业编码列表\n List occupationTypeOptions = Stream.of(OccupationType.values())\n .map(status -> new PatientInfoInitDto.statusEnumOption(status.getValue(), status.getInfo()))\n .collect(Collectors.toList());\n initDto.setOccupationType(occupationTypeOptions);\n\n // 获取性别列表\n List sexOptions = new ArrayList<>();\n sexOptions.add(new PatientInfoInitDto.statusEnumOption(AdministrativeGender.MALE.getValue(),\n AdministrativeGender.MALE.getInfo()));\n sexOptions.add(new PatientInfoInitDto.statusEnumOption(AdministrativeGender.FEMALE.getValue(),\n AdministrativeGender.FEMALE.getInfo()));\n initDto.setSex(sexOptions);\n\n // 获取ABO血型列表\n List bloodTypeABOOptions = Stream.of(BloodTypeABO.values())\n .map(status -> new PatientInfoInitDto.statusEnumOption(status.getValue(), status.getInfo()))\n .collect(Collectors.toList());\n initDto.setBloodTypeABO(bloodTypeABOOptions);\n\n // 获取RH血型列表\n List bloodTypeRHOptions = Stream.of(BloodTypeRH.values())\n .map(status -> new PatientInfoInitDto.statusEnumOption(status.getValue(), status.getInfo()))\n .collect(Collectors.toList());\n initDto.setBloodTypeRH(bloodTypeRHOptions);\n\n // 获取家庭关系列表\n List familyRelationshipType = Stream.of(FamilyRelationshipType.values())\n .map(status -> new PatientInfoInitDto.statusEnumOption(status.getValue(), status.getInfo()))\n .collect(Collectors.toList());\n initDto.setFamilyRelationshipType(familyRelationshipType);\n\n // 获取是/否状态\n List whetherOptions = new ArrayList<>();\n whetherOptions.add(new PatientInfoInitDto.statusEnumOption(PublicationStatus.ACTIVE.getValue(),\n PublicationStatus.ACTIVE.getInfo()));\n whetherOptions.add(new PatientInfoInitDto.statusEnumOption(PublicationStatus.RETIRED.getValue(),\n PublicationStatus.RETIRED.getInfo()));\n initDto.setWhetherStatus(whetherOptions);\n\n return initDto;\n }\n\n /**\n * 分页查询门诊记录\n *\n * @param patientBaseInfoDto 病人查询参数\n * @param searchKey 查询条件-模糊查询\n * @param pageNo 页码(默认为1)\n * @param pageSize 每页大小(默认为10)\n * @return 分页查询\n */\n @Override\n public IPage getPatientInfo(PatientBaseInfoDto patientBaseInfoDto, String searchKey,\n Integer pageNo, Integer pageSize, HttpServletRequest request) {\n // 构建基础查询条件\n LoginUser loginUser = SecurityUtils.getLoginUser();\n QueryWrapper queryWrapper = HisQueryUtils.buildQueryWrapper(\n patientBaseInfoDto, searchKey, new HashSet<>(Arrays.asList(CommonConstants.FieldName.Name,\n CommonConstants.FieldName.BusNo, CommonConstants.FieldName.PyStr, CommonConstants.FieldName.WbStr)),\n request);\n \n \n IPage patientInformationPage\n = patientManageMapper.getPatientPage(new Page<>(pageNo, pageSize), queryWrapper);\n // 患者id集合\n List patientIdList\n = patientInformationPage.getRecords().stream().map(PatientBaseInfoDto::getId).collect(Collectors.toList());\n // 患者身份信息\n List patientIdInfo = patientManageMapper.getPatientIdInfo(patientIdList);\n \n patientInformationPage.getRecords().forEach(e -> {\n // 性别枚举类回显赋值\n e.setGenderEnum_enumText(EnumUtils.getInfoByValue(AdministrativeGender.class, e.getGenderEnum()));\n // 婚姻状态枚举类回显赋值\n e.setMaritalStatusEnum_enumText(EnumUtils.getInfoByValue(MaritalStatus.class, e.getMaritalStatusEnum()));\n // 职业编码枚举类回显赋值\n e.setPrfsEnum_enumText(EnumUtils.getInfoByValue(OccupationType.class, e.getPrfsEnum()));\n // 血型ABO枚举类回显赋值\n e.setBloodAbo_enumText(EnumUtils.getInfoByValue(BloodTypeABO.class, e.getBloodAbo()));\n // 血型RH枚举类回显赋值\n e.setBloodRh_enumText(EnumUtils.getInfoByValue(BloodTypeRH.class, e.getBloodRh()));\n // 家庭关系枚举类回显赋值\n e.setLinkRelationCode_enumText(\n EnumUtils.getInfoByValue(FamilyRelationshipType.class, e.getLinkRelationCode()));\n // 患者身份子表信息\n List idInfo = patientIdInfo.stream()\n .filter(idInfoDto -> idInfoDto.getPatientId().equals(e.getId())).collect(Collectors.toList());\n e.setPatientIdInfoList(idInfo);\n // 登记医院\n e.setOrganizationId(loginUser.getOrgId());\n });\n return patientInformationPage;\n }\n\n /**\n * 修改病人信息\n *\n * @param patientInfoDto 病人信息\n * @return 更新结果\n */\n @Override\n public R> editPatient(PatientBaseInfoDto patientInfoDto) {\n // 如果患者没有输入身份证号则根据年龄自动生成\n String idCard = patientInfoDto.getIdCard();\n if (idCard == null || CommonConstants.Common.AREA_CODE.equals(idCard.substring(0, 6))) {\n if (patientInfoDto.getAge() != null) {\n idCard = IdCardUtil.generateIdByAge(patientInfoDto.getAge());\n patientInfoDto.setIdCard(idCard);\n }\n }\n // 身份证号是否存在\n List idCardList\n = patientService.list(new LambdaQueryWrapper().eq(Patient::getIdCard, patientInfoDto.getIdCard())\n .ne(patientInfoDto.getId() != null, Patient::getId, patientInfoDto.getId()));\n if (!idCardList.isEmpty()) {\n Patient patient = idCardList.get(0);\n if (patientInfoDto.getIdCard().equals(patient.getIdCard())\n && !patientInfoDto.getId().equals(patientInfoDto.getId())) {\n throw new ServiceException(\"身份证号:\" + patientInfoDto.getIdCard() + \"已经存在\");\n }\n }\n\n // 处理患者信息\n Patient patient = this.handlePatientInfo(patientInfoDto);\n // 患者身份子表信息,先删除再新增\n patientIdentifierService\n .remove(new LambdaQueryWrapper().eq(PatientIdentifier::getPatientId, patient.getId()));\n if (patientInfoDto.getPatientIdInfoList() != null) {\n // 新增患者身份子表信息\n List patientIdInfoList = patientInfoDto.getPatientIdInfoList();\n PatientIdentifier patientIdentifier;\n for (PatientIdInfoDto patientIdInfoDto : patientIdInfoList) {\n patientIdentifier = new PatientIdentifier();\n patientIdentifier.setPatientId(patient.getId());// 患者ID\n patientIdentifier.setTypeCode(patientIdInfoDto.getTypeCode()); // 标识类型编码\n patientIdentifier.setIdentifierNo(patientIdInfoDto.getIdentifierNo()); // 标识号\n patientIdentifier.setStartTime(patientIdInfoDto.getStartTime()); // 有效时间Start\n patientIdentifier.setEndTime(patientIdInfoDto.getEndTime()); // 有效时间end\n\n patientIdentifierService.save(patientIdentifier);\n }\n }\n return R.ok(patient.getIdCard(),\n MessageUtils.createMessage(PromptMsgConstant.Common.M00002, new Object[]{\"病人信息\"}));\n }\n\n /**\n * 添加病人信息\n *\n * @param patientBaseInfoDto 病人信息\n */\n @Override\n public R> addPatient(PatientBaseInfoDto patientBaseInfoDto) {\n// log.debug(\"添加病人信息,patientInfoDto:{}\", patientBaseInfoDto);\n // 如果患者没有输入身份证号则根据年龄自动生成\n String idCard = patientBaseInfoDto.getIdCard();\n if (idCard == null || idCard.length() < 6 || CommonConstants.Common.AREA_CODE.equals(idCard.substring(0, 6))) {\n if (patientBaseInfoDto.getAge() != null) {\n idCard = IdCardUtil.generateIdByAge(patientBaseInfoDto.getAge());\n patientBaseInfoDto.setIdCard(idCard);\n }\n }\n // 身份证号是否存在\n List idCardList\n = patientService.list(new LambdaQueryWrapper().eq(Patient::getIdCard, patientBaseInfoDto.getIdCard()));\n if (!idCardList.isEmpty()) {\n throw new ServiceException(\"身份证号:\" + patientBaseInfoDto.getIdCard() + \"已经存在\");\n }\n\n // 处理患者信息\n Patient patient = this.handlePatientInfo(patientBaseInfoDto);\n\n // 新增患者身份子表信息\n if (patientBaseInfoDto.getPatientIdInfoList() != null) {\n List patientIdInfoList = patientBaseInfoDto.getPatientIdInfoList();\n PatientIdentifier patientIdentifier;\n for (PatientIdInfoDto patientIdInfoDto : patientIdInfoList) {\n patientIdentifier = new PatientIdentifier();\n patientIdentifier.setPatientId(patient.getId());// 患者ID\n patientIdentifier.setTypeCode(patientIdInfoDto.getTypeCode()); // 标识类型编码\n patientIdentifier.setIdentifierNo(patientIdInfoDto.getIdentifierNo()); // 标识号\n patientIdentifier.setStartTime(patientIdInfoDto.getStartTime()); // 有效时间Start\n patientIdentifier.setEndTime(patientIdInfoDto.getEndTime()); // 有效时间end\n patientIdentifierService.save(patientIdentifier);\n }\n }\n\n return R.ok(patient, MessageUtils.createMessage(PromptMsgConstant.Common.M00001, new Object[]{\"患者信息\"}));\n }\n\n /**\n * 处理患者信息\n *\n * @param patientInfoDto 患者信息dto\n * @return 患者信息\n */\n private Patient handlePatientInfo(PatientBaseInfoDto patientInfoDto) {\n Patient patient;\n if (patientInfoDto.getId() != null) {\n // 更新现有患者信息\n patient = patientService.getById(patientInfoDto.getId());\n if (patient == null) {\n throw new ServiceException(\"患者信息不存在,无法更新\");\n }\n } else {\n // 新增患者信息\n patient = new Patient();\n patient.setBusNo(assignSeqUtil.getSeq(AssignSeqEnum.PATIENT_NUM.getPrefix(), 10));\n patient.setActiveFlag(PublicationStatus.ACTIVE.getValue()); // 默认启用\n }\n\n patient.setName(patientInfoDto.getName()); // 患者姓名\n patient.setPyStr(ChineseConvertUtils.toPinyinFirstLetter(patientInfoDto.getName())); // 拼音首拼\n patient.setWbStr(ChineseConvertUtils.toWBFirstLetter(patientInfoDto.getName())); // 五笔首拼\n patient.setIdCard(patientInfoDto.getIdCard()); // 身份证号\n patient.setBirthDate(IdCardUtil.extractBirthdayFromIdCard(patientInfoDto.getIdCard())); // 生日\n patient.setGenderEnum(patientInfoDto.getGenderEnum()); // 性别\n patient.setPhone(patientInfoDto.getPhone()); // 联系方式\n patient.setPrfsEnum(patientInfoDto.getPrfsEnum()); // 职业\n patient.setWorkCompany(patientInfoDto.getWorkCompany()); // 工作单位\n patient.setLinkName(patientInfoDto.getLinkName()); // 联系人\n patient.setLinkRelationCode(patientInfoDto.getLinkRelationCode()); // 联系人关系\n patient.setLinkTelcom(patientInfoDto.getLinkTelcom()); // 联系人电话\n patient.setAddress(patientInfoDto.getAddress()); // 地址\n patient.setAddressProvince(patientInfoDto.getAddressProvince()); // 地址省\n patient.setAddressCity(patientInfoDto.getAddressCity()); // 地址市\n patient.setAddressDistrict(patientInfoDto.getAddressDistrict()); // 地址区\n patient.setAddressStreet(patientInfoDto.getAddressStreet()); // 地址街道\n patient.setBloodAbo(patientInfoDto.getBloodAbo()); // 血型ABO\n patient.setBloodRh(patientInfoDto.getBloodRh()); // 血型RH\n patient.setMaritalStatusEnum(patientInfoDto.getMaritalStatusEnum()); // 婚姻状态\n // 死亡时间:支持 yyyy-MM-dd HH:mm:ss 和 yyyy/MM/dd HH:mm:ss 格式\n if (patientInfoDto.getDeceasedDate() != null && !patientInfoDto.getDeceasedDate().isEmpty()) {\n String dateStr = patientInfoDto.getDeceasedDate();\n String[] patterns = {\"yyyy-MM-dd HH:mm:ss\", \"yyyy/MM/dd HH:mm:ss\", \"yyyy/M/d HH:mm:ss\"};\n Date parsedDate = null;\n for (String pat : patterns) {\n try { parsedDate = new SimpleDateFormat(pat).parse(dateStr); break; } catch (Exception ignored) {}\n }\n patient.setDeceasedDate(parsedDate);\n } else {\n patient.setDeceasedDate(null);\n }\n patient.setNationalityCode(patientInfoDto.getNationalityCode());// 民族\n patient.setActiveFlag(patientInfoDto.getActiveFlag());// 活动标识\n patient.setCountryCode(patientInfoDto.getCountryCode());// 国家编码\n patient.setPostalCode(patientInfoDto.getPostalCode());// 邮政编码\n patient.setHukouAddress(patientInfoDto.getHukouAddress());// 户籍地址\n patient.setGuardianName(patientInfoDto.getGuardianName());// 监护人姓名\n patient.setGuardianRelation(patientInfoDto.getGuardianRelation());// 监护人关系\n patient.setGuardianPhone(patientInfoDto.getGuardianPhone());// 监护人电话\n patient.setGuardianIdType(patientInfoDto.getGuardianIdType());// 监护人证件类型\n patient.setGuardianIdNo(patientInfoDto.getGuardianIdNo());// 监护人证件号码\n patient.setGuardianAddress(patientInfoDto.getGuardianAddress());// 监护人地址\n patient.setPatientDerived(patientInfoDto.getPatientDerived());// 患者来源\n patient.setEducationLevel(patientInfoDto.getEducationLevel());// 文化程度\n patient.setCompanyAddress(patientInfoDto.getCompanyAddress());// 单位地址\n\n if (patientInfoDto.getId() != null) {\n // 更新操作 - 使用 LambdaUpdateWrapper 显式设置所有字段,确保 null 值也能正确更新\n LambdaUpdateWrapper updateWrapper = new LambdaUpdateWrapper<>();\n updateWrapper.eq(Patient::getId, patient.getId())\n .set(Patient::getName, patient.getName())\n .set(Patient::getPyStr, patient.getPyStr())\n .set(Patient::getWbStr, patient.getWbStr())\n .set(Patient::getIdCard, patient.getIdCard())\n .set(Patient::getBirthDate, patient.getBirthDate())\n .set(Patient::getGenderEnum, patient.getGenderEnum())\n .set(Patient::getPhone, patient.getPhone())\n .set(Patient::getPrfsEnum, patient.getPrfsEnum())\n .set(Patient::getWorkCompany, patient.getWorkCompany())\n .set(Patient::getLinkName, patient.getLinkName())\n .set(Patient::getLinkRelationCode, patient.getLinkRelationCode())\n .set(Patient::getLinkTelcom, patient.getLinkTelcom())\n .set(Patient::getAddress, patient.getAddress())\n .set(Patient::getAddressProvince, patient.getAddressProvince())\n .set(Patient::getAddressCity, patient.getAddressCity())\n .set(Patient::getAddressDistrict, patient.getAddressDistrict())\n .set(Patient::getAddressStreet, patient.getAddressStreet())\n .set(Patient::getBloodAbo, patient.getBloodAbo())\n .set(Patient::getBloodRh, patient.getBloodRh())\n .set(Patient::getMaritalStatusEnum, patient.getMaritalStatusEnum())\n .set(Patient::getDeceasedDate, patient.getDeceasedDate())\n .set(Patient::getNationalityCode, patient.getNationalityCode())\n .set(Patient::getActiveFlag, patient.getActiveFlag())\n .set(Patient::getCountryCode, patient.getCountryCode())\n .set(Patient::getPostalCode, patient.getPostalCode())\n .set(Patient::getHukouAddress, patient.getHukouAddress())\n .set(Patient::getGuardianName, patient.getGuardianName())\n .set(Patient::getGuardianRelation, patient.getGuardianRelation())\n .set(Patient::getGuardianPhone, patient.getGuardianPhone())\n .set(Patient::getGuardianIdType, patient.getGuardianIdType())\n .set(Patient::getGuardianIdNo, patient.getGuardianIdNo())\n .set(Patient::getGuardianAddress, patient.getGuardianAddress())\n .set(Patient::getPatientDerived, patient.getPatientDerived())\n .set(Patient::getEducationLevel, patient.getEducationLevel())\n .set(Patient::getCompanyAddress, patient.getCompanyAddress());\n patientService.update(updateWrapper);\n } else {\n // 新增操作\n patientService.save(patient);\n }\n\n return patient;\n }\n\n /**\n * 更新患者手机号\n *\n * @param patientBaseInfoDto 患者信息\n */\n @Override\n public R> updatePatientPhone(PatientBaseInfoDto patientBaseInfoDto) {\n if (patientBaseInfoDto != null) {\n if (patientBaseInfoDto.getId() != null && patientBaseInfoDto.getPhone() != null) {\n LambdaUpdateWrapper updateWrapper = new LambdaUpdateWrapper<>();\n updateWrapper.set(Patient::getPhone, patientBaseInfoDto.getPhone());\n updateWrapper.eq(Patient::getId, patientBaseInfoDto.getId());\n // 手机号码不相等时更新号码\n updateWrapper.ne(Patient::getPhone, patientBaseInfoDto.getPhone()).or().isNull(Patient::getPhone);\n patientService.update(updateWrapper);\n return R.ok();\n }\n }\n return R.fail(\"更新患者手机号失败\");\n }\n\n /**\n * 检查患者是否存在\n *\n * @param name 患者姓名\n * @param idCardNo 身份证号\n * @return 是否存在\n */\n @Override\n public boolean checkPatientExists(String name, String idCardNo) {\n LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>();\n queryWrapper.eq(Patient::getName, name)\n .eq(Patient::getIdCard, idCardNo)\n .eq(Patient::getDeleteFlag, DelFlag.NO.getCode());\n return patientService.count(queryWrapper) > 0;\n }\n}\n -Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP -<+>UTF-8 -=================================================================== -diff --git a/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/patientmanage/appservice/impl/PatientInformationServiceImpl.java b/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/patientmanage/appservice/impl/PatientInformationServiceImpl.java ---- a/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/patientmanage/appservice/impl/PatientInformationServiceImpl.java (revision 8eb6feb70dc30c5984fa709873aac1c4c5809fb5) -+++ b/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/patientmanage/appservice/impl/PatientInformationServiceImpl.java (date 1781585543589) -@@ -29,7 +29,9 @@ - import com.healthlink.his.web.patientmanage.dto.PatientIdInfoDto; - import com.healthlink.his.web.patientmanage.dto.PatientInfoInitDto; - import com.healthlink.his.web.patientmanage.mapper.PatientManageMapper; -+import com.healthlink.his.empi.event.PatientSavedEvent; - import org.springframework.beans.factory.annotation.Autowired; -+import org.springframework.context.ApplicationEventPublisher; - import org.springframework.stereotype.Service; - - import jakarta.servlet.http.HttpServletRequest; -@@ -54,6 +56,9 @@ - @Autowired - PatientManageMapper patientManageMapper; - -+ @Autowired -+ private ApplicationEventPublisher eventPublisher; -+ - @Autowired - private IPatientService patientService; - -@@ -379,9 +384,13 @@ - .set(Patient::getEducationLevel, patient.getEducationLevel()) - .set(Patient::getCompanyAddress, patient.getCompanyAddress()); - patientService.update(updateWrapper); -+ // 发布患者保存事件,触发EMPI同步 -+ eventPublisher.publishEvent(new PatientSavedEvent(this, patient, false)); - } else { - // 新增操作 - patientService.save(patient); -+ // 发布患者保存事件,触发EMPI同步 -+ eventPublisher.publishEvent(new PatientSavedEvent(this, patient, true)); - } - - return patient; -Index: healthlink-his-ui/src/views/empienhanced/api.js -IDEA additional info: -Subsystem: com.intellij.openapi.diff.impl.patch.BaseRevisionTextPatchEP -<+>import request from '@/utils/request'\n\nexport function registerPerson(data) { return request({ url: '/api/v1/empi/person', method: 'post', data }) }\nexport function mergePersons(primaryId, secondaryIds) { return request({ url: '/api/v1/empi/merge', method: 'post', params: { primaryId, secondaryIds: secondaryIds.join(',') } }) }\nexport function findByGlobalId(globalId) { return request({ url: '/api/v1/empi/person/global/' + globalId, method: 'get' }) }\nexport function findByIdCard(idCardNo) { return request({ url: '/api/v1/empi/person/idcard/' + idCardNo, method: 'get' }) }\nexport function getMappings(globalId) { return request({ url: '/api/v1/empi/mappings/' + globalId, method: 'get' }) }\nexport function getStatistics() { return request({ url: '/api/v1/empi/statistics', method: 'get' }) }\n\nexport function getPhotos(patientId) { return request({ url: '/empi-enhanced/photo/list', method: 'get', params: { patientId } }) }\nexport function addPhoto(data) { return request({ url: '/empi-enhanced/photo/add', method: 'post', data }) }\nexport function getFamilyMembers(patientId) { return request({ url: '/empi-enhanced/family/list', method: 'get', params: { patientId } }) }\nexport function addFamilyMember(data) { return request({ url: '/empi-enhanced/family/add', method: 'post', data }) }\nexport function deleteFamilyMember(id) { return request({ url: '/empi-enhanced/family/delete', method: 'delete', params: { id } }) }\nexport function getMergeLogPage(params) { return request({ url: '/empi-enhanced/merge-log/page', method: 'get', params }) }\nexport function addMergeLog(data) { return request({ url: '/empi-enhanced/merge-log/add', method: 'post', data }) }\nexport function undoMergeLog(data) { return request({ url: '/empi-enhanced/merge-log/undo', method: 'post', data }) }\n -Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP -<+>UTF-8 -=================================================================== -diff --git a/healthlink-his-ui/src/views/empienhanced/api.js b/healthlink-his-ui/src/views/empienhanced/api.js ---- a/healthlink-his-ui/src/views/empienhanced/api.js (revision 8eb6feb70dc30c5984fa709873aac1c4c5809fb5) -+++ b/healthlink-his-ui/src/views/empienhanced/api.js (date 1781587990638) -@@ -1,12 +1,19 @@ - import request from '@/utils/request' - -+// EMPI基础操作 - export function registerPerson(data) { return request({ url: '/api/v1/empi/person', method: 'post', data }) } - export function mergePersons(primaryId, secondaryIds) { return request({ url: '/api/v1/empi/merge', method: 'post', params: { primaryId, secondaryIds: secondaryIds.join(',') } }) } - export function findByGlobalId(globalId) { return request({ url: '/api/v1/empi/person/global/' + globalId, method: 'get' }) } - export function findByIdCard(idCardNo) { return request({ url: '/api/v1/empi/person/idcard/' + idCardNo, method: 'get' }) } - export function getMappings(globalId) { return request({ url: '/api/v1/empi/mappings/' + globalId, method: 'get' }) } - export function getStatistics() { return request({ url: '/api/v1/empi/statistics', method: 'get' }) } -+export function listPersons(params) { return request({ url: '/api/v1/empi/persons', method: 'get', params }) } - -+// 关联院内患者查询 -+export function findLinkedPatientsByGlobalId(globalId) { return request({ url: '/api/v1/empi/linked-patients/global/' + globalId, method: 'get' }) } -+export function findLinkedPatientsByIdCard(idCardNo) { return request({ url: '/api/v1/empi/linked-patients/idcard/' + idCardNo, method: 'get' }) } -+ -+// EMPI增强功能 - export function getPhotos(patientId) { return request({ url: '/empi-enhanced/photo/list', method: 'get', params: { patientId } }) } - export function addPhoto(data) { return request({ url: '/empi-enhanced/photo/add', method: 'post', data }) } - export function getFamilyMembers(patientId) { return request({ url: '/empi-enhanced/family/list', method: 'get', params: { patientId } }) } -@@ -14,4 +21,4 @@ - export function deleteFamilyMember(id) { return request({ url: '/empi-enhanced/family/delete', method: 'delete', params: { id } }) } - export function getMergeLogPage(params) { return request({ url: '/empi-enhanced/merge-log/page', method: 'get', params }) } - export function addMergeLog(data) { return request({ url: '/empi-enhanced/merge-log/add', method: 'post', data }) } --export function undoMergeLog(data) { return request({ url: '/empi-enhanced/merge-log/undo', method: 'post', data }) } -+export function undoMergeLog(data) { return request({ url: '/empi-enhanced/merge-log/undo', method: 'post', data }) } -\ No newline at end of file -Index: .cursorrules -IDEA additional info: -Subsystem: com.intellij.openapi.diff.impl.patch.BaseRevisionTextPatchEP -<+># HealthLink-HIS — AI 开发规范 (Cursor)\n\n> \uD83E\uDD16 Cursor IDE 打开本项目时自动加载此文件。\n\n---\n\n# HealthLink-HIS — AI 开发规范(自动加载)\n\n> \uD83E\uDD16 **本文件供所有 AI 编码工具自动读取**。进入本项目后必须遵守以下规范。\n> \n> **模型决定上限,Harness 决定底线。**\n\n---\n\n## 一、项目概览\n\n| 属性 | 值 |\n|------|------|\n| 项目名 | HealthLink-HIS(医院信息系统) |\n| 后端路径 | `healthlink-his-server/` |\n| 前端路径 | `healthlink-his-ui/` |\n| 文档路径 | `MD/` |\n| JDK | 25 (OpenJDK) |\n| Spring Boot | 4.0.6 |\n| MyBatis-Plus | 3.5.16 |\n| Vue | 3.x + Vite + Element Plus |\n| 数据库 | PostgreSQL 15+ |\n| 包名 | `com.healthlink.his` |\n| 后端端口 | 18082 |\n| 前端端口 | 81 |\n\n---\n\n## 二、铁律(必须遵守,违反即失败)\n\n### \uD83D\uDD34 P0 铁律 — 不可违反\n\n**铁律1: 修改完必须测试**\n```\n后端: mvn clean compile -DskipTests → mvn install -DskipTests → mvn test\n前端: npm run build:dev → npm run lint\n```\n- 白盒:编译通过,无 ERROR\n- 黑盒:关键接口返回 `{code:200, data:...}`,验证业务逻辑\n- 冒烟:应用正常启动,核心流程通畅\n\n**铁律2: Flyway 数据库迁移**\n- 凡是新建表、新增字段,必须创建 Flyway 迁移脚本\n- 路径:`healthlink-his-domain/src/main/resources/db/migration/`\n- 命名:`V{版本号}__{描述}.sql`(双下划线)\n\n**铁律3: 测试通过后才提交**\n- 编译 + 测试全部通过后才能 git commit\n- 不提交未完成的功能、调试代码、临时文件\n\n**铁律4: 前后端API路径对齐**\n- 后端前缀:`/healthlink-his/api/v1/`\n- 前端 `request.js` 的 baseURL 必须与后端匹配\n\n**铁律5: 状态值一致性(Bug #574 教训)**\n- 修改任何状态值前,必须先列出完整的状态流转链路\n- 检查项:枚举定义 → Service 设置 → 查询映射 → 前端 STATUS_CLASS_MAP → 前端 v-if → 统计SQL\n- 禁止:只改一端不检查其他端\n\n**铁律6: 禁止删除源文件(Bug #574 教训)**\n- 绝对禁止删除项目中已有的 Java/Vue/SQL 源文件\n- 编译错误 → 修复错误;重复文件 → 重构合并\n- 唯一例外:明确由人类确认删除的文件\n\n**铁律7: 禁止修改已有公开方法签名**\n- 不能删除/重命名已有的 public 方法,不能修改参数列表\n- 需要新功能 → 添加重载方法;需要改行为 → 修改内部实现\n\n**铁律8: 验证后才宣称完成(Verification Before Completion)**\n- **没有跑过验证命令,就不能说\"完成了\"\"通过了\"\"没问题\"**\n- 禁止使用\"应该可以\"\"大概没问题\"\"看起来正确\"\n- 必须:运行命令 → 读取输出 → 确认结果 → 才能宣称\n- 这是诚实原则,不是效率问题\n\n\n**铁律9: 开发前必须审核原有代码(P0 — 铁律)**\n- **任何新功能开发前,必须先搜索项目中是否已有相关代码**\n- 搜索路径:Controller / AppService / Service / Mapper / Entity / 前端页面 / API接口\n- 如果已有部分功能 → 在原有代码基础上**升级优化完善**,禁止另起炉灶\n- 如果已有接口但前端缺失 → 只补前端,不重复建后端\n- 如果已有前端但后端缺失 → 只补后端,不重写前端\n- 搜索命令:`rg -l \"关键词\" healthlink-his-server/ healthlink-his-ui/src/`\n- 禁止:不看代码就新建模块、重复实现已有功能、废弃原有代码另写一套\n\n\n**铁律12: 设计文档确认后自主开发(铁律)**\n- 设计文档(如 `MD/architecture/GRADE3A_GAP_ANALYSIS_AND_DESIGN.md`)一旦确认,后续开发**必须按文档自主执行**\n- **禁止反复询问\"是否继续\"\"下一步做什么\"\"是否开始\"**——直接按计划推进\n- 每完成一个 Sprint,自动提交推送,然后立即开始下一个 Sprint\n- 只在遇到**无法解决的阻塞**(如技术选型冲突、需求不明确、第三方依赖不可用)时才暂停询问\n- 设计文档是\"**已签合同**\",不是\"参考意见\"。铁律执行优先级:设计文档 > 人类临时指令 > AI 自行判断\n\n**铁律18: 禁止破坏原有功能(P0绝对铁律)**\n- **完善增加功能和流程时,绝对不能破坏或者让原有功能不能用**\n- 修改已有实体前必须对比原始文件(`git show HEAD~N:./file.java`),保留所有原有字段和方法\n- 新增字段只能追加,不能删除或重命名已有字段\n- SQL迁移只允许 `ALTER TABLE ADD COLUMN`,不允许 `DROP COLUMN` 或 `RENAME COLUMN`\n- Controller新端点不能修改已有端点的路径或参数\n- 前端新页面不能修改已有页面的组件结构\n- 每次修改后必须 `mvn clean compile -DskipTests` 验证\n- **违规判定**: 因修改导致原有代码编译失败或运行报错,视为违反铁律18,必须立即回滚修复\n\n\n### \uD83D\uDFE1 P1 铁律 — 强烈建议\n\n**铁律9: 先分解再行动**\n- 修改超过3个文件、涉及多模块、数据库变更,必须先制定计划\n\n**铁律10: 验证后信**\n- 每次修改后必须验证编译通过,不信记忆\n\n**铁律13: 文档统一管理**\n- 所有文档存储在 `MD/` 目录\n- 文件名:大写英文+下划线(如 `BACKEND_CHECKLIST.md`)\n- 文档头部必须包含元数据块(文档类型、版本、日期)\n\n---\n\n\n**铁律14: 设计文档必须包含UI设计和调用流程**\n- 所有新模块/页面的设计文档必须包含:UI布局描述、交互效果清单、前后端调用流程\n- 没有明确UI设计的模块,禁止直接编码\n- 详见 \n- 设计文档必须写清楚:系统调用关系、方法函数调用关系、完整业务流程\n- 设计文档中每个用户操作必须对应:前端事件 → API调用 → 后端处理链路 → 返回数据 → UI渲染\n\n---\n\n## 三、Karpathy 编码准则\n\n> 减少 LLM 常见编码错误。偏向谨慎而非速度。\n\n### 3.1 先想再写\n- 明确陈述假设,不确定就问\n- 多种解读时都列出来,不要默默选一种\n- 有更简单的方案就说出来,该推回就推回\n- 不清楚的地方停下来,说清楚哪里不清楚\n\n### 3.2 简洁优先\n- 不做没要求的功能,不做一次性代码的抽象\n- 不加没要求的\"灵活性\"和\"可配置性\"\n- 200 行能 50 行搞定就重写\n- 自问:\"高级工程师会不会觉得这过度设计?\"\n\n### 3.3 精准修改\n- 只改必须改的,不\"顺手改进\"相邻代码\n- 匹配现有代码风格,即使你有不同的偏好\n- 每行改动都能追溯到用户的请求\n- 只清理你自己改动产生的无用代码\n\n### 3.4 目标驱动\n- 把任务转化为可验证目标\n- 多步任务声明计划:`[步骤] → 验证: [检查]`\n- 强验收标准让 Agent 能独立循环,弱标准需要持续澄清\n\n---\n\n## 四、全链路 6 环分析\n\n> ⚠\uFE0F **涉及数据库字段的 Bug / 需求,必须走完整链路。**\n\n```\n前端/页面 → Controller → Service → Mapper → DB/SQL → 关联模块\n ①录入 ②验证 ③业务 ④持久化 ⑤存储 ⑥联动\n```\n\n| 环 | 检查内容 |\n|----|---------|\n| ① 录入 | 前端有无输入入口(弹窗、表格行编辑、表单) |\n| ② 验证 | Controller 参数校验、@Valid、权限控制 |\n| ③ 业务 | Service 业务逻辑、事务边界、多个 Service 实现类入口 |\n| ④ 持久化 | Mapper XML、DTO 字段映射、类型转换 |\n| ⑤ 存储 | 数据库表结构、索引、NOT NULL 约束 |\n| ⑥ 联动 | 上游(医嘱→护士站)、下游(打印、计费、报表)是否同步 |\n\n**修复后的验证顺序**:\n1. 数据库:确认状态值已正确写入\n2. 后端接口:确认返回的状态映射正确\n3. 前端显示:确认页面显示正确状态文本\n4. 前端交互:确认按钮/操作基于正确状态启用/禁用\n5. 统计数据:确认池/报表统计包含新状态\n\n---\n\n## 五、Harness Engineering 方法论\n\n> Harness = 约束 + 反馈 + 控制平面 + 持久执行\n\n### 5.1 四层约束金字塔\n\n| 层级 | 内容 | 落地方式 |\n|------|------|---------|\n| **L1 架构约束** | 接口合约、包结构、命名规范、禁止模式 | 本文件铁律 |\n| **L2 代码质量** | 圈复杂度、代码风格、类型提示 | 编译门禁 + ESLint |\n| **L3 安全约束** | 敏感信息检测、权限检查、输入验证 | 配置不可硬编码 |\n| **L4 业务规则** | 领域逻辑、数据一致性、事务边界 | 全链路 6 环验证 |\n\n**约束设计原则**:\n- **可验证**:每条约束必须能被自动化检查(\"覆盖率>90%\"✅ \"质量要高\"❌)\n- **无歧义**:\"每函数不超过50行\"✅ \"函数不要太长\"❌\n- **优先级**:安全(1) > 架构(2) > 业务(3) > 质量(4) > 性能(5)\n- **渐进增强**:L1编译通过 → L2+命名规范 → L3+测试覆盖 → L4+安全扫描\n\n### 5.2 三层反馈系统\n\n| 层级 | 速度 | 覆盖范围 | 失败处理 |\n|------|------|---------|---------|\n| **L1 编译检查** | <30秒 | 语法、类型、签名 | 立即阻断,自行修复 |\n| **L2 数据流验证** | <5分钟 | 全链路字段、Mapper XML、DTO | 修复后上报 |\n| **L3 人工审查** | 10-30分钟 | 架构、设计、业务正确性 | 驳回/指导/批准 |\n\n**反馈铁律**:\n- 反馈必须可行动(文件 + 行号 + 错误类型 + 修复方向)\n- 失败后先回滚到最近检查点,再重试\n- 持续失败3次 → 上报人类\n\n### 5.3 控制平面\n\n```\n战略层(人类) → 设定目标、审批决策、异常升级\n战术层(Agent) → 任务分解、update_plan、依赖协调、检查点保存\n执行层(Agent) → 代码生成、测试执行、错误恢复、幂等重试\n```\n\n### 5.4 持久执行\n\n- 每个关键步骤保存检查点(`update_plan` 进度)\n- 失败后从最新检查点恢复,不从头开始\n- 幂等设计:同一操作重复执行结果一致\n- **三层状态管理**:系统层(工作流ID/超时/重试) → 执行层(当前活动/进度) → 业务层(已完成工作/中间产物)\n\n---\n\n## 六、五层质量门禁\n\n| 门禁 | 时间 | 范围 | 失败处理 |\n|------|------|------|---------|\n| **L1 编译检查** | <30秒 | 语法、类型、导入 | Agent 自行修复 |\n| **L2 静态分析** | <2分钟 | 代码风格、复杂度、安全 | Agent 修复 |\n| **L3 单元测试** | <5分钟 | 功能正确性、边界条件 | 自动修复或上报 |\n| **L4 集成测试** | <15分钟 | 模块间交互、数据流 | 上报人工 |\n| **L5 生产验证** | 持续 | 监控、告警、性能 | 自动回滚 |\n\n**提交铁律**:L1-L2 必须通过才能 commit,L3(如有DB变更)必须通过才能 push\n\n---\n\n## 七、系统化调试(Systematic Debugging)\n\n> **铁律:没有根因调查,不能提出修复方案。**\n\n### 四阶段流程\n\n**阶段1:根因调查**(修复前必须完成)\n1. 仔细阅读错误信息(堆栈、行号、错误码)\n2. 稳定复现(能否可靠触发?步骤?每次?)\n3. 检查最近变更(git diff、新依赖、配置变更)\n4. 多组件系统:在每个组件边界加诊断日志,定位哪一层断裂\n5. 追踪数据流:坏值从哪里来?谁调用的?一直追溯到源头\n\n**阶段2:模式分析**\n- 找到同代码库中类似的正常工作代码\n- 逐项对比差异\n- 理解依赖关系\n\n**阶段3:假设与测试**\n- 形成单一假设:\"我认为X是根因,因为Y\"\n- 做最小改动测试\n- 有效 → 阶段4;无效 → 新假设\n\n**阶段4:实施**\n- 创建失败测试用例\n- 修复根因(不是症状)\n- 验证修复\n\n---\n\n## 八、后端开发规范\n\n### 分层架构\n```\nController → AppService → Service → Mapper → Entity\n```\n\n### 命名规范\n| 类型 | 规则 | 示例 |\n|------|------|------|\n| Controller | `XxxController` | `RegistrationController` |\n| AppService | `IXxxAppService` / `XxxAppServiceImpl` | `IRegistrationAppService` |\n| Service | `IXxxService` / `XxxServiceImpl` | `IRegistrationService` |\n| Mapper | `XxxMapper` | `RegistrationMapper` |\n| Entity | `Xxx` | `Registration` |\n| DTO | `XxxDto` / `XxxQueryDto` | `RegistrationDto` |\n\n### 包结构\n```\ncom.healthlink.his.web.{module}.controller\ncom.healthlink.his.web.{module}.appservice\ncom.healthlink.his.web.{module}.service\ncom.healthlink.his.web.{module}.mapper\ncom.healthlink.his.web.{module}.dto\ncom.healthlink.his.domain.{module}\ncom.healthlink.his.common.enums\n```\n\n### 关键约束\n- 所有查询使用 `LambdaQueryWrapper`,禁止字符串拼接 SQL\n- `@Transactional(rollbackFor = Exception.class)` 管理事务\n- 所有接口标注 `@PreAuthorize` 权限控制\n- 患者敏感信息在日志中脱敏\n- **扩展功能不修改原有函数签名**\n\n---\n\n## 九、前端开发规范\n\n### 技术栈\n- Vue 3 + Vite + Element Plus + Pinia + Axios(基于 RuoYi-Vue3)\n\n### 目录结构\n```\nsrc/api/{module}/ # API接口\nsrc/views/{module}/ # 页面组件\nsrc/store/modules/ # Pinia状态管理\nsrc/components/ # 公共组件\n```\n\n### 关键约束\n- API前缀:`/healthlink-his/api/v1/`\n- 路由懒加载:`() => import('@/views/xxx/index.vue')`\n- 页面使用 ` -Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP -<+>UTF-8 -=================================================================== -diff --git a/healthlink-his-ui/src/views/empienhanced/patient/index.vue b/healthlink-his-ui/src/views/empienhanced/patient/index.vue ---- a/healthlink-his-ui/src/views/empienhanced/patient/index.vue (revision 954462272e2cde4d73ad13d07d849ee7d1f9ba71) -+++ b/healthlink-his-ui/src/views/empienhanced/patient/index.vue (date 1781594038715) -@@ -6,6 +6,7 @@ - - - -+ - - - -@@ -13,18 +14,49 @@ - 注册患者 - - -+ - -- -- -+ -+ - -- 查询 -- 重置 -+ 查询 -+ 重置 - - -- -+ -+ -+ -+ -+ -+ {{ row.gender === 'M' ? '男' : row.gender === 'F' ? '女' : row.gender === '1' ? '男' : row.gender === '2' ? '女' : row.gender }} -+ -+ -+ -+ -+ -+ -+ -+ -+ {{ row.mergeStatus === 'ACTIVE' ? '正常' : row.mergeStatus === 'MERGED' ? '已合并' : row.mergeStatus }} -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ 患者详情 -+ 关闭 -+ -+ -+ - {{ patientData.globalId }} - {{ patientData.name }} -- {{ patientData.gender === 'M' ? '男' : '女' }} -+ {{ patientData.gender === 'M' ? '男' : patientData.gender === 'F' ? '女' : patientData.gender === '1' ? '男' : patientData.gender === '2' ? '女' : patientData.gender }} - {{ patientData.birthDate }} - {{ patientData.idCardNo }} - {{ patientData.phone }} -@@ -36,14 +68,11 @@ - - - -- - - - - -- -- 关联院内患者记录 ({{ linkedPatients.length }}) -- -+ 关联院内患者记录 ({{ linkedPatients.length }}) - - - -@@ -62,9 +91,7 @@ - - - -- -- ID映射关系 -- -+ ID映射关系 - - - -@@ -99,14 +126,16 @@ - import { useDict } from '@/utils/dict' - import { ref, reactive, onMounted } from 'vue' - import { -- registerPerson, findByGlobalId, findByIdCard, getStatistics, -+ registerPerson, findByGlobalId, findByIdCard, getStatistics, listPersons, - findLinkedPatientsByGlobalId, findLinkedPatientsByIdCard, getMappings - } from '../api' - import { ElMessage } from 'element-plus' - - const { sys_user_sex } = useDict('sys_user_sex') - const stats = ref({}) --const searchForm = reactive({ globalId: '', idCardNo: '' }) -+const loading = ref(false) -+const patientList = ref([]) -+const searchForm = reactive({ name: '', idCardNo: '' }) - const patientData = ref(null) - const linkedPatients = ref([]) - const mappings = ref([]) -@@ -115,41 +144,49 @@ - - const loadStats = async () => { const res = await getStatistics(); stats.value = res.data || {} } - -+const loadPatientList = async (params = {}) => { -+ loading.value = true -+ try { -+ const res = await listPersons(params) -+ patientList.value = res.data || [] -+ } finally { -+ loading.value = false -+ } -+} -+ - const handleSearch = async () => { -+ patientData.value = null - linkedPatients.value = [] - mappings.value = [] -- if (searchForm.globalId) { -- const res = await findByGlobalId(searchForm.globalId) -- patientData.value = res.data -- if (res.data) { -- const lp = await findLinkedPatientsByGlobalId(searchForm.globalId) -- linkedPatients.value = lp.data || [] -- const mp = await getMappings(searchForm.globalId) -- mappings.value = mp.data || [] -- } -- } else if (searchForm.idCardNo) { -- const res = await findByIdCard(searchForm.idCardNo) -- patientData.value = res.data -- if (res.data) { -- const lp = await findLinkedPatientsByIdCard(searchForm.idCardNo) -- linkedPatients.value = lp.data || [] -- const mp = await getMappings(res.data.globalId) -- mappings.value = mp.data || [] -- } -- } else { -- ElMessage.warning('请输入查询条件') -- } -+ const params = {} -+ if (searchForm.name) params.name = searchForm.name -+ if (searchForm.idCardNo) params.idCardNo = searchForm.idCardNo -+ await loadPatientList(params) - } - - const handleReset = () => { -- searchForm.globalId = '' -+ searchForm.name = '' - searchForm.idCardNo = '' - patientData.value = null - linkedPatients.value = [] - mappings.value = [] -+ loadPatientList() -+} -+ -+const handleRowClick = async (row) => { -+ patientData.value = row -+ linkedPatients.value = [] -+ mappings.value = [] -+ try { -+ const lp = await findLinkedPatientsByGlobalId(row.globalId) -+ linkedPatients.value = lp.data || [] -+ const mp = await getMappings(row.globalId) -+ mappings.value = mp.data || [] -+ } catch (e) { /* ignore */ } - } - - const handleRegister = () => { formData.value = {}; dialogVisible.value = true } --const submitForm = async () => { await registerPerson(formData.value); ElMessage.success('注册成功'); dialogVisible.value = false; loadStats() } --onMounted(() => loadStats()) -+const submitForm = async () => { await registerPerson(formData.value); ElMessage.success('注册成功'); dialogVisible.value = false; loadStats(); loadPatientList() } -+ -+onMounted(() => { loadStats(); loadPatientList() }) - -\ No newline at end of file -Index: .windsurfrules -IDEA additional info: -Subsystem: com.intellij.openapi.diff.impl.patch.BaseRevisionTextPatchEP -<+># HealthLink-HIS — AI 开发规范 (Windsurf)\n\n> \uD83E\uDD16 Windsurf 打开本项目时自动加载此文件。\n\n---\n\n# HealthLink-HIS — AI 开发规范(自动加载)\n\n> \uD83E\uDD16 **本文件供所有 AI 编码工具自动读取**。进入本项目后必须遵守以下规范。\n> \n> **模型决定上限,Harness 决定底线。**\n\n---\n\n## 一、项目概览\n\n| 属性 | 值 |\n|------|------|\n| 项目名 | HealthLink-HIS(医院信息系统) |\n| 后端路径 | `healthlink-his-server/` |\n| 前端路径 | `healthlink-his-ui/` |\n| 文档路径 | `MD/` |\n| JDK | 25 (OpenJDK) |\n| Spring Boot | 4.0.6 |\n| MyBatis-Plus | 3.5.16 |\n| Vue | 3.x + Vite + Element Plus |\n| 数据库 | PostgreSQL 15+ |\n| 包名 | `com.healthlink.his` |\n| 后端端口 | 18082 |\n| 前端端口 | 81 |\n\n---\n\n## 二、铁律(必须遵守,违反即失败)\n\n### \uD83D\uDD34 P0 铁律 — 不可违反\n\n**铁律1: 修改完必须测试**\n```\n后端: mvn clean compile -DskipTests → mvn install -DskipTests → mvn test\n前端: npm run build:dev → npm run lint\n```\n- 白盒:编译通过,无 ERROR\n- 黑盒:关键接口返回 `{code:200, data:...}`,验证业务逻辑\n- 冒烟:应用正常启动,核心流程通畅\n\n**铁律2: Flyway 数据库迁移**\n- 凡是新建表、新增字段,必须创建 Flyway 迁移脚本\n- 路径:`healthlink-his-domain/src/main/resources/db/migration/`\n- 命名:`V{版本号}__{描述}.sql`(双下划线)\n\n**铁律3: 测试通过后才提交**\n- 编译 + 测试全部通过后才能 git commit\n- 不提交未完成的功能、调试代码、临时文件\n\n**铁律4: 前后端API路径对齐**\n- 后端前缀:`/healthlink-his/api/v1/`\n- 前端 `request.js` 的 baseURL 必须与后端匹配\n\n**铁律5: 状态值一致性(Bug #574 教训)**\n- 修改任何状态值前,必须先列出完整的状态流转链路\n- 检查项:枚举定义 → Service 设置 → 查询映射 → 前端 STATUS_CLASS_MAP → 前端 v-if → 统计SQL\n- 禁止:只改一端不检查其他端\n\n**铁律6: 禁止删除源文件(Bug #574 教训)**\n- 绝对禁止删除项目中已有的 Java/Vue/SQL 源文件\n- 编译错误 → 修复错误;重复文件 → 重构合并\n- 唯一例外:明确由人类确认删除的文件\n\n**铁律7: 禁止修改已有公开方法签名**\n- 不能删除/重命名已有的 public 方法,不能修改参数列表\n- 需要新功能 → 添加重载方法;需要改行为 → 修改内部实现\n\n**铁律8: 验证后才宣称完成(Verification Before Completion)**\n- **没有跑过验证命令,就不能说\"完成了\"\"通过了\"\"没问题\"**\n- 禁止使用\"应该可以\"\"大概没问题\"\"看起来正确\"\n- 必须:运行命令 → 读取输出 → 确认结果 → 才能宣称\n- 这是诚实原则,不是效率问题\n\n\n**铁律9: 开发前必须审核原有代码(P0 — 铁律)**\n- **任何新功能开发前,必须先搜索项目中是否已有相关代码**\n- 搜索路径:Controller / AppService / Service / Mapper / Entity / 前端页面 / API接口\n- 如果已有部分功能 → 在原有代码基础上**升级优化完善**,禁止另起炉灶\n- 如果已有接口但前端缺失 → 只补前端,不重复建后端\n- 如果已有前端但后端缺失 → 只补后端,不重写前端\n- 搜索命令:`rg -l \"关键词\" healthlink-his-server/ healthlink-his-ui/src/`\n- 禁止:不看代码就新建模块、重复实现已有功能、废弃原有代码另写一套\n\n\n**铁律12: 设计文档确认后自主开发(铁律)**\n- 设计文档(如 `MD/architecture/GRADE3A_GAP_ANALYSIS_AND_DESIGN.md`)一旦确认,后续开发**必须按文档自主执行**\n- **禁止反复询问\"是否继续\"\"下一步做什么\"\"是否开始\"**——直接按计划推进\n- 每完成一个 Sprint,自动提交推送,然后立即开始下一个 Sprint\n- 只在遇到**无法解决的阻塞**(如技术选型冲突、需求不明确、第三方依赖不可用)时才暂停询问\n- 设计文档是\"**已签合同**\",不是\"参考意见\"。铁律执行优先级:设计文档 > 人类临时指令 > AI 自行判断\n\n**铁律18: 禁止破坏原有功能(P0绝对铁律)**\n- **完善增加功能和流程时,绝对不能破坏或者让原有功能不能用**\n- 修改已有实体前必须对比原始文件(`git show HEAD~N:./file.java`),保留所有原有字段和方法\n- 新增字段只能追加,不能删除或重命名已有字段\n- SQL迁移只允许 `ALTER TABLE ADD COLUMN`,不允许 `DROP COLUMN` 或 `RENAME COLUMN`\n- Controller新端点不能修改已有端点的路径或参数\n- 前端新页面不能修改已有页面的组件结构\n- 每次修改后必须 `mvn clean compile -DskipTests` 验证\n- **违规判定**: 因修改导致原有代码编译失败或运行报错,视为违反铁律18,必须立即回滚修复\n\n\n### \uD83D\uDFE1 P1 铁律 — 强烈建议\n\n**铁律9: 先分解再行动**\n- 修改超过3个文件、涉及多模块、数据库变更,必须先制定计划\n\n**铁律10: 验证后信**\n- 每次修改后必须验证编译通过,不信记忆\n\n**铁律13: 文档统一管理**\n- 所有文档存储在 `MD/` 目录\n- 文件名:大写英文+下划线(如 `BACKEND_CHECKLIST.md`)\n- 文档头部必须包含元数据块(文档类型、版本、日期)\n\n---\n\n\n**铁律14: 设计文档必须包含UI设计和调用流程**\n- 所有新模块/页面的设计文档必须包含:UI布局描述、交互效果清单、前后端调用流程\n- 没有明确UI设计的模块,禁止直接编码\n- 详见 \n- 设计文档必须写清楚:系统调用关系、方法函数调用关系、完整业务流程\n- 设计文档中每个用户操作必须对应:前端事件 → API调用 → 后端处理链路 → 返回数据 → UI渲染\n\n---\n\n## 三、Karpathy 编码准则\n\n> 减少 LLM 常见编码错误。偏向谨慎而非速度。\n\n### 3.1 先想再写\n- 明确陈述假设,不确定就问\n- 多种解读时都列出来,不要默默选一种\n- 有更简单的方案就说出来,该推回就推回\n- 不清楚的地方停下来,说清楚哪里不清楚\n\n### 3.2 简洁优先\n- 不做没要求的功能,不做一次性代码的抽象\n- 不加没要求的\"灵活性\"和\"可配置性\"\n- 200 行能 50 行搞定就重写\n- 自问:\"高级工程师会不会觉得这过度设计?\"\n\n### 3.3 精准修改\n- 只改必须改的,不\"顺手改进\"相邻代码\n- 匹配现有代码风格,即使你有不同的偏好\n- 每行改动都能追溯到用户的请求\n- 只清理你自己改动产生的无用代码\n\n### 3.4 目标驱动\n- 把任务转化为可验证目标\n- 多步任务声明计划:`[步骤] → 验证: [检查]`\n- 强验收标准让 Agent 能独立循环,弱标准需要持续澄清\n\n---\n\n## 四、全链路 6 环分析\n\n> ⚠\uFE0F **涉及数据库字段的 Bug / 需求,必须走完整链路。**\n\n```\n前端/页面 → Controller → Service → Mapper → DB/SQL → 关联模块\n ①录入 ②验证 ③业务 ④持久化 ⑤存储 ⑥联动\n```\n\n| 环 | 检查内容 |\n|----|---------|\n| ① 录入 | 前端有无输入入口(弹窗、表格行编辑、表单) |\n| ② 验证 | Controller 参数校验、@Valid、权限控制 |\n| ③ 业务 | Service 业务逻辑、事务边界、多个 Service 实现类入口 |\n| ④ 持久化 | Mapper XML、DTO 字段映射、类型转换 |\n| ⑤ 存储 | 数据库表结构、索引、NOT NULL 约束 |\n| ⑥ 联动 | 上游(医嘱→护士站)、下游(打印、计费、报表)是否同步 |\n\n**修复后的验证顺序**:\n1. 数据库:确认状态值已正确写入\n2. 后端接口:确认返回的状态映射正确\n3. 前端显示:确认页面显示正确状态文本\n4. 前端交互:确认按钮/操作基于正确状态启用/禁用\n5. 统计数据:确认池/报表统计包含新状态\n\n---\n\n## 五、Harness Engineering 方法论\n\n> Harness = 约束 + 反馈 + 控制平面 + 持久执行\n\n### 5.1 四层约束金字塔\n\n| 层级 | 内容 | 落地方式 |\n|------|------|---------|\n| **L1 架构约束** | 接口合约、包结构、命名规范、禁止模式 | 本文件铁律 |\n| **L2 代码质量** | 圈复杂度、代码风格、类型提示 | 编译门禁 + ESLint |\n| **L3 安全约束** | 敏感信息检测、权限检查、输入验证 | 配置不可硬编码 |\n| **L4 业务规则** | 领域逻辑、数据一致性、事务边界 | 全链路 6 环验证 |\n\n**约束设计原则**:\n- **可验证**:每条约束必须能被自动化检查(\"覆盖率>90%\"✅ \"质量要高\"❌)\n- **无歧义**:\"每函数不超过50行\"✅ \"函数不要太长\"❌\n- **优先级**:安全(1) > 架构(2) > 业务(3) > 质量(4) > 性能(5)\n- **渐进增强**:L1编译通过 → L2+命名规范 → L3+测试覆盖 → L4+安全扫描\n\n### 5.2 三层反馈系统\n\n| 层级 | 速度 | 覆盖范围 | 失败处理 |\n|------|------|---------|---------|\n| **L1 编译检查** | <30秒 | 语法、类型、签名 | 立即阻断,自行修复 |\n| **L2 数据流验证** | <5分钟 | 全链路字段、Mapper XML、DTO | 修复后上报 |\n| **L3 人工审查** | 10-30分钟 | 架构、设计、业务正确性 | 驳回/指导/批准 |\n\n**反馈铁律**:\n- 反馈必须可行动(文件 + 行号 + 错误类型 + 修复方向)\n- 失败后先回滚到最近检查点,再重试\n- 持续失败3次 → 上报人类\n\n### 5.3 控制平面\n\n```\n战略层(人类) → 设定目标、审批决策、异常升级\n战术层(Agent) → 任务分解、update_plan、依赖协调、检查点保存\n执行层(Agent) → 代码生成、测试执行、错误恢复、幂等重试\n```\n\n### 5.4 持久执行\n\n- 每个关键步骤保存检查点(`update_plan` 进度)\n- 失败后从最新检查点恢复,不从头开始\n- 幂等设计:同一操作重复执行结果一致\n- **三层状态管理**:系统层(工作流ID/超时/重试) → 执行层(当前活动/进度) → 业务层(已完成工作/中间产物)\n\n---\n\n## 六、五层质量门禁\n\n| 门禁 | 时间 | 范围 | 失败处理 |\n|------|------|------|---------|\n| **L1 编译检查** | <30秒 | 语法、类型、导入 | Agent 自行修复 |\n| **L2 静态分析** | <2分钟 | 代码风格、复杂度、安全 | Agent 修复 |\n| **L3 单元测试** | <5分钟 | 功能正确性、边界条件 | 自动修复或上报 |\n| **L4 集成测试** | <15分钟 | 模块间交互、数据流 | 上报人工 |\n| **L5 生产验证** | 持续 | 监控、告警、性能 | 自动回滚 |\n\n**提交铁律**:L1-L2 必须通过才能 commit,L3(如有DB变更)必须通过才能 push\n\n---\n\n## 七、系统化调试(Systematic Debugging)\n\n> **铁律:没有根因调查,不能提出修复方案。**\n\n### 四阶段流程\n\n**阶段1:根因调查**(修复前必须完成)\n1. 仔细阅读错误信息(堆栈、行号、错误码)\n2. 稳定复现(能否可靠触发?步骤?每次?)\n3. 检查最近变更(git diff、新依赖、配置变更)\n4. 多组件系统:在每个组件边界加诊断日志,定位哪一层断裂\n5. 追踪数据流:坏值从哪里来?谁调用的?一直追溯到源头\n\n**阶段2:模式分析**\n- 找到同代码库中类似的正常工作代码\n- 逐项对比差异\n- 理解依赖关系\n\n**阶段3:假设与测试**\n- 形成单一假设:\"我认为X是根因,因为Y\"\n- 做最小改动测试\n- 有效 → 阶段4;无效 → 新假设\n\n**阶段4:实施**\n- 创建失败测试用例\n- 修复根因(不是症状)\n- 验证修复\n\n---\n\n## 八、后端开发规范\n\n### 分层架构\n```\nController → AppService → Service → Mapper → Entity\n```\n\n### 命名规范\n| 类型 | 规则 | 示例 |\n|------|------|------|\n| Controller | `XxxController` | `RegistrationController` |\n| AppService | `IXxxAppService` / `XxxAppServiceImpl` | `IRegistrationAppService` |\n| Service | `IXxxService` / `XxxServiceImpl` | `IRegistrationService` |\n| Mapper | `XxxMapper` | `RegistrationMapper` |\n| Entity | `Xxx` | `Registration` |\n| DTO | `XxxDto` / `XxxQueryDto` | `RegistrationDto` |\n\n### 包结构\n```\ncom.healthlink.his.web.{module}.controller\ncom.healthlink.his.web.{module}.appservice\ncom.healthlink.his.web.{module}.service\ncom.healthlink.his.web.{module}.mapper\ncom.healthlink.his.web.{module}.dto\ncom.healthlink.his.domain.{module}\ncom.healthlink.his.common.enums\n```\n\n### 关键约束\n- 所有查询使用 `LambdaQueryWrapper`,禁止字符串拼接 SQL\n- `@Transactional(rollbackFor = Exception.class)` 管理事务\n- 所有接口标注 `@PreAuthorize` 权限控制\n- 患者敏感信息在日志中脱敏\n- **扩展功能不修改原有函数签名**\n\n---\n\n## 九、前端开发规范\n\n### 技术栈\n- Vue 3 + Vite + Element Plus + Pinia + Axios(基于 RuoYi-Vue3)\n\n### 目录结构\n```\nsrc/api/{module}/ # API接口\nsrc/views/{module}/ # 页面组件\nsrc/store/modules/ # Pinia状态管理\nsrc/components/ # 公共组件\n```\n\n### 关键约束\n- API前缀:`/healthlink-his/api/v1/`\n- 路由懒加载:`() => import('@/views/xxx/index.vue')`\n- 页面使用 `\n\n\n -Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP -<+>UTF-8 -=================================================================== -diff --git a/healthlink-his-ui/src/views/orderclosedloop/statistics/index.vue b/healthlink-his-ui/src/views/orderclosedloop/statistics/index.vue ---- a/healthlink-his-ui/src/views/orderclosedloop/statistics/index.vue (revision 954462272e2cde4d73ad13d07d849ee7d1f9ba71) -+++ b/healthlink-his-ui/src/views/orderclosedloop/statistics/index.vue (date 1781590382637) -@@ -76,7 +76,7 @@ - - - 未闭环医嘱预警 -- {{ unclosedWarnings.length }} 条待处理 -+ {{ warningTotal }} 条待处理 - - - -@@ -97,6 +97,16 @@ - - - -+ - - - -@@ -167,25 +177,25 @@ - } - - function handleRemind(row) { -- ElMessageBox.confirm('??? ' + row.doctorName + ' ???????', '????', { -+ ElMessageBox.confirm('确认提醒 ' + row.doctorName + ' 处理未闭环医嘱?', '催办提醒', { - type: 'warning', -- confirmButtonText: '????', -- cancelButtonText: '??' -+ confirmButtonText: '确认催办', -+ cancelButtonText: '取消' - }).then(() => { -- apiRemindOrder({ orderNo: row.orderNo, message: '?????????????' }).then(res => { -+ apiRemindOrder({ orderNo: row.orderNo, message: '您有未闭环医嘱需要处理' }).then(res => { - if (res.code === 200) { -- ElMessage.success('???????? ' + row.doctorName) -+ ElMessage.success('催办消息已发送给 ' + row.doctorName) - } else { -- ElMessage.error(res.msg || '????') -+ ElMessage.error(res.msg || '催办失败') - } - }).catch(() => { -- ElMessage.error('??????') -+ ElMessage.error('催办失败') - }) - }).catch(() => {}) - } - - function handleViewDetail(row) { -- ElMessage.info('???: ' + row.orderNo + ' | ??: ' + row.patientName + ' | ????: ' + row.currentStep) -+ ElMessage.info('医嘱号: ' + row.orderNo + ' | 患者: ' + row.patientName + ' | 当前环节: ' + row.currentStep) - } - - onMounted(() => { -Index: .github/copilot-instructions.md -IDEA additional info: -Subsystem: com.intellij.openapi.diff.impl.patch.BaseRevisionTextPatchEP -<+># HealthLink-HIS — AI 开发规范 (GitHub Copilot)\n\n> \uD83E\uDD16 GitHub Copilot 打开本项目时自动加载此文件。\n\n---\n\n# HealthLink-HIS — AI 开发规范(自动加载)\n\n> \uD83E\uDD16 **本文件供所有 AI 编码工具自动读取**。进入本项目后必须遵守以下规范。\n> \n> **模型决定上限,Harness 决定底线。**\n\n---\n\n## 一、项目概览\n\n| 属性 | 值 |\n|------|------|\n| 项目名 | HealthLink-HIS(医院信息系统) |\n| 后端路径 | `healthlink-his-server/` |\n| 前端路径 | `healthlink-his-ui/` |\n| 文档路径 | `MD/` |\n| JDK | 25 (OpenJDK) |\n| Spring Boot | 4.0.6 |\n| MyBatis-Plus | 3.5.16 |\n| Vue | 3.x + Vite + Element Plus |\n| 数据库 | PostgreSQL 15+ |\n| 包名 | `com.healthlink.his` |\n| 后端端口 | 18082 |\n| 前端端口 | 81 |\n\n---\n\n## 二、铁律(必须遵守,违反即失败)\n\n### \uD83D\uDD34 P0 铁律 — 不可违反\n\n**铁律1: 修改完必须测试**\n```\n后端: mvn clean compile -DskipTests → mvn install -DskipTests → mvn test\n前端: npm run build:dev → npm run lint\n```\n- 白盒:编译通过,无 ERROR\n- 黑盒:关键接口返回 `{code:200, data:...}`,验证业务逻辑\n- 冒烟:应用正常启动,核心流程通畅\n\n**铁律2: Flyway 数据库迁移**\n- 凡是新建表、新增字段,必须创建 Flyway 迁移脚本\n- 路径:`healthlink-his-domain/src/main/resources/db/migration/`\n- 命名:`V{版本号}__{描述}.sql`(双下划线)\n\n**铁律3: 测试通过后才提交**\n- 编译 + 测试全部通过后才能 git commit\n- 不提交未完成的功能、调试代码、临时文件\n\n**铁律4: 前后端API路径对齐**\n- 后端前缀:`/healthlink-his/api/v1/`\n- 前端 `request.js` 的 baseURL 必须与后端匹配\n\n**铁律5: 状态值一致性(Bug #574 教训)**\n- 修改任何状态值前,必须先列出完整的状态流转链路\n- 检查项:枚举定义 → Service 设置 → 查询映射 → 前端 STATUS_CLASS_MAP → 前端 v-if → 统计SQL\n- 禁止:只改一端不检查其他端\n\n**铁律6: 禁止删除源文件(Bug #574 教训)**\n- 绝对禁止删除项目中已有的 Java/Vue/SQL 源文件\n- 编译错误 → 修复错误;重复文件 → 重构合并\n- 唯一例外:明确由人类确认删除的文件\n\n**铁律7: 禁止修改已有公开方法签名**\n- 不能删除/重命名已有的 public 方法,不能修改参数列表\n- 需要新功能 → 添加重载方法;需要改行为 → 修改内部实现\n\n**铁律8: 验证后才宣称完成(Verification Before Completion)**\n- **没有跑过验证命令,就不能说\"完成了\"\"通过了\"\"没问题\"**\n- 禁止使用\"应该可以\"\"大概没问题\"\"看起来正确\"\n- 必须:运行命令 → 读取输出 → 确认结果 → 才能宣称\n- 这是诚实原则,不是效率问题\n\n\n**铁律9: 开发前必须审核原有代码(P0 — 铁律)**\n- **任何新功能开发前,必须先搜索项目中是否已有相关代码**\n- 搜索路径:Controller / AppService / Service / Mapper / Entity / 前端页面 / API接口\n- 如果已有部分功能 → 在原有代码基础上**升级优化完善**,禁止另起炉灶\n- 如果已有接口但前端缺失 → 只补前端,不重复建后端\n- 如果已有前端但后端缺失 → 只补后端,不重写前端\n- 搜索命令:`rg -l \"关键词\" healthlink-his-server/ healthlink-his-ui/src/`\n- 禁止:不看代码就新建模块、重复实现已有功能、废弃原有代码另写一套\n\n\n**铁律12: 设计文档确认后自主开发(铁律)**\n- 设计文档(如 `MD/architecture/GRADE3A_GAP_ANALYSIS_AND_DESIGN.md`)一旦确认,后续开发**必须按文档自主执行**\n- **禁止反复询问\"是否继续\"\"下一步做什么\"\"是否开始\"**——直接按计划推进\n- 每完成一个 Sprint,自动提交推送,然后立即开始下一个 Sprint\n- 只在遇到**无法解决的阻塞**(如技术选型冲突、需求不明确、第三方依赖不可用)时才暂停询问\n- 设计文档是\"**已签合同**\",不是\"参考意见\"。铁律执行优先级:设计文档 > 人类临时指令 > AI 自行判断\n\n\n### \uD83D\uDFE1 P1 铁律 — 强烈建议\n\n**铁律9: 先分解再行动**\n- 修改超过3个文件、涉及多模块、数据库变更,必须先制定计划\n\n**铁律10: 验证后信**\n- 每次修改后必须验证编译通过,不信记忆\n\n**铁律13: 文档统一管理**\n- 所有文档存储在 `MD/` 目录\n- 文件名:大写英文+下划线(如 `BACKEND_CHECKLIST.md`)\n- 文档头部必须包含元数据块(文档类型、版本、日期)\n\n---\n\n\n**铁律14: 设计文档必须包含UI设计和调用流程**\n- 所有新模块/页面的设计文档必须包含:UI布局描述、交互效果清单、前后端调用流程\n- 没有明确UI设计的模块,禁止直接编码\n- 详见 \n- 设计文档必须写清楚:系统调用关系、方法函数调用关系、完整业务流程\n- 设计文档中每个用户操作必须对应:前端事件 → API调用 → 后端处理链路 → 返回数据 → UI渲染\n\n---\n\n## 三、Karpathy 编码准则\n\n> 减少 LLM 常见编码错误。偏向谨慎而非速度。\n\n### 3.1 先想再写\n- 明确陈述假设,不确定就问\n- 多种解读时都列出来,不要默默选一种\n- 有更简单的方案就说出来,该推回就推回\n- 不清楚的地方停下来,说清楚哪里不清楚\n\n### 3.2 简洁优先\n- 不做没要求的功能,不做一次性代码的抽象\n- 不加没要求的\"灵活性\"和\"可配置性\"\n- 200 行能 50 行搞定就重写\n- 自问:\"高级工程师会不会觉得这过度设计?\"\n\n### 3.3 精准修改\n- 只改必须改的,不\"顺手改进\"相邻代码\n- 匹配现有代码风格,即使你有不同的偏好\n- 每行改动都能追溯到用户的请求\n- 只清理你自己改动产生的无用代码\n\n### 3.4 目标驱动\n- 把任务转化为可验证目标\n- 多步任务声明计划:`[步骤] → 验证: [检查]`\n- 强验收标准让 Agent 能独立循环,弱标准需要持续澄清\n\n---\n\n## 四、全链路 6 环分析\n\n> ⚠\uFE0F **涉及数据库字段的 Bug / 需求,必须走完整链路。**\n\n```\n前端/页面 → Controller → Service → Mapper → DB/SQL → 关联模块\n ①录入 ②验证 ③业务 ④持久化 ⑤存储 ⑥联动\n```\n\n| 环 | 检查内容 |\n|----|---------|\n| ① 录入 | 前端有无输入入口(弹窗、表格行编辑、表单) |\n| ② 验证 | Controller 参数校验、@Valid、权限控制 |\n| ③ 业务 | Service 业务逻辑、事务边界、多个 Service 实现类入口 |\n| ④ 持久化 | Mapper XML、DTO 字段映射、类型转换 |\n| ⑤ 存储 | 数据库表结构、索引、NOT NULL 约束 |\n| ⑥ 联动 | 上游(医嘱→护士站)、下游(打印、计费、报表)是否同步 |\n\n**修复后的验证顺序**:\n1. 数据库:确认状态值已正确写入\n2. 后端接口:确认返回的状态映射正确\n3. 前端显示:确认页面显示正确状态文本\n4. 前端交互:确认按钮/操作基于正确状态启用/禁用\n5. 统计数据:确认池/报表统计包含新状态\n\n---\n\n## 五、Harness Engineering 方法论\n\n> Harness = 约束 + 反馈 + 控制平面 + 持久执行\n\n### 5.1 四层约束金字塔\n\n| 层级 | 内容 | 落地方式 |\n|------|------|---------|\n| **L1 架构约束** | 接口合约、包结构、命名规范、禁止模式 | 本文件铁律 |\n| **L2 代码质量** | 圈复杂度、代码风格、类型提示 | 编译门禁 + ESLint |\n| **L3 安全约束** | 敏感信息检测、权限检查、输入验证 | 配置不可硬编码 |\n| **L4 业务规则** | 领域逻辑、数据一致性、事务边界 | 全链路 6 环验证 |\n\n**约束设计原则**:\n- **可验证**:每条约束必须能被自动化检查(\"覆盖率>90%\"✅ \"质量要高\"❌)\n- **无歧义**:\"每函数不超过50行\"✅ \"函数不要太长\"❌\n- **优先级**:安全(1) > 架构(2) > 业务(3) > 质量(4) > 性能(5)\n- **渐进增强**:L1编译通过 → L2+命名规范 → L3+测试覆盖 → L4+安全扫描\n\n### 5.2 三层反馈系统\n\n| 层级 | 速度 | 覆盖范围 | 失败处理 |\n|------|------|---------|---------|\n| **L1 编译检查** | <30秒 | 语法、类型、签名 | 立即阻断,自行修复 |\n| **L2 数据流验证** | <5分钟 | 全链路字段、Mapper XML、DTO | 修复后上报 |\n| **L3 人工审查** | 10-30分钟 | 架构、设计、业务正确性 | 驳回/指导/批准 |\n\n**反馈铁律**:\n- 反馈必须可行动(文件 + 行号 + 错误类型 + 修复方向)\n- 失败后先回滚到最近检查点,再重试\n- 持续失败3次 → 上报人类\n\n### 5.3 控制平面\n\n```\n战略层(人类) → 设定目标、审批决策、异常升级\n战术层(Agent) → 任务分解、update_plan、依赖协调、检查点保存\n执行层(Agent) → 代码生成、测试执行、错误恢复、幂等重试\n```\n\n### 5.4 持久执行\n\n- 每个关键步骤保存检查点(`update_plan` 进度)\n- 失败后从最新检查点恢复,不从头开始\n- 幂等设计:同一操作重复执行结果一致\n- **三层状态管理**:系统层(工作流ID/超时/重试) → 执行层(当前活动/进度) → 业务层(已完成工作/中间产物)\n\n---\n\n## 六、五层质量门禁\n\n| 门禁 | 时间 | 范围 | 失败处理 |\n|------|------|------|---------|\n| **L1 编译检查** | <30秒 | 语法、类型、导入 | Agent 自行修复 |\n| **L2 静态分析** | <2分钟 | 代码风格、复杂度、安全 | Agent 修复 |\n| **L3 单元测试** | <5分钟 | 功能正确性、边界条件 | 自动修复或上报 |\n| **L4 集成测试** | <15分钟 | 模块间交互、数据流 | 上报人工 |\n| **L5 生产验证** | 持续 | 监控、告警、性能 | 自动回滚 |\n\n**提交铁律**:L1-L2 必须通过才能 commit,L3(如有DB变更)必须通过才能 push\n\n---\n\n## 七、系统化调试(Systematic Debugging)\n\n> **铁律:没有根因调查,不能提出修复方案。**\n\n### 四阶段流程\n\n**阶段1:根因调查**(修复前必须完成)\n1. 仔细阅读错误信息(堆栈、行号、错误码)\n2. 稳定复现(能否可靠触发?步骤?每次?)\n3. 检查最近变更(git diff、新依赖、配置变更)\n4. 多组件系统:在每个组件边界加诊断日志,定位哪一层断裂\n5. 追踪数据流:坏值从哪里来?谁调用的?一直追溯到源头\n\n**阶段2:模式分析**\n- 找到同代码库中类似的正常工作代码\n- 逐项对比差异\n- 理解依赖关系\n\n**阶段3:假设与测试**\n- 形成单一假设:\"我认为X是根因,因为Y\"\n- 做最小改动测试\n- 有效 → 阶段4;无效 → 新假设\n\n**阶段4:实施**\n- 创建失败测试用例\n- 修复根因(不是症状)\n- 验证修复\n\n---\n\n## 八、后端开发规范\n\n### 分层架构\n```\nController → AppService → Service → Mapper → Entity\n```\n\n### 命名规范\n| 类型 | 规则 | 示例 |\n|------|------|------|\n| Controller | `XxxController` | `RegistrationController` |\n| AppService | `IXxxAppService` / `XxxAppServiceImpl` | `IRegistrationAppService` |\n| Service | `IXxxService` / `XxxServiceImpl` | `IRegistrationService` |\n| Mapper | `XxxMapper` | `RegistrationMapper` |\n| Entity | `Xxx` | `Registration` |\n| DTO | `XxxDto` / `XxxQueryDto` | `RegistrationDto` |\n\n### 包结构\n```\ncom.healthlink.his.web.{module}.controller\ncom.healthlink.his.web.{module}.appservice\ncom.healthlink.his.web.{module}.service\ncom.healthlink.his.web.{module}.mapper\ncom.healthlink.his.web.{module}.dto\ncom.healthlink.his.domain.{module}\ncom.healthlink.his.common.enums\n```\n\n### 关键约束\n- 所有查询使用 `LambdaQueryWrapper`,禁止字符串拼接 SQL\n- `@Transactional(rollbackFor = Exception.class)` 管理事务\n- 所有接口标注 `@PreAuthorize` 权限控制\n- 患者敏感信息在日志中脱敏\n- **扩展功能不修改原有函数签名**\n\n---\n\n## 九、前端开发规范\n\n### 技术栈\n- Vue 3 + Vite + Element Plus + Pinia + Axios(基于 RuoYi-Vue3)\n\n### 目录结构\n```\nsrc/api/{module}/ # API接口\nsrc/views/{module}/ # 页面组件\nsrc/store/modules/ # Pinia状态管理\nsrc/components/ # 公共组件\n```\n\n### 关键约束\n- API前缀:`/healthlink-his/api/v1/`\n- 路由懒加载:`() => import('@/views/xxx/index.vue')`\n- 页面使用 `\n\n -Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP -<+>UTF-8 -=================================================================== -diff --git a/healthlink-his-ui/src/views/features/config.vue b/healthlink-his-ui/src/views/features/config.vue ---- a/healthlink-his-ui/src/views/features/config.vue (revision 954462272e2cde4d73ad13d07d849ee7d1f9ba71) -+++ b/healthlink-his-ui/src/views/features/config.vue (date 1781593908398) -@@ -33,6 +33,7 @@ - class="tree-card" - > - { -+ if (!treeRef.value) return -+ const store = treeRef.value.store -+ const traverse = (nodeList) => { -+ nodeList.forEach(node => { -+ store.nodesMap[node.menuId] && (store.nodesMap[node.menuId].expanded = true) -+ if (node.children && node.children.length > 0) { -+ traverse(node.children) -+ } -+ }) -+ } -+ traverse(nodes) -+} -+ - // 获取所有节点ID - const getAllNodeIds = (nodes) => { - const ids = [] -@@ -392,69 +415,25 @@ - // 只保留菜单类型(C类型)的节点 - const validMenuNodes = checkedNodes.filter(node => node.menuType === 'C') - -- // 创建包含菜单ID和完整路径的对象数组 -- const menuPromises = validMenuNodes.map(async (node) => { -- try { -- console.log(`开始获取菜单 ${node.menuName} (ID: ${node.menuId}) 的完整路径`); -- console.log(`节点对象:`, node); -- console.log(`节点的 path 属性:`, node.path); -- -- // 获取菜单的完整路径 -- const fullPathResponse = await getMenuFullPath(node.menuId); -- console.log(`菜单 ${node.menuName} 的完整路径响应:`, fullPathResponse); -- -- let fullPath = fullPathResponse.code === 200 ? (fullPathResponse.data || fullPathResponse.msg) : node.path; -- // 确保路径格式正确,去除多余的斜杠 -- if (fullPath && typeof fullPath === 'string') { -- // 将多个连续的斜杠替换为单个斜杠,但保留协议部分的双斜杠(如 http://) -- fullPath = fullPath.replace(/([^:])\/{2,}/g, '$1/'); -- } -- console.log(`菜单 ${node.menuName} 的完整路径:`, fullPath); -- -- const menuItem = { -- menuId: node.menuId, -- fullPath: fullPath, -- menuName: node.menuName, -- path: node.path, -- icon: node.icon, // 保存数据库中的图标类名 -- menuType: node.menuType // 保存菜单类型信息 -- }; -- -- console.log(`构造的菜单项对象:`, menuItem); -- return menuItem; -- } catch (error) { -- console.error(`获取菜单 ${node.menuName} 的完整路径失败:`, error); -- console.error(`错误堆栈:`, error.stack); -- -- // 如果获取完整路径失败,使用现有路径作为备选 -- console.log(`在错误处理中,节点的 path 属性:`, node.path); -- const menuItem = { -- menuId: node.menuId, -- fullPath: node.path, -- menuName: node.menuName, -- path: node.path, -- icon: node.icon, // 保存数据库中的图标类名 -- menuType: node.menuType // 保存菜单类型信息 -- }; -- console.log(`构造的菜单项对象(错误处理):`, menuItem); -- return menuItem; -+ // fullPath 已在 userMenus 接口返回时填充到节点数据上,直接使用 -+ const menuDataWithPaths = validMenuNodes.map((node) => { -+ let fullPath = node.fullPath || node.path || '' -+ // 将多个连续的斜杠替换为单个斜杠,但保留协议部分的双斜杠 -+ if (fullPath && typeof fullPath === 'string') { -+ fullPath = fullPath.replace(/([^:])\/{2,}/g, '$1/') -+ } -+ return { -+ menuId: node.menuId, -+ fullPath: fullPath, -+ menuName: node.menuName, -+ path: node.path, -+ icon: node.icon, -+ menuType: node.menuType - } -- }); -- -- // 等待所有完整路径获取完成 -- const menuDataWithPaths = await Promise.all(menuPromises); -- -- // 添加调试信息 -- console.log('准备保存的菜单数据:', menuDataWithPaths); -- -- // 检查每个对象是否包含 fullPath 属性 -- menuDataWithPaths.forEach((item, index) => { -- console.log(`菜单项 ${index} 包含 fullPath:`, item.hasOwnProperty('fullPath'), '值为:', item.fullPath); -- }); -+ }) - - // 对配置值进行URL编码以避免特殊字符问题 - const encodedConfigValue = encodeURIComponent(JSON.stringify(menuDataWithPaths)) -- console.log('编码后的配置值:', encodedConfigValue); - - // 保存到数据库 - const saveResult = await saveCurrentUserConfig('homeFeaturesConfig', encodedConfigValue) diff --git a/.idea/shelf/在进行更新之前于_2026_6_16_16_12_取消提交了更改_[更改]/HEALTHLINK_HIS_PRICING_v0.1.docx b/.idea/shelf/在进行更新之前于_2026_6_16_16_12_取消提交了更改_[更改]/HEALTHLINK_HIS_PRICING_v0.1.docx deleted file mode 100644 index cc4e0c4eb..000000000 Binary files a/.idea/shelf/在进行更新之前于_2026_6_16_16_12_取消提交了更改_[更改]/HEALTHLINK_HIS_PRICING_v0.1.docx and /dev/null differ diff --git a/.idea/shelf/在进行更新之前于_2026_6_16_16_12_取消提交了更改_[更改]/shelved.patch b/.idea/shelf/在进行更新之前于_2026_6_16_16_12_取消提交了更改_[更改]/shelved.patch deleted file mode 100644 index 33255fbab..000000000 --- a/.idea/shelf/在进行更新之前于_2026_6_16_16_12_取消提交了更改_[更改]/shelved.patch +++ /dev/null @@ -1,249 +0,0 @@ -Index: .windsurfrules -IDEA additional info: -Subsystem: com.intellij.openapi.diff.impl.patch.BaseRevisionTextPatchEP -<+># HealthLink-HIS — AI 开发规范 (Windsurf)\n\n> \uD83E\uDD16 Windsurf 打开本项目时自动加载此文件。\n\n---\n\n# HealthLink-HIS — AI 开发规范(自动加载)\n\n> \uD83E\uDD16 **本文件供所有 AI 编码工具自动读取**。进入本项目后必须遵守以下规范。\n> \n> **模型决定上限,Harness 决定底线。**\n\n---\n\n## 一、项目概览\n\n| 属性 | 值 |\n|------|------|\n| 项目名 | HealthLink-HIS(医院信息系统) |\n| 后端路径 | `healthlink-his-server/` |\n| 前端路径 | `healthlink-his-ui/` |\n| 文档路径 | `MD/` |\n| JDK | 25 (OpenJDK) |\n| Spring Boot | 4.0.6 |\n| MyBatis-Plus | 3.5.16 |\n| Vue | 3.x + Vite + Element Plus |\n| 数据库 | PostgreSQL 15+ |\n| 包名 | `com.healthlink.his` |\n| 后端端口 | 18082 |\n| 前端端口 | 81 |\n\n---\n\n## 二、铁律(必须遵守,违反即失败)\n\n### \uD83D\uDD34 P0 铁律 — 不可违反\n\n**铁律1: 修改完必须测试**\n```\n后端: mvn clean compile -DskipTests → mvn install -DskipTests → mvn test\n前端: npm run build:dev → npm run lint\n```\n- 白盒:编译通过,无 ERROR\n- 黑盒:关键接口返回 `{code:200, data:...}`,验证业务逻辑\n- 冒烟:应用正常启动,核心流程通畅\n\n**铁律2: Flyway 数据库迁移**\n- 凡是新建表、新增字段,必须创建 Flyway 迁移脚本\n- 路径:`healthlink-his-domain/src/main/resources/db/migration/`\n- 命名:`V{版本号}__{描述}.sql`(双下划线)\n\n**铁律3: 测试通过后才提交**\n- 编译 + 测试全部通过后才能 git commit\n- 不提交未完成的功能、调试代码、临时文件\n\n**铁律4: 前后端API路径对齐**\n- 后端前缀:`/healthlink-his/api/v1/`\n- 前端 `request.js` 的 baseURL 必须与后端匹配\n\n**铁律5: 状态值一致性(Bug #574 教训)**\n- 修改任何状态值前,必须先列出完整的状态流转链路\n- 检查项:枚举定义 → Service 设置 → 查询映射 → 前端 STATUS_CLASS_MAP → 前端 v-if → 统计SQL\n- 禁止:只改一端不检查其他端\n\n**铁律6: 禁止删除源文件(Bug #574 教训)**\n- 绝对禁止删除项目中已有的 Java/Vue/SQL 源文件\n- 编译错误 → 修复错误;重复文件 → 重构合并\n- 唯一例外:明确由人类确认删除的文件\n\n**铁律7: 禁止修改已有公开方法签名**\n- 不能删除/重命名已有的 public 方法,不能修改参数列表\n- 需要新功能 → 添加重载方法;需要改行为 → 修改内部实现\n\n**铁律8: 验证后才宣称完成(Verification Before Completion)**\n- **没有跑过验证命令,就不能说\"完成了\"\"通过了\"\"没问题\"**\n- 禁止使用\"应该可以\"\"大概没问题\"\"看起来正确\"\n- 必须:运行命令 → 读取输出 → 确认结果 → 才能宣称\n- 这是诚实原则,不是效率问题\n\n\n**铁律9: 开发前必须审核原有代码(P0 — 铁律)**\n- **任何新功能开发前,必须先搜索项目中是否已有相关代码**\n- 搜索路径:Controller / AppService / Service / Mapper / Entity / 前端页面 / API接口\n- 如果已有部分功能 → 在原有代码基础上**升级优化完善**,禁止另起炉灶\n- 如果已有接口但前端缺失 → 只补前端,不重复建后端\n- 如果已有前端但后端缺失 → 只补后端,不重写前端\n- 搜索命令:`rg -l \"关键词\" healthlink-his-server/ healthlink-his-ui/src/`\n- 禁止:不看代码就新建模块、重复实现已有功能、废弃原有代码另写一套\n\n\n**铁律12: 设计文档确认后自主开发(铁律)**\n- 设计文档(如 `MD/architecture/GRADE3A_GAP_ANALYSIS_AND_DESIGN.md`)一旦确认,后续开发**必须按文档自主执行**\n- **禁止反复询问\"是否继续\"\"下一步做什么\"\"是否开始\"**——直接按计划推进\n- 每完成一个 Sprint,自动提交推送,然后立即开始下一个 Sprint\n- 只在遇到**无法解决的阻塞**(如技术选型冲突、需求不明确、第三方依赖不可用)时才暂停询问\n- 设计文档是\"**已签合同**\",不是\"参考意见\"。铁律执行优先级:设计文档 > 人类临时指令 > AI 自行判断\n\n**铁律18: 禁止破坏原有功能(P0绝对铁律)**\n- **完善增加功能和流程时,绝对不能破坏或者让原有功能不能用**\n- 修改已有实体前必须对比原始文件(`git show HEAD~N:./file.java`),保留所有原有字段和方法\n- 新增字段只能追加,不能删除或重命名已有字段\n- SQL迁移只允许 `ALTER TABLE ADD COLUMN`,不允许 `DROP COLUMN` 或 `RENAME COLUMN`\n- Controller新端点不能修改已有端点的路径或参数\n- 前端新页面不能修改已有页面的组件结构\n- 每次修改后必须 `mvn clean compile -DskipTests` 验证\n- **违规判定**: 因修改导致原有代码编译失败或运行报错,视为违反铁律18,必须立即回滚修复\n\n\n### \uD83D\uDFE1 P1 铁律 — 强烈建议\n\n**铁律9: 先分解再行动**\n- 修改超过3个文件、涉及多模块、数据库变更,必须先制定计划\n\n**铁律10: 验证后信**\n- 每次修改后必须验证编译通过,不信记忆\n\n**铁律13: 文档统一管理**\n- 所有文档存储在 `MD/` 目录\n- 文件名:大写英文+下划线(如 `BACKEND_CHECKLIST.md`)\n- 文档头部必须包含元数据块(文档类型、版本、日期)\n\n---\n\n\n**铁律14: 设计文档必须包含UI设计和调用流程**\n- 所有新模块/页面的设计文档必须包含:UI布局描述、交互效果清单、前后端调用流程\n- 没有明确UI设计的模块,禁止直接编码\n- 详见 \n- 设计文档必须写清楚:系统调用关系、方法函数调用关系、完整业务流程\n- 设计文档中每个用户操作必须对应:前端事件 → API调用 → 后端处理链路 → 返回数据 → UI渲染\n\n---\n\n## 三、Karpathy 编码准则\n\n> 减少 LLM 常见编码错误。偏向谨慎而非速度。\n\n### 3.1 先想再写\n- 明确陈述假设,不确定就问\n- 多种解读时都列出来,不要默默选一种\n- 有更简单的方案就说出来,该推回就推回\n- 不清楚的地方停下来,说清楚哪里不清楚\n\n### 3.2 简洁优先\n- 不做没要求的功能,不做一次性代码的抽象\n- 不加没要求的\"灵活性\"和\"可配置性\"\n- 200 行能 50 行搞定就重写\n- 自问:\"高级工程师会不会觉得这过度设计?\"\n\n### 3.3 精准修改\n- 只改必须改的,不\"顺手改进\"相邻代码\n- 匹配现有代码风格,即使你有不同的偏好\n- 每行改动都能追溯到用户的请求\n- 只清理你自己改动产生的无用代码\n\n### 3.4 目标驱动\n- 把任务转化为可验证目标\n- 多步任务声明计划:`[步骤] → 验证: [检查]`\n- 强验收标准让 Agent 能独立循环,弱标准需要持续澄清\n\n---\n\n## 四、全链路 6 环分析\n\n> ⚠\uFE0F **涉及数据库字段的 Bug / 需求,必须走完整链路。**\n\n```\n前端/页面 → Controller → Service → Mapper → DB/SQL → 关联模块\n ①录入 ②验证 ③业务 ④持久化 ⑤存储 ⑥联动\n```\n\n| 环 | 检查内容 |\n|----|---------|\n| ① 录入 | 前端有无输入入口(弹窗、表格行编辑、表单) |\n| ② 验证 | Controller 参数校验、@Valid、权限控制 |\n| ③ 业务 | Service 业务逻辑、事务边界、多个 Service 实现类入口 |\n| ④ 持久化 | Mapper XML、DTO 字段映射、类型转换 |\n| ⑤ 存储 | 数据库表结构、索引、NOT NULL 约束 |\n| ⑥ 联动 | 上游(医嘱→护士站)、下游(打印、计费、报表)是否同步 |\n\n**修复后的验证顺序**:\n1. 数据库:确认状态值已正确写入\n2. 后端接口:确认返回的状态映射正确\n3. 前端显示:确认页面显示正确状态文本\n4. 前端交互:确认按钮/操作基于正确状态启用/禁用\n5. 统计数据:确认池/报表统计包含新状态\n\n---\n\n## 五、Harness Engineering 方法论\n\n> Harness = 约束 + 反馈 + 控制平面 + 持久执行\n\n### 5.1 四层约束金字塔\n\n| 层级 | 内容 | 落地方式 |\n|------|------|---------|\n| **L1 架构约束** | 接口合约、包结构、命名规范、禁止模式 | 本文件铁律 |\n| **L2 代码质量** | 圈复杂度、代码风格、类型提示 | 编译门禁 + ESLint |\n| **L3 安全约束** | 敏感信息检测、权限检查、输入验证 | 配置不可硬编码 |\n| **L4 业务规则** | 领域逻辑、数据一致性、事务边界 | 全链路 6 环验证 |\n\n**约束设计原则**:\n- **可验证**:每条约束必须能被自动化检查(\"覆盖率>90%\"✅ \"质量要高\"❌)\n- **无歧义**:\"每函数不超过50行\"✅ \"函数不要太长\"❌\n- **优先级**:安全(1) > 架构(2) > 业务(3) > 质量(4) > 性能(5)\n- **渐进增强**:L1编译通过 → L2+命名规范 → L3+测试覆盖 → L4+安全扫描\n\n### 5.2 三层反馈系统\n\n| 层级 | 速度 | 覆盖范围 | 失败处理 |\n|------|------|---------|---------|\n| **L1 编译检查** | <30秒 | 语法、类型、签名 | 立即阻断,自行修复 |\n| **L2 数据流验证** | <5分钟 | 全链路字段、Mapper XML、DTO | 修复后上报 |\n| **L3 人工审查** | 10-30分钟 | 架构、设计、业务正确性 | 驳回/指导/批准 |\n\n**反馈铁律**:\n- 反馈必须可行动(文件 + 行号 + 错误类型 + 修复方向)\n- 失败后先回滚到最近检查点,再重试\n- 持续失败3次 → 上报人类\n\n### 5.3 控制平面\n\n```\n战略层(人类) → 设定目标、审批决策、异常升级\n战术层(Agent) → 任务分解、update_plan、依赖协调、检查点保存\n执行层(Agent) → 代码生成、测试执行、错误恢复、幂等重试\n```\n\n### 5.4 持久执行\n\n- 每个关键步骤保存检查点(`update_plan` 进度)\n- 失败后从最新检查点恢复,不从头开始\n- 幂等设计:同一操作重复执行结果一致\n- **三层状态管理**:系统层(工作流ID/超时/重试) → 执行层(当前活动/进度) → 业务层(已完成工作/中间产物)\n\n---\n\n## 六、五层质量门禁\n\n| 门禁 | 时间 | 范围 | 失败处理 |\n|------|------|------|---------|\n| **L1 编译检查** | <30秒 | 语法、类型、导入 | Agent 自行修复 |\n| **L2 静态分析** | <2分钟 | 代码风格、复杂度、安全 | Agent 修复 |\n| **L3 单元测试** | <5分钟 | 功能正确性、边界条件 | 自动修复或上报 |\n| **L4 集成测试** | <15分钟 | 模块间交互、数据流 | 上报人工 |\n| **L5 生产验证** | 持续 | 监控、告警、性能 | 自动回滚 |\n\n**提交铁律**:L1-L2 必须通过才能 commit,L3(如有DB变更)必须通过才能 push\n\n---\n\n## 七、系统化调试(Systematic Debugging)\n\n> **铁律:没有根因调查,不能提出修复方案。**\n\n### 四阶段流程\n\n**阶段1:根因调查**(修复前必须完成)\n1. 仔细阅读错误信息(堆栈、行号、错误码)\n2. 稳定复现(能否可靠触发?步骤?每次?)\n3. 检查最近变更(git diff、新依赖、配置变更)\n4. 多组件系统:在每个组件边界加诊断日志,定位哪一层断裂\n5. 追踪数据流:坏值从哪里来?谁调用的?一直追溯到源头\n\n**阶段2:模式分析**\n- 找到同代码库中类似的正常工作代码\n- 逐项对比差异\n- 理解依赖关系\n\n**阶段3:假设与测试**\n- 形成单一假设:\"我认为X是根因,因为Y\"\n- 做最小改动测试\n- 有效 → 阶段4;无效 → 新假设\n\n**阶段4:实施**\n- 创建失败测试用例\n- 修复根因(不是症状)\n- 验证修复\n\n---\n\n## 八、后端开发规范\n\n### 分层架构\n```\nController → AppService → Service → Mapper → Entity\n```\n\n### 命名规范\n| 类型 | 规则 | 示例 |\n|------|------|------|\n| Controller | `XxxController` | `RegistrationController` |\n| AppService | `IXxxAppService` / `XxxAppServiceImpl` | `IRegistrationAppService` |\n| Service | `IXxxService` / `XxxServiceImpl` | `IRegistrationService` |\n| Mapper | `XxxMapper` | `RegistrationMapper` |\n| Entity | `Xxx` | `Registration` |\n| DTO | `XxxDto` / `XxxQueryDto` | `RegistrationDto` |\n\n### 包结构\n```\ncom.healthlink.his.web.{module}.controller\ncom.healthlink.his.web.{module}.appservice\ncom.healthlink.his.web.{module}.service\ncom.healthlink.his.web.{module}.mapper\ncom.healthlink.his.web.{module}.dto\ncom.healthlink.his.domain.{module}\ncom.healthlink.his.common.enums\n```\n\n### 关键约束\n- 所有查询使用 `LambdaQueryWrapper`,禁止字符串拼接 SQL\n- `@Transactional(rollbackFor = Exception.class)` 管理事务\n- 所有接口标注 `@PreAuthorize` 权限控制\n- 患者敏感信息在日志中脱敏\n- **扩展功能不修改原有函数签名**\n\n---\n\n## 九、前端开发规范\n\n### 技术栈\n- Vue 3 + Vite + Element Plus + Pinia + Axios(基于 RuoYi-Vue3)\n\n### 目录结构\n```\nsrc/api/{module}/ # API接口\nsrc/views/{module}/ # 页面组件\nsrc/store/modules/ # Pinia状态管理\nsrc/components/ # 公共组件\n```\n\n### 关键约束\n- API前缀:`/healthlink-his/api/v1/`\n- 路由懒加载:`() => import('@/views/xxx/index.vue')`\n- 页面使用 `
将被合并的患者({{ selectedRows.length }}人):