Skip to content

Commit 071d8da

Browse files
committed
更新博客展示UI
1 parent 406c488 commit 071d8da

2 files changed

Lines changed: 147 additions & 37 deletions

File tree

frontend/src/assets/blog.css

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1210,6 +1210,105 @@ body {
12101210
background: #1a1a1a;
12111211
}
12121212

1213+
/* 文章详情:居中阅读列 + 顶部信息区 */
1214+
.article-detail-page .article-detail-wrap {
1215+
max-width: 720px;
1216+
margin: 0 auto;
1217+
}
1218+
1219+
.article-detail-loading,
1220+
.article-detail-empty {
1221+
padding: 48px 0;
1222+
color: var(--muted);
1223+
text-align: center;
1224+
}
1225+
1226+
.article-back {
1227+
display: inline-flex;
1228+
align-items: center;
1229+
gap: 6px;
1230+
font-size: 13px;
1231+
font-weight: 550;
1232+
color: var(--muted);
1233+
text-decoration: none;
1234+
margin-bottom: 28px;
1235+
}
1236+
1237+
.article-back:hover {
1238+
color: var(--text);
1239+
}
1240+
1241+
.article-back-chev {
1242+
font-size: 18px;
1243+
line-height: 1;
1244+
transform: translateY(-1px);
1245+
opacity: 0.85;
1246+
}
1247+
1248+
.article-kicker-meta {
1249+
margin: 0 0 22px;
1250+
font-size: 13px;
1251+
color: var(--muted);
1252+
letter-spacing: 0.02em;
1253+
}
1254+
1255+
.article-kicker-dot {
1256+
margin: 0 0.35em;
1257+
opacity: 0.65;
1258+
}
1259+
1260+
.article-reader-title {
1261+
margin: 0 0 18px;
1262+
font-size: clamp(28px, 4.2vw, 42px);
1263+
font-weight: 900;
1264+
letter-spacing: -0.035em;
1265+
line-height: 1.12;
1266+
color: var(--text);
1267+
}
1268+
1269+
.article-reader-lead {
1270+
margin: 0 0 32px;
1271+
font-size: 17px;
1272+
line-height: 1.7;
1273+
color: var(--muted);
1274+
font-weight: 450;
1275+
}
1276+
1277+
.article-reader-cover {
1278+
margin: 0 0 44px;
1279+
border-radius: 18px;
1280+
overflow: hidden;
1281+
background: var(--placeholder-tint);
1282+
}
1283+
1284+
.article-reader-cover img {
1285+
width: 100%;
1286+
display: block;
1287+
vertical-align: middle;
1288+
object-fit: cover;
1289+
max-height: min(52vh, 440px);
1290+
}
1291+
1292+
.article-markdown--body {
1293+
margin-top: 0;
1294+
}
1295+
1296+
.article-detail-page .markdown-body h1 {
1297+
font-size: 1.65rem;
1298+
font-weight: 900;
1299+
margin-top: 1.5em;
1300+
letter-spacing: -0.02em;
1301+
}
1302+
1303+
.article-detail-page .markdown-body h2 {
1304+
font-size: 1.35rem;
1305+
margin-top: 1.35em;
1306+
}
1307+
1308+
.article-detail-page .comment-section {
1309+
margin-top: 56px;
1310+
}
1311+
12131312
.article-content {
12141313
padding: 20px 18px 40px;
12151314
}

frontend/src/views/ArticleDetailView.vue

Lines changed: 48 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,39 @@
11
<template>
2-
<div class="blog-container">
2+
<div class="blog-container article-detail-page">
33
<div class="article-detail-wrap">
4-
<div>
5-
<div v-if="loading" class="card">
6-
<div class="card-body" style="padding: 22px">加载中...</div>
7-
</div>
8-
9-
<div v-else>
10-
<div class="card">
11-
<div class="article-content">
12-
<div v-if="!article?.id" style="padding: 20px 0">文章不存在</div>
13-
14-
<div v-else>
15-
<div style="margin-bottom: 14px">
16-
<img
17-
v-if="article.coverMediaKey"
18-
:src="coverUrl(article.coverMediaKey)"
19-
alt="cover"
20-
style="width: 100%; max-height: 340px; object-fit: cover; border-radius: 12px"
21-
/>
22-
</div>
23-
24-
<h1 class="article-hero-title">{{ article.title }}</h1>
25-
<div class="article-hero-meta">
26-
发布于 {{ formatDate(article.publishedAt) }} · 作者 {{ article.authorNickname || '' }} · 阅读
27-
{{ article.viewCount ?? 0 }}
28-
</div>
29-
30-
<div class="markdown-body article-markdown" v-html="html" />
31-
</div>
32-
</div>
33-
</div>
34-
35-
<CommentSection v-if="article?.id" :article-id="article.id" />
36-
</div>
37-
</div>
4+
<div v-if="loading" class="article-detail-loading">加载中...</div>
5+
6+
<template v-else>
7+
<article v-if="article?.id" class="article-reader">
8+
<router-link class="article-back" to="/all">
9+
<span class="article-back-chev" aria-hidden="true">‹</span>
10+
返回博客
11+
</router-link>
12+
13+
<p class="article-kicker-meta">
14+
<span>{{ formatDate(article.publishedAt) }}</span>
15+
<span class="article-kicker-dot">·</span>
16+
<span>{{ readMinutes }} 分钟</span>
17+
<template v-if="categoryLabel">
18+
<span class="article-kicker-dot">·</span>
19+
<span>{{ categoryLabel }}</span>
20+
</template>
21+
</p>
22+
23+
<h1 class="article-reader-title">{{ article.title }}</h1>
24+
<p v-if="article.summary" class="article-reader-lead">{{ article.summary }}</p>
25+
26+
<figure v-if="article.coverMediaKey" class="article-reader-cover">
27+
<img :src="coverUrl(article.coverMediaKey)" alt="" />
28+
</figure>
29+
30+
<div class="markdown-body article-markdown article-markdown--body" v-html="html" />
31+
</article>
32+
33+
<div v-else class="article-detail-empty">文章不存在</div>
34+
35+
<CommentSection v-if="article?.id" :article-id="article.id" />
36+
</template>
3837
</div>
3938
</div>
4039
</template>
@@ -65,10 +64,23 @@ onMounted(async () => {
6564
const html = computed(() => {
6665
const md = article.value.contentMarkdown || ''
6766
const raw = marked.parse(md)
68-
// MVP:做基础消毒,避免把恶意 HTML 直接注入
6967
return DOMPurify.sanitize(raw)
7068
})
7169
70+
/** 后端若将来返回分类名则展示 */
71+
const categoryLabel = computed(() => {
72+
const a = article.value
73+
return a?.categoryName || a?.categoryLabel || ''
74+
})
75+
76+
const readMinutes = computed(() => estimateReadMinutes(article.value?.contentMarkdown))
77+
78+
function estimateReadMinutes(md) {
79+
if (!md || typeof md !== 'string') return 1
80+
const chars = md.replace(/\s+/g, '').length
81+
return Math.max(1, Math.round(chars / 450))
82+
}
83+
7284
function formatDate(v) {
7385
if (!v) return ''
7486
try {
@@ -78,4 +90,3 @@ function formatDate(v) {
7890
}
7991
}
8092
</script>
81-

0 commit comments

Comments
 (0)