feat: 更新上传功能,返回文件ID和公共URL;优化实名认证表单数据处理

This commit is contained in:
2026-01-20 01:31:26 +07:00
parent 4fd3a691ee
commit 9710c80a1b
3 changed files with 23 additions and 31 deletions

8
components.d.ts vendored
View File

@@ -21,7 +21,6 @@ declare module 'vue' {
IonButtons: typeof import('@ionic/vue')['IonButtons']
IonCheckbox: typeof import('@ionic/vue')['IonCheckbox']
IonContent: typeof import('@ionic/vue')['IonContent']
IonDatetime: typeof import('@ionic/vue')['IonDatetime']
IonHeader: typeof import('@ionic/vue')['IonHeader']
IonIcon: typeof import('@ionic/vue')['IonIcon']
IonInfiniteScroll: typeof import('@ionic/vue')['IonInfiniteScroll']
@@ -29,15 +28,12 @@ declare module 'vue' {
IonInput: typeof import('@ionic/vue')['IonInput']
IonItem: typeof import('@ionic/vue')['IonItem']
IonLabel: typeof import('@ionic/vue')['IonLabel']
IonList: typeof import('@ionic/vue')['IonList']
IonPage: typeof import('@ionic/vue')['IonPage']
IonRefresher: typeof import('@ionic/vue')['IonRefresher']
IonRefresherContent: typeof import('@ionic/vue')['IonRefresherContent']
IonRouterOutlet: typeof import('@ionic/vue')['IonRouterOutlet']
IonSegment: typeof import('@ionic/vue')['IonSegment']
IonSegmentButton: typeof import('@ionic/vue')['IonSegmentButton']
IonSelect: typeof import('@ionic/vue')['IonSelect']
IonSelectOption: typeof import('@ionic/vue')['IonSelectOption']
IonSpinner: typeof import('@ionic/vue')['IonSpinner']
IonTabBar: typeof import('@ionic/vue')['IonTabBar']
IonTabButton: typeof import('@ionic/vue')['IonTabButton']
@@ -63,7 +59,6 @@ declare global {
const IonButtons: typeof import('@ionic/vue')['IonButtons']
const IonCheckbox: typeof import('@ionic/vue')['IonCheckbox']
const IonContent: typeof import('@ionic/vue')['IonContent']
const IonDatetime: typeof import('@ionic/vue')['IonDatetime']
const IonHeader: typeof import('@ionic/vue')['IonHeader']
const IonIcon: typeof import('@ionic/vue')['IonIcon']
const IonInfiniteScroll: typeof import('@ionic/vue')['IonInfiniteScroll']
@@ -71,15 +66,12 @@ declare global {
const IonInput: typeof import('@ionic/vue')['IonInput']
const IonItem: typeof import('@ionic/vue')['IonItem']
const IonLabel: typeof import('@ionic/vue')['IonLabel']
const IonList: typeof import('@ionic/vue')['IonList']
const IonPage: typeof import('@ionic/vue')['IonPage']
const IonRefresher: typeof import('@ionic/vue')['IonRefresher']
const IonRefresherContent: typeof import('@ionic/vue')['IonRefresherContent']
const IonRouterOutlet: typeof import('@ionic/vue')['IonRouterOutlet']
const IonSegment: typeof import('@ionic/vue')['IonSegment']
const IonSegmentButton: typeof import('@ionic/vue')['IonSegmentButton']
const IonSelect: typeof import('@ionic/vue')['IonSelect']
const IonSelectOption: typeof import('@ionic/vue')['IonSelectOption']
const IonSpinner: typeof import('@ionic/vue')['IonSpinner']
const IonTabBar: typeof import('@ionic/vue')['IonTabBar']
const IonTabButton: typeof import('@ionic/vue')['IonTabButton']

View File

@@ -9,21 +9,22 @@ interface UploadOptions {
export type UploadFetchOptions = TreatyBody<typeof client.api.file_storage.upload_url.post>;
export async function uploadToS3(file: File, options: UploadOptions): Promise<string> {
export async function uploadToS3(file: File, options: UploadOptions): Promise<{ fileId: string; publicUrl: string }> {
const { onProgress, signal, fetchOptions } = options;
// 1. 获取预签名 URL
const { data, error } = await safeClient(client.api.file_storage.upload_url.post({
...fetchOptions,
accessControl: "public",
}));
if (error.value || !data.value) {
throw new Error("获取上传 URL 失败");
}
const { fileId, uploadUrl, method, headers } = toRefs(data.value);
const { fileId, uploadUrl, method, headers, publicUrl } = toRefs(data.value);
// 2. 上传文件到 S3
return new Promise((resolve, reject) => {
return new Promise<{ fileId: string; publicUrl: string }>((resolve, reject) => {
const xhr = new XMLHttpRequest();
// 监听上传进度
@@ -37,7 +38,10 @@ export async function uploadToS3(file: File, options: UploadOptions): Promise<st
// 上传成功
xhr.addEventListener("load", () => {
if (xhr.status >= 200 && xhr.status < 300) {
resolve(fileId.value);
resolve({
fileId: fileId.value,
publicUrl: publicUrl!.value!,
});
}
else {
reject(new Error(`上传失败: ${xhr.status} ${xhr.statusText}`));

View File

@@ -8,7 +8,6 @@ import { client, safeClient } from "@/api";
const router = useRouter();
interface RealNameFormData {
kycMethod: "id_card";
documentName: string;
documentNumber: string;
fileId1: string;
@@ -16,14 +15,11 @@ interface RealNameFormData {
}
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");
@@ -123,7 +119,7 @@ async function selectFromGallery(type: "front" | "back") {
"upload.jpeg",
{ type: "image/jpeg" },
);
const fileId = await uploadToS3(file, {
const { publicUrl } = await uploadToS3(file, {
fetchOptions: {
fileName: `idCard_${Date.now()}.jpeg`,
fileSize: image.dataUrl?.length || 0,
@@ -133,12 +129,10 @@ async function selectFromGallery(type: "front" | "back") {
if (image.dataUrl) {
if (type === "front") {
formData.value.fileId1 = fileId;
image1.value = image.dataUrl;
formData.value.fileId1 = publicUrl;
}
else {
formData.value.fileId2 = fileId;
image2.value = image.dataUrl;
formData.value.fileId2 = publicUrl;
}
}
}
@@ -181,9 +175,16 @@ async function handleSubmit() {
isSubmitting.value = true;
try {
await safeClient(client.api.kyc({ kycMethod: "id_card" }).post({
...formData.value,
}));
if (status.value !== "unverified") {
await safeClient(client.api.kyc({ kycMethod: "id_card" }).put({
...formData.value,
}));
}
else {
await safeClient(client.api.kyc({ kycMethod: "id_card" }).post({
...formData.value,
}));
}
await showToast("实名认证提交成功,等待审核", "success");
router.back();
@@ -205,11 +206,6 @@ onMounted(async () => {
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>
@@ -313,7 +309,7 @@ onMounted(async () => {
</div>
<img
v-else
:src="image1"
:src="formData.fileId1"
alt="身份证正面"
class="uploaded-image"
>
@@ -342,7 +338,7 @@ onMounted(async () => {
</div>
<img
v-else
:src="image2"
:src="formData.fileId2"
alt="身份证反面"
class="uploaded-image"
>