338 lines
10 KiB
Vue
338 lines
10 KiB
Vue
<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" allow-clear placeholder="搜索专家姓名/单位" 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"
|
||
:pagination="{ total, pageSize, current: currentPage, onChange: handlePageChange, showTotal: (t: number) => `共 ${t} 条` }"
|
||
row-key="id"
|
||
>
|
||
<template #bodyCell="{ column, record }">
|
||
<template v-if="column.key === 'applicant'">
|
||
<div class="applicant-info">
|
||
<a-avatar :size="36" :src="record.avatar">{{ 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 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-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"
|
||
:confirm-loading="saving"
|
||
title="填写拒绝原因"
|
||
@ok="confirmReject"
|
||
>
|
||
<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"
|
||
:footer="null"
|
||
:title="`${currentRecord?.name} - 申请详情`"
|
||
width="700px"
|
||
>
|
||
<div v-if="currentRecord" class="detail-content">
|
||
<a-descriptions :column="2" bordered>
|
||
<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 :span="2" label="电子邮箱">{{ currentRecord.email }}</a-descriptions-item>
|
||
<a-descriptions-item :span="2" label="个人简介">{{ 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 lang="ts" setup>
|
||
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>
|