refactor: 用户管理重构优化

This commit is contained in:
2024-10-26 16:47:07 +08:00
parent b064c4cb87
commit 0283e018de
19 changed files with 1390 additions and 1248 deletions

View File

@@ -26,7 +26,11 @@
"version": "16.8" "version": "16.8"
} }
}, },
"plugins": ["react", "babel", "@typescript-eslint/eslint-plugin"], "plugins": [
"react",
"babel",
"@typescript-eslint/eslint-plugin"
],
"rules": { "rules": {
"react/display-name": 0, "react/display-name": 0,
"react/prop-types": 0 "react/prop-types": 0

24
admin/src/enum/dict.tsx Normal file
View 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 : '';
}

View File

@@ -1,7 +0,0 @@
export const enum Gender {
MALE,
FEMALE,
UNKNOWN
}
export const GenderDict = ['女', '男', '未知'];

View File

@@ -1,101 +1,103 @@
import './style/global.less'; import './style/global.less';
import React, { useEffect } from 'react'; import React, {useEffect} from 'react';
import ReactDOM from 'react-dom'; 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 zhCN from '@arco-design/web-react/es/locale/zh-CN';
import enUS from '@arco-design/web-react/es/locale/en-US'; 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 PageLayout from './layout';
import { GlobalContext } from './context'; import {GlobalContext} from './context';
import Login from './pages/login'; import Login from './pages/login';
import checkLogin from './utils/checkLogin'; import checkLogin from './utils/checkLogin';
import changeTheme from './utils/changeTheme'; import changeTheme from './utils/changeTheme';
import useStorage from './utils/useStorage'; import useStorage from './utils/useStorage';
import { QueryClientProvider, QueryClient } from 'react-query'; import {QueryClientProvider, QueryClient} from 'react-query';
import { useStore } from '@/store'; import {useStore} from '@/store';
const queryClient = new QueryClient({ const queryClient = new QueryClient({
defaultOptions: { defaultOptions: {
queries: { queries: {
retry: 0 retry: 0,
} refetchOnWindowFocus: false,
} keepPreviousData: true
},
}
}); });
function Index() { function Index() {
const [lang, setLang] = useStorage('arco-lang', 'zh-CN'); const [lang, setLang] = useStorage('arco-lang', 'zh-CN');
const [theme, setTheme] = useStorage('arco-theme', 'light'); const [theme, setTheme] = useStorage('arco-theme', 'light');
const { fetchUserInfo, init } = useStore(); const {fetchUserInfo, init} = useStore();
function getArcoLocale() { function getArcoLocale() {
switch (lang) { switch (lang) {
case 'zh-CN': case 'zh-CN':
return zhCN; return zhCN;
case 'en-US': case 'en-US':
return enUS; return enUS;
default: default:
return zhCN; return zhCN;
} }
} }
useEffect(() => { useEffect(() => {
init(); init();
if (checkLogin()) { if (checkLogin()) {
fetchUserInfo(); fetchUserInfo();
} else if (window.location.pathname.replace(/\//g, '') !== 'login') { } else if (window.location.pathname.replace(/\//g, '') !== 'login') {
window.location.pathname = '/login'; window.location.pathname = '/login';
} }
}, []); }, []);
useEffect(() => { useEffect(() => {
changeTheme(theme); changeTheme(theme);
}, [theme]); }, [theme]);
const contextValue = { const contextValue = {
lang, lang,
setLang, setLang,
theme, theme,
setTheme setTheme
}; };
return ( return (
<BrowserRouter> <BrowserRouter>
<ConfigProvider <ConfigProvider
locale={getArcoLocale()} locale={getArcoLocale()}
componentConfig={{ componentConfig={{
Card: { Card: {
bordered: false bordered: false
}, },
List: { List: {
bordered: false bordered: false
}, },
Table: { Table: {
border: { headerCell: true, bodyCell: true, wrapper: true } border: {wrapper: true}
}, },
Form: { Form: {
labelCol: { span: 5 }, labelCol: {span: 5},
wrapperCol: { span: 18 }, wrapperCol: {span: 18},
}, },
Modal: { Modal: {
style: { width: '550px' }, style: {width: '550px'},
autoFocus: false, autoFocus: false,
focusLock: false, focusLock: false,
escToExit:false escToExit: false
} }
}} }}
> >
<GlobalContext.Provider value={contextValue}> <GlobalContext.Provider value={contextValue}>
<Switch> <Switch>
<Route path="/login" component={Login} /> <Route path="/login" component={Login}/>
<QueryClientProvider client={queryClient}> <QueryClientProvider client={queryClient}>
<Route path="/" component={PageLayout} /> <Route path="/" component={PageLayout}/>
</QueryClientProvider> </QueryClientProvider>
</Switch> </Switch>
</GlobalContext.Provider> </GlobalContext.Provider>
</ConfigProvider> </ConfigProvider>
</BrowserRouter> </BrowserRouter>
); );
} }
ReactDOM.render(<Index />, document.getElementById('root')); ReactDOM.render(<Index/>, document.getElementById('root'));

View File

@@ -1,101 +1,101 @@
import React, { useEffect, useState } from 'react'; import React, {useEffect, useState} from 'react';
import { Modal, Form, Input, Message, Select } from '@arco-design/web-react'; import {Modal, Form, Input, Message, Select, InputNumber} from '@arco-design/web-react';
import { createMenu, getMenu, updateMenu } from '@/api/menu'; import {createMenu, getMenu, updateMenu} from '@/api/menu';
interface Props { interface Props {
record: { [key: string]: any } | null, record: { [key: string]: any } | null,
visible: boolean, visible: boolean,
handleConfirm: () => void, handleConfirm: () => void,
handleCancel: () => void handleCancel: () => void
} }
function FormComponent(props: Props) { export default function FormComponent(props: Props) {
const { visible, record, handleConfirm, handleCancel } = props; const {visible, record, handleConfirm, handleCancel} = props;
const [confirmLoading, setConfirmLoading] = useState(false); const [confirmLoading, setConfirmLoading] = useState(false);
const onOk = async () => { const onOk = async () => {
try { try {
setConfirmLoading(true); setConfirmLoading(true);
const values = await form.validate(); const values = await form.validate();
if (record?.id) { if (record?.id) {
await updateMenu(values); await updateMenu(values);
} else { } else {
await createMenu(values); await createMenu(values);
} }
Message.success(`${record?.id ? '更新' : '新增'}成功!`); Message.success(`${record?.id ? '更新' : '新增'}成功!`);
handleConfirm(); handleConfirm();
} catch (e) { } catch (e) {
console.log(e); console.log(e);
} finally { } finally {
setConfirmLoading(false); setConfirmLoading(false);
} }
}; };
const [form] = Form.useForm(); const [form] = Form.useForm();
const [menuList, setMenuList] = useState([]); const [menuList, setMenuList] = useState([]);
const fetchMenuList = async () => { const fetchMenuList = async () => {
const { data } = await getMenu(); const {data} = await getMenu();
const mergeMenu = [ const mergeMenu = [
{ {
id: 0, id: 0,
name: '无父级' name: '无父级'
}, },
...data ...data
]; ];
setMenuList(mergeMenu); setMenuList(mergeMenu);
}; };
useEffect(() => { useEffect(() => {
record && form.setFieldsValue({ record && form.setFieldsValue({
...record ...record
}); });
}, [record]); }, [record]);
useEffect(() => { useEffect(() => {
if (visible) { if (visible) {
fetchMenuList(); fetchMenuList();
} else { } else {
form.resetFields(); form.resetFields();
} }
}, [visible]); }, [visible]);
return ( return (
<div> <div>
<Modal <Modal
title={<span>{`${record?.id ? '更新' : '新增'}菜单信息`}</span>} title={<span>{`${record?.id ? '更新' : '新增'}菜单信息`}</span>}
visible={visible} visible={visible}
onOk={onOk} onOk={onOk}
onCancel={handleCancel} onCancel={handleCancel}
confirmLoading={confirmLoading} confirmLoading={confirmLoading}
> >
<Form form={form}> <Form form={form}>
<Form.Item hidden field="id"> <Form.Item hidden field="id">
<Input /> <Input/>
</Form.Item> </Form.Item>
<Form.Item label="父级菜单" field="parentId" rules={[{ required: true }]}> <Form.Item label="父级菜单" field="parentId" rules={[{required: true}]}>
<Select <Select
placeholder="请选择父级菜单" placeholder="请选择父级菜单"
allowClear allowClear
> >
{menuList.map((option) => ( {menuList.map((option) => (
<Select.Option disabled={option.id === record?.id} key={option.id} value={option.id}> <Select.Option disabled={option.id === record?.id} key={option.id} value={option.id}>
{option.name} {option.name}
</Select.Option> </Select.Option>
))} ))}
</Select> </Select>
</Form.Item> </Form.Item>
<Form.Item label="菜单名称" field="name" rules={[{ required: true }]}> <Form.Item label="菜单名称" field="name" rules={[{required: true}]}>
<Input placeholder="请输入菜单名称" /> <Input placeholder="请输入菜单名称"/>
</Form.Item> </Form.Item>
<Form.Item label="菜单url" field="url" rules={[{ required: true }]}> <Form.Item label="菜单url" field="url" rules={[{required: true}]}>
<Input placeholder="请输入菜单url" /> <Input placeholder="请输入菜单url"/>
</Form.Item> </Form.Item>
<Form.Item label="排序" field="sort">
</Form> <InputNumber placeholder="请输入排序"/>
</Modal> </Form.Item>
</div> </Form>
); </Modal>
</div>
);
} }
export default FormComponent;

View File

@@ -35,6 +35,11 @@ function ConfigManage() {
dataIndex: 'url', dataIndex: 'url',
align: 'center', align: 'center',
}, },
{
title: '排序',
dataIndex: 'sort',
align: 'center',
},
{ {
title: '操作', title: '操作',
dataIndex: 'operations', dataIndex: 'operations',

View 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>
)
}
]
}

