feat: 更新 '@capp/eden' 依赖至 0.0.12,优化实名认证表单逻辑,添加状态管理和图片上传功能
This commit is contained in:
@@ -3,19 +3,49 @@ import { Camera, CameraResultType, CameraSource } from "@capacitor/camera";
|
||||
import { actionSheetController, toastController } from "@ionic/vue";
|
||||
import { cameraOutline, cardOutline, checkmarkCircleOutline, imageOutline, personOutline } from "ionicons/icons";
|
||||
import zod from "zod";
|
||||
import { client } from "@/api";
|
||||
import { client, safeClient } from "@/api";
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
const formData = ref({
|
||||
interface RealNameFormData {
|
||||
kycMethod: "id_card";
|
||||
documentName: string;
|
||||
documentNumber: string;
|
||||
fileId1: string;
|
||||
fileId2: string;
|
||||
}
|
||||
|
||||
const formData = ref<RealNameFormData>({
|
||||
kycMethod: "id_card",
|
||||
documentName: "",
|
||||
documentNumber: "",
|
||||
fileId1: "",
|
||||
fileId2: "",
|
||||
});
|
||||
const image1 = ref<string>("");
|
||||
const image2 = ref<string>("");
|
||||
|
||||
const isSubmitting = ref(false);
|
||||
const status = ref<"unverified" | "pending" | "approved" | "rejected">("unverified");
|
||||
|
||||
// 计算属性:是否可以编辑
|
||||
const canEdit = computed(() => {
|
||||
return status.value === "unverified" || status.value === "rejected";
|
||||
});
|
||||
|
||||
// 计算属性:状态文本和颜色
|
||||
const statusInfo = computed(() => {
|
||||
switch (status.value) {
|
||||
case "pending":
|
||||
return { text: "审核中", color: "#faad14", bg: "#fff7e6" };
|
||||
case "approved":
|
||||
return { text: "已通过", color: "#52c41a", bg: "#f6ffed" };
|
||||
case "rejected":
|
||||
return { text: "已拒绝", color: "#f5222d", bg: "#fff1f0" };
|
||||
default:
|
||||
return { text: "未认证", color: "#999", bg: "#f7f8fa" };
|
||||
}
|
||||
});
|
||||
|
||||
// 表单验证 Schema
|
||||
const RealNameSchema = zod.object({
|
||||
@@ -104,9 +134,11 @@ async function selectFromGallery(type: "front" | "back") {
|
||||
if (image.dataUrl) {
|
||||
if (type === "front") {
|
||||
formData.value.fileId1 = fileId;
|
||||
image1.value = image.dataUrl;
|
||||
}
|
||||
else {
|
||||
formData.value.fileId2 = fileId;
|
||||
image2.value = image.dataUrl;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -149,11 +181,9 @@ async function handleSubmit() {
|
||||
|
||||
isSubmitting.value = true;
|
||||
try {
|
||||
// TODO: 调用实名认证 API
|
||||
// const { data } = await safeClient(client.api.realname.post(formData.value));
|
||||
|
||||
// 模拟 API 调用
|
||||
await new Promise(resolve => setTimeout(resolve, 1500));
|
||||
await safeClient(client.api.kyc({ kycMethod: "id_card" }).post({
|
||||
...formData.value,
|
||||
}));
|
||||
|
||||
await showToast("实名认证提交成功,等待审核", "success");
|
||||
router.back();
|
||||
@@ -165,6 +195,22 @@ async function handleSubmit() {
|
||||
isSubmitting.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
const { data } = await safeClient(client.api.kyc({ kycMethod: "id_card" }).get());
|
||||
if (data.value) {
|
||||
formData.value.documentName = data.value.documentName || "";
|
||||
formData.value.documentNumber = data.value.documentNumber || "";
|
||||
formData.value.fileId1 = data.value.fileId1 || "";
|
||||
formData.value.fileId2 = data.value.fileId2 || "";
|
||||
status.value = data.value?.status || "unverified";
|
||||
}
|
||||
const { data: urls } = await safeClient(client.api.file_storage.access_urls.post({
|
||||
fileIds: [formData.value.fileId1, formData.value.fileId2],
|
||||
}));
|
||||
image1.value = urls.value?.[0].url || "";
|
||||
image2.value = urls.value?.[1].url || "";
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -180,8 +226,27 @@ async function handleSubmit() {
|
||||
|
||||
<ion-content class="ion-padding">
|
||||
<div class="space-y-5">
|
||||
<!-- 状态提示 -->
|
||||
<div v-if="status !== 'unverified'" class="status-card" :style="{ background: statusInfo.bg, borderColor: statusInfo.color }">
|
||||
<ion-icon :icon="checkmarkCircleOutline" class="text-2xl" :style="{ color: statusInfo.color }" />
|
||||
<div class="flex-1">
|
||||
<div class="text-sm font-semibold" :style="{ color: statusInfo.color }">
|
||||
认证状态:{{ statusInfo.text }}
|
||||
</div>
|
||||
<div v-if="status === 'pending'" class="text-xs text-[#666] mt-1">
|
||||
您的认证资料正在审核中,请耐心等待
|
||||
</div>
|
||||
<div v-else-if="status === 'approved'" class="text-xs text-[#666] mt-1">
|
||||
您已完成实名认证
|
||||
</div>
|
||||
<div v-else-if="status === 'rejected'" class="text-xs text-[#666] mt-1">
|
||||
认证未通过,请重新提交资料
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 提示信息 -->
|
||||
<div class="info-card">
|
||||
<div v-if="status === 'unverified'" class="info-card">
|
||||
<ion-icon :icon="checkmarkCircleOutline" class="text-2xl text-[#c41e3a]" />
|
||||
<div class="text-sm text-[#666] leading-relaxed">
|
||||
为了保障您的账户安全,请完成实名认证。您的个人信息将严格保密。
|
||||
@@ -203,6 +268,7 @@ async function handleSubmit() {
|
||||
type="text"
|
||||
placeholder="请输入真实姓名"
|
||||
class="custom-input"
|
||||
:disabled="!canEdit"
|
||||
/>
|
||||
</ion-item>
|
||||
</div>
|
||||
@@ -220,6 +286,7 @@ async function handleSubmit() {
|
||||
placeholder="请输入身份证号码"
|
||||
class="custom-input"
|
||||
:maxlength="18"
|
||||
:disabled="!canEdit"
|
||||
/>
|
||||
</ion-item>
|
||||
</div>
|
||||
@@ -232,7 +299,8 @@ async function handleSubmit() {
|
||||
</div>
|
||||
<div
|
||||
class="upload-box"
|
||||
@click="showImageSourceOptions('front')"
|
||||
:class="{ 'upload-disabled': !canEdit }"
|
||||
@click="canEdit && showImageSourceOptions('front')"
|
||||
>
|
||||
<div v-if="!formData.fileId1" class="upload-placeholder">
|
||||
<ion-icon :icon="cameraOutline" class="text-4xl text-[#999]" />
|
||||
@@ -245,7 +313,7 @@ async function handleSubmit() {
|
||||
</div>
|
||||
<img
|
||||
v-else
|
||||
:src="formData.fileId1"
|
||||
:src="image1"
|
||||
alt="身份证正面"
|
||||
class="uploaded-image"
|
||||
>
|
||||
@@ -260,7 +328,8 @@ async function handleSubmit() {
|
||||
</div>
|
||||
<div
|
||||
class="upload-box"
|
||||
@click="showImageSourceOptions('back')"
|
||||
:class="{ 'upload-disabled': !canEdit }"
|
||||
@click="canEdit && showImageSourceOptions('back')"
|
||||
>
|
||||
<div v-if="!formData.fileId2" class="upload-placeholder">
|
||||
<ion-icon :icon="cameraOutline" class="text-4xl text-[#999]" />
|
||||
@@ -273,7 +342,7 @@ async function handleSubmit() {
|
||||
</div>
|
||||
<img
|
||||
v-else
|
||||
:src="formData.fileId2"
|
||||
:src="image2"
|
||||
alt="身份证反面"
|
||||
class="uploaded-image"
|
||||
>
|
||||
@@ -284,13 +353,14 @@ async function handleSubmit() {
|
||||
|
||||
<!-- 提交按钮 -->
|
||||
<ion-button
|
||||
v-if="canEdit"
|
||||
expand="block"
|
||||
class="submit-button"
|
||||
:disabled="isSubmitting"
|
||||
@click="handleSubmit"
|
||||
>
|
||||
<ion-spinner v-if="isSubmitting" name="crescent" />
|
||||
<span v-else>提交认证</span>
|
||||
<span v-else>{{ status === 'rejected' ? '重新提交' : '提交认证' }}</span>
|
||||
</ion-button>
|
||||
|
||||
<!-- 注意事项 -->
|
||||
@@ -369,6 +439,21 @@ async function handleSubmit() {
|
||||
border-color: var(--ion-color-primary);
|
||||
}
|
||||
|
||||
.upload-disabled {
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.status-card {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 12px;
|
||||
padding: 16px;
|
||||
border-radius: 12px;
|
||||
border: 1px solid;
|
||||
}
|
||||
|
||||
.upload-placeholder {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
<script lang='ts' setup>
|
||||
import { toastController } from "@ionic/vue";
|
||||
import { checkmarkCircleOutline, chevronForwardOutline, closeCircleOutline, keyOutline, lockClosedOutline, shieldCheckmarkOutline } from "ionicons/icons";
|
||||
import { client, safeClient } from "@/api";
|
||||
|
||||
const router = useRouter();
|
||||
const { data: status } = await safeClient(() => client.api.user.security["transaction-password"].status.get());
|
||||
|
||||
// 交易密码是否已启用
|
||||
const hasPaymentPassword = ref(true);
|
||||
@@ -82,15 +84,15 @@ async function handleTogglePaymentPassword() {
|
||||
</div>
|
||||
<div class="item-right">
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="status-badge" :class="hasPaymentPassword ? 'enabled' : 'disabled'">
|
||||
<div class="status-badge" :class="status?.enabled ? 'enabled' : 'disabled'">
|
||||
<ion-icon
|
||||
:icon="hasPaymentPassword ? checkmarkCircleOutline : closeCircleOutline"
|
||||
:icon="status?.enabled ? checkmarkCircleOutline : closeCircleOutline"
|
||||
class="text-sm"
|
||||
/>
|
||||
<span>{{ hasPaymentPassword ? '已启用' : '未启用' }}</span>
|
||||
<span>{{ status?.enabled ? '已启用' : '未启用' }}</span>
|
||||
</div>
|
||||
<ion-toggle
|
||||
:checked="hasPaymentPassword"
|
||||
:checked="status?.enabled"
|
||||
color="danger"
|
||||
@ion-change="handleTogglePaymentPassword"
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user