refactor: 用户管理重构优化
This commit is contained in:
@@ -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
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,32 +1,34 @@
|
|||||||
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) {
|
||||||
@@ -71,25 +73,25 @@ function Index() {
|
|||||||
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>
|
||||||
@@ -98,4 +100,4 @@ function Index() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
ReactDOM.render(<Index />, document.getElementById('root'));
|
ReactDOM.render(<Index/>, document.getElementById('root'));
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
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,
|
||||||
@@ -9,8 +9,8 @@ interface Props {
|
|||||||
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 () => {
|
||||||
@@ -35,7 +35,7 @@ function FormComponent(props: Props) {
|
|||||||
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,
|
||||||
@@ -71,9 +71,9 @@ function FormComponent(props: Props) {
|
|||||||
>
|
>
|
||||||
<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
|
||||||
@@ -85,17 +85,17 @@ function FormComponent(props: Props) {
|
|||||||
))}
|
))}
|
||||||
</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 label="排序" field="sort">
|
||||||
|
<InputNumber placeholder="请输入排序"/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
</Form>
|
</Form>
|
||||||
</Modal>
|
</Modal>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default FormComponent;
|
|
||||||
|
|||||||
@@ -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',
|
||||||
|
|||||||
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,7 +1,8 @@
|
|||||||
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,
|
||||||
@@ -11,7 +12,7 @@ interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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 () => {
|
||||||
@@ -55,36 +56,35 @@ function FormComponent(props: Props) {
|
|||||||
|
|
||||||
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={[{ label: '女', value: 0 }, { label: '男', value: 1 }, { label: '未知', value: 2 }]} />
|
options={genderDict}/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</Form>
|
</Form>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|||||||
@@ -1,97 +1,27 @@
|
|||||||
import React, { useState } from 'react';
|
import React, {useEffect, useState} from 'react';
|
||||||
import {
|
import {
|
||||||
Card,
|
|
||||||
Table,
|
|
||||||
Typography,
|
|
||||||
TableColumnProps,
|
|
||||||
Badge,
|
|
||||||
PaginationProps,
|
|
||||||
Button,
|
Button,
|
||||||
Popconfirm,
|
Card,
|
||||||
Divider,
|
Message,
|
||||||
Avatar,
|
PaginationProps,
|
||||||
Message
|
Table,
|
||||||
|
TableColumnProps,
|
||||||
|
Typography
|
||||||
} 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[] = [
|
|
||||||
{
|
|
||||||
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 [pagination, setPagination] = useState<PaginationProps>({
|
const [pagination, setPagination] = useState<PaginationProps>({
|
||||||
sizeCanChange: true,
|
sizeCanChange: true,
|
||||||
showTotal: true,
|
showTotal: true,
|
||||||
@@ -101,47 +31,9 @@ function UserManage() {
|
|||||||
total: 0
|
total: 0
|
||||||
});
|
});
|
||||||
|
|
||||||
const onDelete = async ({ id }) => {
|
const [searchQuery, setSearchQuery] = useState<Record<string, any>>({
|
||||||
await deleteUser(id);
|
page: pagination.current,
|
||||||
Message.success('删除成功!');
|
pageSize: pagination.pageSize,
|
||||||
await refetch();
|
|
||||||
};
|
|
||||||
|
|
||||||
const onChangeTable = async ({ current, pageSize }) => {
|
|
||||||
setPagination({
|
|
||||||
...pagination,
|
|
||||||
current,
|
|
||||||
pageSize
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
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 {
|
|
||||||
data,
|
|
||||||
isLoading,
|
|
||||||
isFetching,
|
|
||||||
refetch
|
|
||||||
} = useQuery(
|
|
||||||
['user', { page: pagination.current, pageSize: pagination.pageSize }],
|
|
||||||
fetchData,
|
|
||||||
{
|
|
||||||
refetchOnWindowFocus: false,
|
|
||||||
keepPreviousData: true
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const showAssignRolesModal = (payload) => {
|
const showAssignRolesModal = (payload) => {
|
||||||
@@ -154,6 +46,62 @@ function UserManage() {
|
|||||||
setVisible(true);
|
setVisible(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const onDelete = async ({id}) => {
|
||||||
|
await deleteUser(id);
|
||||||
|
Message.success('删除成功!');
|
||||||
|
await refetch();
|
||||||
|
};
|
||||||
|
|
||||||
|
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 columns: TableColumnProps[] = getColumns(tableCallback)
|
||||||
|
|
||||||
|
const handleSearch = async (params) => {
|
||||||
|
setSearchQuery(prev => ({...prev, page: 1, ...params}))
|
||||||
|
}
|
||||||
|
|
||||||
|
const onChangeTable = async ({current, pageSize}) => {
|
||||||
|
setSearchQuery(prev => ({...prev, page: current, pageSize}));
|
||||||
|
};
|
||||||
|
|
||||||
|
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;
|
||||||
|
};
|
||||||
|
|
||||||
|
const {data, isLoading, isFetching, refetch} = useQuery(
|
||||||
|
[reactQueryKey, {
|
||||||
|
page: pagination.current,
|
||||||
|
pageSize: pagination.pageSize,
|
||||||
|
...searchQuery
|
||||||
|
}],
|
||||||
|
fetchData);
|
||||||
|
|
||||||
const handleConfirm = async () => {
|
const handleConfirm = async () => {
|
||||||
setRecord(null);
|
setRecord(null);
|
||||||
setVisible(false);
|
setVisible(false);
|
||||||
@@ -167,6 +115,14 @@ function UserManage() {
|
|||||||
setVisibleAssignRoles(false);
|
setVisibleAssignRoles(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setPagination(prev => ({
|
||||||
|
...prev,
|
||||||
|
current: searchQuery.page,
|
||||||
|
pageSize: pagination.pageSize,
|
||||||
|
}))
|
||||||
|
}, [searchQuery])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Card>
|
<Card>
|
||||||
@@ -184,12 +140,15 @@ function UserManage() {
|
|||||||
/>
|
/>
|
||||||
<Title heading={6}>用户管理</Title>
|
<Title heading={6}>用户管理</Title>
|
||||||
|
|
||||||
|
<SearchForm onSearch={handleSearch}/>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
style={{ marginBottom: 12 }}
|
style={{marginBottom: 12}}
|
||||||
type="primary"
|
type="primary"
|
||||||
|
icon={<IconPlus/>}
|
||||||
onClick={() => showModal()}
|
onClick={() => showModal()}
|
||||||
>
|
>
|
||||||
新增用户
|
新建
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<Table
|
<Table
|
||||||
|
|||||||
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 { 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) {
|
||||||
|
|||||||
2
server/prisma/migrations/20241026052827_/migration.sql
Normal file
2
server/prisma/migrations/20241026052827_/migration.sql
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "sys_menu" ADD COLUMN "sort" INTEGER NOT NULL DEFAULT 0;
|
||||||
@@ -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")
|
||||||
|
|||||||
@@ -12,4 +12,8 @@ export class CreateMenuDto {
|
|||||||
@IsOptional()
|
@IsOptional()
|
||||||
@IsInt()
|
@IsInt()
|
||||||
parentId?: number;
|
parentId?: number;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsInt()
|
||||||
|
sort?: number;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
|||||||
@@ -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([
|
||||||
|
|||||||
1550
server/yarn.lock
1550
server/yarn.lock
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user