图标可移动

This commit is contained in:
liaibo
2025-12-31 19:26:38 +08:00
parent f46ec3c2f5
commit 314b6f9a7e
4 changed files with 500 additions and 21 deletions
@@ -32,6 +32,9 @@ import {
themeList,
layoutList,
outlineDepthOptions,
backgroundRepeatList,
backgroundPositionList,
backgroundSizeList,
} from "./mindmapOptions";
import iconConfig from "./mindmapIconConfig";
import imageConfig from "./mindmapImageConfig";
@@ -288,7 +291,8 @@ const BaseStylePanel = ({ mindmap }: { mindmap: any }) => {
const newStyle: Record<string, any> = {};
[
"backgroundColor", "lineColor", "lineWidth", "lineStyle",
"paddingX", "paddingY"
"paddingX", "paddingY",
"backgroundImage", "backgroundRepeat", "backgroundPosition", "backgroundSize",
].forEach(key => {
newStyle[key] = mindmap.getThemeConfig(key);
});
@@ -315,6 +319,31 @@ const BaseStylePanel = ({ mindmap }: { mindmap: any }) => {
value={style.backgroundColor}
onChange={(v) => updateThemeConfig("backgroundColor", v)}
/>
<div className="space-y-2">
<Label className="text-[11px] text-gray-500"></Label>
<Input
value={style.backgroundImage || ""}
onChange={(e) => updateThemeConfig("backgroundImage", e.target.value || "")}
placeholder="可粘贴图片地址"
/>
<div className="grid grid-cols-3 gap-2 text-[12px] text-gray-600">
<NativeSelect
value={style.backgroundRepeat || "no-repeat"}
options={backgroundRepeatList.map((i) => ({ label: i.name, value: i.value }))}
onChange={(v) => updateThemeConfig("backgroundRepeat", v)}
/>
<NativeSelect
value={style.backgroundPosition || "center center"}
options={backgroundPositionList.map((i) => ({ label: i.name, value: i.value }))}
onChange={(v) => updateThemeConfig("backgroundPosition", v)}
/>
<NativeSelect
value={style.backgroundSize || "cover"}
options={backgroundSizeList.map((i) => ({ label: i.name, value: i.value }))}
onChange={(v) => updateThemeConfig("backgroundSize", v)}
/>
</div>
</div>
</div>
<div className="space-y-3">
@@ -512,12 +541,12 @@ const IconPanel = ({ activeNodes }: { activeNodes: MindMapNode[] }) => {
<button
key={`${group.type}-${item.name}`}
onClick={() => addIcon(group.type, item.name)}
className="flex h-9 w-9 items-center justify-center rounded border border-gray-200 bg-white text-xs hover:border-blue-400 hover:shadow-sm"
className="flex h-9 w-9 items-center justify-center rounded border border-gray-200 bg-white text-xs hover:border-blue-400 hover:shadow-sm overflow-hidden"
>
{item.icon ? (
typeof item.icon === "string" && item.icon.trim().startsWith("<svg") ? (
<span
className="inline-flex h-6 w-6 items-center justify-center"
className="inline-flex h-6 w-6 items-center justify-center overflow-hidden"
// @ts-expect-error: dangerouslySetInnerHTML 用于复用官方 SVG 片段
dangerouslySetInnerHTML={{ __html: item.icon }}
/>
@@ -691,19 +720,76 @@ const FormulaPanel = ({ mindmap }: { mindmap: any }) => {
);
};
const NotePanel = ({ mindmap }: { mindmap: any }) => {
const [note, setNote] = useState("备注内容");
const apply = () => {
const active = mindmap?.renderer?.activeNodeList || [];
active.forEach((node: any) => mindmap?.execCommand?.("SET_NODE_NOTE", node, note));
const NotePanel = ({ mindmap, activeNodes }: { mindmap: any; activeNodes: MindMapNode[] }) => {
const [note, setNote] = useState("");
const readNote = (node: any) => {
try {
return (
node?.getData?.("note") ??
node?.nodeData?.data?.note ??
node?.data?.note ??
""
);
} catch {
return "";
}
};
// 当选中节点变化时,自动展示首个节点的备注
useEffect(() => {
if (!activeNodes?.length) {
setNote("");
return;
}
const current = readNote(activeNodes[0]);
setNote(typeof current === "string" ? current : "");
}, [activeNodes]);
const getActiveList = () =>
mindmap?.renderer?.activeNodeList?.length
? mindmap.renderer.activeNodeList
: activeNodes || [];
const apply = () => {
const list = getActiveList();
list.forEach((node: any) => mindmap?.execCommand?.("SET_NODE_NOTE", node, note));
};
const clearNote = () => {
const list = getActiveList();
list.forEach((node: any) => mindmap?.execCommand?.("SET_NODE_NOTE", node, ""));
setNote("");
};
if (!activeNodes?.length) {
return <div className="p-4 text-sm text-gray-400"></div>;
}
return (
<div className="space-y-3">
<Label className="text-xs text-gray-500"></Label>
<Input value={note} onChange={(e) => setNote(e.target.value)} />
<button className="w-full rounded-md bg-blue-500 px-3 py-2 text-sm text-white hover:bg-blue-600" onClick={apply}>
</button>
<Label className="text-xs text-gray-500"></Label>
<textarea
className="w-full rounded-md border border-gray-200 p-2 text-sm"
rows={38}
value={note}
onChange={(e) => setNote(e.target.value)}
placeholder="输入备注内容"
/>
<div className="flex gap-2">
<button
className="flex-1 rounded-md bg-blue-500 px-3 py-2 text-sm text-white hover:bg-blue-600"
onClick={apply}
>
/
</button>
<button
className="flex-1 rounded-md border border-gray-300 px-3 py-2 text-sm text-gray-700 hover:bg-gray-50"
onClick={clearNote}
>
</button>
</div>
</div>
);
};
@@ -750,7 +836,7 @@ export const MindmapSidebar = ({ mindmap, activeNodes, activeTab, onClose }: Sid
case "formula":
return <FormulaPanel mindmap={mindmap} />;
case "note":
return <NotePanel mindmap={mindmap} />;
return <NotePanel mindmap={mindmap} activeNodes={activeNodes} />;
case "ai":
return <AiPanel />;
default: