李志伟 3 years ago
parent
commit
10fe4afd24

+ 105
- 0
src/components/ExtendContent/List.jsx View File

1
+import { useEffect, useState, forwardRef, useImperativeHandle, useRef } from "react";
2
+import { Table, Space, Button, Popconfirm } from 'antd';
3
+import { getList, remove } from '@/services/extendContent';
4
+
5
+export default forwardRef((props, ref) => {
6
+  const { targetType, targetId, onEdit } = props;
7
+
8
+  const [loading, setLoading] = useState(false)
9
+  const [list, setList] = useState([])
10
+  const refs = useRef();
11
+
12
+
13
+  const tableList = () => {
14
+    setLoading(true)
15
+    getList({
16
+      pageSize: 9999,
17
+      targetId,
18
+      targetType,
19
+    }).then((res) => {
20
+      setList(res.records || [])
21
+      setLoading(false)
22
+    }).catch(() => setLoading(false))
23
+  }
24
+
25
+  refs.current = tableList
26
+
27
+
28
+
29
+  // 父组件调用 updateList 来修改 list
30
+  useImperativeHandle(ref, () => ({
31
+    reload: refs.current,
32
+    setLoading,
33
+  }))
34
+
35
+  const handleEdit = (record) => {
36
+    onEdit(record)
37
+
38
+  }
39
+  //删除
40
+  const handleDelete = (record) => {
41
+    setLoading(true)
42
+    remove(record.extId).then(() => {
43
+      setList(list.filter(x => x.extId !== record.extId))
44
+      setLoading(false)
45
+      // refs.current.reload();
46
+
47
+    }).catch(() => setLoading(false))
48
+  }
49
+
50
+  const columns = [
51
+    {
52
+      title: '序号',
53
+      key: 'index',
54
+      dataIndex: 'index',
55
+      render: (t, record, index) => `${index + 1}`
56
+    },
57
+    {
58
+      title: '权重',
59
+      key: 'sort',
60
+      dataIndex: 'sort',
61
+      render: (t, record) => `${record.sort}`
62
+    },
63
+    {
64
+      title: '内容',
65
+      key: 'content',
66
+      dataIndex: 'content',
67
+      ellipsis: true,
68
+      render: (t, record) => {
69
+        if (record.contentType === 'text') return t;
70
+        if (record.contentType === 'image') {
71
+          return <img style={{ width: '110px' }} src={t} alt="" />
72
+        }
73
+        if (record.contentType === 'video') {
74
+          return <video style={{ width: '110px' }} src={t} />
75
+        }
76
+      }
77
+    },
78
+    {
79
+      title: '操作',
80
+      key: 'option',
81
+      render: (_, record) => {
82
+        return (
83
+          <Space>
84
+            <Button type="link" onClick={() => handleEdit(record)}>编辑</Button>
85
+            {/* <Button type="link" onClick={() => handleEdit(record)}>插入</Button> */}
86
+            <Popconfirm title="确定进行当前操作 ?" onConfirm={() => handleDelete(record)}>
87
+              <Button type="link">删除</Button>
88
+            </Popconfirm>
89
+          </Space>
90
+        )
91
+      }
92
+    },
93
+  ]
94
+
95
+
96
+  useEffect(() => {
97
+    if (targetType) {
98
+      refs.current()
99
+    }
100
+  }, [targetType])
101
+  return (
102
+
103
+    <Table loading={loading} rowKey="extId" columns={columns} dataSource={list} pagination={false} />
104
+  )
105
+})

+ 53
- 0
src/components/ExtendContent/ModalButton.jsx View File

1
+import { useEffect, useImperativeHandle, useState, forwardRef } from "react"
2
+import { Button } from 'antd'
3
+import ModalText from './ModalText'
4
+import ModalImage from './Modalimage'
5
+import ModalVideo from './ModalVideo'
6
+import { parseGeoJson } from "echarts"
7
+
8
+export default forwardRef((props, ref) => {
9
+  const { contentType, record, onChange, onClose, ...btnProps } = props
10
+
11
+  const [visible, setVisible] = useState(false)
12
+  const [content, setContent] = useState()
13
+  const [sortVale, setSortVale] = useState()
14
+
15
+
16
+  const handleClose = () => {
17
+    setContent()
18
+    setSortVale()
19
+    setVisible(false)
20
+    onClose()
21
+  }
22
+
23
+  const handleChange = (val, e) => {
24
+    onChange({
25
+      ...record || {},
26
+      content: val,
27
+      sort: e - 0,
28
+      contentType,
29
+    }, handleClose)
30
+  }
31
+
32
+  useImperativeHandle(ref, () => ({
33
+    toggle: () => {
34
+      setVisible(!visible)
35
+    }
36
+  }))
37
+
38
+  useEffect(() => {
39
+    setContent(record?.content)
40
+    setSortVale(record?.sort)
41
+  }, [record, visible])
42
+
43
+
44
+
45
+  return (
46
+    <>
47
+      <Button onClick={() => setVisible(true)} {...btnProps}>{props.children}</Button>
48
+      {contentType === 'text' && <ModalText sortVale={sortVale} visible={visible} value={content} onChange={handleChange} onCancel={handleClose} />}
49
+      {contentType === 'image' && <ModalImage sortVale={sortVale} visible={visible} value={content} onChange={handleChange} onCancel={handleClose} />}
50
+      {contentType === 'video' && <ModalVideo sortVale={sortVale} visible={visible} value={content} onChange={handleChange} onCancel={handleClose} />}
51
+    </>
52
+  )
53
+})

