wip: dnd step
This commit is contained in:
parent
1e7127bf67
commit
b4e3e7d5c6
@ -4,7 +4,7 @@
|
|||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>Vite + React + TS</title>
|
<title>u-pipe</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
@ -10,9 +10,14 @@
|
|||||||
"preview": "vite preview"
|
"preview": "vite preview"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@ant-design/icons": "^6.0.0",
|
||||||
|
"@dnd-kit/core": "^6.3.1",
|
||||||
|
"@dnd-kit/sortable": "^10.0.0",
|
||||||
|
"@dnd-kit/utilities": "^3.2.2",
|
||||||
|
"@mui/styles": "^6.4.8",
|
||||||
"antd": "^5.24.5",
|
"antd": "^5.24.5",
|
||||||
"react": "^19.0.0",
|
"react": "18.3.1",
|
||||||
"react-dom": "^19.0.0",
|
"react-dom": "^18.3.1",
|
||||||
"react-router-dom": "^7.4.1",
|
"react-router-dom": "^7.4.1",
|
||||||
"zustand": "^5.0.3"
|
"zustand": "^5.0.3"
|
||||||
},
|
},
|
||||||
|
949
frontend/pnpm-lock.yaml
generated
949
frontend/pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@ -1,20 +1,25 @@
|
|||||||
import { Layout, Menu } from 'antd';
|
import { Layout } from 'antd';
|
||||||
import { Link } from 'react-router-dom';
|
|
||||||
|
|
||||||
const { Header } = Layout;
|
const { Header } = Layout;
|
||||||
|
|
||||||
|
// Import the makeStyles function. If you're using @mui/styles, it should be imported like this.
|
||||||
|
import { makeStyles } from '@mui/styles';
|
||||||
|
|
||||||
|
const useStyle = makeStyles({
|
||||||
|
header: {
|
||||||
|
padding: 0,
|
||||||
|
margin: 0,
|
||||||
|
position: 'static',
|
||||||
|
top: 0,
|
||||||
|
display: 'flex',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
const HeaderNav = () => {
|
const HeaderNav = () => {
|
||||||
|
const style = useStyle();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Header style={{ position: 'fixed', zIndex: 1, width: '100%' }}>
|
<Header className={style.header}></Header>
|
||||||
<Menu theme="dark" mode="horizontal">
|
|
||||||
<Menu.Item key="1" icon={<></>}>
|
|
||||||
<Link to="/">首页</Link>
|
|
||||||
</Menu.Item>
|
|
||||||
<Menu.Item key="2" icon={<></>}>
|
|
||||||
<Link to="/tasks">任务列表</Link>
|
|
||||||
</Menu.Item>
|
|
||||||
</Menu>
|
|
||||||
</Header>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,19 +1,23 @@
|
|||||||
|
import { makeStyles } from '@mui/styles';
|
||||||
import { Layout, Menu } from 'antd';
|
import { Layout, Menu } from 'antd';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
|
|
||||||
const { Sider } = Layout;
|
const { Sider } = Layout;
|
||||||
|
|
||||||
|
const useStyle = makeStyles({
|
||||||
|
container: {
|
||||||
|
padding: 0,
|
||||||
|
margin: 0,
|
||||||
|
top: 0,
|
||||||
|
display: 'flex',
|
||||||
|
flex: 1,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
const SideNav = () => {
|
const SideNav = () => {
|
||||||
|
const style = useStyle();
|
||||||
return (
|
return (
|
||||||
<Sider
|
<Sider className={style.container}>
|
||||||
style={{
|
|
||||||
overflow: 'auto',
|
|
||||||
height: '100vh',
|
|
||||||
position: 'fixed',
|
|
||||||
left: 0,
|
|
||||||
top: 64,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Menu theme="dark" mode="inline">
|
<Menu theme="dark" mode="inline">
|
||||||
<Menu.Item key="1" icon={<></>}>
|
<Menu.Item key="1" icon={<></>}>
|
||||||
<Link to="/">首页</Link>
|
<Link to="/">首页</Link>
|
||||||
|
6
frontend/src/index.css
Normal file
6
frontend/src/index.css
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
html, body {
|
||||||
|
height: 100vh;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
11
frontend/src/interfaces/step.ts
Normal file
11
frontend/src/interfaces/step.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import { Status } from "./task";
|
||||||
|
|
||||||
|
export interface Step {
|
||||||
|
id: number;
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
created_at: number;
|
||||||
|
status: Status;
|
||||||
|
task_id: number;
|
||||||
|
type: "src" | "dst" | "mid";
|
||||||
|
}
|
@ -1,8 +1,9 @@
|
|||||||
|
export type Status = "pending" | "blocked" | "running" | "completed" | "failed";
|
||||||
|
|
||||||
export interface Task {
|
export interface Task {
|
||||||
id: number;
|
id: number;
|
||||||
title: string;
|
title: string;
|
||||||
description: string;
|
description: string;
|
||||||
created_at: number;
|
created_at: number;
|
||||||
status: "pending" | "blocked" | "running" | "completed" | "failed";
|
status: Status;
|
||||||
};
|
};
|
@ -1,6 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ReactDOM from 'react-dom/client';
|
import ReactDOM from 'react-dom/client';
|
||||||
import AppRoutes from './routes';
|
import AppRoutes from './routes';
|
||||||
|
import './index.css';
|
||||||
|
|
||||||
const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement);
|
const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement);
|
||||||
root.render(
|
root.render(
|
||||||
|
71
frontend/src/pages/LoginPage.tsx
Normal file
71
frontend/src/pages/LoginPage.tsx
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { makeStyles } from '@mui/styles';
|
||||||
|
import { Button, Checkbox, Form, FormProps, Input, Space, Typography } from 'antd';
|
||||||
|
import { LockOutlined, UserOutlined } from '@ant-design/icons';
|
||||||
|
|
||||||
|
const useStyle = makeStyles({
|
||||||
|
container: {
|
||||||
|
padding: 0,
|
||||||
|
margin: 0,
|
||||||
|
top: 0,
|
||||||
|
display: 'flex',
|
||||||
|
flex: 1,
|
||||||
|
height: '100vh',
|
||||||
|
alignItems: 'center', // 垂直居中对齐
|
||||||
|
justifyContent: 'center', // 水平居中对齐
|
||||||
|
backgroundImage: `url('https://cert.umisen.com/api/other/wallpaper')`, // 替换为实际的背景图 URL
|
||||||
|
backgroundSize: 'cover',
|
||||||
|
backgroundPosition: 'center',
|
||||||
|
},
|
||||||
|
formContainer: {
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
width: '400px',
|
||||||
|
padding: '40px',
|
||||||
|
backdropFilter: 'blur(10px)', // 模糊效果
|
||||||
|
backgroundColor: 'rgba(255, 255, 255, 0.2)', // 背景颜色
|
||||||
|
borderRadius: '10px', // 圆角
|
||||||
|
boxShadow: '0 4px 8px rgba(0, 0, 0, 0.2)', // 阴影
|
||||||
|
},
|
||||||
|
form: {
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
alignItems: 'center',
|
||||||
|
width: '100%',
|
||||||
|
},
|
||||||
|
"form-filed": {
|
||||||
|
width: '100%',
|
||||||
|
"&:nth-child(n+2)": {
|
||||||
|
marginTop: '20px',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
input: {
|
||||||
|
width: '100%',
|
||||||
|
marginTop: '10px',
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const LoginPage: React.FC = () => {
|
||||||
|
const classes = useStyle();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={classes.container}>
|
||||||
|
<div className={classes.formContainer}>
|
||||||
|
<form className={classes.form}>
|
||||||
|
<div className={classes['form-filed']}>
|
||||||
|
<Input className={classes.input} size="large" placeholder="用户名" prefix={<UserOutlined />} />
|
||||||
|
</div>
|
||||||
|
<div className={classes['form-filed']}>
|
||||||
|
<Input className={classes.input} size="large" placeholder="密码" prefix={<LockOutlined />} />
|
||||||
|
</div>
|
||||||
|
<div className={classes['form-filed']}>
|
||||||
|
<Button type='primary' size='large' style={{width: '100%'}}>登录</Button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default LoginPage;
|
@ -1,72 +0,0 @@
|
|||||||
import { Table } from 'antd';
|
|
||||||
import { Layout } from 'antd';
|
|
||||||
import useTaskStore from '../services/taskService';
|
|
||||||
import { Task } from '../interfaces/task';
|
|
||||||
import { TimePipe } from '../utils/pipe';
|
|
||||||
import { Button } from 'antd';
|
|
||||||
import HeaderNav from '../components/HeaderNav';
|
|
||||||
import SideNav from '../components/SideNav';
|
|
||||||
|
|
||||||
const { Content } = Layout;
|
|
||||||
|
|
||||||
const columns = [
|
|
||||||
{
|
|
||||||
title: '标题',
|
|
||||||
dataIndex: 'title',
|
|
||||||
key: 'title',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '描述',
|
|
||||||
dataIndex: 'description',
|
|
||||||
key: 'description',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '创建日期',
|
|
||||||
dataIndex: 'created_at',
|
|
||||||
key: 'created_at',
|
|
||||||
render: (v: number) => {
|
|
||||||
return <TimePipe timestamp={v} />;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '状态',
|
|
||||||
dataIndex: 'status',
|
|
||||||
key: 'status',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const TaskPage = () => {
|
|
||||||
const { tasks } = useTaskStore();
|
|
||||||
|
|
||||||
function onClickNewTask(e: React.MouseEvent<HTMLElement, MouseEvent>) {
|
|
||||||
console.log('新建任务', e)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Layout>
|
|
||||||
<HeaderNav />
|
|
||||||
<Layout>
|
|
||||||
<SideNav />
|
|
||||||
<Content
|
|
||||||
style={{
|
|
||||||
margin: '64px 0 0 200px',
|
|
||||||
padding: 24,
|
|
||||||
minHeight: 280,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{/* 新增的新建任务按钮 */}
|
|
||||||
<Button onClick={(e) => onClickNewTask(e)}>新建任务</Button>
|
|
||||||
<Table<Task>
|
|
||||||
columns={columns}
|
|
||||||
dataSource={tasks.map((v) => ({ ...v, key: v.id }))}
|
|
||||||
expandable={{
|
|
||||||
expandedRowRender: (v) => <p style={{ margin: 0 }}>{v.description}</p>,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Content>
|
|
||||||
</Layout>
|
|
||||||
</Layout>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default TaskPage;
|
|
80
frontend/src/pages/TestPage.tsx
Normal file
80
frontend/src/pages/TestPage.tsx
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
import { makeStyles } from "@mui/styles";
|
||||||
|
import { Tag } from "antd";
|
||||||
|
|
||||||
|
const useStyles = makeStyles({
|
||||||
|
container: {
|
||||||
|
height: '200px',
|
||||||
|
width: '600px',
|
||||||
|
border: '1px solid black',
|
||||||
|
margin: "100px",
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
flexWrap: 'wrap',
|
||||||
|
overflowY: 'scroll',
|
||||||
|
},
|
||||||
|
names: {
|
||||||
|
maxWidth: '140px',
|
||||||
|
minWidth: '140px',
|
||||||
|
},
|
||||||
|
phones: {
|
||||||
|
maxWidth: '140px',
|
||||||
|
minWidth: '140px',
|
||||||
|
},
|
||||||
|
address: {
|
||||||
|
maxWidth: '140px',
|
||||||
|
minWidth: '140px',
|
||||||
|
}
|
||||||
|
});
|
||||||
|
export const TestPage = () => {
|
||||||
|
const style = useStyles();
|
||||||
|
return (
|
||||||
|
<div className={style.container}>
|
||||||
|
<div className={style.names}>
|
||||||
|
<Tag>name1</Tag>
|
||||||
|
<Tag>name2</Tag>
|
||||||
|
<Tag>name3</Tag>
|
||||||
|
<Tag>name4</Tag>
|
||||||
|
<Tag>name5</Tag>
|
||||||
|
<Tag>name6</Tag>
|
||||||
|
<Tag>name7</Tag>
|
||||||
|
<Tag>name8</Tag>
|
||||||
|
<Tag>name9</Tag>
|
||||||
|
<Tag>name10</Tag>
|
||||||
|
<Tag>name11</Tag>
|
||||||
|
<Tag>name12</Tag>
|
||||||
|
<Tag>name13</Tag>
|
||||||
|
<Tag>name14</Tag>
|
||||||
|
<Tag>name15</Tag>
|
||||||
|
</div>
|
||||||
|
<div className={style.phones}>
|
||||||
|
<Tag color="success">phone1</Tag>
|
||||||
|
<Tag color="success">phone2</Tag>
|
||||||
|
<Tag color="success">phone3</Tag>
|
||||||
|
<Tag color="success">phone4</Tag>
|
||||||
|
<Tag color="success">phone5</Tag>
|
||||||
|
<Tag color="success">phone6</Tag>
|
||||||
|
<Tag color="success">phone7</Tag>
|
||||||
|
<Tag color="success">phone8</Tag>
|
||||||
|
<Tag color="success">phone9</Tag>
|
||||||
|
</div>
|
||||||
|
<div className={style.address}>
|
||||||
|
<Tag color="error">address1</Tag>
|
||||||
|
<Tag color="error">address2</Tag>
|
||||||
|
<Tag color="error">address3</Tag>
|
||||||
|
<Tag color="error">address4</Tag>
|
||||||
|
<Tag color="error">address5</Tag>
|
||||||
|
<Tag color="error">address6</Tag>
|
||||||
|
<Tag color="error">address7</Tag>
|
||||||
|
<Tag color="error">address8</Tag>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Tag color="warning">birthday</Tag>
|
||||||
|
<Tag color="warning">birthday</Tag>
|
||||||
|
<Tag color="warning">birthday</Tag>
|
||||||
|
<Tag color="warning">birthday</Tag>
|
||||||
|
<Tag color="warning">birthday</Tag>
|
||||||
|
<Tag color="warning">birthday</Tag>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
103
frontend/src/pages/task/TaskPage.tsx
Normal file
103
frontend/src/pages/task/TaskPage.tsx
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
import { Table } from 'antd';
|
||||||
|
import { Layout } from 'antd';
|
||||||
|
import useTaskStore from '../../services/taskService';
|
||||||
|
import { Task, Status as TaskStatus } from '../../interfaces/task';
|
||||||
|
import { TimePipe } from '../../utils/pipe';
|
||||||
|
import { Button } from 'antd';
|
||||||
|
import HeaderNav from '../../components/HeaderNav';
|
||||||
|
import SideNav from '../../components/SideNav';
|
||||||
|
import { Status } from './components/TaskStatus';
|
||||||
|
import { makeStyles } from '@mui/styles';
|
||||||
|
import { useState } from 'react';
|
||||||
|
import { TaskCreate } from './components/TaskCreate';
|
||||||
|
|
||||||
|
const { Content } = Layout;
|
||||||
|
|
||||||
|
const useStyle = makeStyles({
|
||||||
|
layout: {
|
||||||
|
minHeight: '100vh', // 设置布局的最小高度为视口高度
|
||||||
|
},
|
||||||
|
container: {
|
||||||
|
padding: 0,
|
||||||
|
margin: 0,
|
||||||
|
top: 0,
|
||||||
|
display: 'flex',
|
||||||
|
flex: 1,
|
||||||
|
width: '100%',
|
||||||
|
flexDirection: 'column', // 垂直方向布局
|
||||||
|
},
|
||||||
|
new_task: {
|
||||||
|
padding: 10,
|
||||||
|
margin: 10,
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'row', // 水平方向布局
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const columns = [
|
||||||
|
{
|
||||||
|
title: '标题',
|
||||||
|
dataIndex: 'title',
|
||||||
|
key: 'title',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '描述',
|
||||||
|
dataIndex: 'description',
|
||||||
|
key: 'description',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '创建日期',
|
||||||
|
dataIndex: 'created_at',
|
||||||
|
key: 'created_at',
|
||||||
|
render: (v: number) => {
|
||||||
|
return <TimePipe timestamp={v} />;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '状态',
|
||||||
|
dataIndex: 'status',
|
||||||
|
key: 'status',
|
||||||
|
render: (v: TaskStatus) => {
|
||||||
|
return <Status status={v} />;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const TaskPage = () => {
|
||||||
|
const style = useStyle();
|
||||||
|
const { tasks } = useTaskStore();
|
||||||
|
|
||||||
|
const [open_create, set_open_create] = useState(false); // 控制新建任务弹窗的显示和隐藏
|
||||||
|
function onClickNewTask(e: React.MouseEvent<HTMLElement, MouseEvent>) {
|
||||||
|
console.log('新建任务', e)
|
||||||
|
set_open_create(true) // 显示新建任务弹窗
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<TaskCreate open={open_create} set_open={set_open_create} />
|
||||||
|
<Layout className={style.layout}>
|
||||||
|
<HeaderNav />
|
||||||
|
<Layout>
|
||||||
|
<SideNav />
|
||||||
|
<Content
|
||||||
|
className={style.container}
|
||||||
|
>
|
||||||
|
<div className={style.new_task}>
|
||||||
|
<Button type='primary' onClick={(e) => onClickNewTask(e)}>新建任务</Button>
|
||||||
|
</div>
|
||||||
|
<Table<Task>
|
||||||
|
columns={columns}
|
||||||
|
dataSource={tasks.map((v) => ({ ...v, key: v.id }))}
|
||||||
|
expandable={{
|
||||||
|
expandedRowRender: (v) => <p style={{ margin: 0 }}>{v.description}</p>,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Content>
|
||||||
|
</Layout>
|
||||||
|
</Layout>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default TaskPage;
|
107
frontend/src/pages/task/components/TaskCreate.tsx
Normal file
107
frontend/src/pages/task/components/TaskCreate.tsx
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
import { makeStyles } from "@mui/styles"
|
||||||
|
import { Modal, Collapse } from "antd"
|
||||||
|
import { DndContext } from '@dnd-kit/core'
|
||||||
|
import { SortableContext, useSortable, verticalListSortingStrategy } from '@dnd-kit/sortable';
|
||||||
|
import { CSS } from '@dnd-kit/utilities'
|
||||||
|
import { arrayMove } from '@dnd-kit/sortable'
|
||||||
|
import { JSX, useState } from "react"
|
||||||
|
import { Step } from "../../../interfaces/step";
|
||||||
|
|
||||||
|
const useStyle = makeStyles({
|
||||||
|
footer: {
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'row',
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
export interface TaskCreateProps {
|
||||||
|
open: boolean,
|
||||||
|
set_open: (v: boolean) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const steps: Step[] = [
|
||||||
|
{ id: 1, title: '步骤1', type: 'src' } as Step,
|
||||||
|
{ id: 2, title: '步骤2', type: 'mid' } as Step,
|
||||||
|
{ id: 2, title: '步骤3', type: 'mid' } as Step,
|
||||||
|
{ id: 2, title: '步骤4', type: 'mid' } as Step,
|
||||||
|
{ id: 2, title: '步骤5', type: 'mid' } as Step,
|
||||||
|
{ id: 2, title: '步骤6', type: 'mid' } as Step,
|
||||||
|
{ id: 3, title: '步骤7', type: 'dst' } as Step,
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
export const TaskCreate = (props: TaskCreateProps) => {
|
||||||
|
const style = useStyle();
|
||||||
|
const [items, setItems] = useState(steps);
|
||||||
|
|
||||||
|
const onDragEnd = ({ active, over }) => {
|
||||||
|
if (active.id !== over?.id) {
|
||||||
|
setItems(items => {
|
||||||
|
const oldIndex = items.indexOf(active.id);
|
||||||
|
const newIndex = items.indexOf(over.id);
|
||||||
|
return arrayMove(items, oldIndex, newIndex);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Modal
|
||||||
|
width={{
|
||||||
|
xs: '90%',
|
||||||
|
sm: '80%',
|
||||||
|
md: '70%',
|
||||||
|
lg: '60%',
|
||||||
|
xxl: '50%',
|
||||||
|
}}
|
||||||
|
title="新建任务"
|
||||||
|
open={props.open}
|
||||||
|
|
||||||
|
// footer={null}
|
||||||
|
>
|
||||||
|
<DndContext onDragEnd={onDragEnd}>
|
||||||
|
<SortableContext items={items} strategy={verticalListSortingStrategy}>
|
||||||
|
<Collapse>
|
||||||
|
{items.filter(v => v.type === "mid").map(item => (
|
||||||
|
<SortableItem key={item.id} id={item.id}>
|
||||||
|
<Collapse.Panel key={item.id} header={item.title}>
|
||||||
|
步骤内容
|
||||||
|
</Collapse.Panel>
|
||||||
|
</SortableItem>
|
||||||
|
))}
|
||||||
|
</Collapse>
|
||||||
|
</SortableContext>
|
||||||
|
</DndContext>
|
||||||
|
</Modal>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// Bug fix: Correctly define the type of the children prop to avoid implicit 'any' type
|
||||||
|
const SortableItem = ({ id, children }: { id: number; children: JSX.Element }) => {
|
||||||
|
const { attributes, listeners, setNodeRef, transform, transition } = useSortable({ id });
|
||||||
|
|
||||||
|
const style = {
|
||||||
|
transform: CSS.Transform.toString(transform),
|
||||||
|
transition,
|
||||||
|
cursor: 'move',
|
||||||
|
backgroundColor: 'white',
|
||||||
|
marginBottom: 8,
|
||||||
|
borderRadius: 4,
|
||||||
|
boxShadow: '0 2px 4px rgba(0,0,0,0.1)'
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div ref={setNodeRef} style={style} {...attributes}>
|
||||||
|
<div {...listeners} style={{
|
||||||
|
padding: '8px 16px',
|
||||||
|
borderRight: '2px solid #1890ff',
|
||||||
|
display: 'inline-block',
|
||||||
|
marginRight: 16
|
||||||
|
}}>⋮⋮</div>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
42
frontend/src/pages/task/components/TaskStatus.tsx
Normal file
42
frontend/src/pages/task/components/TaskStatus.tsx
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
import { Tag } from "antd";
|
||||||
|
import { Status as TaskStatus } from "../../../interfaces/task";
|
||||||
|
import { LoadingOutlined, CheckCircleOutlined, CloseCircleOutlined, PauseCircleOutlined, ExclamationCircleOutlined } from '@ant-design/icons';
|
||||||
|
|
||||||
|
export const Status = ({ status }: { status: TaskStatus }) => {
|
||||||
|
let tag;
|
||||||
|
switch (status) {
|
||||||
|
case "pending":
|
||||||
|
tag = <Tag icon={<PauseCircleOutlined />} color="default">
|
||||||
|
等待中
|
||||||
|
</Tag>
|
||||||
|
break;
|
||||||
|
case "blocked":
|
||||||
|
tag = <Tag icon={<ExclamationCircleOutlined />} color="warning">
|
||||||
|
被阻塞
|
||||||
|
</Tag>
|
||||||
|
break;
|
||||||
|
case "running":
|
||||||
|
tag = <Tag icon={<LoadingOutlined />} color="processing">
|
||||||
|
运行中
|
||||||
|
</Tag>
|
||||||
|
break;
|
||||||
|
case "completed":
|
||||||
|
tag = <Tag icon={<CheckCircleOutlined />} color="success">
|
||||||
|
已完成
|
||||||
|
</Tag>;
|
||||||
|
break;
|
||||||
|
case "failed":
|
||||||
|
tag = <Tag icon={<CloseCircleOutlined />} color="error">
|
||||||
|
失败
|
||||||
|
</Tag>
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
tag = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<span>
|
||||||
|
{tag}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
};
|
@ -1,7 +1,9 @@
|
|||||||
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
|
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
|
||||||
import { HomePage } from './pages/HomePage';
|
import { HomePage } from './pages/HomePage';
|
||||||
import { AboutPage } from './pages/AboutPage';
|
import { AboutPage } from './pages/AboutPage';
|
||||||
import TaskPage from './pages/TaskPage';
|
import TaskPage from './pages/task/TaskPage';
|
||||||
|
import LoginPage from './pages/LoginPage';
|
||||||
|
import { TestPage } from './pages/TestPage';
|
||||||
|
|
||||||
const AppRoutes = () => {
|
const AppRoutes = () => {
|
||||||
return (
|
return (
|
||||||
@ -10,6 +12,8 @@ const AppRoutes = () => {
|
|||||||
<Route path="/" element={<HomePage />} />
|
<Route path="/" element={<HomePage />} />
|
||||||
<Route path="/about" element={<AboutPage />} />
|
<Route path="/about" element={<AboutPage />} />
|
||||||
<Route path="/tasks" element={<TaskPage />} />
|
<Route path="/tasks" element={<TaskPage />} />
|
||||||
|
<Route path="/login" element={<LoginPage />} />
|
||||||
|
<Route path="/test" element={<TestPage/>} />
|
||||||
</Routes>
|
</Routes>
|
||||||
</Router>
|
</Router>
|
||||||
);
|
);
|
||||||
|
@ -25,6 +25,27 @@ const useTaskStore = create<TaskState>(() => ({
|
|||||||
description: '这是任务2的描述',
|
description: '这是任务2的描述',
|
||||||
created_at: Date.now(),
|
created_at: Date.now(),
|
||||||
status: 'running'
|
status: 'running'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
title: '任务3',
|
||||||
|
description: '这是任务3的描述',
|
||||||
|
created_at: Date.now(),
|
||||||
|
status: 'completed'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 4,
|
||||||
|
title: '任务4',
|
||||||
|
description: '这是任务4的描述',
|
||||||
|
created_at: Date.now(),
|
||||||
|
status: 'failed'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 5,
|
||||||
|
title: '任务5',
|
||||||
|
description: '这是任务5的描述',
|
||||||
|
created_at: Date.now(),
|
||||||
|
status: 'blocked'
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
getList: () => {
|
getList: () => {
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
import { Status } from "../interfaces/task";
|
||||||
|
|
||||||
export const TimePipe = (props: { timestamp: number }) => {
|
export const TimePipe = (props: { timestamp: number }) => {
|
||||||
const date = new Date(props.timestamp);
|
const date = new Date(props.timestamp);
|
||||||
return (
|
return (
|
||||||
|
Loading…
x
Reference in New Issue
Block a user