Files
template-nuxt4/app/pages/article/[id].vue
2026-04-29 01:33:33 +08:00

603 lines
15 KiB
Vue
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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="article-detail-page">
<div class="mx-auto max-w-screen-xl px-4 py-8">
<a-row :gutter="[32, 0]">
<!-- 主内容区 -->
<a-col :lg="17" :xs="24">
<!-- 面包屑 -->
<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 :paragraph="{ rows: 12 }" active />
</div>
<div v-else-if="!article.id" class="article-empty">
<a-result status="404" sub-title="您查找的文章不存在或已被删除" 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 :alt="article.title" :src="article.cover" />
</div>
<!-- 标题区 -->
<div class="article-header">
<div v-if="article.categoryName" class="article-category-tag">
{{ article.categoryName }}
</div>
<h1 class="article-title">{{ article.title }}</h1>
<div class="article-meta">
<span v-if="article.source" class="meta-item">
<span class="meta-icon">📰</span>来源{{ article.source }}
</span>
<span v-if="article.author" class="meta-item">
<span class="meta-icon"></span>{{ article.author }}
</span>
<span v-if="article.publishTime" class="meta-item">
<span class="meta-icon">🕐</span>{{ article.publishTime }}
</span>
<span v-if="article.views" class="meta-item">
<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"
class="attachment-item"
target="_blank"
>
<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 v-if="prevArticle" class="nav-prev" @click="goArticle(prevArticle)">
<span class="nav-dir">« 上一篇</span>
<span class="nav-title">{{ prevArticle.title }}</span>
</div>
<div v-if="nextArticle" class="nav-next" @click="goArticle(nextArticle)">
<span class="nav-title">{{ nextArticle.title }}</span>
<span class="nav-dir">下一篇 »</span>
</div>
</div>
</article>
</a-col>
<!-- 侧栏 -->
<a-col :lg="7" :xs="0" 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 lang="ts" setup>
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>