Просмотр исходного кода

:sparkles: 文集目录树形拖拽编辑

visuddhinanda 3 лет назад
Родитель
Сommit
7b49579ad9

+ 160 - 0
dashboard/src/components/studio/EditableTree.tsx

@@ -0,0 +1,160 @@
+import React, { useState } from "react";
+import { Tree } from "antd";
+import type { DataNode, TreeProps } from "antd/es/tree";
+
+type TreeNodeData = {
+	key: string;
+	title: string;
+	children: TreeNodeData[];
+	level: number;
+};
+export type ListNodeData = {
+	article: string;
+	title: string;
+	level: number;
+};
+
+var tocActivePath: TreeNodeData[] = [];
+function tocGetTreeData(articles: ListNodeData[], active = "") {
+	let treeData = [];
+
+	let treeParents = [];
+	let rootNode: TreeNodeData = { key: "0", title: "root", level: 0, children: [] };
+	treeData.push(rootNode);
+	let lastInsNode: TreeNodeData = rootNode;
+
+	let iCurrLevel = 0;
+	for (let index = 0; index < articles.length; index++) {
+		const element = articles[index];
+
+		let newNode: TreeNodeData = { key: element.article, title: element.title, children: [], level: element.level };
+		/*
+		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.article) {
+			tocActivePath = [];
+			for (let index = 1; index < treeParents.length; index++) {
+				//treeParents[index]["expanded"] = true;
+				tocActivePath.push(treeParents[index]);
+			}
+		}
+	}
+	return treeData[0].children;
+}
+
+type IWidgetEditableTree = {
+	treeData?: ListNodeData[];
+};
+const Widget = ({ treeData = [] }: IWidgetEditableTree) => {
+	const data = tocGetTreeData(treeData);
+	const [gData, setGData] = useState(data);
+	const [expandedKeys] = useState(["0-0", "0-0-0", "0-0-0-0"]);
+
+	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);
+		console.log(gData);
+	};
+	return (
+		<Tree
+			className="draggable-tree"
+			defaultExpandedKeys={expandedKeys}
+			draggable
+			blockNode
+			multiple
+			onDragEnter={onDragEnter}
+			onDrop={onDrop}
+			treeData={gData}
+		/>
+	);
+};
+
+export default Widget;

+ 100 - 13
dashboard/src/pages/studio/anthology/edit.tsx

@@ -1,29 +1,116 @@
 import { useParams } from "react-router-dom";
 import { useIntl } from "react-intl";
+
 import { Layout } from "antd";
 
+import { ProForm, ProFormText, ProFormSelect, ProFormTextArea } from "@ant-design/pro-components";
+import { message } from "antd";
+
 import HeadBar from "../../../components/studio/HeadBar";
 import LeftSider from "../../../components/studio/LeftSider";
 import Footer from "../../../components/studio/Footer";
+import EditableTree from "../../../components/studio/EditableTree";
+import type { ListNodeData } from "../../../components/studio/EditableTree";
 
-const {  Content } = Layout;
+const { Content } = Layout;
+
+interface IFormData {
+	title: string;
+	subtitle: string;
+	summary: string;
+	lang: string;
+	studio: string;
+	toc: string;
+}
 
 const Widget = () => {
 	const intl = useIntl();
-	const { studioname,anthology_id } = useParams();//url 参数
-  return (
-	<Layout>
-		<HeadBar/>
+	const { studioname, anthology_id } = useParams(); //url 参数
+	const listdata: ListNodeData[] = [
+		{ article: "1", title: "title1", level: 1 },
+		{ article: "2", title: "title2", level: 2 },
+		{ article: "3", title: "title3", level: 1 },
+		{ article: "4", title: "title4", level: 2 },
+	];
+	return (
 		<Layout>
-			<LeftSider selectedKeys="anthology"/>
-			<Content>
-				<h2>studio/{studioname}/{intl.formatMessage({ id: "columns.studio.anthology.title" })}/anthology/{anthology_id}</h2>
-			</Content>
-		</Layout>
-		<Footer/>
-    </Layout>
+			<HeadBar />
+			<Layout>
+				<LeftSider selectedKeys="anthology" />
+				<Content>
+					<h2>
+						studio/{studioname}/{intl.formatMessage({ id: "columns.studio.anthology.title" })}/anthology/
+						{anthology_id}
+					</h2>
 
-  );
+					<ProForm<IFormData>
+						onFinish={async (values: IFormData) => {
+							// TODO
+							values.studio = "aaaa";
+							console.log(values);
+							message.success(intl.formatMessage({ id: "flashes.success" }));
+						}}
+					>
+						<ProForm.Group>
+							<ProFormText
+								width="md"
+								name="title"
+								required
+								label={intl.formatMessage({ id: "forms.fields.title.label" })}
+								rules={[
+									{
+										required: true,
+										message: intl.formatMessage({ id: "forms.create.message.no.title" }),
+									},
+								]}
+							/>
+						</ProForm.Group>
+						<ProForm.Group>
+							<ProFormText
+								width="md"
+								name="subtitle"
+								label={intl.formatMessage({ id: "forms.fields.subtitle.label" })}
+							/>
+						</ProForm.Group>
+						<ProForm.Group>
+							<ProFormTextArea
+								name="summary"
+								label={intl.formatMessage({ id: "forms.fields.summary.label" })}
+							/>
+						</ProForm.Group>
+						<ProForm.Group>
+							<ProFormSelect
+								options={[
+									{ value: "zh-Hans", label: "简体中文" },
+									{ value: "zh-Hant", label: "繁体中文" },
+									{ value: "en-US", label: "English" },
+								]}
+								width="md"
+								name="lang"
+								rules={[
+									{
+										required: true,
+										message: intl.formatMessage({ id: "forms.create.message.no.lang" }),
+									},
+								]}
+								label={intl.formatMessage({ id: "channel.lang" })}
+							/>
+						</ProForm.Group>
+
+						<ProForm.Group>
+							<ProFormTextArea
+								name="toc"
+								label={intl.formatMessage({ id: "forms.fields.content.label" })}
+							/>
+						</ProForm.Group>
+					</ProForm>
+
+					<EditableTree treeData={listdata} />
+				</Content>
+			</Layout>
+			<Footer />
+		</Layout>
+	);
 };
 
 export default Widget;