fix(F006): fix rating/score page runtime crash from undefined supplierLevel/score

This commit is contained in:
zou 2026-08-22 16:24:25 +08:00
parent 512111af3e
commit 6571eadc20
2 changed files with 12 additions and 17 deletions

View File

@ -62,17 +62,17 @@ const filtered = computed(() => {
return rs return rs
}) })
// 等级统计 + 分数均值 // 统计 + 平均分 (用可选链防御 API 字段缺失)
const stats = computed(() => { const stats = computed(() => {
const rs = filtered.value const rs = filtered.value
const aCount = rs.filter(r => r.supplierLevel.startsWith('A')).length const aCount = rs.filter(r => r.supplierLevel?.startsWith('A') ?? false).length
const avgFinal = rs.length ? Math.round(rs.reduce((s, r) => s + r.finalScore, 0) / rs.length) : 0 const avgFinal = rs.length ? Math.round(rs.reduce((s, r) => s + (r.finalScore || 0), 0) / rs.length) : 0
const avgClient = rs.length ? Math.round(rs.reduce((s, r) => s + r.clientScore, 0) / rs.length) : 0 const avgClient = rs.length ? Math.round(rs.reduce((s, r) => s + (r.clientScore || 0), 0) / rs.length) : 0
return [ return [
{ label: '评级总数', value: rs.length, icon: 'Medal', color: 'var(--brand)' }, { label: '评级总数', value: rs.length, icon: 'Medal', color: 'var(--brand)' },
{ label: 'A 战略级', value: aCount, icon: 'Top', color: 'var(--green)' }, { label: 'A 战略级', value: aCount, icon: 'Top', color: 'var(--green)' },
{ label: '平均最终得分', value: avgFinal, icon: 'DataAnalysis', color: 'var(--amber)' }, { label: '平均最终得分', value: avgFinal, icon: 'Trophy', color: 'var(--amber)' },
{ label: '平均客户评价', value: avgClient, icon: 'ChatLineSquare', color: 'var(--purple)' } { label: '平均客户评价', value: avgClient, icon: 'Star', color: 'var(--purple)' }
] ]
}) })
@ -98,12 +98,11 @@ async function onBatchExport() {
} }
function onExport() { ElMessage.success(`已发起导出 (前端示例, 共 ${filtered.value.length} 条)`) } function onExport() { ElMessage.success(`已发起导出 (前端示例, 共 ${filtered.value.length} 条)`) }
// 等级 st-pill 颜色映射
function levelPill(r: RatingRow) { function levelPill(r: RatingRow) {
if (r.supplierLevel.startsWith('A')) return { label: r.supplierLevel, color: 'var(--st-ok-fg)', bg: 'var(--st-ok-bg)' } if (r.supplierLevel?.startsWith('A')) return { label: r.supplierLevel, color: 'var(--st-ok-fg)', bg: 'var(--st-ok-bg)' }
if (r.supplierLevel.startsWith('B')) return { label: r.supplierLevel, color: 'var(--brand)', bg: 'var(--brand-soft)' } if (r.supplierLevel?.startsWith('B')) return { label: r.supplierLevel, color: 'var(--brand)', bg: 'var(--brand-soft)' }
if (r.supplierLevel.startsWith('C')) return { label: r.supplierLevel, color: 'var(--st-warn-fg)', bg: 'var(--st-warn-bg)' } if (r.supplierLevel?.startsWith('C')) return { label: r.supplierLevel, color: 'var(--st-warn-fg)', bg: 'var(--st-warn-bg)' }
return { label: r.supplierLevel, color: 'var(--st-err-fg)', bg: 'var(--st-err-bg)' } return { label: r.supplierLevel ?? '-', color: 'var(--st-err-fg)', bg: 'var(--st-err-bg)' }
} }
onMounted(load) onMounted(load)
@ -115,7 +114,6 @@ onMounted(load)
<el-button @click="onExport">导出明细</el-button> <el-button @click="onExport">导出明细</el-button>
</PageHeader> </PageHeader>
<!-- 顶部统计卡 -->
<div class="stats-grid"> <div class="stats-grid">
<div v-for="(s, i) in stats" :key="i" class="stat-card"> <div v-for="(s, i) in stats" :key="i" class="stat-card">
<div class="stat-icon" :style="{ color: s.color, background: 'var(--brand-soft)' }"> <div class="stat-icon" :style="{ color: s.color, background: 'var(--brand-soft)' }">
@ -131,7 +129,6 @@ onMounted(load)
<SearchBar v-model:filters="filters" :fields="searchFields" @search="onSearch" @reset="onReset" /> <SearchBar v-model:filters="filters" :fields="searchFields" @search="onSearch" @reset="onReset" />
<div class="rr-list-card"> <div class="rr-list-card">
<!-- 批量操作栏 -->
<div class="batch-bar" :class="{ 'has-selection': selection.length > 0 }"> <div class="batch-bar" :class="{ 'has-selection': selection.length > 0 }">
<span v-if="selection.length > 0" class="batch-count">已选 <strong>{{ selection.length }}</strong> 条评级</span> <span v-if="selection.length > 0" class="batch-count">已选 <strong>{{ selection.length }}</strong> 条评级</span>
<span v-else class="batch-hint">勾选后可批量导出</span> <span v-else class="batch-hint">勾选后可批量导出</span>
@ -147,7 +144,7 @@ onMounted(load)
@limit-change="(l: number) => { limit = l; page = 1; load() }"> @limit-change="(l: number) => { limit = l; page = 1; load() }">
<template #level="{ row }"> <template #level="{ row }">
<span class="st-pill" :style="{ color: levelPill(row).color, background: levelPill(row).bg }"> <span class="st-pill" :style="{ color: levelPill(row).color, background: levelPill(row).bg }">
<span class="st-dot" /><span class="truncate-text">{{ levelPill(row).label }}</span> <span class="st-dot"></span><span class="truncate-text">{{ levelPill(row).label }}</span>
</span> </span>
</template> </template>
<template #award="{ row }"> <template #award="{ row }">
@ -174,8 +171,6 @@ onMounted(load)
<style scoped lang="scss"> <style scoped lang="scss">
.rr-page { padding: 16px; } .rr-page { padding: 16px; }
/* 顶部统计卡 */
.stats-grid { .stats-grid {
display: grid; grid-template-columns: repeat(4, 1fr); gap: 12px; display: grid; grid-template-columns: repeat(4, 1fr); gap: 12px;
margin-bottom: 16px; margin-bottom: 16px;

View File

@ -67,7 +67,7 @@ const stats = computed(() => {
const done = rs.filter(r => r.status === 1).length const done = rs.filter(r => r.status === 1).length
const pending = rs.length - done const pending = rs.length - done
const avg = rs.filter(r => r.status === 1).length const avg = rs.filter(r => r.status === 1).length
? Math.round(rs.filter(r => r.status === 1).reduce((s, r) => s + r.score, 0) / rs.filter(r => r.status === 1).length) ? Math.round(rs.filter(r => r.status === 1).reduce((s, r) => s + (r.score || 0), 0) / rs.filter(r => r.status === 1).length)
: 0 : 0
return [ return [
{ label: '评分记录', value: rs.length, icon: 'Document', color: 'var(--brand)' }, { label: '评分记录', value: rs.length, icon: 'Document', color: 'var(--brand)' },