diff --git a/CHANGELOG.md b/CHANGELOG.md index 7bcfa922..997b8e11 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,27 @@ - 暂无。 +## 2026-07-25 15:40–16:15|组织姓名与考试发布修复 + +### 生产数据修复 + +- **2026-07-25 15:40**:在 `/opt/wygj/backups/org-name-sync-20260725-1540/org-data.sql` 备份生产组织数据;备份大小 `711248` 字节,SHA-256 为 `9e298bf5e6ac88b6d0e105f510cad3fb9838ed1cf0324a23ee15453a54e3c9dd`。 +- **2026-07-25 15:40–15:48**:刷新开放组织目录并将租户 `000000` 绑定到集团根节点 `G:1`。仅按稳定 `employee_id` 精确匹配,补齐 `aihr_org_snapshot.person_name` 共 `3417` 行;未改手机号、项目、岗位和在职状态。 +- **2026-07-25 15:48**:生产复核为组织快照 `3417` 行、在职 `2938` 人、在职有姓名 `2938` 人、手机号映射 `3392` 个。使用既有生产主管账号调用团队接口返回 `2316` 名成员,姓名完整 `2316` 名、编号兜底 `0` 名;账号、手机号、验证码和 Token 未写入仓库、截图或本记录。 +- **2026-07-25 15:48**:未执行全量组织覆盖。只读预检发现上游 `3424` 名员工中 `3398` 个手机号已脱敏、存在 `1` 条重复项目成员关系,并且当前 `employee_number` 仅能匹配生产既有主体 `7/3417`,稳定 `employee_id` 可匹配 `3417/3417`。在身份字段契约和手机号通道修复前,禁止用 `allowPartialReplace=true` 绕过后执行覆盖同步。 + +### 考试发布修复 + +- **2026-07-25 15:55**:移除客户端“考试名称至少 4 个字”的无依据限制,改为仅要求名称非空;同时保留及格分 `1–100`、至少一道题、总分 `100`、题干/选项/答案完整和至少选择一名员工的发布校验。备份当时生产 H5 到 `/opt/wygj/backups/h5-exam-title-20260725-1555/` 后完成首次静态发布。 +- **2026-07-25 16:09**:复核发现 `16:01` 的后续 H5 发布重新带入旧 `length>=4` 校验;该次覆盖被识别为生产回退,未将 `15:55` 的短暂上线状态作为最终完成。 +- **2026-07-25 16:12**:基于当前主线 `75469b85` 重新构建 H5,保留已发布的陪练“建议回复”功能并叠加考试修复;发布前备份到 `/opt/wygj/backups/h5-exam-restore-20260725-1612/`。生产入口更新为 `/h5/assets/index-QQ2kC1xm.js`,考试页资源为 `/h5/assets/pages-supervisor-exams-edit.B-v9k7L-.js`。 + +### 验证与边界 + +- **2026-07-25 16:12**:目标回归 `5/5`、`vue-tsc --noEmit` 和 `npm --prefix mobile-uni run build:h5` 通过;生产考试资源包含“请填写考试名称”和“发布前请至少选择 1 名员工”,不再包含旧 `length>=4` 校验。 +- **2026-07-25 16:14**:在生产 H5 `390×844` 视口使用隔离浏览器会话和两名模拟员工完成无写入验收;考试名“测试”可通过名称校验,员工姓名正常显示,“发布给 2 人”按钮启用,未调用保存或发布接口。 +- **2026-07-25 16:15**:本次结论为:姓名数据修复已生产验证,考试校验已实现、已本地验证、已重新部署并完成生产 H5 浏览器验证;未执行后端 JAR 发布、服务重启、schema 迁移或全量组织覆盖。对应两处考试源码改动仍在工作区,尚未提交。 + ## 2026-07-25 15:44–16:05|陪练建议回复 ### 功能更新 diff --git a/README.md b/README.md index ffcf91ee..6c0042d9 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ - 用户侧:`mobile-uni/` 承载员工、候选人和主管三端;“问”已改为数字师傅 Agent,文字、ASR 转写和图片/视频统一先做意图规划,再按服务端权限调用知识、训练、待办、记忆、媒体或全网工具,寒暄和不完整表达不再机械落入 RAG。现有能力还包括手机号登录、30 分钟短期多轮、原文件/视频受控交付、大喇叭公司文件摘要/下载/多岗位解读/追问、五渠道直通反馈、“记一下”确认卡、三级字号、训练/题目反馈、问题榜与积分、岗位考试、案例、候选面试/资料、主管复盘/派发及学员模式。Agent 代码已随 2026-07-25 完整包部署,但 Agent 专项认证态业务回归和真机验收尚未完成;旧 `mobile/` 只用于历史回溯。 - 英雄路径:AI 面试(真模型出题/评分)、三角色对练(真 LLM 客户回复+评分,asr/tts 配置后支持语音)、案例沉淀(真实语音上传+ASR 转写+模型整理)均为真模型优先、未配置或失败回退本地兜底;SOP 知识库已接中文关键词 + MySQL Fulltext + Qdrant 混合召回 + rerank 重排,支持 txt/md/PDF/Word/Excel/PPT/图片/视频上传解析入库(图片走 vision OCR,视频走 ffmpeg 抽音轨转写+关键帧描述),资料处理中心支持 ZIP 异步展开后逐文件入队;模型配置页可查看向量库维度/点数并一键重建。 - 资料处理:已新增解析任务状态页,按 `aihr_knowledge_attach` 和 `aihr_knowledge_fragment` 展示等待解析、解析中、已完成、失败、片段数和向量化状态;任务列表按当前筛选结果前端分页(10/20/50 条,筛选或刷新回到第一页)。页面只保留“批量导入”,通过异步队列处理多文件或 ZIP,不提供浏览器目录选择和服务端目录导入入口。后端目录导入接口仅保留为运维/调试能力。 -- 组织人员:`aihr_org_snapshot` 本地 reset 保留演示 seed;生产通过 `POST /api/aihr/org/sync` 同步外部组织。2026-07-15 记录的生产快照为 3417 人、2938 人在职、3392 个手机号映射、175 名在职主管和 20 名项目经理;人数属于时点事实,发布或验收前应重新查询。 +- 组织人员:`aihr_org_snapshot` 本地 reset 保留演示 seed;生产通过 `POST /api/aihr/org/sync` 同步外部组织。2026-07-25 记录的生产快照为 3417 人、2938 人在职且均有姓名、3392 个手机号映射、175 名在职主管和 20 名项目经理;姓名通过稳定 `employee_id` 定向补齐,未执行全量覆盖。当前上游 `employee_number` 与既有主体不一致且手机号大量脱敏,修复契约前禁止强制全量同步;人数属于时点事实,发布或验收前应重新查询。 - 正式试点:服务端提供按起止日期导出的受角色保护 CSV;当前管理端不展示个人训练或试点导出入口,正式试点需在明确独立后台契约后再开放。严格门禁只统计唯一映射到在职组织的训练、校准和 SOP 评审。组织身份同步和至少 20 名手机号映射门槛已具备;下一步仍需选定真实试点窗口,产生员工训练、人工校准和 SOP 评审样本后重跑严格门禁,详见 [演示与试点验收](docs/DEMO_ACCEPTANCE.md)。 - 侧栏:只展示当前 AIHR 业务菜单;过滤名单在 `frontend/src/layout/components/Sidebar/index.vue`,不要恢复若依默认全量菜单。 - 本地认证:真实阿里云短信参数放根目录 `.env.local`,由 `scripts/dev-backend.sh` 自动加载;不要把密钥写入 `application-dev.yml`。dev 未配短信时短信验证码固定 `123456`;管理端图形验证码默认关闭,需要时设置 `AIHR_DEV_CAPTCHA_ENABLED=true`。生产不读取该图形验证码开发开关,短信固定码也保持默认关闭,只有试点明确同时开启两项生产固定码配置时才生效。 diff --git a/mobile-uni/src/pages/supervisor/exams/edit.vue b/mobile-uni/src/pages/supervisor/exams/edit.vue index 397fbbdc..676c90d7 100644 --- a/mobile-uni/src/pages/supervisor/exams/edit.vue +++ b/mobile-uni/src/pages/supervisor/exams/edit.vue @@ -122,6 +122,7 @@ + {{ publishValidationMessage }} @@ -161,14 +162,20 @@ const dateAfter = (days: number) => { const date = new Date(); date.setHours(12, const today = dateAfter(0); const requestId = () => `exam-publish:${examId.value || 'new'}:${Date.now()}:${Math.random().toString(36).slice(2, 8)}`; const totalScore = computed(() => questions.value.reduce((sum, item) => sum + Number(item.score || 0), 0)); -const valid = computed(() => title.value.trim().length >= 4 - && passingScore.value > 0 && passingScore.value <= 100 - && totalScore.value === 100 - && questions.value.length > 0 - && questions.value.every((item, index) => item.stem.trim() - && Number(item.score) > 0 - && singleAnswerModels.value[index]?.trim() - && (item.format === 'short' || item.options.filter((option) => option.trim()).length >= 2))); +const validationMessage = computed(() => { + if (!title.value.trim()) return '请填写考试名称'; + if (passingScore.value < 1 || passingScore.value > 100) return '及格分必须在 1–100 分之间'; + if (!questions.value.length) return '请至少添加一道考试题'; + if (totalScore.value !== 100) return '试卷题目分值合计必须为 100 分'; + const incompleteIndex = questions.value.findIndex((item, index) => !item.stem.trim() + || Number(item.score) <= 0 + || !singleAnswerModels.value[index]?.trim() + || (item.format !== 'short' && item.options.filter((option) => option.trim()).length < 2)); + return incompleteIndex < 0 ? '' : `请补全第 ${incompleteIndex + 1} 题的题干、分值、选项和答案`; +}); +const valid = computed(() => !validationMessage.value); +const publishValidationMessage = computed(() => validationMessage.value + || (!selectedMemberIds.value.length ? '发布前请至少选择 1 名员工' : '')); const goBack = () => uni.navigateBack({ delta: 1, fail: () => uni.redirectTo({ url: '/pages/supervisor/exams/index' }) }); const selectPosition = (event: { detail: { value: string } }) => { positionIndex.value = Number(event.detail.value || 0); }; const selectDueDate = (event: { detail: { value: string } }) => { dueDate.value = event.detail.value; }; @@ -710,6 +717,15 @@ onLoad((options) => { examId.value = Number(options?.id || 0); void load(); }); background: rgba(255, 255, 255, 0.97); } +.publish-validation-message { + grid-column: 1 / -1; + color: #a86100; + font-size: 12px; + font-weight: 700; + line-height: 1.4; + text-align: center; +} + .dock-primary, .dock-secondary { display: flex; diff --git a/mobile-uni/tests/customer-feedback-closure.test.mjs b/mobile-uni/tests/customer-feedback-closure.test.mjs index bcbf8673..6148e22d 100644 --- a/mobile-uni/tests/customer-feedback-closure.test.mjs +++ b/mobile-uni/tests/customer-feedback-closure.test.mjs @@ -55,6 +55,9 @@ test('员工考试与主管组卷路由、题目播报、真实人员多选和 assert.match(edit, /selectedMemberIds\.value\.length < 100/); assert.match(edit, /members\.value\.slice\(0, 100\)/); assert.match(edit, /requestId/); + assert.doesNotMatch(edit, /title\.value\.trim\(\)\.length >= 4/); + assert.match(edit, /请填写考试名称/); + assert.match(edit, /publish-validation-message/); assert.match(service, /\/api\/aihr\/mobile\/exams\/supervisor/); });