+ 47
- 0
src/components/ExtendContent/ModalText.jsx View File

1
+import { Input, Modal, Form, Button } from 'antd'
2
+import { useEffect, useState } from 'react'
3
+
4
+
5
+const FormItem = Form.Item
6
+export default (props) => {
7
+  const { value, visible, onChange, onCancel, sortVale } = props
8
+
9
+
10
+  const [text, setText] = useState()
11
+  const [sort, setSort] = useState()
12
+
13
+  const Submit = (e) => {
14
+    setSort(e.target.value)
15
+  }
16
+
17
+  const handleChange = (e) => {
18
+    setText(e.target.value)
19
+  }
20
+
21
+
22
+  const handleOk = () => {
23
+    onChange(text, sort)
24
+  }
25
+
26
+
27
+  useEffect(() => {
28
+    setText(value)
29
+    setSort(sortVale)
30
+
31
+  }, [value, visible, sortVale])
32
+
33
+  return (
34
+    <Modal title="请输入文字" visible={visible} onOk={handleOk} onCancel={onCancel} destroyOnClose={true}>
35
+      <Form onFinish={Submit} >
36
+        <FormItem label="内容" rules={[{ required: true, message: '请输入内容' }]} >
37
+          <Input.TextArea value={text} onChange={handleChange} />
38
+        </FormItem>
39
+        <FormItem label="权重" rules={[{ required: true, message: '请设置权重' }]}>
40
+          <Input min={0} placeholder="请输入权重" type='number' value={sort} onChange={Submit} style={{ width: '350px' }} />
41
+        </FormItem>
42
+      </Form>
43
+
44
+
45
+    </Modal>
46
+  )
47
+}

+ 43
- 0
src/components/ExtendContent/ModalVideo.jsx View File

1
+
2
+import { useEffect, useState } from 'react'
3
+import { UploadVideo } from '@/components/Upload'
4
+import { Input, Modal, Form, Button } from 'antd'
5
+
6
+
7
+const FormItem = Form.Item
8
+export default (props) => {
9
+  const { value, visible, onChange, onCancel, sortVale } = props
10
+
11
+  const [video, setVideo] = useState()
12
+  const [sort, setSort] = useState()
13
+
14
+  const Submit = (e) => {
15
+    setSort(e.target.value)
16
+  }
17
+  const handleOk = () => {
18
+    onChange(video, sort)
19
+  }
20
+  useEffect(() => {
21
+    setVideo(value)
22
+    setSort(sortVale)
23
+
24
+  }, [value, visible, sortVale])
25
+  const setld=()=>{
26
+
27
+  }
28
+  return (
29
+    <Modal title="请上传视频" visible={visible} onOk={handleOk} onCancel={onCancel} destroyOnClose={true}>
30
+
31
+      <Form onFinish={Submit} >
32
+        <FormItem label="内容" rules={[{ required: true, message: '请输入内容' }]} >
33
+          <UploadVideo value={video} onChange={setVideo}  poster='' setPoster={setld} setld={setld}/>
34
+
35
+        </FormItem>
36
+        <FormItem label="权重" rules={[{ required: true, message: '请设置权重' }]}>
37
+          <Input min={0} placeholder="请输入权重" type='number' value={sort} onChange={Submit} style={{ width: '350px' }} />
38
+        </FormItem>
39
+      </Form>
40
+    </Modal>
41
+
42
+  )
43
+}

+ 40
- 0
src/components/ExtendContent/Modalimage.jsx View File

1
+import { useEffect, useState } from 'react'
2
+import { UploadImage } from '@/components/Upload'
3
+import { Input, Modal, Form, Button } from 'antd'
4
+
5
+
6
+const FormItem = Form.Item
7
+export default (props) => {
8
+  const { value, visible, onChange, onCancel, sortVale } = props
9
+
10
+  const [image, setImage] = useState()
11
+  const [sort, setSort] = useState()
12
+
13
+  const Submit = (e) => {
14
+    setSort(e.target.value)
15
+  }
16
+  const handleOk = () => {
17
+    onChange(image, sort)
18
+  }
19
+
20
+
21
+
22
+  useEffect(() => {
23
+    setImage(value)
24
+    setSort(sortVale)
25
+  }, [value, visible, sortVale])
26
+
27
+
28
+  return (
29
+    <Modal title="请上传图片" visible={visible} onOk={handleOk} onCancel={onCancel} destroyOnClose={true}>
30
+      <Form onFinish={Submit} >
31
+        <FormItem label="内容" rules={[{ required: true, message: '请输入内容' }]} >
32
+          <UploadImage value={image} onChange={setImage} />
33
+        </FormItem>
34
+        <FormItem label="权重" rules={[{ required: true, message: '请设置权重' }]}>
35
+          <Input min={0} placeholder="请输入权重" type='number' value={sort} onChange={Submit} style={{ width: '350px' }} />
36
+        </FormItem>
37
+      </Form>
38
+    </Modal>
39
+  )
40
+}

