数据结构优化

This commit is contained in:
李琦
2026-01-19 16:14:08 +08:00
parent 6d789c7c2a
commit 68c4c6df1c
36 changed files with 4063 additions and 760 deletions

View File

@@ -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()
})