feat: 创建路由
This commit is contained in:
parent
70fcc0f410
commit
8b043a58a1
|
|
@ -1,2 +1,11 @@
|
|||
## 环境准备
|
||||
|
||||
```shell
|
||||
# python
|
||||
nvm local 3.13.0
|
||||
python -m venv venv
|
||||
source venv/bin/activate
|
||||
|
||||
# node
|
||||
nvm use v20.18.0
|
||||
```
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
import asyncio
|
||||
import uvicorn
|
||||
import uuid
|
||||
import time
|
||||
|
|
@ -23,9 +24,7 @@ scheduler = AsyncIOScheduler()
|
|||
def clear_session():
|
||||
TTL = 60 * 60 * 24 # 1天有效期
|
||||
now = time.time()
|
||||
expired_keys = [
|
||||
token for token, (expires_at, _) in sessions.items() if expires_at + TTL < now
|
||||
]
|
||||
expired_keys = [token for token, (expires_at, _) in sessions.items() if expires_at + TTL < now]
|
||||
for token in expired_keys:
|
||||
sessions.pop(token, None)
|
||||
|
||||
|
|
@ -49,12 +48,12 @@ async def is_init():
|
|||
|
||||
@asynccontextmanager
|
||||
async def lifespan(app: FastAPI):
|
||||
tasks = {}
|
||||
app.state.tasks = tasks
|
||||
# 数据库
|
||||
if await is_init():
|
||||
init_config = await load_init_config()
|
||||
await init_tortoise(
|
||||
init_config["db_type"], init_config, init_config.get("admin_password")
|
||||
)
|
||||
await init_tortoise(init_config["db_type"], init_config, init_config.get("admin_password"))
|
||||
logger.info("初始化数据库")
|
||||
# 定时器
|
||||
scheduler.add_job(clear_session, "interval", seconds=30, misfire_grace_time=10)
|
||||
|
|
@ -63,6 +62,9 @@ async def lifespan(app: FastAPI):
|
|||
|
||||
yield
|
||||
|
||||
for _, v in tasks.items():
|
||||
v.cancel()
|
||||
|
||||
scheduler.shutdown()
|
||||
logger.info("关闭定时器")
|
||||
|
||||
|
|
@ -108,9 +110,7 @@ def create_app():
|
|||
init_config["admin_password"] = request.admin_password
|
||||
init_config["is_init"] = True
|
||||
|
||||
init_success = await init_tortoise(
|
||||
request.db_type, init_config, request.admin_password
|
||||
)
|
||||
init_success = await init_tortoise(request.db_type, init_config, request.admin_password)
|
||||
|
||||
if init_success:
|
||||
await save_init_config(init_config)
|
||||
|
|
@ -127,16 +127,12 @@ def create_app():
|
|||
response_model=LoginResponse,
|
||||
)
|
||||
async def login(login_info: LoginRequest):
|
||||
user = await DB.User.filter(
|
||||
username=login_info.username, password=login_info.get_md5_password()
|
||||
).first()
|
||||
user = await DB.User.filter(username=login_info.username, password=login_info.get_md5_password(), is_deleted=0).first()
|
||||
if user:
|
||||
token = uuid.uuid4().hex
|
||||
sessions[token] = time.time(), user
|
||||
logger.info(sessions)
|
||||
return LoginResponse(
|
||||
data={"token": token, "user": user.json(del_columns=["password"])}
|
||||
)
|
||||
return LoginResponse(data={"token": token, "user": user.json(del_columns=["password"])})
|
||||
return LoginResponse(success=False, message="帐号或密码错误")
|
||||
|
||||
@app.post(
|
||||
|
|
@ -160,79 +156,112 @@ def create_app():
|
|||
return LoginResponse(data=user.json(del_columns=["password"]))
|
||||
|
||||
@app.post(
|
||||
"/api/run", tags=["Flow"], description="执行命令", response_model=BaseResponse
|
||||
"/api/run",
|
||||
tags=["Flow"],
|
||||
description="执行命令",
|
||||
response_model=BaseResponse,
|
||||
)
|
||||
async def flow_run(id: int):
|
||||
|
||||
task_id = uuid.uuid4().hex
|
||||
|
||||
async def _task(task_id):
|
||||
try:
|
||||
nodes = []
|
||||
node_0 = F.GitNode()
|
||||
node_0.set_id("01K3JHSEGBHHAVQ92RAH8M6GPX")
|
||||
nodes.append(node_0)
|
||||
node_1 = F.GitNode()
|
||||
node_1.set_id("01K3JHSHKESMWYEGSY4VNCJY4D")
|
||||
nodes.append(node_1)
|
||||
node_2 = F.GitNode()
|
||||
node_2.set_id("01K3T4GTWZJF3RT5FFH25D3T9C")
|
||||
nodes.append(node_2)
|
||||
node_3 = F.CmdNode()
|
||||
node_3.set_id("01K3T4S2HN97T1B0TPFBCFSB00")
|
||||
nodes.append(node_3)
|
||||
|
||||
git_node_0 = F.GitNode()
|
||||
git_node_0.set_id("01K3JHSEGBHHAVQ92RAH8M6GPX")
|
||||
nodes.append(git_node_0)
|
||||
|
||||
git_node_1 = F.GitNode()
|
||||
git_node_1.set_id("01K3JHSHKESMWYEGSY4VNCJY4D")
|
||||
nodes.append(git_node_1)
|
||||
|
||||
cmd_node_2 = F.CmdNode()
|
||||
cmd_node_2.set_id("01K3JHSMYM584C63QB74G3Q6X4")
|
||||
nodes.append(cmd_node_2)
|
||||
|
||||
cmd_node_3 = F.CmdNode()
|
||||
cmd_node_3.set_id("01K3JHS5HTY78Z4EYWRFW5TA77")
|
||||
nodes.append(cmd_node_3)
|
||||
logger.debug(app.state.tasks)
|
||||
|
||||
# 设置依赖关系: node0 - "true" >> node1 - "true" >> node2
|
||||
for i in range(len(nodes) - 1):
|
||||
nodes[i] - "true" >> nodes[i + 1]
|
||||
|
||||
flow = F.AsyncFlow(git_node_0)
|
||||
|
||||
flow = F.AsyncFlow(node_0)
|
||||
flow.set_params(
|
||||
{
|
||||
"01K3JHSEGBHHAVQ92RAH8M6GPX": {
|
||||
"git_path": "/Users/liruwei/Documents/code/project/dabai/dbpai-web",
|
||||
"host": "58.214.239.10",
|
||||
"user": "dgx07",
|
||||
"password": "f=bHt/.7kRo:KIXq",
|
||||
"port": 6378,
|
||||
"sudo": True,
|
||||
"git_path": "/data/wrok-flow/dbpai-web",
|
||||
"git_user": "liruwei",
|
||||
"git_password": "Wsad.8246",
|
||||
"git_url": "https://tencentgit.dabby.com.cn/dbpai/dbpai-web.git",
|
||||
"git_branch": "v3.4.2",
|
||||
"git_branch": "pai-beta-build",
|
||||
"git_clone_type": "http/https",
|
||||
"git_clone_depth": 0,
|
||||
"git_clone_depth": 1,
|
||||
},
|
||||
"01K3JHSHKESMWYEGSY4VNCJY4D": {
|
||||
"git_path": "/Users/liruwei/Documents/code/project/dabai/dbpai-server",
|
||||
"host": "58.214.239.10",
|
||||
"user": "dgx07",
|
||||
"password": "f=bHt/.7kRo:KIXq",
|
||||
"port": 6378,
|
||||
"sudo": True,
|
||||
"git_path": "/data/wrok-flow/dbpai-server",
|
||||
"git_user": "liruwei",
|
||||
"git_password": "Wsad.8246",
|
||||
"git_url": "https://tencentgit.dabby.com.cn/dbpai/dbpai-server.git",
|
||||
"git_branch": "v3.4.2",
|
||||
"git_clone_type": "http/https",
|
||||
"git_clone_depth": 0,
|
||||
"git_clone_depth": 1,
|
||||
},
|
||||
"01K3JHSMYM584C63QB74G3Q6X4": {
|
||||
"01K3T4GTWZJF3RT5FFH25D3T9C": {
|
||||
"host": "58.214.239.10",
|
||||
"user": "dgx07",
|
||||
"password": "f=bHt/.7kRo:KIXq",
|
||||
"port": 6378,
|
||||
"sudo": True,
|
||||
"git_path": "/data/wrok-flow/dbpai-jenkins",
|
||||
"git_user": "liruwei",
|
||||
"git_password": "Wsad.8246",
|
||||
"git_url": "https://tencentgit.dabby.com.cn/dbpai/dbpai-jenkins.git",
|
||||
"git_branch": "v3.4.2",
|
||||
"git_clone_type": "http/https",
|
||||
"git_clone_depth": 1,
|
||||
},
|
||||
"01K3T4S2HN97T1B0TPFBCFSB00": {
|
||||
"host": "58.214.239.10",
|
||||
"user": "dgx07",
|
||||
"password": "f=bHt/.7kRo:KIXq",
|
||||
"port": 6378,
|
||||
"sudo": True,
|
||||
"cmd": "\n".join(
|
||||
[
|
||||
"cd /Users/liruwei/Documents/code/project/dabai",
|
||||
"cp ./dbpai-web/public/dbpai-chat-widget.min.js ./dbpai-server/src/static",
|
||||
"echo '开始打包'",
|
||||
"sh build-dbpai-jenkisn.sh all 2> /dev/null",
|
||||
"echo '打包结束'",
|
||||
"pwd",
|
||||
"cd ./dbpai-jenkins",
|
||||
"git add .",
|
||||
"git commit -m 'feat: build'",
|
||||
"git push",
|
||||
"cd /data/wrok-flow/dbpai-jenkins",
|
||||
"git log",
|
||||
]
|
||||
)
|
||||
},
|
||||
"01K3JHS5HTY78Z4EYWRFW5TA77": {
|
||||
"cmd": "\n".join(["pwd"]),
|
||||
),
|
||||
},
|
||||
}
|
||||
)
|
||||
shared = {}
|
||||
await flow.run_async(shared)
|
||||
app.state.tasks.pop(task_id, None)
|
||||
except asyncio.CancelledError:
|
||||
logger.debug(f"任务被取消: {task_id}")
|
||||
app.state.tasks.pop(task_id, None)
|
||||
|
||||
logger.info(shared)
|
||||
app.state.tasks[task_id] = asyncio.create_task(_task(task_id))
|
||||
return BaseResponse(data=task_id)
|
||||
|
||||
@app.post("/api/stop", tags=["Flow"], description="停止执行指令", response_model=BaseResponse)
|
||||
async def stop_run(task_id: str):
|
||||
task = app.state.tasks.pop(task_id, None)
|
||||
if task:
|
||||
task.cancel()
|
||||
return BaseResponse()
|
||||
|
||||
return app
|
||||
|
|
|
|||
|
|
@ -1,2 +1,3 @@
|
|||
from .system import *
|
||||
from .user import *
|
||||
from .work import *
|
||||
|
|
@ -5,6 +5,7 @@ class User(BaseModel):
|
|||
username = fields.CharField(max_length=50, null=True)
|
||||
password = fields.CharField(max_length=128, null=True)
|
||||
level = fields.IntField(default=1, null=True, description="0:超级用户 1:普通用户")
|
||||
is_deleted = fields.IntField(default=0, null=True)
|
||||
|
||||
class Meta:
|
||||
table = "tb_users"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,49 @@
|
|||
from tortoise import fields
|
||||
from .system import BaseModel
|
||||
from enum import Enum
|
||||
|
||||
class DBAnalysisFileStatus(str, Enum):
|
||||
PROCESSING = "PROCESSING" # 处理中
|
||||
ERROR = "ERROR" # 处理失败
|
||||
DONE = "DONE" # 处理完成
|
||||
|
||||
|
||||
class Work(BaseModel):
|
||||
uuid = fields.CharField(max_length=128, index=True)
|
||||
creator_id = fields.IntField(null=True)
|
||||
work_title = fields.CharField(max_length=128, null=True)
|
||||
work_desc = fields.CharField(max_length=255, null=True)
|
||||
is_deleted = fields.IntField(default=0, null=True)
|
||||
|
||||
class Meta:
|
||||
table = "tb_work"
|
||||
|
||||
|
||||
class WorkLogsStatus(str, Enum):
|
||||
DEFAULT = "DEFAULT"
|
||||
RUNNING = "RUNNING"
|
||||
SUCCESS = "SUCCESS"
|
||||
FAILED = "FAILED"
|
||||
|
||||
|
||||
class WorkLogs(BaseModel):
|
||||
user_id = fields.IntField(null=True)
|
||||
work_id = fields.IntField(index=True)
|
||||
status = fields.CharField(max_length=128, null=True)
|
||||
result = fields.JSONField(default={})
|
||||
work_title = fields.CharField(max_length=128, null=True)
|
||||
work_desc = fields.CharField(max_length=255, null=True)
|
||||
is_deleted = fields.IntField(default=0)
|
||||
|
||||
class Meta:
|
||||
table = "tb_work_logs"
|
||||
|
||||
|
||||
class WorkContent(BaseModel):
|
||||
user_id = fields.IntField(null=True)
|
||||
work_id = fields.IntField(index=True)
|
||||
content = fields.JSONField(default={})
|
||||
is_deleted = fields.IntField(default=0)
|
||||
|
||||
class Meta:
|
||||
table = "tb_work_content"
|
||||
|
|
@ -1,11 +1,11 @@
|
|||
from pydantic import BaseModel
|
||||
|
||||
from typing import Any
|
||||
|
||||
class BaseResponse(BaseModel):
|
||||
data: Any | None = None
|
||||
success: bool = True
|
||||
message: str | None = None
|
||||
|
||||
|
||||
class InitConfigRequest(BaseModel):
|
||||
db_type: str = "sqlite"
|
||||
admin_password: str = "admin"
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
from core.pocket_flow import AsyncNode
|
||||
from core.logger import logger
|
||||
import asyncio
|
||||
import asyncssh
|
||||
|
||||
|
|
@ -22,7 +23,6 @@ async def async_run_cmd(
|
|||
text = line
|
||||
if not isinstance(text, str):
|
||||
text = line.decode()
|
||||
print(text)
|
||||
yield stream_type, text.rstrip()
|
||||
|
||||
if not host:
|
||||
|
|
@ -127,8 +127,10 @@ class CmdNode(AsyncNode):
|
|||
async for stream_type, line in async_run_cmd(**prep_res):
|
||||
if stream_type == "stderr":
|
||||
err.append(str(line))
|
||||
logger.debug(f'err: {line}')
|
||||
else:
|
||||
out.append(str(line))
|
||||
logger.debug(f"out: {line}")
|
||||
|
||||
err_str = "\n".join(err)
|
||||
out_str = "\n".join(out)
|
||||
|
|
|
|||
|
|
@ -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</title>
|
||||
<title>工作流</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
|
|
|||
|
|
@ -1,42 +1,14 @@
|
|||
html,
|
||||
body,
|
||||
#root {
|
||||
max-width: 1280px;
|
||||
margin: 0 auto;
|
||||
padding: 2rem;
|
||||
text-align: center;
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial,
|
||||
'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol',
|
||||
'Noto Color Emoji';
|
||||
}
|
||||
|
||||
.logo {
|
||||
height: 6em;
|
||||
padding: 1.5em;
|
||||
will-change: filter;
|
||||
transition: filter 300ms;
|
||||
}
|
||||
.logo:hover {
|
||||
filter: drop-shadow(0 0 2em #646cffaa);
|
||||
}
|
||||
.logo.react:hover {
|
||||
filter: drop-shadow(0 0 2em #61dafbaa);
|
||||
}
|
||||
|
||||
@keyframes logo-spin {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
a:nth-of-type(2) .logo {
|
||||
animation: logo-spin infinite 20s linear;
|
||||
}
|
||||
}
|
||||
|
||||
.card {
|
||||
padding: 2em;
|
||||
}
|
||||
|
||||
.read-the-docs {
|
||||
color: #888;
|
||||
div.no-select {
|
||||
user-select: none;
|
||||
}
|
||||
|
|
@ -1,14 +1,16 @@
|
|||
import { createContext, useContext, useEffect, useState } from "react";
|
||||
import { login as loginToServer, currentUser } from "../api/user";
|
||||
const AuthContext = createContext(null);
|
||||
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
export function AuthProvider({ children }) {
|
||||
const token = localStorage.getItem('_token')
|
||||
const [user, setUser] = useState(null);
|
||||
const navigate = useNavigate();
|
||||
const [loading, setLoading] = useState(token?.length > 0);
|
||||
|
||||
const getUserInfoFromToken = async () => {
|
||||
try {
|
||||
const res = await currentUser();
|
||||
if (res?.success) {
|
||||
setUser({
|
||||
|
|
@ -16,6 +18,9 @@ export function AuthProvider({ children }) {
|
|||
...res?.data
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
navigate("/login")
|
||||
}
|
||||
setLoading(false);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -13,14 +13,6 @@
|
|||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
a {
|
||||
font-weight: 500;
|
||||
color: #646cff;
|
||||
text-decoration: inherit;
|
||||
}
|
||||
a:hover {
|
||||
color: #535bf2;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
|
|
@ -49,10 +41,6 @@ button {
|
|||
button:hover {
|
||||
border-color: #646cff;
|
||||
}
|
||||
button:focus,
|
||||
button:focus-visible {
|
||||
outline: 4px auto -webkit-focus-ring-color;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: light) {
|
||||
:root {
|
||||
|
|
|
|||
|
|
@ -11,13 +11,7 @@ import { ProConfigProvider } from "@ant-design/pro-components";
|
|||
createRoot(document.getElementById('root')).render(
|
||||
<BrowserRouter>
|
||||
<AuthProvider>
|
||||
<ConfigProvider locale={zhCN} theme={{
|
||||
components: {
|
||||
Button: {
|
||||
algorithm: true
|
||||
}
|
||||
}
|
||||
}}>
|
||||
<ConfigProvider locale={zhCN}>
|
||||
<ProConfigProvider>
|
||||
<App />
|
||||
</ProConfigProvider>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,107 @@
|
|||
import { ProTable } from "@ant-design/pro-components"
|
||||
import { Button, Card, Modal, Popconfirm, Space, Steps } from "antd"
|
||||
import { useAuth } from "../../context/AuthContext";
|
||||
import { LoadingOutlined } from "@ant-design/icons";
|
||||
import { useState } from "react";
|
||||
export default () => {
|
||||
const { user } = useAuth();
|
||||
const run = async record => {
|
||||
|
||||
}
|
||||
const columns = [
|
||||
{
|
||||
dataIndex: 'id',
|
||||
title: "序号",
|
||||
},
|
||||
{
|
||||
dataIndex: 'title',
|
||||
title: "工作流",
|
||||
},
|
||||
{
|
||||
dataIndex: 'status',
|
||||
title: "状态",
|
||||
render: (_, record) => {
|
||||
return <RunningStatus record={record} />
|
||||
}
|
||||
},
|
||||
{
|
||||
dataIndex: 'user',
|
||||
title: '最后执行用户',
|
||||
},
|
||||
{
|
||||
dataIndex: 'time',
|
||||
title: '最后执行时间',
|
||||
},
|
||||
{
|
||||
dataIndex: 'result',
|
||||
title: '最后执行结果',
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
valueType: 'option',
|
||||
key: 'option',
|
||||
width: user.level === 0 ? 120 : 60,
|
||||
fixed: 'right',
|
||||
render: (_, record) => [
|
||||
<Popconfirm key='1' title="运行工作" onConfirm={() => run(record)}>
|
||||
<Button type="link" style={{ padding: '0 0', border: 'none' }}>运行</Button>
|
||||
</Popconfirm>,
|
||||
user?.level === 0 && <Button key='2' type="link" style={{ padding: '0 0', border: 'none' }}>编辑</Button>,
|
||||
user?.level === 0 && <Button key='3' type="link" danger style={{ padding: '0 0', border: 'none' }}>删除</Button>,
|
||||
]
|
||||
}
|
||||
];
|
||||
return (
|
||||
<Card title="工作台">
|
||||
<ProTable
|
||||
rowKey='id'
|
||||
search={false}
|
||||
scroll={{ x: 'max-content' }}
|
||||
options={false}
|
||||
columns={columns}
|
||||
dataSource={[{ id: 1 }]}
|
||||
/>
|
||||
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
||||
const RunningStatus = ({record}) => {
|
||||
const [open, setOpen] = useState(false);
|
||||
return <>
|
||||
<a onClick={() => setOpen(true)}>
|
||||
<Space>
|
||||
<LoadingOutlined />
|
||||
执行中
|
||||
</Space>
|
||||
</a>
|
||||
<Modal
|
||||
title={record?.title || `#${record?.id} 执行状态`}
|
||||
closable={false}
|
||||
maskClosable={false}
|
||||
open={open}
|
||||
onCancel={() => setOpen(false)}
|
||||
footer={[<Button key="cancel" onClick={() => setOpen(false)}>关闭</Button>]}
|
||||
>
|
||||
<Steps
|
||||
progressDot
|
||||
current={1}
|
||||
direction="vertical"
|
||||
items={[
|
||||
{
|
||||
title: 'Finished',
|
||||
description: 'This is a description.',
|
||||
},
|
||||
{
|
||||
title: <Space>In Progress<LoadingOutlined /></Space>,
|
||||
description: 'This is a description.',
|
||||
},
|
||||
{
|
||||
title: 'Waiting',
|
||||
description: 'This is a description.',
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</Modal>
|
||||
</>
|
||||
}
|
||||
|
|
@ -1,5 +1,7 @@
|
|||
import { Button, Card } from "antd"
|
||||
|
||||
export default () => {
|
||||
return <>
|
||||
<h1>Home</h1>
|
||||
</>
|
||||
return <Card>
|
||||
<Button type='text'>ABC</Button>
|
||||
</Card>
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
.page-layout {
|
||||
min-width: 100vw;
|
||||
min-height: 100vh;
|
||||
|
||||
.logo {
|
||||
height: 40px;
|
||||
width: calc(100% - 16px);
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
margin: 8px 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
font-size: 30px;
|
||||
font-weight: bold;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
.page-content {
|
||||
background-color: #f2f3f5;
|
||||
}
|
||||
|
|
@ -1,28 +1,28 @@
|
|||
import React, { useState } from "react";
|
||||
import { Outlet, useNavigate, useLocation } from "react-router-dom";
|
||||
import { Layout, Menu, Button, theme, Flex } from "antd";
|
||||
import { MenuFoldOutlined, MenuUnfoldOutlined, UserOutlined, VideoCameraOutlined, UploadOutlined, LoginOutlined } from "@ant-design/icons";
|
||||
import { Layout, Menu, Button, Flex, Dropdown, Steps } from "antd";
|
||||
import { MenuFoldOutlined, MenuUnfoldOutlined, UserOutlined, LoginOutlined, ProfileOutlined, NodeExpandOutlined } from "@ant-design/icons";
|
||||
import { useAuth } from "../context/AuthContext";
|
||||
import './Layout.css';
|
||||
|
||||
const { Header, Sider, Content } = Layout;
|
||||
|
||||
export default function LayoutPage() {
|
||||
const { logout } = useAuth();
|
||||
const [collapsed, setCollapsed] = useState(false);
|
||||
const { logout, user } = useAuth();
|
||||
const [collapsed, setCollapsed] = useState(true);
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
|
||||
const menuItems = [
|
||||
{ key: "1", icon: <UserOutlined />, label: "首页", path: "/welcome" },
|
||||
{ key: "2", icon: <VideoCameraOutlined />, label: "用户", path: "/user" },
|
||||
{ key: "1", icon: <NodeExpandOutlined />, label: "工作台", path: "/flow" },
|
||||
];
|
||||
|
||||
const selectedKey = menuItems.find(item => location.pathname.startsWith(item.path))?.key || "1";
|
||||
const selectedKey = menuItems.find(item => location.pathname.startsWith(item.path))?.key || "";
|
||||
|
||||
return (
|
||||
<Layout style={{ height: '100vh', width: '100vw' }}>
|
||||
<Layout className="page-layout">
|
||||
<Sider trigger={null} collapsible collapsed={collapsed} width={200}>
|
||||
<div style={{ height: 40, width: 'calc(100% - 16px)', backgroundColor: 'lightgray', margin: '8px 8px' }} />
|
||||
<Logo collapsed={collapsed} />
|
||||
<Menu
|
||||
theme="dark"
|
||||
mode="inline"
|
||||
|
|
@ -34,7 +34,7 @@ export default function LayoutPage() {
|
|||
}}
|
||||
/>
|
||||
</Sider>
|
||||
<Layout style={{ backgroundColor: '#f2f3f5' }}>
|
||||
<Layout className="page-content">
|
||||
<Header style={{ padding: 0, backgroundColor: 'white', width: "100%" }}>
|
||||
<Flex align="center" justify="space-between" style={{
|
||||
height: '100%',
|
||||
|
|
@ -42,15 +42,12 @@ export default function LayoutPage() {
|
|||
boxSizing: 'border-box'
|
||||
}}>
|
||||
<Button
|
||||
style={{ border: 'none'}}
|
||||
type='text'
|
||||
icon={collapsed ? <MenuUnfoldOutlined /> : <MenuFoldOutlined />}
|
||||
onClick={() => setCollapsed(!collapsed)}
|
||||
/>
|
||||
<Button type="text"
|
||||
icon={<LoginOutlined />}
|
||||
onClick={() => logout()}
|
||||
/>
|
||||
|
||||
<HeaderRight logout={logout} navigate={navigate} user={user} />
|
||||
</Flex>
|
||||
</Header>
|
||||
<Content
|
||||
|
|
@ -65,3 +62,47 @@ export default function LayoutPage() {
|
|||
</Layout>
|
||||
);
|
||||
}
|
||||
|
||||
const Logo = ({ collapsed }) => {
|
||||
return <div className="logo">
|
||||
{collapsed ? "F" : "FLOW"}
|
||||
</div>
|
||||
}
|
||||
|
||||
const HeaderRight = ({ logout, navigate, user }) => {
|
||||
const items = [
|
||||
{
|
||||
label: '用户信息',
|
||||
key: '2',
|
||||
icon: <ProfileOutlined />
|
||||
},
|
||||
{
|
||||
type: 'divider',
|
||||
},
|
||||
{
|
||||
label: '退出登录',
|
||||
key: '1',
|
||||
icon: <LoginOutlined/>
|
||||
},
|
||||
];
|
||||
const onClick = ({ key }) => {
|
||||
if (key === '1') {
|
||||
logout();
|
||||
}
|
||||
if (key === '2') {
|
||||
navigate('/user/detail')
|
||||
}
|
||||
}
|
||||
return (
|
||||
<Dropdown menu={{ items, onClick }} arrow>
|
||||
<a onClick={e => e.preventDefault()}>
|
||||
<Button type="text"
|
||||
style={{ border: 'none' }}
|
||||
icon={<UserOutlined />}
|
||||
>
|
||||
{user.username}
|
||||
</Button>
|
||||
</a>
|
||||
</Dropdown>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ export default () => {
|
|||
const navigate = useNavigate();
|
||||
|
||||
useEffect(() => {
|
||||
user && navigate("/welcome")
|
||||
user && navigate("/flow")
|
||||
!user && !loading && navigate("/login")
|
||||
}, [user])
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
export default () => {
|
||||
return <>
|
||||
<h1>Profile</h1>
|
||||
</>
|
||||
}
|
||||
|
|
@ -4,6 +4,8 @@ import Loading from './pages/Loading';
|
|||
import Home from "./pages/Home";
|
||||
import Login from "./pages/Login";
|
||||
import User from "./pages/User";
|
||||
import Profile from "./pages/User/Profile";
|
||||
import Flow from "./pages/Flow";
|
||||
import LayoutPage from "./pages/Layout";
|
||||
|
||||
|
||||
|
|
@ -25,8 +27,9 @@ const routes = [
|
|||
{
|
||||
element: <LayoutPage />,
|
||||
children: [
|
||||
{ path: "/welcome", element: <Home /> },
|
||||
{ path: "/User", element: <User /> },
|
||||
{ path: "/flow", element: <Flow /> },
|
||||
{ path: "/user", element: <User /> },
|
||||
{ path: "/user/detail", element: <Profile /> }
|
||||
]
|
||||
}
|
||||
],
|
||||
|
|
|
|||
Loading…
Reference in New Issue