基础的文章列表功能

This commit is contained in:
WindowBird 2025-11-03 17:56:22 +08:00
parent efb6a8c222
commit 61b129631d
2 changed files with 196 additions and 2 deletions

View File

@ -134,8 +134,16 @@ onMounted(() => {
class="wow fadeInDown animated active mb-8 lg:mb-12">
<!-- 分类标题 -->
<div :title="category.name" class="text-xl lg:text-2xl text-blue-600 text-center cursor-pointer mb-5">
{{ category.name }}
<div class="flex items-center justify-between mb-5">
<div :title="category.name" class="text-xl lg:text-2xl text-blue-600">
{{ category.name }}
</div>
<a
:href="`/article?code=${category.code}`"
class="text-sm text-blue-600 hover:text-blue-800 hover:underline transition-colors"
>
查看更多
</a>
</div>
<hr class="border-gray-300 mb-5"/>
<div class="py-5"/>
@ -169,6 +177,16 @@ onMounted(() => {
<div class="text-gray-600 text-lg">暂无新闻数据</div>
<div class="py-12"/>
</div>
<!-- 查看全部链接 -->
<div v-if="!loading && !error && newsData.length > 0" class="text-center mt-8 mb-4">
<a
href="/article"
class="inline-block px-6 py-3 bg-blue-600 hover:bg-blue-700 text-white text-base font-medium rounded-lg transition-all duration-150 ease-in-out no-underline hover:shadow-lg"
>
查看全部文章
</a>
</div>
</div>
</template>

176
app/pages/article/index.vue Normal file
View File

@ -0,0 +1,176 @@
<script lang="ts" setup>
import { ref, onMounted, watch, computed } from 'vue'
import { getArticleList, type Article } from '~/config/api'
const route = useRoute()
const router = useRouter()
//
const code = computed(() => route.query.code as string | undefined)
//
const articles = ref<Article[]>([])
const loading = ref(false)
const error = ref('')
const page = ref(1)
const pageSize = ref(5)
const total = ref(0)
//
const articleTypeMap: Record<string, string> = {
solution: '解决方案',
developKnowledge: '开发知识',
industryTrend: '行业动态'
}
//
const formatDate = (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')
return `${Y}.${M}.${D}`
}
//
const fetchArticles = async () => {
loading.value = true
error.value = ''
try {
const response = await getArticleList({
pageNum: page.value,
pageSize: pageSize.value,
code: code.value
})
articles.value = response.rows || []
total.value = response.total || 0
} catch (err) {
error.value = err instanceof Error ? err.message : '获取文章列表失败'
console.error('获取文章列表时发生错误:', err)
articles.value = []
total.value = 0
} finally {
loading.value = false
}
}
//
watch(page, () => {
fetchArticles()
//
window.scrollTo({ top: 0, behavior: 'smooth' })
})
//
watch(() => route.query.code, (newCode) => {
if (newCode !== code.value) {
page.value = 1
fetchArticles()
}
}, { immediate: false })
onMounted(() => {
fetchArticles()
})
</script>
<template>
<UPage>
<img alt="" src="https://api.ccttiot.com/image-1762162779566.png">
<UPageSection
:ui="{
container:'lg:px-0 lg:pt-10'
}">
<!-- 筛选提示 -->
<div v-if="code" class="mb-6 px-4">
<div class="inline-flex items-center gap-2 px-4 py-2 bg-blue-50 text-blue-700 rounded-lg">
<span>当前筛选{{ articleTypeMap[code] || code }}</span>
<button
@click="router.push('/article')"
class="text-blue-600 hover:text-blue-800 underline text-sm"
>
清除筛选
</button>
</div>
</div>
<!-- 加载状态 -->
<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-lg mb-6">{{ error }}</div>
<UButton @click="fetchArticles" color="primary">
重新加载
</UButton>
</div>
<!-- 文章列表 -->
<div v-else-if="articles.length > 0" class="space-y-6">
<UPageCard
v-for="article in articles"
:key="article.id"
:description="article.brief || ''"
:title="article.title"
:to="`/article/${article.id}`"
:ui="{
root:'group',
container: 'relative grid lg:grid-cols-[1fr_4fr] gap-8 p-6',
}"
class="hover:shadow-2xl hover:scale-105 hover:translate-x-8"
orientation="horizontal"
reverse
spotlight
spotlight-color="primary"
>
<!-- 默认图片占位 -->
<div class="object-cover aspect-[4/3] w-full bg-gradient-to-br from-blue-100 to-blue-200 flex items-center justify-center rounded-lg">
<UIcon name="i-heroicons-document-text" class="size-16 text-blue-400" />
</div>
<template #footer>
<UButton
class="px-0 gap-0 text-right"
label="去阅读"
size="xl"
variant="link"
>
<template #trailing>
<UIcon
class="size-4 text-primary transition-all opacity-0 group-hover:translate-x-1 group-hover:opacity-100"
name="i-lucide-arrow-right"
/>
</template>
</UButton>
</template>
<template #header>
<div class="text-left text-gray-600">{{ formatDate(article.createTime) }}</div>
</template>
</UPageCard>
</div>
<!-- 空状态 -->
<div v-else class="text-center py-20">
<div class="text-gray-600 text-lg">暂无文章</div>
</div>
<!-- 分页 -->
<div v-if="!loading && !error && total > 0" class="mt-8 flex justify-center">
<UPagination
v-model:page="page"
:total="total"
:page-size="pageSize"
/>
</div>
</UPageSection>
</UPage>
</template>
<style scoped>
</style>