wip: dnd step
This commit is contained in:
parent
1e7127bf67
commit
b4e3e7d5c6
@ -4,7 +4,7 @@
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Vite + React + TS</title>
|
||||
<title>u-pipe</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
@ -10,9 +10,14 @@
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"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",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"react": "18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-router-dom": "^7.4.1",
|
||||
"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 { Link } from 'react-router-dom';
|
||||
import { Layout } from 'antd';
|
||||
|
||||
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 style = useStyle();
|
||||
|
||||
return (
|
||||
<Header style={{ position: 'fixed', zIndex: 1, width: '100%' }}>
|
||||
<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>
|
||||
<Header className={style.header}></Header>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -1,19 +1,23 @@
|
||||
import { makeStyles } from '@mui/styles';
|
||||
import { Layout, Menu } from 'antd';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
const { Sider } = Layout;
|
||||
|
||||
const useStyle = makeStyles({
|
||||
container: {
|
||||
padding: 0,
|
||||
margin: 0,
|
||||
top: 0,
|
||||
display: 'flex',
|
||||
flex: 1,
|
||||
},
|
||||
});
|
||||
|
||||
const SideNav = () => {
|
||||
const style = useStyle();
|
||||
return (
|
||||
<Sider
|
||||
style={{
|
||||
overflow: 'auto',
|
||||
height: '100vh',
|
||||
position: 'fixed',
|
||||
left: 0,
|
||||
top: 64,
|
||||
}}
|
||||
>
|
||||
<Sider className={style.container}>
|
||||
<Menu theme="dark" mode="inline">
|
||||
<Menu.Item key="1" icon={<></>}>
|
||||
<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 {
|
||||
id: number;
|
||||
title: string;
|
||||
description: string;
|
||||
created_at: number;
|
||||
status: "pending" | "blocked" | "running" | "completed" | "failed";
|
||||
status: Status;
|
||||
};
|
@ -1,6 +1,7 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom/client';
|
||||
import AppRoutes from './routes';
|
||||
import './index.css';
|
||||
|
||||
const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement);
|
||||
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 { HomePage } from './pages/HomePage';
|
||||
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 = () => {
|
||||
return (
|
||||
@ -10,6 +12,8 @@ const AppRoutes = () => {
|
||||
<Route path="/" element={<HomePage />} />
|
||||
<Route path="/about" element={<AboutPage />} />
|
||||
<Route path="/tasks" element={<TaskPage />} />
|
||||
<Route path="/login" element={<LoginPage />} />
|
||||
<Route path="/test" element={<TestPage/>} />
|
||||
</Routes>
|
||||
</Router>
|
||||
);
|
||||
|
@ -25,6 +25,27 @@ const useTaskStore = create<TaskState>(() => ({
|
||||
description: '这是任务2的描述',
|
||||
created_at: Date.now(),
|
||||
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: () => {
|
||||
|
@ -1,3 +1,5 @@
|
||||
import { Status } from "../interfaces/task";
|
||||
|
||||
export const TimePipe = (props: { timestamp: number }) => {
|
||||
const date = new Date(props.timestamp);
|
||||
return (
|
||||
@ -5,4 +7,4 @@ export const TimePipe = (props: { timestamp: number }) => {
|
||||
{date.toLocaleDateString()}
|
||||
</>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user