添加文章详细页和接口
This commit is contained in:
parent
22f8d99a2a
commit
08ac441870
|
|
@ -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>
|
||||
|
||||
|
|
|
|||
|
|
@ -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
203
app/pages/article/[id].vue
Normal 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>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user