139 lines
4.0 KiB
TypeScript
139 lines
4.0 KiB
TypeScript
"use client";
|
|
|
|
import useEmblaCarousel from "embla-carousel-react";
|
|
import { useCallback, useEffect, useState } from "react";
|
|
import { useHomeData } from "../../context/HomeDataContext";
|
|
import { AudioPlayerProvider } from "../../context/AudioPlayerContext";
|
|
import StartChatItem from "./StartChatItem";
|
|
import StartChatSkeleton from "./StartChatSkeleton";
|
|
import { IconButton } from "@/components/ui/button";
|
|
|
|
const StartChat = () => {
|
|
// 从 Context 获取数据
|
|
const { data: homeData, isLoading } = useHomeData();
|
|
|
|
// 只取前20条数据
|
|
const displayData = homeData?.starAChat?.slice(0, 20) || [];
|
|
|
|
// 根据屏幕宽度计算每次滑动的卡片数量
|
|
const getSlidesToScroll = () => {
|
|
if (typeof window === 'undefined') return 4;
|
|
const width = window.innerWidth;
|
|
if (width < 640) return 1; // 移动端: 1列
|
|
if (width < 768) return 2; // sm: 2列
|
|
if (width < 1024) return 2; // md: 2列
|
|
if (width < 1280) return 3; // lg: 3列
|
|
return 4; // xl+: 4列
|
|
};
|
|
|
|
const [slidesToScroll, setSlidesToScroll] = useState(getSlidesToScroll());
|
|
|
|
const [emblaRef, emblaApi] = useEmblaCarousel({
|
|
align: "start",
|
|
containScroll: "trimSnaps",
|
|
slidesToScroll: slidesToScroll,
|
|
skipSnaps: false
|
|
});
|
|
|
|
const [canScrollPrev, setCanScrollPrev] = useState(false);
|
|
const [canScrollNext, setCanScrollNext] = useState(false);
|
|
|
|
const scrollPrev = useCallback(() => {
|
|
if (emblaApi) emblaApi.scrollPrev();
|
|
}, [emblaApi]);
|
|
|
|
const scrollNext = useCallback(() => {
|
|
if (emblaApi) emblaApi.scrollNext();
|
|
}, [emblaApi]);
|
|
|
|
const onSelect = useCallback(() => {
|
|
if (!emblaApi) return;
|
|
setCanScrollPrev(emblaApi.canScrollPrev());
|
|
setCanScrollNext(emblaApi.canScrollNext());
|
|
}, [emblaApi]);
|
|
|
|
useEffect(() => {
|
|
if (!emblaApi) return;
|
|
|
|
onSelect();
|
|
emblaApi.on("select", onSelect);
|
|
emblaApi.on("reInit", onSelect);
|
|
|
|
return () => {
|
|
emblaApi.off("select", onSelect);
|
|
emblaApi.off("reInit", onSelect);
|
|
};
|
|
}, [emblaApi, onSelect]);
|
|
|
|
// 监听窗口大小变化,动态调整 slidesToScroll
|
|
useEffect(() => {
|
|
const handleResize = () => {
|
|
const newSlidesToScroll = getSlidesToScroll();
|
|
if (newSlidesToScroll !== slidesToScroll) {
|
|
setSlidesToScroll(newSlidesToScroll);
|
|
// 重新初始化 Embla
|
|
if (emblaApi) {
|
|
emblaApi.reInit();
|
|
}
|
|
}
|
|
};
|
|
|
|
window.addEventListener('resize', handleResize);
|
|
return () => window.removeEventListener('resize', handleResize);
|
|
}, [slidesToScroll, emblaApi]);
|
|
|
|
return (
|
|
<AudioPlayerProvider>
|
|
<div className="mt-12">
|
|
{/* 标题栏 */}
|
|
<div className="flex items-center gap-2 mb-6">
|
|
<h2 className="txt-headline-s">🎲 Start Chatting</h2>
|
|
</div>
|
|
|
|
{/* 轮播容器 */}
|
|
<div className="relative">
|
|
<div className="overflow-hidden" ref={emblaRef}>
|
|
{isLoading ? (
|
|
<StartChatSkeleton />
|
|
) : (
|
|
<div className="flex gap-6">
|
|
{displayData.map((character) => (
|
|
<StartChatItem
|
|
key={character.aiId}
|
|
character={character}
|
|
/>
|
|
))}
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
{/* 左侧箭头按钮 */}
|
|
{!isLoading && canScrollPrev && (
|
|
<IconButton
|
|
onClick={scrollPrev}
|
|
variant="tertiary"
|
|
size="large"
|
|
iconfont="icon-arrow-left-border"
|
|
className="absolute left-0 top-1/2 -translate-y-1/2 -translate-x-1/2 z-10"
|
|
/>
|
|
)}
|
|
|
|
{/* 右侧箭头按钮 */}
|
|
{!isLoading && canScrollNext && (
|
|
<IconButton
|
|
onClick={scrollNext}
|
|
variant="tertiary"
|
|
size="large"
|
|
iconfont="icon-arrow-right-border"
|
|
className="absolute right-0 top-1/2 -translate-y-1/2 translate-x-1/2 z-10"
|
|
/>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</AudioPlayerProvider>
|
|
);
|
|
};
|
|
|
|
export default StartChat;
|
|
|