Yansen 2 年前
父节点
当前提交
1a93772f85

+ 5
- 1
src/components/Page/Curd.jsx 查看文件

7
   destroyOnClose: true
7
   destroyOnClose: true
8
 }
8
 }
9
 export default (props) => {
9
 export default (props) => {
10
-  const { rowKey, columns, request, formProps, renderFormItems, ...leftProps } = props;
10
+  const { rowKey, columns, request, formProps, renderFormItems, tableRef, ...leftProps } = props;
11
 
11
 
12
   const [form] = Form.useForm();
12
   const [form] = Form.useForm();
13
   const listRef = React.useRef();
13
   const listRef = React.useRef();
14
   const rowRef = React.useRef();
14
   const rowRef = React.useRef();
15
   const [open, setOpen] = React.useState(false);
15
   const [open, setOpen] = React.useState(false);
16
+
17
+  if (tableRef) {
18
+    tableRef.current = listRef.current;
19
+  }
16
   
20
   
17
   const onAdd = () => {
21
   const onAdd = () => {
18
     rowRef.current = undefined;
22
     rowRef.current = undefined;

+ 10
- 0
src/components/Upload/Upload.jsx 查看文件

1
+import { Upload } from 'antd';
2
+import { uploadFile } from './request'
3
+
4
+export default (props) => {
5
+  return (
6
+    <Upload {...props} customRequest={uploadFile} >
7
+      {props.children}
8
+    </Upload>
9
+  )
10
+}

+ 58
- 0
src/components/Upload/UploadImage.jsx 查看文件

1
+import React, { useState } from 'react';
2
+import { LoadingOutlined, PlusOutlined } from '@ant-design/icons';
3
+import Upload from './Upload';
4
+
5
+function beforeUpload(file) {
6
+  const isImage = file.type === 'image/jpeg' || file.type === 'image/png'||file.type==='image/gif';
7
+  if (!isImage) {
8
+    message.error('请上传 JPG , PNG或GIF 图片!');
9
+  }
10
+  const isLt10M = file.size / 1024 / 1024 < 10;
11
+  if (!isLt10M) {
12
+    message.error('图片大小必须小于 10MB!');
13
+  }
14
+
15
+  return isImage && isLt10M;
16
+}
17
+
18
+const UploadButton = (props) => (
19
+  <div>
20
+    {props.loading ? <LoadingOutlined /> : <PlusOutlined />}
21
+    <div style={{ marginTop: 8 }}>上传</div>
22
+  </div>
23
+);
24
+
25
+export default (props) => {
26
+  const { value, onChange } = props;
27
+
28
+  const [loading, setLoading] = useState(false);
29
+
30
+  const handleChange = info => {
31
+    if (info.file.status === 'uploading') {
32
+      setLoading(true);
33
+      return;
34
+    }
35
+    if (info.file.status === 'error') {
36
+      setLoading(false);
37
+      return;
38
+    }
39
+
40
+    if (info.file.status === 'done') {
41
+      setLoading(false);
42
+      const { url, fileType } = info.file.response;
43
+      onChange(url);
44
+    }
45
+  };
46
+
47
+  return (
48
+    <Upload
49
+      listType="picture-card"
50
+      className="image-uploader"
51
+      showUploadList={false}
52
+      beforeUpload={beforeUpload}
53
+      onChange={handleChange}
54
+    >
55
+      {value ? <img src={value} alt="avatar" style={{ width: '100%', height: '100%' }} /> : <UploadButton loading={loading} />}
56
+    </Upload>
57
+  );
58
+}

+ 115
- 0
src/components/Upload/UploadImageList.jsx 查看文件

1
+
2
+import React, { useEffect, useState } from 'react';
3
+import { Modal } from 'antd'
4
+import { LoadingOutlined, PlusOutlined } from '@ant-design/icons';
5
+import Upload from './Upload';
6
+
7
+function beforeUpload(file) {
8
+  const isJpgOrPng = file.type === 'image/jpeg' || file.type === 'image/png'|| file.type === 'image/gif';
9
+  if (!isJpgOrPng) {
10
+    message.error('请上传 JPG,PNG 或 GIF 图片!');
11
+  }
12
+  const isLt10M = file.size / 1024 / 1024 < 10;
13
+  if (!isLt10M) {
14
+    message.error('图片大小必须小于 10MB!');
15
+  }
16
+
17
+  return isJpgOrPng && isLt10M;
18
+}
19
+
20
+const UploadButton = (props) => (
21
+  <div>
22
+    {props.loading ? <LoadingOutlined /> : <PlusOutlined />}
23
+    <div style={{ marginTop: 8 }}>上传</div>
24
+  </div>
25
+);
26
+
27
+export default (props) => {
28
+  const { value, onChange, input, output } = props;
29
+
30
+  const [loading, setLoading] = useState(false);
31
+  const [previewVisible, setPreviewVisible] = useState(false);
32
+  const [previewImage, setPreviewImage] = useState();
33
+  const [fileList, setFileList] = useState([]);
34
+
35
+  const handleChange = info => {
36
+    if (info.file.status === 'uploading') {
37
+      setLoading(true);
38
+      return;
39
+    }
40
+    if (info.file.status === 'error') {
41
+      setLoading(false);
42
+      return;
43
+    }
44
+
45
+    if (info.file.status === 'removed') {
46
+      toggleChange(info.fileList)
47
+    }
48
+  };
49
+
50
+  const handleSuccess = ({url}) => {
51
+    setLoading(false)
52
+    const inx = fileList?.length ? fileList.length + 1 : 1
53
+    const list = fileList.concat({
54
+      url,
55
+      uid: `new-${inx}`,
56
+      status: 'done'
57
+    })
58
+    toggleChange(list)
59
+  }
60
+
61
+  const toggleChange = (list) => {
62
+    if (output) {
63
+      onChange(list.map(x => output(x)))
64
+    } else {
65
+      onChange(list)
66
+    }
67
+  }
68
+
69
+  const handlePreview = (file) => {
70
+    setPreviewImage(file.url)
71
+    setPreviewVisible(true)
72
+  }
73
+
74
+  useEffect(() => {
75
+    if (input) {
76
+      const lst = (value || []).map((it) => {
77
+        const { uid, url } = input(it)
78
+        return {
79
+          uid,
80
+          url,
81
+          status: 'done',
82
+          raw: it,
83
+        }
84
+      })
85
+      setFileList(lst)
86
+    } else {
87
+      setFileList(value || [])
88
+    }
89
+  }, [value])
90
+
91
+  return (
92
+    <>
93
+      <Upload
94
+        listType="picture-card"
95
+        className="image-uploader"
96
+        fileList={fileList}
97
+        beforeUpload={beforeUpload}
98
+        onChange={handleChange}
99
+        onPreview={handlePreview}
100
+        onSuccess={handleSuccess}
101
+      >
102
+        <UploadButton loading={loading} />
103
+      </Upload>
104
+      <Modal
105
+        visible={previewVisible}
106
+        title="图片预览"
107
+        footer={null}
108
+        onCancel={() => setPreviewVisible(false)}
109
+      >
110
+        <img alt="example" style={{ width: '100%' }} src={previewImage} />
111
+      </Modal>
112
+    </>
113
+  );
114
+}
115
+

+ 94
- 0
src/components/Upload/UploadVideo.jsx 查看文件

1
+import React, { useState, useRef, useEffect } from 'react';
2
+import { Button } from 'antd'
3
+import Upload from './Upload';
4
+// import { uplodeNoteImage } from '@/services/note';
5
+import styles from './style.less';
6
+
7
+function beforeUpload(file) {
8
+  const isMp4 = file.type === 'video/mp4';
9
+  if (!isMp4) {
10
+    message.error('请上传 MP4 视频!');
11
+  }
12
+
13
+  return isMp4;
14
+}
15
+
16
+export default (props) => {
17
+  //onPoster 改变主图事件   isScreenshot判断父组件是否需要截屏按钮
18
+  const { value, onChange, onPoster, isScreenshot } = props;
19
+  const [loading, setLoading] = useState(false);//上传按钮loading框
20
+  const [capturing, setCapturing] = useState(false);//截屏按钮loading框
21
+  const [video, setVideo] = useState()//当前上传的视频
22
+  const [isVideoReady, setIsVideoReady] = useState(false)//判断视频是否加载成功
23
+  
24
+  const canvasRef = useRef()
25
+  const videoRef = useRef()
26
+
27
+  useEffect(() => {
28
+    setVideo(value)//父组件的视频获取成功时改变当前页面的视频
29
+  }, [value])
30
+
31
+  const handleChange = info => {
32
+    if (info.file.status === 'uploading') {
33
+      setLoading(true);
34
+      return;
35
+    }
36
+    if (info.file.status === 'error') {
37
+      setLoading(false);
38
+      return;
39
+    }
40
+    if (info.file.status === 'done') {
41
+      onChange(info.file.response);
42
+      setLoading(false);
43
+    }
44
+  };
45
+
46
+  //开始截图
47
+  const handelScreenshot = () => {
48
+    const ctx = canvasRef.current.getContext("2d");  
49
+    ctx.drawImage(videoRef.current, 0, 0);    
50
+    const image = canvasRef.current.toDataURL("image/png", 0.9)
51
+    setCapturing(true)
52
+    // uplodeNoteImage({ base64: image }).then((res) => {
53
+    //   onPoster(res)
54
+    //   setCapturing(false)
55
+    // })
56
+  }
57
+
58
+  //视频加载成功事件
59
+  const handleVideoReady = (e) => {
60
+    setIsVideoReady(true)
61
+    canvasRef.current.width = e.target.videoWidth;
62
+    canvasRef.current.height = e.target.videoHeight;
63
+  }
64
+
65
+  return (
66
+    <div className={styles['video-uploader']}>
67
+      <Upload
68
+        className="image-uploader"
69
+        showUploadList={false}
70
+        beforeUpload={beforeUpload}
71
+        onChange={handleChange}
72
+      >
73
+        <div style={{ maxWidth: '1px', maxHeight: '1px', overflow: 'hidden' }}>
74
+          <canvas ref={canvasRef}></canvas>
75
+        </div>
76
+        <Button loading={loading}>
77
+          点击上传视频 (MP4)
78
+        </Button>
79
+
80
+      </Upload>
81
+      {
82
+        isScreenshot && (
83
+          // disabled={!isVideoReady} 当视频准备成功时截屏按钮可以点击  否则不能点击
84
+          <Button loading={capturing} style={{ marginLeft: '1em' }} onClick={handelScreenshot} disabled={!isVideoReady}>截屏</Button>
85
+        )
86
+      }
87
+      {
88
+        !!video && (
89
+          <video ref={videoRef} width="320" height='500' crossOrigin='anonymous' controls src={video} onCanPlay={handleVideoReady}></video>
90
+        )
91
+      }
92
+    </div>
93
+  );
94
+}

+ 11
- 0
src/components/Upload/index.jsx 查看文件

1
+import Upload from './Upload';
2
+import UploadImage from './UploadImage';
3
+import UploadImageList from './UploadImageList';
4
+import UploadVideo from './UploadVideo';
5
+
6
+export {
7
+  Upload,
8
+  UploadImage,
9
+  UploadImageList,
10
+  UploadVideo,
11
+}

+ 32
- 0
src/components/Upload/request.js 查看文件

1
+import request from '@/utils/request';
2
+
3
+const upload = (file, fileType = 'image') => {
4
+  const formData = new FormData();
5
+  formData.append("file", file);
6
+  formData.append("fileType", fileType);
7
+
8
+  return request('/api/admin/file', {
9
+    method: 'post',
10
+    data: formData,
11
+    headers: {
12
+      'Content-Type': 'multipart/form-data',
13
+    }
14
+  });
15
+}
16
+
17
+/**
18
+ * 上传文件
19
+ * @returns 
20
+ */
21
+export function uploadFile(params) {
22
+  const { file, onSuccess, onError } = params;
23
+  upload(file).then((res) => {
24
+    onSuccess(res, file)
25
+  }).catch((e) => {
26
+    onError(e)
27
+  });
28
+
29
+  return {
30
+    abort: () => {},
31
+  };
32
+}

+ 7
- 0
src/components/Upload/style.less 查看文件

1
+.video-uploader {
2
+  video {
3
+    margin-top: 2em;
4
+    width: 100%;
5
+    max-height: 360px;
6
+  }
7
+}

+ 1
- 1
src/components/Wangeditor/index.jsx 查看文件

16
 
16
 
17
   const {
17
   const {
18
     value = '',
18
     value = '',
19
-    height = '500px',
19
+    height = '300px',
20
     onChange = (e) => {
20
     onChange = (e) => {
21
       setHtml(e);
21
       setHtml(e);
22
     },
22
     },

+ 4
- 0
src/index.less 查看文件

128
   // }
128
   // }
129
 }
129
 }
130
 
130
 
131
+.ant-app {
132
+  height: 100%;
133
+}
134
+
131
 
135
 
132
 // 兼容 360
136
 // 兼容 360
133
 .ant-pro-sider-collapsed-button {
137
 .ant-pro-sider-collapsed-button {

+ 2
- 2
src/pages/issueType/list/index.jsx 查看文件

75
           type="link"
75
           type="link"
76
           onClick={() => {
76
           onClick={() => {
77
             console.log(record, "]]");
77
             console.log(record, "]]");
78
-            navigate(`/system/issueType/edit?id=${record.typeId}`);
78
+            navigate(`/dict/issueType/edit?id=${record.typeId}`);
79
           }}
79
           }}
80
         >
80
         >
81
           编辑
81
           编辑
106
           key="1"
106
           key="1"
107
           type="primary"
107
           type="primary"
108
           onClick={() => {
108
           onClick={() => {
109
-            navigate("/system/issueType/edit");
109
+            navigate("/dict/issueType/edit");
110
           }}
110
           }}
111
         >
111
         >
112
           新增
112
           新增

+ 2
- 2
src/pages/locType/list/index.jsx 查看文件

65
           type="link"
65
           type="link"
66
           onClick={() => {
66
           onClick={() => {
67
             console.log(record, "]]");
67
             console.log(record, "]]");
68
-            navigate(`/system/locType/edit?id=${record.typeId}`);
68
+            navigate(`/dict/locType/edit?id=${record.typeId}`);
69
           }}
69
           }}
70
         >
70
         >
71
           编辑
71
           编辑
96
           key="1"
96
           key="1"
97
           type="primary"
97
           type="primary"
98
           onClick={() => {
98
           onClick={() => {
99
-            navigate("/system/locType/edit");
99
+            navigate("/dict/locType/edit");
100
           }}
100
           }}
101
         >
101
         >
102
           新增
102
           新增

+ 137
- 0
src/pages/notice/index.jsx 查看文件

1
+import React from 'react';
2
+import { ProForm, ProFormText, ProFormDigit, ProFormSelect } from '@ant-design/pro-components';
3
+import { Form, Button } from 'antd';
4
+import Curd from '@/components/Page/Curd';
5
+import Wangeditor from '@/components/Wangeditor';
6
+import { UploadImage } from '@/components/Upload';
7
+import {
8
+  getTaNotice,
9
+  getTaNoticeById,
10
+  postTaNotice,
11
+  putTaNotice,
12
+  deleteTaNotice,
13
+} from '@/service/tanotice';
14
+
15
+const request = {
16
+  list: getTaNotice,
17
+  save: postTaNotice,
18
+  update: putTaNotice,
19
+  del: deleteTaNotice,
20
+}
21
+
22
+export default (props) => {
23
+  const tableRef = React.useRef();
24
+
25
+  const onStatusChange = (row) => {
26
+    tableRef.current.showLoading('正在操作, 请稍候...');
27
+    putTaNotice(row.noticeId, {
28
+      ...row,
29
+      status: Math.abs(0 - row.status),
30
+    }).then(() => {
31
+      tableRef.current.hideLoading();
32
+      tableRef.current.reload();
33
+    }).catch(() => {
34
+      tableRef.current.hideLoading();
35
+    });
36
+  }
37
+
38
+  const columns = React.useMemo(() => [
39
+    {
40
+      title: "日期",
41
+      dataIndex: "createDate",
42
+      valueType: 'date',
43
+      hideInSearch: true,
44
+    },
45
+    {
46
+      title: "标题",
47
+      dataIndex: "title",
48
+    },
49
+    {
50
+      title: "图片",
51
+      dataIndex: "thumb",
52
+      hideInSearch: true,
53
+      render: t => !t || t.length < 3 ? null : <img src={t} alt="" style={{width: '64px', height: '64px'}} />,
54
+    },
55
+    {
56
+      title: "状态",
57
+      dataIndex: "status",
58
+      valueEnum: {
59
+        0: {
60
+          text: "未发布",
61
+          status: "Default",
62
+        },
63
+        1: {
64
+          text: "已发布",
65
+          status: "Success",
66
+        },
67
+      },
68
+    },
69
+  ], []);
70
+
71
+  const optionRender = React.useCallback((_, record) => {
72
+    return [
73
+      <Button style={{ padding: 0 }} type="link" key="pub" onClick={() => onStatusChange(record) }>
74
+        {record.status == 1 ? '取消发布' : '发布'}
75
+      </Button>
76
+    ]
77
+  }, []);
78
+
79
+  return (
80
+    <Curd
81
+      rowKey="noticeId"
82
+      tableRef={tableRef}
83
+      columns={columns}
84
+      request={request}
85
+      optionRender={optionRender}
86
+      renderFormItems={() => (
87
+        <>
88
+          <ProFormText
89
+            label="标题"
90
+            name="title"
91
+            rules={[
92
+              { required: true, message: '请输入标题' },
93
+            ]}
94
+          />
95
+          <Form.Item label="图片" name="thumb">
96
+            <UploadImage />
97
+          </Form.Item>
98
+          <Form.Item label="正文" name="content">
99
+            <Wangeditor
100
+              toolbarConfig={{
101
+                toolbarKeys: [
102
+                  'headerSelect',
103
+                  'blockquote',
104
+                  '|',
105
+                  'bold',
106
+                  'underline',
107
+                  'italic',
108
+                  'color',
109
+                  'fontSize',
110
+                  '|',
111
+                  'bulletedList',
112
+                  'numberedList',
113
+                ]
114
+              }}
115
+            />
116
+          </Form.Item>
117
+          <ProForm.Group>
118
+            <ProFormDigit
119
+              label="权重"
120
+              name="weight"
121
+              min={1}
122
+              fieldProps={{ precision: 0 }}
123
+            />
124
+            <ProFormSelect
125
+              label="状态"
126
+              name="status"
127
+              options={[
128
+                {label: '未发布', value: 0},
129
+                {label: '发布', value: 1},
130
+              ]}
131
+            />
132
+          </ProForm.Group>
133
+        </>
134
+      )}
135
+    />
136
+  )
137
+}

+ 11
- 3
src/routes/routes.jsx 查看文件

9
   EnvironmentOutlined,
9
   EnvironmentOutlined,
10
   BranchesOutlined,
10
   BranchesOutlined,
11
   NodeIndexOutlined,
11
   NodeIndexOutlined,
12
+  NotificationOutlined,
12
 } from '@ant-design/icons';
13
 } from '@ant-design/icons';
13
 import { Outlet } from 'react-router-dom';
14
 import { Outlet } from 'react-router-dom';
14
 import AuthLayout from "@/layouts/AuthLayout";
15
 import AuthLayout from "@/layouts/AuthLayout";
23
 
24
 
24
 import Index from '@/pages/index';
25
 import Index from '@/pages/index';
25
 import Home from "@/pages/sample/home";
26
 import Home from "@/pages/sample/home";
26
-import BasicForm from '@/pages/sample/form';
27
-import BasicTable from '@/pages/sample/table';
28
 
27
 
29
 import PositionList from "@/pages/position/list";
28
 import PositionList from "@/pages/position/list";
30
 import PositionEdit from "@/pages/position/edit";
29
 import PositionEdit from "@/pages/position/edit";
34
 import IssueTypeEdit from "@/pages/issueType/edit";
33
 import IssueTypeEdit from "@/pages/issueType/edit";
35
 import QuestionList from "@/pages/question/list";
34
 import QuestionList from "@/pages/question/list";
36
 import IssueList from '@/pages/issue';
35
 import IssueList from '@/pages/issue';
36
+import Notice from '@/pages/notice';
37
 
37
 
38
 /**
38
 /**
39
  * meta 用来扩展自定义数据数据
39
  * meta 用来扩展自定义数据数据
58
       menuType: 'group',
58
       menuType: 'group',
59
     },
59
     },
60
     children: [
60
     children: [
61
+      {
62
+        path: "notice",
63
+        element: <Notice />,
64
+        meta: {
65
+          title: '通知公告',
66
+          icon: <NotificationOutlined />,
67
+        }
68
+      },
61
       {
69
       {
62
         path: "issue",
70
         path: "issue",
63
         element: <IssueList />,
71
         element: <IssueList />,
86
     ],
94
     ],
87
   },
95
   },
88
   {
96
   {
89
-    path: "system",
97
+    path: "dict",
90
     element: <Outlet />,
98
     element: <Outlet />,
91
     meta: {
99
     meta: {
92
       title: '基础字典',
100
       title: '基础字典',

+ 1
- 1
vite.config.js 查看文件

5
 // https://vitejs.dev/config/
5
 // https://vitejs.dev/config/
6
 export default defineConfig({
6
 export default defineConfig({
7
   server: {
7
   server: {
8
-    port: 3000,
8
+    port: 3001,
9
     proxy: {
9
     proxy: {
10
       '/api': {
10
       '/api': {
11
         target: 'http://localhost:9087',
11
         target: 'http://localhost:9087',