feat: 导出、导入、整理排序
This commit is contained in:
parent
c1f62e75ae
commit
94db0b4d2a
|
|
@ -293,7 +293,7 @@ def create_app():
|
|||
work_list = await DB.Work.filter(**db_filter).order_by("-id")
|
||||
work_ids = [x.id for x in work_list]
|
||||
work_content_list = await DB.WorkContent.filter(work_id__in=work_ids).all()
|
||||
sub_logs = await DB.WorkLogs.filter(work_id__in=work_ids).annotate(max_id=Max("id")).values_list("max_id", flat=True)
|
||||
sub_logs = await DB.WorkLogs.filter(work_id__in=work_ids).group_by('work_id').annotate(max_id=Max("id")).values_list("max_id", flat=True)
|
||||
work_logs_list = await DB.WorkLogs.filter(id__in=sub_logs).all()
|
||||
user_list = await DB.User.filter(id__in=list(set([x.user_id for x in work_content_list]))).all()
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { App, Button, Card, Col, message, Modal, Row, Space, Typography } from "antd"
|
||||
import { App, Button, Card, Col, Dropdown, message, Modal, Row, Space, Typography } from "antd"
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import {
|
||||
|
|
@ -18,6 +18,7 @@ import { flowCreate, flowDetail, flowUpdate } from "../../api/flow";
|
|||
import EditNode from "./components/EditNode";
|
||||
import CustomNode from "./components/CustomNode";
|
||||
import { ProForm, ProFormText, ProFormTextArea } from "@ant-design/pro-components";
|
||||
import { DownloadOutlined, UploadOutlined, EllipsisOutlined, ApartmentOutlined } from "@ant-design/icons";
|
||||
|
||||
const COLORS = {
|
||||
first: "#89A8B2",
|
||||
|
|
@ -62,6 +63,7 @@ const FlowDetail = () => {
|
|||
const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
|
||||
const [record, setRecord] = useState({});
|
||||
const editRef = useRef(null);
|
||||
const inputRef = useRef(null);
|
||||
const controlRef = useRef(null);
|
||||
const onConnect = useCallback((params) => setEdges((eds) => addEdge(params, eds)), [setEdges]);
|
||||
const onBeforeDelete = async (params) => {
|
||||
|
|
@ -126,9 +128,9 @@ const FlowDetail = () => {
|
|||
});
|
||||
}
|
||||
});
|
||||
|
||||
setNodes(tempNodes);
|
||||
setEdges(tempEdges);
|
||||
controlRef?.current?.setNodes(tempNodes);
|
||||
controlRef?.current?.setEdges(tempEdges);
|
||||
await controlRef?.current?.fitView();
|
||||
} else {
|
||||
message.error(res.message);
|
||||
}
|
||||
|
|
@ -176,6 +178,75 @@ const FlowDetail = () => {
|
|||
message.warning("请检查基础信息");
|
||||
}
|
||||
}
|
||||
const downloadAction = () => {
|
||||
modal.confirm({
|
||||
title: '导出',
|
||||
content: '导出当前工作配置?',
|
||||
okText: '导出',
|
||||
cancelText: '取消',
|
||||
onOk: () => {
|
||||
try {
|
||||
const filename = new Date().getTime() + '.json'
|
||||
const jsonStr = JSON.stringify({ nodes, edges }, null, 4)
|
||||
const blob = new Blob([jsonStr], { type: "application/json;charset=utf-8" });
|
||||
// IE / Edge (old) fallback
|
||||
if (window.navigator && window.navigator.msSaveOrOpenBlob) {
|
||||
window.navigator.msSaveOrOpenBlob(blob, filename);
|
||||
return;
|
||||
}
|
||||
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement("a");
|
||||
a.href = url;
|
||||
a.download = filename;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
a.remove();
|
||||
URL.revokeObjectURL(url);
|
||||
} catch (error) {
|
||||
console.error("导出失败", error);
|
||||
message.error("导出失败");
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
const uploadAction = () => {
|
||||
inputRef?.current?.click();
|
||||
}
|
||||
const onFileInputChange = (event) => {
|
||||
const fileInput = event.target;
|
||||
if (fileInput?.files?.length === 0) return;
|
||||
const file = fileInput.files[0];
|
||||
const reader = new FileReader();
|
||||
reader.onload = (e) => {
|
||||
try {
|
||||
const text = e.target.result;
|
||||
const jsonData = JSON.parse(text);
|
||||
controlRef?.current?.setNodes(jsonData.nodes);
|
||||
controlRef?.current?.setEdges(jsonData.edges);
|
||||
} catch (err) {
|
||||
console.error("JSON 解析失败:", err);
|
||||
}
|
||||
};
|
||||
reader.readAsText(file, "utf-8");
|
||||
fileInput.value = "";
|
||||
}
|
||||
const formatAction = async () => {
|
||||
const result = groupNodes(nodes, edges);
|
||||
let y = 0;
|
||||
let x = 0;
|
||||
result?.forEach(rows => {
|
||||
rows.forEach((e) => {
|
||||
e.position = { x, y }
|
||||
controlRef?.current?.updateNode(e.id);
|
||||
x += 180
|
||||
})
|
||||
y += 80;
|
||||
x = 0;
|
||||
});
|
||||
await controlRef?.current?.fitView();
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
loadDetail();
|
||||
|
|
@ -185,8 +256,26 @@ const FlowDetail = () => {
|
|||
<Row gutter={[16, 0]}>
|
||||
<Col span={18} >
|
||||
<Card title="工作" extra={<Space>
|
||||
<Button onClick={() => navigate("/flow")}>取消</Button>
|
||||
<Button type='primary' onClick={() => onSave()}>{isCreate ? '创建':'保存'}</Button>
|
||||
<div style={{ display: 'none' }}>
|
||||
<input ref={inputRef} type="file" accept=".json" onChange={onFileInputChange} />
|
||||
</div>
|
||||
<Button onClick={() => navigate("/flow")}>返回</Button>
|
||||
<Dropdown menu={{
|
||||
onClick: ({ key }) => {
|
||||
if (key === 'download') downloadAction();
|
||||
else if (key === 'upload') uploadAction();
|
||||
else if (key === 'format') formatAction();
|
||||
},
|
||||
items: [
|
||||
{ key: 'format', label: '整理', icon: <ApartmentOutlined /> },
|
||||
{ key: 'line', type: 'divider' },
|
||||
{ key: 'download', label: '导出', icon: <DownloadOutlined /> },
|
||||
{ key: 'upload', label: '导入', icon: <UploadOutlined /> },
|
||||
]
|
||||
}}>
|
||||
<Button><EllipsisOutlined /></Button>
|
||||
</Dropdown>
|
||||
<Button type='primary' onClick={() => onSave()}>{isCreate ? '创建' : '保存'}</Button>
|
||||
</Space>}>
|
||||
<div style={{ height: 'calc(100vh - 220px)', width: '100%' }}>
|
||||
<ReactFlow
|
||||
|
|
@ -256,4 +345,36 @@ const ControlButtons = ({ onLoad }) => {
|
|||
return <></>
|
||||
}
|
||||
|
||||
function groupNodes(nodes, edges) {
|
||||
const nodeMap = Object.fromEntries(nodes.map((n) => [n.id, n]));
|
||||
const graph = {};
|
||||
nodes.forEach((n) => (graph[n.id] = new Set()));
|
||||
|
||||
edges.forEach(({ source, target }) => {
|
||||
graph[source].add(target);
|
||||
graph[target].add(source);
|
||||
});
|
||||
|
||||
const visited = new Set();
|
||||
const result = [];
|
||||
|
||||
const dfs = (nodeId, group) => {
|
||||
visited.add(nodeId);
|
||||
group.push(nodeMap[nodeId]);
|
||||
graph[nodeId].forEach((nei) => {
|
||||
if (!visited.has(nei)) dfs(nei, group);
|
||||
});
|
||||
};
|
||||
|
||||
nodes.forEach((n) => {
|
||||
if (!visited.has(n.id)) {
|
||||
const group = [];
|
||||
dfs(n.id, group);
|
||||
result.push(group);
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
export default FlowDetail;
|
||||
|
|
@ -30,7 +30,6 @@ const EditNode = forwardRef(({ onFinish }, ref) => {
|
|||
|
||||
return <ModalForm
|
||||
title="编辑"
|
||||
trigger={<></>}
|
||||
open={open}
|
||||
onOpenChange={e => setOpen(e)}
|
||||
formRef={formRef}
|
||||
|
|
|
|||
Loading…
Reference in New Issue