"use client"; import { ArrowLeft, MessageSquare, Search, X } from "lucide-react"; import Image from "next/image"; import Link from "next/link"; import { useRouter, useSearchParams } from "next/navigation"; import { type FormEvent, useCallback, useEffect, useState } from "react"; import { VIDEO_CONTENT_TAGS, isVideoContentTag } from "@/lib/video-tags"; // 匹配搜索结果类型(适配后端API) type SearchResultItem = { id: string; awemeId: string; type: "video" | "image"; rank: number; snippet: string; // 后端返回的高亮片段,已包含标签 contentTags: string[]; video?: { aweme_id: string; desc: string; cover_url: string | null; video_url: string | null; duration_ms: number; author: { sec_uid: string; nickname: string; avatar_url: string | null; }; }; imagePost?: { aweme_id: string; desc: string; cover_url: string | null; author: { sec_uid: string; nickname: string; avatar_url: string | null; }; }; }; export default function SearchClient({ initialQuery, }: { initialQuery: string; }) { const searchParams = useSearchParams(); const router = useRouter(); const [query, setQuery] = useState(initialQuery); const [results, setResults] = useState([]); const [loading, setLoading] = useState(false); const [searched, setSearched] = useState(false); const [selectedTag, setSelectedTag] = useState(null); const buildSearchHref = useCallback((q: string, tag: string | null) => { const params = new URLSearchParams(); const trimmed = q.trim(); if (trimmed) params.set("q", trimmed); if (tag) params.set("tag", tag); const queryString = params.toString(); return queryString ? `/search?${queryString}` : "/search"; }, []); const performSearch = useCallback(async (q: string, tag: string | null) => { const trimmed = q.trim(); if (!trimmed && !tag) { setResults([]); setSearched(false); return; } setLoading(true); setSearched(true); try { const params = new URLSearchParams({ limit: "60" }); if (trimmed) params.set("q", trimmed); if (tag) params.set("tag", tag); const res = await fetch(`/api/search?${params.toString()}`); if (!res.ok) throw new Error("Search failed"); const data = await res.json(); setResults(data.results || []); } catch (err) { console.error("Search error:", err); setResults([]); } finally { setLoading(false); } }, []); // 初始 & URL 变化时同步(用 searchParams 订阅 URL 更稳妥) useEffect(() => { const q = searchParams.get("q") || ""; const tagParam = searchParams.get("tag") || ""; const tag = tagParam && isVideoContentTag(tagParam) ? tagParam : null; setQuery(q); setSelectedTag(tag); if (q || tag) { performSearch(q, tag); } else { setResults([]); setSearched(false); } }, [searchParams, performSearch]); const handleSearch = (e: FormEvent) => { e.preventDefault(); const trimmed = query.trim(); if (!trimmed && !selectedTag) return; router.push(buildSearchHref(trimmed, selectedTag)); }; const handleTagSelect = (tag: string) => { const nextTag = selectedTag === tag ? null : tag; setSelectedTag(nextTag); router.push(buildSearchHref(query, nextTag)); }; const handleClear = () => { setQuery(""); setSelectedTag(null); setResults([]); setSearched(false); router.push("/search"); }; const renderSnippet = (snippet: string) => { let highlighted = false; let offset = 0; return snippet.split(/(|<\/mark>)/g).map((part) => { offset += part.length; if (part === "") { highlighted = true; return null; } if (part === "") { highlighted = false; return null; } if (!part) return null; return highlighted ? {part} : part; }); }; return (
{/* 顶部搜索栏 */}
setQuery(e.target.value)} placeholder="搜索视频、图文描述或语音内容..." className="w-full bg-white/5 border border-white/10 rounded-full px-5 py-3 pl-12 pr-12 text-white placeholder-white/40 focus:outline-none focus:ring-2 focus:ring-blue-500/50 focus:border-blue-500/50 transition-all" /> {(query || selectedTag) && ( )}
{VIDEO_CONTENT_TAGS.map((tag) => ( ))}
{/* 结果区域 */}
{loading && (

正在搜索...

)} {!loading && searched && results.length === 0 && (

未找到相关内容

试试其他关键词

)} {!loading && !searched && !query && !selectedTag && (

输入关键词开始搜索

支持搜索视频描述、图文描述及语音转写内容

)} {!loading && results.length > 0 && (

找到{" "} {results.length} {" "} 个结果

{/* 单列列表布局 */}
{results.map((item) => { const content = item.type === "video" ? item.video : item.imagePost; if (!content) return null; return (
{/* 主内容区 */}
{/* 左侧:封面预览 */} {content.cover_url ? ( {content.desc} ) : (
)} {/* 右侧:信息区 */}
{/* 类型标签 */}
{item.type === "video" ? "视频" : "图文"} 匹配度: {(item.rank * 100).toFixed(1)}% {item.contentTags.map((tag) => ( {tag} ))}
{/* 描述 */}

{content.desc || "无描述"}

{/* 作者信息 */}
{content.author.avatar_url ? ( {content.author.nickname} ) : (
)} {content.author.nickname}
{/* 匹配详情:展示后端返回的snippet(已包含标签) */}

匹配内容

{renderSnippet(item.snippet)}
); })}
)}
); }