Files
jczxw-pc/app/pages/admin/members/review.vue
赵忠林 56aea4ad86 feat(about): 重构“关于我们”页面并丰富内容展示
- 采用左右分栏布局,左侧新增图标导航
- 全新设计顶部 Banner,提升视觉效果
- 添加学会简介数据亮点和主要职能展示
- 新增组织机构图、主要领导及专家委员会成员展示
- 引入学会章程章节分明条目展示
- 丰富咨询服务内容,新增服务项目卡片和联系方式
- “加入我们”板块支持企业与个人会员申请详情说明
- 支持资料下载并优化排版与交互体验
- 增强响应式支持,保证移动端体验一致
- 页面样式大幅调整,提升整体美观与可读性
2026-04-26 01:44:07 +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" placeholder="搜索申请人姓名/单位" allow-clear 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"
row-key="id"
:pagination="{ total, pageSize, current: currentPage, onChange: handlePageChange, showTotal: (t: number) => `共 ${t} 条` }"
>
<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 type="primary" size="small" @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" :title="`${currentRecord?.applicantName} 的申请材料`" width="700px" :footer="null">
<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 size="small" type="primary" ghost>预览/下载</a-button>
</div>
<div class="material-item">
<span class="material-icon">🏢</span>
<span class="material-name">营业执照副本</span>
<a-button size="small" type="primary" ghost>预览/下载</a-button>
</div>
<div class="material-item">
<span class="material-icon">🪪</span>
<span class="material-name">法人身份证</span>
<a-button size="small" type="primary" ghost>预览/下载</a-button>
</div>
<div class="material-item">
<span class="material-icon">📝</span>
<span class="material-name">单位简介</span>
<a-button size="small" type="primary" ghost>预览/下载</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 size="small" type="primary" ghost>预览/下载</a-button>
</div>
<div class="material-item">
<span class="material-icon">📖</span>
<span class="material-name">个人简介</span>
<a-button size="small" type="primary" ghost>预览/下载</a-button>
</div>
<div class="material-item">
<span class="material-icon">🎓</span>
<span class="material-name">职称证书/学历证书</span>
<a-button size="small" type="primary" ghost>预览/下载</a-button>
</div>
<div class="material-item">
<span class="material-icon">🪪</span>
<span class="material-name">身份证复印件</span>
<a-button size="small" type="primary" ghost>预览/下载</a-button>
</div>
<div class="material-item">
<span class="material-icon">🏆</span>
<span class="material-name">研究成果/获奖证明</span>
<a-button size="small" type="primary" ghost>预览/下载</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" :title="`会员申请详情`" width="700px" :footer="null">
<a-descriptions v-if="currentRecord" bordered :column="2">
<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 label="单位/组织" v-if="currentRecord.memberType === 'enterprise'">{{ currentRecord.organization }}</a-descriptions-item>
<a-descriptions-item label="联系方式">{{ currentRecord.phone }}</a-descriptions-item>
<a-descriptions-item label="电子邮箱" :span="2">{{ 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" label="拒绝原因" :span="2">{{ currentRecord.rejectReason }}</a-descriptions-item>
</a-descriptions>
</a-modal>
<!-- 拒绝弹窗 -->
<a-modal v-model:open="rejectModal" title="填写拒绝原因" @ok="confirmReject" :confirm-loading="saving">
<a-textarea v-model:value="rejectReason" :rows="4" placeholder="请说明拒绝原因" />
</a-modal>
</div>
</template>
<script setup lang="ts">
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>