更新首页
This commit is contained in:
+475
-108
@@ -1,99 +1,267 @@
|
||||
<template>
|
||||
<div class="expert-page">
|
||||
<div class="page-header">
|
||||
<h1 class="page-title">专家资讯</h1>
|
||||
<p class="page-desc">汇聚各领域权威专家,提供专业视角与研究成果</p>
|
||||
<h1 class="page-title">{{ currentTitle }}</h1>
|
||||
<p class="page-desc">{{ currentDesc }}</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="view">专家视点</a-radio-button>
|
||||
<a-radio-button value="dynamic">专家动态</a-radio-button>
|
||||
</a-radio-group>
|
||||
</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>
|
||||
|
||||
<!-- 专家/文章列表 -->
|
||||
<div class="expert-list">
|
||||
<div v-for="item in items" :key="item.id" class="expert-item" @click="handleView(item)">
|
||||
<div class="expert-avatar" v-if="item.avatar">
|
||||
<img :src="item.avatar" :alt="item.expertName" />
|
||||
<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 class="expert-default-avatar" v-else>{{ item.expertName?.charAt(0) }}</div>
|
||||
<div class="expert-content">
|
||||
<h3 class="expert-title">{{ item.title }}</h3>
|
||||
<div class="expert-meta">
|
||||
<span class="meta-item">{{ item.expertName }}</span>
|
||||
<span class="meta-item">{{ item.expertTitle }}</span>
|
||||
<span class="meta-item">{{ item.publishTime }}</span>
|
||||
|
||||
<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>
|
||||
<p class="expert-overview">{{ item.overview }}</p>
|
||||
</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 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'
|
||||
|
||||
useHead({ title: '专家资讯 - 决策咨询网' })
|
||||
import { mainNav } from '@/config/nav'
|
||||
import { pageSiteArticles, pageSiteExperts, resolveSiteAssetUrl, type SiteArticle, type SiteExpert } from '@/api/site'
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
const activeType = ref((useRoute().query.type as string) || '')
|
||||
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 items = ref<any[]>([])
|
||||
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
|
||||
}
|
||||
|
||||
async function loadItems() {
|
||||
loading.value = true
|
||||
try {
|
||||
// TODO: 接入实际API
|
||||
} catch (e: any) {
|
||||
message.error('加载失败')
|
||||
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 handleTypeChange() {
|
||||
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
|
||||
loadItems()
|
||||
router.replace({
|
||||
query: {
|
||||
...(type.value ? { type: type.value } : {}),
|
||||
...(keywords.value ? { keyword: keywords.value } : {}),
|
||||
},
|
||||
})
|
||||
loadCurrentData()
|
||||
}
|
||||
|
||||
function handlePageChange(page: number) {
|
||||
currentPage.value = page
|
||||
loadItems()
|
||||
loadCurrentData()
|
||||
window.scrollTo({ top: 0, behavior: 'smooth' })
|
||||
}
|
||||
|
||||
function handleView(item: any) {
|
||||
router.push(`/expert/${item.id}`)
|
||||
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(() => {
|
||||
loadItems()
|
||||
loadCurrentData()
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -106,7 +274,7 @@ onMounted(() => {
|
||||
|
||||
.page-header {
|
||||
text-align: center;
|
||||
margin-bottom: 40px;
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
@@ -122,92 +290,291 @@ onMounted(() => {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.category-tabs {
|
||||
margin-bottom: 32px;
|
||||
text-align: center;
|
||||
.expert-layout {
|
||||
display: grid;
|
||||
grid-template-columns: 220px minmax(0, 1fr);
|
||||
gap: 28px;
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
.expert-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.expert-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;
|
||||
}
|
||||
|
||||
.expert-item:hover {
|
||||
box-shadow: 0 4px 16px rgba(0,0,0,0.1);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.expert-avatar,
|
||||
.expert-default-avatar {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
border-radius: 50%;
|
||||
flex-shrink: 0;
|
||||
.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);
|
||||
}
|
||||
|
||||
.expert-avatar img {
|
||||
.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;
|
||||
}
|
||||
|
||||
.expert-default-avatar {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: #fff;
|
||||
font-size: 32px;
|
||||
.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-content {
|
||||
flex: 1;
|
||||
.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 {
|
||||
font-size: 18px;
|
||||
color: #2563eb;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #1f2937;
|
||||
margin: 0 0 8px;
|
||||
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;
|
||||
color: #6b7280;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.expert-overview {
|
||||
font-size: 14px;
|
||||
color: #6b7280;
|
||||
margin: 0;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.loading-placeholder,
|
||||
.empty-placeholder {
|
||||
padding: 60px 0;
|
||||
text-align: center;
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
|
||||
.pagination-wrap {
|
||||
margin-top: 40px;
|
||||
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>
|
||||
|
||||
Reference in New Issue
Block a user