354 lines
8.3 KiB
Vue
354 lines
8.3 KiB
Vue
<template>
|
|
<SimpleScrollbar>
|
|
<div class="min-h-500px flex-col-stretch gap-16px overflow-hidden lt-sm:overflow-auto">
|
|
<KycSearch
|
|
v-model:model="searchParams"
|
|
:loading="loading"
|
|
@reset="handleReset"
|
|
@search="handleSearch"
|
|
/>
|
|
<ACard
|
|
:title="t('page.kyc.title')"
|
|
:bordered="false"
|
|
:body-style="{ flex: 1, overflow: 'hidden' }"
|
|
class="flex-col-stretch sm:flex-1-hidden card-wrapper"
|
|
>
|
|
<ATable
|
|
ref="wrapperEl"
|
|
:columns="columns"
|
|
:data-source="data"
|
|
:loading="loading"
|
|
row-key="id"
|
|
size="small"
|
|
:pagination="{
|
|
...mobilePagination,
|
|
total: mobilePagination.total,
|
|
current: searchParams.pageNum,
|
|
pageSize: searchParams.pageSize,
|
|
showTotal: (total: number) => `${t('page.kyc.total')} ${total} `
|
|
}"
|
|
:scroll="scrollConfig"
|
|
class="h-full"
|
|
@change="(pagination) => {
|
|
searchParams.pageNum = pagination.current;
|
|
searchParams.pageSize = pagination.pageSize;
|
|
getData();
|
|
}"
|
|
>
|
|
<template #bodyCell="{ column, record }">
|
|
<template v-if="column.key === 'status'">
|
|
<ATag :color="getStatusColor(record.status)">
|
|
{{ getStatusText(record.status) }}
|
|
</ATag>
|
|
</template>
|
|
<template v-if="column.key === 'idFile'">
|
|
<div class="table-image-wrapper">
|
|
<a-image
|
|
class="kyc-image"
|
|
:src="parseFile(record.idFile)"
|
|
:preview="{
|
|
src: parseFile(record.idFile),
|
|
mask: t('common.clickToPreview')
|
|
}"
|
|
/>
|
|
</div>
|
|
</template>
|
|
<template v-if="column.key === 'identifyPicture'">
|
|
<div class="table-image-wrapper">
|
|
<a-image
|
|
class="kyc-image"
|
|
:src="parseFile(record.identifyPicture)"
|
|
:preview="{
|
|
src: parseFile(record.identifyPicture),
|
|
mask: t('common.clickToPreview')
|
|
}"
|
|
/>
|
|
</div>
|
|
</template>
|
|
|
|
<template v-else-if="column.key === 'operate'">
|
|
<ASpace>
|
|
<AButton
|
|
type="primary"
|
|
size="small"
|
|
:disabled="record.status !== 'PENDING'"
|
|
@click="handleApprove(record)"
|
|
>
|
|
{{ t('page.kyc.pass') }}
|
|
</AButton>
|
|
<AButton
|
|
danger
|
|
size="small"
|
|
:disabled="record.status !== 'PENDING'"
|
|
@click="handleReject(record)"
|
|
>
|
|
{{ t('page.kyc.refuse') }}
|
|
</AButton>
|
|
</ASpace>
|
|
</template>
|
|
</template>
|
|
</ATable>
|
|
</ACard>
|
|
</div>
|
|
</SimpleScrollbar>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { useTable } from '@/hooks/common/table';
|
|
import { SimpleScrollbar } from '~/packages/materials/src';
|
|
import { computed, shallowRef, h } from 'vue';
|
|
import { useElementSize } from '@vueuse/core';
|
|
import {fetchKycList, approveKyc, rejectKyc} from '@/service/api/auth';
|
|
import {
|
|
Card as ACard,
|
|
Table as ATable,
|
|
Tag as ATag,
|
|
Space as ASpace,
|
|
Button as AButton,
|
|
Image as AImage,
|
|
Modal as AModal,
|
|
Input as AInput,
|
|
message
|
|
} from 'ant-design-vue';
|
|
import KycSearch from './modules/kyc-search.vue';
|
|
import { useI18n } from 'vue-i18n';
|
|
const { t } = useI18n();
|
|
const wrapperEl = shallowRef<HTMLElement | null>(null);
|
|
const { height: wrapperElHeight } = useElementSize(wrapperEl);
|
|
|
|
const scrollConfig = computed(() => ({
|
|
y: wrapperElHeight.value - 72,
|
|
x: 800
|
|
}));
|
|
|
|
const getStatusColor = (status: string) => {
|
|
switch (status) {
|
|
case 'PENDING':
|
|
return 'warning';
|
|
case 'APPROVED':
|
|
return 'success';
|
|
case 'REJECTED':
|
|
return 'error';
|
|
default:
|
|
return 'default';
|
|
}
|
|
};
|
|
//李工杰作 图片请求url组装
|
|
const parseFile = (path:string)=>{
|
|
let baseUrl = import.meta.env.VITE_SERVICE_BASE_URL;
|
|
if (baseUrl.indexOf('/') !== -1){
|
|
try {
|
|
const parsedUrl = new URL(baseUrl);
|
|
baseUrl = parsedUrl.host;
|
|
} catch (error) {
|
|
console.error('Invalid URL:', error);
|
|
return null;
|
|
}
|
|
}
|
|
return `//${baseUrl}/file${path}`
|
|
}
|
|
const getStatusText = (status: string) => {
|
|
switch (status) {
|
|
case 'VERIFIED':
|
|
return t('page.kyc.verified');
|
|
case 'PENDING':
|
|
return t('page.kyc.pending');
|
|
case 'APPROVED':
|
|
return t('page.kyc.approved');
|
|
case 'REJECTED':
|
|
return t('page.kyc.rejected');
|
|
default:
|
|
return t('page.kyc.unknow');
|
|
}
|
|
};
|
|
|
|
const {
|
|
columns,
|
|
data,
|
|
loading,
|
|
getData,
|
|
mobilePagination,
|
|
searchParams,
|
|
resetSearchParams
|
|
} = useTable({
|
|
apiFn: fetchKycList,
|
|
immediate: true,
|
|
apiParams: {
|
|
pageNum: 1,
|
|
pageSize: 10,
|
|
userName: '',
|
|
status: undefined
|
|
} as Api.Kyc.KycParams,
|
|
rowKey: 'id',
|
|
pagination: true,
|
|
columns: (): AntDesign.TableColumn<Api.Kyc.KycInfo>[] => [
|
|
{
|
|
key: 'userName',
|
|
dataIndex: 'userName',
|
|
title: t('page.kyc.username'),
|
|
align: 'center',
|
|
width: 120
|
|
},
|
|
{
|
|
key: 'realName',
|
|
dataIndex: 'realName',
|
|
title: t('page.kyc.realname'),
|
|
align: 'center',
|
|
width: 120
|
|
},
|
|
{
|
|
key: 'idType',
|
|
dataIndex: 'idType',
|
|
title: t('page.kyc.type'),
|
|
align: 'center',
|
|
width: 100
|
|
},
|
|
{
|
|
key: 'idFile',
|
|
dataIndex: 'idFile',
|
|
title: t('page.kyc.file'),
|
|
align: 'center',
|
|
width: 150,
|
|
},
|
|
{
|
|
key: 'identifyPicture',
|
|
dataIndex: 'identifyPicture',
|
|
title: t('page.kyc.picture'),
|
|
align: 'center',
|
|
width: 150,
|
|
},
|
|
{
|
|
key: 'status',
|
|
dataIndex: 'status',
|
|
title: t('page.kyc.status'),
|
|
align: 'center',
|
|
width: 100
|
|
},
|
|
{
|
|
key: 'createTime',
|
|
dataIndex: 'createTime',
|
|
title: t('page.kyc.createtime'),
|
|
align: 'center',
|
|
width: 150
|
|
},
|
|
{
|
|
key: 'operate',
|
|
title: t('page.kyc.operate'),
|
|
align: 'center',
|
|
width: 150,
|
|
fixed: 'right'
|
|
}
|
|
]
|
|
});
|
|
|
|
|
|
|
|
const handleSearch = () => {
|
|
getData();
|
|
};
|
|
|
|
const handleReset = () => {
|
|
resetSearchParams();
|
|
getData();
|
|
};
|
|
|
|
const handleApprove = async (record: Api.Kyc.KycInfo) => {
|
|
try {
|
|
AModal.confirm({
|
|
title: t('page.kyc.confirmtitle'),
|
|
content: t('page.kyc.confirmcontent'),
|
|
async onOk() {
|
|
await approveKyc(record.id, record.userId);
|
|
message.success(t('page.kyc.confirmsuc'));
|
|
getData(); // 刷新列表
|
|
}
|
|
});
|
|
} catch (error) {
|
|
message.error(t('page.kyc.confirmerr'));
|
|
console.error('Approve failed:', error);
|
|
}
|
|
};
|
|
|
|
const handleReject = (record: Api.Kyc.KycInfo) => {
|
|
let reason = '';
|
|
AModal.confirm({
|
|
title: t('page.kyc.rejecttitle'),
|
|
content: h('div', [
|
|
h('p', t('page.kyc.rejectcontent')),
|
|
h(AInput.TextArea, {
|
|
placeholder: t('page.kyc.rejectpla'),
|
|
rows: 4,
|
|
'onUpdate:value': (val: string) => {
|
|
reason = val;
|
|
}
|
|
})
|
|
]),
|
|
async onOk() {
|
|
if (!reason.trim()) {
|
|
message.error(t('page.kyc.rejectpla'));
|
|
return Promise.reject();
|
|
}
|
|
try {
|
|
await rejectKyc(record.id, record.userId, reason);
|
|
message.success(t('page.kyc.rejectsuc'));
|
|
getData(); // 刷新列表
|
|
} catch (error) {
|
|
message.error(t('page.kyc.confirmerr'));
|
|
console.error('Reject failed:', error);
|
|
}
|
|
}
|
|
});
|
|
};
|
|
</script>
|
|
|
|
<style scoped>
|
|
.h-full {
|
|
height: 100%;
|
|
}
|
|
|
|
.card-wrapper {
|
|
margin-top: 16px;
|
|
}
|
|
|
|
.image-wrapper {
|
|
display: flex;
|
|
justify-content: center;
|
|
align-items: center;
|
|
padding: 4px;
|
|
}
|
|
|
|
:deep(.ant-image) {
|
|
border-radius: 4px;
|
|
overflow: hidden;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.table-image-wrapper {
|
|
display: flex;
|
|
justify-content: center;
|
|
align-items: center;
|
|
padding: 4px;
|
|
width: 100%;
|
|
height: 100%;
|
|
min-height: 80px;
|
|
}
|
|
|
|
:deep(.kyc-image) {
|
|
width: 90%;
|
|
max-width: 140px;
|
|
aspect-ratio: 3/2;
|
|
border-radius: 4px;
|
|
overflow: hidden;
|
|
|
|
img {
|
|
width: 100%;
|
|
height: 100%;
|
|
object-fit: cover;
|
|
}
|
|
}
|
|
|
|
:deep(.ant-image-mask) {
|
|
border-radius: 4px;
|
|
}
|
|
</style>
|