mirror of https://github.com/openspug/spug
update
parent
9c0b41ba51
commit
756599e2e2
|
@ -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",
|
||||
|
|
|
@ -35,6 +35,9 @@ function App() {
|
|||
headerHeight: 48,
|
||||
footerPadding: 16
|
||||
},
|
||||
Tree: {
|
||||
titleHeight: 30
|
||||
}
|
||||
},
|
||||
}}>
|
||||
<IconContext.Provider value={{ className: 'anticon' }}>
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
}
|
||||
|
||||
.search {
|
||||
height: 28px;
|
||||
line-height: 28px;
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -55,3 +55,14 @@ 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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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>
|
||||
)
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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})
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue