Files
jczxw-pc/app/pages/about/index.vue
T
2026-09-11 12:25:38 +08:00

293 lines
6.4 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<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, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;')
}
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>