# 2026-09-15 ## 合同管理:发起/变更/续签弹框区块调整(图片需求,多轮完成) - 需求:①员工信息卡挪到「合同基础信息」模块下面;②标题按模式切换:`createBaseSectionTitle` = 变更合同基础信息 / 续签合同基础信息 / 合同基础信息(computed,模板引用);③变更/续签时基础信息可修改:所属机构/所属部门/岗位名称由 `disabled` 改为 `:disabled="isCreateMode"` + `v-model`(发起模式仍禁用自动带出,即"现有禁用不变");④三者必填带星号:`prop="legalEntityName/departmentName/positionName"` + `createRules` required(blur 触发)。 - 提交链路:`buildCreatePayload` 里 `departmentName = form.departmentName || emp.department`、`legalEntityName = form.legalEntityName || emp.legalEntity`(表单优先);变更模式 `changedValues` 含 legalEntity/department/position 的 id+name 六项;续签 `renewContract(id, { contract: {...payload} })` 全量展开。`fillCreateFormFromContract` 回填 detail 的 id/name,员工档案兜底"为空才补"。 - 员工信息卡最终放置:基础信息区块之后、对比区(`v-if="isCompareMode"`)之前,class `cm-create-employee-card cm-employee-below`;scss 新增 `.cm-employee-below{margin:14px 22px 0}` 与 `.cm-employee-item{margin-left:24px}`(注意要放 `.cm-detail-page` 规则之前,避免覆盖详情页卡片的 margin)。 - 弹框字段最终顺序:签订员工 / 合同编号 / 合同类型 / 拟定薪资 / 所属机构 / 所属部门 / 岗位名称 / 合同起始日期 / 签署方式 / 合同终止日期 / 合同模版(线上)或合同附件(线下 is-span-2)。 ## 解除/终止劳动合同弹框 UI 精简 - 去掉弹框内部标题栏(圈出来的大标题)和底部提示行(圈出来的 footer info)— 用户用图片明确圈出这两处。 - 原 header 里的员工/合同编号信息改为独立的 `.cm-terminate-info` 信息条,位于表单区上方:label「员工姓名」「合同编号」,值用更大更粗的字体,两者间距 48px。 - 新增样式 `.cm-terminate-info{padding:18px 22px 0;display:flex;gap:48px;align-items:center}`、label 15px #7e8b9d、b 17px #243955 font-weight:600。 ## 合同详情弹框 UI 精简 - 按图片圈出位置,移除合同详情弹框底部 footer-info(合同编号 / 状态提示),仅保留「关闭」按钮。 - 校验:lint 干净。 ## 解除弹框:签署方式联动离职证明模板/附件 - 签署方式=线上:下拉「离职证明模板」(prop `templateId`,选项复用 `contractTemplates`,选择后 `onTerminateTemplateChange` 解析 templateVersionId);必填。 - 签署方式=线下:单个上传入口「离职证明附件」(prop `offlineFileIds`);必填。 - 移除原「线下协议/解除依据文件」+「解除依据附件」双上传及 `evidenceFileIds` 字段。 - 提交 payload:ONLINE 传 templateId/templateVersionId,OFFLINE 传 offlineFileId。 - 校验:模板编译 0 error、lint 干净。 ## 解除弹框顶部信息条样式对齐合同详情 - 将原 `.cm-terminate-info` 改为复用 `.cm-create-employee-card` 员工信息卡:左侧头像、员工姓名、合同编号,外观与合同详情 `.cm-detail-employee` 一致。 - 新增 `.cm-terminate-employee{margin:16px 22px 0;padding:14px 18px}`,移除旧的信息条样式。 - 校验:模板编译 0 error、lint 干净。 ## 解除弹框:模板/附件位置调整到生效日期下方 - 将「离职证明模板 / 离职证明附件」表单项从表单底部上移到「生效日期」之后、「最后工作日期」之前。 - 线上/线下两种场景均占满整行(`grid-column: span 2`)。 - 校验:模板编译 0 error、lint 干净。 ## 解除弹框顶部员工信息卡间距调大 - 用户反馈「员工姓名」与「合同编号」挤在一起;新增 `.cm-terminate-employee .cm-employee-no{margin-left:48px}`,把两个标签间距拉开。 - 校验:lint 干净。 ## 人员结构看板两页 vs 接口文档差异盘点(仅分析,未改代码) - 目标模块:接口文档 hr 分组里 tag = **人力资源-人员结构报表**(Hr Personnel Structure Report Controller),共 **13 个 GET**,全部挂在 `/hr/personnel-structure-reports/` 下: - `/dashboard/summary`(顶部指标:员工总量/在岗人数/在岗率/人才库人数/当年入职/当年离职) - `/dashboard/structure-trend`(最近12个月在职/入职/离职/净增,仅对应"结构分析"趋势) - `/dashboard/overall-distribution`(用工类型 + 岗位性质) - `/dashboard/age-distribution`(年龄分段) - `/dashboard/organization-distribution`(按部门聚合) - `/dashboard/talent-structure`(岗位序列/岗位层级/职级) - `/dashboard/quality-analysis`(学历 + 政治面貌;职称留空) - `/analysis/summary`(在职/离职/累计入职 + 同比环比) - `/analysis/{dimensionCode}`(11 个白名单维度:POSITION_LEVEL、WORK_LOCATION、POSITION_SEQUENCE、DEPARTMENT、GENDER、CONTRACT_TYPE、EDUCATION、AGE、TENURE、POSITION_TENURE、PROFESSIONAL_TITLE,职称返回空值占位) - `/mobility-analysis`(当年入职/离职/当月净增 + 12月趋势) - `/effectiveness-analysis`(在岗人数 + 司龄结构;人均产出/编制使用率明确 DATA_UNAVAILABLE) - `/details`(分页明细,仅统计日在岗人员,支持 dimensionCode + dimensionValueCode) - `/details/export`(导出,复用明细口径) - 统一入参:`organizationId`/`departmentId`(int64) + `year`/`month`(int, 1-12) + `contractType`(string,字典 code)。 - 统一返回包装:`{code,message,data}`,业务体 = `人员结构报表响应`{dataStatus, reportMonth(yyyy-MM), statisticsDate(yyyy-MM-dd), message, kpis[], dimensions[], trends[], scopeDepartmentIds[]};`dimensions[].items[]` = `{valueCode,valueName,count,percentage}`;`kpis[]` = `{metricCode,metricName,value,unit,rateValue,monthOnMonthRate,yearOnYearRate,dataStatus,message}`。 - **前端现状(最大问题)**:`src/views/hrAnalysis/personnelStructureDashboard` 与 `personnelStructureAnalysisBoard` 两页 **100% 用本地 `./mock.js`,`src/api/hr/` 下没有任何 personnel-structure 封装,13 个接口一个都没接**。 - 典型错位:字段名(页面 `{name,value}` vs 接口 `{valueName,count}`;页面 `metrics[].key/label` vs 接口 `kpis[].metricCode/metricName`);筛选(页面月分是 `'2026-08'` 字符串、组织是假枚举/部门中文名,无 departmentId);页面B 4 个筛选只 `chart.resize()` 不取数;页面B「导出」是 `window.print()` 不是 `/details/export`;两页明细都是本地切片无分页(接口是 Pagination);页面B 明细列「招聘类型」接口无此字段;页面A「司龄」tab 用的是年龄数据;页面A「职称分析」「职业属性」(无任何维度编码)会展示假数据;页面A 用 `scaleRate` 按组织比例前端硬乘伪造数值;两页都没有 dataStatus/message 空值协议。 ## 已按接口完成两页对接(本次实际改代码) - 新增 `src/api/hr/personnelStructure.js`:13 个接口(`/hr/personnel-structure-reports/**`)+ `adaptDimensions/adaptMetrics/adaptTrends/adaptDetailRows/buildQuery/pickMetric` + `DATA_STATUS`/`DIMENSION` 常量 + `downloadBlob` 导出。 - 新增 `src/views/hrAnalysis/shared.js`:合同类型枚举、`buildOrganizationOptions/rootOrganizationOptions/departmentOptions`(基于 `listOrganizations` + `buildOrganizationTree`)、`formatCount/formatRate/trendTone/ratioWidth/niceMax/recentMonths/splitMonth/monthTick/tooltipStyle`。 - 重写 `personnelStructureDashboard/index.vue`(`psd-screen`):月份用 `el-date-picker type="month"` 取 `yyyy-MM`,另加组织/部门/合同类型三个筛选;6 指标卡走 `dashboard/summary`(按 metricCode→关键字→顺序三级匹配);趋势面板 3 个 tab 分别走 `structure-trend` / `mobility-analysis` / `effectiveness-analysis`(效能无趋势时回退接口返回的司龄结构柱状图);整体人员分布走 `overall-distribution`(按 `dimensions[]` 动态分组成行)、年龄/司龄走 `age-distribution` + `analysis/TENURE`、各单位走 `organization-distribution`、人才结构走 `talent-structure`(动态页签)、素质分析走 `quality-analysis`(动态页签,含职称空值占位);明细弹框走 `/details`,支持关键字、`dimensionCode`+`dimensionValueCode` 下钻、分页。 - 重写 `personnelStructureAnalysisBoard/index.vue`(`psa-board`):3 指标卡走 `analysis/summary`;11 个维度图表全部走 `analysis/{dimensionCode}`;新增「人员结构趋势」(`structure-trend`) 与「整体人员分布」(`overall-distribution`) 两个缺失面板,面板数 10→12,改为 4 列 × 3 行网格;「工作地点」因接口不提供经纬度,改为按 `valueName/count/percentage` 的排名条形列表(保留地图底纹);学历/职称合并成一个带页签的面板;明细弹框走 `/details`(分页 + 关键字 + 维度下钻),「导出」改为调用 `/details/export`(blob 下载),不再 `window.print()`。 - 删除两个页面的 `mock.js`;两页补上 `dataStatus/message` 空值协议(`--` + `title`)、不可用态占位、「统计日」显示(取 `statisticsDate`,不再前端拼 `-31`)。 - 顺手修掉两个自身 bug:① `buildQuery` 原本只输出 5 个 scope 字段,会把明细的 `pageNum/size/dimensionCode` 吃掉,已改成调用方 `compact({ ...buildQuery(filters), ... })`;② `buildQuery` 不识别 `'yyyy-MM'`,月份选择器传进来的值会被丢掉而回退当前自然月,已加正则兼容。 - 验证:`npx vue-cli-service build` 通过(45s),产物里有 `view-hrAnalysis-personnelStructure*` 的 js/css chunk。注意:构建生成了 `dist/`(已在 .gitignore 中,批量删除被安全策略拦截,未清理,保留无影响)。 ## 隐藏职称相关模块 - 新增可开关的隐藏机制 `src/views/hrAnalysis/shared.js`:`HIDDEN_DIMENSION_CODES = [DIMENSION.PROFESSIONAL_TITLE]` + `isDimensionVisible(code)` + `filterVisibleGroups(groups)`。**职称功能上线后,把该数组置空(或移除 PROFESSIONAL_TITLE)即可恢复**,页面代码不用再动。 - 人员结构看板:`quality-analysis` 返回的 dimensions 先过 `filterVisibleGroups`,素质分析面板的页签自动少掉「职称」(剩学历/政治面貌)。 - 人员结构分析看板:`DIMENSION_CODES` 末尾 `.filter(isDimensionVisible)` —— 职称的请求也不再发起;`educationTabs` 由 data 改为 computed,只有一个维度时 `PanelTitle` 不再渲染页签(`tabs.length > 1` 才渲染),面板保持「学历分析」标题。 - 回归:`node tmp/check-blocks.js`(模板编译 + babel 解析)与 `npx vue-cli-service build` 均通过。 ## 人员结构报表模块的接口缺口盘点(查过文档确认) - 全 hr 分组 **没有任何 `enum` 定义**(`Swagger definitions` 里零个 enum),所以 `metricCode` 取值表根本不存在,`dimensionCode` 也只写在 description 文本里。 - 已确认的缺口要点(可直接拿去和后端对齐): 1. `metricCode` 无枚举 → 指标卡只能"编码猜测+名称关键字+返回顺序"兜底。 2. 无"可用统计月份/快照可用性"接口;人员结构报表文档只说"按该月末重算",而**编制看板**每个接口都写了"历史月份仅查询已生成快照",两者口径不一致,前端月份选择器只能硬生成 12 个月。 3. 无维度值字典接口 → `dimensionValueCode` 只能从图表返回里取,做不了筛选下拉。 4. 无"当前用户可统计范围(组织+部门)"接口,`organizationId/departmentId` 的范围只能靠报错;前端复用 main 分组 `/main/group/getGroupList`,两边 id 是否同一套需确认。 5. `contractType` 无字典接口(文档只说"由劳动合同模块维护"),前端硬编码 6 个 code。 6. `details` **同时有 `deptId`(机构id) 与 `departmentId`(统计部门主键)**,语义重叠、前端不知道该传哪个;且维度筛选只支持单个 `dimensionCode`+`dimensionValueCode`,做不了"部门+学历+年龄"组合筛选或维度×维度二次下钻。 7. `details` 只返回统计日在岗人员,但明细模型里有 `statusOnStatisticsDate`(在职/离职)→ 该字段实际恒为"在职"。 8. `/details/export` 的 200 响应 schema 是空的(只有 `{"description":"OK"}`)、`produces: ["*/*"]`,没有文件名/格式契约,也没有异步导出任务机制;入参里还混着 `pageNum/size/offset/sortName/orderBy`(导出应为全量,语义冲突)。 9. `structure-trend` 不支持 `dimensionCode` → 看不了"按部门/序列/学历的趋势"。 10. `talent-structure` 描述含"**职级**分布",但 `/analysis/{dimensionCode}` 的 11 个白名单维度里**没有职级编码** → 职级只能从 talent-structure 的 dimensions 拿。 11. `overall-distribution` 只描述"用工类型和岗位性质",**没给 dimensionCode/dimensionName 取值** → 只能动态渲染。 12. `effectiveness-analysis` 的人均产出/编制使用率固定 `DATA_UNAVAILABLE`,但**编制使用率在编制看板 `/hr/headcount/dashboard/summary` 有**(不过编制看板的 organizationId/departmentId 口径与人员结构报表是否一致需确认)。 13. `mobility-analysis` 与 `structure-trend` 的 `trends` 字段完全同构(month/onJobCount/onboardCount/leaveCount/netChangeCount),数据源重叠,两处易数字不一致。 14. `rateValue` 与 `value`/`unit` 的组合规则没写(比例型是否同时填 value);前端用 `rateValue ?? value` 容忍两种写法。 15. 未被利用的返回字段:`scopeDepartmentIds`("实际参与统计的部门主键集合")目前前端没用,可用来展示"本次统计覆盖 N 个部门"或校验所选部门是否真的参与统计。 ## `fm/directory/createPersonal/{userId}` 调用逻辑梳理(问答,未改代码) - 后端:fm 分组 `POST /fm/directory/createPersonal/{userId}`,summary「创建个人文档区」,path 参数 `userId`(integer, 必填),返回 `响应«R»`(泛型)。 - 前端封装:`src/api/organization/index.js` 的 `createPersonalDirectory(userId)`(空 id 直接返回 null 不发请求)+ `hasJobNumber(record)`(`record.jobNumber ?? record.user?.jobNumber` **有值即视为已入库归档**)+ `ensurePersonalDirectory(userId, record)`(**导出但全项目无调用,死代码**)。 - 唯一调用点:`src/views/hrManagement/onboardingHandling/index.vue`,入口是保存主流程 `persistOnboarding()` → `maybeCreatePersonalDirectory(merged, alreadyArchived)`。 - 语义:**"员工首次入库归档"这一刻才建个人文档区**。判定顺序: 1. 表单当前有工号 → `alreadyArchived=true` → 只记名单、不发请求; 2. 表单没工号但 `userIdBefore` 存在 → 再拉一次 `getOnboardingByUserId` 看详情有没有工号,有则回填 `editing.jobNumber` 并记名单(拉失败就"以保存前表单中的工号为准",不打断); 3. 保存(`updateOnboarding`/`createOnboarding`)→ `mergeSavedResult`(会把 `saved.jobNumber || editing.jobNumber` 带出来)→ 若 `alreadyArchived` 为 false 就**发请求建目录**(即"保存这一步刚拿到工号"= 首次归档,正是要建的时刻);成功后把 userId 记进 `personalDirectoryUserIds`。 - 幂等三层:组件内存名单(本次会话同一 userId 只请求一次)+ 保存前两次工号检查 + "有工号=已归档"业务约定;**失败只 `showRequestError('创建个人文档区失败')`,不 reject、不打断入职保存**。 - 遗留疑点(待与后端确认):接口文档没写是否幂等。若后端保存/详情都不回填 `jobNumber`,则刷新页面(内存名单丢失)后对同一条记录再次保存会**重复调用**该接口。 ### 后续调整:清理死代码 + 跨会话幂等(已改) - `src/api/organization/index.js`:删掉 `ensurePersonalDirectory`(导出但全项目零调用)。 - `src/views/hrManagement/onboardingHandling/index.vue`:新增 `PERSONAL_DIRECTORY_CACHE_KEY = "hr:onboarding:createdPersonalDirectory"` + `readCreatedDirectoryUsers()/writeCreatedDirectoryUsers()`(localStorage,最多保留 500 条、try/catch 兜底隐私模式);data 加 `createdDirectoryUsers`;新增 `rememberCreatedDirectoryUser()`(**仅创建成功才落盘**,失败不落盘以便下次重试);`maybeCreatePersonalDirectory` 增加"已成功创建过就不重复调"这一层判断。 - 判断顺序最终为:`!userId` → `existedJobNumber`(已归档,只记内存名单)→ 会话内存名单 → 持久化已建名单 → 调接口 → 成功后同时记两份。 - **没有**加"保存后仍无工号就不建"的判断:那会把"后端不回填 jobNumber"的场景变成永远不建目录(功能回归),因此只用持久化去重解决刷新后重复调用的问题。 ## 合同管理:发起合同弹框补「岗位名称」禁用输入框 - 文件 `src/views/contractManagement/index.vue`("发起合同弹窗" `ele-modal`,`:visible.sync="createVisible"`,三态复用:发起/变更/续签)。 - 在「所属机构」之后插入 ``。**没有新增数据字段** —— `createForm.positionName` 本来就存在,`applyEmployeeToForm`(选员工)、变更/续签详情回填、路由 query 带入三条路径都已赋值,并已用于模板匹配接口的 `positionName` 参数。 - 该页禁用型"自动带出"字段统一用 `:value`(单向绑定,不发 `input`)而不是 `v-model`,与相邻的「所属部门/所属机构」保持一致;disabled 只读展示字段不加 `prop`(`createRules` 里没有对应规则)。 - 布局提示:`.cm-create-grid` 是 2 列 grid(gap 4px 20px),字段从 10 个变 11 个后,**在线签署模式下最后一行只剩「合同模版」一个**(离线模式的「合同附件」本身有 `is-span-2` 所以仍整齐)。嫌不整齐的话给最后一项统一加 `class="is-span-2"` 即可。 - 追加调整:把**所属机构 与 所属部门 换了位置**。 - 追加调整(变更/续签弹框结构):**对比区(原合同信息/拟定信息两张卡片)挪到「合同基础信息」表单下方**,弹框顺序变为 表单 → 对比区 → 页脚;对比区由内联 `style="background:#fafbfd"` 改为类 `cm-compare-section`(scss:`.cm-create-section.cm-compare-section{background:#fafbfd}`)。表单区块标题按模式切换:`createBaseSectionTitle` computed → 发起「合同基础信息」/ 变更「**变更合同基础信息**」/ 续签「**续签合同基础信息**」。**所属机构/所属部门/岗位名称**改为 `:disabled="isCreateMode"`(发起时禁用自动带出,变更/续签时可编辑,`v-model` 绑 name 字段),并补必填规则(`legalEntityName/departmentName/positionName`,prop 也改到 name 字段上)带星号;签订员工(非发起禁用)与合同编号(恒禁用)不动。变更模式 `changedValues` 补 `legalEntityId/legalEntityName/departmentId`。注意:编辑的是**名称文本**,id 仍是详情回填值 —— 若要真正换机构/部门/岗位,需接下拉(组织树/部门/岗位列表接口)后才能同步 id。 - 追加调整:按用户要求把 **签署方式 与 合同终止日期 换了位置**。当前发起合同弹框字段顺序(`.cm-create-grid` 2 列):签订员工 / 合同编号 / 合同类型 / 拟定薪资 / 所属机构 / 所属部门 / 岗位名称 / 合同起始日期 / **签署方式 / 合同终止日期** / 合同模版(线上)或 合同附件(线下,`is-span-2`)。注意这样「合同起始日期/合同终止日期」不再相邻(起始在上一行右格)。 - 以上两次换位都是纯 DOM 顺序调整,不影响校验(`el-form-item` 规则按 `prop` 匹配)与提交参数。 - 追加调整:**岗位 id 与名称都要提交给接口**。`createForm` 原来只有 `positionName`,`buildCreatePayload` 的 `positionId`/`positionName` 取的是 `emp`(选中员工对象)而不是表单,导致表单里的岗位信息不参与提交。已补齐: - `blankCreateForm()` 加 `positionId: null`; - `applyEmployeeToForm` / `onEmployeeChange` 都写 `createForm.positionId = employee.positionId || null`(`adaptEmployee` 里 `positionId` 来自 `pickId(orgEmp.positionId, user.postId, user.positionId, seed.positionId)`,返回字符串,雪花 ID 不丢精度); - `fillCreateFormFromContract`(变更/续签)补 `positionId: detail.positionId`,并把 `positionId` 一起塞进 `adaptEmployee(detail, seed)`,员工信息回来时补空; - `buildCreatePayload` 改为 `positionId: form.positionId || emp.positionId`、`positionName: form.positionName || emp.position`(**表单优先**,兼容 create/renew,续签是 `{ contract: {...payload} }` 直接展开); - 变更模式的 `changedValues` 也补上 `positionId`(与 `positionName` 成对)。 ## 合同管理:合同详情弹框 UI 优化 + 附件改公共组件 - **重要发现(踩坑记录)**:`ele-modal` 的 `append-to-body` 是**真实 prop**(组件内 `dialogAppendToBody` → 传给内部 `el-dialog` 的 `append-to-body`),所以弹框内容被挪到 `` 下、**脱离了页面的 `.cm-page` 容器**。结果:`src/styles/views/contractManagement/index.scss` 里所有 `.cm-page .xxx` 规则在弹框内全部失效 —— 详情弹框的「合同状态」胶囊(`.cm-page .cm-status`)之前是裸文本、`.cm-page .cm-link` 按钮也没样式。已在 `.cm-status` 变体上补 `.cm-detail-page` 前缀,并给 `.cm-detail-page,.cm-detail-page *` 补 `box-sizing:border-box`(`.cm-page,.cm-page *` 同理够不到)。 - 结论:**这个页面里凡是弹框内的样式,选择器前缀不能只写 `.cm-page`**;新增弹框样式要么带上弹框自己的根类,要么和 `.cm-page` 一起并列。 - 详情弹框新增根类 `cm-detail-page`(与 `cm-create-page` 并存),样式收口在 scss 末尾:正文 10/11/12px → 13/14px(标签 13px、值 14px、岗位职责 14px/1.9、备注 13px、页脚 13px),区块 padding 与 grid gap 放大(`gap:16px 26px`),头像 48px、姓名 16px,状态胶囊 13px;把弹框内所有内联 `style` 换成类名(`.cm-detail-employee / .cm-employee-no / .cm-detail-no / .cm-detail-status / .cm-detail-duty / .cm-detail-remark / .cm-detail-muted`)。 - 去掉「签署文件」行;「合同附件」改用**公共组件** ``(与同一页发起合同弹框线下模式的用法一致;view 模式是「查看」按钮+数量角标,单个附件直接预览、多个弹文件列表,逻辑在 `src/components/addDoc/main.vue` 的 `open()`)。无附件时显示「暂无附件」,避免点开空列表弹框。 - `adaptContract` 清理:删掉 `signDoc` / `attachment`(原来只取单个文件)与只服务它们的 `pickFile` / `adaptFile`,改为 `attachmentFileIds`(新增 `collectAttachmentFileIds()`:优先 `filePurpose` ∈ ATTACHMENT/SCAN/ARCHIVE,缺用途标记时回退全部文件)。同时删掉 `viewFile` 方法及其唯一依赖 `import { downloadFile } from '@/api/file'`。 - 回归:lint 干净;`npx sass` 编译 scss 通过;`vue-template-compiler` 编译模板 0 error;babel 解析脚本 OK。 - 追加调整:去掉**发起/变更/续签共用弹框**里的 `.cm-create-header`(h2 `{{ createDialogTitle }}` + 副标题 `{{ createDialogHint }}`)—— 弹框自带的标题栏已显示「发起合同/变更合同/续签合同」,正文再写一遍是重复;`createDialogHint` computed 随之删除(`createDialogTitle` 仍在 `:title` 上用)。`.cm-create-header` 基础样式保留(解除/终止弹框还在用,现在全页只剩那一处)。**注意**:该弹框正文现在第一块就是对比区(变更/续签)或「合同基础信息」(发起)。 - 追加调整(按用户要求推广到所有合同弹框):把 `.cm-compare-item` 的「一行显示」改成 **基础样式**,不再只在 `.cm-detail-page` 下覆盖 —— 现在详情弹框的 3 个区块、变更/续签弹框的两张对比卡片(`.cm-compare-list`)全部统一为「标签:值」同一行(`flex-direction:row;align-items:baseline;gap:0`,label `flex:none`,value `min-width:0;word-break:break-word`)。同时把 label/value 字号从 10px/12px 统一到 **13px/14px**、`.cm-compare-list` 行 gap 8→10px、`.cm-change-employee` 12→13px,并把 2 处 `style="grid-column: span 2;"` 换成 `.cm-compare-item.is-span-2` 类。 - 合同弹框里 label/value 展示点只有两类:详情弹框与变更/续签的对比卡片(都用 `.cm-compare-item`);解除/终止弹框是表单输入项(`el-form-item`),不属于 label/value 展示,未动。`.cm-change-employee` / `.cm-list-container` 这些类在模板里已无使用(历史遗留 CSS)。 - 追加调整:详情弹框里 `.cm-compare-item` 由「label 在上、值在下」改为**同一行**(`.cm-detail-page .cm-compare-item{flex-direction:row;align-items:baseline;gap:0}`,label `flex:none`、value `min-width:0;word-break:break-word` 防溢出)。注意 `.cm-compare-item` 的基础样式是 `flex-direction:column`,**变更/续签弹框的对比卡片(`.cm-compare-list`)仍保持上下两行**,只有详情弹框改成一行。 - 追加调整:把**直属上级**从「合同期限与薪资」挪到「合同基本信息」末尾(跟 岗位名称/岗位层级 归在一起)。 - 追加调整:去掉详情弹框「签署信息」里的**审核人**行,`adaptContract` 的 `reviewer` 一并删除。详情弹框最终字段:合同基本信息(合同编号/合同类型/所属机构/所属部门/岗位名称/岗位层级) → 合同期限与薪资(合同起始/合同终止/月薪资/试用期至/直属上级) → 签署信息(签署方式/合同附件/签订日期/创建时间,有备注时追加备注)。 - 追加调整:去掉详情弹框整个**「岗位职责与工作信息」区块**(标题 + `暂无岗位职责说明`),`adaptContract` 的 `positionDuty` 与样式 `.cm-detail-page .cm-detail-duty` 一并删除。详情弹框最终只剩 3 个区块:合同基本信息 / 合同期限与薪资 / 签署信息。 - 追加调整:去掉详情弹框员工信息卡里的**副信息行**(`所属机构: · 合同编号: · 合同类型:` 那行 ``),卡片现在只剩 头像 + 「员工姓名 / 员工工号」+ 右侧合同状态。同时删掉随之失效的 `.cm-detail-no`、`.cm-create-employee-card .cm-employee-info small`(基础规则与 `.cm-detail-page` 覆盖规则)—— 注意 `所属机构/合同编号/合同类型` 在下面「合同基本信息」区块里仍然有完整展示,去重后不丢信息。 - 追加调整:去掉详情弹框「合同期限与薪资」里的**工作地点**行(该区块现为 合同起始/合同终止/月薪资/试用期至/直属上级),并同步删掉 `adaptContract` 里随之失效的 `workLocation: item.workLocation || ''`(`buildCreatePayload` 里的 `workLocation: extra.workLocation` 是提交侧字段,保留)。 - 追加调整:按用户截图要求**去掉详情弹框内的标题块**(`.cm-create-header`:h2「合同详情」+ 副标题「查看合同的完整信息…」)—— 弹框自带的标题栏已经有「合同详情」,正文再写一遍是重复。员工信息卡变成弹框正文第一个元素(`.cm-detail-employee{margin:16px 26px 0}` 提供顶部留白),同时删掉随之失效的 3 条 `.cm-detail-page .cm-create-header*` 样式(`.cm-create-header` 本身发起合同弹框仍在用,基础样式保留)。 ## 方法沉淀(抓接口文档的完整姿势) - `Invoke-WebRequest /swagger-resources` → 取分组 → `Invoke-WebRequest "/v2/api-docs?group=hr" -OutFile`(约 1MB,**不能**用 web_fetch,会截断)。 - **不要在 PowerShell 里用 `node -e` 写含 `$ref` 的 JS**(`$ref` 会被 PS 当变量插值掉,且 JSON 是单行 1MB,Select-String 只能返回整行无用)。正确做法:用 `write_to_file` 写一个 `tmp/xxx.js` 脚本再 `node xxx.js`。 - 解析 `$ref` 递归展开时要给足深度(我用 depth>14),否则嵌套的 `dimensions[].items[]` 会直接返回 `{}`,误以为接口没返回字段。