浏览代码

修复富文本相关问题

master
suomingxiang 5 个月前
父节点
当前提交
b8b0f47d04

+ 115
- 148
src/pages/JoinZZ/biddingDocument/edit.tsx 查看文件

2
 import {
2
 import {
3
   ProForm,
3
   ProForm,
4
   ProFormText,
4
   ProFormText,
5
-  ProFormUploadButton,
6
-  ProFormSelect,
7
   ProFormDatePicker,
5
   ProFormDatePicker,
8
   ProFormTextArea,
6
   ProFormTextArea,
9
-  ProFormRadio
10
 } from '@ant-design/pro-components';
7
 } from '@ant-design/pro-components';
11
-import { Form, Modal, Drawer, message, Upload, Space, Button, Switch,Input,Tooltip } from 'antd';
12
-import {InfoCircleOutlined} from '@ant-design/icons';
13
-
14
-import { useIntl, FormattedMessage } from '@umijs/max';
15
-import { DictValueEnumObj } from '@/components/DictTag';
16
-import Editor from '@/components/ueditor';
8
+import { Form, Drawer, Space, Button } from 'antd';
9
+import ReactQuill from 'react-quill';
10
+import 'react-quill/dist/quill.snow.css';
11
+import { useIntl } from '@umijs/max';
17
 import { upload } from '@/services/JoinZZ/biddingDocument';
12
 import { upload } from '@/services/JoinZZ/biddingDocument';
18
-import { getDataEnumList } from '@/services/system/Enum';
19
-import Quill from "quill";
20
-import "quill/dist/quill.snow.css";
13
+// import Quill from "quill";
14
+// import "quill/dist/quill.snow.css";
21
 import moment from 'moment';
15
 import moment from 'moment';
22
-import { trim } from 'lodash';
23
-let editor: any;
24
 
16
 
25
 export type BiddingDocumentProps = {
17
 export type BiddingDocumentProps = {
26
   onCancel: (flag?: boolean, formVals?: any) => void;
18
   onCancel: (flag?: boolean, formVals?: any) => void;
27
   onSubmit: (values: any) => Promise<void>;
19
   onSubmit: (values: any) => Promise<void>;
28
   open: boolean;
20
   open: boolean;
29
   currentRow: any;
21
   currentRow: any;
30
-  date: any;
31
-  title: string;
32
-  content: string;
33
-  surface: string;
34
-  surfaceUrl: string;
35
-};
36
-
22
+}
37
 let toolbarOptions = [
23
 let toolbarOptions = [
38
   ['bold', 'italic', 'underline', 'strike'],        // 切换按钮
24
   ['bold', 'italic', 'underline', 'strike'],        // 切换按钮
39
   ['blockquote', 'code-block'],
25
   ['blockquote', 'code-block'],
40
   ['link', 'image'],
26
   ['link', 'image'],
41
-
42
-
43
-  [{ 'header': 1 }, { 'header': 2 }],               // 用户自定义按钮值
44
   [{ 'list': 'ordered' }, { 'list': 'bullet' }],
27
   [{ 'list': 'ordered' }, { 'list': 'bullet' }],
45
   [{ 'script': 'sub' }, { 'script': 'super' }],      // 上标/下标
28
   [{ 'script': 'sub' }, { 'script': 'super' }],      // 上标/下标
46
   [{ 'indent': '-1' }, { 'indent': '+1' }],          // 减少缩进/缩进
29
   [{ 'indent': '-1' }, { 'indent': '+1' }],          // 减少缩进/缩进
47
   [{ 'direction': 'rtl' }],                         // 文本下划线
30
   [{ 'direction': 'rtl' }],                         // 文本下划线
48
-
49
-
50
   [{ 'size': ['small', false, 'large', 'huge'] }],  // 用户自定义下拉
31
   [{ 'size': ['small', false, 'large', 'huge'] }],  // 用户自定义下拉
51
   [{ 'header': [1, 2, 3, 4, 5, 6, false] }],
32
   [{ 'header': [1, 2, 3, 4, 5, 6, false] }],
52
-
53
-
54
   [{ 'color': [] }, { 'background': [] }],          // 主题默认下拉,使用主题提供的值
33
   [{ 'color': [] }, { 'background': [] }],          // 主题默认下拉,使用主题提供的值
55
   [{ 'font': [] }],
34
   [{ 'font': [] }],
56
   [{ 'align': [] }],
35
   [{ 'align': [] }],
59
   ['clean'],                                         // 清除格式
38
   ['clean'],                                         // 清除格式
60
 
39
 
61
 ];
40
 ];
41
+const modules = {
42
+  toolbar: {
43
+    container: toolbarOptions, // 使用自定义工具栏选项
44
+  },
45
+};
62
 
46
 
63
-function image(){
64
-  var _this3 = this;
65
-
66
-  var fileInput = this.container.querySelector('input.ql-image[type=file]');
67
-  fileInput = null
68
-  if (fileInput == null) {
69
-    fileInput = document.createElement('input');
70
-    fileInput.setAttribute('type', 'file');
71
-    fileInput.setAttribute('accept', 'image/png, image/gif, image/jpeg, image/bmp, image/x-icon');
72
-    fileInput.classList.add('ql-image');
73
-    fileInput.addEventListener('change', async function () {
74
-      if (fileInput.files != null && fileInput.files[0] != null) {
75
-        let index = editor.getSelection() && editor.getSelection().index ? editor.getSelection().index : 0;
76
-
77
-        let file = fileInput.files[0]
78
-        const formData = new FormData();
79
-        formData.append('file', file, file.name);
80
-        const res = await upload(formData)
81
-        editor.insertEmbed(index, 'image', res.data.url);
82
-        var reader = new FileReader();
83
-        reader.onload = function (e) {
84
-          fileInput.value = "";
85
-        };
86
-
87
-      }
88
-    });
89
-    this.container.appendChild(fileInput);
90
-  }
91
-  fileInput.click();
92
-}
47
+// function image(){
48
+//   var _this3 = this;
49
+
50
+//   var fileInput = this.container.querySelector('input.ql-image[type=file]');
51
+//   fileInput = null
52
+//   if (fileInput == null) {
53
+//     fileInput = document.createElement('input');
54
+//     fileInput.setAttribute('type', 'file');
55
+//     fileInput.setAttribute('accept', 'image/png, image/gif, image/jpeg, image/bmp, image/x-icon');
56
+//     fileInput.classList.add('ql-image');
57
+//     fileInput.addEventListener('change', async function () {
58
+//       if (fileInput.files != null && fileInput.files[0] != null) {
59
+//         let index = editor.getSelection() && editor.getSelection().index ? editor.getSelection().index : 0;
60
+
61
+//         let file = fileInput.files[0]
62
+//         const formData = new FormData();
63
+//         formData.append('file', file, file.name);
64
+//         const res = await upload(formData)
65
+//         editor.insertEmbed(index, 'image', res.data.url);
66
+//         var reader = new FileReader();
67
+//         reader.onload = function (e) {
68
+//           fileInput.value = "";
69
+//         };
70
+
71
+//       }
72
+//     });
73
+//     this.container.appendChild(fileInput);
74
+//   }
75
+//   fileInput.click();
76
+// }
93
 const BiddingDocumentForm: React.FC<BiddingDocumentProps> = (props) => {
77
 const BiddingDocumentForm: React.FC<BiddingDocumentProps> = (props) => {
94
 
78
 
95
   const [form] = Form.useForm();
79
   const [form] = Form.useForm();
96
   const intl = useIntl();
80
   const intl = useIntl();
97
   let editorRef = useRef<HTMLDivElement>(null)
81
   let editorRef = useRef<HTMLDivElement>(null)
98
-
82
+  const [editWho, setEditWho] = useState('')
83
+  const [userIndex, setUserIndex] = useState(0)
84
+  const [editorHtml, setEditorHtml] = useState('');
99
   useEffect(() => {
85
   useEffect(() => {
100
-    console.log(props)
101
     form.resetFields();
86
     form.resetFields();
102
     if(!props.open) return;
87
     if(!props.open) return;
103
     // const { title, content, surface, surfaceUrl, date } = props;
88
     // const { title, content, surface, surfaceUrl, date } = props;
129
         }
114
         }
130
       ])
115
       ])
131
     }
116
     }
132
-    if (editorRef && editorRef.current && editorRef.current.children[0]) {
133
-      editorRef.current.children[0].innerHTML = content
134
-    }
135
-
117
+    setEditorHtml(content || '')
136
   }, [props.open]);
118
   }, [props.open]);
137
-
119
+  const base64ToFile = (base64, fileName = `${Math.random()}`) => {
120
+    let arr = base64.split(',')
121
+    console.log(arr[0])
122
+    let mime = arr[0].match(/:(.*?);/)[1]
123
+    let bstr = atob(arr[1])
124
+    let n = bstr.length
125
+    let u8arr = new Uint8Array(n)
126
+    while (n--) {
127
+      u8arr[n] = bstr.charCodeAt(n)
128
+    }
129
+    return new File([u8arr], `${fileName}.${mime.split('/')[1]}`, {
130
+      type: mime,
131
+    })
132
+  }
138
   useEffect(() => {
133
   useEffect(() => {
139
-    if (!props.open) return;
140
-    var Parchment = Quill.import('parchment');
141
-
142
-    let config = {
143
-      scope: Parchment.Scope.INLINE,
144
-      // 会有下拉框列表
145
-      whitelist: ['20px', '22px', '24px', '26px', '28px', '30px', '32px', '34px', '36px', '38px', '40px', '42px', '44px']
146
-      //可设置成没有下拉框的,但会导致无法清除样式
147
-      // whitelist: ['1px solid #000000']  
148
-    };
149
-
150
-    let wordBox = new Parchment.Attributor.Style('wordBox', 'line-height', config);
151
-
152
-    Quill.register(wordBox, true);
153
-    editor = new Quill('#editor', {
154
-      modules: {
155
-        toolbar: toolbarOptions,
156
-        clipboard: {
157
-          dangerouslyPasteHTML: true
158
-        }
159
-      },
160
-      theme: 'snow'
161
-    });
162
-
163
-    // 工具栏附件按钮
164
-    let a = document.querySelectorAll(".ql-wordBox")[0]
165
-    let b = document.querySelectorAll(".ql-wordBox")[1]
166
-    //添加默认显示内容
167
-    let stit = document.createElement('span')
168
-    stit.innerHTML = intl.formatMessage({ id: 'public.lineSpacing' });
169
-    stit.setAttribute('style', 'margin-right:20px;line-height: 24px;')
170
-    a.children[0].insertBefore(stit, a.children[0].children[0])
171
-
172
-    // 遍历下拉框列表添加值
173
-    let i = a.children[1].children.length
174
-    while (i) {
175
-      i--;
176
-      let item = a.children[1].children[i]
177
-      item.innerHTML = item.dataset.value ? item.dataset.value : '无'
134
+    if(editorRef?.current){
135
+      const quill = editorRef.current.getEditor()
136
+      if (editWho === 'api' && userIndex !== 0) {
137
+        quill.setSelection(userIndex + 2)
138
+      }
178
     }
139
     }
179
-
180
-    // console.log('editor', editor)
181
-    var toolbar = editor.getModule('toolbar');
182
-
183
-    toolbar.image2 = image
184
-    // console.log("toolbar.handlers.image", toolbar.handlers.image)
185
-    toolbar.addHandler('image', (e) => {
186
-      toolbar.image2()
187
-      // 
188
-
189
-    });
190
-
191
-    // toolbar.addHandler('wordBox', (e) => {
192
-    //   console.log('e-----++++', e)
193
-    //   // 
194
-
195
-    // });
196
-
197
-  }, [props.open]);
198
-
140
+  }, [editorHtml,userIndex])
141
+ 
199
 
142
 
200
   const [fileList, setFileList] = useState<any>([]);
143
   const [fileList, setFileList] = useState<any>([]);
