f
This commit is contained in:
@@ -360,7 +360,7 @@ const featureMap = {
|
||||
|
||||
IVYZ0S0D:{
|
||||
name: "劳动仲裁信息查询(个人版)",
|
||||
component: defineAsyncComponent(() => import("@/ui/IVYZ0S0D.vue")),
|
||||
component: defineAsyncComponent(() => import("@/ui/CIVYZ0S0D.vue")),
|
||||
remark: '劳动仲裁信息查询(个人版)用于查询个人在劳动仲裁方面的信息,包括劳动仲裁案件数量、劳动仲裁案件类型、劳动仲裁案件结果等。',
|
||||
},
|
||||
|
||||
@@ -410,6 +410,175 @@ const featureMap = {
|
||||
// name: "规则风险提示",
|
||||
// component: defineAsyncComponent(() => import("@/ui/cDwBG8B4D/components/MultcourtInfosection.vue")),
|
||||
// }
|
||||
|
||||
// 法院被执行人限高版
|
||||
CFLX3A9B: {
|
||||
name: "法院被执行人限高版",
|
||||
component: defineAsyncComponent(() => import("@/ui/CFLX3A9B.vue")),
|
||||
remark: '法院被执行人限高版用于查询个人在法院的被执行人信息,包括被执行人数量、被执行人类型、被执行人结果等。',
|
||||
},
|
||||
|
||||
CIVYZ6M8P: {
|
||||
name: "职业资格证书查询",
|
||||
component: defineAsyncComponent(() => import("@/ui/CIVYZ6M8P.vue")),
|
||||
remark: '职业资格证书查询展示查询到的职业资格考试名称、级别、专业及通过日期等信息。查询结果来源于相关职业技能鉴定机构,仅供参考。',
|
||||
},
|
||||
CIVYZ7F3A: {
|
||||
name: "学历信息查询B",
|
||||
component: defineAsyncComponent(() => import("@/ui/CIVYZ7F3A.vue")),
|
||||
remark: '学历信息查询B展示学生毕业院校排名、专业、学历层次、学习形式、毕业时间、学校属性(985/211/双一流)等多维度学历信息。查询结果来源于教育部门等权威机构,仅供参考。',
|
||||
},
|
||||
CIVYZ9K7F: {
|
||||
name: "公安二要素认证即时版",
|
||||
component: defineAsyncComponent(() => import("@/ui/CIVYZ9K7F.vue")),
|
||||
remark: '公安二要素认证即时版用于核验姓名与身份证号是否一致,同时返回涉诈风险评估等级。查询结果来源于公安部门等权威机构,仅供参考。',
|
||||
},
|
||||
CIVYZA1B3: {
|
||||
name: "公安三要素即时版",
|
||||
component: defineAsyncComponent(() => import("@/ui/CIVYZA1B3.vue")),
|
||||
remark: '公安三要素即时版通过比对人像与身份证信息判断是否为同一人,返回相似度分值及个人基本信息。查询结果来源于公安部门等权威机构,仅供参考。',
|
||||
},
|
||||
CJRZQ0B6Y: {
|
||||
name: "银行卡黑名单(实时)",
|
||||
component: defineAsyncComponent(() => import("@/ui/CJRZQ0B6Y.vue")),
|
||||
remark: '银行卡黑名单(实时)查询银行卡是否命中不良持卡人、涉案卡片、交易欺诈卡片、线上/线下卡号黑名单等各类风险名单。查询结果仅供参考,具体信息以相关机构官方记录为准。',
|
||||
},
|
||||
CJRZQACAB: {
|
||||
name: "银行卡四要素验证(详版)",
|
||||
component: defineAsyncComponent(() => import("@/ui/CJRZQACAB.vue")),
|
||||
remark: '银行卡四要素验证(详版)核验姓名、身份证号、银行卡号与预留手机号是否匹配,返回详细的验证状态说明。',
|
||||
},
|
||||
CQCXG1H7Y: {
|
||||
name: "车辆过户简版查询",
|
||||
component: defineAsyncComponent(() => import("@/ui/CQCXG1H7Y.vue")),
|
||||
remark: '车辆过户简版查询用于查看车辆最近是否发生过户及累计过户次数,数据来源于车辆管理部门等权威机构,仅供参考。',
|
||||
},
|
||||
CQCXG1U4U: {
|
||||
name: "车辆里程记录(混合查询)",
|
||||
component: defineAsyncComponent(() => import("@/ui/CQCXG1U4U.vue")),
|
||||
remark: '车辆里程记录(混合查询)综合诊断与维保记录,展示车辆里程变化与是否存在调表嫌疑,数据来源于车辆维保及检测机构,仅供参考。',
|
||||
},
|
||||
CQCXG3Y6B: {
|
||||
name: "车辆维保简版查询",
|
||||
component: defineAsyncComponent(() => import("@/ui/CQCXG3Y6B.vue")),
|
||||
remark: '车辆维保简版查询按时间轴展示维保记录,包含保养与更换材料明细,数据来源于车辆维保机构,仅供参考。',
|
||||
},
|
||||
CQCXG3Z3L: {
|
||||
name: "车辆维保详细版查询",
|
||||
component: defineAsyncComponent(() => import("@/ui/CQCXG3Z3L.vue")),
|
||||
remark: '车辆维保详细版查询展示品牌、车架号等基本信息及每次维保的详细内容,数据来源于车辆维保机构,仅供参考。',
|
||||
},
|
||||
CQCXG4D2E: {
|
||||
name: "名下车辆数量查询",
|
||||
component: defineAsyncComponent(() => import("@/ui/CQCXG4D2E.vue")),
|
||||
remark: '名下车辆数量查询展示查询对象名下登记的车辆数量及车牌、车牌颜色、车辆类型等基本信息,数据来源于车辆管理部门等权威机构,仅供参考。',
|
||||
},
|
||||
CQCXG4I1Z: {
|
||||
name: "车辆过户详版查询",
|
||||
component: defineAsyncComponent(() => import("@/ui/CQCXG4I1Z.vue")),
|
||||
remark: '车辆过户详版查询按时间轴展示每一次车辆过户的车牌与地区变更情况,数据来源于车辆管理部门等权威机构,仅供参考。',
|
||||
},
|
||||
CQCXG5U0Z: {
|
||||
name: "车辆静态信息查询",
|
||||
component: defineAsyncComponent(() => import("@/ui/CQCXG5U0Z.vue")),
|
||||
remark: '车辆静态信息查询展示车辆生产日期、排放标准、燃料类型、发动机型号、生产企业名称等核心静态信息,数据来源于车辆管理部门等权威机构,仅供参考。',
|
||||
},
|
||||
CQCXG6B4E: {
|
||||
name: "车辆出险记录核验",
|
||||
component: defineAsyncComponent(() => import("@/ui/CQCXG6B4E.vue")),
|
||||
remark: '车辆出险记录核验综合车辆出险、脱保、重大事故等信息评估风险等级,并给出二手车价格参考,数据来源于保险行业信息平台,仅供参考。',
|
||||
},
|
||||
CQCXGGB2Q: {
|
||||
name: "车辆二要素核验V1",
|
||||
component: defineAsyncComponent(() => import("@/ui/CQCXGGB2Q.vue")),
|
||||
remark: '车辆二要素核验V1用于校验人员姓名与车辆号牌是否匹配,数据来源于车辆管理部门等权威机构,仅供参考。',
|
||||
},
|
||||
CQCXGP00W: {
|
||||
name: "车辆出险详版查询",
|
||||
component: defineAsyncComponent(() => import("@/ui/CQCXGP00W.vue")),
|
||||
remark: '车辆出险详版查询展示多维出险记录、碰撞部位、车辆配件类别及车况信息,辅助评估车辆风险,数据来源于保险行业信息平台,仅供参考。',
|
||||
},
|
||||
CQCXGY7F2: {
|
||||
name: "二手车VIN估值",
|
||||
component: defineAsyncComponent(() => import("@/ui/CQCXGY7F2.vue")),
|
||||
remark: '二手车VIN估值基于车型、排量、排放标准等信息给出参考估值,实际价格以市场为准,仅供参考。',
|
||||
},
|
||||
CQCXGYTS2: {
|
||||
name: "车辆二要素核验V2",
|
||||
component: defineAsyncComponent(() => import("@/ui/CQCXGYTS2.vue")),
|
||||
remark: '车辆二要素核验V2展示人员与车辆的详细匹配结果及相关说明,数据来源于车辆管理部门等权威机构,仅供参考。',
|
||||
},
|
||||
CQVehicleGeneric: {
|
||||
name: "车辆通用查询",
|
||||
component: defineAsyncComponent(() => import("@/ui/CQVehicleGeneric.vue")),
|
||||
remark: '车辆通用查询用于展示各类车辆相关接口的原始返回数据,便于调试和查看。',
|
||||
},
|
||||
CQYGL2S0W: {
|
||||
name: "失信被执行人(企业,个人)",
|
||||
component: defineAsyncComponent(() => import("@/ui/CQYGL2S0W.vue")),
|
||||
remark: '失信被执行人查询用于识别个人或企业的严重违约风险,展示命中最高法院公布的失信被执行人信息,数据来源于法院公开信息,仅供参考。',
|
||||
},
|
||||
CQYGL5F6A: {
|
||||
name: "名下企业关联",
|
||||
component: defineAsyncComponent(() => import("@/ui/CQYGL5F6A.vue")),
|
||||
remark: '名下企业关联展示查询对象作为法人、股东或高管关联的企业信息,包括企业状态、成立日期、注册资本、行业等多维度数据,仅供参考。',
|
||||
},
|
||||
CQYGL66SL: {
|
||||
name: "全国企业司法模型服务查询新详版",
|
||||
component: defineAsyncComponent(() => import("@/ui/CQYGL66SL.vue")),
|
||||
remark: '全国企业司法模型服务查询新详版展示企业在全国法院公开信息中的民事、刑事、行政、执行、破产、保全等多类司法案件情况,数据来源于法院公开信息,仅供参考。',
|
||||
},
|
||||
CYYSY3M8S: {
|
||||
name: "运营商二要素V即时版",
|
||||
component: defineAsyncComponent(() => import("@/ui/CYYSY3M8S.vue")),
|
||||
remark: '运营商二要素V即时版用于核验手机号与姓名是否一致,返回核验结果及计费状态,数据来源于运营商系统,仅供参考。',
|
||||
},
|
||||
CYYSY6F2B: {
|
||||
name: "手机消费区间验证",
|
||||
component: defineAsyncComponent(() => import("@/ui/CYYSY6F2B.vue")),
|
||||
remark: '手机消费区间验证根据运营商数据评估消费能力档位,同时展示运营商及携号转网信息,数据来源于运营商系统,仅供参考。',
|
||||
},
|
||||
CYYSY9E4A: {
|
||||
name: "天远手机号码归属地核验A",
|
||||
component: defineAsyncComponent(() => import("@/ui/CYYSY9E4A.vue")),
|
||||
remark: '天远手机号码归属地核验A展示手机号码的省市、运营商、区号及邮编等归属信息,数据来源于运营商系统,仅供参考。',
|
||||
},
|
||||
CYYSYE7V5: {
|
||||
name: "手机在网状态V即时版",
|
||||
component: defineAsyncComponent(() => import("@/ui/CYYSYE7V5.vue")),
|
||||
remark: '手机在网状态V即时版用于判断号码当前是否在网可用,返回在网状态及运营商信息,数据来源于运营商系统,仅供参考。',
|
||||
},
|
||||
CYYSYF2T7: {
|
||||
name: "号码二次放号V即时版",
|
||||
component: defineAsyncComponent(() => import("@/ui/CYYSYF2T7.vue")),
|
||||
remark: '号码二次放号V即时版用于判断该手机号是否为二次放号,返回核验结果及运营商信息,数据来源于运营商系统,仅供参考。',
|
||||
},
|
||||
CYYSYK8R3: {
|
||||
name: "手机空号检测V即时版",
|
||||
component: defineAsyncComponent(() => import("@/ui/CYYSYK8R3.vue")),
|
||||
remark: '手机空号检测V即时版用于判断号码是空号、实号还是沉默号或风险号,返回号码状态及归属地信息,数据来源于运营商系统,仅供参考。',
|
||||
},
|
||||
CYYSYK9R4: {
|
||||
name: "全网手机三要素验证",
|
||||
component: defineAsyncComponent(() => import("@/ui/CYYSYK9R4.vue")),
|
||||
remark: '全网手机三要素验证用于核验手机号、身份证号与姓名是否一致,返回验证结果,数据来源于运营商系统,仅供参考。',
|
||||
},
|
||||
CYYSYP0T4: {
|
||||
name: "手机号码在网时长V即时版",
|
||||
component: defineAsyncComponent(() => import("@/ui/CYYSYP0T4.vue")),
|
||||
remark: '手机号码在网时长V即时版展示号码在当前运营商下的在网时长区间,数据来源于运营商系统,仅供参考。',
|
||||
},
|
||||
CYYSYS9W1: {
|
||||
name: "手机携号转网V即时版",
|
||||
component: defineAsyncComponent(() => import("@/ui/CYYSYS9W1.vue")),
|
||||
remark: '手机携号转网V即时版查询号码是否发生携号转网及前后运营商变更情况,数据来源于运营商系统,仅供参考。',
|
||||
},
|
||||
QCXG5F3A: {
|
||||
name: "名下车辆车牌查询B",
|
||||
component: defineAsyncComponent(() => import("@/ui/QCXG5F3A.vue")),
|
||||
remark: '名下车辆车牌查询B展示查询对象名下登记的车辆车牌号、车牌颜色、车辆类型等信息,数据来源于车辆管理部门等权威机构,仅供参考。',
|
||||
},
|
||||
|
||||
};
|
||||
|
||||
const maskValue = computed(() => {
|
||||
@@ -500,6 +669,7 @@ const featureRiskLevels = {
|
||||
'JRZQ3C9R': 7, // 支付行为指数
|
||||
'YYSY7D3E': 5, // 手机携号转网
|
||||
'YYSY8B1C': 5, // 手机在网时长
|
||||
'CFLX3A9B': 5, // 法院被执行人限高版
|
||||
|
||||
// 🟡 中风险类 - 权重 5
|
||||
'QYGL3F8E': 5, // 人企关系加强版
|
||||
@@ -556,6 +726,40 @@ const featureRiskLevels = {
|
||||
'CQYGL3F8E_Punishment': 7,
|
||||
'CQYGL3F8E_Abnormal': 6,
|
||||
'CQYGL3F8E_TaxRisk': 7,
|
||||
|
||||
// 新增模块 - 默认权重3(待调整)
|
||||
'CIVYZ6M8P': 3,
|
||||
'CIVYZ7F3A': 3,
|
||||
'CIVYZ9K7F': 3,
|
||||
'CIVYZA1B3': 3,
|
||||
'CJRZQ0B6Y': 3,
|
||||
'CJRZQACAB': 3,
|
||||
'CQCXG1H7Y': 3,
|
||||
'CQCXG1U4U': 3,
|
||||
'CQCXG3Y6B': 3,
|
||||
'CQCXG3Z3L': 3,
|
||||
'CQCXG4D2E': 3,
|
||||
'CQCXG4I1Z': 3,
|
||||
'CQCXG5U0Z': 3,
|
||||
'CQCXG6B4E': 3,
|
||||
'CQCXGGB2Q': 3,
|
||||
'CQCXGP00W': 3,
|
||||
'CQCXGY7F2': 3,
|
||||
'CQCXGYTS2': 3,
|
||||
'CQVehicleGeneric': 3,
|
||||
'CQYGL2S0W': 3,
|
||||
'CQYGL5F6A': 3,
|
||||
'CQYGL66SL': 3,
|
||||
'CYYSY3M8S': 3,
|
||||
'CYYSY6F2B': 3,
|
||||
'CYYSY9E4A': 3,
|
||||
'CYYSYE7V5': 3,
|
||||
'CYYSYF2T7': 3,
|
||||
'CYYSYK8R3': 3,
|
||||
'CYYSYK9R4': 3,
|
||||
'CYYSYP0T4': 3,
|
||||
'CYYSYS9W1': 3,
|
||||
'QCXG5F3A': 3,
|
||||
};
|
||||
|
||||
// 处理数据拆分(支持DWBG8B4D、DWBG6A2C、CJRZQ5E9F和CQYGL3F8E)
|
||||
|
||||
@@ -14,9 +14,9 @@
|
||||
<OverdueRiskSection :overdue-risk-product="data.overdueRiskProduct" />
|
||||
|
||||
<!-- 法院曝光台信息 - 暂时隐藏 -->
|
||||
<!-- <MultCourtInfoSection
|
||||
<MultCourtInfoSection
|
||||
:mult-court-info="data.multCourtInfo"
|
||||
/> -->
|
||||
/>
|
||||
|
||||
<!-- 借贷评估 -->
|
||||
<LoanEvaluationSection :loan-evaluation-verification-detail="data.loanEvaluationVerificationDetail" />
|
||||
|
||||
309
src/ui/CFLX3A9B.vue
Normal file
309
src/ui/CFLX3A9B.vue
Normal file
@@ -0,0 +1,309 @@
|
||||
<template>
|
||||
<div class="card shadow-sm rounded-xl overflow-hidden p-4">
|
||||
<div class="border border-[#EEEEEE] rounded-xl">
|
||||
<!-- 标题 -->
|
||||
<div class="flex items-center mb-3 p-4">
|
||||
<div class="w-8 h-8 flex items-center justify-center mr-2">
|
||||
<img src="@/assets/images/report/ssfxztgl.png" alt="限高/失信风险" class="w-8 h-8 object-contain" />
|
||||
</div>
|
||||
<span class="font-bold text-gray-800">限高 / 失信风险</span>
|
||||
</div>
|
||||
|
||||
<!-- 风险整体概览 -->
|
||||
<div class="px-4 mb-4">
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-3">
|
||||
<div class="p-3 rounded-lg bg-[#EB3C3C1A] border border-[#EB3C3C4D]">
|
||||
<div class="text-xs text-gray-600 mb-1">规则最终决策</div>
|
||||
<div class="text-lg font-semibold" :class="finalDecision === 'Accept' ? 'text-green-600' : 'text-red-600'">
|
||||
{{ finalDecisionText }}
|
||||
</div>
|
||||
<div v-if="finalWeight" class="text-xs text-gray-500 mt-1">
|
||||
风险权重:{{ finalWeight }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="p-3 rounded-lg bg-[#D6943E1A] border border-[#D6943E4D]">
|
||||
<div class="text-xs text-gray-600 mb-1">失信记录</div>
|
||||
<div class="text-lg font-semibold text-orange-600">
|
||||
{{ sxList.length }} 条
|
||||
</div>
|
||||
<div class="text-xs text-gray-500 mt-1">
|
||||
命中失信被执行人公告
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="p-3 rounded-lg bg-[#2B79EE1A] border border-[#2B79EE4D]">
|
||||
<div class="text-xs text-gray-600 mb-1">限高记录</div>
|
||||
<div class="text-lg font-semibold text-blue-600">
|
||||
{{ xgList.length }} 条
|
||||
</div>
|
||||
<div class="text-xs text-gray-500 mt-1">
|
||||
{{ recentLimitDesc }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 失信记录 -->
|
||||
<div v-if="sxList.length" class="px-4 mb-4">
|
||||
<LTitle title="失信被执行人记录" />
|
||||
<div class="space-y-3">
|
||||
<div v-for="(item, index) in sxList" :key="'sx_' + index" class="case-wrapper">
|
||||
<div class="bg-white rounded-xl overflow-hidden border px-4 pt-3 border-[#DDDDDD]">
|
||||
<div class="cursor-pointer relative" @click="toggleExpand('sx', index)">
|
||||
<div class="flex items-center">
|
||||
<div class="font-bold text-base text-[#333333] mr-2">
|
||||
{{ item.casecode || '暂无案号' }}
|
||||
</div>
|
||||
<span class="px-2 py-1 text-xs rounded-md font-medium bg-[#F9ECEC] text-[#EB3C3C]">
|
||||
{{ item.datatype || '失信被执行人' }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="pb-2 text-sm">
|
||||
<span class="text-[#666666]">立案:</span>
|
||||
<span class="text-[#333333]">{{ item.regdate || '-' }}</span>
|
||||
</div>
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="px-2 py-1 text-xs rounded-md font-medium bg-[#EB3C3C1A] text-[#EB3C3C]">
|
||||
{{ item.signalDesc || '主体存在失信记录' }}
|
||||
</span>
|
||||
<div class="flex items-center text-xs text-gray-500">
|
||||
<span class="mr-1">
|
||||
{{ isExpanded('sx', index) ? '收起详情' : '展开详情' }}
|
||||
</span>
|
||||
<img src="@/assets/images/report/zk.png" alt="展开" class="w-4 h-4"
|
||||
:class="{ 'rotate-180': isExpanded('sx', index) }" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-3 overflow-hidden transition-all duration-300 ease-in-out" :class="{
|
||||
'max-h-0 opacity-0': !isExpanded('sx', index),
|
||||
'max-h-[500px] opacity-100': isExpanded('sx', index),
|
||||
}">
|
||||
<div class="border-t border-dashed border-gray-200 pt-3 pb-2 text-xs">
|
||||
<div class="flex mb-1">
|
||||
<span class="w-16 text-gray-500">被执行人</span>
|
||||
<span class="flex-1 text-gray-800">
|
||||
{{ item.iname || '-' }}
|
||||
<span v-if="item.sexname || item.age" class="text-gray-500 ml-1">
|
||||
({{ [item.sexname, item.age && item.age + '岁'].filter(Boolean).join(',') }})
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex mb-1">
|
||||
<span class="w-16 text-gray-500">地域</span>
|
||||
<span class="flex-1 text-gray-800">{{ item.areaname || '-' }}</span>
|
||||
</div>
|
||||
<div class="flex mb-1">
|
||||
<span class="w-16 text-gray-500">法院</span>
|
||||
<span class="flex-1 text-gray-800">{{ item.courtname || '-' }}</span>
|
||||
</div>
|
||||
<div class="flex mb-1">
|
||||
<span class="w-16 text-gray-500">执行依据</span>
|
||||
<span class="flex-1 text-gray-800">{{ item.gistcid || '-' }}</span>
|
||||
</div>
|
||||
<div class="flex mb-1">
|
||||
<span class="w-16 text-gray-500">立案时间</span>
|
||||
<span class="flex-1 text-gray-800">{{ item.regdate || '-' }}</span>
|
||||
</div>
|
||||
<div class="flex mb-1">
|
||||
<span class="w-16 text-gray-500">发布日期</span>
|
||||
<span class="flex-1 text-gray-800">{{ item.publishdate || '-' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 限高记录 -->
|
||||
<div v-if="xgList.length" class="px-4 mb-4">
|
||||
<LTitle title="限高被执行人记录" />
|
||||
<div class="space-y-3">
|
||||
<div v-for="(item, index) in xgList" :key="'xg_' + index" class="case-wrapper">
|
||||
<div class="bg-white rounded-xl overflow-hidden border px-4 pt-3 border-[#DDDDDD]">
|
||||
<div class="cursor-pointer relative" @click="toggleExpand('xg', index)">
|
||||
<div class="flex items-center">
|
||||
<div class="font-bold text-base text-[#333333] mr-2">
|
||||
{{ item.casecode || '暂无案号' }}
|
||||
</div>
|
||||
<span class="px-2 py-1 text-xs rounded-md font-medium bg-[#F9ECEC] text-[#EB3C3C]">
|
||||
{{ item.datatype || '限高被执行人' }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="pb-2 text-sm">
|
||||
<span class="text-[#666666]">立案:</span>
|
||||
<span class="text-[#333333]">{{ item.regdate || '-' }}</span>
|
||||
</div>
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="px-2 py-1 text-xs rounded-md font-medium bg-[#D6943E1A] text-[#D6943E]">
|
||||
{{ item.signalDesc || '主体被限制高消费' }}
|
||||
</span>
|
||||
<div class="flex items-center text-xs text-gray-500">
|
||||
<span class="mr-1">
|
||||
{{ isExpanded('xg', index) ? '收起详情' : '展开详情' }}
|
||||
</span>
|
||||
<img src="@/assets/images/report/zk.png" alt="展开" class="w-4 h-4"
|
||||
:class="{ 'rotate-180': isExpanded('xg', index) }" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-3 overflow-hidden transition-all duration-300 ease-in-out" :class="{
|
||||
'max-h-0 opacity-0': !isExpanded('xg', index),
|
||||
'max-h-[500px] opacity-100': isExpanded('xg', index),
|
||||
}">
|
||||
<div class="border-t border-dashed border-gray-200 pt-3 pb-2 text-xs">
|
||||
<div class="flex mb-1">
|
||||
<span class="w-16 text-gray-500">被执行人</span>
|
||||
<span class="flex-1 text-gray-800">
|
||||
{{ item.iname || '-' }}
|
||||
<span v-if="item.sexname || item.age" class="text-gray-500 ml-1">
|
||||
({{ [item.sexname, item.age && item.age + '岁'].filter(Boolean).join(',') }})
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex mb-1">
|
||||
<span class="w-16 text-gray-500">地域</span>
|
||||
<span class="flex-1 text-gray-800">{{ item.areaname || '-' }}</span>
|
||||
</div>
|
||||
<div class="flex mb-1">
|
||||
<span class="w-16 text-gray-500">法院</span>
|
||||
<span class="flex-1 text-gray-800">{{ item.courtname || '-' }}</span>
|
||||
</div>
|
||||
<div class="flex mb-1">
|
||||
<span class="w-16 text-gray-500">立案时间</span>
|
||||
<span class="flex-1 text-gray-800">{{ item.regdate || '-' }}</span>
|
||||
</div>
|
||||
<div class="flex mb-1">
|
||||
<span class="w-16 text-gray-500">发布日期</span>
|
||||
<span class="flex-1 text-gray-800">{{ item.publishdate || '-' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="!sxList.length && !xgList.length"
|
||||
class="text-gray-500 py-10 text-center bg-gray-50 rounded-lg mx-4 mb-4">
|
||||
<div class="text-gray-300 text-3xl mb-2">⚖️</div>
|
||||
暂未命中失信或限高被执行人记录
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, ref, watchEffect } from 'vue'
|
||||
import LTitle from '@/components/LTitle.vue'
|
||||
|
||||
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 raw = computed(() => props.data || {})
|
||||
|
||||
const finalDecision = computed(() => raw.value.Rule_final_decision || '')
|
||||
const finalWeight = computed(() => raw.value.Rule_final_weight || '')
|
||||
|
||||
const finalDecisionText = computed(() => {
|
||||
const v = finalDecision.value
|
||||
if (!v) return '未知'
|
||||
if (v.toLowerCase() === 'accept') return '通过'
|
||||
if (v.toLowerCase() === 'reject') return '拒绝'
|
||||
return v
|
||||
})
|
||||
|
||||
// 解析失信记录(el_sx1_*)
|
||||
const sxList = computed(() => {
|
||||
const d = raw.value
|
||||
const list = []
|
||||
if (d.el_sx1_datatype) {
|
||||
list.push({
|
||||
datatype: d.el_sx1_datatype,
|
||||
age: d.el_sx1_age,
|
||||
areaname: d.el_sx1_areaname,
|
||||
casecode: d.el_sx1_casecode,
|
||||
courtname: d.el_sx1_courtname,
|
||||
gistcid: d.el_sx1_gistcid,
|
||||
iname: d.el_sx1_iname,
|
||||
partytypename: d.el_sx1_partytypename,
|
||||
publishdate: d.el_sx1_publishdate,
|
||||
regdate: d.el_sx1_regdate,
|
||||
sexname: d.el_sx1_sexname,
|
||||
sign: d.el_sx1_sign,
|
||||
signalDesc: d.el_sx1_signalDesc,
|
||||
signalRating: d.el_sx1_signalRating,
|
||||
})
|
||||
}
|
||||
return list
|
||||
})
|
||||
|
||||
// 解析限高记录(el_xg1_* / el_xg2_* / el_xg3_*)
|
||||
const xgList = computed(() => {
|
||||
const d = raw.value
|
||||
const result = []
|
||||
;[1, 2, 3].forEach((n) => {
|
||||
const prefix = `el_xg${n}_`
|
||||
const datatype = d[`${prefix}datatype`]
|
||||
if (!datatype) return
|
||||
result.push({
|
||||
datatype,
|
||||
age: d[`${prefix}age`],
|
||||
areaname: d[`${prefix}areaname`],
|
||||
casecode: d[`${prefix}casecode`],
|
||||
courtname: d[`${prefix}courtname`],
|
||||
iname: d[`${prefix}iname`],
|
||||
publishdate: d[`${prefix}publishdate`],
|
||||
regdate: d[`${prefix}regdate`],
|
||||
sexname: d[`${prefix}sexname`],
|
||||
sign: d[`${prefix}sign`],
|
||||
signalDesc: d[`${prefix}signalDesc`],
|
||||
signalRating: d[`${prefix}signalRating`],
|
||||
})
|
||||
})
|
||||
return result
|
||||
})
|
||||
|
||||
const recentLimitDesc = computed(() => {
|
||||
if (!xgList.value.length) return '暂无限高记录'
|
||||
const recent = xgList.value[0]
|
||||
return recent.signalDesc || '主体存在限高被执行人记录'
|
||||
})
|
||||
|
||||
// 展开状态
|
||||
const expandedMap = ref({})
|
||||
const keyOf = (type, index) => `${type}_${index}`
|
||||
const toggleExpand = (type, index) => {
|
||||
const k = keyOf(type, index)
|
||||
expandedMap.value[k] = !expandedMap.value[k]
|
||||
}
|
||||
const isExpanded = (type, index) => !!expandedMap.value[keyOf(type, index)]
|
||||
|
||||
// 上报风险:只要有失信或限高记录即视为有风险
|
||||
watchEffect(() => {
|
||||
if (!props.notifyRiskStatus) return
|
||||
const hasRisk = sxList.value.length > 0 || xgList.value.length > 0
|
||||
props.notifyRiskStatus(props.apiId || 'FLXG3A9B', props.index || 0, {
|
||||
hasRisk,
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.card {
|
||||
padding: 0.5rem;
|
||||
}
|
||||
|
||||
.case-wrapper {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
481
src/ui/CIVYZ0S0D.vue
Normal file
481
src/ui/CIVYZ0S0D.vue
Normal file
@@ -0,0 +1,481 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
|
||||
// 接收父组件传入的 props
|
||||
const props = defineProps({
|
||||
data: Object,
|
||||
params: Object,
|
||||
});
|
||||
|
||||
// 定义组件名称,用于在控制台输出调试信息
|
||||
const componentName = 'IVYZ0S0D';
|
||||
|
||||
// 将 props.data 赋值给 reportData 变量
|
||||
let reportData: any = props.data || {};
|
||||
|
||||
// 如果 reportData 不为空,则将其赋值给变量
|
||||
if (reportData) {
|
||||
console.log(`${componentName} 组件接收到的数据:`, reportData);
|
||||
} else {
|
||||
console.log(`${componentName} 组件未接收到数据`);
|
||||
}
|
||||
|
||||
// 获取状态描述文本
|
||||
const getStatusText = (value: number) => {
|
||||
if (value === 1) return '未命中';
|
||||
if (value === 2) return '命中';
|
||||
return '未知';
|
||||
};
|
||||
|
||||
// 获取通知函状态描述文本
|
||||
const getNoticeLetterStatusText = (value: number) => {
|
||||
if (value === 1) return '未命中';
|
||||
if (value === 2) return '命中';
|
||||
return '未知';
|
||||
};
|
||||
|
||||
// 获取通知函期间描述文本
|
||||
const getNoticeLetterPeriodText = (period: number) => {
|
||||
const periodMap: Record<number, string> = {
|
||||
0: '没有被发送通知函',
|
||||
1: '近2年内',
|
||||
2: '2-4年',
|
||||
3: '5年以上'
|
||||
};
|
||||
|
||||
return periodMap[period] || '未知期间';
|
||||
};
|
||||
|
||||
// 获取背景颜色
|
||||
const getBackgroundColor = (value: number) => {
|
||||
if (value === 1) return '#e8f5e8'; // 浅绿色
|
||||
if (value === 2) return '#ffe8e8'; // 浅红色
|
||||
return '#f5f5f5'; // 默认灰色
|
||||
};
|
||||
|
||||
// 获取边框颜色
|
||||
const getBorderColor = (value: number) => {
|
||||
if (value === 1) return '#4caf50'; // 绿色边框
|
||||
if (value === 2) return '#f44336'; // 红色边框
|
||||
return '#ccc'; // 默认灰色边框
|
||||
};
|
||||
|
||||
// 判断是否应该隐藏该条目(如果是带时间范围的"未命中")
|
||||
const shouldHideItem = (itemText: string) => {
|
||||
// 检查是否包含时间范围关键词并且结果是"未命中"
|
||||
const timeRangeKeywords = ['近2年', '近3年', '近4年', '近5年', '2-4年', '5年以上'];
|
||||
const isTimeRangeItem = timeRangeKeywords.some(keyword => itemText.includes(keyword));
|
||||
const isNoRisk = itemText.includes('未命中');
|
||||
|
||||
// 如果是时间范围项目且结果是"未命中",则隐藏
|
||||
return isTimeRangeItem && isNoRisk;
|
||||
};
|
||||
|
||||
// 获取风险类型数组 - 所有模块都显示
|
||||
const riskTypes = computed(() => {
|
||||
const risks: {title: string, value: number, details: string | string[], bgColor: string, borderColor: string}[] = [];
|
||||
|
||||
// 总体风险
|
||||
if (reportData.risk_flag !== undefined) {
|
||||
risks.push({
|
||||
title: '总体风险',
|
||||
value: reportData.risk_flag,
|
||||
details: getStatusText(reportData.risk_flag),
|
||||
bgColor: getBackgroundColor(reportData.risk_flag),
|
||||
borderColor: getBorderColor(reportData.risk_flag)
|
||||
});
|
||||
}
|
||||
|
||||
// 失信风险
|
||||
if (reportData.dishonesty && reportData.dishonesty.dishonesty !== undefined) {
|
||||
risks.push({
|
||||
title: '失信风险',
|
||||
value: reportData.dishonesty.dishonesty,
|
||||
details: getStatusText(reportData.dishonesty.dishonesty),
|
||||
bgColor: getBackgroundColor(reportData.dishonesty.dishonesty),
|
||||
borderColor: getBorderColor(reportData.dishonesty.dishonesty)
|
||||
});
|
||||
}
|
||||
|
||||
// 高消费限制风险
|
||||
if (reportData.high_consumption && reportData.high_consumption.high_consumption !== undefined) {
|
||||
risks.push({
|
||||
title: '高消费限制风险',
|
||||
value: reportData.high_consumption.high_consumption,
|
||||
details: getStatusText(reportData.high_consumption.high_consumption),
|
||||
bgColor: getBackgroundColor(reportData.high_consumption.high_consumption),
|
||||
borderColor: getBorderColor(reportData.high_consumption.high_consumption)
|
||||
});
|
||||
}
|
||||
|
||||
// 劳动争议风险
|
||||
if (reportData.labor_disputes) {
|
||||
let details: string[] = [];
|
||||
if (reportData.labor_disputes.labor_disputes !== undefined) {
|
||||
details.push(`当前: ${getStatusText(reportData.labor_disputes.labor_disputes)}`);
|
||||
}
|
||||
if (reportData.labor_disputes.labor_disputes_3y !== undefined) {
|
||||
const detail = `近3年: ${getStatusText(reportData.labor_disputes.labor_disputes_3y)}`;
|
||||
if (!shouldHideItem(detail)) {
|
||||
details.push(detail);
|
||||
}
|
||||
}
|
||||
if (reportData.labor_disputes.labor_disputes_5y !== undefined) {
|
||||
const detail = `近5年: ${getStatusText(reportData.labor_disputes.labor_disputes_5y)}`;
|
||||
if (!shouldHideItem(detail)) {
|
||||
details.push(detail);
|
||||
}
|
||||
}
|
||||
if (reportData.labor_disputes.labor_contract !== undefined) {
|
||||
details.push(`劳动合同: ${getStatusText(reportData.labor_disputes.labor_contract)}`);
|
||||
}
|
||||
if (reportData.labor_disputes.labor_relation !== undefined) {
|
||||
details.push(`劳动关系: ${getStatusText(reportData.labor_disputes.labor_relation)}`);
|
||||
}
|
||||
if (reportData.labor_disputes.labor_relation_3y !== undefined) {
|
||||
const detail = `近3年劳动关系: ${getStatusText(reportData.labor_disputes.labor_relation_3y)}`;
|
||||
if (!shouldHideItem(detail)) {
|
||||
details.push(detail);
|
||||
}
|
||||
}
|
||||
if (reportData.labor_disputes.labor_relation_5y !== undefined) {
|
||||
const detail = `近5年劳动关系: ${getStatusText(reportData.labor_disputes.labor_relation_5y)}`;
|
||||
if (!shouldHideItem(detail)) {
|
||||
details.push(detail);
|
||||
}
|
||||
}
|
||||
|
||||
if (details.length > 0) {
|
||||
risks.push({
|
||||
title: '劳动争议风险',
|
||||
value: Math.max(...Object.values(reportData.labor_disputes).filter(v => typeof v === 'number')),
|
||||
details: details,
|
||||
bgColor: getBackgroundColor(Math.max(...Object.values(reportData.labor_disputes).filter(v => typeof v === 'number'))),
|
||||
borderColor: getBorderColor(Math.max(...Object.values(reportData.labor_disputes).filter(v => typeof v === 'number')))
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 社会保险纠纷风险
|
||||
if (reportData.social_insurance) {
|
||||
let details: string[] = [];
|
||||
if (reportData.social_insurance.social_insurance !== undefined) {
|
||||
details.push(`社保纠纷: ${getStatusText(reportData.social_insurance.social_insurance)}`);
|
||||
}
|
||||
if (reportData.social_insurance.pension !== undefined) {
|
||||
details.push(`养老纠纷: ${getStatusText(reportData.social_insurance.pension)}`);
|
||||
}
|
||||
if (reportData.social_insurance.pension_3y !== undefined) {
|
||||
const detail = `近3年养老: ${getStatusText(reportData.social_insurance.pension_3y)}`;
|
||||
if (!shouldHideItem(detail)) {
|
||||
details.push(detail);
|
||||
}
|
||||
}
|
||||
if (reportData.social_insurance.pension_5y !== undefined) {
|
||||
const detail = `近5年养老: ${getStatusText(reportData.social_insurance.pension_5y)}`;
|
||||
if (!shouldHideItem(detail)) {
|
||||
details.push(detail);
|
||||
}
|
||||
}
|
||||
if (reportData.social_insurance.injury_insurance !== undefined) {
|
||||
details.push(`工伤纠纷: ${getStatusText(reportData.social_insurance.injury_insurance)}`);
|
||||
}
|
||||
if (reportData.social_insurance.injury_insurance_3y !== undefined) {
|
||||
const detail = `近3年工伤: ${getStatusText(reportData.social_insurance.injury_insurance_3y)}`;
|
||||
if (!shouldHideItem(detail)) {
|
||||
details.push(detail);
|
||||
}
|
||||
}
|
||||
if (reportData.social_insurance.injury_insurance_5y !== undefined) {
|
||||
const detail = `近5年工伤: ${getStatusText(reportData.social_insurance.injury_insurance_5y)}`;
|
||||
if (!shouldHideItem(detail)) {
|
||||
details.push(detail);
|
||||
}
|
||||
}
|
||||
if (reportData.social_insurance.unemployment_insurance !== undefined) {
|
||||
details.push(`失业纠纷: ${getStatusText(reportData.social_insurance.unemployment_insurance)}`);
|
||||
}
|
||||
if (reportData.social_insurance.unemployment_insurance_3y !== undefined) {
|
||||
const detail = `近3年失业: ${getStatusText(reportData.social_insurance.unemployment_insurance_3y)}`;
|
||||
if (!shouldHideItem(detail)) {
|
||||
details.push(detail);
|
||||
}
|
||||
}
|
||||
if (reportData.social_insurance.unemployment_insurance_5y !== undefined) {
|
||||
const detail = `近5年失业: ${getStatusText(reportData.social_insurance.unemployment_insurance_5y)}`;
|
||||
if (!shouldHideItem(detail)) {
|
||||
details.push(detail);
|
||||
}
|
||||
}
|
||||
if (reportData.social_insurance.medical_insurance !== undefined) {
|
||||
details.push(`医疗纠纷: ${getStatusText(reportData.social_insurance.medical_insurance)}`);
|
||||
}
|
||||
if (reportData.social_insurance.medical_insurance_3y !== undefined) {
|
||||
const detail = `近3年医疗: ${getStatusText(reportData.social_insurance.medical_insurance_3y)}`;
|
||||
if (!shouldHideItem(detail)) {
|
||||
details.push(detail);
|
||||
}
|
||||
}
|
||||
if (reportData.social_insurance.medical_insurance_5y !== undefined) {
|
||||
const detail = `近5年医疗: ${getStatusText(reportData.social_insurance.medical_insurance_5y)}`;
|
||||
if (!shouldHideItem(detail)) {
|
||||
details.push(detail);
|
||||
}
|
||||
}
|
||||
if (reportData.social_insurance.maternity_insurance !== undefined) {
|
||||
details.push(`生育纠纷: ${getStatusText(reportData.social_insurance.maternity_insurance)}`);
|
||||
}
|
||||
if (reportData.social_insurance.maternity_insurance_3y !== undefined) {
|
||||
const detail = `近3年生育: ${getStatusText(reportData.social_insurance.maternity_insurance_3y)}`;
|
||||
if (!shouldHideItem(detail)) {
|
||||
details.push(detail);
|
||||
}
|
||||
}
|
||||
if (reportData.social_insurance.maternity_insurance_5y !== undefined) {
|
||||
const detail = `近5年生育: ${getStatusText(reportData.social_insurance.maternity_insurance_5y)}`;
|
||||
if (!shouldHideItem(detail)) {
|
||||
details.push(detail);
|
||||
}
|
||||
}
|
||||
|
||||
if (details.length > 0) {
|
||||
risks.push({
|
||||
title: '社会保险纠纷风险',
|
||||
value: Math.max(...Object.values(reportData.social_insurance).filter(v => typeof v === 'number')),
|
||||
details: details,
|
||||
bgColor: getBackgroundColor(Math.max(...Object.values(reportData.social_insurance).filter(v => typeof v === 'number'))),
|
||||
borderColor: getBorderColor(Math.max(...Object.values(reportData.social_insurance).filter(v => typeof v === 'number')))
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 福利待遇纠纷
|
||||
if (reportData.welfare_disputes && reportData.welfare_disputes.welfare !== undefined) {
|
||||
risks.push({
|
||||
title: '福利待遇纠纷',
|
||||
value: reportData.welfare_disputes.welfare,
|
||||
details: getStatusText(reportData.welfare_disputes.welfare),
|
||||
bgColor: getBackgroundColor(reportData.welfare_disputes.welfare),
|
||||
borderColor: getBorderColor(reportData.welfare_disputes.welfare)
|
||||
});
|
||||
}
|
||||
|
||||
// 人事争议类纠纷
|
||||
if (reportData.personnel_disputes) {
|
||||
let details: string[] = [];
|
||||
if (reportData.personnel_disputes.personnel_dispute !== undefined) {
|
||||
details.push(`人事争议: ${getStatusText(reportData.personnel_disputes.personnel_dispute)}`);
|
||||
}
|
||||
if (reportData.personnel_disputes.resignation_dispute !== undefined) {
|
||||
details.push(`辞职争议: ${getStatusText(reportData.personnel_disputes.resignation_dispute)}`);
|
||||
}
|
||||
if (reportData.personnel_disputes.resignation_dispute_3y !== undefined) {
|
||||
const detail = `近3年辞职: ${getStatusText(reportData.personnel_disputes.resignation_dispute_3y)}`;
|
||||
if (!shouldHideItem(detail)) {
|
||||
details.push(detail);
|
||||
}
|
||||
}
|
||||
if (reportData.personnel_disputes.resignation_dispute_5y !== undefined) {
|
||||
const detail = `近5年辞职: ${getStatusText(reportData.personnel_disputes.resignation_dispute_5y)}`;
|
||||
if (!shouldHideItem(detail)) {
|
||||
details.push(detail);
|
||||
}
|
||||
}
|
||||
if (reportData.personnel_disputes.dismissal_dispute !== undefined) {
|
||||
details.push(`辞退争议: ${getStatusText(reportData.personnel_disputes.dismissal_dispute)}`);
|
||||
}
|
||||
if (reportData.personnel_disputes.dismissal_dispute_3y !== undefined) {
|
||||
const detail = `近3年辞退: ${getStatusText(reportData.personnel_disputes.dismissal_dispute_3y)}`;
|
||||
if (!shouldHideItem(detail)) {
|
||||
details.push(detail);
|
||||
}
|
||||
}
|
||||
if (reportData.personnel_disputes.dismissal_dispute_5y !== undefined) {
|
||||
const detail = `近5年辞退: ${getStatusText(reportData.personnel_disputes.dismissal_dispute_5y)}`;
|
||||
if (!shouldHideItem(detail)) {
|
||||
details.push(detail);
|
||||
}
|
||||
}
|
||||
|
||||
if (details.length > 0) {
|
||||
risks.push({
|
||||
title: '人事争议类纠纷',
|
||||
value: Math.max(...Object.values(reportData.personnel_disputes).filter(v => typeof v === 'number')),
|
||||
details: details,
|
||||
bgColor: getBackgroundColor(Math.max(...Object.values(reportData.personnel_disputes).filter(v => typeof v === 'number'))),
|
||||
borderColor: getBorderColor(Math.max(...Object.values(reportData.personnel_disputes).filter(v => typeof v === 'number')))
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 仲裁相关案件
|
||||
if (reportData.arbitration) {
|
||||
let details: string[] = [];
|
||||
if (reportData.arbitration.arbitration_confirmation !== undefined) {
|
||||
details.push(`仲裁确认: ${getStatusText(reportData.arbitration.arbitration_confirmation)}`);
|
||||
}
|
||||
if (reportData.arbitration.arbitration_confirmation_3y !== undefined) {
|
||||
const detail = `近3年仲裁确认: ${getStatusText(reportData.arbitration.arbitration_confirmation_3y)}`;
|
||||
if (!shouldHideItem(detail)) {
|
||||
details.push(detail);
|
||||
}
|
||||
}
|
||||
if (reportData.arbitration.arbitration_confirmation_5y !== undefined) {
|
||||
const detail = `近5年仲裁确认: ${getStatusText(reportData.arbitration.arbitration_confirmation_5y)}`;
|
||||
if (!shouldHideItem(detail)) {
|
||||
details.push(detail);
|
||||
}
|
||||
}
|
||||
if (reportData.arbitration.arbitration_revocation !== undefined) {
|
||||
details.push(`仲裁撤销: ${getStatusText(reportData.arbitration.arbitration_revocation)}`);
|
||||
}
|
||||
if (reportData.arbitration.arbitration_revocation_3y !== undefined) {
|
||||
const detail = `近3年仲裁撤销: ${getStatusText(reportData.arbitration.arbitration_revocation_3y)}`;
|
||||
if (!shouldHideItem(detail)) {
|
||||
details.push(detail);
|
||||
}
|
||||
}
|
||||
if (reportData.arbitration.arbitration_revocation_5y !== undefined) {
|
||||
const detail = `近5年仲裁撤销: ${getStatusText(reportData.arbitration.arbitration_revocation_5y)}`;
|
||||
if (!shouldHideItem(detail)) {
|
||||
details.push(detail);
|
||||
}
|
||||
}
|
||||
|
||||
if (details.length > 0) {
|
||||
risks.push({
|
||||
title: '仲裁相关案件',
|
||||
value: Math.max(...Object.values(reportData.arbitration).filter(v => typeof v === 'number')),
|
||||
details: details,
|
||||
bgColor: getBackgroundColor(Math.max(...Object.values(reportData.arbitration).filter(v => typeof v === 'number'))),
|
||||
borderColor: getBorderColor(Math.max(...Object.values(reportData.arbitration).filter(v => typeof v === 'number')))
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 通知函触达
|
||||
if (reportData.notice_letter && reportData.notice_letter.notice_letter !== undefined) {
|
||||
let statusText = getNoticeLetterStatusText(reportData.notice_letter.notice_letter);
|
||||
let periodText = '';
|
||||
|
||||
if (reportData.notice_letter.notice_letter_period !== undefined) {
|
||||
periodText = `期间: ${getNoticeLetterPeriodText(reportData.notice_letter.notice_letter_period)}`;
|
||||
}
|
||||
|
||||
const detailParts = [`状态: ${statusText}`];
|
||||
if (periodText) {
|
||||
detailParts.push(periodText);
|
||||
}
|
||||
|
||||
risks.push({
|
||||
title: '通知函触达',
|
||||
value: reportData.notice_letter.notice_letter,
|
||||
details: detailParts,
|
||||
bgColor: getBackgroundColor(reportData.notice_letter.notice_letter),
|
||||
borderColor: getBorderColor(reportData.notice_letter.notice_letter)
|
||||
});
|
||||
}
|
||||
|
||||
return risks;
|
||||
});
|
||||
|
||||
// 检查是否至少有一个数据类别有内容
|
||||
const hasAnyData = computed(() => {
|
||||
return riskTypes.value.length > 0;
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="ivyz0s0d-container">
|
||||
<!-- 风险卡片网格 -->
|
||||
<div v-if="hasAnyData" class="risk-cards-grid">
|
||||
<div
|
||||
v-for="(risk, index) in riskTypes"
|
||||
:key="index"
|
||||
class="risk-card"
|
||||
:style="{ backgroundColor: risk.bgColor, borderLeft: `4px solid ${risk.borderColor}` }"
|
||||
>
|
||||
<div class="risk-card__content">
|
||||
<h4 class="risk-card__title">{{ risk.title }}</h4>
|
||||
<div class="risk-card__status">
|
||||
<!-- 当 details 是字符串时显示单行 -->
|
||||
<p v-if="typeof risk.details === 'string'" class="risk-detail-item">{{ risk.details }}</p>
|
||||
<!-- 当 details 是数组时,每项占一行 -->
|
||||
<p
|
||||
v-else
|
||||
v-for="(detail, idx) in risk.details"
|
||||
:key="idx"
|
||||
class="risk-detail-item"
|
||||
>{{ detail }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 无数据提示 -->
|
||||
<div v-if="!hasAnyData" class="no-data">
|
||||
<p>暂无相关风险数据</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.ivyz0s0d-container {
|
||||
padding: 20px;
|
||||
font-family: Arial, sans-serif;
|
||||
}
|
||||
|
||||
.risk-cards-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
|
||||
gap: 15px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.risk-card {
|
||||
border: 1px solid #e0e0e0;
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
||||
}
|
||||
|
||||
.risk-card:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 8px rgba(0,0,0,0.15);
|
||||
}
|
||||
|
||||
.risk-card__content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.risk-card__title {
|
||||
margin: 0 0 8px 0;
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.risk-card__status {
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.risk-detail-item {
|
||||
margin: 0 0 4px 0;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.risk-detail-item:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.no-data {
|
||||
text-align: center;
|
||||
color: #999;
|
||||
font-style: italic;
|
||||
padding: 20px;
|
||||
}
|
||||
</style>
|
||||
104
src/ui/CIVYZ6M8P.vue
Normal file
104
src/ui/CIVYZ6M8P.vue
Normal file
@@ -0,0 +1,104 @@
|
||||
<template>
|
||||
<div class="card">
|
||||
<div class="header-box">
|
||||
<h3 class="header-title">职业资格证书查询</h3>
|
||||
<p class="header-desc">展示查询到的职业资格考试及等级信息</p>
|
||||
</div>
|
||||
|
||||
<div v-if="hasData" class="result-section">
|
||||
<div class="info-row">
|
||||
<span class="info-label">考试名称</span>
|
||||
<span class="info-value">{{ examName || '-' }}</span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">级别</span>
|
||||
<span class="info-value">{{ level || '-' }}</span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">专业</span>
|
||||
<span class="info-value">{{ major || '-' }}</span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">通过日期</span>
|
||||
<span class="info-value">{{ passDate || '-' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="hasData && !examName && !major && !level && !passDate" class="empty-tip">
|
||||
已返回结果,但格式不在预期范围内,请联系技术支持查看原始数据。
|
||||
</div>
|
||||
|
||||
<div v-if="!hasData" class="empty-tip">暂无查询结果</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed } from 'vue';
|
||||
|
||||
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 hasData = computed(() => props.data && Object.keys(props.data).length > 0);
|
||||
|
||||
// ED0001: 考试名称, ED0002: 级别, ED0003: 专业, ED0004: 通过年月
|
||||
const examName = computed(() => props.data?.ED0001 || '');
|
||||
const level = computed(() => props.data?.ED0002 || '');
|
||||
const major = computed(() => props.data?.ED0003 || '');
|
||||
const passDate = computed(() => props.data?.ED0004 || '');
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.card {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.header-box {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.header-title {
|
||||
font-size: 1.125rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.header-desc {
|
||||
font-size: 0.875rem;
|
||||
color: #6b7280;
|
||||
margin-top: 0.25rem;
|
||||
}
|
||||
|
||||
.result-section {
|
||||
background: #f9fafb;
|
||||
padding: 0.75rem 0.875rem;
|
||||
border-radius: 0.75rem;
|
||||
}
|
||||
|
||||
.info-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
font-size: 0.875rem;
|
||||
padding: 0.25rem 0;
|
||||
}
|
||||
|
||||
.info-label {
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.info-value {
|
||||
color: #111827;
|
||||
margin-left: 1rem;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.empty-tip {
|
||||
color: #9ca3af;
|
||||
font-size: 0.875rem;
|
||||
padding: 1rem 0;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
296
src/ui/CIVYZ7F3A.vue
Normal file
296
src/ui/CIVYZ7F3A.vue
Normal file
@@ -0,0 +1,296 @@
|
||||
<script setup>
|
||||
import { computed, onMounted } from 'vue';
|
||||
import { useRiskNotifier } from '@/composables/useRiskNotifier';
|
||||
|
||||
const props = defineProps({
|
||||
data: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
apiId: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
index: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
notifyRiskStatus: {
|
||||
type: Function,
|
||||
default: () => { },
|
||||
},
|
||||
});
|
||||
|
||||
// 导入图片图标
|
||||
import rkpmIcon from '@/assets/images/report/rkpm.png';
|
||||
import zymcIcon from '@/assets/images/report/zymc.png';
|
||||
import xxxsIcon from '@/assets/images/report/xxxs.png';
|
||||
import xxlxIcon from '@/assets/images/report/xxlx.png';
|
||||
import bysjIcon from '@/assets/images/report/bysj.png';
|
||||
import xlIcon from '@/assets/images/report/xl.png';
|
||||
|
||||
|
||||
// 计算风险评分(0-100分,分数越高越安全)
|
||||
const riskScore = computed(() => {
|
||||
// 学历信息不算风险,始终返回100分(最安全)
|
||||
return 100;
|
||||
});
|
||||
|
||||
// 使用 composable 通知父组件风险评分
|
||||
useRiskNotifier(props, riskScore);
|
||||
|
||||
// 暴露给父组件
|
||||
defineExpose({
|
||||
riskScore
|
||||
});
|
||||
|
||||
// 格式化日期,从YYYYMM格式转换为YYYY年MM月格式
|
||||
const formatDate = (dateStr) => {
|
||||
if (!dateStr || dateStr.length !== 6) return "未知";
|
||||
|
||||
const year = dateStr.substring(0, 4);
|
||||
const month = dateStr.substring(4, 6);
|
||||
|
||||
return `${year}年${month}月`;
|
||||
};
|
||||
|
||||
// 获取学历等级的描述
|
||||
const getEducationLevelDesc = (level) => {
|
||||
const descriptions = {
|
||||
"专科": "专科学历是高等教育的重要组成部分,培养具有专业知识和技能的应用型人才。",
|
||||
"本科": "本科学历是高等教育的基础学位,培养具有系统专业知识和基本技能的高级人才。",
|
||||
"硕士": "硕士学位是较高层次的学位,培养具有较深厚理论基础和专业技能的高级专门人才。",
|
||||
"博士": "博士学位是最高学位,培养能够独立从事科学研究工作、具有创新能力的高级专门人才。",
|
||||
};
|
||||
return descriptions[level] || "";
|
||||
};
|
||||
|
||||
// 根据学校属性获取徽章配置
|
||||
const getSchoolBadges = () => {
|
||||
const badges = [];
|
||||
|
||||
if (props.data.isProject985 === "是") {
|
||||
badges.push({
|
||||
text: "985院校",
|
||||
class: "bg-amber-500 text-white",
|
||||
icon: "🏆"
|
||||
});
|
||||
}
|
||||
|
||||
if (props.data.isProject211 === "是") {
|
||||
badges.push({
|
||||
text: "211院校",
|
||||
class: "bg-blue-500 text-white",
|
||||
icon: "⭐"
|
||||
});
|
||||
}
|
||||
|
||||
if (props.data.isDoubleFirstClass === "是") {
|
||||
badges.push({
|
||||
text: "双一流",
|
||||
class: "bg-green-500 text-white",
|
||||
icon: "✨"
|
||||
});
|
||||
}
|
||||
|
||||
return badges;
|
||||
};
|
||||
|
||||
// 获取学校类型徽章样式
|
||||
const getSchoolTypeBadgeClass = (type) => {
|
||||
const typeClasses = {
|
||||
"综合": "bg-blue-50 text-blue-700 border-blue-300",
|
||||
"理工": "bg-purple-50 text-purple-700 border-purple-300",
|
||||
"农业": "bg-green-50 text-green-700 border-green-300",
|
||||
"医学": "bg-red-50 text-red-700 border-red-300",
|
||||
"师范": "bg-yellow-50 text-yellow-700 border-yellow-300",
|
||||
"财经": "bg-cyan-50 text-cyan-700 border-cyan-300",
|
||||
"艺术": "bg-pink-50 text-pink-700 border-pink-300",
|
||||
};
|
||||
return typeClasses[type] || "bg-gray-50 text-gray-700 border-gray-300";
|
||||
};
|
||||
|
||||
// 获取软科排名样式
|
||||
const getRankingClass = () => {
|
||||
if (!props.data.Ranking) return "bg-gray-50 text-gray-700";
|
||||
|
||||
// 解析排名范围 (例如: "151-155")
|
||||
const match = props.data.Ranking.match(/(\d+)-(\d+)/);
|
||||
if (match) {
|
||||
const minRank = parseInt(match[1]);
|
||||
if (minRank <= 50) return "bg-green-50 text-green-700 border-green-300";
|
||||
if (minRank <= 100) return "bg-blue-50 text-blue-700 border-blue-300";
|
||||
if (minRank <= 200) return "bg-yellow-50 text-yellow-700 border-yellow-300";
|
||||
}
|
||||
return "bg-gray-50 text-gray-700 border-gray-300";
|
||||
};
|
||||
|
||||
// 判断是否有数据
|
||||
const hasData = computed(() => {
|
||||
return props.data && Object.keys(props.data).length > 0;
|
||||
});
|
||||
|
||||
// 获取徽章列表
|
||||
const schoolBadges = computed(() => getSchoolBadges());
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="hasData" class="card max-w-4xl mx-auto">
|
||||
<!-- 头部区域 -->
|
||||
<div class="mb-6">
|
||||
<!-- 学历等级标题 -->
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="w-12 h-12 flex items-center justify-center">
|
||||
<img :src="xlIcon" alt="学历图标" class="w-12 h-12" />
|
||||
</div>
|
||||
<div>
|
||||
<h2 class="text-2xl font-bold text-gray-900">{{ data.educationLevel }}学历</h2>
|
||||
<p class="text-sm text-gray-500">Education Information</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 主要内容区域 -->
|
||||
<div class="space-y-4">
|
||||
<!-- 软科排名 -->
|
||||
<div v-if="data.Ranking"
|
||||
class="bg-gradient-to-r from-blue-50 to-indigo-50 rounded-lg p-4 border border-blue-200">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="w-8 h-8 flex items-center justify-center flex-shrink-0">
|
||||
<img :src="rkpmIcon" alt="软科排名" class="w-8 h-8" />
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<div class="text-sm text-gray-600 mb-1">软科排名</div>
|
||||
<div
|
||||
:class="['inline-block px-3 py-1 rounded-full text-sm font-medium border', getRankingClass()]">
|
||||
第 {{ data.Ranking }} 名
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 学校徽章 -->
|
||||
<div v-if="schoolBadges.length > 0" class="flex gap-2 flex-wrap">
|
||||
<div v-for="(badge, index) in schoolBadges" :key="index"
|
||||
:class="['inline-flex items-center gap-1 px-4 py-2 rounded-lg text-sm font-medium shadow-sm', badge.class]">
|
||||
<span>{{ badge.icon }}</span>
|
||||
<span>{{ badge.text }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 详细信息卡片 -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<!-- 专业信息 -->
|
||||
<div v-if="data.major"
|
||||
class="bg-gray-50 rounded-lg p-4 border border-gray-200 hover:border-blue-300 transition-colors">
|
||||
<div class="flex items-start gap-3">
|
||||
<div class="w-8 h-8 flex items-center justify-center flex-shrink-0 mt-1">
|
||||
<img :src="zymcIcon" alt="专业名称" class="w-8 h-8" />
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<div class="text-sm text-gray-600 mb-1">专业名称</div>
|
||||
<div class="text-base font-medium text-gray-900">{{ data.major }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 学校类型 -->
|
||||
<div v-if="data.schoolType"
|
||||
class="bg-gray-50 rounded-lg p-4 border border-gray-200 hover:border-blue-300 transition-colors">
|
||||
<div class="flex items-start gap-3">
|
||||
<div class="w-8 h-8 flex items-center justify-center flex-shrink-0 mt-1">
|
||||
<img :src="xxlxIcon" alt="学校类型" class="w-8 h-8" />
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<div class="text-sm text-gray-600 mb-1">学校类型</div>
|
||||
<div
|
||||
:class="['inline-block px-3 py-1 rounded text-sm font-medium border', getSchoolTypeBadgeClass(data.schoolType)]">
|
||||
{{ data.schoolType }}类院校
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 学习形式 -->
|
||||
<div v-if="data.educationType"
|
||||
class="bg-gray-50 rounded-lg p-4 border border-gray-200 hover:border-blue-300 transition-colors">
|
||||
<div class="flex items-start gap-3">
|
||||
<div class="w-8 h-8 flex items-center justify-center flex-shrink-0 mt-1">
|
||||
<img :src="xxxsIcon" alt="学习形式" class="w-8 h-8" />
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<div class="text-sm text-gray-600 mb-1">学习形式</div>
|
||||
<div class="text-base font-medium text-gray-900">{{ data.educationType }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 毕业时间 -->
|
||||
<div v-if="data.graduationDate"
|
||||
class="bg-gray-50 rounded-lg p-4 border border-gray-200 hover:border-blue-300 transition-colors">
|
||||
<div class="flex items-start gap-3">
|
||||
<div class="w-8 h-8 flex items-center justify-center flex-shrink-0 mt-1">
|
||||
<img :src="bysjIcon" alt="毕业时间" class="w-8 h-8" />
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<div class="text-sm text-gray-600 mb-1">毕业时间</div>
|
||||
<div class="text-base font-medium text-gray-900">{{ formatDate(data.graduationDate) }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 学历描述 -->
|
||||
<div v-if="data.educationLevel"
|
||||
class="mt-6 bg-gradient-to-r from-blue-50 to-indigo-50 rounded-lg p-5 border border-blue-200">
|
||||
<div class="flex items-start gap-3">
|
||||
<div class="w-10 h-10 rounded-full bg-blue-500 flex items-center justify-center flex-shrink-0 mt-1">
|
||||
<span class="text-white text-xl">💡</span>
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<div class="flex items-center gap-2 mb-2">
|
||||
<h3 class="text-lg font-semibold text-gray-900">学历说明</h3>
|
||||
<span class="px-2 py-1 bg-blue-500 text-white text-xs rounded">信息</span>
|
||||
</div>
|
||||
<p class="text-gray-700 leading-relaxed">
|
||||
{{ getEducationLevelDesc(data.educationLevel) }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 无数据状态 -->
|
||||
<div v-else class="card max-w-2xl mx-auto">
|
||||
<div class="flex flex-col items-center py-12 text-center">
|
||||
<div class="w-20 h-20 flex items-center justify-center mb-4">
|
||||
<img :src="xlIcon" alt="学历图标" class="w-20 h-20 opacity-40" />
|
||||
</div>
|
||||
<h3 class="text-lg font-medium text-gray-900 mb-2">暂无学历信息</h3>
|
||||
<p class="text-sm text-gray-500 max-w-md">
|
||||
系统中暂无相关的学历信息记录。这可能是因为学历信息未公开或数据正在同步中。
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.card {
|
||||
padding: 1.5rem;
|
||||
box-shadow: 0px 0px 24px 0px #3F3F3F0F;
|
||||
border-radius: 12px;
|
||||
background: white;
|
||||
}
|
||||
|
||||
/* 添加一些动画效果 */
|
||||
.bg-gray-50 {
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.bg-gray-50:hover {
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
</style>
|
||||
227
src/ui/CIVYZ9K7F.vue
Normal file
227
src/ui/CIVYZ9K7F.vue
Normal file
@@ -0,0 +1,227 @@
|
||||
<template>
|
||||
<div class="card">
|
||||
<div class="header-box">
|
||||
<h3 class="header-title">公安二要素认证</h3>
|
||||
<p class="header-desc">核验姓名与身份证号是否一致</p>
|
||||
</div>
|
||||
|
||||
<div v-if="hasData" class="result-section" :class="resultSectionClass">
|
||||
<div class="result-main">
|
||||
<div class="result-label">核验结果</div>
|
||||
<div class="result-value" :class="resultClass">
|
||||
{{ resultText }}
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="desc" class="result-desc">
|
||||
{{ desc }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="hasBaseInfo" class="info-block">
|
||||
<div class="block-title">被核验人信息</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">姓名</span>
|
||||
<span class="info-value">{{ maskedName }}</span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">性别</span>
|
||||
<span class="info-value">{{ sex || '-' }}</span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">生日</span>
|
||||
<span class="info-value">{{ birthdayDisplay }}</span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">户籍地址</span>
|
||||
<span class="info-value">{{ address || '-' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="!hasData" class="empty-tip">暂无核验结果</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed } from 'vue';
|
||||
|
||||
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 hasData = computed(() => props.data && Object.keys(props.data).length > 0);
|
||||
|
||||
const resultCode = computed(() => {
|
||||
const v = props.data?.result;
|
||||
if (v === 0 || v === '0') return 0;
|
||||
if (v === 1 || v === '1') return 1;
|
||||
if (v === 2 || v === '2') return 2;
|
||||
return null;
|
||||
});
|
||||
|
||||
const resultText = computed(() => {
|
||||
switch (resultCode.value) {
|
||||
case 0:
|
||||
return '一致';
|
||||
case 1:
|
||||
return '不一致';
|
||||
case 2:
|
||||
return '无记录';
|
||||
default:
|
||||
return '暂无结果';
|
||||
}
|
||||
});
|
||||
|
||||
const resultClass = computed(() => {
|
||||
if (resultCode.value === 0) return 'result-ok';
|
||||
if (resultCode.value === 1) return 'result-bad';
|
||||
if (resultCode.value === 2) return 'result-unknown';
|
||||
return 'result-unknown';
|
||||
});
|
||||
|
||||
const resultSectionClass = computed(() => {
|
||||
if (resultCode.value === 0) return 'result-section ok';
|
||||
if (resultCode.value === 1) return 'result-section bad';
|
||||
if (resultCode.value === 2) return 'result-section unknown';
|
||||
return 'result-section unknown';
|
||||
});
|
||||
|
||||
const desc = computed(() => props.data?.desc || '');
|
||||
const sex = computed(() => props.data?.sex || '');
|
||||
const birthday = computed(() => props.data?.birthday || '');
|
||||
const address = computed(() => props.data?.address || '');
|
||||
|
||||
const birthdayDisplay = computed(() => {
|
||||
const b = birthday.value;
|
||||
if (!b || b.length !== 8) return b || '-';
|
||||
const y = b.slice(0, 4);
|
||||
const m = b.slice(4, 6);
|
||||
const d = b.slice(6, 8);
|
||||
return `${y}-${m}-${d}`;
|
||||
});
|
||||
|
||||
const maskedName = computed(() => {
|
||||
const name = props.params?.name || '';
|
||||
if (!name) return '-';
|
||||
return name.length > 1 ? name[0] + '*'.repeat(name.length - 1) : '*';
|
||||
});
|
||||
|
||||
const hasBaseInfo = computed(() => sex.value || birthday.value || address.value || props.params?.name);
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.card {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.header-box {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.header-title {
|
||||
font-size: 1.125rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.header-desc {
|
||||
font-size: 0.875rem;
|
||||
color: #6b7280;
|
||||
margin-top: 0.25rem;
|
||||
}
|
||||
|
||||
.result-section {
|
||||
padding: 0.75rem 0.875rem;
|
||||
border-radius: 0.75rem;
|
||||
margin-bottom: 1rem;
|
||||
border: 1px solid #e5e7eb;
|
||||
}
|
||||
|
||||
.result-section.ok {
|
||||
background: #ecfdf3;
|
||||
border-color: #22c55e33;
|
||||
}
|
||||
|
||||
.result-section.bad {
|
||||
background: #fef2f2;
|
||||
border-color: #ef444433;
|
||||
}
|
||||
|
||||
.result-section.unknown {
|
||||
background: #f9fafb;
|
||||
}
|
||||
|
||||
.result-main {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.result-label {
|
||||
font-size: 0.875rem;
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.result-value {
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.result-ok {
|
||||
color: #16a34a;
|
||||
}
|
||||
|
||||
.result-bad {
|
||||
color: #dc2626;
|
||||
}
|
||||
|
||||
.result-unknown {
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.result-desc {
|
||||
margin-top: 0.5rem;
|
||||
font-size: 0.875rem;
|
||||
color: #4b5563;
|
||||
}
|
||||
|
||||
.info-block {
|
||||
margin-top: 1rem;
|
||||
padding: 0.75rem 0.875rem;
|
||||
border-radius: 0.75rem;
|
||||
background: #f9fafb;
|
||||
}
|
||||
|
||||
.block-title {
|
||||
font-size: 0.875rem;
|
||||
font-weight: 600;
|
||||
color: #374151;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.info-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
font-size: 0.875rem;
|
||||
padding: 0.15rem 0;
|
||||
}
|
||||
|
||||
.info-label {
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.info-value {
|
||||
color: #111827;
|
||||
margin-left: 1rem;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.empty-tip {
|
||||
color: #9ca3af;
|
||||
font-size: 0.875rem;
|
||||
padding: 1rem 0;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
239
src/ui/CIVYZA1B3.vue
Normal file
239
src/ui/CIVYZA1B3.vue
Normal file
@@ -0,0 +1,239 @@
|
||||
<template>
|
||||
<div class="card">
|
||||
<div class="header-box">
|
||||
<h3 class="header-title">公安三要素</h3>
|
||||
<p class="header-desc">比对人像与身份证信息是否为同一人</p>
|
||||
</div>
|
||||
|
||||
<div v-if="hasData" class="result-section" :class="scoreLevelClass">
|
||||
<div class="result-main">
|
||||
<div class="result-label">系统判断</div>
|
||||
<div class="result-value" :class="scoreTextClass">
|
||||
{{ msg || scoreConclusion }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="result-score">
|
||||
相似度分值:<span class="font-semibold">{{ scoreDisplay }}</span>
|
||||
<span class="text-xs text-gray-500 ml-1">(0–1,越高越相似)</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="hasBaseInfo" class="info-block">
|
||||
<div class="block-title">被核验人信息</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">姓名</span>
|
||||
<span class="info-value">{{ maskedName }}</span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">性别</span>
|
||||
<span class="info-value">{{ sex || '-' }}</span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">生日</span>
|
||||
<span class="info-value">{{ birthdayDisplay }}</span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">户籍地址</span>
|
||||
<span class="info-value">{{ address || '-' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="!hasData" class="empty-tip">暂无核验结果</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed } from 'vue';
|
||||
|
||||
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 hasData = computed(() => props.data && Object.keys(props.data).length > 0);
|
||||
|
||||
const score = computed(() => {
|
||||
const v = props.data?.score;
|
||||
if (typeof v === 'number') return v;
|
||||
const n = Number(v);
|
||||
return Number.isFinite(n) ? n : null;
|
||||
});
|
||||
|
||||
const msg = computed(() => props.data?.msg || '');
|
||||
const incorrect = computed(() => props.data?.incorrect ?? null);
|
||||
const sex = computed(() => props.data?.sex || '');
|
||||
const birthday = computed(() => props.data?.birthday || '');
|
||||
const address = computed(() => props.data?.address || '');
|
||||
|
||||
const scoreDisplay = computed(() => {
|
||||
if (score.value == null) return '-';
|
||||
return score.value.toFixed(2);
|
||||
});
|
||||
|
||||
const scoreConclusion = computed(() => {
|
||||
const s = score.value;
|
||||
if (s == null) return '暂无结论';
|
||||
if (s < 0.4) return '系统判断为不同人';
|
||||
if (s < 0.45) return '不能确定是否为同一人';
|
||||
return '系统判断为同一人';
|
||||
});
|
||||
|
||||
const scoreLevelClass = computed(() => {
|
||||
const s = score.value;
|
||||
if (s == null) return 'result-section unknown';
|
||||
if (s < 0.4) return 'result-section bad';
|
||||
if (s < 0.45) return 'result-section warn';
|
||||
return 'result-section ok';
|
||||
});
|
||||
|
||||
const scoreTextClass = computed(() => {
|
||||
const s = score.value;
|
||||
if (s == null) return 'result-unknown';
|
||||
if (s < 0.4) return 'result-bad';
|
||||
if (s < 0.45) return 'result-warn';
|
||||
return 'result-ok';
|
||||
});
|
||||
|
||||
const birthdayDisplay = computed(() => {
|
||||
const b = birthday.value;
|
||||
if (!b || b.length !== 8) return b || '-';
|
||||
const y = b.slice(0, 4);
|
||||
const m = b.slice(4, 6);
|
||||
const d = b.slice(6, 8);
|
||||
return `${y}-${m}-${d}`;
|
||||
});
|
||||
|
||||
const maskedName = computed(() => {
|
||||
const name = props.params?.name || '';
|
||||
if (!name) return '-';
|
||||
return name.length > 1 ? name[0] + '*'.repeat(name.length - 1) : '*';
|
||||
});
|
||||
|
||||
const hasBaseInfo = computed(() => sex.value || birthday.value || address.value || props.params?.name);
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.card {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.header-box {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.header-title {
|
||||
font-size: 1.125rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.header-desc {
|
||||
font-size: 0.875rem;
|
||||
color: #6b7280;
|
||||
margin-top: 0.25rem;
|
||||
}
|
||||
|
||||
.result-section {
|
||||
padding: 0.75rem 0.875rem;
|
||||
border-radius: 0.75rem;
|
||||
margin-bottom: 1rem;
|
||||
border: 1px solid #e5e7eb;
|
||||
}
|
||||
|
||||
.result-section.ok {
|
||||
background: #ecfdf3;
|
||||
border-color: #22c55e33;
|
||||
}
|
||||
|
||||
.result-section.bad {
|
||||
background: #fef2f2;
|
||||
border-color: #ef444433;
|
||||
}
|
||||
|
||||
.result-section.warn {
|
||||
background: #fffbeb;
|
||||
border-color: #f9731633;
|
||||
}
|
||||
|
||||
.result-section.unknown {
|
||||
background: #f9fafb;
|
||||
}
|
||||
|
||||
.result-main {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.result-label {
|
||||
font-size: 0.875rem;
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.result-value {
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.result-ok {
|
||||
color: #16a34a;
|
||||
}
|
||||
|
||||
.result-bad {
|
||||
color: #dc2626;
|
||||
}
|
||||
|
||||
.result-warn {
|
||||
color: #d97706;
|
||||
}
|
||||
|
||||
.result-unknown {
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.result-score {
|
||||
margin-top: 0.5rem;
|
||||
font-size: 0.875rem;
|
||||
color: #4b5563;
|
||||
}
|
||||
|
||||
.info-block {
|
||||
margin-top: 1rem;
|
||||
padding: 0.75rem 0.875rem;
|
||||
border-radius: 0.75rem;
|
||||
background: #f9fafb;
|
||||
}
|
||||
|
||||
.block-title {
|
||||
font-size: 0.875rem;
|
||||
font-weight: 600;
|
||||
color: #374151;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.info-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
font-size: 0.875rem;
|
||||
padding: 0.15rem 0;
|
||||
}
|
||||
|
||||
.info-label {
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.info-value {
|
||||
color: #111827;
|
||||
margin-left: 1rem;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.empty-tip {
|
||||
color: #9ca3af;
|
||||
font-size: 0.875rem;
|
||||
padding: 1rem 0;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
235
src/ui/CJRZQ0B6Y.vue
Normal file
235
src/ui/CJRZQ0B6Y.vue
Normal file
@@ -0,0 +1,235 @@
|
||||
<template>
|
||||
<div class="card">
|
||||
<div class="header-box">
|
||||
<h3 class="header-title">银行卡黑名单(实时)</h3>
|
||||
<p class="header-desc">查询银行卡是否命中各类黑名单/风险名单</p>
|
||||
</div>
|
||||
|
||||
<div v-if="hasData" class="result-section">
|
||||
<div class="risk-summary" :class="summaryClass">
|
||||
<span class="risk-label">综合结果:</span>
|
||||
<span class="risk-value">
|
||||
{{ summaryText }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="risk-tags">
|
||||
<div v-for="item in riskItems" :key="item.key" class="risk-item" :class="item.hit ? 'hit' : 'normal'">
|
||||
<div class="risk-item-title">{{ item.label }}</div>
|
||||
<div class="risk-item-value">
|
||||
{{ item.hit ? '命中' : '未命中' }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="hasParams" class="info-block">
|
||||
<div class="block-title">被核验信息</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">持卡人姓名</span>
|
||||
<span class="info-value">{{ maskedName }}</span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">身份证号</span>
|
||||
<span class="info-value font-mono">{{ maskedIdCard }}</span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">银行卡号</span>
|
||||
<span class="info-value font-mono">{{ maskedBankCard }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="!hasData" class="empty-tip">暂无核验结果</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed } from 'vue';
|
||||
|
||||
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 hasData = computed(() => props.data && Object.keys(props.data).length > 0);
|
||||
|
||||
const riskItems = computed(() => {
|
||||
const d = props.data || {};
|
||||
const toBool = (v) => v === '1' || v === 1;
|
||||
return [
|
||||
{ key: 'badCardHolder', label: '不良持卡人', hit: toBool(d.badCardHolder) },
|
||||
{ key: 'caseRelated', label: '涉案卡片', hit: toBool(d.caseRelated) },
|
||||
{ key: 'fraudTrans', label: '交易欺诈卡片', hit: toBool(d.fraudTrans) },
|
||||
{ key: 'offlineBlack', label: '线下卡号黑名单', hit: toBool(d.offlineBlack) },
|
||||
{ key: 'onlineBlack', label: '线上卡号黑名单', hit: toBool(d.onlineBlack) },
|
||||
{ key: 'otherBlack', label: '其他卡号黑名单', hit: toBool(d.otherBlack) },
|
||||
];
|
||||
});
|
||||
|
||||
const anyHit = computed(() => riskItems.value.some((i) => i.hit));
|
||||
|
||||
const summaryText = computed(() => {
|
||||
if (!hasData.value) return '暂无结果';
|
||||
if (!anyHit.value) return '未命中任何银行卡黑名单';
|
||||
return '存在银行卡黑名单或风险记录';
|
||||
});
|
||||
|
||||
const summaryClass = computed(() => {
|
||||
if (!hasData.value) return 'summary unknown';
|
||||
if (anyHit.value) return 'summary bad';
|
||||
return 'summary ok';
|
||||
});
|
||||
|
||||
const maskedName = computed(() => {
|
||||
const name = props.params?.name || '';
|
||||
if (!name) return '-';
|
||||
return name.length > 1 ? name[0] + '*'.repeat(name.length - 1) : '*';
|
||||
});
|
||||
|
||||
const maskedIdCard = computed(() => {
|
||||
const id = props.params?.id_card || '';
|
||||
if (!id || id.length < 8) return id || '-';
|
||||
return id.slice(0, 4) + '********' + id.slice(-4);
|
||||
});
|
||||
|
||||
const maskedBankCard = computed(() => {
|
||||
const card = props.params?.bank_card || '';
|
||||
if (!card || card.length < 8) return card || '-';
|
||||
return card.slice(0, 4) + '********' + card.slice(-4);
|
||||
});
|
||||
|
||||
const hasParams = computed(() => {
|
||||
const p = props.params || {};
|
||||
return p.name || p.id_card || p.bank_card;
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.card {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.header-box {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.header-title {
|
||||
font-size: 1.125rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.header-desc {
|
||||
font-size: 0.875rem;
|
||||
color: #6b7280;
|
||||
margin-top: 0.25rem;
|
||||
}
|
||||
|
||||
.result-section {
|
||||
padding: 0.75rem 0.875rem;
|
||||
border-radius: 0.75rem;
|
||||
margin-bottom: 1rem;
|
||||
border: 1px solid #e5e7eb;
|
||||
background: #f9fafb;
|
||||
}
|
||||
|
||||
.risk-summary {
|
||||
font-size: 0.95rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.summary.ok {
|
||||
background: #ecfdf3;
|
||||
border-color: #22c55e33;
|
||||
}
|
||||
|
||||
.summary.bad {
|
||||
background: #fef2f2;
|
||||
border-color: #ef444433;
|
||||
}
|
||||
|
||||
.summary.unknown {
|
||||
background: #f9fafb;
|
||||
}
|
||||
|
||||
.risk-label {
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.risk-value {
|
||||
font-weight: 600;
|
||||
color: #111827;
|
||||
}
|
||||
|
||||
.risk-tags {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.risk-item {
|
||||
border-radius: 0.5rem;
|
||||
padding: 0.5rem 0.6rem;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
.risk-item.hit {
|
||||
background: #fef2f2;
|
||||
border: 1px solid #ef4444;
|
||||
color: #b91c1c;
|
||||
}
|
||||
|
||||
.risk-item.normal {
|
||||
background: #ecfdf3;
|
||||
border: 1px solid #22c55e33;
|
||||
color: #166534;
|
||||
}
|
||||
|
||||
.risk-item-title {
|
||||
margin-bottom: 0.15rem;
|
||||
}
|
||||
|
||||
.risk-item-value {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.info-block {
|
||||
margin-top: 1rem;
|
||||
padding: 0.75rem 0.875rem;
|
||||
border-radius: 0.75rem;
|
||||
background: #f9fafb;
|
||||
}
|
||||
|
||||
.block-title {
|
||||
font-size: 0.875rem;
|
||||
font-weight: 600;
|
||||
color: #374151;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.info-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
font-size: 0.875rem;
|
||||
padding: 0.15rem 0;
|
||||
}
|
||||
|
||||
.info-label {
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.info-value {
|
||||
color: #111827;
|
||||
margin-left: 1rem;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.empty-tip {
|
||||
color: #9ca3af;
|
||||
font-size: 0.875rem;
|
||||
padding: 1rem 0;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
227
src/ui/CJRZQACAB.vue
Normal file
227
src/ui/CJRZQACAB.vue
Normal file
@@ -0,0 +1,227 @@
|
||||
<template>
|
||||
<div class="card">
|
||||
<div class="header-box">
|
||||
<h3 class="header-title">银行卡四要素验证(详版)</h3>
|
||||
<p class="header-desc">核验姓名、身份证号、银行卡号与预留手机号是否匹配</p>
|
||||
</div>
|
||||
|
||||
<div v-if="hasData" class="result-section" :class="resultSectionClass">
|
||||
<div class="result-main">
|
||||
<div class="result-label">验证结果</div>
|
||||
<div class="result-value" :class="resultClass">
|
||||
{{ stateText }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="hasParams" class="info-block">
|
||||
<div class="block-title">被核验信息</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">持卡人姓名</span>
|
||||
<span class="info-value">{{ maskedName }}</span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">身份证号</span>
|
||||
<span class="info-value font-mono">{{ maskedIdCard }}</span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">银行卡号</span>
|
||||
<span class="info-value font-mono">{{ maskedBankCard }}</span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">预留手机号</span>
|
||||
<span class="info-value font-mono">{{ maskedMobile }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="!hasData" class="empty-tip">暂无核验结果</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed } from 'vue';
|
||||
|
||||
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 hasData = computed(() => props.data && Object.keys(props.data).length > 0);
|
||||
|
||||
const state = computed(() => props.data?.state || '');
|
||||
|
||||
const stateText = computed(() => {
|
||||
switch (state.value) {
|
||||
case '1':
|
||||
return '验证一致';
|
||||
case '2':
|
||||
return '认证不一致:此卡已过期或卡号无效';
|
||||
case '3':
|
||||
return '认证不一致:认证未通过';
|
||||
case '4':
|
||||
return '认证不一致:今日验证次数过多,请明日再试';
|
||||
case '5':
|
||||
return '认证不一致:当前提交数量过多,请降低提交频率';
|
||||
case '6':
|
||||
return '认证不一致:持卡人信息有误或卡状态异常';
|
||||
case '7':
|
||||
return '认证不一致:未开通无卡支付或发卡行不支持该卡验证';
|
||||
case '8':
|
||||
return '认证不一致:认证受限';
|
||||
default:
|
||||
return '暂无结果';
|
||||
}
|
||||
});
|
||||
|
||||
const resultClass = computed(() => {
|
||||
if (state.value === '1') return 'result-ok';
|
||||
if (!state.value) return 'result-unknown';
|
||||
return 'result-bad';
|
||||
});
|
||||
|
||||
const resultSectionClass = computed(() => {
|
||||
if (state.value === '1') return 'result-section ok';
|
||||
if (!state.value) return 'result-section unknown';
|
||||
return 'result-section bad';
|
||||
});
|
||||
|
||||
const maskedName = computed(() => {
|
||||
const name = props.params?.name || '';
|
||||
if (!name) return '-';
|
||||
return name.length > 1 ? name[0] + '*'.repeat(name.length - 1) : '*';
|
||||
});
|
||||
|
||||
const maskedIdCard = computed(() => {
|
||||
const id = props.params?.id_card || '';
|
||||
if (!id || id.length < 8) return id || '-';
|
||||
return id.slice(0, 4) + '********' + id.slice(-4);
|
||||
});
|
||||
|
||||
const maskedBankCard = computed(() => {
|
||||
const card = props.params?.bank_card || '';
|
||||
if (!card || card.length < 8) return card || '-';
|
||||
return card.slice(0, 4) + '********' + card.slice(-4);
|
||||
});
|
||||
|
||||
const maskedMobile = computed(() => {
|
||||
const m = props.params?.mobile || props.params?.mobile_no || '';
|
||||
if (!m || m.length < 7) return m || '-';
|
||||
return m.slice(0, 3) + '****' + m.slice(-4);
|
||||
});
|
||||
|
||||
const hasParams = computed(() => {
|
||||
const p = props.params || {};
|
||||
return p.name || p.id_card || p.bank_card || p.mobile || p.mobile_no;
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.card {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.header-box {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.header-title {
|
||||
font-size: 1.125rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.header-desc {
|
||||
font-size: 0.875rem;
|
||||
color: #6b7280;
|
||||
margin-top: 0.25rem;
|
||||
}
|
||||
|
||||
.result-section {
|
||||
padding: 0.75rem 0.875rem;
|
||||
border-radius: 0.75rem;
|
||||
margin-bottom: 1rem;
|
||||
border: 1px solid #e5e7eb;
|
||||
}
|
||||
|
||||
.result-section.ok {
|
||||
background: #ecfdf3;
|
||||
border-color: #22c55e33;
|
||||
}
|
||||
|
||||
.result-section.bad {
|
||||
background: #fef2f2;
|
||||
border-color: #ef444433;
|
||||
}
|
||||
|
||||
.result-section.unknown {
|
||||
background: #f9fafb;
|
||||
}
|
||||
|
||||
.result-main {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.result-label {
|
||||
font-size: 0.875rem;
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.result-value {
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.result-ok {
|
||||
color: #16a34a;
|
||||
}
|
||||
|
||||
.result-bad {
|
||||
color: #dc2626;
|
||||
}
|
||||
|
||||
.result-unknown {
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.info-block {
|
||||
margin-top: 1rem;
|
||||
padding: 0.75rem 0.875rem;
|
||||
border-radius: 0.75rem;
|
||||
background: #f9fafb;
|
||||
}
|
||||
|
||||
.block-title {
|
||||
font-size: 0.875rem;
|
||||
font-weight: 600;
|
||||
color: #374151;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.info-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
font-size: 0.875rem;
|
||||
padding: 0.15rem 0;
|
||||
}
|
||||
|
||||
.info-label {
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.info-value {
|
||||
color: #111827;
|
||||
margin-left: 1rem;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.empty-tip {
|
||||
color: #9ca3af;
|
||||
font-size: 0.875rem;
|
||||
padding: 1rem 0;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
204
src/ui/CQCXG1H7Y.vue
Normal file
204
src/ui/CQCXG1H7Y.vue
Normal file
@@ -0,0 +1,204 @@
|
||||
<template>
|
||||
<div class="card">
|
||||
<div class="header-box">
|
||||
<div class="header-left">
|
||||
<h3 class="header-title">车辆过户简版查询</h3>
|
||||
<p class="header-desc">查看车辆最近是否发生过户及过户次数</p>
|
||||
</div>
|
||||
<div class="header-tag" :class="flagClass">
|
||||
<span class="tag-label">是否过户</span>
|
||||
<span class="tag-value">{{ flagText }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template v-if="hasData">
|
||||
<div class="summary-card" :class="flagClass">
|
||||
<div class="summary-main">
|
||||
<div class="summary-title">
|
||||
最近过户情况
|
||||
</div>
|
||||
<div class="summary-flag">{{ flagDetailText }}</div>
|
||||
</div>
|
||||
<div class="summary-meta">
|
||||
<div class="meta-line">
|
||||
<span class="meta-label">最近过户时间</span>
|
||||
<span class="meta-value">{{ formattedTransferDate }}</span>
|
||||
</div>
|
||||
<div class="meta-line">
|
||||
<span class="meta-label">累计过户次数</span>
|
||||
<span class="meta-value strong">{{ data.transferNum || '0' }} 次</span>
|
||||
</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 hasData = computed(() => !!props.data && Object.keys(props.data).length > 0);
|
||||
|
||||
// transferFlag: 0-否;1-是(字符串)
|
||||
const flag = computed(() => props.data?.transferFlag);
|
||||
|
||||
const flagText = computed(() => {
|
||||
if (flag.value === '1') return '已过户';
|
||||
if (flag.value === '0') return '未过户';
|
||||
return '未知';
|
||||
});
|
||||
|
||||
const flagDetailText = computed(() => {
|
||||
if (flag.value === '1') return '该车辆存在过户记录';
|
||||
if (flag.value === '0') return '该车辆暂无过户记录';
|
||||
return '未能识别过户状态';
|
||||
});
|
||||
|
||||
const flagClass = computed(() => {
|
||||
if (flag.value === '1') return 'flag-yes';
|
||||
if (flag.value === '0') return 'flag-no';
|
||||
return 'flag-unknown';
|
||||
});
|
||||
|
||||
const formattedTransferDate = computed(() => {
|
||||
const raw = props.data?.transferDate;
|
||||
if (!raw) return '-';
|
||||
if (raw === '近一年内过户') return '近一年内过户';
|
||||
// 期望格式 yyyyMM
|
||||
if (raw.length === 6) {
|
||||
const y = raw.slice(0, 4);
|
||||
const m = raw.slice(4, 6);
|
||||
return `${y}年${m}月`;
|
||||
}
|
||||
return raw;
|
||||
});
|
||||
|
||||
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 justify-between mb-5 px-5 py-4 rounded-2xl bg-gradient-to-r from-sky-50 via-blue-50 to-sky-50;
|
||||
}
|
||||
|
||||
.header-left {
|
||||
@apply flex flex-col;
|
||||
}
|
||||
|
||||
.header-title {
|
||||
@apply text-2xl font-semibold m-0 text-sky-900;
|
||||
}
|
||||
|
||||
.header-desc {
|
||||
@apply text-base mt-3 m-0 text-sky-800 opacity-90;
|
||||
}
|
||||
|
||||
.header-tag {
|
||||
@apply inline-flex flex-col items-end gap-1 px-3 py-2 rounded-xl bg-white/80 shadow-sm;
|
||||
}
|
||||
|
||||
.header-tag.flag-yes {
|
||||
@apply text-amber-800;
|
||||
}
|
||||
|
||||
.header-tag.flag-no {
|
||||
@apply text-emerald-800;
|
||||
}
|
||||
|
||||
.header-tag.flag-unknown {
|
||||
@apply text-gray-700;
|
||||
}
|
||||
|
||||
.tag-label {
|
||||
@apply text-sm text-gray-500;
|
||||
}
|
||||
|
||||
.tag-value {
|
||||
@apply text-xl font-bold leading-none whitespace-nowrap;
|
||||
}
|
||||
|
||||
.summary-card {
|
||||
@apply rounded-2xl border px-5 py-4 mb-4;
|
||||
}
|
||||
|
||||
.summary-card.flag-yes {
|
||||
@apply bg-amber-50 border-amber-100;
|
||||
}
|
||||
|
||||
.summary-card.flag-no {
|
||||
@apply bg-emerald-50 border-emerald-100;
|
||||
}
|
||||
|
||||
.summary-card.flag-unknown {
|
||||
@apply bg-gray-50 border-gray-200;
|
||||
}
|
||||
|
||||
.summary-main {
|
||||
@apply flex items-baseline justify-between mb-3;
|
||||
}
|
||||
|
||||
.summary-title {
|
||||
@apply text-lg font-semibold text-gray-900;
|
||||
}
|
||||
|
||||
.summary-flag {
|
||||
@apply text-base text-gray-700;
|
||||
}
|
||||
|
||||
.summary-meta {
|
||||
@apply space-y-2 text-base text-gray-800;
|
||||
}
|
||||
|
||||
.meta-line {
|
||||
@apply flex items-center gap-2;
|
||||
}
|
||||
|
||||
.meta-label {
|
||||
@apply text-gray-500;
|
||||
}
|
||||
|
||||
.meta-value {
|
||||
@apply font-medium;
|
||||
}
|
||||
|
||||
.meta-value.strong {
|
||||
@apply text-lg font-semibold;
|
||||
}
|
||||
|
||||
.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>
|
||||
374
src/ui/CQCXG1U4U.vue
Normal file
374
src/ui/CQCXG1U4U.vue
Normal file
@@ -0,0 +1,374 @@
|
||||
<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" :class="suspectClass">
|
||||
<div class="summary-main">
|
||||
<div class="summary-left">
|
||||
<div class="vin-label">VIN</div>
|
||||
<div class="vin-value font-mono">{{ vin || '-' }}</div>
|
||||
</div>
|
||||
<div class="summary-right">
|
||||
<div class="summary-label">最新里程</div>
|
||||
<div class="summary-mileage">{{ latestMileageText }}</div>
|
||||
<div class="summary-sub">最近记录日期:{{ latestReportTime || '-' }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="summary-meta">
|
||||
<div class="meta-line">
|
||||
<span>里程是否异常:</span>
|
||||
<span class="strong" :class="suspectClass">{{ suspectedText }}</span>
|
||||
</div>
|
||||
<div class="meta-line" v-if="imageUrl">
|
||||
<span>行驶证图片:</span>
|
||||
</div>
|
||||
<div v-if="imageUrl" class="image-wrap">
|
||||
<img :src="imageUrl" alt="行驶证图片" class="licence-img" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 里程时间轴 -->
|
||||
<div class="detail-card">
|
||||
<h4 class="section-title">里程记录时间轴</h4>
|
||||
<div v-if="mileageList && mileageList.length" class="timeline">
|
||||
<div v-for="(item, idx) in mileageList" :key="idx" class="timeline-item">
|
||||
<div class="timeline-left">
|
||||
<div class="dot-wrap">
|
||||
<div class="dot" :class="item.mileageStatus === '1' ? 'dot-abnormal' : 'dot-normal'">
|
||||
</div>
|
||||
<div v-if="idx !== mileageList.length - 1" class="line"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="timeline-content">
|
||||
<div class="row-main">
|
||||
<div class="date">{{ formatDate(item.reportTime) }}</div>
|
||||
<div class="km">{{ formatMileage(item.mileage) }}</div>
|
||||
</div>
|
||||
<div class="row-sub">
|
||||
<span>来源:{{ sourceText(item.source) }}</span>
|
||||
<span v-if="item.mileageStatus === '1'" class="badge-abnormal">异常里程</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="empty-small">
|
||||
暂无里程记录
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 异常里程列表 -->
|
||||
<div class="detail-card" v-if="adjustList && adjustList.length">
|
||||
<h4 class="section-title">疑似调表记录</h4>
|
||||
<div class="adjust-list">
|
||||
<div v-for="(item, idx) in adjustList" :key="idx" class="adjust-item">
|
||||
<div class="adjust-time">{{ formatDate(item.reportTime) }}</div>
|
||||
<div class="adjust-body">
|
||||
<div>
|
||||
<div class="adjust-label">调整前</div>
|
||||
<div class="adjust-value">{{ formatMileage(item.beforeMileage) }}</div>
|
||||
</div>
|
||||
<div class="adjust-arrow">→</div>
|
||||
<div>
|
||||
<div class="adjust-label">调整后</div>
|
||||
<div class="adjust-value">{{ formatMileage(item.afterMileage) }}</div>
|
||||
</div>
|
||||
</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 hasData = computed(() => !!props.data && Object.keys(props.data).length > 0);
|
||||
|
||||
const vin = computed(() => props.data?.vehicleInfo?.vin || '');
|
||||
const mileageList = computed(() => props.data?.mileageInfo?.mileageList || []);
|
||||
const adjustList = computed(() => props.data?.mileageInfo?.suspectedAdjustMileageList || []);
|
||||
const suspectedAdjust = computed(() => props.data?.mileageInfo?.suspectedAdjust);
|
||||
const imageUrl = computed(() => props.data?.imageUrl || '');
|
||||
|
||||
const latestRecord = computed(() => {
|
||||
const list = mileageList.value;
|
||||
if (!list || !list.length) return null;
|
||||
// 默认接口数据已按时间升序,直接取最后一条
|
||||
return list[list.length - 1];
|
||||
});
|
||||
|
||||
const latestMileageText = computed(() => {
|
||||
if (!latestRecord.value) return '-';
|
||||
return formatMileage(latestRecord.value.mileage);
|
||||
});
|
||||
|
||||
const latestReportTime = computed(() => latestRecord.value?.reportTime || '');
|
||||
|
||||
const suspectedText = computed(() => {
|
||||
if (suspectedAdjust.value === 'true') return '存在异常里程行为';
|
||||
if (suspectedAdjust.value === 'false') return '未发现里程异常';
|
||||
return '未知';
|
||||
});
|
||||
|
||||
const suspectClass = computed(() => {
|
||||
if (suspectedAdjust.value === 'true') return 'suspect-yes';
|
||||
if (suspectedAdjust.value === 'false') return 'suspect-no';
|
||||
return 'suspect-unknown';
|
||||
});
|
||||
|
||||
const formatMileage = (val) => {
|
||||
if (!val && val !== 0) return '-';
|
||||
const num = Number(val);
|
||||
if (Number.isNaN(num)) return `${val} km`;
|
||||
// 默认 km,按 km 展示
|
||||
return `${num.toLocaleString()} km`;
|
||||
};
|
||||
|
||||
const sourceText = (source) => {
|
||||
if (source === '0') return '诊断里程';
|
||||
if (source === '1') return '维保里程';
|
||||
return '其他';
|
||||
};
|
||||
|
||||
const formatDate = (val) => {
|
||||
if (!val) return '-';
|
||||
// 期望格式 yyyy-MM-dd
|
||||
const m = String(val).match(/^(\d{4})-(\d{2})-(\d{2})/);
|
||||
if (m) {
|
||||
return `${m[1]}年${m[2]}月${m[3]}日`;
|
||||
}
|
||||
return val;
|
||||
};
|
||||
|
||||
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-sky-50 via-blue-50 to-indigo-50;
|
||||
}
|
||||
|
||||
.header-left {
|
||||
@apply flex flex-col;
|
||||
}
|
||||
|
||||
.header-title {
|
||||
@apply text-2xl font-semibold m-0 text-sky-900;
|
||||
}
|
||||
|
||||
.header-desc {
|
||||
@apply text-base mt-3 m-0 text-sky-800 opacity-90;
|
||||
}
|
||||
|
||||
.summary-card {
|
||||
@apply rounded-2xl border px-5 py-4 mb-4;
|
||||
}
|
||||
|
||||
.summary-card.suspect-yes {
|
||||
@apply bg-amber-50 border-amber-100;
|
||||
}
|
||||
|
||||
.summary-card.suspect-no {
|
||||
@apply bg-emerald-50 border-emerald-100;
|
||||
}
|
||||
|
||||
.summary-card.suspect-unknown {
|
||||
@apply bg-gray-50 border-gray-200;
|
||||
}
|
||||
|
||||
.summary-main {
|
||||
@apply flex flex-col mb-3 gap-3;
|
||||
}
|
||||
|
||||
.summary-left {
|
||||
@apply flex flex-col gap-1;
|
||||
}
|
||||
|
||||
.vin-label {
|
||||
@apply text-sm text-gray-500;
|
||||
}
|
||||
|
||||
.vin-value {
|
||||
@apply text-base text-gray-900;
|
||||
}
|
||||
|
||||
.summary-right {
|
||||
@apply text-left;
|
||||
}
|
||||
|
||||
.summary-label {
|
||||
@apply text-sm text-gray-500;
|
||||
}
|
||||
|
||||
.summary-mileage {
|
||||
@apply text-2xl font-bold text-sky-800 mt-1;
|
||||
}
|
||||
|
||||
.summary-sub {
|
||||
@apply text-sm text-sky-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 .strong {
|
||||
@apply font-semibold;
|
||||
}
|
||||
|
||||
.image-wrap {
|
||||
@apply mt-2 flex justify-center;
|
||||
}
|
||||
|
||||
.licence-img {
|
||||
@apply rounded-xl border border-gray-200 max-w-full;
|
||||
max-height: 220px;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.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 mb-3;
|
||||
}
|
||||
|
||||
.timeline-left {
|
||||
@apply mr-3 flex flex-col items-center;
|
||||
}
|
||||
|
||||
.dot-wrap {
|
||||
@apply flex flex-col items-stretch;
|
||||
}
|
||||
|
||||
.dot {
|
||||
@apply w-3 h-3 rounded-full bg-gray-400 self-center;
|
||||
}
|
||||
|
||||
.dot-normal {
|
||||
@apply bg-emerald-500;
|
||||
}
|
||||
|
||||
.dot-abnormal {
|
||||
@apply bg-red-500;
|
||||
}
|
||||
|
||||
.line {
|
||||
@apply flex-1 w-px bg-gray-300 mx-auto;
|
||||
}
|
||||
|
||||
.timeline-content {
|
||||
@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 .km {
|
||||
@apply text-lg font-semibold text-gray-900;
|
||||
}
|
||||
|
||||
.row-sub {
|
||||
@apply flex items-center justify-between text-sm text-gray-600 mt-1;
|
||||
}
|
||||
|
||||
.badge-abnormal {
|
||||
@apply inline-flex items-center px-2 py-0.5 rounded-full bg-red-50 text-red-700 text-xs font-medium;
|
||||
}
|
||||
|
||||
.adjust-list {
|
||||
@apply space-y-3;
|
||||
}
|
||||
|
||||
.adjust-item {
|
||||
@apply rounded-2xl border border-amber-100 bg-amber-50/70 px-4 py-3;
|
||||
}
|
||||
|
||||
.adjust-time {
|
||||
@apply text-sm text-gray-700 mb-2;
|
||||
}
|
||||
|
||||
.adjust-body {
|
||||
@apply flex items-center justify-between gap-4;
|
||||
}
|
||||
|
||||
.adjust-label {
|
||||
@apply text-xs text-gray-500 mb-1;
|
||||
}
|
||||
|
||||
.adjust-value {
|
||||
@apply text-base font-semibold text-gray-900;
|
||||
}
|
||||
|
||||
.adjust-arrow {
|
||||
@apply text-2xl text-gray-400;
|
||||
}
|
||||
|
||||
.empty {
|
||||
@apply text-center py-10 text-gray-500;
|
||||
}
|
||||
|
||||
.empty-small {
|
||||
@apply text-center py-6 text-sm 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>
|
||||
284
src/ui/CQCXG3Y6B.vue
Normal file
284
src/ui/CQCXG3Y6B.vue
Normal file
@@ -0,0 +1,284 @@
|
||||
<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">
|
||||
<!-- 概览:VIN + 维保次数 + 最近一次维保 -->
|
||||
<div class="summary-card">
|
||||
<div class="summary-main">
|
||||
<div class="summary-left">
|
||||
<div class="summary-label">车架号 VIN</div>
|
||||
<div class="summary-vin font-mono">{{ vin || '-' }}</div>
|
||||
</div>
|
||||
<div class="summary-right">
|
||||
<div class="summary-count">维保记录 {{ totalCount }} 条</div>
|
||||
<div class="summary-last" v-if="lastRecord">
|
||||
最近一次:{{ formatDate(lastRecord.lastTime) }} · {{ formatMileage(lastRecord.mileage) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 维保时间轴 -->
|
||||
<div class="detail-card">
|
||||
<h4 class="section-title">维保记录时间轴</h4>
|
||||
<div v-if="records && records.length" class="timeline">
|
||||
<div v-for="(item, idx) in records" :key="idx" class="timeline-item">
|
||||
<div class="timeline-left">
|
||||
<div class="dot-wrap">
|
||||
<div class="dot"></div>
|
||||
<div v-if="idx !== records.length - 1" class="line"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="timeline-content">
|
||||
<div class="row-main">
|
||||
<div class="date">{{ formatDate(item.lastTime) }}</div>
|
||||
<div class="km">{{ formatMileage(item.mileage) }}</div>
|
||||
</div>
|
||||
<div class="row-sub">
|
||||
<span class="repair-type">{{ item.repairType || '维保' }}</span>
|
||||
<span class="vin-small font-mono">VIN: {{ item.vin || vin || '-' }}</span>
|
||||
</div>
|
||||
|
||||
<div v-if="item.details && item.details.length" class="sub-section">
|
||||
<div class="sub-title">维修项目</div>
|
||||
<ul class="sub-list">
|
||||
<li v-for="(d, di) in item.details" :key="di">
|
||||
<span v-if="d.type" class="tag">{{ d.type }}</span>
|
||||
<span>{{ d.content }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div v-if="item.materials && item.materials.length" class="sub-section">
|
||||
<div class="sub-title">使用材料</div>
|
||||
<ul class="sub-list">
|
||||
<li v-for="(m, mi) in item.materials" :key="mi">
|
||||
<span v-if="m.type" class="tag tag-material">{{ m.type }}</span>
|
||||
<span>{{ m.content }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="empty-small">
|
||||
暂无维保记录
|
||||
</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 hasData = computed(() => !!props.data && Object.keys(props.data).length > 0);
|
||||
const records = computed(() => Array.isArray(props.data?.record) ? props.data.record : []);
|
||||
|
||||
const vin = computed(() => {
|
||||
if (records.value.length && records.value[0].vin) return records.value[0].vin;
|
||||
return props.params?.vin_code || '';
|
||||
});
|
||||
|
||||
const totalCount = computed(() => records.value.length || 0);
|
||||
const lastRecord = computed(() => (records.value.length ? records.value[records.value.length - 1] : null));
|
||||
|
||||
const formatDate = (val) => {
|
||||
if (!val) return '-';
|
||||
const m = String(val).match(/^(\d{4})-(\d{2})-(\d{2})/);
|
||||
if (m) {
|
||||
return `${m[1]}年${m[2]}月${m[3]}日`;
|
||||
}
|
||||
return val;
|
||||
};
|
||||
|
||||
const formatMileage = (val) => {
|
||||
if (!val && val !== 0) return '-';
|
||||
const num = Number(val);
|
||||
if (Number.isNaN(num)) return `${val} km`;
|
||||
return `${num.toLocaleString()} km`;
|
||||
};
|
||||
|
||||
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-emerald-50 via-sky-50 to-teal-50;
|
||||
}
|
||||
|
||||
.header-left {
|
||||
@apply flex flex-col;
|
||||
}
|
||||
|
||||
.header-title {
|
||||
@apply text-2xl font-semibold m-0 text-emerald-900;
|
||||
}
|
||||
|
||||
.header-desc {
|
||||
@apply text-base mt-3 m-0 text-emerald-800 opacity-90;
|
||||
}
|
||||
|
||||
.summary-card {
|
||||
@apply rounded-2xl border border-emerald-100 bg-emerald-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-label {
|
||||
@apply text-sm text-gray-500;
|
||||
}
|
||||
|
||||
.summary-vin {
|
||||
@apply text-base text-gray-900;
|
||||
}
|
||||
|
||||
.summary-right {
|
||||
@apply text-right;
|
||||
}
|
||||
|
||||
.summary-count {
|
||||
@apply text-base font-semibold text-gray-900;
|
||||
}
|
||||
|
||||
.summary-last {
|
||||
@apply text-sm text-emerald-800 mt-1;
|
||||
}
|
||||
|
||||
.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 mb-3;
|
||||
}
|
||||
|
||||
.timeline-left {
|
||||
@apply mr-3 flex flex-col items-center;
|
||||
}
|
||||
|
||||
.dot-wrap {
|
||||
@apply flex flex-col items-stretch;
|
||||
}
|
||||
|
||||
.dot {
|
||||
@apply w-3 h-3 rounded-full bg-emerald-500 self-center;
|
||||
}
|
||||
|
||||
.line {
|
||||
@apply flex-1 w-px bg-gray-300 mx-auto;
|
||||
}
|
||||
|
||||
.timeline-content {
|
||||
@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 .km {
|
||||
@apply text-lg font-semibold text-gray-900;
|
||||
}
|
||||
|
||||
.row-sub {
|
||||
@apply flex items-center justify-between text-sm text-gray-600 mt-1;
|
||||
}
|
||||
|
||||
.repair-type {
|
||||
@apply font-medium text-gray-800;
|
||||
}
|
||||
|
||||
.vin-small {
|
||||
@apply text-xs text-gray-500;
|
||||
}
|
||||
|
||||
.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-emerald-50 text-emerald-700 text-xs font-medium;
|
||||
}
|
||||
|
||||
.tag-material {
|
||||
@apply bg-sky-50 text-sky-700;
|
||||
}
|
||||
|
||||
.empty {
|
||||
@apply text-center py-10 text-gray-500;
|
||||
}
|
||||
|
||||
.empty-small {
|
||||
@apply text-center py-6 text-sm 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>
|
||||
288
src/ui/CQCXG3Z3L.vue
Normal file
288
src/ui/CQCXG3Z3L.vue
Normal file
@@ -0,0 +1,288 @@
|
||||
<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">
|
||||
<!-- 概览:品牌 + VIN + 车牌号 + 发动机号 -->
|
||||
<div class="summary-card">
|
||||
<div class="summary-main">
|
||||
<div class="summary-left">
|
||||
<div class="summary-label">品牌名称</div>
|
||||
<div class="summary-brand">{{ data.brandName || '未知品牌' }}</div>
|
||||
</div>
|
||||
<div class="summary-right">
|
||||
<div class="summary-label">车架号 VIN</div>
|
||||
<div class="summary-vin font-mono">{{ data.vin || '-' }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="summary-meta">
|
||||
<div class="meta-line">
|
||||
<span>车牌号:</span><span class="strong">{{ data.licensePlate || '未提供' }}</span>
|
||||
<span class="dot"></span>
|
||||
<span>发动机号:</span><span class="strong code">{{ data.engine || '-' }}</span>
|
||||
</div>
|
||||
<div class="meta-line" v-if="records.length">
|
||||
<span>维保记录:</span><span class="strong">{{ records.length }} 条</span>
|
||||
<span class="dot"></span>
|
||||
<span>最近一次:</span>
|
||||
<span class="strong">{{ formatDate(records[records.length - 1].date) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 维保时间轴 -->
|
||||
<div class="detail-card">
|
||||
<h4 class="section-title">维保记录时间轴</h4>
|
||||
<div v-if="records && records.length" class="timeline">
|
||||
<div v-for="(item, idx) in records" :key="idx" class="timeline-item">
|
||||
<div class="timeline-left">
|
||||
<div class="dot-wrap">
|
||||
<div class="dot"></div>
|
||||
<div v-if="idx !== records.length - 1" class="line"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="timeline-content">
|
||||
<div class="row-main">
|
||||
<div class="date">{{ formatDate(item.date) }}</div>
|
||||
<div class="km">{{ formatMileage(item.mileage) }}</div>
|
||||
</div>
|
||||
<div class="row-sub">
|
||||
<span class="repair-type">{{ item.type || '维保' }}</span>
|
||||
<span class="brand-small" v-if="data.brandName">{{ data.brandName }}</span>
|
||||
</div>
|
||||
|
||||
<div class="sub-section" v-if="item.content">
|
||||
<div class="sub-title">维修内容</div>
|
||||
<div class="sub-text">{{ item.content }}</div>
|
||||
</div>
|
||||
|
||||
<div class="sub-section" v-if="item.material">
|
||||
<div class="sub-title">材料</div>
|
||||
<div class="sub-text">{{ item.material }}</div>
|
||||
</div>
|
||||
|
||||
<div class="sub-section" v-if="item.remark">
|
||||
<div class="sub-title">备注</div>
|
||||
<div class="sub-text">{{ item.remark }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="empty-small">
|
||||
暂无维保记录
|
||||
</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 hasData = computed(() => !!props.data && Object.keys(props.data).length > 0);
|
||||
const records = computed(() => Array.isArray(props.data?.record) ? props.data.record : []);
|
||||
|
||||
const data = computed(() => props.data || {});
|
||||
|
||||
const formatDate = (val) => {
|
||||
if (!val) return '-';
|
||||
const m = String(val).match(/^(\d{4})-(\d{2})-(\d{2})/);
|
||||
if (m) {
|
||||
return `${m[1]}年${m[2]}月${m[3]}日`;
|
||||
}
|
||||
return val;
|
||||
};
|
||||
|
||||
const formatMileage = (val) => {
|
||||
if (!val && val !== 0) return '-';
|
||||
const num = Number(val);
|
||||
if (Number.isNaN(num)) return `${val} km`;
|
||||
return `${num.toLocaleString()} km`;
|
||||
};
|
||||
|
||||
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-indigo-50 via-sky-50 to-emerald-50;
|
||||
}
|
||||
|
||||
.header-left {
|
||||
@apply flex flex-col;
|
||||
}
|
||||
|
||||
.header-title {
|
||||
@apply text-2xl font-semibold m-0 text-indigo-900;
|
||||
}
|
||||
|
||||
.header-desc {
|
||||
@apply text-base mt-3 m-0 text-indigo-800 opacity-90;
|
||||
}
|
||||
|
||||
.summary-card {
|
||||
@apply rounded-2xl border border-indigo-100 bg-indigo-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-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;
|
||||
}
|
||||
|
||||
.timeline {
|
||||
@apply mt-2;
|
||||
}
|
||||
|
||||
.timeline-item {
|
||||
@apply flex mb-3;
|
||||
}
|
||||
|
||||
.timeline-left {
|
||||
@apply mr-3 flex flex-col items-center;
|
||||
}
|
||||
|
||||
.dot-wrap {
|
||||
@apply flex flex-col items-stretch;
|
||||
}
|
||||
|
||||
.dot {
|
||||
@apply w-3 h-3 rounded-full bg-indigo-500 self-center;
|
||||
}
|
||||
|
||||
.line {
|
||||
@apply flex-1 w-px bg-gray-300 mx-auto;
|
||||
}
|
||||
|
||||
.timeline-content {
|
||||
@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 .km {
|
||||
@apply text-lg font-semibold text-gray-900;
|
||||
}
|
||||
|
||||
.row-sub {
|
||||
@apply flex items-center justify-between text-sm text-gray-600 mt-1;
|
||||
}
|
||||
|
||||
.repair-type {
|
||||
@apply font-medium text-gray-800;
|
||||
}
|
||||
|
||||
.brand-small {
|
||||
@apply text-xs text-gray-500;
|
||||
}
|
||||
|
||||
.sub-section {
|
||||
@apply mt-3;
|
||||
}
|
||||
|
||||
.sub-title {
|
||||
@apply text-sm font-semibold text-gray-800 mb-1;
|
||||
}
|
||||
|
||||
.sub-text {
|
||||
@apply text-sm text-gray-800 whitespace-pre-wrap break-words;
|
||||
}
|
||||
|
||||
.empty {
|
||||
@apply text-center py-10 text-gray-500;
|
||||
}
|
||||
|
||||
.empty-small {
|
||||
@apply text-center py-6 text-sm 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>
|
||||
126
src/ui/CQCXG4D2E.vue
Normal file
126
src/ui/CQCXG4D2E.vue
Normal file
@@ -0,0 +1,126 @@
|
||||
<template>
|
||||
<div class="bg-white rounded-lg shadow-sm border border-gray-200 p-6">
|
||||
<!-- 车辆总数统计 -->
|
||||
<div class="flex justify-between items-center mb-6 pb-4 border-b border-gray-100">
|
||||
<div class="flex items-center gap-3">
|
||||
<div>
|
||||
<div class="text-lg font-semibold text-gray-900">名下车辆(数量)</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bg-blue-50 text-blue-700 px-4 py-2 rounded-full text-sm font-medium">
|
||||
共 {{ vehicleCount }} 辆
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 车辆列表 -->
|
||||
<div class="space-y-3" v-if="vehicleList && vehicleList.length > 0">
|
||||
<div class="bg-gray-50 rounded-lg p-4 border border-gray-200 hover:bg-blue-50 hover:border-blue-200 transition-colors duration-200"
|
||||
v-for="(vehicle, index) in vehicleList" :key="index">
|
||||
<div class="space-y-3">
|
||||
<div class="text-xl font-bold text-gray-900 font-mono tracking-wider">
|
||||
{{ vehicle.plateNum }}
|
||||
</div>
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="inline-flex items-center gap-1 px-3 py-1 rounded text-xs font-medium text-white"
|
||||
:class="getPlateColorClass(vehicle.plateColor)">
|
||||
<span>🏷️</span>
|
||||
<span>{{ getPlateColorText(vehicle.plateColor) }}</span>
|
||||
</div>
|
||||
<div class="text-sm text-gray-600">
|
||||
<span class="text-gray-500">车辆类型:</span>
|
||||
<span class="font-medium text-gray-900 ml-1">{{ getVehicleTypeText(vehicle.vehicleType)
|
||||
}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 无数据状态 -->
|
||||
<div class="text-center py-12 text-gray-500" v-else>
|
||||
<div class="text-4xl mb-3">🚫</div>
|
||||
<div class="text-lg font-medium mb-1">暂无车辆信息</div>
|
||||
<div class="text-sm">No vehicle records found</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { defineProps, computed } from 'vue';
|
||||
import { useRiskNotifier } from '@/composables/useRiskNotifier';
|
||||
|
||||
const props = defineProps({
|
||||
data: Object,
|
||||
params: Object,
|
||||
apiId: { type: String, default: '' },
|
||||
index: { type: Number, default: 0 },
|
||||
notifyRiskStatus: { type: Function, default: () => { } },
|
||||
});
|
||||
|
||||
const plateColorMap = {
|
||||
0: '蓝色 - 普通燃油车',
|
||||
1: '黄色 - 大型车/货车',
|
||||
2: '黑色 - 外籍车辆/港澳台车',
|
||||
3: '白色 - 警车/军车/武警车',
|
||||
4: '渐变绿色 - 新能源汽车',
|
||||
5: '黄绿双拼色 - 大型新能源汽车',
|
||||
6: '蓝白渐变色 - 临时牌照',
|
||||
7: '临时牌照 - 临时行驶车辆',
|
||||
11: '绿色 - 新能源汽车',
|
||||
12: '红色 - 教练车/试验车'
|
||||
};
|
||||
|
||||
const vehicleTypeMap = {
|
||||
1: '一型客车',
|
||||
2: '二型客车',
|
||||
3: '三型客车',
|
||||
4: '四型客车',
|
||||
11: '一型货车',
|
||||
12: '二型货车',
|
||||
13: '三型货车',
|
||||
14: '四型货车',
|
||||
15: '五型货车',
|
||||
16: '六型货车',
|
||||
21: '一型专项作业车',
|
||||
22: '二型专项作业车',
|
||||
23: '三型专项作业车',
|
||||
24: '四型专项作业车',
|
||||
25: '五型专项作业车',
|
||||
26: '六型专项作业车'
|
||||
};
|
||||
|
||||
const vehicleList = computed(() => props.data?.list || []);
|
||||
const vehicleCount = computed(() => props.data?.vehicleCount || 0);
|
||||
|
||||
const getPlateColorText = (plateColor) => {
|
||||
return plateColorMap[plateColor] || '未知颜色 - 未知类型';
|
||||
};
|
||||
|
||||
const getPlateColorClass = (plateColor) => {
|
||||
const colorClassMap = {
|
||||
0: 'bg-blue-500',
|
||||
1: 'bg-yellow-500',
|
||||
2: 'bg-gray-800',
|
||||
3: 'bg-gray-200 text-gray-800',
|
||||
4: 'bg-green-500',
|
||||
5: 'bg-gradient-to-r from-yellow-500 to-green-500',
|
||||
6: 'bg-gradient-to-r from-blue-500 to-white text-blue-800',
|
||||
7: 'bg-red-500',
|
||||
11: 'bg-green-500',
|
||||
12: 'bg-red-500'
|
||||
};
|
||||
return colorClassMap[plateColor] || 'bg-gray-500';
|
||||
};
|
||||
|
||||
const getVehicleTypeText = (vehicleType) => {
|
||||
return vehicleTypeMap[vehicleType] || '未知类型';
|
||||
};
|
||||
|
||||
const riskScore = computed(() => 100);
|
||||
useRiskNotifier(props, riskScore);
|
||||
defineExpose({ riskScore });
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 保持与 CQCXG9P1C 一致的布局风格 */
|
||||
</style>
|
||||
256
src/ui/CQCXG4I1Z.vue
Normal file
256
src/ui/CQCXG4I1Z.vue
Normal file
@@ -0,0 +1,256 @@
|
||||
<template>
|
||||
<div class="card">
|
||||
<div class="header-box">
|
||||
<div class="header-left">
|
||||
<h3 class="header-title">车辆过户详版查询</h3>
|
||||
<p class="header-desc">按时间轴展示每一次车辆过户的车牌与地区变更情况</p>
|
||||
</div>
|
||||
<div class="header-tag" v-if="totalTimes">
|
||||
<span class="tag-label">总过户次数</span>
|
||||
<span class="tag-value">{{ totalTimes }} 次</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template v-if="transfers && transfers.length">
|
||||
<div class="timeline">
|
||||
<div v-for="(item, index) in transfers" :key="index" class="timeline-item">
|
||||
<div class="timeline-left">
|
||||
<div class="dot-wrap">
|
||||
<div class="dot"></div>
|
||||
<div v-if="index !== transfers.length - 1" class="line"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="timeline-content">
|
||||
<div class="transfer-header">
|
||||
<div class="transfer-date">{{ item.changeMonthFormatted }}</div>
|
||||
<div class="transfer-count">第 {{ item.transTimeSum }} 次过户</div>
|
||||
</div>
|
||||
<div class="plates-row">
|
||||
<div class="plate old">
|
||||
<div class="label">过户前车牌</div>
|
||||
<div class="value">{{ item.oldCp || '未知' }}</div>
|
||||
<div class="city" v-if="item.cityBefore">所在城市:{{ item.cityBefore }}</div>
|
||||
</div>
|
||||
<div class="arrow">→</div>
|
||||
<div class="plate new">
|
||||
<div class="label">过户后车牌</div>
|
||||
<div class="value">{{ item.newCp || '未知' }}</div>
|
||||
<div class="city" v-if="item.cityAfter">所在城市:{{ item.cityAfter }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="interval-row">
|
||||
<span>距上次过户:</span>
|
||||
<span class="strong">{{ item.intervalText }}</span>
|
||||
</div>
|
||||
<div class="vin-row">
|
||||
<span class="vin-label">VIN:</span>
|
||||
<span class="vin-value font-mono">{{ item.vin || '-' }}</span>
|
||||
</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 rawList = computed(() => Array.isArray(props.data?.retdata) ? props.data.retdata : []);
|
||||
|
||||
const transfers = computed(() =>
|
||||
rawList.value.map((item) => {
|
||||
const changeMonth = item.changeMonth;
|
||||
let changeMonthFormatted = '-';
|
||||
if (changeMonth === '近一年内过户') {
|
||||
changeMonthFormatted = '近一年内过户';
|
||||
} else if (typeof changeMonth === 'string' && changeMonth.length === 6) {
|
||||
const y = changeMonth.slice(0, 4);
|
||||
const m = changeMonth.slice(4, 6);
|
||||
changeMonthFormatted = `${y}年${m}月`;
|
||||
} else if (changeMonth) {
|
||||
changeMonthFormatted = changeMonth;
|
||||
}
|
||||
|
||||
let intervalText = '-';
|
||||
if (item.transYear || item.transMonth) {
|
||||
const years = item.transYear ? `${item.transYear}年` : '';
|
||||
const months = item.transMonth ? `${item.transMonth}个月` : '';
|
||||
intervalText = `${years}${months}` || '-';
|
||||
}
|
||||
|
||||
return {
|
||||
...item,
|
||||
changeMonthFormatted,
|
||||
intervalText,
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
const totalTimes = computed(() => {
|
||||
if (!transfers.value.length) return '';
|
||||
const last = transfers.value[transfers.value.length - 1];
|
||||
return last.transTimeSum ?? '';
|
||||
});
|
||||
|
||||
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 justify-between mb-5 px-5 py-4 rounded-2xl bg-gradient-to-r from-indigo-50 via-blue-50 to-indigo-50;
|
||||
}
|
||||
|
||||
.header-left {
|
||||
@apply flex flex-col;
|
||||
}
|
||||
|
||||
.header-title {
|
||||
@apply text-2xl font-semibold m-0 text-indigo-900;
|
||||
}
|
||||
|
||||
.header-desc {
|
||||
@apply text-base mt-3 m-0 text-indigo-800 opacity-90;
|
||||
}
|
||||
|
||||
.header-tag {
|
||||
@apply inline-flex flex-col items-end gap-1 px-3 py-2 rounded-xl bg-white/80 shadow-sm text-indigo-800;
|
||||
}
|
||||
|
||||
.tag-label {
|
||||
@apply text-sm text-gray-500;
|
||||
}
|
||||
|
||||
.tag-value {
|
||||
@apply text-2xl font-bold leading-none whitespace-nowrap;
|
||||
}
|
||||
|
||||
.timeline {
|
||||
@apply mt-4;
|
||||
}
|
||||
|
||||
.timeline-item {
|
||||
@apply flex mb-4;
|
||||
}
|
||||
|
||||
.timeline-left {
|
||||
@apply mr-3 flex flex-col items-center;
|
||||
}
|
||||
|
||||
.dot-wrap {
|
||||
@apply flex flex-col items-center;
|
||||
}
|
||||
|
||||
.dot {
|
||||
@apply w-3 h-3 rounded-full bg-indigo-500;
|
||||
}
|
||||
|
||||
.line {
|
||||
@apply flex-1 w-px bg-gray-300 mt-1;
|
||||
}
|
||||
|
||||
.timeline-content {
|
||||
@apply flex-1 rounded-2xl border border-gray-100 bg-gray-50/70 px-4 py-3;
|
||||
}
|
||||
|
||||
.transfer-header {
|
||||
@apply flex items-baseline justify-between mb-2;
|
||||
}
|
||||
|
||||
.transfer-date {
|
||||
@apply text-lg font-semibold text-gray-900;
|
||||
}
|
||||
|
||||
.transfer-count {
|
||||
@apply text-sm text-gray-600;
|
||||
}
|
||||
|
||||
.plates-row {
|
||||
@apply flex items-stretch gap-3 mt-2;
|
||||
}
|
||||
|
||||
.plate {
|
||||
@apply flex-1 rounded-xl px-3 py-2 border border-gray-200 bg-white;
|
||||
}
|
||||
|
||||
.plate .label {
|
||||
@apply text-xs text-gray-500 mb-1;
|
||||
}
|
||||
|
||||
.plate .value {
|
||||
@apply text-lg font-semibold text-gray-900;
|
||||
}
|
||||
|
||||
.plate .city {
|
||||
@apply text-sm text-gray-600 mt-1;
|
||||
}
|
||||
|
||||
.plate.old {
|
||||
@apply bg-gray-50;
|
||||
}
|
||||
|
||||
.plate.new {
|
||||
@apply bg-indigo-50/60 border-indigo-100;
|
||||
}
|
||||
|
||||
.arrow {
|
||||
@apply flex items-center justify-center text-2xl text-gray-400;
|
||||
}
|
||||
|
||||
.interval-row {
|
||||
@apply mt-3 text-base text-gray-700;
|
||||
}
|
||||
|
||||
.interval-row .strong {
|
||||
@apply font-semibold;
|
||||
}
|
||||
|
||||
.vin-row {
|
||||
@apply mt-2 text-sm text-gray-600;
|
||||
}
|
||||
|
||||
.vin-label {
|
||||
@apply text-gray-500;
|
||||
}
|
||||
|
||||
.vin-value {
|
||||
@apply text-base text-gray-900;
|
||||
}
|
||||
|
||||
.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>
|
||||
212
src/ui/CQCXG5U0Z.vue
Normal file
212
src/ui/CQCXG5U0Z.vue
Normal file
@@ -0,0 +1,212 @@
|
||||
<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="records && records.length">
|
||||
<div v-for="(item, idx) in records" :key="idx" class="vehicle-card">
|
||||
<div class="vehicle-title">
|
||||
<div>
|
||||
<div class="vehicle-chip">车辆 {{ idx + 1 }}</div>
|
||||
<div class="vehicle-model">
|
||||
{{ item.vType || '未知车型' }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="vehicle-meta">
|
||||
<span class="badge">{{ item.vFuelType || '燃料未知' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field-grid">
|
||||
<div class="field">
|
||||
<div class="field-label">发动机号</div>
|
||||
<div class="field-value code">{{ item.engineNO || '-' }}</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<div class="field-label">发动机型号</div>
|
||||
<div class="field-value code">{{ item.engineType || '-' }}</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<div class="field-label">生产日期</div>
|
||||
<div class="field-value">{{ item.vScdate || '-' }}</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<div class="field-label">排放阶段</div>
|
||||
<div class="field-value">{{ item.dischargeStage || '-' }}</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<div class="field-label">车辆分类</div>
|
||||
<div class="field-value">{{ item.vClassification || '-' }}</div>
|
||||
</div>
|
||||
<div class="field field-span">
|
||||
<div class="field-label">生产企业名称</div>
|
||||
<div class="field-value">{{ item.vManufacturer || '-' }}</div>
|
||||
</div>
|
||||
<div class="field field-span">
|
||||
<div class="field-label">生产厂地址</div>
|
||||
<div class="field-value">{{ item.vSccdz || '-' }}</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, String, Array], default: () => ({}) },
|
||||
params: { type: Object, default: () => ({}) },
|
||||
apiId: { type: String, default: '' },
|
||||
index: { type: Number, default: 0 },
|
||||
notifyRiskStatus: { type: Function, default: () => { } },
|
||||
});
|
||||
|
||||
// 解析返回的 JSON 字符串,得到数组
|
||||
const records = computed(() => {
|
||||
const raw = props.data;
|
||||
if (!raw) return [];
|
||||
|
||||
// data 本身是字符串
|
||||
if (typeof raw === 'string') {
|
||||
try {
|
||||
const parsed = JSON.parse(raw);
|
||||
if (Array.isArray(parsed)) return parsed;
|
||||
return [];
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
// data 已经是数组
|
||||
if (Array.isArray(raw)) {
|
||||
return raw;
|
||||
}
|
||||
|
||||
// data 是对象,里层再包了一层字符串/数组的情况
|
||||
if (typeof raw === 'object') {
|
||||
if (Array.isArray(raw.list)) return raw.list;
|
||||
if (typeof raw.data === 'string') {
|
||||
try {
|
||||
const parsed = JSON.parse(raw.data);
|
||||
if (Array.isArray(parsed)) return parsed;
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return [];
|
||||
});
|
||||
|
||||
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 rounded-2xl mb-5 px-5 py-4 bg-gradient-to-r from-blue-50 via-indigo-50 to-blue-50 flex items-center justify-between;
|
||||
color: #1e40af;
|
||||
}
|
||||
|
||||
.header-title {
|
||||
@apply text-xl font-semibold m-0;
|
||||
}
|
||||
|
||||
.header-desc {
|
||||
@apply text-sm mt-2 m-0 opacity-80 text-gray-700;
|
||||
}
|
||||
|
||||
.header-left {
|
||||
@apply flex flex-col;
|
||||
}
|
||||
|
||||
.header-tag {
|
||||
@apply inline-flex items-center gap-2 px-3 py-1 rounded-full text-base font-medium bg-white/80 text-blue-700 shadow-sm;
|
||||
}
|
||||
|
||||
.header-tag .dot {
|
||||
@apply w-2 h-2 rounded-full bg-green-500;
|
||||
}
|
||||
|
||||
.vehicle-card {
|
||||
@apply mb-4 p-5 rounded-2xl border border-gray-100 bg-gray-50/80;
|
||||
}
|
||||
|
||||
.vehicle-title {
|
||||
@apply flex items-start justify-between text-base text-gray-700;
|
||||
}
|
||||
|
||||
.vehicle-title .label {
|
||||
@apply text-base uppercase tracking-wide text-gray-500;
|
||||
}
|
||||
|
||||
.vehicle-title .value {
|
||||
@apply font-semibold text-lg text-gray-900 mt-1;
|
||||
}
|
||||
|
||||
.vehicle-model {
|
||||
@apply text-base font-semibold text-gray-900 mt-1;
|
||||
}
|
||||
|
||||
.vehicle-meta {
|
||||
@apply flex items-center gap-2;
|
||||
}
|
||||
|
||||
.badge {
|
||||
@apply inline-flex items-center px-3 py-1 rounded-full text-base font-medium bg-blue-100 text-blue-700;
|
||||
}
|
||||
|
||||
.field-label {
|
||||
@apply text-base text-gray-500 mb-1;
|
||||
}
|
||||
|
||||
.field-value {
|
||||
@apply text-base text-gray-900;
|
||||
}
|
||||
|
||||
.field-value.code {
|
||||
@apply font-mono tracking-wide;
|
||||
}
|
||||
|
||||
.field-grid {
|
||||
@apply grid gap-y-3 gap-x-6 mt-4;
|
||||
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
|
||||
}
|
||||
|
||||
.field-span {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
|
||||
.empty {
|
||||
@apply text-center py-10 text-gray-500;
|
||||
}
|
||||
|
||||
.empty .icon {
|
||||
@apply text-3xl mb-2;
|
||||
}
|
||||
|
||||
.empty .title {
|
||||
@apply text-base font-medium mb-1;
|
||||
}
|
||||
|
||||
.empty .sub {
|
||||
@apply text-base;
|
||||
}
|
||||
</style>
|
||||
493
src/ui/CQCXG6B4E.vue
Normal file
493
src/ui/CQCXG6B4E.vue
Normal file
@@ -0,0 +1,493 @@
|
||||
<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>
|
||||
216
src/ui/CQCXGGB2Q.vue
Normal file
216
src/ui/CQCXGGB2Q.vue
Normal file
@@ -0,0 +1,216 @@
|
||||
<template>
|
||||
<div class="card">
|
||||
<div class="header-box">
|
||||
<h3 class="header-title">人车核验(简版)</h3>
|
||||
<p class="header-desc">校验人员姓名与车辆号牌是否匹配</p>
|
||||
</div>
|
||||
|
||||
<div class="result-section" :class="resultSectionClass">
|
||||
<div class="result-icon-wrap">
|
||||
<span class="result-icon" :class="iconClass">
|
||||
{{ iconChar }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="result-label">核验结果</div>
|
||||
<div class="result-value" :class="resultClass">{{ resultText }}</div>
|
||||
</div>
|
||||
|
||||
<div v-if="hasParams" class="info-rows">
|
||||
<div class="info-row">
|
||||
<span class="info-label">姓名</span>
|
||||
<span class="info-value">{{ maskedName }}</span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">车牌号</span>
|
||||
<span class="info-value font-mono">{{ params?.plate_no || params?.car_license || '-' }}</span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">号牌类型</span>
|
||||
<span class="info-value">{{ params?.carplate_type || params?.car_type || '-' }}</span>
|
||||
</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 maskedName = computed(() => {
|
||||
const name = props.params?.name || '';
|
||||
if (!name) return '-';
|
||||
return name.length > 1 ? name[0] + '*'.repeat(name.length - 1) : '*';
|
||||
});
|
||||
|
||||
// verify_code: 1 一致,2 不匹配
|
||||
const isMatch = computed(() => {
|
||||
const code = props.data?.verify_code;
|
||||
if (code === 1) return true;
|
||||
if (code === 2) return false;
|
||||
return null; // 无有效数据时
|
||||
});
|
||||
|
||||
const resultText = computed(() => {
|
||||
if (isMatch.value === true) return '一致';
|
||||
if (isMatch.value === false) return '不匹配';
|
||||
return '暂无结果';
|
||||
});
|
||||
|
||||
const resultClass = computed(() => {
|
||||
if (isMatch.value === true) return 'result-match';
|
||||
if (isMatch.value === false) return 'result-mismatch';
|
||||
return 'result-unknown';
|
||||
});
|
||||
|
||||
const resultSectionClass = computed(() => {
|
||||
if (isMatch.value === true) return 'result-section match';
|
||||
if (isMatch.value === false) return 'result-section mismatch';
|
||||
return 'result-section unknown';
|
||||
});
|
||||
|
||||
const iconClass = computed(() => {
|
||||
if (isMatch.value === true) return 'icon-match';
|
||||
if (isMatch.value === false) return 'icon-mismatch';
|
||||
return 'icon-unknown';
|
||||
});
|
||||
|
||||
const iconChar = computed(() => {
|
||||
if (isMatch.value === true) return '✓';
|
||||
if (isMatch.value === false) return '✕';
|
||||
return '?';
|
||||
});
|
||||
|
||||
const hasParams = computed(() => {
|
||||
const p = props.params || {};
|
||||
return p.name || p.plate_no || p.car_license || p.carplate_type || p.car_type;
|
||||
});
|
||||
|
||||
// 简版人车核验本身不直接计为负面风险,给满分
|
||||
const riskScore = computed(() => 100);
|
||||
|
||||
useRiskNotifier(props, riskScore);
|
||||
|
||||
defineExpose({
|
||||
riskScore,
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.card {
|
||||
@apply bg-white rounded-lg p-4 shadow-sm border border-gray-100;
|
||||
}
|
||||
|
||||
.header-box {
|
||||
@apply rounded-lg mb-4 p-4;
|
||||
background: linear-gradient(135deg, var(--van-theme-primary) 0%, var(--van-theme-primary-dark, #1565c0) 100%);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.header-title {
|
||||
@apply text-lg font-semibold m-0;
|
||||
}
|
||||
|
||||
.header-desc {
|
||||
@apply text-sm mt-1 opacity-90 m-0;
|
||||
}
|
||||
|
||||
.result-section {
|
||||
@apply rounded-xl p-5 text-center mb-4;
|
||||
}
|
||||
|
||||
.result-section.match {
|
||||
background: linear-gradient(135deg, #e8f5e9 0%, #c8e6c9 100%);
|
||||
border: 1px solid rgba(76, 175, 80, 0.3);
|
||||
}
|
||||
|
||||
.result-section.mismatch {
|
||||
background: linear-gradient(135deg, #ffebee 0%, #ffcdd2 100%);
|
||||
border: 1px solid rgba(244, 67, 54, 0.3);
|
||||
}
|
||||
|
||||
.result-section.unknown {
|
||||
@apply bg-gray-50 border border-gray-200;
|
||||
}
|
||||
|
||||
.result-icon-wrap {
|
||||
@apply mb-2;
|
||||
}
|
||||
|
||||
.result-icon {
|
||||
@apply inline-flex items-center justify-center w-12 h-12 rounded-full text-2xl font-bold text-white;
|
||||
}
|
||||
|
||||
.result-icon.icon-match {
|
||||
background: linear-gradient(135deg, #43a047 0%, #2e7d32 100%);
|
||||
box-shadow: 0 2px 8px rgba(76, 175, 80, 0.4);
|
||||
}
|
||||
|
||||
.result-icon.icon-mismatch {
|
||||
background: linear-gradient(135deg, #e53935 0%, #c62828 100%);
|
||||
box-shadow: 0 2px 8px rgba(244, 67, 54, 0.4);
|
||||
}
|
||||
|
||||
.result-icon.icon-unknown {
|
||||
background: linear-gradient(135deg, #78909c 0%, #546e7a 100%);
|
||||
box-shadow: 0 2px 8px rgba(96, 125, 139, 0.3);
|
||||
}
|
||||
|
||||
.result-label {
|
||||
@apply text-sm text-gray-500 mb-1;
|
||||
}
|
||||
|
||||
.result-value {
|
||||
@apply text-xl font-semibold;
|
||||
}
|
||||
|
||||
.result-match {
|
||||
color: #2e7d32;
|
||||
}
|
||||
|
||||
.result-mismatch {
|
||||
color: #c62828;
|
||||
}
|
||||
|
||||
.result-unknown {
|
||||
@apply text-gray-500;
|
||||
}
|
||||
|
||||
.info-rows {
|
||||
@apply space-y-3 pt-2 border-t border-gray-100;
|
||||
}
|
||||
|
||||
.info-row {
|
||||
@apply flex items-center text-sm;
|
||||
}
|
||||
|
||||
.info-label {
|
||||
@apply w-20 text-gray-500 shrink-0;
|
||||
}
|
||||
|
||||
.info-value {
|
||||
@apply font-medium text-gray-800;
|
||||
}
|
||||
</style>
|
||||
634
src/ui/CQCXGP00W.vue
Normal file
634
src/ui/CQCXGP00W.vue
Normal 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>
|
||||
228
src/ui/CQCXGY7F2.vue
Normal file
228
src/ui/CQCXGY7F2.vue
Normal file
@@ -0,0 +1,228 @@
|
||||
<template>
|
||||
<div class="card">
|
||||
<div class="header-box">
|
||||
<div class="header-left">
|
||||
<h3 class="header-title">二手车VIN估值</h3>
|
||||
<p class="header-desc">基于车型、排量、排放标准等信息给出参考估值</p>
|
||||
</div>
|
||||
<div class="header-tag">
|
||||
<span class="tag-label">估值结果</span>
|
||||
<span class="tag-value">{{ data.estimatedValue || '-' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template v-if="hasData">
|
||||
<div class="summary-card">
|
||||
<div class="summary-main">
|
||||
<div class="summary-price">{{ data.estimatedValue || '-' }}</div>
|
||||
<div class="summary-sub">参考估值(仅供参考,实际价格以市场为准)</div>
|
||||
</div>
|
||||
<div class="summary-meta">
|
||||
<div class="badge">{{ data.seriesName || '未知车系' }}</div>
|
||||
<div class="meta-line">
|
||||
<span>{{ data.manufacturerName || '未知厂商' }}</span>
|
||||
<span v-if="data.productionDate" class="dot"></span>
|
||||
<span v-if="data.productionDate">{{ data.productionDate }} 年出厂</span>
|
||||
</div>
|
||||
<div class="meta-line meta-small">
|
||||
<span v-if="data.displacement">排量:{{ data.displacement }}</span>
|
||||
<span v-if="data.transmissionType" class="dot"></span>
|
||||
<span v-if="data.transmissionType">变速箱:{{ data.transmissionType }}</span>
|
||||
<span v-if="data.emissionStandard" class="dot"></span>
|
||||
<span v-if="data.emissionStandard">排放:{{ data.emissionStandard }}</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">{{ data.manufacturerName || '-' }}</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<div class="field-label">车系名称</div>
|
||||
<div class="field-value">{{ data.seriesName || '-' }}</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<div class="field-label">车型年款</div>
|
||||
<div class="field-value">{{ data.modelYear || data.productionDate || '-' }}</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<div class="field-label">座位数</div>
|
||||
<div class="field-value">{{ data.seatingCapacity || '-' }}</div>
|
||||
</div>
|
||||
<div class="field field-span">
|
||||
<div class="field-label">车型名称</div>
|
||||
<div class="field-value">{{ data.modelName || '-' }}</div>
|
||||
</div>
|
||||
<div class="field field-span">
|
||||
<div class="field-label">车型指导价</div>
|
||||
<div class="field-value">{{ data.msrp || '-' }}</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">{{ data.displacement || '-' }}</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<div class="field-label">变速箱类型</div>
|
||||
<div class="field-value">{{ data.transmissionType || '-' }}</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<div class="field-label">排放标准</div>
|
||||
<div class="field-value">{{ data.emissionStandard || '-' }}</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<div class="field-label">车身颜色</div>
|
||||
<div class="field-value">{{ data.color || '-' }}</div>
|
||||
</div>
|
||||
<div class="field field-span" v-if="data.seriesGroupName">
|
||||
<div class="field-label">车系组名</div>
|
||||
<div class="field-value">{{ data.seriesGroupName }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div v-else class="empty">
|
||||
<div class="icon">ℹ️</div>
|
||||
<div class="title">暂无估值结果</div>
|
||||
<div class="sub">未查询到有效的估值数据,请检查 VIN 与车辆信息是否正确</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 hasData = computed(() => !!props.data && Object.keys(props.data).length > 0);
|
||||
|
||||
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 justify-between mb-5 px-5 py-4 rounded-2xl bg-gradient-to-r from-amber-50 via-orange-50 to-amber-50;
|
||||
}
|
||||
|
||||
.header-left {
|
||||
@apply flex flex-col;
|
||||
}
|
||||
|
||||
.header-title {
|
||||
@apply text-2xl font-semibold m-0 text-amber-900;
|
||||
}
|
||||
|
||||
.header-desc {
|
||||
@apply text-base mt-3 m-0 text-amber-800 opacity-90;
|
||||
}
|
||||
|
||||
.header-tag {
|
||||
@apply inline-flex flex-col items-end gap-1 px-3 py-2 rounded-xl bg-white/80 shadow-sm;
|
||||
}
|
||||
|
||||
.tag-label {
|
||||
@apply text-sm text-gray-500;
|
||||
}
|
||||
|
||||
.tag-value {
|
||||
@apply text-2xl font-bold text-amber-700 leading-none whitespace-nowrap;
|
||||
}
|
||||
|
||||
.summary-card {
|
||||
@apply rounded-2xl border border-amber-100 bg-amber-50/60 px-5 py-5 mb-4 flex flex-col gap-4;
|
||||
}
|
||||
|
||||
.summary-main {}
|
||||
|
||||
.summary-price {
|
||||
@apply text-3xl font-extrabold text-amber-800 leading-tight;
|
||||
}
|
||||
|
||||
.summary-sub {
|
||||
@apply text-sm text-amber-700 opacity-90;
|
||||
}
|
||||
|
||||
.summary-meta {
|
||||
@apply space-y-2 text-base text-gray-800;
|
||||
}
|
||||
|
||||
.badge {
|
||||
@apply inline-flex items-center px-3 py-1 rounded-full text-xs font-medium bg-amber-100 text-amber-800;
|
||||
}
|
||||
|
||||
.meta-line {
|
||||
@apply flex flex-wrap items-center gap-2 text-sm text-gray-700;
|
||||
}
|
||||
|
||||
.meta-line .dot {
|
||||
@apply w-1 h-1 rounded-full bg-gray-400;
|
||||
}
|
||||
|
||||
.meta-small {
|
||||
@apply text-sm text-gray-600;
|
||||
}
|
||||
|
||||
.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-4;
|
||||
}
|
||||
|
||||
.field-grid {
|
||||
@apply grid gap-y-3 gap-x-6;
|
||||
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
|
||||
}
|
||||
|
||||
.field-label {
|
||||
@apply text-sm text-gray-500 mb-1.5;
|
||||
}
|
||||
|
||||
.field-value {
|
||||
@apply text-base text-gray-900;
|
||||
}
|
||||
|
||||
.field-span {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
|
||||
.empty {
|
||||
@apply text-center py-12 text-gray-500;
|
||||
}
|
||||
|
||||
.empty .icon {
|
||||
@apply text-4xl mb-3;
|
||||
}
|
||||
|
||||
.empty .title {
|
||||
@apply text-lg font-medium mb-2;
|
||||
}
|
||||
|
||||
.empty .sub {
|
||||
@apply text-sm;
|
||||
}
|
||||
</style>
|
||||
250
src/ui/CQCXGYTS2.vue
Normal file
250
src/ui/CQCXGYTS2.vue
Normal file
@@ -0,0 +1,250 @@
|
||||
<template>
|
||||
<div class="card">
|
||||
<div class="header-box">
|
||||
<h3 class="header-title">人车核验(详版)</h3>
|
||||
<p class="header-desc">展示人员与车辆的详细匹配结果及相关说明</p>
|
||||
</div>
|
||||
|
||||
<div class="result-section" :class="resultSectionClass">
|
||||
<div class="result-icon-wrap">
|
||||
<span class="result-icon" :class="iconClass">
|
||||
{{ iconChar }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="result-label">认证结果</div>
|
||||
<div class="result-value" :class="resultTextClass">{{ resultText }}</div>
|
||||
<p v-if="resultDesc" class="result-desc">{{ resultDesc }}</p>
|
||||
</div>
|
||||
|
||||
<div v-if="hasParams" class="info-rows">
|
||||
<div class="info-row">
|
||||
<span class="info-label">姓名</span>
|
||||
<span class="info-value">{{ maskedName }}</span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">车牌号</span>
|
||||
<span class="info-value font-mono">{{ params?.plate_no || params?.car_license || '-' }}</span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">号牌类型</span>
|
||||
<span class="info-value">{{ params?.carplate_type || params?.car_type || '-' }}</span>
|
||||
</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 maskedName = computed(() => {
|
||||
const name = props.params?.name || '';
|
||||
if (!name) return '-';
|
||||
return name.length > 1 ? name[0] + '*'.repeat(name.length - 1) : '*';
|
||||
});
|
||||
|
||||
// status: 0 一致, -1 不一致, -2 非法姓名, -4 无记录
|
||||
const status = computed(() => {
|
||||
const s = props.data?.status;
|
||||
if (s === 0 || s === -1 || s === -2 || s === -4) return s;
|
||||
return null;
|
||||
});
|
||||
|
||||
const resultText = computed(() => {
|
||||
const s = status.value;
|
||||
if (s === 0) return '一致';
|
||||
if (s === -1) return '不一致';
|
||||
if (s === -2) return '非法姓名';
|
||||
if (s === -4) return '无记录';
|
||||
return '暂无结果';
|
||||
});
|
||||
|
||||
const resultDesc = computed(() => {
|
||||
const s = status.value;
|
||||
if (s === -2) return '姓名长度或格式不正确,请核对后重试';
|
||||
if (s === -4) return '未查询到相关核验记录';
|
||||
return '';
|
||||
});
|
||||
|
||||
const resultTextClass = computed(() => {
|
||||
const s = status.value;
|
||||
if (s === 0) return 'result-match';
|
||||
if (s === -1) return 'result-mismatch';
|
||||
if (s === -2) return 'result-invalid';
|
||||
if (s === -4) return 'result-norecord';
|
||||
return 'result-unknown';
|
||||
});
|
||||
|
||||
const resultSectionClass = computed(() => {
|
||||
const s = status.value;
|
||||
if (s === 0) return 'result-section match';
|
||||
if (s === -1) return 'result-section mismatch';
|
||||
if (s === -2) return 'result-section invalid';
|
||||
if (s === -4) return 'result-section norecord';
|
||||
return 'result-section unknown';
|
||||
});
|
||||
|
||||
const iconClass = computed(() => {
|
||||
const s = status.value;
|
||||
if (s === 0) return 'icon-match';
|
||||
if (s === -1) return 'icon-mismatch';
|
||||
if (s === -2) return 'icon-invalid';
|
||||
if (s === -4) return 'icon-norecord';
|
||||
return 'icon-unknown';
|
||||
});
|
||||
|
||||
const iconChar = computed(() => {
|
||||
const s = status.value;
|
||||
if (s === 0) return '✓';
|
||||
if (s === -1) return '✕';
|
||||
if (s === -2) return '!';
|
||||
if (s === -4) return '—';
|
||||
return '?';
|
||||
});
|
||||
|
||||
const hasParams = computed(() => {
|
||||
const p = props.params || {};
|
||||
return p.name || p.plate_no || p.car_license || p.carplate_type || p.car_type;
|
||||
});
|
||||
|
||||
const riskScore = computed(() => 100);
|
||||
useRiskNotifier(props, riskScore);
|
||||
defineExpose({ riskScore });
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.card {
|
||||
@apply bg-white rounded-lg p-4 shadow-sm border border-gray-100;
|
||||
}
|
||||
|
||||
.header-box {
|
||||
@apply rounded-lg mb-4 p-4;
|
||||
background: linear-gradient(135deg, #5c6bc0 0%, #3949ab 100%);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.header-title {
|
||||
@apply text-lg font-semibold m-0;
|
||||
}
|
||||
|
||||
.header-desc {
|
||||
@apply text-sm mt-1 opacity-90 m-0;
|
||||
}
|
||||
|
||||
.result-section {
|
||||
@apply rounded-xl p-5 text-center mb-4;
|
||||
}
|
||||
|
||||
.result-section.match {
|
||||
background: linear-gradient(135deg, #e8f5e9 0%, #c8e6c9 100%);
|
||||
border: 1px solid rgba(76, 175, 80, 0.3);
|
||||
}
|
||||
|
||||
.result-section.mismatch {
|
||||
background: linear-gradient(135deg, #ffebee 0%, #ffcdd2 100%);
|
||||
border: 1px solid rgba(244, 67, 54, 0.3);
|
||||
}
|
||||
|
||||
.result-section.invalid {
|
||||
background: linear-gradient(135deg, #fff3e0 0%, #ffe0b2 100%);
|
||||
border: 1px solid rgba(255, 152, 0, 0.4);
|
||||
}
|
||||
|
||||
.result-section.norecord {
|
||||
background: linear-gradient(135deg, #eceff1 0%, #cfd8dc 100%);
|
||||
border: 1px solid rgba(96, 125, 139, 0.3);
|
||||
}
|
||||
|
||||
.result-section.unknown {
|
||||
@apply bg-gray-50 border border-gray-200;
|
||||
}
|
||||
|
||||
.result-icon-wrap {
|
||||
@apply mb-2;
|
||||
}
|
||||
|
||||
.result-icon {
|
||||
@apply inline-flex items-center justify-center w-12 h-12 rounded-full text-2xl font-bold text-white;
|
||||
}
|
||||
|
||||
.result-icon.icon-match {
|
||||
background: linear-gradient(135deg, #43a047 0%, #2e7d32 100%);
|
||||
box-shadow: 0 2px 8px rgba(76, 175, 80, 0.4);
|
||||
}
|
||||
|
||||
.result-icon.icon-mismatch {
|
||||
background: linear-gradient(135deg, #e53935 0%, #c62828 100%);
|
||||
box-shadow: 0 2px 8px rgba(244, 67, 54, 0.4);
|
||||
}
|
||||
|
||||
.result-icon.icon-invalid {
|
||||
background: linear-gradient(135deg, #fb8c00 0%, #ef6c00 100%);
|
||||
box-shadow: 0 2px 8px rgba(255, 152, 0, 0.4);
|
||||
}
|
||||
|
||||
.result-icon.icon-norecord {
|
||||
background: linear-gradient(135deg, #78909c 0%, #546e7a 100%);
|
||||
box-shadow: 0 2px 8px rgba(96, 125, 139, 0.3);
|
||||
}
|
||||
|
||||
.result-icon.icon-unknown {
|
||||
background: linear-gradient(135deg, #78909c 0%, #546e7a 100%);
|
||||
box-shadow: 0 2px 8px rgba(96, 125, 139, 0.3);
|
||||
}
|
||||
|
||||
.result-label {
|
||||
@apply text-sm text-gray-500 mb-1;
|
||||
}
|
||||
|
||||
.result-value {
|
||||
@apply text-xl font-semibold;
|
||||
}
|
||||
|
||||
.result-desc {
|
||||
@apply text-sm mt-2 m-0 text-gray-600 max-w-xs mx-auto;
|
||||
}
|
||||
|
||||
.result-match {
|
||||
color: #2e7d32;
|
||||
}
|
||||
|
||||
.result-mismatch {
|
||||
color: #c62828;
|
||||
}
|
||||
|
||||
.result-invalid {
|
||||
color: #e65100;
|
||||
}
|
||||
|
||||
.result-norecord {
|
||||
color: #546e7a;
|
||||
}
|
||||
|
||||
.result-unknown {
|
||||
@apply text-gray-500;
|
||||
}
|
||||
|
||||
.info-rows {
|
||||
@apply space-y-3 pt-2 border-t border-gray-100;
|
||||
}
|
||||
|
||||
.info-row {
|
||||
@apply flex items-center text-sm;
|
||||
}
|
||||
|
||||
.info-label {
|
||||
@apply w-20 text-gray-500 shrink-0;
|
||||
}
|
||||
|
||||
.info-value {
|
||||
@apply font-medium text-gray-800;
|
||||
}
|
||||
</style>
|
||||
60
src/ui/CQVehicleGeneric.vue
Normal file
60
src/ui/CQVehicleGeneric.vue
Normal file
@@ -0,0 +1,60 @@
|
||||
<template>
|
||||
<div class="card">
|
||||
<div class="bg-gray-100 text-gray-800 p-4 rounded-lg mb-4">
|
||||
<h3 class="text-lg font-semibold">{{ title }}</h3>
|
||||
<p class="text-sm mt-1">返回数据如下,传参后续可按接口单独配置。</p>
|
||||
</div>
|
||||
<div v-if="hasRawData" class="text-xs">
|
||||
<pre
|
||||
class="bg-gray-50 rounded p-3 overflow-x-auto whitespace-pre-wrap break-all border border-gray-200">{{ prettyData }}</pre>
|
||||
</div>
|
||||
<div v-else class="text-gray-500 text-sm">暂无数据</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 titleMap = {
|
||||
QCXG4D2E: '名下车辆(数量)',
|
||||
QCXG5U0Z: '车辆静态信息查询',
|
||||
QCXG1U4U: '车辆里程记录(混合查询)',
|
||||
QCXGY7F2: '二手车VIN估值',
|
||||
QCXG1H7Y: '车辆过户简版查询',
|
||||
QCXG4I1Z: '车辆过户详版查询',
|
||||
QCXG3Y6B: '车辆维保简版查询',
|
||||
QCXG3Z3L: '车辆维保详细版查询',
|
||||
QCXGP00W: '车辆出险详版查询',
|
||||
QCXG6B4E: '车辆出险记录核验',
|
||||
};
|
||||
const title = computed(() => titleMap[props.apiId] || props.apiId || '车辆查询');
|
||||
|
||||
const hasRawData = computed(() => !!props.data && Object.keys(props.data).length > 0);
|
||||
|
||||
const prettyData = computed(() => {
|
||||
try {
|
||||
return JSON.stringify(props.data, null, 2);
|
||||
} catch {
|
||||
return String(props.data || '');
|
||||
}
|
||||
});
|
||||
|
||||
const riskScore = computed(() => 100);
|
||||
useRiskNotifier(props, riskScore);
|
||||
defineExpose({ riskScore });
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.card {
|
||||
@apply bg-white rounded-lg p-4 shadow-sm border border-gray-100;
|
||||
}
|
||||
</style>
|
||||
479
src/ui/CQYGL2S0W.vue
Normal file
479
src/ui/CQYGL2S0W.vue
Normal file
@@ -0,0 +1,479 @@
|
||||
<template>
|
||||
<div class="card">
|
||||
<div class="header-box">
|
||||
<h3 class="header-title">失信被执行人</h3>
|
||||
<p class="header-desc">
|
||||
展示命中最高法院公布的失信被执行人信息,用于识别严重违约风险。
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div v-if="hasList" class="summary-section">
|
||||
<div class="summary-row">
|
||||
<div class="summary-card summary-risk">
|
||||
<div class="summary-label">命中失信被执行人</div>
|
||||
<div class="summary-value">{{ totalCount }}</div>
|
||||
<div class="summary-sub">
|
||||
{{ areaSummary }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="summary-card summary-status">
|
||||
<div class="summary-label">履行情况</div>
|
||||
<div class="summary-value">
|
||||
{{ mainPerformance }}
|
||||
</div>
|
||||
<div class="summary-sub">
|
||||
重点关注「全部未履行」「部分履行」等高风险状态
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="hasList" class="block">
|
||||
<div class="block-title">失信记录详情</div>
|
||||
<div class="record-list">
|
||||
<div v-for="(item, index) in list" :key="item.id || index" class="record-wrapper">
|
||||
<div class="record-card">
|
||||
<!-- 摘要区域:仿 FLXG7E8F 的案件卡片风格 -->
|
||||
<div class="record-summary" @click="toggleRecordExpand(item.id || index)">
|
||||
<div class="summary-top">
|
||||
<div class="summary-title">
|
||||
<span class="record-case-no">
|
||||
{{ item.case_code || '暂无案号' }}
|
||||
</span>
|
||||
<span class="record-type-tag">
|
||||
{{ item.disrupt_type_name || '失信被执行人' }}
|
||||
</span>
|
||||
</div>
|
||||
<span class="relate-tag">
|
||||
{{ item.relateType || '失信被执行人' }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="summary-middle">
|
||||
<span class="summary-label">立案:</span>
|
||||
<span class="summary-value">{{ formatDate(item.reg_date) }}</span>
|
||||
<span class="summary-label ml-2">法院:</span>
|
||||
<span class="summary-value">{{ item.court_name || item.gist_unit || '-' }}</span>
|
||||
</div>
|
||||
<div class="summary-bottom">
|
||||
<span class="risk-tag">
|
||||
{{ item.performance || '履行情况未知' }}
|
||||
</span>
|
||||
<span class="expand-indicator">
|
||||
<span class="expand-text">
|
||||
{{ isRecordExpanded(item.id || index) ? '收起详情' : '展开详情' }}
|
||||
</span>
|
||||
<img src="@/assets/images/report/zk.png" alt="展开" class="w-4 h-4"
|
||||
:class="{ 'rotate-180': isRecordExpanded(item.id || index) }" />
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 详情区域:可展开/收起 -->
|
||||
<div class="record-detail" :class="{
|
||||
'detail-collapsed': !isRecordExpanded(item.id || index),
|
||||
'detail-expanded': isRecordExpanded(item.id || index),
|
||||
}">
|
||||
<div class="record-body">
|
||||
<div class="info-row">
|
||||
<span class="info-label">被执行人</span>
|
||||
<span class="info-value">
|
||||
{{ item.entity_name || '-' }}
|
||||
<span v-if="item.sexy || item.age" class="info-sub">
|
||||
({{ [item.sexy, item.age && item.age + '岁'].filter(Boolean).join(',') }})
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">主体代码</span>
|
||||
<span class="info-value">
|
||||
{{ maskId(item.entity_id) }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">身份类型</span>
|
||||
<span class="info-value">
|
||||
{{ item.party_type_name || '自然人/法人' }}
|
||||
<span v-if="item.business_entity" class="info-sub">
|
||||
(法定代表人/负责人:{{ item.business_entity }})
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">涉案地域</span>
|
||||
<span class="info-value">{{ item.area_name || '-' }}</span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">执行法院</span>
|
||||
<span class="info-value">{{ item.court_name || item.gist_unit || '-' }}</span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">执行依据</span>
|
||||
<span class="info-value">{{ item.gist_id || '-' }}</span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">立案时间</span>
|
||||
<span class="info-value">{{ formatDate(item.reg_date) }}</span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">发布日期</span>
|
||||
<span class="info-value">{{ formatDate(item.publish_date) }}</span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">履行情况</span>
|
||||
<span class="info-value">
|
||||
{{ item.performance || '-' }}
|
||||
<span v-if="item.performed_part || item.unPerform_part" class="info-sub">
|
||||
{{ formatPerformDetail(item) }}
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">履行义务</span>
|
||||
<span class="info-value">
|
||||
{{ item.duty || '-' }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">下架状态</span>
|
||||
<span class="info-value">
|
||||
{{ item.case_status || '-' }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-else class="empty-tip">
|
||||
暂未命中失信被执行人记录。
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, ref, watchEffect } from "vue";
|
||||
|
||||
const props = defineProps({
|
||||
data: { type: Object, default: () => ({}) },
|
||||
params: { type: Object, default: () => ({}) },
|
||||
apiId: { type: String, default: "" },
|
||||
index: { type: Number, default: 0 },
|
||||
notifyRiskStatus: { type: Function, default: () => { } },
|
||||
});
|
||||
|
||||
// 统一 dataList 结构
|
||||
const list = computed(() => {
|
||||
const d = props.data || {};
|
||||
if (Array.isArray(d.dataList)) return d.dataList;
|
||||
if (d.data && Array.isArray(d.data.dataList)) return d.data.dataList;
|
||||
return [];
|
||||
});
|
||||
|
||||
const hasList = computed(() => list.value.length > 0);
|
||||
const totalCount = computed(() => list.value.length);
|
||||
|
||||
const areaSummary = computed(() => {
|
||||
if (!hasList.value) return "未命中失信被执行人";
|
||||
const areas = Array.from(
|
||||
new Set(
|
||||
list.value
|
||||
.map((i) => i.area_name)
|
||||
.filter((v) => !!v)
|
||||
)
|
||||
);
|
||||
if (!areas.length) return "地区信息未知";
|
||||
if (areas.length === 1) return `集中在 ${areas[0]}`;
|
||||
return `涉及 ${areas.length} 个地区,如 ${areas.slice(0, 2).join("、")} 等`;
|
||||
});
|
||||
|
||||
const mainPerformance = computed(() => {
|
||||
if (!hasList.value) return "暂无记录";
|
||||
const perf = list.value
|
||||
.map((i) => i.performance)
|
||||
.filter((v) => !!v);
|
||||
if (!perf.length) return "履行情况未知";
|
||||
const first = perf[0];
|
||||
if (perf.every((p) => p === first)) return first;
|
||||
return `${first} 等多种情况`;
|
||||
});
|
||||
|
||||
function formatPerformDetail(item) {
|
||||
const parts = [];
|
||||
if (item.performed_part) parts.push(`已履行:${item.performed_part}`);
|
||||
if (item.unPerform_part) parts.push(`未履行:${item.unPerform_part}`);
|
||||
return parts.join(";");
|
||||
}
|
||||
|
||||
function formatDate(str) {
|
||||
if (!str) return "-";
|
||||
// 支持 yyyy-MM-dd 或 yyyy-MM-dd HH:mm:ss
|
||||
if (str.length >= 10) return str.slice(0, 10);
|
||||
return str;
|
||||
}
|
||||
|
||||
function maskId(id) {
|
||||
if (!id) return "-";
|
||||
const s = String(id);
|
||||
if (s.length <= 8) return s[0] + "***" + s.slice(-1);
|
||||
return s.slice(0, 4) + "****" + s.slice(-4);
|
||||
}
|
||||
|
||||
// 展开/收起记录详情,参考 FLXG7E8F 的交互
|
||||
const expandedRecords = ref({});
|
||||
function toggleRecordExpand(key) {
|
||||
const id = String(key);
|
||||
expandedRecords.value[id] = !expandedRecords.value[id];
|
||||
}
|
||||
function isRecordExpanded(key) {
|
||||
const id = String(key);
|
||||
return !!expandedRecords.value[id];
|
||||
}
|
||||
|
||||
// 上报风险命中情况(命中视为高风险)
|
||||
watchEffect(() => {
|
||||
if (!props.notifyRiskStatus) return;
|
||||
const hit = hasList.value;
|
||||
props.notifyRiskStatus(props.apiId || "QYGL2S0W", props.index || 0, {
|
||||
hasRisk: hit,
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.card {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.header-box {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.header-title {
|
||||
font-size: 1.1rem;
|
||||
font-weight: 600;
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
.header-desc {
|
||||
margin-top: 0.25rem;
|
||||
font-size: 0.85rem;
|
||||
color: #666666;
|
||||
}
|
||||
|
||||
.summary-section {
|
||||
padding: 0.75rem 0;
|
||||
border-top: 1px solid #f1f1f1;
|
||||
border-bottom: 1px solid #f1f1f1;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.summary-row {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.summary-row {
|
||||
flex-direction: row;
|
||||
}
|
||||
}
|
||||
|
||||
.summary-card {
|
||||
flex: 1;
|
||||
padding: 0.75rem;
|
||||
border-radius: 0.75rem;
|
||||
}
|
||||
|
||||
.summary-risk {
|
||||
background: rgba(235, 60, 60, 0.04);
|
||||
border: 1px solid rgba(235, 60, 60, 0.3);
|
||||
}
|
||||
|
||||
.summary-status {
|
||||
background: rgba(214, 148, 62, 0.04);
|
||||
border: 1px solid rgba(214, 148, 62, 0.3);
|
||||
}
|
||||
|
||||
.summary-label {
|
||||
font-size: 0.85rem;
|
||||
color: #666666;
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.summary-value {
|
||||
font-size: 1.4rem;
|
||||
font-weight: 600;
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
.summary-sub {
|
||||
margin-top: 0.25rem;
|
||||
font-size: 0.75rem;
|
||||
color: #999999;
|
||||
}
|
||||
|
||||
.block {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.block-title {
|
||||
font-size: 0.95rem;
|
||||
font-weight: 600;
|
||||
color: #333333;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.record-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.record-wrapper {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.record-card {
|
||||
border-radius: 0.75rem;
|
||||
border: 1px solid #dddddd;
|
||||
background-color: #ffffff;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.record-summary {
|
||||
padding: 0.75rem 0.75rem 0.5rem;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.summary-top {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.summary-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.record-case-no {
|
||||
font-size: 0.9rem;
|
||||
font-weight: 600;
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
.record-type-tag {
|
||||
padding: 0.1rem 0.4rem;
|
||||
border-radius: 999px;
|
||||
font-size: 0.75rem;
|
||||
background-color: #f9ecec;
|
||||
color: #eb3c3c;
|
||||
}
|
||||
|
||||
.relate-tag {
|
||||
flex-shrink: 0;
|
||||
padding: 0.2rem 0.5rem;
|
||||
border-radius: 999px;
|
||||
font-size: 0.75rem;
|
||||
background-color: rgba(214, 148, 62, 0.08);
|
||||
color: #d6943e;
|
||||
}
|
||||
|
||||
.summary-middle {
|
||||
font-size: 0.8rem;
|
||||
padding-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.summary-label {
|
||||
color: #666666;
|
||||
}
|
||||
|
||||
.summary-value {
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
.summary-bottom {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.risk-tag {
|
||||
padding: 0.2rem 0.6rem;
|
||||
border-radius: 999px;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 500;
|
||||
background-color: rgba(235, 60, 60, 0.08);
|
||||
color: #eb3c3c;
|
||||
}
|
||||
|
||||
.expand-indicator {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.25rem;
|
||||
font-size: 0.75rem;
|
||||
color: #999999;
|
||||
}
|
||||
|
||||
.expand-indicator img {
|
||||
transition: transform 0.2s ease;
|
||||
}
|
||||
|
||||
.expand-indicator img.rotate-180 {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
.record-detail {
|
||||
border-top: 1px dashed #e5e5e5;
|
||||
padding: 0 0.75rem 0.5rem;
|
||||
max-height: 0;
|
||||
opacity: 0;
|
||||
overflow: hidden;
|
||||
transition: max-height 0.25s ease, opacity 0.25s ease;
|
||||
}
|
||||
|
||||
.record-detail.detail-expanded {
|
||||
max-height: 500px;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.info-row {
|
||||
display: flex;
|
||||
font-size: 0.8rem;
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.info-label {
|
||||
width: 4.5rem;
|
||||
color: #999999;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.info-value {
|
||||
color: #333333;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.info-sub {
|
||||
margin-left: 0.25rem;
|
||||
font-size: 0.75rem;
|
||||
color: #777777;
|
||||
}
|
||||
|
||||
.empty-tip {
|
||||
margin-top: 0.75rem;
|
||||
padding: 0.75rem;
|
||||
text-align: center;
|
||||
font-size: 0.85rem;
|
||||
color: #999999;
|
||||
background-color: #fafafa;
|
||||
border-radius: 0.75rem;
|
||||
}
|
||||
</style>
|
||||
238
src/ui/CQYGL3F8E/README.md
Normal file
238
src/ui/CQYGL3F8E/README.md
Normal file
@@ -0,0 +1,238 @@
|
||||
# CQYGL3F8E 人企关系加强版模块
|
||||
|
||||
## 概述
|
||||
|
||||
CQYGL3F8E 是人企关系加强版模块,提供全面的企业关联分析功能。该模块通过拆分功能,将原本的单一组件分解为三个独立的子模块,每个子模块专注于特定类型的企业关联信息展示。
|
||||
|
||||
## 模块结构
|
||||
|
||||
### 主模块
|
||||
- **文件位置**: `src/ui/CQYGL3F8E/index.vue`
|
||||
- **功能**: 整合三个子模块,提供完整的人企关系分析视图
|
||||
- **API ID**: `QYGL3F8E`
|
||||
|
||||
### 子模块
|
||||
|
||||
#### 1. 投资企业记录 (Investment)
|
||||
- **文件位置**: `src/ui/CQYGL3F8E/components/Investment.vue`
|
||||
- **API ID**: `CQYGL3F8E_Investment`
|
||||
- **功能**: 展示用户作为股东、历史股东、法人、历史法人的企业记录
|
||||
- **数据来源**: 过滤 `relationship` 字段包含 `["sh", "his_sh", "lp", "his_lp"]` 的企业
|
||||
|
||||
#### 2. 高管任职记录 (SeniorExecutive)
|
||||
- **文件位置**: `src/ui/CQYGL3F8E/components/SeniorExecutive.vue`
|
||||
- **API ID**: `CQYGL3F8E_SeniorExecutive`
|
||||
- **功能**: 展示用户作为高管、历史高管的企业任职记录
|
||||
- **数据来源**: 过滤 `relationship` 字段包含 `["tm", "his_tm"]` 的企业
|
||||
|
||||
#### 3. 涉诉风险 (Lawsuit)
|
||||
- **文件位置**: `src/ui/CQYGL3F8E/components/Lawsuit.vue`
|
||||
- **API ID**: `CQYGL3F8E_Lawsuit`
|
||||
- **功能**: 展示存在涉诉风险的企业信息
|
||||
- **数据来源**: 过滤 `lawsuitInfo` 字段包含有效涉诉数据的企业
|
||||
|
||||
#### 4. 对外投资历史 (InvestHistory)
|
||||
- **文件位置**: `src/ui/CQYGL3F8E/components/InvestHistory.vue`
|
||||
- **API ID**: `CQYGL3F8E_InvestHistory`
|
||||
- **功能**: 展示企业的对外投资历史记录
|
||||
- **数据来源**: `invest_history` 字段
|
||||
|
||||
#### 5. 融资历史 (FinancingHistory)
|
||||
- **文件位置**: `src/ui/CQYGL3F8E/components/FinancingHistory.vue`
|
||||
- **API ID**: `CQYGL3F8E_FinancingHistory`
|
||||
- **功能**: 展示企业的融资历史记录
|
||||
- **数据来源**: `financing_history` 字段
|
||||
|
||||
#### 6. 行政处罚 (Punishment)
|
||||
- **文件位置**: `src/ui/CQYGL3F8E/components/Punishment.vue`
|
||||
- **API ID**: `CQYGL3F8E_Punishment`
|
||||
- **功能**: 展示企业的行政处罚记录
|
||||
- **数据来源**: `punishment_info` 字段
|
||||
|
||||
#### 7. 经营异常 (Abnormal)
|
||||
- **文件位置**: `src/ui/CQYGL3F8E/components/Abnormal.vue`
|
||||
- **API ID**: `CQYGL3F8E_Abnormal`
|
||||
- **功能**: 展示企业的经营异常记录
|
||||
- **数据来源**: `abnormal_info` 字段
|
||||
|
||||
#### 8. 欠税公告 (OwnTax)
|
||||
- **文件位置**: `src/ui/CQYGL3F8E/components/OwnTax.vue`
|
||||
- **API ID**: `CQYGL3F8E_OwnTax`
|
||||
- **功能**: 展示企业的欠税公告记录
|
||||
- **数据来源**: `own_tax` 字段
|
||||
|
||||
#### 9. 税收违法 (TaxContravention)
|
||||
- **文件位置**: `src/ui/CQYGL3F8E/components/TaxContravention.vue`
|
||||
- **API ID**: `CQYGL3F8E_TaxContravention`
|
||||
- **功能**: 展示企业的税收违法记录
|
||||
- **数据来源**: `tax_contravention` 字段
|
||||
|
||||
## 数据拆分逻辑
|
||||
|
||||
### 数据源结构
|
||||
```javascript
|
||||
{
|
||||
data: {
|
||||
apiID: 'QYGL3F8E',
|
||||
data: {
|
||||
items: [
|
||||
{
|
||||
orgName: '企业名称',
|
||||
relationship: ['sh', 'tm'], // 关系类型
|
||||
lawsuitInfo: { ... }, // 涉诉信息
|
||||
basicInfo: { ... }, // 基本信息
|
||||
stockHolderItem: { ... }, // 持股信息
|
||||
staffList: { ... } // 人员列表
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 拆分规则
|
||||
|
||||
#### 投资企业记录
|
||||
- **过滤条件**: `relationship` 包含投资类关系
|
||||
- **关系类型**: `["sh", "his_sh", "lp", "his_lp"]`
|
||||
- **包含字段**: 完整的企业信息,包括持股详情
|
||||
|
||||
#### 高管任职记录
|
||||
- **过滤条件**: `relationship` 包含高管类关系
|
||||
- **关系类型**: `["tm", "his_tm"]`
|
||||
- **包含字段**: 完整的企业信息,重点关注任职信息
|
||||
|
||||
#### 涉诉风险
|
||||
- **过滤条件**: `lawsuitInfo` 包含有效涉诉数据
|
||||
- **检查字段**:
|
||||
- `lawsuitInfo.entout.data` (非空对象)
|
||||
- `lawsuitInfo.sxbzxr.data.sxbzxr` (非空数组)
|
||||
- `lawsuitInfo.xgbzxr.data.xgbzxr` (非空数组)
|
||||
- **包含字段**: 涉诉企业和总数统计
|
||||
|
||||
#### 对外投资历史
|
||||
- **数据来源**: `invest_history.items` 数组
|
||||
- **包含字段**: 投资企业信息、持股比例、注册资本等
|
||||
|
||||
#### 融资历史
|
||||
- **数据来源**: `financing_history.items` 数组
|
||||
- **包含字段**: 融资轮次、融资金额、投资者、新闻信息等
|
||||
|
||||
#### 行政处罚
|
||||
- **数据来源**: `punishment_info.items` 数组
|
||||
- **包含字段**: 处罚类型、处罚金额、处罚原因、处罚部门等
|
||||
|
||||
#### 经营异常
|
||||
- **数据来源**: `abnormal_info.items` 数组
|
||||
- **包含字段**: 异常原因、列入/移出日期、相关部门等
|
||||
|
||||
#### 欠税公告
|
||||
- **数据来源**: `own_tax.items` 数组
|
||||
- **包含字段**: 欠税金额、税务类型、欠税税种、纳税人信息、税务机关等
|
||||
|
||||
#### 税收违法
|
||||
- **数据来源**: `tax_contravention.items` 数组
|
||||
- **包含字段**: 案件性质、违法ID、税务机关、发布时间、纳税人名称等
|
||||
|
||||
## 工具函数
|
||||
|
||||
### simpleSplitter.js
|
||||
位置: `src/ui/CQYGL3F8E/utils/simpleSplitter.js`
|
||||
|
||||
#### 主要函数
|
||||
- `splitCQYGL3F8EForTabs(reportData)`: 数据拆分主函数
|
||||
- `getRelationshipText(relation)`: 获取关系文本描述
|
||||
- `getRelationshipClass(relation)`: 获取关系样式类
|
||||
- `getStatusClass(status)`: 获取企业状态样式类
|
||||
- `formatCapital(capital, currency)`: 格式化资本金额
|
||||
- `formatDate(dateStr)`: 格式化日期显示
|
||||
|
||||
## 集成配置
|
||||
|
||||
### BaseReport.vue 配置
|
||||
```javascript
|
||||
// 导入拆分函数
|
||||
import { splitCQYGL3F8EForTabs } from '@/ui/CQYGL3F8E/utils/simpleSplitter.js';
|
||||
|
||||
// 数据处理
|
||||
const processedReportData = computed(() => {
|
||||
let data = reportData.value;
|
||||
// ... 其他拆分
|
||||
data = splitCQYGL3F8EForTabs(data);
|
||||
return data;
|
||||
});
|
||||
|
||||
// 功能映射
|
||||
const featureMap = {
|
||||
QYGL3F8E: {
|
||||
name: "人企关系加强版",
|
||||
component: defineAsyncComponent(() => import("@/ui/CQYGL3F8E/index.vue")),
|
||||
remark: '人企关系加强版提供全面的企业关联分析,包括投资企业记录、高管任职记录和涉诉风险等多维度信息。'
|
||||
},
|
||||
CQYGL3F8E_Investment: {
|
||||
name: "投资企业记录",
|
||||
component: defineAsyncComponent(() => import("@/ui/CQYGL3F8E/components/Investment.vue")),
|
||||
},
|
||||
CQYGL3F8E_SeniorExecutive: {
|
||||
name: "高管任职记录",
|
||||
component: defineAsyncComponent(() => import("@/ui/CQYGL3F8E/components/SeniorExecutive.vue")),
|
||||
},
|
||||
CQYGL3F8E_Lawsuit: {
|
||||
name: "涉诉风险",
|
||||
component: defineAsyncComponent(() => import("@/ui/CQYGL3F8E/components/Lawsuit.vue")),
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
## 使用方式
|
||||
|
||||
### 完整视图
|
||||
访问 `QYGL3F8E` 将显示完整的人企关系分析,包含所有九个子模块。
|
||||
|
||||
### 独立子模块
|
||||
- 访问 `CQYGL3F8E_Investment` 仅显示投资企业记录
|
||||
- 访问 `CQYGL3F8E_SeniorExecutive` 仅显示高管任职记录
|
||||
- 访问 `CQYGL3F8E_Lawsuit` 仅显示涉诉风险
|
||||
- 访问 `CQYGL3F8E_InvestHistory` 仅显示对外投资历史
|
||||
- 访问 `CQYGL3F8E_FinancingHistory` 仅显示融资历史
|
||||
- 访问 `CQYGL3F8E_Punishment` 仅显示行政处罚
|
||||
- 访问 `CQYGL3F8E_Abnormal` 仅显示经营异常
|
||||
- 访问 `CQYGL3F8E_OwnTax` 仅显示欠税公告
|
||||
- 访问 `CQYGL3F8E_TaxContravention` 仅显示税收违法
|
||||
|
||||
## 特性
|
||||
|
||||
### 1. 数据过滤
|
||||
- 基于关系类型智能过滤企业数据
|
||||
- 支持多种关系类型的组合展示
|
||||
|
||||
### 2. 展开式详情
|
||||
- 企业卡片支持点击展开查看详细信息
|
||||
- 包含持股信息、基本信息、联系方式等
|
||||
|
||||
### 3. 状态标识
|
||||
- 企业状态颜色编码(存续、注销、吊销等)
|
||||
- 关系类型标签展示
|
||||
|
||||
### 4. 数据格式化
|
||||
- 资本金额自动转换为万元单位
|
||||
- 日期格式化显示
|
||||
- 持股比例可视化进度条
|
||||
|
||||
### 5. 响应式设计
|
||||
- 支持移动端和桌面端
|
||||
- 自适应布局和交互
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **数据完整性**: 拆分后的数据保持原始结构的完整性
|
||||
2. **性能优化**: 使用 `defineAsyncComponent` 实现组件懒加载
|
||||
3. **错误处理**: 对缺失数据进行安全处理,避免渲染错误
|
||||
4. **样式一致性**: 保持与整体设计系统的视觉一致性
|
||||
|
||||
## 更新历史
|
||||
|
||||
- **v1.0.0**: 初始版本,支持基本的企业关联信息展示
|
||||
- **v2.0.0**: 模块拆分重构,支持独立子模块访问
|
||||
- **v2.1.0**: 优化数据处理逻辑,增强错误处理能力
|
||||
- **v2.2.0**: 新增欠税公告和税收违法模块,完善企业风险分析功能
|
||||
171
src/ui/CQYGL5F6A.vue
Normal file
171
src/ui/CQYGL5F6A.vue
Normal file
@@ -0,0 +1,171 @@
|
||||
<template>
|
||||
<div class="card">
|
||||
<div class="header-box">
|
||||
<h3 class="header-title">名下企业关联</h3>
|
||||
<p class="header-desc">展示查询对象作为法人/股东/高管关联的企业</p>
|
||||
</div>
|
||||
|
||||
<div v-if="companyItems && companyItems.length" class="company-list">
|
||||
<div v-for="(item, idx) in companyItems" :key="idx" class="company-card">
|
||||
<div class="company-header">
|
||||
<div class="company-name">{{ item.orgName || item.basicInfo?.name || '未知企业' }}</div>
|
||||
<div class="company-tags">
|
||||
<span v-for="tag in item.relationshipTags" :key="tag" class="tag">
|
||||
{{ tag }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="company-body">
|
||||
<div class="row">
|
||||
<span class="label">企业状态</span>
|
||||
<span class="value">{{ item.basicInfo?.regStatus || '-' }}</span>
|
||||
</div>
|
||||
<div class="row">
|
||||
<span class="label">成立日期</span>
|
||||
<span class="value">{{ item.basicInfo?.estiblishTime || '-' }}</span>
|
||||
</div>
|
||||
<div class="row">
|
||||
<span class="label">注册资本</span>
|
||||
<span class="value">{{ item.basicInfo?.regCapital || '-' }}</span>
|
||||
</div>
|
||||
<div class="row">
|
||||
<span class="label">行业</span>
|
||||
<span class="value">{{ item.basicInfo?.industry || '-' }}</span>
|
||||
</div>
|
||||
<div class="row">
|
||||
<span class="label">企业类型</span>
|
||||
<span class="value">{{ item.basicInfo?.companyOrgType || '-' }}</span>
|
||||
</div>
|
||||
<div class="row">
|
||||
<span class="label">法定代表人</span>
|
||||
<span class="value">{{ item.basicInfo?.legalPersonName || '-' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="!companyItems || !companyItems.length" class="empty-tip">
|
||||
暂未查询到名下企业关联信息
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed } from 'vue';
|
||||
|
||||
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 rawItems = computed(() => {
|
||||
const report = props.data?.ent_report_001;
|
||||
const items = report?.queryResult?.items;
|
||||
return Array.isArray(items) ? items : [];
|
||||
});
|
||||
|
||||
const relationshipMap = {
|
||||
lp: '法人',
|
||||
sh: '股东',
|
||||
tm: '高管',
|
||||
};
|
||||
|
||||
const companyItems = computed(() =>
|
||||
rawItems.value.map((item) => {
|
||||
const relArr = Array.isArray(item.relationship) ? item.relationship : [];
|
||||
const relationshipTags = relArr.map((r) => relationshipMap[r] || r);
|
||||
return {
|
||||
...item,
|
||||
relationshipTags,
|
||||
};
|
||||
})
|
||||
);
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.card {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.header-box {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.header-title {
|
||||
font-size: 1.125rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.header-desc {
|
||||
font-size: 0.875rem;
|
||||
color: #6b7280;
|
||||
margin-top: 0.25rem;
|
||||
}
|
||||
|
||||
.company-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.company-card {
|
||||
border-radius: 0.75rem;
|
||||
border: 1px solid #e5e7eb;
|
||||
background: #f9fafb;
|
||||
padding: 0.75rem 0.875rem;
|
||||
}
|
||||
|
||||
.company-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.company-name {
|
||||
font-size: 0.95rem;
|
||||
font-weight: 600;
|
||||
color: #111827;
|
||||
}
|
||||
|
||||
.company-tags {
|
||||
display: flex;
|
||||
gap: 0.25rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.tag {
|
||||
font-size: 0.75rem;
|
||||
padding: 0.1rem 0.4rem;
|
||||
border-radius: 999px;
|
||||
background: #eef2ff;
|
||||
color: #4f46e5;
|
||||
}
|
||||
|
||||
.company-body .row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
font-size: 0.875rem;
|
||||
padding: 0.15rem 0;
|
||||
}
|
||||
|
||||
.label {
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.value {
|
||||
color: #111827;
|
||||
margin-left: 1rem;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.empty-tip {
|
||||
color: #9ca3af;
|
||||
font-size: 0.875rem;
|
||||
padding: 1rem 0;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
542
src/ui/CQYGL66SL.vue
Normal file
542
src/ui/CQYGL66SL.vue
Normal file
@@ -0,0 +1,542 @@
|
||||
<template>
|
||||
<div class="card">
|
||||
<div class="header-box">
|
||||
<h3 class="header-title">企业司法涉诉</h3>
|
||||
<p class="header-desc">展示企业在全国法院公开信息中的涉诉情况</p>
|
||||
<p v-if="entName" class="header-ent">被查询企业:{{ entName }}</p>
|
||||
</div>
|
||||
|
||||
<div v-if="hasCivilData" class="summary-section">
|
||||
<div class="summary-row">
|
||||
<div class="summary-card summary-risk">
|
||||
<div class="summary-label">民事案件总数</div>
|
||||
<div class="summary-value">{{ totalCivilCases }}</div>
|
||||
<div class="summary-sub">
|
||||
已结案 {{ civilClosedCases }} 件 · 未结案 {{ civilPendingCases }} 件
|
||||
</div>
|
||||
</div>
|
||||
<div class="summary-card summary-money">
|
||||
<div class="summary-label">案件地域/案由概览</div>
|
||||
<div class="summary-value small">
|
||||
{{ civilAreaStat || '地域分布未知' }}
|
||||
</div>
|
||||
<div class="summary-sub">
|
||||
{{ civilAyStat || '案由分布暂无统计' }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tag-row" v-if="civilAreaStat || civilAyStat || civilJafsStat">
|
||||
<span v-if="civilAreaStat" class="stat-tag">
|
||||
涉案地域:{{ civilAreaStat }}
|
||||
</span>
|
||||
<span v-if="civilAyStat" class="stat-tag">
|
||||
案由分布:{{ civilAyStat }}
|
||||
</span>
|
||||
<span v-if="civilJafsStat" class="stat-tag">
|
||||
结案方式:{{ civilJafsStat }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="civilCases.length" class="block">
|
||||
<div class="block-title">民事案件列表</div>
|
||||
<div class="case-list">
|
||||
<div v-for="(item, index) in civilCases" :key="item.c_id || index" class="case-wrapper">
|
||||
<div class="case-card">
|
||||
<!-- 可点击的摘要区域,参考 FLXG7E8F 样式 -->
|
||||
<div class="case-summary" @click="toggleCaseExpand(item.c_id || index)">
|
||||
<div class="summary-top">
|
||||
<div class="summary-title">
|
||||
<span class="case-no-text">
|
||||
{{ item.c_ah || '暂无案号' }}
|
||||
</span>
|
||||
<span class="case-type-tag">
|
||||
{{ item.n_ajlx || item.n_laay_tag || item.n_laay || '民事案件' }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="summary-middle">
|
||||
<span class="summary-label">立案:</span>
|
||||
<span class="summary-value">{{ formatDate(item.d_larq) }}</span>
|
||||
</div>
|
||||
<div class="summary-bottom">
|
||||
<span class="risk-tag" :class="statusClass(item.n_ajjzjd)">
|
||||
{{ item.n_ajjzjd || '未知进展' }}
|
||||
</span>
|
||||
<span v-if="item.n_pj_victory" class="victory-tag">
|
||||
胜诉估计:{{ item.n_pj_victory }}
|
||||
</span>
|
||||
<span class="expand-indicator">
|
||||
<span class="expand-text">
|
||||
{{ isCaseExpanded(item.c_id || index) ? '收起详情' : '展开详情' }}
|
||||
</span>
|
||||
<img src="@/assets/images/report/zk.png" alt="展开" class="w-4 h-4"
|
||||
:class="{ 'rotate-180': isCaseExpanded(item.c_id || index) }" />
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 详情区域,可展开/收起 -->
|
||||
<div class="case-detail" :class="{
|
||||
'detail-collapsed': !isCaseExpanded(item.c_id || index),
|
||||
'detail-expanded': isCaseExpanded(item.c_id || index),
|
||||
}">
|
||||
<div class="case-body">
|
||||
<div class="info-row">
|
||||
<span class="info-label">立案时间</span>
|
||||
<span class="info-value">{{ formatDate(item.d_larq) }}</span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">结案时间</span>
|
||||
<span class="info-value">{{ formatDate(item.d_jarq) }}</span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">经办法院</span>
|
||||
<span class="info-value">
|
||||
{{ item.n_jbfy || '-' }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">审理程序</span>
|
||||
<span class="info-value">
|
||||
{{ item.n_slcx || '-' }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">结案案由</span>
|
||||
<span class="info-value">
|
||||
{{ item.n_jaay || '-' }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">立案案由</span>
|
||||
<span class="info-value">
|
||||
{{ item.n_laay || '-' }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="info-row" v-if="item.c_gkws_pjjg">
|
||||
<span class="info-label">判决结果</span>
|
||||
<span class="info-value">
|
||||
{{ item.c_gkws_pjjg }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="info-row" v-if="casePartiesText(item)">
|
||||
<span class="info-label">当事人</span>
|
||||
<span class="info-value">
|
||||
{{ casePartiesText(item) }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-else-if="hasAnyData" class="empty-tip">
|
||||
暂未检索到民事案件记录,可留意其他风险模块。
|
||||
</div>
|
||||
<div v-else class="empty-tip">
|
||||
暂无企业涉诉信息
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, ref } from "vue";
|
||||
|
||||
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 entName = computed(() => {
|
||||
return props.params?.ent_name || props.params?.entName || "";
|
||||
});
|
||||
|
||||
// 提取 entout.data 结构
|
||||
const entoutData = computed(() => {
|
||||
const d = props.data || {};
|
||||
// 优先使用 entout.data
|
||||
if (d.entout && d.entout.data) return d.entout.data;
|
||||
if (d.entout) return d.entout;
|
||||
// 兼容 data.entout.data 结构
|
||||
if (d.data && d.data.entout && d.data.entout.data) return d.data.entout.data;
|
||||
return null;
|
||||
});
|
||||
|
||||
const civil = computed(() => entoutData.value?.civil || {});
|
||||
const civilCases = computed(() => civil.value.cases || []);
|
||||
const civilCount = computed(() => civil.value.count || {});
|
||||
|
||||
const totalCivilCases = computed(() => civilCount.value.count_total ?? civilCases.value.length ?? 0);
|
||||
const civilClosedCases = computed(() => civilCount.value.count_jie_total ?? 0);
|
||||
const civilPendingCases = computed(() => civilCount.value.count_wei_total ?? 0);
|
||||
|
||||
const civilAreaStat = computed(() => civilCount.value.area_stat || "");
|
||||
const civilAyStat = computed(() => civilCount.value.ay_stat || "");
|
||||
const civilJafsStat = computed(() => civilCount.value.jafs_stat || "");
|
||||
|
||||
const hasCivilData = computed(() => totalCivilCases.value > 0);
|
||||
const hasAnyData = computed(() => !!entoutData.value);
|
||||
|
||||
// 展开/收起案件详情,参考 FLXG7E8F 的交互
|
||||
const expandedCases = ref({});
|
||||
function toggleCaseExpand(key) {
|
||||
const id = String(key);
|
||||
expandedCases.value[id] = !expandedCases.value[id];
|
||||
}
|
||||
function isCaseExpanded(key) {
|
||||
const id = String(key);
|
||||
return !!expandedCases.value[id];
|
||||
}
|
||||
|
||||
const moneySummary = computed(() => {
|
||||
const total = civilCount.value.money_jie_total ?? null;
|
||||
if (total == null) return "暂无金额统计";
|
||||
if (typeof total === "number" && total === 0) return "金额较小或未公开";
|
||||
return "已结案金额估计 " + formatMoney(total) + " 元";
|
||||
});
|
||||
|
||||
function formatMoney(val) {
|
||||
if (val == null || val === "") return "-";
|
||||
const num = Number(val);
|
||||
if (Number.isNaN(num)) return String(val);
|
||||
if (num >= 1e8) {
|
||||
return (num / 1e8).toFixed(2) + " 亿";
|
||||
}
|
||||
if (num >= 1e4) {
|
||||
return (num / 1e4).toFixed(2) + " 万";
|
||||
}
|
||||
return num.toFixed(2);
|
||||
}
|
||||
|
||||
function formatDate(str) {
|
||||
if (!str) return "-";
|
||||
// 支持 yyyy-MM-dd 或 yyyy-MM-dd HH:mm:ss
|
||||
if (str.length >= 10) {
|
||||
return str.slice(0, 10);
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
function statusClass(status) {
|
||||
if (!status) return "status-tag status-unknown";
|
||||
if (String(status).includes("已结案")) return "status-tag status-closed";
|
||||
if (String(status).includes("执行中") || String(status).includes("审理中")) {
|
||||
return "status-tag status-processing";
|
||||
}
|
||||
return "status-tag status-unknown";
|
||||
}
|
||||
|
||||
function casePartiesText(item) {
|
||||
const list = item.c_dsrxx || [];
|
||||
if (!Array.isArray(list) || list.length === 0) return "";
|
||||
|
||||
// 尝试优先提取与当前企业相关的角色
|
||||
const name = entName.value;
|
||||
const related = name
|
||||
? list.filter((p) => typeof p.c_mc === "string" && p.c_mc.includes(name))
|
||||
: [];
|
||||
const targetList = related.length ? related : list;
|
||||
|
||||
const parts = targetList.map((p) => {
|
||||
const n = p.c_mc || "";
|
||||
const role = p.n_ssdw || "";
|
||||
if (n && role) return `${n}(${role})`;
|
||||
return n || role || "";
|
||||
}).filter(Boolean);
|
||||
|
||||
return parts.join(",");
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.card {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.header-box {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.header-title {
|
||||
font-size: 1.1rem;
|
||||
font-weight: 600;
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
.header-desc {
|
||||
margin-top: 0.25rem;
|
||||
font-size: 0.85rem;
|
||||
color: #666666;
|
||||
}
|
||||
|
||||
.header-ent {
|
||||
margin-top: 0.25rem;
|
||||
font-size: 0.85rem;
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
.summary-section {
|
||||
padding: 0.75rem 0;
|
||||
border-top: 1px solid #f1f1f1;
|
||||
border-bottom: 1px solid #f1f1f1;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.summary-row {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.summary-row {
|
||||
flex-direction: row;
|
||||
}
|
||||
}
|
||||
|
||||
.summary-card {
|
||||
flex: 1;
|
||||
padding: 0.75rem;
|
||||
border-radius: 0.75rem;
|
||||
}
|
||||
|
||||
.summary-risk {
|
||||
background: rgba(235, 60, 60, 0.04);
|
||||
border: 1px solid rgba(235, 60, 60, 0.3);
|
||||
}
|
||||
|
||||
.summary-money {
|
||||
background: rgba(214, 148, 62, 0.04);
|
||||
border: 1px solid rgba(214, 148, 62, 0.3);
|
||||
}
|
||||
|
||||
.summary-label {
|
||||
font-size: 0.85rem;
|
||||
color: #666666;
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.summary-value {
|
||||
font-size: 1.4rem;
|
||||
font-weight: 600;
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
.summary-value.small {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.summary-sub {
|
||||
margin-top: 0.25rem;
|
||||
font-size: 0.75rem;
|
||||
color: #999999;
|
||||
}
|
||||
|
||||
.tag-row {
|
||||
margin-top: 0.75rem;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.stat-tag {
|
||||
padding: 0.25rem 0.5rem;
|
||||
font-size: 0.75rem;
|
||||
border-radius: 999px;
|
||||
background-color: #f5f5f5;
|
||||
color: #666666;
|
||||
}
|
||||
|
||||
.block {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.block-title {
|
||||
font-size: 0.95rem;
|
||||
font-weight: 600;
|
||||
color: #333333;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.case-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.case-wrapper {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.case-card {
|
||||
border-radius: 0.75rem;
|
||||
border: 1px solid #dddddd;
|
||||
background-color: #ffffff;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.case-summary {
|
||||
padding: 0.75rem 0.75rem 0.5rem;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.summary-top {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.summary-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.case-no-text {
|
||||
font-size: 0.9rem;
|
||||
font-weight: 600;
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
.case-type-tag {
|
||||
padding: 0.1rem 0.4rem;
|
||||
border-radius: 999px;
|
||||
font-size: 0.75rem;
|
||||
background-color: #f9ecec;
|
||||
color: #eb3c3c;
|
||||
}
|
||||
|
||||
.summary-middle {
|
||||
font-size: 0.8rem;
|
||||
padding-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.summary-label {
|
||||
color: #666666;
|
||||
}
|
||||
|
||||
.summary-value {
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
.summary-bottom {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.risk-tag {
|
||||
padding: 0.2rem 0.6rem;
|
||||
border-radius: 999px;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.status-closed {
|
||||
background-color: rgba(31, 190, 93, 0.1);
|
||||
color: #1fbe5d;
|
||||
}
|
||||
|
||||
.status-processing {
|
||||
background-color: rgba(235, 60, 60, 0.08);
|
||||
color: #eb3c3c;
|
||||
}
|
||||
|
||||
.status-unknown {
|
||||
background-color: rgba(153, 153, 153, 0.1);
|
||||
color: #666666;
|
||||
}
|
||||
|
||||
.victory-tag {
|
||||
font-size: 0.75rem;
|
||||
color: #d6943e;
|
||||
}
|
||||
|
||||
.expand-indicator {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.25rem;
|
||||
font-size: 0.75rem;
|
||||
color: #999999;
|
||||
}
|
||||
|
||||
.expand-indicator img {
|
||||
transition: transform 0.2s ease;
|
||||
}
|
||||
|
||||
.expand-indicator img.rotate-180 {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
.case-detail {
|
||||
border-top: 1px dashed #eeeeee;
|
||||
padding: 0 0.75rem 0.5rem;
|
||||
max-height: 0;
|
||||
opacity: 0;
|
||||
overflow: hidden;
|
||||
transition: max-height 0.25s ease, opacity 0.25s ease;
|
||||
}
|
||||
|
||||
.case-detail.detail-expanded {
|
||||
max-height: 500px;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.status-closed {
|
||||
background-color: rgba(31, 190, 93, 0.1);
|
||||
color: #1fbe5d;
|
||||
}
|
||||
|
||||
.status-processing {
|
||||
background-color: rgba(235, 60, 60, 0.08);
|
||||
color: #eb3c3c;
|
||||
}
|
||||
|
||||
.status-unknown {
|
||||
background-color: rgba(153, 153, 153, 0.1);
|
||||
color: #666666;
|
||||
}
|
||||
|
||||
.case-body {
|
||||
border-top: 1px dashed #e5e5e5;
|
||||
padding-top: 0.5rem;
|
||||
}
|
||||
|
||||
.info-row {
|
||||
display: flex;
|
||||
font-size: 0.8rem;
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.info-label {
|
||||
width: 4.5rem;
|
||||
color: #999999;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.info-value {
|
||||
color: #333333;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.empty-tip {
|
||||
margin-top: 0.75rem;
|
||||
padding: 0.75rem;
|
||||
text-align: center;
|
||||
font-size: 0.85rem;
|
||||
color: #999999;
|
||||
background-color: #fafafa;
|
||||
border-radius: 0.75rem;
|
||||
}
|
||||
</style>
|
||||
212
src/ui/CYYSY3M8S.vue
Normal file
212
src/ui/CYYSY3M8S.vue
Normal file
@@ -0,0 +1,212 @@
|
||||
<template>
|
||||
<div class="card">
|
||||
<div class="header-box">
|
||||
<h3 class="header-title">运营商二要素</h3>
|
||||
<p class="header-desc">核验手机号与姓名是否一致</p>
|
||||
</div>
|
||||
|
||||
<div v-if="hasData" class="result-section" :class="resultSectionClass">
|
||||
<div class="result-main">
|
||||
<div class="result-label">核验结果</div>
|
||||
<div class="result-value" :class="resultClass">
|
||||
{{ resultText }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="result-sub">
|
||||
<span>计费状态:</span>
|
||||
<span class="font-medium">{{ feeText }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="hasParams" class="info-block">
|
||||
<div class="block-title">被核验号码</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">手机号</span>
|
||||
<span class="info-value font-mono">{{ params.mobile || params.mobile_no || '-' }}</span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">姓名</span>
|
||||
<span class="info-value">{{ maskedName }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="!hasData" class="empty-tip">暂无核验结果</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed } from 'vue';
|
||||
|
||||
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 hasData = computed(() => props.data && Object.keys(props.data).length > 0);
|
||||
|
||||
const resultCode = computed(() => {
|
||||
const v = props.data?.result;
|
||||
if (v === 0 || v === '0') return 0;
|
||||
if (v === 1 || v === '1') return 1;
|
||||
return null;
|
||||
});
|
||||
|
||||
const resultText = computed(() => {
|
||||
if (resultCode.value === 0) return '一致';
|
||||
if (resultCode.value === 1) return '不一致';
|
||||
return '暂无结果';
|
||||
});
|
||||
|
||||
const fee = computed(() => {
|
||||
const v = props.data?.fee;
|
||||
if (v === 0 || v === '0') return 0;
|
||||
if (v === 1 || v === '1') return 1;
|
||||
return null;
|
||||
});
|
||||
|
||||
const feeText = computed(() => {
|
||||
if (fee.value === 0) return '不计费';
|
||||
if (fee.value === 1) return '计费';
|
||||
return '未知';
|
||||
});
|
||||
|
||||
const resultClass = computed(() => {
|
||||
if (resultCode.value === 0) return 'result-ok';
|
||||
if (resultCode.value === 1) return 'result-bad';
|
||||
return 'result-unknown';
|
||||
});
|
||||
|
||||
const resultSectionClass = computed(() => {
|
||||
if (resultCode.value === 0) return 'result-section ok';
|
||||
if (resultCode.value === 1) return 'result-section bad';
|
||||
return 'result-section unknown';
|
||||
});
|
||||
|
||||
const hasParams = computed(() => {
|
||||
const p = props.params || {};
|
||||
return p.mobile || p.mobile_no || p.name;
|
||||
});
|
||||
|
||||
const maskedName = computed(() => {
|
||||
const name = props.params?.name || '';
|
||||
if (!name) return '-';
|
||||
return name.length > 1 ? name[0] + '*'.repeat(name.length - 1) : '*';
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.card {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.header-box {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.header-title {
|
||||
font-size: 1.125rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.header-desc {
|
||||
font-size: 0.875rem;
|
||||
color: #6b7280;
|
||||
margin-top: 0.25rem;
|
||||
}
|
||||
|
||||
.result-section {
|
||||
padding: 0.75rem 0.875rem;
|
||||
border-radius: 0.75rem;
|
||||
margin-bottom: 1rem;
|
||||
border: 1px solid #e5e7eb;
|
||||
}
|
||||
|
||||
.result-section.ok {
|
||||
background: #ecfdf3;
|
||||
border-color: #22c55e33;
|
||||
}
|
||||
|
||||
.result-section.bad {
|
||||
background: #fef2f2;
|
||||
border-color: #ef444433;
|
||||
}
|
||||
|
||||
.result-section.unknown {
|
||||
background: #f9fafb;
|
||||
}
|
||||
|
||||
.result-main {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.result-label {
|
||||
font-size: 0.875rem;
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.result-value {
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.result-ok {
|
||||
color: #16a34a;
|
||||
}
|
||||
|
||||
.result-bad {
|
||||
color: #dc2626;
|
||||
}
|
||||
|
||||
.result-unknown {
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.result-sub {
|
||||
margin-top: 0.5rem;
|
||||
font-size: 0.875rem;
|
||||
color: #4b5563;
|
||||
}
|
||||
|
||||
.info-block {
|
||||
margin-top: 1rem;
|
||||
padding: 0.75rem 0.875rem;
|
||||
border-radius: 0.75rem;
|
||||
background: #f9fafb;
|
||||
}
|
||||
|
||||
.block-title {
|
||||
font-size: 0.875rem;
|
||||
font-weight: 600;
|
||||
color: #374151;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.info-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
font-size: 0.875rem;
|
||||
padding: 0.15rem 0;
|
||||
}
|
||||
|
||||
.info-label {
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.info-value {
|
||||
color: #111827;
|
||||
margin-left: 1rem;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.empty-tip {
|
||||
color: #9ca3af;
|
||||
font-size: 0.875rem;
|
||||
padding: 1rem 0;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
162
src/ui/CYYSY6F2B.vue
Normal file
162
src/ui/CYYSY6F2B.vue
Normal file
@@ -0,0 +1,162 @@
|
||||
<template>
|
||||
<div class="card">
|
||||
<div class="header-box">
|
||||
<h3 class="header-title">手机消费区间验证</h3>
|
||||
<p class="header-desc">根据运营商数据评估消费能力档位</p>
|
||||
</div>
|
||||
|
||||
<div v-if="hasData" class="result-section">
|
||||
<div class="result-main">
|
||||
<div class="result-label">消费档位</div>
|
||||
<div class="result-value">
|
||||
{{ stateText }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="result-sub">
|
||||
<span>号码所属运营商:{{ operatorText }}</span>
|
||||
<span v-if="isPortedText" class="ml-2">是否携号转网:{{ isPortedText }}</span>
|
||||
<span v-if="operatorRealText" class="ml-2">实际运营商:{{ operatorRealText }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="hasParams" class="info-block">
|
||||
<div class="block-title">被核验号码</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">手机号</span>
|
||||
<span class="info-value font-mono">{{ params.mobile || params.mobile_no || '-' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="!hasData" class="empty-tip">暂无核验结果</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed } from 'vue';
|
||||
|
||||
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 hasData = computed(() => props.data && Object.keys(props.data).length > 0);
|
||||
|
||||
const state = computed(() => props.data?.state || '');
|
||||
const operator = computed(() => props.data?.operator || '');
|
||||
const operatorReal = computed(() => props.data?.operator_real || '');
|
||||
const isXhzw = computed(() => props.data?.is_xhzw || '');
|
||||
|
||||
const operatorMap = {
|
||||
'1': '移动',
|
||||
'2': '联通',
|
||||
'3': '电信',
|
||||
};
|
||||
|
||||
const operatorText = computed(() => operatorMap[operator.value] || operator.value || '-');
|
||||
const operatorRealText = computed(() => operatorMap[operatorReal.value] || '');
|
||||
|
||||
const isPortedText = computed(() => {
|
||||
if (isXhzw.value === '0') return '否';
|
||||
if (isXhzw.value === '1') return '是';
|
||||
return '';
|
||||
});
|
||||
|
||||
const stateText = computed(() => {
|
||||
// 这里只提示“消费档位编号”,具体金额区间见注释说明
|
||||
if (!state.value) return '未知';
|
||||
return `消费档位:${state.value}(不同运营商对应区间不同)`;
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.card {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.header-box {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.header-title {
|
||||
font-size: 1.125rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.header-desc {
|
||||
font-size: 0.875rem;
|
||||
color: #6b7280;
|
||||
margin-top: 0.25rem;
|
||||
}
|
||||
|
||||
.result-section {
|
||||
padding: 0.75rem 0.875rem;
|
||||
border-radius: 0.75rem;
|
||||
margin-bottom: 1rem;
|
||||
border: 1px solid #e5e7eb;
|
||||
background: #f9fafb;
|
||||
}
|
||||
|
||||
.result-main {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.result-label {
|
||||
font-size: 0.875rem;
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.result-value {
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
color: #111827;
|
||||
}
|
||||
|
||||
.result-sub {
|
||||
margin-top: 0.5rem;
|
||||
font-size: 0.875rem;
|
||||
color: #4b5563;
|
||||
}
|
||||
|
||||
.info-block {
|
||||
margin-top: 1rem;
|
||||
padding: 0.75rem 0.875rem;
|
||||
border-radius: 0.75rem;
|
||||
background: #f9fafb;
|
||||
}
|
||||
|
||||
.block-title {
|
||||
font-size: 0.875rem;
|
||||
font-weight: 600;
|
||||
color: #374151;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.info-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
font-size: 0.875rem;
|
||||
padding: 0.15rem 0;
|
||||
}
|
||||
|
||||
.info-label {
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.info-value {
|
||||
color: #111827;
|
||||
margin-left: 1rem;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.empty-tip {
|
||||
color: #9ca3af;
|
||||
font-size: 0.875rem;
|
||||
padding: 1rem 0;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
124
src/ui/CYYSY9E4A.vue
Normal file
124
src/ui/CYYSY9E4A.vue
Normal file
@@ -0,0 +1,124 @@
|
||||
<template>
|
||||
<div class="card">
|
||||
<div class="header-box">
|
||||
<h3 class="header-title">手机号码归属地核验</h3>
|
||||
<p class="header-desc">展示手机号码的省市、运营商和区号等信息</p>
|
||||
</div>
|
||||
|
||||
<div v-if="hasData" class="result-section">
|
||||
<div class="info-row">
|
||||
<span class="info-label">号段</span>
|
||||
<span class="info-value font-mono">{{ data.mobilePrefix || '-' }}</span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">省份</span>
|
||||
<span class="info-value">{{ data.provinceName || '-' }}</span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">城市</span>
|
||||
<span class="info-value">{{ data.cityName || '-' }}</span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">运营商</span>
|
||||
<span class="info-value">{{ data.channel || '-' }}</span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">区号</span>
|
||||
<span class="info-value">{{ data.areaCode || '-' }}</span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">邮编</span>
|
||||
<span class="info-value">{{ data.postCode || '-' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="hasParams" class="info-block">
|
||||
<div class="block-title">被核验号码</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">手机号</span>
|
||||
<span class="info-value font-mono">{{ params.mobile || params.mobile_no || '-' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="!hasData" class="empty-tip">暂无核验结果</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed } from 'vue';
|
||||
|
||||
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 hasData = computed(() => props.data && Object.keys(props.data).length > 0);
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.card {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.header-box {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.header-title {
|
||||
font-size: 1.125rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.header-desc {
|
||||
font-size: 0.875rem;
|
||||
color: #6b7280;
|
||||
margin-top: 0.25rem;
|
||||
}
|
||||
|
||||
.result-section {
|
||||
background: #f9fafb;
|
||||
padding: 0.75rem 0.875rem;
|
||||
border-radius: 0.75rem;
|
||||
}
|
||||
|
||||
.info-block {
|
||||
margin-top: 1rem;
|
||||
padding: 0.75rem 0.875rem;
|
||||
border-radius: 0.75rem;
|
||||
background: #f9fafb;
|
||||
}
|
||||
|
||||
.block-title {
|
||||
font-size: 0.875rem;
|
||||
font-weight: 600;
|
||||
color: #374151;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.info-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
font-size: 0.875rem;
|
||||
padding: 0.15rem 0;
|
||||
}
|
||||
|
||||
.info-label {
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.info-value {
|
||||
color: #111827;
|
||||
margin-left: 1rem;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.empty-tip {
|
||||
color: #9ca3af;
|
||||
font-size: 0.875rem;
|
||||
padding: 1rem 0;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
192
src/ui/CYYSYE7V5.vue
Normal file
192
src/ui/CYYSYE7V5.vue
Normal file
@@ -0,0 +1,192 @@
|
||||
<template>
|
||||
<div class="card">
|
||||
<div class="header-box">
|
||||
<h3 class="header-title">手机在网状态</h3>
|
||||
<p class="header-desc">判断号码当前是否在网可用</p>
|
||||
</div>
|
||||
|
||||
<div v-if="hasData" class="result-section" :class="statusSectionClass">
|
||||
<div class="result-main">
|
||||
<div class="result-label">在网状态</div>
|
||||
<div class="result-value" :class="statusClass">
|
||||
{{ statusText }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="result-sub">
|
||||
<span>运营商:{{ channel || '-' }}</span>
|
||||
<span v-if="status === 1 && desc" class="ml-2">原因:{{ desc }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="hasParams" class="info-block">
|
||||
<div class="block-title">被核验号码</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">手机号</span>
|
||||
<span class="info-value font-mono">{{ params.mobile || params.mobile_no || '-' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="!hasData" class="empty-tip">暂无核验结果</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed } from 'vue';
|
||||
|
||||
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 hasData = computed(() => props.data && Object.keys(props.data).length > 0);
|
||||
|
||||
const status = computed(() => {
|
||||
const v = props.data?.status;
|
||||
if (v === 0 || v === '0') return 0;
|
||||
if (v === 1 || v === '1') return 1;
|
||||
return null;
|
||||
});
|
||||
|
||||
const statusText = computed(() => {
|
||||
if (status.value === 0) return '在网';
|
||||
if (status.value === 1) return '不在网';
|
||||
return '未知';
|
||||
});
|
||||
|
||||
const statusClass = computed(() => {
|
||||
if (status.value === 0) return 'result-ok';
|
||||
if (status.value === 1) return 'result-bad';
|
||||
return 'result-unknown';
|
||||
});
|
||||
|
||||
const statusSectionClass = computed(() => {
|
||||
if (status.value === 0) return 'result-section ok';
|
||||
if (status.value === 1) return 'result-section bad';
|
||||
return 'result-section unknown';
|
||||
});
|
||||
|
||||
const channel = computed(() => props.data?.channel || '');
|
||||
const desc = computed(() => props.data?.desc || '');
|
||||
|
||||
const hasParams = computed(() => {
|
||||
const p = props.params || {};
|
||||
return p.mobile || p.mobile_no;
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.card {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.header-box {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.header-title {
|
||||
font-size: 1.125rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.header-desc {
|
||||
font-size: 0.875rem;
|
||||
color: #6b7280;
|
||||
margin-top: 0.25rem;
|
||||
}
|
||||
|
||||
.result-section {
|
||||
padding: 0.75rem 0.875rem;
|
||||
border-radius: 0.75rem;
|
||||
margin-bottom: 1rem;
|
||||
border: 1px solid #e5e7eb;
|
||||
}
|
||||
|
||||
.result-section.ok {
|
||||
background: #ecfdf3;
|
||||
border-color: #22c55e33;
|
||||
}
|
||||
|
||||
.result-section.bad {
|
||||
background: #fef2f2;
|
||||
border-color: #ef444433;
|
||||
}
|
||||
|
||||
.result-section.unknown {
|
||||
background: #f9fafb;
|
||||
}
|
||||
|
||||
.result-main {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.result-label {
|
||||
font-size: 0.875rem;
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.result-value {
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.result-ok {
|
||||
color: #16a34a;
|
||||
}
|
||||
|
||||
.result-bad {
|
||||
color: #dc2626;
|
||||
}
|
||||
|
||||
.result-unknown {
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.result-sub {
|
||||
margin-top: 0.5rem;
|
||||
font-size: 0.875rem;
|
||||
color: #4b5563;
|
||||
}
|
||||
|
||||
.info-block {
|
||||
margin-top: 1rem;
|
||||
padding: 0.75rem 0.875rem;
|
||||
border-radius: 0.75rem;
|
||||
background: #f9fafb;
|
||||
}
|
||||
|
||||
.block-title {
|
||||
font-size: 0.875rem;
|
||||
font-weight: 600;
|
||||
color: #374151;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.info-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
font-size: 0.875rem;
|
||||
padding: 0.15rem 0;
|
||||
}
|
||||
|
||||
.info-label {
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.info-value {
|
||||
color: #111827;
|
||||
margin-left: 1rem;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.empty-tip {
|
||||
color: #9ca3af;
|
||||
font-size: 0.875rem;
|
||||
padding: 1rem 0;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
210
src/ui/CYYSYF2T7.vue
Normal file
210
src/ui/CYYSYF2T7.vue
Normal file
@@ -0,0 +1,210 @@
|
||||
<template>
|
||||
<div class="card">
|
||||
<div class="header-box">
|
||||
<h3 class="header-title">号码二次放号</h3>
|
||||
<p class="header-desc">判断该手机号是否为二次放号</p>
|
||||
</div>
|
||||
|
||||
<div v-if="hasData" class="result-section" :class="resultSectionClass">
|
||||
<div class="result-main">
|
||||
<div class="result-label">核验结果</div>
|
||||
<div class="result-value" :class="resultClass">
|
||||
{{ resultText }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="result-sub">
|
||||
<span>运营商:</span>
|
||||
<span class="font-medium">{{ channelText }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="hasParams" class="info-block">
|
||||
<div class="block-title">被核验号码</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">手机号</span>
|
||||
<span class="info-value font-mono">{{ params.mobile || params.mobile_no || '-' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="!hasData" class="empty-tip">暂无核验结果</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed } from 'vue';
|
||||
|
||||
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 hasData = computed(() => props.data && Object.keys(props.data).length > 0);
|
||||
|
||||
const result = computed(() => {
|
||||
const v = props.data?.result;
|
||||
if (v === 0 || v === '0') return 0;
|
||||
if (v === 1 || v === '1') return 1;
|
||||
if (v === 2 || v === '2') return 2;
|
||||
return null;
|
||||
});
|
||||
|
||||
const resultText = computed(() => {
|
||||
switch (result.value) {
|
||||
case 0:
|
||||
return '是二次卡';
|
||||
case 1:
|
||||
return '不是二次卡';
|
||||
case 2:
|
||||
return '数据库中无信息';
|
||||
default:
|
||||
return '暂无结果';
|
||||
}
|
||||
});
|
||||
|
||||
const resultClass = computed(() => {
|
||||
if (result.value === 0) return 'result-bad';
|
||||
if (result.value === 1) return 'result-ok';
|
||||
if (result.value === 2) return 'result-unknown';
|
||||
return 'result-unknown';
|
||||
});
|
||||
|
||||
const resultSectionClass = computed(() => {
|
||||
if (result.value === 0) return 'result-section bad';
|
||||
if (result.value === 1) return 'result-section ok';
|
||||
if (result.value === 2) return 'result-section unknown';
|
||||
return 'result-section unknown';
|
||||
});
|
||||
|
||||
const channelRaw = computed(() => props.data?.channel || '');
|
||||
|
||||
const channelText = computed(() => {
|
||||
const c = channelRaw.value;
|
||||
if (!c) return '-';
|
||||
if (c === 'cmcc') return '移动 (cmcc)';
|
||||
if (c === 'cucc') return '联通 (cucc)';
|
||||
if (c === 'ctcc') return '电信 (ctcc)';
|
||||
return c;
|
||||
});
|
||||
|
||||
const hasParams = computed(() => {
|
||||
const p = props.params || {};
|
||||
return p.mobile || p.mobile_no;
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.card {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.header-box {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.header-title {
|
||||
font-size: 1.125rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.header-desc {
|
||||
font-size: 0.875rem;
|
||||
color: #6b7280;
|
||||
margin-top: 0.25rem;
|
||||
}
|
||||
|
||||
.result-section {
|
||||
padding: 0.75rem 0.875rem;
|
||||
border-radius: 0.75rem;
|
||||
margin-bottom: 1rem;
|
||||
border: 1px solid #e5e7eb;
|
||||
}
|
||||
|
||||
.result-section.ok {
|
||||
background: #ecfdf3;
|
||||
border-color: #22c55e33;
|
||||
}
|
||||
|
||||
.result-section.bad {
|
||||
background: #fef2f2;
|
||||
border-color: #ef444433;
|
||||
}
|
||||
|
||||
.result-section.unknown {
|
||||
background: #f9fafb;
|
||||
}
|
||||
|
||||
.result-main {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.result-label {
|
||||
font-size: 0.875rem;
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.result-value {
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.result-ok {
|
||||
color: #16a34a;
|
||||
}
|
||||
|
||||
.result-bad {
|
||||
color: #dc2626;
|
||||
}
|
||||
|
||||
.result-unknown {
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.result-sub {
|
||||
margin-top: 0.5rem;
|
||||
font-size: 0.875rem;
|
||||
color: #4b5563;
|
||||
}
|
||||
|
||||
.info-block {
|
||||
margin-top: 1rem;
|
||||
padding: 0.75rem 0.875rem;
|
||||
border-radius: 0.75rem;
|
||||
background: #f9fafb;
|
||||
}
|
||||
|
||||
.block-title {
|
||||
font-size: 0.875rem;
|
||||
font-weight: 600;
|
||||
color: #374151;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.info-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
font-size: 0.875rem;
|
||||
padding: 0.15rem 0;
|
||||
}
|
||||
|
||||
.info-label {
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.info-value {
|
||||
color: #111827;
|
||||
margin-left: 1rem;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.empty-tip {
|
||||
color: #9ca3af;
|
||||
font-size: 0.875rem;
|
||||
padding: 1rem 0;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
218
src/ui/CYYSYK8R3.vue
Normal file
218
src/ui/CYYSYK8R3.vue
Normal file
@@ -0,0 +1,218 @@
|
||||
<template>
|
||||
<div class="card">
|
||||
<div class="header-box">
|
||||
<h3 class="header-title">手机空号检测</h3>
|
||||
<p class="header-desc">判断号码是空号、实号还是沉默/风险号</p>
|
||||
</div>
|
||||
|
||||
<div v-if="hasData" class="result-section" :class="statusSectionClass">
|
||||
<div class="result-main">
|
||||
<div class="result-label">号码状态</div>
|
||||
<div class="result-value" :class="statusClass">
|
||||
{{ statusText }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="result-sub" v-if="area || channel">
|
||||
<span v-if="area">归属地:{{ area }}</span>
|
||||
<span v-if="channel" class="ml-2">运营商:{{ channel }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="hasParams" class="info-block">
|
||||
<div class="block-title">被核验号码</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">手机号</span>
|
||||
<span class="info-value font-mono">{{ params.mobile || params.mobile_no || '-' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="!hasData" class="empty-tip">暂无核验结果</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed } from 'vue';
|
||||
|
||||
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 hasData = computed(() => props.data && Object.keys(props.data).length > 0);
|
||||
|
||||
const statusCode = computed(() => {
|
||||
const v = props.data?.status;
|
||||
if (v == null) return null;
|
||||
const n = Number(v);
|
||||
return Number.isFinite(n) ? n : null;
|
||||
});
|
||||
|
||||
const statusText = computed(() => {
|
||||
switch (statusCode.value) {
|
||||
case 0:
|
||||
return '空号';
|
||||
case 1:
|
||||
return '实号';
|
||||
case 2:
|
||||
return '停机';
|
||||
case 3:
|
||||
return '库无(预留)';
|
||||
case 4:
|
||||
return '沉默号';
|
||||
case 5:
|
||||
return '风险号';
|
||||
default:
|
||||
return '未知';
|
||||
}
|
||||
});
|
||||
|
||||
const statusClass = computed(() => {
|
||||
const s = statusCode.value;
|
||||
if (s === 1) return 'result-ok';
|
||||
if (s === 0 || s === 4 || s === 5) return 'result-bad';
|
||||
if (s === 2 || s === 3) return 'result-warn';
|
||||
return 'result-unknown';
|
||||
});
|
||||
|
||||
const statusSectionClass = computed(() => {
|
||||
const s = statusCode.value;
|
||||
if (s === 1) return 'result-section ok';
|
||||
if (s === 0 || s === 4 || s === 5) return 'result-section bad';
|
||||
if (s === 2 || s === 3) return 'result-section warn';
|
||||
return 'result-section unknown';
|
||||
});
|
||||
|
||||
const area = computed(() => props.data?.area || '');
|
||||
const channel = computed(() => props.data?.channel || '');
|
||||
|
||||
const hasParams = computed(() => {
|
||||
const p = props.params || {};
|
||||
return p.mobile || p.mobile_no;
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.card {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.header-box {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.header-title {
|
||||
font-size: 1.125rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.header-desc {
|
||||
font-size: 0.875rem;
|
||||
color: #6b7280;
|
||||
margin-top: 0.25rem;
|
||||
}
|
||||
|
||||
.result-section {
|
||||
padding: 0.75rem 0.875rem;
|
||||
border-radius: 0.75rem;
|
||||
margin-bottom: 1rem;
|
||||
border: 1px solid #e5e7eb;
|
||||
}
|
||||
|
||||
.result-section.ok {
|
||||
background: #ecfdf3;
|
||||
border-color: #22c55e33;
|
||||
}
|
||||
|
||||
.result-section.bad {
|
||||
background: #fef2f2;
|
||||
border-color: #ef444433;
|
||||
}
|
||||
|
||||
.result-section.warn {
|
||||
background: #fffbeb;
|
||||
border-color: #f9731633;
|
||||
}
|
||||
|
||||
.result-section.unknown {
|
||||
background: #f9fafb;
|
||||
}
|
||||
|
||||
.result-main {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.result-label {
|
||||
font-size: 0.875rem;
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.result-value {
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.result-ok {
|
||||
color: #16a34a;
|
||||
}
|
||||
|
||||
.result-bad {
|
||||
color: #dc2626;
|
||||
}
|
||||
|
||||
.result-warn {
|
||||
color: #d97706;
|
||||
}
|
||||
|
||||
.result-unknown {
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.result-sub {
|
||||
margin-top: 0.5rem;
|
||||
font-size: 0.875rem;
|
||||
color: #4b5563;
|
||||
}
|
||||
|
||||
.info-block {
|
||||
margin-top: 1rem;
|
||||
padding: 0.75rem 0.875rem;
|
||||
border-radius: 0.75rem;
|
||||
background: #f9fafb;
|
||||
}
|
||||
|
||||
.block-title {
|
||||
font-size: 0.875rem;
|
||||
font-weight: 600;
|
||||
color: #374151;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.info-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
font-size: 0.875rem;
|
||||
padding: 0.15rem 0;
|
||||
}
|
||||
|
||||
.info-label {
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.info-value {
|
||||
color: #111827;
|
||||
margin-left: 1rem;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.empty-tip {
|
||||
color: #9ca3af;
|
||||
font-size: 0.875rem;
|
||||
padding: 1rem 0;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
216
src/ui/CYYSYK9R4.vue
Normal file
216
src/ui/CYYSYK9R4.vue
Normal file
@@ -0,0 +1,216 @@
|
||||
<template>
|
||||
<div class="card">
|
||||
<div class="header-box">
|
||||
<h3 class="header-title">全网手机三要素验证周更</h3>
|
||||
<p class="header-desc">核验手机号+身份证+姓名是否一致</p>
|
||||
</div>
|
||||
|
||||
<div v-if="hasData" class="result-section" :class="resultSectionClass">
|
||||
<div class="result-main">
|
||||
<div class="result-label">核验结果</div>
|
||||
<div class="result-value" :class="resultClass">
|
||||
{{ resultText }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="hasParams" class="info-block">
|
||||
<div class="block-title">被核验信息</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">手机号</span>
|
||||
<span class="info-value font-mono">{{ params.mobile || params.mobile_no || '-' }}</span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">姓名</span>
|
||||
<span class="info-value">{{ maskedName }}</span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">身份证号</span>
|
||||
<span class="info-value font-mono">{{ maskedIdCard }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="!hasData" class="empty-tip">暂无核验结果</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed } from 'vue';
|
||||
|
||||
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 hasData = computed(() => props.data && Object.keys(props.data).length > 0);
|
||||
|
||||
const state = computed(() => {
|
||||
const v = props.data?.state;
|
||||
if (v == null) return null;
|
||||
return String(v);
|
||||
});
|
||||
|
||||
const resultText = computed(() => {
|
||||
switch (state.value) {
|
||||
case '1':
|
||||
return '验证一致';
|
||||
case '2':
|
||||
return '验证不一致';
|
||||
case '3':
|
||||
return '异常情况';
|
||||
default:
|
||||
return '暂无结果';
|
||||
}
|
||||
});
|
||||
|
||||
const resultClass = computed(() => {
|
||||
if (state.value === '1') return 'result-ok';
|
||||
if (state.value === '2') return 'result-bad';
|
||||
if (state.value === '3') return 'result-warn';
|
||||
return 'result-unknown';
|
||||
});
|
||||
|
||||
const resultSectionClass = computed(() => {
|
||||
if (state.value === '1') return 'result-section ok';
|
||||
if (state.value === '2') return 'result-section bad';
|
||||
if (state.value === '3') return 'result-section warn';
|
||||
return 'result-section unknown';
|
||||
});
|
||||
|
||||
const hasParams = computed(() => {
|
||||
const p = props.params || {};
|
||||
return p.mobile || p.mobile_no || p.name || p.id_card;
|
||||
});
|
||||
|
||||
const maskedName = computed(() => {
|
||||
const name = props.params?.name || '';
|
||||
if (!name) return '-';
|
||||
return name.length > 1 ? name[0] + '*'.repeat(name.length - 1) : '*';
|
||||
});
|
||||
|
||||
const maskedIdCard = computed(() => {
|
||||
const id = props.params?.id_card || '';
|
||||
if (!id || id.length < 8) return id || '-';
|
||||
return id.slice(0, 4) + '********' + id.slice(-4);
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.card {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.header-box {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.header-title {
|
||||
font-size: 1.125rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.header-desc {
|
||||
font-size: 0.875rem;
|
||||
color: #6b7280;
|
||||
margin-top: 0.25rem;
|
||||
}
|
||||
|
||||
.result-section {
|
||||
padding: 0.75rem 0.875rem;
|
||||
border-radius: 0.75rem;
|
||||
margin-bottom: 1rem;
|
||||
border: 1px solid #e5e7eb;
|
||||
}
|
||||
|
||||
.result-section.ok {
|
||||
background: #ecfdf3;
|
||||
border-color: #22c55e33;
|
||||
}
|
||||
|
||||
.result-section.bad {
|
||||
background: #fef2f2;
|
||||
border-color: #ef444433;
|
||||
}
|
||||
|
||||
.result-section.warn {
|
||||
background: #fffbeb;
|
||||
border-color: #f9731633;
|
||||
}
|
||||
|
||||
.result-section.unknown {
|
||||
background: #f9fafb;
|
||||
}
|
||||
|
||||
.result-main {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.result-label {
|
||||
font-size: 0.875rem;
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.result-value {
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.result-ok {
|
||||
color: #16a34a;
|
||||
}
|
||||
|
||||
.result-bad {
|
||||
color: #dc2626;
|
||||
}
|
||||
|
||||
.result-warn {
|
||||
color: #d97706;
|
||||
}
|
||||
|
||||
.result-unknown {
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.info-block {
|
||||
margin-top: 1rem;
|
||||
padding: 0.75rem 0.875rem;
|
||||
border-radius: 0.75rem;
|
||||
background: #f9fafb;
|
||||
}
|
||||
|
||||
.block-title {
|
||||
font-size: 0.875rem;
|
||||
font-weight: 600;
|
||||
color: #374151;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.info-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
font-size: 0.875rem;
|
||||
padding: 0.15rem 0;
|
||||
}
|
||||
|
||||
.info-label {
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.info-value {
|
||||
color: #111827;
|
||||
margin-left: 1rem;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.empty-tip {
|
||||
color: #9ca3af;
|
||||
font-size: 0.875rem;
|
||||
padding: 1rem 0;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
172
src/ui/CYYSYP0T4.vue
Normal file
172
src/ui/CYYSYP0T4.vue
Normal file
@@ -0,0 +1,172 @@
|
||||
<template>
|
||||
<div class="card">
|
||||
<div class="header-box">
|
||||
<h3 class="header-title">手机号码在网时长</h3>
|
||||
<p class="header-desc">展示号码在当前运营商下的在网时长区间</p>
|
||||
</div>
|
||||
|
||||
<div v-if="hasData" class="result-section">
|
||||
<div class="result-main">
|
||||
<div class="result-label">在网时长</div>
|
||||
<div class="result-value">
|
||||
{{ timeText }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="result-sub">
|
||||
<span>运营商:{{ channelText }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="hasParams" class="info-block">
|
||||
<div class="block-title">被核验号码</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">手机号</span>
|
||||
<span class="info-value font-mono">{{ params.mobile || params.mobile_no || '-' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="!hasData" class="empty-tip">暂无核验结果</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed } from 'vue';
|
||||
|
||||
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 hasData = computed(() => props.data && Object.keys(props.data).length > 0);
|
||||
|
||||
const time = computed(() => props.data?.time || '');
|
||||
|
||||
const timeText = computed(() => {
|
||||
const t = time.value;
|
||||
if (!t) return '-';
|
||||
switch (t) {
|
||||
case '[0,3)':
|
||||
return '0–3 个月';
|
||||
case '[3,6)':
|
||||
return '3–6 个月';
|
||||
case '[6,12)':
|
||||
return '6–12 个月';
|
||||
case '[12,24)':
|
||||
return '12–24 个月';
|
||||
case '[24,-1)':
|
||||
return '24 个月及以上';
|
||||
default:
|
||||
return t;
|
||||
}
|
||||
});
|
||||
|
||||
const channelRaw = computed(() => props.data?.channel || '');
|
||||
|
||||
const channelText = computed(() => {
|
||||
const c = channelRaw.value;
|
||||
if (!c) return '-';
|
||||
if (c === 'cmcc') return '移动 (cmcc)';
|
||||
if (c === 'cucc') return '联通 (cucc)';
|
||||
if (c === 'ctcc') return '电信 (ctcc)';
|
||||
if (c === 'gdcc') return '广电 (gdcc)';
|
||||
return c;
|
||||
});
|
||||
|
||||
const hasParams = computed(() => {
|
||||
const p = props.params || {};
|
||||
return p.mobile || p.mobile_no;
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.card {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.header-box {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.header-title {
|
||||
font-size: 1.125rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.header-desc {
|
||||
font-size: 0.875rem;
|
||||
color: #6b7280;
|
||||
margin-top: 0.25rem;
|
||||
}
|
||||
|
||||
.result-section {
|
||||
padding: 0.75rem 0.875rem;
|
||||
border-radius: 0.75rem;
|
||||
margin-bottom: 1rem;
|
||||
border: 1px solid #e5e7eb;
|
||||
background: #f9fafb;
|
||||
}
|
||||
|
||||
.result-main {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.result-label {
|
||||
font-size: 0.875rem;
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.result-value {
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
color: #111827;
|
||||
}
|
||||
|
||||
.result-sub {
|
||||
margin-top: 0.5rem;
|
||||
font-size: 0.875rem;
|
||||
color: #4b5563;
|
||||
}
|
||||
|
||||
.info-block {
|
||||
margin-top: 1rem;
|
||||
padding: 0.75rem 0.875rem;
|
||||
border-radius: 0.75rem;
|
||||
background: #f9fafb;
|
||||
}
|
||||
|
||||
.block-title {
|
||||
font-size: 0.875rem;
|
||||
font-weight: 600;
|
||||
color: #374151;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.info-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
font-size: 0.875rem;
|
||||
padding: 0.15rem 0;
|
||||
}
|
||||
|
||||
.info-label {
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.info-value {
|
||||
color: #111827;
|
||||
margin-left: 1rem;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.empty-tip {
|
||||
color: #9ca3af;
|
||||
font-size: 0.875rem;
|
||||
padding: 1rem 0;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
151
src/ui/CYYSYS9W1.vue
Normal file
151
src/ui/CYYSYS9W1.vue
Normal file
@@ -0,0 +1,151 @@
|
||||
<template>
|
||||
<div class="card">
|
||||
<div class="header-box">
|
||||
<h3 class="header-title">手机携号转网</h3>
|
||||
<p class="header-desc">查询号码是否发生携号转网及前后运营商</p>
|
||||
</div>
|
||||
|
||||
<div v-if="hasData" class="result-section">
|
||||
<div class="result-main">
|
||||
<div class="result-label">携号转网情况</div>
|
||||
<div class="result-value">
|
||||
{{ portabilityText }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="result-sub">
|
||||
<span>原运营商:{{ ispType || '-' }}</span>
|
||||
<span class="ml-2">当前运营商:{{ newIspType || '-' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="hasParams" class="info-block">
|
||||
<div class="block-title">被核验号码</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">提交手机号</span>
|
||||
<span class="info-value font-mono">{{ params.mobile || params.mobile_no || '-' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="!hasData" class="empty-tip">暂无核验结果</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed } from 'vue';
|
||||
|
||||
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 hasData = computed(() => props.data && Object.keys(props.data).length > 0);
|
||||
|
||||
const ispType = computed(() => props.data?.ispType || '');
|
||||
const newIspType = computed(() => props.data?.newIspType || '');
|
||||
|
||||
const portabilityText = computed(() => {
|
||||
if (!ispType.value && !newIspType.value) return '暂无结果';
|
||||
if (ispType.value && newIspType.value && ispType.value !== newIspType.value) {
|
||||
return `已发生携号转网(从 ${ispType.value} 转到 ${newIspType.value})`;
|
||||
}
|
||||
return '未发现运营商变更';
|
||||
});
|
||||
|
||||
const hasParams = computed(() => {
|
||||
const p = props.params || {};
|
||||
return p.mobile || p.mobile_no;
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.card {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.header-box {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.header-title {
|
||||
font-size: 1.125rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.header-desc {
|
||||
font-size: 0.875rem;
|
||||
color: #6b7280;
|
||||
margin-top: 0.25rem;
|
||||
}
|
||||
|
||||
.result-section {
|
||||
padding: 0.75rem 0.875rem;
|
||||
border-radius: 0.75rem;
|
||||
margin-bottom: 1rem;
|
||||
border: 1px solid #e5e7eb;
|
||||
background: #f9fafb;
|
||||
}
|
||||
|
||||
.result-main {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.result-label {
|
||||
font-size: 0.875rem;
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.result-value {
|
||||
font-size: 0.95rem;
|
||||
font-weight: 600;
|
||||
color: #111827;
|
||||
}
|
||||
|
||||
.result-sub {
|
||||
margin-top: 0.5rem;
|
||||
font-size: 0.875rem;
|
||||
color: #4b5563;
|
||||
}
|
||||
|
||||
.info-block {
|
||||
margin-top: 1rem;
|
||||
padding: 0.75rem 0.875rem;
|
||||
border-radius: 0.75rem;
|
||||
background: #f9fafb;
|
||||
}
|
||||
|
||||
.block-title {
|
||||
font-size: 0.875rem;
|
||||
font-weight: 600;
|
||||
color: #374151;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.info-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
font-size: 0.875rem;
|
||||
padding: 0.15rem 0;
|
||||
}
|
||||
|
||||
.info-label {
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.info-value {
|
||||
color: #111827;
|
||||
margin-left: 1rem;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.empty-tip {
|
||||
color: #9ca3af;
|
||||
font-size: 0.875rem;
|
||||
padding: 1rem 0;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
Binary file not shown.
@@ -1,811 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue';
|
||||
|
||||
// 接收父组件传入的 props
|
||||
const props = defineProps({
|
||||
data: Object,
|
||||
params: Object,
|
||||
});
|
||||
|
||||
// 当前激活的标签页
|
||||
const activeTab = ref('summary');
|
||||
|
||||
// 定义组件名称,用于在控制台输出调试信息
|
||||
const componentName = 'IVYZ0S0D';
|
||||
|
||||
// 将 props.data 赋值给 reportData 变量
|
||||
let reportData: any = props.data || {};
|
||||
|
||||
// 如果 reportData 不为空,则将其赋值给变量
|
||||
if (reportData) {
|
||||
console.log(`${componentName} 组件接收到的数据:`, reportData);
|
||||
} else {
|
||||
console.log(`${componentName} 组件未接收到数据`);
|
||||
}
|
||||
|
||||
// 获取状态描述文本
|
||||
const getStatusText = (value: number) => {
|
||||
if (value === 1) return '未命中';
|
||||
if (value === 2) return '命中';
|
||||
return '未知';
|
||||
};
|
||||
|
||||
// 获取通知函状态描述文本
|
||||
const getNoticeLetterStatusText = (value: number) => {
|
||||
if (value === 1) return '未命中';
|
||||
if (value === 2) return '命中';
|
||||
return '未知';
|
||||
};
|
||||
|
||||
// 获取通知函期间描述文本
|
||||
const getNoticeLetterPeriodText = (period: number) => {
|
||||
const periodMap: Record<number, string> = {
|
||||
0: '没有被发送通知函',
|
||||
1: '近2年内',
|
||||
2: '2-4年',
|
||||
3: '5年以上'
|
||||
};
|
||||
|
||||
return periodMap[period] || '未知期间';
|
||||
};
|
||||
|
||||
// 获取背景颜色
|
||||
const getBackgroundColor = (value: number) => {
|
||||
if (value === 1) return '#e8f5e8'; // 浅绿色
|
||||
if (value === 2) return '#ffe8e8'; // 浅红色
|
||||
return '#f5f5f5'; // 默认灰色
|
||||
};
|
||||
|
||||
// 获取边框颜色
|
||||
const getBorderColor = (value: number) => {
|
||||
if (value === 1) return '#4caf50'; // 绿色边框
|
||||
if (value === 2) return '#f44336'; // 红色边框
|
||||
return '#ccc'; // 默认灰色边框
|
||||
};
|
||||
|
||||
// 检查是否至少有一个数据类别有内容
|
||||
const hasAnyData = computed(() => {
|
||||
return Object.keys(reportData).length > 0;
|
||||
});
|
||||
|
||||
// 汇总数据 - 只包含基础字段,不包含时间相关字段
|
||||
const summaryData = computed(() => {
|
||||
const risks: {title: string, value: number, details: string | string[], bgColor: string, borderColor: string, fieldName: string}[] = [];
|
||||
|
||||
// 该人员是否有风险
|
||||
if (reportData.risk_flag !== undefined) {
|
||||
risks.push({
|
||||
title: '该人员是否有风险',
|
||||
value: reportData.risk_flag,
|
||||
details: getStatusText(reportData.risk_flag),
|
||||
bgColor: getBackgroundColor(reportData.risk_flag),
|
||||
borderColor: getBorderColor(reportData.risk_flag),
|
||||
fieldName: 'basic_info'
|
||||
});
|
||||
}
|
||||
|
||||
// 失信/限高风险
|
||||
if (reportData.dishonesty && reportData.dishonesty.dishonesty !== undefined) {
|
||||
risks.push({
|
||||
title: '失信人员风险',
|
||||
value: reportData.dishonesty.dishonesty,
|
||||
details: getStatusText(reportData.dishonesty.dishonesty),
|
||||
bgColor: getBackgroundColor(reportData.dishonesty.dishonesty),
|
||||
borderColor: getBorderColor(reportData.dishonesty.dishonesty),
|
||||
fieldName: 'dishonesty'
|
||||
});
|
||||
}
|
||||
|
||||
// high_consumption
|
||||
if (reportData.high_consumption && reportData.high_consumption.high_consumption !== undefined) {
|
||||
risks.push({
|
||||
title: '限制高消费人员风险',
|
||||
value: reportData.high_consumption.high_consumption,
|
||||
details: getStatusText(reportData.high_consumption.high_consumption),
|
||||
bgColor: getBackgroundColor(reportData.high_consumption.high_consumption),
|
||||
borderColor: getBorderColor(reportData.high_consumption.high_consumption),
|
||||
fieldName: 'high_consumption'
|
||||
});
|
||||
}
|
||||
|
||||
// 是否包含劳动争议 - 只显示基本的劳动争议状态,不显示时间相关
|
||||
if (reportData.labor_disputes) {
|
||||
let details: string[] = [];
|
||||
if (reportData.labor_disputes.labor_disputes !== undefined) {
|
||||
details.push(`劳动争议: ${getStatusText(reportData.labor_disputes.labor_disputes)}`);
|
||||
}
|
||||
if (reportData.labor_disputes.labor_contract !== undefined) {
|
||||
details.push(`劳动合同: ${getStatusText(reportData.labor_disputes.labor_contract)}`);
|
||||
}
|
||||
if (reportData.labor_disputes.labor_relation !== undefined) {
|
||||
details.push(`劳动关系: ${getStatusText(reportData.labor_disputes.labor_relation)}`);
|
||||
}
|
||||
|
||||
if (details.length > 0) {
|
||||
risks.push({
|
||||
title: '是否包含劳动争议',
|
||||
value: Math.max(
|
||||
reportData.labor_disputes.labor_disputes || 0,
|
||||
reportData.labor_disputes.labor_contract || 0,
|
||||
reportData.labor_disputes.labor_relation || 0
|
||||
),
|
||||
details: details,
|
||||
bgColor: getBackgroundColor(Math.max(
|
||||
reportData.labor_disputes.labor_disputes || 0,
|
||||
reportData.labor_disputes.labor_contract || 0,
|
||||
reportData.labor_disputes.labor_relation || 0
|
||||
)),
|
||||
borderColor: getBorderColor(Math.max(
|
||||
reportData.labor_disputes.labor_disputes || 0,
|
||||
reportData.labor_disputes.labor_contract || 0,
|
||||
reportData.labor_disputes.labor_relation || 0
|
||||
)),
|
||||
fieldName: 'labor_disputes'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// social_insurance - 只显示基本的社保相关状态,不显示时间相关
|
||||
if (reportData.social_insurance) {
|
||||
let details: string[] = [];
|
||||
if (reportData.social_insurance.social_insurance !== undefined) {
|
||||
details.push(`社保纠纷: ${getStatusText(reportData.social_insurance.social_insurance)}`);
|
||||
}
|
||||
if (reportData.social_insurance.pension !== undefined) {
|
||||
details.push(`养老纠纷: ${getStatusText(reportData.social_insurance.pension)}`);
|
||||
}
|
||||
if (reportData.social_insurance.injury_insurance !== undefined) {
|
||||
details.push(`工伤纠纷: ${getStatusText(reportData.social_insurance.injury_insurance)}`);
|
||||
}
|
||||
if (reportData.social_insurance.unemployment_insurance !== undefined) {
|
||||
details.push(`失业纠纷: ${getStatusText(reportData.social_insurance.unemployment_insurance)}`);
|
||||
}
|
||||
if (reportData.social_insurance.medical_insurance !== undefined) {
|
||||
details.push(`医疗纠纷: ${getStatusText(reportData.social_insurance.medical_insurance)}`);
|
||||
}
|
||||
if (reportData.social_insurance.maternity_insurance !== undefined) {
|
||||
details.push(`生育纠纷: ${getStatusText(reportData.social_insurance.maternity_insurance)}`);
|
||||
}
|
||||
|
||||
if (details.length > 0) {
|
||||
risks.push({
|
||||
title: '社会保险纠纷风险',
|
||||
value: Math.max(
|
||||
reportData.social_insurance.social_insurance || 0,
|
||||
reportData.social_insurance.pension || 0,
|
||||
reportData.social_insurance.injury_insurance || 0,
|
||||
reportData.social_insurance.unemployment_insurance || 0,
|
||||
reportData.social_insurance.medical_insurance || 0,
|
||||
reportData.social_insurance.maternity_insurance || 0
|
||||
),
|
||||
details: details,
|
||||
bgColor: getBackgroundColor(Math.max(
|
||||
reportData.social_insurance.social_insurance || 0,
|
||||
reportData.social_insurance.pension || 0,
|
||||
reportData.social_insurance.injury_insurance || 0,
|
||||
reportData.social_insurance.unemployment_insurance || 0,
|
||||
reportData.social_insurance.medical_insurance || 0,
|
||||
reportData.social_insurance.maternity_insurance || 0
|
||||
)),
|
||||
borderColor: getBorderColor(Math.max(
|
||||
reportData.social_insurance.social_insurance || 0,
|
||||
reportData.social_insurance.pension || 0,
|
||||
reportData.social_insurance.injury_insurance || 0,
|
||||
reportData.social_insurance.unemployment_insurance || 0,
|
||||
reportData.social_insurance.medical_insurance || 0,
|
||||
reportData.social_insurance.maternity_insurance || 0
|
||||
)),
|
||||
fieldName: 'social_insurance'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// welfare_disputes
|
||||
if (reportData.welfare_disputes && reportData.welfare_disputes.welfare !== undefined) {
|
||||
risks.push({
|
||||
title: '福利待遇纠纷',
|
||||
value: reportData.welfare_disputes.welfare,
|
||||
details: getStatusText(reportData.welfare_disputes.welfare),
|
||||
bgColor: getBackgroundColor(reportData.welfare_disputes.welfare),
|
||||
borderColor: getBorderColor(reportData.welfare_disputes.welfare),
|
||||
fieldName: 'welfare_disputes'
|
||||
});
|
||||
}
|
||||
|
||||
// 人事争议 - 只显示基本的人事争议状态,不显示时间相关
|
||||
if (reportData.personnel_disputes) {
|
||||
let details: string[] = [];
|
||||
if (reportData.personnel_disputes.personnel_dispute !== undefined) {
|
||||
details.push(`人事争议: ${getStatusText(reportData.personnel_disputes.personnel_dispute)}`);
|
||||
}
|
||||
if (reportData.personnel_disputes.resignation_dispute !== undefined) {
|
||||
details.push(`辞职争议: ${getStatusText(reportData.personnel_disputes.resignation_dispute)}`);
|
||||
}
|
||||
if (reportData.personnel_disputes.dismissal_dispute !== undefined) {
|
||||
details.push(`辞退争议: ${getStatusText(reportData.personnel_disputes.dismissal_dispute)}`);
|
||||
}
|
||||
|
||||
if (details.length > 0) {
|
||||
risks.push({
|
||||
title: '人事争议',
|
||||
value: Math.max(
|
||||
reportData.personnel_disputes.personnel_dispute || 0,
|
||||
reportData.personnel_disputes.resignation_dispute || 0,
|
||||
reportData.personnel_disputes.dismissal_dispute || 0
|
||||
),
|
||||
details: details,
|
||||
bgColor: getBackgroundColor(Math.max(
|
||||
reportData.personnel_disputes.personnel_dispute || 0,
|
||||
reportData.personnel_disputes.resignation_dispute || 0,
|
||||
reportData.personnel_disputes.dismissal_dispute || 0
|
||||
)),
|
||||
borderColor: getBorderColor(Math.max(
|
||||
reportData.personnel_disputes.personnel_dispute || 0,
|
||||
reportData.personnel_disputes.resignation_dispute || 0,
|
||||
reportData.personnel_disputes.dismissal_dispute || 0
|
||||
)),
|
||||
fieldName: 'personnel_disputes'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 仲裁流程相关 - 只显示基本的仲裁状态,不显示时间相关
|
||||
if (reportData.arbitration) {
|
||||
let details: string[] = [];
|
||||
if (reportData.arbitration.arbitration_confirmation !== undefined) {
|
||||
details.push(`仲裁确认: ${getStatusText(reportData.arbitration.arbitration_confirmation)}`);
|
||||
}
|
||||
if (reportData.arbitration.arbitration_revocation !== undefined) {
|
||||
details.push(`仲裁撤销: ${getStatusText(reportData.arbitration.arbitration_revocation)}`);
|
||||
}
|
||||
|
||||
if (details.length > 0) {
|
||||
risks.push({
|
||||
title: '仲裁流程相关',
|
||||
value: Math.max(
|
||||
reportData.arbitration.arbitration_confirmation || 0,
|
||||
reportData.arbitration.arbitration_revocation || 0
|
||||
),
|
||||
details: details,
|
||||
bgColor: getBackgroundColor(Math.max(
|
||||
reportData.arbitration.arbitration_confirmation || 0,
|
||||
reportData.arbitration.arbitration_revocation || 0
|
||||
)),
|
||||
borderColor: getBorderColor(Math.max(
|
||||
reportData.arbitration.arbitration_confirmation || 0,
|
||||
reportData.arbitration.arbitration_revocation || 0
|
||||
)),
|
||||
fieldName: 'arbitration'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 通知函触达
|
||||
if (reportData.notice_letter && reportData.notice_letter.notice_letter !== undefined) {
|
||||
let statusText = getNoticeLetterStatusText(reportData.notice_letter.notice_letter);
|
||||
let periodText = '';
|
||||
|
||||
if (reportData.notice_letter.notice_letter_period !== undefined) {
|
||||
periodText = `该人员被发送通知函,仲裁、调解、涉诉距今时间: ${getNoticeLetterPeriodText(reportData.notice_letter.notice_letter_period)}`;
|
||||
}
|
||||
|
||||
const detailParts = [`状态: ${statusText}`];
|
||||
if (periodText) {
|
||||
detailParts.push(periodText);
|
||||
}
|
||||
|
||||
risks.push({
|
||||
title: '该人员是否有被发送仲裁、调解、涉诉通知函风险',
|
||||
value: reportData.notice_letter.notice_letter,
|
||||
details: detailParts,
|
||||
bgColor: getBackgroundColor(reportData.notice_letter.notice_letter),
|
||||
borderColor: getBorderColor(reportData.notice_letter.notice_letter),
|
||||
fieldName: 'notice_letter'
|
||||
});
|
||||
}
|
||||
|
||||
return risks;
|
||||
});
|
||||
|
||||
// 近三年案件数据 - 只显示命中的时间相关字段
|
||||
const recentThreeYearsData = computed(() => {
|
||||
const risks: {title: string, value: number, details: string | string[], bgColor: string, borderColor: string, fieldName: string}[] = [];
|
||||
|
||||
// 劳动争议风险 - 近3年,只显示命中的
|
||||
if (reportData.labor_disputes) {
|
||||
let details: string[] = [];
|
||||
if (reportData.labor_disputes.labor_disputes_3y === 2) { // 只显示命中的
|
||||
details.push(`近3年劳动争议: ${getStatusText(reportData.labor_disputes.labor_disputes_3y)}`);
|
||||
}
|
||||
if (reportData.labor_disputes.labor_relation_3y === 2) { // 只显示命中的
|
||||
details.push(`近3年劳动关系: ${getStatusText(reportData.labor_disputes.labor_relation_3y)}`);
|
||||
}
|
||||
|
||||
if (details.length > 0) {
|
||||
risks.push({
|
||||
title: '劳动争议风险',
|
||||
value: 2, // 因为至少有一个命中,所以值设为2
|
||||
details: details,
|
||||
bgColor: getBackgroundColor(2),
|
||||
borderColor: getBorderColor(2),
|
||||
fieldName: 'labor_disputes_3y'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 社会保险纠纷风险 - 近3年,只显示命中的
|
||||
if (reportData.social_insurance) {
|
||||
let details: string[] = [];
|
||||
if (reportData.social_insurance.pension_3y === 2) { // 只显示命中的
|
||||
details.push(`近3年养老: ${getStatusText(reportData.social_insurance.pension_3y)}`);
|
||||
}
|
||||
if (reportData.social_insurance.injury_insurance_3y === 2) { // 只显示命中的
|
||||
details.push(`近3年工伤: ${getStatusText(reportData.social_insurance.injury_insurance_3y)}`);
|
||||
}
|
||||
if (reportData.social_insurance.unemployment_insurance_3y === 2) { // 只显示命中的
|
||||
details.push(`近3年失业: ${getStatusText(reportData.social_insurance.unemployment_insurance_3y)}`);
|
||||
}
|
||||
if (reportData.social_insurance.medical_insurance_3y === 2) { // 只显示命中的
|
||||
details.push(`近3年医疗: ${getStatusText(reportData.social_insurance.medical_insurance_3y)}`);
|
||||
}
|
||||
if (reportData.social_insurance.maternity_insurance_3y === 2) { // 只显示命中的
|
||||
details.push(`近3年生育: ${getStatusText(reportData.social_insurance.maternity_insurance_3y)}`);
|
||||
}
|
||||
|
||||
if (details.length > 0) {
|
||||
risks.push({
|
||||
title: '社会保险纠纷风险',
|
||||
value: 2, // 因为至少有一个命中,所以值设为2
|
||||
details: details,
|
||||
bgColor: getBackgroundColor(2),
|
||||
borderColor: getBorderColor(2),
|
||||
fieldName: 'social_insurance_3y'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 人事争议类纠纷 - 近3年,只显示命中的
|
||||
if (reportData.personnel_disputes) {
|
||||
let details: string[] = [];
|
||||
if (reportData.personnel_disputes.resignation_dispute_3y === 2) { // 只显示命中的
|
||||
details.push(`近3年辞职: ${getStatusText(reportData.personnel_disputes.resignation_dispute_3y)}`);
|
||||
}
|
||||
if (reportData.personnel_disputes.dismissal_dispute_3y === 2) { // 只显示命中的
|
||||
details.push(`近3年辞退: ${getStatusText(reportData.personnel_disputes.dismissal_dispute_3y)}`);
|
||||
}
|
||||
|
||||
if (details.length > 0) {
|
||||
risks.push({
|
||||
title: '人事争议类纠纷',
|
||||
value: 2, // 因为至少有一个命中,所以值设为2
|
||||
details: details,
|
||||
bgColor: getBackgroundColor(2),
|
||||
borderColor: getBorderColor(2),
|
||||
fieldName: 'personnel_disputes_3y'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 仲裁相关案件 - 近3年,只显示命中的
|
||||
if (reportData.arbitration) {
|
||||
let details: string[] = [];
|
||||
if (reportData.arbitration.arbitration_confirmation_3y === 2) { // 只显示命中的
|
||||
details.push(`近3年仲裁确认: ${getStatusText(reportData.arbitration.arbitration_confirmation_3y)}`);
|
||||
}
|
||||
if (reportData.arbitration.arbitration_revocation_3y === 2) { // 只显示命中的
|
||||
details.push(`近3年仲裁撤销: ${getStatusText(reportData.arbitration.arbitration_revocation_3y)}`);
|
||||
}
|
||||
|
||||
if (details.length > 0) {
|
||||
risks.push({
|
||||
title: '仲裁相关案件',
|
||||
value: 2, // 因为至少有一个命中,所以值设为2
|
||||
details: details,
|
||||
bgColor: getBackgroundColor(2),
|
||||
borderColor: getBorderColor(2),
|
||||
fieldName: 'arbitration_3y'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return risks;
|
||||
});
|
||||
|
||||
// 近五年案件数据 - 只显示命中的时间相关字段
|
||||
const recentFiveYearsData = computed(() => {
|
||||
const risks: {title: string, value: number, details: string | string[], bgColor: string, borderColor: string, fieldName: string}[] = [];
|
||||
|
||||
// 劳动争议风险 - 近5年,只显示命中的
|
||||
if (reportData.labor_disputes) {
|
||||
let details: string[] = [];
|
||||
if (reportData.labor_disputes.labor_disputes_5y === 2) { // 只显示命中的
|
||||
details.push(`近5年劳动争议: ${getStatusText(reportData.labor_disputes.labor_disputes_5y)}`);
|
||||
}
|
||||
if (reportData.labor_disputes.labor_relation_5y === 2) { // 只显示命中的
|
||||
details.push(`近5年劳动关系: ${getStatusText(reportData.labor_disputes.labor_relation_5y)}`);
|
||||
}
|
||||
|
||||
if (details.length > 0) {
|
||||
risks.push({
|
||||
title: '劳动争议风险',
|
||||
value: 2, // 因为至少有一个命中,所以值设为2
|
||||
details: details,
|
||||
bgColor: getBackgroundColor(2),
|
||||
borderColor: getBorderColor(2),
|
||||
fieldName: 'labor_disputes_5y'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 社会保险纠纷风险 - 近5年,只显示命中的
|
||||
if (reportData.social_insurance) {
|
||||
let details: string[] = [];
|
||||
if (reportData.social_insurance.pension_5y === 2) { // 只显示命中的
|
||||
details.push(`近5年养老: ${getStatusText(reportData.social_insurance.pension_5y)}`);
|
||||
}
|
||||
if (reportData.social_insurance.injury_insurance_5y === 2) { // 只显示命中的
|
||||
details.push(`近5年工伤: ${getStatusText(reportData.social_insurance.injury_insurance_5y)}`);
|
||||
}
|
||||
if (reportData.social_insurance.unemployment_insurance_5y === 2) { // 只显示命中的
|
||||
details.push(`近5年失业: ${getStatusText(reportData.social_insurance.unemployment_insurance_5y)}`);
|
||||
}
|
||||
if (reportData.social_insurance.medical_insurance_5y === 2) { // 只显示命中的
|
||||
details.push(`近5年医疗: ${getStatusText(reportData.social_insurance.medical_insurance_5y)}`);
|
||||
}
|
||||
if (reportData.social_insurance.maternity_insurance_5y === 2) { // 只显示命中的
|
||||
details.push(`近5年生育: ${getStatusText(reportData.social_insurance.maternity_insurance_5y)}`);
|
||||
}
|
||||
|
||||
if (details.length > 0) {
|
||||
risks.push({
|
||||
title: '社会保险纠纷风险',
|
||||
value: 2, // 因为至少有一个命中,所以值设为2
|
||||
details: details,
|
||||
bgColor: getBackgroundColor(2),
|
||||
borderColor: getBorderColor(2),
|
||||
fieldName: 'social_insurance_5y'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 人事争议类纠纷 - 近5年,只显示命中的
|
||||
if (reportData.personnel_disputes) {
|
||||
let details: string[] = [];
|
||||
if (reportData.personnel_disputes.resignation_dispute_5y === 2) { // 只显示命中的
|
||||
details.push(`近5年辞职: ${getStatusText(reportData.personnel_disputes.resignation_dispute_5y)}`);
|
||||
}
|
||||
if (reportData.personnel_disputes.dismissal_dispute_5y === 2) { // 只显示命中的
|
||||
details.push(`近5年辞退: ${getStatusText(reportData.personnel_disputes.dismissal_dispute_5y)}`);
|
||||
}
|
||||
|
||||
if (details.length > 0) {
|
||||
risks.push({
|
||||
title: '人事争议类纠纷',
|
||||
value: 2, // 因为至少有一个命中,所以值设为2
|
||||
details: details,
|
||||
bgColor: getBackgroundColor(2),
|
||||
borderColor: getBorderColor(2),
|
||||
fieldName: 'personnel_disputes_5y'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 仲裁相关案件 - 近5年,只显示命中的
|
||||
if (reportData.arbitration) {
|
||||
let details: string[] = [];
|
||||
if (reportData.arbitration.arbitration_confirmation_5y === 2) { // 只显示命中的
|
||||
details.push(`近5年仲裁确认: ${getStatusText(reportData.arbitration.arbitration_confirmation_5y)}`);
|
||||
}
|
||||
if (reportData.arbitration.arbitration_revocation_5y === 2) { // 只显示命中的
|
||||
details.push(`近5年仲裁撤销: ${getStatusText(reportData.arbitration.arbitration_revocation_5y)}`);
|
||||
}
|
||||
|
||||
if (details.length > 0) {
|
||||
risks.push({
|
||||
title: '仲裁相关案件',
|
||||
value: 2, // 因为至少有一个命中,所以值设为2
|
||||
details: details,
|
||||
bgColor: getBackgroundColor(2),
|
||||
borderColor: getBorderColor(2),
|
||||
fieldName: 'arbitration_5y'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return risks;
|
||||
});
|
||||
|
||||
// 切换标签页
|
||||
const switchTab = (tabName: string) => {
|
||||
activeTab.value = tabName;
|
||||
};
|
||||
|
||||
// 获取当前显示的数据
|
||||
const currentData = computed(() => {
|
||||
switch (activeTab.value) {
|
||||
case 'summary':
|
||||
return summaryData.value;
|
||||
case 'threeYears':
|
||||
return recentThreeYearsData.value;
|
||||
case 'fiveYears':
|
||||
return recentFiveYearsData.value;
|
||||
default:
|
||||
return summaryData.value;
|
||||
}
|
||||
});
|
||||
|
||||
// 获取当前标签页标题
|
||||
const currentTabTitle = computed(() => {
|
||||
switch (activeTab.value) {
|
||||
case 'summary':
|
||||
return '汇总';
|
||||
case 'threeYears':
|
||||
return '近三年案件';
|
||||
case 'fiveYears':
|
||||
return '近五年案件';
|
||||
default:
|
||||
return '汇总';
|
||||
}
|
||||
});
|
||||
|
||||
// 获取当前标签页副标题
|
||||
const currentTabSubtitle = computed(() => {
|
||||
switch (activeTab.value) {
|
||||
case 'summary':
|
||||
return '基础风险概览';
|
||||
case 'threeYears':
|
||||
return '近期风险动态';
|
||||
case 'fiveYears':
|
||||
return '长期风险趋势';
|
||||
default:
|
||||
return '基础风险概览';
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="ivyz0s0d-container">
|
||||
<div class="card">
|
||||
<!-- 标签页导航 -->
|
||||
<div class="tabs-nav">
|
||||
<button
|
||||
class="tab-button"
|
||||
:class="{ 'active': activeTab === 'summary' }"
|
||||
@click="switchTab('summary')"
|
||||
>
|
||||
汇总
|
||||
</button>
|
||||
<button
|
||||
class="tab-button"
|
||||
:class="{ 'active': activeTab === 'threeYears' }"
|
||||
@click="switchTab('threeYears')"
|
||||
>
|
||||
近三年案件
|
||||
</button>
|
||||
<button
|
||||
class="tab-button"
|
||||
:class="{ 'active': activeTab === 'fiveYears' }"
|
||||
@click="switchTab('fiveYears')"
|
||||
>
|
||||
近五年案件
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- 标签页内容 -->
|
||||
<div class="tab-content">
|
||||
<div class="tab-header">
|
||||
<h3 class="tab-title">{{ currentTabTitle }}</h3>
|
||||
<div class="tab-subtitle">{{ currentTabSubtitle }}</div>
|
||||
</div>
|
||||
|
||||
<div v-if="hasAnyData" class="risk-cards-container">
|
||||
<div
|
||||
v-for="(risk, index) in currentData"
|
||||
:key="index"
|
||||
class="risk-card"
|
||||
:class="{ 'hit-risk': risk.value === 2 }"
|
||||
:style="{ backgroundColor: risk.bgColor, borderLeft: `4px solid ${risk.borderColor}` }"
|
||||
>
|
||||
<div class="risk-card__content">
|
||||
<h4 class="risk-card__title">{{ risk.title }}</h4>
|
||||
<div class="risk-card__status">
|
||||
<!-- 当 details 是字符串时显示单行 -->
|
||||
<p v-if="typeof risk.details === 'string'" class="risk-detail-item">{{ risk.details }}</p>
|
||||
<!-- 当 details 是数组时,每项占一行 -->
|
||||
<p
|
||||
v-else
|
||||
v-for="(detail, idx) in risk.details"
|
||||
:key="idx"
|
||||
class="risk-detail-item"
|
||||
>{{ detail }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 如果当前标签页没有数据,显示提示 -->
|
||||
<div v-if="currentData.length === 0" class="no-data-tab">
|
||||
<p>暂无{{ currentTabTitle }}数据</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 无数据提示 -->
|
||||
<div v-if="!hasAnyData" class="no-data">
|
||||
<p>暂无相关风险数据</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.ivyz0s0d-container {
|
||||
padding: 20px;
|
||||
font-family: Arial, sans-serif;
|
||||
background-color: #f8fafc;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.card {
|
||||
background-color: white;
|
||||
border-radius: 0.75rem;
|
||||
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
|
||||
padding: 1.25rem;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.tabs-nav {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
border-bottom: 2px solid #e5e7eb;
|
||||
margin-bottom: 1.25rem;
|
||||
}
|
||||
|
||||
.tab-button {
|
||||
flex: 1;
|
||||
padding: 0.75rem 1rem;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
font-weight: 500;
|
||||
font-size: 0.875rem;
|
||||
color: #6b7280;
|
||||
background: transparent;
|
||||
border: none;
|
||||
border-bottom: 2px solid transparent;
|
||||
transition: all 0.3s ease;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.tab-button:hover {
|
||||
color: #4b5563;
|
||||
background-color: #f9fafb;
|
||||
}
|
||||
|
||||
.tab-button.active {
|
||||
color: #3b82f6;
|
||||
border-bottom-color: #3b82f6;
|
||||
background-color: #eff6ff;
|
||||
}
|
||||
|
||||
.tab-content {
|
||||
min-height: 300px;
|
||||
}
|
||||
|
||||
.tab-header {
|
||||
margin-bottom: 1rem;
|
||||
padding-bottom: 0.75rem;
|
||||
border-bottom: 1px solid #e5e7eb;
|
||||
}
|
||||
|
||||
.tab-title {
|
||||
margin: 0 0 0.25rem 0;
|
||||
font-size: 1.25rem;
|
||||
font-weight: bold;
|
||||
color: #1f2937;
|
||||
}
|
||||
|
||||
.tab-subtitle {
|
||||
margin: 0;
|
||||
font-size: 0.875rem;
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.risk-cards-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.risk-card {
|
||||
border: 1px solid #e5e7eb;
|
||||
border-radius: 0.5rem;
|
||||
padding: 1rem;
|
||||
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
|
||||
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
.risk-card.hit-risk {
|
||||
border-left: 4px solid #f44336 !important; /* 命中风险的红色边框 */
|
||||
background-color: #ffe8e8 !important; /* 命中风险的浅红色背景 */
|
||||
}
|
||||
|
||||
.risk-card:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 8px rgba(0,0,0,0.15);
|
||||
}
|
||||
|
||||
.risk-card__content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.risk-card__title {
|
||||
margin: 0 0 0.5rem 0;
|
||||
font-size: 1rem;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.risk-card__status {
|
||||
margin: 0;
|
||||
font-size: 0.875rem;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.risk-detail-item {
|
||||
margin: 0 0 0.25rem 0;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.risk-detail-item:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.no-data {
|
||||
text-align: center;
|
||||
color: #999;
|
||||
font-style: italic;
|
||||
padding: 2.5rem 1.25rem;
|
||||
background-color: white;
|
||||
border-radius: 0.75rem;
|
||||
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.no-data-tab {
|
||||
text-align: center;
|
||||
color: #9ca3af;
|
||||
font-style: italic;
|
||||
padding: 1.5rem 1rem;
|
||||
background-color: #f9fafb;
|
||||
border-radius: 0.5rem;
|
||||
border: 1px dashed #e5e7eb;
|
||||
}
|
||||
|
||||
/* 响应式设计 */
|
||||
@media (max-width: 768px) {
|
||||
.ivyz0s0d-container {
|
||||
padding: 0.75rem;
|
||||
}
|
||||
|
||||
.card {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.tab-button {
|
||||
font-size: 0.8rem;
|
||||
padding: 0.5rem;
|
||||
}
|
||||
|
||||
.tab-title {
|
||||
font-size: 1.125rem;
|
||||
}
|
||||
|
||||
.risk-card__title {
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
.risk-card__status {
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,5 +1,11 @@
|
||||
<template>
|
||||
<div class="flex flex-col gap-4">
|
||||
|
||||
<!-- <div>
|
||||
<h1><div>{{ riskScore }}</div>
|
||||
<div>{{ variableValue }}</div></h1>
|
||||
</div> -->
|
||||
|
||||
<!-- 统计概览小模块(内部自带维度切换按钮) -->
|
||||
<SummaryApplyStats v-model:dimension="activeDimension" :data="variableValue" />
|
||||
|
||||
@@ -75,9 +81,9 @@ const riskScore = computed(() => {
|
||||
const d7Total =
|
||||
parseInt(data[`als_d7_${dimKey}_bank_allnum`] || 0) +
|
||||
parseInt(data[`als_d7_${dimKey}_nbank_allnum`] || 0)
|
||||
if (d7Total > 5) {
|
||||
if (d7Total > 3) {
|
||||
score -= 20 // 近7天申请过多
|
||||
} else if (d7Total > 3) {
|
||||
} else if (d7Total > 0) {
|
||||
score -= 10
|
||||
}
|
||||
|
||||
@@ -85,7 +91,7 @@ const riskScore = computed(() => {
|
||||
const d15Total =
|
||||
parseInt(data[`als_d15_${dimKey}_bank_allnum`] || 0) +
|
||||
parseInt(data[`als_d15_${dimKey}_nbank_allnum`] || 0)
|
||||
if (d15Total > 10) {
|
||||
if (d15Total > 8) {
|
||||
score -= 15
|
||||
} else if (d15Total > 5) {
|
||||
score -= 8
|
||||
@@ -97,7 +103,7 @@ const riskScore = computed(() => {
|
||||
parseInt(data[`als_m1_${dimKey}_nbank_allnum`] || 0)
|
||||
if (m1Total > 15) {
|
||||
score -= 20
|
||||
} else if (m1Total > 8) {
|
||||
} else if (m1Total > 10) {
|
||||
score -= 10
|
||||
}
|
||||
|
||||
@@ -107,7 +113,7 @@ const riskScore = computed(() => {
|
||||
parseInt(data[`als_m3_${dimKey}_nbank_allnum`] || 0)
|
||||
if (m3Total > 20) {
|
||||
score -= 15
|
||||
} else if (m3Total > 10) {
|
||||
} else if (m3Total > 16) {
|
||||
score -= 8
|
||||
}
|
||||
|
||||
@@ -117,7 +123,7 @@ const riskScore = computed(() => {
|
||||
parseInt(data[`als_m6_${dimKey}_nbank_allnum`] || 0)
|
||||
if (m6Total > 30) {
|
||||
score -= 10
|
||||
} else if (m6Total > 15) {
|
||||
} else if (m6Total > 21) {
|
||||
score -= 5
|
||||
}
|
||||
|
||||
@@ -125,6 +131,72 @@ const riskScore = computed(() => {
|
||||
return Math.max(10, Math.min(100, score))
|
||||
})
|
||||
|
||||
// 计算风险评分(0-100分,分数越高越安全)
|
||||
// const riskScore = computed(() => {
|
||||
// const data = variableValue.value
|
||||
// const dimKey = activeDimension.value === 'cell' ? 'cell' : 'id'
|
||||
|
||||
// if (!data || Object.keys(data).length === 0) {
|
||||
// return 100
|
||||
// }
|
||||
|
||||
// let score = 100
|
||||
|
||||
// // 近7天
|
||||
// const d7Total =
|
||||
// parseInt(data[`als_d7_${dimKey}_coon_allnum`] || 0) +
|
||||
// parseInt(data[`als_d7_${dimKey}_nbank_allnum`] || 0)
|
||||
|
||||
// // 只要大于0就扣分
|
||||
// if (d7Total > 0) {
|
||||
// score -= 10
|
||||
// }
|
||||
|
||||
// // 近15天
|
||||
// const d15Total =
|
||||
// parseInt(data[`als_d15_${dimKey}_coon_allnum`] || 0) +
|
||||
// parseInt(data[`als_d15_${dimKey}_nbank_allnum`] || 0)
|
||||
// if (d15Total > 5) {
|
||||
// score -= 15
|
||||
// } else if (d15Total > 0) {
|
||||
// score -= 8
|
||||
// }
|
||||
|
||||
// // 近1个月
|
||||
// const m1Total =
|
||||
// parseInt(data[`als_m1_${dimKey}_coon_allnum`] || 0) +
|
||||
// parseInt(data[`als_m1_${dimKey}_nbank_allnum`] || 0)
|
||||
// if (m1Total > 10) {
|
||||
// score -= 20
|
||||
// } else if (m1Total > 0) {
|
||||
// score -= 10
|
||||
// }
|
||||
|
||||
// // 近3个月
|
||||
// const m3Total =
|
||||
// parseInt(data[`als_m3_${dimKey}_coon_allnum`] || 0) +
|
||||
// parseInt(data[`als_m3_${dimKey}_nbank_allnum`] || 0)
|
||||
// if (m3Total > 16) {
|
||||
// score -= 15
|
||||
// } else if (m3Total > 0) {
|
||||
// score -= 8
|
||||
// }
|
||||
|
||||
// // 近6个月
|
||||
// const m6Total =
|
||||
// parseInt(data[`als_m6_${dimKey}_coon_allnum`] || 0) +
|
||||
// parseInt(data[`als_m6_${dimKey}_nbank_allnum`] || 0)
|
||||
// if (m6Total > 21) {
|
||||
// score -= 10
|
||||
// } else if (m6Total > 0) {
|
||||
// score -= 5
|
||||
// }
|
||||
|
||||
// return Math.max(10, Math.min(100, score))
|
||||
// })
|
||||
|
||||
|
||||
|
||||
// 使用 composable 通知父组件风险评分
|
||||
useRiskNotifier(props, riskScore)
|
||||
|
||||
|
||||
126
src/ui/QCXG5F3A.vue
Normal file
126
src/ui/QCXG5F3A.vue
Normal file
@@ -0,0 +1,126 @@
|
||||
<template>
|
||||
<div class="bg-white rounded-lg shadow-sm border border-gray-200 p-6">
|
||||
<!-- 车辆总数统计 -->
|
||||
<div class="flex justify-between items-center mb-6 pb-4 border-b border-gray-100">
|
||||
<div class="flex items-center gap-3">
|
||||
<div>
|
||||
<div class="text-lg font-semibold text-gray-900">名下车辆(车牌)</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bg-blue-50 text-blue-700 px-4 py-2 rounded-full text-sm font-medium">
|
||||
共 {{ vehicleCount }} 辆
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 车辆列表 -->
|
||||
<div class="space-y-3" v-if="vehicleList && vehicleList.length > 0">
|
||||
<div class="bg-gray-50 rounded-lg p-4 border border-gray-200 hover:bg-blue-50 hover:border-blue-200 transition-colors duration-200"
|
||||
v-for="(vehicle, index) in vehicleList" :key="index">
|
||||
<div class="space-y-3">
|
||||
<div class="text-xl font-bold text-gray-900 font-mono tracking-wider">
|
||||
{{ vehicle.plateNum }}
|
||||
</div>
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="inline-flex items-center gap-1 px-3 py-1 rounded text-xs font-medium text-white"
|
||||
:class="getPlateColorClass(vehicle.plateColor)">
|
||||
<span>🏷️</span>
|
||||
<span>{{ getPlateColorText(vehicle.plateColor) }}</span>
|
||||
</div>
|
||||
<div class="text-sm text-gray-600">
|
||||
<span class="text-gray-500">车辆类型:</span>
|
||||
<span class="font-medium text-gray-900 ml-1">{{ getVehicleTypeText(vehicle.vehicleType)
|
||||
}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 无数据状态 -->
|
||||
<div class="text-center py-12 text-gray-500" v-else>
|
||||
<div class="text-4xl mb-3">🚫</div>
|
||||
<div class="text-lg font-medium mb-1">暂无车辆信息</div>
|
||||
<div class="text-sm">No vehicle records found</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { defineProps, computed } from 'vue';
|
||||
import { useRiskNotifier } from '@/composables/useRiskNotifier';
|
||||
|
||||
const props = defineProps({
|
||||
data: Object,
|
||||
params: Object,
|
||||
apiId: { type: String, default: '' },
|
||||
index: { type: Number, default: 0 },
|
||||
notifyRiskStatus: { type: Function, default: () => { } },
|
||||
});
|
||||
|
||||
const plateColorMap = {
|
||||
0: '蓝色 - 普通燃油车',
|
||||
1: '黄色 - 大型车/货车',
|
||||
2: '黑色 - 外籍车辆/港澳台车',
|
||||
3: '白色 - 警车/军车/武警车',
|
||||
4: '渐变绿色 - 新能源汽车',
|
||||
5: '黄绿双拼色 - 大型新能源汽车',
|
||||
6: '蓝白渐变色 - 临时牌照',
|
||||
7: '临时牌照 - 临时行驶车辆',
|
||||
11: '绿色 - 新能源汽车',
|
||||
12: '红色 - 教练车/试验车'
|
||||
};
|
||||
|
||||
const vehicleTypeMap = {
|
||||
1: '一型客车',
|
||||
2: '二型客车',
|
||||
3: '三型客车',
|
||||
4: '四型客车',
|
||||
11: '一型货车',
|
||||
12: '二型货车',
|
||||
13: '三型货车',
|
||||
14: '四型货车',
|
||||
15: '五型货车',
|
||||
16: '六型货车',
|
||||
21: '一型专项作业车',
|
||||
22: '二型专项作业车',
|
||||
23: '三型专项作业车',
|
||||
24: '四型专项作业车',
|
||||
25: '五型专项作业车',
|
||||
26: '六型专项作业车'
|
||||
};
|
||||
|
||||
const vehicleList = computed(() => props.data?.list || []);
|
||||
const vehicleCount = computed(() => props.data?.vehicleCount || 0);
|
||||
|
||||
const getPlateColorText = (plateColor) => {
|
||||
return plateColorMap[plateColor] || '未知颜色 - 未知类型';
|
||||
};
|
||||
|
||||
const getPlateColorClass = (plateColor) => {
|
||||
const colorClassMap = {
|
||||
0: 'bg-blue-500',
|
||||
1: 'bg-yellow-500',
|
||||
2: 'bg-gray-800',
|
||||
3: 'bg-gray-200 text-gray-800',
|
||||
4: 'bg-green-500',
|
||||
5: 'bg-gradient-to-r from-yellow-500 to-green-500',
|
||||
6: 'bg-gradient-to-r from-blue-500 to-white text-blue-800',
|
||||
7: 'bg-red-500',
|
||||
11: 'bg-green-500',
|
||||
12: 'bg-red-500'
|
||||
};
|
||||
return colorClassMap[plateColor] || 'bg-gray-500';
|
||||
};
|
||||
|
||||
const getVehicleTypeText = (vehicleType) => {
|
||||
return vehicleTypeMap[vehicleType] || '未知类型';
|
||||
};
|
||||
|
||||
const riskScore = computed(() => 100);
|
||||
useRiskNotifier(props, riskScore);
|
||||
defineExpose({ riskScore });
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 保持与 CQCXG9P1C 一致的布局风格 */
|
||||
</style>
|
||||
Reference in New Issue
Block a user