Yansen пре 2 година
родитељ
комит
1a93772f85

+ 5
- 1
src/components/Page/Curd.jsx Прегледај датотеку

@@ -7,12 +7,16 @@ const drawerProps = {
7 7
   destroyOnClose: true
8 8
 }
9 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 12
   const [form] = Form.useForm();
13 13
   const listRef = React.useRef();
14 14
   const rowRef = React.useRef();
15 15
   const [open, setOpen] = React.useState(false);
16
+
17
+  if (tableRef) {
18
+    tableRef.current = listRef.current;
19
+  }
16 20
   
17 21
   const onAdd = () => {
18 22
     rowRef.current = undefined;

+ 10
- 0
src/components/Upload/Upload.jsx Прегледај датотеку

@@ -0,0 +1,10 @@
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 Прегледај датотеку

@@ -0,0 +1,58 @@
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 Прегледај датотеку

@@ -0,0 +1,115 @@
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 Прегледај датотеку

@@ -0,0 +1,94 @@
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 Прегледај датотеку

@@ -0,0 +1,11 @@
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 Прегледај датотеку

@@ -0,0 +1,32 @@
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 Прегледај датотеку

@@ -0,0 +1,7 @@
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,7 +16,7 @@ function MyEditor(props) {
16 16
 
17 17
   const {
18 18
     value = '',
19
-    height = '500px',
19
+    height = '300px',
20 20
     onChange = (e) => {
21 21
       setHtml(e);
22 22
     },

+ 4
- 0
src/index.less Прегледај датотеку

@@ -128,6 +128,10 @@ html, body, #root {
128 128
   // }
129 129
 }
130 130
 
131
+.ant-app {
132
+  height: 100%;
133
+}
134
+
131 135
 
132 136
 // 兼容 360
133 137
 .ant-pro-sider-collapsed-button {

+ 2
- 2
src/pages/issueType/list/index.jsx Прегледај датотеку

@@ -75,7 +75,7 @@ export default (props) => {
75 75
           type="link"
76 76
           onClick={() => {
77 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,7 +106,7 @@ export default (props) => {
106 106
           key="1"
107 107
           type="primary"
108 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,7 +65,7 @@ export default (props) => {
65 65
           type="link"
66 66
           onClick={() => {
67 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,7 +96,7 @@ export default (props) => {
96 96
           key="1"
97 97
           type="primary"
98 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 Прегледај датотеку

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

+ 1
- 1
vite.config.js Прегледај датотеку

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