|
@@ -1,21 +1,37 @@
|
|
|
-import React, { useCallback, useEffect, useRef, useState } from "react";
|
|
|
-import { Collapse, Input, Space, Tooltip } from "antd";
|
|
|
+import React, {
|
|
|
+ useCallback,
|
|
|
+ useEffect,
|
|
|
+ useMemo,
|
|
|
+ useRef,
|
|
|
+ useState,
|
|
|
+} from "react";
|
|
|
+import { Collapse, Input, Space, Spin, Tooltip } from "antd";
|
|
|
import { useModel } from "umi";
|
|
|
-import { Dnd } from '@antv/x6-plugin-dnd';
|
|
|
-import insertCss from 'insert-css';
|
|
|
+import { Dnd } from "@antv/x6-plugin-dnd";
|
|
|
+import insertCss from "insert-css";
|
|
|
+import { SearchOutlined } from "@ant-design/icons";
|
|
|
+import { CompoundedComponent } from "@/types";
|
|
|
+import { useRequest } from "ahooks";
|
|
|
+import ImageNode from "@/components/ImageNode";
|
|
|
|
|
|
import { basic } from "@/components/basic";
|
|
|
import { flowchart } from "@/components/flowchart";
|
|
|
import { er } from "@/components/er";
|
|
|
import { lane } from "@/components/lane";
|
|
|
export default function Libary() {
|
|
|
- const { graph, initDnd, startDrag } = useModel('graphModel');
|
|
|
-
|
|
|
+ const { graph, initDnd, startDrag } = useModel("graphModel");
|
|
|
+
|
|
|
const containerRef = useRef<HTMLDivElement>(null);
|
|
|
+ const [search, setSearch] = useState("");
|
|
|
+ const [showSearch, setShowSearch] = useState(false);
|
|
|
+ const [systemSearchResult, setSystemSearchResult] = useState<
|
|
|
+ CompoundedComponent[]
|
|
|
+ >([]);
|
|
|
+ const [activeKeys, setActiveKeys] = useState(["1", "2", "3", "4"]);
|
|
|
|
|
|
useEffect(() => {
|
|
|
- if(!containerRef.current || !graph) return;
|
|
|
-
|
|
|
+ if (!containerRef.current || !graph) return;
|
|
|
+
|
|
|
const dnd = new Dnd({
|
|
|
target: graph,
|
|
|
scaled: false,
|
|
@@ -34,45 +50,207 @@ export default function Libary() {
|
|
|
overflow: auto;
|
|
|
}
|
|
|
`);
|
|
|
-
|
|
|
- const renderItem = (data: any, key: number) => {
|
|
|
- return <Tooltip title={data.name} key={key}>
|
|
|
- <img className="w-32px cursor-move" src={data.icon} onMouseDown={(e) => startDrag(e, data.node)}/>
|
|
|
- </Tooltip>
|
|
|
+
|
|
|
+ const paramRef = useRef<Record<string, any>>({
|
|
|
+ appkey: "66dbfc87e4b0eb0606e13055",
|
|
|
+ page: 1,
|
|
|
+ size: 25,
|
|
|
+ query: "",
|
|
|
+ });
|
|
|
+
|
|
|
+ const [iconList, setIconList] = useState<CompoundedComponent[]>([]);
|
|
|
+
|
|
|
+ const searchServer = async (): Promise<any> => {
|
|
|
+ const params = new URLSearchParams(paramRef.current).toString();
|
|
|
+ const res = await fetch(`https://iconsapi.com/api/search?${params}`).then(
|
|
|
+ (res) => res.json()
|
|
|
+ );
|
|
|
+ const list: CompoundedComponent[] = (res?.pages?.elements || []).map(
|
|
|
+ (item: { iconName: string; url: string }) => {
|
|
|
+ return {
|
|
|
+ name: item.iconName,
|
|
|
+ icon: item.url,
|
|
|
+ node: {
|
|
|
+ ...ImageNode,
|
|
|
+ data: {
|
|
|
+ ...ImageNode.data,
|
|
|
+ fill: {
|
|
|
+ ...ImageNode.data.fill,
|
|
|
+ fillType: "image",
|
|
|
+ imageUrl: item.url,
|
|
|
+ },
|
|
|
+ },
|
|
|
+ },
|
|
|
+ };
|
|
|
+ }
|
|
|
+ );
|
|
|
+ if(paramRef.current.page === 1) {
|
|
|
+ setIconList(list);
|
|
|
+ } else {
|
|
|
+ setIconList([...iconList, ...list]);
|
|
|
+ }
|
|
|
+ return res?.pages || {};
|
|
|
};
|
|
|
|
|
|
- const [items, setItems] = useState([
|
|
|
- {
|
|
|
- key: "1",
|
|
|
- label: "基础图形",
|
|
|
- children: <Space wrap size={6}>{basic.map((item, index) => renderItem(item, index))}</Space>,
|
|
|
- },
|
|
|
- {
|
|
|
- key: "2",
|
|
|
- label: "Flowchart流程图",
|
|
|
- children: <Space wrap size={6}>{flowchart.map((item, index) => renderItem(item, index))}</Space>,
|
|
|
- },
|
|
|
- {
|
|
|
- key: "3",
|
|
|
- label: "实体关系图(E-R图)",
|
|
|
- children: <Space wrap size={6}>{er.map((item, index) => renderItem(item, index))}</Space>,
|
|
|
- },
|
|
|
- {
|
|
|
- key: "4",
|
|
|
- label: "泳池/泳道",
|
|
|
- children: <Space wrap size={6}>{lane.map((item, index) => renderItem(item, index))}</Space>,
|
|
|
- },
|
|
|
- ]);
|
|
|
+ const { data, loading, run } = useRequest(searchServer, {
|
|
|
+ manual: true,
|
|
|
+ cacheKey: "iconsapi",
|
|
|
+ cacheTime: 1000 * 60 * 60 * 24,
|
|
|
+ });
|
|
|
|
|
|
- const [activeKeys, setActiveKeys] = useState(["1", "2", "3", "4"]);
|
|
|
+ const renderItem = (data: any, key: number) => {
|
|
|
+ return (
|
|
|
+ <Tooltip title={data.name} key={key}>
|
|
|
+ <img
|
|
|
+ className="w-32px cursor-move"
|
|
|
+ src={data.icon}
|
|
|
+ onMouseDown={(e) => startDrag(e, data.node)}
|
|
|
+ />
|
|
|
+ </Tooltip>
|
|
|
+ );
|
|
|
+ };
|
|
|
+
|
|
|
+ const items = useMemo(() => {
|
|
|
+ const list = [
|
|
|
+ {
|
|
|
+ key: "1",
|
|
|
+ label: "基础图形",
|
|
|
+ children: (
|
|
|
+ <Space wrap size={6}>
|
|
|
+ {basic.map((item, index) => renderItem(item, index))}
|
|
|
+ </Space>
|
|
|
+ ),
|
|
|
+ },
|
|
|
+ {
|
|
|
+ key: "2",
|
|
|
+ label: "Flowchart流程图",
|
|
|
+ children: (
|
|
|
+ <Space wrap size={6}>
|
|
|
+ {flowchart.map((item, index) => renderItem(item, index))}
|
|
|
+ </Space>
|
|
|
+ ),
|
|
|
+ },
|
|
|
+ {
|
|
|
+ key: "3",
|
|
|
+ label: "实体关系图(E-R图)",
|
|
|
+ children: (
|
|
|
+ <Space wrap size={6}>
|
|
|
+ {er.map((item, index) => renderItem(item, index))}
|
|
|
+ </Space>
|
|
|
+ ),
|
|
|
+ },
|
|
|
+ {
|
|
|
+ key: "4",
|
|
|
+ label: "泳池/泳道",
|
|
|
+ children: (
|
|
|
+ <Space wrap size={6}>
|
|
|
+ {lane.map((item, index) => renderItem(item, index))}
|
|
|
+ </Space>
|
|
|
+ ),
|
|
|
+ },
|
|
|
+ ];
|
|
|
+ if (showSearch) {
|
|
|
+ list.unshift({
|
|
|
+ key: "search-icon",
|
|
|
+ label: "网络图形",
|
|
|
+ children: (
|
|
|
+ <Spin spinning={loading}>
|
|
|
+ <Space wrap size={6}>
|
|
|
+ {iconList.map((item, index) => renderItem(item, index))}
|
|
|
+ </Space>
|
|
|
+ <div
|
|
|
+ className="text-12px text-center color-#9aa5b8 w-full h-20px leading-20px cursor-pointer hover:bg-#f2f2f2 hover:color-#212930"
|
|
|
+ onClick={() => {
|
|
|
+ if(!(data && data?.pageCount === data?.curPage)) {
|
|
|
+ handleNextPage(data.curPage + 1);
|
|
|
+ }
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ {
|
|
|
+ loading ? '加载中...'
|
|
|
+ : data && data?.pageCount === data?.curPage ? '暂无更多' : '加载更多'
|
|
|
+ }
|
|
|
+ </div>
|
|
|
+ </Spin>
|
|
|
+ ),
|
|
|
+ });
|
|
|
+ if (!activeKeys.includes("search-icon")) {
|
|
|
+ setActiveKeys((state) => [...state, "search-icon"]);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (showSearch && systemSearchResult.length) {
|
|
|
+ list.unshift({
|
|
|
+ key: "search-system",
|
|
|
+ label: "系统图形",
|
|
|
+ children: (
|
|
|
+ <Space wrap size={6}>
|
|
|
+ {systemSearchResult.map((item, index) => renderItem(item, index))}
|
|
|
+ </Space>
|
|
|
+ ),
|
|
|
+ });
|
|
|
+ if (!activeKeys.includes("search-system")) {
|
|
|
+ setActiveKeys((state) => [...state, "search-system"]);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return list;
|
|
|
+ }, [systemSearchResult, showSearch, data, iconList, loading]);
|
|
|
|
|
|
const handleChange = (keys: string | string[]) => {
|
|
|
setActiveKeys(Array.isArray(keys) ? keys : [keys]);
|
|
|
};
|
|
|
+
|
|
|
+ const handleSearch = () => {
|
|
|
+ if (!search) return;
|
|
|
+
|
|
|
+ const allData = [...basic, ...flowchart, ...er, ...lane];
|
|
|
+ setShowSearch(true);
|
|
|
+ setSystemSearchResult(allData.filter((item) => item.name.includes(search)));
|
|
|
+ paramRef.current = {
|
|
|
+ ...paramRef.current,
|
|
|
+ page: 1,
|
|
|
+ query: search,
|
|
|
+ };
|
|
|
+ run();
|
|
|
+ };
|
|
|
+
|
|
|
+ const handleNextPage = (page: number) => {
|
|
|
+ paramRef.current = {
|
|
|
+ ...paramRef.current,
|
|
|
+ page,
|
|
|
+ };
|
|
|
+ run();
|
|
|
+ }
|
|
|
+
|
|
|
+ useEffect(() => {
|
|
|
+ if (!search) {
|
|
|
+ setActiveKeys(["1", "2", "3", "4"]);
|
|
|
+ setShowSearch(false);
|
|
|
+ setSystemSearchResult([]);
|
|
|
+ paramRef.current = {
|
|
|
+ ...paramRef.current,
|
|
|
+ page: 1,
|
|
|
+ query: "",
|
|
|
+ };
|
|
|
+ setActiveKeys((state) =>
|
|
|
+ state.filter(
|
|
|
+ (item) => !(item === "search-system" || item === "search-icon")
|
|
|
+ )
|
|
|
+ );
|
|
|
+ }
|
|
|
+ }, [search]);
|
|
|
+
|
|
|
return (
|
|
|
<div ref={containerRef} className="h-full overflow-auto">
|
|
|
<div className="px-4">
|
|
|
- <Input size="small" allowClear placeholder="请输入搜索内容" />
|
|
|
+ <Input
|
|
|
+ prefix={<SearchOutlined />}
|
|
|
+ size="small"
|
|
|
+ allowClear
|
|
|
+ placeholder="请输入搜索内容"
|
|
|
+ value={search}
|
|
|
+ onChange={(e) => setSearch(e.target.value)}
|
|
|
+ onPressEnter={handleSearch}
|
|
|
+ />
|
|
|
</div>
|
|
|
<Collapse
|
|
|
ghost
|