201
   const [surface, setSurface] = useState<any>('');
144
   const [surface, setSurface] = useState<any>('');
204
   const handleOk = async () => {
147
   const handleOk = async () => {
205
     let data = form.getFieldsValue()
148
     let data = form.getFieldsValue()
206
     let val = await form.validateFields()
149
     let val = await form.validateFields()
207
-    
208
-    let htmlStr = editorRef.current?.children[0].innerHTML
150
+    const htmlStr = editorHtml.replaceAll('"', "'")
151
+    // let htmlStr = editorRef.current?.children[0].innerHTML
209
 
152
 
210
-    htmlStr = htmlStr?.replaceAll('"', "'")
211
-    let content = editor.getContents()
153
+    // htmlStr = htmlStr?.replaceAll('"', "'")
154
+    // let content = editor.getContents()
212
     // console.log('htmlStr----', htmlStr)
155
     // console.log('htmlStr----', htmlStr)
213
-    editor.setContents(content)
156
+    // editor.setContents(content)
214
     data.date = moment(data.date).format('YYYY-MM-DD');
157
     data.date = moment(data.date).format('YYYY-MM-DD');
215
     if(checkTop){
158
     if(checkTop){
216
       data.top = 1;
159
       data.top = 1;
229
     })
172
     })
230
     setFileList([]);
173
     setFileList([]);
231
     setCheckTop(false)
174
     setCheckTop(false)
232
-    editor.clipboard.dangerouslyPasteHTML(0, '')
233
-    editor.setContents([])
175
+    // editor.clipboard.dangerouslyPasteHTML(0, '')
176
+    // editor.setContents([])
177
+    setEditorHtml('');
234
     props.onSubmit(formData);
178
     props.onSubmit(formData);
235
   };
179
   };
236
   const handleCancel = () => {
180
   const handleCancel = () => {
241
     })
185
     })
242
     setFileList([]);
186
     setFileList([]);
243
     setCheckTop(false)
187
     setCheckTop(false)
244
-    editor.clipboard.dangerouslyPasteHTML(0, '')
245
-    editor.setContents([])
188
+    // editor.clipboard.dangerouslyPasteHTML(0, '')
189
+    // editor.setContents([])
190
+    setEditorHtml('');
246
     props.onCancel();
191
     props.onCancel();
247
 
192
 
248
   };
193
   };
249
 
194
 
250
-  const handleChange = async (res) => {
251
-    const { fileList } = res;
252
-    setFileList(fileList);
253
-  };
254
-
255
-  const handleBeforeUpload = async (file: RcFile) => {
256
-    const formData = new FormData();
257
-    formData.append('file', file, file.name);
258
-    const res = await upload(formData);
259
-    if (res?.data?.uuid) {
260
-      setSurface(res.data.uuid)
195
+  const handleChangeQuill = (content, delta, source, editor) => {
196
+    let quill = editorRef.current.getEditor()
197
+    quill.focus()
198
+    let delta_ops = delta.ops
199
+    let quilContent = editor.getContents()
200
+    setEditorHtml(content)
201
+    const range = quill.getSelection()
202
+    if (range) {
203
+      if (range.length == 0 && range.index !== 0) {
204
+        console.log('User cursor is at index', range.index)
205
+        setUserIndex(range.index)
206
+      } else {
207
+        const text = quill.getText(range.index, range.length)
208
+        console.log('User has highlighted: ', text)
209
+      }
210
+    } else {
211
+      console.log('User cursor is not in editor')
261
     }
212
     }
262
-    return false;
263
-  };
264
-
213
+    setEditWho(source)
214
+    if (delta_ops && delta_ops.length > 0) {
215
+      quilContent.ops.map((item) => {
216
+        if (item.insert) {
217
+          let imgStr = item.insert.image
218
+          if (imgStr && imgStr?.includes('data:image/')) {
219
+            let file = base64ToFile(imgStr)
220
+            let formData = new FormData()
221
+            formData.append('file', file)
222
+            upload(formData).then((res) => {
223
+              item.insert.image = res.data.url
224
+              quill.setContents(quilContent)
225
+            })
226
+          }
227
+        }
228
+      })
229
+    }
230
+  }
265
 
231
 
266
   return (
232
   return (
267
     <Drawer
233
     <Drawer
347
           style: { width: 95 }
313
           style: { width: 95 }
348
         }}
314
         }}
349
         >
315
         >
350
-          <div id='editor' style={{ width: '70vw', height: '500px' }} ref={editorRef}></div>
316
+          {/* <div id='editor' style={{ width: '70vw', height: '500px' }} ref={editorRef}></div> */}
317
+          <ReactQuill ref={editorRef} placeholder='请输入...' theme='snow' className="ql-editor" modules={modules} value={editorHtml} onChange={handleChangeQuill} />
351
         </ProForm.Item>
318
         </ProForm.Item>
352
       </ProForm>
319
       </ProForm>
353
     </Drawer>
320
     </Drawer>

+ 79
- 123
src/pages/JoinZZ/vacancy/edit.tsx 查看文件

1
 import React, { useEffect, useState, useRef } from 'react';
1
 import React, { useEffect, useState, useRef } from 'react';
2
 import {
2
 import {
3
   ProForm,
3
   ProForm,
4
-  ProFormText,
5
-  ProFormUploadButton,
6
   ProFormSelect,
4
   ProFormSelect,
7
-  ProFormDatePicker,
8
-  ProFormRadio
9
 } from '@ant-design/pro-components';
5
 } from '@ant-design/pro-components';
10
-import { Form, Modal, Drawer, message, Upload, Space, Button, Switch,Input,Tooltip } from 'antd';
11
-import {InfoCircleOutlined} from '@ant-design/icons';
6
+import { Form, Drawer, Space, Button } from 'antd';
12
 
7
 
13
-import { useIntl, FormattedMessage } from '@umijs/max';
14
-import { DictValueEnumObj } from '@/components/DictTag';
15
-import Editor from '@/components/ueditor';
8
+import { useIntl } from '@umijs/max';
16
 import { upload } from '@/services/JoinZZ/vacancy';
9
 import { upload } from '@/services/JoinZZ/vacancy';
17
 import { getDataEnumList } from '@/services/system/Enum';
10
 import { getDataEnumList } from '@/services/system/Enum';
18
-import Quill from "quill";
19
-import "quill/dist/quill.snow.css";
11
+// import Quill from "quill";
12
+// import "quill/dist/quill.snow.css";
13
+import ReactQuill from 'react-quill';
14
+import 'react-quill/dist/quill.snow.css';
20
 import moment from 'moment';
15
 import moment from 'moment';
21
-import { trim } from 'lodash';
22
-let editor: any;
23
 
16
 
24
 export type VacancyProps = {
17
 export type VacancyProps = {
25
   onCancel: (flag?: boolean, formVals?: any) => void;
18
   onCancel: (flag?: boolean, formVals?: any) => void;
26
   onSubmit: (values: any) => Promise<void>;
19
   onSubmit: (values: any) => Promise<void>;
27
   open: boolean;
20
   open: boolean;
28
   currentRow: any;
21
   currentRow: any;
29
-  date: any;
30
-  title: string;
31
-  content: string;
32
-  surface: string;
33
-  surfaceUrl: string;
34
 };
22
 };
35
 
23
 
36
 let toolbarOptions = [
24
 let toolbarOptions = [
58
   ['clean'],                                         // 清除格式
46
   ['clean'],                                         // 清除格式
59
 
47
 
60
 ];
48
 ];
49
+const modules = {
50
+  toolbar: {
51
+    container: toolbarOptions, // 使用自定义工具栏选项
52
+  },
53
+};
61
 
54
 
62
-function image(){
63
-  var _this3 = this;
64
-
65
-  var fileInput = this.container.querySelector('input.ql-image[type=file]');
66
-  fileInput = null
67
-  if (fileInput == null) {
68
-    fileInput = document.createElement('input');
69
-    fileInput.setAttribute('type', 'file');
70
-    fileInput.setAttribute('accept', 'image/png, image/gif, image/jpeg, image/bmp, image/x-icon');
71
-    fileInput.classList.add('ql-image');
72
-    fileInput.addEventListener('change', async function () {
73
-      if (fileInput.files != null && fileInput.files[0] != null) {
74
-        let index = editor.getSelection() && editor.getSelection().index ? editor.getSelection().index : 0;
75
-
76
-        let file = fileInput.files[0]
77
-        const formData = new FormData();
78
-        formData.append('file', file, file.name);
79
-        const res = await upload(formData)
80
-        editor.insertEmbed(index, 'image', res.data.url);
81
-        var reader = new FileReader();
82
-        reader.onload = function (e) {
83
-          fileInput.value = "";
84
-        };
85
-
86
-      }
87
-    });
88
-    this.container.appendChild(fileInput);
89
-  }
90
-  fileInput.click();
91
-}
92
 const VacancyForm: React.FC<VacancyProps> = (props) => {
55
 const VacancyForm: React.FC<VacancyProps> = (props) => {
93
 
56
 
94
   const [form] = Form.useForm();
57
   const [form] = Form.useForm();
95
   const intl = useIntl();
58
   const intl = useIntl();
96
   let editorRef = useRef<HTMLDivElement>(null)
59
   let editorRef = useRef<HTMLDivElement>(null)
97
-
60
+  const [editWho, setEditWho] = useState('')
61
+  const [userIndex, setUserIndex] = useState(0)
62
+  const [editorHtml, setEditorHtml] = useState('');
98
   useEffect(() => {
63
   useEffect(() => {
99
-    console.log(props)
100
     form.resetFields();
64
     form.resetFields();
101
     if(!props.open) return;
65
     if(!props.open) return;
102
     // const { title, content, surface, surfaceUrl, date } = props;
66
     // const { title, content, surface, surfaceUrl, date } = props;
128
         }
92
         }
129
       ])
93
       ])
130
     }
94
     }
131
-    if (editorRef && editorRef.current && editorRef.current.children[0]) {
132
-      editorRef.current.children[0].innerHTML = content
133
-    }
134
 
95
 
96
+    setEditorHtml(content || '')
135
   }, [props.open]);
97
   }, [props.open]);
136
-
98
+  const base64ToFile = (base64, fileName = `${Math.random()}`) => {
99
+    let arr = base64.split(',')
100
+    console.log(arr[0])
101
+    let mime = arr[0].match(/:(.*?);/)[1]
102
+    let bstr = atob(arr[1])
103
+    let n = bstr.length
104
+    let u8arr = new Uint8Array(n)
105
+    while (n--) {
106
+      u8arr[n] = bstr.charCodeAt(n)
107
+    }
108
+    return new File([u8arr], `${fileName}.${mime.split('/')[1]}`, {
109
+      type: mime,
110
+    })
111
+  }
137
   useEffect(() => {
112
   useEffect(() => {
138
-    if (!props.open) return;
139
-    var Parchment = Quill.import('parchment');
140
-
141
-    let config = {
142
-      scope: Parchment.Scope.INLINE,
143
-      // 会有下拉框列表
144
-      whitelist: ['20px', '22px', '24px', '26px', '28px', '30px', '32px', '34px', '36px', '38px', '40px', '42px', '44px']
145
-      //可设置成没有下拉框的,但会导致无法清除样式
146
-      // whitelist: ['1px solid #000000']  
147
-    };
148
-
149
-    let wordBox = new Parchment.Attributor.Style('wordBox', 'line-height', config);
150
-
151
-    Quill.register(wordBox, true);
152
-    editor = new Quill('#editor', {
153
-      modules: {
154
-        toolbar: toolbarOptions,
155
-        clipboard: {
156
-          dangerouslyPasteHTML: true
157
-        }
158
-      },
159
-      theme: 'snow'
160
-    });
161
-
162
-    // 工具栏附件按钮
163
-    let a = document.querySelectorAll(".ql-wordBox")[0]
164
-    let b = document.querySelectorAll(".ql-wordBox")[1]
165
-    //添加默认显示内容
166
-    let stit = document.createElement('span')
167
-    stit.innerHTML = intl.formatMessage({ id: 'public.lineSpacing' });
168
-    stit.setAttribute('style', 'margin-right:20px;line-height: 24px;')
169
-    a.children[0].insertBefore(stit, a.children[0].children[0])
170
-
171
-    // 遍历下拉框列表添加值
172
-    let i = a.children[1].children.length
173
-    while (i) {
174
-      i--;
175
-      let item = a.children[1].children[i]
176
-      item.innerHTML = item.dataset.value ? item.dataset.value : '无'
113
+    if(editorRef?.current){
114
+      const quill = editorRef.current.getEditor()
115
+      if (editWho === 'api' && userIndex !== 0) {
116
+        quill.setSelection(userIndex + 2)
117
+      }
177
     }
118
     }
178
-
179
-    // console.log('editor', editor)
180
-    var toolbar = editor.getModule('toolbar');
181
-
182
-    toolbar.image2 = image
183
-    // console.log("toolbar.handlers.image", toolbar.handlers.image)
184
-    toolbar.addHandler('image', (e) => {
185
-      toolbar.image2()
186
-      // 
187
-
188
-    });
189
-
190
-    // toolbar.addHandler('wordBox', (e) => {
191
-    //   console.log('e-----++++', e)
192
-    //   // 
193
-
194
-    // });
195
-
196
-  }, [props.open]);
197
-
198
-
119
+  }, [editorHtml,userIndex])
199
   const [fileList, setFileList] = useState<any>([]);
