feat(app): report flow with polling and 32-dimension result view(F3)

This commit is contained in:
xsl
2026-08-15 00:49:47 +08:00
parent 9503c6e259
commit 995c18e7d5
5 changed files with 1573 additions and 10 deletions
@@ -0,0 +1,138 @@
/**
* 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'
const CARD_PRIMARY_COLOR: string = '#C2185B'
@Component
export struct DimensionCard {
dim: DimensionWire = EMPTY_DIMENSION
@State ruleExpanded: boolean = false
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 '本期未采集到足够信息'
}
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)
}
}
}
.width('100%')
.padding(12)
.borderRadius(12)
.backgroundColor(this.isSkipped() ? '#F7F7F8' : '#FFFFFF')
.opacity(this.isSkipped() ? 0.85 : 1)
}
}