diff --git a/app/searchResults.tsx b/app/searchResults.tsx
index bf42bfb..262294f 100644
--- a/app/searchResults.tsx
+++ b/app/searchResults.tsx
@@ -3,7 +3,7 @@ import {
View,
StyleSheet,
StatusBar as RNStatusBar,
- RefreshControl,
+ ScrollView,
} from 'react-native'
import { StatusBar } from 'expo-status-bar'
import { SafeAreaView } from 'react-native-safe-area-context'
@@ -11,6 +11,10 @@ import { useRouter, useLocalSearchParams } from 'expo-router'
import SearchResultsGrid from '@/components/SearchResultsGrid'
import SearchBar from '@/components/SearchBar'
+import RefreshControl from '@/components/RefreshControl'
+import LoadingState from '@/components/LoadingState'
+import ErrorState from '@/components/ErrorState'
+import PaginationLoader from '@/components/PaginationLoader'
import { useTemplates, useSearchHistory } from '@/hooks'
export default function SearchResultsScreen() {
@@ -19,27 +23,20 @@ export default function SearchResultsScreen() {
const [searchText, setSearchText] = useState((params.q as string) || '')
const [refreshing, setRefreshing] = useState(false)
- // 使用 useTemplates hook 进行搜索
- const { templates, loading, error, execute, refetch } = useTemplates()
-
- // 使用搜索历史 hook
+ const { templates, loading, loadingMore, error, execute, refetch, loadMore, hasMore } = useTemplates()
const { addToHistory } = useSearchHistory()
- // 当搜索关键词变化时,执行搜索
useEffect(() => {
if (params.q && typeof params.q === 'string') {
const query = params.q.trim()
setSearchText(query)
if (query) {
- // 执行搜索
- execute({ search: query, limit: 50, sortBy: 'createdAt', sortOrder: 'desc', page: 1 })
- // 添加到搜索历史
+ execute({ search: query, limit: 20, sortBy: 'createdAt', sortOrder: 'desc', page: 1 })
addToHistory(query)
}
}
}, [params.q, execute, addToHistory])
- // 处理下拉刷新
const handleRefresh = async () => {
setRefreshing(true)
if (searchText) {
@@ -48,7 +45,12 @@ export default function SearchResultsScreen() {
setRefreshing(false)
}
- // 将模板数据转换为搜索结果格式
+ const handleLoadMore = () => {
+ if (hasMore && !loadingMore) {
+ loadMore()
+ }
+ }
+
const searchResults = templates.map(template => ({
id: template.id,
title: template.title || template.titleEn || '',
@@ -58,6 +60,76 @@ export default function SearchResultsScreen() {
aspectRatio: template.aspectRatio ? parseFloat(template.aspectRatio as string) : undefined,
}))
+ if (loading && !refreshing) {
+ return (
+
+
+
+ {
+ router.push({
+ pathname: '/searchTemplate',
+ params: { q: text, focus: 'true' },
+ })
+ }}
+ onBack={() => router.back()}
+ readOnly={true}
+ onInputPress={() => {
+ router.push({
+ pathname: '/searchTemplate',
+ params: { q: searchText, focus: 'true' },
+ })
+ }}
+ onClearPress={() => {
+ router.push({
+ pathname: '/searchTemplate',
+ params: { q: '', focus: 'true' },
+ })
+ }}
+ marginBottom={12}
+ />
+
+
+ )
+ }
+
+ if (error) {
+ return (
+
+
+
+ {
+ router.push({
+ pathname: '/searchTemplate',
+ params: { q: text, focus: 'true' },
+ })
+ }}
+ onBack={() => router.back()}
+ readOnly={true}
+ onInputPress={() => {
+ router.push({
+ pathname: '/searchTemplate',
+ params: { q: searchText, focus: 'true' },
+ })
+ }}
+ onClearPress={() => {
+ router.push({
+ pathname: '/searchTemplate',
+ params: { q: '', focus: 'true' },
+ })
+ }}
+ marginBottom={12}
+ />
+
+
+ )
+ }
+
return (
@@ -91,7 +163,10 @@ export default function SearchResultsScreen() {
}
+ onEndReached={handleLoadMore}
+ ListFooterComponent={loadingMore ? : null}
/>
)
diff --git a/app/searchWorksResults.tsx b/app/searchWorksResults.tsx
index 05f4bba..db5e350 100644
--- a/app/searchWorksResults.tsx
+++ b/app/searchWorksResults.tsx
@@ -11,7 +11,7 @@ import { useTranslation } from 'react-i18next'
import SearchBar from '@/components/SearchBar'
import WorksGallery, { type Category, type WorkItem } from '@/components/WorksGallery'
-// 模拟搜索结果数据
+// TODO: Replace with actual API call when backend supports works search
const mockSearchResults: WorkItem[] = [
{ id: 1, date: new Date(2025, 10, 28), duration: '00:05', category: '写真' },
{ id: 2, date: new Date(2025, 10, 28), duration: '00:05', category: '写真' },
diff --git a/components/SearchResultsGrid.tsx b/components/SearchResultsGrid.tsx
index 078e57a..0ada988 100644
--- a/components/SearchResultsGrid.tsx
+++ b/components/SearchResultsGrid.tsx
@@ -30,6 +30,9 @@ interface TemplateSearchResultItem {
interface SearchResultsGridProps {
results: TemplateSearchResultItem[]
loading?: boolean
+ refreshControl?: React.ReactElement
+ onEndReached?: () => void
+ ListFooterComponent?: React.ReactElement | null
}
// 计算卡片高度的辅助函数
@@ -41,7 +44,7 @@ const calculateCardHeight = (width: number, aspectRatio?: number): number => {
return width * 1.2
}
-export default function SearchResultsGrid({ results, loading }: SearchResultsGridProps) {
+export default function SearchResultsGrid({ results, loading, refreshControl, onEndReached, ListFooterComponent }: SearchResultsGridProps) {
const { t } = useTranslation()
const router = useRouter()
const [gridWidth, setGridWidth] = useState(screenWidth)
@@ -57,6 +60,14 @@ export default function SearchResultsGrid({ results, loading }: SearchResultsGri
})
}
+ const handleScroll = (event: any) => {
+ const { layoutMeasurement, contentOffset, contentSize } = event.nativeEvent
+ const paddingToBottom = 20
+ if (layoutMeasurement.height + contentOffset.y >= contentSize.height - paddingToBottom) {
+ onEndReached?.()
+ }
+ }
+
if (loading) {
return (
@@ -77,6 +88,9 @@ export default function SearchResultsGrid({ results, loading }: SearchResultsGri
+ {ListFooterComponent}
)
}
diff --git a/hooks/index.ts b/hooks/index.ts
index cae00b8..edf15d6 100644
--- a/hooks/index.ts
+++ b/hooks/index.ts
@@ -6,3 +6,4 @@ export { useTemplateDetail, type TemplateDetail as TemplateDetailType } from './
export { useTemplateGenerations, type TemplateGeneration } from './use-template-generations'
export { useSearchHistory } from './use-search-history'
export { useTags } from './use-tags'
+export { useDebounce } from './use-debounce'
diff --git a/hooks/use-debounce.ts b/hooks/use-debounce.ts
new file mode 100644
index 0000000..7fdb859
--- /dev/null
+++ b/hooks/use-debounce.ts
@@ -0,0 +1,17 @@
+import { useEffect, useState } from 'react'
+
+export function useDebounce(value: T, delay: number = 500): T {
+ const [debouncedValue, setDebouncedValue] = useState(value)
+
+ useEffect(() => {
+ const handler = setTimeout(() => {
+ setDebouncedValue(value)
+ }, delay)
+
+ return () => {
+ clearTimeout(handler)
+ }
+ }, [value, delay])
+
+ return debouncedValue
+}