581 lines
12 KiB
Vue
581 lines
12 KiB
Vue
<template>
|
|
<div class="expert-page">
|
|
<div class="page-header">
|
|
<h1 class="page-title">{{ currentTitle }}</h1>
|
|
<p class="page-desc">{{ currentDesc }}</p>
|
|
</div>
|
|
|
|
<div class="expert-layout">
|
|
<aside class="category-sidebar">
|
|
<div class="category-sidebar-title">专家资讯</div>
|
|
<NuxtLink
|
|
v-for="item in expertNavItems"
|
|
:key="item.to"
|
|
:to="item.to"
|
|
class="category-item"
|
|
:class="{ active: isNavActive(item.to) }"
|
|
>
|
|
{{ item.label }}
|
|
</NuxtLink>
|
|
</aside>
|
|
|
|
<main class="expert-content">
|
|
<div class="toolbar">
|
|
<a-input-search
|
|
v-model:value="keywords"
|
|
:placeholder="showArticleList ? '搜索文章标题 / 摘要' : '搜索姓名 / 单位 / 研究领域'"
|
|
class="toolbar-search"
|
|
@search="handleSearch"
|
|
/>
|
|
<a-button type="primary" @click="navigateTo('/expert/apply')">专家申请</a-button>
|
|
</div>
|
|
|
|
<div v-if="loading" class="loading-placeholder">
|
|
<a-spin size="large" />
|
|
</div>
|
|
|
|
<template v-else-if="showArticleList">
|
|
<div class="content-summary">
|
|
<span>{{ currentTitle }}</span>
|
|
<span>共 {{ total }} 篇文章</span>
|
|
</div>
|
|
|
|
<div class="article-list">
|
|
<div v-for="item in articles" :key="item.id" class="article-item" @click="handleArticleView(item)">
|
|
<div v-if="item.image" class="article-thumb">
|
|
<img :src="item.image" :alt="item.title" />
|
|
</div>
|
|
<div class="article-main">
|
|
<h3 class="article-title">{{ item.title }}</h3>
|
|
<p class="article-overview">{{ item.overview }}</p>
|
|
<div class="article-meta">
|
|
<span>{{ item.source }}</span>
|
|
<span>{{ item.publishTime }}</span>
|
|
<span v-if="item.views">浏览 {{ item.views }}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div v-if="!articles.length" class="empty-placeholder">
|
|
<a-empty description="暂无内容" />
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<div v-else class="expert-grid">
|
|
<div v-for="item in experts" :key="item.expertId" class="expert-card" @click="handleView(item)">
|
|
<div class="expert-avatar-wrap">
|
|
<img v-if="item.avatar" :src="item.avatar" :alt="item.name" class="expert-avatar" />
|
|
<div v-else class="expert-avatar-placeholder">{{ item.name?.charAt(0) }}</div>
|
|
</div>
|
|
<h3 class="expert-name">{{ item.name }}</h3>
|
|
<div class="expert-title">{{ item.title || '未填写职称' }}</div>
|
|
<div class="expert-org">{{ item.organization || '未填写单位' }}</div>
|
|
<p class="expert-research">{{ item.researchArea || '未填写研究领域' }}</p>
|
|
<div class="expert-meta">
|
|
<span>{{ item.education || '学历未填写' }}</span>
|
|
<span>{{ item.joinTime || '待补充' }}</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div v-if="!experts.length" 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>
|
|
</main>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { message } from 'ant-design-vue'
|
|
import { mainNav } from '@/config/nav'
|
|
import { pageSiteArticles, pageSiteExperts, resolveSiteAssetUrl, type SiteArticle, type SiteExpert } from '@/api/site'
|
|
|
|
const router = useRouter()
|
|
const route = useRoute()
|
|
const type = computed(() => (route.query.type as string) || '')
|
|
const keywords = ref((route.query.keyword as string) || '')
|
|
const currentPage = ref(1)
|
|
const pageSize = ref(12)
|
|
const total = ref(0)
|
|
const loading = ref(false)
|
|
const experts = ref<SiteExpert[]>([])
|
|
const articles = ref<ArticleItem[]>([])
|
|
const showArticleList = computed(() => ['view', 'dynamic'].includes(type.value))
|
|
const expertNavItems = computed(() => mainNav.find((item) => item.key === 'expert')?.children || [])
|
|
const currentTitle = computed(() => {
|
|
if (type.value === 'view') return '专家视点'
|
|
if (type.value === 'dynamic') return '专家动态'
|
|
return '专家库'
|
|
})
|
|
const currentDesc = computed(() => {
|
|
if (type.value === 'view') return '聚合专家研究观点与专业观察'
|
|
if (type.value === 'dynamic') return '展示专家动态、活动资讯与相关报道'
|
|
return '汇聚各领域认证专家,展示研究方向与专业背景'
|
|
})
|
|
|
|
type ArticleItem = {
|
|
id: number
|
|
title: string
|
|
overview: string
|
|
image: string
|
|
source: string
|
|
publishTime: string
|
|
views: number
|
|
}
|
|
|
|
useHead({
|
|
title: computed(() => {
|
|
if (type.value === 'view') {
|
|
return '专家视点 - 决策咨询网'
|
|
}
|
|
if (type.value === 'dynamic') {
|
|
return '专家动态 - 决策咨询网'
|
|
}
|
|
return '专家库 - 决策咨询网'
|
|
})
|
|
})
|
|
|
|
function isNavActive(to: string) {
|
|
if (to === '/expert') {
|
|
return route.path === '/expert' && !type.value
|
|
}
|
|
if (to.startsWith('/expert?type=')) {
|
|
return route.path === '/expert' && type.value === to.split('type=')[1]
|
|
}
|
|
return route.path === to
|
|
}
|
|
|
|
async function loadExperts() {
|
|
if (showArticleList.value) {
|
|
return
|
|
}
|
|
|
|
loading.value = true
|
|
try {
|
|
const data = await pageSiteExperts({
|
|
page: currentPage.value,
|
|
limit: pageSize.value,
|
|
keywords: keywords.value || undefined,
|
|
})
|
|
experts.value = data.list || []
|
|
total.value = data.count || 0
|
|
} catch (e: unknown) {
|
|
message.error(e instanceof Error ? e.message : '加载失败')
|
|
experts.value = []
|
|
total.value = 0
|
|
} finally {
|
|
loading.value = false
|
|
}
|
|
}
|
|
|
|
function normalizeArticle(item: SiteArticle): ArticleItem {
|
|
return {
|
|
id: item.articleId || item.id,
|
|
title: item.title,
|
|
overview: item.overview || '暂无摘要',
|
|
image: resolveSiteAssetUrl(item.image),
|
|
source: item.source || '广西决策咨询网',
|
|
publishTime: item.publishTime || item.createTime || '',
|
|
views: Number(item.views || item.actualViews || 0),
|
|
}
|
|
}
|
|
|
|
async function loadArticles() {
|
|
if (!showArticleList.value) {
|
|
return
|
|
}
|
|
|
|
loading.value = true
|
|
try {
|
|
const data = await pageSiteArticles({
|
|
page: currentPage.value,
|
|
limit: pageSize.value,
|
|
keywords: keywords.value || undefined,
|
|
category: 'expert',
|
|
type: type.value,
|
|
})
|
|
articles.value = (data.list || []).map(normalizeArticle)
|
|
total.value = data.count || 0
|
|
} catch (e: unknown) {
|
|
message.error(e instanceof Error ? e.message : '加载失败')
|
|
articles.value = []
|
|
total.value = 0
|
|
} finally {
|
|
loading.value = false
|
|
}
|
|
}
|
|
|
|
function loadCurrentData() {
|
|
if (showArticleList.value) {
|
|
loadArticles()
|
|
return
|
|
}
|
|
loadExperts()
|
|
}
|
|
|
|
function handleSearch() {
|
|
currentPage.value = 1
|
|
router.replace({
|
|
query: {
|
|
...(type.value ? { type: type.value } : {}),
|
|
...(keywords.value ? { keyword: keywords.value } : {}),
|
|
},
|
|
})
|
|
loadCurrentData()
|
|
}
|
|
|
|
function handlePageChange(page: number) {
|
|
currentPage.value = page
|
|
loadCurrentData()
|
|
window.scrollTo({ top: 0, behavior: 'smooth' })
|
|
}
|
|
|
|
function handleView(item: SiteExpert) {
|
|
router.push(`/expert/${item.expertId}`)
|
|
}
|
|
|
|
function handleArticleView(item: ArticleItem) {
|
|
router.push(`/article/${item.id}`)
|
|
}
|
|
|
|
watch(() => route.query.keyword, (value) => {
|
|
keywords.value = (value as string) || ''
|
|
currentPage.value = 1
|
|
loadCurrentData()
|
|
})
|
|
|
|
watch(() => route.query.type, () => {
|
|
currentPage.value = 1
|
|
loadCurrentData()
|
|
})
|
|
|
|
onMounted(() => {
|
|
loadCurrentData()
|
|
})
|
|
</script>
|
|
|
|
<style scoped>
|
|
.expert-page {
|
|
max-width: 1200px;
|
|
margin: 0 auto;
|
|
padding: 40px 20px;
|
|
}
|
|
|
|
.page-header {
|
|
text-align: center;
|
|
margin-bottom: 32px;
|
|
}
|
|
|
|
.page-title {
|
|
font-size: 32px;
|
|
font-weight: 700;
|
|
color: #1f2937;
|
|
margin: 0 0 12px;
|
|
}
|
|
|
|
.page-desc {
|
|
font-size: 16px;
|
|
color: #6b7280;
|
|
margin: 0;
|
|
}
|
|
|
|
.expert-layout {
|
|
display: grid;
|
|
grid-template-columns: 220px minmax(0, 1fr);
|
|
gap: 28px;
|
|
align-items: start;
|
|
}
|
|
|
|
.category-sidebar {
|
|
position: sticky;
|
|
top: 80px;
|
|
overflow: hidden;
|
|
background: #fff;
|
|
border-radius: 8px;
|
|
border: 1px solid #edf2f7;
|
|
box-shadow: 0 4px 16px rgba(15, 23, 42, 0.05);
|
|
}
|
|
|
|
.category-sidebar-title {
|
|
padding: 14px 18px;
|
|
background: #1e3a5f;
|
|
color: #fff;
|
|
font-size: 15px;
|
|
font-weight: 700;
|
|
}
|
|
|
|
.category-item {
|
|
display: block;
|
|
padding: 13px 18px;
|
|
border-bottom: 1px solid #f1f5f9;
|
|
color: #334155;
|
|
font-size: 14px;
|
|
transition: color 0.2s ease, background 0.2s ease;
|
|
}
|
|
|
|
.category-item:last-child {
|
|
border-bottom: 0;
|
|
}
|
|
|
|
.category-item:hover,
|
|
.category-item.active {
|
|
background: #eff6ff;
|
|
color: #1e3a5f;
|
|
font-weight: 700;
|
|
}
|
|
|
|
.expert-content {
|
|
min-width: 0;
|
|
}
|
|
|
|
.toolbar {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
gap: 16px;
|
|
margin-bottom: 28px;
|
|
}
|
|
|
|
.toolbar-search {
|
|
max-width: 360px;
|
|
}
|
|
|
|
.content-summary {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
margin-bottom: 16px;
|
|
padding: 12px 16px;
|
|
background: #fff;
|
|
border-radius: 8px;
|
|
color: #1e3a5f;
|
|
font-weight: 700;
|
|
box-shadow: 0 2px 8px rgba(15, 23, 42, 0.04);
|
|
}
|
|
|
|
.content-summary span:last-child {
|
|
color: #94a3b8;
|
|
font-size: 13px;
|
|
font-weight: 400;
|
|
}
|
|
|
|
.article-list {
|
|
display: grid;
|
|
gap: 12px;
|
|
}
|
|
|
|
.article-item {
|
|
display: flex;
|
|
gap: 18px;
|
|
padding: 18px;
|
|
background: #fff;
|
|
border-radius: 8px;
|
|
border: 1px solid #edf2f7;
|
|
cursor: pointer;
|
|
transition: box-shadow 0.2s ease, transform 0.2s ease;
|
|
}
|
|
|
|
.article-item:hover {
|
|
transform: translateY(-2px);
|
|
box-shadow: 0 8px 20px rgba(15, 23, 42, 0.08);
|
|
}
|
|
|
|
.article-thumb {
|
|
width: 150px;
|
|
height: 100px;
|
|
flex-shrink: 0;
|
|
border-radius: 6px;
|
|
overflow: hidden;
|
|
background: #f8fafc;
|
|
}
|
|
|
|
.article-thumb img {
|
|
width: 100%;
|
|
height: 100%;
|
|
object-fit: cover;
|
|
}
|
|
|
|
.article-main {
|
|
min-width: 0;
|
|
}
|
|
|
|
.article-title {
|
|
margin: 0 0 8px;
|
|
color: #1f2937;
|
|
font-size: 17px;
|
|
font-weight: 700;
|
|
line-height: 1.45;
|
|
}
|
|
|
|
.article-overview {
|
|
margin: 0;
|
|
color: #64748b;
|
|
font-size: 13px;
|
|
line-height: 1.7;
|
|
display: -webkit-box;
|
|
overflow: hidden;
|
|
-webkit-line-clamp: 2;
|
|
-webkit-box-orient: vertical;
|
|
}
|
|
|
|
.article-meta {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 12px;
|
|
margin-top: 12px;
|
|
color: #94a3b8;
|
|
font-size: 12px;
|
|
}
|
|
|
|
.expert-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(4, minmax(0, 1fr));
|
|
gap: 22px;
|
|
}
|
|
|
|
.expert-card {
|
|
background: #fff;
|
|
border-radius: 16px;
|
|
padding: 24px 20px;
|
|
box-shadow: 0 6px 20px rgba(15, 23, 42, 0.06);
|
|
cursor: pointer;
|
|
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
|
}
|
|
|
|
.expert-card:hover {
|
|
transform: translateY(-4px);
|
|
box-shadow: 0 12px 28px rgba(15, 23, 42, 0.1);
|
|
}
|
|
|
|
.expert-avatar-wrap {
|
|
display: flex;
|
|
justify-content: center;
|
|
margin-bottom: 16px;
|
|
}
|
|
|
|
.expert-avatar,
|
|
.expert-avatar-placeholder {
|
|
width: 96px;
|
|
height: 96px;
|
|
border-radius: 50%;
|
|
}
|
|
|
|
.expert-avatar {
|
|
object-fit: cover;
|
|
}
|
|
|
|
.expert-avatar-placeholder {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
background: linear-gradient(135deg, #1e3a5f, #3498db);
|
|
color: #fff;
|
|
font-size: 36px;
|
|
font-weight: 700;
|
|
}
|
|
|
|
.expert-name {
|
|
margin: 0 0 8px;
|
|
text-align: center;
|
|
font-size: 20px;
|
|
color: #1f2937;
|
|
}
|
|
|
|
.expert-title,
|
|
.expert-org,
|
|
.expert-research,
|
|
.expert-meta {
|
|
text-align: center;
|
|
}
|
|
|
|
.expert-title {
|
|
color: #2563eb;
|
|
font-size: 14px;
|
|
font-weight: 600;
|
|
margin-bottom: 8px;
|
|
}
|
|
|
|
.expert-org {
|
|
color: #475569;
|
|
font-size: 14px;
|
|
margin-bottom: 8px;
|
|
}
|
|
|
|
.expert-research {
|
|
min-height: 44px;
|
|
color: #6b7280;
|
|
font-size: 13px;
|
|
line-height: 1.7;
|
|
margin: 0 0 12px;
|
|
}
|
|
|
|
.expert-meta {
|
|
display: flex;
|
|
justify-content: center;
|
|
gap: 12px;
|
|
color: #94a3b8;
|
|
font-size: 12px;
|
|
}
|
|
|
|
.loading-placeholder,
|
|
.empty-placeholder {
|
|
padding: 60px 0;
|
|
text-align: center;
|
|
grid-column: 1 / -1;
|
|
}
|
|
|
|
.pagination-wrap {
|
|
margin-top: 32px;
|
|
text-align: center;
|
|
}
|
|
|
|
@media (max-width: 1024px) {
|
|
.expert-layout {
|
|
grid-template-columns: 1fr;
|
|
}
|
|
|
|
.category-sidebar {
|
|
position: static;
|
|
}
|
|
|
|
.expert-grid {
|
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
}
|
|
}
|
|
|
|
@media (max-width: 640px) {
|
|
.toolbar {
|
|
flex-direction: column;
|
|
align-items: stretch;
|
|
}
|
|
|
|
.toolbar-search {
|
|
max-width: none;
|
|
}
|
|
|
|
.article-item {
|
|
flex-direction: column;
|
|
}
|
|
|
|
.article-thumb {
|
|
width: 100%;
|
|
height: 180px;
|
|
}
|
|
|
|
.expert-grid {
|
|
grid-template-columns: 1fr;
|
|
}
|
|
}
|
|
</style>
|