View File

@@ -1,94 +1,94 @@
import React, { useEffect, useState } from 'react'; import React, {useEffect, useState} from 'react';
import { Modal, Form, Input, Select, Message } from '@arco-design/web-react'; import {Modal, Form, Input, Select, Message} from '@arco-design/web-react';
import UploadFile from '@/components/Upload'; 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 { interface Props {
record: { [key: string]: any } | null, record: { [key: string]: any } | null,
visible: boolean, visible: boolean,
handleConfirm: () => void, handleConfirm: () => void,
handleCancel: () => void handleCancel: () => void
} }
function FormComponent(props: Props) { function FormComponent(props: Props) {
const { visible, record, handleConfirm, handleCancel } = props; const {visible, record, handleConfirm, handleCancel} = props;
const [confirmLoading, setConfirmLoading] = useState(false); const [confirmLoading, setConfirmLoading] = useState(false);
const onOk = async () => { const onOk = async () => {
try { try {
setConfirmLoading(true); setConfirmLoading(true);
const values = await form.validate(); const values = await form.validate();
const data = { const data = {
...values, ...values,
avatarId: values.avatar?.[0]?.response ?? null avatarId: values.avatar?.[0]?.response ?? null
}; };
if (record?.id) { if (record?.id) {
await updateUser(data); await updateUser(data);
} else { } else {
await createUser(data); await createUser(data);
} }
Message.success(`${record?.id ? '更新' : '新增'}成功!`); Message.success(`${record?.id ? '更新' : '新增'}成功!`);
handleConfirm(); handleConfirm();
} catch (e) { } catch (e) {
console.log(e); console.log(e);
} finally { } finally {
setConfirmLoading(false); setConfirmLoading(false);
} }
}; };
const [form] = Form.useForm(); const [form] = Form.useForm();
useEffect(() => { useEffect(() => {
record && form.setFieldsValue({ record && form.setFieldsValue({
...record, ...record,
avatar: record.avatar ? [{ avatar: record.avatar ? [{
uid: record.avatar.id, uid: record.avatar.id,
response: record.avatar.id, response: record.avatar.id,
url: record.avatar.path url: record.avatar.path
}] : undefined }] : undefined
}); });
}, [record]); }, [record]);
useEffect(() => { useEffect(() => {
!visible && form.resetFields() !visible && form.resetFields()
}, [visible]); }, [visible]);
return ( return (
<Modal <Modal
title={<span>{`${record?.id ? '更新' : '新增'}用户信息`}</span>} title={<span>{`${record?.id ? '更新' : '新增'}数据`}</span>}
visible={visible} visible={visible}
onOk={onOk} onOk={onOk}
onCancel={handleCancel} onCancel={handleCancel}
confirmLoading={confirmLoading} confirmLoading={confirmLoading}
> >
<Form form={form}> <Form form={form}>
<Form.Item hidden={true} field="id" rules={[{ required: false }]}> <Form.Item hidden={true} field="id" rules={[{required: false}]}>
<Input /> <Input/>
</Form.Item> </Form.Item>
<Form.Item triggerPropName="fileList" label="用户头像" <Form.Item triggerPropName="fileList" label="用户头像"
field="avatar"> field="avatar">
<UploadFile limit={1} dir="/user" /> <UploadFile limit={1} dir="/user"/>
</Form.Item> </Form.Item>
<Form.Item label="用户昵称" field="name" rules={[{ required: true }]}> <Form.Item label="用户昵称" field="name" rules={[{required: true}]}>
<Input placeholder="请输入用户昵称" /> <Input placeholder="请输入用户昵称"/>
</Form.Item> </Form.Item>
<Form.Item disabled={record?.id} label="用户账号" field="account" rules={[{ required: true }]}> <Form.Item disabled={record?.id} label="用户账号" field="account" rules={[{required: true}]}>
<Input placeholder="请输入用户账号" /> <Input placeholder="请输入用户账号"/>
</Form.Item> </Form.Item>
{!record?.id && <Form.Item label="用户密码" field="password" rules={[{ required: true }]}> {!record?.id && <Form.Item label="用户密码" field="password" rules={[{required: true}]}>
<Input type="password" placeholder="请输入用户密码" /> <Input type="password" placeholder="请输入用户密码"/>
</Form.Item>} </Form.Item>}
<Form.Item label="用户状态" field="status" rules={[{required: true}]} initialValue={1}>
<Form.Item label="用户状态" field="status" rules={[{ required: true }]}> <Select placeholder="请选择" options={statusDict}/>
<Select placeholder="请选择" options={[{ label: '启用', value: 1 }, { label: '禁用', value: 0 }]} /> </Form.Item>
</Form.Item> <Form.Item label="用户性别" field="gender" rules={[{required: true}]}>
<Form.Item label="用户性别" field="gender" rules={[{ required: true }]}> <Select placeholder="请选择"
<Select placeholder="请选择" options={genderDict}/>
options={[{ label: '女', value: 0 }, { label: '男', value: 1 }, { label: '未知', value: 2 }]} /> </Form.Item>
</Form.Item> </Form>
</Form> </Modal>
</Modal> );
);
} }
export default FormComponent; export default FormComponent;

