Przeglądaj źródła

修复富文本相关问题

master
suomingxiang 3 miesięcy temu
rodzic
commit
b8b0f47d04

+ 115
- 148
src/pages/JoinZZ/biddingDocument/edit.tsx Wyświetl plik

@@ -2,55 +2,34 @@ import React, { useEffect, useState, useRef } from 'react';
2 2
 import {
3 3
   ProForm,
4 4
   ProFormText,
5
-  ProFormUploadButton,
6
-  ProFormSelect,
7 5
   ProFormDatePicker,
8 6
   ProFormTextArea,
9
-  ProFormRadio
10 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 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 15
 import moment from 'moment';
22
-import { trim } from 'lodash';
23
-let editor: any;
24 16
 
25 17
 export type BiddingDocumentProps = {
26 18
   onCancel: (flag?: boolean, formVals?: any) => void;
27 19
   onSubmit: (values: any) => Promise<void>;
28 20
   open: boolean;
29 21
   currentRow: any;
30
-  date: any;
31
-  title: string;
32
-  content: string;
33
-  surface: string;
34
-  surfaceUrl: string;
35
-};
36
-
22
+}
37 23
 let toolbarOptions = [
38 24
   ['bold', 'italic', 'underline', 'strike'],        // 切换按钮
39 25
   ['blockquote', 'code-block'],
40 26
   ['link', 'image'],
41
-
42
-
43
-  [{ 'header': 1 }, { 'header': 2 }],               // 用户自定义按钮值
44 27
   [{ 'list': 'ordered' }, { 'list': 'bullet' }],
45 28
   [{ 'script': 'sub' }, { 'script': 'super' }],      // 上标/下标
46 29
   [{ 'indent': '-1' }, { 'indent': '+1' }],          // 减少缩进/缩进
47 30
   [{ 'direction': 'rtl' }],                         // 文本下划线
48
-
49
-
50 31
   [{ 'size': ['small', false, 'large', 'huge'] }],  // 用户自定义下拉
51 32
   [{ 'header': [1, 2, 3, 4, 5, 6, false] }],
52
-
53
-
54 33
   [{ 'color': [] }, { 'background': [] }],          // 主题默认下拉,使用主题提供的值
55 34
   [{ 'font': [] }],
56 35
   [{ 'align': [] }],
@@ -59,45 +38,51 @@ let toolbarOptions = [
59 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 77
 const BiddingDocumentForm: React.FC<BiddingDocumentProps> = (props) => {
94 78
 
95 79
   const [form] = Form.useForm();
96 80
   const intl = useIntl();
97 81
   let editorRef = useRef<HTMLDivElement>(null)
98
-
82
+  const [editWho, setEditWho] = useState('')
83
+  const [userIndex, setUserIndex] = useState(0)
84
+  const [editorHtml, setEditorHtml] = useState('');
99 85
   useEffect(() => {
100
-    console.log(props)
101 86
     form.resetFields();
102 87
     if(!props.open) return;
103 88
     // const { title, content, surface, surfaceUrl, date } = props;
@@ -129,73 +114,31 @@ const BiddingDocumentForm: React.FC<BiddingDocumentProps> = (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 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 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 143
   const [fileList, setFileList] = useState<any>([]);
201 144
   const [surface, setSurface] = useState<any>('');
@@ -204,13 +147,13 @@ const BiddingDocumentForm: React.FC<BiddingDocumentProps> = (props) => {
204 147
   const handleOk = async () => {
205 148
     let data = form.getFieldsValue()
206 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 155
     // console.log('htmlStr----', htmlStr)
213
-    editor.setContents(content)
156
+    // editor.setContents(content)
214 157
     data.date = moment(data.date).format('YYYY-MM-DD');
215 158
     if(checkTop){
216 159
       data.top = 1;
@@ -229,8 +172,9 @@ const BiddingDocumentForm: React.FC<BiddingDocumentProps> = (props) => {
229 172
     })
230 173
     setFileList([]);
231 174
     setCheckTop(false)
232
-    editor.clipboard.dangerouslyPasteHTML(0, '')
233
-    editor.setContents([])
175
+    // editor.clipboard.dangerouslyPasteHTML(0, '')
176
+    // editor.setContents([])
177
+    setEditorHtml('');
234 178
     props.onSubmit(formData);
235 179
   };
236 180
   const handleCancel = () => {
@@ -241,27 +185,49 @@ const BiddingDocumentForm: React.FC<BiddingDocumentProps> = (props) => {
241 185
     })
242 186
     setFileList([]);
243 187
     setCheckTop(false)
244
-    editor.clipboard.dangerouslyPasteHTML(0, '')
245
-    editor.setContents([])
188
+    // editor.clipboard.dangerouslyPasteHTML(0, '')
189
+    // editor.setContents([])
190
+    setEditorHtml('');
246 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 232
   return (
267 233
     <Drawer
@@ -347,7 +313,8 @@ const BiddingDocumentForm: React.FC<BiddingDocumentProps> = (props) => {
347 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 318
         </ProForm.Item>
352 319
       </ProForm>
353 320
     </Drawer>

+ 79
- 123
src/pages/JoinZZ/vacancy/edit.tsx Wyświetl plik

@@ -1,36 +1,24 @@
1 1
 import React, { useEffect, useState, useRef } from 'react';
2 2
 import {
3 3
   ProForm,
4
-  ProFormText,
5
-  ProFormUploadButton,
6 4
   ProFormSelect,
7
-  ProFormDatePicker,
8
-  ProFormRadio
9 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 9
 import { upload } from '@/services/JoinZZ/vacancy';
17 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 15
 import moment from 'moment';
21
-import { trim } from 'lodash';
22
-let editor: any;
23 16
 
24 17
 export type VacancyProps = {
25 18
   onCancel: (flag?: boolean, formVals?: any) => void;
26 19
   onSubmit: (values: any) => Promise<void>;
27 20
   open: boolean;
28 21
   currentRow: any;
29
-  date: any;
30
-  title: string;
31
-  content: string;
32
-  surface: string;
33
-  surfaceUrl: string;
34 22
 };
35 23
 
36 24
 let toolbarOptions = [
@@ -58,45 +46,21 @@ let toolbarOptions = [
58 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 55
 const VacancyForm: React.FC<VacancyProps> = (props) => {
93 56
 
94 57
   const [form] = Form.useForm();
95 58
   const intl = useIntl();
96 59
   let editorRef = useRef<HTMLDivElement>(null)
97
-
60
+  const [editWho, setEditWho] = useState('')
61
+  const [userIndex, setUserIndex] = useState(0)
62
+  const [editorHtml, setEditorHtml] = useState('');
98 63
   useEffect(() => {
99
-    console.log(props)
100 64
     form.resetFields();
101 65
     if(!props.open) return;
102 66
     // const { title, content, surface, surfaceUrl, date } = props;
@@ -128,74 +92,31 @@ const VacancyForm: React.FC<VacancyProps> = (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 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 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 120
   const [fileList, setFileList] = useState<any>([]);
200 121
   const [surface, setSurface] = useState<any>('');
201 122
 
@@ -204,12 +125,11 @@ const VacancyForm: React.FC<VacancyProps> = (props) => {
204 125
     let data = form.getFieldsValue()
205 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 131
     // console.log('htmlStr----', htmlStr)
212
-    editor.setContents(content)
132
+    // editor.setContents(content)
213 133
     data.date = moment(data.date).format('YYYY-MM-DD');
214 134
     if(checkTop){
215 135
       data.top = 1;
@@ -227,9 +147,8 @@ const VacancyForm: React.FC<VacancyProps> = (props) => {
227 147
       digest: ""
228 148
     })
229 149
     setFileList([]);
150
+    setEditorHtml('');
230 151
     setCheckTop(false)
231
-    editor.clipboard.dangerouslyPasteHTML(0, '')
232
-    editor.setContents([])
233 152
     props.onSubmit(formData);
234 153
   };
235 154
   const handleCancel = () => {
@@ -240,8 +159,7 @@ const VacancyForm: React.FC<VacancyProps> = (props) => {
240 159
     })
241 160
     setFileList([]);
242 161
     setCheckTop(false)
243
-    editor.clipboard.dangerouslyPasteHTML(0, '')
244
-    editor.setContents([])
162
+    setEditorHtml('');
245 163
     props.onCancel();
246 164
 
247 165
   };
@@ -261,6 +179,43 @@ const VacancyForm: React.FC<VacancyProps> = (props) => {
261 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 220
   return (
266 221
     <Drawer
@@ -313,7 +268,8 @@ const VacancyForm: React.FC<VacancyProps> = (props) => {
313 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 273
         </ProForm.Item>
318 274
       </ProForm>
319 275
     </Drawer>

+ 240
- 0
src/pages/News/NewsList/edit copy.tsx Wyświetl plik

@@ -0,0 +1,240 @@
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 Wyświetl plik

@@ -3,271 +3,175 @@ import {
3 3
   ProForm,
4 4
   ProFormText,
5 5
   ProFormTextArea,
6
-  ProFormUploadButton,
7 6
   ProFormSelect,
8 7
   ProFormDatePicker,
9
-  ProFormRadio,
10 8
   ProFormSwitch,
11 9
   ProFormDateTimePicker
12 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 15
 import { upload } from '@/services/news/news';
20 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 19
 export type NewsProps = {
28 20
   onCancel: (flag?: boolean, formVals?: any) => void;
29 21
   onSubmit: (values: any) => Promise<void>;
30 22
   open: boolean;
31 23
   currentRow: any;
32
-  date: any;
33
-  title: string;
34
-  content: string;
35
-  surface: string;
36
-  surfaceUrl: string;
37 24
 };
38
-
39 25
 let toolbarOptions = [
40
-  ['bold', 'italic', 'underline', 'strike'],        // 切换按钮
26
+  ['bold', 'italic', 'underline', 'strike'],
41 27
   ['blockquote', 'code-block'],
42 28
   ['link', 'image'],
43
-
44
-
45
-  [{ 'header': 1 }, { 'header': 2 }],               // 用户自定义按钮值
46 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 34
   [{ 'header': [1, 2, 3, 4, 5, 6, false] }],
54
-
55
-
56
-  [{ 'color': [] }, { 'background': [] }],          // 主题默认下拉,使用主题提供的值
35
+  [{ 'color': [] }, { 'background': [] }],
57 36
   [{ 'font': [] }],
58 37
   [{ 'align': [] }],
59
-  // [{ 'wordBox': ['20px', '22px', '24px', '26px', '28px', '28px', '30px'] }],
60 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 46
 const NewsForm: React.FC<NewsProps> = (props) => {
96
-
97 47
   const [form] = Form.useForm();
98 48
   const intl = useIntl();
99 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 56
   useEffect(() => {
102 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 62
     form.setFieldsValue({
63
+      digest,
64
+      top,
65
+      key,
66
+      column,
67
+      source,
68
+      createTime,
69
+      newsTop: newsTop ? true : false,
126 70
       title: title,
127 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 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 85
     data.date = moment(data.date).format('YYYY-MM-DD');
219 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 89
       ...data,
228 90
       surface,
229 91
       content: htmlStr
230
-    }
231
-    form.setFieldsValue({
232
-      title: '',
233
-      digest: ""
234
-    })
92
+    };
93
+    form.resetFields();
235 94
     setFileList([]);
236
-    setCheckTop(false)
237
-    editor.clipboard.dangerouslyPasteHTML(0, '')
238
-    editor.setContents([])
95
+    setEditorHtml('');
239 96
     props.onSubmit(formData);
240 97
   };
241 98
   const handleCancel = () => {
242
-    form.setFieldsValue({
243
-      title: '',
244
-      digest: "",
245
-      top: ""
246
-    })
99
+    form.resetFields();
247 100
     setFileList([]);
248
-    setCheckTop(false)
249
-    editor.clipboard.dangerouslyPasteHTML(0, '')
250
-    editor.setContents([])
101
+    setEditorHtml('');
251 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 127
     const { fileList } = res;
257 128
     setFileList(fileList);
258 129
   };
259
-
260 130
   const handleBeforeUpload = async (file: RcFile) => {
261 131
     const formData = new FormData();
262 132
     formData.append('file', file, file.name);
263 133
     const res = await upload(formData);
264 134
     if (res?.data?.uuid) {
265
-      setSurface(res.data.uuid)
135
+      setSurface(res.data.uuid);
266 136
     }
267 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 175
   return (
272 176
     <Drawer
273 177
       width={'80%'}
@@ -292,137 +196,82 @@ const NewsForm: React.FC<NewsProps> = (props) => {
292 196
         <ProFormText
293 197
           name="title"
294 198
           label='标题'
295
-          labelCol={{
296
-            style: { width: 95 }
297
-          }}
199
+          labelCol={{ style: { width: 95 } }}
298 200
           placeholder='请输入标题'
299
-          rules={[
300
-            {
301
-              required: true,
302
-              message: '请输入标题!',
303
-            },
304
-          ]}
201
+          rules={[{ required: true, message: '请输入标题!' }]}
305 202
         />
306 203
         <ProForm.Item
307 204
           label={'封面图'}
308 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 209
           <Upload
320
-            listType="picture-card" // 设置为图片卡片模式
210
+            listType="picture-card"
321 211
             fileList={fileList}
322 212
             maxCount={1}
323 213
             onChange={handleChange}
324 214
             beforeUpload={handleBeforeUpload}
325 215
           >
326
-            {fileList?.length < 1 && '+' + intl.formatMessage({ id: 'public.uploadImg' })}
216
+            {fileList.length < 1 && '+' + intl.formatMessage({ id: 'public.uploadImg' })}
327 217
           </Upload>
328 218
         </ProForm.Item>
329
-        <ProFormSwitch labelCol={{
330
-            style: { width: 95 }
331
-          }} name="newsTop" label="置顶" />
219
+        <ProFormSwitch labelCol={{ style: { width: 95 } }} name="newsTop" label="置顶" />
332 220
         <ProFormDatePicker
333 221
           name="date"
334 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 227
         <ProFormDateTimePicker
349 228
           name="createTime"
350 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 234
         <ProFormSelect
365 235
           name="column"
366
-          key="column"
367 236
           label='新闻分类'
368 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 246
           placeholder={'请选择新闻分类'}
385
-
386 247
         />
387 248
         <ProFormTextArea
388 249
           name="digest"
389 250
           label='摘要'
390
-          labelCol={{
391
-            style: { width: 95 }
392
-          }}
251
+          labelCol={{ style: { width: 95 } }}
393 252
           placeholder={'请输入摘要'}
394 253
         />
395
-         
396 254
         <ProFormText
397 255
           name="key"
398 256
           label='网页关键字'
399
-          labelCol={{
400
-            style: { width: 95 }
401
-          }}
257
+          labelCol={{ style: { width: 95 } }}
402 258
           placeholder={'请输入页面关键字'}
403 259
         />
404
-
405 260
         <ProFormText
406 261
           name="source"
407 262
           label='来源'
408
-          labelCol={{
409
-            style: { width: 95 }
410
-          }}
263
+          labelCol={{ style: { width: 95 } }}
411 264
           placeholder={'中泽集团'}
412 265
           initialValue={'中泽集团'}
413 266
         />
414
-
415 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 272
         </ProForm.Item>
423 273
       </ProForm>
424 274
     </Drawer>
425 275
   );
426 276
 };
427
-
428
-export default NewsForm;
277
+export default NewsForm;

+ 16
- 0
src/pages/Other/footer/index.tsx Wyświetl plik

@@ -227,6 +227,22 @@ const NoticeTableList: React.FC = () => {
227 227
                             labelCol={{
228 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 247
                         <ProForm.Item
232 248
                             label={'微信二维码'}

+ 115
- 120
src/pages/PartyWork/partyWork/edit.tsx Wyświetl plik

@@ -7,15 +7,14 @@ import {
7 7
 } from '@ant-design/pro-components';
8 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 11
 import { upload } from '@/services/partyWork/partyWork';
13 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 17
 import moment from 'moment';
17
-let editor: any;
18
-
19 18
 export type PartyWorkProps = {
20 19
   onCancel: (flag?: boolean, formVals?: any) => void;
21 20
   onSubmit: (values: any) => Promise<void>;
@@ -27,19 +26,12 @@ let toolbarOptions = [
27 26
   ['bold', 'italic', 'underline', 'strike'],        // 切换按钮
28 27
   ['blockquote', 'code-block'],
29 28
   ['link', 'image'],
30
-
31
-
32
-  [{ 'header': 1 }, { 'header': 2 }],               // 用户自定义按钮值
33 29
   [{ 'list': 'ordered' }, { 'list': 'bullet' }],
34 30
   [{ 'script': 'sub' }, { 'script': 'super' }],      // 上标/下标
35 31
   [{ 'indent': '-1' }, { 'indent': '+1' }],          // 减少缩进/缩进
36 32
   [{ 'direction': 'rtl' }],                         // 文本下划线
37
-
38
-
39 33
   [{ 'size': ['small', false, 'large', 'huge'] }],  // 用户自定义下拉
40 34
   [{ 'header': [1, 2, 3, 4, 5, 6, false] }],
41
-
42
-
43 35
   [{ 'color': [] }, { 'background': [] }],          // 主题默认下拉,使用主题提供的值
44 36
   [{ 'font': [] }],
45 37
   [{ 'align': [] }],
@@ -48,45 +40,50 @@ let toolbarOptions = [
48 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 78
 const PartyWorkForm: React.FC<PartyWorkProps> = (props) => {
83 79
 
84 80
   const [form] = Form.useForm();
85 81
   const intl = useIntl();
86 82
   let editorRef = useRef<HTMLDivElement>(null)
87
-
83
+  const [editWho, setEditWho] = useState('')
84
+  const [userIndex, setUserIndex] = useState(0)
85
+  const [editorHtml, setEditorHtml] = useState('');
88 86
   useEffect(() => {
89
-    console.log(props)
90 87
     form.resetFields();
91 88
     if(!props.open) return;
92 89
     // const { title, content, surface, surfaceUrl, date } = props;
@@ -120,74 +117,33 @@ const PartyWorkForm: React.FC<PartyWorkProps> = (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 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 148
   const [fileList, setFileList] = useState<any>([]);
193 149
   const [surface, setSurface] = useState<any>('');
@@ -197,12 +153,11 @@ const PartyWorkForm: React.FC<PartyWorkProps> = (props) => {
197 153
     let data = form.getFieldsValue()
198 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 159
     // console.log('htmlStr----', htmlStr)
205
-    editor.setContents(content)
160
+    // editor.setContents(content)
206 161
     data.date = moment(data.date).format('YYYY-MM-DD');
207 162
     if(checkTop){
208 163
       data.top = 1;
@@ -221,8 +176,9 @@ const PartyWorkForm: React.FC<PartyWorkProps> = (props) => {
221 176
     })
222 177
     setFileList([]);
223 178
     setCheckTop(false)
224
-    editor.clipboard.dangerouslyPasteHTML(0, '')
225
-    editor.setContents([])
179
+    // editor.clipboard.dangerouslyPasteHTML(0, '')
180
+    // editor.setContents([])
181
+    setEditorHtml('');
226 182
     props.onSubmit(formData);
227 183
   };
228 184
   const handleCancel = () => {
@@ -233,8 +189,9 @@ const PartyWorkForm: React.FC<PartyWorkProps> = (props) => {
233 189
     })
234 190
     setFileList([]);
235 191
     setCheckTop(false)
236
-    editor.clipboard.dangerouslyPasteHTML(0, '')
237
-    editor.setContents([])
192
+    // editor.clipboard.dangerouslyPasteHTML(0, '')
193
+    // editor.setContents([])
194
+    setEditorHtml('');
238 195
     props.onCancel();
239 196
 
240 197
   };
@@ -254,6 +211,43 @@ const PartyWorkForm: React.FC<PartyWorkProps> = (props) => {
254 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 252
   return (
259 253
     <Drawer
@@ -378,7 +372,8 @@ const PartyWorkForm: React.FC<PartyWorkProps> = (props) => {
378 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 377
         </ProForm.Item>
383 378
       </ProForm>
384 379
     </Drawer>

+ 89
- 153
src/pages/SocialRespon/socialRespon/edit.tsx Wyświetl plik

@@ -3,25 +3,14 @@ import {
3 3
   ProForm,
4 4
   ProFormText,
5 5
   ProFormTextArea,
6
-  ProFormUploadButton,
7
-  ProFormSelect,
8 6
   ProFormDatePicker,
9
-  ProFormRadio
10 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 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 13
 import moment from 'moment';
22
-import { trim } from 'lodash';
23
-let editor: any;
24
-
25 14
 export type SocialResponProps = {
26 15
   onCancel: (flag?: boolean, formVals?: any) => void;
27 16
   onSubmit: (values: any) => Promise<void>;
@@ -38,19 +27,12 @@ let toolbarOptions = [
38 27
   ['bold', 'italic', 'underline', 'strike'],        // 切换按钮
39 28
   ['blockquote', 'code-block'],
40 29
   ['link', 'image'],
41
-
42
-
43
-  [{ 'header': 1 }, { 'header': 2 }],               // 用户自定义按钮值
44 30
   [{ 'list': 'ordered' }, { 'list': 'bullet' }],
45 31
   [{ 'script': 'sub' }, { 'script': 'super' }],      // 上标/下标
46 32
   [{ 'indent': '-1' }, { 'indent': '+1' }],          // 减少缩进/缩进
47 33
   [{ 'direction': 'rtl' }],                         // 文本下划线
48
-
49
-
50 34
   [{ 'size': ['small', false, 'large', 'huge'] }],  // 用户自定义下拉
51 35
   [{ 'header': [1, 2, 3, 4, 5, 6, false] }],
52
-
53
-
54 36
   [{ 'color': [] }, { 'background': [] }],          // 主题默认下拉,使用主题提供的值
55 37
   [{ 'font': [] }],
56 38
   [{ 'align': [] }],
@@ -60,49 +42,24 @@ let toolbarOptions = [
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 45
 const SocialResponForm: React.FC<SocialResponProps> = (props) => {
94 46
 
95 47
   const [form] = Form.useForm();
96 48
   const intl = useIntl();
97 49
   let editorRef = useRef<HTMLDivElement>(null)
50
+  const [editorHtml, setEditorHtml] = useState('');
98 51
 
52
+  const modules = {
53
+    toolbar: {
54
+      container: toolbarOptions, // 使用自定义工具栏选项
55
+    },
56
+  };
99 57
   useEffect(() => {
100
-    console.log(props)
101 58
     form.resetFields();
102
-    if(!props.open) return;
59
+    if (!props.open) return;
103 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 63
     if (props.currentRow) {
107 64
       form.setFieldsValue({
108 65
         digest,
@@ -131,93 +88,31 @@ const SocialResponForm: React.FC<SocialResponProps> = (props) => {
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 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 95
   const [fileList, setFileList] = useState<any>([]);
202 96
   const [surface, setSurface] = useState<any>('');
203 97
 
98
+  const [editWho, setEditWho] = useState('')
99
+  const [userIndex, setUserIndex] = useState(0)
204 100
   const [checkTop, setCheckTop] = useState<any>('')
205 101
   const handleOk = async () => {
206 102
     let data = form.getFieldsValue()
207 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 108
     // console.log('htmlStr----', htmlStr)
214
-    editor.setContents(content)
109
+    // editor.setContents(content)
215 110
     data.date = moment(data.date).format('YYYY-MM-DD');
216
-    if(checkTop){
111
+    if (checkTop) {
217 112
       data.top = 1;
218
-    }else{
113
+    } else {
219 114
       data.top = 0;
220
-    } 
115
+    }
221 116
     // data.top = checkTop;
222 117
     let formData = {
223 118
       ...data,
@@ -230,8 +125,7 @@ const SocialResponForm: React.FC<SocialResponProps> = (props) => {
230 125
     })
231 126
     setFileList([]);
232 127
     setCheckTop(false)
233
-    editor.clipboard.dangerouslyPasteHTML(0, '')
234
-    editor.setContents([])
128
+    setEditorHtml('');
235 129
     props.onSubmit(formData);
236 130
   };
237 131
   const handleCancel = () => {
@@ -242,26 +136,68 @@ const SocialResponForm: React.FC<SocialResponProps> = (props) => {
242 136
     })
243 137
     setFileList([]);
244 138
     setCheckTop(false)
245
-    editor.clipboard.dangerouslyPasteHTML(0, '')
246
-    editor.setContents([])
139
+    setEditorHtml('');
247 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 203
   return (
@@ -323,7 +259,7 @@ const SocialResponForm: React.FC<SocialResponProps> = (props) => {
323 259
           ]
324 260
           }
325 261
         />
326
-         
262
+
327 263
         <ProFormText
328 264
           name="key"
329 265
           label='网页关键字'
@@ -343,13 +279,13 @@ const SocialResponForm: React.FC<SocialResponProps> = (props) => {
343 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 289
         </ProForm.Item>
354 290
       </ProForm>
355 291
     </Drawer>

Ładowanie…
Anuluj
Zapisz