diff --git a/app/entry/src/main/ets/pages/ReportCreatePage.ets b/app/entry/src/main/ets/pages/ReportCreatePage.ets index 64f524a..e313325 100644 --- a/app/entry/src/main/ets/pages/ReportCreatePage.ets +++ b/app/entry/src/main/ets/pages/ReportCreatePage.ets @@ -11,7 +11,7 @@ * 6) SUCCEEDED → router.replaceUrl 结果页(params 带 reportId);FAILED → 失败态 + 幂等重试 */ -import { router } from '@kit.ArkUI' +import { router, componentUtils } from '@kit.ArkUI' import { ApiClient } from '../core/ApiClient' import { ApiError } from '../core/ApiTypes' import { AuthStore } from '../core/AuthStore' @@ -24,6 +24,12 @@ const GOAL_OPTIONS: string[] = ['显高', '显瘦', '显白', '显气质', '遮 const STYLE_OPTIONS: string[] = ['戏剧', '睿智', '自然', '古典', '优雅', '浪漫', '前卫', '少女', '少年'] const LAST_REPORT_STORAGE_KEY: string = 'f3_last_report_id' +interface FieldLabelParams { + label: string + err: string + required: boolean +} + @Entry @Component struct ReportCreatePage { @@ -51,6 +57,7 @@ struct ReportCreatePage { private requestKey: string = '' // 失败重试沿用同一 key:重新提交即服务端幂等重放,不重复消耗额度 private taskId: string = '' private reportId: string = '' + private scroller: Scroller = new Scroller() aboutToAppear(): void { const cached = AppStorage.get(LAST_REPORT_STORAGE_KEY) @@ -111,15 +118,61 @@ struct ReportCreatePage { 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} 项未完成,请检查标红的字段(性别/身高/体重/核心诉求/心理风格)` + // 汇总条点名缺哪几项,并自动滚到第一个缺失字段。 + const labels: string[] = this.missingLabels() + this.formSummary = labels.length > 0 ? `请完成:${labels.join('、')}` : '请检查标红的字段' + this.scrollToFirstError() return } this.doSubmit() } + private missingLabels(): string[] { + const labels: string[] = [] + if (this.genderErr !== '') { + labels.push('性别') + } + if (this.heightErr !== '') { + labels.push('身高') + } + if (this.weightErr !== '') { + labels.push('体重') + } + if (this.goalsErr !== '') { + labels.push('核心诉求') + } + if (this.styleErr !== '') { + labels.push('心理风格') + } + return labels + } + + private scrollToFirstError(): void { + let firstId = '' + if (this.genderErr !== '') { + firstId = 'field_gender' + } else if (this.heightErr !== '' || this.weightErr !== '') { + firstId = 'field_height' + } else if (this.goalsErr !== '') { + firstId = 'field_goals' + } else if (this.styleErr !== '') { + firstId = 'field_style' + } + if (firstId === '') { + return + } + try { + // windowOffset 随滚动同步移动,两处相减即为内容坐标差, + // 与当前滚动位置无关 + const root = componentUtils.getRectangleById('form_root') + const target = componentUtils.getRectangleById(firstId) + const dy = target.windowOffset.y - root.windowOffset.y - 8 + this.scroller.scrollTo({ xOffset: 0, yOffset: Math.max(0, dy), animation: { duration: 250, curve: Curve.EaseOut } }) + } catch (e) { + console.error(`scrollTo ${firstId} failed: ${String(e).substring(0, 80)}`) + } + } + private async doSubmit(): Promise { this.submitting = true this.submitError = '' @@ -226,14 +279,21 @@ struct ReportCreatePage { .padding({ left: 12, right: 16, top: 10, bottom: 6 }) } + // 按引用传递($$):@Builder 按值传参时 @State 变化不会触发刷新, + // 字段级红字将永远停在初始空值(真机实测复现) @Builder - FieldLabel(label: string, err: string) { + FieldLabel($$: FieldLabelParams) { Row({ space: 6 }) { - Text(label) + if ($$.required) { + Text('*') + .fontSize(14) + .fontColor('#E5484D') + } + Text($$.label) .fontSize(13) .fontColor('#666666') - if (err !== '') { - Text(err) + if ($$.err !== '') { + Text($$.err) .fontSize(11) .fontColor('#E5484D') .layoutWeight(1) @@ -245,7 +305,7 @@ struct ReportCreatePage { @Builder FormBody() { - Scroll() { + Scroll(this.scroller) { Column({ space: 12 }) { Column({ space: 4 }) { Text('生成你的形象诊断报告') @@ -266,7 +326,7 @@ struct ReportCreatePage { .fontWeight(FontWeight.Medium) .fontColor('#333333') Column({ space: 8 }) { - this.FieldLabel('性别', this.genderErr) + this.FieldLabel({ label: '性别', err: this.genderErr, required: true }) Row({ space: 8 }) { ForEach(GENDER_OPTIONS, (g: string) => { Text(g) @@ -284,10 +344,11 @@ struct ReportCreatePage { } .width('100%') .alignItems(HorizontalAlign.Start) + .id('field_gender') Row({ space: 12 }) { Column({ space: 8 }) { - this.FieldLabel('身高 (cm)', this.heightErr) + this.FieldLabel({ label: '身高 (cm)', err: this.heightErr, required: true }) TextInput({ placeholder: '如 165', text: this.heightInput }) .type(InputType.Number) .height(44) @@ -301,9 +362,10 @@ struct ReportCreatePage { } .layoutWeight(1) .alignItems(HorizontalAlign.Start) + .id('field_height') Column({ space: 8 }) { - this.FieldLabel('体重 (kg)', this.weightErr) + this.FieldLabel({ label: '体重 (kg)', err: this.weightErr, required: true }) TextInput({ placeholder: '如 52', text: this.weightInput }) .type(InputType.Number) .height(44) @@ -321,7 +383,7 @@ struct ReportCreatePage { .width('100%') Column({ space: 8 }) { - this.FieldLabel('生日', '') + this.FieldLabel({ label: '生日', err: '', required: false }) Row({ space: 12 }) { Text(`当前选择:${formatDate(this.birthday)}`) .fontSize(13) @@ -363,7 +425,7 @@ struct ReportCreatePage { .fontWeight(FontWeight.Medium) .fontColor('#333333') Column({ space: 8 }) { - this.FieldLabel('核心诉求(多选)', this.goalsErr) + this.FieldLabel({ label: '核心诉求(多选)', err: this.goalsErr, required: true }) Flex({ wrap: FlexWrap.Wrap }) { ForEach(GOAL_OPTIONS, (g: string) => { Text(g) @@ -382,9 +444,10 @@ struct ReportCreatePage { } .width('100%') .alignItems(HorizontalAlign.Start) + .id('field_goals') Column({ space: 8 }) { - this.FieldLabel('心理风格(九型·单选)', this.styleErr) + this.FieldLabel({ label: '心理风格(九型·单选)', err: this.styleErr, required: true }) Flex({ wrap: FlexWrap.Wrap }) { ForEach(STYLE_OPTIONS, (s: string) => { Text(s) @@ -404,6 +467,7 @@ struct ReportCreatePage { } .width('100%') .alignItems(HorizontalAlign.Start) + .id('field_style') } .width('100%') .padding(16) @@ -445,6 +509,9 @@ struct ReportCreatePage { .backgroundColor('#FDECEC') .borderRadius(8) .textAlign(TextAlign.Start) + .onClick(() => { + this.scrollToFirstError() + }) } if (this.submitError !== '') { Text(this.submitError) @@ -466,6 +533,7 @@ struct ReportCreatePage { }) } .padding({ left: 16, right: 16 }) + .id('form_root') } .layoutWeight(1) .width('100%')