120
   const [fileList, setFileList] = useState<any>([]);
200
   const [surface, setSurface] = useState<any>('');
121
   const [surface, setSurface] = useState<any>('');
201
 
122
 
204
     let data = form.getFieldsValue()
125
     let data = form.getFieldsValue()
205
     let val = await form.validateFields()
126
     let val = await form.validateFields()
206
     
127
     
207
-    let htmlStr = editorRef.current?.children[0].innerHTML
128
+    const htmlStr = editorHtml.replaceAll('"', "'")
208
 
129
 
209
-    htmlStr = htmlStr?.replaceAll('"', "'")
210
-    let content = editor.getContents()
130
+    // let content = editor.getContents()
211
     // console.log('htmlStr----', htmlStr)
131
     // console.log('htmlStr----', htmlStr)
212
-    editor.setContents(content)
132
+    // editor.setContents(content)
213
     data.date = moment(data.date).format('YYYY-MM-DD');
133
     data.date = moment(data.date).format('YYYY-MM-DD');
214
     if(checkTop){
134
     if(checkTop){
215
       data.top = 1;
135
       data.top = 1;
227
       digest: ""
147
       digest: ""
228
     })
148
     })
229
     setFileList([]);
149
     setFileList([]);
150
+    setEditorHtml('');
230
     setCheckTop(false)
151
     setCheckTop(false)
231
-    editor.clipboard.dangerouslyPasteHTML(0, '')
232
-    editor.setContents([])
233
     props.onSubmit(formData);
152
     props.onSubmit(formData);
234
   };
153
   };
235
   const handleCancel = () => {
154
   const handleCancel = () => {
240
     })
159
     })
241
     setFileList([]);
160
     setFileList([]);
242
     setCheckTop(false)
161
     setCheckTop(false)
243
-    editor.clipboard.dangerouslyPasteHTML(0, '')
244
-    editor.setContents([])
162
+    setEditorHtml('');
245
     props.onCancel();
163
     props.onCancel();
246
 
164
 
247
   };
165
   };
261
     return false;
179
     return false;
262
   };
180
   };
263
 
181
 
182
+  const handleChangeQuill = (content, delta, source, editor) => {
183
+    let quill = editorRef.current.getEditor()
184
+    quill.focus()
185
+    let delta_ops = delta.ops
186
+    let quilContent = editor.getContents()
187
+    setEditorHtml(content)
188
+    const range = quill.getSelection()
189
+    if (range) {
190
+      if (range.length == 0 && range.index !== 0) {
191
+        console.log('User cursor is at index', range.index)
192
+        setUserIndex(range.index)
193
+      } else {
194
+        const text = quill.getText(range.index, range.length)
195
+        console.log('User has highlighted: ', text)
196
+      }
197
+    } else {
198
+      console.log('User cursor is not in editor')
199
+    }
200
+    setEditWho(source)
201
+    if (delta_ops && delta_ops.length > 0) {
202
+      quilContent.ops.map((item) => {
203
+        if (item.insert) {
204
+          let imgStr = item.insert.image
205
+          if (imgStr && imgStr?.includes('data:image/')) {
206
+            let file = base64ToFile(imgStr)
207
+            let formData = new FormData()
208
+            formData.append('file', file)
209
+            upload(formData).then((res) => {
210
+              item.insert.image = res.data.url
211
+              quill.setContents(quilContent)
212
+            })
213
+          }
214
+        }
215
+      })
216
+    }
217
+  }
218
+
264
 
219
 
265
   return (
220
   return (
266
     <Drawer
221
     <Drawer
313
           style: { width: 95 }
268
           style: { width: 95 }
314
         }}
269
         }}
315
         >
270
         >
316
-          <div id='editor' style={{ width: '70vw', height: '500px' }} ref={editorRef}></div>
271
+          {/* <div id='editor' style={{ width: '70vw', height: '500px' }} ref={editorRef}></div> */}
272
+          <ReactQuill ref={editorRef} placeholder='请输入...' theme='snow' className="ql-editor" modules={modules} value={editorHtml} onChange={handleChangeQuill} />
317
         </ProForm.Item>
273
         </ProForm.Item>
318
       </ProForm>
274
       </ProForm>
319
     </Drawer>
275
     </Drawer>

+ 240
- 0
src/pages/News/NewsList/edit copy.tsx 查看文件

1
+import React, { useEffect, useState, useRef } from 'react';
2
+import {
3
+  ProForm,
4
+  ProFormText,
5
+  ProFormTextArea,
6
+  ProFormSelect,
7
+  ProFormDatePicker,
8
+  ProFormSwitch,
9
+  ProFormDateTimePicker
10
+} from '@ant-design/pro-components';
11
+import { Form, Drawer, Upload, Space, Button } from 'antd';
12
+import { useIntl } from '@umijs/max';
13
+import { upload } from '@/services/news/news';
14
+import { getDataEnumList } from '@/services/system/Enum';
15
+import Quill from "quill";
16
+import "quill/dist/quill.snow.css";
17
+import moment from 'moment';
18
+export type NewsProps = {
19
+  onCancel: (flag?: boolean, formVals?: any) => void;
20
+  onSubmit: (values: any) => Promise<void>;
21
+  open: boolean;
22
+  currentRow: any;
23
+};
24
+let toolbarOptions = [
25
+  ['bold', 'italic', 'underline', 'strike'],
26
+  ['blockquote', 'code-block'],
27
+  ['link', 'image'],
28
+  [{ 'header': 1 }, { 'header': 2 }],
29
+  [{ 'list': 'ordered' }, { 'list': 'bullet' }],
30
+  [{ 'script': 'sub' }, { 'script': 'super' }],
31
+  [{ 'indent': '-1' }, { 'indent': '+1' }],
32
+  [{ 'direction': 'rtl' }],
33
+  [{ 'size': ['small', false, 'large', 'huge'] }],
34
+  [{ 'header': [1, 2, 3, 4, 5, 6, false] }],
35
+  [{ 'color': [] }, { 'background': [] }],
36
+  [{ 'font': [] }],
37
+  [{ 'align': [] }],
38
+  [{ wordBox: ['20px', '22px', '24px', '26px', '28px', '30px', '32px', '34px', '36px', '38px', '40px', '42px', '44px'] }],
39
+  ['clean'],
40
+];
41
+const NewsForm: React.FC<NewsProps> = (props) => {
42
+  const [form] = Form.useForm();
43
+  const intl = useIntl();
44
+  const editorRef = useRef<Quill | null>(null); // 使用 useRef 来存储 Quill 实例
45
+  const [fileList, setFileList] = useState<any>([]);
46
+  const [surface, setSurface] = useState<any>('');
47
+  
48
+  useEffect(() => {
49
+    form.resetFields();
50
+    if (!props.open) return;
51
+    if (!props.currentRow) return;
52
+    const { digest, top, key, content, surface, surfaceUrl, date, title, source, column, newsTop, createTime } = props.currentRow;
53
+    
54
+    form.setFieldsValue({
55
+      digest,
56
+      top,
57
+      key,
58
+      column,
59
+      source,
60
+      createTime,
61
+      newsTop: newsTop ? true : false,
62
+      title: title,
63
+      date: date,
64
+    });
65
+    setSurface(surface);
66
+    if (surfaceUrl && surface) {
67
+      setFileList([{ uid: surface, thumbUrl: surfaceUrl }]);
68
+    }
69
+    if (editorRef.current) {
70
+      editorRef.current.root.innerHTML = content; // 使用 editorRef 来设置内容
71
+    }
72
+  }, [props.open,props.currentRow]);
73
+  useEffect(() => {
74
+    const Parchment = Quill.import('parchment');
75
+    const config = {
76
+      scope: Parchment.Scope.INLINE,
77
+      whitelist: ['20px', '22px', '24px', '26px', '28px', '30px', '32px', '34px', '36px', '38px', '40px', '42px', '44px']
78
+    };
79
+    const wordBox = new Parchment.Attributor.Style('wordBox', 'line-height', config);
80
+    Quill.register(wordBox, true);
81
+    editorRef.current = new Quill('#editor', {
82
+      modules: {
83
+        toolbar: toolbarOptions,
84
+        clipboard: {
85
+          dangerouslyPasteHTML: true
86
+        }
87
+      },
88
+      theme: 'snow'
89
+    });
90
+    try {
91
+        const toolbar = editorRef.current.getModule('toolbar');
92
+        toolbar.image2 = image;
93
+        toolbar.addHandler('image', () => {
94
+          toolbar.image2();
95
+        });
96
+    } catch (error) {
97
+        console.log(error)
98
+    }
99
+  }, []);
100
+  const handleOk = async () => {
101
+    const data = form.getFieldsValue();
102
+    await form.validateFields();
103
+    const htmlStr = editorRef.current?.root.innerHTML.replaceAll('"', "'");
104
+    data.date = moment(data.date).format('YYYY-MM-DD');
105
+    data.createTime = moment(data.createTime).format('YYYY-MM-DD HH:mm:ss');
106
+    data.newsTop = data.newsTop ? 1 : 0;
107
+    const formData = {
108
+      ...data,
109
+      surface,
110
+      content: htmlStr
111
+    };
112
+    form.resetFields();
113
+    setFileList([]);
114
+    editorRef.current?.clipboard.dangerouslyPasteHTML(0, '');
115
+    editorRef.current?.setContents([]);
116
+    props.onSubmit(formData);
117
+  };
118
+  const handleCancel = () => {
119
+    form.resetFields();
120
+    setFileList([]);
121
+    editorRef.current?.clipboard.dangerouslyPasteHTML(0, '');
122
+    editorRef.current?.setContents([]);
123
+    props.onCancel();
124
+  };
125
+  const handleChange = (res) => {
126
+    const { fileList } = res;
127
+    setFileList(fileList);
128
+  };
129
+  const handleBeforeUpload = async (file: RcFile) => {
130
+    const formData = new FormData();
131
+    formData.append('file', file, file.name);
132
+    const res = await upload(formData);
133
+    if (res?.data?.uuid) {
134
+      setSurface(res.data.uuid);
135
+    }
136
+    return false;
137
+  };
138
+  return (
139
+    <Drawer
140
+      width={'80%'}
141
+      title={props.currentRow ? '新闻编辑' : '新建新闻'}
142
+      forceRender
143
+      open={props.open}
144
+      destroyOnClose
145
+      onClose={handleCancel}
146
+      extra={
147
+        <Space>
148
+          <Button onClick={handleCancel}>取消</Button>
149
+          <Button type="primary" onClick={handleOk}>确认</Button>
150
+        </Space>
151
+      }
152
+    >
153
+      <ProForm
154
+        form={form}
155
+        grid={true}
156
+        submitter={false}
157
+        layout="horizontal"
158
+      >
159
+        <ProFormText
160
+          name="title"
161
+          label='标题'
162
+          labelCol={{ style: { width: 95 } }}
163
+          placeholder='请输入标题'
164
+          rules={[{ required: true, message: '请输入标题!' }]}
165
+        />
166
+        <ProForm.Item
167
+          label={'封面图'}
168
+          extra={'图片尺寸1218*915'}
169
+          labelCol={{ style: { width: 95 } }}
170
+          rules={[{ required: true }]}
171
+        >
172
+          <Upload
173
+            listType="picture-card"
174
+            fileList={fileList}
175
+            maxCount={1}
176
+            onChange={handleChange}
177
+            beforeUpload={handleBeforeUpload}
178
+          >
179
+            {fileList.length < 1 && '+' + intl.formatMessage({ id: 'public.uploadImg' })}
180
+          </Upload>
181
+        </ProForm.Item>
182
+        <ProFormSwitch labelCol={{ style: { width: 95 } }} name="newsTop" label="置顶" />
183
+        <ProFormDatePicker
184
+          name="date"
185
+          label='日期'
186
+          fieldProps={{ format: (value) => value.format('YYYY-MM-DD') }}
187
+          labelCol={{ style: { width: 95 } }}
188
+          rules={[{ required: true }]}
189
+        />
190
+        <ProFormDateTimePicker
191
+          name="createTime"
192
+          label='发稿时间'
193
+          fieldProps={{ format: (value) => value.format('YYYY-MM-DD HH:mm:ss') }}
194
+          labelCol={{ style: { width: 95 } }}
195
+          rules={[{ required: true }]}
196
+        />
197
+        <ProFormSelect
198
+          name="column"
199
+          label='新闻分类'
200
+          request={() =>
201
+            getDataEnumList({ enumUuid: '2024810214616101' }).then((res) => {
202
+              return res.rows.map(item => ({
203
+                label: item.dataName,
204
+                value: item.uuid,
205
+              }));
206
+            })
207
+          }
208
+          labelCol={{ style: { width: 95 } }}
209
+          placeholder={'请选择新闻分类'}
210
+        />
211
+        <ProFormTextArea
212
+          name="digest"
213
+          label='摘要'
214
+          labelCol={{ style: { width: 95 } }}
215
+          placeholder={'请输入摘要'}
216
+        />
217
+        <ProFormText
218
+          name="key"
219
+          label='网页关键字'
220
+          labelCol={{ style: { width: 95 } }}
221
+          placeholder={'请输入页面关键字'}
222
+        />
223
+        <ProFormText
224
+          name="source"
225
+          label='来源'
226
+          labelCol={{ style: { width: 95 } }}
227
+          placeholder={'中泽集团'}
228
+          initialValue={'中泽集团'}
229
+        />
230
+        <ProForm.Item 
231
+          label={'新闻内容'} 
232
+          labelCol={{ style: { width: 95 } }}
233
+        >
234
+          <div id='editor' style={{ width: '70vw', height: '500px' }}></div> {/* 移除 ref,因为我们使用了 useRef */}
235
+        </ProForm.Item>
236
+      </ProForm>
237
+    </Drawer>
238
+  );
239
+};
240
+export default NewsForm;

