<script lang="ts" setup>
|
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
import type { WxCpActivityApi } from '#/api/wxcp/activity';
|
|
import { ref } from 'vue';
|
|
import { useVbenModal } from '@vben/common-ui';
|
import {
|
Button,
|
Empty,
|
message,
|
Modal as AntModal,
|
Spin,
|
Tag,
|
Tooltip,
|
} from 'ant-design-vue';
|
|
import { TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
|
import {
|
cancelSignup,
|
checkInSignup,
|
getSignupAttachments,
|
getSignupPage,
|
} from '#/api/wxcp/activity';
|
|
import { SIGNUP_STATUS, SIGNUP_STATUS_OPTIONS, toLabelMap } from '../../data';
|
import { useSignupGridColumns, useSignupSearchFormSchema } from '../data';
|
|
defineOptions({ name: 'WxCpActivitySignupList' });
|
|
const emit = defineEmits<{ success: [] }>();
|
|
const statusLabelMap = toLabelMap(SIGNUP_STATUS_OPTIONS);
|
|
const activity = ref<WxCpActivityApi.Activity>({});
|
|
/** 附件查看 */
|
const attachmentVisible = ref(false);
|
const attachmentLoading = ref(false);
|
const attachments = ref<WxCpActivityApi.SignupAttachment[]>([]);
|
const attachmentTitle = ref('');
|
|
async function handleViewAttachments(row: WxCpActivityApi.Signup) {
|
if (!row.id) {
|
return;
|
}
|
attachmentTitle.value = `${row.name || ''} 提交的附件`;
|
attachmentVisible.value = true;
|
attachmentLoading.value = true;
|
attachments.value = [];
|
try {
|
attachments.value = (await getSignupAttachments(row.id)) ?? [];
|
} catch (error: unknown) {
|
message.error((error as Error)?.message || '附件加载失败');
|
} finally {
|
attachmentLoading.value = false;
|
}
|
}
|
|
function isImage(item: WxCpActivityApi.SignupAttachment) {
|
return (item.contentType || '').startsWith('image/');
|
}
|
|
function formatSize(bytes?: number) {
|
if (!bytes || bytes <= 0) {
|
return '';
|
}
|
if (bytes < 1024) return `${bytes} B`;
|
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
return `${(bytes / 1024 / 1024).toFixed(1)} MB`;
|
}
|
|
async function handleCancel(row: WxCpActivityApi.Signup) {
|
await cancelSignup(row.id!);
|
message.success('已取消该报名');
|
gridApi.query();
|
emit('success');
|
}
|
|
async function handleCheckIn(row: WxCpActivityApi.Signup) {
|
await checkInSignup(row.id!);
|
message.success('签到成功');
|
gridApi.query();
|
emit('success');
|
}
|
|
const [Grid, gridApi] = useVbenVxeGrid({
|
formOptions: {
|
schema: useSignupSearchFormSchema(),
|
},
|
gridOptions: {
|
columns: useSignupGridColumns(),
|
height: 'auto',
|
keepSource: true,
|
proxyConfig: {
|
ajax: {
|
query: async ({ page }, formValues) => {
|
if (!activity.value.id) {
|
return { list: [], total: 0 };
|
}
|
return await getSignupPage({
|
pageNo: page.currentPage,
|
pageSize: page.pageSize,
|
activityId: activity.value.id,
|
...formValues,
|
});
|
},
|
},
|
},
|
rowConfig: { keyField: 'id', isHover: true },
|
toolbarConfig: { refresh: true, search: true },
|
} as VxeTableGridOptions<WxCpActivityApi.Signup>,
|
});
|
|
const [Modal, modalApi] = useVbenModal({
|
onOpenChange(isOpen: boolean) {
|
if (!isOpen) {
|
activity.value = {};
|
return;
|
}
|
const data = modalApi.getData<{ activity: WxCpActivityApi.Activity }>();
|
activity.value = data.activity ?? {};
|
// 活动ID就绪后再查询
|
gridApi.query();
|
},
|
});
|
</script>
|
|
<template>
|
<Modal :title="`报名记录 - ${activity.title || ''}`" class="w-11/12">
|
<div class="px-4">
|
<Grid table-title="报名记录">
|
<template #content="{ row }">
|
<Tooltip v-if="row.content" :title="row.content">
|
<span class="line-clamp-2">{{ row.content }}</span>
|
</Tooltip>
|
<span v-else class="text-gray-400">-</span>
|
</template>
|
|
<template #attachment="{ row }">
|
<Button type="link" class="p-0" @click="handleViewAttachments(row)">
|
查看
|
</Button>
|
</template>
|
|
<template #status="{ row }">
|
<Tag
|
:color="
|
row.status === SIGNUP_STATUS.CHECKED_IN
|
? 'green'
|
: row.status === SIGNUP_STATUS.CANCELED
|
? 'default'
|
: 'blue'
|
"
|
>
|
{{ statusLabelMap[row.status!] || '-' }}
|
</Tag>
|
</template>
|
|
<template #actions="{ row }">
|
<TableAction
|
:actions="[
|
{
|
label: '签到',
|
type: 'link',
|
auth: ['wxcp:signup:update'],
|
ifShow: row.status === SIGNUP_STATUS.SIGNED_UP,
|
popConfirm: {
|
title: `确认为【${row.name}】签到?`,
|
confirm: handleCheckIn.bind(null, row),
|
},
|
},
|
{
|
label: '取消报名',
|
type: 'link',
|
danger: true,
|
auth: ['wxcp:signup:update'],
|
ifShow: row.status !== SIGNUP_STATUS.CANCELED,
|
popConfirm: {
|
title: `确认取消【${row.name}】的报名?取消后报名人数会相应减少。`,
|
confirm: handleCancel.bind(null, row),
|
},
|
},
|
]"
|
/>
|
</template>
|
</Grid>
|
</div>
|
|
<template #footer>
|
<div class="flex justify-end">
|
<Button @click="modalApi.close()">关闭</Button>
|
</div>
|
</template>
|
|
<!-- 附件查看 -->
|
<AntModal
|
v-model:open="attachmentVisible"
|
:title="attachmentTitle"
|
:footer="null"
|
width="640px"
|
>
|
<Spin :spinning="attachmentLoading">
|
<div v-if="attachments.length" class="space-y-3">
|
<div
|
v-for="item in attachments"
|
:key="item.id"
|
class="rounded border border-gray-200 p-3 dark:border-gray-700"
|
>
|
<div class="mb-2 text-sm break-all">
|
{{ item.originalFilename || item.name || `附件${item.id}` }}
|
<span class="ml-2 text-xs text-gray-400">
|
{{ formatSize(item.byteSize) }}
|
</span>
|
</div>
|
<a
|
v-if="isImage(item)"
|
:href="item.previewURL || item.url"
|
target="_blank"
|
rel="noopener noreferrer"
|
>
|
<img
|
:src="item.previewURL || item.url"
|
alt="附件预览"
|
class="max-h-40 rounded object-contain"
|
/>
|
</a>
|
<a
|
:href="item.downloadURL || item.url"
|
target="_blank"
|
rel="noopener noreferrer"
|
class="text-xs text-blue-500 hover:underline"
|
>
|
下载
|
</a>
|
</div>
|
</div>
|
<Empty v-else-if="!attachmentLoading" description="该报名未上传附件" />
|
</Spin>
|
</AntModal>
|
</Modal>
|
</template>
|