| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463 |
- import React, { useState } from "react";
- import { useEffect } from "react";
- import { message, Modal, Tree } from "antd";
- import type { DataNode, TreeProps } from "antd/es/tree";
- import type { Key } from "antd/lib/table/interface";
- import { DeleteOutlined, SaveOutlined } from "@ant-design/icons";
- import { FileAddOutlined, LinkOutlined } from "@ant-design/icons";
- import { Button, Divider, Space } from "antd";
- import { useIntl } from "react-intl";
- import EditableTreeNode from "./EditableTreeNode";
- import { randomString } from "../../utils";
- export interface TreeNodeData {
- key: string;
- id: string;
- title: string | React.ReactNode;
- title_text?: string;
- icon?: React.ReactNode;
- children: TreeNodeData[];
- status?: number;
- deletedAt?: string | null;
- level: number;
- }
- export type ListNodeData = {
- key: string;
- title: string | React.ReactNode;
- title_text?: string;
- level: number;
- status?: number;
- children?: number;
- deletedAt?: string | null;
- };
- let tocActivePath: TreeNodeData[] = [];
- function tocGetTreeData(articles: ListNodeData[], active = "") {
- const treeData = [];
- const treeParents = [];
- const rootNode: TreeNodeData = {
- key: randomString(),
- id: "0",
- title: "root",
- title_text: "root",
- level: 0,
- children: [],
- };
- treeData.push(rootNode);
- let lastInsNode: TreeNodeData = rootNode;
- let iCurrLevel = 0;
- const keys: string[] = [];
- for (let index = 0; index < articles.length; index++) {
- const element = articles[index];
- const newNode: TreeNodeData = {
- key: randomString(),
- id: element.key,
- title: element.title,
- title_text: element.title_text,
- children: [],
- icon: keys.includes(element.key) ? <LinkOutlined /> : undefined,
- status: element.status,
- level: element.level,
- deletedAt: element.deletedAt,
- };
- if (!keys.includes(element.key)) {
- keys.push(element.key);
- }
- /*
- if (active == element.article) {
- newNode["extraClasses"] = "active";
- }
- */
- if (newNode.level > iCurrLevel) {
- //新的层级比较大,为上一个的子目录
- treeParents.push(lastInsNode);
- lastInsNode.children.push(newNode);
- } else if (newNode.level === iCurrLevel) {
- //目录层级相同,为平级
- treeParents[treeParents.length - 1].children.push(newNode);
- } else {
- // 小于 挂在上一个层级
- while (treeParents.length > 1) {
- treeParents.pop();
- if (treeParents[treeParents.length - 1].level < newNode.level) {
- break;
- }
- }
- treeParents[treeParents.length - 1].children.push(newNode);
- }
- lastInsNode = newNode;
- iCurrLevel = newNode.level;
- if (active === element.key) {
- tocActivePath = [];
- for (let index = 1; index < treeParents.length; index++) {
- tocActivePath.push(treeParents[index]);
- }
- }
- }
- return treeData[0].children;
- }
- function treeToList(treeNode: TreeNodeData[]): ListNodeData[] {
- let iTocTreeCurrLevel = 1;
- const arrTocTree: ListNodeData[] = [];
- for (const iterator of treeNode) {
- getTreeNodeData(iterator);
- }
- function getTreeNodeData(node: TreeNodeData) {
- let children = 0;
- if (typeof node.children != "undefined") {
- children = node.children.length;
- }
- arrTocTree.push({
- key: node.id,
- title: node.title,
- title_text: node.title_text,
- level: iTocTreeCurrLevel,
- children: children,
- deletedAt: node.deletedAt,
- });
- if (children > 0) {
- iTocTreeCurrLevel++;
- for (const iterator of node.children) {
- getTreeNodeData(iterator);
- }
- iTocTreeCurrLevel--;
- }
- }
- return arrTocTree;
- }
- interface IWidget {
- treeData: ListNodeData[];
- addFileButton?: React.ReactNode;
- addOnArticle?: TreeNodeData;
- updatedNode?: TreeNodeData;
- onChange?: Function;
- onSelect?: Function;
- onSave?: Function;
- onAddFile?: Function;
- onAppend?: Function;
- onTitleClick?: Function;
- }
- const EditableTreeWidget = ({
- treeData,
- addFileButton,
- addOnArticle,
- updatedNode,
- onChange,
- onSelect,
- onSave,
- onAppend,
- onTitleClick,
- }: IWidget) => {
- const intl = useIntl();
- const [checkKeys, setCheckKeys] = useState<string[]>([]);
- const [checkNodes, setCheckNodes] = useState<TreeNodeData[]>([]);
- const [gData, setGData] = useState<TreeNodeData[]>([]);
- const [listTreeData, setListTreeData] = useState<ListNodeData[]>();
- const [keys, setKeys] = useState<Key>("");
- useEffect(() => {
- if (typeof onChange !== "undefined") {
- onChange(listTreeData);
- }
- }, [listTreeData]);
- useEffect(() => {
- //找到节点并更新
- if (typeof updatedNode === "undefined") {
- return;
- }
- const update = (_node: TreeNodeData[]) => {
- _node.forEach((value, index, array) => {
- if (value.id === updatedNode.id) {
- array[index].title = updatedNode.title;
- array[index].title_text = updatedNode.title_text;
- console.log("key found");
- return;
- } else {
- update(array[index].children);
- }
- return;
- });
- };
- const newTree = [...gData];
- update(newTree);
- setGData(newTree);
- const list = treeToList(newTree);
- setListTreeData(list);
- }, [updatedNode]);
- const appendNode = (key: string, node: TreeNodeData) => {
- console.log("key", key);
- const append = (_node: TreeNodeData[]) => {
- _node.forEach((value, index, array) => {
- if (value.key === key) {
- array[index].children.push(node);
- console.log("key found");
- return;
- } else {
- append(array[index].children);
- }
- return;
- });
- };
- const newTree = [...gData];
- append(newTree);
- setGData(newTree);
- const list = treeToList(newTree);
- setListTreeData(list);
- };
- useEffect(() => {
- if (typeof addOnArticle === "undefined") {
- return;
- }
- console.log("add ", addOnArticle);
- const newTreeData = [...gData, addOnArticle];
- setGData(newTreeData);
- const list = treeToList(newTreeData);
- setListTreeData(list);
- }, [addOnArticle]);
- useEffect(() => {
- const data = tocGetTreeData(treeData);
- console.log("tree data", data);
- setGData(data);
- }, [treeData]);
- const onCheck: TreeProps["onCheck"] = (checkedKeys, info) => {
- console.log("onCheck", checkedKeys, info);
- setCheckKeys(checkedKeys as string[]);
- setCheckNodes(info.checkedNodes as TreeNodeData[]);
- };
- const onDragEnter: TreeProps["onDragEnter"] = (info) => {
- console.log(info);
- // expandedKeys 需要受控时设置
- // setExpandedKeys(info.expandedKeys)
- };
- const onDrop: TreeProps["onDrop"] = (info) => {
- console.log(info);
- const dropKey = info.node.key;
- const dragKey = info.dragNode.key;
- const dropPos = info.node.pos.split("-");
- const dropPosition =
- info.dropPosition - Number(dropPos[dropPos.length - 1]);
- const loop = (
- data: DataNode[],
- key: React.Key,
- callback: (node: DataNode, i: number, data: DataNode[]) => void
- ) => {
- for (let i = 0; i < data.length; i++) {
- if (data[i].key === key) {
- return callback(data[i], i, data);
- }
- if (data[i].children) {
- loop(data[i].children!, key, callback);
- }
- }
- };
- const data = [...gData];
- // Find dragObject
- let dragObj: DataNode;
- loop(data, dragKey, (item, index, arr) => {
- arr.splice(index, 1);
- dragObj = item;
- });
- if (!info.dropToGap) {
- // Drop on the content
- loop(data, dropKey, (item) => {
- item.children = item.children || [];
- // where to insert 示例添加到头部,可以是随意位置
- item.children.unshift(dragObj);
- });
- } else if (
- ((info.node as any).props.children || []).length > 0 && // Has children
- (info.node as any).props.expanded && // Is expanded
- dropPosition === 1 // On the bottom gap
- ) {
- loop(data, dropKey, (item) => {
- item.children = item.children || [];
- // where to insert 示例添加到头部,可以是随意位置
- item.children.unshift(dragObj);
- // in previous version, we use item.children.push(dragObj) to insert the
- // item to the tail of the children
- });
- } else {
- let ar: DataNode[] = [];
- let i: number;
- loop(data, dropKey, (_item, index, arr) => {
- ar = arr;
- i = index;
- });
- if (dropPosition === -1) {
- ar.splice(i!, 0, dragObj!);
- } else {
- ar.splice(i! + 1, 0, dragObj!);
- }
- }
- setGData(data);
- const list = treeToList(data);
- setListTreeData(list);
- };
- return (
- <>
- <Space>
- {addFileButton}
- <Button
- icon={<FileAddOutlined />}
- onClick={async () => {
- if (typeof onAppend !== "undefined") {
- const newNode = await onAppend({
- key: "",
- title: "",
- children: [],
- level: 0,
- });
- console.log("newNode", newNode);
- if (newNode) {
- const append = [...gData, newNode];
- setGData(append);
- const list = treeToList(append);
- setListTreeData(list);
- return true;
- } else {
- message.error("添加失败");
- return false;
- }
- } else {
- return false;
- }
- }}
- >
- {intl.formatMessage({ id: "buttons.create" })}
- </Button>
- <Button
- icon={<DeleteOutlined />}
- danger
- disabled={checkKeys.length === 0}
- onClick={() => {
- const delTree = (node: TreeNodeData[]): boolean => {
- for (let index = 0; index < node.length; index++) {
- if (checkKeys.includes(node[index].key)) {
- node.splice(index, 1);
- return true;
- } else {
- const cf = delTree(node[index].children);
- if (cf) {
- return cf;
- }
- }
- }
- return false;
- };
- Modal.confirm({
- title: "从文集移除下列文章吗?(文章不会被删除)",
- content: (
- <>
- {checkNodes.map((item, id) => (
- <div key={id}>
- {id + 1} {item.title}
- </div>
- ))}
- </>
- ),
- onOk() {
- const tmp = [...gData];
- const find = delTree(tmp);
- console.log("delete", keys, find, tmp);
- setGData(tmp);
- const list = treeToList(tmp);
- setListTreeData(list);
- },
- });
- }}
- >
- {intl.formatMessage({ id: "buttons.remove" })}
- </Button>
- <Button
- icon={<SaveOutlined />}
- onClick={() => {
- if (typeof onSave !== "undefined") {
- onSave(listTreeData);
- }
- }}
- type="primary"
- >
- {intl.formatMessage({ id: "buttons.save" })}
- </Button>
- </Space>
- <Divider></Divider>
- <Tree
- showLine
- showIcon
- checkable
- rootClassName="draggable-tree"
- draggable
- blockNode
- selectable={false}
- onDragEnter={onDragEnter}
- onDrop={onDrop}
- onCheck={onCheck}
- onSelect={(selectedKeys: Key[]) => {
- if (selectedKeys.length > 0) {
- setKeys(selectedKeys[0]);
- } else {
- setKeys("");
- }
- if (typeof onSelect !== "undefined") {
- onSelect(selectedKeys);
- }
- }}
- treeData={gData}
- titleRender={(node: TreeNodeData) => {
- return (
- <EditableTreeNode
- node={node}
- onAdd={async () => {
- if (typeof onAppend !== "undefined") {
- const newNode = await onAppend(node);
- console.log("newNode", newNode);
- if (newNode) {
- appendNode(node.key, newNode);
- return true;
- } else {
- message.error("添加失败");
- return false;
- }
- } else {
- return false;
- }
- }}
- onTitleClick={(e: React.MouseEvent<HTMLElement, MouseEvent>) => {
- if (typeof onTitleClick !== "undefined") {
- onTitleClick(e, node);
- }
- }}
- />
- );
- }}
- />
- </>
- );
- };
- export default EditableTreeWidget;
|