first commit

This commit is contained in:
2026-03-20 19:40:17 +08:00
commit 8dcebff7a6
41 changed files with 9322 additions and 0 deletions

View File

@@ -0,0 +1,90 @@
"use client";
import { memo, useCallback, type MouseEvent } from "react";
import { Handle, Position, type NodeProps } from "reactflow";
import type { GraphNodeData } from "@/types/mindmap";
function MindmapNodeCard({ data }: NodeProps<GraphNodeData>) {
const isRoot = data.level === 0;
const canOpenChat = typeof data.onOpenChat === "function";
const handleToggleClick = useCallback(
(event: MouseEvent<HTMLButtonElement>) => {
event.preventDefault();
event.stopPropagation();
data.onToggle?.();
},
[data],
);
const handleContentDoubleClick = useCallback(
(event: MouseEvent<HTMLButtonElement>) => {
event.preventDefault();
event.stopPropagation();
data.onOpenChat?.();
},
[data],
);
return (
<div
className={`relative flex h-[84px] w-[220px] items-center justify-center rounded-[18px] border px-5 py-4 ${
isRoot
? "border-blue-500 bg-blue-600 text-white shadow-[0_18px_40px_rgba(37,99,235,0.24)]"
: "border-slate-200 bg-white text-slate-900 shadow-[0_12px_28px_rgba(15,23,42,0.08)]"
} cursor-default`}
>
<Handle
type="target"
position={Position.Left}
className={`!pointer-events-none !left-[-7px] !h-3 !w-3 !border ${
isRoot ? "!border-blue-200 !bg-blue-100" : "!border-slate-300 !bg-white"
}`}
/>
<button
type="button"
onDoubleClick={handleContentDoubleClick}
disabled={!canOpenChat}
title={canOpenChat ? "双击发起聊天" : undefined}
className={`nodrag nopan max-w-[156px] select-none rounded-xl px-2 py-1 text-center text-sm font-semibold leading-6 transition-colors ${
canOpenChat
? isRoot
? "cursor-pointer hover:bg-white/10"
: "cursor-pointer hover:bg-slate-50"
: "cursor-default"
} disabled:pointer-events-none disabled:opacity-100`}
>
{data.label}
</button>
{data.hasChildren ? (
<>
<Handle
type="source"
position={Position.Right}
className={`!pointer-events-none !right-[-7px] !h-3 !w-3 !border ${
isRoot ? "!border-blue-200 !bg-blue-100" : "!border-blue-300 !bg-white"
}`}
/>
<button
type="button"
aria-label={data.isExpanded ? "收起子节点" : "展开子节点"}
onClick={handleToggleClick}
className="nodrag nopan absolute right-0 top-1/2 z-20 flex h-9 w-9 translate-x-1/2 -translate-y-1/2 items-center justify-center rounded-full border border-blue-200 bg-white text-lg font-semibold text-blue-700 shadow-[0_8px_18px_rgba(37,99,235,0.14)] transition-all hover:border-blue-300 hover:bg-blue-50 active:scale-95"
>
{data.isExpanded ? "-" : "+"}
</button>
</>
) : (
<span
className={`pointer-events-none absolute -right-[7px] top-1/2 h-3 w-3 -translate-y-1/2 rounded-full border ${
isRoot ? "border-blue-200 bg-blue-100" : "border-slate-300 bg-white"
}`}
/>
)}
</div>
);
}
export default memo(MindmapNodeCard);