添加文章详细页和接口

This commit is contained in:
WindowBird 2025-10-31 17:09:26 +08:00
parent 22f8d99a2a
commit 08ac441870
3 changed files with 238 additions and 6 deletions

View File

@ -75,8 +75,8 @@ const fetchNewsData = async (): Promise<void> => {
}
})
const results = await Promise.all(promises)
newsData.value = results
newsData.value = await Promise.all(promises)
} catch (err) {
error.value = err instanceof Error ? err.message : '获取新闻数据失败'
@ -95,9 +95,9 @@ onMounted(() => {
<!--box3 新闻-->
<div class="container mx-auto px-4">
<!-- 标题区域 -->
<div class="text-center py-12">
<div class="text-center ">
<div
class="text-black text-4xl leading-tight mb-10 wow fadeInDown animated"
class="text-black text-4xl leading-tight mb-3 wow fadeInDown animated"
style="visibility: visible; animation-name: fadeInDown;">
开发资讯
</div>
@ -161,8 +161,6 @@ onMounted(() => {
<div v-else class="text-center text-gray-600 text-sm py-5">
暂无文章
</div>
</div>
</div>

View File

@ -158,3 +158,34 @@ export async function getArticleList(
}
}
/**
*
* @param id ID
* @returns Promise<Article>
*/
export async function getArticleDetail(id: string): Promise<Article | null> {
try {
const response = await $fetch<ApiResponse<Article>>(
`${API_BASE_URL}/app/owArticle/detail`,
{
method: 'GET',
params: { id },
headers: {
'Content-Type': 'application/json',
},
}
)
// 检查响应状态
if (response.code === 200 && response.data) {
return response.data
}
console.warn('获取文章详情失败', response)
return null
} catch (error) {
console.error('获取文章详情时发生错误:', error)
return null
}
}

203
app/pages/article/[id].vue Normal file
View File

@ -0,0 +1,203 @@
<script lang="ts" setup>
import { ref, onMounted } from 'vue'
import { getArticleDetail, type Article } from '~/config/api'
//
const route = useRoute()
const articleId = route.params.id as string
//
const article = ref<Article | null>(null)
const loading = ref(false)
const error = ref('')
//
const formatTime = (timeString: string): string => {
if (!timeString) return ''
const date = new Date(timeString)
if (isNaN(date.getTime())) return timeString
const Y = date.getFullYear()
const M = String(date.getMonth() + 1).padStart(2, '0')
const D = String(date.getDate()).padStart(2, '0')
const h = String(date.getHours()).padStart(2, '0')
const m = String(date.getMinutes()).padStart(2, '0')
return `${Y}-${M}-${D} ${h}:${m}`
}
//
const fetchArticleDetail = async (): Promise<void> => {
if (!articleId) {
error.value = '文章ID不存在'
return
}
loading.value = true
error.value = ''
try {
const data = await getArticleDetail(articleId)
if (data) {
article.value = data
//
useHead({
title: data.title || '文章详情',
meta: [
{ name: 'description', content: data.brief || data.title || '' }
]
})
} else {
error.value = '文章不存在'
}
} catch (err) {
error.value = err instanceof Error ? err.message : '获取文章详情失败'
console.error('获取文章详情时发生错误:', err)
} finally {
loading.value = false
}
}
onMounted(() => {
fetchArticleDetail()
})
</script>
<template>
<UPage>
<UPageBody>
<div class="container mx-auto px-4 py-8 max-w-4xl">
<!-- 加载状态 -->
<div v-if="loading" class="text-center py-20">
<div class="text-gray-600 text-lg animate-pulse">正在加载文章...</div>
</div>
<!-- 错误状态 -->
<div v-else-if="error" class="text-center py-20">
<div class="text-red-600 text-xl mb-6">{{ error }}</div>
<button
class="inline-block px-6 py-3 bg-blue-600 hover:bg-blue-700 text-white text-base font-normal rounded transition-all duration-150 ease-in-out cursor-pointer border border-transparent"
@click="fetchArticleDetail">
重新加载
</button>
</div>
<!-- 文章内容 -->
<article v-else-if="article" class="bg-white rounded-lg shadow-sm p-6 md:p-8 lg:p-10">
<!-- 文章标题 -->
<header class="mb-8 pb-6 border-b border-gray-200">
<h1 class="text-3xl md:text-4xl lg:text-5xl font-bold text-gray-900 mb-4 leading-tight">
{{ article.title }}
</h1>
<div class="flex items-center text-gray-500 text-sm md:text-base">
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" />
</svg>
<time :datetime="article.createTime">{{ formatTime(article.createTime) }}</time>
</div>
</header>
<!-- 文章简介 -->
<div v-if="article.brief" class="mb-8 p-4 bg-gray-50 rounded-lg border-l-4 border-blue-500">
<p class="text-gray-700 text-base md:text-lg leading-relaxed">{{ article.brief }}</p>
</div>
<!-- 文章正文内容 -->
<div
v-if="article.content"
class="prose prose-lg max-w-none article-content"
v-html="article.content">
</div>
<!-- 文章为空提示 -->
<div v-else class="text-center py-12 text-gray-500">
暂无内容
</div>
</article>
</div>
</UPageBody>
</UPage>
</template>
<style scoped>
@reference "~/assets/css/main.css";
/* 文章内容样式优化 */
.article-content :deep(h1),
.article-content :deep(h2),
.article-content :deep(h3),
.article-content :deep(h4) {
@apply font-bold text-gray-900 mt-8 mb-4;
}
.article-content :deep(h1) {
@apply text-3xl;
}
.article-content :deep(h2) {
@apply text-2xl;
}
.article-content :deep(h3) {
@apply text-xl;
}
.article-content :deep(h4) {
@apply text-lg;
}
.article-content :deep(p) {
@apply text-gray-700 leading-relaxed mb-4;
line-height: 1.8;
}
.article-content :deep(img) {
@apply max-w-full h-auto rounded-lg my-6 mx-auto block;
}
.article-content :deep(ul),
.article-content :deep(ol) {
@apply mb-4 pl-6;
}
.article-content :deep(li) {
@apply mb-2 text-gray-700 leading-relaxed;
}
.article-content :deep(strong) {
@apply font-semibold text-gray-900;
}
.article-content :deep(a) {
@apply text-blue-600 hover:text-blue-800 underline;
}
.article-content :deep(blockquote) {
@apply border-l-4 border-gray-300 pl-4 italic text-gray-600 my-4;
}
.article-content :deep(code) {
@apply bg-gray-100 px-2 py-1 rounded text-sm font-mono;
}
.article-content :deep(pre) {
@apply bg-gray-100 p-4 rounded-lg overflow-x-auto my-4;
}
.article-content :deep(pre code) {
@apply bg-transparent p-0;
}
/* 确保图片响应式 */
.article-content :deep(img) {
max-width: 100%;
height: auto;
}
/* 段落间距优化 */
.article-content :deep(p + p) {
margin-top: 1rem;
}
</style>