@sy-common/wang-editor 1.0.0-beta.20 → 1.0.0-beta.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/hr-color-menu.js +177 -0
- package/src/index.vue +469 -381
package/package.json
CHANGED
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 分割线颜色菜单插件
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const MENU_KEY = 'hrColor'
|
|
6
|
+
|
|
7
|
+
// 颜色列表
|
|
8
|
+
const COLOR_LIST = [
|
|
9
|
+
'',
|
|
10
|
+
'rgb(0, 0, 0)',
|
|
11
|
+
'rgb(38, 38, 38)',
|
|
12
|
+
'rgb(89, 89, 89)',
|
|
13
|
+
'rgb(140, 140, 140)',
|
|
14
|
+
'rgb(191, 191, 191)',
|
|
15
|
+
'rgb(225, 60, 57)',
|
|
16
|
+
'rgb(231, 95, 51)',
|
|
17
|
+
'rgb(235, 144, 58)',
|
|
18
|
+
'rgb(245, 219, 77)',
|
|
19
|
+
'rgb(114, 192, 64)',
|
|
20
|
+
'rgb(89, 191, 192)',
|
|
21
|
+
'rgb(66, 144, 247)',
|
|
22
|
+
'rgb(54, 88, 226)',
|
|
23
|
+
'rgb(106, 57, 201)',
|
|
24
|
+
'rgb(216, 68, 147)',
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
// 图标 SVG:分割线 + 彩色条
|
|
28
|
+
const HR_COLOR_SVG = '<svg viewBox="0 0 1092 1024"><path d="M0 51.2m51.2 0l989.866667 0q51.2 0 51.2 51.2l0 0q0 51.2-51.2 51.2l-989.866667 0q-51.2 0-51.2-51.2l0 0q0 51.2 51.2-51.2Z" fill="#999"/><path d="M0 460.8m51.2 0l170.666667 0q51.2 0 51.2 51.2l0 0q0 51.2-51.2 51.2l-170.666667 0q-51.2 0-51.2-51.2l0 0q0 51.2 51.2-51.2Z" fill="#ff4d4f"/><path d="M273 460.8m51.2 0l170.666667 0q51.2 0 51.2 51.2l0 0q0 51.2-51.2 51.2l-170.666667 0q-51.2 0-51.2-51.2l0 0q0 51.2 51.2-51.2Z" fill="#faad14"/><path d="M546 460.8m51.2 0l170.666667 0q51.2 0 51.2 51.2l0 0q0 51.2-51.2 51.2l-170.666667 0q-51.2 0-51.2-51.2l0 0q0 51.2 51.2-51.2Z" fill="#52c41a"/><path d="M819 460.8m51.2 0l170.666667 0q51.2 0 51.2 51.2l0 0q0 51.2-51.2 51.2l-170.666667 0q-51.2 0-51.2-51.2l0 0q0 51.2 51.2-51.2Z" fill="#1890ff"/><path d="M0 870.4m51.2 0l989.866667 0q51.2 0 51.2 51.2l0 0q0 51.2-51.2 51.2l-989.866667 0q-51.2 0-51.2-51.2l0 0q0 51.2 51.2-51.2Z" fill="#999"/></svg>'
|
|
29
|
+
|
|
30
|
+
function checkHrSelected(editor) {
|
|
31
|
+
if (editor.isDisabled()) return false
|
|
32
|
+
if (!editor.selection) return false
|
|
33
|
+
try {
|
|
34
|
+
const nodeGen = editor.nodes({ match: n => n.type === 'divider', universal: true })
|
|
35
|
+
for (const [node] of nodeGen) {
|
|
36
|
+
if (node) return true
|
|
37
|
+
}
|
|
38
|
+
} catch (e) {}
|
|
39
|
+
return false
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function getDividerCurrentColor(editor) {
|
|
43
|
+
try {
|
|
44
|
+
const [nodeEntry] = editor.nodes({ match: n => n.type === 'divider', universal: true })
|
|
45
|
+
if (!nodeEntry) return ''
|
|
46
|
+
const [node] = nodeEntry
|
|
47
|
+
const domNode = editor.toDOMNode(node)
|
|
48
|
+
if (domNode) {
|
|
49
|
+
const hrElement = domNode.querySelector('hr') || domNode
|
|
50
|
+
return hrElement.getAttribute('data-divider-color') || ''
|
|
51
|
+
}
|
|
52
|
+
} catch (e) {}
|
|
53
|
+
return ''
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function setDividerColor(editor, color) {
|
|
57
|
+
try {
|
|
58
|
+
const [nodeEntry] = editor.nodes({ match: n => n.type === 'divider', universal: true })
|
|
59
|
+
if (!nodeEntry) return
|
|
60
|
+
const [node] = nodeEntry
|
|
61
|
+
|
|
62
|
+
const domNode = editor.toDOMNode(node)
|
|
63
|
+
if (domNode) {
|
|
64
|
+
const hrElement = domNode.querySelector('hr') || domNode
|
|
65
|
+
if (color) {
|
|
66
|
+
hrElement.style.border = 'none'
|
|
67
|
+
hrElement.style.borderTop = '2px solid ' + color
|
|
68
|
+
hrElement.style.margin = '10px 0'
|
|
69
|
+
hrElement.setAttribute('data-divider-color', color)
|
|
70
|
+
} else {
|
|
71
|
+
hrElement.style.border = ''
|
|
72
|
+
hrElement.style.borderTop = ''
|
|
73
|
+
hrElement.style.margin = ''
|
|
74
|
+
hrElement.removeAttribute('data-divider-color')
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
} catch (e) {
|
|
78
|
+
console.warn('setDividerColor error:', e)
|
|
79
|
+
}
|
|
80
|
+
editor.emit('change')
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// 创建颜色选择面板
|
|
84
|
+
function createColorPanel() {
|
|
85
|
+
const $content = document.createElement('div')
|
|
86
|
+
$content.style.cssText = 'padding: 10px; display: flex; flex-wrap: wrap; gap: 4px; width: 200px;'
|
|
87
|
+
|
|
88
|
+
COLOR_LIST.forEach(color => {
|
|
89
|
+
const $item = document.createElement('button')
|
|
90
|
+
$item.type = 'button'
|
|
91
|
+
$item.style.cssText = color
|
|
92
|
+
? 'width: 24px; height: 24px; border: 1px solid #d9d9d9; border-radius: 4px; cursor: pointer; background-color: ' + color + ';'
|
|
93
|
+
: 'width: 24px; height: 24px; border: 2px dashed #d9d9d9; border-radius: 4px; cursor: pointer; background: transparent;'
|
|
94
|
+
$item.setAttribute('data-color', color || '')
|
|
95
|
+
$content.appendChild($item)
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
return $content
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// 从 HTML 提取所有 hr 的颜色
|
|
102
|
+
function extractHrColorsFromHtml(html) {
|
|
103
|
+
const colors = []
|
|
104
|
+
const tempDiv = document.createElement('div')
|
|
105
|
+
tempDiv.innerHTML = html
|
|
106
|
+
const hrs = tempDiv.querySelectorAll('hr')
|
|
107
|
+
hrs.forEach(hr => {
|
|
108
|
+
const style = hr.getAttribute('style') || ''
|
|
109
|
+
const colorMatch = style.match(/border-top\s*:\s*\d+px\s+solid\s+(rgb\([^)]+\)|#[a-fA-F0-9]+)/i)
|
|
110
|
+
colors.push(colorMatch ? colorMatch[1] : '')
|
|
111
|
+
})
|
|
112
|
+
return colors
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function registerHrColorMenu(Boot) {
|
|
116
|
+
const menuConf = {
|
|
117
|
+
key: MENU_KEY,
|
|
118
|
+
factory() {
|
|
119
|
+
let $panel = null
|
|
120
|
+
|
|
121
|
+
return {
|
|
122
|
+
title: '分割线颜色',
|
|
123
|
+
iconSvg: HR_COLOR_SVG,
|
|
124
|
+
tag: 'button',
|
|
125
|
+
showDropPanel: true,
|
|
126
|
+
|
|
127
|
+
isDisabled(editor) {
|
|
128
|
+
return !checkHrSelected(editor)
|
|
129
|
+
},
|
|
130
|
+
|
|
131
|
+
isActive(editor) {
|
|
132
|
+
return false
|
|
133
|
+
},
|
|
134
|
+
|
|
135
|
+
getValue(editor) {
|
|
136
|
+
return getDividerCurrentColor(editor)
|
|
137
|
+
},
|
|
138
|
+
|
|
139
|
+
exec(editor, value) {
|
|
140
|
+
setDividerColor(editor, value)
|
|
141
|
+
},
|
|
142
|
+
|
|
143
|
+
getPanelContentElem(editor) {
|
|
144
|
+
if (!$panel) {
|
|
145
|
+
$panel = createColorPanel()
|
|
146
|
+
$panel.querySelectorAll('button').forEach(btn => {
|
|
147
|
+
btn.addEventListener('click', () => {
|
|
148
|
+
const color = btn.getAttribute('data-color')
|
|
149
|
+
setDividerColor(editor, color)
|
|
150
|
+
editor.hidePanelOrModal()
|
|
151
|
+
})
|
|
152
|
+
})
|
|
153
|
+
}
|
|
154
|
+
return $panel
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
try {
|
|
161
|
+
Boot.registerMenu(menuConf)
|
|
162
|
+
} catch (e) {
|
|
163
|
+
if (!e.message.includes('already') && !e.message.includes('duplicate')) {
|
|
164
|
+
console.warn('HrColorMenu register warning:', e.message)
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export {
|
|
170
|
+
MENU_KEY,
|
|
171
|
+
COLOR_LIST,
|
|
172
|
+
registerHrColorMenu,
|
|
173
|
+
checkHrSelected,
|
|
174
|
+
getDividerCurrentColor,
|
|
175
|
+
setDividerColor,
|
|
176
|
+
extractHrColorsFromHtml
|
|
177
|
+
}
|
package/src/index.vue
CHANGED
|
@@ -1,381 +1,469 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<div style="z-index: 999">
|
|
3
|
-
<!-- 工具栏 -->
|
|
4
|
-
<Toolbar
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
/>
|
|
8
|
-
<!-- 编辑器 -->
|
|
9
|
-
<Editor
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
/>
|
|
16
|
-
</div>
|
|
17
|
-
</template>
|
|
18
|
-
|
|
19
|
-
<script>
|
|
20
|
-
import '@wangeditor-next/editor/dist/css/style.css'
|
|
21
|
-
import {oneOf} from '@lambo-design/shared/utils/platform';
|
|
22
|
-
import {Editor, Toolbar} from '@wangeditor-next/editor-for-vue2'
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
type: Boolean,
|
|
94
|
-
default: false
|
|
95
|
-
},
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
type:
|
|
106
|
-
default:
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
"
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
"
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
"
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
"rgb(
|
|
149
|
-
"rgb(
|
|
150
|
-
"rgb(
|
|
151
|
-
"rgb(
|
|
152
|
-
"rgb(
|
|
153
|
-
"rgb(
|
|
154
|
-
"rgb(
|
|
155
|
-
"rgb(
|
|
156
|
-
"rgb(
|
|
157
|
-
"rgb(
|
|
158
|
-
"rgb(
|
|
159
|
-
"rgb(
|
|
160
|
-
"rgb(
|
|
161
|
-
"rgb(
|
|
162
|
-
"rgb(
|
|
163
|
-
"rgb(
|
|
164
|
-
"rgb(
|
|
165
|
-
"rgb(
|
|
166
|
-
"rgb(
|
|
167
|
-
"rgb(
|
|
168
|
-
"rgb(
|
|
169
|
-
"rgb(
|
|
170
|
-
"rgb(
|
|
171
|
-
"rgb(
|
|
172
|
-
"rgb(
|
|
173
|
-
"rgb(
|
|
174
|
-
"rgb(
|
|
175
|
-
"rgb(
|
|
176
|
-
"rgb(
|
|
177
|
-
"rgb(
|
|
178
|
-
"rgb(
|
|
179
|
-
"rgb(255,
|
|
180
|
-
"rgb(255,
|
|
181
|
-
"rgb(255,
|
|
182
|
-
"rgb(
|
|
183
|
-
"rgb(
|
|
184
|
-
"rgb(
|
|
185
|
-
"rgb(
|
|
186
|
-
"rgb(
|
|
187
|
-
"rgb(
|
|
188
|
-
"rgb(
|
|
189
|
-
"rgb(
|
|
190
|
-
"rgb(
|
|
191
|
-
"rgb(
|
|
192
|
-
"rgb(
|
|
193
|
-
"rgb(
|
|
194
|
-
"rgb(
|
|
195
|
-
"rgb(
|
|
196
|
-
"rgb(
|
|
197
|
-
"rgb(
|
|
198
|
-
"rgb(
|
|
199
|
-
"rgb(
|
|
200
|
-
"rgb(
|
|
201
|
-
"rgb(
|
|
202
|
-
"rgb(
|
|
203
|
-
"rgb(
|
|
204
|
-
"rgb(
|
|
205
|
-
"rgb(
|
|
206
|
-
"rgb(
|
|
207
|
-
"rgb(
|
|
208
|
-
"rgb(
|
|
209
|
-
"rgb(
|
|
210
|
-
"rgb(
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
const
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
//
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
1
|
+
<template>
|
|
2
|
+
<div style="z-index: 999">
|
|
3
|
+
<!-- 工具栏 -->
|
|
4
|
+
<Toolbar
|
|
5
|
+
:editor="editor"
|
|
6
|
+
:defaultConfig="toolbarConfig"
|
|
7
|
+
/>
|
|
8
|
+
<!-- 编辑器 -->
|
|
9
|
+
<Editor
|
|
10
|
+
:style="editorStyle"
|
|
11
|
+
:defaultConfig="editorConfig"
|
|
12
|
+
v-model="html"
|
|
13
|
+
@onChange="onChange"
|
|
14
|
+
@onCreated="onCreated"
|
|
15
|
+
/>
|
|
16
|
+
</div>
|
|
17
|
+
</template>
|
|
18
|
+
|
|
19
|
+
<script>
|
|
20
|
+
import '@wangeditor-next/editor/dist/css/style.css'
|
|
21
|
+
import {oneOf} from '@lambo-design/shared/utils/platform';
|
|
22
|
+
import {Editor, Toolbar} from '@wangeditor-next/editor-for-vue2'
|
|
23
|
+
import {Boot} from '@wangeditor-next/editor'
|
|
24
|
+
import {registerHrColorMenu, MENU_KEY, extractHrColorsFromHtml} from './hr-color-menu'
|
|
25
|
+
|
|
26
|
+
// 注册分割线颜色菜单(模块级别,只执行一次)
|
|
27
|
+
registerHrColorMenu(Boot)
|
|
28
|
+
|
|
29
|
+
export default {
|
|
30
|
+
name: 'WangEditor',
|
|
31
|
+
components: {Editor, Toolbar},
|
|
32
|
+
props: {
|
|
33
|
+
value: {
|
|
34
|
+
type: String,
|
|
35
|
+
default: ''
|
|
36
|
+
},
|
|
37
|
+
/**
|
|
38
|
+
* 绑定的值的类型, enum: ['html', 'text']
|
|
39
|
+
*/
|
|
40
|
+
valueType: {
|
|
41
|
+
type: String,
|
|
42
|
+
default: 'html',
|
|
43
|
+
validator: (val) => {
|
|
44
|
+
return oneOf(val, ['html', 'text'])
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
/**
|
|
48
|
+
* @description 设置change事件触发时间间隔
|
|
49
|
+
*/
|
|
50
|
+
changeInterval: {
|
|
51
|
+
type: Number,
|
|
52
|
+
default: 200
|
|
53
|
+
},
|
|
54
|
+
/**
|
|
55
|
+
* @description 是否开启本地存储
|
|
56
|
+
*/
|
|
57
|
+
cache: {
|
|
58
|
+
type: Boolean,
|
|
59
|
+
default: true
|
|
60
|
+
},
|
|
61
|
+
/**
|
|
62
|
+
* @description 是否使用 base64 保存图片
|
|
63
|
+
*/
|
|
64
|
+
uploadImgShowBase64: {
|
|
65
|
+
type: Boolean,
|
|
66
|
+
default: false
|
|
67
|
+
},
|
|
68
|
+
/**
|
|
69
|
+
* 上传图片上下文
|
|
70
|
+
*/
|
|
71
|
+
ossServerContext: {
|
|
72
|
+
type: String,
|
|
73
|
+
default: ''
|
|
74
|
+
},
|
|
75
|
+
/**
|
|
76
|
+
* 上传图片Url地址
|
|
77
|
+
*/
|
|
78
|
+
ossFilePutUrl: {
|
|
79
|
+
type: String,
|
|
80
|
+
default: "/oss/file/put"
|
|
81
|
+
},
|
|
82
|
+
/**
|
|
83
|
+
* 获取图片url地址
|
|
84
|
+
*/
|
|
85
|
+
ossFileGetImgUrl: {
|
|
86
|
+
type: String,
|
|
87
|
+
default: "/oss/file/get/"
|
|
88
|
+
},
|
|
89
|
+
/**
|
|
90
|
+
* @description 是否保留粘贴内容的样式
|
|
91
|
+
*/
|
|
92
|
+
preservePasteStyle: {
|
|
93
|
+
type: Boolean,
|
|
94
|
+
default: false
|
|
95
|
+
},
|
|
96
|
+
|
|
97
|
+
readOnly: {
|
|
98
|
+
type: Boolean,
|
|
99
|
+
default: false
|
|
100
|
+
},
|
|
101
|
+
/**
|
|
102
|
+
* @description: 隐藏菜单
|
|
103
|
+
*/
|
|
104
|
+
hideMenu: {
|
|
105
|
+
type: Array,
|
|
106
|
+
default: () => []
|
|
107
|
+
},
|
|
108
|
+
|
|
109
|
+
height: {
|
|
110
|
+
type: [Number, String],
|
|
111
|
+
default: 400
|
|
112
|
+
}
|
|
113
|
+
},
|
|
114
|
+
data() {
|
|
115
|
+
return {
|
|
116
|
+
editor: null,
|
|
117
|
+
html: '',
|
|
118
|
+
hrCount: 0, // 追踪分割线数量
|
|
119
|
+
toolbarConfig: {
|
|
120
|
+
insertKeys: {
|
|
121
|
+
index: 24, // 在分割线菜单后面插入
|
|
122
|
+
keys: [MENU_KEY]
|
|
123
|
+
}
|
|
124
|
+
},
|
|
125
|
+
editorConfig: {
|
|
126
|
+
placeholder: '请输入内容...',
|
|
127
|
+
MENU_CONF: {
|
|
128
|
+
fontFamily: {
|
|
129
|
+
fontFamilyList: [
|
|
130
|
+
"黑体",
|
|
131
|
+
{
|
|
132
|
+
name: "仿宋",
|
|
133
|
+
value: "仿宋"
|
|
134
|
+
},
|
|
135
|
+
"仿宋_GB2312",
|
|
136
|
+
"楷体",
|
|
137
|
+
"楷体_GB2312",
|
|
138
|
+
"标楷体",
|
|
139
|
+
"华文仿宋",
|
|
140
|
+
"华文楷体",
|
|
141
|
+
{
|
|
142
|
+
name: "宋体",
|
|
143
|
+
value: "宋体"
|
|
144
|
+
}, "隶书", "方正小标宋简体", "思源宋体", "微软雅黑", "Arial", "Tahoma", "Verdana", "Times New Roman", "Courier New"]
|
|
145
|
+
},
|
|
146
|
+
color: {
|
|
147
|
+
colors: [
|
|
148
|
+
"rgb(0, 0, 0)",
|
|
149
|
+
"rgb(38, 38, 38)",
|
|
150
|
+
"rgb(89, 89, 89)",
|
|
151
|
+
"rgb(140, 140, 140)",
|
|
152
|
+
"rgb(191, 191, 191)",
|
|
153
|
+
"rgb(217, 217, 217)",
|
|
154
|
+
"rgb(233, 233, 233)",
|
|
155
|
+
"rgb(245, 245, 245)",
|
|
156
|
+
"rgb(250, 250, 250)",
|
|
157
|
+
"rgb(255, 255, 255)",
|
|
158
|
+
"rgb(225, 60, 57)",
|
|
159
|
+
"rgb(231, 95, 51)",
|
|
160
|
+
"rgb(235, 144, 58)",
|
|
161
|
+
"rgb(245, 219, 77)",
|
|
162
|
+
"rgb(114, 192, 64)",
|
|
163
|
+
"rgb(89, 191, 192)",
|
|
164
|
+
"rgb(66, 144, 247)",
|
|
165
|
+
"rgb(54, 88, 226)",
|
|
166
|
+
"rgb(106, 57, 201)",
|
|
167
|
+
"rgb(216, 68, 147)",
|
|
168
|
+
"rgb(251, 233, 230)",
|
|
169
|
+
"rgb(252, 237, 225)",
|
|
170
|
+
"rgb(252, 239, 212)",
|
|
171
|
+
"rgb(252, 251, 207)",
|
|
172
|
+
"rgb(231, 246, 213)",
|
|
173
|
+
"rgb(218, 244, 240)",
|
|
174
|
+
"rgb(217, 237, 250)",
|
|
175
|
+
"rgb(224, 232, 250)",
|
|
176
|
+
"rgb(237, 225, 248)",
|
|
177
|
+
"rgb(246, 226, 234)",
|
|
178
|
+
"rgb(255, 163, 158)",
|
|
179
|
+
"rgb(255, 187, 150)",
|
|
180
|
+
"rgb(255, 213, 145)",
|
|
181
|
+
"rgb(255, 251, 143)",
|
|
182
|
+
"rgb(183, 235, 143)",
|
|
183
|
+
"rgb(135, 232, 222)",
|
|
184
|
+
"rgb(145, 213, 255)",
|
|
185
|
+
"rgb(173, 198, 255)",
|
|
186
|
+
"rgb(211, 173, 247)",
|
|
187
|
+
"rgb(255, 173, 210)",
|
|
188
|
+
"rgb(255, 77, 79)",
|
|
189
|
+
"rgb(255, 122, 69)",
|
|
190
|
+
"rgb(255, 169, 64)",
|
|
191
|
+
"rgb(255, 236, 61)",
|
|
192
|
+
"rgb(115, 209, 61)",
|
|
193
|
+
"rgb(54, 207, 201)",
|
|
194
|
+
"rgb(64, 169, 255)",
|
|
195
|
+
"rgb(89, 126, 247)",
|
|
196
|
+
"rgb(146, 84, 222)",
|
|
197
|
+
"rgb(247, 89, 171)",
|
|
198
|
+
"rgb(207, 19, 34)",
|
|
199
|
+
"rgb(212, 56, 13)",
|
|
200
|
+
"rgb(212, 107, 8)",
|
|
201
|
+
"rgb(212, 177, 6)",
|
|
202
|
+
"rgb(56, 158, 13)",
|
|
203
|
+
"rgb(8, 151, 156)",
|
|
204
|
+
"rgb(9, 109, 217)",
|
|
205
|
+
"rgb(29, 57, 196)",
|
|
206
|
+
"rgb(83, 29, 171)",
|
|
207
|
+
"rgb(196, 29, 127)",
|
|
208
|
+
"rgb(130, 0, 20)",
|
|
209
|
+
"rgb(135, 20, 0)",
|
|
210
|
+
"rgb(135, 56, 0)",
|
|
211
|
+
"rgb(97, 71, 0)",
|
|
212
|
+
"rgb(19, 82, 0)",
|
|
213
|
+
"rgb(0, 71, 79)",
|
|
214
|
+
"rgb(0, 58, 140)",
|
|
215
|
+
"rgb(6, 17, 120)",
|
|
216
|
+
"rgb(34, 7, 94)",
|
|
217
|
+
"rgb(120, 6, 80)",
|
|
218
|
+
"rgb(250,64,6)"
|
|
219
|
+
]
|
|
220
|
+
},
|
|
221
|
+
uploadImage: {
|
|
222
|
+
//上传图片配置
|
|
223
|
+
server: this.ossServerContext + this.ossFilePutUrl, //上传接口地址
|
|
224
|
+
fieldName: 'file', //上传文件名
|
|
225
|
+
methods: 'post',
|
|
226
|
+
metaWithUrl: true, // 参数拼接到 url 上
|
|
227
|
+
//自定义插入图片
|
|
228
|
+
customInsert: (res, insertFn) => {
|
|
229
|
+
let self = this;
|
|
230
|
+
const imgInfo = res.data[0] || {}
|
|
231
|
+
const {fileId, fileName, contentType} = imgInfo
|
|
232
|
+
insertFn(self.ossServerContext + self.ossFileGetImgUrl + fileId, fileName, self.ossServerContext + self.ossFileGetImgUrl + fileId)
|
|
233
|
+
},
|
|
234
|
+
|
|
235
|
+
},
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
},
|
|
240
|
+
watch: {
|
|
241
|
+
value: {
|
|
242
|
+
handler: function (newVal, oldVal) {
|
|
243
|
+
const isEmpty = !newVal || newVal === '<p><br></p>'
|
|
244
|
+
const wasEmpty = !oldVal || oldVal === '<p><br></p>'
|
|
245
|
+
|
|
246
|
+
if (!isEmpty && wasEmpty) {
|
|
247
|
+
if (this.valueType === 'html') {
|
|
248
|
+
let html = newVal;
|
|
249
|
+
const hrColors = extractHrColorsFromHtml(html)
|
|
250
|
+
|
|
251
|
+
// 【关键修复】在 setHtml 之前,先从 HTML 字符串计算 hr 数量
|
|
252
|
+
// 这样 setHtml 触发 onChange 时,hrCount 已经是正确的
|
|
253
|
+
const tempDivForCount = document.createElement('div')
|
|
254
|
+
tempDivForCount.innerHTML = html
|
|
255
|
+
this.hrCount = tempDivForCount.querySelectorAll('hr').length
|
|
256
|
+
|
|
257
|
+
// 表格处理...
|
|
258
|
+
const tableRegex = /(<table)([\s\S]*?)(<\/table>)/gi;
|
|
259
|
+
html = html.replace(tableRegex, (match, startTag, content, endTag) => {
|
|
260
|
+
const updatedContent = content.replace(/border-width:\s*(\d+)px;/g, 'border-width: $1;');
|
|
261
|
+
return `${startTag}${updatedContent}${endTag}`;
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
const tempDiv = document.createElement('div');
|
|
265
|
+
tempDiv.innerHTML = html;
|
|
266
|
+
const tables = tempDiv.querySelectorAll('table');
|
|
267
|
+
tables.forEach(table => {
|
|
268
|
+
table.setAttribute('data-processed', 'true');
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
this.editor.setHtml(tempDiv.innerHTML)
|
|
272
|
+
|
|
273
|
+
// setHtml 后异步设置 hr 颜色样式到 DOM
|
|
274
|
+
if (hrColors.length > 0) {
|
|
275
|
+
setTimeout(() => {
|
|
276
|
+
try {
|
|
277
|
+
const container = this.editor.getEditableContainer()
|
|
278
|
+
if (!container) return
|
|
279
|
+
const hrs = container.querySelectorAll('hr')
|
|
280
|
+
hrs.forEach((hr, index) => {
|
|
281
|
+
const color = hrColors[index] || ''
|
|
282
|
+
if (color) {
|
|
283
|
+
hr.style.border = 'none'
|
|
284
|
+
hr.style.borderTop = '2px solid ' + color
|
|
285
|
+
hr.style.margin = '10px 0'
|
|
286
|
+
hr.setAttribute('data-divider-color', color)
|
|
287
|
+
}
|
|
288
|
+
})
|
|
289
|
+
this.hrCount = hrs.length
|
|
290
|
+
} catch (e) {
|
|
291
|
+
console.warn('设置 hr 颜色失败:', e)
|
|
292
|
+
}
|
|
293
|
+
}, 100)
|
|
294
|
+
}
|
|
295
|
+
} else {
|
|
296
|
+
this.editor.insertText(newVal)
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
},
|
|
301
|
+
|
|
302
|
+
readOnly: {
|
|
303
|
+
handler(newVal){
|
|
304
|
+
this.editorConfig.readOnly = newVal
|
|
305
|
+
if(newVal){
|
|
306
|
+
this.toolbarConfig.toolbarKeys = ['fullScreen']
|
|
307
|
+
}
|
|
308
|
+
},
|
|
309
|
+
immediate: true,
|
|
310
|
+
},
|
|
311
|
+
|
|
312
|
+
hideMenu: {
|
|
313
|
+
handler(newVal){
|
|
314
|
+
this.toolbarConfig.excludeKeys = newVal
|
|
315
|
+
},
|
|
316
|
+
immediate: true,
|
|
317
|
+
deep: true
|
|
318
|
+
}
|
|
319
|
+
},
|
|
320
|
+
computed: {
|
|
321
|
+
editorStyle() {
|
|
322
|
+
let finalHeight = this.height;
|
|
323
|
+
// 如果是数字,自动拼接 px 单位
|
|
324
|
+
if (typeof finalHeight === 'number') {
|
|
325
|
+
finalHeight = `${finalHeight}px`;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
return {
|
|
329
|
+
height: finalHeight,
|
|
330
|
+
'overflow-y': 'hidden'
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
},
|
|
334
|
+
methods: {
|
|
335
|
+
|
|
336
|
+
onCreated(editor) {
|
|
337
|
+
this.editor = Object.seal(editor) // 【注意】一定要用 Object.seal() 否则会报错
|
|
338
|
+
|
|
339
|
+
let html = this.value
|
|
340
|
+
if (html) {
|
|
341
|
+
this.editor.setHtml(html)
|
|
342
|
+
// 处理已有颜色的 hr 元素,从 style 中提取颜色
|
|
343
|
+
this.$nextTick(() => {
|
|
344
|
+
try {
|
|
345
|
+
const container = this.editor.getEditableContainer()
|
|
346
|
+
if (!container) return
|
|
347
|
+
const hrs = container.querySelectorAll('hr')
|
|
348
|
+
// 初始化 hrCount,记录已有分割线数量
|
|
349
|
+
this.hrCount = hrs.length
|
|
350
|
+
hrs.forEach(hr => {
|
|
351
|
+
const style = hr.getAttribute('style') || ''
|
|
352
|
+
// 匹配 border 中的颜色
|
|
353
|
+
const colorMatch = style.match(/border\s*:[^;]*solid\s+(rgb\([^)]+\)|#[a-fA-F0-9]+)/i)
|
|
354
|
+
if (colorMatch) {
|
|
355
|
+
hr.setAttribute('data-divider-color', colorMatch[1])
|
|
356
|
+
}
|
|
357
|
+
})
|
|
358
|
+
} catch (e) {
|
|
359
|
+
console.warn('处理已存在 hr 颜色失败:', e)
|
|
360
|
+
}
|
|
361
|
+
})
|
|
362
|
+
}
|
|
363
|
+
},
|
|
364
|
+
onChange(editor) {
|
|
365
|
+
let self = this;
|
|
366
|
+
let html = this.editor.getHtml()
|
|
367
|
+
|
|
368
|
+
// 创建一个临时的 div 容器来解析 HTML
|
|
369
|
+
const tempDiv = document.createElement('div');
|
|
370
|
+
tempDiv.innerHTML = html;
|
|
371
|
+
|
|
372
|
+
// 从编辑器 DOM 获取所有 hr 的颜色,写入输出 HTML
|
|
373
|
+
const editorContainer = this.editor.getEditableContainer()
|
|
374
|
+
const editorHrs = editorContainer ? editorContainer.querySelectorAll('hr') : []
|
|
375
|
+
const outputHrs = tempDiv.querySelectorAll('hr')
|
|
376
|
+
|
|
377
|
+
// 检测新增的分割线,设置默认颜色
|
|
378
|
+
const DEFAULT_HR_COLOR = 'rgb(250, 64, 6)'
|
|
379
|
+
if (editorHrs.length > this.hrCount) {
|
|
380
|
+
// 有新增的分割线:只给没有颜色的 hr 设置默认色
|
|
381
|
+
for (let i = this.hrCount; i < editorHrs.length; i++) {
|
|
382
|
+
const hr = editorHrs[i]
|
|
383
|
+
// 【关键】如果 hr 已经有颜色(回显的),不要覆盖
|
|
384
|
+
if (!hr.getAttribute('data-divider-color')) {
|
|
385
|
+
hr.style.border = 'none'
|
|
386
|
+
hr.style.borderTop = '2px solid ' + DEFAULT_HR_COLOR
|
|
387
|
+
hr.style.margin = '10px 0'
|
|
388
|
+
hr.setAttribute('data-divider-color', DEFAULT_HR_COLOR)
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
this.hrCount = editorHrs.length
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
editorHrs.forEach((hr, index) => {
|
|
395
|
+
const color = hr.getAttribute('data-divider-color') || ''
|
|
396
|
+
if (color && outputHrs[index]) {
|
|
397
|
+
outputHrs[index].setAttribute('style', 'border: none; border-top: 2px solid ' + color + '; margin: 10px 0;')
|
|
398
|
+
}
|
|
399
|
+
})
|
|
400
|
+
|
|
401
|
+
// 处理 table 元素 -- 查找新增的表格并添加默认样式
|
|
402
|
+
const tables = tempDiv.querySelectorAll('table:not([data-processed])');
|
|
403
|
+
tables.forEach(table => {
|
|
404
|
+
const currentStyle = table.getAttribute('style') || '';
|
|
405
|
+
const newStyle = `border-collapse: collapse;border-style: solid; border-color: rgb(204, 204, 204);border-width: 1;${currentStyle}`.trim();
|
|
406
|
+
table.setAttribute('class', 'wangeditor-styled-table');
|
|
407
|
+
table.setAttribute('style', newStyle);
|
|
408
|
+
|
|
409
|
+
// 处理表头
|
|
410
|
+
const thCells = table.querySelectorAll('th');
|
|
411
|
+
thCells.forEach(cell => {
|
|
412
|
+
const cellCurrentStyle = cell.getAttribute('style') || '';
|
|
413
|
+
const cellNewStyle = `background-color: rgb(245, 242, 240);border-style: solid; border-color: rgb(204, 204, 204);border-width: 1;padding: 3px 5px;${cellCurrentStyle}`.trim();
|
|
414
|
+
cell.setAttribute('style', cellNewStyle);
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
// 处理单元格
|
|
418
|
+
const tdCells = table.querySelectorAll('td');
|
|
419
|
+
tdCells.forEach(cell => {
|
|
420
|
+
const cellCurrentStyle = cell.getAttribute('style') || '';
|
|
421
|
+
const cellNewStyle = `border-style: solid; border-color: rgb(204, 204, 204);border-width: 1;padding: 3px 5px;${cellCurrentStyle}`.trim();
|
|
422
|
+
cell.setAttribute('style', cellNewStyle);
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
// 标记已处理
|
|
426
|
+
table.setAttribute('data-processed', 'true');
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
// 更新处理后的 HTML
|
|
430
|
+
html = tempDiv.innerHTML;
|
|
431
|
+
this.$emit('input', self.valueType === 'html' ? html : editor.getText())
|
|
432
|
+
this.$emit('on-change', html, editor.getText())
|
|
433
|
+
},
|
|
434
|
+
customPaste(editor, event, callback) {
|
|
435
|
+
//console.log('ClipboardEvent 粘贴事件对象', event)
|
|
436
|
+
const html = event.clipboardData.getData('text/html') // 获取粘贴的 html
|
|
437
|
+
const text = event.clipboardData.getData('text/plain') // 获取粘贴的纯文本
|
|
438
|
+
const rtf = event.clipboardData.getData('text/rtf') // 获取 rtf 数据(如从 word wsp 复制粘贴)
|
|
439
|
+
|
|
440
|
+
//console.log("customPaste-html",rtf)
|
|
441
|
+
// 自定义插入内容
|
|
442
|
+
editor.setHtml(html)
|
|
443
|
+
|
|
444
|
+
// 返回 false ,阻止默认粘贴行为
|
|
445
|
+
event.preventDefault()
|
|
446
|
+
callback(false) // 返回值(注意,vue 事件的返回值,不能用 return)
|
|
447
|
+
|
|
448
|
+
// 返回 true ,继续默认的粘贴行为
|
|
449
|
+
// callback(true)
|
|
450
|
+
},
|
|
451
|
+
},
|
|
452
|
+
mounted() {
|
|
453
|
+
|
|
454
|
+
},
|
|
455
|
+
beforeDestroy() {
|
|
456
|
+
const editor = this.editor
|
|
457
|
+
if (editor == null) return
|
|
458
|
+
editor.destroy() // 组件销毁时,及时销毁 editor ,重要!!!
|
|
459
|
+
},
|
|
460
|
+
|
|
461
|
+
}
|
|
462
|
+
</script>
|
|
463
|
+
|
|
464
|
+
<style lang="less" scoped>
|
|
465
|
+
/deep/ .table-container {
|
|
466
|
+
width: fit-content !important;
|
|
467
|
+
margin: 10px auto !important;
|
|
468
|
+
}
|
|
469
|
+
</style>
|