Your Name 4 years ago
parent
commit
04dbf7cb96
44 changed files with 667 additions and 378 deletions
  1. 2
    2
      package.json
  2. 1
    1
      src/components/Authorized/CheckPermissions.jsx
  3. 1
    1
      src/components/Authorized/renderAuthorize.js
  4. 5
    5
      src/components/Container/index.jsx
  5. 9
    1
      src/components/GlobalHeader/AvatarDropdown.jsx
  6. 3
    0
      src/components/GlobalHeader/NoticeIconView.jsx
  7. 2
    1
      src/components/NoticeIcon/NoticeList.jsx
  8. 1
    0
      src/components/NoticeIcon/index.jsx
  9. 154
    0
      src/components/TestQuestions/index.jsx
  10. 32
    29
      src/components/UploadImage/index.jsx
  11. 44
    0
      src/components/UploadVideo/index.jsx
  12. 10
    0
      src/components/UploadVideo/style.less
  13. 13
    13
      src/components/WangEditor/Preview.jsx
  14. 12
    12
      src/components/WangEditor/PreviewMenu.js
  15. 77
    82
      src/components/WangEditor/WangEditor.jsx
  16. 3
    0
      src/components/WangEditor/index.js
  17. 1
    1
      src/global.jsx
  18. 38
    38
      src/layouts/BasicLayout.jsx
  19. 1
    1
      src/layouts/UserLayout.jsx
  20. 1
    0
      src/locales/en-US.js
  21. 1
    0
      src/locales/id-ID.js
  22. 1
    0
      src/locales/ja-JP.js
  23. 1
    0
      src/locales/pt-BR.js
  24. 1
    0
      src/locales/zh-CN.js
  25. 1
    0
      src/locales/zh-TW.js
  26. 1
    0
      src/models/global.js
  27. 10
    10
      src/models/login.js
  28. 2
    2
      src/models/user.js
  29. 2
    1
      src/pages/Admin.jsx
  30. 10
    0
      src/pages/Post/Edit/components/Answer.jsx
  31. 68
    56
      src/pages/Post/Edit/components/Form.jsx
  32. 34
    30
      src/pages/Post/Edit/index.jsx
  33. 3
    1
      src/pages/Post/List/index.jsx
  34. 3
    3
      src/pages/TableList/components/UpdateForm.jsx
  35. 3
    6
      src/pages/TableList/index.jsx
  36. 1
    0
      src/pages/TableList/service.js
  37. 7
    9
      src/pages/User/login/index.jsx
  38. 2
    4
      src/pages/Welcome.jsx
  39. 4
    4
      src/pages/model.js
  40. 3
    3
      src/utils/authority.js
  41. 43
    39
      src/utils/request.js
  42. 47
    15
      src/utils/uploadFile.js
  43. 8
    8
      src/utils/utils.js
  44. 1
    0
      src/utils/utils.test.js

+ 2
- 2
package.json View File

@@ -18,7 +18,6 @@
18 18
     "lint:js": "eslint --cache --ext .js,.jsx,.ts,.tsx --format=pretty ./src",
19 19
     "lint:prettier": "prettier --check \"src/**/*\" --end-of-line auto",
20 20
     "lint:style": "stylelint --fix \"src/**/*.less\" --syntax less",
21
-    "precommit": "lint-staged",
22 21
     "prettier": "prettier -c --write \"src/**/*\"",
23 22
     "start": "cross-env UMI_ENV=dev umi dev",
24 23
     "start:dev": "cross-env REACT_APP_ENV=dev MOCK=none UMI_ENV=dev umi dev",
@@ -58,10 +57,11 @@
58 57
     "md5": "^2.3.0",
59 58
     "moment": "^2.25.3",
60 59
     "omit.js": "^2.0.2",
61
-    "react": "^16.14.0",
60
+    "react": "17.0.0",
62 61
     "react-dev-inspector": "^1.1.1",
63 62
     "react-dom": "^17.0.0",
64 63
     "react-helmet-async": "^1.0.4",
64
+    "react-player": "^2.9.0",
65 65
     "umi": "^3.4.1",
66 66
     "umi-request": "^1.0.8",
67 67
     "wangeditor": "^4.6.15"

+ 1
- 1
src/components/Authorized/CheckPermissions.jsx View File

@@ -66,7 +66,7 @@ const checkPermissions = (authority, currentAuthority, target, Exception) => {
66 66
 export { checkPermissions };
67 67
 
68 68
 function check(authority, target, Exception) {
69
-  const permit = typeof CURRENT === 'function' ? CURRENT() : CURRENT
69
+  const permit = typeof CURRENT === 'function' ? CURRENT() : CURRENT;
70 70
   return checkPermissions(authority, permit, target, Exception);
71 71
 }
72 72
 

+ 1
- 1
src/components/Authorized/renderAuthorize.js View File

@@ -9,7 +9,7 @@ let CURRENT;
9 9
  * @param {string|()=>String} currentAuthority
10 10
  */
11 11
 const renderAuthorize = (Authorized) => (currentAuthority) => {
12
-  CURRENT = currentAuthority
12
+  CURRENT = currentAuthority;
13 13
   return Authorized;
14 14
 };
15 15
 

+ 5
- 5
src/components/Container/index.jsx View File

@@ -1,12 +1,12 @@
1
-import React from 'react'
2
-import { Spin } from 'antd'
1
+import React from 'react';
2
+import { Spin } from 'antd';
3 3
 
4 4
 export default (props) => {
5
-  const loading = typeof props.loading === 'boolean' ? props.loading : false
5
+  const loading = typeof props.loading === 'boolean' ? props.loading : false;
6 6
 
7 7
   return (
8 8
     <Spin spinning={loading} tip={props.loadingTip}>
9 9
       <div style={{ background: '#fff', padding: '2em' }}>{props.children}</div>
10 10
     </Spin>
11
-  )
12
-}
11
+  );
12
+};

+ 9
- 1
src/components/GlobalHeader/AvatarDropdown.jsx View File

@@ -42,7 +42,15 @@ class AvatarDropdown extends React.Component {
42 42
     return currentUser && currentUser.userName ? (
43 43
       <HeaderDropdown overlay={menuHeaderDropdown}>
44 44
         <span className={`${styles.action} ${styles.account}`}>
45
-          <Avatar size="small" className={styles.avatar} src={currentUser.avatar || 'https://gw.alipayobjects.com/zos/antfincdn/XAosXuNZyF/BiazfanxmamNRoxxVxka.png'} alt="avatar" />
45
+          <Avatar
46
+            size="small"
47
+            className={styles.avatar}
48
+            src={
49
+              currentUser.avatar ||
50
+              'https://gw.alipayobjects.com/zos/antfincdn/XAosXuNZyF/BiazfanxmamNRoxxVxka.png'
51
+            }
52
+            alt="avatar"
53
+          />
46 54
           <span className={`${styles.name} anticon`}>{currentUser.userName}</span>
47 55
         </span>
48 56
       </HeaderDropdown>

+ 3
- 0
src/components/GlobalHeader/NoticeIconView.jsx View File

@@ -28,6 +28,7 @@ class GlobalHeaderRight extends Component {
28 28
       });
29 29
     }
30 30
   };
31
+
31 32
   handleNoticeClear = (title, key) => {
32 33
     const { dispatch } = this.props;
33 34
     message.success(`${'清空了'} ${title}`);
@@ -39,6 +40,7 @@ class GlobalHeaderRight extends Component {
39 40
       });
40 41
     }
41 42
   };
43
+
42 44
   getNoticeData = () => {
43 45
     const { notices = [] } = this.props;
44 46
 
@@ -80,6 +82,7 @@ class GlobalHeaderRight extends Component {
80 82
     });
81 83
     return groupBy(newNotices, 'type');
82 84
   };
