初始化
This commit is contained in:
@@ -3,7 +3,31 @@
|
||||
<div class="max-w-4xl mx-auto pt-32 px-6 pb-20">
|
||||
<div class="mb-16 text-center">
|
||||
<h2 class="font-serif text-5xl italic text-white mb-6">深度思考</h2>
|
||||
<p class="text-art-muted max-w-lg mx-auto">关于前端技术、交互设计以及数字艺术的深度思考。</p>
|
||||
<p class="text-art-muted max-w-lg mx-auto mb-8">关于前端技术、交互设计以及数字艺术的深度思考。</p>
|
||||
|
||||
<!-- Search UI -->
|
||||
<div class="max-w-md mx-auto relative">
|
||||
<input
|
||||
type="text"
|
||||
v-model="searchQuery"
|
||||
@keyup.enter="handleSearch"
|
||||
placeholder="搜索文章标题或内容..."
|
||||
class="w-full bg-white/5 border border-white/10 rounded-full px-6 py-3 text-white placeholder-white/30 focus:outline-none focus:border-art-accent transition-colors"
|
||||
/>
|
||||
<button
|
||||
v-if="searchQuery"
|
||||
@click="clearSearch"
|
||||
class="absolute right-12 top-1/2 -translate-y-1/2 text-white/30 hover:text-white transition-colors"
|
||||
>
|
||||
<i data-lucide="x" class="w-4 h-4"></i>
|
||||
</button>
|
||||
<button
|
||||
@click="handleSearch"
|
||||
class="absolute right-4 top-1/2 -translate-y-1/2 text-white/50 hover:text-art-accent transition-colors"
|
||||
>
|
||||
<i data-lucide="search" class="w-5 h-5"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Loading state -->
|
||||
@@ -19,6 +43,14 @@
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Empty state -->
|
||||
<div v-else-if="blogPosts.length === 0" class="text-center py-20 text-art-muted">
|
||||
<p>没有找到相关文章</p>
|
||||
<button v-if="isSearching" @click="clearSearch" class="mt-4 text-art-accent hover:underline">
|
||||
清除搜索条件
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Blog posts list -->
|
||||
<div v-else class="space-y-12" id="blog-list-container">
|
||||
<!-- Blog posts will be rendered here -->
|
||||
@@ -34,11 +66,9 @@
|
||||
<span class="text-sm text-art-muted">{{ post.date }}</span>
|
||||
</div>
|
||||
<div class="md:w-3/4 space-y-4">
|
||||
<h3 class="font-serif text-3xl text-white group-hover:text-art-accent transition-colors leading-tight">
|
||||
{{ post.title }}
|
||||
<h3 class="font-serif text-3xl text-white group-hover:text-art-accent transition-colors leading-tight" v-html="highlightText(post.title)">
|
||||
</h3>
|
||||
<p class="text-art-muted font-light leading-relaxed">
|
||||
{{ post.excerpt }}
|
||||
<p class="text-art-muted font-light leading-relaxed" v-html="highlightText(post.excerpt)">
|
||||
</p>
|
||||
<div class="text-xs text-art-accent font-medium mt-4 group-hover:text-white transition-colors">阅读全文 -></div>
|
||||
</div>
|
||||
@@ -50,24 +80,34 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, onBeforeMount } from 'vue'
|
||||
import { ref, onMounted, nextTick } from 'vue'
|
||||
import { fetchPosts, Post } from '../services/api'
|
||||
import { useScrollAnimation } from '../composables/useScrollAnimation'
|
||||
|
||||
const blogPosts = ref<Post[]>([])
|
||||
const loading = ref(false)
|
||||
const error = ref('')
|
||||
const searchQuery = ref('')
|
||||
const isSearching = ref(false)
|
||||
|
||||
const { initObserver } = useScrollAnimation()
|
||||
|
||||
const fetchBlogPosts = async () => {
|
||||
const refreshIcons = () => {
|
||||
if ((window as any).lucide) {
|
||||
(window as any).lucide.createIcons()
|
||||
}
|
||||
}
|
||||
|
||||
const fetchBlogPosts = async (query?: string) => {
|
||||
loading.value = true
|
||||
error.value = ''
|
||||
try {
|
||||
const posts = await fetchPosts()
|
||||
const posts = await fetchPosts(query)
|
||||
blogPosts.value = posts
|
||||
// 初始化动画 - 在数据加载完成后
|
||||
initObserver()
|
||||
nextTick(() => {
|
||||
initObserver()
|
||||
refreshIcons()
|
||||
})
|
||||
} catch (err) {
|
||||
console.error('Error fetching blog posts:', err)
|
||||
error.value = '获取博客文章失败,请稍后重试'
|
||||
@@ -76,11 +116,28 @@ const fetchBlogPosts = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
const handleSearch = () => {
|
||||
if (!searchQuery.value.trim() && !isSearching.value) return
|
||||
isSearching.value = !!searchQuery.value.trim()
|
||||
fetchBlogPosts(searchQuery.value)
|
||||
}
|
||||
|
||||
const clearSearch = () => {
|
||||
searchQuery.value = ''
|
||||
isSearching.value = false
|
||||
fetchBlogPosts()
|
||||
}
|
||||
|
||||
const highlightText = (text: string | undefined) => {
|
||||
if (!text) return ''
|
||||
if (!isSearching.value || !searchQuery.value) return text
|
||||
|
||||
const regex = new RegExp(`(${searchQuery.value})`, 'gi')
|
||||
return text.replace(regex, '<span class="text-art-accent bg-art-accent/10 font-bold">$1</span>')
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
fetchBlogPosts()
|
||||
// 初始化Lucide图标
|
||||
if (window.lucide) {
|
||||
window.lucide.createIcons()
|
||||
}
|
||||
refreshIcons()
|
||||
})
|
||||
</script>
|
||||
@@ -125,7 +125,7 @@ const activeId = ref<string>('')
|
||||
const observer = ref<IntersectionObserver | null>(null)
|
||||
|
||||
const post = ref<Post>({
|
||||
id: postId,
|
||||
id: Number(postId) || 0,
|
||||
title: '',
|
||||
category: '',
|
||||
date: '',
|
||||
|
||||
@@ -69,7 +69,7 @@ const fetchPosts = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
const deletePost = async (id: string) => {
|
||||
const deletePost = async (id: number) => {
|
||||
if (confirm('确定要删除这篇文章吗?')) {
|
||||
try {
|
||||
await deletePostApi(id)
|
||||
|
||||
@@ -56,7 +56,7 @@ export interface Work {
|
||||
|
||||
// 文章相关类型
|
||||
export interface Post {
|
||||
id: string
|
||||
id: number
|
||||
title: string
|
||||
category: string
|
||||
date: string
|
||||
@@ -67,7 +67,7 @@ export interface Post {
|
||||
|
||||
export interface PostHistory {
|
||||
id: number
|
||||
postId: string
|
||||
postId: number
|
||||
version: number
|
||||
title: string
|
||||
category: string
|
||||
@@ -425,9 +425,13 @@ export const getAdminPosts = async (): Promise<Post[]> => {
|
||||
}
|
||||
}
|
||||
|
||||
export const fetchPosts = async (): Promise<Post[]> => {
|
||||
export const fetchPosts = async (query?: string): Promise<Post[]> => {
|
||||
try {
|
||||
const response = await fetch(`${API_BASE}/posts`)
|
||||
let url = `${API_BASE}/posts`
|
||||
if (query) {
|
||||
url += `?q=${encodeURIComponent(query)}`
|
||||
}
|
||||
const response = await fetch(url)
|
||||
if (!response.ok) throw new Error('Failed to fetch posts')
|
||||
return await response.json()
|
||||
} catch (error) {
|
||||
@@ -436,7 +440,7 @@ export const fetchPosts = async (): Promise<Post[]> => {
|
||||
}
|
||||
}
|
||||
|
||||
export const fetchPost = async (id: string): Promise<Post> => {
|
||||
export const fetchPost = async (id: number | string): Promise<Post> => {
|
||||
try {
|
||||
const response = await fetch(`${API_BASE}/posts/${id}`)
|
||||
if (!response.ok) throw new Error('Failed to fetch post')
|
||||
@@ -444,7 +448,7 @@ export const fetchPost = async (id: string): Promise<Post> => {
|
||||
} catch (error) {
|
||||
console.error(`Error fetching post ${id}:`, error)
|
||||
return {
|
||||
id: id,
|
||||
id: Number(id),
|
||||
title: '默认文章',
|
||||
category: '默认分类',
|
||||
date: '2024-01-01'
|
||||
@@ -469,7 +473,7 @@ export const createPost = async (postData: Omit<Post, 'id'>): Promise<void> => {
|
||||
}
|
||||
}
|
||||
|
||||
export const updatePost = async (id: string, postData: Omit<Post, 'id'>): Promise<void> => {
|
||||
export const updatePost = async (id: number | string, postData: Omit<Post, 'id'>): Promise<void> => {
|
||||
try {
|
||||
const response = await fetch(`${API_BASE}/admin/posts/${id}`, {
|
||||
method: 'PUT',
|
||||
@@ -486,7 +490,7 @@ export const updatePost = async (id: string, postData: Omit<Post, 'id'>): Promis
|
||||
}
|
||||
}
|
||||
|
||||
export const deletePost = async (id: string): Promise<void> => {
|
||||
export const deletePost = async (id: number | string): Promise<void> => {
|
||||
try {
|
||||
const response = await fetch(`${API_BASE}/admin/posts/${id}`, {
|
||||
method: 'DELETE',
|
||||
@@ -503,7 +507,7 @@ export const deletePost = async (id: string): Promise<void> => {
|
||||
}
|
||||
|
||||
// 文章历史记录API
|
||||
export const getPostHistory = async (postId: string): Promise<PostHistory[]> => {
|
||||
export const getPostHistory = async (postId: number | string): Promise<PostHistory[]> => {
|
||||
try {
|
||||
const response = await fetch(`${API_BASE}/admin/posts/${postId}/history`, {
|
||||
headers: getAuthHeaders()
|
||||
@@ -519,7 +523,7 @@ export const getPostHistory = async (postId: string): Promise<PostHistory[]> =>
|
||||
}
|
||||
}
|
||||
|
||||
export const getPostHistoryByVersion = async (postId: string, version: number): Promise<PostHistory> => {
|
||||
export const getPostHistoryByVersion = async (postId: number | string, version: number): Promise<PostHistory> => {
|
||||
try {
|
||||
const response = await fetch(`${API_BASE}/admin/posts/${postId}/history/${version}`, {
|
||||
headers: getAuthHeaders()
|
||||
|
||||
Reference in New Issue
Block a user