feat: 文章标签展示、搜索框清除按钮
- 文章详情页和文章卡片展示标签 - 搜索框右侧添加 × 清除按钮 - 标签点击跳转文章列表页自动搜索 - 修复 hasAnyFilter 未包含 tags 的 bug Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -164,6 +164,25 @@ body {
|
|||||||
opacity: 0.9;
|
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 {
|
::-webkit-scrollbar {
|
||||||
width: 6px;
|
width: 6px;
|
||||||
|
|||||||
@@ -86,6 +86,16 @@ function renderDesc(): string {
|
|||||||
v-html="renderDesc()"
|
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-if="matchedLines && matchedLines.length > 0" class="mt-3 space-y-1.5">
|
||||||
<div
|
<div
|
||||||
v-for="(line, idx) in (expanded ? matchedLines : matchedLines.slice(0, 3))"
|
v-for="(line, idx) in (expanded ? matchedLines : matchedLines.slice(0, 3))"
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { siteConfig } from '~/config/site'
|
import { siteConfig } from '~/config/site'
|
||||||
|
|
||||||
|
const router = useRouter()
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const slug = route.params.slug as string
|
const slug = route.params.slug as string
|
||||||
|
|
||||||
@@ -52,6 +53,17 @@ function formatDate(date: string) {
|
|||||||
{{ post.description }}
|
{{ post.description }}
|
||||||
</p>
|
</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">
|
<div v-if="post.image" class="mt-6">
|
||||||
<img
|
<img
|
||||||
:src="post.image"
|
:src="post.image"
|
||||||
|
|||||||
+18
-2
@@ -6,7 +6,15 @@ useSeoMeta({
|
|||||||
description: `${siteConfig.siteName} 的所有文章`,
|
description: `${siteConfig.siteName} 的所有文章`,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const route = useRoute()
|
||||||
const searchQuery = ref('')
|
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 currentPage = ref(1)
|
||||||
const postsPerPage = 10
|
const postsPerPage = 10
|
||||||
|
|
||||||
@@ -46,7 +54,7 @@ const { data: allPosts } = await useAsyncData('posts-list', () =>
|
|||||||
|
|
||||||
const { searchResults, filteredPosts, highlight } = usePostSearch(allPosts, searchQuery, searchFilters)
|
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))
|
const totalPages = computed(() => Math.ceil(searchResults.value.length / postsPerPage))
|
||||||
|
|
||||||
@@ -83,10 +91,18 @@ watch(() => searchFilters.value, () => {
|
|||||||
v-model="searchQuery"
|
v-model="searchQuery"
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="搜索文章标题、描述或内容..."
|
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);"
|
style="border-color: var(--color-border); --tw-ring-color: var(--color-border);"
|
||||||
@input="currentPage = 1"
|
@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"
|
||||||
|
>
|
||||||
|
×
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mt-3 flex flex-wrap gap-4">
|
<div class="mt-3 flex flex-wrap gap-4">
|
||||||
|
|||||||
Reference in New Issue
Block a user