Your Name vor 2 Jahren
Ursprung
Commit
89faa8763a

+ 0
- 8
src/index.less Datei anzeigen

@@ -34,11 +34,3 @@ html, body, #root {
34 34
   color: var(--theme-front);
35 35
 }
36 36
 
37
-
38
-main {
39
-  &.ant-layout-content {
40
-    & > div {
41
-      height: 100%;
42
-    }
43
-  }
44
-}

+ 21
- 7
src/layouts/AuthLayout/components/Container.jsx Datei anzeigen

@@ -1,18 +1,32 @@
1
-import React from 'react';
1
+import React, { useEffect, useRef, useMemo, useState } from 'react';
2 2
 import { Layout, Spin } from 'antd';
3 3
 import { Outlet, useLocation } from "react-router-dom";
4 4
 import PageTransition from './PageTransition';
5
+import Footer from './Footer';
5 6
 
6 7
 const { Content } = Layout;
7 8
 
8 9
 export default (props) => {
9
-  const location = useLocation()
10
+  const location = useLocation();
11
+  
12
+  const containerRef = useRef();
13
+  const [minHeight, setMinHeight] = useState(0);
14
+  const contentStyle = useMemo(() => ({ minHeight: `${minHeight}px` }), [minHeight]);
15
+  
16
+  useEffect(() => {
17
+    const containerHeight = containerRef.current.offsetHeight;
18
+    const footerHeight = document.querySelector('.ant-layout-footer').offsetHeight;
19
+    setMinHeight(containerHeight - footerHeight);
20
+  }, [minHeight]);
10 21
 
11 22
   return (
12
-    <Content>
13
-      <PageTransition location={location}>
14
-        <Outlet />
15
-      </PageTransition>
16
-    </Content>
23
+    <div className='layout-container' ref={containerRef}>
24
+      <Content style={contentStyle}>
25
+        <PageTransition location={location}>
26
+          <Outlet />
27
+        </PageTransition>
28
+      </Content>
29
+      <Footer />
30
+    </div>
17 31
   )
18 32
 }

+ 2
- 37
src/layouts/AuthLayout/components/Menus.jsx Datei anzeigen

@@ -1,42 +1,7 @@
1 1
 import React, { useState } from 'react';
2 2
 import { useNavigate } from "react-router-dom";
3 3
 import { Menu } from 'antd';
