494 lines
16 KiB
Vue
494 lines
16 KiB
Vue
|
|
<template>
|
|||
|
|
<div class="card">
|
|||
|
|
<div class="header-box">
|
|||
|
|
<div class="header-left">
|
|||
|
|
<h3 class="header-title">车辆出险记录核验</h3>
|
|||
|
|
<p class="header-desc">综合车辆出险、脱保、重大事故等信息评估风险</p>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<template v-if="hasData">
|
|||
|
|
<div class="risk-band" :class="riskLevelClass">
|
|||
|
|
<div class="risk-band-label">风险等级</div>
|
|||
|
|
<div class="risk-band-text">{{ riskLevelText }}</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<!-- 顶部车辆与价格概览 -->
|
|||
|
|
<div class="summary-card">
|
|||
|
|
<div class="summary-main">
|
|||
|
|
<div class="summary-left">
|
|||
|
|
<div class="plate" v-if="data.LicensePlate">{{ data.LicensePlate }}</div>
|
|||
|
|
<div class="car-type">{{ data.CarType || '未知车型' }}</div>
|
|||
|
|
</div>
|
|||
|
|
<div class="summary-right">
|
|||
|
|
<div class="summary-label">二手车价格参考</div>
|
|||
|
|
<div class="summary-price">{{ usedCarPriceText }}</div>
|
|||
|
|
<div class="summary-sub">新车购置价:{{ newCarPriceText }}</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
<div class="summary-meta">
|
|||
|
|
<div class="meta-line">
|
|||
|
|
<span>燃料:</span><span class="strong">{{ data.FuelType || '未知' }}</span>
|
|||
|
|
<span class="dot"></span>
|
|||
|
|
<span>发动机号:</span><span class="strong code">{{ data.EngineNumber || '-' }}</span>
|
|||
|
|
</div>
|
|||
|
|
<div class="meta-line">
|
|||
|
|
<span>初登日期:</span><span class="strong">{{ data.DebutDate || '-' }}</span>
|
|||
|
|
<span class="dot"></span>
|
|||
|
|
<span>车龄:</span><span class="strong">{{ carAgeText }}</span>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<!-- 核心风险指标 -->
|
|||
|
|
<div class="detail-card">
|
|||
|
|
<h4 class="section-title">核心风险指标</h4>
|
|||
|
|
<div class="field-grid">
|
|||
|
|
<div class="field">
|
|||
|
|
<div class="field-label">是否高风险车辆</div>
|
|||
|
|
<div class="field-value" :class="flagClass(data.IfHighriskVehicle === '1')">
|
|||
|
|
{{ yesNoText(data.IfHighriskVehicle, '高风险车辆') }}
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
<div class="field">
|
|||
|
|
<div class="field-label">是否营运车辆</div>
|
|||
|
|
<div class="field-value" :class="flagClass(data.IsOperation === '1')">
|
|||
|
|
{{ yesNoText(data.IsOperation, '营运车辆') }}
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
<div class="field">
|
|||
|
|
<div class="field-label">是否投保车损险</div>
|
|||
|
|
<div class="field-value" :class="flagClass(data.IfCarDamage === '1')">
|
|||
|
|
{{ yesNoText(data.IfCarDamage, '已投保车损险', '未投保车损险') }}
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
<div class="field">
|
|||
|
|
<div class="field-label">是否连续投保</div>
|
|||
|
|
<div class="field-value" :class="flagClass(data.IsConInsure === '1')">
|
|||
|
|
{{ yesNoText(data.IsConInsure, '连续投保', '非连续投保') }}
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
<div class="field">
|
|||
|
|
<div class="field-label">历史是否脱保</div>
|
|||
|
|
<div class="field-value" :class="flagClass(data.IfTuoBao === '1')">
|
|||
|
|
{{ yesNoText(data.IfTuoBao, '有脱保记录', '无脱保记录') }}
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
<div class="field">
|
|||
|
|
<div class="field-label">历史最大脱保时间</div>
|
|||
|
|
<div class="field-value">{{ data.TuoBaoTime || '-' }}</div>
|
|||
|
|
</div>
|
|||
|
|
<div class="field">
|
|||
|
|
<div class="field-label">最高车损险损失比例</div>
|
|||
|
|
<div class="field-value">{{ data.CompensationRatioo || '-' }}</div>
|
|||
|
|
</div>
|
|||
|
|
<div class="field">
|
|||
|
|
<div class="field-label">车损险综合评分</div>
|
|||
|
|
<div class="field-value strong">{{ data.Total || '-' }}</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<!-- 出险与事故情况 -->
|
|||
|
|
<div class="detail-card">
|
|||
|
|
<h4 class="section-title">出险与事故情况</h4>
|
|||
|
|
<div class="field-grid">
|
|||
|
|
<div class="field">
|
|||
|
|
<div class="field-label">商业险出险</div>
|
|||
|
|
<div class="field-value">{{ formatDangerCount(data.CommercialPolicyDangerCount, '商业险') }}</div>
|
|||
|
|
</div>
|
|||
|
|
<div class="field">
|
|||
|
|
<div class="field-label">交强险出险</div>
|
|||
|
|
<div class="field-value">{{ formatDangerCount(data.CompulsoryPolicyDangerCount, '交强险') }}</div>
|
|||
|
|
</div>
|
|||
|
|
<div class="field">
|
|||
|
|
<div class="field-label">三者险出险次数</div>
|
|||
|
|
<div class="field-value">{{ formatDangerCount(data.ThreeRisksDangerCount, '三者险') }}</div>
|
|||
|
|
</div>
|
|||
|
|
<div class="field">
|
|||
|
|
<div class="field-label">全损情况</div>
|
|||
|
|
<div class="field-value">{{ totalLossText }}</div>
|
|||
|
|
</div>
|
|||
|
|
<div class="field field-span">
|
|||
|
|
<div class="field-label">重大事故标志</div>
|
|||
|
|
<div class="field-value">{{ formatMajorAccident(data.MajorAccident) }}</div>
|
|||
|
|
</div>
|
|||
|
|
<div class="field">
|
|||
|
|
<div class="field-label">事故次数</div>
|
|||
|
|
<div class="field-value">{{ data.IsMajorAccidentData || '-' }}</div>
|
|||
|
|
</div>
|
|||
|
|
<div class="field">
|
|||
|
|
<div class="field-label">事故等级</div>
|
|||
|
|
<div class="field-value">{{ data.IsMajorAccidentLevel || '-' }}</div>
|
|||
|
|
</div>
|
|||
|
|
<div class="field field-span">
|
|||
|
|
<div class="field-label">损失部位</div>
|
|||
|
|
<div class="field-value">{{ formatLossPart(data.LossPart) }}</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<!-- 保单与责任险可保情况 -->
|
|||
|
|
<div class="detail-card">
|
|||
|
|
<h4 class="section-title">保单与责任险承保情况</h4>
|
|||
|
|
<div class="field-grid">
|
|||
|
|
<div class="field">
|
|||
|
|
<div class="field-label">商业险保单倒计时</div>
|
|||
|
|
<div class="field-value">{{ formatPolicyTime(data.CommercialPolicyTime, '商业险') }}</div>
|
|||
|
|
</div>
|
|||
|
|
<div class="field">
|
|||
|
|
<div class="field-label">交强险保单倒计时</div>
|
|||
|
|
<div class="field-value">{{ formatPolicyTime(data.CompulsoryPolicyTime, '交强险') }}</div>
|
|||
|
|
</div>
|
|||
|
|
<div class="field">
|
|||
|
|
<div class="field-label">商业险过户次数</div>
|
|||
|
|
<div class="field-value">{{ formatTransferCount(data.CommercialPolicyTransferCount) }}</div>
|
|||
|
|
</div>
|
|||
|
|
<div class="field">
|
|||
|
|
<div class="field-label">交强险过户次数</div>
|
|||
|
|
<div class="field-value">{{ formatTransferCount(data.CompulsoryPolicyTransferCount) }}</div>
|
|||
|
|
</div>
|
|||
|
|
<div class="field">
|
|||
|
|
<div class="field-label">是否可投保责任险</div>
|
|||
|
|
<div class="field-value" :class="flagClass(data.IsLiabilityAvailable === 'Y')">
|
|||
|
|
{{ ynText(data.IsLiabilityAvailable, '可投保', '不可投保') }}
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
<div class="field">
|
|||
|
|
<div class="field-label">是否可承保延保</div>
|
|||
|
|
<div class="field-value" :class="flagClass(data.IsExtendAvailable === 'Y')">
|
|||
|
|
{{ ynText(data.IsExtendAvailable, '可承保', '不可承保') }}
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</template>
|
|||
|
|
|
|||
|
|
<div v-else class="empty">
|
|||
|
|
<div class="icon">ℹ️</div>
|
|||
|
|
<div class="title">暂无出险记录</div>
|
|||
|
|
<div class="sub">未查询到车辆出险记录或返回数据为空</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</template>
|
|||
|
|
|
|||
|
|
<script setup>
|
|||
|
|
import { computed } from 'vue';
|
|||
|
|
import { useRiskNotifier } from '@/composables/useRiskNotifier';
|
|||
|
|
|
|||
|
|
const props = defineProps({
|
|||
|
|
data: { type: Object, default: () => ({}) },
|
|||
|
|
params: { type: Object, default: () => ({}) },
|
|||
|
|
apiId: { type: String, default: '' },
|
|||
|
|
index: { type: Number, default: 0 },
|
|||
|
|
notifyRiskStatus: { type: Function, default: () => { } },
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
const data = computed(() => props.data || {});
|
|||
|
|
const hasData = computed(() => !!data.value && Object.keys(data.value).length > 0);
|
|||
|
|
|
|||
|
|
const usedCarPriceText = computed(() => {
|
|||
|
|
const v = data.value.UsedCarPrice;
|
|||
|
|
if (!v) return '-';
|
|||
|
|
return `${v} 元`;
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
const newCarPriceText = computed(() => {
|
|||
|
|
const v = data.value.PurchasePrice;
|
|||
|
|
if (!v) return '-';
|
|||
|
|
return `${v} 元`;
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
const carAgeText = computed(() => {
|
|||
|
|
const m = data.value.CarAge;
|
|||
|
|
if (!m) return '-';
|
|||
|
|
return `${m} 个月`;
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
const totalLossText = computed(() => {
|
|||
|
|
const v = data.value.TotalLoss;
|
|||
|
|
if (v === '1') return '存在全损记录';
|
|||
|
|
if (v === '0') return '无全损记录';
|
|||
|
|
return '-';
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 简单按高风险车辆/重大事故等情况给出一个文字风险等级
|
|||
|
|
const riskLevelText = computed(() => {
|
|||
|
|
if (data.value.IfHighriskVehicle === '1') return '高风险';
|
|||
|
|
if (data.value.IsMajorAccidentLevel && data.value.IsMajorAccidentLevel !== '一般') return '较高风险';
|
|||
|
|
if (data.value.IsMajorAccidentData && data.value.IsMajorAccidentData !== '0') return '有事故记录';
|
|||
|
|
return '风险可控';
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
const riskLevelClass = computed(() => {
|
|||
|
|
const t = riskLevelText.value;
|
|||
|
|
if (t === '高风险') return 'risk-high';
|
|||
|
|
if (t === '较高风险' || t === '有事故记录') return 'risk-mid';
|
|||
|
|
return 'risk-low';
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
const flagClass = (flag) => {
|
|||
|
|
return flag ? 'flag-yes' : 'flag-no';
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const yesNoText = (val, yesText, noText = '否') => {
|
|||
|
|
if (val === '1') return yesText;
|
|||
|
|
if (val === '0') return noText;
|
|||
|
|
return '-';
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const ynText = (val, yesText, noText) => {
|
|||
|
|
if (val === 'Y') return yesText;
|
|||
|
|
if (val === 'N') return noText;
|
|||
|
|
return '-';
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const formatPolicyTime = (val, label) => {
|
|||
|
|
if (!val || val === 'NULL') {
|
|||
|
|
return `当期无${label}保单`;
|
|||
|
|
}
|
|||
|
|
const parts = String(val).split(':');
|
|||
|
|
if (parts.length < 2) return val;
|
|||
|
|
const daysRaw = parts[1];
|
|||
|
|
if (!daysRaw || daysRaw.toLowerCase() === 'null') {
|
|||
|
|
return `${label}保单已过期`;
|
|||
|
|
}
|
|||
|
|
const days = Number(daysRaw);
|
|||
|
|
if (Number.isNaN(days)) return val;
|
|||
|
|
if (days < 0) return `${label}保单已过期`;
|
|||
|
|
return `${label}保单剩余 ${days} 天`;
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const formatDangerCount = (val, label) => {
|
|||
|
|
if (!val) return '-';
|
|||
|
|
const parts = String(val).split(':');
|
|||
|
|
const countRaw = parts[1] ?? '';
|
|||
|
|
if (!countRaw || countRaw.toLowerCase() === 'null') {
|
|||
|
|
return `${label}暂无出险记录`;
|
|||
|
|
}
|
|||
|
|
const count = Number(countRaw);
|
|||
|
|
if (Number.isNaN(count)) return val;
|
|||
|
|
if (count === 0) return `${label}暂无出险记录`;
|
|||
|
|
return `${label}出险 ${count} 次`;
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const formatTransferCount = (val) => {
|
|||
|
|
if (!val) return '-';
|
|||
|
|
const parts = String(val).split(':');
|
|||
|
|
const countRaw = parts[1] ?? '';
|
|||
|
|
if (!countRaw || countRaw.toLowerCase() === 'null') return '-';
|
|||
|
|
const count = Number(countRaw);
|
|||
|
|
if (Number.isNaN(count)) return val;
|
|||
|
|
return `${count} 次`;
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const formatMajorAccident = (val) => {
|
|||
|
|
if (!val) return '-';
|
|||
|
|
const map = {
|
|||
|
|
A: '碰撞',
|
|||
|
|
B: '火自燃',
|
|||
|
|
C: '水淹',
|
|||
|
|
D: '盗抢',
|
|||
|
|
};
|
|||
|
|
const list = [];
|
|||
|
|
String(val)
|
|||
|
|
.split(',')
|
|||
|
|
.forEach((pair) => {
|
|||
|
|
const [k, v] = pair.split(':');
|
|||
|
|
if (v === '1' && map[k]) {
|
|||
|
|
list.push(map[k]);
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
if (!list.length) return '无重大事故记录';
|
|||
|
|
return `重大事故类型:${list.join('、')}`;
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const formatLossPart = (val) => {
|
|||
|
|
if (!val) return '-';
|
|||
|
|
const partMap = {
|
|||
|
|
1: '正前方',
|
|||
|
|
2: '正后方',
|
|||
|
|
3: '顶部',
|
|||
|
|
4: '底部',
|
|||
|
|
5: '前方左侧',
|
|||
|
|
6: '后方左侧',
|
|||
|
|
7: '中间左侧',
|
|||
|
|
8: '前方右侧',
|
|||
|
|
9: '后方右侧',
|
|||
|
|
10: '中间右侧',
|
|||
|
|
11: '内部',
|
|||
|
|
12: '其它',
|
|||
|
|
13: '不详',
|
|||
|
|
};
|
|||
|
|
const items = [];
|
|||
|
|
String(val)
|
|||
|
|
.split(',')
|
|||
|
|
.forEach((pair) => {
|
|||
|
|
const [kRaw, vRaw] = pair.split(':');
|
|||
|
|
const key = Number(kRaw);
|
|||
|
|
const count = Number(vRaw);
|
|||
|
|
if (!Number.isNaN(key) && !Number.isNaN(count) && count > 0) {
|
|||
|
|
const label = partMap[key] || `部位${key}`;
|
|||
|
|
items.push(`${label}${count}次`);
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
if (!items.length) return '暂无损失部位信息';
|
|||
|
|
return items.join('、');
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const riskScore = computed(() => 100);
|
|||
|
|
useRiskNotifier(props, riskScore);
|
|||
|
|
defineExpose({ riskScore });
|
|||
|
|
</script>
|
|||
|
|
|
|||
|
|
<style scoped>
|
|||
|
|
.card {
|
|||
|
|
@apply bg-white rounded-2xl p-6 shadow-sm border border-gray-100;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.header-box {
|
|||
|
|
@apply flex items-center mb-3 px-5 py-4 rounded-2xl bg-gradient-to-r from-rose-50 via-orange-50 to-amber-50;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.header-left {
|
|||
|
|
@apply flex flex-col;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.header-title {
|
|||
|
|
@apply text-2xl font-semibold m-0 text-rose-900;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.header-desc {
|
|||
|
|
@apply text-base mt-3 m-0 text-rose-800 opacity-90;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.risk-band {
|
|||
|
|
@apply mb-4 px-4 py-3 rounded-2xl flex items-center justify-between;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.risk-band.risk-high {
|
|||
|
|
@apply bg-red-50 border border-red-100;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.risk-band.risk-mid {
|
|||
|
|
@apply bg-amber-50 border border-amber-100;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.risk-band.risk-low {
|
|||
|
|
@apply bg-emerald-50 border border-emerald-100;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.risk-band-label {
|
|||
|
|
@apply text-sm text-gray-600;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.risk-band-text {
|
|||
|
|
@apply text-xl font-bold;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.summary-card {
|
|||
|
|
@apply rounded-2xl border border-amber-100 bg-amber-50/60 px-5 py-4 mb-4;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.summary-main {
|
|||
|
|
@apply flex items-start justify-between mb-3 gap-4;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.summary-left {
|
|||
|
|
@apply flex flex-col gap-2;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.plate {
|
|||
|
|
@apply inline-flex items-center px-4 py-2 rounded-full bg-slate-900 text-white text-xl font-semibold tracking-widest;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.car-type {
|
|||
|
|
@apply text-lg font-medium text-gray-800;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.summary-right {
|
|||
|
|
@apply text-right;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.summary-label {
|
|||
|
|
@apply text-sm text-gray-500;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.summary-price {
|
|||
|
|
@apply text-2xl font-bold text-amber-800 mt-1;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.summary-sub {
|
|||
|
|
@apply text-sm text-amber-700 opacity-90;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.summary-meta {
|
|||
|
|
@apply space-y-1 text-base text-gray-800;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.meta-line {
|
|||
|
|
@apply flex flex-wrap items-center gap-2;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.meta-line .dot {
|
|||
|
|
@apply w-1 h-1 rounded-full bg-gray-400;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.strong {
|
|||
|
|
@apply font-semibold;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.code {
|
|||
|
|
@apply font-mono tracking-wide;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.detail-card {
|
|||
|
|
@apply rounded-2xl border border-gray-100 bg-gray-50/60 px-5 py-4 mb-4;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.section-title {
|
|||
|
|
@apply text-base font-semibold text-gray-800 mb-3;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.field-grid {
|
|||
|
|
@apply grid gap-y-3 gap-x-6;
|
|||
|
|
grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.field-label {
|
|||
|
|
@apply text-sm text-gray-500 mb-1;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.field-value {
|
|||
|
|
@apply text-base text-gray-900;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.field-span {
|
|||
|
|
grid-column: 1 / -1;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.flag-yes {
|
|||
|
|
@apply text-red-700;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.flag-no {
|
|||
|
|
@apply text-emerald-700;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.empty {
|
|||
|
|
@apply text-center py-10 text-gray-500;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.empty .icon {
|
|||
|
|
@apply text-3xl mb-2;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.empty .title {
|
|||
|
|
@apply text-lg font-medium mb-1;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.empty .sub {
|
|||
|
|
@apply text-sm;
|
|||
|
|
}
|
|||
|
|
</style>
|