166 lines
4.2 KiB
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>
|