fix(app): 真机首验三连修——信封解析/DatePicker 折叠/跳转兜底
真机+模拟器注入实测发现并修复: 1. ApiClient 把一切非 200 HTTP 吞成 50000「服务开小差了」——后端业务 错误(40920/40301/42910…)走非 200 状态码但 body 是统一信封,先验 信封形状再判成败。这是「提交不起作用」的直接原因(用户二次提交 =409,红字被布局挤在视野外) 2. 生日 DatePicker 默认展开高约半屏:九型必填项与提交按钮被推出视野、 滚动手势被选择器消费。改点击展开,表单一屏可见 3. 校验失败加顶部汇总红条(字段红字可能在视野外的可见性兜底) 4. 轮询完成不自动跳结果页:@Builder 传函数给子组件在真机调用无效 (手动按钮同一方法正常)——加「查看报告」兜底按钮 + replaceUrl 显式 catch + 完成态文案引导,回调保留 try/catch 与日志 验证:40920 对话框正常弹出;新用户提交→轮询→查看报告→32 维结果页 完整渲染(含降级黄条、报告编号)
This commit is contained in:
@@ -81,7 +81,15 @@ export struct TaskPollingPanel {
|
|||||||
}
|
}
|
||||||
if (t.status === 'SUCCEEDED') {
|
if (t.status === 'SUCCEEDED') {
|
||||||
this.stopTimer()
|
this.stopTimer()
|
||||||
this.onSucceeded()
|
this.stageText = '已完成——点击下方「查看报告」查看结果'
|
||||||
|
// 回调经 @Builder 传函数在真机上存在调用无效的坑(手动按钮同一
|
||||||
|
// 方法完全正常,真机实测轮询完成不自动跳转)。保留回调尝试,
|
||||||
|
// 失败静默并由宿主的「查看报告」兜底按钮承担导航。
|
||||||
|
try {
|
||||||
|
this.onSucceeded()
|
||||||
|
} catch (e) {
|
||||||
|
console.error(`poll onSucceeded failed: ${JSON.stringify(e)}`)
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (t.status === 'FAILED') {
|
if (t.status === 'FAILED') {
|
||||||
|
|||||||
@@ -76,15 +76,20 @@ export class ApiClient {
|
|||||||
connectTimeout: 10000,
|
connectTimeout: 10000,
|
||||||
readTimeout: 30000
|
readTimeout: 30000
|
||||||
})
|
})
|
||||||
if (resp.responseCode !== 200 || resp.result === null || resp.result === undefined) {
|
// 后端业务错误(40920/40301/42910…)走非 200 的 HTTP 状态码,
|
||||||
// 网关/代理层错误:没有信封可用,给统一话术(NFR-UX-02)
|
// 但 body 仍是统一信封——先验信封形状再判成败;只有 body 不是
|
||||||
throw new ApiError(50000, '服务开小差了', '请稍后重试', resp.responseCode)
|
// 信封(网关 502 的 HTML、字符串等)才按网关错误兜底。
|
||||||
}
|
// 真机实测教训:把非 200 一律吞成 50000,会吃掉全部业务错误分支
|
||||||
|
// (40920 的"已生成过"对话框、40301 的额度提示全部失效)。
|
||||||
const env = resp.result as ApiEnvelope<T>
|
const env = resp.result as ApiEnvelope<T>
|
||||||
if (env.code !== 0) {
|
if (typeof env === 'object' && env !== null && typeof env.code === 'number') {
|
||||||
throw new ApiError(env.code, env.message, env.hint, resp.responseCode)
|
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 {
|
} finally {
|
||||||
client.destroy()
|
client.destroy()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,6 +41,9 @@ struct ReportCreatePage {
|
|||||||
@State goalsErr: string = ''
|
@State goalsErr: string = ''
|
||||||
@State styleErr: string = ''
|
@State styleErr: string = ''
|
||||||
@State submitting: boolean = false
|
@State submitting: boolean = false
|
||||||
|
@State showDatePicker: boolean = false
|
||||||
|
@State formSummary: string = ''
|
||||||
|
@State navError: string = ''
|
||||||
@State submitError: string = ''
|
@State submitError: string = ''
|
||||||
@State failMsg: string = ''
|
@State failMsg: string = ''
|
||||||
@State showDupDialog: boolean = false
|
@State showDupDialog: boolean = false
|
||||||
@@ -105,7 +108,13 @@ struct ReportCreatePage {
|
|||||||
if (this.submitting) {
|
if (this.submitting) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
this.formSummary = ''
|
||||||
if (!this.validateForm()) {
|
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
|
return
|
||||||
}
|
}
|
||||||
this.doSubmit()
|
this.doSubmit()
|
||||||
@@ -176,7 +185,12 @@ struct ReportCreatePage {
|
|||||||
|
|
||||||
private onTaskSucceeded(): void {
|
private onTaskSucceeded(): void {
|
||||||
const params: Record<string, Object> = { 'reportId': this.reportId }
|
const params: Record<string, Object> = { 'reportId': this.reportId }
|
||||||
|
// 显式 catch:replaceUrl 的 Promise 拒绝在无 catch 时完全静默(真机实测
|
||||||
|
// 卡在轮询页无任何提示),至少把失败暴露出来并由兜底按钮接管。
|
||||||
router.replaceUrl({ url: 'pages/ReportResultPage', params: params })
|
router.replaceUrl({ url: 'pages/ReportResultPage', params: params })
|
||||||
|
.catch((e: Object): void => {
|
||||||
|
this.navError = `报告已生成,但打开结果页失败:${String(e).substring(0, 80)}`
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
private onTaskFailed(reason: string): void {
|
private onTaskFailed(reason: string): void {
|
||||||
@@ -308,17 +322,32 @@ struct ReportCreatePage {
|
|||||||
|
|
||||||
Column({ space: 8 }) {
|
Column({ space: 8 }) {
|
||||||
this.FieldLabel('生日', '')
|
this.FieldLabel('生日', '')
|
||||||
Text(`当前选择:${formatDate(this.birthday)}`)
|
Row({ space: 12 }) {
|
||||||
.fontSize(12)
|
Text(`当前选择:${formatDate(this.birthday)}`)
|
||||||
.fontColor('#999999')
|
.fontSize(13)
|
||||||
DatePicker({
|
.fontColor('#333333')
|
||||||
start: new Date(1930, 0, 1),
|
.layoutWeight(1)
|
||||||
end: new Date(),
|
Text(this.showDatePicker ? '收起 ▲' : '修改 ▼')
|
||||||
selected: this.birthday
|
.fontSize(13)
|
||||||
})
|
.fontColor('#C2185B')
|
||||||
.onDateChange((d: Date) => {
|
.onClick(() => {
|
||||||
this.birthday = d
|
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%')
|
.width('100%')
|
||||||
.alignItems(HorizontalAlign.Start)
|
.alignItems(HorizontalAlign.Start)
|
||||||
@@ -407,6 +436,16 @@ struct ReportCreatePage {
|
|||||||
.backgroundColor('#FFFFFF')
|
.backgroundColor('#FFFFFF')
|
||||||
.alignItems(HorizontalAlign.Start)
|
.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 !== '') {
|
if (this.submitError !== '') {
|
||||||
Text(this.submitError)
|
Text(this.submitError)
|
||||||
.fontSize(12)
|
.fontSize(12)
|
||||||
@@ -453,6 +492,27 @@ struct ReportCreatePage {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
.width('86%')
|
.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%')
|
.width('100%')
|
||||||
.layoutWeight(1)
|
.layoutWeight(1)
|
||||||
|
|||||||
Reference in New Issue
Block a user