1+ <template >
2+ <article
3+ class =" group relative flex flex-col h-full bg-white dark:bg-[#111] border border-gray-200 dark:border-gray-800 transition-all duration-300 hover:border-gray-900 dark:hover:border-gray-500 hover:shadow-xl"
4+ >
5+ <div class =" relative w-full h-56 overflow-hidden bg-gray-100 dark:bg-gray-800 border-b border-gray-100 dark:border-gray-800" >
6+ <img
7+ v-if =" article.coverImage"
8+ :src =" article.coverImage"
9+ :alt =" article.title"
10+ class =" w-full h-full object-cover transition-transform duration-700 group-hover:scale-105"
11+ />
12+ <div v-else class =" w-full h-full flex items-center justify-center text-gray-300 dark:text-gray-700" >
13+ <span class =" text-4xl font-mono font-bold opacity-20" >M / 451</span >
14+ </div >
15+
16+ <router-link
17+ v-if =" article.category"
18+ :to =" `/categories/${article.category.slug || article.category.id}`"
19+ class =" absolute top-4 right-4 bg-white/90 dark:bg-black/80 backdrop-blur-sm px-3 py-1 text-xs font-mono font-medium uppercase tracking-wider text-gray-900 dark:text-white border border-gray-200 dark:border-gray-700 hover:bg-black hover:text-white transition-colors"
20+ >
21+ {{ article.category.name }}
22+ </router-link >
23+ </div >
24+
25+ <div class =" flex flex-col flex-grow p-6" >
26+ <div class =" flex items-center text-xs font-mono text-gray-500 mb-3 space-x-4" >
27+ <time :datetime =" article.createdAt" >
28+ {{ formatDate(article.createdAt) }}
29+ </time >
30+ <span v-if =" article.views" class =" flex items-center" >
31+ <span class =" mr-1" >/</span > {{ article.views }} READS
32+ </span >
33+ </div >
34+
35+ <router-link :to =" `/posts/${article.id}`" class =" block group-hover:underline decoration-2 decoration-blue-600 underline-offset-4" >
36+ <h3 class =" text-xl font-bold text-gray-900 dark:text-gray-100 leading-tight mb-3" >
37+ {{ article.title }}
38+ </h3 >
39+ </router-link >
40+
41+ <p class =" text-gray-600 dark:text-gray-400 text-sm leading-relaxed line-clamp-3 mb-4 flex-grow" >
42+ {{ article.excerpt || stripHtml(article.content) }}
43+ </p >
44+
45+ <div class =" pt-4 border-t border-gray-100 dark:border-gray-800 flex items-center justify-between mt-auto" >
46+ <div class =" flex flex-wrap gap-2" >
47+ <router-link
48+ v-for =" tag in article.tags.slice(0, 3)"
49+ :key =" tag.id"
50+ :to =" `/tags/${tag.slug || tag.id}`"
51+ class =" text-xs font-mono text-gray-500 hover:text-blue-600 transition-colors"
52+ >
53+ #{{ tag.name }}
54+ </router-link >
55+ </div >
56+
57+ <router-link :to =" `/posts/${article.id}`" class =" text-gray-900 dark:text-white group-hover:translate-x-1 transition-transform" >
58+ <svg xmlns =" http://www.w3.org/2000/svg" fill =" none" viewBox =" 0 0 24 24" stroke-width =" 1.5" stroke =" currentColor" class =" w-5 h-5" >
59+ <path stroke-linecap =" round" stroke-linejoin =" round" d =" M17.25 8.25L21 12m0 0l-3.75 3.75M21 12H3" />
60+ </svg >
61+ </router-link >
62+ </div >
63+ </div >
64+ </article >
65+ </template >
66+
67+ <script setup>
68+ import { defineProps } from ' vue' ;
69+
70+ const props = defineProps ({
71+ article: {
72+ type: Object ,
73+ required: true ,
74+ // 默认值保护,防止报错
75+ default : () => ({
76+ tags: [],
77+ category: null ,
78+ title: ' Untitled Post' ,
79+ content: ' '
80+ })
81+ }
82+ });
83+
84+ // 1. 更优雅的日期格式化 (Intl API)
85+ const formatDate = (dateString ) => {
86+ if (! dateString) return ' ' ;
87+ return new Intl.DateTimeFormat (' en-US' , {
88+ year: ' numeric' ,
89+ month: ' short' , // "Dec" 比 "12月" 在技术博客里更好看
90+ day: ' numeric'
91+ }).format (new Date (dateString));
92+ };
93+
94+ // 2. 简单的去 HTML 标签工具 (防止摘要显示 raw markdown)
95+ const stripHtml = (html ) => {
96+ if (! html) return ' No preview available.' ;
97+ // 创建一个临时的 DOM 元素来提取纯文本
98+ const tmp = document .createElement (" DIV" );
99+ tmp .innerHTML = html;
100+ return tmp .textContent || tmp .innerText || " " ;
101+ };
102+ </script >
103+
104+ <style scoped>
105+ /* 确保 line-clamp 生效 (Tailwind 通常自带,但以防万一) */
106+ .line-clamp-3 {
107+ display : -webkit-box ;
108+ -webkit-line-clamp : 3 ;
109+ -webkit-box-orient : vertical ;
110+ overflow : hidden ;
111+ }
112+ </style >
0 commit comments