Yansen 2 years ago
parent
commit
76770bef5b

+ 1
- 1
src/layouts/AuthLayout/components/Container.jsx View File

@@ -1,5 +1,5 @@
1 1
 import React, { useEffect, useRef, useMemo, useState } from 'react';
2
-import { Layout, Spin } from 'antd';
2
+import { Layout } from 'antd';
3 3
 import { Outlet } from "react-router-dom";
4 4
 import PageTransition from './PageTransition';
5 5
 import Footer from './Footer';

+ 3
- 19
src/layouts/AuthLayout/components/Header/SplitMenu.jsx View File

@@ -1,28 +1,12 @@
1 1
 import React from 'react';
2 2
 import { Menu } from 'antd';
3
-import { useNavigate } from 'react-router-dom';
4 3
 
5 4
 export default (props) => {
6
-  const { items, onChange, location } = props;
5
+  const { items, location } = props;
7 6
 
8
-  const navigate = useNavigate();
9
-  const [selectedKeys, setSelectedKeys] = React.useState([]);
10
-
11
-  const renderItems = React.useMemo(() => items.map(x => ({ ...x, children: undefined })), [items]);
12
-
13
-  const rootPath = React.useMemo(() => `/${location.pathname.split('/')[1]}`, [location.pathname]);
14
-
15
-  const onClick = (item) => {
16
-    onChange(item.key);
17
-    // navigate(item.key);
18
-  };
19
-
20
-  React.useEffect(() => {
21
-    setSelectedKeys([rootPath]);
22
-    onChange(rootPath);
23
-  }, [rootPath]);
7
+  const selectedKeys = React.useMemo(() => `/${location.pathname.split('/')[1]}`, [location.pathname]);
24 8
 
25 9
   return (
26
-    <Menu className='split-menu' mode="horizontal" items={renderItems} selectedKeys={selectedKeys} onClick={onClick} />
10
+    <Menu className='split-menu' mode="horizontal" items={items} selectedKeys={selectedKeys} />
27 11
   )
28 12
 }

+ 2
- 2
src/layouts/AuthLayout/components/Header/index.jsx View File

@@ -10,7 +10,7 @@ import SplitMenu from './SplitMenu';
10 10
 const { Header } = Layout;
11 11
 
