This commit is contained in:
2026-02-12 19:48:28 +08:00
parent 2584e24fa1
commit 94c0af4039
62 changed files with 9597 additions and 127 deletions

634
src/ui/CQCXGP00W.vue Normal file
View File

@@ -0,0 +1,634 @@
<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>