+ 135
- 286
src/pages/News/NewsList/edit.tsx 查看文件

3
   ProForm,
3
   ProForm,
4
   ProFormText,
4
   ProFormText,
5
   ProFormTextArea,
5
   ProFormTextArea,
6
-  ProFormUploadButton,
7
   ProFormSelect,
6
   ProFormSelect,
8
   ProFormDatePicker,
7
   ProFormDatePicker,
9
-  ProFormRadio,
10
   ProFormSwitch,
8
   ProFormSwitch,
11
   ProFormDateTimePicker
9
   ProFormDateTimePicker
12
 } from '@ant-design/pro-components';
10
 } from '@ant-design/pro-components';
13
-import { Form, Modal, Drawer, message, Upload, Space, Button, Switch,Input,Tooltip } from 'antd';
14
-import {InfoCircleOutlined} from '@ant-design/icons';
15
-
16
-import { useIntl, FormattedMessage } from '@umijs/max';
17
-import { DictValueEnumObj } from '@/components/DictTag';
18
-import Editor from '@/components/ueditor';
11
+import { Form, Drawer, Upload, Space, Button } from 'antd';
12
+import { useIntl } from '@umijs/max';
13
+import ReactQuill from 'react-quill';
14
+import 'react-quill/dist/quill.snow.css';
19
 import { upload } from '@/services/news/news';
15
 import { upload } from '@/services/news/news';
20
 import { getDataEnumList } from '@/services/system/Enum';
16
 import { getDataEnumList } from '@/services/system/Enum';
21
-import Quill from "quill";
22
-import "quill/dist/quill.snow.css";
23
-import moment from 'moment';
24
-import { trim } from 'lodash';
25
-let editor: any;
26
 
17
 
18
+import moment from 'moment';
27
 export type NewsProps = {
19
 export type NewsProps = {
28
   onCancel: (flag?: boolean, formVals?: any) => void;
20
   onCancel: (flag?: boolean, formVals?: any) => void;
29
   onSubmit: (values: any) => Promise<void>;
21
   onSubmit: (values: any) => Promise<void>;
30
   open: boolean;
22
   open: boolean;
31
   currentRow: any;
23
   currentRow: any;
32
-  date: any;
33
-  title: string;
34
-  content: string;
35
-  surface: string;
36
-  surfaceUrl: string;
37
 };
24
 };
38
-
39
 let toolbarOptions = [
25
 let toolbarOptions = [
40
-  ['bold', 'italic', 'underline', 'strike'],        // 切换按钮
26
+  ['bold', 'italic', 'underline', 'strike'],
41
   ['blockquote', 'code-block'],
27
   ['blockquote', 'code-block'],
42
   ['link', 'image'],
28
   ['link', 'image'],
43
-
44
-
45
-  [{ 'header': 1 }, { 'header': 2 }],               // 用户自定义按钮值
46
   [{ 'list': 'ordered' }, { 'list': 'bullet' }],
29
   [{ 'list': 'ordered' }, { 'list': 'bullet' }],
47
-  [{ 'script': 'sub' }, { 'script': 'super' }],      // 上标/下标
48
-  [{ 'indent': '-1' }, { 'indent': '+1' }],          // 减少缩进/缩进
49
-  [{ 'direction': 'rtl' }],                         // 文本下划线
50
-
51
-
52
-  [{ 'size': ['small', false, 'large', 'huge'] }],  // 用户自定义下拉
30
+  [{ 'script': 'sub' }, { 'script': 'super' }],
31
+  [{ 'indent': '-1' }, { 'indent': '+1' }],
32
+  [{ 'direction': 'rtl' }],
33
+  [{ 'size': ['small', false, 'large', 'huge'] }],
53
   [{ 'header': [1, 2, 3, 4, 5, 6, false] }],
34
   [{ 'header': [1, 2, 3, 4, 5, 6, false] }],
54
-
55
-
56
-  [{ 'color': [] }, { 'background': [] }],          // 主题默认下拉,使用主题提供的值
35
+  [{ 'color': [] }, { 'background': [] }],
57
   [{ 'font': [] }],
36
   [{ 'font': [] }],
58
   [{ 'align': [] }],
37
   [{ 'align': [] }],
59
-  // [{ 'wordBox': ['20px', '22px', '24px', '26px', '28px', '28px', '30px'] }],
60
   [{ wordBox: ['20px', '22px', '24px', '26px', '28px', '30px', '32px', '34px', '36px', '38px', '40px', '42px', '44px'] }],
38
   [{ wordBox: ['20px', '22px', '24px', '26px', '28px', '30px', '32px', '34px', '36px', '38px', '40px', '42px', '44px'] }],
61
-  ['clean'],                                         // 清除格式
62
-
39
+  ['clean'],
63
 ];
40
 ];