12 12
 export default (props) => {
13
-  const { menus, theme, location, onMenuChange } = props;
13
+  const { menus, theme, location } = props;
14 14
 
15 15
   const className = useMemo(() => classNames({
16 16
     'layout-header': true,
@@ -20,7 +20,7 @@ export default (props) => {
20 20
   return (
21 21
     <Header className={className}>
22 22
       <Logo />
23
-      <SplitMenu items={menus} location={location} onChange={onMenuChange} />
23
+      <SplitMenu items={menus} location={location} />
24 24
       <Space>
25 25
         <User />
26 26
         <Exit />

+ 0
- 29
src/layouts/AuthLayout/components/Menus.jsx View File

@@ -1,39 +1,10 @@
1 1
 import React, { useState } from 'react';
2
-import { useNavigate, useLocation } from "react-router-dom";
3 2
 import { Menu } from 'antd';
4 3
 
5
-const linkTo = url => {
6
-  const a = document.createElement('a');
7
-  a.href = item.key;
8
-  a.target = '_blank';
9
-  a.click();
10
-}
11
-
12 4
 const menuStyle = { height: '100%' };
13 5
 
14 6
 export default (props) => {
15 7
   const { theme, items, location } = props;
16
-  // const [selectedKeys, setSelectedKeys] = useState([]);
17
-
18
-  const navigate = useNavigate();
19
-
20
-  const onClick = (item) => {
21
-    // console.log(item);
22
-
23
-    if (item.key.indexOf('http') === 0) {
24
-      // http 开头说明是外部链接
25
-      linkTo(item.key);
26
-    } else {
27
-      // setSelectedKeys([item.key]);
28
-      navigate(item.key);
29
-    }
30
-  }
31
-
32
-  // React.useEffect(() => {
33
-  //   if (location.pathname) {
34
-  //     setSelectedKeys([location.pathname]);
35
-  //   }
36
-  // }, [location.pathname]);
37 8
 
38 9
   return (
39 10
     <Menu style={menuStyle} theme={theme} items={items} selectedKeys={[location.pathname]} />

+ 3
- 5
src/layouts/AuthLayout/components/RequireLogin.jsx View File

@@ -1,16 +1,14 @@
1 1
 import React, { useState, useEffect } from 'react';
2
-import { useLocation, Navigate } from "react-router-dom";
2
+import { Navigate } from "react-router-dom";
3 3
 import { useModel } from '@/store';
4
-import { queryCurrentUser } from '@/services/user';
5 4
 
6 5
 export default (props) => {
7
-  const { user, setUser } = useModel('user');
6
+  const { user, getCurrentUser } = useModel('user');
8 7
   const [userStatus, setUserStatus] = useState(user && user.id ? 1 : 0);
9 8
 
10 9
   useEffect(() => {
11 10
     if (!user || !user.id) {
12
-      queryCurrentUser().then((res) => {
13
-        setUser(res);
11
+      getCurrentUser().then(() => {
14 12
         setUserStatus(1);
15 13
       }).catch(() => {
16 14
         setUserStatus(-1);

+ 8
- 13
src/layouts/AuthLayout/index.jsx View File

@@ -2,7 +2,7 @@ import React, { useEffect, useRef, useMemo, useState } from 'react';
2 2
 import { Layout, Spin } from 'antd';
3 3
 import { useLocation, Outlet } from "react-router-dom";
4 4
 import { useModel } from '@/store';
5
-import { getItems } from '@/routes/menus';
5
+import { getMenuItems } from '@/routes/menus';
6 6
 import useRoute from '@/utils/hooks/useRoute';
7 7
 import RequireLogin from './components/RequireLogin';
8 8
 import SiderBar from './components/SiderBar';
@@ -14,20 +14,15 @@ import './style.less';
14 14
 
15 15
 export default (props) => {
16 16
   const { theme } = useModel('system');
17
-  const { user } = useModel('user');
18
-
19
-  const allMenus = useMemo(() => getItems(), []);
20
-  const [siderMenus, setSiderMenus] = useState([]);
21
-
17
+  const { user, menus } = useModel('user');
22 18
   const location = useLocation();
23 19
   const currentRoute = useRoute();
24 20
 
25
-  const onSplitMenuChange = (key) => {
26
-    const target = allMenus.filter(x => x.key === key)[0];
27
-    if (target) {
28
-      setSiderMenus(target.children || []);
29
-    }
30
-  }
21
+  const splitMenus = useMemo(() => menus.map(x => ({ ...x, children: undefined })), [menus]);
22
+  const siderMenus = useMemo(() => {
23
+    const target = menus.filter(x => location.pathname.indexOf(x.key) === 0)[0];
24
+    return target ? target.children :  [];
25
+  }, [menus, location.pathname]);
31 26
 
32 27
   return (
33 28
     <Spin spinning={!user} size="large">
@@ -38,7 +33,7 @@ export default (props) => {
38 33
             ? <Outlet />
39 34
             : (
40 35
               <Layout style={{ minHeight: '100vh' }}>
41
-                <Header theme={theme} menus={allMenus} location={location} onMenuChange={onSplitMenuChange} />
36
+                <Header theme={theme} menus={splitMenus} location={location} />
42 37
                 <Layout>
43 38
                   <SiderBar theme={theme} menus={siderMenus} location={location} />
44 39
                   <Container location={location} />

+ 5
- 3
src/layouts/Container.jsx View File

@@ -1,4 +1,5 @@
1 1
 import React from 'react';
2
+import { Typography } from 'antd';
2 3
 import { Outlet } from 'react-router-dom';
3 4
 import useRoute from '@/utils/hooks/useRoute';
4 5
 
@@ -6,14 +7,15 @@ const containerStyle = {
6 7
   margin: '24px 24px 0 24px',
7 8
   height: '100%',
8 9
 }
10
+const { Title } = Typography;
9 11
 
10 12
 export default (props) => {
11
-
12
-  const currentRoute = useRoute();
13
-  const style = currentRoute.meta && currentRoute.meta.noLayout ? { height: '100%' } : containerStyle;
13
+  const { meta = {} } = useRoute() || {};
14
+  const style = meta.noLayout ? { height: '100%' } : containerStyle;
14 15
 
15 16
   return (
16 17
     <div style={style}>
18
+      {/* { meta.title && !meta.noLayout && <Title level={4} style={{ paddingBottom: '12px' }}>{ meta.title }</Title> } */}
17 19
       <Outlet />
18 20
     </div>
19 21
   )

+ 2
- 2
src/pages/roles/list.jsx View File

@@ -93,8 +93,8 @@ export default (props) => {
93 93
 
94 94
   React.useEffect(() => {
95 95
     startLoading();
96
-    getRoleList({ pageSize: 100 }).then((res) => {
97
-      setList(res.records || []);
96
+    getRoleList().then((res = []) => {
97
+      setList(res);
98 98
       cancelLoading();
99 99
     }).catch(() => {
100 100
       cancelLoading();

+ 147
- 0
src/pages/user/Edit.jsx View File

@@ -0,0 +1,147 @@
1
+import React from 'react';
2
+import { Button, Card, Form, Input, Select } from 'antd';
3
+import useBool from '@/utils/hooks/useBool';
4
+import { getRoleList } from '@/services/role';
5
+import { saveUser, getUser } from '@/services/user';
6
+import { useSearchParams, useNavigate } from 'react-router-dom';
7
+import md5 from 'md5';
8
+
9
+const formItemLayout = {
10
+  labelCol: {
11
+    xs: { span: 24 },
12
+    sm: { span: 8 },
13
+  },
14
+  wrapperCol: {
15
+    xs: { span: 24 },
16
+    sm: { span: 16 },
17
+  },
18
+};
19
+const tailFormItemLayout = {
20
+  wrapperCol: {
21
+    xs: {
22
+      span: 24,
23
+      offset: 0,
24
+    },
25
+    sm: {
26
+      span: 16,
27
+      offset: 8,
28
+    },
29
+  },
30
+};
31
+
32
+const { Option } = Select;
33
+
34
+export default (props) => {
35
+  const [loading, startLoading, cancelLoading] = useBool();
36
+  const [submiting, startSubmit, cancelSubmit] = useBool();
37
+  const [roleList, setRoleList] = React.useState([]);
38
+  const [searchParams] = useSearchParams();
39
+  const [form] = Form.useForm();
40
+  const userRef = React.useRef();
41
+  const navigate = useNavigate();
42
+
43
+  const id = searchParams.get('id');
44
+
45
+  const onFinish = (values) => {
46
+    const password = values.password ? md5(values.password) : undefined;
47
+    const loginName = values.phone;
48
+    const rolesList = (values.rolesList || []).map(x => ({ id: x }));
49
+
50
+    startSubmit();
51
+    saveUser({ ...values, loginName, password, rolesList, id }).then(res => {
52
+      cancelSubmit();
53
+      navigate(-1);
54
+    }).catch(() => {
55
+      cancelSubmit();
56
+    });
57
+  }
58
+
59
+  React.useEffect(() => {
60
+    getRoleList().then(setRoleList);
61
+  }, []);
62
+  
63
+  React.useEffect(() => {
64
+    if (id) {
65
+      startLoading();
66
+      getUser(id).then(user => {
67
+        userRef.current = user;
68
+        form.setFieldsValue({
69
+          ...user,
70
+          rolesList: (user.rolesList || []).map(x => `${x.id}`),
71
+        });
72
+        cancelLoading();
73
+      }).catch(() => {
74
+        cancelLoading();
75
+      });
76
+    } else {
77
+      form.setFieldsValue({ password: '123456' });
78
+    }
79
+  }, [id]);
80
+
81
+  return (
82
+    <Card loading={loading}>
83
+      <Form onFinish={onFinish} form={form} {...formItemLayout} scrollToFirstError>
84
+        <Form.Item
85
+          name="name"
86
+          label="姓名"
87
+          rules={[
88
+            {
89
+              required: true,
90
+              message: '请填写姓名',
91
+            },
92
+          ]}
93
+        >
94
+          <Input />
95
+        </Form.Item>
96
+        <Form.Item
97
+          name="dept"
98
+          label="部门"
99
+        >
100
+          <Input />
101
+        </Form.Item>
102
+        <Form.Item
103
+          name="phone"
104
+          label="手机号"
105
+        >
106
+          <Input />
107
+        </Form.Item>
108
+        <Form.Item label="账号">
109
+          系统账号与手机号相同
110
+        </Form.Item>
111
+        <Form.Item
112
+          name="password"
113
+          label="密码"
114
+          extra={id ? '填写密码意味着重置该账户密码' : '默认密码 123456'}
115
+          hasFeedback
116
+        >
117
+          <Input.Password allowClear />
118
+        </Form.Item>
119
+        <Form.Item
120
+          name="rolesList"
121
+          label="角色"
122
+        >
123
+          <Select
124
+            mode="multiple"
125
+            allowClear
126
+            style={{ width: '100%' }}
127
+            placeholder="请选择角色"
128
+          >
129
+            {
130
+              roleList.map(role => (<Option key={role.id}>{role.name}</Option>))
131
+            }
132
+          </Select>
133
+        </Form.Item>
134
+        <Form.Item {...tailFormItemLayout}>
135
+          <Button loading={submiting} type="primary" htmlType="submit">
136
+            保存
137
+          </Button>
138
+          <Button style={{ marginLeft: '2em' }} onClick={() => navigate(-1)}>
139
+            返回
140
+          </Button>
141
+        </Form.Item>
142
+      </Form>
143
+    </Card>
144
+  )
145
+}
146
+
147
+

+ 0
- 79
src/pages/user/Role.jsx View File

@@ -1,79 +0,0 @@
1
-import React from 'react';
2
-import { Button, Row, Col, Card, List, Popconfirm, Tree, Space, Modal } from 'antd';
3
-import List from './list';
4
-
5
-export default (props) => {
6
-
7
-  const treeData = [
8
-    {
9
-      title: 'parent 1',
10
-      key: '0-0',
11
-      children: [
12
-        {
13
-          title: 'parent 1-0',
14
-          key: '0-0-0',
15
-          disabled: true,
16
-          children: [
17
-            {
18
-              title: 'leaf',
19
-              key: '0-0-0-0',
20
-              disableCheckbox: true,
21
-            },
22
-            {
23
-              title: 'leaf',
24
-              key: '0-0-0-1',
25
-            },
26
-          ],
27
-        },
28
-        {
29
-          title: 'parent 1-1',
30
-          key: '0-0-1',
31
-          children: [
32
-            {
33
-              title: (
34
-                <span
35
-                  style={{
36
-                    color: '#1890ff',
37
-                  }}
38
-                >
39
-                  sss
40
-                </span>
41
-              ),
42
-              key: '0-0-1-0',
43
-            },
44
-          ],
45
-        },
46
-      ],
47
-    },
48
-  ];
49
-
50
-  const onSelect = (selectedKeys, info) => {
51
-    console.log('selected', selectedKeys, info);
52
-  };
53
-  const onCheck = (checkedKeys, info) => {
54
-    console.log('onCheck', checkedKeys, info);
55
-  };
56
-
57
-  return (
58
-    <div>
59
-      <Row gutter={24}>
60
-        <Col span={8}>
61
-        </Col>
62
-        <Col span={16}>
63
-          <Card title='授权菜单' extra={<Button type='primary' ghost>保存</Button>}>
64
-            <Tree
65
-              checkable
66
-              defaultExpandedKeys={['0-0-0', '0-0-1']}
67
-              defaultSelectedKeys={['0-0-0', '0-0-1']}
68
-              defaultCheckedKeys={['0-0-0', '0-0-1']}
69
-              onSelect={onSelect}
70
-              onCheck={onCheck}
71
-              treeData={treeData}
72
-            />
73
-          </Card>
74
-        </Col>
75
-      </Row>
76
-
77
-    </div>
78
-  )
79
-}

+ 112
- 0
src/pages/user/index.jsx View File

@@ -0,0 +1,112 @@
1
+import React from 'react';
2
+import { useNavigate } from 'react-router-dom';
3
+import { queryTable } from '@/utils/request';
4
+import { ProTable } from '@ant-design/pro-components';
5
+import { Button, message, Popconfirm } from 'antd';
6
+import { getUserList, updateUserStatus } from '@/services/user';
7
+
8
+const queryUserList = queryTable(getUserList);
9
+
10
+export default (props) => {
11
+  const actionRef = React.useRef();
12
+  const navigate = useNavigate();
13
+
14
+  const updateStatus = (user) => {
15
+    const status = user.status === 1 ? 0 : 1;
16
+    const hide = message.loading('请稍候...', 0);
17
+    updateUserStatus(user.id, status).then(res => {
18
+      hide();
19
+      actionRef.current.reload();
20
+    }).catch(() => {
21
+      hide();
22
+    })
23
+  }
24
+
25
+  const columns = [
26
+    {
27
+      title: 'id',
28
+      dataIndex: 'id',
29
+      search: false,
30
+    },
31
+    {
32
+      title: '姓名',
33
+      dataIndex: 'name',
34
+    },
35
+    {
36
+      title: '部门',
37
+      dataIndex: 'dept',
38
+    },
39
+    {
40
+      title: '手机号',
41
+      dataIndex: 'phone',
42
+    },
43
+    {
44
+      title: '账号',
45
+      dataIndex: 'loginName',
46
+      search: false,
47
+    },
48
+    {
49
+      title: '状态',
50
+      dataIndex: 'status',
51
+      search: false,
52
+      valueEnum: {
53
+        1: {
54
+          text: '正常',
55
+          status: 'Processing',
56
+        },
57
+        0: {
58
+          text: '禁用',
59
+          status: 'Error',
60
+        },
61
+      },
62
+    },
63
+    {
64
+      title: '操作',
65
+      valueType: 'option',
66
+      width: 200,
67
+      render: (_, record) => [
68
+        <Button
69
+          key={1}
70
+          style={{ padding: 0 }}
71
+          type="link"
72
+          onClick={() => {
73
+            updateStatus(record);
74
+          }}
75
+        >
76
+          {record.status === 1 ? '禁用' : '启用'}
77
+        </Button>,
78
+        <Button
79
+          key={2}
80
+          style={{ padding: 0 }}
81
+          type="link"
82
+          onClick={() => {
83
+            console.log(record, ']]');
84
+            navigate(`/system/user/edit?id=${record.id}`);
85
+          }}
86
+        >
87
+          编辑
88
+        </Button>,
89
+      ],
90
+    },
91
+  ]
92
+
93
+  return (
94
+    <ProTable
95
+      actionRef={actionRef}
96
+      rowKey="id"
97
+      toolBarRender={() => [
98
+        <Button
99
+          key="1"
100
+          type="primary"
101
+          onClick={() => {
102
+            navigate('/system/user/edit');
103
+          }}
104
+        >
105
+          新增
106
+        </Button>,
107
+      ]}
108
+      request={queryUserList}
109
+      columns={columns}
110
+    />
111
+  )
112
+}

+ 13
- 4
src/routes/Router.jsx View File

@@ -1,9 +1,18 @@
1
+import React from "react";
1 2
 import { createHashRouter, RouterProvider } from "react-router-dom";
2
-import routes from './routes'
3
-
4
-const router = createHashRouter(routes);
5
-export const hashRouter = true;
3
+import { useModel } from "@/store";
4
+import { defaultRoutes } from './routes';
6 5
 
7 6
 export default (props) => {
7
+  const { routes } = useModel('user');
8
+
9
+  const router = React.useMemo(() => {
10
+    if (!routes || routes.length < 1) {
11
+      return createHashRouter(defaultRoutes);
12
+    } else {
13
+      return createHashRouter(routes);
14
+    }
15
+  }, [routes]);
16
+
8 17
   return <RouterProvider router={router} />
9 18
 }

+ 12
- 41
src/routes/menus.jsx View File

@@ -1,8 +1,4 @@
1 1
 import { Link } from 'react-router-dom';
2
-import routes from './routes';
3
-import { hashRouter } from './Router';
4
-
5
-let routeArr = [];
6 2
 
7 3
 // 菜单是否显示
8 4
 // 没有 meta 或者 meta.title 为空, 或者 meta.hideInMenu = true 的 都不显示
@@ -20,55 +16,30 @@ const getPath = (parent, current = '') => {
20 16
   return `${parent}/${current}`.replace(/\/\//g, '/');
21 17
 }
22 18
 
23
-const getMenuItems = (dts = [], fullPath = '/') => {
24
-  return dts.map(item => {
25
-    const path = getPath(fullPath, item.path);
19
+export const getMenuItems = (routes = [], fullPath = '/') => {
20
+  return routes.map(route => {
21
+    const path = getPath(fullPath, route.path);
26 22
 
27 23
     //
28
-    if (!isShow(item)) return false;
24
+    if (!isShow(route)) return false;
29 25
     
30
-    const children = hasChildren(item.children) ? getMenuItems(item.children, path) : false;
26
+    const children = hasChildren(route.children) ? getMenuItems(route.children, path) : false;
27
+
28
+    const { target, title, icon } = route.meta || {}
31 29
 
32 30
     // 坑爹 react-router v6 不支持 hash 路由的 target 跳转
33
-    const label = hashRouter && item.meta && item.meta.target === '_blank' ?
34
-      <a href={`${window.location.pathname}#${path}`} target={item.meta.target}>{item.meta.title}</a>
35
-      : <Link to={path} target={item.meta.target}>{item.meta.title}</Link>;
31
+    const label = target === '_blank' ?
32
+      <a href={`${window.location.pathname}#${path}`} target={target}>{title}</a>
33
+      : <Link to={path} target={target}>{title}</Link>;
36 34
 
37 35
     return Object.assign(
38 36
       {
39 37
         key: path,
40 38
         label,
41
-        title: item.meta.title,
42
-        icon: item.meta.icon,
39
+        title,
40
+        icon,
43 41
       },
44 42
       children && { children },
45 43
     )
46 44
   }).filter(Boolean);
47 45
 }
48
-
49
-const flatten = (routes, parent) => {
50
-  (routes || []).forEach((route) => {
51
-    const path = getPath(parent, route.path);
52
-    routeArr.push({
53
-      ...route,
54
-      path
55
-    });
56
-
57
-    if (route.children) {
58
-      flatten(route.children, path);
59
-    }
60
-  });
61
-}
62
-
63
-const getItems = () => getMenuItems(routes[0]?.children);
64
-const getRouteArr = () => {
65
-  if (routeArr.length < 1) {
66
-    flatten(routes, '/');
67
-  }
68
-  return routeArr;
69
-}
70
-// 
71
-export {
72
-  getItems,
73
-  getRouteArr,
74
-};

+ 14
- 0
src/routes/permissions.js View File

@@ -0,0 +1,14 @@
1
+
2
+export const getAuthedRoutes = (routes, permissions) => {
3
+  if (!routes || routes.length < 1) return [];
4
+
5
+  return routes.map(route => {
6
+    if (route.meta && route.meta.permission && permissions.indexOf(route.meta.permission) < 0) return false;
7
+
8
+    if (route.children) {
9
+      route.children = getAuthedRoutes(route.children, permissions);
10
+    }
11
+
12
+    return route;
13
+  }).filter(Boolean);
14
+}

+ 247
- 205
src/routes/routes.jsx View File

@@ -31,6 +31,8 @@ import RotationChartList from '@/pages/rotationChart/list';
31 31
 import RotationChartEdit from '@/pages/rotationChart/edit';
32 32
 import RotationChartIntroduction from '@/pages/rotationChart/introduction';
33 33
 import Roles from '@/pages/roles/index';
34
+import UserList from '@/pages/user';
35
+import UserEdit from '@/pages/user/Edit';
34 36
 
35 37
 /**
36 38
  * meta 用来扩展自定义数据数据
@@ -42,242 +44,253 @@ import Roles from '@/pages/roles/index';
42 44
  * }
43 45
  */
44 46
 
45
-export default [
47
+export const authRoutes = [
46 48
   {
47
-    path: '/',
48
-    element: <AuthLayout />,
49
+    path: 'task',
50
+    element: <Container />,
51
+    meta: {
52
+      title: '军供任务',
53
+      icon: <AppstoreOutlined />,
54
+    },
49 55
     children: [
50 56
       {
51 57
         index: true,
52
-        element: <Home />,
58
+        element: <Navigate to='guaranteeTask' replace />,
53 59
       },
54 60
       {
55
-        path: 'home',
56
-        element: <Home />,
61
+        path: 'guaranteeTask',
62
+        element: <GuaranteeTaskList />,
57 63
         meta: {
58
-          title: '首页',
59
-          icon: <DesktopOutlined />,
64
+          title: '军供通报',
65
+        },
66
+      },
67
+      {
68
+        path: 'guaranteeTask/edit',
69
+        element: <GuaranteeTaskEdit />,
70
+        meta: {
71
+          title: '任务配置',
72
+        },
73
+      },
74
+      {
75
+        path: 'guaranteeTask/print',
76
+        element: <GuaranteeTaskPrint />,
77
+        meta: {
78
+          hideInMenu: true,
79
+          noLayout: true,
80
+          target: '_blank',
81
+          title: '任务执行',
82
+        },
83
+      }
84
+    ]
85
+  },
86
+  {
87
+    path: 'material',
88
+    element: <Container />,
89
+    meta: {
90
+      title: '物资管理',
91
+      icon: <AppstoreOutlined />,
92
+    },
93
+    children: [
94
+      {
95
+        index: true,
96
+        element: <Navigate to='dish/list' replace />,
97
+      },
98
+      {
99
+        path: 'dish/list',
100
+        element: <DishList />,
101
+        meta: {
102
+          title: '菜肴管理',
103
+        },
104
+      },
105
+      {
106
+        path: 'dish/edit',
107
+        element: <DishEdit />,
108
+        meta: {
109
+          hideInMenu: true,
110
+          title: '菜肴维护',
111
+        },
112
+      },
113
+      {
114
+        path: 'package/list',
115
+        element: <PackageList />,
116
+        meta: {
117
+          title: '套餐管理',
118
+        },
119
+      },
120
+    ]
121
+  },
122
+  {
123
+    path: 'stock',
124
+    element: <Container />,
125
+    meta: {
126
+      title: '库存管理',
127
+      icon: <AppstoreOutlined />,
128
+    },
129
+    children: [
130
+      {
131
+        index: true,
132
+        element: <Navigate to='list' replace />,
133
+      },
134
+      {
135
+        path: 'list',
136
+        element: <StockList />,
137
+        meta: {
138
+          title: '库存列表',
139
+        },
140
+      },
141
+      {
142
+        path: 'add',
143
+        element: <StockEdit />,
144
+        meta: {
145
+          title: '库存维护',
146
+        },
147
+      },
148
+    ],
149
+  },
150
+  {
151
+    path: 'cms',
152
+    element: <Container />,
153
+    meta: {
154
+      title: '公告文件',
155
+    },
156
+    children: [
157
+      {
158
+        index: true,
159
+        element: <Navigate to='rotationChart/list' replace />,
160
+      },
161
+      {
162
+        path: 'station',
163
+        element: null,
164
+        meta: {
165
+          title: '本站信息',
166
+        },
167
+      },
168
+      {
169
+        path: 'rotationChart/list',
170
+        element: <RotationChartList />,
171
+        meta: {
172
+          title: '公告内容',
173
+        },
174
+      },
175
+      {
176
+        path: 'rotationChart/edit',
177
+        element: <RotationChartEdit />,
178
+        meta: {
179
+          title: '公告维护',
180
+        },
181
+      },
182
+      {
183
+        path: 'rotationChart/introduction',
184
+        element: <RotationChartIntroduction />,
185
+        meta: {
186
+          title: '本站信息简介',
187
+        },
188
+      },
189
+      {
190
+        path: 'regulation',
191
+        element: null,
192
+        meta: {
193
+          title: '规章制度',
60 194
         },
61 195
       },
62 196
       {
63
-        path: 'task',
64
-        element: <Container />,
197
+        path: 'emergency-plan',
198
+        element: null,
65 199
         meta: {
66
-          title: '军供任务',
67
-          icon: <AppstoreOutlined />,
200
+          title: '应急预案',
68 201
         },
69
-        children: [
70
-          {
71
-            index: true,
72
-            element: <Navigate to='guaranteeTask' replace />,
73
-          },
74
-          {
75
-            path: 'guaranteeTask',
76
-            element: <GuaranteeTaskList />,
77
-            meta: {
78
-              title: '军供通报',
79
-            },
80
-          },
81
-          {
82
-            path: 'guaranteeTask/edit',
83
-            element: <GuaranteeTaskEdit />,
84
-            meta: {
85
-              title: '任务配置',
86
-            },
87
-          },
88
-          {
89
-            path: 'guaranteeTask/print',
90
-            element: <GuaranteeTaskPrint />,
91
-            meta: {
92
-              hideInMenu: true,
93
-              noLayout: true,
94
-              target: '_blank',
95
-              title: '任务执行',
96
-            },
97
-          }
98
-        ]
99 202
       },
100 203
       {
101
-        path: 'material',
102
-        element: <Container />,
204
+        path: 'files',
205
+        element: null,
103 206
         meta: {
104
-          title: '物资管理',
105
-          icon: <AppstoreOutlined />,
207
+          title: '文件管理',
106 208
         },
107
-        children: [
108
-          {
109
-            index: true,
110
-            element: <Navigate to='dish/list' replace />,
111
-          },
112
-          {
113
-            path: 'dish/list',
114
-            element: <DishList />,
115
-            meta: {
116
-              title: '菜肴管理',
117
-            },
118
-          },
119
-          {
120
-            path: 'dish/edit',
121
-            element: <DishEdit />,
122
-            meta: {
123
-              hideInMenu: true,
124
-              title: '菜肴维护',
125
-            },
126
-          },
127
-          {
128
-            path: 'package/list',
129
-            element: <PackageList />,
130
-            meta: {
131
-              title: '套餐管理',
132
-            },
133
-          },
134
-        ]
135 209
       },
210
+    ],
211
+  },
212
+  {
213
+    path: 'static',
214
+    element: <Container />,
215
+    meta: {
216
+      title: '数据分析',
217
+    },
218
+  },
219
+  {
220
+    path: 'system',
221
+    element: <Container />,
222
+    meta: {
223
+      title: '系统管理',
224
+    },
225
+    children: [
136 226
       {
137
-        path: 'stock',
138
-        element: <Container />,
227
+        index: true,
228
+        element: <Navigate to='stockClassification/list' replace />,
229
+      },
230
+      {
231
+        path: 'stockClassification/list',
232
+        element: <StockClassificationList />,
139 233
         meta: {
140
-          title: '库存管理',
141
-          icon: <AppstoreOutlined />,
234
+          title: '库存分类管理',
142 235
         },
143
-        children: [
144
-          {
145
-            index: true,
146
-            element: <Navigate to='list' replace />,
147
-          },
148
-          {
149
-            path: 'list',
150
-            element: <StockList />,
151
-            meta: {
152
-              title: '库存列表',
153
-            },
154
-          },
155
-          {
156
-            path: 'add',
157
-            element: <StockEdit />,
158
-            meta: {
159
-              title: '库存维护',
160
-            },
161
-          },
162
-        ],
163 236
       },
164 237
       {
165
-        path: 'cms',
166
-        element: <Container />,
238
+        path: 'stockClassification/edit',
239
+        element: <StockClassificationEdit />,
167 240
         meta: {
168
-          title: '公告文件',
241
+          title: '库存分类维护',
242
+          hideInMenu: true,
169 243
         },
170
-        children: [
171
-          {
172
-            index: true,
173
-            element: <Navigate to='rotationChart/list' replace />,
174
-          },
175
-          {
176
-            path: 'station',
177
-            element: null,
178
-            meta: {
179
-              title: '本站信息',
180
-            },
181
-          },
182
-          {
183
-            path: 'rotationChart/list',
184
-            element: <RotationChartList />,
185
-            meta: {
186
-              title: '公告内容',
187
-            },
188
-          },
189
-          {
190
-            path: 'rotationChart/edit',
191
-            element: <RotationChartEdit />,
192
-            meta: {
193
-              title: '公告维护',
194
-            },
195
-          },
196
-          {
197
-            path: 'rotationChart/introduction',
198
-            element: <RotationChartIntroduction />,
199
-            meta: {
200
-              title: '本站信息简介',
201
-            },
202
-          },
203
-          {
204
-            path: 'regulation',
205
-            element: null,
206
-            meta: {
207
-              title: '规章制度',
208
-            },
209
-          },
210
-          {
211
-            path: 'emergency-plan',
212
-            element: null,
213
-            meta: {
214
-              title: '应急预案',
215
-            },
216
-          },
217
-          {
218
-            path: 'files',
219
-            element: null,
220
-            meta: {
221
-              title: '文件管理',
222
-            },
223
-          },
224
-        ],
225 244
       },
226 245
       {
227
-        path: 'static',
228
-        element: <Container />,
246
+        path: 'log',
247
+        element: <StockLog />,
229 248
         meta: {
230
-          title: '数据分析',
249
+          title: '库存操作日志',
231 250
         },
232 251
       },
233 252
       {
234
-        path: 'system',
235
-        element: <Container />,
253
+        path: 'roles',
254
+        element: <Roles />,
236 255
         meta: {
237
-          title: '系统管理',
256
+          title: '系统角色管理',
257
+        },
258
+      },
259
+      {
260
+        path: 'user',
261
+        element: <UserList />,
262
+        meta: {
263
+          title: '系统用户管理',
264
+        },
265
+      },
266
+      {
267
+        path: 'user/edit',
268
+        element: <UserEdit />,
269
+        meta: {
270
+          hideInMenu: true,
271
+          title: '系统用户编辑',
272
+        },
273
+      }
274
+    ],
275
+  },
276
+]
277
+
278
+export const defaultRoutes = [
279
+  {
280
+    path: '/',
281
+    element: <AuthLayout />,
282
+    children: [
283
+      {
284
+        index: true,
285
+        element: <Home />,
286
+      },
287
+      {
288
+        path: 'home',
289
+        element: <Home />,
290
+        meta: {
291
+          title: '首页',
292
+          icon: <DesktopOutlined />,
238 293
         },
239
-        children: [
240
-          {
241
-            index: true,
242
-            element: <Navigate to='stockClassification/list' replace />,
243
-          },
244
-          {
245
-            path: 'stockClassification/list',
246
-            element: <StockClassificationList />,
247
-            meta: {
248
-              title: '库存分类管理',
249
-            },
250
-          },
251
-          {
252
-            path: 'stockClassification/edit',
253
-            element: <StockClassificationEdit />,
254
-            meta: {
255
-              title: '库存分类维护',
256
-              hideInMenu: true,
257
-            },
258
-          },
259
-          {
260
-            path: 'log',
261
-            element: <StockLog />,
262
-            meta: {
263
-              title: '库存操作日志',
264
-            },
265
-          },
266
-          {
267
-            path: 'roles',
268
-            element: <Roles />,
269
-            meta: {
270
-              title: '系统角色管理',
271
-            },
272
-          },
273
-          {
274
-            path: 'user',
275
-            element: null,
276
-            meta: {
277
-              title: '系统用户管理',
278
-            },
279
-          }
280
-        ],
281 294
       },
282 295
       {
283 296
         path: '*',
@@ -293,4 +306,33 @@ export default [
293 306
     path: '*',
294 307
     element: <Page404 />
295 308
   }
296
-]
309
+];
310
+
311
+export function mergeAuthRoutes (r1, r2) {
312
+  const r = r1.slice();
313
+  const children = r1[0].children.slice();
314
+  r[0].children = children.concat(r2);
315
+  return r;
316
+}
317
+
318
+// 全部路由
319
+export const routes = mergeAuthRoutes(defaultRoutes, authRoutes);
320
+
321
+function getPath(parent = '/', current = '') {
322
+  if (current.indexOf('/') === 0 || current.indexOf('http') === 0) return current;
323
+  return `${parent}/${current}`.replace(/\/\//g, '/');
324
+}
325
+
326
+// 路由数组, 一维数组
327
+export const routeArr = (() => {
328
+  const flatten = (routes, parentPath = '/') => {
329
+    return routes.reduce((acc, route) => {
330
+      const path = getPath(parentPath, route.path);
331
+      const children = route.children ? flatten(route.children, path) : [];
332
+      
333
+      return acc.concat([{ ...route, path }].concat(children));
334
+    }, []);
335
+  }
336
+
337
+  return flatten(routes);
338
+})();

+ 25
- 1
src/services/user.js View File

@@ -1,4 +1,4 @@
1
-import request from '@/utils/request';
1
+import request, { restful } from '@/utils/request';
2 2
 
3 3
 /**
4 4
  * 获取当前人员
@@ -17,3 +17,27 @@ import request from '@/utils/request';
17 17
   */
18 18
   export const login = (data) => request('/login', { data, method: 'post' });
19 19
 
20
+
21
+  const [
22
+    getUserList,
23
+    getUser,
24
+    saveUser,
25
+    updateUser,
26
+    deleteUser,
27
+   ] = restful('/users');
28
+
29
+  export {
30
+    getUserList,
31
+    getUser,
32
+    saveUser,
33
+    updateUser,
34
+    deleteUser,
35
+  }
36
+
37
+  /**
38
+   * 更新用户状态
39
+   * @param {*} id 
40
+   * @param {*} status 
41
+   * @returns 
42
+   */
43
+  export const updateUserStatus = (id, status) => request.put(`/users/${id}/status?status=${status}`);

+ 26
- 2
src/store/models/user.js View File

@@ -1,12 +1,36 @@
1
-import { useState } from "react";
1
+import { useState, useRef } from "react";
2
+import { queryCurrentUser } from "@/services/user";
3
+import { defaultRoutes, authRoutes, mergeAuthRoutes } from '@/routes/routes';
4
+import { getMenuItems } from "@/routes/menus";
5
+import { getAuthedRoutes } from "@/routes/permissions";
2 6
 
3 7
 export default function useUser() {
4
-
5 8
   const [user, setUser] = useState();
9
+  const menusRef = useRef();
10
+  const routesRef = useRef();
11
+
12
+  const getCurrentUser = () => new Promise((resolve, reject) => {
13
+    queryCurrentUser().then(res => {
14
+      const permissions = (res.resourcesList || []).map(x => x.code);
15
+
16
+      // authRoutes 是所有待验证授权的路由
17
+      // authedRoutes 是已经被授权的路由
18
+      const authedRoutes = getAuthedRoutes(authRoutes, permissions);
19
+
20
+      menusRef.current = getMenuItems(authedRoutes);
21
+      routesRef.current = mergeAuthRoutes(defaultRoutes, authedRoutes);
22
+
23
+      setUser(res);
24
+      resolve(res);
25
+    })
26
+  });
6 27
 
7 28
   return {
8 29
     user,
9 30
     setUser,
31
+    getCurrentUser,
32
+    menus: menusRef.current || [],
33
+    routes: routesRef.current || [],
10 34
   }
11 35
 
12 36
 }

+ 1
- 4
src/utils/hooks/useRoute.jsx View File

@@ -1,12 +1,9 @@
1 1
 import { useLocation } from "react-router-dom";
2
-import { getRouteArr } from '@/routes/menus';
2
+import { routeArr } from '@/routes/routes';
3 3
 
4 4
 // 获取当前的 route 信息
5 5
 export default function useRoute() {
6 6
   const location = useLocation();
7
-  const routeArr = getRouteArr();
8
-
9 7
   const currentRoute = routeArr.filter(x => x.path === location.pathname)[0];
10
-
11 8
   return currentRoute;
12 9
 }