|
|
@@ -0,0 +1,149 @@
|
|
|
+<template>
|
|
|
+ <view class="indicator-change-row">
|
|
|
+ <block v-if="isDegrade || isIncomparable">
|
|
|
+ <text class="change-degrade gray-text">{{ resolved.trendLabel }}</text>
|
|
|
+ </block>
|
|
|
+ <block v-else>
|
|
|
+ <text class="change-compare-label gray-text-subtle">较{{ prevDateText }}{{ prevDateStar }}:</text>
|
|
|
+ <text class="change-values">{{ prevValue }} → {{ currValue }}{{ unitText }}{{ valueStar }}</text>
|
|
|
+ <text v-if="showArrow" class="change-arrow" :class="colorClass">{{ arrow }}</text>
|
|
|
+ <text class="change-delta" :class="colorClass">{{ deltaText }}</text>
|
|
|
+ <text class="change-trend" :class="colorClass">{{ trendLabel }}</text>
|
|
|
+ </block>
|
|
|
+ <view v-if="showSkipHint && skipGaps > 0" class="skip-row">
|
|
|
+ <text class="skip-text">ⓘ {{ skipHintText }}</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script>
|
|
|
+import { resolveChange, formatDelta } from '@/utils/indicator-change.js'
|
|
|
+
|
|
|
+export default {
|
|
|
+ name: 'IndicatorChangeRow',
|
|
|
+ props: {
|
|
|
+ /** 上一期 { reportDate, dateSource, value, numericValue, unit, status } | null */
|
|
|
+ previous: { type: Object, default: null },
|
|
|
+ /** 本期 { value, numericValue, unit } */
|
|
|
+ current: { type: Object, required: true },
|
|
|
+ /** { delta, deltaPct, movement, trendLabel, trendCode, skipGaps, unitMismatch, comparable } | null */
|
|
|
+ change: { type: Object, default: null },
|
|
|
+ /** 'normal' 报告详情/时间轴 | 'compact' 列表窄位(省略单位) */
|
|
|
+ size: { type: String, default: 'normal' },
|
|
|
+ /** 是否显示跨期跳比提示行 */
|
|
|
+ showSkipHint: { type: Boolean, default: true },
|
|
|
+ /** 跳比提示的具体缺失日期列表(可选,不传则显示「中间 N 期未检测该项」) */
|
|
|
+ skipDates: { type: Array, default: function() { return [] } }
|
|
|
+ },
|
|
|
+ computed: {
|
|
|
+ /**
|
|
|
+ * 【单一事实来源】消费后端 trendCode,优先使用后端下发的 trendLabel。
|
|
|
+ * 兼容老数据:无 trendCode 时按 previous 有无走 first_record 兜底,
|
|
|
+ * 前端不按 delta 正负自行推导改善/恶化。
|
|
|
+ */
|
|
|
+ resolved: function() {
|
|
|
+ var ch = this.change
|
|
|
+ var code = ch && ch.trendCode
|
|
|
+ if (code) {
|
|
|
+ return resolveChange(code, ch.movement, ch.trendLabel)
|
|
|
+ }
|
|
|
+ // 老数据兜底:无 trendCode 时,previous 为空 → 首次记录
|
|
|
+ if (!this.previous) return resolveChange('first_record', 'none')
|
|
|
+ return resolveChange('flat', 'flat')
|
|
|
+ },
|
|
|
+ /** 降级态:首次记录 / 历史整理中 */
|
|
|
+ isDegrade: function() {
|
|
|
+ var ch = this.change
|
|
|
+ return ch && (ch.trendCode === 'first_record' || ch.trendCode === 'pending_backfill')
|
|
|
+ },
|
|
|
+ /** 不可比态:非数值 / 单位不可比(trendCode 仍走前端,文案由后端下发) */
|
|
|
+ isIncomparable: function() {
|
|
|
+ var ch = this.change
|
|
|
+ return ch && ch.comparable === false && !this.isDegrade
|
|
|
+ },
|
|
|
+ colorClass: function() {
|
|
|
+ return this.resolved && this.resolved.colorClass || 'gray-text'
|
|
|
+ },
|
|
|
+ showArrow: function() {
|
|
|
+ return this.resolved && this.resolved.showArrow === true
|
|
|
+ },
|
|
|
+ arrow: function() {
|
|
|
+ return this.resolved ? this.resolved.arrow : ''
|
|
|
+ },
|
|
|
+ trendLabel: function() {
|
|
|
+ return this.resolved ? this.resolved.trendLabel : ''
|
|
|
+ },
|
|
|
+ /** 上一期日期文案 + 上传日 * 标记 */
|
|
|
+ prevDateText: function() {
|
|
|
+ var pd = this.previous && this.previous.reportDate
|
|
|
+ var cd = this.current && this.current.reportDate
|
|
|
+ if (!pd) return '未知日期'
|
|
|
+ if (!cd) return pd
|
|
|
+ var sameYear = String(pd).slice(0, 4) === String(cd).slice(0, 4)
|
|
|
+ return sameYear ? String(pd).slice(5) : pd
|
|
|
+ },
|
|
|
+ prevDateStar: function() {
|
|
|
+ var ds = this.previous && this.previous.dateSource
|
|
|
+ return (ds && ds !== 'real' && ds !== 'unknown') ? '*' : ''
|
|
|
+ },
|
|
|
+ prevValue: function() {
|
|
|
+ if (!this.previous) return '--'
|
|
|
+ return this.previous.value != null ? this.previous.value : '--'
|
|
|
+ },
|
|
|
+ currValue: function() {
|
|
|
+ if (!this.current) return '--'
|
|
|
+ return this.current.value != null ? this.current.value : '--'
|
|
|
+ },
|
|
|
+ unitText: function() {
|
|
|
+ if (this.size === 'compact') return ''
|
|
|
+ return this.current && this.current.unit ? ' ' + this.current.unit : ''
|
|
|
+ },
|
|
|
+ /** 单位不一致 → 值后加 * */
|
|
|
+ valueStar: function() {
|
|
|
+ var ch = this.change
|
|
|
+ return ch && ch.unitMismatch ? '*' : ''
|
|
|
+ },
|
|
|
+ /** Δ 展示文本(PRD §6.4:unitMismatch 时百分比显示 —) */
|
|
|
+ deltaText: function() {
|
|
|
+ var ch = this.change
|
|
|
+ if (!ch || ch.delta == null) return '--'
|
|
|
+ return formatDelta(ch.delta, ch.deltaPct, ch.unitMismatch)
|
|
|
+ },
|
|
|
+ skipGaps: function() {
|
|
|
+ var ch = this.change
|
|
|
+ return ch && ch.skipGaps ? ch.skipGaps : 0
|
|
|
+ },
|
|
|
+ skipHintText: function() {
|
|
|
+ if (this.skipDates && this.skipDates.length > 0) {
|
|
|
+ return this.skipDates.join('、') + ' 多次未检测该项,对比的是更早期'
|
|
|
+ }
|
|
|
+ return '中间 ' + this.skipGaps + ' 期未检测该项'
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped lang="scss">
|
|
|
+.indicator-change-row {
|
|
|
+ padding: 8rpx 0;
|
|
|
+ font-size: calc(22rpx * var(--font-scale, 1));
|
|
|
+ line-height: 1.6;
|
|
|
+}
|
|
|
+.change-compare-label { color: $app-text-subtle; }
|
|
|
+.gray-text-subtle { color: $app-text-subtle; }
|
|
|
+.change-values { color: $facade-text-strong; }
|
|
|
+.change-delta { font-weight: 600; }
|
|
|
+.change-trend { font-weight: 500; }
|
|
|
+
|
|
|
+/* 变化着色:改善=绿 / 恶化=橙红 / 中性=灰(双通道解耦,颜色=变化性质) */
|
|
|
+.green-text { color: $status-success-strong-2; }
|
|
|
+.red-text { color: $status-danger-deep; }
|
|
|
+.gray-text { color: $app-text-subtle; }
|
|
|
+
|
|
|
+/* 第三行:跨期跳比提示 */
|
|
|
+.skip-row { display: block; padding-top: 4rpx; }
|
|
|
+.skip-text {
|
|
|
+ font-size: calc(20rpx * var(--font-scale, 1));
|
|
|
+ color: $mat-orange-base;
|
|
|
+}
|
|
|
+</style>
|