64
-
65
-function image(){
66
-  var _this3 = this;
67
-
68
-  var fileInput = this.container.querySelector('input.ql-image[type=file]');
69
-  fileInput = null
70
-  if (fileInput == null) {
71
-    fileInput = document.createElement('input');
72
-    fileInput.setAttribute('type', 'file');
73
-    fileInput.setAttribute('accept', 'image/png, image/gif, image/jpeg, image/bmp, image/x-icon');
74
-    fileInput.classList.add('ql-image');
75
-    fileInput.addEventListener('change', async function () {
76
-      if (fileInput.files != null && fileInput.files[0] != null) {
77
-        let index = editor.getSelection() && editor.getSelection().index ? editor.getSelection().index : 0;
78
-
79
-        let file = fileInput.files[0]
80
-        const formData = new FormData();
81
-        formData.append('file', file, file.name);
82
-        const res = await upload(formData)
83
-        editor.insertEmbed(index, 'image', res.data.url);
84
-        var reader = new FileReader();
85
-        reader.onload = function (e) {
86
-          fileInput.value = "";
87
-        };
88
-
89
-      }
90
-    });
91
-    this.container.appendChild(fileInput);
92
-  }
93
-  fileInput.click();
94
-}
41
+const modules = {
42
+  toolbar: {
43
+    container: toolbarOptions, // 使用自定义工具栏选项
44
+  },
45
+};
95
 const NewsForm: React.FC<NewsProps> = (props) => {
46
 const NewsForm: React.FC<NewsProps> = (props) => {
96
-
97
   const [form] = Form.useForm();
47
   const [form] = Form.useForm();
98
   const intl = useIntl();
48
   const intl = useIntl();
99
   let editorRef = useRef<HTMLDivElement>(null)
49
   let editorRef = useRef<HTMLDivElement>(null)
100
-
50
+  const [fileList, setFileList] = useState<any>([]);
51
+  const [surface, setSurface] = useState<any>('');
52
+  const [editWho, setEditWho] = useState('')
53
+  const [userIndex, setUserIndex] = useState(0)
54
+  const [editorHtml, setEditorHtml] = useState('');
55
+;
101
   useEffect(() => {
56
   useEffect(() => {
102
     form.resetFields();
57
     form.resetFields();
103
-    if(!props.open) return;
104
-    // const { title, content, surface, surfaceUrl, date } = props;
105
-    if(!props.currentRow) return;
106
-    const { digest, top,key,content,surface,surfaceUrl,date,title,source,column,newsTop,createTime} = props.currentRow;
107
-    if (props.currentRow) {
108
-      form.setFieldsValue({
109
-        digest,
110
-        top,
111
-        key,
112
-        column,
113
-        source,
114
-        createTime,
115
-        newsTop:newsTop?true:false,
116
-      })
117
-      // if (top) {
118
-      //   setCheckTop(1)
119
-      // } else {
120
-      //   setCheckTop(0)
121
-      // }
122
-    }
123
-
124
-    setSurface(surface)
58
+    if (!props.open) return;
59
+    if (!props.currentRow) return;
60
+    const { digest, top, key, content, surface, surfaceUrl, date, title, source, column, newsTop, createTime } = props.currentRow;
61
+    
125
     form.setFieldsValue({
62
     form.setFieldsValue({
63
+      digest,
64
+      top,
65
+      key,
66
+      column,
67
+      source,
68
+      createTime,
69
+      newsTop: newsTop ? true : false,
126
       title: title,
70
       title: title,
127
       date: date,
71
       date: date,
128
-    })
129
-    if (surfaceUrl && surface) {
130
-      setFileList([
131
-        {
132
-          uid: surface,
133
-          thumbUrl: surfaceUrl
134
-        }
135
-      ])
136
-    }
137
-    if (editorRef && editorRef.current && editorRef.current.children[0]) {
138
-      editorRef.current.children[0].innerHTML = content
139
-    }
140
-
141
-  }, [props.open]);
142
-
143
-  useEffect(() => {
144
-    var Parchment = Quill.import('parchment');
145
-
146
-    let config = {
147
-      scope: Parchment.Scope.INLINE,
148
-      // 会有下拉框列表
149
-      whitelist: ['20px', '22px', '24px', '26px', '28px', '30px', '32px', '34px', '36px', '38px', '40px', '42px', '44px']
150
-      //可设置成没有下拉框的,但会导致无法清除样式
151
-      // whitelist: ['1px solid #000000']  
152
-    };
153
-
154
-    let wordBox = new Parchment.Attributor.Style('wordBox', 'line-height', config);
155
-
156
-    Quill.register(wordBox, true);
157
-    editor = new Quill('#editor', {
158
-      modules: {
159
-        toolbar: toolbarOptions,
160
-        clipboard: {
161
-          dangerouslyPasteHTML: true
162
-        }
163
-      },
164
-      theme: 'snow'
165
     });
72
     });
166
-    console.log('editor',editor)
167
-    // 工具栏附件按钮
168
-    let a = document.querySelectorAll(".ql-wordBox")[0]
169
-    let b = document.querySelectorAll(".ql-wordBox")[1]
170
-    //添加默认显示内容
171
-    let stit = document.createElement('span')
172
-    stit.innerHTML = intl.formatMessage({ id: 'public.lineSpacing' });
173
-    stit.setAttribute('style', 'margin-right:20px;line-height: 24px;')
174
-    a.children[0].insertBefore(stit, a.children[0].children[0])
175
-
176
-    // 遍历下拉框列表添加值
177
-    let i = a.children[1].children.length
178
-    while (i) {
179
-      i--;
180
-      let item = a.children[1].children[i]
181
-      item.innerHTML = item.dataset.value ? item.dataset.value : '无'
73
+    setSurface(surface);
74
+    if (surfaceUrl && surface) {
75
+      setFileList([{ uid: surface, thumbUrl: surfaceUrl }]);
182
     }
76
     }
77
+    setEditorHtml(content || '')
78
+  }, [props.open,props.currentRow]);
183
 
79
 
184
-    // console.log('editor', editor)
185
-    var toolbar = editor.getModule('toolbar');
186
-
187
-    toolbar.image2 = image
188
-    // console.log("toolbar.handlers.image", toolbar.handlers.image)
189
-    toolbar.addHandler('image', (e) => {
190
-      toolbar.image2()
191
-      // 
192
-
193
-    });
194
-
195
-    // toolbar.addHandler('wordBox', (e) => {
196
-    //   console.log('e-----++++', e)
197
-    //   // 
198
-
199
-    // });
200
-
201
-  }, []);
202
-
203
-
204
-  const [fileList, setFileList] = useState<any>([]);
205
-  const [surface, setSurface] = useState<any>('');
206
-
207
-  const [checkTop, setCheckTop] = useState<any>('')
208
   const handleOk = async () => {
80
   const handleOk = async () => {
209
-    let data = form.getFieldsValue()
210
-    let val = await form.validateFields()
81
+    const data = form.getFieldsValue();
82
+    await form.validateFields();
211
     
83
     
212
-    let htmlStr = editorRef.current?.children[0].innerHTML
213
-
214
-    htmlStr = htmlStr?.replaceAll('"', "'")
215
-    let content = editor.getContents()
216
-    // console.log('htmlStr----', htmlStr)
217
-    editor.setContents(content)
84
+    const htmlStr = editorHtml.replaceAll('"', "'");
218
     data.date = moment(data.date).format('YYYY-MM-DD');
85
     data.date = moment(data.date).format('YYYY-MM-DD');
219
     data.createTime = moment(data.createTime).format('YYYY-MM-DD HH:mm:ss');
86
     data.createTime = moment(data.createTime).format('YYYY-MM-DD HH:mm:ss');
220
-    if(data.newsTop){
221
-      data.newsTop = 1;
222
-    }else{
223
-      data.newsTop = 0;
224
-    } 
225
-    // data.top = checkTop;
226
-    let formData = {
87
+    data.newsTop = data.newsTop ? 1 : 0;
88
+    const formData = {
227
       ...data,
89
       ...data,
228
       surface,
90
       surface,
229
       content: htmlStr
91
       content: htmlStr
230
-    }
231
-    form.setFieldsValue({
232
-      title: '',
233
-      digest: ""
234
-    })
92
+    };
93
+    form.resetFields();
235
     setFileList([]);
94
     setFileList([]);
236
-    setCheckTop(false)
237
-    editor.clipboard.dangerouslyPasteHTML(0, '')
238
-    editor.setContents([])
95
+    setEditorHtml('');
239
     props.onSubmit(formData);
96
     props.onSubmit(formData);
240
   };
97
   };
241
   const handleCancel = () => {
98
   const handleCancel = () => {
242
-    form.setFieldsValue({
243
-      title: '',
244
-      digest: "",
245
-      top: ""
246
-    })
99
+    form.resetFields();
247
     setFileList([]);
100
     setFileList([]);
248
-    setCheckTop(false)
249
-    editor.clipboard.dangerouslyPasteHTML(0, '')
250
-    editor.setContents([])
101
+    setEditorHtml('');
251
     props.onCancel();
102
     props.onCancel();
252
-
253
   };
103
   };
254
-
255
-  const handleChange = async (res) => {
104
+  const base64ToFile = (base64, fileName = `${Math.random()}`) => {
105
+    let arr = base64.split(',')
106
+    console.log(arr[0])
107
+    let mime = arr[0].match(/:(.*?);/)[1]
108
+    let bstr = atob(arr[1])
109
+    let n = bstr.length
110
+    let u8arr = new Uint8Array(n)
111
+    while (n--) {
112
+      u8arr[n] = bstr.charCodeAt(n)
113
+    }
114
+    return new File([u8arr], `${fileName}.${mime.split('/')[1]}`, {
115
+      type: mime,
116
+    })
117
+  }
118
+  useEffect(() => {
119
+    if(editorRef?.current){
120
+      const quill = editorRef.current.getEditor()
121
+      if (editWho === 'api' && userIndex !== 0) {
122
+        quill.setSelection(userIndex + 2)
123
+      }
124
+    }
125
+  }, [editorHtml])
126
+  const handleChange = (res) => {
256
     const { fileList } = res;
127
     const { fileList } = res;
257
     setFileList(fileList);
128
     setFileList(fileList);
258
   };
129
   };
259
-
260
   const handleBeforeUpload = async (file: RcFile) => {
130
   const handleBeforeUpload = async (file: RcFile) => {
261
     const formData = new FormData();
131
     const formData = new FormData();
262
     formData.append('file', file, file.name);
132
     formData.append('file', file, file.name);
263
     const res = await upload(formData);
133
     const res = await upload(formData);
264
     if (res?.data?.uuid) {
134
     if (res?.data?.uuid) {
265
-      setSurface(res.data.uuid)
135
+      setSurface(res.data.uuid);
266
     }
136
     }
267
     return false;
137
     return false;
268
   };
138
   };
269
-
270
-
139
+  const handleChangeQuill = (content, delta, source, editor) => {
140
+    let quill = editorRef.current.getEditor()
141
+    quill.focus()
142
+    let delta_ops = delta.ops
143
+    let quilContent = editor.getContents()
144
+    setEditorHtml(content)
145
+    const range = quill.getSelection()
146
+    if (range) {
147
+      if (range.length == 0 && range.index !== 0) {
148
+        console.log('User cursor is at index', range.index)
149
+        setUserIndex(range.index)
150
+      } else {
151
+        const text = quill.getText(range.index, range.length)
152
+        console.log('User has highlighted: ', text)
153
+      }
154
+    } else {
155
+      console.log('User cursor is not in editor')
156
+    }
157
+    setEditWho(source)
158
+    if (delta_ops && delta_ops.length > 0) {
159
+      quilContent.ops.map((item) => {
160
+        if (item.insert) {
161
+          let imgStr = item.insert.image
162
+          if (imgStr && imgStr?.includes('data:image/')) {
163
+            let file = base64ToFile(imgStr)
164
+            let formData = new FormData()
165
+            formData.append('file', file)
166
+            upload(formData).then((res) => {
167
+              item.insert.image = res.data.url
168
+              quill.setContents(quilContent)
169
+            })
170
+          }
171
+        }
172
+      })
173
+    }
174
+  }
271
   return (
175
   return (
272
     <Drawer
176
     <Drawer
273
       width={'80%'}
177
       width={'80%'}
292
         <ProFormText
196
         <ProFormText
293
           name="title"
197
           name="title"
294
           label='标题'
198
           label='标题'
295
-          labelCol={{
296
-            style: { width: 95 }
297
-          }}
199
+          labelCol={{ style: { width: 95 } }}
298
           placeholder='请输入标题'
200
           placeholder='请输入标题'
299
-          rules={[
300
-            {
301
-              required: true,
302
-              message: '请输入标题!',
303
-            },
304
-          ]}
201
+          rules={[{ required: true, message: '请输入标题!' }]}
305
         />
202
         />
306
         <ProForm.Item
203
         <ProForm.Item
307
           label={'封面图'}
204
           label={'封面图'}
308
           extra={'图片尺寸1218*915'}
205
           extra={'图片尺寸1218*915'}
309
-          labelCol={{
310
-            style: { width: 95 }
311
-          }}
312
-          rules={[
313
-            {
314
-              required: true
315
-            }
316
-          ]
317
-          }
206
+          labelCol={{ style: { width: 95 } }}
207
+          rules={[{ required: true }]}
318
         >
208
         >
319
           <Upload
209
           <Upload
320
-            listType="picture-card" // 设置为图片卡片模式
210
+            listType="picture-card"
321
             fileList={fileList}
211
             fileList={fileList}
322
             maxCount={1}
212
             maxCount={1}
323
             onChange={handleChange}
213
             onChange={handleChange}
324
             beforeUpload={handleBeforeUpload}
214
             beforeUpload={handleBeforeUpload}
325
           >
215
           >
326
-            {fileList?.length < 1 && '+' + intl.formatMessage({ id: 'public.uploadImg' })}
216
+            {fileList.length < 1 && '+' + intl.formatMessage({ id: 'public.uploadImg' })}
327
           </Upload>
217
           </Upload>
328
         </ProForm.Item>
218
         </ProForm.Item>
329
-        <ProFormSwitch labelCol={{
330
-            style: { width: 95 }
331
-          }} name="newsTop" label="置顶" />
219
+        <ProFormSwitch labelCol={{ style: { width: 95 } }} name="newsTop" label="置顶" />
332
         <ProFormDatePicker
220
         <ProFormDatePicker
333
           name="date"
221
           name="date"
334
           label='日期'
222
           label='日期'
335
-          fieldProps={{
336
-            format: (value) => value.format('YYYY-MM-DD'),
337
-          }}
338
-          labelCol={{
339
-            style: { width: 95 }
340
-          }}
341
-          rules={[
342
-            {
343
-              required: true
344
-            }
345
-          ]
346
-          }
223
+          fieldProps={{ format: (value) => value.format('YYYY-MM-DD') }}
224
+          labelCol={{ style: { width: 95 } }}
225
+          rules={[{ required: true }]}
347
         />
226
         />
348
         <ProFormDateTimePicker
227
         <ProFormDateTimePicker
349
           name="createTime"
228
           name="createTime"
350
           label='发稿时间'
229
           label='发稿时间'
351
-          fieldProps={{
352
-            format: (value) => value.format('YYYY-MM-DD HH:mm:ss'),
353
-          }}
354
-          labelCol={{
355
-            style: { width: 95 }
356
-          }}
357
-          rules={[
358
-            {
359
-              required: true
360
-            }
361
-          ]
362
-          }
230
+          fieldProps={{ format: (value) => value.format('YYYY-MM-DD HH:mm:ss') }}
231
+          labelCol={{ style: { width: 95 } }}
232
+          rules={[{ required: true }]}
363
         />
233
         />
364
         <ProFormSelect
234
         <ProFormSelect
365
           name="column"
235
           name="column"
366
-          key="column"
367
           label='新闻分类'
236
           label='新闻分类'
368
           request={() =>
237
           request={() =>
369
-            getDataEnumList({enumUuid: '2024810214616101'}).then((res) => {
370
-              let tList = []
371
-              res.rows.forEach(item => {
372
-                tList.push({
373
-                  label: item.dataName,
374
-                  value: item.uuid,
375
-                })
376
-              })
377
-              return tList;
238
+            getDataEnumList({ enumUuid: '2024810214616101' }).then((res) => {
239
+              return res.rows.map(item => ({
240
+                label: item.dataName,
241
+                value: item.uuid,
242
+              }));
378
             })
243
             })
379
-
380
           }
244
           }
381
-          labelCol={{
382
-            style: { width: 95 }
383
-          }}
245
+          labelCol={{ style: { width: 95 } }}
384
           placeholder={'请选择新闻分类'}
246
           placeholder={'请选择新闻分类'}
385
-
386
         />
247
         />
387
         <ProFormTextArea
248
         <ProFormTextArea
388
           name="digest"
249
           name="digest"
389
           label='摘要'
250
           label='摘要'
390
-          labelCol={{
391
-            style: { width: 95 }
392
-          }}
251
+          labelCol={{ style: { width: 95 } }}
393
           placeholder={'请输入摘要'}
252
           placeholder={'请输入摘要'}
394
         />
253
         />
395
-         
396
         <ProFormText
254
         <ProFormText
397
           name="key"
255
           name="key"
398
           label='网页关键字'
256
           label='网页关键字'
399
-          labelCol={{
400
-            style: { width: 95 }
401
-          }}
257
+          labelCol={{ style: { width: 95 } }}
402
           placeholder={'请输入页面关键字'}
258
           placeholder={'请输入页面关键字'}
403
         />
259
         />
404
-
405
         <ProFormText
260
         <ProFormText
406
           name="source"
261
           name="source"
407
           label='来源'
262
           label='来源'
408
-          labelCol={{
409
-            style: { width: 95 }
410
-          }}
263
+          labelCol={{ style: { width: 95 } }}
411
           placeholder={'中泽集团'}
264
           placeholder={'中泽集团'}
412
           initialValue={'中泽集团'}
265
           initialValue={'中泽集团'}
413
         />
266
         />
414
-
415
         <ProForm.Item 
267
         <ProForm.Item 
416
-        label={'新闻内容'} 
417
-        labelCol={{
418
-          style: { width: 95 }
419
-        }}
268
+          label={'新闻内容'} 
269
+          labelCol={{ style: { width: 95 } }}
420
         >
270
         >
421
-          <div id='editor' style={{ width: '70vw', height: '500px' }} ref={editorRef}></div>
271
+          <ReactQuill ref={editorRef} placeholder='请输入...' theme='snow' className="ql-editor" modules={modules} value={editorHtml} onChange={handleChangeQuill} />
422
         </ProForm.Item>
272
         </ProForm.Item>
423
       </ProForm>
273
       </ProForm>
424
     </Drawer>
274
     </Drawer>
425
   );
275
   );
426
 };
276
 };
