Files
tydata-webview-v2/report-viewer/src/components/BaseReport.vue
2025-12-25 12:57:40 +08:00

632 lines
24 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<script setup>
import ShareReportButton from "./ShareReportButton.vue";
import TitleBanner from "./TitleBanner.vue";
import VerificationCard from "./VerificationCard.vue";
import StyledTabs from "./StyledTabs.vue";
import { splitDWBG8B4DForTabs } from '@/ui/CDWBG8B4D/utils/simpleSplitter.js';
import { splitDWBG6A2CForTabs } from '@/ui/DWBG6A2C/utils/simpleSplitter.js';
import { splitCQYGL3F8EForTabs } from '@/ui/CQYGL3F8E/utils/simpleSplitter.js';
const props = defineProps({
isShare: {
type: Boolean,
default: false,
},
orderId: {
type: String,
required: false,
default: "",
},
orderNo: {
type: String,
default: "",
},
feature: {
type: String,
required: true,
},
reportData: {
type: Array,
required: true,
},
reportParams: {
type: Object,
required: true,
},
reportName: {
type: String,
required: true,
},
reportDateTime: {
type: [String, null],
required: false,
default: null,
},
isEmpty: {
type: Boolean,
required: true,
},
isDone: {
type: Boolean,
required: true,
},
isExample: {
type: Boolean,
default: false,
},
});
// 使用toRefs将props转换为组件内的ref
const {
feature,
reportData,
reportParams,
reportName,
reportDateTime,
isEmpty,
isDone,
isExample,
} = toRefs(props);
const active = ref(null);
const reportScore = ref(0); // 默认分数
const trapezoidBgImage = ref(''); // 牌匾背景图片
// 动态加载牌匾背景图片
const loadTrapezoidBackground = async () => {
try {
const bgModule = await import("@/assets/images/report/title_inquire_bg.png");
trapezoidBgImage.value = bgModule.default;
} catch (error) {
console.warn(`Failed to load trapezoid background image:`, error);
}
};
// 在组件挂载时加载牌匾背景图
onMounted(async () => {
await loadTrapezoidBackground();
});
// 处理数据拆分支持DWBG8B4D、DWBG6A2C、CJRZQ5E9F和CQYGL3F8E
const processedReportData = computed(() => {
let data = reportData.value;
// 拆分DWBG8B4D数据
data = splitDWBG8B4DForTabs(data);
// 拆分DWBG6A2C数据
data = splitDWBG6A2CForTabs(data);
// 拆分CQYGL3F8E数据
data = splitCQYGL3F8EForTabs(data);
// 过滤掉在featureMap中没有对应的项
return data.filter(item => featureMap[item.data.apiID]);
});
// 牌匾背景图片样式
const trapezoidBgStyle = computed(() => {
if (trapezoidBgImage.value) {
return {
backgroundImage: `url(${trapezoidBgImage.value})`,
};
}
return {};
});
const featureMap = {
// 司南报告
DWBG6A2C: {
name: "司南报告",
component: defineAsyncComponent(() => import("@/ui/DWBG6A2C/index.vue")),
remark: '司南报告提供全面的个人信用风险评估,包括身份核验、风险名单、借贷行为、履约情况等多维度分析。'
},
// 司南报告拆分模块
DWBG6A2C_StandLiveInfo: {
name: "身份信息核验",
component: defineAsyncComponent(() => import("@/ui/DWBG6A2C/components/StandLiveInfoSection.vue")),
},
DWBG6A2C_RiskPoint: {
name: "命中风险标注",
component: defineAsyncComponent(() => import("@/ui/DWBG6A2C/components/RiskPointSection.vue")),
},
DWBG6A2C_SecurityInfo: {
name: "公安重点人员核验",
component: defineAsyncComponent(() => import("@/ui/DWBG6A2C/components/SecurityInfoSection.vue")),
},
DWBG6A2C_AntiFraudInfo: {
name: "涉赌涉诈人员核验",
component: defineAsyncComponent(() => import("@/ui/DWBG6A2C/components/AntiFraudInfoSection.vue")),
},
DWBG6A2C_RiskList: {
name: "风险名单",
component: defineAsyncComponent(() => import("@/ui/DWBG6A2C/components/RiskListSection.vue")),
},
DWBG6A2C_ApplicationStatistics: {
name: "历史借贷行为",
component: defineAsyncComponent(() => import("@/ui/DWBG6A2C/components/ApplicationStatisticsSection.vue")),
},
DWBG6A2C_LendingStatistics: {
name: "近24个月放款情况",
component: defineAsyncComponent(() => import("@/ui/DWBG6A2C/components/LendingStatisticsSection.vue")),
},
DWBG6A2C_PerformanceStatistics: {
name: "履约情况",
component: defineAsyncComponent(() => import("@/ui/DWBG6A2C/components/PerformanceStatisticsSection.vue")),
},
DWBG6A2C_OverdueRecord: {
name: "历史逾期记录",
component: defineAsyncComponent(() => import("@/ui/DWBG6A2C/components/OverdueRecordSection.vue")),
},
DWBG6A2C_CreditDetail: {
name: "授信详情",
component: defineAsyncComponent(() => import("@/ui/DWBG6A2C/components/CreditDetailSection.vue")),
},
DWBG6A2C_RentalBehavior: {
name: "租赁行为",
component: defineAsyncComponent(() => import("@/ui/DWBG6A2C/components/RentalBehaviorSection.vue")),
},
DWBG6A2C_RiskSupervision: {
name: "关联风险监督",
component: defineAsyncComponent(() => import("@/ui/DWBG6A2C/components/RiskSupervisionSection.vue")),
},
// 司法涉诉
FLXG0V4B: {
name: "司法涉诉",
component: defineAsyncComponent(() =>
import("@/ui/CFLXG0V4B/index.vue")
),
},
// 人企关系加强版
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")),
},
CQYGL3F8E_InvestHistory: {
name: "对外投资历史",
component: defineAsyncComponent(() => import("@/ui/CQYGL3F8E/components/InvestHistory.vue")),
},
CQYGL3F8E_FinancingHistory: {
name: "融资历史",
component: defineAsyncComponent(() => import("@/ui/CQYGL3F8E/components/FinancingHistory.vue")),
},
CQYGL3F8E_Punishment: {
name: "行政处罚",
component: defineAsyncComponent(() => import("@/ui/CQYGL3F8E/components/Punishment.vue")),
},
CQYGL3F8E_Abnormal: {
name: "经营异常",
component: defineAsyncComponent(() => import("@/ui/CQYGL3F8E/components/Abnormal.vue")),
},
CQYGL3F8E_TaxRisk: {
name: "税务风险",
component: defineAsyncComponent(() => import("@/ui/CQYGL3F8E/components/TaxRisk/index.vue")),
},
// 信贷表现
JRZQ4B6C: {
name: "信贷表现",
component: defineAsyncComponent(() => import("@/ui/JRZQ4B6C/index.vue")),
remark: '信贷表现主要为企业在背景调查过程中探查用户近期信贷表现时提供参考,帮助企业对其内部员工、外部业务进行个人信用过滤。数据来源于多个征信机构,可能存在数据延迟或不完整的情况。'
},
// 收入评估
JRZQ09J8: {
name: "收入评估",
component: defineAsyncComponent(() => import("@/ui/JRZQ09J8/index.vue")),
remark: '基于全国社会保险信息系统的缴费基数数据进行收入水平评估。评级反映相对收入水平,实际收入可能因地区差异而有所不同,建议结合其他收入证明材料进行综合评估。'
},
// 名下车辆
QCXG9P1C: {
name: "名下车辆",
component: defineAsyncComponent(() => import("@/ui/CQCXG9P1C.vue")),
},
// 谛听多维报告
DWBG8B4D: {
name: "谛听多维报告",
component: defineAsyncComponent(() => import("@/ui/CDWBG8B4D/index.vue")),
},
// 谛听多维报告拆分模块
DWBG8B4D_Overview: {
name: "报告概览",
component: defineAsyncComponent(() => import("@/ui/CDWBG8B4D/components/ReportOverview.vue")),
},
DWBG8B4D_ElementVerification: {
name: "要素核查",
component: defineAsyncComponent(() => import("@/ui/CDWBG8B4D/components/ElementVerification.vue")),
},
DWBG8B4D_Identity: {
name: "运营商核验",
component: defineAsyncComponent(() => import("@/ui/CDWBG8B4D/components/Identity.vue")),
},
DWBG8B4D_RiskWarning: {
name: "公安重点人员检验",
component: defineAsyncComponent(() => import("@/ui/CDWBG8B4D/components/RiskWarning.vue")),
},
DWBG8B4D_OverdueRisk: {
name: "逾期风险综述",
component: defineAsyncComponent(() => import("@/ui/CDWBG8B4D/components/OverdueRiskSection.vue")),
},
DWBG8B4D_LoanEvaluation: {
name: "借贷评估",
component: defineAsyncComponent(() => import("@/ui/CDWBG8B4D/components/LoanEvaluationSection.vue")),
},
DWBG8B4D_LeasingRisk: {
name: "租赁风险评估",
component: defineAsyncComponent(() => import("@/ui/CDWBG8B4D/components/LeasingRiskSection.vue")),
},
DWBG8B4D_RiskSupervision: {
name: "关联风险监督",
component: defineAsyncComponent(() => import("@/ui/CDWBG8B4D/components/RiskSupervisionSection.vue")),
},
DWBG8B4D_RiskWarningTab: {
name: "规则风险提示",
component: defineAsyncComponent(() => import("@/ui/CDWBG8B4D/components/RiskWarningTab.vue")),
},
};
const maskValue = computed(() => {
return (type, value) => {
if (!value) return value;
if (type === "name") {
// 姓名脱敏(保留首位)
if (value.length === 1) {
return "*"; // 只保留一个字,返回 "*"
} else if (value.length === 2) {
return value[0] + "*"; // 两个字,保留姓氏,第二个字用 "*" 替代
} else {
return (
value[0] +
"*".repeat(value.length - 2) +
value[value.length - 1]
); // 两个字以上,保留第一个和最后一个字,其余的用 "*" 替代
}
} else if (type === "id_card") {
// 身份证号脱敏保留前6位和最后4位
return value.replace(/^(.{6})(?:\d+)(.{4})$/, "$1****$2");
} else if (type === "mobile") {
if (value.length === 11) {
return value.substring(0, 3) + "****" + value.substring(7);
}
return value; // 如果手机号不合法或长度不为 11 位,直接返回原手机号
} else if (type === "bank_card") {
// 银行卡号脱敏保留前6位和后4位
return value.replace(/^(.{6})(?:\d+)(.{4})$/, "$1****$2");
} else if (type === "ent_name") {
// 企业名称脱敏保留前3个字符和后3个字符中间部分用 "*" 替代)
if (value.length <= 6) {
return value[0] + "*".repeat(value.length - 1); // 少于6个字符时只保留第一个字符其他用 * 替代
} else {
return (
value.slice(0, 3) +
"*".repeat(value.length - 6) +
value.slice(-3)
); // 多于6个字符时保留前3和后3
}
} else if (type === "ent_code") {
// 企业代码脱敏保留前4个字符和后4个字符中间部分用 "*" 替代)
if (value.length <= 8) {
return value.slice(0, 4) + "*".repeat(value.length - 4); // 长度不超过8时保留前4个字符其他用 * 替代
} else {
return (
value.slice(0, 4) +
"*".repeat(value.length - 8) +
value.slice(-4)
); // 长度超过8时保留前4个字符和后4个字符
}
} else if (type === "car_license") {
// 车牌号脱敏保留前2个字符后2个字符其他部分用 "*" 替代)
if (value.length <= 4) {
return value[0] + "*".repeat(value.length - 1); // 如果车牌号长度小于等于4只保留首字符
} else {
// 如果车牌号较长保留前2个字符后2个字符其余部分用 "*" 替代
return (
value.slice(0, 2) +
"*".repeat(value.length - 4) +
value.slice(-2)
);
}
}
return value;
};
});
// ==================== 新评分系统 ====================
// Feature 风险等级配置(权重越高表示风险越大,最终分数越高越安全)
const featureRiskLevels = {
// 🔴 高风险类
'FLXG0V4B': 20, // 司法涉诉
// 🟠 中高风险类 - 权重 7
'JRZQ4B6C': 7, // 信贷表现
// 🟡 中风险类 - 权重 5
'QYGL3F8E': 5, // 人企关系加强版
'QCXG9P1C': 5, // 名下车辆
'JRZQ09J8': 5, // 收入评估
// 📊 复合报告类 - 按子模块动态计算
'DWBG8B4D': 0, // 谛听多维报告(由子模块计算)
'DWBG6A2C': 0, // 司南报告(由子模块计算)
// 谛听多维报告子模块
'DWBG8B4D_Overview': 10,
'DWBG8B4D_ElementVerification': 4,
'DWBG8B4D_Identity': 4,
'DWBG8B4D_RiskWarning': 10,
'DWBG8B4D_OverdueRisk': 9,
'DWBG8B4D_LoanEvaluation': 7,
'DWBG8B4D_LeasingRisk': 6,
'DWBG8B4D_RiskSupervision': 8,
'DWBG8B4D_RiskWarningTab': 9,
// 司南报告子模块
'DWBG6A2C_StandLiveInfo': 4,
'DWBG6A2C_RiskPoint': 9,
'DWBG6A2C_SecurityInfo': 15,
'DWBG6A2C_AntiFraudInfo': 15,
'DWBG6A2C_RiskList': 12,
'DWBG6A2C_ApplicationStatistics': 7,
'DWBG6A2C_LendingStatistics': 6,
'DWBG6A2C_PerformanceStatistics': 7,
'DWBG6A2C_OverdueRecord': 9,
'DWBG6A2C_CreditDetail': 5,
'DWBG6A2C_RentalBehavior': 5,
'DWBG6A2C_RiskSupervision': 8,
// 人企关系加强版子模块
'CQYGL3F8E_Investment': 4,
'CQYGL3F8E_SeniorExecutive': 4,
'CQYGL3F8E_Lawsuit': 8,
'CQYGL3F8E_InvestHistory': 3,
'CQYGL3F8E_FinancingHistory': 3,
'CQYGL3F8E_Punishment': 7,
'CQYGL3F8E_Abnormal': 6,
'CQYGL3F8E_TaxRisk': 7,
};
// 存储每个组件的 ref 引用
const componentRefs = ref({});
// 存储每个组件的风险评分(由组件主动通知)
const componentRiskScores = ref({});
// 提供方法让子组件通知自己的风险评分0-100分分数越高越安全
const notifyRiskStatus = (apiID, index, riskScore) => {
const key = `${apiID}_${index}`;
componentRiskScores.value[key] = riskScore;
};
// 暴露给子组件
defineExpose({
notifyRiskStatus
});
// 计算综合评分的函数(分数越高越安全)
const calculateScore = () => {
// 收集实际存在的 features 及其风险权重
const presentFeatures = [];
processedReportData.value.forEach((item, index) => {
const apiID = item.data?.apiID;
if (!apiID) return;
// 获取风险权重(如果不在配置中,默认为 3
const weight = featureRiskLevels[apiID] ?? 3;
// 跳过权重为 0 的复合报告主模块(它们由子模块计算)
if (weight === 0) return;
presentFeatures.push({
apiID,
index,
weight
});
});
if (presentFeatures.length === 0) return 100; // 无有效特征时返回满分(最安全)
// 累计总风险分数
let totalRiskScore = 0;
const riskDetails = []; // 用于调试
presentFeatures.forEach(({ apiID, index, weight }) => {
// 从组件风险评分中获取评分0-100分分数越高越安全
const key = `${apiID}_${index}`;
const componentScore = componentRiskScores.value[key] ?? 100; // 默认100分最安全
// 将组件评分转换为风险分数0-100 -> 100-0
const componentRisk = 100 - componentScore;
// 计算该模块的风险贡献(固定分值,不按占比)
// 使用权重系数放大高风险模块的影响
// 高风险模块权重10如果风险分数是0扣20分权重10 × 系数2
// 中风险模块权重7如果风险分数是0扣14分权重7 × 系数2
// 低风险模块权重3如果风险分数是0扣6分权重3 × 系数2
const weightMultiplier = 1.5; // 权重系数,可以调整这个值来控制影响程度
const riskContribution = (componentRisk / 100) * weight * weightMultiplier;
riskDetails.push({
apiID,
index,
weight,
componentScore,
componentRisk,
riskContribution,
hasStatus: key in componentRiskScores.value
});
// 累加风险分数
totalRiskScore += riskContribution;
});
// 将总风险分数限制在 0-90 范围内确保最低分为10分
const finalRiskScore = Math.max(0, Math.min(90, Math.round(totalRiskScore)));
// 转换为安全分数分数越高越安全100 - 风险分数)
// 最终分数范围10-100分
const safetyScore = 100 - finalRiskScore;
return safetyScore;
};
// 监听 reportData 和 componentRiskScores 变化并计算评分
watch([reportData, componentRiskScores], () => {
reportScore.value = calculateScore();
// 将评分系统数据整理到一个对象中
const scoreData = {
timestamp: new Date().toISOString(),
finalScore: reportScore.value,
reportModules: processedReportData.value.map((item, index) => ({
apiID: item.data.apiID,
name: featureMap[item.data.apiID]?.name || '未知',
index: index,
riskScore: componentRiskScores.value[`${item.data.apiID}_${index}`] ?? '未上报',
weight: featureRiskLevels[item.data.apiID] ?? 0
})),
componentScores: componentRiskScores.value,
riskLevels: featureRiskLevels
};
}, { immediate: true, deep: true });
</script>
<template>
<div class="min-h-full bg-[#f6f8fe]">
<template v-if="isDone">
<!-- Tabs 区域 -->
<StyledTabs v-model:active="active" scrollspy sticky :offset-top="46">
<div class="flex flex-col gap-y-4 p-4">
<LEmpty v-if="isEmpty" />
<van-tab title="分析指数">
<div class="relative mb-4">
<!-- 产品卡片牌匾效果 - 使用背景图片 -->
<div class="absolute -top-[12px] left-1/2 transform -translate-x-1/2 w-[140px]">
<div class="trapezoid-bg-image flex items-center justify-center"
:style="trapezoidBgStyle">
<div class="text-xl whitespace-nowrap">分析指数</div>
</div>
</div>
<div class="card mb-4 mt-6 !pt-10">
<div class="my-4">
<GaugeChart :score="reportScore" />
</div>
</div>
</div>
</van-tab>
<van-tab title="基本信息">
<TitleBanner id="basic" class="mb-4">基本信息</TitleBanner>
<VerificationCard :report-params="reportParams" :report-date-time="reportDateTime"
:report-name="reportName" :is-empty="isEmpty" :is-share="isShare" :order-id="orderId"
:order-no="orderNo" />
<LRemark content="如查询的姓名/身份证与运营商提供的不一致,可能会存在报告内容不匹配的情况" />
</van-tab>
<van-tab v-for="(item, index) in processedReportData" :key="`${item.data.apiID}_${index}`"
:title="featureMap[item.data.apiID]?.name">
<TitleBanner :id="item.data.apiID" class="mb-4">
{{ featureMap[item.data.apiID]?.name }}
</TitleBanner>
<component :is="featureMap[item.data.apiID]?.component" :ref="el => {
if (el) {
const refKey = `${item.data.apiID}_${index}`;
componentRefs[refKey] = el;
}
}" :data="item.data.data" :params="reportParams" :api-id="item.data.apiID" :index="index"
:notify-risk-status="notifyRiskStatus">
</component>
<LRemark v-if="featureMap[item.data.apiID]?.remark"
:content="featureMap[item.data.apiID]?.remark" />
</van-tab>
<ShareReportButton v-if="!isShare" class="h-12 text-3xl mt-8" :order-id="orderId"
:order-no="orderNo" :is-example="isExample" />
<span class="mb-4 text-center text-sm text-gray-500">分享当前{{ isExample ? '示例' : '报告' }}链接</span>
<div class="card">
<div>
<div class="text-bold text-center text-[#333333] mb-2">
免责声明
</div>
<p class="text-[#999999]">
&nbsp; &nbsp;
1本份报告是在取得您个人授权后我们才向合法存有您以上个人信息的机构去调取相关内容我们不会以任何形式对您的报告进行存储除您和您授权的人外不会提供给任何人和机构进行查看
</p>
<p class="text-[#999999]">
&nbsp; &nbsp; 2本报告自生成之日起有效期 30
过期自动删除如果您对本份报告存有异议可能是合作机构数据有延迟或未能获取到您的相关数据出于合作平台数据隐私的保护本平台将不做任何解释
</p>
<p class="text-[#999999]">
&nbsp; &nbsp; 3若以上数据有错误请联系平台客服
</p>
</div>
</div>
</div>
</StyledTabs>
</template>
</div>
<div class="disclaimer">
<div class="flex flex-col items-center">
<div class="flex items-center">
<img class="w-4 h-4 mr-2" src="@/assets/images/public_security_record_icon.png" alt="公安备案" />
<text>琼公网安备46010002000584号</text>
</div>
<div>
<a class="text-blue-500" href="https://beian.miit.gov.cn">
琼ICP备2024048057号-2
</a>
</div>
</div>
<div>海南海宇大数据有限公司版权所有</div>
</div>
</template>
<style lang="scss" scoped>
.a {
color: #e03131;
}
.disclaimer {
/* margin-top: 24px; */
padding: 10px;
font-size: 12px;
color: #999;
text-align: center;
border-top: 1px solid #e0e0e0;
padding-bottom: 60px;
background: #ffffff;
}
:deep(.card) {
@apply p-3;
box-shadow: 0px 0px 24px 0px #3F3F3F0F;
}
/* 梯形背景图片样式 */
.trapezoid-bg-image {
background-size: contain;
background-repeat: no-repeat;
background-position: center;
height: 44px;
}
</style>