zhaoweijie f9763778fa refactor(components): 重构碘搜索相关组件
- 优化 IodRelevant 组件中的加载状态和计数器动画
- 重构 PlaygroundIod 组件,使用新的 PlaygroundIodProvider 组件
- 改进 Context 提供的数据结构,使用更准确的命名
2025-08-24 13:04:43 +08:00

167 lines
5.1 KiB
TypeScript

import React, { createContext, useContext, useMemo, useState } from "react"
import { AnimatePresence, motion } from "framer-motion"
import { PlaygroundIodRelevant } from "@/components/Common/Playground/IodRelevant.tsx"
import { PlaygroundData } from "@/components/Common/Playground/Data.tsx"
import { PlaygroundScene } from "@/components/Common/Playground/Scene.tsx"
import { PlaygroundTeam } from "@/components/Common/Playground/Team.tsx"
import { Card } from "antd"
import { CloseOutlined } from "@ant-design/icons"
import { useMessageOption } from "@/hooks/useMessageOption.tsx"
import { Message } from "@/types/message.ts"
// 定义 Context 类型
interface IodPlaygroundContextType {
showPlayground: boolean
setShowPlayground: React.Dispatch<React.SetStateAction<boolean>>
detailHeader: React.ReactNode
setDetailHeader: React.Dispatch<React.SetStateAction<React.ReactNode>>
detailMain: React.ReactNode
setDetailMain: React.Dispatch<React.SetStateAction<React.ReactNode>>
currentIodMessage: Message | null
}
// 创建 Context
const PlaygroundContext = createContext<IodPlaygroundContextType | undefined>(
undefined
)
// 创建自定义 hook 以便子组件使用
export const useIodPlaygroundContext = () => {
const context = useContext(PlaygroundContext)
if (context === undefined) {
throw new Error(
"usePlaygroundContext must be used within a PlaygroundProvider"
)
}
return context
}
const PlaygroundIodProvider: React.FC<{ children: React.ReactNode }> = ({
children
}) => {
const { messages, iodLoading, currentMessageId, iodSearch } =
useMessageOption()
const [showPlayground, setShowPlayground] = useState<boolean>(true)
const [detailHeader, setDetailHeader] = useState(<></>)
const [detailMain, setDetailMain] = useState(<></>)
const currentIodMessage = useMemo<Message | null>(() => {
if (iodLoading) {
return null
}
if (messages.length && iodSearch) {
// 如果不存在currentMessageId默认返回最后一个message
if (!currentMessageId) {
return messages.at(-1)
}
const currentMessage = messages?.find(
(message) => message.id === currentMessageId
)
if (currentMessage) {
return currentMessage
}
// 如果当前message不存在最后一个message
return messages.at(-1)
}
return null
}, [currentMessageId, messages, iodLoading, iodSearch])
return (
<PlaygroundContext.Provider
value={{
currentIodMessage,
showPlayground,
setShowPlayground,
detailMain,
setDetailMain,
detailHeader,
setDetailHeader
}}>
{children}
</PlaygroundContext.Provider>
)
}
// 子组件使用修改card的默认样式
const classNames =
"h-full [&_.ant-card-body]:h-full [&_.ant-card-body]:!p-[20px] overflow-y-hidden !bg-[rgba(240,245,255,0.3)] backdrop-blur-sm border border-white/30 shadow-xl rounded-2xl"
// 将原来的返回内容移到这个组件中
const PlaygroundContent = () => {
const { showPlayground, detailMain, detailHeader, setShowPlayground } =
useIodPlaygroundContext()
return (
<AnimatePresence mode="popLayout">
{showPlayground ? (
<motion.div
key="playground"
initial={{ x: "100%" }}
animate={{ x: 0 }}
exit={{ x: "100%" }}
transition={{
duration: 0.6,
ease: "easeInOut"
}}
className="h-full grid grid-rows-12 gap-3">
<div className="w-full row-span-5">
<PlaygroundIodRelevant
className={classNames
.replace("!bg-[rgba(240,245,255,0.3)]", "")
.replace("shadow-xl", "")}
/>
</div>
<div className="w-full row-span-4 grid grid-cols-2 gap-3 custom-scrollbar">
<PlaygroundData className={classNames} />
<PlaygroundScene className={classNames} />
</div>
<div className="w-full row-span-3 pb-3">
<PlaygroundTeam className={classNames} />
</div>
</motion.div>
) : (
<motion.div
key="alternative"
initial={{ x: "100%" }}
animate={{ x: 0 }}
exit={{ x: "100%" }}
transition={{
duration: 0.6,
ease: "easeInOut"
}}
className="h-full pb-5">
<Card className="h-full shadow-xl shadow-gray-500/20 [&_.ant-card-body]:h-full">
<div className="flex flex-col h-full">
<div className="pb-6 flex items-center justify-between">
<div>{detailHeader}</div>
<CloseOutlined
size={30}
className="hover:text-red-500 cursor-pointer transition-colors duration-200 text-xl"
onClick={() => setShowPlayground(true)}
/>
</div>
{detailMain}
</div>
</Card>
</motion.div>
)}
</AnimatePresence>
)
}
export const PlaygroundIod = () => {
return (
<div className="w-[36%] h-full pt-16 pr-5 pb-0">
<PlaygroundIodProvider>
<PlaygroundContent />
</PlaygroundIodProvider>
</div>
)
}