pull/661/head
vapao 2024-02-27 18:16:43 +08:00
parent 9c0b41ba51
commit 756599e2e2
8 changed files with 159 additions and 54 deletions

View File

@ -10,7 +10,7 @@
"preview": "vite preview"
},
"dependencies": {
"antd": "^5.12.4",
"antd": "^5.14.1",
"dayjs": "^1.11.10",
"i18next": "^23.7.7",
"mobx": "^6.12.0",

View File

@ -35,6 +35,9 @@ function App() {
headerHeight: 48,
footerPadding: 16
},
Tree: {
titleHeight: 30
}
},
}}>
<IconContext.Provider value={{ className: 'anticon' }}>

View File

@ -5,7 +5,6 @@
}
.search {
height: 28px;
line-height: 28px;
}

View File

@ -1,7 +1,7 @@
import useSWR from 'swr'
import {message} from 'antd'
import { message } from 'antd'
import app from '@/libs/app.js'
import {redirect} from 'react-router-dom'
import { redirect } from 'react-router-dom'
function fetcher(resource, init) {
return fetch(resource, init)
@ -33,7 +33,7 @@ function SWRGet(url, params) {
}
function request(method, url, params) {
const init = {method, headers: {'X-Token': app.accessToken}}
const init = { method, headers: { 'X-Token': app.access_token } }
if (['GET', 'DELETE'].includes(method)) {
if (params) url = `${url}?${new URLSearchParams(params).toString()}`
return fetcher(url, init)

View File

@ -54,4 +54,15 @@ export function loadJSONStorage(key, defaultValue = null) {
}
}
return defaultValue
}
// 递归查找树节点
export function findNodeByKey(array, key) {
for (let item of array) {
if (item.key === key) return item
if (item.children) {
let tmp = findNodeByKey(item.children, key)
if (tmp) return tmp
}
}
}

View File

@ -1,58 +1,130 @@
import {} from 'react'
import {Card, Tree} from 'antd'
import { useRef, useState, useEffect } from 'react'
import { Card, Tree, Dropdown, Input } from 'antd'
import { FaServer } from 'react-icons/fa6'
import { IoMdMore } from 'react-icons/io'
import { AiOutlineFolder, AiOutlineFolderAdd, AiOutlineEdit, AiOutlineFileAdd, AiOutlineScissor, AiOutlineClose, AiOutlineDelete } from 'react-icons/ai'
import { useImmer } from 'use-immer'
import { http, findNodeByKey } from '@/libs'
import css from './index.module.scss'
let clickNode = null
function Group() {
const dataSource = [
{
title: 'parent 1-0',
key: '0-0-0',
children: [
{
title: 'leaf',
key: '0-0-0-0',
},
{
title: 'leaf',
key: '0-0-0-1',
},
{
title: 'leaf',
key: '0-0-0-2',
},
],
},
{
title: 'parent 1-1',
key: '0-0-1',
children: [
{
title: 'leaf',
key: '0-0-1-0',
},
],
},
{
title: 'parent 1-2',
key: '0-0-2',
children: [
{
title: 'leaf',
key: '0-0-2-0',
},
{
title: 'leaf',
key: '0-0-2-1',
},
],
},
const inputRef = useRef(null)
const [expandedKeys, setExpandedKeys] = useState([])
const [treeData, updateTreeData] = useImmer([])
const menuItems = [
{ label: '新建根分组', key: 'newRoot', icon: <AiOutlineFolder size={18} /> },
{ label: '新建子分组', key: 'newChild', icon: <AiOutlineFolderAdd size={18} /> },
{ label: '重命名', key: 'rename', icon: <AiOutlineEdit size={18} /> },
{ type: 'divider' },
{ label: '添加主机', key: 'addHost', icon: <AiOutlineFileAdd size={18} /> },
{ label: '移动主机', key: 'moveHost', icon: <AiOutlineScissor size={18} /> },
{ label: '删除主机', key: 'deleteHost', icon: <AiOutlineClose size={18} /> },
{ type: 'divider' },
{ label: '删除此分组', key: 'deleteGroup', danger: true, icon: <AiOutlineDelete size={18} /> },
]
useEffect(() => {
fetchData()
// eslint-disable-next-line
}, [])
function fetchData() {
http.get('/api/host/group/')
.then(res => {
updateTreeData(res.treeData)
})
}
function handleNodeClick(e, node) {
e.stopPropagation()
clickNode = node
}
function handleMenuClick({ key, domEvent }) {
domEvent.stopPropagation()
console.log(key, clickNode.key)
switch (key) {
case 'newRoot':
updateTreeData(draft => {
draft.unshift({ key, action: key, selectable: false })
})
break
case 'newChild':
updateTreeData(draft => {
const node = findNodeByKey(draft, clickNode.key)
if (!node) return
if (!node.children) node.children = []
node.children.unshift({ key, action: key, selectable: false })
})
if (![expandedKeys].includes(clickNode.key)) {
setExpandedKeys([...expandedKeys, clickNode.key])
}
break
case 'rename':
updateTreeData(draft => {
const node = findNodeByKey(draft, clickNode.key)
if (!node) return
node.action = key
node.selectable = false
})
break
case 'addHost':
console.log('添加主机')
break
case 'moveHost':
console.log('移动主机')
break
case 'deleteHost':
console.log('删除主机')
break
case 'deleteGroup':
console.log('删除此分组')
break
default:
break
}
if (['newRoot', 'newChild', 'rename'].includes(key)) {
setTimeout(() => {
inputRef.current.focus()
}, 300)
}
}
function handleInputSubmit(e) {
console.log('提交: ', e.target.value)
}
function titleRender(node) {
return ['newRoot', 'newChild', 'rename'].includes(node.action) ? (
<Input ref={inputRef} defaultValue={node.title} onPressEnter={handleInputSubmit} onBlur={handleInputSubmit} />
) : (
<div className={css.treeTitle}>
<FaServer />
<div className={css.title}>{node.title}</div>
<div onClick={e => handleNodeClick(e, node)}>
<Dropdown menu={{ items: menuItems, onClick: handleMenuClick }} trigger={['click']}>
<div className={css.more}>
<IoMdMore />
</div>
</Dropdown>
</div>
</div>
)
}
return (
<Card title="分组列表" className={css.group}>
<Tree.DirectoryTree
treeData={dataSource}
defaultExpandParent
showIcon={false}
treeData={treeData}
expandedKeys={expandedKeys}
expandAction="doubleClick"
titleRender={titleRender}
onExpand={keys => setExpandedKeys(keys)}
/>
</Card>
)

View File

@ -5,6 +5,26 @@
margin-right: -1px;
border-top-right-radius: 0;
border-bottom-right-radius: 0;
.treeTitle {
display: flex;
flex-direction: row;
align-items: center;
.title {
flex: 1;
margin-left: 6px;
}
.more {
width: 30px;
height: 30px;
display: flex;
justify-content: center;
align-items: center;
margin-right: -4px;
}
}
}
.table {

View File

@ -8,8 +8,8 @@ import {useNavigate} from 'react-router-dom'
import {Form, Input, Button, Tabs, Modal, message} from 'antd';
import {AiOutlineUser, AiOutlineLock, AiOutlineCopyright, AiOutlineGithub, AiOutlineMail} from 'react-icons/ai'
import styles from './login.module.css';
import {http, session} from '@/libs';
import logo from '@/assets/logo-spug-txt.png';
import {http, app} from '@/libs';
import logo from '@/assets/spug-default.png';
export default function Login() {
const navigate = useNavigate();
@ -59,7 +59,7 @@ export default function Login() {
function doLogin(data) {
localStorage.setItem('login_type', loginType);
session.update(data)
app.updateSession(data)
navigate('/home', {replace: true})
}