pull/661/head
vapao 2024-02-27 00:09:33 +08:00
parent 89d8f30570
commit 9c0b41ba51
10 changed files with 235 additions and 44 deletions

View File

@ -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

View File

@ -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",

View File

@ -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>

View File

@ -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

View File

@ -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;
}
}

View File

@ -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));
}
}

View File

View File

@ -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

View File

@ -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;
}