feat(about): 重构“关于我们”页面并丰富内容展示
- 采用左右分栏布局,左侧新增图标导航 - 全新设计顶部 Banner,提升视觉效果 - 添加学会简介数据亮点和主要职能展示 - 新增组织机构图、主要领导及专家委员会成员展示 - 引入学会章程章节分明条目展示 - 丰富咨询服务内容,新增服务项目卡片和联系方式 - “加入我们”板块支持企业与个人会员申请详情说明 - 支持资料下载并优化排版与交互体验 - 增强响应式支持,保证移动端体验一致 - 页面样式大幅调整,提升整体美观与可读性
This commit is contained in:
Binary file not shown.
@@ -11,7 +11,18 @@
|
|||||||
"usedAt": 1776933111908,
|
"usedAt": 1776933111908,
|
||||||
"industryId": "02-Engineering"
|
"industryId": "02-Engineering"
|
||||||
}
|
}
|
||||||
|
],
|
||||||
|
"f49030fc1f92485a83c29c921cac6a72": [
|
||||||
|
{
|
||||||
|
"expertId": "SeniorDeveloper",
|
||||||
|
"name": "吴八哥",
|
||||||
|
"profession": "高级开发工程师",
|
||||||
|
"avatarUrl": "https://acc-1258344699.cos.accelerate.myqcloud.com/workbuddy/experts/avatars/02-Engineering/SeniorDeveloper/SeniorDeveloper.png",
|
||||||
|
"promptUrl": "https://acc-1258344699.cos.accelerate.myqcloud.com/workbuddy/experts/experts/02-Engineering/SeniorDeveloper/SeniorDeveloper_zh.md",
|
||||||
|
"usedAt": 1777135926044,
|
||||||
|
"industryId": "02-Engineering"
|
||||||
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"lastUpdated": 1776935969275
|
"lastUpdated": 1777136548111
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
# 项目长期记忆
|
||||||
|
|
||||||
|
## 项目信息
|
||||||
|
- **项目名称**:广西决策咨询网(jczxw-pc)
|
||||||
|
- **PC端路径**:/Users/gxwebsoft/VUE/jczxw-pc(Nuxt 3 + Ant Design Vue + Tailwind CSS)
|
||||||
|
- **Java后端路径**:/Users/gxwebsoft/JAVA/jczxw-java
|
||||||
|
- **后台管理**:集成在PC端 /admin 目录下(不单独部署)
|
||||||
|
|
||||||
|
## 技术栈
|
||||||
|
- Nuxt 3 + Vue 3 + TypeScript
|
||||||
|
- Ant Design Vue
|
||||||
|
- Tailwind CSS
|
||||||
|
- i18n(中英文)
|
||||||
|
- @nuxt/content
|
||||||
|
|
||||||
|
## 网站结构(已确认菜单)
|
||||||
|
### 一级菜单
|
||||||
|
首页、政策要闻、决策咨询、决策参考、专家资讯、智库观察、建言献策、会员服务、翰墨文谈、关于我们
|
||||||
|
|
||||||
|
### 二级菜单
|
||||||
|
1. **政策要闻**:党中央国务院信息、自治区党委政府信息、其他(厅委办)信息、最新发布
|
||||||
|
2. **决策咨询**:市县决策、前沿观察、行业资讯、企业动态、研究热点、学术活动、其他汇编
|
||||||
|
3. **决策参考**:政策原文、深度解读、研究成果、专题研究、东盟研究、数据服务(会员限制)
|
||||||
|
4. **专家资讯**:专家视点、专家动态、专家申请(含资料上传审核系统)
|
||||||
|
5. **智库观察**:智库介绍、智库视角
|
||||||
|
6. **建言献策**:后台处理显示,需注册身份
|
||||||
|
7. **会员服务**:企业咨询、专项服务
|
||||||
|
8. **翰墨文谈**:(独立栏目)
|
||||||
|
9. **关于我们**:学会简介、组织机构、学会章程、咨询服务、加入我们(含会员申请+资料上传审核)
|
||||||
|
|
||||||
|
## 已有页面
|
||||||
|
- app/pages/index.vue(首页,已完成)
|
||||||
|
- app/pages/news/index.vue(政策要闻列表)
|
||||||
|
- app/pages/consultation/(决策咨询)
|
||||||
|
- app/pages/reference/(决策参考)
|
||||||
|
- app/pages/expert/index.vue(专家资讯列表)
|
||||||
|
- app/pages/expert/apply.vue(专家申请)
|
||||||
|
- app/pages/think-tank/(智库观察)
|
||||||
|
- app/pages/suggestions/(建言献策)
|
||||||
|
- app/pages/membership/(会员服务)
|
||||||
|
- app/pages/hanmo/(翰墨文谈)
|
||||||
|
- app/pages/about/index.vue(关于我们)
|
||||||
|
- app/pages/about/join/enterprise.vue(企业会员申请)
|
||||||
|
- app/pages/about/join/personal.vue(个人会员申请)
|
||||||
|
- app/pages/admin/(后台管理,多个管理页面)
|
||||||
|
|
||||||
|
## 待补充页面(需制作)
|
||||||
|
- 各栏目的详情页(文章详情)
|
||||||
|
- 关于我们子页:组织机构、学会章程、咨询服务、资料下载
|
||||||
|
- 政策要闻子分类页
|
||||||
|
- 专家详情页
|
||||||
|
- 个人中心 /profile
|
||||||
|
- 建言献策提交表单
|
||||||
|
- 后台:栏目管理、专家审核、会员审核详情、申请审核系统
|
||||||
|
|
||||||
|
## 设计风格
|
||||||
|
- 主色:深蓝色 #1e3a5f / #0d1b2a
|
||||||
|
- 强调色:橙色 #f97316
|
||||||
|
- 背景:白色卡片 + 浅灰底
|
||||||
|
- 风格:政务门户,专业大气
|
||||||
|
|||||||
368
app/components/ArticleListPage.vue
Normal file
368
app/components/ArticleListPage.vue
Normal file
@@ -0,0 +1,368 @@
|
|||||||
|
<template>
|
||||||
|
<div class="list-page">
|
||||||
|
<!-- 页面头部 Banner -->
|
||||||
|
<div class="page-banner" :style="{ background: config.bannerGradient }">
|
||||||
|
<div class="mx-auto max-w-screen-xl px-4">
|
||||||
|
<h1 class="banner-title">{{ config.title }}</h1>
|
||||||
|
<p class="banner-desc">{{ config.desc }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mx-auto max-w-screen-xl px-4 py-8">
|
||||||
|
<a-row :gutter="[32, 0]">
|
||||||
|
<!-- 左侧分类导航 -->
|
||||||
|
<a-col :xs="24" :lg="5" class="mb-6 lg:mb-0">
|
||||||
|
<div class="category-sidebar">
|
||||||
|
<div class="category-sidebar-title">{{ config.title }}</div>
|
||||||
|
<div
|
||||||
|
v-for="cat in config.categories"
|
||||||
|
:key="cat.type"
|
||||||
|
class="category-item"
|
||||||
|
:class="{ active: activeType === cat.type }"
|
||||||
|
@click="selectType(cat.type)"
|
||||||
|
>
|
||||||
|
{{ cat.label }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a-col>
|
||||||
|
|
||||||
|
<!-- 右侧内容区 -->
|
||||||
|
<a-col :xs="24" :lg="19">
|
||||||
|
<!-- 当前分类提示 -->
|
||||||
|
<div class="category-breadcrumb">
|
||||||
|
<span class="category-name">{{ currentCategoryLabel }}</span>
|
||||||
|
<span class="article-count">共 {{ total }} 篇文章</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="loading" class="loading-state">
|
||||||
|
<a-skeleton v-for="i in 5" :key="i" active :paragraph="{ rows: 2 }" style="margin-bottom:16px" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-else>
|
||||||
|
<div class="article-list">
|
||||||
|
<div
|
||||||
|
v-for="article in articles"
|
||||||
|
:key="article.id"
|
||||||
|
class="article-item"
|
||||||
|
@click="handleView(article)"
|
||||||
|
>
|
||||||
|
<div class="article-thumb" v-if="article.image">
|
||||||
|
<img :src="article.image" :alt="article.title" />
|
||||||
|
</div>
|
||||||
|
<div class="article-main">
|
||||||
|
<h3 class="article-title">{{ article.title }}</h3>
|
||||||
|
<p class="article-overview">{{ article.overview }}</p>
|
||||||
|
<div class="article-meta">
|
||||||
|
<span class="meta-tag" v-if="article.type">{{ getCategoryLabel(article.type) }}</span>
|
||||||
|
<span class="meta-item">{{ article.source }}</span>
|
||||||
|
<span class="meta-item">{{ article.publishTime }}</span>
|
||||||
|
<span class="meta-item" v-if="article.views">👁 {{ article.views }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="articles.length === 0" class="empty-state">
|
||||||
|
<a-empty description="暂无内容" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="pagination-wrap" v-if="total > pageSize">
|
||||||
|
<a-pagination
|
||||||
|
v-model:current="currentPage"
|
||||||
|
:total="total"
|
||||||
|
:page-size="pageSize"
|
||||||
|
show-quick-jumper
|
||||||
|
@change="handlePageChange"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { message } from 'ant-design-vue'
|
||||||
|
|
||||||
|
interface PageConfig {
|
||||||
|
title: string
|
||||||
|
desc: string
|
||||||
|
bannerGradient: string
|
||||||
|
categories: Array<{ type: string; label: string }>
|
||||||
|
baseRoute: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
config: PageConfig
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const route = useRoute()
|
||||||
|
const router = useRouter()
|
||||||
|
const activeType = ref((route.query.type as string) || '')
|
||||||
|
const currentPage = ref(1)
|
||||||
|
const pageSize = ref(12)
|
||||||
|
const total = ref(0)
|
||||||
|
const loading = ref(false)
|
||||||
|
const articles = ref<any[]>([])
|
||||||
|
|
||||||
|
const currentCategoryLabel = computed(() => {
|
||||||
|
if (!activeType.value) return '全部文章'
|
||||||
|
const found = props.config.categories.find(c => c.type === activeType.value)
|
||||||
|
return found?.label || '全部文章'
|
||||||
|
})
|
||||||
|
|
||||||
|
function getCategoryLabel(type: string) {
|
||||||
|
const found = props.config.categories.find(c => c.type === type)
|
||||||
|
return found?.label || type
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectType(type: string) {
|
||||||
|
activeType.value = type
|
||||||
|
currentPage.value = 1
|
||||||
|
router.replace({ query: type ? { type } : {} })
|
||||||
|
loadArticles()
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadArticles() {
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
|
// TODO: 接入实际API
|
||||||
|
// const res = await listArticles({ category: props.config.baseRoute, type: activeType.value, page: currentPage.value })
|
||||||
|
// Fallback mock data
|
||||||
|
total.value = 35
|
||||||
|
articles.value = Array.from({ length: Math.min(pageSize.value, 35 - (currentPage.value - 1) * pageSize.value) }, (_, i) => ({
|
||||||
|
id: (currentPage.value - 1) * pageSize.value + i + 1,
|
||||||
|
title: `${currentCategoryLabel.value}文章标题 ${(currentPage.value - 1) * pageSize.value + i + 1}:广西政策研究成果发布`,
|
||||||
|
overview: '摘要内容:本文就广西经济社会发展中的若干重大问题进行深入研究,提出了切实可行的政策建议和对策措施,为相关决策提供参考依据...',
|
||||||
|
image: `https://picsum.photos/200/130?random=${(currentPage.value - 1) * pageSize.value + i + 1}`,
|
||||||
|
source: '广西决策咨询中心',
|
||||||
|
publishTime: `2024-12-${String(20 - i).padStart(2, '0')}`,
|
||||||
|
views: Math.floor(Math.random() * 2000) + 100,
|
||||||
|
type: activeType.value || props.config.categories[i % props.config.categories.length]?.type,
|
||||||
|
}))
|
||||||
|
} catch (e: any) {
|
||||||
|
message.error('加载失败')
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handlePageChange(page: number) {
|
||||||
|
currentPage.value = page
|
||||||
|
loadArticles()
|
||||||
|
window.scrollTo({ top: 0, behavior: 'smooth' })
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleView(article: any) {
|
||||||
|
router.push(`/article/${article.id}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(() => route.query.type, (newType) => {
|
||||||
|
activeType.value = (newType as string) || ''
|
||||||
|
currentPage.value = 1
|
||||||
|
loadArticles()
|
||||||
|
})
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
loadArticles()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.list-page {
|
||||||
|
background: #f5f7fa;
|
||||||
|
min-height: 60vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-banner {
|
||||||
|
padding: 48px 0 32px;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.banner-title {
|
||||||
|
color: #fff;
|
||||||
|
font-size: 30px;
|
||||||
|
font-weight: 700;
|
||||||
|
margin: 0 0 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.banner-desc {
|
||||||
|
color: rgba(255,255,255,0.75);
|
||||||
|
font-size: 15px;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 左侧分类 */
|
||||||
|
.category-sidebar {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 12px;
|
||||||
|
box-shadow: 0 2px 8px rgba(0,0,0,0.06);
|
||||||
|
overflow: hidden;
|
||||||
|
position: sticky;
|
||||||
|
top: 80px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-sidebar-title {
|
||||||
|
padding: 14px 18px;
|
||||||
|
background: #1e3a5f;
|
||||||
|
color: #fff;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-item {
|
||||||
|
padding: 12px 18px;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #374151;
|
||||||
|
cursor: pointer;
|
||||||
|
border-bottom: 1px solid #f5f5f5;
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-item:hover {
|
||||||
|
background: #f0f7ff;
|
||||||
|
color: #1e3a5f;
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-item.active {
|
||||||
|
background: #eff6ff;
|
||||||
|
color: #1e3a5f;
|
||||||
|
font-weight: 600;
|
||||||
|
border-left: 3px solid #1e3a5f;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 内容区 */
|
||||||
|
.category-breadcrumb {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 12px 16px;
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 8px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
box-shadow: 0 1px 4px rgba(0,0,0,0.04);
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-name {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #1e3a5f;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-count {
|
||||||
|
font-size: 13px;
|
||||||
|
color: #9ca3af;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-item {
|
||||||
|
display: flex;
|
||||||
|
gap: 20px;
|
||||||
|
padding: 20px;
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 12px;
|
||||||
|
box-shadow: 0 2px 6px rgba(0,0,0,0.05);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-item:hover {
|
||||||
|
box-shadow: 0 4px 16px rgba(0,0,0,0.1);
|
||||||
|
transform: translateY(-2px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-thumb {
|
||||||
|
width: 160px;
|
||||||
|
height: 108px;
|
||||||
|
border-radius: 8px;
|
||||||
|
overflow: hidden;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-thumb img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-main {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-title {
|
||||||
|
font-size: 17px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #1f2937;
|
||||||
|
margin: 0 0 8px;
|
||||||
|
line-height: 1.4;
|
||||||
|
display: -webkit-box;
|
||||||
|
-webkit-line-clamp: 2;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-overview {
|
||||||
|
font-size: 13px;
|
||||||
|
color: #6b7280;
|
||||||
|
margin: 0 0 auto;
|
||||||
|
line-height: 1.6;
|
||||||
|
display: -webkit-box;
|
||||||
|
-webkit-line-clamp: 2;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-meta {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
margin-top: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meta-tag {
|
||||||
|
padding: 2px 8px;
|
||||||
|
background: #eff6ff;
|
||||||
|
color: #1e40af;
|
||||||
|
font-size: 11px;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meta-item {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #9ca3af;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-state {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-state {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 60px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination-wrap {
|
||||||
|
margin-top: 32px;
|
||||||
|
text-align: center;
|
||||||
|
padding-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.article-item { flex-direction: column; }
|
||||||
|
.article-thumb { width: 100%; height: 180px; }
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,15 +1,19 @@
|
|||||||
import type { Component } from 'vue'
|
import type { Component } from 'vue'
|
||||||
import {
|
import {
|
||||||
AppstoreOutlined,
|
|
||||||
AuditOutlined,
|
|
||||||
DashboardOutlined,
|
DashboardOutlined,
|
||||||
|
FileTextOutlined,
|
||||||
TeamOutlined,
|
TeamOutlined,
|
||||||
SettingOutlined,
|
SettingOutlined,
|
||||||
FileTextOutlined,
|
AuditOutlined,
|
||||||
CustomerServiceOutlined,
|
MessageOutlined,
|
||||||
ShopOutlined,
|
UserOutlined,
|
||||||
GitlabOutlined,
|
SolutionOutlined,
|
||||||
GlobalOutlined,
|
BankOutlined,
|
||||||
|
NotificationOutlined,
|
||||||
|
FolderOutlined,
|
||||||
|
BookOutlined,
|
||||||
|
StarOutlined,
|
||||||
|
FormOutlined,
|
||||||
} from '@ant-design/icons-vue'
|
} from '@ant-design/icons-vue'
|
||||||
|
|
||||||
export type AdminNavItem = {
|
export type AdminNavItem = {
|
||||||
@@ -42,63 +46,73 @@ export const adminNav: AdminNavEntry[] = [
|
|||||||
icon: DashboardOutlined,
|
icon: DashboardOutlined,
|
||||||
to: '/admin',
|
to: '/admin',
|
||||||
},
|
},
|
||||||
{
|
// ── 内容管理 ──
|
||||||
key: 'admin-app-review',
|
|
||||||
label: '应用审核',
|
|
||||||
icon: AuditOutlined,
|
|
||||||
badge: 'NEW',
|
|
||||||
to: '/admin/app-review',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'admin-git-review',
|
|
||||||
label: 'Git 审核',
|
|
||||||
icon: GitlabOutlined,
|
|
||||||
to: '/admin/git-review',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'admin-apps',
|
|
||||||
label: '应用管理',
|
|
||||||
icon: AppstoreOutlined,
|
|
||||||
children: [
|
|
||||||
{ key: 'admin-all-apps', label: '全局应用', icon: GlobalOutlined, to: '/admin/all-apps' },
|
|
||||||
{ key: 'admin-apps-list', label: '我的应用', icon: AppstoreOutlined, to: '/admin/apps' },
|
|
||||||
{ key: 'admin-market', label: '应用市场', icon: ShopOutlined, to: '/admin/market' },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'admin-users',
|
|
||||||
label: '用户管理',
|
|
||||||
icon: TeamOutlined,
|
|
||||||
children: [
|
|
||||||
{ key: 'admin-users-list', label: '所有用户', icon: TeamOutlined, to: '/admin/users' },
|
|
||||||
{ key: 'admin-developers', label: '开发者', icon: TeamOutlined, to: '/admin/developers' },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'admin-tickets',
|
|
||||||
label: '工单处理',
|
|
||||||
icon: CustomerServiceOutlined,
|
|
||||||
to: '/admin/tickets',
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
key: 'admin-content',
|
key: 'admin-content',
|
||||||
label: '内容管理',
|
label: '内容管理',
|
||||||
icon: FileTextOutlined,
|
icon: FileTextOutlined,
|
||||||
children: [
|
children: [
|
||||||
{ key: 'admin-articles', label: '文章管理', icon: FileTextOutlined, to: '/admin/articles' },
|
{ key: 'admin-articles', label: '文章管理', icon: FileTextOutlined, to: '/admin/articles' },
|
||||||
{ key: 'admin-article-categories', label: '文章分类', icon: FileTextOutlined, to: '/admin/article-categories' },
|
{ key: 'admin-categories', label: '栏目管理', icon: FolderOutlined, to: '/admin/categories' },
|
||||||
{ key: 'admin-announcements', label: '公告管理', icon: FileTextOutlined, to: '/admin/announcements' },
|
{ key: 'admin-announcements', label: '公告管理', icon: NotificationOutlined, to: '/admin/announcements' },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
// ── 专家管理 ──
|
||||||
{
|
{
|
||||||
key: 'admin-tenant',
|
key: 'admin-experts',
|
||||||
label: '租户管理',
|
label: '专家管理',
|
||||||
icon: ShopOutlined,
|
icon: StarOutlined,
|
||||||
to: '/admin/tenants',
|
children: [
|
||||||
|
{ key: 'admin-experts-list', label: '专家列表', icon: SolutionOutlined, to: '/admin/experts' },
|
||||||
|
{ key: 'admin-experts-review', label: '专家审核', icon: AuditOutlined, to: '/admin/experts/review', },
|
||||||
|
],
|
||||||
},
|
},
|
||||||
|
// ── 会员管理 ──
|
||||||
|
{
|
||||||
|
key: 'admin-members',
|
||||||
|
label: '会员管理',
|
||||||
|
icon: BankOutlined,
|
||||||
|
children: [
|
||||||
|
{ key: 'admin-members-list', label: '会员列表', icon: TeamOutlined, to: '/admin/members' },
|
||||||
|
{ key: 'admin-members-review', label: '会员审核', icon: AuditOutlined, to: '/admin/members/review' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
// ── 建言献策 ──
|
||||||
|
{
|
||||||
|
key: 'admin-suggestions',
|
||||||
|
label: '建言献策',
|
||||||
|
icon: MessageOutlined,
|
||||||
|
to: '/admin/suggestions',
|
||||||
|
badge: 'NEW',
|
||||||
|
},
|
||||||
|
// ── 用户管理 ──
|
||||||
|
{
|
||||||
|
key: 'admin-users',
|
||||||
|
label: '用户管理',
|
||||||
|
icon: UserOutlined,
|
||||||
|
to: '/admin/users',
|
||||||
|
},
|
||||||
|
// ── 资料下载 ──
|
||||||
|
{
|
||||||
|
key: 'admin-downloads',
|
||||||
|
label: '资料下载管理',
|
||||||
|
icon: BookOutlined,
|
||||||
|
to: '/admin/downloads',
|
||||||
|
},
|
||||||
|
// ── 申请管理 ──
|
||||||
|
{
|
||||||
|
key: 'admin-applications',
|
||||||
|
label: '申请管理',
|
||||||
|
icon: FormOutlined,
|
||||||
|
children: [
|
||||||
|
{ key: 'admin-expert-applications', label: '专家申请', icon: SolutionOutlined, to: '/admin/applications/expert' },
|
||||||
|
{ key: 'admin-member-applications', label: '会员申请', icon: TeamOutlined, to: '/admin/applications/member' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
// ── 系统设置 ──
|
||||||
{
|
{
|
||||||
key: 'admin-settings',
|
key: 'admin-settings',
|
||||||
label: '平台设置',
|
label: '系统设置',
|
||||||
icon: SettingOutlined,
|
icon: SettingOutlined,
|
||||||
to: '/admin/settings',
|
to: '/admin/settings',
|
||||||
},
|
},
|
||||||
|
|||||||
236
app/error.vue
Normal file
236
app/error.vue
Normal file
@@ -0,0 +1,236 @@
|
|||||||
|
<template>
|
||||||
|
<div class="error-page">
|
||||||
|
<div class="error-content">
|
||||||
|
<!-- 装饰背景 -->
|
||||||
|
<div class="error-bg">
|
||||||
|
<div class="bg-circle circle-1"></div>
|
||||||
|
<div class="bg-circle circle-2"></div>
|
||||||
|
<div class="bg-circle circle-3"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 主内容 -->
|
||||||
|
<div class="error-main">
|
||||||
|
<div class="error-code">{{ error?.statusCode || 404 }}</div>
|
||||||
|
<div class="error-title">{{ errorTitle }}</div>
|
||||||
|
<div class="error-desc">{{ errorDesc }}</div>
|
||||||
|
|
||||||
|
<div class="error-actions">
|
||||||
|
<a-button type="primary" size="large" @click="goHome">
|
||||||
|
🏠 返回首页
|
||||||
|
</a-button>
|
||||||
|
<a-button size="large" @click="goBack">
|
||||||
|
← 返回上一页
|
||||||
|
</a-button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 快速导航 -->
|
||||||
|
<div class="quick-nav-section">
|
||||||
|
<div class="quick-nav-title">您可能想访问</div>
|
||||||
|
<div class="quick-nav-grid">
|
||||||
|
<NuxtLink to="/" class="quick-nav-item">首页</NuxtLink>
|
||||||
|
<NuxtLink to="/news" class="quick-nav-item">政策要闻</NuxtLink>
|
||||||
|
<NuxtLink to="/consultation" class="quick-nav-item">决策咨询</NuxtLink>
|
||||||
|
<NuxtLink to="/expert" class="quick-nav-item">专家资讯</NuxtLink>
|
||||||
|
<NuxtLink to="/think-tank" class="quick-nav-item">智库观察</NuxtLink>
|
||||||
|
<NuxtLink to="/suggestions" class="quick-nav-item">建言献策</NuxtLink>
|
||||||
|
<NuxtLink to="/about" class="quick-nav-item">关于我们</NuxtLink>
|
||||||
|
<NuxtLink to="/membership" class="quick-nav-item">会员服务</NuxtLink>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
const props = defineProps<{
|
||||||
|
error?: {
|
||||||
|
statusCode?: number
|
||||||
|
message?: string
|
||||||
|
}
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const errorCode = computed(() => props.error?.statusCode || 404)
|
||||||
|
|
||||||
|
const errorTitle = computed(() => {
|
||||||
|
switch (errorCode.value) {
|
||||||
|
case 403: return '没有访问权限'
|
||||||
|
case 404: return '页面不存在'
|
||||||
|
case 500: return '服务器错误'
|
||||||
|
default: return '出错了'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const errorDesc = computed(() => {
|
||||||
|
switch (errorCode.value) {
|
||||||
|
case 403: return '抱歉,您没有权限访问此页面'
|
||||||
|
case 404: return '抱歉,您访问的页面不存在或已被删除'
|
||||||
|
case 500: return '抱歉,服务器发生了一些问题,请稍后再试'
|
||||||
|
default: return '抱歉,发生了一些未知错误'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
function goHome() {
|
||||||
|
clearError({ redirect: '/' })
|
||||||
|
}
|
||||||
|
|
||||||
|
function goBack() {
|
||||||
|
if (import.meta.client) {
|
||||||
|
window.history.back()
|
||||||
|
}
|
||||||
|
clearError({ redirect: '/' })
|
||||||
|
}
|
||||||
|
|
||||||
|
useHead({
|
||||||
|
title: computed(() => `${errorCode.value} - ${errorTitle.value} | 广西决策咨询网`),
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.error-page {
|
||||||
|
min-height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background: linear-gradient(135deg, #f0f4ff 0%, #e8f0fe 50%, #f5f3ff 100%);
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-content {
|
||||||
|
text-align: center;
|
||||||
|
padding: 40px 20px;
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 装饰圆形 */
|
||||||
|
.error-bg {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
.bg-circle {
|
||||||
|
position: absolute;
|
||||||
|
border-radius: 50%;
|
||||||
|
opacity: 0.08;
|
||||||
|
}
|
||||||
|
.circle-1 {
|
||||||
|
width: 600px;
|
||||||
|
height: 600px;
|
||||||
|
background: #1e3a5f;
|
||||||
|
top: -200px;
|
||||||
|
right: -100px;
|
||||||
|
}
|
||||||
|
.circle-2 {
|
||||||
|
width: 400px;
|
||||||
|
height: 400px;
|
||||||
|
background: #3b82f6;
|
||||||
|
bottom: -100px;
|
||||||
|
left: -100px;
|
||||||
|
}
|
||||||
|
.circle-3 {
|
||||||
|
width: 200px;
|
||||||
|
height: 200px;
|
||||||
|
background: #f97316;
|
||||||
|
top: 50%;
|
||||||
|
left: 20%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-main {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-code {
|
||||||
|
font-size: 160px;
|
||||||
|
font-weight: 900;
|
||||||
|
line-height: 1;
|
||||||
|
background: linear-gradient(135deg, #1e3a5f 0%, #3b82f6 100%);
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
-webkit-text-fill-color: transparent;
|
||||||
|
background-clip: text;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
letter-spacing: -8px;
|
||||||
|
text-shadow: 0 4px 30px rgba(30, 58, 95, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-title {
|
||||||
|
font-size: 32px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #1f2937;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-desc {
|
||||||
|
font-size: 16px;
|
||||||
|
color: #6b7280;
|
||||||
|
margin-bottom: 40px;
|
||||||
|
max-width: 400px;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 16px;
|
||||||
|
justify-content: center;
|
||||||
|
margin-bottom: 60px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quick-nav-section {
|
||||||
|
max-width: 560px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quick-nav-title {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #9ca3af;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quick-nav-grid {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 12px;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quick-nav-item {
|
||||||
|
padding: 8px 20px;
|
||||||
|
background: #fff;
|
||||||
|
border: 1px solid #e5e7eb;
|
||||||
|
border-radius: 100px;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #374151;
|
||||||
|
text-decoration: none;
|
||||||
|
transition: all 0.2s;
|
||||||
|
box-shadow: 0 1px 3px rgba(0,0,0,0.06);
|
||||||
|
}
|
||||||
|
|
||||||
|
.quick-nav-item:hover {
|
||||||
|
background: #1e3a5f;
|
||||||
|
color: #fff;
|
||||||
|
border-color: #1e3a5f;
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 4px 12px rgba(30, 58, 95, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 640px) {
|
||||||
|
.error-code {
|
||||||
|
font-size: 100px;
|
||||||
|
letter-spacing: -4px;
|
||||||
|
}
|
||||||
|
.error-title {
|
||||||
|
font-size: 24px;
|
||||||
|
}
|
||||||
|
.error-actions {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -15,7 +15,7 @@
|
|||||||
<div class="sider-logo" @click="navigateTo('/admin')">
|
<div class="sider-logo" @click="navigateTo('/admin')">
|
||||||
<img src="/logo.png" alt="logo" class="logo-img" />
|
<img src="/logo.png" alt="logo" class="logo-img" />
|
||||||
<transition name="logo-text">
|
<transition name="logo-text">
|
||||||
<span v-if="!collapsed" class="logo-name">平台管理</span>
|
<span v-if="!collapsed" class="logo-name">决策咨询网</span>
|
||||||
</transition>
|
</transition>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -79,15 +79,15 @@
|
|||||||
<a-layout-header class="main-header">
|
<a-layout-header class="main-header">
|
||||||
<div class="header-inner">
|
<div class="header-inner">
|
||||||
<div class="header-left">
|
<div class="header-left">
|
||||||
<a-tag color="red" class="admin-badge">管理员</a-tag>
|
<a-tag color="red" class="admin-badge">决策咨询网</a-tag>
|
||||||
<span class="page-title">{{ currentPageTitle }}</span>
|
<span class="page-title">{{ currentPageTitle }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="header-right">
|
<div class="header-right">
|
||||||
<a-tooltip title="返回控制台">
|
<a-tooltip title="查看网站首页">
|
||||||
<a-button type="text" size="small" @click="navigateTo('/console')">
|
<a-button type="text" size="small" @click="navigateTo('/')">
|
||||||
<template #icon><DesktopOutlined /></template>
|
<template #icon><DesktopOutlined /></template>
|
||||||
控制台
|
网站首页
|
||||||
</a-button>
|
</a-button>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
<a-dropdown placement="bottomRight" :trigger="['click']">
|
<a-dropdown placement="bottomRight" :trigger="['click']">
|
||||||
@@ -106,18 +106,14 @@
|
|||||||
<UserOutlined style="margin-right: 8px" />
|
<UserOutlined style="margin-right: 8px" />
|
||||||
账户信息
|
账户信息
|
||||||
</a-menu-item>
|
</a-menu-item>
|
||||||
<a-menu-item key="orders">
|
<a-menu-item key="profile">
|
||||||
<ShoppingOutlined style="margin-right: 8px" />
|
|
||||||
我的订单
|
|
||||||
</a-menu-item>
|
|
||||||
<a-menu-item key="account-kyc">
|
|
||||||
<IdcardOutlined style="margin-right: 8px" />
|
<IdcardOutlined style="margin-right: 8px" />
|
||||||
实名认证
|
个人信息
|
||||||
</a-menu-item>
|
</a-menu-item>
|
||||||
<a-menu-divider />
|
<a-menu-divider />
|
||||||
<a-menu-item key="developer">
|
<a-menu-item key="view-site">
|
||||||
<CodeOutlined style="margin-right: 8px" />
|
<DesktopOutlined style="margin-right: 8px" />
|
||||||
开发者中心
|
查看网站
|
||||||
</a-menu-item>
|
</a-menu-item>
|
||||||
<a-menu-divider />
|
<a-menu-divider />
|
||||||
<a-menu-item key="logout" class="logout-item">
|
<a-menu-item key="logout" class="logout-item">
|
||||||
@@ -145,14 +141,12 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { message } from 'ant-design-vue'
|
import { message } from 'ant-design-vue'
|
||||||
import {
|
import {
|
||||||
CodeOutlined,
|
|
||||||
DesktopOutlined,
|
DesktopOutlined,
|
||||||
DownOutlined,
|
DownOutlined,
|
||||||
IdcardOutlined,
|
IdcardOutlined,
|
||||||
LeftOutlined,
|
LeftOutlined,
|
||||||
LogoutOutlined,
|
LogoutOutlined,
|
||||||
RightOutlined,
|
RightOutlined,
|
||||||
ShoppingOutlined,
|
|
||||||
UserOutlined,
|
UserOutlined,
|
||||||
} from '@ant-design/icons-vue'
|
} from '@ant-design/icons-vue'
|
||||||
import { adminNav, type AdminNavEntry, type AdminNavGroup, type AdminNavLink } from '@/config/admin-nav'
|
import { adminNav, type AdminNavEntry, type AdminNavGroup, type AdminNavLink } from '@/config/admin-nav'
|
||||||
@@ -186,7 +180,7 @@ const currentPageTitle = computed(() => {
|
|||||||
if (path === entry.to || path.startsWith(entry.to + '/')) return (entry as AdminNavLink).label
|
if (path === entry.to || path.startsWith(entry.to + '/')) return (entry as AdminNavLink).label
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return '平台管理'
|
return '管理后台'
|
||||||
})
|
})
|
||||||
|
|
||||||
// 选中 key
|
// 选中 key
|
||||||
@@ -233,15 +227,14 @@ function logout() {
|
|||||||
localStorage.removeItem('UserId')
|
localStorage.removeItem('UserId')
|
||||||
} catch { /* ignore */ }
|
} catch { /* ignore */ }
|
||||||
clearAuthz()
|
clearAuthz()
|
||||||
navigateTo('/')
|
navigateTo('/login')
|
||||||
}
|
}
|
||||||
|
|
||||||
function onUserMenuClick(info: { key: string }) {
|
function onUserMenuClick(info: { key: string }) {
|
||||||
const key = String(info.key)
|
const key = String(info.key)
|
||||||
if (key === 'developer') navigateTo('/developer')
|
if (key === 'account-info') navigateTo('/admin/users')
|
||||||
if (key === 'account-info') navigateTo('/console/account')
|
if (key === 'profile') navigateTo('/profile')
|
||||||
if (key === 'orders') navigateTo('/console/orders')
|
if (key === 'view-site') window.open('/', '_blank')
|
||||||
if (key === 'account-kyc') navigateTo('/console/account/kyc')
|
|
||||||
if (key === 'logout') logout()
|
if (key === 'logout') logout()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -264,9 +257,9 @@ onMounted(async () => {
|
|||||||
setAuthzFromUser(me)
|
setAuthzFromUser(me)
|
||||||
// 权限校验:isAdmin=true 方可访问管理后台
|
// 权限校验:isAdmin=true 方可访问管理后台
|
||||||
if (!(me as any).isAdmin) {
|
if (!(me as any).isAdmin) {
|
||||||
message.error('您无权访问平台管理中心,该区域仅限管理员使用')
|
message.error('您无权访问管理后台,该区域仅限管理员使用')
|
||||||
clearAuthz()
|
clearAuthz()
|
||||||
await navigateTo('/console')
|
await navigateTo('/login')
|
||||||
ready.value = true
|
ready.value = true
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
8
app/pages/about/charter.vue
Normal file
8
app/pages/about/charter.vue
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<template>
|
||||||
|
<AboutIndexVue />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
// 学会章程 - 复用关于我们页面,通过路由path自动切换到对应section
|
||||||
|
import AboutIndexVue from './index.vue'
|
||||||
|
</script>
|
||||||
7
app/pages/about/consultation.vue
Normal file
7
app/pages/about/consultation.vue
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<template>
|
||||||
|
<AboutIndexVue />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import AboutIndexVue from './index.vue'
|
||||||
|
</script>
|
||||||
@@ -1,105 +1,256 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="about-page">
|
<div class="about-subpage">
|
||||||
<div class="page-header">
|
<!-- 顶部 Banner -->
|
||||||
<h1 class="page-title">关于我们</h1>
|
<div class="about-banner">
|
||||||
<p class="page-desc">广西决策咨询网 - 汇聚智慧,服务决策</p>
|
<div class="mx-auto max-w-screen-xl px-4">
|
||||||
|
<h1 class="banner-title">关于我们</h1>
|
||||||
|
<p class="banner-desc">广西决策咨询网 · 汇聚智慧,服务决策</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="about-nav">
|
<div class="mx-auto max-w-screen-xl px-4 py-8">
|
||||||
<a-space wrap>
|
<a-row :gutter="[32, 0]">
|
||||||
<a-button type="primary" @click="currentSection = 'intro'">学会简介</a-button>
|
<!-- 左侧导航 -->
|
||||||
<a-button @click="currentSection = 'organization'">组织机构</a-button>
|
<a-col :xs="24" :lg="6">
|
||||||
<a-button @click="currentSection = 'charter'">学会章程</a-button>
|
<div class="side-nav">
|
||||||
<a-button @click="currentSection = 'consultation'">咨询服务</a-button>
|
<div class="side-nav-title">关于我们</div>
|
||||||
<a-button @click="currentSection = 'join'">加入我们</a-button>
|
<div
|
||||||
</a-space>
|
v-for="item in navItems"
|
||||||
|
:key="item.key"
|
||||||
|
class="side-nav-item"
|
||||||
|
:class="{ active: currentSection === item.key }"
|
||||||
|
@click="switchSection(item.key)"
|
||||||
|
>
|
||||||
|
<span class="nav-item-icon">{{ item.icon }}</span>
|
||||||
|
<span>{{ item.label }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</a-col>
|
||||||
|
|
||||||
|
<!-- 右侧内容 -->
|
||||||
|
<a-col :xs="24" :lg="18">
|
||||||
<!-- 学会简介 -->
|
<!-- 学会简介 -->
|
||||||
<div v-if="currentSection === 'intro'" class="content-section">
|
<div v-show="currentSection === 'intro'" class="content-card">
|
||||||
<h2>学会简介</h2>
|
<h2 class="content-title">学会简介</h2>
|
||||||
<div class="content-body">
|
<div class="content-body">
|
||||||
<p>广西决策咨询网是由广西壮族自治区决策咨询委员会主管的专业决策咨询平台,致力于为各级政府和企业提供高质量的决策咨询服务。</p>
|
<p>广西决策咨询学会(广西决策咨询中心)是在中共广西壮族自治区委员会、广西壮族自治区人民政府的领导下,由全区各高校、科研机构、政府部门从事决策咨询研究的专家学者和实际工作者自愿组成的学术性、非营利性社会组织。</p>
|
||||||
<p>网站汇聚了区内外的知名专家学者,围绕经济社会发展中的重大问题开展研究,为科学决策提供参考依据。</p>
|
<p>学会以服务党政决策为核心使命,围绕广西经济社会发展中的重大问题开展战略性、综合性、前瞻性研究,为自治区党委政府重大决策提供智力支撑。</p>
|
||||||
|
|
||||||
|
<div class="info-highlight">
|
||||||
|
<div class="highlight-item">
|
||||||
|
<div class="highlight-number">200+</div>
|
||||||
|
<div class="highlight-label">签约专家</div>
|
||||||
|
</div>
|
||||||
|
<div class="highlight-item">
|
||||||
|
<div class="highlight-number">20年</div>
|
||||||
|
<div class="highlight-label">服务历史</div>
|
||||||
|
</div>
|
||||||
|
<div class="highlight-item">
|
||||||
|
<div class="highlight-number">1000+</div>
|
||||||
|
<div class="highlight-label">咨询报告</div>
|
||||||
|
</div>
|
||||||
|
<div class="highlight-item">
|
||||||
|
<div class="highlight-number">50+</div>
|
||||||
|
<div class="highlight-label">重大课题</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<h3>主要职能</h3>
|
<h3>主要职能</h3>
|
||||||
<ul>
|
<div class="function-list">
|
||||||
<li>组织开展重大决策咨询课题研究</li>
|
<div class="function-item" v-for="item in mainFunctions" :key="item.title">
|
||||||
<li>为各级政府提供政策建议和咨询报告</li>
|
<div class="function-icon">{{ item.icon }}</div>
|
||||||
<li>搭建专家学者交流合作平台</li>
|
<div class="function-content">
|
||||||
<li>推广决策咨询研究成果</li>
|
<h4>{{ item.title }}</h4>
|
||||||
</ul>
|
<p>{{ item.desc }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 组织机构 -->
|
<!-- 组织机构 -->
|
||||||
<div v-if="currentSection === 'organization'" class="content-section">
|
<div v-show="currentSection === 'organization'" class="content-card">
|
||||||
<h2>组织机构</h2>
|
<h2 class="content-title">组织机构</h2>
|
||||||
<div class="content-body">
|
<div class="content-body">
|
||||||
<h3>顾问委员会</h3>
|
<div class="org-chart">
|
||||||
<p>由区内外知名专家学者组成,为学会发展提供指导和咨询。</p>
|
<div class="org-level org-top">
|
||||||
|
<div class="org-box org-primary">理事会</div>
|
||||||
|
</div>
|
||||||
|
<div class="org-connector"></div>
|
||||||
|
<div class="org-level org-mid">
|
||||||
|
<div class="org-box org-secondary">常务理事会</div>
|
||||||
|
</div>
|
||||||
|
<div class="org-connector"></div>
|
||||||
|
<div class="org-level org-bottom">
|
||||||
|
<div class="org-box org-third">学术委员会</div>
|
||||||
|
<div class="org-box org-third">秘书处</div>
|
||||||
|
<div class="org-box org-third">专家委员会</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<h3>理事会</h3>
|
<h3 class="mt-8">主要领导</h3>
|
||||||
<p>负责学会日常运营管理,制定和执行各项决策。</p>
|
<div class="leader-grid">
|
||||||
|
<div v-for="leader in leaders" :key="leader.name" class="leader-card">
|
||||||
|
<div class="leader-avatar">{{ leader.name.charAt(0) }}</div>
|
||||||
|
<div class="leader-info">
|
||||||
|
<div class="leader-name">{{ leader.name }}</div>
|
||||||
|
<div class="leader-pos">{{ leader.position }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<h3>专家库</h3>
|
<h3 class="mt-8">专家委员会成员</h3>
|
||||||
<p>汇聚各领域专业人才,提供智力支持。</p>
|
<div class="committee-tags">
|
||||||
|
<a-tag v-for="name in committeeMembers" :key="name" color="blue" style="margin-bottom:8px">{{ name }}</a-tag>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 学会章程 -->
|
<!-- 学会章程 -->
|
||||||
<div v-if="currentSection === 'charter'" class="content-section">
|
<div v-show="currentSection === 'charter'" class="content-card">
|
||||||
<h2>学会章程</h2>
|
<h2 class="content-title">学会章程</h2>
|
||||||
<div class="content-body">
|
<div class="content-body charter-body">
|
||||||
<h3>第一章 总则</h3>
|
<div v-for="chapter in charter" :key="chapter.title" class="charter-chapter">
|
||||||
<p>广西决策咨询学会是由区内从事决策咨询研究的专家学者和实际工作者自愿组成的学术性、非营利性社会组织。</p>
|
<h3>{{ chapter.title }}</h3>
|
||||||
|
<div v-for="(item, idx) in chapter.items" :key="idx" class="charter-item">
|
||||||
<h3>第二章 业务范围</h3>
|
<span class="charter-no">第{{ idx + 1 }}条</span>
|
||||||
<ul>
|
<span>{{ item }}</span>
|
||||||
<li>开展决策咨询理论研究</li>
|
</div>
|
||||||
<li>组织学术交流活动</li>
|
</div>
|
||||||
<li>提供决策咨询服务</li>
|
|
||||||
<li>培养决策咨询人才</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 咨询服务 -->
|
<!-- 咨询服务 -->
|
||||||
<div v-if="currentSection === 'consultation'" class="content-section">
|
<div v-show="currentSection === 'consultation'" class="content-card">
|
||||||
<h2>咨询服务</h2>
|
<h2 class="content-title">咨询服务</h2>
|
||||||
<div class="content-body">
|
<div class="content-body">
|
||||||
<h3>服务内容</h3>
|
<p class="service-intro">广西决策咨询网为各级政府机构、科研单位及企业提供专业、系统的决策咨询服务,涵盖政策研究、战略规划、项目评估等多个领域。</p>
|
||||||
<ul>
|
|
||||||
<li><strong>政策研究:</strong>为各级政府提供政策研究和评估服务</li>
|
|
||||||
<li><strong>规划咨询:</strong>区域规划、产业规划编制咨询</li>
|
|
||||||
<li><strong>项目评估:</strong>重大投资项目可行性研究和评估</li>
|
|
||||||
<li><strong>专题调研:</strong>根据需求开展专题调研</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<h3>联系方式</h3>
|
<div class="service-cards">
|
||||||
<p>📞 联系电话:0771-1234567</p>
|
<div v-for="service in consultationServices" :key="service.title" class="service-card">
|
||||||
<p>📧 电子邮箱:service@jczxw.org</p>
|
<div class="service-icon">{{ service.icon }}</div>
|
||||||
<p>📍 地址:广西南宁市民族大道XX号</p>
|
<h3>{{ service.title }}</h3>
|
||||||
|
<p>{{ service.desc }}</p>
|
||||||
|
<div class="service-tags-wrap">
|
||||||
|
<a-tag v-for="tag in service.tags" :key="tag">{{ tag }}</a-tag>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="contact-box">
|
||||||
|
<h3>联系我们</h3>
|
||||||
|
<div class="contact-grid">
|
||||||
|
<div class="contact-item">
|
||||||
|
<span class="contact-icon">📞</span>
|
||||||
|
<div>
|
||||||
|
<div class="contact-label">联系电话</div>
|
||||||
|
<div class="contact-value">0771-5386339</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="contact-item">
|
||||||
|
<span class="contact-icon">📧</span>
|
||||||
|
<div>
|
||||||
|
<div class="contact-label">电子邮箱</div>
|
||||||
|
<div class="contact-value">gxjzxzx@126.com</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="contact-item">
|
||||||
|
<span class="contact-icon">📍</span>
|
||||||
|
<div>
|
||||||
|
<div class="contact-label">办公地址</div>
|
||||||
|
<div class="contact-value">广西南宁市良庆区五象大道401号</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="contact-item">
|
||||||
|
<span class="contact-icon">⏰</span>
|
||||||
|
<div>
|
||||||
|
<div class="contact-label">工作时间</div>
|
||||||
|
<div class="contact-value">周一至周五 9:00-17:30</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 加入我们 -->
|
<!-- 加入我们 -->
|
||||||
<div v-if="currentSection === 'join'" class="content-section">
|
<div v-show="currentSection === 'join'" class="content-card">
|
||||||
<h2>加入我们</h2>
|
<h2 class="content-title">加入我们</h2>
|
||||||
<div class="content-body">
|
<div class="content-body">
|
||||||
<p>我们热忱欢迎符合条件的专家学者和有志于决策咨询事业的各界人士加入我们的大家庭。</p>
|
<p>我们热忱欢迎符合条件的单位和个人加入广西决策咨询学会,共同推动广西决策咨询事业高质量发展。</p>
|
||||||
|
|
||||||
<h3>入会资格</h3>
|
<div class="join-cards">
|
||||||
<p>详见:<NuxtLink to="/about/join">入会指南</NuxtLink></p>
|
<div class="join-card enterprise-card">
|
||||||
|
<div class="join-card-icon">🏢</div>
|
||||||
<div class="action-buttons">
|
<h3>企业会员</h3>
|
||||||
<a-button type="primary" size="large" @click="navigateTo('/about/join/enterprise')">
|
<div class="join-qualifications">
|
||||||
|
<h4>入会资格</h4>
|
||||||
|
<ul>
|
||||||
|
<li>在广西依法注册,具有法人资格的企事业单位</li>
|
||||||
|
<li>认同学会章程,支持学会工作</li>
|
||||||
|
<li>具有一定规模和社会影响力</li>
|
||||||
|
</ul>
|
||||||
|
<h4>所需材料</h4>
|
||||||
|
<ul>
|
||||||
|
<li>入会申请表(加盖公章)</li>
|
||||||
|
<li>营业执照副本</li>
|
||||||
|
<li>法人代表身份证</li>
|
||||||
|
<li>单位简介</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<a-button type="primary" block size="large" @click="navigateTo('/about/join/enterprise')">
|
||||||
企业会员申请
|
企业会员申请
|
||||||
</a-button>
|
</a-button>
|
||||||
<a-button size="large" @click="navigateTo('/about/join/personal')">
|
</div>
|
||||||
|
|
||||||
|
<div class="join-card personal-card">
|
||||||
|
<div class="join-card-icon">👤</div>
|
||||||
|
<h3>个人会员</h3>
|
||||||
|
<div class="join-qualifications">
|
||||||
|
<h4>入会资格</h4>
|
||||||
|
<ul>
|
||||||
|
<li>热爱决策咨询研究,认同学会章程</li>
|
||||||
|
<li>大学本科及以上学历</li>
|
||||||
|
<li>具有相关专业工作经历</li>
|
||||||
|
</ul>
|
||||||
|
<h4>所需材料</h4>
|
||||||
|
<ul>
|
||||||
|
<li>入会申请表(本人签字)</li>
|
||||||
|
<li>个人简介及研究成果</li>
|
||||||
|
<li>职称证书或学历证书</li>
|
||||||
|
<li>身份证复印件</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<a-button block size="large" @click="navigateTo('/about/join/personal')">
|
||||||
个人会员申请
|
个人会员申请
|
||||||
</a-button>
|
</a-button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="download-section">
|
||||||
|
<h3>📥 资料下载</h3>
|
||||||
|
<div class="download-list">
|
||||||
|
<a href="#" class="download-item">
|
||||||
|
<span class="download-icon">📄</span>
|
||||||
|
<span class="download-name">企业会员入会申请表.docx</span>
|
||||||
|
<a-button size="small" type="primary" ghost>下载</a-button>
|
||||||
|
</a>
|
||||||
|
<a href="#" class="download-item">
|
||||||
|
<span class="download-icon">📄</span>
|
||||||
|
<span class="download-name">个人会员入会申请表.docx</span>
|
||||||
|
<a-button size="small" type="primary" ghost>下载</a-button>
|
||||||
|
</a>
|
||||||
|
<a href="#" class="download-item">
|
||||||
|
<span class="download-icon">📋</span>
|
||||||
|
<span class="download-name">广西决策咨询学会章程.pdf</span>
|
||||||
|
<a-button size="small" type="primary" ghost>下载</a-button>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -108,86 +259,590 @@
|
|||||||
useHead({ title: '关于我们 - 决策咨询网' })
|
useHead({ title: '关于我们 - 决策咨询网' })
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const currentSection = ref((route.query.section as string) || 'intro')
|
|
||||||
|
|
||||||
watch(() => route.query.section, (newSection) => {
|
const navItems = [
|
||||||
currentSection.value = (newSection as string) || 'intro'
|
{ key: 'intro', label: '学会简介', icon: '🏛️' },
|
||||||
|
{ key: 'organization', label: '组织机构', icon: '🔧' },
|
||||||
|
{ key: 'charter', label: '学会章程', icon: '📋' },
|
||||||
|
{ key: 'consultation', label: '咨询服务', icon: '💼' },
|
||||||
|
{ key: 'join', label: '加入我们', icon: '🤝' },
|
||||||
|
]
|
||||||
|
|
||||||
|
// 根据路由path判断当前section
|
||||||
|
const sectionMap: Record<string, string> = {
|
||||||
|
'/about': 'intro',
|
||||||
|
'/about/organization': 'organization',
|
||||||
|
'/about/charter': 'charter',
|
||||||
|
'/about/consultation': 'consultation',
|
||||||
|
'/about/join': 'join',
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentSection = ref(
|
||||||
|
route.query.section as string ||
|
||||||
|
sectionMap[route.path] ||
|
||||||
|
'intro'
|
||||||
|
)
|
||||||
|
|
||||||
|
function switchSection(key: string) {
|
||||||
|
currentSection.value = key
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(() => route.path, (newPath) => {
|
||||||
|
currentSection.value = sectionMap[newPath] || 'intro'
|
||||||
})
|
})
|
||||||
|
|
||||||
|
watch(() => route.query.section, (sec) => {
|
||||||
|
if (sec) currentSection.value = sec as string
|
||||||
|
})
|
||||||
|
|
||||||
|
const mainFunctions = [
|
||||||
|
{ icon: '🔬', title: '决策咨询研究', desc: '围绕广西经济社会发展重大问题,开展战略性、综合性、前瞻性研究' },
|
||||||
|
{ icon: '📝', title: '政策建议提供', desc: '为各级政府提供有参考价值的政策建议和咨询报告' },
|
||||||
|
{ icon: '👥', title: '专家交流合作', desc: '搭建区内外专家学者交流合作平台,推动学术思想碰撞' },
|
||||||
|
{ icon: '📡', title: '成果宣传推广', desc: '多渠道发布和推广决策咨询研究成果,服务社会各界' },
|
||||||
|
]
|
||||||
|
|
||||||
|
const leaders = [
|
||||||
|
{ name: '陈某某', position: '会长' },
|
||||||
|
{ name: '李某某', position: '副会长' },
|
||||||
|
{ name: '王某某', position: '副会长' },
|
||||||
|
{ name: '张某某', position: '秘书长' },
|
||||||
|
]
|
||||||
|
|
||||||
|
const committeeMembers = ['张教授', '李研究员', '王专家', '刘学者', '赵教授', '黄学者', '林研究员', '吴教授']
|
||||||
|
|
||||||
|
const charter = [
|
||||||
|
{
|
||||||
|
title: '第一章 总则',
|
||||||
|
items: [
|
||||||
|
'广西决策咨询学会是由全区从事决策咨询研究的专家学者和实际工作者自愿组成的学术性、非营利性社会组织。',
|
||||||
|
'学会的宗旨是:以服务党政决策为核心使命,汇聚全区高端智慧,围绕经济社会发展重大问题开展研究,为科学决策提供智力支撑。',
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '第二章 业务范围',
|
||||||
|
items: [
|
||||||
|
'开展决策咨询理论与应用研究,撰写决策咨询报告。',
|
||||||
|
'组织学术交流、研讨会议等活动,促进学科发展。',
|
||||||
|
'为政府机构、企事业单位提供专业决策咨询服务。',
|
||||||
|
'培养决策咨询专业人才,开展业务培训。',
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '第三章 会员',
|
||||||
|
items: [
|
||||||
|
'凡符合本章程规定,经申请并经理事会审议通过,即为本学会会员。',
|
||||||
|
'会员分为单位会员(企业会员)和个人会员两类。',
|
||||||
|
'会员有权出席会员大会,参与学会活动,享受学会提供的服务和资源。',
|
||||||
|
]
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
const consultationServices = [
|
||||||
|
{ icon: '🎯', title: '政策研究', desc: '深入研究党中央国务院及自治区重要政策,提供权威解读和实施建议', tags: ['政策解读', '战略规划'] },
|
||||||
|
{ icon: '🗺️', title: '规划咨询', desc: '为地方政府、园区和企业提供区域规划、产业规划、专项规划编制咨询', tags: ['区域规划', '产业规划'] },
|
||||||
|
{ icon: '📊', title: '项目评估', desc: '对重大投资项目开展可行性研究、风险评估和后评价服务', tags: ['可行性研究', '风险评估'] },
|
||||||
|
{ icon: '🔍', title: '专题调研', desc: '根据委托需求开展实地调研,形成翔实的调研报告和对策建议', tags: ['实地调研', '对策建议'] },
|
||||||
|
]
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.about-page {
|
.about-banner {
|
||||||
max-width: 1000px;
|
background: linear-gradient(135deg, #1e3a5f 0%, #0d1b2a 100%);
|
||||||
margin: 0 auto;
|
padding: 60px 0 40px;
|
||||||
padding: 40px 20px;
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.page-header {
|
.about-banner::before {
|
||||||
text-align: center;
|
content: '';
|
||||||
margin-bottom: 40px;
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
background: url('https://picsum.photos/1920/400?random=200') center/cover;
|
||||||
|
opacity: 0.1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.page-title {
|
.banner-title {
|
||||||
font-size: 32px;
|
color: #fff;
|
||||||
|
font-size: 36px;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
color: #1f2937;
|
|
||||||
margin: 0 0 12px;
|
margin: 0 0 12px;
|
||||||
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.page-desc {
|
.banner-desc {
|
||||||
|
color: rgba(255,255,255,0.75);
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
color: #6b7280;
|
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.about-nav {
|
.side-nav {
|
||||||
margin-bottom: 40px;
|
background: #fff;
|
||||||
text-align: center;
|
border-radius: 12px;
|
||||||
|
box-shadow: 0 2px 8px rgba(0,0,0,0.06);
|
||||||
|
overflow: hidden;
|
||||||
|
position: sticky;
|
||||||
|
top: 80px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.content-section {
|
.side-nav-title {
|
||||||
|
padding: 16px 20px;
|
||||||
|
background: #1e3a5f;
|
||||||
|
color: #fff;
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.side-nav-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
padding: 14px 20px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #374151;
|
||||||
|
border-bottom: 1px solid #f5f5f5;
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.side-nav-item:hover {
|
||||||
|
background: #f0f7ff;
|
||||||
|
color: #1e3a5f;
|
||||||
|
}
|
||||||
|
|
||||||
|
.side-nav-item.active {
|
||||||
|
background: #eff6ff;
|
||||||
|
color: #1e3a5f;
|
||||||
|
font-weight: 600;
|
||||||
|
border-left: 3px solid #1e3a5f;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-item-icon {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-card {
|
||||||
background: #fff;
|
background: #fff;
|
||||||
border-radius: 16px;
|
border-radius: 16px;
|
||||||
padding: 40px;
|
padding: 40px;
|
||||||
box-shadow: 0 4px 16px rgba(0,0,0,0.08);
|
box-shadow: 0 2px 12px rgba(0,0,0,0.06);
|
||||||
|
min-height: 400px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.content-section h2 {
|
.content-title {
|
||||||
font-size: 24px;
|
font-size: 24px;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
color: #1f2937;
|
color: #1e3a5f;
|
||||||
margin: 0 0 24px;
|
margin: 0 0 24px;
|
||||||
padding-bottom: 16px;
|
padding-bottom: 16px;
|
||||||
border-bottom: 2px solid #f0f0f0;
|
border-bottom: 2px solid #e8f0fe;
|
||||||
}
|
}
|
||||||
|
|
||||||
.content-section h3 {
|
.content-body h3 {
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: #374151;
|
color: #1f2937;
|
||||||
margin: 24px 0 12px;
|
margin: 24px 0 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.content-section p {
|
.content-body p {
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
color: #4b5563;
|
color: #4b5563;
|
||||||
line-height: 1.8;
|
line-height: 1.8;
|
||||||
margin: 0 0 12px;
|
margin: 0 0 12px;
|
||||||
|
text-indent: 2em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.content-section ul {
|
/* 数据亮点 */
|
||||||
padding-left: 24px;
|
.info-highlight {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(4, 1fr);
|
||||||
|
gap: 16px;
|
||||||
|
margin: 24px 0;
|
||||||
|
padding: 24px;
|
||||||
|
background: linear-gradient(135deg, #eff6ff, #dbeafe);
|
||||||
|
border-radius: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.content-section li {
|
.highlight-item {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.highlight-number {
|
||||||
|
font-size: 32px;
|
||||||
|
font-weight: 800;
|
||||||
|
color: #1e3a5f;
|
||||||
|
}
|
||||||
|
|
||||||
|
.highlight-label {
|
||||||
|
font-size: 13px;
|
||||||
|
color: #6b7280;
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 职能列表 */
|
||||||
|
.function-list {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.function-item {
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
padding: 16px;
|
||||||
|
background: #f9fafb;
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.function-icon {
|
||||||
|
font-size: 28px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.function-content h4 {
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #1f2937;
|
||||||
|
margin: 0 0 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.function-content p {
|
||||||
|
font-size: 13px;
|
||||||
|
color: #6b7280;
|
||||||
|
margin: 0;
|
||||||
|
text-indent: 0;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 组织结构图 */
|
||||||
|
.org-chart {
|
||||||
|
text-align: center;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.org-level {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.org-connector {
|
||||||
|
height: 24px;
|
||||||
|
width: 2px;
|
||||||
|
background: #d1d5db;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.org-box {
|
||||||
|
padding: 10px 24px;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.org-primary {
|
||||||
|
background: #1e3a5f;
|
||||||
|
color: #fff;
|
||||||
|
min-width: 120px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.org-secondary {
|
||||||
|
background: #3b82f6;
|
||||||
|
color: #fff;
|
||||||
|
min-width: 140px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.org-third {
|
||||||
|
background: #eff6ff;
|
||||||
|
color: #1e3a5f;
|
||||||
|
border: 1px solid #bfdbfe;
|
||||||
|
min-width: 110px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.leader-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(4, 1fr);
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.leader-card {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 16px;
|
||||||
|
background: #f9fafb;
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.leader-avatar {
|
||||||
|
width: 48px;
|
||||||
|
height: 48px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: #1e3a5f;
|
||||||
|
color: #fff;
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: 700;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.leader-name {
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #1f2937;
|
||||||
|
}
|
||||||
|
|
||||||
|
.leader-pos {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #6b7280;
|
||||||
|
}
|
||||||
|
|
||||||
|
.committee-tags {
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 章程 */
|
||||||
|
.charter-body .charter-chapter {
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.charter-chapter h3 {
|
||||||
|
font-size: 17px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #1e3a5f;
|
||||||
|
background: #eff6ff;
|
||||||
|
padding: 10px 16px;
|
||||||
|
border-radius: 6px;
|
||||||
|
margin: 0 0 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.charter-item {
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
padding: 10px 16px;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #4b5563;
|
||||||
|
line-height: 1.8;
|
||||||
|
border-bottom: 1px dashed #f0f0f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.charter-no {
|
||||||
|
color: #1e3a5f;
|
||||||
|
font-weight: 600;
|
||||||
|
flex-shrink: 0;
|
||||||
|
width: 48px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 咨询服务 */
|
||||||
|
.service-intro {
|
||||||
|
text-indent: 2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.service-cards {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
gap: 20px;
|
||||||
|
margin: 24px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.service-card {
|
||||||
|
padding: 24px;
|
||||||
|
background: #f9fafb;
|
||||||
|
border-radius: 12px;
|
||||||
|
border: 1px solid #e5e7eb;
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.service-card:hover {
|
||||||
|
border-color: #1e3a5f;
|
||||||
|
box-shadow: 0 4px 12px rgba(30,58,95,0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.service-icon {
|
||||||
|
font-size: 36px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.service-card h3 {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #1e3a5f;
|
||||||
|
margin: 0 0 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.service-card p {
|
||||||
|
font-size: 13px;
|
||||||
|
color: #6b7280;
|
||||||
|
margin: 0 0 12px;
|
||||||
|
text-indent: 0;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.service-tags-wrap {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.contact-box {
|
||||||
|
background: #1e3a5f;
|
||||||
|
border-radius: 16px;
|
||||||
|
padding: 32px;
|
||||||
|
margin-top: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.contact-box h3 {
|
||||||
|
color: #fff;
|
||||||
|
font-size: 18px;
|
||||||
|
margin: 0 0 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.contact-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.contact-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 12px;
|
||||||
|
padding: 16px;
|
||||||
|
background: rgba(255,255,255,0.08);
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.contact-icon {
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.contact-label {
|
||||||
|
font-size: 12px;
|
||||||
|
color: rgba(255,255,255,0.65);
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.contact-value {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #fff;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 加入我们 */
|
||||||
|
.join-cards {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
gap: 24px;
|
||||||
|
margin: 24px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.join-card {
|
||||||
|
padding: 28px;
|
||||||
|
border-radius: 16px;
|
||||||
|
border: 2px solid transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.enterprise-card {
|
||||||
|
background: #eff6ff;
|
||||||
|
border-color: #bfdbfe;
|
||||||
|
}
|
||||||
|
|
||||||
|
.personal-card {
|
||||||
|
background: #f0fdf4;
|
||||||
|
border-color: #bbf7d0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.join-card-icon {
|
||||||
|
font-size: 40px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.join-card h3 {
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #1f2937;
|
||||||
|
margin: 0 0 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.join-qualifications h4 {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #374151;
|
||||||
|
margin: 12px 0 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.join-qualifications ul {
|
||||||
|
padding-left: 18px;
|
||||||
|
margin: 0 0 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.join-qualifications li {
|
||||||
|
font-size: 13px;
|
||||||
color: #4b5563;
|
color: #4b5563;
|
||||||
line-height: 2;
|
line-height: 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
.action-buttons {
|
.download-section {
|
||||||
margin-top: 32px;
|
background: #f9fafb;
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 24px;
|
||||||
|
margin-top: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.download-section h3 {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #1f2937;
|
||||||
|
margin: 0 0 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.download-list {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 16px;
|
flex-direction: column;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.download-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
padding: 12px 16px;
|
||||||
|
background: #fff;
|
||||||
|
border: 1px solid #e5e7eb;
|
||||||
|
border-radius: 8px;
|
||||||
|
text-decoration: none;
|
||||||
|
color: #374151;
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.download-item:hover {
|
||||||
|
border-color: #1e3a5f;
|
||||||
|
}
|
||||||
|
|
||||||
|
.download-icon {
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.download-name {
|
||||||
|
flex: 1;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #374151;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mt-6 { margin-top: 24px; }
|
||||||
|
.mt-8 { margin-top: 32px; }
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.info-highlight { grid-template-columns: repeat(2, 1fr); }
|
||||||
|
.function-list { grid-template-columns: 1fr; }
|
||||||
|
.service-cards { grid-template-columns: 1fr; }
|
||||||
|
.join-cards { grid-template-columns: 1fr; }
|
||||||
|
.leader-grid { grid-template-columns: repeat(2, 1fr); }
|
||||||
|
.contact-grid { grid-template-columns: 1fr; }
|
||||||
|
.content-card { padding: 20px; }
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
8
app/pages/about/organization.vue
Normal file
8
app/pages/about/organization.vue
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<template>
|
||||||
|
<AboutIndexVue />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
// 组织机构 - 复用关于我们页面,通过路由path自动切换到对应section
|
||||||
|
import AboutIndexVue from './index.vue'
|
||||||
|
</script>
|
||||||
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
602
app/pages/article/[id].vue
Normal file
602
app/pages/article/[id].vue
Normal file
@@ -0,0 +1,602 @@
|
|||||||
|
<template>
|
||||||
|
<div class="article-detail-page">
|
||||||
|
<div class="mx-auto max-w-screen-xl px-4 py-8">
|
||||||
|
<a-row :gutter="[32, 0]">
|
||||||
|
<!-- 主内容区 -->
|
||||||
|
<a-col :xs="24" :lg="17">
|
||||||
|
<!-- 面包屑 -->
|
||||||
|
<a-breadcrumb class="mb-6">
|
||||||
|
<a-breadcrumb-item><NuxtLink to="/">首页</NuxtLink></a-breadcrumb-item>
|
||||||
|
<a-breadcrumb-item v-if="article.categoryPath">
|
||||||
|
<NuxtLink :to="article.categoryPath">{{ article.categoryName }}</NuxtLink>
|
||||||
|
</a-breadcrumb-item>
|
||||||
|
<a-breadcrumb-item>{{ article.title || '文章详情' }}</a-breadcrumb-item>
|
||||||
|
</a-breadcrumb>
|
||||||
|
|
||||||
|
<div v-if="loading" class="article-loading">
|
||||||
|
<a-skeleton active :paragraph="{ rows: 12 }" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-else-if="!article.id" class="article-empty">
|
||||||
|
<a-result status="404" title="文章不存在" sub-title="您查找的文章不存在或已被删除">
|
||||||
|
<template #extra>
|
||||||
|
<a-button type="primary" @click="$router.back()">返回上一页</a-button>
|
||||||
|
</template>
|
||||||
|
</a-result>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<article v-else class="article-container">
|
||||||
|
<!-- 封面图 -->
|
||||||
|
<div v-if="article.cover" class="article-cover">
|
||||||
|
<img :src="article.cover" :alt="article.title" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 标题区 -->
|
||||||
|
<div class="article-header">
|
||||||
|
<div class="article-category-tag" v-if="article.categoryName">
|
||||||
|
{{ article.categoryName }}
|
||||||
|
</div>
|
||||||
|
<h1 class="article-title">{{ article.title }}</h1>
|
||||||
|
<div class="article-meta">
|
||||||
|
<span class="meta-item" v-if="article.source">
|
||||||
|
<span class="meta-icon">📰</span>来源:{{ article.source }}
|
||||||
|
</span>
|
||||||
|
<span class="meta-item" v-if="article.author">
|
||||||
|
<span class="meta-icon">✍️</span>{{ article.author }}
|
||||||
|
</span>
|
||||||
|
<span class="meta-item" v-if="article.publishTime">
|
||||||
|
<span class="meta-icon">🕐</span>{{ article.publishTime }}
|
||||||
|
</span>
|
||||||
|
<span class="meta-item" v-if="article.views">
|
||||||
|
<span class="meta-icon">👁</span>{{ article.views }} 次阅读
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 摘要 -->
|
||||||
|
<div v-if="article.summary" class="article-summary">
|
||||||
|
<div class="summary-label">摘要</div>
|
||||||
|
<p>{{ article.summary }}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 正文 -->
|
||||||
|
<div class="article-body" v-html="article.content"></div>
|
||||||
|
|
||||||
|
<!-- 附件下载 -->
|
||||||
|
<div v-if="article.attachments && article.attachments.length" class="article-attachments">
|
||||||
|
<div class="attachments-title">📎 相关附件</div>
|
||||||
|
<div class="attachments-list">
|
||||||
|
<a
|
||||||
|
v-for="file in article.attachments"
|
||||||
|
:key="file.url"
|
||||||
|
:href="file.url"
|
||||||
|
target="_blank"
|
||||||
|
class="attachment-item"
|
||||||
|
>
|
||||||
|
<span class="attachment-icon">📄</span>
|
||||||
|
<span class="attachment-name">{{ file.name }}</span>
|
||||||
|
<span class="attachment-size">{{ file.size }}</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 标签 -->
|
||||||
|
<div v-if="article.tags && article.tags.length" class="article-tags">
|
||||||
|
<span class="tags-label">标签:</span>
|
||||||
|
<a-tag v-for="tag in article.tags" :key="tag" color="blue">{{ tag }}</a-tag>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 声明 -->
|
||||||
|
<div class="article-disclaimer">
|
||||||
|
<p>声明:本文内容仅代表作者本人观点,不代表本网站立场。如有侵权请联系我们删除。</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 分享 & 上一篇下一篇 -->
|
||||||
|
<div class="article-nav">
|
||||||
|
<div class="nav-prev" v-if="prevArticle" @click="goArticle(prevArticle)">
|
||||||
|
<span class="nav-dir">« 上一篇</span>
|
||||||
|
<span class="nav-title">{{ prevArticle.title }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="nav-next" v-if="nextArticle" @click="goArticle(nextArticle)">
|
||||||
|
<span class="nav-title">{{ nextArticle.title }}</span>
|
||||||
|
<span class="nav-dir">下一篇 »</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
</a-col>
|
||||||
|
|
||||||
|
<!-- 侧栏 -->
|
||||||
|
<a-col :xs="0" :lg="7" class="hidden lg:block">
|
||||||
|
<div class="sidebar">
|
||||||
|
<!-- 相关文章 -->
|
||||||
|
<div class="sidebar-card">
|
||||||
|
<div class="sidebar-title">相关文章</div>
|
||||||
|
<div class="related-list">
|
||||||
|
<div
|
||||||
|
v-for="item in relatedArticles"
|
||||||
|
:key="item.id"
|
||||||
|
class="related-item"
|
||||||
|
@click="goArticle(item)"
|
||||||
|
>
|
||||||
|
<span class="related-dot">›</span>
|
||||||
|
<span class="related-title">{{ item.title }}</span>
|
||||||
|
</div>
|
||||||
|
<div v-if="!relatedArticles.length" class="related-empty">暂无相关文章</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 热门推荐 -->
|
||||||
|
<div class="sidebar-card mt-6">
|
||||||
|
<div class="sidebar-title">热门推荐</div>
|
||||||
|
<div class="related-list">
|
||||||
|
<div
|
||||||
|
v-for="item in hotArticles"
|
||||||
|
:key="item.id"
|
||||||
|
class="related-item hot-item"
|
||||||
|
@click="goArticle(item)"
|
||||||
|
>
|
||||||
|
<span class="hot-rank">{{ item.rank }}</span>
|
||||||
|
<span class="related-title">{{ item.title }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { message } from 'ant-design-vue'
|
||||||
|
|
||||||
|
const route = useRoute()
|
||||||
|
const router = useRouter()
|
||||||
|
const articleId = computed(() => route.params.id as string)
|
||||||
|
|
||||||
|
const loading = ref(true)
|
||||||
|
const article = ref<any>({})
|
||||||
|
const prevArticle = ref<any>(null)
|
||||||
|
const nextArticle = ref<any>(null)
|
||||||
|
const relatedArticles = ref<any[]>([])
|
||||||
|
const hotArticles = ref<any[]>([
|
||||||
|
{ id: 1, title: '广西数字经济发展报告(2024)', rank: 1 },
|
||||||
|
{ id: 2, title: '自治区关于优化营商环境的实施意见', rank: 2 },
|
||||||
|
{ id: 3, title: '面向东盟的产业合作政策解读', rank: 3 },
|
||||||
|
{ id: 4, title: '广西乡村振兴战略实施进展报告', rank: 4 },
|
||||||
|
{ id: 5, title: '北部湾经济区发展最新动态', rank: 5 },
|
||||||
|
])
|
||||||
|
|
||||||
|
useHead({
|
||||||
|
title: computed(() => `${article.value?.title || '文章详情'} - 决策咨询网`),
|
||||||
|
meta: [
|
||||||
|
{ name: 'description', content: computed(() => article.value?.summary || '') },
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
async function loadArticle() {
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
|
// TODO: 接入实际API
|
||||||
|
// const res = await getArticleDetail(articleId.value)
|
||||||
|
// article.value = res
|
||||||
|
// prevArticle.value = res.prev
|
||||||
|
// nextArticle.value = res.next
|
||||||
|
// relatedArticles.value = res.related || []
|
||||||
|
|
||||||
|
// 模拟数据
|
||||||
|
article.value = {
|
||||||
|
id: articleId.value,
|
||||||
|
title: '广西自治区党委政府关于加快数字经济发展的实施意见',
|
||||||
|
cover: `https://picsum.photos/900/400?random=${articleId.value}`,
|
||||||
|
categoryName: '政策要闻',
|
||||||
|
categoryPath: '/news',
|
||||||
|
source: '广西壮族自治区人民政府',
|
||||||
|
author: '政策研究处',
|
||||||
|
publishTime: '2024-12-20 09:30:00',
|
||||||
|
views: 1286,
|
||||||
|
summary: '本意见旨在深入贯彻党中央、国务院关于发展数字经济的战略部署,结合广西实际,加快推进数字产业化和产业数字化,培育壮大数字经济新动能。',
|
||||||
|
content: `
|
||||||
|
<h2>一、总体要求</h2>
|
||||||
|
<p>以习近平新时代中国特色社会主义思想为指导,全面贯彻党的二十大精神,围绕建设数字中国战略部署,立足广西比较优势,坚持创新驱动、数据赋能、融合发展,加快推动数字经济与实体经济深度融合,着力打造面向东盟的数字经济发展高地。</p>
|
||||||
|
|
||||||
|
<h2>二、主要目标</h2>
|
||||||
|
<p>到2026年,数字经济核心产业增加值占GDP比重达到12%,数字经济总量突破1万亿元,数字化转型企业数量超过5000家,建成5G基站15万座。</p>
|
||||||
|
|
||||||
|
<h2>三、重点任务</h2>
|
||||||
|
<h3>(一)加快数字基础设施建设</h3>
|
||||||
|
<p>系统推进新型基础设施建设,加快5G、大数据中心、工业互联网等数字基础设施部署,构建高速、泛在、天地一体、云网融合、智能敏捷、绿色低碳的新型数字基础设施体系。</p>
|
||||||
|
|
||||||
|
<h3>(二)深化数字技术与实体经济融合</h3>
|
||||||
|
<p>推动制造业、农业、服务业数字化转型,加快工业互联网创新应用,推进数字农业农村建设,促进数字技术与传统产业深度融合。</p>
|
||||||
|
|
||||||
|
<h3>(三)培育壮大数字经济核心产业</h3>
|
||||||
|
<p>重点发展软件和信息技术服务业、大数据、云计算、人工智能、区块链等核心产业,打造广西数字经济核心产业集群。</p>
|
||||||
|
|
||||||
|
<h2>四、保障措施</h2>
|
||||||
|
<p>加强组织领导,完善工作机制,强化政策支持,健全评估体系,确保各项任务落到实处。</p>
|
||||||
|
`,
|
||||||
|
tags: ['数字经济', '政策解读', '广西'],
|
||||||
|
attachments: [
|
||||||
|
{ name: '广西数字经济发展实施意见(全文).pdf', url: '#', size: '2.4MB' },
|
||||||
|
{ name: '附件:实施细则.docx', url: '#', size: '856KB' },
|
||||||
|
]
|
||||||
|
}
|
||||||
|
} catch (e: any) {
|
||||||
|
message.error('加载失败')
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function goArticle(item: any) {
|
||||||
|
router.push(`/article/${item.id}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
loadArticle()
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(articleId, () => {
|
||||||
|
loadArticle()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.article-detail-page {
|
||||||
|
background: #f5f7fa;
|
||||||
|
min-height: 60vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-loading,
|
||||||
|
.article-empty {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-container {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 40px;
|
||||||
|
box-shadow: 0 2px 12px rgba(0,0,0,0.06);
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-cover {
|
||||||
|
margin: -40px -40px 32px;
|
||||||
|
border-radius: 12px 12px 0 0;
|
||||||
|
overflow: hidden;
|
||||||
|
height: 320px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-cover img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-header {
|
||||||
|
margin-bottom: 24px;
|
||||||
|
padding-bottom: 24px;
|
||||||
|
border-bottom: 1px solid #f0f0f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-category-tag {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 3px 12px;
|
||||||
|
background: #1e3a5f;
|
||||||
|
color: #fff;
|
||||||
|
font-size: 12px;
|
||||||
|
border-radius: 4px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-title {
|
||||||
|
font-size: 26px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #1f2937;
|
||||||
|
line-height: 1.5;
|
||||||
|
margin: 0 0 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-meta {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meta-item {
|
||||||
|
font-size: 13px;
|
||||||
|
color: #9ca3af;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meta-icon {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 摘要 */
|
||||||
|
.article-summary {
|
||||||
|
background: #f8fafc;
|
||||||
|
border-left: 4px solid #1e3a5f;
|
||||||
|
border-radius: 0 8px 8px 0;
|
||||||
|
padding: 16px 20px;
|
||||||
|
margin-bottom: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.summary-label {
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #1e3a5f;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-summary p {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #4b5563;
|
||||||
|
line-height: 1.8;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 正文 */
|
||||||
|
.article-body {
|
||||||
|
font-size: 16px;
|
||||||
|
color: #374151;
|
||||||
|
line-height: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-body :deep(h2) {
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #1e3a5f;
|
||||||
|
margin: 32px 0 16px;
|
||||||
|
padding-bottom: 8px;
|
||||||
|
border-bottom: 2px solid #e5e7eb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-body :deep(h3) {
|
||||||
|
font-size: 17px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #374151;
|
||||||
|
margin: 24px 0 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-body :deep(p) {
|
||||||
|
margin: 0 0 16px;
|
||||||
|
text-indent: 2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-body :deep(ul), .article-body :deep(ol) {
|
||||||
|
padding-left: 24px;
|
||||||
|
margin: 12px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-body :deep(li) {
|
||||||
|
margin: 8px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 附件 */
|
||||||
|
.article-attachments {
|
||||||
|
margin-top: 32px;
|
||||||
|
padding: 20px;
|
||||||
|
background: #f9fafb;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.attachments-title {
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #374151;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.attachments-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.attachment-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 10px 14px;
|
||||||
|
background: #fff;
|
||||||
|
border: 1px solid #e5e7eb;
|
||||||
|
border-radius: 6px;
|
||||||
|
text-decoration: none;
|
||||||
|
color: #374151;
|
||||||
|
font-size: 14px;
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.attachment-item:hover {
|
||||||
|
border-color: #1e3a5f;
|
||||||
|
color: #1e3a5f;
|
||||||
|
background: #f0f7ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.attachment-size {
|
||||||
|
margin-left: auto;
|
||||||
|
color: #9ca3af;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 标签 */
|
||||||
|
.article-tags {
|
||||||
|
margin-top: 24px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tags-label {
|
||||||
|
font-size: 13px;
|
||||||
|
color: #9ca3af;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 声明 */
|
||||||
|
.article-disclaimer {
|
||||||
|
margin-top: 32px;
|
||||||
|
padding: 12px 16px;
|
||||||
|
background: #fefce8;
|
||||||
|
border: 1px solid #fef08a;
|
||||||
|
border-radius: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-disclaimer p {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #92400e;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 上下篇 */
|
||||||
|
.article-nav {
|
||||||
|
margin-top: 32px;
|
||||||
|
display: flex;
|
||||||
|
gap: 16px;
|
||||||
|
padding-top: 20px;
|
||||||
|
border-top: 1px solid #f0f0f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-prev, .nav-next {
|
||||||
|
flex: 1;
|
||||||
|
padding: 12px 16px;
|
||||||
|
background: #f9fafb;
|
||||||
|
border-radius: 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-prev:hover, .nav-next:hover {
|
||||||
|
background: #eff6ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-next {
|
||||||
|
text-align: right;
|
||||||
|
align-items: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-dir {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #9ca3af;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-title {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #1e3a5f;
|
||||||
|
font-weight: 500;
|
||||||
|
display: -webkit-box;
|
||||||
|
-webkit-line-clamp: 2;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 侧栏 */
|
||||||
|
.sidebar {
|
||||||
|
position: sticky;
|
||||||
|
top: 80px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-card {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 20px;
|
||||||
|
box-shadow: 0 2px 8px rgba(0,0,0,0.06);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-title {
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #1e3a5f;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
border-bottom: 2px solid #1e3a5f;
|
||||||
|
}
|
||||||
|
|
||||||
|
.related-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.related-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 6px 8px;
|
||||||
|
border-radius: 6px;
|
||||||
|
transition: background 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.related-item:hover {
|
||||||
|
background: #f3f4f6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.related-dot {
|
||||||
|
color: #1e3a5f;
|
||||||
|
font-weight: 600;
|
||||||
|
flex-shrink: 0;
|
||||||
|
margin-top: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.related-title {
|
||||||
|
font-size: 13px;
|
||||||
|
color: #374151;
|
||||||
|
line-height: 1.5;
|
||||||
|
display: -webkit-box;
|
||||||
|
-webkit-line-clamp: 2;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.related-empty {
|
||||||
|
font-size: 13px;
|
||||||
|
color: #9ca3af;
|
||||||
|
text-align: center;
|
||||||
|
padding: 12px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hot-item {
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hot-rank {
|
||||||
|
width: 22px;
|
||||||
|
height: 22px;
|
||||||
|
background: #1e3a5f;
|
||||||
|
color: #fff;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 700;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hot-item:nth-child(-n+3) .hot-rank {
|
||||||
|
background: #dc2626;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.article-container {
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-cover {
|
||||||
|
margin: -20px -20px 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-title {
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,187 +1,24 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="consultation-page">
|
<ArticleListPage :config="pageConfig" />
|
||||||
<div class="page-header">
|
|
||||||
<h1 class="page-title">决策咨询</h1>
|
|
||||||
<p class="page-desc">提供市县决策、前沿观察、行业资讯等决策咨询服务</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 分类标签 -->
|
|
||||||
<div class="category-tabs">
|
|
||||||
<a-radio-group v-model:value="activeType" button-style="solid" @change="handleTypeChange">
|
|
||||||
<a-radio-button value="">全部</a-radio-button>
|
|
||||||
<a-radio-button value="city">市县决策</a-radio-button>
|
|
||||||
<a-radio-button value="frontier">前沿观察</a-radio-button>
|
|
||||||
<a-radio-button value="industry">行业资讯</a-radio-button>
|
|
||||||
<a-radio-button value="enterprise">企业动态</a-radio-button>
|
|
||||||
<a-radio-button value="research">研究热点</a-radio-button>
|
|
||||||
<a-radio-button value="academic">学术活动</a-radio-button>
|
|
||||||
<a-radio-button value="other">其他汇编</a-radio-button>
|
|
||||||
</a-radio-group>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 文章列表 -->
|
|
||||||
<div class="article-list">
|
|
||||||
<div v-for="article in articles" :key="article.id" class="article-item" @click="handleView(article)">
|
|
||||||
<div class="article-content">
|
|
||||||
<h3 class="article-title">{{ article.title }}</h3>
|
|
||||||
<p class="article-overview">{{ article.overview }}</p>
|
|
||||||
<div class="article-meta">
|
|
||||||
<span class="meta-item">{{ article.source }}</span>
|
|
||||||
<span class="meta-item">{{ article.publishTime }}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-if="loading" class="loading-placeholder">
|
|
||||||
<a-spin size="large" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-if="!loading && articles.length === 0" class="empty-placeholder">
|
|
||||||
<a-empty description="暂无文章" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 分页 -->
|
|
||||||
<div class="pagination-wrap" v-if="total > pageSize">
|
|
||||||
<a-pagination
|
|
||||||
v-model:current="currentPage"
|
|
||||||
:total="total"
|
|
||||||
:page-size="pageSize"
|
|
||||||
@change="handlePageChange"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { message } from 'ant-design-vue'
|
|
||||||
|
|
||||||
useHead({ title: '决策咨询 - 决策咨询网' })
|
useHead({ title: '决策咨询 - 决策咨询网' })
|
||||||
|
|
||||||
const route = useRoute()
|
const pageConfig = {
|
||||||
const router = useRouter()
|
title: '决策咨询',
|
||||||
|
desc: '聚焦市县决策、前沿观察、行业资讯、企业动态,提供全面的决策咨询服务',
|
||||||
const activeType = ref((route.query.type as string) || '')
|
bannerGradient: 'linear-gradient(135deg, #1e3a5f 0%, #2563eb 100%)',
|
||||||
const currentPage = ref(1)
|
baseRoute: 'consultation',
|
||||||
const pageSize = ref(12)
|
categories: [
|
||||||
const total = ref(0)
|
{ type: '', label: '全部文章' },
|
||||||
const loading = ref(false)
|
{ type: 'city', label: '市县决策' },
|
||||||
const articles = ref<any[]>([])
|
{ type: 'frontier', label: '前沿观察' },
|
||||||
|
{ type: 'industry', label: '行业资讯' },
|
||||||
async function loadArticles() {
|
{ type: 'enterprise', label: '企业动态' },
|
||||||
loading.value = true
|
{ type: 'research', label: '研究热点' },
|
||||||
try {
|
{ type: 'academic', label: '学术活动' },
|
||||||
// TODO: 接入实际API
|
{ type: 'other', label: '其他汇编' },
|
||||||
} catch (e: any) {
|
]
|
||||||
message.error('加载失败')
|
|
||||||
} finally {
|
|
||||||
loading.value = false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleTypeChange() {
|
|
||||||
currentPage.value = 1
|
|
||||||
loadArticles()
|
|
||||||
}
|
|
||||||
|
|
||||||
function handlePageChange(page: number) {
|
|
||||||
currentPage.value = page
|
|
||||||
loadArticles()
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleView(article: any) {
|
|
||||||
router.push(`/consultation/${article.id}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
watch(() => route.query.type, (newType) => {
|
|
||||||
activeType.value = (newType as string) || ''
|
|
||||||
loadArticles()
|
|
||||||
})
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
loadArticles()
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.consultation-page {
|
|
||||||
max-width: 1200px;
|
|
||||||
margin: 0 auto;
|
|
||||||
padding: 40px 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.page-header {
|
|
||||||
text-align: center;
|
|
||||||
margin-bottom: 40px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.page-title {
|
|
||||||
font-size: 32px;
|
|
||||||
font-weight: 700;
|
|
||||||
color: #1f2937;
|
|
||||||
margin: 0 0 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.page-desc {
|
|
||||||
font-size: 16px;
|
|
||||||
color: #6b7280;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.category-tabs {
|
|
||||||
margin-bottom: 32px;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.article-list {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.article-item {
|
|
||||||
padding: 20px;
|
|
||||||
background: #fff;
|
|
||||||
border-radius: 12px;
|
|
||||||
box-shadow: 0 2px 8px rgba(0,0,0,0.06);
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all 0.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.article-item:hover {
|
|
||||||
box-shadow: 0 4px 16px rgba(0,0,0,0.1);
|
|
||||||
transform: translateY(-2px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.article-title {
|
|
||||||
font-size: 18px;
|
|
||||||
font-weight: 600;
|
|
||||||
color: #1f2937;
|
|
||||||
margin: 0 0 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.article-overview {
|
|
||||||
font-size: 14px;
|
|
||||||
color: #6b7280;
|
|
||||||
margin: 0 0 12px;
|
|
||||||
line-height: 1.6;
|
|
||||||
}
|
|
||||||
|
|
||||||
.article-meta {
|
|
||||||
display: flex;
|
|
||||||
gap: 16px;
|
|
||||||
font-size: 12px;
|
|
||||||
color: #9ca3af;
|
|
||||||
}
|
|
||||||
|
|
||||||
.loading-placeholder,
|
|
||||||
.empty-placeholder {
|
|
||||||
padding: 60px 0;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pagination-wrap {
|
|
||||||
margin-top: 40px;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|||||||
477
app/pages/expert/[id].vue
Normal file
477
app/pages/expert/[id].vue
Normal file
@@ -0,0 +1,477 @@
|
|||||||
|
<template>
|
||||||
|
<div class="expert-detail-page">
|
||||||
|
<div class="mx-auto max-w-screen-xl px-4 py-8">
|
||||||
|
<!-- 面包屑 -->
|
||||||
|
<a-breadcrumb class="mb-6">
|
||||||
|
<a-breadcrumb-item><NuxtLink to="/">首页</NuxtLink></a-breadcrumb-item>
|
||||||
|
<a-breadcrumb-item><NuxtLink to="/expert">专家资讯</NuxtLink></a-breadcrumb-item>
|
||||||
|
<a-breadcrumb-item>专家详情</a-breadcrumb-item>
|
||||||
|
</a-breadcrumb>
|
||||||
|
|
||||||
|
<div v-if="loading">
|
||||||
|
<a-skeleton active avatar :paragraph="{ rows: 6 }" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-else>
|
||||||
|
<a-row :gutter="[32, 24]">
|
||||||
|
<!-- 专家信息卡片 -->
|
||||||
|
<a-col :xs="24" :lg="7">
|
||||||
|
<div class="expert-card">
|
||||||
|
<div class="expert-avatar-wrapper">
|
||||||
|
<img v-if="expert.avatar" :src="expert.avatar" :alt="expert.name" class="expert-avatar" />
|
||||||
|
<div v-else class="expert-avatar-placeholder">{{ expert.name?.charAt(0) }}</div>
|
||||||
|
</div>
|
||||||
|
<h2 class="expert-name">{{ expert.name }}</h2>
|
||||||
|
<div class="expert-title-tag">{{ expert.title }}</div>
|
||||||
|
<div class="expert-org">{{ expert.organization }}</div>
|
||||||
|
|
||||||
|
<div class="expert-info-list">
|
||||||
|
<div class="info-item" v-if="expert.researchArea">
|
||||||
|
<span class="info-label">研究领域</span>
|
||||||
|
<span class="info-value">{{ expert.researchArea }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="info-item" v-if="expert.education">
|
||||||
|
<span class="info-label">学历</span>
|
||||||
|
<span class="info-value">{{ expert.education }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="info-item" v-if="expert.joinTime">
|
||||||
|
<span class="info-label">入库时间</span>
|
||||||
|
<span class="info-value">{{ expert.joinTime }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<a-button type="primary" block size="large" class="mt-4" @click="handleConsult">
|
||||||
|
预约咨询
|
||||||
|
</a-button>
|
||||||
|
</div>
|
||||||
|
</a-col>
|
||||||
|
|
||||||
|
<!-- 专家详情 -->
|
||||||
|
<a-col :xs="24" :lg="17">
|
||||||
|
<div class="expert-content-card">
|
||||||
|
<a-tabs v-model:activeKey="activeTab">
|
||||||
|
<a-tab-pane key="intro" tab="专家简介">
|
||||||
|
<div class="tab-content">
|
||||||
|
<h3 class="section-title">个人简介</h3>
|
||||||
|
<p class="intro-text">{{ expert.introduction || '暂无简介' }}</p>
|
||||||
|
|
||||||
|
<h3 class="section-title mt-6">主要成就</h3>
|
||||||
|
<ul class="achievement-list">
|
||||||
|
<li v-for="(item, idx) in expert.achievements" :key="idx">{{ item }}</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h3 class="section-title mt-6">荣誉奖项</h3>
|
||||||
|
<div class="honors-grid">
|
||||||
|
<div v-for="(honor, idx) in expert.honors" :key="idx" class="honor-item">
|
||||||
|
<span class="honor-icon">🏆</span>
|
||||||
|
<span>{{ honor }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a-tab-pane>
|
||||||
|
|
||||||
|
<a-tab-pane key="articles" tab="专家文章">
|
||||||
|
<div class="tab-content">
|
||||||
|
<div v-if="expertArticles.length === 0" class="empty-state">
|
||||||
|
<a-empty description="暂无文章" />
|
||||||
|
</div>
|
||||||
|
<div class="article-list">
|
||||||
|
<div
|
||||||
|
v-for="item in expertArticles"
|
||||||
|
:key="item.id"
|
||||||
|
class="article-item"
|
||||||
|
@click="goArticle(item)"
|
||||||
|
>
|
||||||
|
<div class="article-thumb" v-if="item.image">
|
||||||
|
<img :src="item.image" :alt="item.title" />
|
||||||
|
</div>
|
||||||
|
<div class="article-info">
|
||||||
|
<h4 class="article-title">{{ item.title }}</h4>
|
||||||
|
<p class="article-overview">{{ item.overview }}</p>
|
||||||
|
<span class="article-date">{{ item.date }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a-tab-pane>
|
||||||
|
|
||||||
|
<a-tab-pane key="research" tab="研究成果">
|
||||||
|
<div class="tab-content">
|
||||||
|
<div v-if="expert.researchResults && expert.researchResults.length">
|
||||||
|
<div v-for="(result, idx) in expert.researchResults" :key="idx" class="research-item">
|
||||||
|
<span class="research-year">{{ result.year }}</span>
|
||||||
|
<div class="research-content">
|
||||||
|
<h4>{{ result.title }}</h4>
|
||||||
|
<p>{{ result.description }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<a-empty v-else description="暂无研究成果" />
|
||||||
|
</div>
|
||||||
|
</a-tab-pane>
|
||||||
|
</a-tabs>
|
||||||
|
</div>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { message } from 'ant-design-vue'
|
||||||
|
|
||||||
|
const route = useRoute()
|
||||||
|
const router = useRouter()
|
||||||
|
const expertId = computed(() => route.params.id as string)
|
||||||
|
|
||||||
|
const loading = ref(true)
|
||||||
|
const activeTab = ref('intro')
|
||||||
|
const expert = ref<any>({})
|
||||||
|
const expertArticles = ref<any[]>([])
|
||||||
|
|
||||||
|
useHead({
|
||||||
|
title: computed(() => `${expert.value?.name || '专家详情'} - 决策咨询网`),
|
||||||
|
})
|
||||||
|
|
||||||
|
async function loadExpert() {
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
|
// TODO: 接入实际API
|
||||||
|
expert.value = {
|
||||||
|
id: expertId.value,
|
||||||
|
name: '张教授',
|
||||||
|
avatar: `https://picsum.photos/200/200?random=${expertId.value}`,
|
||||||
|
title: '研究员/教授',
|
||||||
|
organization: '广西社会科学院',
|
||||||
|
researchArea: '区域经济、数字经济、产业政策',
|
||||||
|
education: '经济学博士',
|
||||||
|
joinTime: '2022-06-15',
|
||||||
|
introduction: '张教授长期从事区域经济和产业政策研究,主持多项国家级和省部级科研项目,发表学术论文80余篇,著有《广西经济发展战略研究》等专著,是广西省级决策咨询委员会专家委员。',
|
||||||
|
achievements: [
|
||||||
|
'主持国家社科基金重点项目"面向东盟的广西产业协同发展研究"',
|
||||||
|
'参与编制《广西"十四五"经济发展规划》',
|
||||||
|
'提出"广西向海经济发展战略"并获省政府采纳',
|
||||||
|
'为南宁、柳州、桂林等城市提供产业规划咨询服务',
|
||||||
|
],
|
||||||
|
honors: [
|
||||||
|
'广西优秀专家',
|
||||||
|
'省级社科研究成果一等奖',
|
||||||
|
'国务院政府特殊津贴享受者',
|
||||||
|
'广西"十百千"人才',
|
||||||
|
],
|
||||||
|
researchResults: [
|
||||||
|
{
|
||||||
|
year: '2024',
|
||||||
|
title: '广西数字经济与传统产业融合研究',
|
||||||
|
description: '发表于《经济学研究》,探讨数字技术赋能广西传统制造业转型升级路径。'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
year: '2023',
|
||||||
|
title: '面向东盟的广西跨境产业协同模式研究',
|
||||||
|
description: '国家社科基金资助项目结项报告,构建"中国-东盟产业链合作新模式"理论框架。'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
year: '2022',
|
||||||
|
title: '西部陆海新通道经济带产业布局优化',
|
||||||
|
description: '广西社科规划重点课题,提出沿线城市产业差异化发展建议。'
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
expertArticles.value = [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
title: '张教授:关于广西产业升级的几点建议',
|
||||||
|
overview: '从经济发展规律角度分析广西产业转型升级面临的机遇与挑战,提出针对性建议...',
|
||||||
|
date: '2024-12-10',
|
||||||
|
image: 'https://picsum.photos/120/80?random=1'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
title: '数字经济时代广西制造业高质量发展路径探析',
|
||||||
|
overview: '以数字化转型为切入点,系统梳理广西制造业现状,提出差异化发展策略...',
|
||||||
|
date: '2024-10-28',
|
||||||
|
image: 'https://picsum.photos/120/80?random=2'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
} catch (e: any) {
|
||||||
|
message.error('加载失败')
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleConsult() {
|
||||||
|
message.info('请先联系我们预约咨询服务')
|
||||||
|
}
|
||||||
|
|
||||||
|
function goArticle(item: any) {
|
||||||
|
router.push(`/article/${item.id}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
loadExpert()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.expert-detail-page {
|
||||||
|
background: #f5f7fa;
|
||||||
|
min-height: 60vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.expert-card {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 16px;
|
||||||
|
padding: 32px 24px;
|
||||||
|
text-align: center;
|
||||||
|
box-shadow: 0 2px 12px rgba(0,0,0,0.06);
|
||||||
|
position: sticky;
|
||||||
|
top: 80px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.expert-avatar-wrapper {
|
||||||
|
margin: 0 auto 16px;
|
||||||
|
width: 100px;
|
||||||
|
height: 100px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.expert-avatar {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
border-radius: 50%;
|
||||||
|
object-fit: cover;
|
||||||
|
border: 4px solid #e8f0fe;
|
||||||
|
}
|
||||||
|
|
||||||
|
.expert-avatar-placeholder {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: linear-gradient(135deg, #1e3a5f, #3498db);
|
||||||
|
color: #fff;
|
||||||
|
font-size: 40px;
|
||||||
|
font-weight: 700;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.expert-name {
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #1f2937;
|
||||||
|
margin: 0 0 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.expert-title-tag {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 4px 16px;
|
||||||
|
background: #eff6ff;
|
||||||
|
color: #1e40af;
|
||||||
|
font-size: 13px;
|
||||||
|
border-radius: 20px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.expert-org {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #6b7280;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.expert-info-list {
|
||||||
|
text-align: left;
|
||||||
|
border-top: 1px solid #f0f0f0;
|
||||||
|
padding-top: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-item {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 8px 0;
|
||||||
|
border-bottom: 1px dashed #f0f0f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-item:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-label {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #9ca3af;
|
||||||
|
width: 65px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-value {
|
||||||
|
font-size: 13px;
|
||||||
|
color: #374151;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.expert-content-card {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 16px;
|
||||||
|
padding: 24px;
|
||||||
|
box-shadow: 0 2px 12px rgba(0,0,0,0.06);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-content {
|
||||||
|
padding: 8px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-title {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #1e3a5f;
|
||||||
|
margin: 0 0 12px;
|
||||||
|
padding-left: 10px;
|
||||||
|
border-left: 3px solid #1e3a5f;
|
||||||
|
}
|
||||||
|
|
||||||
|
.intro-text {
|
||||||
|
font-size: 15px;
|
||||||
|
color: #4b5563;
|
||||||
|
line-height: 2;
|
||||||
|
text-indent: 2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.achievement-list {
|
||||||
|
padding-left: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.achievement-list li {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #4b5563;
|
||||||
|
line-height: 2;
|
||||||
|
margin: 4px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.honors-grid {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.honor-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
padding: 8px 14px;
|
||||||
|
background: #fffbeb;
|
||||||
|
border: 1px solid #fef3c7;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-size: 13px;
|
||||||
|
color: #92400e;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-item {
|
||||||
|
display: flex;
|
||||||
|
gap: 16px;
|
||||||
|
padding: 16px;
|
||||||
|
border-radius: 10px;
|
||||||
|
background: #f9fafb;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-item:hover {
|
||||||
|
background: #eff6ff;
|
||||||
|
transform: translateX(4px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-thumb {
|
||||||
|
width: 100px;
|
||||||
|
height: 68px;
|
||||||
|
border-radius: 6px;
|
||||||
|
overflow: hidden;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-thumb img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-info {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-title {
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #1f2937;
|
||||||
|
margin: 0 0 6px;
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-overview {
|
||||||
|
font-size: 13px;
|
||||||
|
color: #6b7280;
|
||||||
|
margin: 0 0 8px;
|
||||||
|
line-height: 1.5;
|
||||||
|
display: -webkit-box;
|
||||||
|
-webkit-line-clamp: 2;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-date {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #9ca3af;
|
||||||
|
}
|
||||||
|
|
||||||
|
.research-item {
|
||||||
|
display: flex;
|
||||||
|
gap: 16px;
|
||||||
|
padding: 16px 0;
|
||||||
|
border-bottom: 1px dashed #f0f0f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.research-item:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.research-year {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 52px;
|
||||||
|
height: 52px;
|
||||||
|
background: #1e3a5f;
|
||||||
|
color: #fff;
|
||||||
|
border-radius: 50%;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 700;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.research-content h4 {
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #1f2937;
|
||||||
|
margin: 0 0 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.research-content p {
|
||||||
|
font-size: 13px;
|
||||||
|
color: #6b7280;
|
||||||
|
margin: 0;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-state {
|
||||||
|
padding: 40px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mt-4 { margin-top: 16px; }
|
||||||
|
.mt-6 { margin-top: 24px; }
|
||||||
|
</style>
|
||||||
@@ -1,193 +1,21 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="hanmo-page">
|
<ArticleListPage :config="pageConfig" />
|
||||||
<div class="page-header">
|
|
||||||
<h1 class="page-title">翰墨文谈</h1>
|
|
||||||
<p class="page-desc">以文会友,汇聚文化精品,传承智慧结晶</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 文章列表 -->
|
|
||||||
<div class="article-grid">
|
|
||||||
<div v-for="article in articles" :key="article.id" class="article-card" @click="handleView(article)">
|
|
||||||
<div class="card-image" v-if="article.image">
|
|
||||||
<img :src="article.image" :alt="article.title" />
|
|
||||||
</div>
|
|
||||||
<div class="card-body">
|
|
||||||
<h3 class="card-title">{{ article.title }}</h3>
|
|
||||||
<p class="card-overview">{{ article.overview }}</p>
|
|
||||||
<div class="card-meta">
|
|
||||||
<span>{{ article.author }}</span>
|
|
||||||
<span>{{ article.publishTime }}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-if="loading" class="loading-placeholder">
|
|
||||||
<a-spin size="large" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-if="!loading && articles.length === 0" class="empty-placeholder">
|
|
||||||
<a-empty description="暂无文章" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 分页 -->
|
|
||||||
<div class="pagination-wrap" v-if="total > pageSize">
|
|
||||||
<a-pagination
|
|
||||||
v-model:current="currentPage"
|
|
||||||
:total="total"
|
|
||||||
:page-size="pageSize"
|
|
||||||
@change="handlePageChange"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { message } from 'ant-design-vue'
|
|
||||||
|
|
||||||
useHead({ title: '翰墨文谈 - 决策咨询网' })
|
useHead({ title: '翰墨文谈 - 决策咨询网' })
|
||||||
|
|
||||||
const router = useRouter()
|
const pageConfig = {
|
||||||
|
title: '翰墨文谈',
|
||||||
const currentPage = ref(1)
|
desc: '笔墨流传思想,文章承载智慧,汇聚各界名家随笔,分享从实践中来的感悟',
|
||||||
const pageSize = ref(12)
|
bannerGradient: 'linear-gradient(135deg, #92400e 0%, #b45309 100%)',
|
||||||
const total = ref(0)
|
baseRoute: 'hanmo',
|
||||||
const loading = ref(false)
|
categories: [
|
||||||
const articles = ref<any[]>([])
|
{ type: '', label: '全部文章' },
|
||||||
|
{ type: 'essay', label: '随笔散文' },
|
||||||
async function loadArticles() {
|
{ type: 'review', label: '书评影评' },
|
||||||
loading.value = true
|
{ type: 'poetry', label: '诗词歌赋' },
|
||||||
try {
|
{ type: 'other', label: '其他' },
|
||||||
// TODO: 接入实际API
|
]
|
||||||
} catch (e: any) {
|
|
||||||
message.error('加载失败')
|
|
||||||
} finally {
|
|
||||||
loading.value = false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function handlePageChange(page: number) {
|
|
||||||
currentPage.value = page
|
|
||||||
loadArticles()
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleView(article: any) {
|
|
||||||
router.push(`/hanmo/${article.id}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
loadArticles()
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.hanmo-page {
|
|
||||||
max-width: 1200px;
|
|
||||||
margin: 0 auto;
|
|
||||||
padding: 40px 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.page-header {
|
|
||||||
text-align: center;
|
|
||||||
margin-bottom: 40px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.page-title {
|
|
||||||
font-size: 32px;
|
|
||||||
font-weight: 700;
|
|
||||||
color: #1f2937;
|
|
||||||
margin: 0 0 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.page-desc {
|
|
||||||
font-size: 16px;
|
|
||||||
color: #6b7280;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.article-grid {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(3, 1fr);
|
|
||||||
gap: 24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.article-card {
|
|
||||||
background: #fff;
|
|
||||||
border-radius: 12px;
|
|
||||||
overflow: hidden;
|
|
||||||
box-shadow: 0 2px 8px rgba(0,0,0,0.06);
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all 0.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.article-card:hover {
|
|
||||||
box-shadow: 0 4px 16px rgba(0,0,0,0.1);
|
|
||||||
transform: translateY(-4px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-image {
|
|
||||||
width: 100%;
|
|
||||||
height: 200px;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-image img {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
object-fit: cover;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-body {
|
|
||||||
padding: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-title {
|
|
||||||
font-size: 16px;
|
|
||||||
font-weight: 600;
|
|
||||||
color: #1f2937;
|
|
||||||
margin: 0 0 8px;
|
|
||||||
line-height: 1.4;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-overview {
|
|
||||||
font-size: 13px;
|
|
||||||
color: #6b7280;
|
|
||||||
margin: 0 0 12px;
|
|
||||||
line-height: 1.6;
|
|
||||||
display: -webkit-box;
|
|
||||||
-webkit-line-clamp: 2;
|
|
||||||
-webkit-box-orient: vertical;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-meta {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
font-size: 12px;
|
|
||||||
color: #9ca3af;
|
|
||||||
}
|
|
||||||
|
|
||||||
.loading-placeholder,
|
|
||||||
.empty-placeholder {
|
|
||||||
grid-column: 1 / -1;
|
|
||||||
padding: 60px 0;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pagination-wrap {
|
|
||||||
margin-top: 40px;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 1024px) {
|
|
||||||
.article-grid {
|
|
||||||
grid-template-columns: repeat(2, 1fr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 640px) {
|
|
||||||
.article-grid {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|||||||
@@ -1,232 +1,21 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="news-page">
|
<ArticleListPage :config="pageConfig" />
|
||||||
<div class="page-header">
|
|
||||||
<h1 class="page-title">政策要闻</h1>
|
|
||||||
<p class="page-desc">汇聚党中央国务院、自治区党委政府及各部门最新政策信息</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 分类标签 -->
|
|
||||||
<div class="category-tabs">
|
|
||||||
<a-radio-group v-model:value="activeType" button-style="solid" @change="handleTypeChange">
|
|
||||||
<a-radio-button value="">全部</a-radio-button>
|
|
||||||
<a-radio-button value="central">党中央国务院</a-radio-button>
|
|
||||||
<a-radio-button value="region">自治区党委政府</a-radio-button>
|
|
||||||
<a-radio-button value="department">其他厅委办</a-radio-button>
|
|
||||||
<a-radio-button value="latest">最新发布</a-radio-button>
|
|
||||||
</a-radio-group>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 文章列表 -->
|
|
||||||
<div class="article-list">
|
|
||||||
<div v-for="article in articles" :key="article.id" class="article-item" @click="handleView(article)">
|
|
||||||
<div class="article-image" v-if="article.image">
|
|
||||||
<img :src="article.image" :alt="article.title" />
|
|
||||||
</div>
|
|
||||||
<div class="article-content">
|
|
||||||
<h3 class="article-title">{{ article.title }}</h3>
|
|
||||||
<p class="article-overview">{{ article.overview }}</p>
|
|
||||||
<div class="article-meta">
|
|
||||||
<span class="meta-item">{{ article.source }}</span>
|
|
||||||
<span class="meta-item">{{ article.publishTime }}</span>
|
|
||||||
<span class="meta-item">浏览 {{ article.views }}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-if="loading" class="loading-placeholder">
|
|
||||||
<a-spin size="large" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-if="!loading && articles.length === 0" class="empty-placeholder">
|
|
||||||
<a-empty description="暂无文章" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 分页 -->
|
|
||||||
<div class="pagination-wrap" v-if="total > pageSize">
|
|
||||||
<a-pagination
|
|
||||||
v-model:current="currentPage"
|
|
||||||
:total="total"
|
|
||||||
:page-size="pageSize"
|
|
||||||
@change="handlePageChange"
|
|
||||||
show-quick-jumper
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { message } from 'ant-design-vue'
|
|
||||||
|
|
||||||
useHead({ title: '政策要闻 - 决策咨询网' })
|
useHead({ title: '政策要闻 - 决策咨询网' })
|
||||||
|
|
||||||
const route = useRoute()
|
const pageConfig = {
|
||||||
const router = useRouter()
|
title: '政策要闻',
|
||||||
|
desc: '汇聚党中央国务院、自治区党委政府、各厅委办最新政策信息',
|
||||||
const activeType = ref((route.query.type as string) || '')
|
bannerGradient: 'linear-gradient(135deg, #dc2626 0%, #b91c1c 100%)',
|
||||||
const currentPage = ref(1)
|
baseRoute: 'news',
|
||||||
const pageSize = ref(12)
|
categories: [
|
||||||
const total = ref(0)
|
{ type: '', label: '全部资讯' },
|
||||||
const loading = ref(false)
|
{ type: 'central', label: '党中央国务院信息' },
|
||||||
const articles = ref<any[]>([])
|
{ type: 'region', label: '自治区党委政府信息' },
|
||||||
|
{ type: 'department', label: '其他(厅委办)信息' },
|
||||||
async function loadArticles() {
|
{ type: 'latest', label: '最新发布' },
|
||||||
loading.value = true
|
]
|
||||||
try {
|
|
||||||
// TODO: 接入实际API
|
|
||||||
// const res = await listArticles({ category: 'news', type: activeType.value, page: currentPage.value })
|
|
||||||
// articles.value = res.list
|
|
||||||
// total.value = res.count
|
|
||||||
} catch (e: any) {
|
|
||||||
message.error('加载失败')
|
|
||||||
} finally {
|
|
||||||
loading.value = false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleTypeChange() {
|
|
||||||
currentPage.value = 1
|
|
||||||
loadArticles()
|
|
||||||
}
|
|
||||||
|
|
||||||
function handlePageChange(page: number) {
|
|
||||||
currentPage.value = page
|
|
||||||
loadArticles()
|
|
||||||
window.scrollTo({ top: 0, behavior: 'smooth' })
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleView(article: any) {
|
|
||||||
router.push(`/news/${article.id}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
watch(() => route.query.type, (newType) => {
|
|
||||||
activeType.value = (newType as string) || ''
|
|
||||||
loadArticles()
|
|
||||||
})
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
loadArticles()
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.news-page {
|
|
||||||
max-width: 1200px;
|
|
||||||
margin: 0 auto;
|
|
||||||
padding: 40px 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.page-header {
|
|
||||||
text-align: center;
|
|
||||||
margin-bottom: 40px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.page-title {
|
|
||||||
font-size: 32px;
|
|
||||||
font-weight: 700;
|
|
||||||
color: #1f2937;
|
|
||||||
margin: 0 0 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.page-desc {
|
|
||||||
font-size: 16px;
|
|
||||||
color: #6b7280;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.category-tabs {
|
|
||||||
margin-bottom: 32px;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.article-list {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.article-item {
|
|
||||||
display: flex;
|
|
||||||
gap: 20px;
|
|
||||||
padding: 20px;
|
|
||||||
background: #fff;
|
|
||||||
border-radius: 12px;
|
|
||||||
box-shadow: 0 2px 8px rgba(0,0,0,0.06);
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all 0.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.article-item:hover {
|
|
||||||
box-shadow: 0 4px 16px rgba(0,0,0,0.1);
|
|
||||||
transform: translateY(-2px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.article-image {
|
|
||||||
width: 200px;
|
|
||||||
height: 140px;
|
|
||||||
flex-shrink: 0;
|
|
||||||
border-radius: 8px;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.article-image img {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
object-fit: cover;
|
|
||||||
}
|
|
||||||
|
|
||||||
.article-content {
|
|
||||||
flex: 1;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.article-title {
|
|
||||||
font-size: 18px;
|
|
||||||
font-weight: 600;
|
|
||||||
color: #1f2937;
|
|
||||||
margin: 0 0 8px;
|
|
||||||
line-height: 1.4;
|
|
||||||
}
|
|
||||||
|
|
||||||
.article-overview {
|
|
||||||
font-size: 14px;
|
|
||||||
color: #6b7280;
|
|
||||||
margin: 0 0 auto;
|
|
||||||
line-height: 1.6;
|
|
||||||
display: -webkit-box;
|
|
||||||
-webkit-line-clamp: 2;
|
|
||||||
-webkit-box-orient: vertical;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.article-meta {
|
|
||||||
display: flex;
|
|
||||||
gap: 16px;
|
|
||||||
font-size: 12px;
|
|
||||||
color: #9ca3af;
|
|
||||||
margin-top: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.loading-placeholder,
|
|
||||||
.empty-placeholder {
|
|
||||||
padding: 60px 0;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pagination-wrap {
|
|
||||||
margin-top: 40px;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
|
||||||
.article-item {
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.article-image {
|
|
||||||
width: 100%;
|
|
||||||
height: 180px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
612
app/pages/profile/index.vue
Normal file
612
app/pages/profile/index.vue
Normal file
@@ -0,0 +1,612 @@
|
|||||||
|
<template>
|
||||||
|
<div class="profile-page">
|
||||||
|
<div class="mx-auto max-w-screen-xl px-4 py-8">
|
||||||
|
<!-- 需要登录 -->
|
||||||
|
<div v-if="!isAuthed" class="not-authed">
|
||||||
|
<a-result
|
||||||
|
status="403"
|
||||||
|
title="请先登录"
|
||||||
|
sub-title="登录后可查看和编辑个人信息"
|
||||||
|
>
|
||||||
|
<template #extra>
|
||||||
|
<a-button type="primary" size="large" @click="navigateTo('/login')">去登录</a-button>
|
||||||
|
</template>
|
||||||
|
</a-result>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-else>
|
||||||
|
<a-row :gutter="[32, 24]">
|
||||||
|
<!-- 左侧:用户信息卡片 -->
|
||||||
|
<a-col :xs="24" :lg="7">
|
||||||
|
<div class="profile-card">
|
||||||
|
<div class="avatar-section">
|
||||||
|
<a-upload
|
||||||
|
name="avatar"
|
||||||
|
list-type="picture-circle"
|
||||||
|
class="avatar-uploader"
|
||||||
|
:show-upload-list="false"
|
||||||
|
:before-upload="beforeUpload"
|
||||||
|
@change="handleAvatarChange"
|
||||||
|
>
|
||||||
|
<div class="avatar-wrap">
|
||||||
|
<img v-if="userInfo.avatar" :src="userInfo.avatar" alt="avatar" class="avatar-img" />
|
||||||
|
<div v-else class="avatar-placeholder">{{ userInfo.nickname?.charAt(0) || '用' }}</div>
|
||||||
|
<div class="avatar-overlay">
|
||||||
|
<span>更换头像</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a-upload>
|
||||||
|
</div>
|
||||||
|
<h2 class="user-name">{{ userInfo.nickname || userInfo.username || '用户' }}</h2>
|
||||||
|
<div class="user-role">
|
||||||
|
<a-tag :color="userInfo.isAdmin ? 'red' : 'blue'">
|
||||||
|
{{ userInfo.isAdmin ? '管理员' : '普通用户' }}
|
||||||
|
</a-tag>
|
||||||
|
</div>
|
||||||
|
<div class="user-stats">
|
||||||
|
<div class="stat-item">
|
||||||
|
<div class="stat-num">{{ stats.suggestions }}</div>
|
||||||
|
<div class="stat-label">建言</div>
|
||||||
|
</div>
|
||||||
|
<div class="stat-item">
|
||||||
|
<div class="stat-num">{{ stats.favorites }}</div>
|
||||||
|
<div class="stat-label">收藏</div>
|
||||||
|
</div>
|
||||||
|
<div class="stat-item">
|
||||||
|
<div class="stat-num">{{ stats.views }}</div>
|
||||||
|
<div class="stat-label">浏览</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="side-menu">
|
||||||
|
<div
|
||||||
|
v-for="item in sideMenuItems"
|
||||||
|
:key="item.key"
|
||||||
|
class="side-menu-item"
|
||||||
|
:class="{ active: activeTab === item.key }"
|
||||||
|
@click="activeTab = item.key"
|
||||||
|
>
|
||||||
|
<span class="menu-icon">{{ item.icon }}</span>
|
||||||
|
<span>{{ item.label }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a-col>
|
||||||
|
|
||||||
|
<!-- 右侧:内容区 -->
|
||||||
|
<a-col :xs="24" :lg="17">
|
||||||
|
<!-- 基本信息 -->
|
||||||
|
<div v-show="activeTab === 'info'" class="content-panel">
|
||||||
|
<div class="panel-header">
|
||||||
|
<h3>基本信息</h3>
|
||||||
|
<a-button type="primary" v-if="!editing" @click="editing = true">编辑资料</a-button>
|
||||||
|
<a-space v-else>
|
||||||
|
<a-button @click="editing = false">取消</a-button>
|
||||||
|
<a-button type="primary" :loading="saving" @click="saveInfo">保存</a-button>
|
||||||
|
</a-space>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<a-form :model="editForm" layout="vertical" class="info-form">
|
||||||
|
<a-row :gutter="16">
|
||||||
|
<a-col :span="12">
|
||||||
|
<a-form-item label="昵称">
|
||||||
|
<a-input v-model:value="editForm.nickname" :disabled="!editing" placeholder="请输入昵称" />
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="12">
|
||||||
|
<a-form-item label="手机号">
|
||||||
|
<a-input v-model:value="editForm.phone" :disabled="!editing" placeholder="请输入手机号" />
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="12">
|
||||||
|
<a-form-item label="电子邮箱">
|
||||||
|
<a-input v-model:value="editForm.email" :disabled="!editing" placeholder="请输入邮箱" />
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="12">
|
||||||
|
<a-form-item label="工作单位">
|
||||||
|
<a-input v-model:value="editForm.organization" :disabled="!editing" placeholder="请输入工作单位" />
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="24">
|
||||||
|
<a-form-item label="个人简介">
|
||||||
|
<a-textarea v-model:value="editForm.bio" :disabled="!editing" :rows="3" placeholder="请输入个人简介" />
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
</a-form>
|
||||||
|
|
||||||
|
<!-- 账号安全 -->
|
||||||
|
<div class="security-section">
|
||||||
|
<h4>账号安全</h4>
|
||||||
|
<div class="security-items">
|
||||||
|
<div class="security-item">
|
||||||
|
<div class="security-info">
|
||||||
|
<span class="security-icon">🔒</span>
|
||||||
|
<div>
|
||||||
|
<div class="security-name">登录密码</div>
|
||||||
|
<div class="security-desc">建议定期修改密码保护账户安全</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<a-button size="small" @click="showChangePwd = true">修改</a-button>
|
||||||
|
</div>
|
||||||
|
<div class="security-item">
|
||||||
|
<div class="security-info">
|
||||||
|
<span class="security-icon">📱</span>
|
||||||
|
<div>
|
||||||
|
<div class="security-name">绑定手机</div>
|
||||||
|
<div class="security-desc">{{ editForm.phone ? `已绑定 ${editForm.phone.replace(/(\d{3})\d{4}(\d{4})/, '$1****$2')}` : '未绑定手机号' }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<a-button size="small">{{ editForm.phone ? '修改' : '绑定' }}</a-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 我的建言 -->
|
||||||
|
<div v-show="activeTab === 'suggestions'" class="content-panel">
|
||||||
|
<div class="panel-header">
|
||||||
|
<h3>我的建言</h3>
|
||||||
|
<a-button type="primary" @click="navigateTo('/suggestions')">提交新建言</a-button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="mySuggestions.length === 0" class="empty-state">
|
||||||
|
<a-empty description="暂无建言记录">
|
||||||
|
<template #extra>
|
||||||
|
<a-button type="primary" @click="navigateTo('/suggestions')">立即建言</a-button>
|
||||||
|
</template>
|
||||||
|
</a-empty>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="suggestion-list">
|
||||||
|
<div v-for="item in mySuggestions" :key="item.id" class="suggestion-item">
|
||||||
|
<div class="suggestion-header">
|
||||||
|
<span class="suggestion-title">{{ item.title }}</span>
|
||||||
|
<a-tag :color="getStatusColor(item.status)">{{ getStatusText(item.status) }}</a-tag>
|
||||||
|
</div>
|
||||||
|
<p class="suggestion-content">{{ item.content }}</p>
|
||||||
|
<div class="suggestion-meta">
|
||||||
|
<span>{{ item.createTime }}</span>
|
||||||
|
<span v-if="item.reply" class="has-reply">已回复</span>
|
||||||
|
</div>
|
||||||
|
<div v-if="item.reply" class="suggestion-reply">
|
||||||
|
<span class="reply-label">官方回复:</span>
|
||||||
|
<span>{{ item.reply }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 收藏记录 -->
|
||||||
|
<div v-show="activeTab === 'favorites'" class="content-panel">
|
||||||
|
<div class="panel-header">
|
||||||
|
<h3>我的收藏</h3>
|
||||||
|
</div>
|
||||||
|
<a-empty description="暂无收藏内容" style="padding: 60px 0" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 浏览历史 -->
|
||||||
|
<div v-show="activeTab === 'history'" class="content-panel">
|
||||||
|
<div class="panel-header">
|
||||||
|
<h3>浏览历史</h3>
|
||||||
|
<a-button @click="clearHistory">清空历史</a-button>
|
||||||
|
</div>
|
||||||
|
<a-empty description="暂无浏览记录" style="padding: 60px 0" />
|
||||||
|
</div>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 修改密码弹窗 -->
|
||||||
|
<a-modal v-model:open="showChangePwd" title="修改密码" @ok="handleChangePwd" :confirm-loading="saving">
|
||||||
|
<a-form :model="pwdForm" layout="vertical">
|
||||||
|
<a-form-item label="当前密码">
|
||||||
|
<a-input-password v-model:value="pwdForm.oldPwd" placeholder="请输入当前密码" />
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label="新密码">
|
||||||
|
<a-input-password v-model:value="pwdForm.newPwd" placeholder="请输入新密码(至少6位)" />
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label="确认新密码">
|
||||||
|
<a-input-password v-model:value="pwdForm.confirmPwd" placeholder="请再次输入新密码" />
|
||||||
|
</a-form-item>
|
||||||
|
</a-form>
|
||||||
|
</a-modal>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { message } from 'ant-design-vue'
|
||||||
|
import { getToken } from '@/utils/token-util'
|
||||||
|
|
||||||
|
useHead({ title: '个人中心 - 决策咨询网' })
|
||||||
|
|
||||||
|
const isAuthed = computed(() => !!getToken())
|
||||||
|
const activeTab = ref('info')
|
||||||
|
const editing = ref(false)
|
||||||
|
const saving = ref(false)
|
||||||
|
const showChangePwd = ref(false)
|
||||||
|
|
||||||
|
const sideMenuItems = [
|
||||||
|
{ key: 'info', label: '基本信息', icon: '👤' },
|
||||||
|
{ key: 'suggestions', label: '我的建言', icon: '💬' },
|
||||||
|
{ key: 'favorites', label: '我的收藏', icon: '⭐' },
|
||||||
|
{ key: 'history', label: '浏览历史', icon: '📖' },
|
||||||
|
]
|
||||||
|
|
||||||
|
const userInfo = reactive<any>({
|
||||||
|
nickname: '用户',
|
||||||
|
username: '',
|
||||||
|
avatar: '',
|
||||||
|
isAdmin: false,
|
||||||
|
phone: '',
|
||||||
|
email: '',
|
||||||
|
organization: '',
|
||||||
|
bio: '',
|
||||||
|
})
|
||||||
|
|
||||||
|
const editForm = reactive({
|
||||||
|
nickname: '',
|
||||||
|
phone: '',
|
||||||
|
email: '',
|
||||||
|
organization: '',
|
||||||
|
bio: '',
|
||||||
|
})
|
||||||
|
|
||||||
|
const pwdForm = reactive({
|
||||||
|
oldPwd: '',
|
||||||
|
newPwd: '',
|
||||||
|
confirmPwd: '',
|
||||||
|
})
|
||||||
|
|
||||||
|
const stats = reactive({
|
||||||
|
suggestions: 0,
|
||||||
|
favorites: 0,
|
||||||
|
views: 0,
|
||||||
|
})
|
||||||
|
|
||||||
|
const mySuggestions = ref<any[]>([])
|
||||||
|
|
||||||
|
function getStatusColor(status: string) {
|
||||||
|
const map: Record<string, string> = { pending: 'orange', processing: 'blue', done: 'green', rejected: 'red' }
|
||||||
|
return map[status] || 'default'
|
||||||
|
}
|
||||||
|
|
||||||
|
function getStatusText(status: string) {
|
||||||
|
const map: Record<string, string> = { pending: '待处理', processing: '处理中', done: '已处理', rejected: '已关闭' }
|
||||||
|
return map[status] || status
|
||||||
|
}
|
||||||
|
|
||||||
|
function beforeUpload() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleAvatarChange() {
|
||||||
|
// TODO: 上传头像
|
||||||
|
}
|
||||||
|
|
||||||
|
async function saveInfo() {
|
||||||
|
saving.value = true
|
||||||
|
try {
|
||||||
|
// TODO: 调用API保存
|
||||||
|
message.success('保存成功')
|
||||||
|
editing.value = false
|
||||||
|
} catch (e: any) {
|
||||||
|
message.error(e?.message || '保存失败')
|
||||||
|
} finally {
|
||||||
|
saving.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleChangePwd() {
|
||||||
|
if (pwdForm.newPwd !== pwdForm.confirmPwd) {
|
||||||
|
message.error('两次密码不一致')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
saving.value = true
|
||||||
|
try {
|
||||||
|
// TODO: 调用API修改密码
|
||||||
|
message.success('密码修改成功,请重新登录')
|
||||||
|
showChangePwd.value = false
|
||||||
|
} catch (e: any) {
|
||||||
|
message.error(e?.message || '修改失败')
|
||||||
|
} finally {
|
||||||
|
saving.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearHistory() {
|
||||||
|
message.info('已清空浏览历史')
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
if (!isAuthed.value) return
|
||||||
|
// TODO: 加载用户信息和统计数据
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.profile-page {
|
||||||
|
background: #f5f7fa;
|
||||||
|
min-height: 60vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.not-authed {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 16px;
|
||||||
|
padding: 60px;
|
||||||
|
margin: 20px 0;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-card {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 16px;
|
||||||
|
padding: 28px 20px;
|
||||||
|
box-shadow: 0 2px 12px rgba(0,0,0,0.06);
|
||||||
|
position: sticky;
|
||||||
|
top: 80px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar-section {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar-wrap {
|
||||||
|
position: relative;
|
||||||
|
width: 80px;
|
||||||
|
height: 80px;
|
||||||
|
border-radius: 50%;
|
||||||
|
overflow: hidden;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar-img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar-placeholder {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: linear-gradient(135deg, #1e3a5f, #3498db);
|
||||||
|
color: #fff;
|
||||||
|
font-size: 32px;
|
||||||
|
font-weight: 700;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar-overlay {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
background: rgba(0,0,0,0.4);
|
||||||
|
color: #fff;
|
||||||
|
font-size: 12px;
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-end;
|
||||||
|
justify-content: center;
|
||||||
|
padding-bottom: 6px;
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar-wrap:hover .avatar-overlay {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-name {
|
||||||
|
text-align: center;
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #1f2937;
|
||||||
|
margin: 0 0 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-role {
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-stats {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-around;
|
||||||
|
padding: 16px 0;
|
||||||
|
border-top: 1px solid #f0f0f0;
|
||||||
|
border-bottom: 1px solid #f0f0f0;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-item {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-num {
|
||||||
|
font-size: 22px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #1e3a5f;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-label {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #9ca3af;
|
||||||
|
margin-top: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.side-menu {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.side-menu-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
padding: 12px 14px;
|
||||||
|
border-radius: 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #374151;
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.side-menu-item:hover {
|
||||||
|
background: #f3f4f6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.side-menu-item.active {
|
||||||
|
background: #eff6ff;
|
||||||
|
color: #1e3a5f;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-icon {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-panel {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 16px;
|
||||||
|
padding: 28px;
|
||||||
|
box-shadow: 0 2px 12px rgba(0,0,0,0.06);
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
padding-bottom: 16px;
|
||||||
|
border-bottom: 1px solid #f0f0f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel-header h3 {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #1f2937;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-form {
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.security-section {
|
||||||
|
margin-top: 32px;
|
||||||
|
padding-top: 24px;
|
||||||
|
border-top: 1px solid #f0f0f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.security-section h4 {
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #374151;
|
||||||
|
margin: 0 0 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.security-items {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.security-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 14px 16px;
|
||||||
|
background: #f9fafb;
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.security-info {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.security-icon {
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.security-name {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #374151;
|
||||||
|
}
|
||||||
|
|
||||||
|
.security-desc {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #9ca3af;
|
||||||
|
margin-top: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.suggestion-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.suggestion-item {
|
||||||
|
padding: 16px;
|
||||||
|
background: #f9fafb;
|
||||||
|
border-radius: 10px;
|
||||||
|
border: 1px solid #f0f0f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.suggestion-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.suggestion-title {
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #1f2937;
|
||||||
|
}
|
||||||
|
|
||||||
|
.suggestion-content {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #6b7280;
|
||||||
|
margin: 0 0 8px;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.suggestion-meta {
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #9ca3af;
|
||||||
|
}
|
||||||
|
|
||||||
|
.has-reply {
|
||||||
|
color: #059669;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.suggestion-reply {
|
||||||
|
margin-top: 10px;
|
||||||
|
padding: 10px 12px;
|
||||||
|
background: #eff6ff;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 13px;
|
||||||
|
color: #374151;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reply-label {
|
||||||
|
font-weight: 600;
|
||||||
|
color: #1e3a5f;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-state {
|
||||||
|
padding: 40px 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,210 +1,23 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="reference-page">
|
<ArticleListPage :config="pageConfig" />
|
||||||
<div class="page-header">
|
|
||||||
<h1 class="page-title">决策参考</h1>
|
|
||||||
<p class="page-desc">政策原文、深度解读、研究成果及专题研究报告</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 分类标签 -->
|
|
||||||
<div class="category-tabs">
|
|
||||||
<a-radio-group v-model:value="activeType" button-style="solid" @change="handleTypeChange">
|
|
||||||
<a-radio-button value="">全部</a-radio-button>
|
|
||||||
<a-radio-button value="policy">政策原文</a-radio-button>
|
|
||||||
<a-radio-button value="analysis">深度解读</a-radio-button>
|
|
||||||
<a-radio-button value="research">研究成果</a-radio-button>
|
|
||||||
<a-radio-button value="special">专题研究</a-radio-button>
|
|
||||||
<a-radio-button value="asean">东盟研究</a-radio-button>
|
|
||||||
<a-radio-button value="data">数据服务</a-radio-button>
|
|
||||||
</a-radio-group>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 文章列表 -->
|
|
||||||
<div class="article-list">
|
|
||||||
<div v-for="article in articles" :key="article.id" class="article-item" @click="handleView(article)">
|
|
||||||
<div class="article-tag" v-if="article.isVip">
|
|
||||||
<a-tag color="gold">VIP</a-tag>
|
|
||||||
</div>
|
|
||||||
<h3 class="article-title">{{ article.title }}</h3>
|
|
||||||
<p class="article-overview">{{ article.overview }}</p>
|
|
||||||
<div class="article-meta">
|
|
||||||
<span class="meta-item">{{ article.source }}</span>
|
|
||||||
<span class="meta-item">{{ article.publishTime }}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-if="loading" class="loading-placeholder">
|
|
||||||
<a-spin size="large" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-if="!loading && articles.length === 0" class="empty-placeholder">
|
|
||||||
<a-empty description="暂无文章" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 分页 -->
|
|
||||||
<div class="pagination-wrap" v-if="total > pageSize">
|
|
||||||
<a-pagination
|
|
||||||
v-model:current="currentPage"
|
|
||||||
:total="total"
|
|
||||||
:page-size="pageSize"
|
|
||||||
@change="handlePageChange"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { message } from 'ant-design-vue'
|
|
||||||
|
|
||||||
useHead({ title: '决策参考 - 决策咨询网' })
|
useHead({ title: '决策参考 - 决策咨询网' })
|
||||||
|
|
||||||
const router = useRouter()
|
const pageConfig = {
|
||||||
|
title: '决策参考',
|
||||||
const activeType = ref((useRoute().query.type as string) || '')
|
desc: '汇聚政策原文、深度解读、研究成果、专题研究、东盟研究等权威参考资料',
|
||||||
const currentPage = ref(1)
|
bannerGradient: 'linear-gradient(135deg, #7c3aed 0%, #4f46e5 100%)',
|
||||||
const pageSize = ref(12)
|
baseRoute: 'reference',
|
||||||
const total = ref(0)
|
categories: [
|
||||||
const loading = ref(false)
|
{ type: '', label: '全部文章' },
|
||||||
const articles = ref<any[]>([])
|
{ type: 'policy', label: '政策原文' },
|
||||||
|
{ type: 'analysis', label: '深度解读' },
|
||||||
async function loadArticles() {
|
{ type: 'research', label: '研究成果' },
|
||||||
loading.value = true
|
{ type: 'special', label: '专题研究' },
|
||||||
try {
|
{ type: 'asean', label: '东盟研究' },
|
||||||
// TODO: 接入实际API
|
{ type: 'data', label: '数据服务(会员)' },
|
||||||
} catch (e: any) {
|
]
|
||||||
message.error('加载失败')
|
|
||||||
} finally {
|
|
||||||
loading.value = false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleTypeChange() {
|
|
||||||
currentPage.value = 1
|
|
||||||
loadArticles()
|
|
||||||
}
|
|
||||||
|
|
||||||
function handlePageChange(page: number) {
|
|
||||||
currentPage.value = page
|
|
||||||
loadArticles()
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleView(article: any) {
|
|
||||||
// 如果是VIP内容,需要登录
|
|
||||||
if (article.isVip) {
|
|
||||||
// TODO: 检查登录状态
|
|
||||||
}
|
|
||||||
router.push(`/reference/${article.id}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
loadArticles()
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.reference-page {
|
|
||||||
max-width: 1200px;
|
|
||||||
margin: 0 auto;
|
|
||||||
padding: 40px 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.page-header {
|
|
||||||
text-align: center;
|
|
||||||
margin-bottom: 40px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.page-title {
|
|
||||||
font-size: 32px;
|
|
||||||
font-weight: 700;
|
|
||||||
color: #1f2937;
|
|
||||||
margin: 0 0 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.page-desc {
|
|
||||||
font-size: 16px;
|
|
||||||
color: #6b7280;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.category-tabs {
|
|
||||||
margin-bottom: 32px;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.article-list {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(3, 1fr);
|
|
||||||
gap: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.article-item {
|
|
||||||
position: relative;
|
|
||||||
padding: 20px;
|
|
||||||
background: #fff;
|
|
||||||
border-radius: 12px;
|
|
||||||
box-shadow: 0 2px 8px rgba(0,0,0,0.06);
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all 0.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.article-item:hover {
|
|
||||||
box-shadow: 0 4px 16px rgba(0,0,0,0.1);
|
|
||||||
transform: translateY(-2px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.article-tag {
|
|
||||||
position: absolute;
|
|
||||||
top: 16px;
|
|
||||||
right: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.article-title {
|
|
||||||
font-size: 16px;
|
|
||||||
font-weight: 600;
|
|
||||||
color: #1f2937;
|
|
||||||
margin: 0 0 8px;
|
|
||||||
line-height: 1.4;
|
|
||||||
}
|
|
||||||
|
|
||||||
.article-overview {
|
|
||||||
font-size: 13px;
|
|
||||||
color: #6b7280;
|
|
||||||
margin: 0 0 12px;
|
|
||||||
line-height: 1.6;
|
|
||||||
display: -webkit-box;
|
|
||||||
-webkit-line-clamp: 3;
|
|
||||||
-webkit-box-orient: vertical;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.article-meta {
|
|
||||||
display: flex;
|
|
||||||
gap: 12px;
|
|
||||||
font-size: 12px;
|
|
||||||
color: #9ca3af;
|
|
||||||
}
|
|
||||||
|
|
||||||
.loading-placeholder,
|
|
||||||
.empty-placeholder {
|
|
||||||
grid-column: 1 / -1;
|
|
||||||
padding: 60px 0;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pagination-wrap {
|
|
||||||
margin-top: 40px;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 1024px) {
|
|
||||||
.article-list {
|
|
||||||
grid-template-columns: repeat(2, 1fr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 640px) {
|
|
||||||
.article-list {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|||||||
@@ -1,210 +1,19 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="thinktank-page">
|
<ArticleListPage :config="pageConfig" />
|
||||||
<div class="page-header">
|
|
||||||
<h1 class="page-title">智库观察</h1>
|
|
||||||
<p class="page-desc">汇聚专家智慧,洞察发展趋势,提供决策支持</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 分类标签 -->
|
|
||||||
<div class="category-tabs">
|
|
||||||
<a-radio-group v-model:value="activeType" button-style="solid" @change="handleTypeChange">
|
|
||||||
<a-radio-button value="">全部</a-radio-button>
|
|
||||||
<a-radio-button value="intro">智库介绍</a-radio-button>
|
|
||||||
<a-radio-button value="view">智库视角</a-radio-button>
|
|
||||||
</a-radio-group>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 内容 -->
|
|
||||||
<div class="content-grid">
|
|
||||||
<div v-for="item in items" :key="item.id" class="content-card" @click="handleView(item)">
|
|
||||||
<div class="card-image" v-if="item.image">
|
|
||||||
<img :src="item.image" :alt="item.title" />
|
|
||||||
</div>
|
|
||||||
<div class="card-body">
|
|
||||||
<h3 class="card-title">{{ item.title }}</h3>
|
|
||||||
<p class="card-overview">{{ item.overview }}</p>
|
|
||||||
<div class="card-meta">
|
|
||||||
<span>{{ item.publishTime }}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-if="loading" class="loading-placeholder">
|
|
||||||
<a-spin size="large" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-if="!loading && items.length === 0" class="empty-placeholder">
|
|
||||||
<a-empty description="暂无内容" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 分页 -->
|
|
||||||
<div class="pagination-wrap" v-if="total > pageSize">
|
|
||||||
<a-pagination
|
|
||||||
v-model:current="currentPage"
|
|
||||||
:total="total"
|
|
||||||
:page-size="pageSize"
|
|
||||||
@change="handlePageChange"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { message } from 'ant-design-vue'
|
|
||||||
|
|
||||||
useHead({ title: '智库观察 - 决策咨询网' })
|
useHead({ title: '智库观察 - 决策咨询网' })
|
||||||
|
|
||||||
const router = useRouter()
|
const pageConfig = {
|
||||||
|
title: '智库观察',
|
||||||
const activeType = ref((useRoute().query.type as string) || '')
|
desc: '智库介绍、智库视角,全面展示广西决策咨询智库建设成果',
|
||||||
const currentPage = ref(1)
|
bannerGradient: 'linear-gradient(135deg, #0f766e 0%, #0891b2 100%)',
|
||||||
const pageSize = ref(9)
|
baseRoute: 'think-tank',
|
||||||
const total = ref(0)
|
categories: [
|
||||||
const loading = ref(false)
|
{ type: '', label: '全部文章' },
|
||||||
const items = ref<any[]>([])
|
{ type: 'intro', label: '智库介绍' },
|
||||||
|
{ type: 'view', label: '智库视角' },
|
||||||
async function loadItems() {
|
]
|
||||||
loading.value = true
|
|
||||||
try {
|
|
||||||
// TODO: 接入实际API
|
|
||||||
} catch (e: any) {
|
|
||||||
message.error('加载失败')
|
|
||||||
} finally {
|
|
||||||
loading.value = false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleTypeChange() {
|
|
||||||
currentPage.value = 1
|
|
||||||
loadItems()
|
|
||||||
}
|
|
||||||
|
|
||||||
function handlePageChange(page: number) {
|
|
||||||
currentPage.value = page
|
|
||||||
loadItems()
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleView(item: any) {
|
|
||||||
router.push(`/think-tank/${item.id}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
loadItems()
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.thinktank-page {
|
|
||||||
max-width: 1200px;
|
|
||||||
margin: 0 auto;
|
|
||||||
padding: 40px 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.page-header {
|
|
||||||
text-align: center;
|
|
||||||
margin-bottom: 40px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.page-title {
|
|
||||||
font-size: 32px;
|
|
||||||
font-weight: 700;
|
|
||||||
color: #1f2937;
|
|
||||||
margin: 0 0 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.page-desc {
|
|
||||||
font-size: 16px;
|
|
||||||
color: #6b7280;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.category-tabs {
|
|
||||||
margin-bottom: 32px;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.content-grid {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(3, 1fr);
|
|
||||||
gap: 24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.content-card {
|
|
||||||
background: #fff;
|
|
||||||
border-radius: 12px;
|
|
||||||
overflow: hidden;
|
|
||||||
box-shadow: 0 2px 8px rgba(0,0,0,0.06);
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all 0.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.content-card:hover {
|
|
||||||
box-shadow: 0 4px 16px rgba(0,0,0,0.1);
|
|
||||||
transform: translateY(-4px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-image {
|
|
||||||
width: 100%;
|
|
||||||
height: 180px;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-image img {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
object-fit: cover;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-body {
|
|
||||||
padding: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-title {
|
|
||||||
font-size: 16px;
|
|
||||||
font-weight: 600;
|
|
||||||
color: #1f2937;
|
|
||||||
margin: 0 0 8px;
|
|
||||||
line-height: 1.4;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-overview {
|
|
||||||
font-size: 13px;
|
|
||||||
color: #6b7280;
|
|
||||||
margin: 0 0 12px;
|
|
||||||
line-height: 1.6;
|
|
||||||
display: -webkit-box;
|
|
||||||
-webkit-line-clamp: 2;
|
|
||||||
-webkit-box-orient: vertical;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-meta {
|
|
||||||
font-size: 12px;
|
|
||||||
color: #9ca3af;
|
|
||||||
}
|
|
||||||
|
|
||||||
.loading-placeholder,
|
|
||||||
.empty-placeholder {
|
|
||||||
grid-column: 1 / -1;
|
|
||||||
padding: 60px 0;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pagination-wrap {
|
|
||||||
margin-top: 40px;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 1024px) {
|
|
||||||
.content-grid {
|
|
||||||
grid-template-columns: repeat(2, 1fr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 640px) {
|
|
||||||
.content-grid {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|||||||
Reference in New Issue
Block a user