427
-
428
-export default NewsForm;
277
+export default NewsForm;

+ 16
- 0
src/pages/Other/footer/index.tsx 查看文件

227
                             labelCol={{
227
                             labelCol={{
228
                                 style: { width: 95 }
228
                                 style: { width: 95 }
229
                             }}
229
                             }}
230
+                        />
231
+                         <ProFormText
232
+                            name="reportPhone"
233
+                            label="反舞弊举报电话"
234
+                            placeholder={'请输入'}
235
+                            labelCol={{
236
+                                style: { width: 110 }
237
+                            }}
238
+                        />
239
+                         <ProFormText
240
+                            name="reportEmail"
241
+                            label="反舞弊举报邮箱"
242
+                            placeholder={'请输入'}
243
+                            labelCol={{
244
+                                style: { width: 110 }
245
+                            }}
230
                         />
246
                         />
231
                         <ProForm.Item
247
                         <ProForm.Item
232
                             label={'微信二维码'}
248
                             label={'微信二维码'}

+ 115
- 120
src/pages/PartyWork/partyWork/edit.tsx 查看文件

7
 } from '@ant-design/pro-components';
7
 } from '@ant-design/pro-components';
8
 import { Form, Drawer, Upload, Space, Button } from 'antd';
8
 import { Form, Drawer, Upload, Space, Button } from 'antd';
9
 
9
 
10
-import { useIntl, FormattedMessage } from '@umijs/max';
11
-import Editor from '@/components/ueditor';
10
+import { useIntl } from '@umijs/max';
12
 import { upload } from '@/services/partyWork/partyWork';
11
 import { upload } from '@/services/partyWork/partyWork';
13
 import { getDataEnumList } from '@/services/system/Enum';
12
 import { getDataEnumList } from '@/services/system/Enum';
14
-import Quill from "quill";
15
-import "quill/dist/quill.snow.css";
13
+// import Quill from "quill";
14
+// import "quill/dist/quill.snow.css";
15
+import ReactQuill from 'react-quill';
16
+import 'react-quill/dist/quill.snow.css';
16
 import moment from 'moment';
17
 import moment from 'moment';
17
-let editor: any;
18
-
19
 export type PartyWorkProps = {
18
 export type PartyWorkProps = {
20
   onCancel: (flag?: boolean, formVals?: any) => void;
19
   onCancel: (flag?: boolean, formVals?: any) => void;
21
   onSubmit: (values: any) => Promise<void>;
20
   onSubmit: (values: any) => Promise<void>;
27
   ['bold', 'italic', 'underline', 'strike'],        // 切换按钮
26
   ['bold', 'italic', 'underline', 'strike'],        // 切换按钮
28
   ['blockquote', 'code-block'],
27
   ['blockquote', 'code-block'],
29
   ['link', 'image'],
28
   ['link', 'image'],
30
-
31
-
32
-  [{ 'header': 1 }, { 'header': 2 }],               // 用户自定义按钮值
33
   [{ 'list': 'ordered' }, { 'list': 'bullet' }],
29
   [{ 'list': 'ordered' }, { 'list': 'bullet' }],
34
   [{ 'script': 'sub' }, { 'script': 'super' }],      // 上标/下标
30
   [{ 'script': 'sub' }, { 'script': 'super' }],      // 上标/下标
35
   [{ 'indent': '-1' }, { 'indent': '+1' }],          // 减少缩进/缩进
31
   [{ 'indent': '-1' }, { 'indent': '+1' }],          // 减少缩进/缩进
36
   [{ 'direction': 'rtl' }],                         // 文本下划线
32
   [{ 'direction': 'rtl' }],                         // 文本下划线
37
-
38
-
39
   [{ 'size': ['small', false, 'large', 'huge'] }],  // 用户自定义下拉
33
   [{ 'size': ['small', false, 'large', 'huge'] }],  // 用户自定义下拉
40
   [{ 'header': [1, 2, 3, 4, 5, 6, false] }],
34
   [{ 'header': [1, 2, 3, 4, 5, 6, false] }],
41
-
42
-
43
   [{ 'color': [] }, { 'background': [] }],          // 主题默认下拉,使用主题提供的值
35
   [{ 'color': [] }, { 'background': [] }],          // 主题默认下拉,使用主题提供的值
44
   [{ 'font': [] }],
36
   [{ 'font': [] }],
45
   [{ 'align': [] }],
37
   [{ 'align': [] }],
48
   ['clean'],                                         // 清除格式
40
   ['clean'],                                         // 清除格式
49
 
41
 
50
 ];
42
 ];
51
-
52
-function image(){
53
-  var _this3 = this;
54
-
55
-  var fileInput = this.container.querySelector('input.ql-image[type=file]');
56
-  fileInput = null
57
-  if (fileInput == null) {
58
-    fileInput = document.createElement('input');
59
-    fileInput.setAttribute('type', 'file');
60
-    fileInput.setAttribute('accept', 'image/png, image/gif, image/jpeg, image/bmp, image/x-icon');
61
-    fileInput.classList.add('ql-image');
62
-    fileInput.addEventListener('change', async function () {
63
-      if (fileInput.files != null && fileInput.files[0] != null) {
64
-        let index = editor.getSelection() && editor.getSelection().index ? editor.getSelection().index : 0;
65
-
66
-        let file = fileInput.files[0]
67
-        const formData = new FormData();
68
-        formData.append('file', file, file.name);
69
-        const res = await upload(formData)
70
-        editor.insertEmbed(index, 'image', res.data.url);
71
-        var reader = new FileReader();
72
-        reader.onload = function (e) {
73
-          fileInput.value = "";
74
-        };
75
-
76
-      }
77
-    });
78
-    this.container.appendChild(fileInput);
79
-  }
80
-  fileInput.click();
81
-}
43
+const modules = {
44
+  toolbar: {
45
+    container: toolbarOptions, // 使用自定义工具栏选项
46
+  },
47
+};
48
+// function image(){
49
+//   var _this3 = this;
50
+
51
+//   var fileInput = this.container.querySelector('input.ql-image[type=file]');
52
+//   fileInput = null
53
+//   if (fileInput == null) {
54
+//     fileInput = document.createElement('input');
55
+//     fileInput.setAttribute('type', 'file');
56
+//     fileInput.setAttribute('accept', 'image/png, image/gif, image/jpeg, image/bmp, image/x-icon');
57
+//     fileInput.classList.add('ql-image');
58
+//     fileInput.addEventListener('change', async function () {
59
+//       if (fileInput.files != null && fileInput.files[0] != null) {
60
+//         let index = editor.getSelection() && editor.getSelection().index ? editor.getSelection().index : 0;
61
+
62
+//         let file = fileInput.files[0]
63
+//         const formData = new FormData();
64
+//         formData.append('file', file, file.name);
65
+//         const res = await upload(formData)
66
+//         editor.insertEmbed(index, 'image', res.data.url);
67
+//         var reader = new FileReader();
68
+//         reader.onload = function (e) {
69
+//           fileInput.value = "";
70
+//         };
71
+
72
+//       }
73
+//     });
74
+//     this.container.appendChild(fileInput);
75
+//   }
76
+//   fileInput.click();
77
+// }
82
 const PartyWorkForm: React.FC<PartyWorkProps> = (props) => {
78
 const PartyWorkForm: React.FC<PartyWorkProps> = (props) => {
83
 
79
 
84
   const [form] = Form.useForm();
80
   const [form] = Form.useForm();
85
   const intl = useIntl();
81
   const intl = useIntl();
86
   let editorRef = useRef<HTMLDivElement>(null)
82
   let editorRef = useRef<HTMLDivElement>(null)
87
-
83
+  const [editWho, setEditWho] = useState('')
84
+  const [userIndex, setUserIndex] = useState(0)
85
+  const [editorHtml, setEditorHtml] = useState('');
88
   useEffect(() => {
86
   useEffect(() => {
89
-    console.log(props)
90
     form.resetFields();
87
     form.resetFields();
91
     if(!props.open) return;
88
     if(!props.open) return;
92
     // const { title, content, surface, surfaceUrl, date } = props;
89
     // const { title, content, surface, surfaceUrl, date } = props;
120
         }
117
         }
121
       ])
118
       ])
122
     }
119
     }
