635 lines
19 KiB
Vue
635 lines
19 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="summary-card">
|
||
<div class="summary-main">
|
||
<div class="summary-left">
|
||
<div class="summary-label">品牌名称</div>
|
||
<div class="summary-brand">{{ clxx.brandName || '未知品牌' }}</div>
|
||
<div class="summary-subline" v-if="clxx.vehicleStyle">
|
||
{{ clxx.vehicleStyle }}
|
||
</div>
|
||
</div>
|
||
<div class="summary-right">
|
||
<div class="summary-label">车架号 VIN</div>
|
||
<div class="summary-vin font-mono">{{ pzVin || clxxVin || '-' }}</div>
|
||
<div class="summary-subline" v-if="clxx.licensePlate">
|
||
车牌号:{{ clxx.licensePlate }}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="summary-meta">
|
||
<div class="meta-line">
|
||
<span class="meta-label">事故总次数</span>
|
||
<span class="meta-value strong">{{ tjxx.claimCount ?? '-' }}</span>
|
||
</div>
|
||
<div class="meta-line">
|
||
<span class="meta-label">总维修金额</span>
|
||
<span class="meta-value strong">{{ tjxx.totalAmount || '-' }}</span>
|
||
</div>
|
||
<div class="meta-line">
|
||
<span class="meta-label">最大单次维修金额</span>
|
||
<span class="meta-value strong">{{ tjxx.largestAmount || '-' }}</span>
|
||
</div>
|
||
<div class="meta-line">
|
||
<span class="meta-label">已结案次数</span>
|
||
<span class="meta-value strong">{{ tjxx.claimCacCount ?? 0 }} 次</span>
|
||
</div>
|
||
<div class="meta-line">
|
||
<span class="meta-label">未结案次数</span>
|
||
<span class="meta-value strong">{{ tjxx.claimUnCacCount ?? 0 }} 次</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<!-- 碰撞记录时间轴 -->
|
||
<div class="detail-card" v-if="pzRecords && pzRecords.length">
|
||
<h4 class="section-title">碰撞出险记录</h4>
|
||
<div class="timeline">
|
||
<div v-for="(rec, idx) in pzRecords" :key="idx" class="timeline-item">
|
||
<div class="timeline-content">
|
||
<div class="row-main">
|
||
<div class="date">{{ rec.date || '-' }}</div>
|
||
<div class="amount">{{ formatFen(rec.serviceMoney) }}</div>
|
||
</div>
|
||
<div class="row-sub">
|
||
<span>{{ rec.accidentType || '出险' }}</span>
|
||
<span class="status">{{ rec.claimStatus || '-' }}</span>
|
||
</div>
|
||
<div class="sub-section" v-if="rec.result && rec.result.length">
|
||
<div class="sub-title">维修明细</div>
|
||
<ul class="sub-list">
|
||
<li v-for="(d, di) in rec.result" :key="di">
|
||
<span class="tag">{{ dangerTypeText(d.dangerSingleType) }}</span>
|
||
<span>{{ d.dangerSingleName }}</span>
|
||
<span v-if="d.dangerSingleNum">×{{ d.dangerSingleNum }}</span>
|
||
<span v-if="d.dangerSingleMoney" class="money">
|
||
({{ formatFen(d.dangerSingleMoney) }})
|
||
</span>
|
||
</li>
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 车况排查(大类) -->
|
||
<div class="detail-card" v-if="ckdlpc">
|
||
<h4 class="section-title">车况排查(大类)</h4>
|
||
<div class="ckdlpc-grid">
|
||
<div v-for="item in ckdlpcList" :key="item.key" class="ckdlpc-item">
|
||
<div class="ckdlpc-name">{{ item.label }}</div>
|
||
<div class="ckdlpc-status" :class="ckLevelClass(item.value)">
|
||
{{ ckLevelText(item.value) }}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 车况明细排查部件:所有分组都展示;如整体无命中则显示一行“暂无车况明细排查记录” -->
|
||
<div class="detail-card" v-if="ckpclbGroups && ckpclbGroups.length">
|
||
<h4 class="section-title">车况明细排查部件</h4>
|
||
<template v-if="hasCkpclbHit">
|
||
<div class="ckpclb-grid">
|
||
<div v-for="group in ckpclbGroups" :key="group && group.key" v-if="group" class="ckpclb-group">
|
||
<div class="ckpclb-title">{{ group.label }}</div>
|
||
<div class="ckpclb-tags" v-if="group.items && group.items.length">
|
||
<span v-for="(p, pi) in group.items" :key="pi" class="part-tag part-tag-hit">
|
||
{{ p.name }}({{ p.type }})
|
||
</span>
|
||
</div>
|
||
<div v-else class="ckpclb-empty">无相关排查记录</div>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
<div v-else class="text-sm text-gray-500">暂无车况明细排查记录</div>
|
||
</div>
|
||
|
||
<!-- 车辆损失方位总结:所有方位按矩阵展示,有受损的高亮显示 -->
|
||
<div class="detail-card" v-if="clfwzjMatrix && clfwzjMatrix.length">
|
||
<h4 class="section-title">车辆损失方位总结</h4>
|
||
<div class="clfwzj-matrix">
|
||
<div v-for="(row, ri) in clfwzjMatrix" :key="ri" class="clfwzj-row">
|
||
<div v-for="(cell, ci) in row" :key="ci" class="clfwzj-cell">
|
||
<div v-if="cell && cell.label"
|
||
:class="['pos-box', cell.value === 1 ? 'pos-box-hit' : 'pos-box-normal']">
|
||
{{ cell.label }}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<p class="mt-4 text-sm text-gray-500">注:红色方位表示该部位存在受损记录,灰色表示当前无受损记录。</p>
|
||
</div>
|
||
|
||
<!-- 车况信息(简要) -->
|
||
<div class="detail-card" v-if="ckxx">
|
||
<h4 class="section-title">车况信息概览</h4>
|
||
<div class="field-grid">
|
||
<div class="field">
|
||
<div class="field-label">是否火烧</div>
|
||
<div class="field-value" :class="flagClass(ckxx.isFire === 1)">
|
||
{{ bool01Text(ckxx.isFire) }}
|
||
</div>
|
||
</div>
|
||
<div class="field">
|
||
<div class="field-label">是否水淹</div>
|
||
<div class="field-value" :class="flagClass(ckxx.isFlood === 1)">
|
||
{{ bool01Text(ckxx.isFlood) }}
|
||
</div>
|
||
</div>
|
||
<div class="field">
|
||
<div class="field-label">是否偷盗</div>
|
||
<div class="field-value" :class="flagClass(ckxx.isTheft === 1)">
|
||
{{ bool01Text(ckxx.isTheft) }}
|
||
</div>
|
||
</div>
|
||
<div class="field">
|
||
<div class="field-label">是否覆盖件损伤</div>
|
||
<div class="field-value" :class="flagClass(ckxx.isPanel === 1)">
|
||
{{ bool01Text(ckxx.isPanel) }}
|
||
</div>
|
||
</div>
|
||
<div class="field">
|
||
<div class="field-label">是否大额赔偿</div>
|
||
<div class="field-value">
|
||
{{ largeCostText(ckxx.isLargeCost) }}
|
||
</div>
|
||
</div>
|
||
<div class="field">
|
||
<div class="field-label">未结案记录</div>
|
||
<div class="field-value">{{ ynUnknown(ckxx.recordIcpending) }}</div>
|
||
</div>
|
||
<div class="field">
|
||
<div class="field-label">注销记录</div>
|
||
<div class="field-value">{{ ynUnknown(ckxx.recordIwriteoff) }}</div>
|
||
</div>
|
||
<div class="field">
|
||
<div class="field-label">拒赔记录</div>
|
||
<div class="field-value">{{ ynUnknown(ckxx.refusalRecord) }}</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 retdata = computed(() => props.data?.retdata || {});
|
||
const hasData = computed(() => !!retdata.value && Object.keys(retdata.value).length > 0);
|
||
|
||
const pzlsmx = computed(() => retdata.value.pzlsmx || {});
|
||
const pzRecords = computed(() => Array.isArray(pzlsmx.value.records) ? pzlsmx.value.records : []);
|
||
const pzVin = computed(() => (pzRecords.value[0]?.vin) || '');
|
||
|
||
const ckdlpc = computed(() => retdata.value.ckdlpc || null);
|
||
const ckxx = computed(() => retdata.value.ckxx || null);
|
||
const ckpclb = computed(() => retdata.value.ckpclb || null);
|
||
const clxx = computed(() => retdata.value.clxx || {});
|
||
const clfwzj = computed(() => retdata.value.clfwzj || null);
|
||
const tjxx = computed(() => retdata.value.tjxx || {});
|
||
|
||
const clxxVin = computed(() => clxx.value.vin || '');
|
||
|
||
const ckdlpcList = computed(() => {
|
||
if (!ckdlpc.value) return [];
|
||
const map = [
|
||
{ key: 'type1', label: '骨架' },
|
||
{ key: 'type2', label: '外观' },
|
||
{ key: 'type3', label: '发动机/变速箱' },
|
||
{ key: 'type4', label: '火烧' },
|
||
{ key: 'type5', label: '水淹' },
|
||
{ key: 'type6', label: '气囊' },
|
||
{ key: 'type7', label: '加强件' },
|
||
];
|
||
return map.map((m) => ({
|
||
key: m.key,
|
||
label: m.label,
|
||
value: ckdlpc.value[m.key],
|
||
}));
|
||
});
|
||
|
||
const hasCkpclb = computed(() => {
|
||
const v = ckpclb.value;
|
||
if (!v) return false;
|
||
return Object.values(v).some((arr) => Array.isArray(arr) && arr.length > 0);
|
||
});
|
||
|
||
const hasCkpclbHit = computed(() => {
|
||
const v = ckpclb.value;
|
||
if (!v) return false;
|
||
return Object.values(v).some((arr) => Array.isArray(arr) && arr.length > 0);
|
||
});
|
||
|
||
const ckpclbGroups = computed(() => {
|
||
if (!ckpclb.value) return [];
|
||
const labelMap = {
|
||
dp: '底盘悬挂',
|
||
fdj: '发动机',
|
||
fspj: '附属配件',
|
||
gj: '骨架',
|
||
hs: '火烧',
|
||
jqj: '加强件',
|
||
qn: '气囊',
|
||
sy: '水淹',
|
||
wg: '外观',
|
||
};
|
||
return Object.entries(ckpclb.value).map(([key, arr]) => ({
|
||
key,
|
||
label: labelMap[key] || key,
|
||
items: Array.isArray(arr) ? arr : [],
|
||
}));
|
||
});
|
||
|
||
const hasClfwzj = computed(() => {
|
||
if (!clfwzj.value) return false;
|
||
return Object.values(clfwzj.value).some((v) => v === 1);
|
||
});
|
||
|
||
const clfwzjMatrix = computed(() => {
|
||
const src = clfwzj.value || {};
|
||
const val = (key) => (src[key] === 1 ? 1 : 0);
|
||
// 按大致方位排布成矩阵,便于理解
|
||
return [
|
||
[
|
||
{ label: '', value: null },
|
||
{ label: '正前方', value: val('正前方') },
|
||
{ label: '', value: null },
|
||
],
|
||
[
|
||
{ label: '前方左侧', value: val('前方左侧') },
|
||
{ label: '顶部', value: val('顶部') },
|
||
{ label: '前方右侧', value: val('前方右侧') },
|
||
],
|
||
[
|
||
{ label: '中间左侧', value: val('中间左侧') },
|
||
{ label: '内部', value: val('内部') },
|
||
{ label: '中间右侧', value: val('中间右侧') },
|
||
],
|
||
[
|
||
{ label: '后方左侧', value: val('后方左侧') },
|
||
{ label: '底部', value: val('底部') },
|
||
{ label: '后方右侧', value: val('后方右侧') },
|
||
],
|
||
[
|
||
{ label: '', value: null },
|
||
{ label: '正后方', value: val('正后方') },
|
||
{ label: '其他', value: val('其他') },
|
||
],
|
||
];
|
||
});
|
||
|
||
const ckLevelText = (v) => {
|
||
const num = Number(v);
|
||
if (Number.isNaN(num)) return '未知';
|
||
if (num === 0) return '正常';
|
||
if (num === 1) return '无法确定';
|
||
if (num === 2) return '疑似异常';
|
||
if (num === 3) return '维保异常';
|
||
if (num === 4) return '碰撞异常';
|
||
return '未知';
|
||
};
|
||
|
||
const ckLevelClass = (v) => {
|
||
const num = Number(v);
|
||
if (num === 0) return 'level-ok';
|
||
if (num === 1) return 'level-unknown';
|
||
if (num === 2) return 'level-suspect';
|
||
if (num === 3) return 'level-maintain';
|
||
if (num === 4) return 'level-collision';
|
||
return 'level-unknown';
|
||
};
|
||
|
||
const formatFen = (val) => {
|
||
if (!val && val !== 0) return '-';
|
||
const n = Number(val);
|
||
if (Number.isNaN(n)) return `${val} 元`;
|
||
const yuan = n / 100;
|
||
return `${yuan.toLocaleString()} 元`;
|
||
};
|
||
|
||
const dangerTypeText = (t) => {
|
||
if (t === '1') return '更换';
|
||
if (t === '2') return '维修';
|
||
if (t === '3') return '材料';
|
||
return '其他';
|
||
};
|
||
|
||
const bool01Text = (v) => {
|
||
if (v === 1) return '是';
|
||
if (v === 0) return '否';
|
||
return '未知';
|
||
};
|
||
|
||
const largeCostText = (v) => {
|
||
if (v === 0) return '无大额赔偿记录';
|
||
if (v === 1) return '有大额赔偿记录';
|
||
if (v === 2) return '无法确定是否大额赔偿';
|
||
return '未知';
|
||
};
|
||
|
||
const ynUnknown = (v) => {
|
||
if (v === '是') return '是';
|
||
if (v === '否') return '否';
|
||
if (v == null) return '未知';
|
||
return v;
|
||
};
|
||
|
||
const flagClass = (flag) => {
|
||
return flag ? 'flag-yes' : 'flag-no';
|
||
};
|
||
|
||
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-4 px-5 py-4 rounded-2xl bg-gradient-to-r from-orange-50 via-amber-50 to-rose-50;
|
||
}
|
||
|
||
.header-left {
|
||
@apply flex flex-col;
|
||
}
|
||
|
||
.header-title {
|
||
@apply text-2xl font-semibold m-0 text-orange-900;
|
||
}
|
||
|
||
.header-desc {
|
||
@apply text-base mt-3 m-0 text-orange-800 opacity-90;
|
||
}
|
||
|
||
.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-1;
|
||
}
|
||
|
||
.summary-right {
|
||
@apply text-right;
|
||
}
|
||
|
||
.summary-label {
|
||
@apply text-sm text-gray-500;
|
||
}
|
||
|
||
.summary-brand {
|
||
@apply text-lg font-semibold text-gray-900;
|
||
}
|
||
|
||
.summary-vin {
|
||
@apply text-base text-gray-900;
|
||
}
|
||
|
||
.summary-subline {
|
||
@apply text-sm text-gray-700 mt-1;
|
||
}
|
||
|
||
.summary-meta {
|
||
@apply space-y-2 text-base text-gray-800;
|
||
}
|
||
|
||
.meta-line {
|
||
@apply flex justify-between items-center;
|
||
}
|
||
|
||
.meta-label {
|
||
@apply text-sm text-gray-600;
|
||
}
|
||
|
||
.meta-value {
|
||
@apply text-base;
|
||
}
|
||
|
||
.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;
|
||
}
|
||
|
||
.timeline {
|
||
@apply mt-2;
|
||
}
|
||
|
||
.timeline-item {
|
||
@apply flex-1 rounded-2xl border border-gray-100 bg-white px-4 py-3;
|
||
}
|
||
|
||
.row-main {
|
||
@apply flex items-baseline justify-between mb-1;
|
||
}
|
||
|
||
.row-main .date {
|
||
@apply text-base font-medium text-gray-900;
|
||
}
|
||
|
||
.row-main .amount {
|
||
@apply text-lg font-semibold text-gray-900;
|
||
}
|
||
|
||
.row-sub {
|
||
@apply flex items-center justify-between text-sm text-gray-600 mt-1;
|
||
}
|
||
|
||
.status {
|
||
@apply text-xs px-2 py-0.5 rounded-full bg-emerald-50 text-emerald-700 font-medium;
|
||
}
|
||
|
||
.sub-section {
|
||
@apply mt-3;
|
||
}
|
||
|
||
.sub-title {
|
||
@apply text-sm font-semibold text-gray-800 mb-1;
|
||
}
|
||
|
||
.sub-list {
|
||
@apply text-sm text-gray-800 space-y-1;
|
||
}
|
||
|
||
.sub-list li {
|
||
@apply flex flex-wrap gap-1;
|
||
}
|
||
|
||
.tag {
|
||
@apply inline-flex items-center px-2 py-0.5 rounded-full bg-orange-50 text-orange-700 text-xs font-medium;
|
||
}
|
||
|
||
.money {
|
||
@apply text-xs text-gray-500;
|
||
}
|
||
|
||
.ckdlpc-grid {
|
||
@apply grid gap-3;
|
||
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
|
||
}
|
||
|
||
.ckdlpc-item {
|
||
@apply p-3 rounded-xl bg-white border border-gray-100;
|
||
}
|
||
|
||
.ckdlpc-name {
|
||
@apply text-sm text-gray-600 mb-1;
|
||
}
|
||
|
||
.ckdlpc-status {
|
||
@apply text-sm font-semibold;
|
||
}
|
||
|
||
.level-ok {
|
||
@apply text-emerald-700;
|
||
}
|
||
|
||
.level-unknown {
|
||
@apply text-gray-600;
|
||
}
|
||
|
||
.level-suspect {
|
||
@apply text-amber-700;
|
||
}
|
||
|
||
.level-maintain {
|
||
@apply text-blue-700;
|
||
}
|
||
|
||
.level-collision {
|
||
@apply text-red-700;
|
||
}
|
||
|
||
.ckpclb-grid {
|
||
@apply grid gap-3;
|
||
}
|
||
|
||
.ckpclb-group {
|
||
@apply p-3 rounded-xl bg-white border border-gray-100;
|
||
}
|
||
|
||
.ckpclb-title {
|
||
@apply text-sm font-semibold text-gray-800 mb-2;
|
||
}
|
||
|
||
.ckpclb-tags {
|
||
@apply flex flex-wrap gap-2;
|
||
}
|
||
|
||
.part-tag {
|
||
@apply inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium;
|
||
}
|
||
|
||
.part-tag-hit {
|
||
@apply bg-sky-50 text-sky-700;
|
||
}
|
||
|
||
.clfwzj-matrix {
|
||
@apply grid gap-2;
|
||
}
|
||
|
||
.clfwzj-row {
|
||
@apply grid gap-2;
|
||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||
}
|
||
|
||
.clfwzj-cell {
|
||
@apply flex items-center justify-center;
|
||
}
|
||
|
||
.pos-box {
|
||
@apply w-full text-center px-3 py-2 rounded-xl text-xs font-medium;
|
||
}
|
||
|
||
.pos-box-hit {
|
||
@apply bg-rose-50 text-rose-700;
|
||
}
|
||
|
||
.pos-box-normal {
|
||
@apply bg-gray-100 text-gray-500;
|
||
}
|
||
|
||
.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;
|
||
}
|
||
|
||
.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>
|