mirror of https://github.com/openspug/spug
update
parent
89d8f30570
commit
9c0b41ba51
|
@ -1,8 +1,9 @@
|
|||
apscheduler==3.10.4
|
||||
Django >= 4.2.0, < 4.3.0
|
||||
paramiko==3.3.1
|
||||
paramiko==3.4.0
|
||||
channels >= 4.0.0, < 5.0.0
|
||||
channels-redis >= 4.1.0, < 5.0.0
|
||||
django_redis >= 5.4.0, < 6.0.0
|
||||
asgiref==3.7.2
|
||||
requests >= 2.31.0, < 3.0.0
|
||||
python-ldap==3.4.3
|
||||
|
|
|
@ -13,6 +13,8 @@
|
|||
"antd": "^5.12.4",
|
||||
"dayjs": "^1.11.10",
|
||||
"i18next": "^23.7.7",
|
||||
"mobx": "^6.12.0",
|
||||
"mobx-react-lite": "^4.0.5",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-i18next": "^13.5.0",
|
||||
|
|
|
@ -11,12 +11,12 @@ function Setting(props) {
|
|||
useEffect(() => {
|
||||
const newColumns = []
|
||||
for (const item of columns) {
|
||||
if (state[item.key] ?? !item.hidden) {
|
||||
if (state[item.title] ?? !item.hidden) {
|
||||
newColumns.push(item)
|
||||
}
|
||||
}
|
||||
setCols(newColumns)
|
||||
}, [state]);
|
||||
}, [columns, state]);
|
||||
|
||||
function handleChange(e) {
|
||||
const { value, checked } = e.target
|
||||
|
@ -27,7 +27,7 @@ function Setting(props) {
|
|||
|
||||
function handleReset() {
|
||||
setState({})
|
||||
app.updateStable(skey, {})
|
||||
app.updateStable(skey, null)
|
||||
}
|
||||
|
||||
return (
|
||||
|
@ -43,9 +43,9 @@ function Setting(props) {
|
|||
<Flex vertical gap="small">
|
||||
{columns.map((item, index) => (
|
||||
<Checkbox
|
||||
value={item.key}
|
||||
value={item.title}
|
||||
key={index}
|
||||
checked={state[item.key] ?? !item.hidden}
|
||||
checked={state[item.title] ?? !item.hidden}
|
||||
onChange={handleChange}>
|
||||
{item.title}
|
||||
</Checkbox>
|
||||
|
|
|
@ -1,18 +1,87 @@
|
|||
import {useRef, useState} from 'react'
|
||||
import {Card, Table, Flex, Divider} from 'antd'
|
||||
import { useRef, useState, useEffect } from 'react'
|
||||
import { Card, Table, Flex, Divider, Checkbox, Button, Input, Tag } from 'antd'
|
||||
import { IoExpand, IoContract, IoReloadOutline } from 'react-icons/io5'
|
||||
import {clsNames} from '@/libs'
|
||||
import { useImmer } from 'use-immer'
|
||||
import { clsNames, includes } from '@/libs'
|
||||
import Setting from './Setting.jsx'
|
||||
import css from './index.module.scss'
|
||||
|
||||
function Stable(props) {
|
||||
function STable(props) {
|
||||
const { skey, loading, columns, dataSource, actions, pagination } = props
|
||||
const ref = useRef();
|
||||
const ref = useRef()
|
||||
const sMap = useRef({})
|
||||
const [sColumns, setSColumns] = useState([])
|
||||
const [cols, setCols] = useState([])
|
||||
const [isFull, setIsFull] = useState(false)
|
||||
const [filters, updateFilters] = useImmer({})
|
||||
|
||||
if (!skey) throw new Error('skey is required')
|
||||
|
||||
useEffect(() => {
|
||||
const newColumns = []
|
||||
for (const item of columns) {
|
||||
const key = item.dataIndex
|
||||
if (item.filterKey) {
|
||||
let inputRef = null
|
||||
item.onFilter = (value, record) => includes(record[key], value)
|
||||
item.filterDropdown = (x) => {
|
||||
sMap.current[key] = x
|
||||
return (
|
||||
<div style={{ padding: 8, width: 200 }}>
|
||||
<Input.Search
|
||||
allowClear
|
||||
enterButton
|
||||
placeholder="请输入"
|
||||
value={x.selectedKeys[0] ?? ''}
|
||||
ref={ref => inputRef = ref}
|
||||
onChange={e => x.setSelectedKeys(e.target.value ? [e.target.value] : [])}
|
||||
onSearch={v => handleSearch(key, v)}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
item.onFilterDropdownOpenChange = (visible) => {
|
||||
if (visible) {
|
||||
setTimeout(() => inputRef.focus(), 100)
|
||||
}
|
||||
}
|
||||
} else if (item.filterItems) {
|
||||
item.onFilter = (value, record) => record[key] === value
|
||||
item.filterDropdown = (x) => {
|
||||
sMap.current[key] = x
|
||||
return (
|
||||
<div className={css.filterItems}>
|
||||
<Checkbox.Group
|
||||
options={item.filterItems}
|
||||
value={x.selectedKeys}
|
||||
onChange={x.setSelectedKeys} />
|
||||
<Divider style={{ margin: '0' }} />
|
||||
<Flex justify="space-between" className={css.action}>
|
||||
<Button size="small" type="link" disabled={x.selectedKeys.length === 0} onClick={() => x.setSelectedKeys([])}>{t('重置')}</Button>
|
||||
<Button size="small" type="primary" onClick={() => handleSearch(key, x.selectedKeys)}>{t('确定')}</Button>
|
||||
</Flex>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
newColumns.push(item)
|
||||
}
|
||||
setSColumns(newColumns)
|
||||
}, [columns])
|
||||
|
||||
function handleSearch(key, v) {
|
||||
const x = sMap.current[key]
|
||||
updateFilters(draft => {
|
||||
if (Array.isArray(v)) {
|
||||
v.length ? draft[key] = v : delete draft[key]
|
||||
} else {
|
||||
v ? draft[key] = v : delete draft[key]
|
||||
}
|
||||
})
|
||||
if (!v) x.setSelectedKeys([])
|
||||
x.confirm()
|
||||
}
|
||||
|
||||
function handleFullscreen() {
|
||||
if (ref.current && document.fullscreenEnabled) {
|
||||
if (document.fullscreenElement) {
|
||||
|
@ -25,14 +94,34 @@ function Stable(props) {
|
|||
}
|
||||
}
|
||||
|
||||
function SearchItem(props) {
|
||||
const { cKey, value } = props
|
||||
const column = columns.find(item => item.dataIndex === cKey)
|
||||
|
||||
return (
|
||||
<Tag closable bordered={false} color="blue" onClose={() => handleSearch(cKey, '')} className={css.search}>
|
||||
{column.title}: {Array.isArray(value) ? value.join(' | ') : value}
|
||||
</Tag>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<Card ref={ref} className={clsNames(css.stable, props.className)} style={props.style}>
|
||||
<Flex align="center" justify="flex-end" className={css.toolbar}>
|
||||
<Flex align="center" justify="space-between" className={css.toolbar}>
|
||||
{Object.keys(filters).length ? (
|
||||
<Flex>
|
||||
{Object.entries(filters).map(([key, value]) => (
|
||||
<SearchItem key={key} cKey={key} value={value} />
|
||||
))}
|
||||
</Flex>
|
||||
) : (
|
||||
<div className={css.title}>{props.title}</div>
|
||||
)}
|
||||
<Flex gap="middle" align="center">
|
||||
{actions}
|
||||
{actions.length ? <Divider type="vertical" /> : null}
|
||||
<IoReloadOutline className={css.icon} onClick={props.onReload} />
|
||||
<Setting className={css.icon} skey={skey} columns={columns} setCols={setCols}/>
|
||||
<Setting className={css.icon} skey={skey} columns={sColumns} setCols={setCols} />
|
||||
{isFull ? (
|
||||
<IoContract className={css.icon} onClick={handleFullscreen} />
|
||||
) : (
|
||||
|
@ -45,7 +134,7 @@ function Stable(props) {
|
|||
)
|
||||
}
|
||||
|
||||
Stable.defaultProps = {
|
||||
STable.defaultProps = {
|
||||
sKey: null,
|
||||
loading: false,
|
||||
actions: [],
|
||||
|
@ -60,4 +149,4 @@ Stable.defaultProps = {
|
|||
},
|
||||
}
|
||||
|
||||
export default Stable
|
||||
export default STable
|
|
@ -4,12 +4,35 @@
|
|||
}
|
||||
}
|
||||
|
||||
.search {
|
||||
height: 28px;
|
||||
line-height: 28px;
|
||||
}
|
||||
|
||||
.toolbar {
|
||||
margin-bottom: 12px;
|
||||
|
||||
.title {
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.icon {
|
||||
font-size: 18px;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.filterItems {
|
||||
min-width: 150px;
|
||||
|
||||
:global(.ant-checkbox-group) {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 8px 16px;
|
||||
}
|
||||
|
||||
.action {
|
||||
padding: 8px 16px 8px 8px;
|
||||
}
|
||||
}
|
|
@ -38,6 +38,7 @@ class App {
|
|||
|
||||
updateStable(key, data) {
|
||||
this.stable[key] = data;
|
||||
if (data === null) delete this.stable[key];
|
||||
localStorage.setItem('stable', JSON.stringify(this.stable));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,61 @@
|
|||
import {} from 'react'
|
||||
import {Card, Tree} from 'antd'
|
||||
import css from './index.module.scss'
|
||||
|
||||
|
||||
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',
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
return (
|
||||
<Card title="分组列表" className={css.group}>
|
||||
<Tree.DirectoryTree
|
||||
treeData={dataSource}
|
||||
/>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
||||
export default Group
|
|
@ -0,0 +1,14 @@
|
|||
.group {
|
||||
flex: 1;
|
||||
min-width: 200px;
|
||||
max-width: 300px;
|
||||
margin-right: -1px;
|
||||
border-top-right-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
}
|
||||
|
||||
.table {
|
||||
flex: 3;
|
||||
border-top-left-radius: 0;
|
||||
border-bottom-left-radius: 0;
|
||||
}
|
Loading…
Reference in New Issue