123
-    if (editorRef && editorRef.current && editorRef.current.children[0]) {
124
-      editorRef.current.children[0].innerHTML = content
120
+    // if (editorRef && editorRef.current && editorRef.current.children[0]) {
121
+    //   editorRef.current.children[0].innerHTML = content
122
+    // }
123
+    setEditorHtml(content || '')
124
+  }, [props.open,props.currentRow]);
125
+  const base64ToFile = (base64, fileName = `${Math.random()}`) => {
126
+    let arr = base64.split(',')
127
+    console.log(arr[0])
128
+    let mime = arr[0].match(/:(.*?);/)[1]
129
+    let bstr = atob(arr[1])
130
+    let n = bstr.length
131
+    let u8arr = new Uint8Array(n)
132
+    while (n--) {
133
+      u8arr[n] = bstr.charCodeAt(n)
125
     }
134
     }
126
-
127
-  }, [props.open]);
128
-
135
+    return new File([u8arr], `${fileName}.${mime.split('/')[1]}`, {
136
+      type: mime,
137
+    })
138
+  }
129
   useEffect(() => {
139
   useEffect(() => {
130
-    if(!props.open) return;
131
-    var Parchment = Quill.import('parchment');
132
-
133
-    let config = {
134
-      scope: Parchment.Scope.INLINE,
135
-      // 会有下拉框列表
136
-      whitelist: ['20px', '22px', '24px', '26px', '28px', '30px', '32px', '34px', '36px', '38px', '40px', '42px', '44px']
137
-      //可设置成没有下拉框的,但会导致无法清除样式
138
-      // whitelist: ['1px solid #000000']  
139
-    };
140
-
141
-    let wordBox = new Parchment.Attributor.Style('wordBox', 'line-height', config);
142
-    console.log('wordBox',wordBox)
143
-
144
-    Quill.register(wordBox, true);
145
-    editor = new Quill('#editor', {
146
-      modules: {
147
-        toolbar: toolbarOptions,
148
-        clipboard: {
149
-          dangerouslyPasteHTML: true
150
-        }
151
-      },
152
-      theme: 'snow'
153
-    });
154
-    console.log('editor',editor)
155
-    // 工具栏附件按钮
156
-    let a = document.querySelectorAll(".ql-wordBox")[0]
157
-    let b = document.querySelectorAll(".ql-wordBox")[1]
158
-    //添加默认显示内容
159
-    let stit = document.createElement('span')
160
-    stit.innerHTML = intl.formatMessage({ id: 'public.lineSpacing' });
161
-    stit.setAttribute('style', 'margin-right:20px;line-height: 24px;')
162
-    a.children[0].insertBefore(stit, a.children[0].children[0])
163
-
164
-    // 遍历下拉框列表添加值
165
-    let i = a.children[1].children.length
166
-    while (i) {
167
-      i--;
168
-      let item = a.children[1].children[i]
169
-      item.innerHTML = item.dataset.value ? item.dataset.value : '无'
140
+    if(editorRef?.current){
141
+      const quill = editorRef.current.getEditor()
142
+      if (editWho === 'api' && userIndex !== 0) {
143
+        quill.setSelection(userIndex + 2)
144
+      }
170
     }
145
     }
171
-
172
-    // console.log('editor', editor)
173
-    var toolbar = editor.getModule('toolbar');
174
-
175
-    toolbar.image2 = image
176
-    // console.log("toolbar.handlers.image", toolbar.handlers.image)
177
-    toolbar.addHandler('image', (e) => {
178
-      toolbar.image2()
179
-      // 
180
-
181
-    });
182
-
183
-    // toolbar.addHandler('wordBox', (e) => {
184
-    //   console.log('e-----++++', e)
185
-    //   // 
186
-
187
-    // });
188
-
189
-  }, [props.open]);
190
-
146
+  }, [editorHtml,userIndex])
191
 
147
 
192
   const [fileList, setFileList] = useState<any>([]);
148
   const [fileList, setFileList] = useState<any>([]);
193
   const [surface, setSurface] = useState<any>('');
149
   const [surface, setSurface] = useState<any>('');
197
     let data = form.getFieldsValue()
153
     let data = form.getFieldsValue()
198
     let val = await form.validateFields()
154
     let val = await form.validateFields()
199
     
155
     
200
-    let htmlStr = editorRef.current?.children[0].innerHTML
156
+    const htmlStr = editorHtml.replaceAll('"', "'")
201
 
157
 
202
-    htmlStr = htmlStr?.replaceAll('"', "'")
203
-    let content = editor.getContents()
158
+    // let content = editor.getContents()
204
     // console.log('htmlStr----', htmlStr)
159
     // console.log('htmlStr----', htmlStr)
205
-    editor.setContents(content)
160
+    // editor.setContents(content)
206
     data.date = moment(data.date).format('YYYY-MM-DD');
161
     data.date = moment(data.date).format('YYYY-MM-DD');
207
     if(checkTop){
162
     if(checkTop){
208
       data.top = 1;
163
       data.top = 1;
221
     })
176
     })
222
     setFileList([]);
177
     setFileList([]);
223
     setCheckTop(false)
178
     setCheckTop(false)
224
-    editor.clipboard.dangerouslyPasteHTML(0, '')
225
-    editor.setContents([])
179
+    // editor.clipboard.dangerouslyPasteHTML(0, '')
180
+    // editor.setContents([])
181
+    setEditorHtml('');
226
     props.onSubmit(formData);
182
     props.onSubmit(formData);
227
   };
183
   };
228
   const handleCancel = () => {
184
   const handleCancel = () => {
233
     })
189
     })
234
     setFileList([]);
190
     setFileList([]);
235
     setCheckTop(false)
191
     setCheckTop(false)
236
-    editor.clipboard.dangerouslyPasteHTML(0, '')
237
-    editor.setContents([])
192
+    // editor.clipboard.dangerouslyPasteHTML(0, '')
193
+    // editor.setContents([])
194
+    setEditorHtml('');
238
     props.onCancel();
195
     props.onCancel();
239
 
196
 
240
   };
197
   };
254
     return false;
211
     return false;
255
   };
212
   };
256
 
213
 
214
+  const handleChangeQuill = (content, delta, source, editor) => {
215
+    let quill = editorRef.current.getEditor()
216
+    quill.focus()
217
+    let delta_ops = delta.ops
218
+    let quilContent = editor.getContents()
219
+    setEditorHtml(content)
220
+    const range = quill.getSelection()
221
+    if (range) {
222
+      if (range.length == 0 && range.index !== 0) {
223
+        console.log('User cursor is at index', range.index)
224
+        setUserIndex(range.index)
225
+      } else {
226
+        const text = quill.getText(range.index, range.length)
227
+        console.log('User has highlighted: ', text)
228
+      }
229
+    } else {
230
+      console.log('User cursor is not in editor')
231
+    }
232
+    setEditWho(source)
233
+    if (delta_ops && delta_ops.length > 0) {
234
+      quilContent.ops.map((item) => {
235
+        if (item.insert) {
236
+          let imgStr = item.insert.image
237
+          if (imgStr && imgStr?.includes('data:image/')) {
238
+            let file = base64ToFile(imgStr)
239
+            let formData = new FormData()
240
+            formData.append('file', file)
241
+            upload(formData).then((res) => {
242
+              item.insert.image = res.data.url
243
+              quill.setContents(quilContent)
244
+            })
245
+          }
246
+        }
247
+      })
248
+    }
249
+  }
250
+
257
 
251
 
258
   return (
252
   return (
259
     <Drawer
253
     <Drawer
378
           style: { width: 95 }
372
           style: { width: 95 }
379
         }}
373
         }}
380
         >
374
         >
381
-          <div id='editor' style={{ width: '70vw', height: '500px' }} ref={editorRef}></div>
375
+          {/* <div id='editor' style={{ width: '70vw', height: '500px' }} ref={editorRef}></div> */}
376
+          <ReactQuill ref={editorRef} placeholder='请输入...' theme='snow' className="ql-editor" modules={modules} value={editorHtml} onChange={handleChangeQuill} />
382
         </ProForm.Item>
377
         </ProForm.Item>
383
       </ProForm>
378
       </ProForm>
384
     </Drawer>
379
     </Drawer>

+ 89
- 153
src/pages/SocialRespon/socialRespon/edit.tsx 查看文件

3
   ProForm,
3
   ProForm,
4
   ProFormText,
4
   ProFormText,
5
   ProFormTextArea,
5
   ProFormTextArea,
6
-  ProFormUploadButton,
7
-  ProFormSelect,
8
   ProFormDatePicker,
6
   ProFormDatePicker,
9
-  ProFormRadio
10
 } from '@ant-design/pro-components';
7
 } from '@ant-design/pro-components';
11
-import { Form, Modal, Drawer, message, Upload, Space, Button, Switch,Input,Tooltip } from 'antd';
12
-import {InfoCircleOutlined} from '@ant-design/icons';
13
-
14
-import { useIntl, FormattedMessage } from '@umijs/max';
15
-import { DictValueEnumObj } from '@/components/DictTag';
16
-import Editor from '@/components/ueditor';
8
+import { Form, Drawer, Space, Button } from 'antd';
9
+import ReactQuill from 'react-quill';
10
+import 'react-quill/dist/quill.snow.css';
11
+import { useIntl } from '@umijs/max';
17
 import { upload } from '@/services/socialRespon/socialRespon';
12
 import { upload } from '@/services/socialRespon/socialRespon';
18
-import { getDataEnumList } from '@/services/system/Enum';
19
-import Quill from "quill";
20
-import "quill/dist/quill.snow.css";
21
 import moment from 'moment';
13
 import moment from 'moment';
22
-import { trim } from 'lodash';
23
-let editor: any;
24
-
25
 export type SocialResponProps = {
14
 export type SocialResponProps = {
26
   onCancel: (flag?: boolean, formVals?: any) => void;
15
   onCancel: (flag?: boolean, formVals?: any) => void;
27
   onSubmit: (values: any) => Promise<void>;
16
   onSubmit: (values: any) => Promise<void>;
38
   ['bold', 'italic', 'underline', 'strike'],        // 切换按钮
27
   ['bold', 'italic', 'underline', 'strike'],        // 切换按钮
39
   ['blockquote', 'code-block'],
28
   ['blockquote', 'code-block'],
40
   ['link', 'image'],
29
   ['link', 'image'],
41
-
42
-
43
-  [{ 'header': 1 }, { 'header': 2 }],               // 用户自定义按钮值
44
   [{ 'list': 'ordered' }, { 'list': 'bullet' }],
30
   [{ 'list': 'ordered' }, { 'list': 'bullet' }],
45
   [{ 'script': 'sub' }, { 'script': 'super' }],      // 上标/下标
31
   [{ 'script': 'sub' }, { 'script': 'super' }],      // 上标/下标
46
   [{ 'indent': '-1' }, { 'indent': '+1' }],          // 减少缩进/缩进
32
   [{ 'indent': '-1' }, { 'indent': '+1' }],          // 减少缩进/缩进
47
   [{ 'direction': 'rtl' }],                         // 文本下划线
33
   [{ 'direction': 'rtl' }],                         // 文本下划线
48
-
49
-
50
   [{ 'size': ['small', false, 'large', 'huge'] }],  // 用户自定义下拉
34
   [{ 'size': ['small', false, 'large', 'huge'] }],  // 用户自定义下拉
51
   [{ 'header': [1, 2, 3, 4, 5, 6, false] }],
35
   [{ 'header': [1, 2, 3, 4, 5, 6, false] }],
52
-
53
-
54
   [{ 'color': [] }, { 'background': [] }],          // 主题默认下拉,使用主题提供的值
36
   [{ 'color': [] }, { 'background': [] }],          // 主题默认下拉,使用主题提供的值
55
   [{ 'font': [] }],
37
   [{ 'font': [] }],
56
   [{ 'align': [] }],
38
   [{ 'align': [] }],
60
 
42
 
61
 ];
43
 ];
62
 
44
 
