refactor: 用户管理重构优化
This commit is contained in:
24
admin/src/enum/dict.tsx
Normal file
24
admin/src/enum/dict.tsx
Normal file
@@ -0,0 +1,24 @@
|
||||
import type {LabeledValue} from "@arco-design/web-react/es/Select/interface";
|
||||
import {Badge} from "@arco-design/web-react";
|
||||
import React from "react";
|
||||
|
||||
export const statusDict: LabeledValue[] = [
|
||||
{label: '启用', value: 1},
|
||||
{label: '禁用', value: 0}
|
||||
]
|
||||
|
||||
export const tableStatusDict: LabeledValue[] = [
|
||||
{label: <Badge status="success" text="启用中"></Badge>, value: 1},
|
||||
{label: <Badge status="error" text="禁用中"></Badge>, value: 0}
|
||||
]
|
||||
|
||||
export const genderDict: LabeledValue[] = [
|
||||
{label: '男', value: 1},
|
||||
{label: '女', value: 0},
|
||||
{label: '未知', value: 2}
|
||||
]
|
||||
|
||||
export const getDictValue = (dict: LabeledValue[], value: number | string) => {
|
||||
const findOne = dict.find(item => item.value === value);
|
||||
return findOne ? findOne.label : '';
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
export const enum Gender {
|
||||
MALE,
|
||||
FEMALE,
|
||||
UNKNOWN
|
||||
}
|
||||
|
||||
export const GenderDict = ['女', '男', '未知'];
|
||||
|
||||
@@ -1,101 +1,103 @@
|
||||
import './style/global.less';
|
||||
import React, { useEffect } from 'react';
|
||||
import React, {useEffect} from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { ConfigProvider } from '@arco-design/web-react';
|
||||
import {ConfigProvider} from '@arco-design/web-react';
|
||||
import zhCN from '@arco-design/web-react/es/locale/zh-CN';
|
||||
import enUS from '@arco-design/web-react/es/locale/en-US';
|
||||
import { BrowserRouter, Switch, Route } from 'react-router-dom';
|
||||
import {BrowserRouter, Switch, Route} from 'react-router-dom';
|
||||
|
||||
import PageLayout from './layout';
|
||||
import { GlobalContext } from './context';
|
||||
import {GlobalContext} from './context';
|
||||
import Login from './pages/login';
|
||||
import checkLogin from './utils/checkLogin';
|
||||
import changeTheme from './utils/changeTheme';
|
||||
import useStorage from './utils/useStorage';
|
||||
import { QueryClientProvider, QueryClient } from 'react-query';
|
||||
import { useStore } from '@/store';
|
||||
import {QueryClientProvider, QueryClient} from 'react-query';
|
||||
import {useStore} from '@/store';
|
||||
|
||||
const queryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
retry: 0
|
||||
}
|
||||
}
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
retry: 0,
|
||||
refetchOnWindowFocus: false,
|
||||
keepPreviousData: true
|
||||
},
|
||||
}
|
||||
});
|
||||
|
||||
function Index() {
|
||||
const [lang, setLang] = useStorage('arco-lang', 'zh-CN');
|
||||
const [theme, setTheme] = useStorage('arco-theme', 'light');
|
||||
const { fetchUserInfo, init } = useStore();
|
||||
const [lang, setLang] = useStorage('arco-lang', 'zh-CN');
|
||||
const [theme, setTheme] = useStorage('arco-theme', 'light');
|
||||
const {fetchUserInfo, init} = useStore();
|
||||
|
||||
function getArcoLocale() {
|
||||
switch (lang) {
|
||||
case 'zh-CN':
|
||||
return zhCN;
|
||||
case 'en-US':
|
||||
return enUS;
|
||||
default:
|
||||
return zhCN;
|
||||
}
|
||||
}
|
||||
function getArcoLocale() {
|
||||
switch (lang) {
|
||||
case 'zh-CN':
|
||||
return zhCN;
|
||||
case 'en-US':
|
||||
return enUS;
|
||||
default:
|
||||
return zhCN;
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
init();
|
||||
if (checkLogin()) {
|
||||
fetchUserInfo();
|
||||
} else if (window.location.pathname.replace(/\//g, '') !== 'login') {
|
||||
window.location.pathname = '/login';
|
||||
}
|
||||
}, []);
|
||||
useEffect(() => {
|
||||
init();
|
||||
if (checkLogin()) {
|
||||
fetchUserInfo();
|
||||
} else if (window.location.pathname.replace(/\//g, '') !== 'login') {
|
||||
window.location.pathname = '/login';
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
changeTheme(theme);
|
||||
}, [theme]);
|
||||
useEffect(() => {
|
||||
changeTheme(theme);
|
||||
}, [theme]);
|
||||
|
||||
const contextValue = {
|
||||
lang,
|
||||
setLang,
|
||||
theme,
|
||||
setTheme
|
||||
};
|
||||
const contextValue = {
|
||||
lang,
|
||||
setLang,
|
||||
theme,
|
||||
setTheme
|
||||
};
|
||||
|
||||
return (
|
||||
<BrowserRouter>
|
||||
<ConfigProvider
|
||||
locale={getArcoLocale()}
|
||||
componentConfig={{
|
||||
Card: {
|
||||
bordered: false
|
||||
},
|
||||
List: {
|
||||
bordered: false
|
||||
},
|
||||
Table: {
|
||||
border: { headerCell: true, bodyCell: true, wrapper: true }
|
||||
},
|
||||
Form: {
|
||||
labelCol: { span: 5 },
|
||||
wrapperCol: { span: 18 },
|
||||
},
|
||||
Modal: {
|
||||
style: { width: '550px' },
|
||||
autoFocus: false,
|
||||
focusLock: false,
|
||||
escToExit:false
|
||||
}
|
||||
}}
|
||||
>
|
||||
<GlobalContext.Provider value={contextValue}>
|
||||
<Switch>
|
||||
<Route path="/login" component={Login} />
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<Route path="/" component={PageLayout} />
|
||||
</QueryClientProvider>
|
||||
</Switch>
|
||||
</GlobalContext.Provider>
|
||||
</ConfigProvider>
|
||||
</BrowserRouter>
|
||||
);
|
||||
return (
|
||||
<BrowserRouter>
|
||||
<ConfigProvider
|
||||
locale={getArcoLocale()}
|
||||
componentConfig={{
|
||||
Card: {
|
||||
bordered: false
|
||||
},
|
||||
List: {
|
||||
bordered: false
|
||||
},
|
||||
Table: {
|
||||
border: {wrapper: true}
|
||||
},
|
||||
Form: {
|
||||
labelCol: {span: 5},
|
||||
wrapperCol: {span: 18},
|
||||
},
|
||||
Modal: {
|
||||
style: {width: '550px'},
|
||||
autoFocus: false,
|
||||
focusLock: false,
|
||||
escToExit: false
|
||||
}
|
||||
}}
|
||||
>
|
||||
<GlobalContext.Provider value={contextValue}>
|
||||
<Switch>
|
||||
<Route path="/login" component={Login}/>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<Route path="/" component={PageLayout}/>
|
||||
</QueryClientProvider>
|
||||
</Switch>
|
||||
</GlobalContext.Provider>
|
||||
</ConfigProvider>
|
||||
</BrowserRouter>
|
||||
);
|
||||
}
|
||||
|
||||
ReactDOM.render(<Index />, document.getElementById('root'));
|
||||
ReactDOM.render(<Index/>, document.getElementById('root'));
|
||||
|
||||
@@ -1,101 +1,101 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Modal, Form, Input, Message, Select } from '@arco-design/web-react';
|
||||
import { createMenu, getMenu, updateMenu } from '@/api/menu';
|
||||
import React, {useEffect, useState} from 'react';
|
||||
import {Modal, Form, Input, Message, Select, InputNumber} from '@arco-design/web-react';
|
||||
import {createMenu, getMenu, updateMenu} from '@/api/menu';
|
||||
|
||||
interface Props {
|
||||
record: { [key: string]: any } | null,
|
||||
visible: boolean,
|
||||
handleConfirm: () => void,
|
||||
handleCancel: () => void
|
||||
record: { [key: string]: any } | null,
|
||||
visible: boolean,
|
||||
handleConfirm: () => void,
|
||||
handleCancel: () => void
|
||||
}
|
||||
|
||||
function FormComponent(props: Props) {
|
||||
const { visible, record, handleConfirm, handleCancel } = props;
|
||||
const [confirmLoading, setConfirmLoading] = useState(false);
|
||||
export default function FormComponent(props: Props) {
|
||||
const {visible, record, handleConfirm, handleCancel} = props;
|
||||
const [confirmLoading, setConfirmLoading] = useState(false);
|
||||
|
||||
const onOk = async () => {
|
||||
try {
|
||||
setConfirmLoading(true);
|
||||
const values = await form.validate();
|
||||
if (record?.id) {
|
||||
await updateMenu(values);
|
||||
} else {
|
||||
await createMenu(values);
|
||||
}
|
||||
Message.success(`${record?.id ? '更新' : '新增'}成功!`);
|
||||
handleConfirm();
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
} finally {
|
||||
setConfirmLoading(false);
|
||||
}
|
||||
};
|
||||
const onOk = async () => {
|
||||
try {
|
||||
setConfirmLoading(true);
|
||||
const values = await form.validate();
|
||||
if (record?.id) {
|
||||
await updateMenu(values);
|
||||
} else {
|
||||
await createMenu(values);
|
||||
}
|
||||
Message.success(`${record?.id ? '更新' : '新增'}成功!`);
|
||||
handleConfirm();
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
} finally {
|
||||
setConfirmLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const [form] = Form.useForm();
|
||||
const [menuList, setMenuList] = useState([]);
|
||||
const [form] = Form.useForm();
|
||||
const [menuList, setMenuList] = useState([]);
|
||||
|
||||
const fetchMenuList = async () => {
|
||||
const { data } = await getMenu();
|
||||
const mergeMenu = [
|
||||
{
|
||||
id: 0,
|
||||
name: '无父级'
|
||||
},
|
||||
...data
|
||||
];
|
||||
setMenuList(mergeMenu);
|
||||
};
|
||||
const fetchMenuList = async () => {
|
||||
const {data} = await getMenu();
|
||||
const mergeMenu = [
|
||||
{
|
||||
id: 0,
|
||||
name: '无父级'
|
||||
},
|
||||
...data
|
||||
];
|
||||
setMenuList(mergeMenu);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
record && form.setFieldsValue({
|
||||
...record
|
||||
});
|
||||
}, [record]);
|
||||
useEffect(() => {
|
||||
record && form.setFieldsValue({
|
||||
...record
|
||||
});
|
||||
}, [record]);
|
||||
|
||||
useEffect(() => {
|
||||
if (visible) {
|
||||
fetchMenuList();
|
||||
} else {
|
||||
form.resetFields();
|
||||
}
|
||||
}, [visible]);
|
||||
useEffect(() => {
|
||||
if (visible) {
|
||||
fetchMenuList();
|
||||
} else {
|
||||
form.resetFields();
|
||||
}
|
||||
}, [visible]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Modal
|
||||
title={<span>{`${record?.id ? '更新' : '新增'}菜单信息`}</span>}
|
||||
visible={visible}
|
||||
onOk={onOk}
|
||||
onCancel={handleCancel}
|
||||
confirmLoading={confirmLoading}
|
||||
>
|
||||
<Form form={form}>
|
||||
<Form.Item hidden field="id">
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item label="父级菜单" field="parentId" rules={[{ required: true }]}>
|
||||
<Select
|
||||
placeholder="请选择父级菜单"
|
||||
allowClear
|
||||
>
|
||||
{menuList.map((option) => (
|
||||
<Select.Option disabled={option.id === record?.id} key={option.id} value={option.id}>
|
||||
{option.name}
|
||||
</Select.Option>
|
||||
))}
|
||||
</Select>
|
||||
</Form.Item>
|
||||
<Form.Item label="菜单名称" field="name" rules={[{ required: true }]}>
|
||||
<Input placeholder="请输入菜单名称" />
|
||||
</Form.Item>
|
||||
<Form.Item label="菜单url" field="url" rules={[{ required: true }]}>
|
||||
<Input placeholder="请输入菜单url" />
|
||||
</Form.Item>
|
||||
|
||||
</Form>
|
||||
</Modal>
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div>
|
||||
<Modal
|
||||
title={<span>{`${record?.id ? '更新' : '新增'}菜单信息`}</span>}
|
||||
visible={visible}
|
||||
onOk={onOk}
|
||||
onCancel={handleCancel}
|
||||
confirmLoading={confirmLoading}
|
||||
>
|
||||
<Form form={form}>
|
||||
<Form.Item hidden field="id">
|
||||
<Input/>
|
||||
</Form.Item>
|
||||
<Form.Item label="父级菜单" field="parentId" rules={[{required: true}]}>
|
||||
<Select
|
||||
placeholder="请选择父级菜单"
|
||||
allowClear
|
||||
>
|
||||
{menuList.map((option) => (
|
||||
<Select.Option disabled={option.id === record?.id} key={option.id} value={option.id}>
|
||||
{option.name}
|
||||
</Select.Option>
|
||||
))}
|
||||
</Select>
|
||||
</Form.Item>
|
||||
<Form.Item label="菜单名称" field="name" rules={[{required: true}]}>
|
||||
<Input placeholder="请输入菜单名称"/>
|
||||
</Form.Item>
|
||||
<Form.Item label="菜单url" field="url" rules={[{required: true}]}>
|
||||
<Input placeholder="请输入菜单url"/>
|
||||
</Form.Item>
|
||||
<Form.Item label="排序" field="sort">
|
||||
<InputNumber placeholder="请输入排序"/>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default FormComponent;
|
||||
|
||||
@@ -35,6 +35,11 @@ function ConfigManage() {
|
||||
dataIndex: 'url',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '排序',
|
||||
dataIndex: 'sort',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
dataIndex: 'operations',
|
||||
|
||||
86
admin/src/pages/user/constants.tsx
Normal file
86
admin/src/pages/user/constants.tsx
Normal file
@@ -0,0 +1,86 @@
|
||||
import React from 'react';
|
||||
|
||||
import {
|
||||
Typography,
|
||||
TableColumnProps,
|
||||
Button,
|
||||
Popconfirm,
|
||||
Space,
|
||||
Avatar,
|
||||
} from '@arco-design/web-react';
|
||||
import {genderDict, getDictValue, tableStatusDict} from "@/enum/dict";
|
||||
import {dayjs} from "@arco-design/web-react/es/_util/dayjs";
|
||||
|
||||
const {Text} = Typography;
|
||||
|
||||
export const enum TableOptions {
|
||||
EDIT = 'edit',
|
||||
DELETE = 'delete',
|
||||
ASSIGN_ROLE = 'assign_role',
|
||||
}
|
||||
|
||||
export const reactQueryKey = 'user'
|
||||
|
||||
export function getColumns(callback: (type: TableOptions, record: any) => Promise<void>): TableColumnProps[] {
|
||||
return [
|
||||
{
|
||||
title: 'id',
|
||||
dataIndex: 'id',
|
||||
render: (value) => <Text copyable>{value}</Text>,
|
||||
},
|
||||
{
|
||||
title: '头像',
|
||||
dataIndex: 'avatar',
|
||||
render: (value) => (<Avatar>
|
||||
{value ? <img alt="avatar" src={value?.path}/> : <Avatar>未上传</Avatar>}
|
||||
</Avatar>),
|
||||
},
|
||||
{
|
||||
title: '账号',
|
||||
dataIndex: 'account',
|
||||
render: (value) => <Text copyable>{value}</Text>,
|
||||
},
|
||||
{
|
||||
title: '昵称',
|
||||
dataIndex: 'name',
|
||||
},
|
||||
{
|
||||
title: '性别',
|
||||
dataIndex: 'gender',
|
||||
render: (value) => <Text>{getDictValue(genderDict, value)}</Text>
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
dataIndex: 'createdAt',
|
||||
width: '15%',
|
||||
render: (value) => dayjs(value).format('YYYY-MM-DD HH:mm:ss'),
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
dataIndex: 'status',
|
||||
width: '10%',
|
||||
render: (x) => getDictValue(tableStatusDict, x)
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
dataIndex: 'operations',
|
||||
align: 'center',
|
||||
width: '300px',
|
||||
render: (_, record) => (
|
||||
<Space>
|
||||
<Button size='small' onClick={() => callback(TableOptions.EDIT, record)} type="text" status="default">更新</Button>
|
||||
<Button size='small' onClick={() => callback(TableOptions.ASSIGN_ROLE, record)} type="text"
|
||||
status="default">分配角色</Button>
|
||||
<Popconfirm
|
||||
focusLock
|
||||
title="删除数据"
|
||||
content="确认删除该条数据吗?"
|
||||
onOk={() => callback(TableOptions.DELETE, record)}
|
||||
>
|
||||
<Button size='small' type="text" status="danger">删除</Button>
|
||||
</Popconfirm>
|
||||
</Space>
|
||||
)
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,94 +1,94 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Modal, Form, Input, Select, Message } from '@arco-design/web-react';
|
||||
import React, {useEffect, useState} from 'react';
|
||||
import {Modal, Form, Input, Select, Message} from '@arco-design/web-react';
|
||||
import UploadFile from '@/components/Upload';
|
||||
import { createUser, updateUser } from '@/api/user';
|
||||
import {createUser, updateUser} from '@/api/user';
|
||||
import {genderDict, statusDict} from "@/enum/dict";
|
||||
|
||||
interface Props {
|
||||
record: { [key: string]: any } | null,
|
||||
visible: boolean,
|
||||
handleConfirm: () => void,
|
||||
handleCancel: () => void
|
||||
record: { [key: string]: any } | null,
|
||||
visible: boolean,
|
||||
handleConfirm: () => void,
|
||||
handleCancel: () => void
|
||||
}
|
||||
|
||||
function FormComponent(props: Props) {
|
||||
const { visible, record, handleConfirm, handleCancel } = props;
|
||||
const [confirmLoading, setConfirmLoading] = useState(false);
|
||||
const {visible, record, handleConfirm, handleCancel} = props;
|
||||
const [confirmLoading, setConfirmLoading] = useState(false);
|
||||
|
||||
const onOk = async () => {
|
||||
try {
|
||||
setConfirmLoading(true);
|
||||
const values = await form.validate();
|
||||
const data = {
|
||||
...values,
|
||||
avatarId: values.avatar?.[0]?.response ?? null
|
||||
};
|
||||
if (record?.id) {
|
||||
await updateUser(data);
|
||||
} else {
|
||||
await createUser(data);
|
||||
}
|
||||
Message.success(`${record?.id ? '更新' : '新增'}成功!`);
|
||||
handleConfirm();
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
} finally {
|
||||
setConfirmLoading(false);
|
||||
}
|
||||
};
|
||||
const onOk = async () => {
|
||||
try {
|
||||
setConfirmLoading(true);
|
||||
const values = await form.validate();
|
||||
const data = {
|
||||
...values,
|
||||
avatarId: values.avatar?.[0]?.response ?? null
|
||||
};
|
||||
if (record?.id) {
|
||||
await updateUser(data);
|
||||
} else {
|
||||
await createUser(data);
|
||||
}
|
||||
Message.success(`${record?.id ? '更新' : '新增'}成功!`);
|
||||
handleConfirm();
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
} finally {
|
||||
setConfirmLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const [form] = Form.useForm();
|
||||
const [form] = Form.useForm();
|
||||
|
||||
useEffect(() => {
|
||||
record && form.setFieldsValue({
|
||||
...record,
|
||||
avatar: record.avatar ? [{
|
||||
uid: record.avatar.id,
|
||||
response: record.avatar.id,
|
||||
url: record.avatar.path
|
||||
}] : undefined
|
||||
});
|
||||
}, [record]);
|
||||
useEffect(() => {
|
||||
record && form.setFieldsValue({
|
||||
...record,
|
||||
avatar: record.avatar ? [{
|
||||
uid: record.avatar.id,
|
||||
response: record.avatar.id,
|
||||
url: record.avatar.path
|
||||
}] : undefined
|
||||
});
|
||||
}, [record]);
|
||||
|
||||
useEffect(() => {
|
||||
!visible && form.resetFields()
|
||||
}, [visible]);
|
||||
useEffect(() => {
|
||||
!visible && form.resetFields()
|
||||
}, [visible]);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title={<span>{`${record?.id ? '更新' : '新增'}用户信息`}</span>}
|
||||
visible={visible}
|
||||
onOk={onOk}
|
||||
onCancel={handleCancel}
|
||||
confirmLoading={confirmLoading}
|
||||
>
|
||||
<Form form={form}>
|
||||
<Form.Item hidden={true} field="id" rules={[{ required: false }]}>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item triggerPropName="fileList" label="用户头像"
|
||||
field="avatar">
|
||||
<UploadFile limit={1} dir="/user" />
|
||||
</Form.Item>
|
||||
<Form.Item label="用户昵称" field="name" rules={[{ required: true }]}>
|
||||
<Input placeholder="请输入用户昵称" />
|
||||
</Form.Item>
|
||||
<Form.Item disabled={record?.id} label="用户账号" field="account" rules={[{ required: true }]}>
|
||||
<Input placeholder="请输入用户账号" />
|
||||
</Form.Item>
|
||||
{!record?.id && <Form.Item label="用户密码" field="password" rules={[{ required: true }]}>
|
||||
<Input type="password" placeholder="请输入用户密码" />
|
||||
return (
|
||||
<Modal
|
||||
title={<span>{`${record?.id ? '更新' : '新增'}数据`}</span>}
|
||||
visible={visible}
|
||||
onOk={onOk}
|
||||
onCancel={handleCancel}
|
||||
confirmLoading={confirmLoading}
|
||||
>
|
||||
<Form form={form}>
|
||||
<Form.Item hidden={true} field="id" rules={[{required: false}]}>
|
||||
<Input/>
|
||||
</Form.Item>
|
||||
<Form.Item triggerPropName="fileList" label="用户头像"
|
||||
field="avatar">
|
||||
<UploadFile limit={1} dir="/user"/>
|
||||
</Form.Item>
|
||||
<Form.Item label="用户昵称" field="name" rules={[{required: true}]}>
|
||||
<Input placeholder="请输入用户昵称"/>
|
||||
</Form.Item>
|
||||
<Form.Item disabled={record?.id} label="用户账号" field="account" rules={[{required: true}]}>
|
||||
<Input placeholder="请输入用户账号"/>
|
||||
</Form.Item>
|
||||
{!record?.id && <Form.Item label="用户密码" field="password" rules={[{required: true}]}>
|
||||
<Input type="password" placeholder="请输入用户密码"/>
|
||||
</Form.Item>}
|
||||
|
||||
<Form.Item label="用户状态" field="status" rules={[{ required: true }]}>
|
||||
<Select placeholder="请选择" options={[{ label: '启用', value: 1 }, { label: '禁用', value: 0 }]} />
|
||||
</Form.Item>
|
||||
<Form.Item label="用户性别" field="gender" rules={[{ required: true }]}>
|
||||
<Select placeholder="请选择"
|
||||
options={[{ label: '女', value: 0 }, { label: '男', value: 1 }, { label: '未知', value: 2 }]} />
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
);
|
||||
<Form.Item label="用户状态" field="status" rules={[{required: true}]} initialValue={1}>
|
||||
<Select placeholder="请选择" options={statusDict}/>
|
||||
</Form.Item>
|
||||
<Form.Item label="用户性别" field="gender" rules={[{required: true}]}>
|
||||
<Select placeholder="请选择"
|
||||
options={genderDict}/>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
export default FormComponent;
|
||||
|
||||
@@ -1,208 +1,167 @@
|
||||
import React, { useState } from 'react';
|
||||
import React, {useEffect, useState} from 'react';
|
||||
import {
|
||||
Card,
|
||||
Table,
|
||||
Typography,
|
||||
TableColumnProps,
|
||||
Badge,
|
||||
PaginationProps,
|
||||
Button,
|
||||
Popconfirm,
|
||||
Divider,
|
||||
Avatar,
|
||||
Message
|
||||
Button,
|
||||
Card,
|
||||
Message,
|
||||
PaginationProps,
|
||||
Table,
|
||||
TableColumnProps,
|
||||
Typography
|
||||
} from '@arco-design/web-react';
|
||||
import { useQuery } from 'react-query';
|
||||
import {useQuery} from 'react-query';
|
||||
import Form from './form';
|
||||
import GrantRolesForm from './grantRoles';
|
||||
import { GenderDict } from '@/enum';
|
||||
import { deleteUser, getUsers } from '@/api/user';
|
||||
import {deleteUser, getUsers} from '@/api/user';
|
||||
import SearchForm from "./searchForm";
|
||||
import {IconPlus} from '@arco-design/web-react/icon';
|
||||
import {getColumns, TableOptions, reactQueryKey} from './constants';
|
||||
|
||||
const { Title, Text } = Typography;
|
||||
const {Title} = Typography;
|
||||
|
||||
function UserManage() {
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [visibleAssignRoles, setVisibleAssignRoles] = useState(false);
|
||||
const [record, setRecord] = useState(null);
|
||||
const columns: TableColumnProps[] = [
|
||||
{
|
||||
title: 'id',
|
||||
dataIndex: 'id',
|
||||
render: (value) => <Text copyable>{value}</Text>,
|
||||
align: 'center'
|
||||
},
|
||||
{
|
||||
title: '头像',
|
||||
dataIndex: 'avatar',
|
||||
align: 'center',
|
||||
render: (value) => (<Avatar>
|
||||
{value ? <img alt="avatar" src={value?.path} /> : <Avatar>未上传</Avatar>}
|
||||
</Avatar>)
|
||||
},
|
||||
{
|
||||
title: '账号',
|
||||
dataIndex: 'account',
|
||||
align: 'center',
|
||||
render: (value) => <Text copyable>{value}</Text>,
|
||||
ellipsis: true
|
||||
},
|
||||
{
|
||||
title: '昵称',
|
||||
dataIndex: 'name',
|
||||
align: 'center'
|
||||
},
|
||||
{
|
||||
title: '性别',
|
||||
dataIndex: 'gender',
|
||||
align: 'center',
|
||||
render: (value) => <Text>{GenderDict[value]}</Text>
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
dataIndex: 'status',
|
||||
align: 'center',
|
||||
render: (x) => {
|
||||
if (x === 0) {
|
||||
return <Badge status="error" text="禁用中"></Badge>;
|
||||
}
|
||||
return <Badge status="success" text="启用中"></Badge>;
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
dataIndex: 'operations',
|
||||
width: 300,
|
||||
render: (_, record) => (
|
||||
<>
|
||||
<Button onClick={() => showModal(record)} type="text" status="default">更新</Button>
|
||||
<Divider type="vertical" />
|
||||
<Button onClick={() => showAssignRolesModal(record)} type="text" status="default">分配角色</Button>
|
||||
<Divider type="vertical" />
|
||||
<Popconfirm
|
||||
focusLock
|
||||
title="删除数据"
|
||||
content="确认删除该条数据吗?"
|
||||
onOk={() => onDelete(record)}
|
||||
>
|
||||
<Button type="text" status="danger">删除</Button>
|
||||
</Popconfirm>
|
||||
</>
|
||||
),
|
||||
align: 'center'
|
||||
}
|
||||
];
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [visibleAssignRoles, setVisibleAssignRoles] = useState(false);
|
||||
const [record, setRecord] = useState(null);
|
||||
const [pagination, setPagination] = useState<PaginationProps>({
|
||||
sizeCanChange: true,
|
||||
showTotal: true,
|
||||
pageSize: 10,
|
||||
current: 1,
|
||||
pageSizeChangeResetCurrent: true,
|
||||
total: 0
|
||||
});
|
||||
|
||||
const [pagination, setPagination] = useState<PaginationProps>({
|
||||
sizeCanChange: true,
|
||||
showTotal: true,
|
||||
pageSize: 10,
|
||||
current: 1,
|
||||
pageSizeChangeResetCurrent: true,
|
||||
total: 0
|
||||
});
|
||||
const [searchQuery, setSearchQuery] = useState<Record<string, any>>({
|
||||
page: pagination.current,
|
||||
pageSize: pagination.pageSize,
|
||||
});
|
||||
|
||||
const onDelete = async ({ id }) => {
|
||||
await deleteUser(id);
|
||||
Message.success('删除成功!');
|
||||
await refetch();
|
||||
};
|
||||
const showAssignRolesModal = (payload) => {
|
||||
setRecord(payload);
|
||||
setVisibleAssignRoles(true);
|
||||
};
|
||||
|
||||
const onChangeTable = async ({ current, pageSize }) => {
|
||||
setPagination({
|
||||
...pagination,
|
||||
current,
|
||||
pageSize
|
||||
});
|
||||
};
|
||||
const showModal = (payload?) => {
|
||||
payload && setRecord(payload);
|
||||
setVisible(true);
|
||||
};
|
||||
|
||||
const fetchData = async ({ queryKey }) => {
|
||||
const [, { page, pageSize }] = queryKey;
|
||||
const { data } = await getUsers({ page, pageSize });
|
||||
setPagination(prevState => {
|
||||
prevState.total = data.count;
|
||||
return prevState;
|
||||
});
|
||||
if (page > 1 && data.list.length === 0) {
|
||||
setPagination({
|
||||
...pagination,
|
||||
current: page - 1
|
||||
});
|
||||
}
|
||||
return data;
|
||||
};
|
||||
const onDelete = async ({id}) => {
|
||||
await deleteUser(id);
|
||||
Message.success('删除成功!');
|
||||
await refetch();
|
||||
};
|
||||
|
||||
const {
|
||||
data,
|
||||
isLoading,
|
||||
isFetching,
|
||||
refetch
|
||||
} = useQuery(
|
||||
['user', { page: pagination.current, pageSize: pagination.pageSize }],
|
||||
fetchData,
|
||||
{
|
||||
refetchOnWindowFocus: false,
|
||||
keepPreviousData: true
|
||||
});
|
||||
const tableCallback = async (type: TableOptions, record: any) => {
|
||||
switch (type) {
|
||||
case TableOptions.EDIT:
|
||||
showModal(record)
|
||||
break;
|
||||
case TableOptions.DELETE:
|
||||
await onDelete(record);
|
||||
break
|
||||
case TableOptions.ASSIGN_ROLE:
|
||||
showAssignRolesModal(record);
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
const showAssignRolesModal = (payload) => {
|
||||
setRecord(payload);
|
||||
setVisibleAssignRoles(true);
|
||||
};
|
||||
const columns: TableColumnProps[] = getColumns(tableCallback)
|
||||
|
||||
const showModal = (payload?) => {
|
||||
payload && setRecord(payload);
|
||||
setVisible(true);
|
||||
};
|
||||
const handleSearch = async (params) => {
|
||||
setSearchQuery(prev => ({...prev, page: 1, ...params}))
|
||||
}
|
||||
|
||||
const handleConfirm = async () => {
|
||||
setRecord(null);
|
||||
setVisible(false);
|
||||
setVisibleAssignRoles(false);
|
||||
await refetch();
|
||||
};
|
||||
const onChangeTable = async ({current, pageSize}) => {
|
||||
setSearchQuery(prev => ({...prev, page: current, pageSize}));
|
||||
};
|
||||
|
||||
const handleCancel = () => {
|
||||
setRecord(null);
|
||||
setVisible(false);
|
||||
setVisibleAssignRoles(false);
|
||||
};
|
||||
const fetchData = async ({queryKey}) => {
|
||||
const [, params] = queryKey;
|
||||
console.log(params)
|
||||
const {data} = await getUsers(params);
|
||||
setPagination(prev => ({
|
||||
...prev,
|
||||
total: data.count
|
||||
}))
|
||||
// 当前列表不存在数据回退1页
|
||||
if (params.page > 1 && data.list.length === 0) {
|
||||
setSearchQuery({
|
||||
...searchQuery,
|
||||
page: params.page - 1
|
||||
});
|
||||
}
|
||||
return data;
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Card>
|
||||
<Form
|
||||
handleConfirm={handleConfirm}
|
||||
handleCancel={handleCancel}
|
||||
visible={visible}
|
||||
record={record}
|
||||
/>
|
||||
<GrantRolesForm
|
||||
handleConfirm={handleConfirm}
|
||||
handleCancel={handleCancel}
|
||||
visible={visibleAssignRoles}
|
||||
record={record}
|
||||
/>
|
||||
<Title heading={6}>用户管理</Title>
|
||||
const {data, isLoading, isFetching, refetch} = useQuery(
|
||||
[reactQueryKey, {
|
||||
page: pagination.current,
|
||||
pageSize: pagination.pageSize,
|
||||
...searchQuery
|
||||
}],
|
||||
fetchData);
|
||||
|
||||
<Button
|
||||
style={{ marginBottom: 12 }}
|
||||
type="primary"
|
||||
onClick={() => showModal()}
|
||||
>
|
||||
新增用户
|
||||
</Button>
|
||||
const handleConfirm = async () => {
|
||||
setRecord(null);
|
||||
setVisible(false);
|
||||
setVisibleAssignRoles(false);
|
||||
await refetch();
|
||||
};
|
||||
|
||||
<Table
|
||||
rowKey="id"
|
||||
loading={isLoading || isFetching}
|
||||
columns={columns}
|
||||
onChange={onChangeTable}
|
||||
pagination={pagination}
|
||||
data={data?.list}
|
||||
/>
|
||||
</Card>
|
||||
</>
|
||||
);
|
||||
const handleCancel = () => {
|
||||
setRecord(null);
|
||||
setVisible(false);
|
||||
setVisibleAssignRoles(false);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setPagination(prev => ({
|
||||
...prev,
|
||||
current: searchQuery.page,
|
||||
pageSize: pagination.pageSize,
|
||||
}))
|
||||
}, [searchQuery])
|
||||
|
||||
return (
|
||||
<>
|
||||
<Card>
|
||||
<Form
|
||||
handleConfirm={handleConfirm}
|
||||
handleCancel={handleCancel}
|
||||
visible={visible}
|
||||
record={record}
|
||||
/>
|
||||
<GrantRolesForm
|
||||
handleConfirm={handleConfirm}
|
||||
handleCancel={handleCancel}
|
||||
visible={visibleAssignRoles}
|
||||
record={record}
|
||||
/>
|
||||
<Title heading={6}>用户管理</Title>
|
||||
|
||||
<SearchForm onSearch={handleSearch}/>
|
||||
|
||||
<Button
|
||||
style={{marginBottom: 12}}
|
||||
type="primary"
|
||||
icon={<IconPlus/>}
|
||||
onClick={() => showModal()}
|
||||
>
|
||||
新建
|
||||
</Button>
|
||||
|
||||
<Table
|
||||
rowKey="id"
|
||||
loading={isLoading || isFetching}
|
||||
columns={columns}
|
||||
onChange={onChangeTable}
|
||||
pagination={pagination}
|
||||
data={data?.list}
|
||||
/>
|
||||
</Card>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
75
admin/src/pages/user/searchForm.tsx
Normal file
75
admin/src/pages/user/searchForm.tsx
Normal file
@@ -0,0 +1,75 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
Form,
|
||||
Input,
|
||||
Button,
|
||||
Grid,
|
||||
Space, Divider, Select
|
||||
} from '@arco-design/web-react';
|
||||
import {IconRefresh, IconSearch} from '@arco-design/web-react/icon';
|
||||
import {statusDict} from "@/enum/dict";
|
||||
|
||||
const {Row, Col} = Grid;
|
||||
const {useForm} = Form;
|
||||
|
||||
function SearchForm(props: {
|
||||
onSearch: (values: Record<string, any>) => void;
|
||||
}) {
|
||||
const [form] = useForm();
|
||||
|
||||
const handleSubmit = () => {
|
||||
const values = form.getFieldsValue();
|
||||
props.onSearch(values);
|
||||
};
|
||||
|
||||
const handleReset = () => {
|
||||
form.resetFields();
|
||||
props.onSearch({});
|
||||
};
|
||||
|
||||
const colSpan = 6
|
||||
|
||||
return (
|
||||
<>
|
||||
<Form
|
||||
form={form}
|
||||
labelCol={{span: 5}}
|
||||
wrapperCol={{span: 19}}
|
||||
labelAlign="left"
|
||||
>
|
||||
<Row gutter={24}>
|
||||
<Col span={colSpan}>
|
||||
<Form.Item label="账号" field="account">
|
||||
<Input placeholder="请输入账号" allowClear/>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={colSpan}>
|
||||
<Form.Item label="昵称" field="name">
|
||||
<Input placeholder="请输入昵称" allowClear/>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={colSpan}>
|
||||
<Form.Item label="状态" field="status">
|
||||
<Select placeholder="请选择" options={statusDict} allowClear/>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={colSpan}>
|
||||
<Form.Item>
|
||||
<Space>
|
||||
<Button type="primary" icon={<IconSearch/>} onClick={handleSubmit}>
|
||||
搜索
|
||||
</Button>
|
||||
<Button icon={<IconRefresh/>} onClick={handleReset}>
|
||||
重置
|
||||
</Button>
|
||||
</Space>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
</Row>
|
||||
</Form>
|
||||
<Divider style={{margin: '0 0 20px 0'}}/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default SearchForm;
|
||||
@@ -1,15 +0,0 @@
|
||||
.search-form-wrapper {
|
||||
display: flex;
|
||||
border-bottom: 1px solid var(--color-border-1);
|
||||
margin-bottom: 20px;
|
||||
|
||||
.right-button {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
padding-left: 20px;
|
||||
margin-bottom: 20px;
|
||||
border-left: 1px solid var(--color-border-2);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useContext } from 'react';
|
||||
import { GlobalContext } from '../context';
|
||||
import { GlobalContext } from '@/context';
|
||||
import defaultLocale from '../locale';
|
||||
|
||||
function useLocale(locale = null) {
|
||||
|
||||
Reference in New Issue
Block a user