新增图标

This commit is contained in:
liaibo
2025-12-30 18:39:38 +08:00
parent c34b5ac911
commit f46ec3c2f5
226 changed files with 1726 additions and 45 deletions
@@ -33,6 +33,15 @@ import {
layoutList,
outlineDepthOptions,
} from "./mindmapOptions";
import iconConfig from "./mindmapIconConfig";
import imageConfig from "./mindmapImageConfig";
// 避免 SSR 阶段触发浏览器依赖,改为运行时动态引入
// @ts-expect-error 第三方库缺少类型定义
const loadIconModules = async () => {
const { nodeIconList } = await import("simple-mind-map/src/svg/icons.js");
const { mergerIconList } = await import("simple-mind-map/src/utils/index.js");
return { nodeIconList, mergerIconList };
};
type MindMapNode = {
getStyle: (prop: string, checkRoot?: boolean) => any;
@@ -407,55 +416,150 @@ const StructurePanel = ({ mindmap }: { mindmap: any }) => {
}
const IconPanel = ({ activeNodes }: { activeNodes: MindMapNode[] }) => {
const addIcon = (type: string, name: string) => {
// simple-mind-map icon key format usually: type_name (e.g. priority_1)
const key = `${type}_${name}`;
activeNodes.forEach(node => {
const icons = node.getData('icon') || [];
// Remove existing icon of same type
const newIcons = icons.filter((i: string) => !i.startsWith(type + '_'));
newIcons.push(key);
node.setIcon(newIcons);
});
const [activeSubTab, setActiveSubTab] = useState<"icon" | "sticker">("icon");
const [{ iconGroups, loading }, setIconGroups] = useState<{
iconGroups: any[];
loading: boolean;
}>({ iconGroups: [], loading: true });
useEffect(() => {
let mounted = true;
(async () => {
const { nodeIconList, mergerIconList } = await loadIconModules();
if (!mounted) return;
setIconGroups({
iconGroups: mergerIconList([
...nodeIconList,
...(iconConfig as unknown as any[]),
]),
loading: false,
});
})();
return () => {
mounted = false;
};
}, []);
const [stickerGroups] = useState(imageConfig);
const removeIcon = (type: string) => {
activeNodes.forEach(node => {
const icons = node.getData('icon') || [];
const newIcons = icons.filter((i: string) => !i.startsWith(type + '_'));
node.setIcon(newIcons);
});
}
const addIcon = (type: string, name: string) => {
const key = `${type}_${name}`;
activeNodes.forEach((node) => {
const icons = node.getData("icon") || [];
const newIcons = icons.filter((i: string) => !i.startsWith(`${type}_`));
newIcons.push(key);
node.setIcon(newIcons);
});
};
if (activeNodes.length === 0) return <div className="p-4 text-center text-gray-400"></div>;
const removeIcon = (type: string) => {
activeNodes.forEach((node) => {
const icons = node.getData("icon") || [];
const newIcons = icons.filter((i: string) => !i.startsWith(`${type}_`));
node.setIcon(newIcons);
});
};
return (
<div className="space-y-4 py-2">
{predefinedIcons.map(group => (
<div key={group.type}>
<div className="mb-2 flex items-center justify-between">
<Label className="text-xs font-bold text-gray-600">{group.name}</Label>
<button onClick={() => removeIcon(group.type)} className="text-[10px] text-red-500 hover:underline"></button>
</div>
<div className="flex flex-wrap gap-2">
{group.list.map(item => (
<button
key={item}
onClick={() => addIcon(group.type, item)}
className="flex h-8 w-8 items-center justify-center rounded border border-gray-200 bg-gray-50 text-xs hover:bg-blue-50 hover:border-blue-300"
>
{item}
</button>
))}
</div>
</div>
const setSticker = (img: { url: string; width?: number; height?: number }) => {
activeNodes.forEach((node) => {
// simple-mind-map 支持 setImage 接收对象,包含 url/width/height
// @ts-expect-error 第三方库无类型
node.setImage({
url: img.url,
width: img.width || 100,
height: img.height || 100,
});
});
};
const clearSticker = () => {
activeNodes.forEach((node) => {
// 传入 null 以清除贴纸
// @ts-expect-error 第三方库无类型
node.setImage(null);
});
};
if (loading) return <div className="p-4 text-center text-gray-400"></div>;
if (iconGroups.length === 0) return <div className="p-4 text-center text-gray-400"></div>;
if (activeNodes.length === 0) return <div className="p-4 text-center text-gray-400"></div>;
return (
<div className="space-y-4 py-2">
<div className="flex gap-2 px-1">
<button
className={`flex-1 rounded border px-2 py-1 text-xs ${activeSubTab === "icon" ? "border-blue-500 bg-blue-50 text-blue-600" : "border-gray-200"}`}
onClick={() => setActiveSubTab("icon")}
>
</button>
<button
className={`flex-1 rounded border px-2 py-1 text-xs ${activeSubTab === "sticker" ? "border-blue-500 bg-blue-50 text-blue-600" : "border-gray-200"}`}
onClick={() => setActiveSubTab("sticker")}
>
</button>
</div>
{activeSubTab === "icon" && iconGroups.map((group) => (
<div key={group.type}>
<div className="mb-2 flex items-center justify-between">
<Label className="text-xs font-bold text-gray-600">{group.name}</Label>
<button onClick={() => removeIcon(group.type)} className="text-[10px] text-blue-500 hover:underline">
</button>
</div>
<div className="flex flex-wrap gap-2">
{group.list.map((item) => (
<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"
>
{item.icon ? (
typeof item.icon === "string" && item.icon.trim().startsWith("<svg") ? (
<span
className="inline-flex h-6 w-6 items-center justify-center"
// @ts-expect-error: dangerouslySetInnerHTML 用于复用官方 SVG 片段
dangerouslySetInnerHTML={{ __html: item.icon }}
/>
) : (
<img src={item.icon} alt={item.name} className="h-6 w-6 object-contain" />
)
) : (
<span className="text-xs text-gray-700">{item.name}</span>
)}
</button>
))}
<div className="text-xs text-gray-400 mt-4">
* SVG
</div>
</div>
</div>
);
}
))}
{activeSubTab === "sticker" && (
<div className="space-y-4">
<div className="flex items-center justify-between px-1">
<Label className="text-xs font-bold text-gray-600"></Label>
<button onClick={clearSticker} className="text-[10px] text-blue-500 hover:underline"></button>
</div>
{stickerGroups.map((group: any) => (
<div key={group.name}>
<div className="mb-2 text-xs font-semibold text-gray-600">{group.name}</div>
<div className="flex flex-wrap gap-2">
{group.list.map((item: any, idx: number) => (
<button
key={`${group.name}-${idx}`}
onClick={() => setSticker(item)}
className="flex h-16 w-16 items-center justify-center rounded border border-gray-200 bg-white hover:border-blue-400 hover:shadow-sm"
>
<img src={item.url} alt={group.name} className="h-14 w-14 object-contain" />
</button>
))}
</div>
</div>
))}
</div>
)}
</div>
);
};
const OutlinePanel = ({ mindmap }: { mindmap: any }) => {
const [depth, setDepth] = useState<number>(-1);