2025-09-11 10:55:59 +08:00

323 lines
9.2 KiB
Vue
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<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>