mirror of
https://github.com/KuekHaoYang/KVideo.git
synced 2026-08-12 23:33:43 +08:00
feat: add homepage category shortcuts
This commit is contained in:
@@ -0,0 +1,100 @@
|
||||
'use client';
|
||||
|
||||
import { Icons } from '@/components/ui/Icon';
|
||||
|
||||
interface QuickSearchItem {
|
||||
label: string;
|
||||
query: string;
|
||||
}
|
||||
|
||||
interface QuickSearchGroup {
|
||||
title: string;
|
||||
icon: keyof typeof Icons;
|
||||
items: QuickSearchItem[];
|
||||
}
|
||||
|
||||
interface CategoryQuickSearchProps {
|
||||
onSearch?: (query: string) => void;
|
||||
}
|
||||
|
||||
const currentYear = new Date().getFullYear();
|
||||
|
||||
const quickSearchGroups: QuickSearchGroup[] = [
|
||||
{
|
||||
title: '内容分类',
|
||||
icon: 'Film',
|
||||
items: [
|
||||
{ label: '电影', query: '电影' },
|
||||
{ label: '电视剧', query: '电视剧' },
|
||||
{ label: '动漫', query: '动漫' },
|
||||
{ label: '综艺', query: '综艺' },
|
||||
{ label: '纪录片', query: '纪录片' },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '电影类型',
|
||||
icon: 'Tag',
|
||||
items: [
|
||||
{ label: '动作片', query: '动作片' },
|
||||
{ label: '喜剧片', query: '喜剧片' },
|
||||
{ label: '爱情片', query: '爱情片' },
|
||||
{ label: '科幻片', query: '科幻片' },
|
||||
{ label: '奇幻片', query: '奇幻片' },
|
||||
{ label: '恐怖片', query: '恐怖片' },
|
||||
{ label: '剧情片', query: '剧情片' },
|
||||
{ label: '战争片', query: '战争片' },
|
||||
{ label: '动画片', query: '动画片' },
|
||||
{ label: '悬疑片', query: '悬疑片' },
|
||||
{ label: '冒险片', query: '冒险片' },
|
||||
{ label: '犯罪片', query: '犯罪片' },
|
||||
{ label: '灾难片', query: '灾难片' },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '年份',
|
||||
icon: 'Calendar',
|
||||
items: Array.from({ length: 8 }, (_, index) => {
|
||||
const year = String(currentYear - index);
|
||||
return { label: year, query: year };
|
||||
}),
|
||||
},
|
||||
];
|
||||
|
||||
export function CategoryQuickSearch({ onSearch }: CategoryQuickSearchProps) {
|
||||
if (!onSearch) return null;
|
||||
|
||||
const handleSearch = (query: string) => {
|
||||
onSearch(query);
|
||||
};
|
||||
|
||||
return (
|
||||
<section className="mb-10 space-y-5" aria-label="分类快捷搜索">
|
||||
{quickSearchGroups.map((group) => {
|
||||
const GroupIcon = Icons[group.icon];
|
||||
|
||||
return (
|
||||
<div key={group.title} className="space-y-3">
|
||||
<div className="flex items-center gap-2 text-sm font-semibold text-[var(--text-color)]">
|
||||
<GroupIcon size={16} className="text-[var(--accent-color)]" />
|
||||
<span>{group.title}</span>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-2 overflow-x-auto pb-1 scrollbar-hide md:flex-wrap md:overflow-visible">
|
||||
{group.items.map((item) => (
|
||||
<button
|
||||
key={`${group.title}-${item.label}`}
|
||||
type="button"
|
||||
onClick={() => handleSearch(item.query)}
|
||||
className="inline-flex h-10 shrink-0 items-center justify-center rounded-full border border-[var(--glass-border)] bg-[var(--glass-bg)] px-4 text-sm font-semibold text-[var(--text-color)] transition-all duration-200 hover:scale-105 hover:border-[var(--accent-color)] hover:text-[var(--accent-color)] active:scale-95"
|
||||
aria-label={`搜索${item.label}`}
|
||||
>
|
||||
{item.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -6,13 +6,22 @@
|
||||
|
||||
'use client';
|
||||
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useState } from 'react';
|
||||
import { TagManager } from './TagManager';
|
||||
import { MovieGrid } from './MovieGrid';
|
||||
import { CategoryQuickSearch } from './CategoryQuickSearch';
|
||||
import { useTagManager } from './hooks/useTagManager';
|
||||
import { usePopularMovies } from './hooks/usePopularMovies';
|
||||
import { usePersonalizedRecommendations } from './hooks/usePersonalizedRecommendations';
|
||||
|
||||
interface DoubanMovie {
|
||||
id: string;
|
||||
title: string;
|
||||
cover: string;
|
||||
rate: string;
|
||||
url: string;
|
||||
}
|
||||
|
||||
interface PopularFeaturesProps {
|
||||
onSearch?: (query: string) => void;
|
||||
}
|
||||
@@ -46,19 +55,9 @@ export function PopularFeatures({ onSearch }: PopularFeaturesProps) {
|
||||
loadMoreRef: recommendLoadMoreRef,
|
||||
} = usePersonalizedRecommendations(false);
|
||||
|
||||
// Track whether the recommendation tab is active
|
||||
const [isRecommendSelected, setIsRecommendSelected] = useState(hasHistory);
|
||||
const [hasSelectedRegularTag, setHasSelectedRegularTag] = useState(false);
|
||||
|
||||
// Sync selection when hasHistory changes after Zustand hydration from localStorage.
|
||||
// On first render the store is empty (hasHistory=false), so useState captures false.
|
||||
// Once hydration completes and hasHistory becomes true, auto-select the recommendation tab.
|
||||
useEffect(() => {
|
||||
if (hasHistory) {
|
||||
setIsRecommendSelected(true);
|
||||
}
|
||||
}, [hasHistory]);
|
||||
|
||||
const effectiveRecommendSelected = hasHistory && isRecommendSelected;
|
||||
const effectiveRecommendSelected = hasHistory && !hasSelectedRegularTag;
|
||||
|
||||
const {
|
||||
movies,
|
||||
@@ -72,14 +71,14 @@ export function PopularFeatures({ onSearch }: PopularFeaturesProps) {
|
||||
contentType
|
||||
);
|
||||
|
||||
const handleMovieClick = (movie: any) => {
|
||||
const handleMovieClick = (movie: DoubanMovie) => {
|
||||
if (onSearch) {
|
||||
onSearch(movie.title);
|
||||
}
|
||||
};
|
||||
|
||||
const handleRecommendSelect = () => {
|
||||
setIsRecommendSelected(true);
|
||||
setHasSelectedRegularTag(false);
|
||||
};
|
||||
|
||||
const handleRegularTagSelect = (tagId: string) => {
|
||||
@@ -87,12 +86,14 @@ export function PopularFeatures({ onSearch }: PopularFeaturesProps) {
|
||||
window.location.href = '/premium';
|
||||
return;
|
||||
}
|
||||
setIsRecommendSelected(false);
|
||||
setHasSelectedRegularTag(true);
|
||||
setSelectedTag(tagId);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="animate-fade-in">
|
||||
<CategoryQuickSearch onSearch={onSearch} />
|
||||
|
||||
{/* Content Type Toggle (Capsule Liquid Glass - Fixed & Centered) */}
|
||||
{!effectiveRecommendSelected && (
|
||||
<div className="mb-10 flex justify-center">
|
||||
|
||||
Reference in New Issue
Block a user