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

207 lines
6.5 KiB
Vue
Raw 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 class="news-card card-hover">
<NuxtLink :to="getArticleUrl(article)">
<div class="news-card-image">
<img v-if="article.image || (article.meta && article.meta.image)" :src="article.image || article.meta.image" :alt="article.title" class="w-full h-48 object-cover rounded-t-lg">
<div v-else class="w-full h-48 bg-gray-200 flex items-center justify-center rounded-t-lg">
<i class="fas fa-cloud text-4xl text-gray-400"></i>
</div>
<span v-if="article.trending" class="trending-badge">
<i class="fas fa-fire-alt mr-1"></i> {{ $t('news.meta.trending') }}
</span>
</div>
<div class="news-card-content">
<div class="news-card-category">
<span :class="getCategoryClass(article.category || (article.meta && article.meta.category) || 'other')">
{{ $t(`news.categories.${article.category || (article.meta && article.meta.category) || 'other'}`) }}
</span>
<span class="news-card-date">{{ formatDate(article.date || (article.meta && article.meta.date)) }}</span>
</div>
<h3 class="news-card-title">{{ article.title || $t('news.noTitle') }}</h3>
<p class="news-card-description">{{ article.description || '' }}</p>
<div class="news-card-meta">
<span>
<i class="fas fa-eye mr-1"></i> {{ article.views || (article.meta && article.meta.views) || 0 }}
</span>
<span>
<!-- <i class="fas fa-user mr-1"></i> {{ article.author || (article.meta && article.meta.author) || $t('news.meta.unknownAuthor') }} -->
<i class="fas fa-user mr-1"></i>
</span>
</div>
</div>
</NuxtLink>
</div>
</template>
<script setup lang="ts">
/// <reference types="../../node_modules/.vue-global-types/vue_3.5_0_0_0.d.ts" />
import type { PropType } from 'vue';
import { useI18n } from 'vue-i18n';
const { t, locale } = useI18n();
// 文章类型定义
const props = defineProps({
article: {
type: Object as PropType<any>,
required: true
}
});
// 生成文章URL
const getArticleUrl = (article: any) => {
if (!article) return `/awsnews`;
// 如果有_path属性使用它来构建链接
if (article._path) {
// 如果路径以content开头移除content前缀
if (article._path.startsWith('content/')) {
return `/${article._path.replace('content/', '')}`;
}
// 如果路径以awsnews开头但不是/awsnews开头添加前导斜杠
if (article._path.startsWith('awsnews/') && !article._path.startsWith('/awsnews/')) {
return `/${article._path}`;
}
// 如果路径已经是/awsnews开头直接使用
if (article._path.startsWith('/awsnews/')) {
return article._path;
}
// 其他情况,添加/awsnews/前缀
return `/awsnews/${article._path}`;
}
// 如果有路径属性
if (article.path) {
// 如果路径以content开头移除content前缀
if (article.path.startsWith('content/')) {
return `/${article.path.replace('content/', '')}`;
}
// 如果路径以awsnews开头但不是/awsnews开头添加前导斜杠
if (article.path.startsWith('awsnews/') && !article.path.startsWith('/awsnews/')) {
return `/${article.path}`;
}
// 如果路径已经是/awsnews开头直接使用
if (article.path.startsWith('/awsnews/')) {
return article.path;
}
// 其他情况,添加/awsnews/前缀
return `/awsnews/${article.path}`;
}
// 如果有_dir属性可能表示内容目录
if (article._dir && article._file) {
const fileName = article._file.split('/').pop() || '';
const fileNameWithoutExt = fileName.replace(/\.md$/, '');
return `/awsnews/${fileNameWithoutExt}`;
}
// 基于标题和可能的类别构建URL
if (article.title) {
// 尝试从meta.category或直接的category获取
const category = article.meta?.category || article.category || 'uncategorized';
const slug = article.title
.toLowerCase()
.replace(/[^\w\s-]/g, '') // 移除特殊字符
.replace(/\s+/g, '-'); // 空格替换为连字符
return `/awsnews/${slug}`;
}
// 后备方案
return `/awsnews`;
};
// 格式化日期
const formatDate = (date: string | Date) => {
if (!date) return '-'; // 如果日期为空,返回占位符
try {
// 检查是否在浏览器环境中
if (typeof window === 'undefined') {
// 服务器端或预渲染环境中
const dateObj = new Date(date);
if (isNaN(dateObj.getTime())) {
return '-';
}
return dateObj.toLocaleDateString();
}
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 (error) {
console.error('日期格式化错误:', error);
return '-';
}
};
// 获取分类标签样式
const getCategoryClass = (category: string) => {
const baseClass = 'px-2 py-1 rounded text-xs font-medium';
const categoryClasses: Record<string, string> = {
'cloud-computing': `${baseClass} bg-blue-100 text-blue-700`,
'security': `${baseClass} bg-red-100 text-red-700`,
'serverless': `${baseClass} bg-purple-100 text-purple-700`,
'ai': `${baseClass} bg-green-100 text-green-700`,
'database': `${baseClass} bg-yellow-100 text-yellow-700`,
'other': `${baseClass} bg-gray-100 text-gray-700`
};
return categoryClasses[category] || categoryClasses.other;
};
</script>
<style scoped>
.news-card {
@apply bg-white dark:bg-gray-800 rounded-lg shadow transition-all duration-300 overflow-hidden;
max-width: 100%;
}
.news-card-image {
@apply relative;
}
.trending-badge {
@apply absolute top-2 right-2 bg-red-500 text-white text-xs px-2 py-1 rounded;
}
.news-card-content {
@apply p-4;
}
.news-card-category {
@apply flex justify-between items-center mb-2;
}
.news-card-date {
@apply text-xs text-gray-500 dark:text-gray-400;
}
.news-card-title {
@apply text-lg font-bold mb-2 line-clamp-2 dark:text-white;
}
.news-card-description {
@apply text-sm text-gray-600 dark:text-gray-300 mb-4 line-clamp-3;
}
.news-card-meta {
@apply flex justify-between text-xs text-gray-500 dark:text-gray-400 pt-2 border-t border-gray-100 dark:border-gray-700;
}
</style>