85
+
83 86
   getUnreadData = (noticeData) => {
84 87
     const unreadMsg = {};
85 88
     Object.keys(noticeData).forEach((key) => {

+ 2
- 1
src/components/NoticeIcon/NoticeList.jsx View File

@@ -35,8 +35,9 @@ const NoticeList = ({
35 35
         renderItem={(item, i) => {
36 36
           const itemCls = classNames(styles.item, {
37 37
             [styles.read]: item.read,
38
-          }); // eslint-disable-next-line no-nested-ternary
38
+          });
39 39
 
40
+          // eslint-disable-next-line no-nested-ternary
40 41
           const leftIcon = item.avatar ? (
41 42
             typeof item.avatar === 'string' ? (
42 43
               <Avatar className={styles.avatar} src={item.avatar} />

+ 1
- 0
src/components/NoticeIcon/index.jsx View File

@@ -6,6 +6,7 @@ import classNames from 'classnames';
6 6
 import NoticeList from './NoticeList';
7 7
 import HeaderDropdown from '../HeaderDropdown';
8 8
 import styles from './index.less';
9
+
9 10
 const { TabPane } = Tabs;
10 11
 
11 12
 const NoticeIcon = (props) => {

+ 154
- 0
src/components/TestQuestions/index.jsx View File

@@ -0,0 +1,154 @@
1
+import React, { useState } from 'react';
2
+import { Button, Row, Col, Checkbox, Radio, Input, Space } from 'antd';
3
+import WangEditor from '@/components/WangEditor';
4
+
5
+export default (props) => {
6
+  const [formData, setFormData] = useState({
7
+    question: undefined,
8
+    answerType: 'radio',
9
+    correctAnswers: undefined,
10
+    optionA: undefined,
11
+    optionB: undefined,
12
+    optionC: undefined,
13
+    optionD: undefined,
14
+  });
15
+
16
+  // const [answerTypeValue, setAnswerTypeValue] = useState([])
17
+
18
+  const handleFormChange = (field) => (e) => {
19
+    const value = e && e.target ? e.target.value : e;
20
+    console.log('------111--->', field, e, value);
21
+    setFormData({
22
+      ...formData,
23
+      [field]: value,
24
+    });
25
+  };
26
+
27
+  const handleCorrectAnswers = () => {};
28
+
29
+  const handleSubmit = () => {};
30
+
31
+  const handleCancel = () => {
32
+    if (props.onCancel) {
33
+      props.onCancel();
34
+    }
35
+  };
36
+
37
+  const answerTypeDict = [
38
+    { value: 'switch', label: '判断' },
39
+    { value: 'radio', label: '单选' },
40
+    { value: 'checkbox', label: '多选' },
41
+  ];
42
+
43
+  return (
44
+    <div>
45
+      <Space size="large" direction="vertical" style={{ width: '100%' }}>
46
+        <section>
47
+          <h3>试题:</h3>
48
+          <WangEditor value={formData.question} onChange={handleFormChange('question')} />
49
+        </section>
50
+
51
+        <section>
52
+          <h3>选项:</h3>
53
+          <Space size="large" direction="vertical" style={{ width: '100%' }}>
54
+            <Row gutter="24">
55
+              <Col span={12}>
56
+                <Input addonBefore="A" onChange={handleFormChange('optionA')} />
57
+              </Col>
58
+              <Col span={12}>
59
+                <Input addonBefore="B" onChange={handleFormChange('optionB')} />
60
+              </Col>
61
+            </Row>
62
+            <Row gutter="24">
63
+              <Col span={12}>
64
+                <Input addonBefore="C" onChange={handleFormChange('optionC')} />
65
+              </Col>
66
+              <Col span={12}>
67
+                <Input addonBefore="D" onChange={handleFormChange('optionD')} />
68
+              </Col>
69
+            </Row>
70
+          </Space>
71
+        </section>
72
+
73
+        <section>
74
+          <Row gutter="24">
75
+            <Col span={12}>
76
+              <h3>题型:</h3>
77
+              <Radio.Group
78
+                options={answerTypeDict}
79
+                value={formData.answerType}
80
+                onChange={handleFormChange('answerType')}
81
+              />
82
+            </Col>
83
+            <Col span={12}>
84
+              <h3>正确答案:</h3>
85
+              {formData.answerType === 'switch' && (
86
+                <Radio.Group onChange={handleCorrectAnswers} style={{ width: '100%' }}>
87
+                  <Row gutter="12">
88
+                    <Col span={6}>
89
+                      <Radio value="A">是</Radio>
90
+                    </Col>
91
+                    <Col span={6}>
92
+                      <Radio value="B">否</Radio>
93
+                    </Col>
94
+                  </Row>
95
+                </Radio.Group>
96
+              )}
97
+              {formData.answerType === 'radio' && (
98
+                <Radio.Group onChange={handleCorrectAnswers} style={{ width: '100%' }}>
99
+                  <Row gutter="12">
100
+                    <Col span={6}>
101
+                      <Radio value="A">A</Radio>
102
+                    </Col>
103
+                    <Col span={6}>
104
+                      <Radio value="B">B</Radio>
105
+                    </Col>
106
+                    <Col span={6}>
107
+                      <Radio value="C">C</Radio>
108
+                    </Col>
109
+                    <Col span={6}>
110
+                      <Radio value="D">D</Radio>
111
+                    </Col>
112
+                  </Row>
113
+                </Radio.Group>
114
+              )}
115
+              {formData.answerType === 'checkbox' && (
116
+                <Checkbox.Group onChange={handleCorrectAnswers} style={{ width: '100%' }}>
117
+                  <Row gutter="12">
118
+                    <Col span={6}>
119
+                      <Checkbox value="A">A</Checkbox>
120
+                    </Col>
121
+                    <Col span={6}>
122
+                      <Checkbox value="B">B</Checkbox>
123
+                    </Col>
124
+                    <Col span={6}>
125
+                      <Checkbox value="C">C</Checkbox>
126
+                    </Col>
127
+                    <Col span={6}>
128
+                      <Checkbox value="D">D</Checkbox>
129
+                    </Col>
130
+                  </Row>
131
+                </Checkbox.Group>
132
+              )}
133
+            </Col>
134
+          </Row>
135
+        </section>
136
+
137
+        <section>
138
+          <Row gutter="24">
139
+            <Col span={4}>
140
+              <Button type="primary" onClick={handleSubmit}>
141
+                确定
142
+              </Button>
143
+            </Col>
144
+            <Col span={4}>
145
+              <Button type="default" onClick={handleCancel}>
146
+                取消
147
+              </Button>
148
+            </Col>
149
+          </Row>
150
+        </section>
151
+      </Space>
152
+    </div>
153
+  );
154
+};

+ 32
- 29
src/components/UploadImage/index.jsx View File

@@ -1,25 +1,28 @@
1
-import React, { useState, useCallback } from 'react'
2
-import { Upload, notification } from 'antd'
3
-import { LoadingOutlined, PlusOutlined } from '@ant-design/icons'
4
-import uploadFile from '@/utils/uploadFile'
1
+import React, { useState, useCallback } from 'react';
2
+import { Upload, notification } from 'antd';
3
+import { LoadingOutlined, PlusOutlined } from '@ant-design/icons';
4
+import uploadFile from '@/utils/uploadFile';
5 5
 
6 6
 export default (props) => {
7
-  const [loading, setLoading] = useState(false)
7
+  const [loading, setLoading] = useState(false);
8 8
 
9
-  const handleChange = useCallback(({ file }) => {
10
-    switch (file.status) {
11
-      case 'done':
12
-        setLoading(false)
13
-        props.onChange(file.response)
14
-        break
15
-      case 'error':
16
-        setLoading(false)
17
-        notification.error({ message: file.error })
18
-      break
19
-      default:
20
-        setLoading(true)
21
-    }
22
-  }, [props])
9
+  const handleChange = useCallback(
10
+    ({ file }) => {
11
+      switch (file.status) {
12
+        case 'done':
13
+          setLoading(false);
14
+          props.onChange(file.response);
15
+          break;
16
+        case 'error':
17
+          setLoading(false);
18
+          notification.error({ message: file.error });
19
+          break;
20
+        default:
21
+          setLoading(true);
22
+      }
23
+    },
24
+    [props],
25
+  );
23 26
 
24 27
   return (
25 28
     <Upload
@@ -30,14 +33,14 @@ export default (props) => {
30 33
       customRequest={uploadFile}
31 34
       onChange={handleChange}
32 35
     >
33
-      {
34
-        props.value ?
35
-          <img src={props.value} width="100%" alt="" /> :
36
-          <div>
37
-            {loading ? <LoadingOutlined /> : <PlusOutlined />}
38
-            <div style={{ marginTop: 8 }}>上传</div>
39
-          </div>
40
-      }
36
+      {props.value ? (
37
+        <img src={props.value} width="100%" alt="" />
38
+      ) : (
39
+        <div>
40
+          {loading ? <LoadingOutlined /> : <PlusOutlined />}
41
+          <div style={{ marginTop: 8 }}>上传</div>
42
+        </div>
43
+      )}
41 44
     </Upload>
42
-  )
43
-}
45
+  );
46
+};

+ 44
- 0
src/components/UploadVideo/index.jsx View File

@@ -0,0 +1,44 @@
1
+import React, { useState, useCallback } from 'react';
2
+import { Button, Spin, Upload, notification } from 'antd';
3
+import { UploadOutlined } from '@ant-design/icons';
4
+import ReactPlayer from 'react-player/lazy';
5
+import uploadFile from '@/utils/uploadFile';
6
+
7
+import Styles from './style.less';
8
+
9
+export default (props) => {
10
+  const [loading, setLoading] = useState(false);
11
+
12
+  const handleChange = useCallback(
13
+    ({ file }) => {
14
+      switch (file.status) {
15
+        case 'done':
16
+          setLoading(false);
17
+          props.onChange(file.response);
18
+          break;
19
+        case 'error':
20
+          setLoading(false);
21
+          notification.error({ message: file.error });
22
+          break;
23
+        default:
24
+          setLoading(true);
25
+      }
26
+    },
27
+    [props],
28
+  );
29
+
30
+  return (
31
+    <div className={Styles['player-wrapper']}>
32
+      <div className={Styles['upload-btn']}>
33
+        <Upload maxCount={1} customRequest={uploadFile} onChange={handleChange}>
34
+          <Button icon={<UploadOutlined />}>上传视频</Button>
35
+        </Upload>
36
+      </div>
37
+      <Spin spinning={loading}>
38
+        <div className={Styles['react-player']}>
39
+          <ReactPlayer controls muted url={props.value} width="100%" height="100%" />
40
+        </div>
41
+      </Spin>
42
+    </div>
43
+  );
44
+};

+ 10
- 0
src/components/UploadVideo/style.less View File

@@ -0,0 +1,10 @@
1
+.player-wrapper {
2
+  width: 600px;
3
+}
4
+
5
+.react-player {
6
+  width: 600px;
7
+  height: 375px;
8
+  margin: 1em 0;
9
+  background: rgba(0, 0, 0, 0.2);
10
+}

+ 13
- 13
src/components/WangEditor/Preview.jsx View File

@@ -1,23 +1,23 @@
1
-import React, { useEffect, useRef } from 'react'
2
-import { Modal } from 'antd'
1
+import React, { useEffect, useRef } from 'react';
2
+import { Modal } from 'antd';
3 3
 
4
-export default props => {
5
-  const ref = useRef()
4
+export default (props) => {
5
+  const ref = useRef();
6 6
 
7 7
   useEffect(() => {
8
-    let t = null
8
+    let t = null;
9 9
     if (props.visible) {
10 10
       // 防止 postMessage 的时候 iframe 内容还没有加载完成
11 11
       t = setInterval(() => {
12 12
         // window.preViewFrame.window.postMessage(props.html, '*')
13 13
         if (ref.current) {
14
-          ref.current.contentWindow.postMessage(props.html, '*')
14
+          ref.current.contentWindow.postMessage(props.html, '*');
15 15
         }
16
-      }, 800)
16
+      }, 800);
17 17
     }
18 18
 
19
-    return () => t && clearInterval(t)
20
-  }, [props.visible, props.html])
19
+    return () => t && clearInterval(t);
20
+  }, [props.visible, props.html]);
21 21
 
22 22
   return (
23 23
     <Modal
@@ -29,14 +29,14 @@ export default props => {
29 29
       closable={false}
30 30
       title={null}
31 31
       onCancel={props.onCancel}
32
-      >
32
+    >
33 33
       <iframe
34 34
         ref={ref}
35
-        style={{width: '100%', height: '100%'}}
35
+        style={{ width: '100%', height: '100%' }}
36 36
         src={`${window.location.origin}/preview-html/index.html`}
37 37
         frameBorder={0}
38 38
         name="preViewFrame"
39 39
       ></iframe>
40 40
     </Modal>
41
-  )
42
-}
41
+  );
42
+};

+ 12
- 12
src/components/WangEditor/PreviewMenu.js View File

@@ -1,27 +1,27 @@
1
-import E from 'wangeditor'
1
+import E from 'wangeditor';
2 2
 
3
-const { BtnMenu } = E
3
+const { BtnMenu } = E;
4 4
 
5 5
 class PreViewMenu extends BtnMenu {
6 6
   constructor(editor) {
7 7
     // data-title属性表示当鼠标悬停在该按钮上时提示该按钮的功能简述
8
-      const $elem = E.$(
9
-          `<div class="w-e-menu" data-title="预览">
8
+    const $elem = E.$(
9
+      `<div class="w-e-menu" data-title="预览">
10 10
             <i>预览</i>
11
-          </div>`
12
-      )
13
-      super($elem, editor)
14
-      this.editor = editor
15
-      // <i class="w-e-icon-fullscreen"></i>
11
+          </div>`,
12
+    );
13
+    super($elem, editor);
14
+    this.editor = editor;
15
+    // <i class="w-e-icon-fullscreen"></i>
16 16
   }
17 17
 
18 18
   static preview() {}
19 19
 
20 20
   clickHandler() {
21
-    PreViewMenu.preview(this.editor.txt.html())
21
+    PreViewMenu.preview(this.editor.txt.html());
22 22
   }
23 23
 
24
-  tryChangeActive(){}
24
+  tryChangeActive() {}
25 25
 }
26 26
 
27
-export default PreViewMenu
27
+export default PreViewMenu;

+ 77
- 82
src/components/WangEditor/WangEditor.jsx View File

@@ -1,119 +1,114 @@
1 1
 import React, { useState, useRef, useEffect, useCallback } from 'react';
2 2
 import E from 'wangeditor';
3
-import PreviewMenu from './PreviewMenu'
4
-import Preview from './Preview'
5
-import { fetch, apis } from '../../utils/request';
6
-
7
-export default props => {
8
-  const ref = useRef()
9
-  const editorRef = useRef()
10
-  const [preview, setPreview] = useState(false)
11
-  const [content, setContent] = useState()
12
-
13
-  // wangeditor 有bug, 初始先触发 onchange
14
-  const firstChanged = useRef(false)
3
+// import PreviewMenu from './PreviewMenu'
4
+import Preview from './Preview';
5
+import { uploadImage } from '@/utils/uploadFile';
6
+
7
+export default (props) => {
8
+  const ref = useRef();
9
+  const editorRef = useRef();
10
+  const [preview, setPreview] = useState(false);
11
+  // const [content, setContent] = useState()
12
+
13
+  const inited = useRef(false);
14
+  const handleChange = useCallback(
15
+    (html) => {
16
+      if ((inited.current || html) && typeof props.onChange === 'function') {
17
+        inited.current = true;
18
+        props.onChange(html);
19
+      }
20
+    },
21
+    [props],
22
+  );
15 23
 
16 24
   const initEditor = useCallback(() => {
17
-    const editor = new E(ref.current)
18
-    editorRef.current = editor
19
-    
25
+    const editor = new E(ref.current);
26
+    editorRef.current = editor;
27
+
20 28
     // 取消自动 focus
21
-    editor.config.focus = false
29
+    editor.config.focus = false;
22 30
 
23 31
     // 触发 change
24
-    editor.config.onchange = html => {
25
-      // 规避 bug
26
-      if (!firstChanged.current) {
27
-        firstChanged.current = true
28
-        return
29
-      }
32
+    editor.config.onchange = handleChange;
30 33
 
31
-      setContent(html)
32
-
33
-      if (typeof props.onChange === 'function') {
34
-        props.onChange(html)
35
-      }
36
-    }
37
-
38
-    editor.config.zIndex = 100
34
+    editor.config.zIndex = 100;
39 35
 
40 36
     // 自定义图片上传
41
-    editor.config.uploadImgMaxLength = 1
42
-    editor.config.customUploadImg = function (files, insert) {
43
-      if (!files.length) return
44
-      
45
-      const data = new FormData()
46
-      data.append('file', files[0])
47
-      fetch(apis.image.upload)({data}).then(insert)
48
-    }
37
+    editor.config.uploadImgMaxLength = 1;
38
+    editor.config.customUploadImg = (files, insert) => {
39
+      if (!files.length) return;
40
+
41
+      uploadImage(files[0]).then(insert);
42
+    };
49 43
 
50 44
     // 扩展预览按钮
51
-    editor.menus.extend('previewMenu', PreviewMenu)
52
-    PreviewMenu.preview = (html) => setPreview(true)
45
+    // editor.menus.extend('previewMenu', PreviewMenu)
46
+    // PreviewMenu.preview = (html) => setPreview(true)
53 47
 
54 48
     // 配置菜单
55 49
     editor.config.menus = [
56
-      'head',  // 标题
57
-      'bold',  // 粗体
58
-      'fontSize',  // 字号
59
-      'fontName',  // 字体
60
-      'italic',  // 斜体
61
-      'underline',  // 下划线
62
-      'strikeThrough',  // 删除线
63
-      'foreColor',  // 文字颜色
64
-      'backColor',  // 背景颜色
65
-      'list',  // 列表
66
-      'justify',  // 对齐方式
67
-      'quote',  // 引用
68
-      'image',  // 插入图片
69
-      'undo',  // 撤销
70
-      'redo',  // 重复
71
-      'previewMenu'
72
-    ]
50
+      'head', // 标题
51
+      'bold', // 粗体
52
+      'fontSize', // 字号
53
+      'fontName', // 字体
54
+      'italic', // 斜体
55
+      'underline', // 下划线
56
+      'strikeThrough', // 删除线
57
+      'foreColor', // 文字颜色
58
+      'backColor', // 背景颜色
59
+      'list', // 列表
60
+      'justify', // 对齐方式
61
+      'quote', // 引用
62
+      'image', // 插入图片
63
+      'undo', // 撤销
64
+      'redo', // 重复
65
+      // 'previewMenu'
66
+    ];
73 67
 
74 68
     // 过滤 word 字符
75
-    editor.config.pasteFilterStyle = false
76
-    editor.config.pasteTextHandle = ctt => {
69
+    editor.config.pasteFilterStyle = false;
70
+    editor.config.pasteTextHandle = (ctt) => {
77 71
       const regs = [
78
-        /<!--\[if [\s\S]*?endif\]-->/ig,
79
-        /<[a-zA-Z0-9]+\:[^>]+>[^>]*<\/[a-zA-Z0-9]+\:[^>]+>/ig,
80
-        /<[a-zA-Z0-9]+\:[^>]+\/>/ig,
81
-        /<style>[\s\S]*?<\/style>/ig,
82
-        new RegExp('\u2029', 'ig'),     // 替换word分隔符 序号 8233
83
-      ]
72
+        /<!--\[if [\s\S]*?endif\]-->/gi,
73
+        /<[a-zA-Z0-9]+:[^>]+>[^>]*<\/[a-zA-Z0-9]+:[^>]+>/gi,
74
+        /<[a-zA-Z0-9]+:[^>]+\/>/gi,
75
+        /<style>[\s\S]*?<\/style>/gi,
76
+        new RegExp('\u2029', 'ig'), // 替换word分隔符 序号 8233
77
+      ];
84 78
 
85 79
       return regs.reduce((acc, reg) => {
86
-        return acc.replace(reg, '')
87
-      }, ctt)
88
-    }
80
+        return acc.replace(reg, '');
81
+      }, ctt);
82
+    };
89 83
 
90
-    editor.create()
91
-    editor.$textElem.attr('contenteditable', props.contenteditable !== false)
84
+    editor.create();
85
+    editor.$textElem.attr('contenteditable', props.contenteditable !== false);
92 86
 
93
-    return () => editor.destroy()
94
-  }, [props.contenteditable])
87
+    return () => editor.destroy();
88
+  }, [props, handleChange]);
95 89
 
