feat(about): 重构“关于我们”页面并丰富内容展示
- 采用左右分栏布局,左侧新增图标导航 - 全新设计顶部 Banner,提升视觉效果 - 添加学会简介数据亮点和主要职能展示 - 新增组织机构图、主要领导及专家委员会成员展示 - 引入学会章程章节分明条目展示 - 丰富咨询服务内容,新增服务项目卡片和联系方式 - “加入我们”板块支持企业与个人会员申请详情说明 - 支持资料下载并优化排版与交互体验 - 增强响应式支持,保证移动端体验一致 - 页面样式大幅调整,提升整体美观与可读性
This commit is contained in:
220
app/pages/admin/applications/expert.vue
Normal file
220
app/pages/admin/applications/expert.vue
Normal file
@@ -0,0 +1,220 @@
|
||||
<template>
|
||||
<div class="admin-applications-expert">
|
||||
<!-- 复用专家审核,这里是专家申请管理入口 -->
|
||||
<div class="page-header">
|
||||
<h3>专家申请管理</h3>
|
||||
<a-space>
|
||||
<a-button type="primary" @click="navigateTo('/admin/experts/review')">前往审核</a-button>
|
||||
<a-button @click="loadData">刷新</a-button>
|
||||
</a-space>
|
||||
</div>
|
||||
|
||||
<div class="stats-row">
|
||||
<div class="stat-item blue">
|
||||
<div class="stat-num">{{ stats.total }}</div>
|
||||
<div class="stat-label">总申请</div>
|
||||
</div>
|
||||
<div class="stat-item orange">
|
||||
<div class="stat-num">{{ stats.pending }}</div>
|
||||
<div class="stat-label">待审核</div>
|
||||
</div>
|
||||
<div class="stat-item green">
|
||||
<div class="stat-num">{{ stats.approved }}</div>
|
||||
<div class="stat-label">已通过</div>
|
||||
</div>
|
||||
<div class="stat-item red">
|
||||
<div class="stat-num">{{ stats.rejected }}</div>
|
||||
<div class="stat-label">已拒绝</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 申请材料模板下载 -->
|
||||
<div class="template-card">
|
||||
<h4>申请材料模板</h4>
|
||||
<p>以下为专家申请所需材料的模板文件,请申请人按要求填写并提交。</p>
|
||||
<div class="template-list">
|
||||
<div class="template-item">
|
||||
<span class="template-icon">📄</span>
|
||||
<span class="template-name">专家申请表(个人签字)</span>
|
||||
<a-button size="small" type="primary">下载模板</a-button>
|
||||
</div>
|
||||
<div class="template-item">
|
||||
<span class="template-icon">📋</span>
|
||||
<span class="template-name">专家申请说明文件</span>
|
||||
<a-button size="small" type="primary">下载模板</a-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 近期申请列表 -->
|
||||
<div class="table-card">
|
||||
<div class="table-header">
|
||||
<span class="table-title">近期申请记录</span>
|
||||
<a-button size="small" @click="navigateTo('/admin/experts/review')">查看全部并审核 →</a-button>
|
||||
</div>
|
||||
<a-table
|
||||
:columns="columns"
|
||||
:data-source="recentApplications"
|
||||
:loading="loading"
|
||||
row-key="id"
|
||||
:pagination="false"
|
||||
size="middle"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'status'">
|
||||
<a-tag :color="getStatusColor(record.status)">{{ getStatusText(record.status) }}</a-tag>
|
||||
</template>
|
||||
<template v-if="column.key === 'action'">
|
||||
<a-button size="small" @click="navigateTo('/admin/experts/review')">审核</a-button>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
definePageMeta({ layout: 'admin' })
|
||||
useHead({ title: '专家申请管理' })
|
||||
|
||||
const loading = ref(false)
|
||||
|
||||
const stats = reactive({ total: 12, pending: 3, approved: 8, rejected: 1 })
|
||||
|
||||
const columns = [
|
||||
{ title: '申请人', dataIndex: 'name', key: 'name' },
|
||||
{ title: '单位', dataIndex: 'organization', key: 'organization' },
|
||||
{ title: '研究领域', dataIndex: 'researchArea', key: 'researchArea' },
|
||||
{ title: '申请时间', dataIndex: 'applyTime', key: 'applyTime' },
|
||||
{ title: '状态', key: 'status', width: 100 },
|
||||
{ title: '操作', key: 'action', width: 80 },
|
||||
]
|
||||
|
||||
const recentApplications = ref([
|
||||
{ id: 1, name: '张某某', organization: '广西大学', researchArea: '区域经济', applyTime: '2024-12-18', status: 'pending' },
|
||||
{ id: 2, name: '李某某', organization: '广西社科院', researchArea: '产业政策', applyTime: '2024-12-17', status: 'pending' },
|
||||
{ id: 3, name: '王某某', organization: '广西师范大学', researchArea: '金融经济', applyTime: '2024-12-15', 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 loadData() {
|
||||
// TODO: 接入API
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.admin-applications-expert { display: flex; flex-direction: column; gap: 16px; }
|
||||
|
||||
.page-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
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;
|
||||
}
|
||||
|
||||
.stats-row {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.stat-item {
|
||||
background: #fff;
|
||||
border-radius: 12px;
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
box-shadow: 0 1px 4px rgba(0,0,0,0.06);
|
||||
}
|
||||
|
||||
.stat-item.blue { border-top: 3px solid #3b82f6; }
|
||||
.stat-item.orange { border-top: 3px solid #f97316; }
|
||||
.stat-item.green { border-top: 3px solid #22c55e; }
|
||||
.stat-item.red { border-top: 3px solid #ef4444; }
|
||||
|
||||
.stat-num {
|
||||
font-size: 32px;
|
||||
font-weight: 800;
|
||||
color: #1f2937;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 13px;
|
||||
color: #9ca3af;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.template-card, .table-card {
|
||||
background: #fff;
|
||||
border-radius: 12px;
|
||||
padding: 20px;
|
||||
box-shadow: 0 1px 4px rgba(0,0,0,0.06);
|
||||
}
|
||||
|
||||
.template-card h4, .table-header {
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
color: #1f2937;
|
||||
margin: 0 0 8px;
|
||||
}
|
||||
|
||||
.table-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.table-title {
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
color: #1f2937;
|
||||
}
|
||||
|
||||
.template-card p {
|
||||
font-size: 13px;
|
||||
color: #6b7280;
|
||||
margin: 0 0 12px;
|
||||
}
|
||||
|
||||
.template-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.template-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 10px 14px;
|
||||
background: #f9fafb;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.template-icon { font-size: 16px; }
|
||||
|
||||
.template-name {
|
||||
flex: 1;
|
||||
font-size: 14px;
|
||||
color: #374151;
|
||||
}
|
||||
</style>
|
||||
186
app/pages/admin/applications/member.vue
Normal file
186
app/pages/admin/applications/member.vue
Normal file
@@ -0,0 +1,186 @@
|
||||
<template>
|
||||
<div class="admin-applications-member">
|
||||
<div class="page-header">
|
||||
<h3>会员申请管理</h3>
|
||||
<a-space>
|
||||
<a-button type="primary" @click="navigateTo('/admin/members/review')">前往审核</a-button>
|
||||
</a-space>
|
||||
</div>
|
||||
|
||||
<div class="stats-row">
|
||||
<div class="stat-item blue">
|
||||
<div class="stat-num">{{ stats.total }}</div>
|
||||
<div class="stat-label">总申请</div>
|
||||
</div>
|
||||
<div class="stat-item orange">
|
||||
<div class="stat-num">{{ stats.pending }}</div>
|
||||
<div class="stat-label">待审核</div>
|
||||
</div>
|
||||
<div class="stat-item green">
|
||||
<div class="stat-num">{{ stats.approved }}</div>
|
||||
<div class="stat-label">已通过</div>
|
||||
</div>
|
||||
<div class="stat-item purple">
|
||||
<div class="stat-num">{{ stats.enterprise }}</div>
|
||||
<div class="stat-label">企业会员</div>
|
||||
</div>
|
||||
<div class="stat-item teal">
|
||||
<div class="stat-num">{{ stats.personal }}</div>
|
||||
<div class="stat-label">个人会员</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 材料模板 -->
|
||||
<div class="template-card">
|
||||
<h4>申请材料模板</h4>
|
||||
<a-tabs>
|
||||
<a-tab-pane key="enterprise" tab="企业会员模板">
|
||||
<div class="template-list">
|
||||
<div class="template-item">
|
||||
<span class="template-icon">📄</span>
|
||||
<span class="template-name">企业会员入会申请表(盖章)</span>
|
||||
<a-button size="small" type="primary">下载模板</a-button>
|
||||
</div>
|
||||
<div class="template-desc">所需材料:营业执照副本、法人身份证、单位简介</div>
|
||||
</div>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="personal" tab="个人会员模板">
|
||||
<div class="template-list">
|
||||
<div class="template-item">
|
||||
<span class="template-icon">📄</span>
|
||||
<span class="template-name">个人会员入会申请表(签字)</span>
|
||||
<a-button size="small" type="primary">下载模板</a-button>
|
||||
</div>
|
||||
<div class="template-desc">所需材料:个人简介、职称证书/学历证书、身份证、研究成果或获奖证明</div>
|
||||
</div>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</div>
|
||||
|
||||
<div class="table-card">
|
||||
<div class="table-header">
|
||||
<span class="table-title">近期申请记录</span>
|
||||
<a-button size="small" @click="navigateTo('/admin/members/review')">查看全部并审核 →</a-button>
|
||||
</div>
|
||||
<a-table
|
||||
:columns="columns"
|
||||
:data-source="recentApplications"
|
||||
row-key="id"
|
||||
:pagination="false"
|
||||
size="middle"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'type'">
|
||||
<a-tag :color="record.type === 'enterprise' ? 'blue' : 'green'">
|
||||
{{ record.type === 'enterprise' ? '企业' : '个人' }}
|
||||
</a-tag>
|
||||
</template>
|
||||
<template v-if="column.key === 'status'">
|
||||
<a-tag :color="record.status === 'pending' ? 'orange' : record.status === 'approved' ? 'green' : 'red'">
|
||||
{{ record.status === 'pending' ? '待审核' : record.status === 'approved' ? '已通过' : '已拒绝' }}
|
||||
</a-tag>
|
||||
</template>
|
||||
<template v-if="column.key === 'action'">
|
||||
<a-button size="small" @click="navigateTo('/admin/members/review')">审核</a-button>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
definePageMeta({ layout: 'admin' })
|
||||
useHead({ title: '会员申请管理' })
|
||||
|
||||
const stats = reactive({ total: 20, pending: 5, approved: 14, enterprise: 8, personal: 12 })
|
||||
|
||||
const columns = [
|
||||
{ title: '申请人', dataIndex: 'name', key: 'name' },
|
||||
{ title: '类型', key: 'type', width: 90 },
|
||||
{ title: '联系方式', dataIndex: 'contact', key: 'contact' },
|
||||
{ title: '申请时间', dataIndex: 'applyTime', key: 'applyTime' },
|
||||
{ title: '状态', key: 'status', width: 100 },
|
||||
{ title: '操作', key: 'action', width: 80 },
|
||||
]
|
||||
|
||||
const recentApplications = ref([
|
||||
{ id: 1, name: '广西某科技公司', type: 'enterprise', contact: '139****0001', applyTime: '2024-12-19', status: 'pending' },
|
||||
{ id: 2, name: '张某某', type: 'personal', contact: '138****0002', applyTime: '2024-12-18', status: 'pending' },
|
||||
{ id: 3, name: '南宁某咨询机构', type: 'enterprise', contact: '137****0003', applyTime: '2024-12-15', status: 'approved' },
|
||||
])
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.admin-applications-member { display: flex; flex-direction: column; gap: 16px; }
|
||||
|
||||
.page-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
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; }
|
||||
|
||||
.stats-row {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(5, 1fr);
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.stat-item {
|
||||
background: #fff;
|
||||
border-radius: 12px;
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
box-shadow: 0 1px 4px rgba(0,0,0,0.06);
|
||||
}
|
||||
|
||||
.stat-item.blue { border-top: 3px solid #3b82f6; }
|
||||
.stat-item.orange { border-top: 3px solid #f97316; }
|
||||
.stat-item.green { border-top: 3px solid #22c55e; }
|
||||
.stat-item.purple { border-top: 3px solid #8b5cf6; }
|
||||
.stat-item.teal { border-top: 3px solid #14b8a6; }
|
||||
|
||||
.stat-num { font-size: 28px; font-weight: 800; color: #1f2937; }
|
||||
.stat-label { font-size: 12px; color: #9ca3af; margin-top: 4px; }
|
||||
|
||||
.template-card, .table-card {
|
||||
background: #fff;
|
||||
border-radius: 12px;
|
||||
padding: 20px;
|
||||
box-shadow: 0 1px 4px rgba(0,0,0,0.06);
|
||||
}
|
||||
|
||||
.template-card h4 { font-size: 15px; font-weight: 600; color: #1f2937; margin: 0 0 12px; }
|
||||
|
||||
.table-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.table-title { font-size: 15px; font-weight: 600; color: #1f2937; }
|
||||
|
||||
.template-list { display: flex; flex-direction: column; gap: 8px; }
|
||||
|
||||
.template-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 10px 14px;
|
||||
background: #f9fafb;
|
||||
border-radius: 8px;
|
||||
}
|
||||
.template-icon { font-size: 16px; }
|
||||
.template-name { flex: 1; font-size: 14px; color: #374151; }
|
||||
|
||||
.template-desc {
|
||||
font-size: 12px;
|
||||
color: #9ca3af;
|
||||
padding: 4px 14px;
|
||||
}
|
||||
</style>
|
||||
334
app/pages/admin/categories.vue
Normal file
334
app/pages/admin/categories.vue
Normal file
@@ -0,0 +1,334 @@
|
||||
<template>
|
||||
<div class="admin-categories">
|
||||
<!-- 操作栏 -->
|
||||
<div class="toolbar">
|
||||
<div class="toolbar-left">
|
||||
<h3 class="page-title">栏目管理</h3>
|
||||
<span class="total-count">共 {{ total }} 个栏目</span>
|
||||
</div>
|
||||
<a-button type="primary" @click="handleAdd">
|
||||
<template #icon><PlusOutlined /></template>
|
||||
新增栏目
|
||||
</a-button>
|
||||
</div>
|
||||
|
||||
<!-- 栏目树形表格 -->
|
||||
<div class="table-card">
|
||||
<a-table
|
||||
:columns="columns"
|
||||
:data-source="dataSource"
|
||||
:loading="loading"
|
||||
row-key="id"
|
||||
:pagination="false"
|
||||
:expand-row-by-click="true"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'name'">
|
||||
<span class="category-name">{{ record.name }}</span>
|
||||
</template>
|
||||
<template v-if="column.key === 'type'">
|
||||
<a-tag :color="record.isSystem ? 'blue' : 'default'">
|
||||
{{ record.isSystem ? '系统栏目' : '自定义' }}
|
||||
</a-tag>
|
||||
</template>
|
||||
<template v-if="column.key === 'status'">
|
||||
<a-switch
|
||||
:checked="record.status === 1"
|
||||
@change="(val: boolean) => handleStatusChange(record, val)"
|
||||
checked-children="显示"
|
||||
un-checked-children="隐藏"
|
||||
/>
|
||||
</template>
|
||||
<template v-if="column.key === 'articleCount'">
|
||||
<a-badge :count="record.articleCount" :overflow-count="999">
|
||||
<a-button size="small" @click="goArticles(record)">查看文章</a-button>
|
||||
</a-badge>
|
||||
</template>
|
||||
<template v-if="column.key === 'action'">
|
||||
<a-space>
|
||||
<a-button size="small" @click="handleEdit(record)">编辑</a-button>
|
||||
<a-button size="small" @click="handleAddSub(record)" type="dashed">添加子栏目</a-button>
|
||||
<a-popconfirm
|
||||
:title="`确定删除栏目"${record.name}"吗?此操作不可恢复!`"
|
||||
@confirm="handleDelete(record)"
|
||||
ok-text="确定"
|
||||
cancel-text="取消"
|
||||
>
|
||||
<a-button size="small" danger :disabled="record.isSystem">删除</a-button>
|
||||
</a-popconfirm>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</div>
|
||||
|
||||
<!-- 新增/编辑弹窗 -->
|
||||
<a-modal
|
||||
v-model:open="modalVisible"
|
||||
:title="editingRecord ? '编辑栏目' : '新增栏目'"
|
||||
@ok="handleSave"
|
||||
:confirm-loading="saving"
|
||||
width="600px"
|
||||
>
|
||||
<a-form :model="formData" :rules="rules" ref="formRef" layout="vertical">
|
||||
<a-form-item label="上级栏目" name="parentId">
|
||||
<a-tree-select
|
||||
v-model:value="formData.parentId"
|
||||
:tree-data="treeSelectData"
|
||||
:field-names="{ label: 'name', value: 'id', children: 'children' }"
|
||||
placeholder="选择上级栏目(不选则为一级)"
|
||||
allow-clear
|
||||
tree-default-expand-all
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="栏目名称" name="name">
|
||||
<a-input v-model:value="formData.name" placeholder="请输入栏目名称" :maxlength="50" show-count />
|
||||
</a-form-item>
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="栏目标识(英文)" name="slug">
|
||||
<a-input v-model:value="formData.slug" placeholder="如 news / policy" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="排序权重" name="sort">
|
||||
<a-input-number v-model:value="formData.sort" :min="0" :max="9999" style="width:100%" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-form-item label="栏目描述" name="description">
|
||||
<a-textarea v-model:value="formData.description" :rows="3" placeholder="请输入栏目描述" />
|
||||
</a-form-item>
|
||||
<a-form-item label="封面图" name="cover">
|
||||
<a-input v-model:value="formData.cover" placeholder="封面图URL" />
|
||||
</a-form-item>
|
||||
<a-form-item label="状态" name="status">
|
||||
<a-radio-group v-model:value="formData.status">
|
||||
<a-radio :value="1">显示</a-radio>
|
||||
<a-radio :value="0">隐藏</a-radio>
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
<a-form-item label="是否需要会员权限" name="memberOnly">
|
||||
<a-switch v-model:checked="formData.memberOnly" checked-children="需要" un-checked-children="不需要" />
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { message } from 'ant-design-vue'
|
||||
import { PlusOutlined } from '@ant-design/icons-vue'
|
||||
|
||||
definePageMeta({ layout: 'admin' })
|
||||
useHead({ title: '栏目管理' })
|
||||
|
||||
const loading = ref(false)
|
||||
const saving = ref(false)
|
||||
const modalVisible = ref(false)
|
||||
const editingRecord = ref<any>(null)
|
||||
const formRef = ref()
|
||||
const total = ref(0)
|
||||
|
||||
const formData = reactive({
|
||||
parentId: undefined as number | undefined,
|
||||
name: '',
|
||||
slug: '',
|
||||
sort: 0,
|
||||
description: '',
|
||||
cover: '',
|
||||
status: 1,
|
||||
memberOnly: false,
|
||||
})
|
||||
|
||||
const rules = {
|
||||
name: [{ required: true, message: '请输入栏目名称' }],
|
||||
slug: [{ required: true, message: '请输入栏目标识' }],
|
||||
}
|
||||
|
||||
const columns = [
|
||||
{ title: '栏目名称', key: 'name', dataIndex: 'name' },
|
||||
{ title: '标识', dataIndex: 'slug', key: 'slug' },
|
||||
{ title: '类型', key: 'type' },
|
||||
{ title: '排序', dataIndex: 'sort', key: 'sort', width: 80 },
|
||||
{ title: '状态', key: 'status', width: 100 },
|
||||
{ title: '文章数', key: 'articleCount', width: 120 },
|
||||
{ title: '操作', key: 'action', width: 220 },
|
||||
]
|
||||
|
||||
const dataSource = ref<any[]>([
|
||||
{
|
||||
id: 1, name: '政策要闻', slug: 'news', sort: 1, status: 1, articleCount: 128, isSystem: true,
|
||||
children: [
|
||||
{ id: 11, name: '党中央国务院信息', slug: 'news-central', sort: 1, status: 1, articleCount: 42, isSystem: true },
|
||||
{ id: 12, name: '自治区党委政府信息', slug: 'news-region', sort: 2, status: 1, articleCount: 35, isSystem: true },
|
||||
{ id: 13, name: '其他厅委办信息', slug: 'news-department', sort: 3, status: 1, articleCount: 29, isSystem: true },
|
||||
{ id: 14, name: '最新发布', slug: 'news-latest', sort: 4, status: 1, articleCount: 22, isSystem: true },
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 2, name: '决策咨询', slug: 'consultation', sort: 2, status: 1, articleCount: 96, isSystem: true,
|
||||
children: [
|
||||
{ id: 21, name: '市县决策', slug: 'consult-city', sort: 1, status: 1, articleCount: 18, isSystem: true },
|
||||
{ id: 22, name: '前沿观察', slug: 'consult-frontier', sort: 2, status: 1, articleCount: 15, isSystem: true },
|
||||
{ id: 23, name: '行业资讯', slug: 'consult-industry', sort: 3, status: 1, articleCount: 20, isSystem: true },
|
||||
{ id: 24, name: '企业动态', slug: 'consult-enterprise', sort: 4, status: 1, articleCount: 12, isSystem: true },
|
||||
{ id: 25, name: '研究热点', slug: 'consult-research', sort: 5, status: 1, articleCount: 14, isSystem: true },
|
||||
{ id: 26, name: '学术活动', slug: 'consult-academic', sort: 6, status: 1, articleCount: 10, isSystem: true },
|
||||
{ id: 27, name: '其他汇编', slug: 'consult-other', sort: 7, status: 1, articleCount: 7, isSystem: true },
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 3, name: '决策参考', slug: 'reference', sort: 3, status: 1, articleCount: 75, isSystem: true,
|
||||
children: [
|
||||
{ id: 31, name: '政策原文', slug: 'ref-policy', sort: 1, status: 1, articleCount: 20, isSystem: true },
|
||||
{ id: 32, name: '深度解读', slug: 'ref-analysis', sort: 2, status: 1, articleCount: 15, isSystem: true },
|
||||
{ id: 33, name: '研究成果', slug: 'ref-research', sort: 3, status: 1, articleCount: 18, isSystem: true },
|
||||
{ id: 34, name: '专题研究', slug: 'ref-special', sort: 4, status: 1, articleCount: 12, isSystem: true },
|
||||
{ id: 35, name: '东盟研究', slug: 'ref-asean', sort: 5, status: 1, articleCount: 8, isSystem: true },
|
||||
{ id: 36, name: '数据服务', slug: 'ref-data', sort: 6, status: 1, articleCount: 2, isSystem: true },
|
||||
]
|
||||
},
|
||||
{ id: 4, name: '专家资讯', slug: 'expert', sort: 4, status: 1, articleCount: 52, isSystem: true },
|
||||
{
|
||||
id: 5, name: '智库观察', slug: 'think-tank', sort: 5, status: 1, articleCount: 38, isSystem: true,
|
||||
children: [
|
||||
{ id: 51, name: '智库介绍', slug: 'thinktank-intro', sort: 1, status: 1, articleCount: 16, isSystem: true },
|
||||
{ id: 52, name: '智库视角', slug: 'thinktank-view', sort: 2, status: 1, articleCount: 22, isSystem: true },
|
||||
]
|
||||
},
|
||||
{ id: 6, name: '建言献策', slug: 'suggestions', sort: 6, status: 1, articleCount: 0, isSystem: true },
|
||||
{ id: 7, name: '翰墨文谈', slug: 'hanmo', sort: 7, status: 1, articleCount: 24, isSystem: true },
|
||||
{ id: 8, name: '关于我们', slug: 'about', sort: 8, status: 1, articleCount: 5, isSystem: true },
|
||||
])
|
||||
|
||||
const treeSelectData = computed(() => dataSource.value.map(item => ({
|
||||
id: item.id,
|
||||
name: item.name,
|
||||
children: item.children,
|
||||
})))
|
||||
|
||||
function handleAdd() {
|
||||
editingRecord.value = null
|
||||
Object.assign(formData, { parentId: undefined, name: '', slug: '', sort: 0, description: '', cover: '', status: 1, memberOnly: false })
|
||||
modalVisible.value = true
|
||||
}
|
||||
|
||||
function handleAddSub(record: any) {
|
||||
editingRecord.value = null
|
||||
Object.assign(formData, { parentId: record.id, name: '', slug: '', sort: 0, description: '', cover: '', status: 1, memberOnly: false })
|
||||
modalVisible.value = true
|
||||
}
|
||||
|
||||
function handleEdit(record: any) {
|
||||
editingRecord.value = record
|
||||
Object.assign(formData, {
|
||||
parentId: record.parentId,
|
||||
name: record.name,
|
||||
slug: record.slug,
|
||||
sort: record.sort,
|
||||
description: record.description || '',
|
||||
cover: record.cover || '',
|
||||
status: record.status,
|
||||
memberOnly: record.memberOnly || false,
|
||||
})
|
||||
modalVisible.value = true
|
||||
}
|
||||
|
||||
async function handleSave() {
|
||||
try {
|
||||
await formRef.value?.validate()
|
||||
saving.value = true
|
||||
// TODO: 调用API
|
||||
message.success(editingRecord.value ? '栏目更新成功' : '栏目创建成功')
|
||||
modalVisible.value = false
|
||||
} catch (e: any) {
|
||||
if (e?.errorFields) return
|
||||
message.error(e?.message || '操作失败')
|
||||
} finally {
|
||||
saving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function handleDelete(record: any) {
|
||||
try {
|
||||
// TODO: 调用API
|
||||
message.success('栏目已删除')
|
||||
loadData()
|
||||
} catch (e: any) {
|
||||
message.error(e?.message || '删除失败')
|
||||
}
|
||||
}
|
||||
|
||||
function handleStatusChange(record: any, val: boolean) {
|
||||
record.status = val ? 1 : 0
|
||||
message.success(`栏目"${record.name}"已${val ? '显示' : '隐藏'}`)
|
||||
// TODO: 调用API
|
||||
}
|
||||
|
||||
function goArticles(record: any) {
|
||||
navigateTo(`/admin/articles?categoryId=${record.id}`)
|
||||
}
|
||||
|
||||
async function loadData() {
|
||||
loading.value = true
|
||||
try {
|
||||
// TODO: 接入实际API
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
total.value = 8
|
||||
// loadData()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.admin-categories {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.toolbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
background: #fff;
|
||||
border-radius: 10px;
|
||||
padding: 14px 20px;
|
||||
box-shadow: 0 1px 4px rgba(0,0,0,0.06);
|
||||
}
|
||||
|
||||
.toolbar-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
color: #1f2937;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.total-count {
|
||||
font-size: 13px;
|
||||
color: #9ca3af;
|
||||
}
|
||||
|
||||
.table-card {
|
||||
background: #fff;
|
||||
border-radius: 12px;
|
||||
padding: 20px;
|
||||
box-shadow: 0 1px 4px rgba(0,0,0,0.06);
|
||||
}
|
||||
|
||||
.category-name {
|
||||
font-weight: 500;
|
||||
color: #1f2937;
|
||||
}
|
||||
</style>
|
||||
244
app/pages/admin/downloads.vue
Normal file
244
app/pages/admin/downloads.vue
Normal file
@@ -0,0 +1,244 @@
|
||||
<template>
|
||||
<div class="admin-downloads">
|
||||
<div class="toolbar">
|
||||
<div class="toolbar-left">
|
||||
<h3 class="page-title">资料下载管理</h3>
|
||||
<a-tag>共 {{ total }} 个文件</a-tag>
|
||||
</div>
|
||||
<a-button type="primary" @click="handleAdd">
|
||||
<template #icon><PlusOutlined /></template>
|
||||
上传文件
|
||||
</a-button>
|
||||
</div>
|
||||
|
||||
<!-- 分类筛选 -->
|
||||
<div class="filter-bar">
|
||||
<a-radio-group v-model:value="activeCategory" button-style="solid" @change="loadData">
|
||||
<a-radio-button value="">全部</a-radio-button>
|
||||
<a-radio-button value="form">申请表格</a-radio-button>
|
||||
<a-radio-button value="report">研究报告</a-radio-button>
|
||||
<a-radio-button value="policy">政策文件</a-radio-button>
|
||||
<a-radio-button value="other">其他</a-radio-button>
|
||||
</a-radio-group>
|
||||
</div>
|
||||
|
||||
<div class="table-card">
|
||||
<a-table
|
||||
:columns="columns"
|
||||
:data-source="dataSource"
|
||||
:loading="loading"
|
||||
row-key="id"
|
||||
:pagination="{ total, pageSize: 15, showTotal: (t: number) => `共 ${t} 条` }"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'fileName'">
|
||||
<div class="file-info">
|
||||
<span class="file-icon">{{ getFileIcon(record.fileType) }}</span>
|
||||
<span class="file-name">{{ record.fileName }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<template v-if="column.key === 'category'">
|
||||
<a-tag>{{ getCategoryLabel(record.category) }}</a-tag>
|
||||
</template>
|
||||
<template v-if="column.key === 'memberOnly'">
|
||||
<a-tag :color="record.memberOnly ? 'blue' : 'default'">
|
||||
{{ record.memberOnly ? '会员专享' : '公开' }}
|
||||
</a-tag>
|
||||
</template>
|
||||
<template v-if="column.key === 'action'">
|
||||
<a-space>
|
||||
<a-button size="small" @click="handleEdit(record)">编辑</a-button>
|
||||
<a-button size="small" @click="previewFile(record)">预览</a-button>
|
||||
<a-popconfirm title="确定删除此文件?" @confirm="handleDelete(record)">
|
||||
<a-button size="small" danger>删除</a-button>
|
||||
</a-popconfirm>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</div>
|
||||
|
||||
<!-- 上传/编辑弹窗 -->
|
||||
<a-modal
|
||||
v-model:open="modalVisible"
|
||||
:title="editingRecord ? '编辑文件信息' : '上传文件'"
|
||||
@ok="handleSave"
|
||||
:confirm-loading="saving"
|
||||
width="560px"
|
||||
>
|
||||
<a-form :model="formData" layout="vertical">
|
||||
<a-form-item label="文件" v-if="!editingRecord">
|
||||
<a-upload
|
||||
v-model:file-list="fileList"
|
||||
:max-count="1"
|
||||
:before-upload="() => false"
|
||||
>
|
||||
<a-button><template #icon>📎</template>选择文件</a-button>
|
||||
</a-upload>
|
||||
</a-form-item>
|
||||
<a-form-item label="显示名称" required>
|
||||
<a-input v-model:value="formData.fileName" placeholder="请输入文件显示名称" />
|
||||
</a-form-item>
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="文件分类">
|
||||
<a-select v-model:value="formData.category">
|
||||
<a-select-option value="form">申请表格</a-select-option>
|
||||
<a-select-option value="report">研究报告</a-select-option>
|
||||
<a-select-option value="policy">政策文件</a-select-option>
|
||||
<a-select-option value="other">其他</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="访问权限">
|
||||
<a-radio-group v-model:value="formData.memberOnly">
|
||||
<a-radio :value="false">公开</a-radio>
|
||||
<a-radio :value="true">会员专享</a-radio>
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-form-item label="文件描述">
|
||||
<a-textarea v-model:value="formData.description" :rows="3" placeholder="请输入文件描述" />
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { message } from 'ant-design-vue'
|
||||
import { PlusOutlined } from '@ant-design/icons-vue'
|
||||
|
||||
definePageMeta({ layout: 'admin' })
|
||||
useHead({ title: '资料下载管理' })
|
||||
|
||||
const loading = ref(false)
|
||||
const saving = ref(false)
|
||||
const modalVisible = ref(false)
|
||||
const editingRecord = ref<any>(null)
|
||||
const fileList = ref<any[]>([])
|
||||
const activeCategory = ref('')
|
||||
const total = ref(0)
|
||||
|
||||
const formData = reactive({
|
||||
fileName: '',
|
||||
category: 'form',
|
||||
memberOnly: false,
|
||||
description: '',
|
||||
})
|
||||
|
||||
const columns = [
|
||||
{ title: '文件名称', key: 'fileName' },
|
||||
{ title: '分类', key: 'category', width: 110 },
|
||||
{ title: '文件大小', dataIndex: 'fileSize', key: 'fileSize', width: 100 },
|
||||
{ title: '下载次数', dataIndex: 'downloadCount', key: 'downloadCount', width: 100 },
|
||||
{ title: '权限', key: 'memberOnly', width: 100 },
|
||||
{ title: '上传时间', dataIndex: 'uploadTime', key: 'uploadTime', width: 150 },
|
||||
{ title: '操作', key: 'action', width: 200 },
|
||||
]
|
||||
|
||||
const dataSource = ref([
|
||||
{ id: 1, fileName: '企业会员入会申请表.docx', fileType: 'docx', category: 'form', fileSize: '35KB', downloadCount: 128, memberOnly: false, uploadTime: '2024-11-01' },
|
||||
{ id: 2, fileName: '个人会员入会申请表.docx', fileType: 'docx', category: 'form', fileSize: '32KB', downloadCount: 96, memberOnly: false, uploadTime: '2024-11-01' },
|
||||
{ id: 3, fileName: '专家申请表.docx', fileType: 'docx', category: 'form', fileSize: '40KB', downloadCount: 65, memberOnly: false, uploadTime: '2024-11-01' },
|
||||
{ id: 4, fileName: '广西经济社会发展研究报告2024.pdf', fileType: 'pdf', category: 'report', fileSize: '2.8MB', downloadCount: 342, memberOnly: true, uploadTime: '2024-12-01' },
|
||||
{ id: 5, fileName: '广西数字经济政策汇编.pdf', fileType: 'pdf', category: 'policy', fileSize: '1.2MB', downloadCount: 215, memberOnly: false, uploadTime: '2024-11-15' },
|
||||
])
|
||||
|
||||
function getFileIcon(type: string) {
|
||||
const iconMap: Record<string, string> = { pdf: '📕', docx: '📘', doc: '📘', xlsx: '📗', pptx: '📙', zip: '📦' }
|
||||
return iconMap[type] || '📄'
|
||||
}
|
||||
|
||||
function getCategoryLabel(cat: string) {
|
||||
const map: Record<string, string> = { form: '申请表格', report: '研究报告', policy: '政策文件', other: '其他' }
|
||||
return map[cat] || cat
|
||||
}
|
||||
|
||||
function handleAdd() {
|
||||
editingRecord.value = null
|
||||
Object.assign(formData, { fileName: '', category: 'form', memberOnly: false, description: '' })
|
||||
fileList.value = []
|
||||
modalVisible.value = true
|
||||
}
|
||||
|
||||
function handleEdit(record: any) {
|
||||
editingRecord.value = record
|
||||
Object.assign(formData, { fileName: record.fileName, category: record.category, memberOnly: record.memberOnly, description: record.description || '' })
|
||||
modalVisible.value = true
|
||||
}
|
||||
|
||||
async function handleSave() {
|
||||
saving.value = true
|
||||
try {
|
||||
message.success(editingRecord.value ? '文件信息已更新' : '文件上传成功')
|
||||
modalVisible.value = false
|
||||
loadData()
|
||||
} finally {
|
||||
saving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function handleDelete(record: any) {
|
||||
// TODO: 调用API
|
||||
message.success('文件已删除')
|
||||
}
|
||||
|
||||
function previewFile(record: any) {
|
||||
message.info(`预览:${record.fileName}`)
|
||||
}
|
||||
|
||||
async function loadData() {
|
||||
total.value = dataSource.value.length
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadData()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.admin-downloads { display: flex; flex-direction: column; gap: 16px; }
|
||||
|
||||
.toolbar, .filter-bar, .table-card {
|
||||
background: #fff;
|
||||
border-radius: 12px;
|
||||
padding: 16px 20px;
|
||||
box-shadow: 0 1px 4px rgba(0,0,0,0.06);
|
||||
}
|
||||
|
||||
.toolbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.toolbar-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
color: #1f2937;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.file-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.file-icon { font-size: 18px; }
|
||||
|
||||
.file-name {
|
||||
font-size: 14px;
|
||||
color: #1f2937;
|
||||
font-weight: 500;
|
||||
}
|
||||
</style>
|
||||
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>
|
||||
324
app/pages/admin/members/review.vue
Normal file
324
app/pages/admin/members/review.vue
Normal file
@@ -0,0 +1,324 @@
|
||||
<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>
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user