mirror of
https://github.com/songquanpeng/one-api.git
synced 2026-04-23 10:14:33 +08:00
feat: add new theme berry (#860)
* feat: add theme berry * docs: add development notes * fix: fix blank page * chore: update implementation * fix: fix package.json * chore: update ui copy --------- Co-authored-by: JustSong <songquanpeng@foxmail.com>
This commit is contained in:
288
web/berry/src/views/User/component/EditModal.js
Normal file
288
web/berry/src/views/User/component/EditModal.js
Normal file
@@ -0,0 +1,288 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import * as Yup from 'yup';
|
||||
import { Formik } from 'formik';
|
||||
import { useTheme } from '@mui/material/styles';
|
||||
import { useState, useEffect } from 'react';
|
||||
import {
|
||||
Dialog,
|
||||
DialogTitle,
|
||||
DialogContent,
|
||||
DialogActions,
|
||||
Button,
|
||||
Divider,
|
||||
FormControl,
|
||||
InputLabel,
|
||||
OutlinedInput,
|
||||
InputAdornment,
|
||||
Select,
|
||||
MenuItem,
|
||||
IconButton,
|
||||
FormHelperText
|
||||
} from '@mui/material';
|
||||
|
||||
import Visibility from '@mui/icons-material/Visibility';
|
||||
import VisibilityOff from '@mui/icons-material/VisibilityOff';
|
||||
|
||||
import { renderQuotaWithPrompt, showSuccess, showError } from 'utils/common';
|
||||
import { API } from 'utils/api';
|
||||
|
||||
const validationSchema = Yup.object().shape({
|
||||
is_edit: Yup.boolean(),
|
||||
username: Yup.string().required('用户名 不能为空'),
|
||||
display_name: Yup.string(),
|
||||
password: Yup.string().when('is_edit', {
|
||||
is: false,
|
||||
then: Yup.string().required('密码 不能为空'),
|
||||
otherwise: Yup.string()
|
||||
}),
|
||||
group: Yup.string().when('is_edit', {
|
||||
is: false,
|
||||
then: Yup.string().required('用户组 不能为空'),
|
||||
otherwise: Yup.string()
|
||||
}),
|
||||
quota: Yup.number().when('is_edit', {
|
||||
is: false,
|
||||
then: Yup.number().min(0, '额度 不能小于 0'),
|
||||
otherwise: Yup.number()
|
||||
})
|
||||
});
|
||||
|
||||
const originInputs = {
|
||||
is_edit: false,
|
||||
username: '',
|
||||
display_name: '',
|
||||
password: '',
|
||||
group: 'default',
|
||||
quota: 0
|
||||
};
|
||||
|
||||
const EditModal = ({ open, userId, onCancel, onOk }) => {
|
||||
const theme = useTheme();
|
||||
const [inputs, setInputs] = useState(originInputs);
|
||||
const [groupOptions, setGroupOptions] = useState([]);
|
||||
const [showPassword, setShowPassword] = useState(false);
|
||||
|
||||
const submit = async (values, { setErrors, setStatus, setSubmitting }) => {
|
||||
setSubmitting(true);
|
||||
|
||||
let res;
|
||||
if (values.is_edit) {
|
||||
res = await API.put(`/api/user/`, { ...values, id: parseInt(userId) });
|
||||
} else {
|
||||
res = await API.post(`/api/user/`, values);
|
||||
}
|
||||
const { success, message } = res.data;
|
||||
if (success) {
|
||||
if (values.is_edit) {
|
||||
showSuccess('用户更新成功!');
|
||||
} else {
|
||||
showSuccess('用户创建成功!');
|
||||
}
|
||||
setSubmitting(false);
|
||||
setStatus({ success: true });
|
||||
onOk(true);
|
||||
} else {
|
||||
showError(message);
|
||||
setErrors({ submit: message });
|
||||
}
|
||||
};
|
||||
|
||||
const handleClickShowPassword = () => {
|
||||
setShowPassword(!showPassword);
|
||||
};
|
||||
|
||||
const handleMouseDownPassword = (event) => {
|
||||
event.preventDefault();
|
||||
};
|
||||
|
||||
const loadUser = async () => {
|
||||
let res = await API.get(`/api/user/${userId}`);
|
||||
const { success, message, data } = res.data;
|
||||
if (success) {
|
||||
data.is_edit = true;
|
||||
setInputs(data);
|
||||
} else {
|
||||
showError(message);
|
||||
}
|
||||
};
|
||||
|
||||
const fetchGroups = async () => {
|
||||
try {
|
||||
let res = await API.get(`/api/group/`);
|
||||
setGroupOptions(res.data.data);
|
||||
} catch (error) {
|
||||
showError(error.message);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchGroups().then();
|
||||
if (userId) {
|
||||
loadUser().then();
|
||||
} else {
|
||||
setInputs(originInputs);
|
||||
}
|
||||
}, [userId]);
|
||||
|
||||
return (
|
||||
<Dialog open={open} onClose={onCancel} fullWidth maxWidth={'md'}>
|
||||
<DialogTitle sx={{ margin: '0px', fontWeight: 700, lineHeight: '1.55556', padding: '24px', fontSize: '1.125rem' }}>
|
||||
{userId ? '编辑用户' : '新建用户'}
|
||||
</DialogTitle>
|
||||
<Divider />
|
||||
<DialogContent>
|
||||
<Formik initialValues={inputs} enableReinitialize validationSchema={validationSchema} onSubmit={submit}>
|
||||
{({ errors, handleBlur, handleChange, handleSubmit, touched, values, isSubmitting }) => (
|
||||
<form noValidate onSubmit={handleSubmit}>
|
||||
<FormControl fullWidth error={Boolean(touched.username && errors.username)} sx={{ ...theme.typography.otherInput }}>
|
||||
<InputLabel htmlFor="channel-username-label">用户名</InputLabel>
|
||||
<OutlinedInput
|
||||
id="channel-username-label"
|
||||
label="用户名"
|
||||
type="text"
|
||||
value={values.username}
|
||||
name="username"
|
||||
onBlur={handleBlur}
|
||||
onChange={handleChange}
|
||||
inputProps={{ autoComplete: 'username' }}
|
||||
aria-describedby="helper-text-channel-username-label"
|
||||
/>
|
||||
{touched.username && errors.username && (
|
||||
<FormHelperText error id="helper-tex-channel-username-label">
|
||||
{errors.username}
|
||||
</FormHelperText>
|
||||
)}
|
||||
</FormControl>
|
||||
|
||||
<FormControl fullWidth error={Boolean(touched.display_name && errors.display_name)} sx={{ ...theme.typography.otherInput }}>
|
||||
<InputLabel htmlFor="channel-display_name-label">显示名称</InputLabel>
|
||||
<OutlinedInput
|
||||
id="channel-display_name-label"
|
||||
label="显示名称"
|
||||
type="text"
|
||||
value={values.display_name}
|
||||
name="display_name"
|
||||
onBlur={handleBlur}
|
||||
onChange={handleChange}
|
||||
inputProps={{ autoComplete: 'display_name' }}
|
||||
aria-describedby="helper-text-channel-display_name-label"
|
||||
/>
|
||||
{touched.display_name && errors.display_name && (
|
||||
<FormHelperText error id="helper-tex-channel-display_name-label">
|
||||
{errors.display_name}
|
||||
</FormHelperText>
|
||||
)}
|
||||
</FormControl>
|
||||
|
||||
<FormControl fullWidth error={Boolean(touched.password && errors.password)} sx={{ ...theme.typography.otherInput }}>
|
||||
<InputLabel htmlFor="channel-password-label">密码</InputLabel>
|
||||
<OutlinedInput
|
||||
id="channel-password-label"
|
||||
label="密码"
|
||||
type={showPassword ? 'text' : 'password'}
|
||||
value={values.password}
|
||||
name="password"
|
||||
onBlur={handleBlur}
|
||||
onChange={handleChange}
|
||||
inputProps={{ autoComplete: 'password' }}
|
||||
endAdornment={
|
||||
<InputAdornment position="end">
|
||||
<IconButton
|
||||
aria-label="toggle password visibility"
|
||||
onClick={handleClickShowPassword}
|
||||
onMouseDown={handleMouseDownPassword}
|
||||
edge="end"
|
||||
size="large"
|
||||
>
|
||||
{showPassword ? <Visibility /> : <VisibilityOff />}
|
||||
</IconButton>
|
||||
</InputAdornment>
|
||||
}
|
||||
aria-describedby="helper-text-channel-password-label"
|
||||
/>
|
||||
{touched.password && errors.password && (
|
||||
<FormHelperText error id="helper-tex-channel-password-label">
|
||||
{errors.password}
|
||||
</FormHelperText>
|
||||
)}
|
||||
</FormControl>
|
||||
|
||||
{values.is_edit && (
|
||||
<>
|
||||
<FormControl fullWidth error={Boolean(touched.quota && errors.quota)} sx={{ ...theme.typography.otherInput }}>
|
||||
<InputLabel htmlFor="channel-quota-label">额度</InputLabel>
|
||||
<OutlinedInput
|
||||
id="channel-quota-label"
|
||||
label="额度"
|
||||
type="number"
|
||||
value={values.quota}
|
||||
name="quota"
|
||||
endAdornment={<InputAdornment position="end">{renderQuotaWithPrompt(values.quota)}</InputAdornment>}
|
||||
onBlur={handleBlur}
|
||||
onChange={handleChange}
|
||||
aria-describedby="helper-text-channel-quota-label"
|
||||
disabled={values.unlimited_quota}
|
||||
/>
|
||||
|
||||
{touched.quota && errors.quota && (
|
||||
<FormHelperText error id="helper-tex-channel-quota-label">
|
||||
{errors.quota}
|
||||
</FormHelperText>
|
||||
)}
|
||||
</FormControl>
|
||||
|
||||
<FormControl fullWidth error={Boolean(touched.group && errors.group)} sx={{ ...theme.typography.otherInput }}>
|
||||
<InputLabel htmlFor="channel-group-label">分组</InputLabel>
|
||||
<Select
|
||||
id="channel-group-label"
|
||||
label="分组"
|
||||
value={values.group}
|
||||
name="group"
|
||||
onBlur={handleBlur}
|
||||
onChange={handleChange}
|
||||
MenuProps={{
|
||||
PaperProps: {
|
||||
style: {
|
||||
maxHeight: 200
|
||||
}
|
||||
}
|
||||
}}
|
||||
>
|
||||
{groupOptions.map((option) => {
|
||||
return (
|
||||
<MenuItem key={option} value={option}>
|
||||
{option}
|
||||
</MenuItem>
|
||||
);
|
||||
})}
|
||||
</Select>
|
||||
{touched.group && errors.group && (
|
||||
<FormHelperText error id="helper-tex-channel-group-label">
|
||||
{errors.group}
|
||||
</FormHelperText>
|
||||
)}
|
||||
</FormControl>
|
||||
</>
|
||||
)}
|
||||
<DialogActions>
|
||||
<Button onClick={onCancel}>取消</Button>
|
||||
<Button disableElevation disabled={isSubmitting} type="submit" variant="contained" color="primary">
|
||||
提交
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</form>
|
||||
)}
|
||||
</Formik>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
export default EditModal;
|
||||
|
||||
EditModal.propTypes = {
|
||||
open: PropTypes.bool,
|
||||
userId: PropTypes.number,
|
||||
onCancel: PropTypes.func,
|
||||
onOk: PropTypes.func
|
||||
};
|
||||
20
web/berry/src/views/User/component/TableHead.js
Normal file
20
web/berry/src/views/User/component/TableHead.js
Normal file
@@ -0,0 +1,20 @@
|
||||
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;
|
||||
193
web/berry/src/views/User/component/TableRow.js
Normal file
193
web/berry/src/views/User/component/TableRow.js
Normal file
@@ -0,0 +1,193 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import { useState } from 'react';
|
||||
|
||||
import {
|
||||
Popover,
|
||||
TableRow,
|
||||
MenuItem,
|
||||
TableCell,
|
||||
IconButton,
|
||||
Dialog,
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
DialogContentText,
|
||||
DialogTitle,
|
||||
Button,
|
||||
Tooltip,
|
||||
Stack
|
||||
} from '@mui/material';
|
||||
|
||||
import Label from 'ui-component/Label';
|
||||
import TableSwitch from 'ui-component/Switch';
|
||||
import { renderQuota, renderNumber } from 'utils/common';
|
||||
import { IconDotsVertical, IconEdit, IconTrash, IconUser, IconBrandWechat, IconBrandGithub, IconMail } from '@tabler/icons-react';
|
||||
import { useTheme } from '@mui/material/styles';
|
||||
|
||||
function renderRole(role) {
|
||||
switch (role) {
|
||||
case 1:
|
||||
return <Label color="default">普通用户</Label>;
|
||||
case 10:
|
||||
return <Label color="orange">管理员</Label>;
|
||||
case 100:
|
||||
return <Label color="success">超级管理员</Label>;
|
||||
default:
|
||||
return <Label color="error">未知身份</Label>;
|
||||
}
|
||||
}
|
||||
|
||||
export default function UsersTableRow({ item, manageUser, handleOpenModal, setModalUserId }) {
|
||||
const theme = useTheme();
|
||||
const [open, setOpen] = useState(null);
|
||||
const [openDelete, setOpenDelete] = useState(false);
|
||||
const [statusSwitch, setStatusSwitch] = useState(item.status);
|
||||
|
||||
const handleDeleteOpen = () => {
|
||||
handleCloseMenu();
|
||||
setOpenDelete(true);
|
||||
};
|
||||
|
||||
const handleDeleteClose = () => {
|
||||
setOpenDelete(false);
|
||||
};
|
||||
|
||||
const handleOpenMenu = (event) => {
|
||||
setOpen(event.currentTarget);
|
||||
};
|
||||
|
||||
const handleCloseMenu = () => {
|
||||
setOpen(null);
|
||||
};
|
||||
|
||||
const handleStatus = async () => {
|
||||
const switchVlue = statusSwitch === 1 ? 2 : 1;
|
||||
const { success } = await manageUser(item.username, 'status', switchVlue);
|
||||
if (success) {
|
||||
setStatusSwitch(switchVlue);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDelete = async () => {
|
||||
handleCloseMenu();
|
||||
await manageUser(item.username, 'delete', '');
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<TableRow tabIndex={item.id}>
|
||||
<TableCell>{item.id}</TableCell>
|
||||
|
||||
<TableCell>{item.username}</TableCell>
|
||||
|
||||
<TableCell>
|
||||
<Label>{item.group}</Label>
|
||||
</TableCell>
|
||||
|
||||
<TableCell>
|
||||
<Stack direction="row" spacing={0.5} alignItems="center" justifyContent="center">
|
||||
<Tooltip title={'剩余额度'} placement="top">
|
||||
<Label color={'primary'} variant="outlined">
|
||||
{' '}
|
||||
{renderQuota(item.quota)}{' '}
|
||||
</Label>
|
||||
</Tooltip>
|
||||
<Tooltip title={'已用额度'} placement="top">
|
||||
<Label color={'primary'} variant="outlined">
|
||||
{' '}
|
||||
{renderQuota(item.used_quota)}{' '}
|
||||
</Label>
|
||||
</Tooltip>
|
||||
<Tooltip title={'请求次数'} placement="top">
|
||||
<Label color={'primary'} variant="outlined">
|
||||
{' '}
|
||||
{renderNumber(item.request_count)}{' '}
|
||||
</Label>
|
||||
</Tooltip>
|
||||
</Stack>
|
||||
</TableCell>
|
||||
<TableCell>{renderRole(item.role)}</TableCell>
|
||||
<TableCell>
|
||||
<Stack direction="row" spacing={0.5} alignItems="center" justifyContent="center">
|
||||
<Tooltip title={item.wechat_id ? item.wechat_id : '未绑定'} placement="top">
|
||||
<IconBrandWechat color={item.wechat_id ? theme.palette.success.dark : theme.palette.grey[400]} />
|
||||
</Tooltip>
|
||||
<Tooltip title={item.github_id ? item.github_id : '未绑定'} placement="top">
|
||||
<IconBrandGithub color={item.github_id ? theme.palette.grey[900] : theme.palette.grey[400]} />
|
||||
</Tooltip>
|
||||
<Tooltip title={item.email ? item.email : '未绑定'} placement="top">
|
||||
<IconMail color={item.email ? theme.palette.grey[900] : theme.palette.grey[400]} />
|
||||
</Tooltip>
|
||||
</Stack>
|
||||
</TableCell>
|
||||
|
||||
<TableCell>
|
||||
{' '}
|
||||
<TableSwitch id={`switch-${item.id}`} checked={statusSwitch === 1} onChange={handleStatus} />
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<IconButton onClick={handleOpenMenu} sx={{ color: 'rgb(99, 115, 129)' }}>
|
||||
<IconDotsVertical />
|
||||
</IconButton>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
|
||||
<Popover
|
||||
open={!!open}
|
||||
anchorEl={open}
|
||||
onClose={handleCloseMenu}
|
||||
anchorOrigin={{ vertical: 'top', horizontal: 'left' }}
|
||||
transformOrigin={{ vertical: 'top', horizontal: 'right' }}
|
||||
PaperProps={{
|
||||
sx: { width: 140 }
|
||||
}}
|
||||
>
|
||||
{item.role !== 100 && (
|
||||
<MenuItem
|
||||
onClick={() => {
|
||||
handleCloseMenu();
|
||||
manageUser(item.username, 'role', item.role === 1 ? true : false);
|
||||
}}
|
||||
>
|
||||
<IconUser style={{ marginRight: '16px' }} />
|
||||
{item.role === 1 ? '设为管理员' : '取消管理员'}
|
||||
</MenuItem>
|
||||
)}
|
||||
|
||||
<MenuItem
|
||||
onClick={() => {
|
||||
handleCloseMenu();
|
||||
handleOpenModal();
|
||||
setModalUserId(item.id);
|
||||
}}
|
||||
>
|
||||
<IconEdit style={{ marginRight: '16px' }} />
|
||||
编辑
|
||||
</MenuItem>
|
||||
<MenuItem onClick={handleDeleteOpen} sx={{ color: 'error.main' }}>
|
||||
<IconTrash style={{ marginRight: '16px' }} />
|
||||
删除
|
||||
</MenuItem>
|
||||
</Popover>
|
||||
|
||||
<Dialog open={openDelete} onClose={handleDeleteClose}>
|
||||
<DialogTitle>删除用户</DialogTitle>
|
||||
<DialogContent>
|
||||
<DialogContentText>是否删除用户 {item.name}?</DialogContentText>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={handleDeleteClose}>关闭</Button>
|
||||
<Button onClick={handleDelete} sx={{ color: 'error.main' }} autoFocus>
|
||||
删除
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
UsersTableRow.propTypes = {
|
||||
item: PropTypes.object,
|
||||
manageUser: PropTypes.func,
|
||||
handleOpenModal: PropTypes.func,
|
||||
setModalUserId: PropTypes.func
|
||||
};
|
||||
205
web/berry/src/views/User/index.js
Normal file
205
web/berry/src/views/User/index.js
Normal file
@@ -0,0 +1,205 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { showError, showSuccess } from 'utils/common';
|
||||
|
||||
import Table from '@mui/material/Table';
|
||||
import TableBody from '@mui/material/TableBody';
|
||||
import TableContainer from '@mui/material/TableContainer';
|
||||
import PerfectScrollbar from 'react-perfect-scrollbar';
|
||||
import TablePagination from '@mui/material/TablePagination';
|
||||
import LinearProgress from '@mui/material/LinearProgress';
|
||||
import ButtonGroup from '@mui/material/ButtonGroup';
|
||||
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 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';
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
export default function Users() {
|
||||
const [users, setUsers] = useState([]);
|
||||
const [activePage, setActivePage] = useState(0);
|
||||
const [searching, setSearching] = useState(false);
|
||||
const [searchKeyword, setSearchKeyword] = useState('');
|
||||
const [openModal, setOpenModal] = useState(false);
|
||||
const [editUserId, setEditUserId] = useState(0);
|
||||
|
||||
const loadUsers = async (startIdx) => {
|
||||
setSearching(true);
|
||||
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);
|
||||
};
|
||||
|
||||
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 searchUsers = async (event) => {
|
||||
event.preventDefault();
|
||||
if (searchKeyword === '') {
|
||||
await loadUsers(0);
|
||||
setActivePage(0);
|
||||
return;
|
||||
}
|
||||
setSearching(true);
|
||||
const res = await API.get(`/api/user/search?keyword=${searchKeyword}`);
|
||||
const { success, message, data } = res.data;
|
||||
if (success) {
|
||||
setUsers(data);
|
||||
setActivePage(0);
|
||||
} else {
|
||||
showError(message);
|
||||
}
|
||||
setSearching(false);
|
||||
};
|
||||
|
||||
const handleSearchKeyword = (event) => {
|
||||
setSearchKeyword(event.target.value);
|
||||
};
|
||||
|
||||
const manageUser = async (username, action, value) => {
|
||||
const url = '/api/user/manage';
|
||||
let data = { username: username, action: '' };
|
||||
let res;
|
||||
switch (action) {
|
||||
case 'delete':
|
||||
data.action = 'delete';
|
||||
break;
|
||||
case 'status':
|
||||
data.action = value === 1 ? 'enable' : 'disable';
|
||||
break;
|
||||
case 'role':
|
||||
data.action = value === true ? 'promote' : 'demote';
|
||||
break;
|
||||
}
|
||||
|
||||
res = await API.post(url, data);
|
||||
const { success, message } = res.data;
|
||||
if (success) {
|
||||
showSuccess('操作成功完成!');
|
||||
await loadUsers(activePage);
|
||||
} else {
|
||||
showError(message);
|
||||
}
|
||||
|
||||
return res.data;
|
||||
};
|
||||
|
||||
// 处理刷新
|
||||
const handleRefresh = async () => {
|
||||
await loadUsers(activePage);
|
||||
};
|
||||
|
||||
const handleOpenModal = (userId) => {
|
||||
setEditUserId(userId);
|
||||
setOpenModal(true);
|
||||
};
|
||||
|
||||
const handleCloseModal = () => {
|
||||
setOpenModal(false);
|
||||
setEditUserId(0);
|
||||
};
|
||||
|
||||
const handleOkModal = (status) => {
|
||||
if (status === true) {
|
||||
handleCloseModal();
|
||||
handleRefresh();
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
loadUsers(0)
|
||||
.then()
|
||||
.catch((reason) => {
|
||||
showError(reason);
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Stack direction="row" alignItems="center" justifyContent="space-between" mb={5}>
|
||||
<Typography variant="h4">用户</Typography>
|
||||
|
||||
<Button variant="contained" color="primary" startIcon={<IconPlus />} onClick={() => handleOpenModal(0)}>
|
||||
新建用户
|
||||
</Button>
|
||||
</Stack>
|
||||
<Card>
|
||||
<Box component="form" onSubmit={searchUsers} noValidate>
|
||||
<TableToolBar
|
||||
filterName={searchKeyword}
|
||||
handleFilterName={handleSearchKeyword}
|
||||
placeholder={'搜索用户的ID,用户名,显示名称,以及邮箱地址...'}
|
||||
/>
|
||||
</Box>
|
||||
<Toolbar
|
||||
sx={{
|
||||
textAlign: 'right',
|
||||
height: 50,
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
p: (theme) => theme.spacing(0, 1, 0, 3)
|
||||
}}
|
||||
>
|
||||
<Container>
|
||||
<ButtonGroup variant="outlined" aria-label="outlined small primary button group">
|
||||
<Button onClick={handleRefresh} startIcon={<IconRefresh width={'18px'} />}>
|
||||
刷新
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
</Container>
|
||||
</Toolbar>
|
||||
{searching && <LinearProgress />}
|
||||
<PerfectScrollbar component="div">
|
||||
<TableContainer sx={{ overflow: 'unset' }}>
|
||||
<Table sx={{ minWidth: 800 }}>
|
||||
<UsersTableHead />
|
||||
<TableBody>
|
||||
{users.slice(activePage * ITEMS_PER_PAGE, (activePage + 1) * ITEMS_PER_PAGE).map((row) => (
|
||||
<UsersTableRow
|
||||
item={row}
|
||||
manageUser={manageUser}
|
||||
key={row.id}
|
||||
handleOpenModal={handleOpenModal}
|
||||
setModalUserId={setEditUserId}
|
||||
/>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
</PerfectScrollbar>
|
||||
<TablePagination
|
||||
page={activePage}
|
||||
component="div"
|
||||
count={users.length + (users.length % ITEMS_PER_PAGE === 0 ? 1 : 0)}
|
||||
rowsPerPage={ITEMS_PER_PAGE}
|
||||
onPageChange={onPaginationChange}
|
||||
rowsPerPageOptions={[ITEMS_PER_PAGE]}
|
||||
/>
|
||||
</Card>
|
||||
<EditeModal open={openModal} onCancel={handleCloseModal} onOk={handleOkModal} userId={editUserId} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user