96 90
   useEffect(() => {
97
-    initEditor()
98
-  }, [])
99
-  
91
+    initEditor();
92
+  }, [initEditor]);
93
+
94
+  //
100 95
   useEffect(() => {
101
-    if (props.value !== content && editorRef.current) {
102
-      setContent(props.value)
103
-      editorRef.current.txt.html(props.value)
96
+    if (props.value && !inited.current && editorRef.current) {
97
+      inited.current = true;
98
+      editorRef.current.txt.html(props.value);
104 99
     }
105
-  }, [props.value, content])
100
+  }, [props.value]);
106 101
 
107 102
   return (
108 103
     <>
109 104
       <div ref={ref} style={{ textAlign: 'left' }} />
110 105
       <Preview
111 106
         width={426}
112
-        style={{width: '426px', height: '863px', margin: 0, padding: 0}}
107
+        style={{ width: '426px', height: '863px', margin: 0, padding: 0 }}
113 108
         visible={preview}
114 109
         html={props.value}
115 110
         onCancel={() => setPreview(false)}
116 111
       />
117 112
     </>
118
-  )
119
-}
113
+  );
114
+};

+ 3
- 0
src/components/WangEditor/index.js View File

@@ -0,0 +1,3 @@
1
+import WangEditor from './WangEditor';
2
+
3
+export default WangEditor;

