From 380d8e6e320b3ac3c8ac3decfc00eaa4f0f901b9 Mon Sep 17 00:00:00 2001 From: key Date: Thu, 30 Jul 2026 00:14:42 +0800 Subject: [PATCH] fix(mobile): converge work report confirmation --- CHANGELOG.md | 2 +- docs/MOBILE_APP_DEVICE_ACCEPTANCE.md | 2 +- ...1日Android安装包测试版任务计划-20260727.md | 2 +- mobile-uni/src/pages/user/report/index.vue | 6 +++-- mobile-uni/src/services/work-report.ts | 23 +++++++++++++++++ mobile-uni/tests/work-report.test.mjs | 25 ++++++++++++++++++- 6 files changed, 54 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 48a01bbe..a49d24fd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ ## Unreleased -- **2026-07-29,Android 文件选择、麦克风拒绝降级与上传路径修复,源码候选升级为 `0.1.16 (116)`,尚无签名 APK**:真机确认 `0.1.13` 在允许麦克风时可完成原生录音和生产 ASR,但拒绝后的降级误用 App 不支持的 `uni.chooseFile`。Android 非媒体文件现改用系统 `ACTION_OPEN_DOCUMENT`,通过 `ParcelFileDescriptor + FileChannel.transferFrom` 复制到应用专属外部缓存,避免 Native.js `byte[]` 参数兼容问题;图片/视频改走 App `uni.chooseMedia`,页面生命周期会保留系统选择器中的待完成任务,录音前统一检查麦克风权限。vivo Android 15 真机在 HBuilderX 5.22 标准调试基座完成取消、重复打开、`100,542` 字节 WAV 精确复制、multipart 上传、生产 ASR 和成果草稿整理,未点击确认投稿。移动端 206 项测试、类型检查、App 发布资源预检和 App-Plus 构建通过;调查用 `0.1.14/0.1.15` APK 已删除。DCloud 当日云打包额度耗尽,最终 `0.1.16` 签名 APK、深检和同包真机复验仍未完成,因此当前仍为 No-Go,未发布移动 H5、原生 App、后端、管理端或数据库。 +- **2026-07-29,Android 文件选择、麦克风拒绝降级与上传路径修复,源码候选升级为 `0.1.16 (116)`,尚无签名 APK**:真机确认 `0.1.13` 在允许麦克风时可完成原生录音和生产 ASR,但拒绝后的降级误用 App 不支持的 `uni.chooseFile`。Android 非媒体文件现改用系统 `ACTION_OPEN_DOCUMENT`,通过 `ParcelFileDescriptor + FileChannel.transferFrom` 复制到应用专属外部缓存,避免 Native.js `byte[]` 参数兼容问题;图片/视频改走 App `uni.chooseMedia`,页面生命周期会保留系统选择器中的待完成任务,录音前统一检查麦克风权限。vivo Android 15 真机在 HBuilderX 5.22 标准调试基座完成取消、重复打开、`100,542` 字节 WAV 精确复制、multipart 上传和生产 ASR。首次成果投稿验证实际只进入追问态,当时并未出现“确认投稿”,此前“未点击确认投稿”的表述已纠正;继续补充时线上整理请求耗时 `94,965ms`,超过客户端 `45,000ms` 门限。当前源码改为仅首轮调用 AI,后续原话并入当前草稿后交由既有服务端本地复核,真机复验后续整理耗时 `1ms`,确认卡及“确认投稿”按钮均已显示但未点击,未形成正式投稿。移动端 206 项测试、类型检查、App 发布资源预检和 App-Plus 构建通过;调查用 `0.1.14/0.1.15` APK 已删除。DCloud 当日云打包额度耗尽,最终 `0.1.16` 签名 APK、深检和同包真机复验仍未完成,因此当前仍为 No-Go,未发布移动 H5、原生 App、后端、管理端或数据库。 - **2026-07-29,资料处理中心普通文档/图片单文件 500MB 已发布并完成生产验证**:管理端批量导入、后端异步队列、ZIP 子文件和 Tika 输入流统一允许受支持文件 `≤500MiB`,沿用生产 Spring/Undertow `500MB/520MB` 传输层配置;仍保留 ZIP 最多 1000 个子文件、解压总量 ≤2GB、禁止嵌套 ZIP 等安全边界,同步 SOP 上传和公司文件继续保持 100MB。生产实测发现首个候选虽已放宽页面和队列校验,但 Tika 内仍残留独立 `100MiB` 输入流上限,导致首批队列 `1194/1195` 上传成功后加工失败;补齐解析器上限及回归断言后,11 项定向后端测试和后端生产打包通过。管理端发布前备份整站到 `/opt/wygj/backups/www-20260729213203`,线上 `processing-BjvQ3qpr.js` 与本地产物 SHA-256 均为 `6ace59477519d6c53cbfe207a229518a7e2f7568e05961cfea28d669e1eeb305`,移动 H5 树哈希保持 `ba4aa656d64fc301141280357c204a67141d1e6b2e2a26a0be21c0dd48a25712` 未变。最终后端 JAR SHA-256 为 `be16c229cfc255e4cdf5ec32df047994b54d7d2c31b6c729dde6d949e191e53d`,旧包备份在 `/opt/wygj/backups/backend-20260729214901-5ef5dd484b5d`;整包及内嵌 `ruoyi-aihr-5.6.2.jar` 远近端哈希一致,服务、公开接口、64/64 schema 和生产禁运行时 DDL 预检均通过。已登录生产 Chrome 验收批次 `b-c2560e95-0cf7-46e0-9bdc-7690ea86ffd7` 中,`4、巡检表(事业部颗粒归仓专用).xlsx`(101.6MB,队列 `1196`、附件 `2036`)完成 5 个片段和 5 个向量,`新员工培训-社区发展中心-社区资源.pptx`(147.9MB,队列 `1197`、附件 `2037`)完成 7 个片段和 7 个向量;两项均显示“已完成/已入库”,数据库片段均含 embedding。资料总数从 1032 增至 1034,目标空间从 22 增至 24 个文件,处理中和需处理均为 0。本次只定向发布后端与管理端,未重发移动 H5、未执行 schema 迁移,不代表当前工作树与线上完整包全部匹配。 - **2026-07-29,资料处理媒体任务恢复已发布并完成生产验证**:修复历史图片/视频在视觉输入不兼容或未提取到文字时长期停在“等待解析”的问题;图片在送入视觉模型前会按方向纠正、限制像素和体积并统一编码,异步队列只有生成至少一个片段才进入完成态,零片段会保留为可重试失败。新增 `POST /api/knowledge/doc/processing-tasks/{attachmentId}/retry`,可从当前租户原 OSS 对象重新暂存并入队;正式迁移 `aihr_20260729_media_reprocess_mysql8.sql` 增加 `source_attach_id` 和 `(tenant_id, source_attach_id, id)` 索引。生产已备份 schema、后端和完整站点,发布管理端与后端 JAR `5376b67b33b171b5c85e22cd6a2f20bbb0bc527fc50a223603d0aec3f6587bac`,移动 H5 未重发;发布脚本在重启后等待公开端点真实就绪再执行预检,服务为 `active`,根站、租户和移动首页均返回 HTTP 200。原卡住任务 `1133/1175/1308/1405` 从原文件重试为队列 `1185–1188`,四项均完成、各生成一个片段且向量存在;资料处理页显示“待重试 0”、Tika `1028/1028`、可引用资料 `1028`。该证据证明本次资料恢复和定向发布成功,不代表移动 H5 与当前工作树完整匹配,也不替代 Android 真机或正式试点验收。 - **2026-07-29,新增移动端 UI token 与渲染态审计,未发布静态资源**:`audit:ui-tokens` 以 `mobile-uni/src/styles/base.css` 为源扫描颜色、字阶、间距和圆角,`audit:ui-rendered` 在 390×844 的真实 Chrome 中审计 H5 构建的六个默认路由并保存截图。两项均把报告写入 Git 已忽略的 `output/ui-ux/`;本地实跑覆盖 53 个源码文件和 6 个渲染路由,渲染路由均无横向溢出或控制台错误,同时如实记录现有 palette 外颜色、孤立字号和非 token 圆角,不能把“脚本执行成功”解释为视觉债务已清零。 diff --git a/docs/MOBILE_APP_DEVICE_ACCEPTANCE.md b/docs/MOBILE_APP_DEVICE_ACCEPTANCE.md index 05aa1f1b..f1371c45 100644 --- a/docs/MOBILE_APP_DEVICE_ACCEPTANCE.md +++ b/docs/MOBILE_APP_DEVICE_ACCEPTANCE.md @@ -54,7 +54,7 @@ Android 每个人工步骤完成后,可在不改变设备状态的前提下保 同一真机随后使用生产现有在职 APP 主体和固定码完成页面登录;账号、固定码和令牌未进入日志、截图或仓库。`0.1.13` 允许麦克风后,原生按住录音、上传和生产 ASR 已跑通,识别结果进入真实“问”主路径。拒绝麦克风后的文件降级同时暴露两个 App-Plus 缺陷:`uni.chooseFile` 在 App 不受支持,且 Native.js 无法可靠构造并传递 Java `byte[]`。调查包 `0.1.14/0.1.15` 仅用于定位,生成的 APK 已删除,不得分发。 -当前源码候选已升级为 `0.1.16 (116)`:Android 非媒体文件通过系统 `ACTION_OPEN_DOCUMENT` 选择,以 `ParcelFileDescriptor + FileChannel.transferFrom` 复制到应用专属外部缓存;页面生命周期会区分系统选择器暂时离开和真实离页,图片/视频改走 App 支持的 `uni.chooseMedia`,麦克风拒绝在启动录音前统一进入降级。vivo 真机使用 HBuilderX 5.22 标准调试基座完成选择器打开、取消、重复打开及选择 `100,542` 字节 WAV;复制后字节数一致,并继续完成 multipart 上传、生产 ASR 和成果投稿草稿整理,识别测试语句后只生成追问草稿,未点击“确认投稿”、未形成正式投稿。当前 206/206 项移动端测试、类型检查、App 发布资源预检和 App-Plus 构建通过。 +当前源码候选已升级为 `0.1.16 (116)`:Android 非媒体文件通过系统 `ACTION_OPEN_DOCUMENT` 选择,以 `ParcelFileDescriptor + FileChannel.transferFrom` 复制到应用专属外部缓存;页面生命周期会区分系统选择器暂时离开和真实离页,图片/视频改走 App 支持的 `uni.chooseMedia`,麦克风拒绝在启动录音前统一进入降级。vivo 真机使用 HBuilderX 5.22 标准调试基座完成选择器打开、取消、重复打开及选择 `100,542` 字节 WAV;复制后字节数一致,并继续完成 multipart 上传和生产 ASR。首次成果投稿验证只生成追问草稿,实际尚未显示“确认投稿”,不是已经出现按钮但未点击。复查又确认每轮补充重新调用模型会继续生成追问,且一次线上整理耗时 `94,965ms`,超过客户端 `45,000ms` 超时门限。当前源码改为首轮 AI 整理、后续原话并入当前草稿后交由既有服务端本地复核;同一真机复验后续整理耗时 `1ms`,已显示成果投稿确认卡和“确认投稿”按钮,但没有点击、没有形成正式投稿。当前 206/206 项移动端测试、类型检查、App 发布资源预检和 App-Plus 构建通过。 由于 DCloud 当日免费云打包次数已用尽,`0.1.16` 的最终签名 APK 尚未生成,标准调试基座证据不能冒充签名包验收。最后一个可重复校验的签名包仍是 `0.1.13 (113)`,但它不包含上述系统选择器修复;额度恢复或购买次数后必须重新云打包、深检并在同一真机复跑麦克风拒绝、选择器取消/选择和 ASR。当前结论因此仍为 No-Go。 diff --git a/docs/帮道8月1日Android安装包测试版任务计划-20260727.md b/docs/帮道8月1日Android安装包测试版任务计划-20260727.md index 184cf578..0eb928a3 100644 --- a/docs/帮道8月1日Android安装包测试版任务计划-20260727.md +++ b/docs/帮道8月1日Android安装包测试版任务计划-20260727.md @@ -113,7 +113,7 @@ 同日后续已获得独立授权并通过 `release-backend.sh` 完成生产后端定向发布;生产 JAR SHA-256 为 `5376b67b33b171b5c85e22cd6a2f20bbb0bc527fc50a223603d0aec3f6587bac`,服务、三个公开入口和后端 + schema 发布后预检通过,后端发布不再是 8 月 1 日 APK 的阻断项。同批发布资料处理媒体恢复 schema 和管理端,移动 H5 未重发;这不改变正式场景/题目、五通道处理人、受控分发与真机设备门禁的 No-Go 状态。 -同日真机继续确认 `0.1.13` 的麦克风允许和生产 ASR 可用,但拒绝后的降级误用 App 不支持的 `uni.chooseFile`。当前源码已升级为 `0.1.16 (116)`,改用 Android 系统文档选择器、`ParcelFileDescriptor + FileChannel.transferFrom`、应用专属外部缓存和 App 媒体选择器,并保留系统选择器暂时离开时的待完成任务。vivo 真机标准调试基座完成取消、重复打开、`100,542` 字节 WAV 选择/复制/上传/生产 ASR 和无提交草稿整理;206 项测试、类型检查、App 发布资源预检和 App-Plus 构建通过。调查用 `0.1.14/0.1.15` APK 已删除,DCloud 当日云打包额度耗尽,`0.1.16` 签名 APK 尚不存在,不能进入分发。 +同日真机继续确认 `0.1.13` 的麦克风允许和生产 ASR 可用,但拒绝后的降级误用 App 不支持的 `uni.chooseFile`。当前源码已升级为 `0.1.16 (116)`,改用 Android 系统文档选择器、`ParcelFileDescriptor + FileChannel.transferFrom`、应用专属外部缓存和 App 媒体选择器,并保留系统选择器暂时离开时的待完成任务。vivo 真机标准调试基座完成取消、重复打开、`100,542` 字节 WAV 选择/复制/上传和生产 ASR。首次成果投稿验证只到追问态,没有出现“确认投稿”;继续补充又暴露整理请求 `94,965ms`、超过客户端 `45,000ms` 的问题。当前源码已改为仅首轮调用 AI,后续补充走服务端本地合并复核,同机复验耗时 `1ms` 并显示确认卡和“确认投稿”按钮,但未点击、未形成正式投稿。206 项测试、类型检查、App 发布资源预检和 App-Plus 构建通过。调查用 `0.1.14/0.1.15` APK 已删除,DCloud 当日云打包额度耗尽,`0.1.16` 签名 APK 尚不存在,不能进入分发。 生活顾问调研已转成 `docs/content-candidates/aug1-life-advisor-content-candidates-v0.1.json`:包含 5 个禁用场景候选和 30 道待审政策题,内容候选校验脚本通过。由于当前只收到 1/24 份访谈,且正式来源、业务审核和 Rubric 签认尚未完成,正式可发布场景与正式有据标准答案均为 `0`;交付包中的候选文件只供审核,不得直接导入或启用。 diff --git a/mobile-uni/src/pages/user/report/index.vue b/mobile-uni/src/pages/user/report/index.vue index 5db9b9a9..963a7d2a 100644 --- a/mobile-uni/src/pages/user/report/index.vue +++ b/mobile-uni/src/pages/user/report/index.vue @@ -251,6 +251,7 @@ import { isCurrentWorkReportRequest, isDefinitiveWorkReportRejection, organizeWorkReport, + prepareWorkReportOrganizeInput, stableWorkReportRequestId, submitWorkReport, uploadWorkReportAttachment, @@ -430,6 +431,7 @@ const organizeInput = async ( const transcript = text.trim(); if (!transcript || organizing.value || draftLocked.value || submittedReport.value) return; const previousDraft = draftPayload(); + const organizeRequest = prepareWorkReportOrganizeInput(transcript, previousDraft); pushMessage({ role: 'user', text: transcript, @@ -443,8 +445,8 @@ const organizeInput = async ( speechPlayback.stop(); try { const result = await organizeWorkReport({ - transcript, - currentDraft: previousDraft, + transcript: organizeRequest.transcript, + currentDraft: organizeRequest.currentDraft, attachmentOssId: attachment.value?.ossId, attachmentName: attachment.value?.fileName }); diff --git a/mobile-uni/src/services/work-report.ts b/mobile-uni/src/services/work-report.ts index e408c049..93422bfa 100644 --- a/mobile-uni/src/services/work-report.ts +++ b/mobile-uni/src/services/work-report.ts @@ -52,6 +52,29 @@ export const isCurrentWorkReportRequest = ( currentAccountKey: string ) => generation === currentGeneration && accountKey === currentAccountKey; +export const prepareWorkReportOrganizeInput = ( + transcript: string, + currentDraft?: WorkReportDraft +): { transcript: string; currentDraft?: WorkReportDraft } => { + const addition = transcript.trim(); + if (!currentDraft) return { transcript: addition }; + + const currentContent = currentDraft.content.trim(); + const content = !currentContent + ? addition + : !addition || currentContent === addition + ? currentContent + : `${currentContent}\n${addition}`; + + return { + transcript: '', + currentDraft: { + ...currentDraft, + content + } + }; +}; + export const workReportTypes: Array<{ value: WorkReportType; label: string; desc: string; icon: string }> = [ { value: 'CASE', label: '优秀案例', desc: '现场处理得好的真实案例', icon: '⭐' }, { value: 'VIDEO', label: '操作视频', desc: '值得推广的操作演示视频', icon: '🎬' }, diff --git a/mobile-uni/tests/work-report.test.mjs b/mobile-uni/tests/work-report.test.mjs index bf888048..19e807aa 100644 --- a/mobile-uni/tests/work-report.test.mjs +++ b/mobile-uni/tests/work-report.test.mjs @@ -36,7 +36,8 @@ test('语音转写、文字和附件补充都进入无状态整理', async () => assert.match(page, /transcribeSpeechFile/); assert.match(page, /organizeWorkReport\(/); assert.match(page, /const previousDraft = draftPayload\(\)/); - assert.match(page, /currentDraft:\s*previousDraft/); + assert.match(page, /prepareWorkReportOrganizeInput\(transcript, previousDraft\)/); + assert.match(page, /currentDraft:\s*organizeRequest\.currentDraft/); assert.match(page, /attachmentOssId:\s*attachment\.value\?\.ossId/); assert.match(page, /void organizeInput\(text, \{ voice: false \}\)/); assert.match(page, /void addMediaAttachment\(\)/); @@ -171,4 +172,26 @@ test('幂等键与异步账号守卫是可执行行为,不只是源码字符 assert.equal(state.isCurrentWorkReportRequest(3, 3, 'account-a', 'account-a'), true); assert.equal(state.isCurrentWorkReportRequest(2, 3, 'account-a', 'account-a'), false); assert.equal(state.isCurrentWorkReportRequest(3, 3, 'account-a', 'account-b'), false); + + const firstTurn = state.prepareWorkReportOrganizeInput(' 首轮原话 '); + assert.deepEqual(firstTurn, { transcript: '首轮原话' }); + + const existingDraft = { + suggestedType: 'CASE', + title: '真机验收', + content: '已完成文件选择和 ASR' + }; + const followUp = state.prepareWorkReportOrganizeInput(' 补充设备和结果 ', existingDraft); + assert.deepEqual(followUp, { + transcript: '', + currentDraft: { + suggestedType: 'CASE', + title: '真机验收', + content: '已完成文件选择和 ASR\n补充设备和结果' + } + }); + assert.equal(existingDraft.content, '已完成文件选择和 ASR'); + + const duplicate = state.prepareWorkReportOrganizeInput(' 已完成文件选择和 ASR ', existingDraft); + assert.equal(duplicate.currentDraft.content, '已完成文件选择和 ASR'); });