Your Name 2 yıl önce
ebeveyn
işleme
13ce3f4f1a

+ 4
- 4
package.json Dosyayı Görüntüle

@@ -19,15 +19,15 @@
19 19
     "classnames": "^2.3.2",
20 20
     "echarts": "^5.4.0",
21 21
     "md5": "^2.3.0",
22
-    "react": "18.1.0",
23
-    "react-dom": "18.1.0",
22
+    "react": "18.2.0",
23
+    "react-dom": "18.2.0",
24 24
     "react-helmet": "^6.1.0",
25 25
     "react-router-dom": "^6.4.2",
26 26
     "react-transition-group": "^4.4.5"
27 27
   },
28 28
   "devDependencies": {
29
-    "@types/react": "^18.0.15",
30
-    "@types/react-dom": "^18.0.6",
29
+    "@types/react": "18.0.27",
30
+    "@types/react-dom": "18.0.10",
31 31
     "@vitejs/plugin-react": "^2.0.0",
32 32
     "less": "^4.1.3",
33 33
     "vite": "^3.0.0",

+ 1546
- 2255
pnpm-lock.yaml
Dosya farkı çok büyük olduğundan ihmal edildi
Dosyayı Görüntüle


+ 83
- 0
src/components/Page/Curd.jsx Dosyayı Görüntüle

@@ -0,0 +1,83 @@
1
+import React from 'react';
2
+import { DrawerForm } from '@ant-design/pro-components';
3
+import { Form } from 'antd';
4
+import List from './List';
5
+
6
+const drawerProps = {
7
+  destroyOnClose: true
8
+}
9
+export default (props) => {
10
+  const { rowKey, columns, request, formProps, renderFormItems, ...leftProps } = props;
11
+
12
+  const [form] = Form.useForm();
13
+  const listRef = React.useRef();
14
+  const rowRef = React.useRef();
15
+  const [open, setOpen] = React.useState(false);
16
+  
17
+  const onAdd = () => {
18
+    rowRef.current = undefined;
19
+    form.resetFields();
20
+    setOpen(true);
21
+  }
22
+
23
+  const onEdit = (row) => {
24
+    rowRef.current = row;
25
+    form.setFieldsValue(row);
26
+    setOpen(true);
27
+  }
28
+
29
+  const onDelete = (row) => {
30
+    rowRef.current = row;
31
+
32
+    listRef.current.showLoading('正在操作, 请稍候...');
33
+    request.del(row[rowKey]).then(() => {
34
+      listRef.current.hideLoading();
35
+      listRef.current.reload();
36
+    }).catch(() => {
37
+      listRef.current.hideLoading();
38
+    });
39
+  }
40
+
41
+  const onFinish = async (values) => {
42
+    if (rowRef.current) {
43
+      await request.update(rowRef.current[rowKey], {
44
+        ...rowRef.current,
45
+        ...values,
46
+      });
47
+    } else {
48
+      await request.save(values);
49
+    }
50
+    listRef.current.reload();
51
+
52
+    return true;
53
+  }
54
+
55
+  return (
56
+    <>
57
+      <List
58
+        ref={listRef}
59
+        rowKey={rowKey}
60
+        columns={columns}
61
+        request={request.list}
62
+        onAdd={onAdd}
63
+        onDelete={onDelete}
64
+        onEdit={onEdit}
65
+        {...leftProps}
66
+      />
67
+
68
+      <DrawerForm
69
+        submitTimeout={3000}
70
+        open={open}
71
+        form={form}
72
+        drawerProps={drawerProps}
73
+        onOpenChange={setOpen}
74
+        onFinish={onFinish}
75
+        {...formProps}
76
+      >
77
+        {
78
+          renderFormItems && renderFormItems()
79
+        }
80
+      </DrawerForm>
81
+    </>
82
+  )
83
+}

+ 24
- 34
src/components/Page/Edit.jsx Dosyayı Görüntüle

@@ -2,7 +2,6 @@ import React from 'react';
2 2
 import { Button, Card, Form } from 'antd';
3 3
 import { useSearchParams, useNavigate } from "react-router-dom";
4 4
 import useBool from '@/utils/hooks/useBool';
5
-import { restful } from '@/utils/request';
6 5
 import Page from './index';
7 6
 
8 7
 const FormItem = Form.Item;
@@ -10,12 +9,12 @@ const formItemLayout = { labelCol: { span: 6 }, wrapperCol: { span: 14 } };
10 9
 
11 10
 export default (props) => {
12 11
   /**
13
-   * resource 用来确定应该调用哪个接口
12
+   * request 编辑或者新增接口
14 13
    * dataFunc 将 form 表单数据转换为接口需要格式, 一般情况下不需要这个操作
15 14
    * formDataFunc 将接口返回的值转换为 form 表单数据格式, 一般情况下不需要这个操作
16 15
    * width form 表单宽度, 默认 800px
17 16
    */
18
-  const { resource, dataFunc, formDataFunc, width = '800px' } = props;
17
+  const { request, dataFunc, formDataFunc, width = '800px', onFinish, ...leftProps } = props;
19 18
   const [loading, startLoading, stopLoading] = useBool();
20 19
   const [form] = Form.useForm();
21 20
   const navigate = useNavigate();
@@ -23,67 +22,58 @@ export default (props) => {
23 22
   // 获取 id query 参数, 如果存在说明是编辑,而不是新增
24 23
   const [params] = useSearchParams();
25 24
   const id = params.get("id");
26
-
27
-  // 接口汇总
28
-  const api = React.useMemo(() => {
29
-    // 通过 resource 知道是哪个接口
30
-    // 通过 restfule 生成 增删改查的接口
31
-    const apis = restful(resource);
32
-
33
-    // 返回三个接口
34
-    // 1. 获取详情
35
-    // 2. 新增
36
-    // 3. 更新
37
-    return {
38
-      get: apis[1],
39
-      save: apis[2],
40
-      update: apis[3],
41
-    }
42
-  }, [resource]);
43 25
   
44
-  const navBack = () => {
26
+  const navBack = React.useCallback(() => {
45 27
     const t = setTimeout(() => {
46 28
       navigate(-1);
47 29
       clearTimeout(t);
48 30
     }, 600);
49
-  }
31
+  }, [navigate]);
50 32
 
51 33
   // 表单提交
52
-  const onFinish = (values) => {    
34
+  const onSubmit = (values) => {    
53 35
     startLoading();
54 36
     // 可能需要转换下格式
55 37
     const data = dataFunc ? dataFunc(id, values) : values;
56 38
     if (id) {
57
-      api.update(id, data).then(() => {
39
+      request.update(id, data).then((res) => {
58 40
         stopLoading();
59
-        navBack();
41
+        if (onFinish) {
42
+          onFinish(res);
43
+        } else {
44
+          navBack();
45
+        }
60 46
       }).catch(stopLoading);
61 47
     } else {
62
-      api.save(data)
63
-        .then(() => {
48
+      request.save(data)
49
+        .then((res) => {
64 50
           stopLoading();
65
-          navBack();
51
+          if (onFinish) {
52
+            onFinish(res);
53
+          } else {
54
+            navBack();
55
+          }
66 56
         }).catch(stopLoading);
67 57
     }
68 58
   }
69 59
 
70 60
   // 查询详情
71 61
   React.useEffect(() => {
72
-    if (id) {
73
-      api.get(id).then(res => {
62
+    if (id && request) {
63
+      request.get(id).then(res => {
74 64
         // 此处可能需要转换下数据格式
75 65
         const formData = formDataFunc ? formDataFunc(res) : res;
76 66
         form.setFieldsValue(formData);
77 67
       })
78 68
     }
79
-  }, [id]);
69
+  }, [request, id]);
80 70
 
81 71
   return (
82 72
     <Page>
83
-      <Card>
84
-        <Form {...formItemLayout} form={form} onFinish={onFinish} style={{width}}>
73
+      <Card bodyStyle={{padding: '48px 24px'}}>
74
+        <Form {...formItemLayout} form={form} onFinish={onSubmit} style={{width}} {...leftProps} >
85 75
           {props.children}
86
-          <FormItem label=" " colon={false}>
76
+          <FormItem label=" " colon={false} style={{marginBottom: 0}}>
87 77
             <Button type="default" onClick={() => navigate(-1)}>
88 78
               取消
89 79
             </Button>

+ 59
- 60
src/components/Page/List.jsx Dosyayı Görüntüle

@@ -1,45 +1,21 @@
1
-import React, { useRef, useMemo } from 'react';
1
+import React from 'react';
2 2
 import { Button, Popconfirm, message } from 'antd';
3 3
 import { ProTable } from '@ant-design/pro-components';
4
-import { Link, useNavigate } from "react-router-dom";
5
-import { queryTable, restful } from '@/utils/request';
4
+import { queryTable } from '@/utils/request';
6 5
 import Page from './index';
7 6
 
8
-export default (props) => {
7
+export default React.forwardRef((props, ref) => {
9 8
   /**
10
-   * resource 用来确定应该调用哪个接口
11
-   * rowKey 标识数据每一行的唯一键, 只能是字符串
12
-   * columns 定义表格每一列, 符合 ProTable 定义
13
-   * editURL 编辑页路由
9
+   * request 与 ProTable 定义不同,这个只是普通的接口即可
14 10
    */
15
-  const { resource, rowKey, columns, editURL } = props;
16
-  const actionRef = useRef();
17
-  const navigate = useNavigate();
11
+  const { request, rowKey, columns, onAdd, onDelete, onEdit, toolBarRender, columnOptionRender, ...leftProps } = props;
12
+  const actionRef = React.useRef();
13
+  const hideRef = React.useRef();
18 14
 
19
-  // 接口汇总
20
-  const api = useMemo(() => {
21
-    // 通过 resource 知道是哪个接口
22
-    // 通过 restfule 生成 增删改查的接口
23
-    const apis = restful(resource);
24
-
25
-    // 返回两个,一个是列表查询, 一个是删除
26
-    return {
27
-      list: apis[0],
28
-      delete: apis[4],
29
-    }
30
-  }, [resource]);
31
-
32
-  // 响应 删除 操作
33
-  const onDelete = (row) => {
34
-    const hide = message.loading('操作中, 请稍候...');
35
-    api.delete(row[rowKey]).then(() => {
36
-      hide();
37
-      actionRef.current.reload();
38
-    }).catch(hide);
39
-  }
15
+  const api = React.useMemo(() => queryTable(request), [request]);
40 16
 
41 17
   // 统一实现操作列
42
-  const cols = [
18
+  const cols = React.useMemo(() => [
43 19
     ...columns,
44 20
     {
45 21
       title: '操作',
@@ -47,44 +23,67 @@ export default (props) => {
47 23
       key: 'option',
48 24
       ellipsis: true,
49 25
       render: (_, record) => [
50
-        <Button style={{ padding: 0 }} type="link" key={1} onClick={() => { navigate(`${editURL}?id=${record[rowKey]}`) }}>
51
-          编辑
52
-        </Button>,
53
-        <Popconfirm
54
-          key={3}
55
-          title="您是否确认删除 ?"
56
-          onConfirm={() => onDelete(record)}
57
-          okText="确定"
58
-          cancelText="取消"
59
-        >
60
-          <Button style={{ padding: 0 }} type="link">
61
-            删除
26
+        (
27
+          onEdit && 
28
+          <Button style={{ padding: 0 }} type="link" key={1} onClick={() => onEdit(record) }>
29
+            编辑
62 30
           </Button>
63
-        </Popconfirm>,
64
-      ],
31
+        ),
32
+        (
33
+          onDelete &&
34
+          <Popconfirm
35
+            key={3}
36
+            title="您是否确认删除 ?"
37
+            onConfirm={() => onDelete(record)}
38
+            okText="确定"
39
+            cancelText="取消"
40
+          >
41
+            <Button style={{ padding: 0 }} type="link">
42
+              删除
43
+            </Button>
44
+          </Popconfirm>
45
+        ),
46
+        ...(columnOptionRender ? columnOptionRender(_, record) : []),
47
+      ].filter(Boolean),
65 48
     },
66
-  ]
49
+  ], [columns, onEdit, onDelete]);
50
+
51
+  React.useImperativeHandle(ref, () => {
52
+    const showLoading = msg => (hideRef.current = message.loading(msg || '操作中, 请稍候...'));
53
+    const hideLoading = () => hideRef.current && hideRef.current();
54
+    const reload = () => actionRef.current?.reload && actionRef.current.reload();
55
+
56
+    return {
57
+      showLoading,
58
+      hideLoading,
59
+      reload,
60
+      actionRef: actionRef,
61
+    }
62
+  }, []);
67 63
 
68 64
   return (
69 65
     <Page>
70 66
       <ProTable
71 67
         rowKey={rowKey}
72 68
         columns={cols}
73
-        request={queryTable(api.list)}
69
+        request={api}
74 70
         cardBordered
75 71
         actionRef={actionRef}
76 72
         toolBarRender={() => [
77
-          <Button
78
-            key="1"
79
-            type="primary"
80
-            onClick={() => {
81
-              navigate(editURL);
82
-            }}
83
-          >
84
-            新增
85
-          </Button>,
86
-        ]}
73
+          (
74
+            onAdd && 
75
+            <Button
76
+              key="1"
77
+              type="primary"
78
+              onClick={onAdd}
79
+            >
80
+              新增
81
+            </Button>
82
+          ),
83
+          ...(toolBarRender ? toolBarRender() : []),
84
+        ].filter(Boolean)}
85
+        {...leftProps}
87 86
       />
88 87
     </Page>
89 88
   )
90
-}
89
+});

+ 5
- 5
src/components/Page/index.jsx Dosyayı Görüntüle

@@ -1,11 +1,11 @@
1 1
 import React from 'react';
2 2
 import { Typography } from 'antd';
3
-import useRoute from '@/utils/hooks/useRoute';
3
+import useRoute from '@/routes/hooks/useRoute';
4 4
 
5 5
 const pageStyle = {
6
-  // margin: '24px 24px 0 24px',
7
-  margin: '24px',
8
-  minHeight: 'calc(100% - 48px)',
6
+  margin: '0 24px 24px 24px',
7
+  // margin: '24px',
8
+  minHeight: 'calc(100% - 24px)',
9 9
 }
10 10
 const { Title } = Typography;
11 11
 
@@ -16,7 +16,7 @@ export default (props) => {
16 16
 
17 17
   return (
18 18
     <div style={style}>
19
-      { title && !meta.noLayout && <Title level={4} style={{ paddingBottom: '12px' }}>{ title }</Title> }
19
+      { title && !meta.noLayout && <Title level={3} style={{ paddingBottom: '12px', fontWeight: 400 }}>{ title }</Title> }
20 20
       {props.children}
21 21
     </div>
22 22
   )

+ 11
- 6
src/index.less Dosyayı Görüntüle

@@ -7,7 +7,8 @@ html, body, #root {
7 7
 :root {
8 8
   --theme-color: #fff;
9 9
   --theme-front: #000;
10
-  --header-height: 48px;
10
+  --theme-main-bg: #f1f7fe; // #f0f2f5;
11
+  --header-height: 60px;
11 12
   --siderbar-width: 240px;
12 13
 }
13 14
 
@@ -26,28 +27,32 @@ html, body, #root {
26 27
   z-index: 100;
27 28
 }
28 29
 
29
-.ant-layout {
30
-  background-color: #f0f2f5;
30
+.ant-layout, .ant-layout-footer {
31
+  background-color: var(--theme-main-bg);
31 32
 }
32 33
 
33 34
 .ant-layout-header {
34 35
   line-height: var(--header-height);
35
-  box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08);
36
+  // box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08);
36 37
   // border-bottom: 1px solid gainsboro;
37 38
   z-index: 10;
38 39
 }
39 40
 
40 41
 .layout-sidebar {
41 42
   width: var(--siderbar-width);
42
-  background-color: var(--theme-color);
43
+  background-color: transparent; // var(--theme-color);
43 44
   color: var(--theme-front);
44
-  box-shadow: 1px 0 4px 0 rgba(0, 21, 41, 0.08);
45
+  // box-shadow: 1px 0 4px 0 rgba(0, 21, 41, 0.08);
45 46
 
46 47
   .ant-menu {
47 48
     background: transparent;
48 49
   }
49 50
 }
50 51
 
52
+.ant-card, .ant-pro-card {
53
+  border-radius: 8px !important;
54
+}
55
+
51 56
 
52 57
 // 兼容 360
53 58
 .ant-pro-sider-collapsed-button {

+ 30
- 0
src/layouts/AuthLayout/Main.jsx Dosyayı Görüntüle

@@ -0,0 +1,30 @@
1
+import React from 'react';
2
+import { Layout } from 'antd';
3
+import { useLocation, Outlet } from "react-router-dom";
4
+import { useModel } from '@/store';
5
+import useRoute from '@/routes/hooks/useRoute';
6
+import SiderBar from './components/SiderBar';
7
+import Header from './components/Header';
8
+import Container from './components/Container';
9
+
10
+import './style.less';
11
+
12
+export default (props) => {
13
+  const { theme } = useModel('system');
14
+  const { user, menus } = useModel('user');
15
+  const location = useLocation();
16
+  const { meta } = useRoute() || {};
17
+  const { noLayout = false, noSiderBar = false, noFooter = false } = meta || {};
18
+
19
+  return noLayout
20
+    ? <Outlet />
21
+    : (
22
+      <Layout style={{ height: '100vh' }}>
23
+        <Header theme={theme} user={user} />
24
+        <Layout style={{ height: 'calc(100vh - var(--header-height))' }}>
25
+          { !noSiderBar && <SiderBar theme={theme} menus={menus} location={location} /> }
26
+          <Container location={location} noFooter={noFooter} />
27
+        </Layout>
28
+      </Layout>
29
+    );
30
+}

+ 4
- 0
src/layouts/AuthLayout/RedirectLogin.jsx Dosyayı Görüntüle

@@ -0,0 +1,4 @@
1
+
2
+import { Navigate } from "react-router-dom";
3
+
4
+return () => <Navigate to="/login?back=true" />;

+ 1
- 1
src/layouts/AuthLayout/components/Container.jsx Dosyayı Görüntüle

@@ -20,7 +20,7 @@ export default (props) => {
20 20
 
21 21
   return (
22 22
     <div className='layout-container'>
23
-      <Content style={{ minHeight: height, margin: `${marginSpace}px` }}>
23
+      <Content style={{ minHeight: height, margin: `0 ${marginSpace}px`}}>
24 24
         <PageTransition location={props.location}>
25 25
           <Outlet />
26 26
         </PageTransition>

+ 1
- 1
src/layouts/AuthLayout/components/Header/Title.jsx Dosyayı Görüntüle

@@ -1,5 +1,5 @@
1 1
 
2
-import useRoute from '@/utils/hooks/useRoute';
2
+import useRoute from '@/routes/hooks/useRoute';
3 3
 
4 4
 const titleStyle = {
5 5
   margin: 0,

+ 1
- 1
src/layouts/AuthLayout/components/HtmlTitle.jsx Dosyayı Görüntüle

@@ -1,7 +1,7 @@
1 1
 
2 2
 import { Helmet } from "react-helmet";
3 3
 import { useModel } from '@/store';
4
-import useRoute from '@/utils/hooks/useRoute';
4
+import useRoute from '@/routes/hooks/useRoute';
5 5
 
6 6
 export default (props) => {
7 7
   const { app } = useModel('system');

+ 1
- 1
src/layouts/AuthLayout/components/PageTransition/style.less Dosyayı Görüntüle

@@ -8,7 +8,7 @@
8 8
   transform: translateY(0%);
9 9
 }
10 10
 .page-fade-exit {
11
-  opacity: 1;
11
+  opacity: 0; // 1;
12 12
   transform: translateY(0%);
13 13
 }
14 14
 .page-fade-exit-active {

+ 0
- 21
src/layouts/AuthLayout/components/RequireLogin.jsx Dosyayı Görüntüle

@@ -1,21 +0,0 @@
1
-import React, { useState, useEffect } from 'react';
2
-import { useLocation, Navigate } from "react-router-dom";
3
-import { useModel } from '@/store';
4
-
5
-export default (props) => {
6
-  const { user, getCurrentUser } = useModel('user');
7
-  const [userStatus, setUserStatus] = useState(user && user.id ? 1 : 0);
8
-
9
-  useEffect(() => {
10
-    if (!user || !user.id) {
11
-      getCurrentUser().then(() => {
12
-        setUserStatus(1);
13
-      }).catch(() => {
14
-        setUserStatus(-1);
15
-      });
16
-    }
17
-  }, []);
18
-
19
-  return userStatus === 0 ? null :
20
-    userStatus === -1 ? <Navigate to="/login?back=true" /> : props.children;
21
-}

+ 15
- 33
src/layouts/AuthLayout/index.jsx Dosyayı Görüntüle

@@ -1,41 +1,23 @@
1
-import React, { useEffect } from 'react';
2
-import { Layout, Spin } from 'antd';
3
-import { useLocation, Outlet } from "react-router-dom";
4
-import { useModel } from '@/store';
5
-import useRoute from '@/utils/hooks/useRoute';
6
-import RequireLogin from './components/RequireLogin';
7
-import SiderBar from './components/SiderBar';
8
-import Header from './components/Header';
9
-import Container from './components/Container';
1
+import React from 'react';
2
+import { Spin } from 'antd';
10 3
 import HtmlTitle from './components/HtmlTitle';
4
+// import Main from './Main';
5
+import withLogin from './withLogin';
6
+const RequireLogin = React.lazy(() => withLogin(import('./Main')));
11 7
 
12
-import './style.less';
8
+const Spinner = () => (
9
+  <div style={{ width: '100%', height: '100%', display: 'grid', placeItems: 'center' }}>
10
+    <Spin />
11
+  </div>
12
+)
13 13
 
14 14
 export default (props) => {
15
-  const { theme } = useModel('system');
16
-  const { user, menus } = useModel('user');
17
-  const location = useLocation();
18
-  const { meta } = useRoute() || {};
19
-  const { noLayout = false, noSiderBar = false, noFooter = false } = meta || {};
20
-
21 15
   return (
22
-    <Spin spinning={!user} size="large">
16
+    <>
23 17
       <HtmlTitle />
24
-      <RequireLogin>
25
-        {
26
-          noLayout
27
-            ? <Outlet />
28
-            : (
29
-              <Layout style={{ height: '100vh' }}>
30
-                <Header theme={theme} user={user} />
31
-                <Layout style={{ height: 'calc(100vh - var(--header-height))' }}>
32
-                  { !noSiderBar && <SiderBar theme={theme} menus={menus} location={location} /> }
33
-                  <Container location={location} noFooter={noFooter} />
34
-                </Layout>
35
-              </Layout>
36
-            )
37
-        }
38
-      </RequireLogin>
39
-    </Spin>
18
+      <React.Suspense fallback={<Spinner />}>
19
+        <RequireLogin />
20
+      </React.Suspense>
21
+    </>
40 22
   );
41 23
 }

+ 12
- 4
src/layouts/AuthLayout/style.less Dosyayı Görüntüle

@@ -7,10 +7,10 @@
7 7
   justify-content: space-between;
8 8
   color: #fff;
9 9
 
10
-  // &.light {
11
-  //   background-color: #fff;
12
-  //   color: #000;
13
-  // }
10
+  &.light {
11
+    background-color: transparent;
12
+    color: #000;
13
+  }
14 14
 
15 15
   .header-content {
16 16
     flex: 1;
@@ -74,4 +74,12 @@
74 74
   }
75 75
 
76 76
   padding-top: 0; // 避免子元素的 margin 影响
77
+}
78
+
79
+.ant-menu-vertical {
80
+  border: none;
81
+}
82
+
83
+.ant-layout-sider-trigger {
84
+  background-color: transparent !important;
77 85
 }

+ 19
- 0
src/layouts/AuthLayout/withLogin.jsx Dosyayı Görüntüle

@@ -0,0 +1,19 @@
1
+import React from 'react';
2
+import store from '@/store';
3
+
4
+export default function withLogin(factory) {
5
+  return new Promise(resolve => {
6
+    const {user, getCurrentUser} = store.getState('user');
7
+    const loged = user && (user.userId || user.id);
8
+    if (loged) {
9
+      resolve(factory);
10
+    } else {
11
+      getCurrentUser().then(() => {
12
+        resolve(factory);
13
+      }).catch((e) => {
14
+        console.error(e)
15
+        resolve(import('./RedirectLogin'));
16
+      });
17
+    }
18
+  });
19
+}

+ 0
- 23
src/layouts/PageContainer.jsx Dosyayı Görüntüle

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

+ 117
- 0
src/pages/sample/curd.jsx Dosyayı Görüntüle

@@ -0,0 +1,117 @@
1
+import React from 'react';
2
+import Curd from '@/components/Page/Curd';
3
+import { Form, Input } from 'antd';
4
+
5
+export default (props) => {
6
+  const columns = [
7
+      {
8
+          dataIndex: 'index',
9
+          valueType: 'indexBorder',
10
+          width: 48,
11
+      },
12
+      {
13
+          title: '标题',
14
+          dataIndex: 'title',
15
+          copyable: true,
16
+          ellipsis: true,
17
+          tip: '标题过长会自动收缩',
18
+          formItemProps: {
19
+              rules: [
20
+                  {
21
+                      required: true,
22
+                      message: '此项为必填项',
23
+                  },
24
+              ],
25
+          },
26
+      },
27
+      {
28
+          disable: true,
29
+          title: '状态',
30
+          dataIndex: 'state',
31
+          filters: true,
32
+          onFilter: true,
33
+          ellipsis: true,
34
+          valueType: 'select',
35
+          valueEnum: {
36
+              all: { text: '超长'.repeat(50) },
37
+              open: {
38
+                  text: '未解决',
39
+                  status: 'Error',
40
+              },
41
+              closed: {
42
+                  text: '已解决',
43
+                  status: 'Success',
44
+                  disabled: true,
45
+              },
46
+              processing: {
47
+                  text: '解决中',
48
+                  status: 'Processing',
49
+              },
50
+          },
51
+      },
52
+      {
53
+          disable: true,
54
+          title: '标签',
55
+          dataIndex: 'labels',
56
+          search: false,
57
+          renderFormItem: (_, { defaultRender }) => {
58
+              return defaultRender(_);
59
+          },
60
+          render: (_, record) => (<Space>
61
+          {record.labels.map(({ name, color }) => (<Tag color={color} key={name}>
62
+              {name}
63
+              </Tag>))}
64
+          </Space>),
65
+      },
66
+      {
67
+          title: '创建时间',
68
+          key: 'showTime',
69
+          dataIndex: 'created_at',
70
+          valueType: 'date',
71
+          sorter: true,
72
+          hideInSearch: true,
73
+      },
74
+      {
75
+          title: '创建时间',
76
+          dataIndex: 'created_at',
77
+          valueType: 'dateRange',
78
+          hideInTable: true,
79
+          search: {
80
+              transform: (value) => {
81
+                  return {
82
+                      startTime: value[0],
83
+                      endTime: value[1],
84
+                  };
85
+              },
86
+          },
87
+      },
88
+  ];
89
+
90
+  return (
91
+    <Curd
92
+      columns={columns}
93
+      request={{
94
+        del: () => Promise.resolve(),
95
+        save: () => Promise.resolve(),
96
+        update: () => Promise.resolve(),
97
+        list: () => Promise.resolve({total: 0}),
98
+      }}
99
+      renderFormItems={() => (
100
+        <>
101
+          <Form.Item label="示例1" name="demo1">
102
+            <Input />
103
+          </Form.Item>
104
+          <Form.Item label="示例2" name="demo2">
105
+            <Input />
106
+          </Form.Item>
107
+          <Form.Item label="示例3" name="demo3">
108
+            <Input />
109
+          </Form.Item>
110
+          <Form.Item label="示例4" name="demo4">
111
+            <Input />
112
+          </Form.Item>
113
+        </>
114
+      )}
115
+    />
116
+  )
117
+}

+ 14
- 58
src/pages/sample/form/index.jsx Dosyayı Görüntüle

@@ -12,6 +12,7 @@ import {
12 12
   Card,
13 13
 } from 'antd';
14 14
 import React, { useState } from 'react';
15
+import Edit from '@/components/Page/Edit';
15 16
 
16 17
 const { Option } = Select;
17 18
 const residences = [
@@ -48,45 +49,8 @@ const residences = [
48 49
     ],
49 50
   },
50 51
 ];
51
-const formItemLayout = {
52
-  labelCol: {
53
-    xs: {
54
-      span: 24,
55
-    },
56
-    sm: {
57
-      span: 8,
58
-    },
59
-  },
60
-  wrapperCol: {
61
-    xs: {
62
-      span: 24,
63
-    },
64
-    sm: {
65
-      span: 16,
66
-    },
67
-  },
68
-};
69
-const tailFormItemLayout = {
70
-  wrapperCol: {
71
-    xs: {
72
-      span: 24,
73
-      offset: 0,
74
-    },
75
-    sm: {
76
-      span: 16,
77
-      offset: 8,
78
-    },
79
-  },
80
-};
81
-
82
-
83
-const BasicForm = () => {
84
-  const [form] = Form.useForm();
85
-
86
-  const onFinish = (values) => {
87
-    console.log('Received values of form: ', values);
88
-  };
89 52
 
53
+export default (props) => {
90 54
   const prefixSelector = (
91 55
     <Form.Item name="prefix" noStyle>
92 56
       <Select
@@ -126,18 +90,7 @@ const BasicForm = () => {
126 90
     value: website,
127 91
   }));
128 92
   return (
129
-    <Form
130
-      {...formItemLayout}
131
-      form={form}
132
-      name="register"
133
-      onFinish={onFinish}
134
-      initialValues={{
135
-        residence: ['zhejiang', 'hangzhou', 'xihu'],
136
-        prefix: '86',
137
-      }}
138
-      scrollToFirstError
139
-      style={{ background: '#fff', boxSizing: 'border-box', padding: '24px' }}
140
-    >
93
+    <Edit width="1000px">
141 94
       <Form.Item
142 95
         name="email"
143 96
         label="E-mail"
@@ -334,19 +287,22 @@ const BasicForm = () => {
334 287
               value ? Promise.resolve() : Promise.reject(new Error('Should accept agreement')),
335 288
           },
336 289
         ]}
337
-        {...tailFormItemLayout}
290
+        wrapperCol={{
291
+          xs: {
292
+            span: 24,
293
+            offset: 0,
294
+          },
295
+          sm: {
296
+            span: 16,
297
+            offset: 8,
298
+          },
299
+        }}
338 300
       >
339 301
         <Checkbox>
340 302
           I have read the <a href="">agreement</a>
341 303
         </Checkbox>
342 304
       </Form.Item>
343
-      <Form.Item {...tailFormItemLayout}>
344
-        <Button type="primary" htmlType="submit">
345
-          Register
346
-        </Button>
347
-      </Form.Item>
348
-    </Form>
305
+    </Edit>
349 306
   );
350 307
 };
351 308
 
352
-export default () => <Card><BasicForm /></Card>;

+ 3
- 2
src/pages/sample/home/index.jsx Dosyayı Görüntüle

@@ -1,5 +1,6 @@
1 1
 import React from 'react';
2 2
 import { Row, Col, Card, Space, Statistic } from 'antd';
3
+import Page from '@/components/Page';
3 4
 import Banner from './components/Banner';
4 5
 import AreaChart from './components/AreaChart';
5 6
 import BarChart from './components/BarChart';
@@ -15,7 +16,7 @@ export default (props) => {
15 16
   }
16 17
 
17 18
   return (
18
-    <div>
19
+    <Page>
19 20
       <Banner />
20 21
 
21 22
       <Row gutter={24} style={{ marginTop: '24px' }}>
@@ -35,6 +36,6 @@ export default (props) => {
35 36
           <BarChart style={chartStyle}/>
36 37
         </Col>
37 38
       </Row>
38
-    </div>
39
+    </Page>
39 40
   )
40 41
 }

+ 81
- 155
src/pages/sample/table/index.jsx Dosyayı Görüntüle

@@ -1,170 +1,96 @@
1
-import { EllipsisOutlined, PlusOutlined } from '@ant-design/icons';
2
-import { ProTable, TableDropdown } from '@ant-design/pro-components';
3
-import { Button, Dropdown, Menu, Space, Tag } from 'antd';
4
-import { useRef, useEffect } from 'react';
5
-import { useModel } from '@/store';
6 1
 
7
-// import request from 'umi-request';
8
-const columns = [
9
-    {
10
-        dataIndex: 'index',
11
-        valueType: 'indexBorder',
12
-        width: 48,
13
-    },
14
-    {
15
-        title: '标题',
16
-        dataIndex: 'title',
17
-        copyable: true,
18
-        ellipsis: true,
19
-        tip: '标题过长会自动收缩',
20
-        formItemProps: {
21
-            rules: [
22
-                {
23
-                    required: true,
24
-                    message: '此项为必填项',
25
-                },
26
-            ],
2
+import { Space, Tag } from 'antd';
3
+import List from '@/components/Page/List';
4
+
5
+export default () => {
6
+    const columns = [
7
+        {
8
+            dataIndex: 'index',
9
+            valueType: 'indexBorder',
10
+            width: 48,
27 11
         },
28
-    },
29
-    {
30
-        disable: true,
31
-        title: '状态',
32
-        dataIndex: 'state',
33
-        filters: true,
34
-        onFilter: true,
35
-        ellipsis: true,
36
-        valueType: 'select',
37
-        valueEnum: {
38
-            all: { text: '超长'.repeat(50) },
39
-            open: {
40
-                text: '未解决',
41
-                status: 'Error',
42
-            },
43
-            closed: {
44
-                text: '已解决',
45
-                status: 'Success',
46
-                disabled: true,
47
-            },
48
-            processing: {
49
-                text: '解决中',
50
-                status: 'Processing',
12
+        {
13
+            title: '标题',
14
+            dataIndex: 'title',
15
+            copyable: true,
16
+            ellipsis: true,
17
+            tip: '标题过长会自动收缩',
18
+            formItemProps: {
19
+                rules: [
20
+                    {
21
+                        required: true,
22
+                        message: '此项为必填项',
23
+                    },
24
+                ],
51 25
             },
52 26
         },
53
-    },
54
-    {
55
-        disable: true,
56
-        title: '标签',
57
-        dataIndex: 'labels',
58
-        search: false,
59
-        renderFormItem: (_, { defaultRender }) => {
60
-            return defaultRender(_);
61
-        },
62
-        render: (_, record) => (<Space>
63
-        {record.labels.map(({ name, color }) => (<Tag color={color} key={name}>
64
-            {name}
65
-          </Tag>))}
66
-      </Space>),
67
-    },
68
-    {
69
-        title: '创建时间',
70
-        key: 'showTime',
71
-        dataIndex: 'created_at',
72
-        valueType: 'date',
73
-        sorter: true,
74
-        hideInSearch: true,
75
-    },
76
-    {
77
-        title: '创建时间',
78
-        dataIndex: 'created_at',
79
-        valueType: 'dateRange',
80
-        hideInTable: true,
81
-        search: {
82
-            transform: (value) => {
83
-                return {
84
-                    startTime: value[0],
85
-                    endTime: value[1],
86
-                };
27
+        {
28
+            disable: true,
29
+            title: '状态',
30
+            dataIndex: 'state',
31
+            filters: true,
32
+            onFilter: true,
33
+            ellipsis: true,
34
+            valueType: 'select',
35
+            valueEnum: {
36
+                all: { text: '超长'.repeat(50) },
37
+                open: {
38
+                    text: '未解决',
39
+                    status: 'Error',
40
+                },
41
+                closed: {
42
+                    text: '已解决',
43
+                    status: 'Success',
44
+                    disabled: true,
45
+                },
46
+                processing: {
47
+                    text: '解决中',
48
+                    status: 'Processing',
49
+                },
87 50
             },
88 51
         },
89
-    },
90
-    {
91
-        title: '操作',
92
-        valueType: 'option',
93
-        key: 'option',
94
-        render: (text, record, _, action) => [
95
-            <a key="editable" onClick={() => {
96
-                    var _a;
97
-                    (_a = action === null || action === void 0 ? void 0 : action.startEditable) === null || _a === void 0 ? void 0 : _a.call(action, record.id);
98
-                }}>
99
-        编辑
100
-      </a>,
101
-            <a href={record.url} target="_blank" rel="noopener noreferrer" key="view">
102
-        查看
103
-      </a>,
104
-            <TableDropdown key="actionGroup" onSelect={() => action === null || action === void 0 ? void 0 : action.reload()} menus={[
105
-                    { key: 'copy', name: '复制' },
106
-                    { key: 'delete', name: '删除' },
107
-                ]}/>,
108
-        ],
109
-    },
110
-];
111
-const menu = (<Menu items={[
112 52
         {
113
-            label: '1st item',
114
-            key: '1',
53
+            disable: true,
54
+            title: '标签',
55
+            dataIndex: 'labels',
56
+            search: false,
57
+            renderFormItem: (_, { defaultRender }) => {
58
+                return defaultRender(_);
59
+            },
60
+            render: (_, record) => (<Space>
61
+            {record.labels.map(({ name, color }) => (<Tag color={color} key={name}>
62
+                {name}
63
+                </Tag>))}
64
+            </Space>),
115 65
         },
116 66
         {
117
-            label: '2nd item',
118
-            key: '1',
67
+            title: '创建时间',
68
+            key: 'showTime',
69
+            dataIndex: 'created_at',
70
+            valueType: 'date',
71
+            sorter: true,
72
+            hideInSearch: true,
119 73
         },
120 74
         {
121
-            label: '3rd item',
122
-            key: '1',
75
+            title: '创建时间',
76
+            dataIndex: 'created_at',
77
+            valueType: 'dateRange',
78
+            hideInTable: true,
79
+            search: {
80
+                transform: (value) => {
81
+                    return {
82
+                        startTime: value[0],
83
+                        endTime: value[1],
84
+                    };
85
+                },
86
+            },
123 87
         },
124
-    ]}/>);
125
-export default () => {
126
-
127
-    const actionRef = useRef();
88
+    ];
89
+    
128 90
     return (
129
-        <ProTable columns={columns} actionRef={actionRef} cardBordered request={async (params = {}, sort, filter) => {
130
-            console.log(sort, filter);
131
-            // return request('https://proapi.azurewebsites.net/github/issues', {
132
-            //     params,
133
-            // });
134
-        }} editable={{
135
-            type: 'multiple',
136
-        }} columnsState={{
137
-            persistenceKey: 'pro-table-singe-demos',
138
-            persistenceType: 'localStorage',
139
-            onChange(value) {
140
-                console.log('value: ', value);
141
-            },
142
-        }} rowKey="id" search={{
143
-            labelWidth: 'auto',
144
-        }} options={{
145
-            setting: {
146
-                listsHeight: 400,
147
-            },
148
-        }} form={{
149
-            // 由于配置了 transform,提交的参与与定义的不同这里需要转化一下
150
-            syncToUrl: (values, type) => {
151
-                if (type === 'get') {
152
-                    return Object.assign(Object.assign({}, values), { created_at: [values.startTime, values.endTime] });
153
-                }
154
-                return values;
155
-            },
156
-        }} pagination={{
157
-            pageSize: 5,
158
-            onChange: (page) => console.log(page),
159
-        }} dateFormatter="string" headerTitle="高级表格" toolBarRender={() => [
160
-            <Button key="button" icon={<PlusOutlined />} type="primary">
161
-          新建
162
-        </Button>,
163
-            <Dropdown key="menu" overlay={menu}>
164
-          <Button>
165
-            <EllipsisOutlined />
166
-          </Button>
167
-        </Dropdown>,
168
-        ]}/>
91
+        <List
92
+            columns={columns}
93
+            request={() => Promise.resolve({ total: 0 })}
94
+        />
169 95
     );
170 96
 };

src/utils/hooks/usePrompt.jsx → src/routes/hooks/usePrompt.jsx Dosyayı Görüntüle

@@ -2,6 +2,7 @@ import React from "react";
2 2
 import { UNSAFE_NavigationContext } from "react-router-dom";
3 3
 
4 4
 // 估计在 react-router v6 的后续某个版本 usePrompt 会回归
5
+// v6.7 已经添加 UNSAFE_usePrompt
5 6
 export function usePrompt(message, when = true) {
6 7
   let blocker = React.useCallback(
7 8
     tx => {

+ 17
- 0
src/routes/hooks/useRoute.jsx Dosyayı Görüntüle

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

+ 1
- 5
src/routes/menus.jsx Dosyayı Görüntüle

@@ -1,4 +1,5 @@
1 1
 import { Link } from 'react-router-dom';
2
+import { getPath } from './utils';
2 3
 
3 4
 // 菜单是否显示
4 5
 // 没有 meta 或者 meta.title 为空, 或者 meta.hideInMenu = true 的 都不显示
@@ -11,11 +12,6 @@ const hasChildren = (list) => {
11 12
   return list.filter(it => !isShow(it)).length !== list.length;
12 13
 }
13 14
 
14
-const getPath = (parent, current = '') => {
15
-  if (current.indexOf('/') === 0 || current.indexOf('http') === 0) return current;
16
-  return `${parent}/${current}`.replace(/\/\//g, '/');
17
-}
18
-
19 15
 export const getMenuItems = (routes = [], fullPath = '/') => {
20 16
   return routes.map(route => {
21 17
     const path = getPath(fullPath, route.path);

+ 11
- 7
src/routes/routes.jsx Dosyayı Görüntüle

@@ -2,18 +2,16 @@ import {
2 2
   AppstoreOutlined,
3 3
   ContainerOutlined,
4 4
   DesktopOutlined,
5
-  MailOutlined,
6
-  MenuFoldOutlined,
7
-  MenuUnfoldOutlined,
8
-  PieChartOutlined,
5
+  FormOutlined,
9 6
 } from '@ant-design/icons';
7
+import { Outlet } from "react-router-dom";
10 8
 import AuthLayout from "@/layouts/AuthLayout";
11
-import PageContainer from "@/layouts/PageContainer";
12 9
 import Login from '@/pages/login';
13 10
 import Page404 from '@/pages/404';
14 11
 import Home from "@/pages/sample/home";
15 12
 import BasicForm from '@/pages/sample/form';
16 13
 import BasicTable from '@/pages/sample/table';
14
+import Curd from '@/pages/sample/curd';
17 15
 
18 16
 /**
19 17
  * meta 用来扩展自定义数据数据
@@ -35,7 +33,6 @@ export const authRoutes = [
35 33
     meta: {
36 34
       title: '表单',
37 35
       icon: <AppstoreOutlined />,
38
-      permission: 'form',
39 36
     },
40 37
   },
41 38
   {
@@ -44,7 +41,14 @@ export const authRoutes = [
44 41
     meta: {
45 42
       title: '表格',
46 43
       icon: <ContainerOutlined />,
47
-      permission: 'table',
44
+    },
45
+  },
46
+  {
47
+    path: "curd",
48
+    element: <Curd />,
49
+    meta: {
50
+      title: '增删改查',
51
+      icon: <FormOutlined />,
48 52
     },
49 53
   },
50 54
 ];

+ 22
- 0
src/routes/utils.js Dosyayı Görüntüle

@@ -0,0 +1,22 @@
1
+
2
+// 获取组件路径
3
+export function getPath (parent = "/", current = "") {
4
+  if (current.indexOf("/") === 0 || current.indexOf("http") === 0)
5
+    return current;
6
+  return `${parent}/${current}`.replace(/\/\//g, "/");
7
+}
8
+
9
+// 展平 routes, 为一个简单对象
10
+// 如果出现 父组件与子组件相同路由, 那么子组件会覆盖父组件
11
+export function flatten (routes, parentPath = "/") {
12
+  return routes.reduce((acc, route) => {
13
+    const path = route.index ? parentPath : getPath(parentPath, route.path);
14
+    const children = route.children ? flatten(route.children, path) : {};
15
+
16
+    return {
17
+      ...acc,
18
+      [path]: route,
19
+      ...children,
20
+    }
21
+  }, {});
22
+};

+ 6
- 0
src/services/sys_user.js Dosyayı Görüntüle

@@ -0,0 +1,6 @@
1
+import request from '@/utils/request';
2
+
3
+/*
4
+ * 查询当前人员
5
+ */
6
+export const currentUser = () => Promise.resolve({ userId: 1, name: '管理员' });

+ 2
- 3
src/store/models/user.js Dosyayı Görüntüle

@@ -2,8 +2,7 @@ import { useState, useRef } from "react";
2 2
 import { defaultRoutes, authRoutes, mergeAuthRoutes } from '@/routes/routes';
3 3
 import { getMenuItems } from "@/routes/menus";
4 4
 import { getAuthedRoutes } from "@/routes/permissions";
5
-
6
-const queryCurrentUser = () => Promise.resolve({ id: 1, name: '管理员', resourcesList: [{ code: 'form' }, { code: 'table' }] });
5
+import { currentUser } from '@/services/sys_user';
7 6
 
8 7
 export default function useUser() {
9 8
   const [user, setUser] = useState();
@@ -11,7 +10,7 @@ export default function useUser() {
11 10
   const routesRef = useRef();
12 11
 
13 12
   const getCurrentUser = () => new Promise((resolve, reject) => {
14
-    queryCurrentUser().then(res => {
13
+    currentUser().then(res => {
15 14
       const permissions = (res.resourcesList || []).map(x => x.code);
16 15
 
17 16
       // authRoutes 是所有待验证授权的路由

+ 0
- 9
src/utils/hooks/useRoute.jsx Dosyayı Görüntüle

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