数据结构优化
This commit is contained in:
@@ -6,7 +6,7 @@
|
||||
<p class="text-art-muted max-w-lg mx-auto mb-8">关于前端技术、交互设计以及数字艺术的深度思考。</p>
|
||||
|
||||
<!-- Search UI -->
|
||||
<div class="max-w-md mx-auto relative">
|
||||
<div class="max-w-md mx-auto relative mb-6">
|
||||
<input
|
||||
type="text"
|
||||
v-model="searchQuery"
|
||||
@@ -28,6 +28,41 @@
|
||||
<i data-lucide="search" class="w-5 h-5"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Filter UI -->
|
||||
<div class="max-w-2xl mx-auto flex flex-wrap gap-4 justify-center items-center">
|
||||
<div class="flex items-center gap-2">
|
||||
<label class="text-sm text-art-muted">分类:</label>
|
||||
<div class="w-40">
|
||||
<CustomSelect
|
||||
v-model="selectedCategoryId"
|
||||
:options="categoryOptions"
|
||||
placeholder="全部"
|
||||
@update:modelValue="applyFilters"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-2">
|
||||
<label class="text-sm text-art-muted">标签:</label>
|
||||
<div class="w-40">
|
||||
<CustomSelect
|
||||
v-model="selectedTagId"
|
||||
:options="tagOptions"
|
||||
placeholder="全部"
|
||||
@update:modelValue="applyFilters"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button
|
||||
v-if="hasActiveFilters"
|
||||
@click="clearFilters"
|
||||
class="text-xs text-art-accent hover:text-white transition-colors px-3 py-1 border border-art-accent/30 rounded hover:border-art-accent"
|
||||
>
|
||||
清除筛选
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Loading state -->
|
||||
@@ -83,29 +118,58 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, nextTick } from 'vue'
|
||||
import { fetchPosts, Post } from '../services/api'
|
||||
import { ref, onMounted, nextTick, computed } from 'vue'
|
||||
import { fetchPosts, fetchCategories, fetchTags, Post, Category, Tag } from '../services/api'
|
||||
import { useScrollAnimation } from '../composables/useScrollAnimation'
|
||||
import CustomSelect from '../components/CustomSelect.vue'
|
||||
|
||||
const blogPosts = ref<Post[]>([])
|
||||
const loading = ref(false)
|
||||
const error = ref('')
|
||||
const searchQuery = ref('')
|
||||
const isSearching = ref(false)
|
||||
const selectedCategoryId = ref(0)
|
||||
const selectedTagId = ref(0)
|
||||
const categories = ref<Category[]>([])
|
||||
const tags = ref<Tag[]>([])
|
||||
|
||||
const { initObserver } = useScrollAnimation()
|
||||
|
||||
const hasActiveFilters = computed(() => {
|
||||
return selectedCategoryId.value > 0 || selectedTagId.value > 0
|
||||
})
|
||||
|
||||
// 计算属性:分类选项
|
||||
const categoryOptions = computed(() => {
|
||||
return [
|
||||
{ value: 0, label: '全部' },
|
||||
...categories.value.map(cat => ({ value: cat.id, label: cat.name }))
|
||||
]
|
||||
})
|
||||
|
||||
// 计算属性:标签选项
|
||||
const tagOptions = computed(() => {
|
||||
return [
|
||||
{ value: 0, label: '全部' },
|
||||
...tags.value.map(tag => ({ value: tag.id, label: tag.name }))
|
||||
]
|
||||
})
|
||||
|
||||
const refreshIcons = () => {
|
||||
if ((window as any).lucide) {
|
||||
(window as any).lucide.createIcons()
|
||||
}
|
||||
}
|
||||
|
||||
const fetchBlogPosts = async (query?: string) => {
|
||||
const fetchBlogPosts = async () => {
|
||||
loading.value = true
|
||||
error.value = ''
|
||||
try {
|
||||
const posts = await fetchPosts(query)
|
||||
const query = searchQuery.value.trim() || undefined
|
||||
const categoryId = selectedCategoryId.value > 0 ? selectedCategoryId.value : undefined
|
||||
const tagId = selectedTagId.value > 0 ? selectedTagId.value : undefined
|
||||
|
||||
const posts = await fetchPosts(query, categoryId, tagId)
|
||||
blogPosts.value = posts
|
||||
nextTick(() => {
|
||||
initObserver()
|
||||
@@ -122,7 +186,7 @@ const fetchBlogPosts = async (query?: string) => {
|
||||
const handleSearch = () => {
|
||||
if (!searchQuery.value.trim() && !isSearching.value) return
|
||||
isSearching.value = !!searchQuery.value.trim()
|
||||
fetchBlogPosts(searchQuery.value)
|
||||
fetchBlogPosts()
|
||||
}
|
||||
|
||||
const clearSearch = () => {
|
||||
@@ -131,6 +195,29 @@ const clearSearch = () => {
|
||||
fetchBlogPosts()
|
||||
}
|
||||
|
||||
const applyFilters = () => {
|
||||
fetchBlogPosts()
|
||||
}
|
||||
|
||||
const clearFilters = () => {
|
||||
selectedCategoryId.value = 0
|
||||
selectedTagId.value = 0
|
||||
fetchBlogPosts()
|
||||
}
|
||||
|
||||
const loadFilters = async () => {
|
||||
try {
|
||||
const [cats, tagList] = await Promise.all([
|
||||
fetchCategories(),
|
||||
fetchTags()
|
||||
])
|
||||
categories.value = cats
|
||||
tags.value = tagList
|
||||
} catch (err) {
|
||||
console.error('Error loading filters:', err)
|
||||
}
|
||||
}
|
||||
|
||||
const highlightText = (text: string | undefined) => {
|
||||
if (!text) return ''
|
||||
if (!isSearching.value || !searchQuery.value) return text
|
||||
@@ -140,6 +227,7 @@ const highlightText = (text: string | undefined) => {
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadFilters()
|
||||
fetchBlogPosts()
|
||||
refreshIcons()
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user