4
-import routes from '@/routes/routes';
5
-
6
-// 菜单是否显示
7
-// 没有 menu 或者 menu.title 为空, 或者 menu.hidden = true 的 都不显示
8
-const isShow = item => item.menu && item.title && !item.menu.hidden;
9
-
10
-const hasChildren = (list) => {
11
-  if (!list || list.length < 1) return false;
12
-
13
-  // 如果子元素全部都是不显示的, 说明子菜单不需要显示
14
-  return list.filter(it => !isShow(it)).length !== list.length;
15
-}
16
-
17
-const getPath = (parent, current = '') => {
18
-  if (current.indexOf('/') === 0 || current.indexOf('http') === 0) return current;
19
-  return `${parent}/${current}`.replace(/\/\//g, '/');
20
-}
21
-
22
-const getItems = (dts = [], fullPath = '/') => {
23
-  return dts.map(item => {
24
-    // 没有 menu 或者 menu.title 为空, 或者 menu.hidden = true 的 都不显示
25
-    if (!item.menu || !item.menu.title || item.menu.hidden) return false;
26
-    
27
-    const path = getPath(fullPath, item.path)
28
-    const children = hasChildren(item.children) ? getItems(item.children, path) : false;
29
-
30
-    return Object.assign(
31
-      {
32
-        key: path,
33
-        label: item.menu.title,
34
-        title: item.menu.title,
35
-      },
36
-      children && { children },
37
-    )
38
-  }).filter(Boolean);
39
-}
4
+import { getItems } from '@/routes/menus';
40 5
 
41 6
 const linkTo = url => {
42 7
   const a = document.createElement('a');
@@ -49,7 +14,7 @@ export default (props) => {
49 14
   const { theme } = props;
50 15
 
51 16
   const navigate = useNavigate();
52
-  const items = getItems(routes[0].children);
17
+  const items = getItems();
53 18
 
54 19
   const onClick = (item) => {
55 20
     // http 开头说明是外部链接

+ 3
- 8
src/layouts/AuthLayout/index.jsx Datei anzeigen

@@ -1,22 +1,20 @@
1
-import React, { useEffect } from 'react';
1
+import React, { useEffect, useRef, useMemo, useState } from 'react';
2 2
 import { Layout, Spin } from 'antd';
3 3
 import { useModel } from '@/store'
4 4
 import RequireLogin from './components/RequireLogin';
5 5
 import SiderBar from './components/SiderBar';
6 6
 import Header from './components/Header';
7 7
 import Container from './components/Container';
8
-import Footer from './components/Footer';
9 8
 import useReady from './useReady';
10 9
 
11 10
 import './style.less';
12 11
 
13
-const { Content } = Layout;
14
-
15 12
 export default (props) => {
16 13
   const { user, getCurrent } = useModel('user');
17 14
   // const isReady = useReady(user)
18 15
   // const isReady = true
19 16
 
17
+
20 18
   useEffect(() => {
21 19
     if (!user) {
22 20
       getCurrent()
@@ -30,10 +28,7 @@ export default (props) => {
30 28
           <Header />
31 29
           <Layout>
32 30
             <SiderBar />
33
-            <Layout>
34
-              <Container />
35
-              <Footer />
36
-            </Layout>
31
+            <Container />
37 32
           </Layout>
38 33
         </div>
39 34
       </RequireLogin>

+ 12
- 0
src/layouts/AuthLayout/style.less Datei anzeigen

@@ -43,4 +43,16 @@
43 43
   .sys-exit {
44 44
     margin-right: 1em;
45 45
   }
46
+}
47
+
48
+.layout-container {
49
+  flex: 1;
50
+
51
+  overflow-y: auto;
52
+  scrollbar-width: none;
53
+  -ms-overflow-style: none;
54
+
55
+  &::--webkit-scrollbar {
56
+    display: none;
57
+  }
46 58
 }

+ 24
- 0
src/layouts/page/index.jsx Datei anzeigen

@@ -0,0 +1,24 @@
1
+import React from 'react';
2
+import { PageHeader } from 'antd';
3
+import { useLocation } from "react-router-dom";
4
+import { getRouteArr } from '@/routes/menus';
5
+import './style.less'
6
+
7
+export default (props) => {
8
+  const { children, ...headerProps } = props;
9
+
10
+  const location = useLocation();
11
+  const routeArr = getRouteArr();
12
+
13
+  const currentRoute = routeArr.filter(x => x.path === location.pathname)[0] || {};
14
+  const { title } = currentRoute.meta || {};
15
+
16
+  return (
17
+    <>
18
+      <PageHeader ghost={false} title={title} {...headerProps}></PageHeader>
19
+      <div className="page-container">
20
+        {children}
21
+      </div>
22
+    </>
23
+  )
24
+}

+ 9
- 0
src/layouts/page/style.less Datei anzeigen

@@ -0,0 +1,9 @@
1
+@pageSpace: 24px;
2
+
3
+.page-container {
4
+  background-color: #fff;
5
+  margin: @pageSpace;
6
+  margin-bottom: 0;
7
+  box-sizing: border-box;
8
+  padding: @pageSpace;
9
+}

+ 351
- 0
src/pages/sample/form/index.jsx Datei anzeigen

@@ -0,0 +1,351 @@
1
+import {
2
+  AutoComplete,
3
+  Button,
4
+  Cascader,
5
+  Checkbox,
6
+  Col,
7
+  Form,
8
+  Input,
9
+  InputNumber,
10
+  Row,
11
+  Select,
12
+} from 'antd';
13
+import React, { useState } from 'react';
14
+import Page from '@/layouts/page';
15
+
16
+const { Option } = Select;
17
+const residences = [
18
+  {
19
+    value: 'zhejiang',
20
+    label: 'Zhejiang',
21
+    children: [
22
+      {
23
+        value: 'hangzhou',
24
+        label: 'Hangzhou',
25
+        children: [
26
+          {
27
+            value: 'xihu',
28
+            label: 'West Lake',
29
+          },
30
+        ],
31
+      },
32
+    ],
33
+  },
34
+  {
35
+    value: 'jiangsu',
36
+    label: 'Jiangsu',
37
+    children: [
38
+      {
39
+        value: 'nanjing',
40
+        label: 'Nanjing',
41
+        children: [
42
+          {
43
+            value: 'zhonghuamen',
44
+            label: 'Zhong Hua Men',
45
+          },
46
+        ],
47
+      },
48
+    ],
49
+  },
50
+];
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
+
90
+  const prefixSelector = (
91
+    <Form.Item name="prefix" noStyle>
92
+      <Select
93
+        style={{
94
+          width: 70,
95
+        }}
96
+      >
97
+        <Option value="86">+86</Option>
98
+        <Option value="87">+87</Option>
99
+      </Select>
100
+    </Form.Item>
101
+  );
102
+  const suffixSelector = (
103
+    <Form.Item name="suffix" noStyle>
104
+      <Select
105
+        style={{
106
+          width: 70,
107
+        }}
108
+      >
109
+        <Option value="USD">$</Option>
110
+        <Option value="CNY">¥</Option>
111
+      </Select>
112
+    </Form.Item>
113
+  );
114
+  const [autoCompleteResult, setAutoCompleteResult] = useState([]);
115
+
116
+  const onWebsiteChange = (value) => {
117
+    if (!value) {
118
+      setAutoCompleteResult([]);
119
+    } else {
120
+      setAutoCompleteResult(['.com', '.org', '.net'].map((domain) => `${value}${domain}`));
121
+    }
122
+  };
123
+
124
+  const websiteOptions = autoCompleteResult.map((website) => ({
125
+    label: website,
126
+    value: website,
127
+  }));
128
+  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
+    >
140
+      <Form.Item
141
+        name="email"
142
+        label="E-mail"
143
+        rules={[
144
+          {
145
+            type: 'email',
146
+            message: 'The input is not valid E-mail!',
147
+          },
148
+          {
149
+            required: true,
150
+            message: 'Please input your E-mail!',
151
+          },
152
+        ]}
153
+      >
154
+        <Input />
155
+      </Form.Item>
156
+
157
+      <Form.Item
158
+        name="password"
159
+        label="Password"
160
+        rules={[
161
+          {
162
+            required: true,
163
+            message: 'Please input your password!',
164
+          },
165
+        ]}
166
+        hasFeedback
167
+      >
168
+        <Input.Password />
169
+      </Form.Item>
170
+
171
+      <Form.Item
172
+        name="confirm"
173
+        label="Confirm Password"
174
+        dependencies={['password']}
175
+        hasFeedback
176
+        rules={[
177
+          {
178
+            required: true,
179
+            message: 'Please confirm your password!',
180
+          },
181
+          ({ getFieldValue }) => ({
182
+            validator(_, value) {
183
+              if (!value || getFieldValue('password') === value) {
184
+                return Promise.resolve();
185
+              }
186
+
187
+              return Promise.reject(new Error('The two passwords that you entered do not match!'));
188
+            },
189
+          }),
190
+        ]}
191
+      >
192
+        <Input.Password />
193
+      </Form.Item>
194
+
195
+      <Form.Item
196
+        name="nickname"
197
+        label="Nickname"
198
+        tooltip="What do you want others to call you?"
199
+        rules={[
200
+          {
201
+            required: true,
202
+            message: 'Please input your nickname!',
203
+            whitespace: true,
204
+          },
205
+        ]}
206
+      >
207
+        <Input />
208
+      </Form.Item>
209
+
210
+      <Form.Item
211
+        name="residence"
212
+        label="Habitual Residence"
213
+        rules={[
214
+          {
215
+            type: 'array',
216
+            required: true,
217
+            message: 'Please select your habitual residence!',
218
+          },
219
+        ]}
220
+      >
221
+        <Cascader options={residences} />
222
+      </Form.Item>
223
+
224
+      <Form.Item
225
+        name="phone"
226
+        label="Phone Number"
227
+        rules={[
228
+          {
229
+            required: true,
230
+            message: 'Please input your phone number!',
231
+          },
232
+        ]}
233
+      >
234
+        <Input
235
+          addonBefore={prefixSelector}
236
+          style={{
237
+            width: '100%',
238
+          }}
239
+        />
240
+      </Form.Item>
241
+
242
+      <Form.Item
243
+        name="donation"
244
+        label="Donation"
245
+        rules={[
246
+          {
247
+            required: true,
248
+            message: 'Please input donation amount!',
249
+          },
250
+        ]}
251
+      >
252
+        <InputNumber
253
+          addonAfter={suffixSelector}
254
+          style={{
255
+            width: '100%',
256
+          }}
257
+        />
258
+      </Form.Item>
259
+
260
+      <Form.Item
261
+        name="website"
262
+        label="Website"
263
+        rules={[
264
+          {
265
+            required: true,
266
+            message: 'Please input website!',
267
+          },
268
+        ]}
269
+      >
270
+        <AutoComplete options={websiteOptions} onChange={onWebsiteChange} placeholder="website">
271
+          <Input />
272
+        </AutoComplete>
273
+      </Form.Item>
274
+
275
+      <Form.Item
276
+        name="intro"
277
+        label="Intro"
278
+        rules={[
279
+          {
280
+            required: true,
281
+            message: 'Please input Intro',
282
+          },
283
+        ]}
284
+      >
285
+        <Input.TextArea showCount maxLength={100} />
286
+      </Form.Item>
287
+
288
+      <Form.Item
289
+        name="gender"
290
+        label="Gender"
291
+        rules={[
292
+          {
293
+            required: true,
294
+            message: 'Please select gender!',
295
+          },
296
+        ]}
297
+      >
298
+        <Select placeholder="select your gender">
299
+          <Option value="male">Male</Option>
300
+          <Option value="female">Female</Option>
301
+          <Option value="other">Other</Option>
302
+        </Select>
303
+      </Form.Item>
304
+
305
+      <Form.Item label="Captcha" extra="We must make sure that your are a human.">
306
+        <Row gutter={8}>
307
+          <Col span={12}>
308
+            <Form.Item
309
+              name="captcha"
310
+              noStyle
311
+              rules={[
312
+                {
313
+                  required: true,
314
+                  message: 'Please input the captcha you got!',
315
+                },
316
+              ]}
317
+            >
318
+              <Input />
319
+            </Form.Item>
320
+          </Col>
321
+          <Col span={12}>
322
+            <Button>Get captcha</Button>
323
+          </Col>
324
+        </Row>
325
+      </Form.Item>
326
+
327
+      <Form.Item
328
+        name="agreement"
329
+        valuePropName="checked"
330
+        rules={[
331
+          {
332
+            validator: (_, value) =>
333
+              value ? Promise.resolve() : Promise.reject(new Error('Should accept agreement')),
334
+          },
335
+        ]}
336
+        {...tailFormItemLayout}
337
+      >
338
+        <Checkbox>
339
+          I have read the <a href="">agreement</a>
340
+        </Checkbox>
341
+      </Form.Item>
342
+      <Form.Item {...tailFormItemLayout}>
343
+        <Button type="primary" htmlType="submit">
344
+          Register
345
+        </Button>
346
+      </Form.Item>
347
+    </Form>
348
+  );
349
+};
350
+
351
+export default () => <Page><BasicForm /></Page>;

+ 57
- 0
src/routes/menus.jsx Datei anzeigen

@@ -0,0 +1,57 @@
1
+import routes from './routes';
2
+
3
+let routeArr = [];
4
+
5
+// 菜单是否显示
6
+// 没有 meta 或者 meta.title 为空, 或者 meta.hiddenInMenu = true 的 都不显示
7
+const isShow = item => item.meta && item.meta.title && !item.meta.hiddenInMenu;
8
+
9
+const hasChildren = (list) => {
10
+  if (!list || list.length < 1) return false;
11
+
12
+  // 如果子元素全部都是不显示的, 说明子菜单不需要显示
13
+  return list.filter(it => !isShow(it)).length !== list.length;
14
+}
15
+
16
+const getPath = (parent, current = '') => {
17
+  if (current.indexOf('/') === 0 || current.indexOf('http') === 0) return current;
18
+  return `${parent}/${current}`.replace(/\/\//g, '/');
19
+}
20
+
21
+const getMenuItems = (dts = [], fullPath = '/') => {
22
+  return dts.map(item => {
23
+    const path = getPath(fullPath, item.path);
24
+
25
+    routeArr.push({
26
+      ...item,
27
+      path,
28
+    });
29
+
30
+    //
31
+    if (!isShow(item)) return false;
32
+    
33
+    const children = hasChildren(item.children) ? getMenuItems(item.children, path) : false;
34
+
35
+    return Object.assign(
36
+      {
37
+        key: path,
38
+        label: item.meta.title,
39
+        title: item.meta.title,
40
+      },
41
+      children && { children },
42
+    )
43
+  }).filter(Boolean);
44
+}
45
+
46
+const getItems = () => getMenuItems(routes[0]?.children);
47
+const getRouteArr = () => {
48
+  if (routeArr.length < 1) {
49
+    getItems();
50
+  }
51
+  return routeArr;
52
+}
53
+// 
54
+export {
55
+  getItems,
56
+  getRouteArr,
57
+};

+ 18
- 9
src/routes/routes.jsx Datei anzeigen

@@ -1,7 +1,16 @@
1
-import AuthLayout from "@/layouts/AuthLayout"
2
-import Home from "@/pages/Home"
3
-import Login from '@/pages/login'
4
-import Page404 from '@/pages/404'
1
+import AuthLayout from "@/layouts/AuthLayout";
2
+import Home from "@/pages/Home";
3
+import Login from '@/pages/login';
4
+import Page404 from '@/pages/404';
5
+import BasicForm from '@/pages/sample/form';
6
+
7
+/**
8
+ * meta 用来扩展自定义数据数据
9
+ * {
10
+ *    title: 用于页面或者菜单的标题, 没有此字段, 菜单不会显示
11
+ *    hiddenInMenu: 布尔值, 如果为 false, 菜单不会显示
12
+ * }
13
+ */
5 14
 
6 15
 export default [
7 16
   {
@@ -13,16 +22,16 @@ export default [
13 22
         element: <Home />,
14 23
       },
15 24
       {
16
-        path: "index",
17
-        element: <Home />,
18
-        menu: {
19
-          title: 'Index',
25
+        path: "form",
26
+        element: <BasicForm />,
27
+        meta: {
28
+          title: '表单',
20 29
         },
21 30
       },
22 31
       {
23 32
         path: "home",
24 33
         element: <Home />,
25
-        menu: {
34
+        meta: {
26 35
           title: 'Home',
27 36
         },
28 37
       },