Files
tyc-webview-v2/src/components/InquireForm.vue
2026-02-24 16:47:49 +08:00

1262 lines
56 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.

<template>
<div class="inquire-bg min-h-screen relative" :class="isDefaultBackground ? 'pt-12' : 'pt-48'"
:style="backgroundStyle">
<!-- 主要内容区域 - 覆盖背景图片 -->
<div class="min-h-screen relative mx-4 pb-12">
<div class="card-container">
<!-- 卡片头部产品名 -->
<div class="card-header text-lg text-center font-bold text-gray-800 mb-4 pb-3 border-b border-gray-200">
{{ featureData.product_name }}
</div>
<!-- 基本信息标题 -->
<div class="mb-6 flex items-center">
<SectionTitle title="基本信息" />
<div class="ml-auto flex items-center text-gray-600 cursor-pointer" @click="toExample">
<img src="@/assets/images/report/slbg_inquire_icon.png" alt="示例报告" class="w-4 h-4 mr-1" />
<span class="">示例报告</span>
</div>
</div>
<!-- 表单输入区域 -->
<div class="space-y-4 mb-6">
<!-- 姓名 -->
<div class="flex items-center py-3 border-b border-gray-100" v-if="isHasInput('name')">
<label for="name" class="w-20 font-medium text-gray-700">姓名</label>
<input v-model="formData.name" id="name" type="text" placeholder="请输入正确的姓名"
class="flex-1 border-none outline-none" @click="handleInputClick" />
</div>
<!-- 身份证号 -->
<div class="flex items-center py-3 border-b border-gray-100" v-if="isHasInput('idCard')">
<label for="idCard" class="w-20 font-medium text-gray-700">身份证号</label>
<input v-model="formData.idCard" id="idCard" type="text" placeholder="请输入准确的身份证号"
class="flex-1 border-none outline-none" @click="handleInputClick" />
</div>
<!-- 企业名称 -->
<div class="flex items-center py-3 border-b border-gray-100" v-if="isHasInput('entName')">
<label for="entName" class="w-20 font-medium text-gray-700">企业名称</label>
<input v-model="formData.entName" id="entName" type="text" placeholder="请输入企业名称"
class="flex-1 border-none outline-none" @click="handleInputClick" />
</div>
<!-- 统一社会信用代码 -->
<div class="flex items-center py-3 border-b border-gray-100" v-if="isHasInput('entCode')">
<label for="entCode" class="w-32 font-medium text-gray-700">统一社会信用代码</label>
<input v-model="formData.entCode" id="entCode" type="text" placeholder="请输入统一社会信用代码"
class="flex-1 border-none outline-none" @click="handleInputClick" />
</div>
<!-- 双人婚姻男方姓名 -->
<div class="flex items-center py-3 border-b border-gray-100" v-if="isHasInput('nameMan')">
<label for="nameMan" class="w-24 font-medium text-gray-700">男方姓名</label>
<input v-model="formData.nameMan" id="nameMan" type="text" placeholder="请输入男方姓名"
class="flex-1 border-none outline-none" @click="handleInputClick" />
</div>
<!-- 双人婚姻男方身份证号 -->
<div class="flex items-center py-3 border-b border-gray-100" v-if="isHasInput('idCardMan')">
<label for="idCardMan" class="w-28 font-medium text-gray-700">男方身份证号</label>
<input v-model="formData.idCardMan" id="idCardMan" type="text" placeholder="请输入男方身份证号"
class="flex-1 border-none outline-none" @click="handleInputClick" />
</div>
<!-- 双人婚姻女方姓名 -->
<div class="flex items-center py-3 border-b border-gray-100" v-if="isHasInput('nameWoman')">
<label for="nameWoman" class="w-24 font-medium text-gray-700">女方姓名</label>
<input v-model="formData.nameWoman" id="nameWoman" type="text" placeholder="请输入女方姓名"
class="flex-1 border-none outline-none" @click="handleInputClick" />
</div>
<!-- 双人婚姻女方身份证号 -->
<div class="flex items-center py-3 border-b border-gray-100" v-if="isHasInput('idCardWoman')">
<label for="idCardWoman" class="w-28 font-medium text-gray-700">女方身份证号</label>
<input v-model="formData.idCardWoman" id="idCardWoman" type="text" placeholder="请输入女方身份证号"
class="flex-1 border-none outline-none" @click="handleInputClick" />
</div>
<!-- 手机号 -->
<div class="flex items-center py-3 border-b border-gray-100" v-if="isHasInput('mobile')">
<label for="mobile" class="w-20 font-medium text-gray-700">手机号</label>
<input v-model="formData.mobile" id="mobile" type="tel" placeholder="请输入手机号"
class="flex-1 border-none outline-none" @click="handleInputClick" />
</div>
<!-- 车牌号 -->
<div class="flex items-center py-3 border-b border-gray-100" v-if="isHasInput('carLicense')">
<label for="carLicense" class="w-20 font-medium text-gray-700">车牌号</label>
<input v-model="formData.carLicense" id="carLicense" type="text" placeholder="请输入车牌号"
class="flex-1 border-none outline-none" @click="handleInputClick" />
</div>
<!-- 号牌类型 -->
<div class="flex items-center py-3 border-b border-gray-100" v-if="isHasInput('carType')">
<label for="carType" class="w-24 font-medium text-gray-700">号牌类型</label>
<input v-model="formData.carType" id="carType" type="text" placeholder="请输入号牌类型"
class="flex-1 border-none outline-none" @click="handleInputClick" />
</div>
<!-- 关系类型(名下车辆数量) -->
<div class="flex items-center py-3 border-b border-gray-100" v-if="isHasInput('userType')">
<label for="userType" class="w-28 font-medium text-gray-700">关系类型</label>
<div class="flex-1">
<van-field :model-value="userTypeDisplay" readonly is-link placeholder="请选择关系类型"
input-align="right" @click="showUserTypePicker = true" class="!p-0 !min-h-0" />
</div>
<van-popup v-model:show="showUserTypePicker" round position="bottom">
<van-picker :columns="userTypeOptions" @confirm="onUserTypeConfirm"
@cancel="showUserTypePicker = false" />
</van-popup>
</div>
<!-- 车架号/VIN -->
<div class="flex items-center py-3 border-b border-gray-100" v-if="isHasInput('vinCode')">
<label for="vinCode" class="w-24 font-medium text-gray-700">车架号/VIN</label>
<input v-model="formData.vinCode" id="vinCode" type="text" placeholder="请输入车架号"
class="flex-1 border-none outline-none" @click="handleInputClick" />
</div>
<!-- 行驶证图片 URL(里程混合等)行驶证形状框上传接口返回 URL限制 3MB -->
<div class="py-3 border-b border-gray-100" v-if="isHasInput('imageUrl')">
<p class="text-sm text-gray-600 mb-2">上传行驶证照片将生成可访问链接</p>
<input ref="imageUrlFileInputRef" type="file" accept="image/*" class="hidden"
@change="onImageUrlFileChange" />
<div class="vlphoto-frame" role="button" tabindex="0" @click="imageUrlFileInputRef?.click()"
@keydown.enter.space.prevent="imageUrlFileInputRef?.click()">
<img v-if="formData.imageUrl" :src="formData.imageUrl" alt="行驶证" class="vlphoto-preview"
@error="onImageUrlError" />
<div v-else class="vlphoto-placeholder">
<span v-if="imageUrlUploading" class="vlphoto-hint">上传中...</span>
<template v-else>
<span class="vlphoto-icon">📄</span>
<span class="vlphoto-hint">点击上传行驶证</span>
</template>
</div>
</div>
<p class="mt-1 text-xs text-amber-600">图片不能超过 3M</p>
</div>
<!-- 车辆所在地区 -->
<div class="flex items-center py-3 border-b border-gray-100" v-if="isHasInput('vehicleLocation')">
<label for="vehicleLocation" class="w-28 font-medium text-gray-700">车辆所在地区</label>
<input v-model="formData.vehicleLocation" id="vehicleLocation" type="text" placeholder="必填"
class="flex-1 border-none outline-none" @click="handleInputClick" />
</div>
<!-- 初次登记日期 -->
<div class="flex items-center py-3 border-b border-gray-100"
v-if="isHasInput('firstRegistrationDate')">
<label for="firstRegistrationDate" class="w-28 font-medium text-gray-700">初次登记日期</label>
<div class="flex-1">
<van-field :model-value="firstRegistrationDateDisplay" readonly is-link placeholder="请选择日期"
input-align="right" @click="openFirstRegistrationDatePicker" class="!p-0 !min-h-0" />
</div>
<van-popup v-model:show="showFirstRegistrationDatePicker" round position="bottom">
<van-date-picker v-model="firstRegistrationDateColumns" title="选择初次登记日期"
:columns-type="['year', 'month']" @confirm="onFirstRegistrationDateConfirm"
@cancel="showFirstRegistrationDatePicker = false" />
</van-popup>
</div>
<!-- 行驶证图片上传(出险详版)行驶证形状框点击上传限制 3MB -->
<div class="py-3 border-b border-gray-100" v-if="isHasInput('vlphotoData')">
<p class="text-sm text-gray-600 mb-2">上传行驶证照片</p>
<input ref="vlphotoFileInputRef" type="file" accept="image/*" class="hidden"
@change="onVlphotoFileChange" />
<div class="vlphoto-frame" role="button" tabindex="0" @click="vlphotoFileInputRef?.click()"
@keydown.enter.space.prevent="vlphotoFileInputRef?.click()">
<img v-if="vlphotoPreviewUrl" :src="vlphotoPreviewUrl" alt="行驶证" class="vlphoto-preview" />
<div v-else class="vlphoto-placeholder">
<span class="vlphoto-icon">📄</span>
<span class="vlphoto-hint">点击上传行驶证</span>
</div>
</div>
<p class="mt-1 text-xs text-amber-600">图片不能超过 3M</p>
</div>
<!-- 银行卡号 -->
<div class="flex items-center py-3 border-b border-gray-100" v-if="isHasInput('bankCard')">
<label for="bankCard" class="w-24 font-medium text-gray-700">银行卡号</label>
<input v-model="formData.bankCard" id="bankCard" type="text" placeholder="请输入银行卡号"
class="flex-1 border-none outline-none" @click="handleInputClick" />
</div>
<!-- 人像图片上传公安三要素人像照片框点击上传限制 500KBjpg/png15~4000px -->
<div class="py-3 border-b border-gray-100" v-if="isHasInput('photoData')">
<p class="text-sm text-gray-600 mb-2">上传人像照片</p>
<input type="file" accept="image/jpeg,image/png" class="hidden" ref="photoDataInputRef"
@change="onPhotoDataFileChange" />
<div class="vlphoto-frame" role="button" tabindex="0" @click="photoDataInputRef?.click()"
@keydown.enter.space.prevent="photoDataInputRef?.click()">
<img v-if="photoDataPreviewUrl" :src="photoDataPreviewUrl" alt="人像照片"
class="vlphoto-preview" />
<div v-else class="vlphoto-placeholder">
<span class="vlphoto-icon">📷</span>
<span class="vlphoto-hint">点击上传人像照片</span>
</div>
</div>
<p class="mt-1 text-xs text-amber-600">
图片格式需为 jpg/png大小不超过 500KB宽高需在 15px ~ 4000px 之间
</p>
</div>
<!-- 是否取得用户授权 -->
<div class="flex items-center py-3 border-b border-gray-100" v-if="isHasInput('authorized')">
<label for="authorized" class="w-32 font-medium text-gray-700">用户授权</label>
<div class="flex-1">
<van-field :model-value="authorizedDisplay" readonly is-link placeholder="请选择"
input-align="right" @click="showAuthorizedPicker = true" class="!p-0 !min-h-0" />
</div>
<van-popup v-model:show="showAuthorizedPicker" round position="bottom">
<van-picker :columns="authorizedOptions" @confirm="onAuthorizedConfirm"
@cancel="showAuthorizedPicker = false" />
</van-popup>
</div>
<!-- 验证码 -->
<div class="flex items-center py-3 border-b border-gray-100" v-if="isHasInput('verificationCode')">
<label for="verificationCode" class="w-20 font-medium text-gray-700">验证码</label>
<input v-model="formData.verificationCode" id="verificationCode" placeholder="请输入验证码"
maxlength="6" class="flex-1 border-none outline-none" @click="handleInputClick" />
<button class="text-primary font-medium text-nowrap"
:disabled="isCountingDown || !isPhoneNumberValid" @click="sendVerificationCode">
{{
isCountingDown
? `${countdown}s重新获取`
: "获取验证码"
}}
</button>
</div>
</div>
<!-- 协议同意 -->
<div class="flex items-center mb-6">
<input type="checkbox" v-model="formData.agreeToTerms" class="mr-3 accent-primary" />
<span class="text-sm text-gray-500">
我已阅读并同意
<span @click="toUserAgreement" class="text-primary cursor-pointer">用户协议</span>
<span @click="toPrivacyPolicy" class="text-primary cursor-pointer">隐私政策</span>
<span @click="toAuthorization" class="text-primary cursor-pointer">授权书</span>
</span>
</div>
<!-- 查询按钮 -->
<button
class="w-full bg-primary text-white py-4 rounded-[48px] text-lg font-medium mb-4 flex items-center justify-center mt-10"
@click="handleSubmit">
<span>{{ buttonText }}</span>
<span class="ml-4">¥{{ featureData.sell_price }}</span>
</button>
<!-- <div class="text-xs text-gray-500 leading-relaxed mt-8" v-html="featureData.description">
</div> -->
<!-- 免责声明 -->
<div class="text-xs text-center text-gray-500 leading-relaxed mt-2">
为保证用户的隐私及数据安全查询结果生成30天后将自动删除
</div>
</div>
<!-- 报告包含内容 -->
<div class="card mt-3" v-if="featureData.features && featureData.features.length > 1">
<div class="mb-3 text-base font-semibold flex items-center" style="color: var(--van-text-color);">
<div class="w-1 h-5 rounded-full mr-2"
style="background: linear-gradient(to bottom, var(--van-theme-primary), var(--van-theme-primary-dark));">
</div>
报告包含内容
</div>
<div class="grid grid-cols-4 gap-2">
<template v-for="(feature, index) in featureData.features" :key="feature.id">
<!-- FLXG0V4B 特殊处理显示8个独立的案件类型 -->
<template v-if="feature.api_id === 'FLXG0V4B'">
<div v-for="(caseType, caseIndex) in [
{ name: '管辖案件', icon: 'beijianguanrenyuan.svg' },
{ name: '刑事案件', icon: 'xingshi.svg' },
{ name: '民事案件', icon: 'minshianjianguanli.svg' },
{ name: '失信被执行', icon: 'shixinren.svg' },
{ name: '行政案件', icon: 'xingzhengfuwu.svg' },
{ name: '赔偿案件', icon: 'yuepeichang.svg' },
{ name: '执行案件', icon: 'zhixinganjian.svg' },
{ name: '限高被执行', icon: 'xianzhigaoxiaofei.svg' },
]" :key="`${feature.id}-${caseIndex}`"
class="aspect-square rounded-xl text-center text-sm text-gray-700 font-medium flex flex-col items-center justify-center p-2 shadow-lg"
:class="getCardClass(index + caseIndex)">
<div class="mb-1">
<img :src="`/inquire_icons/${caseType.icon}`" :alt="caseType.name"
class="w-6 h-6 drop-shadow-sm mx-auto" @error="handleIconError" />
</div>
<div class="text-xs leading-tight font-medium"
style="word-break: break-all; line-height: 1.1; min-height: 28px; display: flex; align-items: center; justify-content: center;">
{{ caseType.name }}
</div>
</div>
</template>
<!-- DWBG8B4D 特殊处理:显示拆分模块 -->
<template v-else-if="feature.api_id === 'DWBG8B4D'">
<div v-for="(module, moduleIndex) in [
{ name: '要素核查', icon: 'beijianguanrenyuan.svg' },
{ name: '运营商核验', icon: 'mingxiacheliang.svg' },
{ name: '公安重点人员检验', icon: 'xingshi.svg' },
{ name: '逾期风险综述', icon: 'huankuanyali.svg' },
{ name: '法院曝光台信息', icon: 'sifasheyu.svg' },
{ name: '借贷评估', icon: 'jiedaishenqing.svg' },
{ name: '租赁风险评估', icon: 'jiedaixingwei.svg' },
{ name: '关联风险监督', icon: 'renqiguanxi.svg' },
{ name: '规则风险提示', icon: 'fengxianxingwei.svg' },
]" :key="`${feature.id}-${moduleIndex}`"
class="aspect-square rounded-xl text-center text-sm text-gray-700 font-medium flex flex-col items-center justify-center p-2 shadow-lg"
:class="getCardClass(index + moduleIndex)">
<div class="text-xl mb-1 flex items-center justify-center">
<img :src="`/inquire_icons/${module.icon}`" :alt="module.name"
class="w-6 h-6 drop-shadow-sm" @error="handleIconError" />
</div>
<div class="text-xs leading-tight px-1 font-medium"
style="word-break: break-all; line-height: 1.2">
{{ module.name }}
</div>
</div>
</template>
<!-- CJRZQ5E9F 特殊处理:显示拆分模块 -->
<template v-else-if="feature.api_id === 'JRZQ5E9F'">
<div v-for="(module, moduleIndex) in [
{ name: '信用评分', icon: 'huankuanyali.svg' },
{ name: '贷款行为分析', icon: 'jiedaixingwei.svg' },
{ name: '机构分析', icon: 'jiedaishenqing.svg' },
{ name: '时间趋势分析', icon: 'zhixinganjian.svg' },
{ name: '风险指标详情', icon: 'fengxianxingwei.svg' },
{ name: '专业建议', icon: 'yuepeichang.svg' },
]" :key="`${feature.id}-${moduleIndex}`"
class="aspect-square rounded-xl text-center text-sm text-gray-700 font-medium flex flex-col items-center justify-center p-2 shadow-lg"
:class="getCardClass(index + moduleIndex)">
<div class="text-xl mb-1 flex items-center justify-center">
<img :src="`/inquire_icons/${module.icon}`" :alt="module.name"
class="w-6 h-6 drop-shadow-sm" @error="handleIconError" />
</div>
<div class="text-xs leading-tight px-1 font-medium"
style="word-break: break-all; line-height: 1.2">
{{ module.name }}
</div>
</div>
</template>
<!-- PersonEnterprisePro/CQYGL3F8E 特殊处理:显示拆分模块 -->
<template v-else-if="feature.api_id === 'PersonEnterprisePro' || feature.api_id === 'QYGL3F8E'">
<div v-for="(module, moduleIndex) in [
{ name: '投资企业记录', icon: 'renqiguanxi.svg' },
{ name: '高管任职记录', icon: 'beijianguanrenyuan.svg' },
{ name: '涉诉风险', icon: 'sifasheyu.svg' },
{ name: '对外投资历史', icon: 'renqiguanxi.svg' },
{ name: '融资历史', icon: 'huankuanyali.svg' },
{ name: '行政处罚', icon: 'xingzhengfuwu.svg' },
{ name: '经营异常', icon: 'fengxianxingwei.svg' },
]" :key="`${feature.id}-${moduleIndex}`"
class="aspect-square rounded-xl text-center text-sm text-gray-700 font-medium flex flex-col items-center justify-center p-2 shadow-lg"
:class="getCardClass(index + moduleIndex)">
<div class="text-xl mb-1 flex items-center justify-center">
<img :src="`/inquire_icons/${module.icon}`" :alt="module.name"
class="w-6 h-6 drop-shadow-sm" @error="handleIconError" />
</div>
<div class="text-xs leading-tight px-1 font-medium"
style="word-break: break-all; line-height: 1.2">
{{ module.name }}
</div>
</div>
</template>
<!-- DWBG6A2C 特殊处理:显示拆分模块 -->
<template v-else-if="feature.api_id === 'DWBG6A2C'">
<div v-for="(module, moduleIndex) in [
{ name: '命中风险标注', icon: 'fengxianxingwei.svg' },
{ name: '公安重点人员核验', icon: 'beijianguanrenyuan.svg' },
{ name: '涉赌涉诈人员核验', icon: 'xingshi.svg' },
{ name: '风险名单', icon: 'jiedaiweiyue.svg' },
{ name: '历史借贷行为', icon: 'jiedaixingwei.svg' },
{ name: '近24个月放款情况', icon: 'jiedaishenqing.svg' },
{ name: '履约情况', icon: 'huankuanyali.svg' },
{ name: '历史逾期记录', icon: 'jiedaiweiyue.svg' },
{ name: '授信详情', icon: 'huankuanyali.svg' },
{ name: '租赁行为', icon: 'mingxiacheliang.svg' },
{ name: '关联风险监督', icon: 'renqiguanxi.svg' },
{ name: '法院风险信息', icon: 'sifasheyu.svg' },
]" :key="`${feature.id}-${moduleIndex}`"
class="aspect-square rounded-xl text-center text-sm text-gray-700 font-medium flex flex-col items-center justify-center p-2 shadow-lg"
:class="getCardClass(index + moduleIndex)">
<div class="text-xl mb-1 flex items-center justify-center">
<img :src="`/inquire_icons/${module.icon}`" :alt="module.name"
class="w-6 h-6 drop-shadow-sm" @error="handleIconError" />
</div>
<div class="text-xs leading-tight px-1 font-medium"
style="word-break: break-all; line-height: 1.2">
{{ module.name }}
</div>
</div>
</template>
<!-- 其他功能正常显示 -->
<div v-else
class="aspect-square rounded-xl text-center text-sm text-gray-700 font-medium flex flex-col items-center justify-between p-2 shadow-lg"
:class="getCardClass(index)">
<div class="flex items-center justify-center flex-1">
<img :src="getFeatureIcon(feature.api_id)" :alt="feature.name"
class="w-6 h-6 drop-shadow-sm" @error="handleIconError" />
</div>
<div class="text-xs leading-tight font-medium h-8 flex items-center justify-center"
style="word-break: break-all; line-height: 1.1">
{{ feature.name }}
</div>
</div>
</template>
</div>
<div class="mt-3 text-center">
<div class="inline-flex items-center px-3 py-1.5 rounded-full border transition-all"
style="background: linear-gradient(135deg, var(--van-theme-primary-light), rgba(255,255,255,0.8)); border-color: var(--van-theme-primary);">
<div class="w-1.5 h-1.5 rounded-full mr-1.5"
style="background-color: var(--van-theme-primary);">
</div>
<span class="text-xs font-medium" style="color: var(--van-theme-primary);">更多信息请解锁报告</span>
</div>
</div>
</div>
<!-- 产品详情卡片 -->
<div class="card mt-4">
<div class="mb-4 text-xl font-bold" style="color: var(--van-text-color);">
{{ featureData.product_name }}
</div>
<div class="mb-4 flex items-start justify-between">
<div class="text-lg" style="color: var(--van-text-color-2);">价格:</div>
<div>
<div class="text-2xl font-semibold text-danger">
¥{{ featureData.sell_price }}
</div>
</div>
</div>
<div class="mb-4 leading-relaxed" style="color: var(--van-text-color-2);"
v-html="featureData.description">
</div>
<div class="mb-2 text-xs italic text-danger">
为保证用户的隐私以及数据安全查询的结果生成30天之后将自动清除。
</div>
</div>
</div>
<!-- 支付组件 -->
<Payment v-model="showPayment" :data="featureData" :id="queryId" type="query" @close="showPayment = false" />
<BindPhoneDialog @bind-success="handleBindSuccess" />
<LoginDialog @login-success="handleLoginSuccess" />
<!-- 历史查询按钮 - 仅推广查询且已登录时显示 -->
<div v-if="props.type === 'promotion' && isLoggedIn" @click="toHistory"
class="fixed right-2 top-3/4 px-4 py-2 text-sm bg-primary rounded-xl cursor-pointer text-white font-bold shadow active:bg-blue-500">
历史查询
</div>
</div>
</template>
<script setup>
import { ref, computed, onMounted, onUnmounted, nextTick } from "vue";
import { aesEncrypt } from "@/utils/crypto";
import { useRoute, useRouter } from "vue-router";
import { useUserStore } from "@/stores/userStore";
import { useDialogStore } from "@/stores/dialogStore";
import { useEnv } from "@/composables/useEnv";
import { showConfirmDialog, showToast, DatePicker } from "vant";
import { useInquireForm } from "@/composables/useInquireForm";
import { useAliyunCaptcha } from "@/composables/useAliyunCaptcha";
import Payment from "@/components/Payment.vue";
import BindPhoneDialog from "@/components/BindPhoneDialog.vue";
import LoginDialog from "@/components/LoginDialog.vue";
import SectionTitle from "@/components/SectionTitle.vue";
// Props
const props = defineProps({
// 查询类型:'normal' | 'promotion'
type: {
type: String,
default: 'normal'
},
// 产品特征
feature: {
type: String,
required: true
},
// 推广链接标识符(仅推广查询需要)
linkIdentifier: {
type: String,
default: ''
},
// 产品数据(从外部传入)
featureData: {
type: Object,
default: () => ({})
}
});
const { feature } = toRefs(props);
// Emits
const emit = defineEmits(['submit-success']);
// 动态导入产品背景图片的函数
const loadProductBackground = async (productType) => {
try {
console.log('1动态导入加载产品背景图片', productType);
switch (productType) {
case 'companyinfo':
return (await import("@/assets/images/report/xwqy_inquire_bg.jpg")).default;
case 'preloanbackgroundcheck':
return (await import("@/assets/images/report/dqfx_inquire_bg.jpg")).default;
case 'personalData':
return (await import("@/assets/images/report/grdsj_inquire_bg.jpg")).default;
case 'marriage':
console.log('2动态导入loadProductBackground marriage_inquire_bg.jpg');
return (await import("@/assets/images/report/marriage_inquire_bg.jpg")).default;
case 'homeservice':
return (await import("@/assets/images/report/homeservice_inquire_bg.jpg")).default;
case 'backgroundcheck':
return (await import("@/assets/images/report/backgroundcheck_inquire_bg.png")).default;
default:
console.log('No matching case for product type:', productType);
return null;
}
} catch (error) {
console.warn(`Failed to load background image for ${productType}:`, error);
console.log('Failed to load background image for ${productType}:', error);
return null;
}
};
const route = useRoute();
const router = useRouter();
const dialogStore = useDialogStore();
const userStore = useUserStore();
const { isWeChat } = useEnv();
// 响应式数据
const showPayment = ref(false);
const pendingPayment = ref(false);
const queryId = ref(null);
const productBackground = ref('');
const isDefaultBackground = ref(false);
const isCountingDown = ref(false);
const countdown = ref(60);
const vlphotoFileInputRef = ref(null);
const vlphotoFileName = ref('');
const vlphotoPreviewUrl = ref('');
const photoDataInputRef = ref(null);
const photoDataFileName = ref('');
const photoDataPreviewUrl = ref('');
const imageUrlFileInputRef = ref(null);
const imageUrlUploading = ref(false);
const showUserTypePicker = ref(false);
const showAuthorizedPicker = ref(false);
const showFirstRegistrationDatePicker = ref(false);
// 初次登记日期(年、月)选择的列值,例如 ['2024', '01']
const firstRegistrationDateColumns = ref([]);
// 使用传入的featureData或创建响应式引用
const featureData = computed(() => props.featureData || {});
// 使用通用查询表单 composable根据 feature 自动决定字段)
const { formData, isPhoneNumberValid, isIdCardValid, isHasInput, buildRequestPayload } = useInquireForm(feature);
const { runWithCaptcha } = useAliyunCaptcha();
const userTypeOptions = [
{ text: "ETC开户人", value: "1" },
{ text: "车辆所有人", value: "2" },
{ text: "ETC经办人", value: "3" },
];
const authorizedOptions = [
{ text: "是", value: "1" },
{ text: "否", value: "0" },
];
const userTypeDisplay = computed(() => {
const opt = userTypeOptions.find((o) => o.value === formData.userType);
return opt ? opt.text : "";
});
const authorizedDisplay = computed(() => {
const opt = authorizedOptions.find((o) => o.value === formData.authorized);
return opt ? opt.text : "";
});
const firstRegistrationDateDisplay = computed(() => formData.firstRegistrationDate || "");
// 额外字段校验(复用旧项目的规则思路)
const isIdCardManValid = computed(() => /^\d{17}[\dX]$/i.test(formData.idCardMan || ""));
const isIdCardWomanValid = computed(() => /^\d{17}[\dX]$/i.test(formData.idCardWoman || ""));
const isCreditCodeValid = computed(() => /^.{18}$/.test(formData.entCode || ""));
const isLoggedIn = computed(() => userStore.isLoggedIn);
const buttonText = computed(() => {
return isLoggedIn.value ? '立即查询' : '前往登录';
});
// 获取产品背景图片
const getProductBackground = computed(() => productBackground.value);
// 背景图片样式
const backgroundStyle = computed(() => {
console.log('getProductBackground', getProductBackground.value);
if (getProductBackground.value) {
return {
backgroundImage: `url(${getProductBackground.value})`,
backgroundSize: 'contain',
backgroundPosition: 'center -20px',
backgroundRepeat: 'no-repeat',
};
}
return {};
});
// 获取功能图标
const getFeatureIcon = (apiId) => {
const iconMap = {
JRZQ4AA8: "/inquire_icons/huankuanyali.svg", // 还款压力
QCXG7A2B: "/inquire_icons/mingxiacheliang.svg", // 名下车辆
QCXGGB2Q: "/inquire_icons/mingxiacheliang.svg", // 人车核验简版
QCXGYTS2: "/inquire_icons/mingxiacheliang.svg", // 人车核验详版
QCXG5F3A: "/inquire_icons/mingxiacheliang.svg", // 名下车辆(车牌)
QCXG4D2E: "/inquire_icons/mingxiacheliang.svg",
QCXG5U0Z: "/inquire_icons/mingxiacheliang.svg",
QCXG1U4U: "/inquire_icons/mingxiacheliang.svg",
QCXGY7F2: "/inquire_icons/mingxiacheliang.svg",
QCXG1H7Y: "/inquire_icons/mingxiacheliang.svg",
QCXG4I1Z: "/inquire_icons/mingxiacheliang.svg",
QCXG3Y6B: "/inquire_icons/mingxiacheliang.svg",
QCXG3Z3L: "/inquire_icons/mingxiacheliang.svg",
QCXGP00W: "/inquire_icons/mingxiacheliang.svg",
QCXG6B4E: "/inquire_icons/mingxiacheliang.svg",
IVYZ9K7F: "/inquire_icons/mingxiacheliang.svg",
IVYZA1B3: "/inquire_icons/mingxiacheliang.svg",
IVYZ6M8P: "/inquire_icons/mingxiacheliang.svg",
JRZQ8B3C: "/inquire_icons/mingxiacheliang.svg",
YYSY3M8S: "/inquire_icons/mingxiacheliang.svg",
YYSYK9R4: "/inquire_icons/mingxiacheliang.svg",
YYSYF2T7: "/inquire_icons/mingxiacheliang.svg",
YYSYK8R3: "/inquire_icons/mingxiacheliang.svg",
YYSYS9W1: "/inquire_icons/mingxiacheliang.svg",
YYSYE7V5: "/inquire_icons/mingxiacheliang.svg",
YYSYP0T4: "/inquire_icons/mingxiacheliang.svg",
YYSY6F2B: "/inquire_icons/mingxiacheliang.svg",
YYSY9E4A: "/inquire_icons/mingxiacheliang.svg",
QYGL5F6A: "/inquire_icons/mingxiacheliang.svg",
JRZQACAB: "/inquire_icons/mingxiacheliang.svg",
JRZQ0B6Y: "/inquire_icons/mingxiacheliang.svg",
BehaviorRiskScan: "/inquire_icons/fengxianxingwei.svg", // 风险行为扫描
IVYZ5733: "/inquire_icons/hunyinzhuangtai.svg", // 婚姻状态
PersonEnterprisePro: "/inquire_icons/renqiguanxi.svg", // 人企关系加强版
JRZQ0A03: "/inquire_icons/jiedaishenqing.svg", // 借贷申请记录
FLXG3D56: "/inquire_icons/jiedaiweiyue.svg", // 借贷违约失信
FLXG0V4B: "/inquire_icons/sifasheyu.svg", // 司法涉诉
JRZQ8203: "/inquire_icons/jiedaixingwei.svg", // 借贷行为记录
JRZQ09J8: "/inquire_icons/beijianguanrenyuan.svg", // 收入评估
JRZQ4B6C: "/inquire_icons/fengxianxingwei.svg", // 探针C风险评估
};
return iconMap[apiId] || "/inquire_icons/default.svg";
};
// 处理图标加载错误
const handleIconError = (event) => {
event.target.style.display = "none";
};
// 获取卡片样式类
const getCardClass = (index) => {
const colorIndex = index % 4;
const colorClasses = [
'bg-gradient-to-br from-blue-50 via-blue-25 to-white border-2 border-blue-200',
'bg-gradient-to-br from-green-50 via-green-25 to-white border-2 border-green-200',
'bg-gradient-to-br from-purple-50 via-purple-25 to-white border-2 border-purple-200',
'bg-gradient-to-br from-orange-50 via-orange-25 to-white border-2 border-orange-200'
];
return colorClasses[colorIndex];
};
// 方法
const validateField = (field, value, validationFn, errorMessage) => {
if (isHasInput(field) && !validationFn(value)) {
showToast({ message: errorMessage });
return false;
}
return true;
};
const MAX_VLPHOTO_SIZE = 3 * 1024 * 1024; // 3MB
function onVlphotoFileChange(e) {
const file = e.target?.files?.[0];
if (!file) return;
if (file.size > MAX_VLPHOTO_SIZE) {
showToast({ message: '图片不能超过 3M' });
e.target.value = '';
return;
}
const reader = new FileReader();
reader.onload = () => {
const dataUrl = reader.result;
let base64 = dataUrl;
if (typeof base64 === 'string' && base64.includes(',')) {
base64 = base64.split(',')[1];
}
formData.vlphotoData = base64;
vlphotoFileName.value = file.name;
vlphotoPreviewUrl.value = dataUrl;
};
reader.readAsDataURL(file);
e.target.value = '';
}
const MAX_PHOTO_DATA_SIZE = 500 * 1024; // 500KB 公安三要素
function onPhotoDataFileChange(e) {
const file = e.target?.files?.[0];
if (!file) return;
// 大小校验
if (file.size > MAX_PHOTO_DATA_SIZE) {
showToast({ message: '人像图片不能超过 500KB' });
e.target.value = '';
return;
}
const reader = new FileReader();
reader.onload = () => {
const dataUrl = reader.result;
if (typeof dataUrl !== 'string') {
showToast({ message: '图片读取失败,请重试' });
e.target.value = '';
return;
}
// 尺寸校验:宽高需在 15px ~ 4000px 之间
const img = new Image();
img.onload = () => {
const w = img.width;
const h = img.height;
if (w < 15 || h < 15 || w > 4000 || h > 4000) {
showToast({ message: '人像图片宽高需在 15px ~ 4000px 之间' });
photoDataFileName.value = '';
photoDataPreviewUrl.value = '';
e.target.value = '';
return;
}
let base64 = dataUrl;
if (typeof base64 === 'string' && base64.includes(',')) {
base64 = base64.split(',')[1];
}
formData.photoData = base64;
photoDataFileName.value = file.name;
photoDataPreviewUrl.value = dataUrl;
e.target.value = '';
};
img.onerror = () => {
showToast({ message: '图片格式不正确,请选择 jpg 或 png 图片' });
e.target.value = '';
};
img.src = dataUrl;
};
reader.readAsDataURL(file);
}
async function onImageUrlFileChange(e) {
const file = e.target?.files?.[0];
if (!file) return;
if (file.size > MAX_VLPHOTO_SIZE) {
showToast({ message: '图片不能超过 3M' });
e.target.value = '';
return;
}
const reader = new FileReader();
reader.onload = async () => {
let base64 = reader.result;
if (typeof base64 === 'string' && base64.includes(',')) {
base64 = base64.split(',')[1];
}
imageUrlUploading.value = true;
try {
const { data, error } = await useApiFetch('/upload/image')
.post({ image_base64: base64 })
.json();
if (data.value && data.value.code === 200 && data.value.data?.url) {
formData.imageUrl = data.value.data.url;
} else {
showToast({ message: (data.value?.msg || error.value?.message) || '上传失败' });
}
} finally {
imageUrlUploading.value = false;
}
e.target.value = '';
};
reader.readAsDataURL(file);
}
function onImageUrlError() {
formData.imageUrl = '';
}
function openFirstRegistrationDatePicker() {
if (formData.firstRegistrationDate) {
const [y, m] = (formData.firstRegistrationDate || "").split("-");
if (y && m) {
firstRegistrationDateColumns.value = [y, m];
} else {
const now = new Date();
const year = String(now.getFullYear());
const month = String(now.getMonth() + 1).padStart(2, "0");
firstRegistrationDateColumns.value = [year, month];
}
} else {
const now = new Date();
const year = String(now.getFullYear());
const month = String(now.getMonth() + 1).padStart(2, "0");
firstRegistrationDateColumns.value = [year, month];
}
showFirstRegistrationDatePicker.value = true;
}
function onFirstRegistrationDateConfirm({ selectedValues }) {
const [year, month] = selectedValues || [];
if (year && month) {
formData.firstRegistrationDate = `${year}-${month}`;
}
showFirstRegistrationDatePicker.value = false;
}
function onUserTypeConfirm({ selectedOptions }) {
const option = selectedOptions?.[0];
if (option) {
formData.userType = option.value;
}
showUserTypePicker.value = false;
}
function onAuthorizedConfirm({ selectedOptions }) {
const option = selectedOptions?.[0];
if (option) {
formData.authorized = option.value;
}
showAuthorizedPicker.value = false;
}
// 处理绑定手机号成功的回调
function handleBindSuccess() {
if (pendingPayment.value) {
pendingPayment.value = false;
submitRequest();
}
}
// 处理登录成功的回调
function handleLoginSuccess() {
if (pendingPayment.value) {
pendingPayment.value = false;
submitRequest();
}
}
// 处理输入框点击事件
const handleInputClick = async () => {
if (!isLoggedIn.value) {
// 非微信浏览器环境:未登录用户提示打开登录弹窗
if (!isWeChat.value) {
try {
await showConfirmDialog({
title: '提示',
message: '您需要登录后才能进行查询,是否立即登录?',
confirmButtonText: '立即登录',
cancelButtonText: '取消',
});
dialogStore.openLogin();
} catch {
// 用户点击取消,什么都不做
}
}
} else {
// 微信浏览器环境:已登录但检查是否需要绑定手机号
if (isWeChat.value && !userStore.mobile) {
dialogStore.openBindPhone();
}
}
};
function handleSubmit() {
// 非微信浏览器环境:检查登录状态
if (!isWeChat.value && !isLoggedIn.value) {
dialogStore.openLogin();
return;
}
// 基本协议验证
if (!formData.agreeToTerms) {
showToast({ message: `请阅读并同意用户协议和隐私政策` });
return;
}
if (
!validateField("name", formData.name, (v) => v, "请输入姓名") ||
!validateField(
"mobile",
formData.mobile,
(v) => isPhoneNumberValid.value,
"请输入有效的手机号"
) ||
!validateField(
"idCard",
formData.idCard,
(v) => isIdCardValid.value,
"请输入有效的身份证号码"
) ||
!validateField(
"entName",
formData.entName,
(v) => v,
"请输入企业名称"
) ||
!validateField(
"entCode",
formData.entCode,
(v) => isCreditCodeValid.value,
"请输入统一社会信用代码"
) ||
!validateField(
"nameMan",
formData.nameMan,
(v) => v,
"请输入男方姓名"
) ||
!validateField(
"idCardMan",
formData.idCardMan,
(v) => isIdCardManValid.value,
"请输入有效的男方身份证号码"
) ||
!validateField(
"nameWoman",
formData.nameWoman,
(v) => v,
"请输入女方姓名"
) ||
!validateField(
"idCardWoman",
formData.idCardWoman,
(v) => isIdCardWomanValid.value,
"请输入有效的女方身份证号码"
) ||
!validateField(
"carLicense",
formData.carLicense,
(v) => v,
"请输入车牌号"
) ||
!validateField(
"carType",
formData.carType,
(v) => v,
"请输入号牌类型"
) ||
!validateField(
"vinCode",
formData.vinCode,
(v) => v && v.trim(),
"请输入车架号/VIN"
) ||
!validateField(
"imageUrl",
formData.imageUrl,
(v) => props.feature !== "toc_VehicleMileageMixed" || (v && v.trim()),
"请输入行驶证图片链接"
) ||
!validateField(
"vehicleLocation",
formData.vehicleLocation,
(v) => v && v.trim(),
"请输入车辆所在地区"
) ||
!validateField(
"firstRegistrationDate",
formData.firstRegistrationDate,
(v) => v && v.trim() && /^\d{4}-\d{2}$/.test(v.trim()),
"请输入初次登记日期(yyyy-MM)"
) ||
!validateField(
"vlphotoData",
formData.vlphotoData,
(v) => v && v.trim(),
"请提供行驶证图片(base64)"
) ||
!validateField(
"verificationCode",
formData.verificationCode,
(v) => v,
"请输入验证码"
)
) {
return;
}
// 检查是否需要绑定手机号
if (!userStore.mobile) {
pendingPayment.value = true;
dialogStore.openBindPhone();
} else if (isHasInput("verificationCode")) {
submitRequest();
} else {
// 无短信验证码时,查询前先过滑块
runWithCaptcha(
(captchaVerifyParam) => doQueryPost(captchaVerifyParam),
(res) => {
if (res?.code === 200 && res?.data) handleQueryResult(res);
}
);
}
}
function handleQueryResult(res) {
queryId.value = res.data.id;
if (props.type === "promotion") {
localStorage.setItem("token", res.data.accessToken);
localStorage.setItem("refreshAfter", res.data.refreshAfter);
localStorage.setItem("accessExpire", res.data.accessExpire);
}
showPayment.value = true;
emit("submit-success", res.data);
}
function doQueryPost(captchaVerifyParam) {
const req = buildRequestPayload();
const reqStr = JSON.stringify(req);
const encodeData = aesEncrypt(reqStr, "ff83609b2b24fc73196aac3d3dfb874f");
let apiUrl = "";
const requestData = { data: encodeData };
if (captchaVerifyParam) requestData.captchaVerifyParam = captchaVerifyParam;
if (props.type === "promotion") {
apiUrl = `/query/service_agent/${props.feature}`;
requestData.agent_identifier = props.linkIdentifier;
} else {
apiUrl = `/query/service/${props.feature}`;
}
return useApiFetch(apiUrl).post(requestData).json();
}
async function submitRequest() {
const { data, error } = await doQueryPost();
if (data.value?.code === 200 && data.value?.data) {
handleQueryResult(data.value);
}
}
function sendVerificationCode() {
if (isCountingDown.value || !isPhoneNumberValid.value) return;
if (!isPhoneNumberValid.value) {
showToast({ message: "请输入有效的手机号" });
return;
}
runWithCaptcha(
(captchaVerifyParam) =>
useApiFetch("/auth/sendSms")
.post({ mobile: formData.mobile, actionType: "query", captchaVerifyParam })
.json(),
(res) => {
if (res?.code === 200) {
showToast({ message: "验证码发送成功", type: "success" });
startCountdown();
nextTick(() => {
const el = document.getElementById("verificationCode");
if (el) el.focus();
});
} else {
showToast({ message: res?.msg || "验证码发送失败,请重试" });
}
}
);
}
let timer = null;
function startCountdown() {
isCountingDown.value = true;
countdown.value = 60;
timer = setInterval(() => {
if (countdown.value > 0) {
countdown.value--;
} else {
clearInterval(timer);
isCountingDown.value = false;
}
}, 1000);
}
function toUserAgreement() {
router.push(`/userAgreement`);
}
function toPrivacyPolicy() {
router.push(`/privacyPolicy`);
}
function toAuthorization() {
router.push(`/authorization`);
}
const toExample = () => {
router.push(`/example?feature=${props.feature}`);
};
const toHistory = () => {
router.push("/historyQuery");
};
// 生命周期
onMounted(async () => {
await loadBackgroundImage();
});
// 加载背景图片
const loadBackgroundImage = async () => {
console.log(' 加载背景图片loadBackgroundImage feature', props.feature);
const background = await loadProductBackground(props.feature);
if (background) {
productBackground.value = background;
isDefaultBackground.value = false;
} else {
// 未匹配到背景时,使用默认背景 bg_2.png
try {
const defaultBgModule = await import("@/assets/images/bg_2.png");
productBackground.value = defaultBgModule.default;
isDefaultBackground.value = true;
} catch (error) {
console.warn('Failed to load default background bg_2.png:', error);
productBackground.value = '';
isDefaultBackground.value = false;
}
}
};
onUnmounted(() => {
if (timer) {
clearInterval(timer);
}
});
watch(feature, async () => {
await loadBackgroundImage();
});
</script>
<style scoped>
/* 背景样式 */
.inquire-bg {
background-color: #D2DFFA;
min-height: 100vh;
position: relative;
}
/* 卡片样式优化 */
.card {
@apply shadow-lg rounded-xl p-6 transition-all hover:shadow-xl;
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.2);
box-shadow:
0 4px 6px -1px rgba(0, 0, 0, 0.1),
0 2px 4px -1px rgba(0, 0, 0, 0.06),
0 0 0 1px rgba(255, 255, 255, 0.05);
}
/* 按钮悬停效果 */
button:hover {
transform: translateY(-1px);
}
button:active {
transform: translateY(0);
}
/* 卡片容器样式 */
.card-container {
background: white;
border-radius: 8px;
padding: 16px;
box-shadow: 0px 0px 24px 0px #3F3F3F0F;
}
.card-container input::placeholder {
color: #DDDDDD;
}
/* 功能标签样式 */
.feature-tag {
background-color: var(--color-primary-light);
color: var(--color-primary);
padding: 6px 12px;
border-radius: 9999px;
font-size: 12px;
font-weight: 500;
display: flex;
align-items: center;
}
/* 功能标签圆点 */
.feature-dot {
width: 6px;
height: 6px;
background-color: var(--color-primary);
border-radius: 50%;
margin-right: 8px;
}
/* 行驶证形状上传框 */
.vlphoto-frame {
width: 100%;
max-width: 320px;
aspect-ratio: 1.55;
margin: 0 auto;
border-radius: 14px;
border: 2px dashed #c9c9c9;
background: linear-gradient(145deg, #f8f9fa 0%, #eef0f2 100%);
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
cursor: pointer;
transition: border-color 0.2s, background 0.2s;
outline: none;
}
.vlphoto-frame:hover {
border-color: var(--color-primary, #2563eb);
background: linear-gradient(145deg, #f0f4ff 0%, #e8eeff 100%);
}
.vlphoto-frame:focus-visible {
border-color: var(--color-primary, #2563eb);
box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.2);
}
.vlphoto-preview {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
}
.vlphoto-placeholder {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 8px;
color: #9ca3af;
}
.vlphoto-icon {
font-size: 2.5rem;
line-height: 1;
}
.vlphoto-hint {
font-size: 14px;
color: #6b7280;
}
</style>