feat(about): 重构“关于我们”页面并丰富内容展示

- 采用左右分栏布局,左侧新增图标导航
- 全新设计顶部 Banner,提升视觉效果
- 添加学会简介数据亮点和主要职能展示
- 新增组织机构图、主要领导及专家委员会成员展示
- 引入学会章程章节分明条目展示
- 丰富咨询服务内容,新增服务项目卡片和联系方式
- “加入我们”板块支持企业与个人会员申请详情说明
- 支持资料下载并优化排版与交互体验
- 增强响应式支持,保证移动端体验一致
- 页面样式大幅调整,提升整体美观与可读性
This commit is contained in:
2026-04-26 01:44:07 +08:00
parent 17ce26411d
commit 56aea4ad86
26 changed files with 5468 additions and 1741 deletions

View 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>

View 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>

View 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>

View 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>

View 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>

View 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