feat: 文章标签展示、搜索框清除按钮

- 文章详情页和文章卡片展示标签
- 搜索框右侧添加 × 清除按钮
- 标签点击跳转文章列表页自动搜索
- 修复 hasAnyFilter 未包含 tags 的 bug

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-31 22:43:59 +08:00
parent b95d06cc2d
commit 169e22b4cb
4 changed files with 59 additions and 2 deletions
+19
View File
@@ -164,6 +164,25 @@ body {
opacity: 0.9;
}
/* 标签 */
.tag-pill {
display: inline-flex;
align-items: center;
padding: 0.125rem 0.625rem;
border-radius: 9999px;
font-size: 0.75rem;
font-weight: 500;
background-color: var(--color-bg-hover);
color: var(--color-text-secondary);
transition: background-color 0.15s ease, color 0.15s ease;
text-decoration: none;
}
.tag-pill:hover {
background-color: var(--color-primary);
color: white;
}
/* 滚动条 */
::-webkit-scrollbar {
width: 6px;
+10
View File
@@ -86,6 +86,16 @@ function renderDesc(): string {
v-html="renderDesc()"
/>
<div v-if="post.tags?.length" class="mt-2 flex flex-wrap gap-1.5">
<span
v-for="tag in post.tags"
:key="tag"
class="tag-pill"
>
{{ tag }}
</span>
</div>
<div v-if="matchedLines && matchedLines.length > 0" class="mt-3 space-y-1.5">
<div
v-for="(line, idx) in (expanded ? matchedLines : matchedLines.slice(0, 3))"
+12
View File
@@ -1,6 +1,7 @@
<script setup lang="ts">
import { siteConfig } from '~/config/site'
const router = useRouter()
const route = useRoute()
const slug = route.params.slug as string
@@ -52,6 +53,17 @@ function formatDate(date: string) {
{{ post.description }}
</p>
<div v-if="post.tags?.length" class="mt-4 flex flex-wrap gap-2">
<span
v-for="tag in post.tags"
:key="tag"
class="tag-pill cursor-pointer"
@click="router.push(`/posts?tag=${encodeURIComponent(tag)}`)"
>
{{ tag }}
</span>
</div>
<div v-if="post.image" class="mt-6">
<img
:src="post.image"
+18 -2
View File
@@ -6,7 +6,15 @@ useSeoMeta({
description: `${siteConfig.siteName} 的所有文章`,
})
const route = useRoute()
const searchQuery = ref('')
watch(() => route.query.tag, (tag) => {
if (typeof tag === 'string' && tag) {
searchQuery.value = tag
currentPage.value = 1
}
}, { immediate: true })
const currentPage = ref(1)
const postsPerPage = 10
@@ -46,7 +54,7 @@ const { data: allPosts } = await useAsyncData('posts-list', () =>
const { searchResults, filteredPosts, highlight } = usePostSearch(allPosts, searchQuery, searchFilters)
const hasAnyFilter = computed(() => searchFilters.value.title || searchFilters.value.description || searchFilters.value.content)
const hasAnyFilter = computed(() => searchFilters.value.title || searchFilters.value.description || searchFilters.value.content || searchFilters.value.tags)
const totalPages = computed(() => Math.ceil(searchResults.value.length / postsPerPage))
@@ -83,10 +91,18 @@ watch(() => searchFilters.value, () => {
v-model="searchQuery"
type="text"
placeholder="搜索文章标题、描述或内容..."
class="w-full h-10 rounded-full border bg-transparent pl-10 pr-4 text-sm outline-none transition-all focus:ring-2"
class="w-full h-10 rounded-full border bg-transparent pl-10 pr-10 text-sm outline-none transition-all focus:ring-2"
style="border-color: var(--color-border); --tw-ring-color: var(--color-border);"
@input="currentPage = 1"
/>
<button
v-if="searchQuery"
type="button"
class="absolute right-3 top-1/2 -translate-y-1/2 w-5 h-5 flex items-center justify-center rounded-full text-muted-foreground hover:bg-muted hover:text-foreground transition-colors cursor-pointer"
@click="searchQuery = ''; currentPage = 1"
>
&times;
</button>
</div>
<div class="mt-3 flex flex-wrap gap-4">