diff --git a/src/auto-imports.d.ts b/src/auto-imports.d.ts index 7faddae..d551ec3 100644 --- a/src/auto-imports.d.ts +++ b/src/auto-imports.d.ts @@ -284,6 +284,7 @@ declare global { const useUserStore: typeof import('./stores/userStore.js')['useUserStore'] const useVModel: typeof import('@vueuse/core')['useVModel'] const useVModels: typeof import('@vueuse/core')['useVModels'] + const useVehiclePayload: typeof import('./composables/useVehiclePayload.js')['useVehiclePayload'] const useVibrate: typeof import('@vueuse/core')['useVibrate'] const useVirtualList: typeof import('@vueuse/core')['useVirtualList'] const useWakeLock: typeof import('@vueuse/core')['useWakeLock'] diff --git a/src/components/BaseReport.vue b/src/components/BaseReport.vue index 1a96611..b72cb96 100644 --- a/src/components/BaseReport.vue +++ b/src/components/BaseReport.vue @@ -14,6 +14,8 @@ import { useAgentStore } from "@/stores/agentStore"; import { storeToRefs } from "pinia"; import { showFailToast } from "vant"; import { checkFeatureWhitelistStatus, offlineFeature, checkOrderAgent } from "@/api/agent"; +import { vehicleFeatureMap, vehicleFeatureRiskLevels } from "@/config/vehicleFeatureMap"; +import { normalizeVehicleReportData } from "@/utils/vehicleReportNormalize"; // 动态导入产品背景图片的函数 const loadProductBackground = async (productType) => { @@ -226,8 +228,10 @@ const processedReportData = computed(() => { // 拆分CQYGL3F8E数据 data = splitCQYGL3F8EForTabs(data); + // 车辆报告:解包嵌套 data,与微信小程序 normalizeVehicleQueryData 对齐 + data = normalizeVehicleReportData(data); // 过滤掉在featureMap中没有对应的项 - return data.filter(item => featureMap[item.data.apiID]); + return data.filter(item => featureMap[item.data?.apiID]); }); // 获取产品背景图片 @@ -553,14 +557,7 @@ const featureMap = { name: "税务风险", component: defineAsyncComponent(() => import("@/ui/CQYGL3F8E/components/TaxRisk/index.vue")), }, - QCXG7A2B: { - name: "名下车辆", - component: defineAsyncComponent(() => import("@/ui/CQCXG7A2B.vue")), - }, - QCXG9P1C: { - name: "名下车辆", - component: defineAsyncComponent(() => import("@/ui/CQCXG9P1C.vue")), - }, + ...vehicleFeatureMap, BehaviorRiskScan: { name: "风险行为扫描", component: defineAsyncComponent(() => @@ -917,7 +914,7 @@ const featureRiskLevels = { // 🟢 中风险类 - 权重 8-12 'QYGL3F8E': 10, // 人企关系加强版 - 'QCXG7A2B': 10, // 名下车辆 + ...vehicleFeatureRiskLevels, 'JRZQ09J8': 10, // 收入评估 'JRZQ3C9R': 10, // 支付行为指数 // 'IVYZ0S0D': 10, // 个人仲裁信息 diff --git a/src/components/ReportFeatures.vue b/src/components/ReportFeatures.vue index 67ed7a2..25e777b 100644 --- a/src/components/ReportFeatures.vue +++ b/src/components/ReportFeatures.vue @@ -203,6 +203,19 @@ const getFeatureIcon = (apiId) => { JRZQ4AA8: "/inquire_icons/huankuanyali.svg", QCXG7A2B: "/inquire_icons/mingxiacheliang.svg", QCXG9P1C: "/inquire_icons/mingxiacheliang.svg", + QCXG4D2E: "/inquire_icons/mingxiacheliang.svg", + QCXG5F3A: "/inquire_icons/mingxiacheliang.svg", + QCXG5U0Z: "/inquire_icons/mingxiacheliang.svg", + QCXGGB2Q: "/inquire_icons/mingxiacheliang.svg", + QCXGYTS2: "/inquire_icons/mingxiacheliang.svg", + QCXG1H7Y: "/inquire_icons/mingxiacheliang.svg", + QCXG4I1Z: "/inquire_icons/mingxiacheliang.svg", + QCXG1U4U: "/inquire_icons/mingxiacheliang.svg", + QCXG3Y6B: "/inquire_icons/mingxiacheliang.svg", + QCXG3Z3L: "/inquire_icons/mingxiacheliang.svg", + QCXG6B4E: "/inquire_icons/mingxiacheliang.svg", + QCXGP00W: "/inquire_icons/mingxiacheliang.svg", + QCXGY7F2: "/inquire_icons/mingxiacheliang.svg", BehaviorRiskScan: "/inquire_icons/fengxianxingwei.svg", IVYZ5733: "/inquire_icons/hunyinzhuangtai.svg", IVYZ81NC: "/inquire_icons/hunyinzhuangtai.svg", diff --git a/src/composables/useVehiclePayload.js b/src/composables/useVehiclePayload.js new file mode 100644 index 0000000..e0d98ca --- /dev/null +++ b/src/composables/useVehiclePayload.js @@ -0,0 +1,19 @@ +import { computed } from 'vue' +import { payloadAsArray, payloadAsObject, unwrapVehiclePayload } from '@/utils/vehiclePayload' + +const defaultProps = { + data: null, + params: () => ({}), + apiId: '', + index: 0, + notifyRiskStatus: () => {}, +} + +export function useVehiclePayload(props = defaultProps) { + const payload = computed(() => unwrapVehiclePayload(props.data)) + const obj = computed(() => payloadAsObject(props.data)) + const list = computed(() => payloadAsArray(props.data)) + const params = computed(() => props.params || {}) + + return { payload, obj, list, params } +} diff --git a/src/config/vehicleFeatureMap.js b/src/config/vehicleFeatureMap.js new file mode 100644 index 0000000..b958d23 --- /dev/null +++ b/src/config/vehicleFeatureMap.js @@ -0,0 +1,41 @@ +import { defineAsyncComponent } from 'vue' +import { VEHICLE_API_TITLES } from './vehicleReportRegistry' + +/** 已单独实现 UI 的 apiID */ +const VEHICLE_COMPONENT_LOADERS = { + QCXG7A2B: () => import('@/ui/CQCXG7A2B.vue'), + QCXG9P1C: () => import('@/ui/CQCXG9P1C.vue'), + QCXG4D2E: () => import('@/ui/CQCXG4D2E.vue'), + QCXG5F3A: () => import('@/ui/CQCXG5F3A.vue'), + QCXG5U0Z: () => import('@/ui/CQCXG5U0Z.vue'), + QCXGGB2Q: () => import('@/ui/CQCXGGB2Q.vue'), + QCXGYTS2: () => import('@/ui/CQCXGYTS2.vue'), + QCXG1H7Y: () => import('@/ui/CQCXG1H7Y.vue'), + QCXG4I1Z: () => import('@/ui/CQCXG4I1Z.vue'), + QCXG1U4U: () => import('@/ui/CQCXG1U4U.vue'), + QCXG3Y6B: () => import('@/ui/CQCXG3Y6B.vue'), + QCXG3Z3L: () => import('@/ui/CQCXG3Z3L.vue'), + QCXG6B4E: () => import('@/ui/CQCXG6B4E.vue'), + QCXGP00W: () => import('@/ui/CQCXGP00W.vue'), + QCXGY7F2: () => import('@/ui/CQCXGY7F2.vue'), +} + +const fallbackLoader = () => import('@/ui/CQCXGFallback.vue') + +export const vehicleFeatureMap = Object.fromEntries( + Object.entries(VEHICLE_API_TITLES).map(([apiId, name]) => { + const loader = VEHICLE_COMPONENT_LOADERS[apiId] || fallbackLoader + return [ + apiId, + { + name, + component: defineAsyncComponent(loader), + }, + ] + }), +) + +/** 车辆类模块默认低风险权重 */ +export const vehicleFeatureRiskLevels = Object.fromEntries( + Object.keys(VEHICLE_API_TITLES).map(id => [id, 10]), +) diff --git a/src/config/vehicleReportRegistry.js b/src/config/vehicleReportRegistry.js new file mode 100644 index 0000000..007966d --- /dev/null +++ b/src/config/vehicleReportRegistry.js @@ -0,0 +1,45 @@ +/** + * 车辆类报告模块:apiID(产品能力编码)→ 展示名称 + * 与 qncV4uni-app、后端 ProductFeature / 上游 api 约定对齐 + */ +export const VEHICLE_API_TITLES = { + QCXG9F5C: '疑似营运车辆注册平台数', + QCXG3B8Z: '疑似运营车辆查询(月度里程)', + QCXGP1W3: '疑似运营车辆查询(季度里程)', + QCXGM7R9: '疑似运营车辆查询(半年度里程)', + QCXGU2K4: '疑似运营车辆查询(年度里程)', + QCXGY7F2: '二手车 VIN 估值', + QCXG5U0Z: '车辆静态信息查询', + QCXG3M7Z: '人车关系核验(ETC)', + QCXG1U4U: '车辆里程记录(混合查询)', + QCXG2T6S: '车辆里程记录(品牌查询)', + QCXG3Y6B: '车辆维保简版查询', + QCXG3Z3L: '车辆维保详细版查询', + QCXG1H7Y: '车辆过户简版查询', + QCXG4I1Z: '车辆过户详版查询', + QCXGGB2Q: '车辆二要素核验简版', + QCXGP00W: '车辆出险详版查询', + QCXGYTS2: '人车核验(详版)', + QCXGGJ3A: '车辆 VIN 码查询号牌简版', + QCXGJJ2A: '车辆 VIN 码查车辆信息详版', + QCXG5F3A: '名下车辆车牌查询 B', + QCXG4D2E: '名下车辆数量查询', + QCXG6B4E: '车辆出险记录核验', + QCXG8A3D: '车辆七项信息核验', + QCXG9P1C: '名下车辆车牌查询 A', + QCXG7A2B: '名下车辆', +} + +export function getVehicleModuleTitle(apiId, featureName) { + if (!apiId || apiId === '__UNLABELED__') { + if (featureName?.trim()) + return featureName.trim() + return '报告模块' + } + const t = VEHICLE_API_TITLES[apiId] + if (t) + return t + if (featureName?.trim()) + return featureName.trim() + return apiId +} diff --git a/src/ui/CQCXG1H7Y.vue b/src/ui/CQCXG1H7Y.vue new file mode 100644 index 0000000..8abeb88 --- /dev/null +++ b/src/ui/CQCXG1H7Y.vue @@ -0,0 +1,76 @@ + + + diff --git a/src/ui/CQCXG1U4U.vue b/src/ui/CQCXG1U4U.vue new file mode 100644 index 0000000..9bbae06 --- /dev/null +++ b/src/ui/CQCXG1U4U.vue @@ -0,0 +1,104 @@ + + + diff --git a/src/ui/CQCXG3Y6B.vue b/src/ui/CQCXG3Y6B.vue new file mode 100644 index 0000000..6568cdc --- /dev/null +++ b/src/ui/CQCXG3Y6B.vue @@ -0,0 +1,66 @@ + + + diff --git a/src/ui/CQCXG3Z3L.vue b/src/ui/CQCXG3Z3L.vue new file mode 100644 index 0000000..e8d9e9d --- /dev/null +++ b/src/ui/CQCXG3Z3L.vue @@ -0,0 +1,53 @@ + + + diff --git a/src/ui/CQCXG4D2E.vue b/src/ui/CQCXG4D2E.vue new file mode 100644 index 0000000..fed8517 --- /dev/null +++ b/src/ui/CQCXG4D2E.vue @@ -0,0 +1,15 @@ + + + diff --git a/src/ui/CQCXG4I1Z.vue b/src/ui/CQCXG4I1Z.vue new file mode 100644 index 0000000..794d4b2 --- /dev/null +++ b/src/ui/CQCXG4I1Z.vue @@ -0,0 +1,84 @@ + + + diff --git a/src/ui/CQCXG5F3A.vue b/src/ui/CQCXG5F3A.vue new file mode 100644 index 0000000..1d8645d --- /dev/null +++ b/src/ui/CQCXG5F3A.vue @@ -0,0 +1,15 @@ + + + diff --git a/src/ui/CQCXG5U0Z.vue b/src/ui/CQCXG5U0Z.vue new file mode 100644 index 0000000..9b138a6 --- /dev/null +++ b/src/ui/CQCXG5U0Z.vue @@ -0,0 +1,52 @@ + + + diff --git a/src/ui/CQCXG6B4E.vue b/src/ui/CQCXG6B4E.vue new file mode 100644 index 0000000..e5c7bdc --- /dev/null +++ b/src/ui/CQCXG6B4E.vue @@ -0,0 +1,76 @@ + + + diff --git a/src/ui/CQCXG7A2B.vue b/src/ui/CQCXG7A2B.vue index 0f602e3..6caf0a6 100644 --- a/src/ui/CQCXG7A2B.vue +++ b/src/ui/CQCXG7A2B.vue @@ -1,65 +1,32 @@ - - - \ No newline at end of file + diff --git a/src/ui/CQCXG9P1C.vue b/src/ui/CQCXG9P1C.vue index 00648cb..94f0fb1 100644 --- a/src/ui/CQCXG9P1C.vue +++ b/src/ui/CQCXG9P1C.vue @@ -1,130 +1,15 @@ - - - \ No newline at end of file + diff --git a/src/ui/CQCXGFallback.vue b/src/ui/CQCXGFallback.vue new file mode 100644 index 0000000..ec50254 --- /dev/null +++ b/src/ui/CQCXGFallback.vue @@ -0,0 +1,43 @@ + + + diff --git a/src/ui/CQCXGGB2Q.vue b/src/ui/CQCXGGB2Q.vue new file mode 100644 index 0000000..602d5fe --- /dev/null +++ b/src/ui/CQCXGGB2Q.vue @@ -0,0 +1,58 @@ + + + diff --git a/src/ui/CQCXGP00W.vue b/src/ui/CQCXGP00W.vue new file mode 100644 index 0000000..40d7c00 --- /dev/null +++ b/src/ui/CQCXGP00W.vue @@ -0,0 +1,99 @@ + + + diff --git a/src/ui/CQCXGY7F2.vue b/src/ui/CQCXGY7F2.vue new file mode 100644 index 0000000..e741142 --- /dev/null +++ b/src/ui/CQCXGY7F2.vue @@ -0,0 +1,51 @@ + + + diff --git a/src/ui/CQCXGYTS2.vue b/src/ui/CQCXGYTS2.vue new file mode 100644 index 0000000..a0d54ca --- /dev/null +++ b/src/ui/CQCXGYTS2.vue @@ -0,0 +1,76 @@ + + + diff --git a/src/ui/vehicle/VehiclePlateList.vue b/src/ui/vehicle/VehiclePlateList.vue new file mode 100644 index 0000000..85db4e6 --- /dev/null +++ b/src/ui/vehicle/VehiclePlateList.vue @@ -0,0 +1,63 @@ + + + diff --git a/src/utils/vehiclePayload.js b/src/utils/vehiclePayload.js new file mode 100644 index 0000000..03192c2 --- /dev/null +++ b/src/utils/vehiclePayload.js @@ -0,0 +1,73 @@ +/** + * 解包车辆报告业务体:兼容 BaseReport 传入的 data 仍带一层 { apiID, data } 或 JSON 字符串 + */ +export function unwrapVehiclePayload(raw, depth = 0) { + if (raw == null || depth > 5) + return raw + + if (typeof raw === 'string') { + const s = raw.trim() + if (!s) + return null + try { + return unwrapVehiclePayload(JSON.parse(s), depth + 1) + } + catch { + return raw + } + } + + if (Array.isArray(raw)) + return raw + + if (typeof raw !== 'object') + return raw + + const hasApiWrapper = ('apiID' in raw || 'apiId' in raw) && 'data' in raw + if (hasApiWrapper) + return unwrapVehiclePayload(raw.data ?? raw, depth + 1) + + if (typeof raw.data === 'string' || (raw.data && typeof raw.data === 'object' && !Array.isArray(raw.data))) { + const inner = raw.data + const onlyDataKey = Object.keys(raw).length <= 2 && 'data' in raw + if (onlyDataKey || (typeof inner === 'object' && inner && !Array.isArray(inner) && Object.keys(inner).length > 0 && !('list' in raw) && !('vehicleCount' in raw) && !('carNum' in raw))) + return unwrapVehiclePayload(inner, depth + 1) + } + + return raw +} + +/** 解析为数组:直连数组、list、record、retdata 等 */ +export function payloadAsArray(raw) { + const p = unwrapVehiclePayload(raw) + if (!p) + return [] + if (Array.isArray(p)) + return p + if (typeof p !== 'object') + return [] + if (Array.isArray(p.list)) + return p.list + if (Array.isArray(p.record)) + return p.record + if (Array.isArray(p.retdata)) + return p.retdata + if (typeof p.data === 'string') { + try { + const parsed = JSON.parse(p.data) + return Array.isArray(parsed) ? parsed : [] + } + catch { + return [] + } + } + return [] +} + +/** 解析为对象 */ +export function payloadAsObject(raw) { + const p = unwrapVehiclePayload(raw) + if (!p || typeof p !== 'object' || Array.isArray(p)) + return {} + return p +} diff --git a/src/utils/vehicleReportBlockMaps.js b/src/utils/vehicleReportBlockMaps.js new file mode 100644 index 0000000..635a71a --- /dev/null +++ b/src/utils/vehicleReportBlockMaps.js @@ -0,0 +1,73 @@ +/** 车辆报告共用车牌颜色、车辆类型、日期里程文案 */ + +export const PLATE_COLOR_LABELS = { + 0: '蓝色', + 1: '黄色', + 2: '黑色', + 3: '白色', + 4: '渐变绿', + 5: '黄绿双拼', + 6: '蓝白渐变', + 7: '临牌', + 11: '绿色', + 12: '红色', +} + +export function plateColorLabel(c) { + const n = Number(c) + if (Number.isNaN(n)) + return '—' + return PLATE_COLOR_LABELS[n] ?? '其他' +} + +const VEHICLE_TYPE_LABELS = { + 1: '一型客车', + 2: '二型客车', + 3: '三型客车', + 4: '四型客车', + 11: '一型货车', + 12: '二型货车', + 13: '三型货车', + 14: '四型货车', + 15: '五型货车', + 16: '六型货车', + 21: '一型专项作业车', + 22: '二型专项作业车', + 23: '三型专项作业车', + 24: '四型专项作业车', + 25: '五型专项作业车', + 26: '六型专项作业车', +} + +export function vehicleTypeLabel(t) { + if (t == null || t === '') + return '—' + const n = Number(t) + if (!Number.isNaN(n) && VEHICLE_TYPE_LABELS[n]) + return VEHICLE_TYPE_LABELS[n] + return String(t) +} + +export function formatDateZh(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 String(val) +} + +export function formatMileageKm(val) { + if (val !== 0 && !val) + return '-' + const num = Number(val) + if (Number.isNaN(num)) + return `${val} km` + return `${num.toLocaleString()} km` +} + +export function maskName(name) { + if (!name) + return '-' + return name.length > 1 ? `${name[0]}${'*'.repeat(name.length - 1)}` : '*' +} diff --git a/src/utils/vehicleReportNormalize.js b/src/utils/vehicleReportNormalize.js new file mode 100644 index 0000000..5401ff8 --- /dev/null +++ b/src/utils/vehicleReportNormalize.js @@ -0,0 +1,44 @@ +import { unwrapVehiclePayload } from './vehiclePayload' + +/** + * 规范 query_data 单项,供 BaseReport 使用:{ data: { apiID, data: 业务体 } } + */ +export function normalizeVehicleReportItem(item) { + if (!item || typeof item !== 'object') + return item + + const outer = item.data + if (outer == null) + return item + + let apiID = '' + let payload = outer + + if (typeof outer === 'object' && !Array.isArray(outer)) { + apiID = String(outer.apiID ?? outer.apiId ?? '') + if ('data' in outer) + payload = outer.data + } + + payload = unwrapVehiclePayload(payload) + + if (!apiID && payload && typeof payload === 'object' && !Array.isArray(payload)) { + apiID = String(payload.apiID ?? payload.apiId ?? '') + if (apiID && 'data' in payload) + payload = unwrapVehiclePayload(payload.data) + } + + return { + ...item, + data: { + apiID: apiID || '', + data: payload, + }, + } +} + +export function normalizeVehicleReportData(items) { + if (!Array.isArray(items)) + return [] + return items.map(normalizeVehicleReportItem) +}