feat(about): 重构“关于我们”页面并丰富内容展示
- 采用左右分栏布局,左侧新增图标导航 - 全新设计顶部 Banner,提升视觉效果 - 添加学会简介数据亮点和主要职能展示 - 新增组织机构图、主要领导及专家委员会成员展示 - 引入学会章程章节分明条目展示 - 丰富咨询服务内容,新增服务项目卡片和联系方式 - “加入我们”板块支持企业与个人会员申请详情说明 - 支持资料下载并优化排版与交互体验 - 增强响应式支持,保证移动端体验一致 - 页面样式大幅调整,提升整体美观与可读性
This commit is contained in:
337
app/pages/admin/experts/review.vue
Normal file
337
app/pages/admin/experts/review.vue
Normal file
@@ -0,0 +1,337 @@
|
||||
<template>
|
||||
<div class="admin-experts-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.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 === 'applicant'">
|
||||
<div class="applicant-info">
|
||||
<a-avatar :src="record.avatar" :size="36">{{ record.name?.charAt(0) }}</a-avatar>
|
||||
<div class="applicant-detail">
|
||||
<div class="applicant-name">{{ record.name }}</div>
|
||||
<div class="applicant-org">{{ record.organization }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</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-space>
|
||||
<a-button size="small" @click="previewFile(record, 'resume')">简历</a-button>
|
||||
<a-button size="small" @click="previewFile(record, 'id')">身份证</a-button>
|
||||
<a-button size="small" @click="previewFile(record, 'cert')">证书</a-button>
|
||||
</a-space>
|
||||
</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-space v-else>
|
||||
<a-button size="small" @click="viewDetail(record)">详情</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</div>
|
||||
|
||||
<!-- 拒绝原因弹窗 -->
|
||||
<a-modal
|
||||
v-model:open="rejectModal"
|
||||
title="填写拒绝原因"
|
||||
@ok="confirmReject"
|
||||
:confirm-loading="saving"
|
||||
>
|
||||
<a-form layout="vertical">
|
||||
<a-form-item label="拒绝原因" required>
|
||||
<a-textarea v-model:value="rejectReason" :rows="4" placeholder="请说明拒绝原因(将通知申请人)" />
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
|
||||
<!-- 详情弹窗 -->
|
||||
<a-modal
|
||||
v-model:open="detailModal"
|
||||
:title="`${currentRecord?.name} - 申请详情`"
|
||||
width="700px"
|
||||
:footer="null"
|
||||
>
|
||||
<div v-if="currentRecord" class="detail-content">
|
||||
<a-descriptions bordered :column="2">
|
||||
<a-descriptions-item label="姓名">{{ currentRecord.name }}</a-descriptions-item>
|
||||
<a-descriptions-item label="职称">{{ currentRecord.title }}</a-descriptions-item>
|
||||
<a-descriptions-item label="所在单位">{{ currentRecord.organization }}</a-descriptions-item>
|
||||
<a-descriptions-item label="研究领域">{{ currentRecord.researchArea }}</a-descriptions-item>
|
||||
<a-descriptions-item label="学历">{{ currentRecord.education }}</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="个人简介" :span="2">{{ currentRecord.intro }}</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>
|
||||
|
||||
<div class="materials-section" style="margin-top:16px">
|
||||
<h4 style="margin-bottom:12px">申请材料</h4>
|
||||
<a-space wrap>
|
||||
<a-button icon="📄" @click="previewFile(currentRecord, 'resume')">查看简历/研究成果</a-button>
|
||||
<a-button icon="🪪" @click="previewFile(currentRecord, 'id')">查看身份证</a-button>
|
||||
<a-button icon="🏆" @click="previewFile(currentRecord, 'cert')">查看职称证书/学历证书</a-button>
|
||||
</a-space>
|
||||
</div>
|
||||
|
||||
<div v-if="currentRecord.status === 'pending'" class="action-area" style="margin-top:16px">
|
||||
<a-space>
|
||||
<a-button type="primary" @click="handleApprove(currentRecord); detailModal = false">通过申请</a-button>
|
||||
<a-button danger @click="handleReject(currentRecord); detailModal = false">拒绝申请</a-button>
|
||||
</a-space>
|
||||
</div>
|
||||
</div>
|
||||
</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 rejectModal = ref(false)
|
||||
const detailModal = ref(false)
|
||||
const rejectReason = ref('')
|
||||
const currentRecord = ref<any>(null)
|
||||
const pendingCount = ref(3)
|
||||
const total = ref(0)
|
||||
const currentPage = ref(1)
|
||||
const pageSize = ref(15)
|
||||
|
||||
const filters = reactive({
|
||||
keyword: '',
|
||||
status: '',
|
||||
})
|
||||
|
||||
const columns = [
|
||||
{ title: '申请人', key: 'applicant', width: 200 },
|
||||
{ title: '职称', dataIndex: 'title', key: 'title' },
|
||||
{ title: '研究领域', dataIndex: 'researchArea', key: 'researchArea' },
|
||||
{ title: '申请时间', dataIndex: 'applyTime', key: 'applyTime', width: 150 },
|
||||
{ title: '状态', key: 'status', width: 100 },
|
||||
{ title: '材料', key: 'materials', width: 160 },
|
||||
{ title: '操作', key: 'action', width: 160 },
|
||||
]
|
||||
|
||||
const dataSource = ref<any[]>([
|
||||
{
|
||||
id: 1,
|
||||
name: '张某某',
|
||||
avatar: '',
|
||||
organization: '广西大学',
|
||||
title: '教授',
|
||||
researchArea: '区域经济',
|
||||
education: '博士',
|
||||
phone: '138****0001',
|
||||
email: 'zhang@gxu.edu.cn',
|
||||
intro: '长期从事区域经济研究...',
|
||||
applyTime: '2024-12-18 10:30',
|
||||
status: 'pending',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: '李某某',
|
||||
avatar: '',
|
||||
organization: '广西社科院',
|
||||
title: '研究员',
|
||||
researchArea: '产业政策',
|
||||
education: '博士',
|
||||
phone: '139****0002',
|
||||
email: 'li@gxss.org',
|
||||
intro: '专注产业政策研究...',
|
||||
applyTime: '2024-12-17 15:00',
|
||||
status: 'pending',
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: '王某某',
|
||||
avatar: '',
|
||||
organization: '广西师范大学',
|
||||
title: '副教授',
|
||||
researchArea: '金融经济',
|
||||
education: '博士',
|
||||
phone: '137****0003',
|
||||
email: 'wang@gxnu.edu.cn',
|
||||
intro: '从事金融经济研究...',
|
||||
applyTime: '2024-12-15 09:00',
|
||||
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
|
||||
}
|
||||
|
||||
async function handleApprove(record: any) {
|
||||
try {
|
||||
// TODO: 调用API
|
||||
record.status = 'approved'
|
||||
pendingCount.value = Math.max(0, pendingCount.value - 1)
|
||||
message.success(`已通过 ${record.name} 的专家申请`)
|
||||
} catch (e: any) {
|
||||
message.error(e?.message || '操作失败')
|
||||
}
|
||||
}
|
||||
|
||||
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'
|
||||
pendingCount.value = Math.max(0, pendingCount.value - 1)
|
||||
message.success('已拒绝申请并通知申请人')
|
||||
rejectModal.value = false
|
||||
} catch (e: any) {
|
||||
message.error(e?.message || '操作失败')
|
||||
} finally {
|
||||
saving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function viewDetail(record: any) {
|
||||
currentRecord.value = record
|
||||
detailModal.value = true
|
||||
}
|
||||
|
||||
function previewFile(record: any, type: string) {
|
||||
message.info(`预览 ${record.name} 的${type === 'resume' ? '简历' : type === 'id' ? '身份证' : '证书'}材料`)
|
||||
// TODO: 打开文件预览
|
||||
}
|
||||
|
||||
function handlePageChange(page: number) {
|
||||
currentPage.value = page
|
||||
loadData()
|
||||
}
|
||||
|
||||
async function loadData() {
|
||||
loading.value = true
|
||||
try {
|
||||
// TODO: 接入实际API
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
total.value = dataSource.value.length
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.admin-experts-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 {
|
||||
background: #fff;
|
||||
border-radius: 10px;
|
||||
padding: 14px 20px;
|
||||
box-shadow: 0 1px 4px rgba(0,0,0,0.06);
|
||||
}
|
||||
|
||||
.table-card {
|
||||
background: #fff;
|
||||
border-radius: 12px;
|
||||
padding: 20px;
|
||||
box-shadow: 0 1px 4px rgba(0,0,0,0.06);
|
||||
}
|
||||
|
||||
.applicant-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.applicant-name {
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
color: #1f2937;
|
||||
}
|
||||
|
||||
.applicant-org {
|
||||
font-size: 12px;
|
||||
color: #9ca3af;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user