初始版本
This commit is contained in:
262
app/pages/console/account/kyc.vue
Normal file
262
app/pages/console/account/kyc.vue
Normal file
@@ -0,0 +1,262 @@
|
||||
<template>
|
||||
<div class="space-y-4">
|
||||
<a-page-header title="实名认证" sub-title="基于阿里云实人认证的身份核验">
|
||||
<template #extra>
|
||||
<a-space>
|
||||
<a-tag v-if="verifyStatus !== 'none'" :color="statusTagColor">{{ statusText }}</a-tag>
|
||||
</a-space>
|
||||
</template>
|
||||
</a-page-header>
|
||||
|
||||
<!-- 已认证状态 -->
|
||||
<a-card v-if="verifyStatus === 'approved'" :bordered="false" class="card">
|
||||
<a-result status="success" title="已完成实名认证" sub-title="您的身份信息已通过核验">
|
||||
<template #extra>
|
||||
<a-descriptions :column="1" size="small" bordered class="max-w-md mx-auto">
|
||||
<a-descriptions-item label="认证类型">个人</a-descriptions-item>
|
||||
<a-descriptions-item label="姓名">{{ maskedRealName }}</a-descriptions-item>
|
||||
<a-descriptions-item label="身份证号">{{ maskedIdCard }}</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
</template>
|
||||
</a-result>
|
||||
</a-card>
|
||||
|
||||
<!-- 未认证 / 认证表单 -->
|
||||
<a-card v-else :bordered="false" class="card">
|
||||
<a-alert
|
||||
show-icon
|
||||
:type="verifyStatus === 'rejected' ? 'error' : 'info'"
|
||||
:message="alertMessage"
|
||||
:description="alertDescription"
|
||||
class="mb-6"
|
||||
/>
|
||||
|
||||
<div class="max-w-lg">
|
||||
<a-form
|
||||
ref="formRef"
|
||||
layout="vertical"
|
||||
:model="form"
|
||||
:rules="rules"
|
||||
:disabled="submitting"
|
||||
@finish="handleVerify"
|
||||
>
|
||||
<a-form-item label="真实姓名" name="realName">
|
||||
<a-input
|
||||
v-model:value="form.realName"
|
||||
placeholder="请输入身份证上的真实姓名"
|
||||
size="large"
|
||||
allow-clear
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="身份证号码" name="idCard">
|
||||
<a-input
|
||||
v-model:value="form.idCard"
|
||||
placeholder="请输入18位身份证号码"
|
||||
size="large"
|
||||
:maxlength="18"
|
||||
allow-clear
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item>
|
||||
<div class="flex items-center gap-3 pt-2">
|
||||
<a-button
|
||||
type="primary"
|
||||
html-type="submit"
|
||||
size="large"
|
||||
:loading="submitting"
|
||||
:disabled="submitting"
|
||||
>
|
||||
{{ submitting ? '核验中...' : '开始核验' }}
|
||||
</a-button>
|
||||
<a-button size="large" @click="resetForm" :disabled="submitting">重置</a-button>
|
||||
</div>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
|
||||
<a-alert
|
||||
type="warning"
|
||||
show-icon
|
||||
message="温馨提示"
|
||||
description="实名认证基于阿里云实人认证服务,您的姓名与身份证号将进行二要素核验。请确保信息真实有效,核验通过后不可更改。"
|
||||
class="mt-6"
|
||||
/>
|
||||
</div>
|
||||
</a-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, reactive, ref } from 'vue'
|
||||
import { message, type FormInstance } from 'ant-design-vue'
|
||||
import { getUserInfo } from '@/api/layout'
|
||||
import { verifyIdCard } from '@/api/system/idVerification'
|
||||
import { listUserVerify } from '@/api/system/userVerify'
|
||||
import type { UserVerify } from '@/api/system/userVerify/model'
|
||||
|
||||
definePageMeta({ layout: 'console' })
|
||||
|
||||
type VerifyStatus = 'none' | 'approved' | 'rejected'
|
||||
|
||||
const verifyStatus = ref<VerifyStatus>('none')
|
||||
const submitting = ref(false)
|
||||
const currentRecord = ref<UserVerify | null>(null)
|
||||
|
||||
// 表单
|
||||
const formRef = ref<FormInstance>()
|
||||
const form = reactive({
|
||||
realName: '',
|
||||
idCard: ''
|
||||
})
|
||||
|
||||
const idCardRegex = /^[1-9]\d{5}(?:19|20)\d{2}(?:0[1-9]|1[0-2])(?:0[1-9]|[12]\d|3[01])\d{3}[\dXx]$/
|
||||
|
||||
const rules = {
|
||||
realName: [
|
||||
{ required: true, message: '请输入真实姓名', type: 'string' },
|
||||
{ min: 2, max: 20, message: '姓名长度2-20个字符', type: 'string' }
|
||||
],
|
||||
idCard: [
|
||||
{ required: true, message: '请输入身份证号码', type: 'string' },
|
||||
{
|
||||
validator: (_rule: unknown, value: string) => {
|
||||
if (!value) return Promise.resolve()
|
||||
const normalized = value.trim().toUpperCase()
|
||||
if (!idCardRegex.test(normalized)) {
|
||||
return Promise.reject(new Error('请输入正确的18位身份证号码'))
|
||||
}
|
||||
return Promise.resolve()
|
||||
},
|
||||
trigger: 'blur'
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
// 状态展示
|
||||
const statusText = computed(() => {
|
||||
if (verifyStatus.value === 'approved') return '已认证'
|
||||
if (verifyStatus.value === 'rejected') return '核验失败'
|
||||
return '未认证'
|
||||
})
|
||||
|
||||
const statusTagColor = computed(() => {
|
||||
if (verifyStatus.value === 'approved') return 'green'
|
||||
if (verifyStatus.value === 'rejected') return 'red'
|
||||
return 'default'
|
||||
})
|
||||
|
||||
const alertMessage = computed(() => {
|
||||
if (verifyStatus.value === 'rejected') {
|
||||
return '身份核验未通过'
|
||||
}
|
||||
return '请填写身份信息完成实名认证'
|
||||
})
|
||||
|
||||
const alertDescription = computed(() => {
|
||||
if (verifyStatus.value === 'rejected') {
|
||||
const reason = (currentRecord.value?.description || '').trim()
|
||||
return reason
|
||||
? `上一次核验失败原因:${reason}。请核实姓名和身份证号后重新提交。`
|
||||
: '请核实姓名和身份证号后重新提交。'
|
||||
}
|
||||
return '请输入您的真实姓名和身份证号码,系统将通过阿里云实人认证进行二要素核验。'
|
||||
})
|
||||
|
||||
// 脱敏展示
|
||||
const maskedRealName = computed(() => {
|
||||
const name = currentRecord.value?.realName || ''
|
||||
if (name.length <= 1) return name
|
||||
return name[0] + '*'.repeat(name.length - 1)
|
||||
})
|
||||
|
||||
const maskedIdCard = computed(() => {
|
||||
const id = currentRecord.value?.idCard || ''
|
||||
if (id.length < 8) return id
|
||||
return id.substring(0, 4) + '**********' + id.substring(id.length - 4)
|
||||
})
|
||||
|
||||
// 加载已有认证状态
|
||||
async function loadVerifyStatus() {
|
||||
try {
|
||||
const user = await getUserInfo()
|
||||
const userId = user.userId
|
||||
if (!userId) return
|
||||
|
||||
const list = await listUserVerify({ userId })
|
||||
const latest = Array.isArray(list)
|
||||
? [...list].sort((a, b) => Number(b.id ?? 0) - Number(a.id ?? 0))[0]
|
||||
: undefined
|
||||
|
||||
if (latest) {
|
||||
currentRecord.value = latest
|
||||
verifyStatus.value = latest.status === 1 ? 'approved' : 'rejected'
|
||||
}
|
||||
} catch {
|
||||
// 静默处理,默认显示未认证
|
||||
}
|
||||
}
|
||||
|
||||
// 提交核验
|
||||
async function handleVerify() {
|
||||
const realName = form.realName.trim()
|
||||
const idCard = form.idCard.trim().toUpperCase()
|
||||
|
||||
if (!realName || !idCard) {
|
||||
message.warning('请填写完整的身份信息')
|
||||
return
|
||||
}
|
||||
|
||||
if (!idCardRegex.test(idCard)) {
|
||||
message.warning('请输入正确的18位身份证号码')
|
||||
return
|
||||
}
|
||||
|
||||
submitting.value = true
|
||||
try {
|
||||
const result = await verifyIdCard(realName, idCard)
|
||||
|
||||
if (result.isMatch) {
|
||||
verifyStatus.value = 'approved'
|
||||
currentRecord.value = {
|
||||
realName,
|
||||
idCard,
|
||||
status: 1,
|
||||
type: 0
|
||||
}
|
||||
message.success('实名认证通过!您的身份信息已核验成功。')
|
||||
} else {
|
||||
verifyStatus.value = 'rejected'
|
||||
currentRecord.value = {
|
||||
realName,
|
||||
idCard,
|
||||
status: 2,
|
||||
type: 0,
|
||||
description: result.message || '身份信息不一致'
|
||||
}
|
||||
message.error(result.message || '身份信息不一致,请核实后重新填写')
|
||||
}
|
||||
} catch (e) {
|
||||
const errMsg = e instanceof Error ? e.message : '核验服务异常,请稍后重试'
|
||||
message.error(errMsg)
|
||||
} finally {
|
||||
submitting.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function resetForm() {
|
||||
form.realName = ''
|
||||
form.idCard = ''
|
||||
formRef.value?.clearValidate()
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadVerifyStatus()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.card {
|
||||
border-radius: 12px;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user