news-classifier/client/src/components/NewsCard.vue

166 lines
4.2 KiB
Vue

<script setup lang="ts">
import { computed } from 'vue'
import type { News } from '@/types/api'
interface Props {
news: News
}
const props = defineProps<Props>()
// 格式化时间
const formattedTime = computed(() => {
const date = new Date(props.news.publishTime || props.news.createdAt)
const now = new Date()
const diff = now.getTime() - date.getTime()
const minutes = Math.floor(diff / 60000)
const hours = Math.floor(diff / 3600000)
const days = Math.floor(diff / 86400000)
if (minutes < 1) return '刚刚'
if (minutes < 60) return `${minutes}分钟前`
if (hours < 24) return `${hours}小时前`
if (days < 7) return `${days}天前`
return date.toLocaleDateString('zh-CN', { month: 'short', day: 'numeric' })
})
// 截取摘要
const summary = computed(() => {
const text = props.news.content.replace(/<[^>]*>/g, '').trim()
return text.length > 100 ? text.slice(0, 100) + '...' : text
})
</script>
<template>
<div class="news-card">
<!-- 分类标签 -->
<div class="news-card-category">
<span class="category-badge">{{ news.categoryName || '未分类' }}</span>
</div>
<!-- 新闻标题 -->
<h3 class="news-card-title">{{ news.title }}</h3>
<!-- 新闻摘要 -->
<p class="news-card-summary">{{ summary }}</p>
<!-- 新闻元信息 -->
<div class="news-card-meta">
<span class="meta-item source">
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="10"/>
<line x1="2" y1="12" x2="22" y2="12"/>
<path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"/>
</svg>
{{ news.source || '未知来源' }}
</span>
<span v-if="news.author" class="meta-item author">
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/>
<circle cx="12" cy="7" r="4"/>
</svg>
{{ news.author }}
</span>
<span class="meta-item time">
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="10"/>
<polyline points="12 6 12 12 16 14"/>
</svg>
{{ formattedTime }}
</span>
</div>
</div>
</template>
<style scoped>
.news-card {
display: flex;
flex-direction: column;
padding: 1.25rem;
background: hsl(var(--card));
border: 1px solid hsl(var(--border));
border-radius: var(--radius);
cursor: pointer;
transition: border-color 0.2s, box-shadow 0.2s;
position: relative;
overflow: hidden;
}
.news-card:hover {
border-color: hsl(var(--primary) / 0.3);
box-shadow: 0 4px 20px rgb(0 0 0 / 0.08);
}
.news-card-category {
margin-bottom: 0.75rem;
}
.category-badge {
display: inline-block;
padding: 0.25rem 0.625rem;
font-size: 0.75rem;
font-weight: 500;
background: hsl(var(--primary) / 0.1);
color: hsl(var(--primary));
border-radius: 4px;
}
.news-card-title {
font-size: 1rem;
font-weight: 600;
color: hsl(var(--foreground));
margin: 0 0 0.75rem 0;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
line-height: 1.5;
}
.news-card-summary {
font-size: 0.875rem;
color: hsl(var(--muted-foreground));
margin: 0 0 1rem 0;
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
overflow: hidden;
line-height: 1.6;
flex: 1;
}
.news-card-meta {
display: flex;
flex-wrap: wrap;
gap: 0.75rem;
padding-top: 0.75rem;
border-top: 1px solid hsl(var(--border));
}
.meta-item {
display: flex;
align-items: center;
gap: 0.25rem;
font-size: 0.75rem;
color: hsl(var(--muted-foreground));
}
.meta-item svg {
flex-shrink: 0;
}
.meta-item.source {
color: hsl(var(--foreground));
}
.meta-item.author {
color: hsl(var(--muted-foreground));
}
.meta-item.time {
margin-left: auto;
}
</style>