323 lines
9.2 KiB
Vue
Raw Normal View History

2025-09-11 10:55:59 +08:00
<template>
<div>
<!-- 页面标题 -->
<Banner
:titleKey="'news.hero.title'"
:subtitleKey="'news.hero.subtitle'"
bgImage="/images/bg/cases-bg.webp"
:descriptionKey="'news.hero.description'"
>
<!-- 按钮或其他内容 -->
</Banner>
<Process/>
<div class="news-header bg-gray-50 py-10 border-b">
<div class="container">
<h1 class="text-3xl font-bold mb-2">{{ $t('news.title') }}</h1>
<p class="text-gray-600">{{ $t('news.description') }}</p>
</div>
</div>
<div class="container py-8">
<div class="grid grid-cols-1 lg:grid-cols-4 gap-8">
<!-- 左侧边栏分类过滤和热门文章 -->
<div class="col-span-1">
<!-- 分类过滤器 -->
<CategoryFilter
:categories="categoryList"
v-model:selectedCategory="selectedCategory"
:totalArticles="articles?.length || 0"
/>
<!-- 热门文章列表 -->
<TrendingNewsList :trendingNews="trendingArticles" />
</div>
<!-- 右侧文章列表和特色文章 -->
<div class="col-span-1 lg:col-span-3">
<!-- 特色文章轮播 (仅在显示全部类别时显示) -->
<div v-if="selectedCategory === 'all' && featuredArticles.length > 0" class="mb-8">
<h2 class="text-xl font-bold mb-4">
<i class="fas fa-star text-yellow-500 mr-2"></i>
{{ $t('news.featuredArticles') }}
</h2>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<NewsCard
v-for="article in featuredArticles.slice(0, 2)"
:key="article._path"
:article="article"
:getPath="getArticlePath"
/>
</div>
</div>
<!-- 分类标题 -->
<div class="mb-6">
<h2 class="text-xl font-bold">
<template v-if="selectedCategory === 'all'">
{{ $t('news.latestArticles') }}
</template>
<template v-else>
{{ $t(`news.categories.${selectedCategory}`) }}
</template>
</h2>
</div>
<!-- 筛选后的文章列表 -->
<div v-if="filteredArticles.length > 0">
<div v-if="isDataReady" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
<NewsCard
v-for="article in optimizedArticles"
:key="article.id"
:article="article"
:getPath="getArticlePath"
/>
</div>
</div>
<div v-else class="text-center py-8 bg-gray-50 rounded-lg">
<div class="text-gray-500">
<i class="fas fa-search text-4xl mb-4"></i>
<p>{{ $t('news.noArticlesFound') }}</p>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import CategoryFilter from '~/components/news/CategoryFilter.vue';
import NewsCard from '~/components/news/NewsCard.vue';
import TrendingNewsList from '~/components/news/TrendingNewsList.vue';
import Process from '~/components/Process.vue';
import { useAsyncData, useHead} from 'nuxt/app';
import { ref, computed, onMounted, onUnmounted, nextTick } from 'vue';
import { useI18n } from 'vue-i18n';
import { useRoute } from 'vue-router';
// Nuxt 自动导入
// 文章类型接口
interface Article {
_path?: string;
path?: string;
title: string;
description: string;
category: string;
date: string;
views: number;
featured?: boolean;
trending?: boolean;
author?: string;
image?: string;
}
const { t, locale } = useI18n();
const route = useRoute();
// 设置页面元数据
useHead({
title: 'AWS资讯中心',
meta: [
{ name: 'description', content: 'AWS云计算最新资讯、技术更新和最佳实践' }
]
});
// 当前选中的分类
const selectedCategory = ref('all');
// 从内容模块获取文章
const { data: articles } = await useAsyncData('aws-articles', async () => {
try {
// 先尝试直接查询全部内容
const allContent = await queryContent('awsnews').find();
// console.log('查询到的所有内容:', allContent);
// 过滤出有效的文章(含必要字段的内容)
return allContent.filter((item: any) => {
// console.log('item:', item);
return item && item.title
});
// item &&
// item.title &&
// (item._path?.includes('awsnews') || item._path?.includes('cloud-computing') || item.category));
} catch (e) {
console.error('文章加载错误:', e);
return [];
}
});
// // 调试输出
// console.log('文章列表数量:', articles.value?.length);
// if (articles.value?.length > 0) {
// console.log('第一篇文章示例:', articles.value[0]);
// }
// 热门文章(按浏览量排序)
const trendingArticles = computed(() => {
if (!articles.value || !Array.isArray(articles.value)) return [];
return [...articles.value]
.filter((article: any) => article && typeof article === 'object')
.sort((a: Article, b: Article) => (b.views || 0) - (a.views || 0))
.slice(0, 5);
});
// 特色文章
const featuredArticles = computed(() => {
if (!articles.value || !Array.isArray(articles.value)) return [];
return articles.value
.filter((article: any) => article && typeof article === 'object' && article.featured);
});
// 根据选择的分类筛选文章
const filteredArticles = computed(() => {
if (!articles.value || !Array.isArray(articles.value)) return [];
const validArticles = articles.value.filter((article: any) => article && typeof article === 'object');
// 如果选择"全部",则返回所有文章
if (selectedCategory.value === 'all') {
return validArticles;
}
// 否则按分类筛选
return validArticles.filter(
(article: Article) => article.category === selectedCategory.value
);
});
// 计算分类列表及每个分类的文章数量
const categoryList = computed(() => {
if (!articles.value || !Array.isArray(articles.value)) return [];
const validArticles = articles.value.filter((article: any) =>
article && typeof article === 'object'
);
// 计算每个分类的文章数量
const categoryCounts: Record<string, number> = {};
for (const article of validArticles) {
const category = article.category;
if (category) {
if (categoryCounts[category]) {
categoryCounts[category]++;
} else {
categoryCounts[category] = 1;
}
}
}
// 确保所有已知分类都在列表中,即使没有文章
const knownCategories = ['cloud-computing', 'security', 'serverless', 'ai', 'database', 'other'];
for (const category of knownCategories) {
if (!categoryCounts[category]) {
categoryCounts[category] = 0;
}
}
// 转换为组件所需格式
return Object.entries(categoryCounts)
.map(([value, count]) => ({
value,
count
}))
.sort((a, b) => b.count - a.count); // 按文章数量降序排序
});
// 添加延迟加载
const mounted = ref(false);
onMounted(() => {
mounted.value = true;
// 首先渲染必要内容
nextTick(() => {
// 延迟加载非关键内容
setTimeout(() => {
// 可以在这里加载更多内容或执行昂贵操作
}, 100);
});
});
onUnmounted(() => {
mounted.value = false;
});
// 限制列表渲染数量
const optimizedArticles = computed(() => {
const result = filteredArticles.value.slice(0, 12); // 限制初始渲染数量
return result;
});
// 格式化日期
const formatDate = (date: string | Date) => {
// 处理空值和无效值
if (!date) return '日期未知';
try {
const dateObj = new Date(date);
// 检查日期是否有效
if (isNaN(dateObj.getTime())) {
return '日期未知';
}
return new Intl.DateTimeFormat(locale.value === 'zh-CN' ? 'zh-CN' : 'en-US', {
year: 'numeric',
month: 'short',
day: 'numeric'
}).format(dateObj);
} catch (e) {
console.error('日期格式化错误:', e);
return '日期未知';
}
};
// 安全获取文章路径
const getArticlePath = (article: any) => {
if (!article) return '#';
try {
// 检查是否存在有效路径
const path = article._path || article.path || '';
// 提取最后一部分作为 slug
const slug = path.split('/').pop() || article.id?.split('/').pop() || '';
return `/awsnews/${slug}`;
} catch (e) {
console.error('路径生成错误:', e);
return '#'; // 发生错误时返回空链接
}
};
// 在NewsCard组件内部的任何DOM操作前检查元素是否存在
const safelyAccessDOM = (callback: () => void) => {
try {
// 仅在组件挂载后执行DOM操作
if (mounted.value) {
callback();
}
} catch (error) {
console.error('DOM操作错误:', error);
}
};
// 添加加载状态控制
const isDataReady = ref(false);
onMounted(async () => {
// ...加载文章数据
await nextTick();
isDataReady.value = true;
});
</script>
<style scoped>
.news-header {
background-image: linear-gradient(to right, rgba(35, 47, 62, 0.05), rgba(255, 153, 0, 0.05));
}
</style>