mirror of
https://github.com/songquanpeng/one-api.git
synced 2025-11-16 21:23:44 +08:00
✨ feat: support paging / sorting (#64)
* ✨ feat: support paging / sorting * 🔥 del: delete unused files
This commit is contained in:
47
web/src/ui-component/TableHead.js
Normal file
47
web/src/ui-component/TableHead.js
Normal file
@@ -0,0 +1,47 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import { TableCell, TableHead, TableRow, TableSortLabel } from '@mui/material';
|
||||
|
||||
const KeywordTableHead = ({ order, orderBy, headLabel, onRequestSort }) => {
|
||||
const onSort = (property) => (event) => {
|
||||
onRequestSort(event, property);
|
||||
};
|
||||
|
||||
return (
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
{headLabel.map((headCell) =>
|
||||
headCell.hide && headCell.hide === true ? null : (
|
||||
<TableCell
|
||||
key={headCell.id}
|
||||
align={headCell.align || 'left'}
|
||||
// sortDirection={orderBy === headCell.id ? order : false}
|
||||
sx={{ width: headCell.width, minWidth: headCell.minWidth }}
|
||||
>
|
||||
{headCell.disableSort ? (
|
||||
headCell.label
|
||||
) : (
|
||||
<TableSortLabel
|
||||
hideSortIcon
|
||||
active={orderBy === headCell.id}
|
||||
direction={orderBy === headCell.id ? order : 'asc'}
|
||||
onClick={onSort(headCell.id)}
|
||||
>
|
||||
{headCell.label}
|
||||
</TableSortLabel>
|
||||
)}
|
||||
</TableCell>
|
||||
)
|
||||
)}
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
);
|
||||
};
|
||||
|
||||
export default KeywordTableHead;
|
||||
|
||||
KeywordTableHead.propTypes = {
|
||||
order: PropTypes.oneOf(['asc', 'desc']),
|
||||
orderBy: PropTypes.string,
|
||||
onRequestSort: PropTypes.func,
|
||||
headLabel: PropTypes.array
|
||||
};
|
||||
@@ -9,7 +9,7 @@ import { IconSearch } from '@tabler/icons-react';
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
export default function TableToolBar({ filterName, handleFilterName, placeholder }) {
|
||||
export default function TableToolBar({ placeholder }) {
|
||||
const theme = useTheme();
|
||||
const grey500 = theme.palette.grey[500];
|
||||
|
||||
@@ -24,11 +24,10 @@ export default function TableToolBar({ filterName, handleFilterName, placeholder
|
||||
>
|
||||
<OutlinedInput
|
||||
id="keyword"
|
||||
name="keyword"
|
||||
sx={{
|
||||
minWidth: '100%'
|
||||
}}
|
||||
value={filterName}
|
||||
onChange={handleFilterName}
|
||||
placeholder={placeholder}
|
||||
startAdornment={
|
||||
<InputAdornment position="start">
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
import { TableCell, TableHead, TableRow } from '@mui/material';
|
||||
|
||||
const ChannelTableHead = () => {
|
||||
return (
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell>ID</TableCell>
|
||||
<TableCell>名称</TableCell>
|
||||
<TableCell>分组</TableCell>
|
||||
<TableCell>类型</TableCell>
|
||||
<TableCell>状态</TableCell>
|
||||
<TableCell>响应时间</TableCell>
|
||||
<TableCell>余额</TableCell>
|
||||
<TableCell>优先级</TableCell>
|
||||
<TableCell>操作</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
);
|
||||
};
|
||||
|
||||
export default ChannelTableHead;
|
||||
@@ -15,83 +15,53 @@ import useMediaQuery from '@mui/material/useMediaQuery';
|
||||
|
||||
import { Button, IconButton, Card, Box, Stack, Container, Typography, Divider } from '@mui/material';
|
||||
import ChannelTableRow from './component/TableRow';
|
||||
import ChannelTableHead from './component/TableHead';
|
||||
import KeywordTableHead from 'ui-component/TableHead';
|
||||
import TableToolBar from 'ui-component/TableToolBar';
|
||||
import { API } from 'utils/api';
|
||||
import { ITEMS_PER_PAGE } from 'constants';
|
||||
import { IconRefresh, IconHttpDelete, IconPlus, IconBrandSpeedtest, IconCoinYuan } from '@tabler/icons-react';
|
||||
import EditeModal from './component/EditModal';
|
||||
import { ITEMS_PER_PAGE } from 'constants';
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// CHANNEL_OPTIONS,
|
||||
export default function ChannelPage() {
|
||||
const [channels, setChannels] = useState([]);
|
||||
const [activePage, setActivePage] = useState(0);
|
||||
const [searching, setSearching] = useState(false);
|
||||
const [page, setPage] = useState(0);
|
||||
const [order, setOrder] = useState('desc');
|
||||
const [orderBy, setOrderBy] = useState('id');
|
||||
const [rowsPerPage, setRowsPerPage] = useState(ITEMS_PER_PAGE);
|
||||
const [listCount, setListCount] = useState(0);
|
||||
const [searchKeyword, setSearchKeyword] = useState('');
|
||||
const [searching, setSearching] = useState(false);
|
||||
const [channels, setChannels] = useState([]);
|
||||
const [refreshFlag, setRefreshFlag] = useState(false);
|
||||
|
||||
const theme = useTheme();
|
||||
const matchUpMd = useMediaQuery(theme.breakpoints.up('sm'));
|
||||
const [openModal, setOpenModal] = useState(false);
|
||||
const [editChannelId, setEditChannelId] = useState(0);
|
||||
|
||||
const loadChannels = async (startIdx) => {
|
||||
setSearching(true);
|
||||
try {
|
||||
const res = await API.get(`/api/channel/?p=${startIdx}`);
|
||||
const { success, message, data } = res.data;
|
||||
if (success) {
|
||||
if (startIdx === 0) {
|
||||
setChannels(data);
|
||||
} else {
|
||||
let newChannels = [...channels];
|
||||
newChannels.splice(startIdx * ITEMS_PER_PAGE, data.length, ...data);
|
||||
setChannels(newChannels);
|
||||
}
|
||||
} else {
|
||||
showError(message);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
const handleSort = (event, id) => {
|
||||
const isAsc = orderBy === id && order === 'asc';
|
||||
if (id !== '') {
|
||||
setOrder(isAsc ? 'desc' : 'asc');
|
||||
setOrderBy(id);
|
||||
}
|
||||
|
||||
setSearching(false);
|
||||
};
|
||||
|
||||
const onPaginationChange = (event, activePage) => {
|
||||
(async () => {
|
||||
if (activePage === Math.ceil(channels.length / ITEMS_PER_PAGE)) {
|
||||
// In this case we have to load more data and then append them.
|
||||
await loadChannels(activePage);
|
||||
}
|
||||
setActivePage(activePage);
|
||||
})();
|
||||
const handleChangePage = (event, newPage) => {
|
||||
setPage(newPage);
|
||||
};
|
||||
|
||||
const handleChangeRowsPerPage = (event) => {
|
||||
setPage(0);
|
||||
setRowsPerPage(parseInt(event.target.value, 10));
|
||||
};
|
||||
|
||||
const searchChannels = async (event) => {
|
||||
event.preventDefault();
|
||||
if (searchKeyword === '') {
|
||||
await loadChannels(0);
|
||||
setActivePage(0);
|
||||
return;
|
||||
}
|
||||
setSearching(true);
|
||||
try {
|
||||
const res = await API.get(`/api/channel/search?keyword=${searchKeyword}`);
|
||||
const { success, message, data } = res.data;
|
||||
if (success) {
|
||||
setChannels(data);
|
||||
setActivePage(0);
|
||||
} else {
|
||||
showError(message);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
setSearching(false);
|
||||
};
|
||||
|
||||
const handleSearchKeyword = (event) => {
|
||||
setSearchKeyword(event.target.value);
|
||||
const formData = new FormData(event.target);
|
||||
setPage(0);
|
||||
setSearchKeyword(formData.get('keyword'));
|
||||
};
|
||||
|
||||
const manageChannel = async (id, action, value) => {
|
||||
@@ -141,7 +111,9 @@ export default function ChannelPage() {
|
||||
|
||||
// 处理刷新
|
||||
const handleRefresh = async () => {
|
||||
await loadChannels(activePage);
|
||||
setOrderBy('id');
|
||||
setOrder('desc');
|
||||
setRefreshFlag(!refreshFlag);
|
||||
};
|
||||
|
||||
// 处理测试所有启用渠道
|
||||
@@ -210,14 +182,36 @@ export default function ChannelPage() {
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
loadChannels(0)
|
||||
.then()
|
||||
.catch((reason) => {
|
||||
showError(reason);
|
||||
const fetchData = async (page, rowsPerPage, keyword, order, orderBy) => {
|
||||
setSearching(true);
|
||||
try {
|
||||
if (orderBy) {
|
||||
orderBy = order === 'desc' ? '-' + orderBy : orderBy;
|
||||
}
|
||||
const res = await API.get(`/api/channel/`, {
|
||||
params: {
|
||||
page: page + 1,
|
||||
size: rowsPerPage,
|
||||
keyword: keyword,
|
||||
order: orderBy
|
||||
}
|
||||
});
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
const { success, message, data } = res.data;
|
||||
if (success) {
|
||||
setListCount(data.total_count);
|
||||
setChannels(data.data);
|
||||
} else {
|
||||
showError(message);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
setSearching(false);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchData(page, rowsPerPage, searchKeyword, order, orderBy);
|
||||
}, [page, rowsPerPage, searchKeyword, order, orderBy, refreshFlag]);
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -237,7 +231,7 @@ export default function ChannelPage() {
|
||||
</Stack>
|
||||
<Card>
|
||||
<Box component="form" onSubmit={searchChannels} noValidate>
|
||||
<TableToolBar filterName={searchKeyword} handleFilterName={handleSearchKeyword} placeholder={'搜索渠道的 ID,名称和密钥 ...'} />
|
||||
<TableToolBar placeholder={'搜索渠道的 ID,名称和密钥 ...'} />
|
||||
</Box>
|
||||
<Toolbar
|
||||
sx={{
|
||||
@@ -292,9 +286,24 @@ export default function ChannelPage() {
|
||||
<PerfectScrollbar component="div">
|
||||
<TableContainer sx={{ overflow: 'unset' }}>
|
||||
<Table sx={{ minWidth: 800 }}>
|
||||
<ChannelTableHead />
|
||||
<KeywordTableHead
|
||||
order={order}
|
||||
orderBy={orderBy}
|
||||
onRequestSort={handleSort}
|
||||
headLabel={[
|
||||
{ id: 'id', label: 'ID', disableSort: false },
|
||||
{ id: 'name', label: '名称', disableSort: false },
|
||||
{ id: 'group', label: '分组', disableSort: true },
|
||||
{ id: 'type', label: '类型', disableSort: false },
|
||||
{ id: 'status', label: '状态', disableSort: false },
|
||||
{ id: 'response_time', label: '响应时间', disableSort: false },
|
||||
{ id: 'balance', label: '余额', disableSort: false },
|
||||
{ id: 'priority', label: '优先级', disableSort: false },
|
||||
{ id: 'action', label: '操作', disableSort: true }
|
||||
]}
|
||||
/>
|
||||
<TableBody>
|
||||
{channels.slice(activePage * ITEMS_PER_PAGE, (activePage + 1) * ITEMS_PER_PAGE).map((row) => (
|
||||
{channels.map((row) => (
|
||||
<ChannelTableRow
|
||||
item={row}
|
||||
manageChannel={manageChannel}
|
||||
@@ -308,12 +317,15 @@ export default function ChannelPage() {
|
||||
</TableContainer>
|
||||
</PerfectScrollbar>
|
||||
<TablePagination
|
||||
page={activePage}
|
||||
page={page}
|
||||
component="div"
|
||||
count={channels.length + (channels.length % ITEMS_PER_PAGE === 0 ? 1 : 0)}
|
||||
rowsPerPage={ITEMS_PER_PAGE}
|
||||
onPageChange={onPaginationChange}
|
||||
rowsPerPageOptions={[ITEMS_PER_PAGE]}
|
||||
count={listCount}
|
||||
rowsPerPage={rowsPerPage}
|
||||
onPageChange={handleChangePage}
|
||||
rowsPerPageOptions={[10, 25, 30]}
|
||||
onRowsPerPageChange={handleChangeRowsPerPage}
|
||||
showFirstButton
|
||||
showLastButton
|
||||
/>
|
||||
</Card>
|
||||
<EditeModal open={openModal} onCancel={handleCloseModal} onOk={handleOkModal} channelId={editChannelId} />
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import { TableCell, TableHead, TableRow } from '@mui/material';
|
||||
|
||||
const LogTableHead = ({ userIsAdmin }) => {
|
||||
return (
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell>时间</TableCell>
|
||||
{userIsAdmin && <TableCell>渠道</TableCell>}
|
||||
{userIsAdmin && <TableCell>用户</TableCell>}
|
||||
<TableCell>令牌</TableCell>
|
||||
<TableCell>类型</TableCell>
|
||||
<TableCell>模型</TableCell>
|
||||
<TableCell>耗时</TableCell>
|
||||
<TableCell>提示</TableCell>
|
||||
<TableCell>补全</TableCell>
|
||||
<TableCell>额度</TableCell>
|
||||
<TableCell>详情</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
);
|
||||
};
|
||||
|
||||
export default LogTableHead;
|
||||
|
||||
LogTableHead.propTypes = {
|
||||
userIsAdmin: PropTypes.bool
|
||||
};
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useState, useEffect, useCallback } from 'react';
|
||||
import { showError } from 'utils/common';
|
||||
|
||||
import Table from '@mui/material/Table';
|
||||
@@ -12,12 +12,13 @@ import Toolbar from '@mui/material/Toolbar';
|
||||
|
||||
import { Button, Card, Stack, Container, Typography, Box } from '@mui/material';
|
||||
import LogTableRow from './component/TableRow';
|
||||
import LogTableHead from './component/TableHead';
|
||||
import KeywordTableHead from 'ui-component/TableHead';
|
||||
import TableToolBar from './component/TableToolBar';
|
||||
import { API } from 'utils/api';
|
||||
import { isAdmin } from 'utils/common';
|
||||
import { ITEMS_PER_PAGE } from 'constants';
|
||||
import { IconRefresh, IconSearch } from '@tabler/icons-react';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
export default function Log() {
|
||||
const originalKeyword = {
|
||||
@@ -26,86 +27,98 @@ export default function Log() {
|
||||
token_name: '',
|
||||
model_name: '',
|
||||
start_timestamp: 0,
|
||||
end_timestamp: new Date().getTime() / 1000 + 3600,
|
||||
end_timestamp: dayjs().unix() + 3600,
|
||||
type: 0,
|
||||
channel: ''
|
||||
};
|
||||
const [logs, setLogs] = useState([]);
|
||||
const [activePage, setActivePage] = useState(0);
|
||||
|
||||
const [page, setPage] = useState(0);
|
||||
const [order, setOrder] = useState('desc');
|
||||
const [orderBy, setOrderBy] = useState('created_at');
|
||||
const [rowsPerPage, setRowsPerPage] = useState(ITEMS_PER_PAGE);
|
||||
const [listCount, setListCount] = useState(0);
|
||||
const [searching, setSearching] = useState(false);
|
||||
const [toolBarValue, setToolBarValue] = useState(originalKeyword);
|
||||
const [searchKeyword, setSearchKeyword] = useState(originalKeyword);
|
||||
const [initPage, setInitPage] = useState(true);
|
||||
const [refreshFlag, setRefreshFlag] = useState(false);
|
||||
|
||||
const [logs, setLogs] = useState([]);
|
||||
const userIsAdmin = isAdmin();
|
||||
|
||||
const loadLogs = async (startIdx) => {
|
||||
setSearching(true);
|
||||
const url = userIsAdmin ? '/api/log/' : '/api/log/self/';
|
||||
const query = searchKeyword;
|
||||
|
||||
query.p = startIdx;
|
||||
if (!userIsAdmin) {
|
||||
delete query.username;
|
||||
delete query.channel;
|
||||
const handleSort = (event, id) => {
|
||||
const isAsc = orderBy === id && order === 'asc';
|
||||
if (id !== '') {
|
||||
setOrder(isAsc ? 'desc' : 'asc');
|
||||
setOrderBy(id);
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
const res = await API.get(url, { params: query });
|
||||
const { success, message, data } = res.data;
|
||||
if (success) {
|
||||
if (startIdx === 0) {
|
||||
setLogs(data);
|
||||
} else {
|
||||
let newLogs = [...logs];
|
||||
newLogs.splice(startIdx * ITEMS_PER_PAGE, data.length, ...data);
|
||||
setLogs(newLogs);
|
||||
const handleChangePage = (event, newPage) => {
|
||||
setPage(newPage);
|
||||
};
|
||||
|
||||
const handleChangeRowsPerPage = (event) => {
|
||||
setPage(0);
|
||||
setRowsPerPage(parseInt(event.target.value, 10));
|
||||
};
|
||||
|
||||
const searchLogs = async () => {
|
||||
setPage(0);
|
||||
setSearchKeyword(toolBarValue);
|
||||
};
|
||||
|
||||
const handleToolBarValue = (event) => {
|
||||
setToolBarValue({ ...toolBarValue, [event.target.name]: event.target.value });
|
||||
};
|
||||
|
||||
const fetchData = useCallback(
|
||||
async (page, rowsPerPage, keyword, order, orderBy) => {
|
||||
setSearching(true);
|
||||
try {
|
||||
if (orderBy) {
|
||||
orderBy = order === 'desc' ? '-' + orderBy : orderBy;
|
||||
}
|
||||
} else {
|
||||
showError(message);
|
||||
const url = userIsAdmin ? '/api/log/' : '/api/log/self/';
|
||||
if (!userIsAdmin) {
|
||||
delete keyword.username;
|
||||
delete keyword.channel;
|
||||
}
|
||||
|
||||
const res = await API.get(url, {
|
||||
params: {
|
||||
page: page + 1,
|
||||
size: rowsPerPage,
|
||||
order: orderBy,
|
||||
...keyword
|
||||
}
|
||||
});
|
||||
const { success, message, data } = res.data;
|
||||
if (success) {
|
||||
setListCount(data.total_count);
|
||||
setLogs(data.data);
|
||||
} else {
|
||||
showError(message);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
|
||||
setSearching(false);
|
||||
};
|
||||
|
||||
const onPaginationChange = (event, activePage) => {
|
||||
(async () => {
|
||||
if (activePage === Math.ceil(logs.length / ITEMS_PER_PAGE)) {
|
||||
// In this case we have to load more data and then append them.
|
||||
await loadLogs(activePage);
|
||||
}
|
||||
setActivePage(activePage);
|
||||
})();
|
||||
};
|
||||
|
||||
const searchLogs = async (event) => {
|
||||
event.preventDefault();
|
||||
await loadLogs(0);
|
||||
setActivePage(0);
|
||||
return;
|
||||
};
|
||||
|
||||
const handleSearchKeyword = (event) => {
|
||||
setSearchKeyword({ ...searchKeyword, [event.target.name]: event.target.value });
|
||||
};
|
||||
setSearching(false);
|
||||
},
|
||||
[userIsAdmin]
|
||||
);
|
||||
|
||||
// 处理刷新
|
||||
const handleRefresh = () => {
|
||||
setInitPage(true);
|
||||
const handleRefresh = async () => {
|
||||
setOrderBy('created_at');
|
||||
setOrder('desc');
|
||||
setToolBarValue(originalKeyword);
|
||||
setSearchKeyword(originalKeyword);
|
||||
setRefreshFlag(!refreshFlag);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setSearchKeyword(originalKeyword);
|
||||
setActivePage(0);
|
||||
loadLogs(0)
|
||||
.then()
|
||||
.catch((reason) => {
|
||||
showError(reason);
|
||||
});
|
||||
setInitPage(false);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [initPage]);
|
||||
fetchData(page, rowsPerPage, searchKeyword, order, orderBy);
|
||||
}, [page, rowsPerPage, searchKeyword, order, orderBy, fetchData, refreshFlag]);
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -113,8 +126,8 @@ export default function Log() {
|
||||
<Typography variant="h4">日志</Typography>
|
||||
</Stack>
|
||||
<Card>
|
||||
<Box component="form" onSubmit={searchLogs} noValidate>
|
||||
<TableToolBar filterName={searchKeyword} handleFilterName={handleSearchKeyword} userIsAdmin={userIsAdmin} />
|
||||
<Box component="form" noValidate>
|
||||
<TableToolBar filterName={toolBarValue} handleFilterName={handleToolBarValue} userIsAdmin={userIsAdmin} />
|
||||
</Box>
|
||||
<Toolbar
|
||||
sx={{
|
||||
@@ -141,9 +154,72 @@ export default function Log() {
|
||||
<PerfectScrollbar component="div">
|
||||
<TableContainer sx={{ overflow: 'unset' }}>
|
||||
<Table sx={{ minWidth: 800 }}>
|
||||
<LogTableHead userIsAdmin={userIsAdmin} />
|
||||
<KeywordTableHead
|
||||
order={order}
|
||||
orderBy={orderBy}
|
||||
onRequestSort={handleSort}
|
||||
headLabel={[
|
||||
{
|
||||
id: 'created_at',
|
||||
label: '时间',
|
||||
disableSort: false
|
||||
},
|
||||
{
|
||||
id: 'channel_id',
|
||||
label: '渠道',
|
||||
disableSort: false,
|
||||
hide: !userIsAdmin
|
||||
},
|
||||
{
|
||||
id: 'user_id',
|
||||
label: '用户',
|
||||
disableSort: false,
|
||||
hide: !userIsAdmin
|
||||
},
|
||||
{
|
||||
id: 'token_name',
|
||||
label: '令牌',
|
||||
disableSort: false
|
||||
},
|
||||
{
|
||||
id: 'type',
|
||||
label: '类型',
|
||||
disableSort: false
|
||||
},
|
||||
{
|
||||
id: 'model_name',
|
||||
label: '模型',
|
||||
disableSort: false
|
||||
},
|
||||
{
|
||||
id: 'duration',
|
||||
label: '耗时',
|
||||
disableSort: true
|
||||
},
|
||||
{
|
||||
id: 'message',
|
||||
label: '提示',
|
||||
disableSort: true
|
||||
},
|
||||
{
|
||||
id: 'completion',
|
||||
label: '补全',
|
||||
disableSort: true
|
||||
},
|
||||
{
|
||||
id: 'quota',
|
||||
label: '额度',
|
||||
disableSort: true
|
||||
},
|
||||
{
|
||||
id: 'detail',
|
||||
label: '详情',
|
||||
disableSort: true
|
||||
}
|
||||
]}
|
||||
/>
|
||||
<TableBody>
|
||||
{logs.slice(activePage * ITEMS_PER_PAGE, (activePage + 1) * ITEMS_PER_PAGE).map((row, index) => (
|
||||
{logs.map((row, index) => (
|
||||
<LogTableRow item={row} key={`${row.id}_${index}`} userIsAdmin={userIsAdmin} />
|
||||
))}
|
||||
</TableBody>
|
||||
@@ -151,12 +227,15 @@ export default function Log() {
|
||||
</TableContainer>
|
||||
</PerfectScrollbar>
|
||||
<TablePagination
|
||||
page={activePage}
|
||||
page={page}
|
||||
component="div"
|
||||
count={logs.length + (logs.length % ITEMS_PER_PAGE === 0 ? 1 : 0)}
|
||||
rowsPerPage={ITEMS_PER_PAGE}
|
||||
onPageChange={onPaginationChange}
|
||||
rowsPerPageOptions={[ITEMS_PER_PAGE]}
|
||||
count={listCount}
|
||||
rowsPerPage={rowsPerPage}
|
||||
onPageChange={handleChangePage}
|
||||
rowsPerPageOptions={[10, 25, 30]}
|
||||
onRowsPerPageChange={handleChangeRowsPerPage}
|
||||
showFirstButton
|
||||
showLastButton
|
||||
/>
|
||||
</Card>
|
||||
</>
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
import { TableCell, TableHead, TableRow } from '@mui/material';
|
||||
|
||||
const RedemptionTableHead = () => {
|
||||
return (
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell>ID</TableCell>
|
||||
<TableCell>名称</TableCell>
|
||||
<TableCell>状态</TableCell>
|
||||
<TableCell>额度</TableCell>
|
||||
<TableCell>创建时间</TableCell>
|
||||
<TableCell>兑换时间</TableCell>
|
||||
<TableCell>操作</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
);
|
||||
};
|
||||
|
||||
export default RedemptionTableHead;
|
||||
@@ -12,7 +12,7 @@ import Toolbar from '@mui/material/Toolbar';
|
||||
|
||||
import { Button, Card, Box, Stack, Container, Typography } from '@mui/material';
|
||||
import RedemptionTableRow from './component/TableRow';
|
||||
import RedemptionTableHead from './component/TableHead';
|
||||
import KeywordTableHead from 'ui-component/TableHead';
|
||||
import TableToolBar from 'ui-component/TableToolBar';
|
||||
import { API } from 'utils/api';
|
||||
import { ITEMS_PER_PAGE } from 'constants';
|
||||
@@ -21,74 +21,81 @@ import EditeModal from './component/EditModal';
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
export default function Redemption() {
|
||||
const [redemptions, setRedemptions] = useState([]);
|
||||
const [activePage, setActivePage] = useState(0);
|
||||
const [searching, setSearching] = useState(false);
|
||||
const [page, setPage] = useState(0);
|
||||
const [order, setOrder] = useState('desc');
|
||||
const [orderBy, setOrderBy] = useState('id');
|
||||
const [rowsPerPage, setRowsPerPage] = useState(ITEMS_PER_PAGE);
|
||||
const [listCount, setListCount] = useState(0);
|
||||
const [searchKeyword, setSearchKeyword] = useState('');
|
||||
const [searching, setSearching] = useState(false);
|
||||
const [redemptions, setRedemptions] = useState([]);
|
||||
const [refreshFlag, setRefreshFlag] = useState(false);
|
||||
|
||||
const [openModal, setOpenModal] = useState(false);
|
||||
const [editRedemptionId, setEditRedemptionId] = useState(0);
|
||||
|
||||
const loadRedemptions = async (startIdx) => {
|
||||
setSearching(true);
|
||||
try {
|
||||
const res = await API.get(`/api/redemption/?p=${startIdx}`);
|
||||
const { success, message, data } = res.data;
|
||||
if (success) {
|
||||
if (startIdx === 0) {
|
||||
setRedemptions(data);
|
||||
} else {
|
||||
let newRedemptions = [...redemptions];
|
||||
newRedemptions.splice(startIdx * ITEMS_PER_PAGE, data.length, ...data);
|
||||
setRedemptions(newRedemptions);
|
||||
}
|
||||
} else {
|
||||
showError(message);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
const handleSort = (event, id) => {
|
||||
const isAsc = orderBy === id && order === 'asc';
|
||||
if (id !== '') {
|
||||
setOrder(isAsc ? 'desc' : 'asc');
|
||||
setOrderBy(id);
|
||||
}
|
||||
setSearching(false);
|
||||
};
|
||||
|
||||
const onPaginationChange = (event, activePage) => {
|
||||
(async () => {
|
||||
if (activePage === Math.ceil(redemptions.length / ITEMS_PER_PAGE)) {
|
||||
// In this case we have to load more data and then append them.
|
||||
await loadRedemptions(activePage);
|
||||
}
|
||||
setActivePage(activePage);
|
||||
})();
|
||||
const handleChangePage = (event, newPage) => {
|
||||
setPage(newPage);
|
||||
};
|
||||
|
||||
const handleChangeRowsPerPage = (event) => {
|
||||
setPage(0);
|
||||
setRowsPerPage(parseInt(event.target.value, 10));
|
||||
};
|
||||
|
||||
const searchRedemptions = async (event) => {
|
||||
event.preventDefault();
|
||||
if (searchKeyword === '') {
|
||||
await loadRedemptions(0);
|
||||
setActivePage(0);
|
||||
return;
|
||||
}
|
||||
setSearching(true);
|
||||
const formData = new FormData(event.target);
|
||||
setPage(0);
|
||||
setSearchKeyword(formData.get('keyword'));
|
||||
};
|
||||
|
||||
const fetchData = async (page, rowsPerPage, keyword, order, orderBy) => {
|
||||
setSearching(true);
|
||||
try {
|
||||
const res = await API.get(`/api/redemption/search?keyword=${searchKeyword}`);
|
||||
if (orderBy) {
|
||||
orderBy = order === 'desc' ? '-' + orderBy : orderBy;
|
||||
}
|
||||
const res = await API.get(`/api/redemption/`, {
|
||||
params: {
|
||||
page: page + 1,
|
||||
size: rowsPerPage,
|
||||
keyword: keyword,
|
||||
order: orderBy
|
||||
}
|
||||
});
|
||||
const { success, message, data } = res.data;
|
||||
if (success) {
|
||||
setRedemptions(data);
|
||||
setActivePage(0);
|
||||
setListCount(data.total_count);
|
||||
setRedemptions(data.data);
|
||||
} else {
|
||||
showError(message);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
console.error(error);
|
||||
}
|
||||
|
||||
setSearching(false);
|
||||
};
|
||||
|
||||
const handleSearchKeyword = (event) => {
|
||||
setSearchKeyword(event.target.value);
|
||||
// 处理刷新
|
||||
const handleRefresh = async () => {
|
||||
setOrderBy('id');
|
||||
setOrder('desc');
|
||||
setRefreshFlag(!refreshFlag);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchData(page, rowsPerPage, searchKeyword, order, orderBy);
|
||||
}, [page, rowsPerPage, searchKeyword, order, orderBy, refreshFlag]);
|
||||
|
||||
const manageRedemptions = async (id, action, value) => {
|
||||
const url = '/api/redemption/';
|
||||
let data = { id };
|
||||
@@ -110,7 +117,7 @@ export default function Redemption() {
|
||||
if (success) {
|
||||
showSuccess('操作成功完成!');
|
||||
if (action === 'delete') {
|
||||
await loadRedemptions(0);
|
||||
await handleRefresh();
|
||||
}
|
||||
} else {
|
||||
showError(message);
|
||||
@@ -122,13 +129,6 @@ export default function Redemption() {
|
||||
}
|
||||
};
|
||||
|
||||
// 处理刷新
|
||||
const handleRefresh = async () => {
|
||||
await loadRedemptions(0);
|
||||
setActivePage(0);
|
||||
setSearchKeyword('');
|
||||
};
|
||||
|
||||
const handleOpenModal = (redemptionId) => {
|
||||
setEditRedemptionId(redemptionId);
|
||||
setOpenModal(true);
|
||||
@@ -146,15 +146,6 @@ export default function Redemption() {
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
loadRedemptions(0)
|
||||
.then()
|
||||
.catch((reason) => {
|
||||
showError(reason);
|
||||
});
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Stack direction="row" alignItems="center" justifyContent="space-between" mb={5}>
|
||||
@@ -166,7 +157,7 @@ export default function Redemption() {
|
||||
</Stack>
|
||||
<Card>
|
||||
<Box component="form" onSubmit={searchRedemptions} noValidate>
|
||||
<TableToolBar filterName={searchKeyword} handleFilterName={handleSearchKeyword} placeholder={'搜索兑换码的ID和名称...'} />
|
||||
<TableToolBar placeholder={'搜索兑换码的ID和名称...'} />
|
||||
</Box>
|
||||
<Toolbar
|
||||
sx={{
|
||||
@@ -189,9 +180,22 @@ export default function Redemption() {
|
||||
<PerfectScrollbar component="div">
|
||||
<TableContainer sx={{ overflow: 'unset' }}>
|
||||
<Table sx={{ minWidth: 800 }}>
|
||||
<RedemptionTableHead />
|
||||
<KeywordTableHead
|
||||
order={order}
|
||||
orderBy={orderBy}
|
||||
onRequestSort={handleSort}
|
||||
headLabel={[
|
||||
{ id: 'id', label: 'ID', disableSort: false },
|
||||
{ id: 'name', label: '名称', disableSort: false },
|
||||
{ id: 'status', label: '状态', disableSort: false },
|
||||
{ id: 'quota', label: '额度', disableSort: false },
|
||||
{ id: 'created_time', label: '创建时间', disableSort: false },
|
||||
{ id: 'redeemed_time', label: '兑换时间', disableSort: false },
|
||||
{ id: 'action', label: '操作', disableSort: true }
|
||||
]}
|
||||
/>
|
||||
<TableBody>
|
||||
{redemptions.slice(activePage * ITEMS_PER_PAGE, (activePage + 1) * ITEMS_PER_PAGE).map((row) => (
|
||||
{redemptions.map((row) => (
|
||||
<RedemptionTableRow
|
||||
item={row}
|
||||
manageRedemption={manageRedemptions}
|
||||
@@ -205,12 +209,15 @@ export default function Redemption() {
|
||||
</TableContainer>
|
||||
</PerfectScrollbar>
|
||||
<TablePagination
|
||||
page={activePage}
|
||||
page={page}
|
||||
component="div"
|
||||
count={redemptions.length + (redemptions.length % ITEMS_PER_PAGE === 0 ? 1 : 0)}
|
||||
rowsPerPage={ITEMS_PER_PAGE}
|
||||
onPageChange={onPaginationChange}
|
||||
rowsPerPageOptions={[ITEMS_PER_PAGE]}
|
||||
count={listCount}
|
||||
rowsPerPage={rowsPerPage}
|
||||
onPageChange={handleChangePage}
|
||||
rowsPerPageOptions={[10, 25, 30]}
|
||||
onRowsPerPageChange={handleChangeRowsPerPage}
|
||||
showFirstButton
|
||||
showLastButton
|
||||
/>
|
||||
</Card>
|
||||
<EditeModal open={openModal} onCancel={handleCloseModal} onOk={handleOkModal} redemptiondId={editRedemptionId} />
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
import { TableCell, TableHead, TableRow } from '@mui/material';
|
||||
|
||||
const TokenTableHead = () => {
|
||||
return (
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell>名称</TableCell>
|
||||
<TableCell>状态</TableCell>
|
||||
<TableCell>已用额度</TableCell>
|
||||
<TableCell>剩余额度</TableCell>
|
||||
<TableCell>创建时间</TableCell>
|
||||
<TableCell>过期时间</TableCell>
|
||||
<TableCell>操作</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
);
|
||||
};
|
||||
|
||||
export default TokenTableHead;
|
||||
@@ -13,94 +13,91 @@ import Toolbar from '@mui/material/Toolbar';
|
||||
|
||||
import { Button, Card, Box, Stack, Container, Typography } from '@mui/material';
|
||||
import TokensTableRow from './component/TableRow';
|
||||
import TokenTableHead from './component/TableHead';
|
||||
import KeywordTableHead from 'ui-component/TableHead';
|
||||
import TableToolBar from 'ui-component/TableToolBar';
|
||||
import { API } from 'utils/api';
|
||||
import { ITEMS_PER_PAGE } from 'constants';
|
||||
import { IconRefresh, IconPlus } from '@tabler/icons-react';
|
||||
import EditeModal from './component/EditModal';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { ITEMS_PER_PAGE } from 'constants';
|
||||
|
||||
export default function Token() {
|
||||
const [tokens, setTokens] = useState([]);
|
||||
const [activePage, setActivePage] = useState(0);
|
||||
const [page, setPage] = useState(0);
|
||||
const [order, setOrder] = useState('desc');
|
||||
const [orderBy, setOrderBy] = useState('id');
|
||||
const [rowsPerPage, setRowsPerPage] = useState(ITEMS_PER_PAGE);
|
||||
const [listCount, setListCount] = useState(0);
|
||||
const [searching, setSearching] = useState(false);
|
||||
const [searchKeyword, setSearchKeyword] = useState('');
|
||||
const [tokens, setTokens] = useState([]);
|
||||
const [refreshFlag, setRefreshFlag] = useState(false);
|
||||
|
||||
const [openModal, setOpenModal] = useState(false);
|
||||
const [editTokenId, setEditTokenId] = useState(0);
|
||||
const siteInfo = useSelector((state) => state.siteInfo);
|
||||
|
||||
const loadTokens = async (startIdx) => {
|
||||
setSearching(true);
|
||||
try {
|
||||
const res = await API.get(`/api/token/?p=${startIdx}`);
|
||||
const { success, message, data } = res.data;
|
||||
if (success) {
|
||||
if (startIdx === 0) {
|
||||
setTokens(data);
|
||||
} else {
|
||||
let newTokens = [...tokens];
|
||||
newTokens.splice(startIdx * ITEMS_PER_PAGE, data.length, ...data);
|
||||
setTokens(newTokens);
|
||||
}
|
||||
} else {
|
||||
showError(message);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
const handleSort = (event, id) => {
|
||||
const isAsc = orderBy === id && order === 'asc';
|
||||
if (id !== '') {
|
||||
setOrder(isAsc ? 'desc' : 'asc');
|
||||
setOrderBy(id);
|
||||
}
|
||||
|
||||
setSearching(false);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
loadTokens(0)
|
||||
.then()
|
||||
.catch((reason) => {
|
||||
showError(reason);
|
||||
});
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
const handleChangePage = (event, newPage) => {
|
||||
setPage(newPage);
|
||||
};
|
||||
|
||||
const onPaginationChange = (event, activePage) => {
|
||||
(async () => {
|
||||
if (activePage === Math.ceil(tokens.length / ITEMS_PER_PAGE)) {
|
||||
// In this case we have to load more data and then append them.
|
||||
await loadTokens(activePage);
|
||||
}
|
||||
setActivePage(activePage);
|
||||
})();
|
||||
const handleChangeRowsPerPage = (event) => {
|
||||
setPage(0);
|
||||
setRowsPerPage(parseInt(event.target.value, 10));
|
||||
};
|
||||
|
||||
const searchTokens = async (event) => {
|
||||
event.preventDefault();
|
||||
if (searchKeyword === '') {
|
||||
await loadTokens(0);
|
||||
setActivePage(0);
|
||||
return;
|
||||
}
|
||||
setSearching(true);
|
||||
const formData = new FormData(event.target);
|
||||
setPage(0);
|
||||
setSearchKeyword(formData.get('keyword'));
|
||||
};
|
||||
|
||||
const fetchData = async (page, rowsPerPage, keyword, order, orderBy) => {
|
||||
setSearching(true);
|
||||
try {
|
||||
const res = await API.get(`/api/token/search?keyword=${searchKeyword}`);
|
||||
if (orderBy) {
|
||||
orderBy = order === 'desc' ? '-' + orderBy : orderBy;
|
||||
}
|
||||
const res = await API.get(`/api/token/`, {
|
||||
params: {
|
||||
page: page + 1,
|
||||
size: rowsPerPage,
|
||||
keyword: keyword,
|
||||
order: orderBy
|
||||
}
|
||||
});
|
||||
const { success, message, data } = res.data;
|
||||
if (success) {
|
||||
setTokens(data);
|
||||
setActivePage(0);
|
||||
setListCount(data.total_count);
|
||||
setTokens(data.data);
|
||||
} else {
|
||||
showError(message);
|
||||
}
|
||||
} catch (error) {
|
||||
return;
|
||||
console.error(error);
|
||||
}
|
||||
|
||||
setSearching(false);
|
||||
};
|
||||
|
||||
const handleSearchKeyword = (event) => {
|
||||
setSearchKeyword(event.target.value);
|
||||
// 处理刷新
|
||||
const handleRefresh = async () => {
|
||||
setOrderBy('id');
|
||||
setOrder('desc');
|
||||
setRefreshFlag(!refreshFlag);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchData(page, rowsPerPage, searchKeyword, order, orderBy);
|
||||
}, [page, rowsPerPage, searchKeyword, order, orderBy, refreshFlag]);
|
||||
|
||||
const manageToken = async (id, action, value) => {
|
||||
const url = '/api/token/';
|
||||
let data = { id };
|
||||
@@ -133,11 +130,6 @@ export default function Token() {
|
||||
}
|
||||
};
|
||||
|
||||
// 处理刷新
|
||||
const handleRefresh = async () => {
|
||||
await loadTokens(activePage);
|
||||
};
|
||||
|
||||
const handleOpenModal = (tokenId) => {
|
||||
setEditTokenId(tokenId);
|
||||
setOpenModal(true);
|
||||
@@ -178,7 +170,7 @@ export default function Token() {
|
||||
</Stack>
|
||||
<Card>
|
||||
<Box component="form" onSubmit={searchTokens} noValidate>
|
||||
<TableToolBar filterName={searchKeyword} handleFilterName={handleSearchKeyword} placeholder={'搜索令牌的名称...'} />
|
||||
<TableToolBar placeholder={'搜索令牌的名称...'} />
|
||||
</Box>
|
||||
<Toolbar
|
||||
sx={{
|
||||
@@ -201,9 +193,22 @@ export default function Token() {
|
||||
<PerfectScrollbar component="div">
|
||||
<TableContainer sx={{ overflow: 'unset' }}>
|
||||
<Table sx={{ minWidth: 800 }}>
|
||||
<TokenTableHead />
|
||||
<KeywordTableHead
|
||||
order={order}
|
||||
orderBy={orderBy}
|
||||
onRequestSort={handleSort}
|
||||
headLabel={[
|
||||
{ id: 'name', label: '名称', disableSort: false },
|
||||
{ id: 'status', label: '状态', disableSort: false },
|
||||
{ id: 'used_quota', label: '已用额度', disableSort: false },
|
||||
{ id: 'remain_quota', label: '剩余额度', disableSort: false },
|
||||
{ id: 'created_time', label: '创建时间', disableSort: false },
|
||||
{ id: 'expired_time', label: '过期时间', disableSort: false },
|
||||
{ id: 'action', label: '操作', disableSort: true }
|
||||
]}
|
||||
/>
|
||||
<TableBody>
|
||||
{tokens.slice(activePage * ITEMS_PER_PAGE, (activePage + 1) * ITEMS_PER_PAGE).map((row) => (
|
||||
{tokens.map((row) => (
|
||||
<TokensTableRow
|
||||
item={row}
|
||||
manageToken={manageToken}
|
||||
@@ -217,12 +222,15 @@ export default function Token() {
|
||||
</TableContainer>
|
||||
</PerfectScrollbar>
|
||||
<TablePagination
|
||||
page={activePage}
|
||||
page={page}
|
||||
component="div"
|
||||
count={tokens.length + (tokens.length % ITEMS_PER_PAGE === 0 ? 1 : 0)}
|
||||
rowsPerPage={ITEMS_PER_PAGE}
|
||||
onPageChange={onPaginationChange}
|
||||
rowsPerPageOptions={[ITEMS_PER_PAGE]}
|
||||
count={listCount}
|
||||
rowsPerPage={rowsPerPage}
|
||||
onPageChange={handleChangePage}
|
||||
rowsPerPageOptions={[10, 25, 30]}
|
||||
onRowsPerPageChange={handleChangeRowsPerPage}
|
||||
showFirstButton
|
||||
showLastButton
|
||||
/>
|
||||
</Card>
|
||||
<EditeModal open={openModal} onCancel={handleCloseModal} onOk={handleOkModal} tokenId={editTokenId} />
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
import { TableCell, TableHead, TableRow } from '@mui/material';
|
||||
|
||||
const UsersTableHead = () => {
|
||||
return (
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell>ID</TableCell>
|
||||
<TableCell>用户名</TableCell>
|
||||
<TableCell>分组</TableCell>
|
||||
<TableCell>统计信息</TableCell>
|
||||
<TableCell>用户角色</TableCell>
|
||||
<TableCell>绑定</TableCell>
|
||||
<TableCell>状态</TableCell>
|
||||
<TableCell>操作</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
);
|
||||
};
|
||||
|
||||
export default UsersTableHead;
|
||||
@@ -19,7 +19,7 @@ import {
|
||||
|
||||
import Label from 'ui-component/Label';
|
||||
import TableSwitch from 'ui-component/Switch';
|
||||
import { renderQuota, renderNumber } from 'utils/common';
|
||||
import { renderQuota, renderNumber, timestamp2string } from 'utils/common';
|
||||
import { IconDotsVertical, IconEdit, IconTrash, IconUser, IconBrandWechat, IconBrandGithub, IconMail } from '@tabler/icons-react';
|
||||
import { useTheme } from '@mui/material/styles';
|
||||
|
||||
@@ -119,7 +119,7 @@ export default function UsersTableRow({ item, manageUser, handleOpenModal, setMo
|
||||
</Tooltip>
|
||||
</Stack>
|
||||
</TableCell>
|
||||
|
||||
<TableCell>{item.created_time === 0 ? '未知' : timestamp2string(item.created_time)}</TableCell>
|
||||
<TableCell>
|
||||
{' '}
|
||||
<TableSwitch id={`switch-${item.id}`} checked={statusSwitch === 1} onChange={handleStatus} />
|
||||
|
||||
@@ -12,7 +12,7 @@ import Toolbar from '@mui/material/Toolbar';
|
||||
|
||||
import { Button, Card, Box, Stack, Container, Typography } from '@mui/material';
|
||||
import UsersTableRow from './component/TableRow';
|
||||
import UsersTableHead from './component/TableHead';
|
||||
import KeywordTableHead from 'ui-component/TableHead';
|
||||
import TableToolBar from 'ui-component/TableToolBar';
|
||||
import { API } from 'utils/api';
|
||||
import { ITEMS_PER_PAGE } from 'constants';
|
||||
@@ -21,74 +21,81 @@ import EditeModal from './component/EditModal';
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
export default function Users() {
|
||||
const [users, setUsers] = useState([]);
|
||||
const [activePage, setActivePage] = useState(0);
|
||||
const [page, setPage] = useState(0);
|
||||
const [order, setOrder] = useState('desc');
|
||||
const [orderBy, setOrderBy] = useState('id');
|
||||
const [rowsPerPage, setRowsPerPage] = useState(ITEMS_PER_PAGE);
|
||||
const [listCount, setListCount] = useState(0);
|
||||
const [searching, setSearching] = useState(false);
|
||||
const [searchKeyword, setSearchKeyword] = useState('');
|
||||
const [users, setUsers] = useState([]);
|
||||
const [refreshFlag, setRefreshFlag] = useState(false);
|
||||
|
||||
const [openModal, setOpenModal] = useState(false);
|
||||
const [editUserId, setEditUserId] = useState(0);
|
||||
|
||||
const loadUsers = async (startIdx) => {
|
||||
setSearching(true);
|
||||
try {
|
||||
const res = await API.get(`/api/user/?p=${startIdx}`);
|
||||
const { success, message, data } = res.data;
|
||||
if (success) {
|
||||
if (startIdx === 0) {
|
||||
setUsers(data);
|
||||
} else {
|
||||
let newUsers = [...users];
|
||||
newUsers.splice(startIdx * ITEMS_PER_PAGE, data.length, ...data);
|
||||
setUsers(newUsers);
|
||||
}
|
||||
} else {
|
||||
showError(message);
|
||||
}
|
||||
setSearching(false);
|
||||
} catch (error) {
|
||||
setSearching(false);
|
||||
return;
|
||||
const handleSort = (event, id) => {
|
||||
const isAsc = orderBy === id && order === 'asc';
|
||||
if (id !== '') {
|
||||
setOrder(isAsc ? 'desc' : 'asc');
|
||||
setOrderBy(id);
|
||||
}
|
||||
};
|
||||
|
||||
const onPaginationChange = (event, activePage) => {
|
||||
(async () => {
|
||||
if (activePage === Math.ceil(users.length / ITEMS_PER_PAGE)) {
|
||||
// In this case we have to load more data and then append them.
|
||||
await loadUsers(activePage);
|
||||
}
|
||||
setActivePage(activePage);
|
||||
})();
|
||||
const handleChangePage = (event, newPage) => {
|
||||
setPage(newPage);
|
||||
};
|
||||
|
||||
const handleChangeRowsPerPage = (event) => {
|
||||
setPage(0);
|
||||
setRowsPerPage(parseInt(event.target.value, 10));
|
||||
};
|
||||
|
||||
const searchUsers = async (event) => {
|
||||
event.preventDefault();
|
||||
if (searchKeyword === '') {
|
||||
await loadUsers(0);
|
||||
setActivePage(0);
|
||||
return;
|
||||
}
|
||||
const formData = new FormData(event.target);
|
||||
setPage(0);
|
||||
setSearchKeyword(formData.get('keyword'));
|
||||
};
|
||||
|
||||
const fetchData = async (page, rowsPerPage, keyword, order, orderBy) => {
|
||||
setSearching(true);
|
||||
try {
|
||||
const res = await API.get(`/api/user/search?keyword=${searchKeyword}`);
|
||||
if (orderBy) {
|
||||
orderBy = order === 'desc' ? '-' + orderBy : orderBy;
|
||||
}
|
||||
const res = await API.get(`/api/user/`, {
|
||||
params: {
|
||||
page: page + 1,
|
||||
size: rowsPerPage,
|
||||
keyword: keyword,
|
||||
order: orderBy
|
||||
}
|
||||
});
|
||||
const { success, message, data } = res.data;
|
||||
if (success) {
|
||||
setUsers(data);
|
||||
setActivePage(0);
|
||||
setListCount(data.total_count);
|
||||
setUsers(data.data);
|
||||
} else {
|
||||
showError(message);
|
||||
}
|
||||
setSearching(false);
|
||||
} catch (error) {
|
||||
setSearching(false);
|
||||
return;
|
||||
console.error(error);
|
||||
}
|
||||
setSearching(false);
|
||||
};
|
||||
|
||||
const handleSearchKeyword = (event) => {
|
||||
setSearchKeyword(event.target.value);
|
||||
// 处理刷新
|
||||
const handleRefresh = async () => {
|
||||
setOrderBy('id');
|
||||
setOrder('desc');
|
||||
setRefreshFlag(!refreshFlag);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchData(page, rowsPerPage, searchKeyword, order, orderBy);
|
||||
}, [page, rowsPerPage, searchKeyword, order, orderBy, refreshFlag]);
|
||||
|
||||
const manageUser = async (username, action, value) => {
|
||||
const url = '/api/user/manage';
|
||||
let data = { username: username, action: '' };
|
||||
@@ -110,7 +117,7 @@ export default function Users() {
|
||||
const { success, message } = res.data;
|
||||
if (success) {
|
||||
showSuccess('操作成功完成!');
|
||||
await loadUsers(activePage);
|
||||
await handleRefresh();
|
||||
} else {
|
||||
showError(message);
|
||||
}
|
||||
@@ -121,11 +128,6 @@ export default function Users() {
|
||||
}
|
||||
};
|
||||
|
||||
// 处理刷新
|
||||
const handleRefresh = async () => {
|
||||
await loadUsers(activePage);
|
||||
};
|
||||
|
||||
const handleOpenModal = (userId) => {
|
||||
setEditUserId(userId);
|
||||
setOpenModal(true);
|
||||
@@ -143,15 +145,6 @@ export default function Users() {
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
loadUsers(0)
|
||||
.then()
|
||||
.catch((reason) => {
|
||||
showError(reason);
|
||||
});
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Stack direction="row" alignItems="center" justifyContent="space-between" mb={5}>
|
||||
@@ -163,11 +156,7 @@ export default function Users() {
|
||||
</Stack>
|
||||
<Card>
|
||||
<Box component="form" onSubmit={searchUsers} noValidate>
|
||||
<TableToolBar
|
||||
filterName={searchKeyword}
|
||||
handleFilterName={handleSearchKeyword}
|
||||
placeholder={'搜索用户的ID,用户名,显示名称,以及邮箱地址...'}
|
||||
/>
|
||||
<TableToolBar placeholder={'搜索用户的ID,用户名,显示名称,以及邮箱地址...'} />
|
||||
</Box>
|
||||
<Toolbar
|
||||
sx={{
|
||||
@@ -190,9 +179,24 @@ export default function Users() {
|
||||
<PerfectScrollbar component="div">
|
||||
<TableContainer sx={{ overflow: 'unset' }}>
|
||||
<Table sx={{ minWidth: 800 }}>
|
||||
<UsersTableHead />
|
||||
<KeywordTableHead
|
||||
order={order}
|
||||
orderBy={orderBy}
|
||||
onRequestSort={handleSort}
|
||||
headLabel={[
|
||||
{ id: 'id', label: 'ID', disableSort: false },
|
||||
{ id: 'username', label: '用户名', disableSort: false },
|
||||
{ id: 'group', label: '分组', disableSort: true },
|
||||
{ id: 'stats', label: '统计信息', disableSort: true },
|
||||
{ id: 'role', label: '用户角色', disableSort: false },
|
||||
{ id: 'bind', label: '绑定', disableSort: true },
|
||||
{ id: 'created_time', label: '创建时间', disableSort: false },
|
||||
{ id: 'status', label: '状态', disableSort: false },
|
||||
{ id: 'action', label: '操作', disableSort: true }
|
||||
]}
|
||||
/>
|
||||
<TableBody>
|
||||
{users.slice(activePage * ITEMS_PER_PAGE, (activePage + 1) * ITEMS_PER_PAGE).map((row) => (
|
||||
{users.map((row) => (
|
||||
<UsersTableRow
|
||||
item={row}
|
||||
manageUser={manageUser}
|
||||
@@ -206,12 +210,15 @@ export default function Users() {
|
||||
</TableContainer>
|
||||
</PerfectScrollbar>
|
||||
<TablePagination
|
||||
page={activePage}
|
||||
page={page}
|
||||
component="div"
|
||||
count={users.length + (users.length % ITEMS_PER_PAGE === 0 ? 1 : 0)}
|
||||
rowsPerPage={ITEMS_PER_PAGE}
|
||||
onPageChange={onPaginationChange}
|
||||
rowsPerPageOptions={[ITEMS_PER_PAGE]}
|
||||
count={listCount}
|
||||
rowsPerPage={rowsPerPage}
|
||||
onPageChange={handleChangePage}
|
||||
rowsPerPageOptions={[10, 25, 30]}
|
||||
onRowsPerPageChange={handleChangeRowsPerPage}
|
||||
showFirstButton
|
||||
showLastButton
|
||||
/>
|
||||
</Card>
|
||||
<EditeModal open={openModal} onCancel={handleCloseModal} onOk={handleOkModal} userId={editUserId} />
|
||||
|
||||
Reference in New Issue
Block a user