+ 87
- 0
src/components/ExtendContent/index.jsx View File

1
+import { Button, message, Space } from 'antd';
2
+import { PlusOutlined } from '@ant-design/icons';
3
+import { useRef, useState, } from 'react';
4
+import ModalButton from './ModalButton';
5
+import List from './List'
6
+import { save, update } from '@/services/extendContent'
7
+
8
+
9
+
10
+export default (props) => {
11
+  const { onCancel, targetId, targetType } = props
12
+
13
+  const [record, setRecord] = useState()
14
+  const listRef = useRef()
15
+  const textBtnRef = useRef()
16
+  const imageBtnRef = useRef()
17
+  const videoBtnRef = useRef()
18
+
19
+
20
+
21
+  const handleChange = (row, next) => {
22
+    const data = {
23
+      ...(row || {}),
24
+      targetId,
25
+      targetType,
26
+    }
27
+
28
+    if (data.extId) {
29
+      // 编辑提交
30
+
31
+      update(data.extId, data,).then(() => {
32
+        listRef.current.reload()
33
+        next()
34
+
35
+        message.success('更新数据成功')
36
+
37
+      })
38
+
39
+    } else if (data.content) {
40
+      // 新增提交
41
+      listRef.current.setLoading(true)
42
+      save(data).then((res) => {
43
+        listRef.current.reload()
44
+        next()
45
+
46
+        message.success('保存数据成功')
47
+      })
48
+    }
49
+    else {
50
+      next()
51
+    }
52
+  }
53
+
54
+  const handleEdit = (row) => {
55
+    setRecord(row)
56
+    switch (row.contentType) {
57
+      case 'text':
58
+        textBtnRef.current.toggle();
59
+        break;
60
+      case 'image':
61
+        imageBtnRef.current.toggle();
62
+        break;
63
+      case 'video':
64
+        videoBtnRef.current.toggle();
65
+        break;
66
+      default:
67
+        break;
68
+    }
69
+  }
70
+
71
+
72
+  const handleClose = () => {
73
+    setRecord()
74
+  }
75
+
76
+  return (
77
+    <div>
78
+      <Space size="large" style={{ marginBottom: '18px' }}>
79
+        <ModalButton ref={textBtnRef} type="dashed" icon={<PlusOutlined />} record={record} contentType="text" onChange={handleChange} onClose={handleClose}>文字</ModalButton>
80
+        <ModalButton ref={imageBtnRef} type="dashed" icon={<PlusOutlined />} record={record} contentType="image" onChange={handleChange} onClose={handleClose}>图片</ModalButton>
81
+        <ModalButton ref={videoBtnRef} type="dashed" icon={<PlusOutlined />} record={record} contentType="video" onChange={handleChange} onClose={handleClose}>视频</ModalButton>
82
+      </Space>
83
+      <List ref={listRef} targetType={targetType} onEdit={handleEdit} targetId={targetId} />
84
+      <Button onClick={onCancel} style={{ marginTop: '2em', float: 'right' }}>返回</Button>
85
+    </div>
86
+  )
87
+}

+ 10
- 0
src/components/Upload/Upload.jsx View File

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

+ 57
- 0
src/components/Upload/UploadImage.jsx View File

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 isJpgOrPng = file.type === 'image/jpeg' || file.type === 'image/png'||file.type==='image/gif';
7
+  if (!isJpgOrPng) {
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 isJpgOrPng && 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
+      onChange(info.file.response);
43
+    }
44
+  };
45
+
46
+  return (
47
+    <Upload
48
+      listType="picture-card"
49
+      className="image-uploader"
50
+      showUploadList={false}
51
+      beforeUpload={beforeUpload}
52
+      onChange={handleChange}
53
+    >
54
+      {value ? <img src={value} alt="avatar" style={{ width: '100%', height: '100%' }} /> : <UploadButton loading={loading} />}
55
+    </Upload>
56
+  );
57
+}

+ 115
- 0
src/components/Upload/UploadImageList.jsx View File

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
+

+ 95
- 0
src/components/Upload/UploadVideo.jsx View File

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

+ 11
- 0
src/components/Upload/index.jsx View File

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
+}

+ 7
- 0
src/components/Upload/style.less View File

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