View File

@@ -1,208 +1,167 @@
import React, { useState } from 'react'; import React, {useEffect, useState} from 'react';
import { import {
Card, Button,
Table, Card,
Typography, Message,
TableColumnProps, PaginationProps,
Badge, Table,
PaginationProps, TableColumnProps,
Button, Typography
Popconfirm,
Divider,
Avatar,
Message
} from '@arco-design/web-react'; } from '@arco-design/web-react';
import { useQuery } from 'react-query'; import {useQuery} from 'react-query';
import Form from './form'; import Form from './form';
import GrantRolesForm from './grantRoles'; 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() { function UserManage() {
const [visible, setVisible] = useState(false); const [visible, setVisible] = useState(false);
const [visibleAssignRoles, setVisibleAssignRoles] = useState(false); const [visibleAssignRoles, setVisibleAssignRoles] = useState(false);
const [record, setRecord] = useState(null); const [record, setRecord] = useState(null);
const columns: TableColumnProps[] = [ const [pagination, setPagination] = useState<PaginationProps>({
{ sizeCanChange: true,
title: 'id', showTotal: true,
dataIndex: 'id', pageSize: 10,
render: (value) => <Text copyable>{value}</Text>, current: 1,
align: 'center' pageSizeChangeResetCurrent: true,
}, total: 0
{ });
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 [pagination, setPagination] = useState<PaginationProps>({ const [searchQuery, setSearchQuery] = useState<Record<string, any>>({
sizeCanChange: true, page: pagination.current,
showTotal: true, pageSize: pagination.pageSize,
pageSize: 10, });
current: 1,
pageSizeChangeResetCurrent: true,
total: 0
});
const onDelete = async ({ id }) => { const showAssignRolesModal = (payload) => {
await deleteUser(id); setRecord(payload);
Message.success('删除成功!'); setVisibleAssignRoles(true);
await refetch(); };
};
const onChangeTable = async ({ current, pageSize }) => { const showModal = (payload?) => {
setPagination({ payload && setRecord(payload);
...pagination, setVisible(true);
current, };
pageSize
});
};
const fetchData = async ({ queryKey }) => { const onDelete = async ({id}) => {
const [, { page, pageSize }] = queryKey; await deleteUser(id);
const { data } = await getUsers({ page, pageSize }); Message.success('删除成功!');
setPagination(prevState => { await refetch();
prevState.total = data.count; };
return prevState;
});
if (page > 1 && data.list.length === 0) {
setPagination({
...pagination,
current: page - 1
});
}
return data;
};
const { const tableCallback = async (type: TableOptions, record: any) => {
data, switch (type) {
isLoading, case TableOptions.EDIT:
isFetching, showModal(record)
refetch break;
} = useQuery( case TableOptions.DELETE:
['user', { page: pagination.current, pageSize: pagination.pageSize }], await onDelete(record);
fetchData, break
{ case TableOptions.ASSIGN_ROLE:
refetchOnWindowFocus: false, showAssignRolesModal(record);
keepPreviousData: true break
}); }
}
const showAssignRolesModal = (payload) => { const columns: TableColumnProps[] = getColumns(tableCallback)
setRecord(payload);
setVisibleAssignRoles(true);
};
const showModal = (payload?) => { const handleSearch = async (params) => {
payload && setRecord(payload); setSearchQuery(prev => ({...prev, page: 1, ...params}))
setVisible(true); }
};
const handleConfirm = async () => { const onChangeTable = async ({current, pageSize}) => {
setRecord(null); setSearchQuery(prev => ({...prev, page: current, pageSize}));
setVisible(false); };
setVisibleAssignRoles(false);
await refetch();
};
const handleCancel = () => { const fetchData = async ({queryKey}) => {
setRecord(null); const [, params] = queryKey;
setVisible(false); console.log(params)
setVisibleAssignRoles(false); 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 ( const {data, isLoading, isFetching, refetch} = useQuery(
<> [reactQueryKey, {
<Card> page: pagination.current,
<Form pageSize: pagination.pageSize,
handleConfirm={handleConfirm} ...searchQuery
handleCancel={handleCancel} }],
visible={visible} fetchData);
record={record}
/>
<GrantRolesForm
handleConfirm={handleConfirm}
handleCancel={handleCancel}
visible={visibleAssignRoles}
record={record}
/>
<Title heading={6}></Title>
<Button const handleConfirm = async () => {
style={{ marginBottom: 12 }} setRecord(null);
type="primary" setVisible(false);
onClick={() => showModal()} setVisibleAssignRoles(false);
> await refetch();
};
</Button>
<Table const handleCancel = () => {
rowKey="id" setRecord(null);
loading={isLoading || isFetching} setVisible(false);
columns={columns} setVisibleAssignRoles(false);
onChange={onChangeTable} };
pagination={pagination}
data={data?.list} useEffect(() => {
/> setPagination(prev => ({
</Card> ...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>
</>
);
} }

View 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;

View File

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

View File

@@ -1,5 +1,5 @@
import { useContext } from 'react'; import { useContext } from 'react';
import { GlobalContext } from '../context'; import { GlobalContext } from '@/context';
import defaultLocale from '../locale'; import defaultLocale from '../locale';
function useLocale(locale = null) { function useLocale(locale = null) {

View File

@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "sys_menu" ADD COLUMN "sort" INTEGER NOT NULL DEFAULT 0;

View File

@@ -21,7 +21,7 @@ model SysUser {
status Int @default(0) @db.SmallInt /// 用户状态 status Int @default(0) @db.SmallInt /// 用户状态
avatarId Int? @map("avatar_id") /// 用户头像id avatarId Int? @map("avatar_id") /// 用户头像id
avatar Files? @relation(fields: [avatarId], references: [id]) avatar Files? @relation(fields: [avatarId], references: [id])
gender Int @default(0) @db.SmallInt /// 用户性别 gender Int @default(0) @db.SmallInt /// 用户性别 0女 1男 2未知
createdAt DateTime @default(now()) @map("created_at") /// 创建时间 createdAt DateTime @default(now()) @map("created_at") /// 创建时间
updatedAt DateTime @updatedAt @map("updated_at") /// 修改时间 updatedAt DateTime @updatedAt @map("updated_at") /// 修改时间
userRole SysUserRole[] userRole SysUserRole[]
@@ -82,6 +82,7 @@ model SysMenu {
name String name String
url String url String
parentId Int @default(0) @map("parent_id") parentId Int @default(0) @map("parent_id")
sort Int @default(0)
sysMenuPermission SysMenuPermission[] sysMenuPermission SysMenuPermission[]
@@map("sys_menu") @@map("sys_menu")

View File

@@ -12,4 +12,8 @@ export class CreateMenuDto {
@IsOptional() @IsOptional()
@IsInt() @IsInt()
parentId?: number; parentId?: number;
@IsOptional()
@IsInt()
sort?: number;
} }

View File

@@ -51,12 +51,7 @@ export class MenuService {
async getTreeMenu() { async getTreeMenu() {
const query: Prisma.SysMenuFindManyArgs = { const query: Prisma.SysMenuFindManyArgs = {
select: { orderBy: [{ sort: 'desc' }, { id: 'asc' }],
id: true,
name: true,
url: true,
parentId: true,
},
}; };
const list = await this.prisma.sysMenu.findMany(query); const list = await this.prisma.sysMenu.findMany(query);
return this.generatorMenu(list); return this.generatorMenu(list);

View File

@@ -1,3 +1,16 @@
import { PaginationDto } from 'src/common/dto/pagination.dto'; import { PaginationDto } from 'src/common/dto/pagination.dto';
import { IsOptional, IsInt } from 'class-validator';
import { Type } from 'class-transformer';
export class QueryListDto extends PaginationDto {} export class QueryListDto extends PaginationDto {
@IsOptional()
account?: string;
@IsOptional()
name?: string;
@IsOptional()
@Type(() => Number)
@IsInt()
status?: number;
}

View File

@@ -48,7 +48,8 @@ export class UserService {
await this.prisma.sysUser.create({ data }); await this.prisma.sysUser.create({ data });
} }
async findAll({ page, pageSize }: QueryListDto): Promise<QueryListResult> { async findAll(dto: QueryListDto): Promise<QueryListResult> {
const { page, pageSize, account, status, name } = dto;
const query: Prisma.SysUserFindManyArgs = { const query: Prisma.SysUserFindManyArgs = {
select: { select: {
id: true, id: true,
@@ -70,6 +71,11 @@ export class UserService {
}, },
skip: pageSize * (page - 1), skip: pageSize * (page - 1),
take: pageSize, take: pageSize,
where: {
...(account && { account: { contains: account } }),
...(!isNaN(status) && { status }),
...(name && { name: { contains: name } }),
},
}; };
const [list, count] = await this.prisma.$transaction([ const [list, count] = await this.prisma.$transaction([

File diff suppressed because it is too large Load Diff