+ 1
- 1
src/global.jsx View File

@@ -12,7 +12,7 @@ if (pwa) {
12 12
       // useIntl().formatMessage({
13 13
       //   id: 'app.pwa.offline',
14 14
       // }),
15
-      "当前处于离线状态"
15
+      '当前处于离线状态',
16 16
     );
17 17
   }); // Pop up a prompt on the page asking the user if they want to use the latest version
18 18
 

+ 38
- 38
src/layouts/BasicLayout.jsx View File

@@ -4,13 +4,13 @@
4 4
  * @see You can view component api by: https://github.com/ant-design/ant-design-pro-layout
5 5
  */
6 6
 import ProLayout, { DefaultFooter } from '@ant-design/pro-layout';
7
-import React, { useEffect, useMemo } from 'react';
7
+import React, { useMemo } from 'react';
8 8
 // import { Link, useIntl, connect, history } from 'umi';
9 9
 import { Link, connect, history } from 'umi';
10
-import { GithubOutlined } from '@ant-design/icons';
10
+// import { GithubOutlined } from '@ant-design/icons';
11 11
 import { Result, Button } from 'antd';
12 12
 import Authorized from '@/utils/Authorized';
13
-import { getCurrentRoute } from '@/utils/utils'
13
+import { getCurrentRoute } from '@/utils/utils';
14 14
 import RightContent from '@/components/GlobalHeader/RightContent';
15 15
 // import { getMatchMenu } from '@umijs/route-utils';
16 16
 import logo from '../assets/logo.svg';
