从UI来讲,删掉一个还是很简单的,无非是打开选择框,点击删除,在弹出框中选择确定。
以ChatGPT为例,总体流程为:
-
点击删除框
-
出现一个删除弹窗
-
点击删除删除成功(ChatGPT没有删除成功的提示,我们可以视情况添加)
在上一篇我们提到过,每一个聊天记录都是通过id这个凭证来进行绑定的,对于删除,我们也同样需要这个id来删除对应的聊天记录。
从组件分析,一步一步实现,我们这里主要使用两个和
在具体实践中,可能会有几个问题:
- 我们不是在Dropdown这个框里点击就删除的,而是在AlertDialog里面点击Delete删除的,点击Dropdown里的删除是记住我们要删除的id,以便我们可能的取消删除, UI的更新
- 由于是在AlertDialog里面点击Delete删除的,我们需要在父组件里的创建AlertDialog这个弹出框,实现handleDelete,避免子组件多次渲染,同时简化逻辑。
- 在handleDelete这里面呢,要注意,当删除成功后,需要将前端的数据一同删除。同时当我们删除后,我们可能自动需要关闭这个弹窗,这时我们就还需要一个状态来管理AlertDialog弹窗的开关。
- 当我们进行到这里的时候,有一点需要考虑,就是如果当我们所在的位置就是删除的那个聊天记录的页面,我们需要跳转到新建页面,即根路由。所以我们要获取一下当前的路由id。
- 到这,我们的删除逻辑基本结束了,不过在删除这个函数里有一个优化点,就是我们不希望需要等到删除成功后再切换,而是不管是否删除成功,当我们点击delete时就直接关闭弹窗,可以进行别的操作了,这里处理方法很多。整体代码架构
从整体的构成来讲,我们需要从父组件来实现:
- 获取当前的路由id:id = useParams();
- 存储选中删除的的聊天id:const [deleteId, setDeleteId] = useState<string | null>(null);
- 删除的函数:handleDelete
const handleDelete = async () => {
// 请求的代码,由于是假数据,直接采用下面的更新
// fetch(`/api/chat/${deleteId}`, {
// method: 'DELETE',
// })
// .then((response) => {
// if (!response.ok) {
// throw new Error('删除失败');
// }
// setChatHistory(chatHistory.filter((chat) => chat.id !== deleteId));
// toast.success('删除成功');
// })
// .catch(() => {
// toast.error('删除失败');
// })
setChatHistory(chatHistory.filter((chat) => chat.id !== deleteId));
setIsDeleteDialogOpen(false)
if (id === deleteId) {
router.push('/');
}
}
- 管理弹窗的状态:const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false);
- 上一篇里我们知道,我们通过如下代码渲染每一个记录条:
{history.map((chat) =>
<ChatItem
key={chat.id}
chat={chat}
/>)}
那么当我们点击某一条的时候,我们可以通过ChatItem得到点击那一条的id,并将它记录下来。所以在这里可以添加一下
{history.map((chat) => {
<ChatItem
key={chat.id}
chat={chat}
onDelete={(chatId) => {
setDeleteId(chatId);
setIsDialogOpen(true);
}}
/>
})}
完整代码
const ChatItem = ({
chat,
isActive,
onDelete,
}: {
chat: any,
isActive: boolean,
onDelete: (chatId: string) => void,
}) => {
return (
<SidebarMenuItem>
<SidebarMenuButton asChild isActive={isActive}>
{/* 提供id,方便跳转到对应的路由,使用id获取数据 */}
<Link href={`/chat/${chat.id}`}>
<span>{chat.title}</span>
</Link>
</SidebarMenuButton>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<SidebarMenuAction>
<MoreHorizontalIcon />
</SidebarMenuAction>
</DropdownMenuTrigger>
<DropdownMenuContent side='bottom' align='end'>
<DropdownMenuItem
className='cursor-pointer text-destructive focus:bg-destructive/10 focus:text-destructive'
onClick={() => onDelete(chat.id)}
>
<Trash2Icon />
<span>删除</span>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</SidebarMenuItem>
)
}
// 从后端获取记录,这里使用一些假记录
const history = [
{
createdAt: "2025-03-14T12:39:15.441Z",
id: "1",
title: "用户问候",
userId: "110",
},
{
createdAt: "2025-03-14T12:39:15.441Z",
id: "2",
title: "介绍自己",
userId: "110",
},
{
createdAt: "2025-03-14T12:39:15.441Z",
id: "3",
title: "OpenAI倒闭",
userId: "110",
},
]
function History() {
// 获取id
const { id } = useParams();
const router = useRouter();
// 使用假数据设置一个初始的聊天记录
const [chatHistory, setChatHistory] = useState(history);
const [deleteId, setDeleteId] = useState<string | null>(null)
const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState<boolean>(false)
const handleDelete = async () => {
// 请求的代码,由于是假数据,直接采用下面的更新
// fetch(`/api/chat/${deleteId}`, {
// method: 'DELETE',
// })
// .then((response) => {
// if (!response.ok) {
// throw new Error('删除失败');
// }
// setChatHistory(chatHistory.filter((chat) => chat.id !== deleteId));
// toast.success('删除成功');
// })
// .catch(() => {
// toast.error('删除失败');
// })
setChatHistory(chatHistory.filter((chat) => chat.id !== deleteId));
setIsDeleteDialogOpen(false)
if (id === deleteId) {
router.push('/');
}
}
return (
<>
<SidebarGroup>
<SidebarGroupContent>
<SidebarMenu>
{/* 将其渲染 */}
{chatHistory.map((chat) =>
<ChatItem
key={chat.id}
chat={chat}
isActive={id === chat.id}
onDelete={(chatId) => {
setDeleteId(chatId)
setIsDeleteDialogOpen(true)
}}
/>)}
</SidebarMenu>
</SidebarGroupContent>
</SidebarGroup>
<AlertDialog open={isDeleteDialogOpen} onOpenChange={setIsDeleteDialogOpen}>
<AlertDialogContent>
<AlertDialogHeader>删除记录</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>取消</AlertDialogCancel>
<AlertDialogAction
onClick={handleDelete}
>确定</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</>
)
}
export default SidebarHistory
最终效果: