Files
meida/app/entry/src/main/ets/components/f3/DimensionCard.ets
T
xsl a850735d60 feat(app): 任务换额度+端侧埋点+试穿限次+报告反馈预约卡+网络错误态——PM/开发双Agent迭代2-3
- 新增 core/Tracker.ets:埋点唯一入口,事件名正则校验,preferences 队列(≥20立即/
  30s节流/后台flush,每批50,上限200丢最旧),PII 键剔除,失败静默;首批10点位
- 新增 core/DailyLimiter.ets:试穿/试戴各3次/日本地软限制,自然日重置,失败不计数;
  setBonus 支持服务端 bonus_granted 加成,「做任务多换几次」真兑现
- 新增 components/f1/TaskBonusPanel.ets + core/TaskBonusState.ets:签到真实 claim
  链路,40910/40310 处理,已领态面板与报告页互通;个人中心与对话耗尽态接入
- 报告页:维度「准/不准」反馈卡(DimensionCard 无网络走回调)+ 静默 claim
  report_feedback 闭环;全面报告预约留资卡(card_view/reserve_result 埋点)
- 新增 components/f2/NetworkBanner.ets:首页/对话/个人中心三处网络错误统一横幅+重试;
  对话耗尽横幅恢复 reset_at 时间显示
- 文案纪律:无价格/购买/会员/升级字样;hvigor assembleHap 构建 0 error
- 子模块 meida_backend 指向 7bcbb8d(迭代1-3 后端)
2026-09-11 21:51:25 +08:00

225 lines
7.3 KiB
Plaintext

