From c24924fa43ea2610eac86a9e06fe6144087f1d4e Mon Sep 17 00:00:00 2001 From: xiangsilian Date: Sat, 15 Aug 2026 13:11:26 +0800 Subject: [PATCH] =?UTF-8?q?fix(app):=20=E7=9C=9F=E6=9C=BA=E9=A6=96?= =?UTF-8?q?=E9=AA=8C=E4=B8=89=E8=BF=9E=E4=BF=AE=E2=80=94=E2=80=94=E4=BF=A1?= =?UTF-8?q?=E5=B0=81=E8=A7=A3=E6=9E=90/DatePicker=20=E6=8A=98=E5=8F=A0/?= =?UTF-8?q?=E8=B7=B3=E8=BD=AC=E5=85=9C=E5=BA=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 真机+模拟器注入实测发现并修复: 1. ApiClient 把一切非 200 HTTP 吞成 50000「服务开小差了」——后端业务 错误(40920/40301/42910…)走非 200 状态码但 body 是统一信封,先验 信封形状再判成败。这是「提交不起作用」的直接原因(用户二次提交 =409,红字被布局挤在视野外) 2. 生日 DatePicker 默认展开高约半屏:九型必填项与提交按钮被推出视野、 滚动手势被选择器消费。改点击展开,表单一屏可见 3. 校验失败加顶部汇总红条(字段红字可能在视野外的可见性兜底) 4. 轮询完成不自动跳结果页:@Builder 传函数给子组件在真机调用无效 (手动按钮同一方法正常)——加「查看报告」兜底按钮 + replaceUrl 显式 catch + 完成态文案引导,回调保留 try/catch 与日志 验证:40920 对话框正常弹出;新用户提交→轮询→查看报告→32 维结果页 完整渲染(含降级黄条、报告编号) --- .../ets/components/f3/TaskPollingPanel.ets | 10 ++- app/entry/src/main/ets/core/ApiClient.ets | 19 +++-- .../src/main/ets/pages/ReportCreatePage.ets | 80 ++++++++++++++++--- 3 files changed, 91 insertions(+), 18 deletions(-) diff --git a/app/entry/src/main/ets/components/f3/TaskPollingPanel.ets b/app/entry/src/main/ets/components/f3/TaskPollingPanel.ets index 38c3d4d..de9d52a 100644 --- a/app/entry/src/main/ets/components/f3/TaskPollingPanel.ets +++ b/app/entry/src/main/ets/components/f3/TaskPollingPanel.ets @@ -81,7 +81,15 @@ export struct TaskPollingPanel { } if (t.status === 'SUCCEEDED') { this.stopTimer() - this.onSucceeded() + this.stageText = '已完成——点击下方「查看报告」查看结果' + // 回调经 @Builder 传函数在真机上存在调用无效的坑(手动按钮同一 + // 方法完全正常,真机实测轮询完成不自动跳转)。保留回调尝试, + // 失败静默并由宿主的「查看报告」兜底按钮承担导航。 + try { + this.onSucceeded() + } catch (e) { + console.error(`poll onSucceeded failed: ${JSON.stringify(e)}`) + } return } if (t.status === 'FAILED') { diff --git a/app/entry/src/main/ets/core/ApiClient.ets b/app/entry/src/main/ets/core/ApiClient.ets index e8a9ccd..2a0a8cf 100644 --- a/app/entry/src/main/ets/core/ApiClient.ets +++ b/app/entry/src/main/ets/core/ApiClient.ets @@ -76,15 +76,20 @@ export class ApiClient { connectTimeout: 10000, readTimeout: 30000 }) - if (resp.responseCode !== 200 || resp.result === null || resp.result === undefined) { - // 网关/代理层错误:没有信封可用,给统一话术(NFR-UX-02) - throw new ApiError(50000, '服务开小差了', '请稍后重试', resp.responseCode) - } + // 后端业务错误(40920/40301/42910…)走非 200 的 HTTP 状态码, + // 但 body 仍是统一信封——先验信封形状再判成败;只有 body 不是 + // 信封(网关 502 的 HTML、字符串等)才按网关错误兜底。 + // 真机实测教训:把非 200 一律吞成 50000,会吃掉全部业务错误分支 + // (40920 的"已生成过"对话框、40301 的额度提示全部失效)。 const env = resp.result as ApiEnvelope - if (env.code !== 0) { - throw new ApiError(env.code, env.message, env.hint, resp.responseCode) + if (typeof env === 'object' && env !== null && typeof env.code === 'number') { + if (env.code !== 0) { + throw new ApiError(env.code, env.message, env.hint, resp.responseCode) + } + return env.data } - return env.data + // 网关/代理层错误:没有信封可用,给统一话术(NFR-UX-02) + throw new ApiError(50000, '服务开小差了', '请稍后重试', resp.responseCode) } finally { client.destroy() } diff --git a/app/entry/src/main/ets/pages/ReportCreatePage.ets b/app/entry/src/main/ets/pages/ReportCreatePage.ets index 3817817..64f524a 100644 --- a/app/entry/src/main/ets/pages/ReportCreatePage.ets +++ b/app/entry/src/main/ets/pages/ReportCreatePage.ets @@ -41,6 +41,9 @@ struct ReportCreatePage { @State goalsErr: string = '' @State styleErr: string = '' @State submitting: boolean = false + @State showDatePicker: boolean = false + @State formSummary: string = '' + @State navError: string = '' @State submitError: string = '' @State failMsg: string = '' @State showDupDialog: boolean = false @@ -105,7 +108,13 @@ struct ReportCreatePage { if (this.submitting) { return } + this.formSummary = '' if (!this.validateForm()) { + // 字段级红字可能滚在视野外(真机实测的"点了没反应"就是这种情况): + // 顶部再放一条汇总,让失败永远可见。 + const errs: string[] = [this.genderErr, this.heightErr, this.weightErr, this.goalsErr, this.styleErr] + const n = errs.filter((e: string): boolean => e !== '').length + this.formSummary = `还有 ${n} 项未完成,请检查标红的字段(性别/身高/体重/核心诉求/心理风格)` return } this.doSubmit() @@ -176,7 +185,12 @@ struct ReportCreatePage { private onTaskSucceeded(): void { const params: Record = { 'reportId': this.reportId } + // 显式 catch:replaceUrl 的 Promise 拒绝在无 catch 时完全静默(真机实测 + // 卡在轮询页无任何提示),至少把失败暴露出来并由兜底按钮接管。 router.replaceUrl({ url: 'pages/ReportResultPage', params: params }) + .catch((e: Object): void => { + this.navError = `报告已生成,但打开结果页失败:${String(e).substring(0, 80)}` + }) } private onTaskFailed(reason: string): void { @@ -308,17 +322,32 @@ struct ReportCreatePage { Column({ space: 8 }) { this.FieldLabel('生日', '') - Text(`当前选择:${formatDate(this.birthday)}`) - .fontSize(12) - .fontColor('#999999') - DatePicker({ - start: new Date(1930, 0, 1), - end: new Date(), - selected: this.birthday - }) - .onDateChange((d: Date) => { - this.birthday = d + Row({ space: 12 }) { + Text(`当前选择:${formatDate(this.birthday)}`) + .fontSize(13) + .fontColor('#333333') + .layoutWeight(1) + Text(this.showDatePicker ? '收起 ▲' : '修改 ▼') + .fontSize(13) + .fontColor('#C2185B') + .onClick(() => { + this.showDatePicker = !this.showDatePicker + }) + } + .width('100%') + // DatePicker 默认收起:展开态高约半屏,会把心理风格/提交按钮挤出 + // 视野并把滚动手势吃掉(真机实测:用户看不到必填项与错误提示, + // 表现为"提交没反应")。需要改生日时才展开。 + if (this.showDatePicker) { + DatePicker({ + start: new Date(1930, 0, 1), + end: new Date(), + selected: this.birthday }) + .onDateChange((d: Date) => { + this.birthday = d + }) + } } .width('100%') .alignItems(HorizontalAlign.Start) @@ -407,6 +436,16 @@ struct ReportCreatePage { .backgroundColor('#FFFFFF') .alignItems(HorizontalAlign.Start) + if (this.formSummary !== '') { + Text(this.formSummary) + .fontSize(13) + .fontColor('#E5484D') + .width('100%') + .padding(10) + .backgroundColor('#FDECEC') + .borderRadius(8) + .textAlign(TextAlign.Start) + } if (this.submitError !== '') { Text(this.submitError) .fontSize(12) @@ -453,6 +492,27 @@ struct ReportCreatePage { } }) .width('86%') + if (this.navError !== '') { + Text(this.navError) + .fontSize(12) + .fontColor('#E5484D') + .width('86%') + .textAlign(TextAlign.Center) + } + // 兜底:自动跳转若失败(或用户中途离开又回来),手动进结果页。 + // reportId 在提交成功时即已保存,轮询页始终可进。 + if (this.reportId !== '') { + Button('查看报告') + .fontSize(14) + .backgroundColor('#FFFFFF') + .fontColor('#C2185B') + .borderRadius(20) + .padding({ left: 24, right: 24 }) + .margin({ top: 8 }) + .onClick(() => { + this.onTaskSucceeded() + }) + } } .width('100%') .layoutWeight(1)