<script lang="ts" setup>
|
import type { TableColumnsType } from 'ant-design-vue';
|
import type { DefaultOptionType } from 'ant-design-vue/es/select';
|
import type { ResearchApplicationApi } from '#/api/research/application';
|
|
import { computed, ref } from 'vue';
|
|
import { downloadFileFromBlobPart } from '@vben/utils';
|
|
import { DICT_TYPE } from '@vben/constants';
|
import { getDictOptions } from '@vben/hooks';
|
|
import {
|
Button,
|
Input,
|
Modal,
|
Select,
|
Table,
|
message,
|
} from 'ant-design-vue';
|
|
import {
|
exportMemberStatistics,
|
getMemberStatistics,
|
} from '#/api/research/application';
|
|
defineOptions({ name: 'ResearchMemberStatisticsModal' });
|
|
const open = ref(false);
|
const loading = ref(false);
|
const keyword = ref('');
|
const status = ref<number | undefined>();
|
const rows = ref<ResearchApplicationApi.MemberStatistics[]>([]);
|
|
const statusOptions = getDictOptions(
|
DICT_TYPE.RESEARCH_APPLICATION_STATUS,
|
'number',
|
) as DefaultOptionType[];
|
|
const columns: TableColumnsType<ResearchApplicationApi.MemberStatistics> = [
|
{ title: '成员姓名', dataIndex: 'name', key: 'name', width: 110, fixed: 'left' },
|
{ title: '联系电话', dataIndex: 'contactPhone', key: 'contactPhone', width: 130 },
|
{ title: '所属单位', dataIndex: 'workUnit', key: 'workUnit', width: 180 },
|
{ title: '职称/职务', dataIndex: 'jobTitle', key: 'jobTitle', width: 120 },
|
{ title: '成员角色', dataIndex: 'memberRoles', key: 'memberRoles', width: 140 },
|
{ title: '参与课题数', dataIndex: 'projectCount', key: 'projectCount', width: 110 },
|
{ title: '参与课题', dataIndex: 'projectTitles', key: 'projectTitles', ellipsis: true },
|
];
|
|
const totalMembers = computed(() => rows.value.length);
|
|
const queryParams = computed(() => ({
|
keyword: keyword.value.trim() || undefined,
|
status: status.value,
|
}));
|
|
async function loadData() {
|
loading.value = true;
|
try {
|
rows.value = (await getMemberStatistics(queryParams.value)) ?? [];
|
} finally {
|
loading.value = false;
|
}
|
}
|
|
function handleOpen() {
|
open.value = true;
|
keyword.value = '';
|
status.value = undefined;
|
loadData();
|
}
|
|
async function handleExport() {
|
const data = await exportMemberStatistics(queryParams.value);
|
downloadFileFromBlobPart({ fileName: '课题成员统计.xls', source: data });
|
}
|
|
/** 生成可直接发到微信群/企业微信的名单文本 */
|
function buildPublishText() {
|
const lines = ['【课题成员名单】'];
|
rows.value.forEach((row, index) => {
|
const meta = [row.workUnit, row.jobTitle].filter(Boolean).join('/');
|
const metaText = meta ? `(${meta})` : '';
|
const roles = row.memberRoles ? `[${row.memberRoles}]` : '';
|
const projects = row.projectTitles ? `:${row.projectTitles}` : '';
|
lines.push(
|
`${index + 1}. ${row.name}${metaText}${roles} 参与课题 ${row.projectCount ?? 0} 项${projects}`,
|
);
|
});
|
return lines.join('\n');
|
}
|
|
async function handleCopy() {
|
if (rows.value.length === 0) {
|
message.warning('暂无可发布的成员数据');
|
return;
|
}
|
const text = buildPublishText();
|
try {
|
await navigator.clipboard.writeText(text);
|
message.success('成员名单已复制,可直接粘贴到群内发布');
|
} catch {
|
// 非 HTTPS 环境下 clipboard 不可用时,退化为手动选择复制
|
Modal.info({
|
title: '请手动复制以下名单',
|
content: text,
|
width: 640,
|
});
|
}
|
}
|
|
defineExpose({ open: handleOpen });
|
</script>
|
|
<template>
|
<Modal
|
v-model:open="open"
|
title="课题成员统计(按成员维度)"
|
width="1080px"
|
:footer="null"
|
>
|
<div class="mb-3 flex flex-wrap items-center gap-2">
|
<Input
|
v-model:value="keyword"
|
placeholder="姓名 / 电话 / 单位"
|
allow-clear
|
style="width: 220px"
|
@press-enter="loadData"
|
/>
|
<Select
|
v-model:value="status"
|
:options="statusOptions"
|
placeholder="申报状态(全部)"
|
allow-clear
|
style="width: 180px"
|
/>
|
<Button type="primary" @click="loadData">查询</Button>
|
<div class="flex-1" />
|
<span class="text-sm text-gray-500">共 {{ totalMembers }} 名成员</span>
|
<Button @click="handleCopy">复制群发名单</Button>
|
<Button @click="handleExport">导出 Excel</Button>
|
</div>
|
|
<Table
|
:columns="columns"
|
:data-source="rows"
|
:loading="loading"
|
:pagination="{ pageSize: 10, showSizeChanger: true }"
|
:scroll="{ x: 1100, y: 420 }"
|
bordered
|
size="small"
|
/>
|
|
<div class="mt-2 text-xs text-gray-400">
|
统计口径:同一「姓名 + 电话 + 单位」视为同一成员;参与课题数按不重复课题计数。
|
</div>
|
</Modal>
|
</template>
|