添加文章详细页和接口
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 = await Promise.all(promises)
|
||||||
newsData.value = results
|
|
||||||
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
error.value = err instanceof Error ? err.message : '获取新闻数据失败'
|
error.value = err instanceof Error ? err.message : '获取新闻数据失败'
|
||||||
|
|
@ -95,9 +95,9 @@ onMounted(() => {
|
||||||
<!--box3 新闻-->
|
<!--box3 新闻-->
|
||||||
<div class="container mx-auto px-4">
|
<div class="container mx-auto px-4">
|
||||||
<!-- 标题区域 -->
|
<!-- 标题区域 -->
|
||||||
<div class="text-center py-12">
|
<div class="text-center ">
|
||||||
<div
|
<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;">
|
style="visibility: visible; animation-name: fadeInDown;">
|
||||||
开发资讯
|
开发资讯
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -161,8 +161,6 @@ onMounted(() => {
|
||||||
<div v-else class="text-center text-gray-600 text-sm py-5">
|
<div v-else class="text-center text-gray-600 text-sm py-5">
|
||||||
暂无文章
|
暂无文章
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</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