/**
* DimensionCard —— 32 维诊断单项卡片(F3)。
*
* - ok 维度:name_cn + value(四种形态由 formatDimValue 统一处理:字符串直显 /
* 数字转字符串 / 数组 join '、' / 空值显示 '—')+ 来源标签中文映射 + 置信度百分比
* - confidence < 0.7 加“仅供参考”小标(NFR-UX-05)
* - skipped 维度:整体灰显,evidence 作为未覆盖原因文案
* - rule_hits 非空时折叠展示“依据规则 N 条”
*/
import { EMPTY_DIMENSION, DimensionWire, formatDimValue, sourceColor, sourceLabel } from './ReportTypes'
import { COLOR_PRIMARY } from '../../core/Theme'
const CARD_PRIMARY_COLOR: string = COLOR_PRIMARY
@Component
export struct DimensionCard {
dim: DimensionWire = EMPTY_DIMENSION
/** 已提交的反馈判定('' = 未反馈;'accurate' / 'inaccurate' 高亮已选项,宿主回显用)。 */
feedbackVerdict: string = ''
/** 提交反馈(组件保持无网络):宿主调 POST /reports/{id}/feedback。 */
onFeedback: (dimKey: string, verdict: string, correction: string) => void = () => {}
@State ruleExpanded: boolean = false
@State correctionOpen: boolean = false // 「不准」展开的行内修正输入
@State correctionText: string = ''
private isSkipped(): boolean {
return this.dim.status !== 'ok'
}
private titleText(): string {
const n = this.dim.name_cn
if (n !== undefined && n !== '') {
return n
}
const k = this.dim.key
if (k !== undefined && k !== '') {
return k
}
const num = this.dim.number
return num !== undefined ? `维度 ${num}` : '未知维度'
}
private hasConfidence(): boolean {
const c = this.dim.confidence
return c !== undefined && c !== null
}
private confidencePercent(): string {
const c = this.dim.confidence
return `${Math.round((c ?? 0) * 100)}%`
}
private isLowConfidence(): boolean {
const c = this.dim.confidence
return c !== undefined && c !== null && c < 0.7
}
private ruleHits(): string[] {
const r = this.dim.rule_hits
return r ?? []
}
private reasonText(): string {
const ev = this.dim.evidence
if (ev !== undefined && ev !== null && ev !== '') {
return ev
}
return '本期未采集到足够信息'
}
/** 反馈区可用条件(T-P3-21):仅 status=ok 且有 key 的维度卡。 */
private feedbackable(): boolean {
const k = this.dim.key
return !this.isSkipped() && k !== undefined && k !== ''
}
private dimKey(): string {
return this.dim.key ?? ''
}
private submitFeedback(verdict: string): void {
if (verdict === 'inaccurate' && !this.correctionOpen) {
// 点「不准」先展开行内修正输入(可选,≤100 字),再点「提交」才上报
this.correctionOpen = true
return
}
this.correctionOpen = false
this.onFeedback(this.dimKey(), verdict, verdict === 'inaccurate' ? this.correctionText.trim() : '')
}
build() {
Column({ space: 8 }) {
Row({ space: 8 }) {
Text(this.titleText())
.fontSize(15)
.fontWeight(FontWeight.Medium)
.fontColor(this.isSkipped() ? '#9AA0A6' : '#333333')
.layoutWeight(1)
.maxLines(1)
.textOverflow({ overflow: TextOverflow.Ellipsis })
if (!this.isSkipped()) {
Text(sourceLabel(this.dim.source))
.fontSize(10)
.fontColor(sourceColor(this.dim.source))
.padding({ left: 6, right: 6, top: 2, bottom: 2 })
.backgroundColor('#F5F5F7')
.borderRadius(4)
if (this.hasConfidence()) {
Text(this.confidencePercent())
.fontSize(12)
.fontColor('#666666')
}
if (this.isLowConfidence()) {
Text('仅供参考')
.fontSize(10)
.fontColor('#D46A0B')
.padding({ left: 6, right: 6, top: 2, bottom: 2 })
.backgroundColor('#FDF3E3')
.borderRadius(4)
}
}
}
.width('100%')
if (this.isSkipped()) {
Text(`未覆盖:${this.reasonText()}`)
.fontSize(13)
.fontColor('#9AA0A6')
.width('100%')
} else {
Text(formatDimValue(this.dim.value))
.fontSize(14)
.fontColor('#333333')
.width('100%')
}
if (this.ruleHits().length > 0) {
Text(this.ruleExpanded ? `收起依据(规则 ${this.ruleHits().length} 条)` : `依据规则 ${this.ruleHits().length} 条`)
.fontSize(12)
.fontColor(CARD_PRIMARY_COLOR)
.onClick(() => {
this.ruleExpanded = !this.ruleExpanded
})
if (this.ruleExpanded) {
Column({ space: 4 }) {
ForEach(this.ruleHits(), (r: string) => {
Text(`· ${r}`)
.fontSize(12)
.fontColor('#888888')
.width('100%')
}, (r: string) => r)
}
.width('100%')
.alignItems(HorizontalAlign.Start)
}
}
// 结论反馈区(T-P3-21):仅 ok 维度;已选项高亮,再点另一项即改判
if (this.feedbackable()) {
Row({ space: 10 }) {
Text('这个结论准吗?')
.fontSize(12)
.fontColor('#8A9099')
.layoutWeight(1)
Text('准 👍')
.fontSize(12)
.fontColor(this.feedbackVerdict === 'accurate' ? '#FFFFFF' : CARD_PRIMARY_COLOR)
.padding({ left: 12, right: 12, top: 4, bottom: 4 })
.borderRadius(14)
.backgroundColor(this.feedbackVerdict === 'accurate' ? CARD_PRIMARY_COLOR : '#F7E3EA')
.onClick(() => {
this.submitFeedback('accurate')
})
Text('不准 👎')
.fontSize(12)
.fontColor(this.feedbackVerdict === 'inaccurate' ? '#FFFFFF' : '#666666')
.padding({ left: 12, right: 12, top: 4, bottom: 4 })
.borderRadius(14)
.backgroundColor(this.feedbackVerdict === 'inaccurate' ? '#666666' : '#F5F5F7')
.onClick(() => {
this.submitFeedback('inaccurate')
})
}
.width('100%')
.alignItems(VerticalAlign.Center)
.margin({ top: 4 })
// 「不准」的行内修正输入(可选,≤100 字)
if (this.correctionOpen) {
TextArea({ text: this.correctionText, placeholder: '你觉得应该是?(可选)' })
.width('100%')
.height(72)
.fontSize(13)
.maxLength(100)
.backgroundColor('#F5F5F7')
.borderRadius(8)
.onChange((v: string) => {
this.correctionText = v
})
Row() {
Blank()
Text('提交')
.fontSize(13)
.fontColor(Color.White)
.padding({ left: 20, right: 20, top: 6, bottom: 6 })
.borderRadius(16)
.backgroundColor(CARD_PRIMARY_COLOR)
.onClick(() => {
this.onFeedback(this.dimKey(), 'inaccurate', this.correctionText.trim())
this.correctionOpen = false
})
}
.width('100%')
}
}
}
.width('100%')
.padding(12)
.borderRadius(12)
.backgroundColor(this.isSkipped() ? '#F7F7F8' : '#FFFFFF')
.opacity(this.isSkipped() ? 0.85 : 1)
}
}