293 lines
6.4 KiB
Vue
293 lines
6.4 KiB
Vue
<template>
|
||
<div class="about-subpage">
|
||
<!-- <div class="about-banner"></div>-->
|
||
|
||
<div class="mx-auto max-w-screen-xl px-4 py-8">
|
||
<a-row :gutter="[32, 0]">
|
||
<a-col :xs="24" :lg="6">
|
||
<div class="side-nav">
|
||
<div class="side-nav-title">关于我们</div>
|
||
<div
|
||
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>
|
||
</a-col>
|
||
|
||
<a-col :xs="24" :lg="18">
|
||
<div class="content-card">
|
||
<div v-if="loading" class="content-loading">
|
||
<a-skeleton active :paragraph="{ rows: 10 }" />
|
||
</div>
|
||
|
||
<div v-else-if="!hasCurrentArticle" class="content-empty">
|
||
<a-empty description="暂无内容" />
|
||
</div>
|
||
|
||
<template v-else>
|
||
<h2 class="content-title">{{ currentArticle.title || currentNavLabel }}</h2>
|
||
<div class="content-meta">
|
||
<span v-if="currentArticle.publishTime">发布时间:{{ currentArticle.publishTime }}</span>
|
||
<span v-if="currentArticle.source">来源:{{ currentArticle.source }}</span>
|
||
</div>
|
||
<div class="content-body article-body" v-html="currentArticleContent" />
|
||
</template>
|
||
</div>
|
||
</a-col>
|
||
</a-row>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { pageSiteArticles, type SiteArticle } from '@/api/site'
|
||
|
||
useHead({ title: '关于我们 - 决策咨询网' })
|
||
|
||
const route = useRoute()
|
||
const router = useRouter()
|
||
|
||
const navItems = [
|
||
{ key: 'intro', label: '学会简介', icon: '🏛️' },
|
||
{ key: 'organization', label: '组织机构', icon: '🔧' },
|
||
{ key: 'charter', label: '学会章程', icon: '📋' },
|
||
{ key: 'consultation', label: '咨询服务', icon: '💼' },
|
||
{ key: 'join', label: '加入我们', icon: '🤝' },
|
||
]
|
||
|
||
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'
|
||
)
|
||
|
||
const loading = ref(false)
|
||
const articleMap = ref<Record<string, SiteArticle | null>>({})
|
||
|
||
const currentArticle = computed(() => articleMap.value[currentSection.value] || null)
|
||
const hasCurrentArticle = computed(() => Boolean(currentArticle.value?.articleId))
|
||
const currentNavLabel = computed(() => {
|
||
return navItems.find((item) => item.key === currentSection.value)?.label || '关于我们'
|
||
})
|
||
|
||
const currentArticleContent = computed(() => {
|
||
const article = currentArticle.value
|
||
if (!article) {
|
||
return ''
|
||
}
|
||
|
||
if (article.content && /<[a-z][\s\S]*>/i.test(article.content)) {
|
||
return article.content
|
||
}
|
||
|
||
const text = article.content || article.overview || ''
|
||
if (!text) {
|
||
return '<p>暂无内容</p>'
|
||
}
|
||
|
||
return text
|
||
.split(/\n+/)
|
||
.map((item) => item.trim())
|
||
.filter(Boolean)
|
||
.map((item) => `<p>${escapeHtml(item)}</p>`)
|
||
.join('')
|
||
})
|
||
|
||
function escapeHtml(value: string) {
|
||
return value
|
||
.replace(/&/g, '&')
|
||
.replace(/</g, '<')
|
||
.replace(/>/g, '>')
|
||
.replace(/"/g, '"')
|
||
.replace(/'/g, ''')
|
||
}
|
||
|
||
function switchSection(key: string) {
|
||
currentSection.value = key
|
||
if (key === 'join') {
|
||
router.push('/about/join')
|
||
return
|
||
}
|
||
const target = Object.entries(sectionMap).find(([, value]) => value === key)?.[0] || '/about'
|
||
router.push(target)
|
||
}
|
||
|
||
async function loadArticles() {
|
||
loading.value = true
|
||
try {
|
||
const resultEntries = await Promise.all(
|
||
navItems.map(async (item) => {
|
||
const data = await pageSiteArticles({
|
||
page: 1,
|
||
limit: 1,
|
||
category: 'about',
|
||
type: item.key,
|
||
})
|
||
return [item.key, data.list?.[0] || null] as const
|
||
})
|
||
)
|
||
|
||
articleMap.value = Object.fromEntries(resultEntries)
|
||
} finally {
|
||
loading.value = false
|
||
}
|
||
}
|
||
|
||
watch(() => route.path, (newPath) => {
|
||
currentSection.value = sectionMap[newPath] || 'intro'
|
||
})
|
||
|
||
watch(() => route.query.section, (sec) => {
|
||
if (sec) currentSection.value = sec as string
|
||
})
|
||
|
||
onMounted(() => {
|
||
loadArticles()
|
||
})
|
||
</script>
|
||
|
||
<style scoped>
|
||
.about-banner {
|
||
background: linear-gradient(135deg, #1e3a5f 0%, #0d1b2a 100%);
|
||
padding: 60px 0 40px;
|
||
position: relative;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.about-banner::before {
|
||
content: '';
|
||
position: absolute;
|
||
inset: 0;
|
||
background: url('https://picsum.photos/1920/400?random=200') center/cover;
|
||
opacity: 0.1;
|
||
}
|
||
|
||
.side-nav {
|
||
background: #fff;
|
||
border-radius: 12px;
|
||
box-shadow: 0 2px 8px rgba(0,0,0,0.06);
|
||
overflow: hidden;
|
||
position: sticky;
|
||
top: 80px;
|
||
}
|
||
|
||
.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;
|
||
border-radius: 16px;
|
||
padding: 40px;
|
||
box-shadow: 0 2px 12px rgba(0,0,0,0.06);
|
||
min-height: 400px;
|
||
}
|
||
|
||
.content-title {
|
||
font-size: 24px;
|
||
font-weight: 700;
|
||
color: #1e3a5f;
|
||
margin: 0 0 16px;
|
||
padding-bottom: 16px;
|
||
border-bottom: 2px solid #e8f0fe;
|
||
}
|
||
|
||
.content-meta {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 16px;
|
||
margin-bottom: 20px;
|
||
color: #94a3b8;
|
||
font-size: 13px;
|
||
}
|
||
|
||
.content-loading,
|
||
.content-empty {
|
||
padding: 8px 0;
|
||
}
|
||
|
||
.article-body {
|
||
color: #334155;
|
||
font-size: 15px;
|
||
line-height: 1.9;
|
||
}
|
||
|
||
.article-body :deep(h2),
|
||
.article-body :deep(h3),
|
||
.article-body :deep(h4) {
|
||
color: #1f2937;
|
||
margin: 24px 0 12px;
|
||
}
|
||
|
||
.article-body :deep(p) {
|
||
margin: 0 0 14px;
|
||
}
|
||
|
||
.article-body :deep(img) {
|
||
max-width: 100%;
|
||
height: auto;
|
||
}
|
||
|
||
.article-body :deep(ul),
|
||
.article-body :deep(ol) {
|
||
padding-left: 22px;
|
||
margin: 0 0 16px;
|
||
}
|
||
|
||
@media (max-width: 920px) {
|
||
.content-card {
|
||
padding: 24px;
|
||
}
|
||
|
||
.about-banner {
|
||
padding: 36px 0 20px;
|
||
}
|
||
}
|
||
</style>
|