@@ -31,9 +31,10 @@ const noMatch = (
31 31
 /** Use Authorized check all menu item */
32 32
 const menuDataRender = (menuRoles) => (menuList) =>
33 33
   menuList.map((item) => {
34
-    const needAuth = !!item.menuCode
34
+    const needAuth = !!item.menuCode;
35 35
 
36
-    const authority = (menuRoles.filter(x => x.menuCode === item.menuCode)[0] || {}).roleString || ''
36
+    const authority =
37
+      (menuRoles.filter((x) => x.menuCode === item.menuCode)[0] || {}).roleString || '';
37 38
 
38 39
     const localItem = {
39 40
       ...item,
@@ -46,26 +47,28 @@ const menuDataRender = (menuRoles) => (menuList) =>
46 47
 const defaultFooterDom = (
47 48
   <DefaultFooter
48 49
     copyright={`${new Date().getFullYear()} 云致科技`}
49
-    links={[
50
-      // {
51
-      //   key: 'Ant Design Pro',
52
-      //   title: 'Ant Design Pro',
53
-      //   href: 'https://pro.ant.design',
54
-      //   blankTarget: true,
55
-      // },
56
-      // {
57
-      //   key: 'github',
58
-      //   title: <GithubOutlined />,
59
-      //   href: 'https://github.com/ant-design/ant-design-pro',
60
-      //   blankTarget: true,
61
-      // },
62
-      // {
63
-      //   key: 'Ant Design',
64
-      //   title: 'Ant Design',
65
-      //   href: 'https://ant.design',
66
-      //   blankTarget: true,
67
-      // },
68
-    ]}
50
+    links={
51
+      [
52
+        // {
53
+        //   key: 'Ant Design Pro',
54
+        //   title: 'Ant Design Pro',
55
+        //   href: 'https://pro.ant.design',
56
+        //   blankTarget: true,
57
+        // },
58
+        // {
59
+        //   key: 'github',
60
+        //   title: <GithubOutlined />,
61
+        //   href: 'https://github.com/ant-design/ant-design-pro',
62
+        //   blankTarget: true,
63
+        // },
64
+        // {
65
+        //   key: 'Ant Design',
66
+        //   title: 'Ant Design',
67
+        //   href: 'https://ant.design',
68
+        //   blankTarget: true,
69
+        // },
70
+      ]
71
+    }
69 72
   />
70 73
 );
71 74
 
@@ -90,20 +93,17 @@ const BasicLayout = (props) => {
90 93
     }
91 94
   }; // get children authority
92 95
 
93
-  const authorized = useMemo(
94
-    () => {
95
-      const routeItem = getCurrentRoute(location.pathname || '/')
96
-      if (!routeItem || !routeItem.menuCode) {
97
-        return { authority: undefined }
98
-      }
96
+  const authorized = useMemo(() => {
97
+    const routeItem = getCurrentRoute(location.pathname || '/');
98
+    if (!routeItem || !routeItem.menuCode) {
99
+      return { authority: undefined };
100
+    }
99 101
 
100
-      const authority = (menus.filter(x => x.menuCode === routeItem.menuCode)[0] || {}).roleString
101
-      return {
102
-        authority: authority ? authority.split(',') : ['any-string-for-no-right']
103
-      }
104
-    },
105
-    [location.pathname, menus],
106
-  );
102
+    const authority = (menus.filter((x) => x.menuCode === routeItem.menuCode)[0] || {}).roleString;
103
+    return {
104
+      authority: authority ? authority.split(',') : ['any-string-for-no-right'],
105
+    };
106
+  }, [location.pathname, menus]);
107 107
 
108 108
   // const { formatMessage } = useIntl();
109 109
 

+ 1
- 1
src/layouts/UserLayout.jsx View File

@@ -23,7 +23,7 @@ const UserLayout = (props) => {
23 23
   const { breadcrumb } = getMenuData(routes);
24 24
   const title = getPageTitle({
25 25
     pathname: location.pathname,
26
-    formatMessage: x => x,
26
+    formatMessage: (x) => x,
27 27
     breadcrumb,
28 28
     ...props,
29 29
   });

+ 1
- 0
src/locales/en-US.js View File

@@ -5,6 +5,7 @@ import pwa from './en-US/pwa';
5 5
 import settingDrawer from './en-US/settingDrawer';
6 6
 import settings from './en-US/settings';
7 7
 import pages from './en-US/pages';
8
+
8 9
 export default {
9 10
   'navBar.lang': 'Languages',
10 11
   'layout.user.link.help': 'Help',

+ 1
- 0
src/locales/id-ID.js View File

@@ -5,6 +5,7 @@ import pwa from './id-ID/pwa';
5 5
 import settingDrawer from './id-ID/settingDrawer';
6 6
 import settings from './id-ID/settings';
7 7
 import pages from './id-ID/pages';
8
+
8 9
 export default {
9 10
   'navbar.lang': 'Bahasa',
10 11
   'layout.user.link.help': 'Bantuan',

+ 1
- 0
src/locales/ja-JP.js View File

@@ -5,6 +5,7 @@ import settings from './ja-JP/settings';
5 5
 import pwa from './ja-JP/pwa';
6 6
 import component from './ja-JP/component';
7 7
 import pages from './ja-JP/pages';
8
+
8 9
 export default {
9 10
   'navBar.lang': '言語',
10 11
   'layout.user.link.help': 'ヘルプ',

+ 1
- 0
src/locales/pt-BR.js View File

@@ -4,6 +4,7 @@ import menu from './pt-BR/menu';
4 4
 import pwa from './pt-BR/pwa';
5 5
 import settingDrawer from './pt-BR/settingDrawer';
6 6
 import settings from './pt-BR/settings';
7
+
7 8
 export default {
8 9
   'navBar.lang': 'Idiomas',
9 10
   'layout.user.link.help': 'ajuda',

+ 1
- 0
src/locales/zh-CN.js View File

@@ -5,6 +5,7 @@ import pwa from './zh-CN/pwa';
5 5
 import settingDrawer from './zh-CN/settingDrawer';
6 6
 import settings from './zh-CN/settings';
7 7
 import pages from './zh-CN/pages';
8
+
8 9
 export default {
9 10
   'navBar.lang': '语言',
10 11
   'layout.user.link.help': '帮助',

+ 1
- 0
src/locales/zh-TW.js View File

@@ -4,6 +4,7 @@ import menu from './zh-TW/menu';
4 4
 import pwa from './zh-TW/pwa';
5 5
 import settingDrawer from './zh-TW/settingDrawer';
6 6
 import settings from './zh-TW/settings';
7
+
7 8
 export default {
8 9
   'navBar.lang': '語言',
9 10
   'layout.user.link.help': '幫助',

+ 1
- 0
src/models/global.js View File

@@ -1,4 +1,5 @@
1 1
 import { queryNotices } from '@/services/user';
2
+
2 3
 const GlobalModel = {
3 4
   namespace: 'global',
4 5
   state: {

+ 10
- 10
src/models/login.js View File

@@ -12,14 +12,14 @@ const Model = {
12 12
   },
13 13
   effects: {
14 14
     *login({ payload }, { call, put }) {
15
-      let response
16
-      let status
15
+      let response;
16
+      let status;
17 17
       try {
18 18
         response = yield call(userLogin, payload);
19
-        status = 'success'
19
+        status = 'success';
20 20
       } catch (e) {
21 21
         // message.error(e.message)
22
-        status = 'error'
22
+        status = 'error';
23 23
       }
24 24
 
25 25
       yield put({
@@ -28,23 +28,23 @@ const Model = {
28 28
       });
29 29
 
30 30
       if (status === 'success') {
31
-        localStorage.setItem('token', response.token)
31
+        localStorage.setItem('token', response.token);
32 32
 
33 33
         const urlParams = new URL(window.location.href);
34 34
         const params = getPageQuery();
35 35
         message.success('🎉 🎉 🎉  登录成功!');
36 36
         let { redirect } = params;
37
-  
37
+
38 38
         if (redirect) {
39 39
           const redirectUrlParams = new URL(redirect);
40
-  
40
+
41 41
           if (redirectUrlParams.origin === urlParams.origin) {
42 42
             redirect = redirect.substr(urlParams.origin.length);
43
-  
43
+
44 44
             if (window.routerBase !== '/') {
45 45
               redirect = redirect.replace(window.routerBase, '/');
46 46
             }
47
-  
47
+
48 48
             if (redirect.match(/^\/.*#/)) {
49 49
               redirect = redirect.substr(redirect.indexOf('#') + 1);
50 50
             }
@@ -53,7 +53,7 @@ const Model = {
53 53
             return;
54 54
           }
55 55
         }
56
-  
56
+
57 57
         history.replace(redirect || '/');
58 58
       }
59 59
     },

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

@@ -30,9 +30,9 @@ const UserModel = {
30 30
   },
31 31
   reducers: {
32 32
     saveCurrentUser(state, action) {
33
-      const { user, menus, roles } = action.payload || {}
33
+      const { user, menus, roles } = action.payload || {};
34 34
 
35
-      setAuthority(roles.map(x => `${x.roleId}`))
35
+      setAuthority(roles.map((x) => `${x.roleId}`));
36 36
 
37 37
       return { ...state, currentUser: user, menus };
38 38
     },

+ 2
- 1
src/pages/Admin.jsx View File

@@ -7,7 +7,8 @@ import { PageHeaderWrapper } from '@ant-design/pro-layout';
7 7
 export default () => {
8 8
   // const intl = useIntl();
9 9
   return (
10
-    <PageHeaderWrapper content="这个页面只有 admin 权限才能查看"
10
+    <PageHeaderWrapper
11
+      content="这个页面只有 admin 权限才能查看"
11 12
       // content={intl.formatMessage({
12 13
       //   id: 'pages.admin.subPage.title',
13 14
       //   defaultMessage: ' 这个页面只有 admin 权限才能查看',

+ 10
- 0
src/pages/Post/Edit/components/Answer.jsx View File

@@ -0,0 +1,10 @@
1
+import React from 'react';
2
+import TestQuestions from '@/components/TestQuestions';
3
+
4
+export default () => {
5
+  return (
6
+    <div>
7
+      <TestQuestions />
8
+    </div>
9
+  );
10
+};

+ 68
- 56
src/pages/Post/Edit/components/Form.jsx View File

@@ -1,49 +1,64 @@
1
-import React, { useEffect, useMemo } from 'react'
2
-import ProForm,
3
-  {
4
-    ProFormText,
5
-    ProFormSelect,
6
-    ProFormUploadButton,
7
-    ProFormTextArea,
8
-    ProFormSwitch,
9
-    ProFormDigit,
10
-    ProFormRadio,
11
-  } from '@ant-design/pro-form'
12
-import UploadImage from '@/components/UploadImage'
13
-import request from '@/utils/request'
14
-import { notification, Form } from 'antd'
15
-  
1
+import React, { useEffect, useMemo, useState } from 'react';
2
+import ProForm, {
3
+  ProFormText,
4
+  ProFormSelect,
5
+  ProFormTextArea,
6
+  ProFormSwitch,
7
+  ProFormDigit,
8
+  ProFormRadio,
9
+} from '@ant-design/pro-form';
10
+import UploadImage from '@/components/UploadImage';
11
+import UploadVideo from '@/components/UploadVideo';
12
+import WangEditor from '@/components/WangEditor';
13
+import request from '@/utils/request';
14
+import { notification, Form } from 'antd';
15
+
16 16
 export default (props) => {
17
-  const [form] = Form.useForm()
18
-  
17
+  const [form] = Form.useForm();
18
+  const [isVideo, setIsVideo] = useState(false);
19
+
19 20
   const typeDict = useMemo(() => {
20 21
     return (props.typeList || []).reduce((acc, item) => {
21 22
       return {
22 23
         ...acc,
23
-        [item.typeId]: item.name
24
-      }
25
-    }, {})
26
-  }, [props.typeList])
24
+        [item.typeId]: item.name,
25
+      };
26
+    }, {});
27
+  }, [props.typeList]);
27 28
 
28 29
   const handleSubmit = (values) => {
29 30
     if (!values.postId) {
30
-      return request('/post', { method: 'post', data: values }).then((res) => {
31
-        props.onChange(res)
32
-      }).catch((e) => {
33
-        notification.error({ message: e.message })
34
-        return Promise.reject(e.message)
35
-      })
31
+      return request('/post', { method: 'post', data: values })
32
+        .then((res) => {
33
+          props.onChange(res);
34
+        })
35
+        .catch((e) => {
36
+          notification.error({ message: e.message });
37
+          return Promise.reject(e.message);
38
+        });
36 39
     }
37
-  }
40
+
41
+    // eslint-disable-next-line
42
+    return;
43
+  };
44
+
45
+  const handleValueChange = (changed) => {
46
+    const key = Object.keys(changed)[0];
47
+
48
+    if (key === 'isVideo') {
49
+      setIsVideo(changed[key]);
50
+    }
51
+  };
38 52
 
39 53
   useEffect(() => {
40 54
     if (props.post && props.post.postId) {
41
-      form.setFieldsValue(props.post)
55
+      form.setFieldsValue(props.post);
56
+      setIsVideo(props.post.isVideo);
42 57
     }
43
-  }, [props.post, form])
58
+  }, [props.post, form]);
44 59
 
45 60
   return (
46
-    <ProForm form={form} onFinish={handleSubmit}>
61
+    <ProForm form={form} onFinish={handleSubmit} onValuesChange={handleValueChange}>
47 62
       <ProFormText
48 63
         label="标题"
49 64
         placeholder="请输入标题"
@@ -70,18 +85,24 @@ export default (props) => {
70 85
         <UploadImage />
71 86
       </Form.Item>
72 87
 
73
-      <ProFormTextArea
74
-        label="简介"
75
-        placeholder="请输入简介"
76
-        name="summary"
77
-      />
88
+      <ProFormTextArea label="简介" placeholder="请输入简介" name="summary" />
78 89
 
79
-      <ProFormSwitch
80
-        label="视频"
81
-        name="isVideo"
82
-        checkedChildren="视频"
83
-        unCheckedChildren="图文"
84
-      />
90
+      <ProFormSwitch label="视频" name="isVideo" checkedChildren="视频" unCheckedChildren="图文" />
91
+
92
+      {isVideo ? (
93
+        <>
94
+          <Form.Item name="videoPoster" label="视频封面" placeholder="请设置封面">
95
+            <UploadImage />
96
+          </Form.Item>
97
+          <Form.Item name="videoUrl" label="视频地址" placeholder="请上传视频">
98
+            <UploadVideo />
99
+          </Form.Item>
100
+        </>
101
+      ) : (
102
+        <Form.Item name="videoUrl" label="图文内容" placeholder="请填写图文内容">
103
+          <WangEditor />
104
+        </Form.Item>
105
+      )}
85 106
 
86 107
       <ProFormDigit
87 108
         label="答题数"
@@ -105,12 +126,7 @@ export default (props) => {
105 126
         precision={0}
106 127
       />
107 128
 
108
-      <ProFormSwitch
109
-        label="热门"
110
-        name="isTopic"
111
-        checkedChildren="是"
112
-        unCheckedChildren="否"
113
-      />
129
+      <ProFormSwitch label="热门" name="isTopic" checkedChildren="是" unCheckedChildren="否" />
114 130
 
115 131
       <ProFormRadio.Group
116 132
         label="状态"
@@ -126,12 +142,8 @@ export default (props) => {
126 142
           },
127 143
         ]}
128 144
       />
129
-      
130
-      <ProFormText
131
-        label="创建日期"
132
-        name="createDate"
133
-        readonly
134
-      />
145
+
146
+      <ProFormText label="创建日期" name="createDate" readonly />
135 147
     </ProForm>
136
-  )
137
-}
148
+  );
149
+};

+ 34
- 30
src/pages/Post/Edit/index.jsx View File

@@ -1,41 +1,44 @@
1
-import React, { useEffect, useMemo, useState } from 'react'
2
-import { connect } from 'umi'
3
-import { notification, Tabs } from 'antd'
4
-import { PageContainer } from '@ant-design/pro-layout'
5
-import Container from '@/components/Container'
6
-import request from '@/utils/request'
7
-import PostForm from './components/Form'
1
+import React, { useEffect, useState } from 'react';
2
+import { connect } from 'umi';
3
+import { notification, Tabs } from 'antd';
4
+import { PageContainer } from '@ant-design/pro-layout';
5
+import Container from '@/components/Container';
6
+import request from '@/utils/request';
7
+import PostForm from './components/Form';
8
+import Answer from './components/Answer';
9
+
10
+const { TabPane } = Tabs;
8 11
 
9
-const { TabPane } = Tabs
10
-  
11 12
 const PostEdit = (props) => {
12
-  const { id } = props.location.query
13
-  const [loading, setLoading] = useState(false)
14
-  const [post, setPost] = useState({})
13
+  const { id } = props.location.query;
14
+  const [loading, setLoading] = useState(false);
15
+  const [post, setPost] = useState({});
15 16
 
16 17
   const handleFormChange = (newPost) => {
17
-    setPost(newPost)
18
-  }
18
+    setPost(newPost);
19
+  };
19 20
 
20 21
   useEffect(() => {
21
-    setLoading(true)
22
-    request(`/post/${id}`).then((res) => {
23
-      setPost(res)
24
-      setLoading(false)
25
-    }).catch((e) => {
26
-      setLoading(false)
27
-      notification.error({ message: e.message })
28
-    })
29
-  }, [id])
22
+    setLoading(true);
23
+    request(`/post/${id}`)
24
+      .then((res) => {
25
+        setPost(res);
26
+        setLoading(false);
27
+      })
28
+      .catch((e) => {
29
+        setLoading(false);
30
+        notification.error({ message: e.message });
31
+      });
32
+  }, [id]);
30 33
 
31 34
   useEffect(() => {
32 35
     if (!props.typeList || !props.typeList.length) {
33 36
       props.dispatch({
34 37
         type: 'post/getTypeList',
35
-        payload: { pageSize: 999 }
36
-      })
38
+        payload: { pageSize: 999 },
39
+      });
37 40
     }
38
-  }, [props])
41
+  }, [props]);
39 42
 
40 43
   return (
41 44
     <PageContainer>
@@ -48,14 +51,15 @@ const PostEdit = (props) => {
48 51
           </TabPane>
49 52
           <TabPane tab="题库设置" key="2">
50 53
             <Container>
54
+              <Answer />
51 55
             </Container>
52 56
           </TabPane>
53 57
         </Tabs>
54 58
       </Container>
55 59
     </PageContainer>
56
-  )
57
-}
60
+  );
61
+};
58 62
 
59 63
 export default connect((s) => ({
60
-  typeList: s.post.typeList
61
-}))(PostEdit)
64
+  typeList: s.post.typeList,
65
+}))(PostEdit);

