mirror of https://github.com/openspug/spug
update
parent
9c0b41ba51
commit
756599e2e2
|
@ -10,7 +10,7 @@
|
||||||
"preview": "vite preview"
|
"preview": "vite preview"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"antd": "^5.12.4",
|
"antd": "^5.14.1",
|
||||||
"dayjs": "^1.11.10",
|
"dayjs": "^1.11.10",
|
||||||
"i18next": "^23.7.7",
|
"i18next": "^23.7.7",
|
||||||
"mobx": "^6.12.0",
|
"mobx": "^6.12.0",
|
||||||
|
|
|
@ -35,6 +35,9 @@ function App() {
|
||||||
headerHeight: 48,
|
headerHeight: 48,
|
||||||
footerPadding: 16
|
footerPadding: 16
|
||||||
},
|
},
|
||||||
|
Tree: {
|
||||||
|
titleHeight: 30
|
||||||
|
}
|
||||||
},
|
},
|
||||||
}}>
|
}}>
|
||||||
<IconContext.Provider value={{ className: 'anticon' }}>
|
<IconContext.Provider value={{ className: 'anticon' }}>
|
||||||
|
|
|
@ -5,7 +5,6 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.search {
|
.search {
|
||||||
height: 28px;
|
|
||||||
line-height: 28px;
|
line-height: 28px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import useSWR from 'swr'
|
import useSWR from 'swr'
|
||||||
import {message} from 'antd'
|
import { message } from 'antd'
|
||||||
import app from '@/libs/app.js'
|
import app from '@/libs/app.js'
|
||||||
import {redirect} from 'react-router-dom'
|
import { redirect } from 'react-router-dom'
|
||||||
|
|
||||||
function fetcher(resource, init) {
|
function fetcher(resource, init) {
|
||||||
return fetch(resource, init)
|
return fetch(resource, init)
|
||||||
|
@ -33,7 +33,7 @@ function SWRGet(url, params) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function request(method, 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 (['GET', 'DELETE'].includes(method)) {
|
||||||
if (params) url = `${url}?${new URLSearchParams(params).toString()}`
|
if (params) url = `${url}?${new URLSearchParams(params).toString()}`
|
||||||
return fetcher(url, init)
|
return fetcher(url, init)
|
||||||
|
|
|
@ -55,3 +55,14 @@ export function loadJSONStorage(key, defaultValue = null) {
|
||||||
}
|
}
|
||||||
return defaultValue
|
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 { useRef, useState, useEffect } from 'react'
|
||||||
import {Card, Tree} from 'antd'
|
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'
|
import css from './index.module.scss'
|
||||||
|
|
||||||
|
let clickNode = null
|
||||||
|
|
||||||
function Group() {
|
function Group() {
|
||||||
const dataSource = [
|
const inputRef = useRef(null)
|
||||||
{
|
const [expandedKeys, setExpandedKeys] = useState([])
|
||||||
title: 'parent 1-0',
|
const [treeData, updateTreeData] = useImmer([])
|
||||||
key: '0-0-0',
|
|
||||||
children: [
|
const menuItems = [
|
||||||
{
|
{ label: '新建根分组', key: 'newRoot', icon: <AiOutlineFolder size={18} /> },
|
||||||
title: 'leaf',
|
{ label: '新建子分组', key: 'newChild', icon: <AiOutlineFolderAdd size={18} /> },
|
||||||
key: '0-0-0-0',
|
{ label: '重命名', key: 'rename', icon: <AiOutlineEdit size={18} /> },
|
||||||
},
|
{ type: 'divider' },
|
||||||
{
|
{ label: '添加主机', key: 'addHost', icon: <AiOutlineFileAdd size={18} /> },
|
||||||
title: 'leaf',
|
{ label: '移动主机', key: 'moveHost', icon: <AiOutlineScissor size={18} /> },
|
||||||
key: '0-0-0-1',
|
{ label: '删除主机', key: 'deleteHost', icon: <AiOutlineClose size={18} /> },
|
||||||
},
|
{ type: 'divider' },
|
||||||
{
|
{ label: '删除此分组', key: 'deleteGroup', danger: true, icon: <AiOutlineDelete size={18} /> },
|
||||||
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',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
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 (
|
return (
|
||||||
<Card title="分组列表" className={css.group}>
|
<Card title="分组列表" className={css.group}>
|
||||||
<Tree.DirectoryTree
|
<Tree.DirectoryTree
|
||||||
treeData={dataSource}
|
defaultExpandParent
|
||||||
|
showIcon={false}
|
||||||
|
treeData={treeData}
|
||||||
|
expandedKeys={expandedKeys}
|
||||||
|
expandAction="doubleClick"
|
||||||
|
titleRender={titleRender}
|
||||||
|
onExpand={keys => setExpandedKeys(keys)}
|
||||||
/>
|
/>
|
||||||
</Card>
|
</Card>
|
||||||
)
|
)
|
||||||
|
|
|
@ -5,6 +5,26 @@
|
||||||
margin-right: -1px;
|
margin-right: -1px;
|
||||||
border-top-right-radius: 0;
|
border-top-right-radius: 0;
|
||||||
border-bottom-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 {
|
.table {
|
||||||
|
|
|
@ -8,8 +8,8 @@ import {useNavigate} from 'react-router-dom'
|
||||||
import {Form, Input, Button, Tabs, Modal, message} from 'antd';
|
import {Form, Input, Button, Tabs, Modal, message} from 'antd';
|
||||||
import {AiOutlineUser, AiOutlineLock, AiOutlineCopyright, AiOutlineGithub, AiOutlineMail} from 'react-icons/ai'
|
import {AiOutlineUser, AiOutlineLock, AiOutlineCopyright, AiOutlineGithub, AiOutlineMail} from 'react-icons/ai'
|
||||||
import styles from './login.module.css';
|
import styles from './login.module.css';
|
||||||
import {http, session} from '@/libs';
|
import {http, app} from '@/libs';
|
||||||
import logo from '@/assets/logo-spug-txt.png';
|
import logo from '@/assets/spug-default.png';
|
||||||
|
|
||||||
export default function Login() {
|
export default function Login() {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
@ -59,7 +59,7 @@ export default function Login() {
|
||||||
|
|
||||||
function doLogin(data) {
|
function doLogin(data) {
|
||||||
localStorage.setItem('login_type', loginType);
|
localStorage.setItem('login_type', loginType);
|
||||||
session.update(data)
|
app.updateSession(data)
|
||||||
navigate('/home', {replace: true})
|
navigate('/home', {replace: true})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue