|
|
@@ -201,7 +201,7 @@
|
|
|
<view class="compare-head" v-if="hasCompareData">
|
|
|
<!-- 历史数据整理中提示条(pendingCount > 0) -->
|
|
|
<view class="compare-pending" v-if="comparePendingCount > 0">
|
|
|
- <text class="compare-pending-text">部分历史数据整理中,暂不可对比</text>
|
|
|
+ <text class="compare-pending-text">部分历史数据正在整理,对比结果可能不全</text>
|
|
|
</view>
|
|
|
<!-- 数据来源 / 报告份数 -->
|
|
|
<view class="compare-meta">
|
|
|
@@ -228,6 +228,7 @@
|
|
|
<text class="indicator-group-name">{{ group.category }}</text>
|
|
|
</view>
|
|
|
<view class="indicator-group-header-right">
|
|
|
+ <text class="indicator-group-delta" v-if="group._deltaText">{{ group._deltaText }}</text>
|
|
|
<text class="indicator-group-abnormal" v-if="group.abnormalCount > 0">{{ group.abnormalCount }} 项异常</text>
|
|
|
<text class="indicator-group-count">{{ group.items.length }} 项</text>
|
|
|
</view>
|
|
|
@@ -242,6 +243,8 @@
|
|
|
<text class="ind-unit-inline" v-if="ind.unit">{{ ind.unit }}</text>
|
|
|
<view class="status-tag" :class="'status-' + _cssClass(ind.status)">{{ statusText(ind.status) }}</view>
|
|
|
<text class="ind-change-badge" v-if="ind._badgeText">{{ ind._badgeText }}</text>
|
|
|
+ <!-- 「N 期 ›」趋势入口(pointsCount ≥ 2 才显示) -->
|
|
|
+ <text class="ind-trend-entry" v-if="ind._showTrendEntry" @tap.stop="goIndicatorTimeline(ind)">{{ ind._pointsCount }} 期 ›</text>
|
|
|
</view>
|
|
|
<view class="ind-detail" v-if="ind.refRange">
|
|
|
<text class="ind-ref" v-if="ind.refRange">参考: {{ ind.refRange }}</text>
|
|
|
@@ -527,6 +530,7 @@ export default {
|
|
|
// ===== 变化对照(T04 第二段:/api/health/report/indicators)=====
|
|
|
// 接口失败时静默降级为现状单期渲染(不显示变化对照区域)
|
|
|
reportIndicatorMap: {},
|
|
|
+ reportIndicatorNameIndex: {},
|
|
|
indicatorSummary: null,
|
|
|
indicatorMeta: null,
|
|
|
// 三档筛选:all 全部 / changed 有变化 / abnormal 异常
|
|
|
@@ -571,28 +575,32 @@ export default {
|
|
|
var parts = []
|
|
|
if (m && m.dateSource && m.dateSource !== 'real') { parts.push('来源:上传日期') }
|
|
|
else if (m && m.reportDate) { parts.push('日期:' + m.reportDate) }
|
|
|
- if (m && m.olderReportCount != null) { parts.push('共 ' + m.olderReportCount + ' 份报告') }
|
|
|
+ if (m && m.olderReportCount != null) { parts.push('更早 ' + m.olderReportCount + ' 份报告') }
|
|
|
return parts.length ? parts.join(' · ') : ''
|
|
|
},
|
|
|
/**
|
|
|
* 指标分组 + 变化对照合并 + 三档筛选(T04 第二段)
|
|
|
* 说明:
|
|
|
- * - 变化数据按 indicatorName 与 payload 指标合并(编辑态下 name 可改,合并以接口返回的 indicatorName 为键)
|
|
|
+ * - 变化数据按 definitionId 合并(definitionId→item + name→definitionId 双索引,
|
|
|
+ * payload 指标名可编辑时用名称索引兜底)
|
|
|
+ * - 趋势词/颜色只消费后端 trendCode + movement + trendLabel,前端不自判改善/恶化
|
|
|
* - 预计算所有展示字段(/:class 不调用带参方法)
|
|
|
*/
|
|
|
indicatorGroups() {
|
|
|
var groups = []
|
|
|
var orderMap = {}
|
|
|
var list = this.payload.indicators || []
|
|
|
- var map = this.reportIndicatorMap || {}
|
|
|
var filter = this.indicatorFilter
|
|
|
for (var i = 0; i < list.length; i++) {
|
|
|
var ind = list[i]
|
|
|
- var ext = map[ind.indicatorName] || null
|
|
|
+ var ext = this.findReportIndicator(ind)
|
|
|
var prev = ext && ext.previous ? ext.previous : null
|
|
|
var ch = ext && ext.change ? ext.change : null
|
|
|
- var tc = ch ? ch.trendCode : 'first_record'
|
|
|
- var st = resolveChange(tc, ch && ch.movement, ch && ch.trendLabel)
|
|
|
+ // ★ 只消费后端 trendCode;change 缺失时一律中性兜底(不自行断言 first_record)
|
|
|
+ var tc = ch ? ch.trendCode : null
|
|
|
+ var st = ch
|
|
|
+ ? resolveChange(tc, ch.movement, ch.trendLabel)
|
|
|
+ : { trendLabel: '', cssClass: 'flat', arrow: '', colorClass: 'gray-text', showArrow: false }
|
|
|
// 预计算字段(模板不调用带参方法)
|
|
|
ind._previous = prev
|
|
|
ind._change = ch
|
|
|
@@ -600,10 +608,21 @@ export default {
|
|
|
ind._cardChangeCss = st.colorClass
|
|
|
ind._isChanged = !!(ch && ch.comparable && tc && tc !== 'flat')
|
|
|
ind._isAbnormal = this.isAbnormalStatus(ind.status)
|
|
|
- // 简报徽标(短词)
|
|
|
+ // 简报徽标(短词):由 trendCode + movement 推导,不看 delta 正负
|
|
|
var badge = ''
|
|
|
- if (tc === 'first_record') { badge = '首次' } else if (tc === 'pending_backfill') { badge = '整理中' } else if (tc === 'improved' || tc === 'worsened') { badge = '有变化' } else if (tc === 'flat') { badge = '持平' }
|
|
|
+ if (tc === 'first_record') { badge = '首次' }
|
|
|
+ else if (tc === 'pending_backfill') { badge = '整理中' }
|
|
|
+ else if (tc === 'improved' || tc === 'worsened') { badge = '有变化' }
|
|
|
+ else if (tc === 'flat') {
|
|
|
+ var mv = ch.movement
|
|
|
+ if (mv === 'changed') badge = '有变化'
|
|
|
+ else if (mv === 'unchanged') badge = '无变化'
|
|
|
+ else badge = '持平'
|
|
|
+ }
|
|
|
ind._badgeText = (badge && ch) ? badge : ''
|
|
|
+ // 「N 期 ›」趋势入口(pointsCount ≥ 2 且命中字典)
|
|
|
+ ind._pointsCount = (ext && ext.pointsCount) ? ext.pointsCount : 0
|
|
|
+ ind._showTrendEntry = !!(ext && ext.definitionId !== null && ext.definitionId !== undefined && ind._pointsCount >= 2)
|
|
|
var curValue = ind.indicatorValue
|
|
|
ind._current = {
|
|
|
value: curValue,
|
|
|
@@ -619,10 +638,23 @@ export default {
|
|
|
if (gIdx === undefined) {
|
|
|
gIdx = groups.length
|
|
|
orderMap[cat] = gIdx
|
|
|
- groups.push({ category: cat, items: [], abnormalCount: 0 })
|
|
|
+ groups.push({ category: cat, items: [], abnormalCount: 0, abnormalDelta: null })
|
|
|
+ }
|
|
|
+ var g = groups[gIdx]
|
|
|
+ g.items.push(ind)
|
|
|
+ if (ind._isAbnormal) g.abnormalCount++
|
|
|
+ // 分组「较上次 ±N」:只统计有上一期对照的项(与后端 summary.abnormalDelta 同口径)
|
|
|
+ if (prev) {
|
|
|
+ if (g.abnormalDelta === null) g.abnormalDelta = 0
|
|
|
+ if (ind._isAbnormal) g.abnormalDelta++
|
|
|
+ if (this.isAbnormalStatus(prev.status)) g.abnormalDelta--
|
|
|
}
|
|
|
- groups[gIdx].items.push(ind)
|
|
|
- if (ind._isAbnormal) groups[gIdx].abnormalCount++
|
|
|
+ }
|
|
|
+ for (var gi = 0; gi < groups.length; gi++) {
|
|
|
+ var grp = groups[gi]
|
|
|
+ grp._deltaText = (grp.abnormalDelta === null)
|
|
|
+ ? ''
|
|
|
+ : ('较上次 ' + (grp.abnormalDelta > 0 ? '+' : '') + grp.abnormalDelta)
|
|
|
}
|
|
|
return groups
|
|
|
},
|
|
|
@@ -780,16 +812,24 @@ export default {
|
|
|
getReportIndicators({ reportId: self.reportId, withPrevious: true }).then(function(res) {
|
|
|
if (!res || res.code !== 200 || !res.data) return
|
|
|
var data = res.data
|
|
|
+ // 双索引:definitionId → item(主键)+ name → definitionId(payload 指标名兜底)
|
|
|
var map = {}
|
|
|
+ var nameIndex = {}
|
|
|
var items = data.items || []
|
|
|
for (var i = 0; i < items.length; i++) {
|
|
|
var it = items[i]
|
|
|
+ var defId = it.definitionId
|
|
|
var name = it.indicatorName || it.name
|
|
|
- if (!name) continue
|
|
|
- // 同名指标保留第一条(后端已按 definitionId 去重)
|
|
|
- if (!map[name]) map[name] = it
|
|
|
+ if (defId !== null && defId !== undefined) {
|
|
|
+ if (!map[defId]) map[defId] = it
|
|
|
+ if (name && !nameIndex[name]) nameIndex[name] = defId
|
|
|
+ } else if (name && !map[name]) {
|
|
|
+ // 未命中字典(definitionId=null):按名称兜底
|
|
|
+ map[name] = it
|
|
|
+ }
|
|
|
}
|
|
|
self.reportIndicatorMap = map
|
|
|
+ self.reportIndicatorNameIndex = nameIndex
|
|
|
self.indicatorSummary = data.summary || null
|
|
|
self.indicatorMeta = {
|
|
|
reportId: data.reportId,
|
|
|
@@ -803,6 +843,36 @@ export default {
|
|
|
// 静默降级:保持现状单期渲染
|
|
|
})
|
|
|
},
|
|
|
+ /** 变化对照合并:definitionId 优先,名称索引兜底(definitionId→item + name→definitionId 双索引) */
|
|
|
+ findReportIndicator: function(ind) {
|
|
|
+ if (!ind) return null
|
|
|
+ var map = this.reportIndicatorMap || {}
|
|
|
+ var nameIndex = this.reportIndicatorNameIndex || {}
|
|
|
+ var defId = ind.definitionId
|
|
|
+ if (defId !== null && defId !== undefined && map[defId]) return map[defId]
|
|
|
+ var name = ind.indicatorName
|
|
|
+ if (name) {
|
|
|
+ var idBy = nameIndex[name]
|
|
|
+ if (idBy !== undefined && map[idBy]) return map[idBy]
|
|
|
+ if (map[name]) return map[name]
|
|
|
+ }
|
|
|
+ return null
|
|
|
+ },
|
|
|
+ /** 指标行「N 期 ›」→ 跳指标时间轴页(传参口径:subjectId + definitionId + reportType + reportId,spec §L2539) */
|
|
|
+ goIndicatorTimeline: function(ind) {
|
|
|
+ if (!ind || !ind._showTrendEntry) return
|
|
|
+ var subjectId = (this.indicatorMeta && this.indicatorMeta.subjectId) || this.selectedMemberId
|
|
|
+ if (!subjectId) {
|
|
|
+ uni.showToast({ title: '未绑定成员,无法查看趋势', icon: 'none' })
|
|
|
+ return
|
|
|
+ }
|
|
|
+ var url = '/pages/health/indicator-timeline?subjectId=' + subjectId +
|
|
|
+ '&definitionId=' + ind._definitionId +
|
|
|
+ '&reportId=' + (this.reportId || '')
|
|
|
+ var rt = this.indicatorMeta && this.indicatorMeta.reportType
|
|
|
+ if (rt) { url += '&reportType=' + encodeURIComponent(rt) }
|
|
|
+ uni.navigateTo({ url: url })
|
|
|
+ },
|
|
|
/** 三档筛选切换:all 全部 / changed 有变化 / abnormal 异常 */
|
|
|
onIndicatorFilterTap(filter) {
|
|
|
this.indicatorFilter = filter
|
|
|
@@ -1984,6 +2054,22 @@ $c-rptd-orange-line: #FFD4A8; // 暖橙描边
|
|
|
padding: 4rpx 12rpx;
|
|
|
flex-shrink: 0;
|
|
|
}
|
|
|
+/* 「N 期 ›」趋势入口(跳指标时间轴页) */
|
|
|
+.ind-trend-entry {
|
|
|
+ font-size: calc(20rpx * var(--font-scale, 1));
|
|
|
+ color: $brand-primary;
|
|
|
+ font-weight: 500;
|
|
|
+ padding: 4rpx 12rpx;
|
|
|
+ border-radius: 24rpx;
|
|
|
+ background: $brand-primary-soft;
|
|
|
+ flex-shrink: 0;
|
|
|
+}
|
|
|
+/* 分组头「较上次 ±N」 */
|
|
|
+.indicator-group-delta {
|
|
|
+ font-size: calc(22rpx * var(--font-scale, 1));
|
|
|
+ color: $mat-blue-strong;
|
|
|
+ font-weight: 500;
|
|
|
+}
|
|
|
|
|
|
/* ===== 指标输入框状态色(补齐 status-* 全档位) ===== */
|
|
|
|