feat: 更新上传功能,返回文件ID和公共URL;优化实名认证表单数据处理
This commit is contained in:
8
components.d.ts
vendored
8
components.d.ts
vendored
@@ -21,7 +21,6 @@ declare module 'vue' {
|
|||||||
IonButtons: typeof import('@ionic/vue')['IonButtons']
|
IonButtons: typeof import('@ionic/vue')['IonButtons']
|
||||||
IonCheckbox: typeof import('@ionic/vue')['IonCheckbox']
|
IonCheckbox: typeof import('@ionic/vue')['IonCheckbox']
|
||||||
IonContent: typeof import('@ionic/vue')['IonContent']
|
IonContent: typeof import('@ionic/vue')['IonContent']
|
||||||
IonDatetime: typeof import('@ionic/vue')['IonDatetime']
|
|
||||||
IonHeader: typeof import('@ionic/vue')['IonHeader']
|
IonHeader: typeof import('@ionic/vue')['IonHeader']
|
||||||
IonIcon: typeof import('@ionic/vue')['IonIcon']
|
IonIcon: typeof import('@ionic/vue')['IonIcon']
|
||||||
IonInfiniteScroll: typeof import('@ionic/vue')['IonInfiniteScroll']
|
IonInfiniteScroll: typeof import('@ionic/vue')['IonInfiniteScroll']
|
||||||
@@ -29,15 +28,12 @@ declare module 'vue' {
|
|||||||
IonInput: typeof import('@ionic/vue')['IonInput']
|
IonInput: typeof import('@ionic/vue')['IonInput']
|
||||||
IonItem: typeof import('@ionic/vue')['IonItem']
|
IonItem: typeof import('@ionic/vue')['IonItem']
|
||||||
IonLabel: typeof import('@ionic/vue')['IonLabel']
|
IonLabel: typeof import('@ionic/vue')['IonLabel']
|
||||||
IonList: typeof import('@ionic/vue')['IonList']
|
|
||||||
IonPage: typeof import('@ionic/vue')['IonPage']
|
IonPage: typeof import('@ionic/vue')['IonPage']
|
||||||
IonRefresher: typeof import('@ionic/vue')['IonRefresher']
|
IonRefresher: typeof import('@ionic/vue')['IonRefresher']
|
||||||
IonRefresherContent: typeof import('@ionic/vue')['IonRefresherContent']
|
IonRefresherContent: typeof import('@ionic/vue')['IonRefresherContent']
|
||||||
IonRouterOutlet: typeof import('@ionic/vue')['IonRouterOutlet']
|
IonRouterOutlet: typeof import('@ionic/vue')['IonRouterOutlet']
|
||||||
IonSegment: typeof import('@ionic/vue')['IonSegment']
|
IonSegment: typeof import('@ionic/vue')['IonSegment']
|
||||||
IonSegmentButton: typeof import('@ionic/vue')['IonSegmentButton']
|
IonSegmentButton: typeof import('@ionic/vue')['IonSegmentButton']
|
||||||
IonSelect: typeof import('@ionic/vue')['IonSelect']
|
|
||||||
IonSelectOption: typeof import('@ionic/vue')['IonSelectOption']
|
|
||||||
IonSpinner: typeof import('@ionic/vue')['IonSpinner']
|
IonSpinner: typeof import('@ionic/vue')['IonSpinner']
|
||||||
IonTabBar: typeof import('@ionic/vue')['IonTabBar']
|
IonTabBar: typeof import('@ionic/vue')['IonTabBar']
|
||||||
IonTabButton: typeof import('@ionic/vue')['IonTabButton']
|
IonTabButton: typeof import('@ionic/vue')['IonTabButton']
|
||||||
@@ -63,7 +59,6 @@ declare global {
|
|||||||
const IonButtons: typeof import('@ionic/vue')['IonButtons']
|
const IonButtons: typeof import('@ionic/vue')['IonButtons']
|
||||||
const IonCheckbox: typeof import('@ionic/vue')['IonCheckbox']
|
const IonCheckbox: typeof import('@ionic/vue')['IonCheckbox']
|
||||||
const IonContent: typeof import('@ionic/vue')['IonContent']
|
const IonContent: typeof import('@ionic/vue')['IonContent']
|
||||||
const IonDatetime: typeof import('@ionic/vue')['IonDatetime']
|
|
||||||
const IonHeader: typeof import('@ionic/vue')['IonHeader']
|
const IonHeader: typeof import('@ionic/vue')['IonHeader']
|
||||||
const IonIcon: typeof import('@ionic/vue')['IonIcon']
|
const IonIcon: typeof import('@ionic/vue')['IonIcon']
|
||||||
const IonInfiniteScroll: typeof import('@ionic/vue')['IonInfiniteScroll']
|
const IonInfiniteScroll: typeof import('@ionic/vue')['IonInfiniteScroll']
|
||||||
@@ -71,15 +66,12 @@ declare global {
|
|||||||
const IonInput: typeof import('@ionic/vue')['IonInput']
|
const IonInput: typeof import('@ionic/vue')['IonInput']
|
||||||
const IonItem: typeof import('@ionic/vue')['IonItem']
|
const IonItem: typeof import('@ionic/vue')['IonItem']
|
||||||
const IonLabel: typeof import('@ionic/vue')['IonLabel']
|
const IonLabel: typeof import('@ionic/vue')['IonLabel']
|
||||||
const IonList: typeof import('@ionic/vue')['IonList']
|
|
||||||
const IonPage: typeof import('@ionic/vue')['IonPage']
|
const IonPage: typeof import('@ionic/vue')['IonPage']
|
||||||
const IonRefresher: typeof import('@ionic/vue')['IonRefresher']
|
const IonRefresher: typeof import('@ionic/vue')['IonRefresher']
|
||||||
const IonRefresherContent: typeof import('@ionic/vue')['IonRefresherContent']
|
const IonRefresherContent: typeof import('@ionic/vue')['IonRefresherContent']
|
||||||
const IonRouterOutlet: typeof import('@ionic/vue')['IonRouterOutlet']
|
const IonRouterOutlet: typeof import('@ionic/vue')['IonRouterOutlet']
|
||||||
const IonSegment: typeof import('@ionic/vue')['IonSegment']
|
const IonSegment: typeof import('@ionic/vue')['IonSegment']
|
||||||
const IonSegmentButton: typeof import('@ionic/vue')['IonSegmentButton']
|
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 IonSpinner: typeof import('@ionic/vue')['IonSpinner']
|
||||||
const IonTabBar: typeof import('@ionic/vue')['IonTabBar']
|
const IonTabBar: typeof import('@ionic/vue')['IonTabBar']
|
||||||
const IonTabButton: typeof import('@ionic/vue')['IonTabButton']
|
const IonTabButton: typeof import('@ionic/vue')['IonTabButton']
|
||||||
|
|||||||
@@ -9,21 +9,22 @@ interface UploadOptions {
|
|||||||
|
|
||||||
export type UploadFetchOptions = TreatyBody<typeof client.api.file_storage.upload_url.post>;
|
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;
|
const { onProgress, signal, fetchOptions } = options;
|
||||||
|
|
||||||
// 1. 获取预签名 URL
|
// 1. 获取预签名 URL
|
||||||
const { data, error } = await safeClient(client.api.file_storage.upload_url.post({
|
const { data, error } = await safeClient(client.api.file_storage.upload_url.post({
|
||||||
...fetchOptions,
|
...fetchOptions,
|
||||||
|
accessControl: "public",
|
||||||
}));
|
}));
|
||||||
|
|
||||||
if (error.value || !data.value) {
|
if (error.value || !data.value) {
|
||||||
throw new Error("获取上传 URL 失败");
|
throw new Error("获取上传 URL 失败");
|
||||||
}
|
}
|
||||||
const { fileId, uploadUrl, method, headers } = toRefs(data.value);
|
const { fileId, uploadUrl, method, headers, publicUrl } = toRefs(data.value);
|
||||||
|
|
||||||
// 2. 上传文件到 S3
|
// 2. 上传文件到 S3
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise<{ fileId: string; publicUrl: string }>((resolve, reject) => {
|
||||||
const xhr = new XMLHttpRequest();
|
const xhr = new XMLHttpRequest();
|
||||||
|
|
||||||
// 监听上传进度
|
// 监听上传进度
|
||||||
@@ -37,7 +38,10 @@ export async function uploadToS3(file: File, options: UploadOptions): Promise<st
|
|||||||
// 上传成功
|
// 上传成功
|
||||||
xhr.addEventListener("load", () => {
|
xhr.addEventListener("load", () => {
|
||||||
if (xhr.status >= 200 && xhr.status < 300) {
|
if (xhr.status >= 200 && xhr.status < 300) {
|
||||||
resolve(fileId.value);
|
resolve({
|
||||||
|
fileId: fileId.value,
|
||||||
|
publicUrl: publicUrl!.value!,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
reject(new Error(`上传失败: ${xhr.status} ${xhr.statusText}`));
|
reject(new Error(`上传失败: ${xhr.status} ${xhr.statusText}`));
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import { client, safeClient } from "@/api";
|
|||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
interface RealNameFormData {
|
interface RealNameFormData {
|
||||||
kycMethod: "id_card";
|
|
||||||
documentName: string;
|
documentName: string;
|
||||||
documentNumber: string;
|
documentNumber: string;
|
||||||
fileId1: string;
|
fileId1: string;
|
||||||
@@ -16,14 +15,11 @@ interface RealNameFormData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const formData = ref<RealNameFormData>({
|
const formData = ref<RealNameFormData>({
|
||||||
kycMethod: "id_card",
|
|
||||||
documentName: "",
|
documentName: "",
|
||||||
documentNumber: "",
|
documentNumber: "",
|
||||||
fileId1: "",
|
fileId1: "",
|
||||||
fileId2: "",
|
fileId2: "",
|
||||||
});
|
});
|
||||||
const image1 = ref<string>("");
|
|
||||||
const image2 = ref<string>("");
|
|
||||||
|
|
||||||
const isSubmitting = ref(false);
|
const isSubmitting = ref(false);
|
||||||
const status = ref<"unverified" | "pending" | "approved" | "rejected">("unverified");
|
const status = ref<"unverified" | "pending" | "approved" | "rejected">("unverified");
|
||||||
@@ -123,7 +119,7 @@ async function selectFromGallery(type: "front" | "back") {
|
|||||||
"upload.jpeg",
|
"upload.jpeg",
|
||||||
{ type: "image/jpeg" },
|
{ type: "image/jpeg" },
|
||||||
);
|
);
|
||||||
const fileId = await uploadToS3(file, {
|
const { publicUrl } = await uploadToS3(file, {
|
||||||
fetchOptions: {
|
fetchOptions: {
|
||||||
fileName: `idCard_${Date.now()}.jpeg`,
|
fileName: `idCard_${Date.now()}.jpeg`,
|
||||||
fileSize: image.dataUrl?.length || 0,
|
fileSize: image.dataUrl?.length || 0,
|
||||||
@@ -133,12 +129,10 @@ async function selectFromGallery(type: "front" | "back") {
|
|||||||
|
|
||||||
if (image.dataUrl) {
|
if (image.dataUrl) {
|
||||||
if (type === "front") {
|
if (type === "front") {
|
||||||
formData.value.fileId1 = fileId;
|
formData.value.fileId1 = publicUrl;
|
||||||
image1.value = image.dataUrl;
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
formData.value.fileId2 = fileId;
|
formData.value.fileId2 = publicUrl;
|
||||||
image2.value = image.dataUrl;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -181,9 +175,16 @@ async function handleSubmit() {
|
|||||||
|
|
||||||
isSubmitting.value = true;
|
isSubmitting.value = true;
|
||||||
try {
|
try {
|
||||||
await safeClient(client.api.kyc({ kycMethod: "id_card" }).post({
|
if (status.value !== "unverified") {
|
||||||
...formData.value,
|
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");
|
await showToast("实名认证提交成功,等待审核", "success");
|
||||||
router.back();
|
router.back();
|
||||||
@@ -205,11 +206,6 @@ onMounted(async () => {
|
|||||||
formData.value.fileId2 = data.value.fileId2 || "";
|
formData.value.fileId2 = data.value.fileId2 || "";
|
||||||
status.value = data.value?.status || "unverified";
|
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>
|
</script>
|
||||||
|
|
||||||
@@ -313,7 +309,7 @@ onMounted(async () => {
|
|||||||
</div>
|
</div>
|
||||||
<img
|
<img
|
||||||
v-else
|
v-else
|
||||||
:src="image1"
|
:src="formData.fileId1"
|
||||||
alt="身份证正面"
|
alt="身份证正面"
|
||||||
class="uploaded-image"
|
class="uploaded-image"
|
||||||
>
|
>
|
||||||
@@ -342,7 +338,7 @@ onMounted(async () => {
|
|||||||
</div>
|
</div>
|
||||||
<img
|
<img
|
||||||
v-else
|
v-else
|
||||||
:src="image2"
|
:src="formData.fileId2"
|
||||||
alt="身份证反面"
|
alt="身份证反面"
|
||||||
class="uploaded-image"
|
class="uploaded-image"
|
||||||
>
|
>
|
||||||
|
|||||||
Reference in New Issue
Block a user