63
-function image(){
64
-  var _this3 = this;
65
-
66
-  var fileInput = this.container.querySelector('input.ql-image[type=file]');
67
-  fileInput = null
68
-  if (fileInput == null) {
69
-    fileInput = document.createElement('input');
70
-    fileInput.setAttribute('type', 'file');
71
-    fileInput.setAttribute('accept', 'image/png, image/gif, image/jpeg, image/bmp, image/x-icon');
72
-    fileInput.classList.add('ql-image');
73
-    fileInput.addEventListener('change', async function () {
74
-      if (fileInput.files != null && fileInput.files[0] != null) {
75
-        let index = editor.getSelection() && editor.getSelection().index ? editor.getSelection().index : 0;
76
-
77
-        let file = fileInput.files[0]
78
-        const formData = new FormData();
79
-        formData.append('file', file, file.name);
80
-        const res = await upload(formData)
81
-        editor.insertEmbed(index, 'image', res.data.url);
82
-        var reader = new FileReader();
83
-        reader.onload = function (e) {
84
-          fileInput.value = "";
85
-        };
86
-
87
-      }
88
-    });
89
-    this.container.appendChild(fileInput);
90
-  }
91
-  fileInput.click();
92
-}
93
 const SocialResponForm: React.FC<SocialResponProps> = (props) => {
45
 const SocialResponForm: React.FC<SocialResponProps> = (props) => {
94
 
46
 
95
   const [form] = Form.useForm();
47
   const [form] = Form.useForm();
96
   const intl = useIntl();
48
   const intl = useIntl();
97
   let editorRef = useRef<HTMLDivElement>(null)
49
   let editorRef = useRef<HTMLDivElement>(null)
50
+  const [editorHtml, setEditorHtml] = useState('');
98
 
51
 
52
+  const modules = {
53
+    toolbar: {
54
+      container: toolbarOptions, // 使用自定义工具栏选项
55
+    },
56
+  };
99
   useEffect(() => {
57
   useEffect(() => {
100
-    console.log(props)
101
     form.resetFields();
58
     form.resetFields();
102
-    if(!props.open) return;
59
+    if (!props.open) return;
103
     // const { title, content, surface, surfaceUrl, date } = props;
60
     // const { title, content, surface, surfaceUrl, date } = props;
104
-    if(!props.currentRow) return;
105
-    const { digest, top,key,content,surface,surfaceUrl,date,title,source,column} = props.currentRow;
61
+    if (!props.currentRow) return;
62
+    const { digest, top, key, content, surface, surfaceUrl, date, title, source, column } = props.currentRow;
106
     if (props.currentRow) {
63
     if (props.currentRow) {
107
       form.setFieldsValue({
64
       form.setFieldsValue({
108
         digest,
65
         digest,
131
         }
88
         }
132
       ])
89
       ])
133
     }
90
     }
134
-    if (editorRef && editorRef.current && editorRef.current.children[0]) {
135
-      editorRef.current.children[0].innerHTML = content
136
-    }
91
+    setEditorHtml(content || '')
137
 
92
 
138
   }, [props.open]);
93
   }, [props.open]);
139
 
94
 
140
-  useEffect(() => {
141
-    var Parchment = Quill.import('parchment');
142
-
143
-    let config = {
144
-      scope: Parchment.Scope.INLINE,
145
-      // 会有下拉框列表
146
-      whitelist: ['20px', '22px', '24px', '26px', '28px', '30px', '32px', '34px', '36px', '38px', '40px', '42px', '44px']
147
-      //可设置成没有下拉框的,但会导致无法清除样式
148
-      // whitelist: ['1px solid #000000']  
149
-    };
150
-
151
-    let wordBox = new Parchment.Attributor.Style('wordBox', 'line-height', config);
152
-
153
-    Quill.register(wordBox, true);
154
-    editor = new Quill('#editor', {
155
-      modules: {
156
-        toolbar: toolbarOptions,
157
-        clipboard: {
158
-          dangerouslyPasteHTML: true
159
-        }
160
-      },
161
-      theme: 'snow'
162
-    });
163
-
164
-    // 工具栏附件按钮
165
-    let a = document.querySelectorAll(".ql-wordBox")[0]
166
-    let b = document.querySelectorAll(".ql-wordBox")[1]
167
-    //添加默认显示内容
168
-    let stit = document.createElement('span')
169
-    stit.innerHTML = intl.formatMessage({ id: 'public.lineSpacing' });
170
-    stit.setAttribute('style', 'margin-right:20px;line-height: 24px;')
171
-    a.children[0].insertBefore(stit, a.children[0].children[0])
172
-
173
-    // 遍历下拉框列表添加值
174
-    let i = a.children[1].children.length
175
-    while (i) {
176
-      i--;
177
-      let item = a.children[1].children[i]
178
-      item.innerHTML = item.dataset.value ? item.dataset.value : '无'
179
-    }
180
-
181
-    // console.log('editor', editor)
182
-    var toolbar = editor.getModule('toolbar');
183
-
184
-    toolbar.image2 = image
185
-    // console.log("toolbar.handlers.image", toolbar.handlers.image)
186
-    toolbar.addHandler('image', (e) => {
187
-      toolbar.image2()
188
-      // 
189
-
190
-    });
191
-
192
-    // toolbar.addHandler('wordBox', (e) => {
193
-    //   console.log('e-----++++', e)
194
-    //   // 
195
-
196
-    // });
197
-
198
-  }, []);
199
-
200
-
201
   const [fileList, setFileList] = useState<any>([]);
95
   const [fileList, setFileList] = useState<any>([]);
202
   const [surface, setSurface] = useState<any>('');
96
   const [surface, setSurface] = useState<any>('');
203
 
97
 
98
+  const [editWho, setEditWho] = useState('')
99
+  const [userIndex, setUserIndex] = useState(0)
204
   const [checkTop, setCheckTop] = useState<any>('')
100
   const [checkTop, setCheckTop] = useState<any>('')
205
   const handleOk = async () => {
101
   const handleOk = async () => {
206
     let data = form.getFieldsValue()
102
     let data = form.getFieldsValue()
207
     let val = await form.validateFields()
103
     let val = await form.validateFields()
208
-    
209
-    let htmlStr = editorRef.current?.children[0].innerHTML
210
 
104
 
211
-    htmlStr = htmlStr?.replaceAll('"', "'")
212
-    let content = editor.getContents()
105
+    let htmlStr = editorHtml?.replaceAll('"', "'")
106
+
107
+    // let content = editor.getContents()
213
     // console.log('htmlStr----', htmlStr)
108
     // console.log('htmlStr----', htmlStr)
214
-    editor.setContents(content)
109
+    // editor.setContents(content)
215
     data.date = moment(data.date).format('YYYY-MM-DD');
110
     data.date = moment(data.date).format('YYYY-MM-DD');
216
-    if(checkTop){
111
+    if (checkTop) {
217
       data.top = 1;
112
       data.top = 1;
218
-    }else{
113
+    } else {
219
       data.top = 0;
114
       data.top = 0;
220
-    } 
115
+    }
221
     // data.top = checkTop;
116
     // data.top = checkTop;
222
     let formData = {
117
     let formData = {
223
       ...data,
118
       ...data,
230
     })
125
     })
231
     setFileList([]);
126
     setFileList([]);
232
     setCheckTop(false)
127
     setCheckTop(false)
233
-    editor.clipboard.dangerouslyPasteHTML(0, '')
234
-    editor.setContents([])
128
+    setEditorHtml('');
235
     props.onSubmit(formData);
129
     props.onSubmit(formData);
236
   };
130
   };
237
   const handleCancel = () => {
131
   const handleCancel = () => {
242
     })
136
     })
243
     setFileList([]);
137
     setFileList([]);
244
     setCheckTop(false)
138
     setCheckTop(false)
245
-    editor.clipboard.dangerouslyPasteHTML(0, '')
246
-    editor.setContents([])
139
+    setEditorHtml('');
247
     props.onCancel();
140
     props.onCancel();
248
 
141
 
249
   };
142
   };
250
-
251
-  const handleChange = async (res) => {
252
-    const { fileList } = res;
253
-    setFileList(fileList);
254
-  };
255
-
256
-  const handleBeforeUpload = async (file: RcFile) => {
257
-    const formData = new FormData();
258
-    formData.append('file', file, file.name);
259
-    const res = await upload(formData);
260
-    if (res?.data?.uuid) {
261
-      setSurface(res.data.uuid)
143
+  useEffect(() => {
144
+    const quill = editorRef.current.getEditor()
145
+    if (editWho === 'api' && userIndex !== 0) {
146
+      quill.setSelection(userIndex + 2)
262
     }
147
     }
263
-    return false;
264
-  };
148
+  }, [editorHtml])
149
+
150
+  const base64ToFile = (base64, fileName = `${Math.random()}`) => {
151
+    let arr = base64.split(',')
152
+    console.log(arr[0])
153
+    let mime = arr[0].match(/:(.*?);/)[1]
154
+    let bstr = atob(arr[1])
155
+    let n = bstr.length
156
+    let u8arr = new Uint8Array(n)
157
+    while (n--) {
158
+      u8arr[n] = bstr.charCodeAt(n)
159
+    }
160
+    return new File([u8arr], `${fileName}.${mime.split('/')[1]}`, {
161
+      type: mime,
162
+    })
163
+  }
164
+  const handleChangeQuill = (content, delta, source, editor) => {
165
+    let quill = editorRef.current.getEditor()
166
+    quill.focus()
167
+    let delta_ops = delta.ops
168
+    let quilContent = editor.getContents()
169
+    setEditorHtml(content)
170
+    const range = quill.getSelection()
171
+    if (range) {
172
+      if (range.length == 0 && range.index !== 0) {
173
+        console.log('User cursor is at index', range.index)
174
+        setUserIndex(range.index)
175
+      } else {
176
+        const text = quill.getText(range.index, range.length)
177
+        console.log('User has highlighted: ', text)
178
+      }
179
+    } else {
180
+      console.log('User cursor is not in editor')
181
+    }
182
+    setEditWho(source)
183
+    if (delta_ops && delta_ops.length > 0) {
184
+      quilContent.ops.map((item) => {
185
+        if (item.insert) {
186
+          let imgStr = item.insert.image
187
+          if (imgStr && imgStr?.includes('data:image/')) {
188
+            let file = base64ToFile(imgStr)
189
+            let formData = new FormData()
190
+            formData.append('file', file)
191
+            upload(formData).then((res) => {
192
+              item.insert.image = res.data.url
193
+              quill.setContents(quilContent)
194
+            })
195
+          }
196
+        }
197
+      })
198
+    }
199
+  }
200
+
265
 
201
 
266
 
202
 
267
   return (
203
   return (
323
           ]
259
           ]
324
           }
260
           }
325
         />
261
         />
326
-         
262
+
327
         <ProFormText
263
         <ProFormText
328
           name="key"
264
           name="key"
329
           label='网页关键字'
265
           label='网页关键字'
343
           initialValue={'中泽集团'}
279
           initialValue={'中泽集团'}
344
         />
280
         />
345
 
281
 
346
-        <ProForm.Item 
347
-        label={'文章内容'} 
348
-        labelCol={{
349
-          style: { width: 95 }
350
-        }}
282
+        <ProForm.Item
283
+          label={'文章内容'}
284
+          labelCol={{
285
+            style: { width: 95 }
286
+          }}
351
         >
287
         >
352
-          <div id='editor' style={{ width: '70vw', height: '500px' }} ref={editorRef}></div>
288
+          <ReactQuill ref={editorRef} placeholder='请输入...' theme='snow' className="ql-editor" modules={modules} value={editorHtml} onChange={handleChangeQuill} />
353
         </ProForm.Item>
289
         </ProForm.Item>
354
       </ProForm>
290
       </ProForm>
355
     </Drawer>
291
     </Drawer>

正在加载...
取消
保存