+ 3
- 1
src/pages/Post/List/index.jsx View File

@@ -100,7 +100,9 @@ const PostList = (props) => {
100 100
       key: 'option',
101 101
       valueType: 'option',
102 102
       render: (_, item) => [
103
-        <a key="opt1" onClick={() => history.push(`/post/edit?id=${item.postId}`)}>编辑</a>,
103
+        <a key="opt1" onClick={() => history.push(`/post/edit?id=${item.postId}`)}>
104
+          编辑
105
+        </a>,
104 106
       ],
105 107
     },
106 108
   ];

+ 3
- 3
src/pages/TableList/components/UpdateForm.jsx View File

@@ -64,7 +64,7 @@ const UpdateForm = (props) => {
64 64
           rules={[
65 65
             {
66 66
               required: true,
67
-              message: "请输入规则名称!",
67
+              message: '请输入规则名称!',
68 68
               // message: (
69 69
               //   <FormattedMessage
70 70
               //     id="pages.searchTable.updateForm.ruleName.nameRules"
@@ -90,7 +90,7 @@ const UpdateForm = (props) => {
90 90
           rules={[
91 91
             {
92 92
               required: true,
93
-              message: "请输入至少五个字符的规则描述!",
93
+              message: '请输入至少五个字符的规则描述!',
94 94
               // message: (
95 95
               //   <FormattedMessage
96 96
               //     id="pages.searchTable.updateForm.ruleDesc.descRules"
@@ -180,7 +180,7 @@ const UpdateForm = (props) => {
180 180
           rules={[
181 181
             {
182 182
               required: true,
183
-              message: "请选择开始时间!",
183
+              message: '请选择开始时间!',
184 184
               // message: (
185 185
               //   <FormattedMessage
186 186
               //     id="pages.searchTable.updateForm.schedulingPeriod.timeRules"

+ 3
- 6
src/pages/TableList/index.jsx View File

@@ -260,7 +260,7 @@ const TableList = () => {
260 260
           extra={
261 261
             <div>
262 262
               {/* <FormattedMessage id="pages.searchTable.chosen" defaultMessage="已选择" /> */}
263
-              已选择 {' '}
263
+              已选择{' '}
264 264
               <a
265 265
                 style={{
266 266
                   fontWeight: 600,
@@ -268,17 +268,14 @@ const TableList = () => {
268 268
               >
269 269
                 {selectedRowsState.length}
270 270
               </a>{' '}
271
-              项
272
-              {/* <FormattedMessage id="pages.searchTable.item" defaultMessage="项" /> */}
271
+              项{/* <FormattedMessage id="pages.searchTable.item" defaultMessage="项" /> */}
273 272
               &nbsp;&nbsp;
274 273
               <span>
275 274
                 {/* <FormattedMessage
276 275
                   id="pages.searchTable.totalServiceCalls"
277 276
                   defaultMessage="服务调用次数总计"
278 277
                 /> */}
279
-                服务调用次数总计
280
-                {' '}
281
-                {selectedRowsState.reduce((pre, item) => pre + item.callNo, 0)}{' '}
278
+                服务调用次数总计 {selectedRowsState.reduce((pre, item) => pre + item.callNo, 0)}{' '}
282 279
                 {/* <FormattedMessage id="pages.searchTable.tenThousand" defaultMessage="万" /> */}
283 280
284 281
               </span>

+ 1
- 0
src/pages/TableList/service.js View File

@@ -1,4 +1,5 @@
1 1
 import request from '@/utils/request';
2
+
2 3
 export async function queryRule(params) {
3 4
   return request('/api/rule', {
4 5
     params,

+ 7
- 9
src/pages/User/login/index.jsx View File

@@ -1,13 +1,10 @@
1
-import {
2
-  LockOutlined,
3
-  UserOutlined,
4
-} from '@ant-design/icons';
1
+import { LockOutlined, UserOutlined } from '@ant-design/icons';
5 2
 import { Alert } from 'antd';
6
-import React, { useState } from 'react';
3
+import React from 'react';
7 4
 import ProForm, { ProFormText } from '@ant-design/pro-form';
8 5
 // import { useIntl, connect, FormattedMessage } from 'umi';
9 6
 import { connect } from 'umi';
10
-import md5 from 'md5'
7
+import md5 from 'md5';
11 8
 
12 9
 import styles from './index.less';
13 10
 
@@ -57,7 +54,8 @@ const Login = (props) => {
57 54
         }}
58 55
       >
59 56
         {status === 'error' && !submitting && (
60
-          <LoginMessage content="账户或密码错误"
57
+          <LoginMessage
58
+            content="账户或密码错误"
61 59
             // content={intl.formatMessage({
62 60
             //   id: 'pages.login.accountLogin.errorMessage',
63 61
             //   defaultMessage: '账户或密码错误(admin/ant.design)',
@@ -78,7 +76,7 @@ const Login = (props) => {
78 76
           rules={[
79 77
             {
80 78
               required: true,
81
-              message: "请输入用户名!",
79
+              message: '请输入用户名!',
82 80
               // message: (
83 81
               //   <FormattedMessage
84 82
               //     id="pages.login.username.required"
@@ -108,7 +106,7 @@ const Login = (props) => {
108 106
               //     defaultMessage="请输入密码!"
109 107
               //   />
110 108
               // ),
111
-              message: "请输入密码!"
109
+              message: '请输入密码!',
112 110
             },
113 111
           ]}
114 112
         />

+ 2
- 4
src/pages/Welcome.jsx View File

@@ -33,8 +33,7 @@ export default () => {
33 33
         />
34 34
         <Typography.Text strong>
35 35
           {/* <FormattedMessage id="pages.welcome.advancedComponent" defaultMessage="高级表格" /> */}
36
-          高级表格
37
-          {' '}
36
+          高级表格{' '}
38 37
           <a
39 38
             href="https://procomponents.ant.design/components/table"
40 39
             rel="noopener noreferrer"
@@ -52,8 +51,7 @@ export default () => {
52 51
           }}
53 52
         >
54 53
           {/* <FormattedMessage id="pages.welcome.advancedLayout" defaultMessage="高级布局" /> */}
55
-          高级布局
56
-          {' '}
54
+          高级布局{' '}
57 55
           <a
58 56
             href="https://procomponents.ant.design/components/layout"
59 57
             rel="noopener noreferrer"

+ 4
- 4
src/pages/model.js View File

@@ -4,17 +4,17 @@ import request from '@/utils/request';
4 4
 const Model = {
5 5
   namespace: 'post',
6 6
   state: {
7
-    typeList: []
7
+    typeList: [],
8 8
   },
9 9
   effects: {
10 10
     *getTypeList({ payload }, { call, put }) {
11
-      let response
11
+      let response;
12 12
 
13 13
       try {
14 14
         response = yield call((params) => request('/post-type', { params }), payload);
15 15
       } catch (e) {
16
-        message.error(e.message)
17
-        return
16
+        message.error(e.message);
17
+        return;
18 18
       }
19 19
 
20 20
       yield put({

+ 3
- 3
src/utils/authority.js View File

@@ -1,19 +1,19 @@
1 1
 import { reloadAuthorized } from './Authorized';
2 2
 
3
-const authority = { value: undefined }
3
+const authority = { value: undefined };
4 4
 
5 5
 export function getAuthority() {
6 6
   // 这个 if 不要删除
7 7
   // 虽然 authority 是全局的, 但是这个地方第一次仍然访问不到
8 8
   // 原因未知
9 9
   if (!authority) {
10
-    return undefined
10
+    return undefined;
11 11
   }
12 12
 
13 13
   return authority.value;
14 14
 }
15 15
 
16 16
 export function setAuthority(auth) {
17
-  authority.value = auth
17
+  authority.value = auth;
18 18
   reloadAuthorized();
19 19
 }

+ 43
- 39
src/utils/request.js View File

@@ -31,7 +31,7 @@ const errorHandler = (error) => {
31 31
       description: errorText,
32 32
     });
33 33
   } else if (error.code && error.message) {
34
-    throw new Error(error.message)
34
+    throw new Error(error.message);
35 35
   } else {
36 36
     notification.error({
37 37
       description: '您的网络发生异常,无法连接服务器',
@@ -44,15 +44,17 @@ const errorHandler = (error) => {
44 44
 /** 配置request请求时的默认参数 */
45 45
 
46 46
 const request = extend({
47
-  prefix: process.env.NODE_ENV === 'production' ? '/api/admin' : '/api/admin', 
47
+  prefix: process.env.NODE_ENV === 'production' ? '/api/admin' : '/api/admin',
48 48
   errorHandler,
49 49
   // 默认错误处理
50 50
   credentials: 'include', // 默认请求是否带上cookie
51 51
 });
52 52
 
53 53
 request.interceptors.request.use((url, options) => {
54
-  const headers = options.headers || {}
55
-  const token = localStorage.getItem('token') ? { 'X-Authorization-JWT': localStorage.getItem('token') } : {}
54
+  const headers = options.headers || {};
55
+  const token = localStorage.getItem('token')
56
+    ? { 'X-Authorization-JWT': localStorage.getItem('token') }
57
+    : {};
56 58
 
57 59
   return {
58 60
     url,
@@ -62,62 +64,64 @@ request.interceptors.request.use((url, options) => {
62 64
         ...headers,
63 65
         ...token,
64 66
       },
65
-    }
66
-  }
67
-})
67
+    },
68
+  };
69
+});
68 70
 
69 71
 request.interceptors.response.use(async (response) => {
70
-  const token = response.headers.get('x-authorization-jwt')
72
+  const token = response.headers.get('x-authorization-jwt');
71 73
   if (token) {
72
-    localStorage.setItem('token', token)
74
+    localStorage.setItem('token', token);
73 75
   }
74 76
 
75
-  const contextType = response.headers.get('content-type')
77
+  const contextType = response.headers.get('content-type');
76 78
   if (contextType.indexOf('json') > -1) {
77
-    const result = await response.clone().json()
79
+    const result = await response.clone().json();
78 80
     if (result.code === 1000) {
79
-      return result.data
81
+      return result.data;
80 82
     }
81
-    return Promise.reject(result)
83
+    return Promise.reject(result);
82 84
   }
83 85
 
84
-  return response
85
-})
86
+  return response;
87
+});
86 88
 
87 89
 export default request;
88 90
 
89 91
 // 专门为 ProTable 定制的 request
90 92
 // https://procomponents.ant.design/components/table#request
91 93
 export const queryTable = (url) => {
92
-  return (params, sort, filter) => {
93
-    return new Promise((resolve, reject) => {
94
-      const { current, pageSize, ...leftParams } = params
94
+  return (params) => {
95
+    return new Promise((resolve) => {
96
+      const { current, pageSize, ...leftParams } = params;
95 97
       const options = {
96 98
         params: {
97 99
           pageNum: current,
98 100
           pageSize,
99 101
           ...leftParams,
100
-        }
101
-      }
102
+        },
103
+      };
102 104
 
103
-      request(url, options).then(res => {
104
-        // 成功返回 success: true
105
-        resolve({
106
-          success: true,
107
-          total: res.total,
108
-          data: res.records
105
+      request(url, options)
106
+        .then((res) => {
107
+          // 成功返回 success: true
108
+          resolve({
109
+            success: true,
110
+            total: res.total,
111
+            data: res.records,
112
+          });
109 113
         })
110
-      }).catch((e) => {
111
-        notification.error({
112
-          message: e.message,
114
+        .catch((e) => {
115
+          notification.error({
116
+            message: e.message,
117
+          });
118
+          // 失败返回 success: false
119
+          resolve({
120
+            success: false,
121
+            total: 0,
122
+            data: [],
123
+          });
113 124
         });
114
-        // 失败返回 success: false
115
-        resolve({
116
-          success: false,
117
-          total: 0,
118
-          data: []
119
-        })
120
-      })
121
-    })
122
-  }
123
-}
125
+    });
126
+  };
127
+};

+ 47
- 15
src/utils/uploadFile.js View File

@@ -3,14 +3,14 @@ import request from './request';
3 3
 
4 4
 // 通过 OSS STS 模式上传
5 5
 // oss client 是单例的
6
-let ossClient
7
-let stsInfo
6
+let ossClient;
7
+let stsInfo;
8 8
 
9 9
 function getOssClient() {
10 10
   // 请求 sts 临时授权
11 11
   return request('/oss-sts')
12 12
     .then((sts) => {
13
-      stsInfo = sts
13
+      stsInfo = sts;
14 14
       ossClient = new OSS({
15 15
         accessKeyId: sts.accessKeyId,
16 16
         accessKeySecret: sts.accessKeySecret,
@@ -23,9 +23,9 @@ function getOssClient() {
23 23
               accessKeyId: res.accessKeyId,
24 24
               accessKeySecret: res.accessKeySecret,
25 25
               stsToken: res.stsToken,
26
-            }
27
-          })
28
-        }
26
+            };
27
+          });
28
+        },
29 29
       });
30 30
     })
31 31
     .catch((e) => {
@@ -35,7 +35,6 @@ function getOssClient() {
35 35
 
36 36
 // https://github.com/react-component/upload#customrequest
37 37
 export default ({ file, onSuccess, onError }) => {
38
-
39 38
   const upload = () => {
40 39
     const data = new FormData();
41 40
     data.append('file', file);
@@ -50,20 +49,53 @@ export default ({ file, onSuccess, onError }) => {
50 49
       .catch((e2) => {
51 50
         onError(e2);
52 51
       });
53
-  }
52
+  };
54 53
 
55 54
   if (!ossClient) {
56
-    getOssClient().then(() => {
57
-      upload()
58
-    })
59
-    .catch((e) => {
60
-      onError(e);
61
-    });
55
+    getOssClient()
56
+      .then(() => {
57
+        upload();
58
+      })
59
+      .catch((e) => {
60
+        onError(e);
61
+      });
62 62
   } else {
63
-    upload()
63
+    upload();
64 64
   }
65 65
 
66 66
   return {
67 67
     abort: () => {},
68 68
   };
69 69
 };
70
+
71
+export function uploadImage(file) {
72
+  return new Promise((resolve, reject) => {
73
+    const upload = () => {
74
+      const data = new FormData();
75
+      data.append('file', file);
76
+      const now = new Date();
77
+      const fileName = `${stsInfo.path}/${now.valueOf()}-${file.name}`;
78
+
79
+      ossClient
80
+        .put(fileName, file)
81
+        .then((res) => {
82
+          resolve(res.url);
83
+        })
84
+        .catch((e2) => {
85
+          reject(e2);
86
+        });
87
+    };
88
+
89
+    if (!ossClient) {
90
+      getOssClient()
91
+        .then(() => {
92
+          upload();
93
+        })
94
+        .catch((e) => {
95
+          reject(e);
96
+        });
97
+    } else {
98
+      upload();
99
+    }
100
+  });
101
+}

+ 8
- 8
src/utils/utils.js View File

@@ -1,5 +1,5 @@
1 1
 import { parse } from 'querystring';
2
-import routes from '../../config/routes'
2
+import routes from '../../config/routes';
3 3
 
4 4
 /* eslint no-useless-escape:0 import/prefer-default-export:0 */
5 5
 
@@ -26,20 +26,20 @@ export const isAntDesignProOrDev = () => {
26 26
 export const getPageQuery = () => parse(window.location.href.split('?')[1]);
27 27
 
28 28
 export function getCurrentRoute(pathname) {
29
-  const routeArr = []
29
+  const routeArr = [];
30 30
 
31 31
   const flatten = (list) => {
32 32
     list.forEach((route) => {
33
-      routeArr.push(route)
33
+      routeArr.push(route);
34 34
 
35 35
       if (route.routes) {
36
-        flatten(route.routes)
36
+        flatten(route.routes);
37 37
       }
38
-    })
39
-  }
38
+    });
39
+  };
40 40
 
41 41
   // 取有效的数据
42
-  flatten(routes[0].routes)
42
+  flatten(routes[0].routes);
43 43
 
44
-  return routeArr.filter(x => x.path === pathname)[0]
44
+  return routeArr.filter((x) => x.path === pathname)[0];
45 45
 }

+ 1
- 0
src/utils/utils.test.js View File

@@ -1,4 +1,5 @@
1 1
 import { isUrl } from './utils';
2
+
2 3
 describe('isUrl tests', () => {
3 4
   it('should return false for invalid and corner case inputs', () => {
4 5
     expect(isUrl([])).toBeFalsy();