Files
2026-04-29 01:33:33 +08:00

325 lines
12 KiB
Vue
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="admin-members-review">
<div class="page-header">
<h3>会员审核</h3>
<span class="pending-count">待审核{{ pendingCount }} </span>
</div>
<!-- 搜索过滤 -->
<div class="filter-bar">
<a-space wrap>
<a-input v-model:value="filters.keyword" allow-clear placeholder="搜索申请人姓名/单位" style="width: 200px" @press-enter="loadData" />
<a-select v-model:value="filters.type" style="width: 130px" @change="loadData">
<a-select-option value="">全部类型</a-select-option>
<a-select-option value="enterprise">企业会员</a-select-option>
<a-select-option value="personal">个人会员</a-select-option>
</a-select>
<a-select v-model:value="filters.status" style="width: 130px" @change="loadData">
<a-select-option value="">全部状态</a-select-option>
<a-select-option value="pending">待审核</a-select-option>
<a-select-option value="approved">已通过</a-select-option>
<a-select-option value="rejected">已拒绝</a-select-option>
</a-select>
<a-button type="primary" @click="loadData">搜索</a-button>
</a-space>
</div>
<div class="table-card">
<a-table
:columns="columns"
:data-source="dataSource"
:loading="loading"
:pagination="{ total, pageSize, current: currentPage, onChange: handlePageChange, showTotal: (t: number) => `共 ${t} 条` }"
row-key="id"
>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'type'">
<a-tag :color="record.memberType === 'enterprise' ? 'blue' : 'green'">
{{ record.memberType === 'enterprise' ? '企业会员' : '个人会员' }}
</a-tag>
</template>
<template v-if="column.key === 'status'">
<a-tag :color="getStatusColor(record.status)">{{ getStatusText(record.status) }}</a-tag>
</template>
<template v-if="column.key === 'materials'">
<a-button size="small" @click="viewMaterials(record)">查看材料</a-button>
</template>
<template v-if="column.key === 'action'">
<a-space v-if="record.status === 'pending'">
<a-button size="small" type="primary" @click="handleApprove(record)">通过</a-button>
<a-button danger size="small" @click="handleReject(record)">拒绝</a-button>
<a-button size="small" @click="viewDetail(record)">详情</a-button>
</a-space>
<a-button v-else size="small" @click="viewDetail(record)">详情</a-button>
</template>
</template>
</a-table>
</div>
<!-- 材料预览弹窗 -->
<a-modal v-model:open="materialsModal" :footer="null" :title="`${currentRecord?.applicantName} 的申请材料`" width="700px">
<div v-if="currentRecord">
<!-- 企业会员材料 -->
<div v-if="currentRecord.memberType === 'enterprise'">
<h4>企业会员申请材料</h4>
<div class="materials-list">
<div class="material-item">
<span class="material-icon">📄</span>
<span class="material-name">入会申请表盖章</span>
<a-button ghost size="small" type="primary">预览/下载</a-button>
</div>
<div class="material-item">
<span class="material-icon">🏢</span>
<span class="material-name">营业执照副本</span>
<a-button ghost size="small" type="primary">预览/下载</a-button>
</div>
<div class="material-item">
<span class="material-icon">🪪</span>
<span class="material-name">法人身份证</span>
<a-button ghost size="small" type="primary">预览/下载</a-button>
</div>
<div class="material-item">
<span class="material-icon">📝</span>
<span class="material-name">单位简介</span>
<a-button ghost size="small" type="primary">预览/下载</a-button>
</div>
</div>
</div>
<!-- 个人会员材料 -->
<div v-else>
<h4>个人会员申请材料</h4>
<div class="materials-list">
<div class="material-item">
<span class="material-icon">📄</span>
<span class="material-name">入会申请表签字</span>
<a-button ghost size="small" type="primary">预览/下载</a-button>
</div>
<div class="material-item">
<span class="material-icon">📖</span>
<span class="material-name">个人简介</span>
<a-button ghost size="small" type="primary">预览/下载</a-button>
</div>
<div class="material-item">
<span class="material-icon">🎓</span>
<span class="material-name">职称证书/学历证书</span>
<a-button ghost size="small" type="primary">预览/下载</a-button>
</div>
<div class="material-item">
<span class="material-icon">🪪</span>
<span class="material-name">身份证复印件</span>
<a-button ghost size="small" type="primary">预览/下载</a-button>
</div>
<div class="material-item">
<span class="material-icon">🏆</span>
<span class="material-name">研究成果/获奖证明</span>
<a-button ghost size="small" type="primary">预览/下载</a-button>
</div>
</div>
</div>
<div v-if="currentRecord.status === 'pending'" class="action-area">
<a-divider />
<a-space>
<a-button type="primary" @click="handleApprove(currentRecord); materialsModal = false">通过申请</a-button>
<a-button danger @click="handleReject(currentRecord); materialsModal = false">拒绝申请</a-button>
</a-space>
</div>
</div>
</a-modal>
<!-- 详情弹窗 -->
<a-modal v-model:open="detailModal" :footer="null" :title="`会员申请详情`" width="700px">
<a-descriptions v-if="currentRecord" :column="2" bordered>
<a-descriptions-item label="申请人">{{ currentRecord.applicantName }}</a-descriptions-item>
<a-descriptions-item label="会员类型">
<a-tag :color="currentRecord.memberType === 'enterprise' ? 'blue' : 'green'">
{{ currentRecord.memberType === 'enterprise' ? '企业会员' : '个人会员' }}
</a-tag>
</a-descriptions-item>
<a-descriptions-item v-if="currentRecord.memberType === 'enterprise'" label="单位/组织">{{ currentRecord.organization }}</a-descriptions-item>
<a-descriptions-item label="联系方式">{{ currentRecord.phone }}</a-descriptions-item>
<a-descriptions-item :span="2" label="电子邮箱">{{ currentRecord.email }}</a-descriptions-item>
<a-descriptions-item label="申请时间">{{ currentRecord.applyTime }}</a-descriptions-item>
<a-descriptions-item label="状态">
<a-tag :color="getStatusColor(currentRecord.status)">{{ getStatusText(currentRecord.status) }}</a-tag>
</a-descriptions-item>
<a-descriptions-item v-if="currentRecord.rejectReason" :span="2" label="拒绝原因">{{ currentRecord.rejectReason }}</a-descriptions-item>
</a-descriptions>
</a-modal>
<!-- 拒绝弹窗 -->
<a-modal v-model:open="rejectModal" :confirm-loading="saving" title="填写拒绝原因" @ok="confirmReject">
<a-textarea v-model:value="rejectReason" :rows="4" placeholder="请说明拒绝原因" />
</a-modal>
</div>
</template>
<script lang="ts" setup>
import { message } from 'ant-design-vue'
definePageMeta({ layout: 'admin' })
useHead({ title: '会员审核' })
const loading = ref(false)
const saving = ref(false)
const materialsModal = ref(false)
const detailModal = ref(false)
const rejectModal = ref(false)
const rejectReason = ref('')
const currentRecord = ref<any>(null)
const pendingCount = ref(5)
const total = ref(0)
const currentPage = ref(1)
const pageSize = ref(15)
const filters = reactive({ keyword: '', type: '', status: '' })
const columns = [
{ title: '申请人', dataIndex: 'applicantName', key: 'applicantName' },
{ title: '会员类型', key: 'type', width: 110 },
{ title: '单位/联系方式', dataIndex: 'orgOrContact', key: 'orgOrContact' },
{ title: '申请时间', dataIndex: 'applyTime', key: 'applyTime', width: 150 },
{ title: '状态', key: 'status', width: 100 },
{ title: '材料', key: 'materials', width: 100 },
{ title: '操作', key: 'action', width: 180 },
]
const dataSource = ref([
{ id: 1, applicantName: '广西某科技公司', memberType: 'enterprise', orgOrContact: '王总 139****0001', phone: '139****0001', email: 'enterprise@xx.com', organization: '广西某科技有限公司', applyTime: '2024-12-19 10:00', status: 'pending' },
{ id: 2, applicantName: '张某某', memberType: 'personal', orgOrContact: '广西大学', phone: '138****0001', email: 'zhang@gxu.edu.cn', applyTime: '2024-12-18 14:30', status: 'pending' },
{ id: 3, applicantName: '南宁某咨询机构', memberType: 'enterprise', orgOrContact: '李经理 137****0002', phone: '137****0002', email: 'nn@xx.com', organization: '南宁某咨询有限公司', applyTime: '2024-12-15 09:20', status: 'approved' },
])
function getStatusColor(status: string) {
const map: Record<string, string> = { pending: 'orange', approved: 'green', rejected: 'red' }
return map[status] || 'default'
}
function getStatusText(status: string) {
const map: Record<string, string> = { pending: '待审核', approved: '已通过', rejected: '已拒绝' }
return map[status] || status
}
function viewMaterials(record: any) {
currentRecord.value = record
materialsModal.value = true
}
function viewDetail(record: any) {
currentRecord.value = record
detailModal.value = true
}
async function handleApprove(record: any) {
// TODO: 调用API
record.status = 'approved'
pendingCount.value = Math.max(0, pendingCount.value - 1)
message.success(`已通过 ${record.applicantName} 的会员申请`)
}
function handleReject(record: any) {
currentRecord.value = record
rejectReason.value = ''
rejectModal.value = true
}
async function confirmReject() {
if (!rejectReason.value.trim()) { message.warning('请填写拒绝原因'); return }
saving.value = true
try {
// TODO: 调用API
currentRecord.value.status = 'rejected'
currentRecord.value.rejectReason = rejectReason.value
pendingCount.value = Math.max(0, pendingCount.value - 1)
message.success('已拒绝申请并通知申请人')
rejectModal.value = false
} finally {
saving.value = false
}
}
function handlePageChange(page: number) {
currentPage.value = page
loadData()
}
async function loadData() {
// TODO: 接入实际API
}
onMounted(() => {
total.value = dataSource.value.length
})
</script>
<style scoped>
.admin-members-review {
display: flex;
flex-direction: column;
gap: 16px;
}
.page-header {
display: flex;
align-items: center;
gap: 12px;
background: #fff;
border-radius: 10px;
padding: 14px 20px;
box-shadow: 0 1px 4px rgba(0,0,0,0.06);
}
.page-header h3 {
font-size: 16px;
font-weight: 700;
color: #1f2937;
margin: 0;
}
.pending-count {
padding: 4px 12px;
background: #fef3c7;
color: #b45309;
border-radius: 20px;
font-size: 13px;
font-weight: 500;
}
.filter-bar, .table-card {
background: #fff;
border-radius: 12px;
padding: 16px 20px;
box-shadow: 0 1px 4px rgba(0,0,0,0.06);
}
.materials-list {
display: flex;
flex-direction: column;
gap: 10px;
margin-top: 12px;
}
.material-item {
display: flex;
align-items: center;
gap: 10px;
padding: 10px 14px;
background: #f9fafb;
border-radius: 8px;
}
.material-icon {
font-size: 18px;
}
.material-name {
flex: 1;
font-size: 14px;
color: #374151;
}
.action-area {
margin-top: 8px;
}
</style>