Editor.vue
<template>
<el-upload
:action="uploadUrl"
:before-upload="handleBeforeUpload"
:on-success="handleUploadSuccess"
name="files"
:on-error="handleUploadError"
:show-file-list="false"
class="editor-img-uploader"
accept=".jpeg,.jpg,.png"
:headers="headers"
>
<i ref="uploadRef" class="Plus editor-img-uploader"></i>
</el-upload>
<!-- 使用input 标签劫持原本视频上传事件,实现视频上传 -->
<input
type="file"
accept="video/*"
name="file"
ref="uploadFileVideo"
id="uploadFileVideo"
@change="handleVideoUpload"
style="opacity: 0; width: 0; height: 0; cursor: pointer"
/>
<div class="editor">
<QuillEditor
id="editorId"
ref="myQuillEditor"
v-model:content="editorContent"
contentType="html"
@update:content="onContentChange"
:options="options"
/>
</div>
</template>
<script setup>
import { Random } from 'mockjs';
import { ElMessage } from 'element-plus';
import { QuillEditor, Quill } from '@vueup/vue-quill';
import '@vueup/vue-quill/dist/vue-quill.snow.css';
import { getCurrentInstance, reactive, ref, toRaw, computed, onMounted } from 'vue';
import { uploadFichFileToOSSNew } from '@/api/treelog/treelog';
import ImageBlot from './quill-image';
import Video from './quill-video';
import { useUserStoreWidthOut } from '@/store/modules/user';
const userStore = useUserStoreWidthOut();
const token = userStore.getToken;
const headers = {
ssoToken: token,
};
Quill.register(Video);
Quill.register(ImageBlot);
const { proxy } = getCurrentInstance();
const emit = defineEmits([
'update:content',
'uploadVideoConfig',
'getFileId',
'handleRichTextContentChange',
]);
const uploadFileVideo = ref();
const props = defineProps({
content: {
type: String,
default: '',
},
readOnly: {
type: Boolean,
default: false,
},
fileSizeLimit: {
type: Number,
default: 10,
},
});
const editorContent = computed({
get: () => props.content,
set: (val) => {
emit('update:content', val);
},
});
const myQuillEditor = ref(null);
const uploadUrl = 'https://mis-api-test4.petem.com.cn/mis/upload/file/oss?fType=24';
const oldContent = ref('');
const options = reactive({
theme: 'snow',
debug: 'warn',
modules: {
toolbar: {
container: [
['bold', 'italic', 'underline', 'strike'],
['blockquote', 'code-block'],
[{ list: 'ordered' }, { list: 'bullet' }],
[{ indent: '-1' }, { indent: '+1' }],
[{ size: ['small', false, 'large', 'huge'] }],
[{ header: [1, 2, 3, 4, 5, 6, false] }],
[{ color: [] }, { background: [] }],
[{ align: [] }],
['clean'],
['link', 'image', 'video'],
],
handlers: {
image: function (value) {
if (value) {
proxy.$refs.uploadRef.click();
} else {
Quill.format('image', true);
}
},
video: function (value) {
if (value) {
document.querySelector('#uploadFileVideo')?.click();
} else {
Quill.format('video', true);
}
},
},
},
},
placeholder: '请输入公告内容...',
readOnly: props.readOnly,
clipboard: {
matchers: [
[
'img',
(node, delta) => {
const src = node.getAttribute('src');
const id = node.getAttribute('id');
delta.insert({ image: { src, id: id } });
},
],
],
},
});
const titleConfig = ref([
{ Choice: '.ql-insertMetric', title: '跳转配置' },
{ Choice: '.ql-bold', title: '加粗' },
{ Choice: '.ql-italic', title: '斜体' },
{ Choice: '.ql-underline', title: '下划线' },
{ Choice: '.ql-header', title: '段落格式' },
{ Choice: '.ql-strike', title: '删除线' },
{ Choice: '.ql-blockquote', title: '块引用' },
{ Choice: '.ql-code', title: '插入代码' },
{ Choice: '.ql-code-block', title: '插入代码段' },
{ Choice: '.ql-font', title: '字体' },
{ Choice: '.ql-size', title: '字体大小' },
{ Choice: '.ql-list[value="ordered"]', title: '编号列表' },
{ Choice: '.ql-list[value="bullet"]', title: '项目列表' },
{ Choice: '.ql-direction', title: '文本方向' },
{ Choice: '.ql-header[value="1"]', title: 'h1' },
{ Choice: '.ql-header[value="2"]', title: 'h2' },
{ Choice: '.ql-align', title: '对齐方式' },
{ Choice: '.ql-color', title: '字体颜色' },
{ Choice: '.ql-background', title: '背景颜色' },
{ Choice: '.ql-image', title: '图像' },
{ Choice: '.ql-video', title: '视频' },
{ Choice: '.ql-link', title: '添加链接' },
{ Choice: '.ql-formula', title: '插入公式' },
{ Choice: '.ql-clean', title: '清除字体格式' },
{ Choice: '.ql-script[value="sub"]', title: '下标' },
{ Choice: '.ql-script[value="super"]', title: '上标' },
{ Choice: '.ql-indent[value="-1"]', title: '向左缩进' },
{ Choice: '.ql-indent[value="+1"]', title: '向右缩进' },
{ Choice: '.ql-header .ql-picker-label', title: '标题大小' },
{ Choice: '.ql-header .ql-picker-item[data-value="1"]', title: '标题一' },
{ Choice: '.ql-header .ql-picker-item[data-value="2"]', title: '标题二' },
{ Choice: '.ql-header .ql-picker-item[data-value="3"]', title: '标题三' },
{ Choice: '.ql-header .ql-picker-item[data-value="4"]', title: '标题四' },
{ Choice: '.ql-header .ql-picker-item[data-value="5"]', title: '标题五' },
{ Choice: '.ql-header .ql-picker-item[data-value="6"]', title: '标题六' },
{ Choice: '.ql-header .ql-picker-item:last-child', title: '标准' },
{ Choice: '.ql-size .ql-picker-item[data-value="small"]', title: '小号' },
{ Choice: '.ql-size .ql-picker-item[data-value="large"]', title: '大号' },
{ Choice: '.ql-size .ql-picker-item[data-value="huge"]', title: '超大号' },
{ Choice: '.ql-size .ql-picker-item:nth-child(2)', title: '标准' },
{ Choice: '.ql-align .ql-picker-item:first-child', title: '居左对齐' },
{ Choice: '.ql-align .ql-picker-item[data-value="center"]', title: '居中对齐' },
{ Choice: '.ql-align .ql-picker-item[data-value="right"]', title: '居右对齐' },
{ Choice: '.ql-align .ql-picker-item[data-value="justify"]', title: '两端对齐' },
]);
const handleBeforeUpload = (file) => {
console.log('file', file);
const fileType = file.type;
const testmsg = file.name.substring(file.name.lastIndexOf('.') + 1);
const extension =
testmsg === 'ts' || testmsg === 'mp4' || testmsg === 'mov' || testmsg === 'avi';
console.log('extension rich editor', extension);
if (
fileType == 'image/jpeg' ||
fileType == 'image/png' ||
fileType == 'image/gif' ||
fileType == 'image/jpg' ||
fileType == 'image/bmp' ||
fileType == 'image/webp' ||
fileType == 'video/mov' ||
fileType == 'video/ts' ||
fileType == 'video/mp4' ||
fileType == 'video/avi'
) {
const fileSizeLimit = file.size;
const isLt = fileSizeLimit / 1024 / 1024 < props.fileSizeLimit;
if (!isLt) {
console.log(`上传文件大小不能超过 ${props.fileSizeLimit} MB!`);
alert(`上传文件大小不能超过 ${props.fileSizeLimit} MB!`);
return false;
} else {
console.log(`RIch MB!`);
return true;
}
} else {
alert(`文件格式不正确!`);
return false;
}
};
const handleVideoUpload = async (evt) => {
if (evt.target.files.length === 0) {
return;
}
const formData = new FormData();
formData.append('files', evt.target.files[0]);
uploadFichFileToOSSNew(formData).then((res) => {
console.log(res, '----res');
if (res.data.fileName) {
handleUploadVideoSuccess(res.data.fileName, 'video');
}
});
};
function onContentChange(content) {
emit('handleRichTextContentChange', content);
}
const handleUploadVideoSuccess = (fileRemotePath, type) => {
try {
let quill = toRaw(myQuillEditor.value).getQuill();
let length = quill.selection.savedRange.index;
quill.insertEmbed(length, type, {
url: fileRemotePath,
});
quill.setSelection(length + 1);
} catch (error) {
console.log(error);
}
uploadFileVideo.value.value = '';
};
function handleUploadSuccess(res, file) {
console.log(res, file, '---res');
if (res.success) {
let rawMyQuillEditor = toRaw(myQuillEditor.value);
let quill = rawMyQuillEditor.getQuill();
let length = quill.selection.savedRange.index;
const imageId = Random.natural(1, 5000);
quill.insertEmbed(length, 'image', {
url: res.data.fileName,
id: imageId,
});
quill.setSelection(length + 1);
emit('getFileId', imageId);
} else {
ElMessage.error('图片插入失败');
}
}
function handleUploadError() {
ElMessage.error('图片插入失败!');
}
function initTitle() {
document.getElementsByClassName('ql-editor')[0].dataset.placeholder = '';
for (let item of titleConfig.value) {
let tip = document.querySelector('.ql-toolbar ' + item.Choice);
if (!tip) continue;
tip.setAttribute('title', item.title);
}
}
onMounted(() => {
initTitle();
oldContent.value = props.content;
});
</script>
<style>
.editor,
.ql-toolbar {
white-space: pre-wrap !important;
line-height: normal !important;
}
.editor-img-uploader {
display: none;
}
.ql-editor {
min-height: 200px;
max-height: 300px;
overflow: auto;
}
.ql-snow .ql-tooltip[data-mode='link']::before {
content: '请输入链接地址:';
}
.ql-snow .ql-tooltip.ql-editing a.ql-action::after {
border-right: 0px;
content: '保存';
padding-right: 0px;
}
.ql-snow .ql-tooltip[data-mode='video']::before {
content: '请输入视频地址:';
}
.ql-snow .ql-picker.ql-size .ql-picker-label::before,
.ql-snow .ql-picker.ql-size .ql-picker-item::before {
content: '14px';
}
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value='small']::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value='small']::before {
content: '10px';
}
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value='large']::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value='large']::before {
content: '18px';
}
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value='huge']::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value='huge']::before {
content: '32px';
}
.ql-snow .ql-picker.ql-header .ql-picker-label::before,
.ql-snow .ql-picker.ql-header .ql-picker-item::before {
content: '文本';
}
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value='1']::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value='1']::before {
content: '标题1';
}
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value='2']::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value='2']::before {
content: '标题2';
}
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value='3']::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value='3']::before {
content: '标题3';
}
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value='4']::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value='4']::before {
content: '标题4';
}
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value='5']::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value='5']::before {
content: '标题5';
}
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value='6']::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value='6']::before {
content: '标题6';
}
.ql-snow .ql-picker.ql-font .ql-picker-label::before,
.ql-snow .ql-picker.ql-font .ql-picker-item::before {
content: '标准字体';
}
.ql-snow .ql-picker.ql-font .ql-picker-label[data-value='serif']::before,
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value='serif']::before {
content: '衬线字体';
}
.ql-snow .ql-picker.ql-font .ql-picker-label[data-value='monospace']::before,
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value='monospace']::before {
content: '等宽字体';
}
</style>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 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
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 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
- 149
- 150
- 151
- 152
- 153
- 154
- 155
- 156
- 157
- 158
- 159
- 160
- 161
- 162
- 163
- 164
- 165
- 166
- 167
- 168
- 169
- 170
- 171
- 172
- 173
- 174
- 175
- 176
- 177
- 178
- 179
- 180
- 181
- 182
- 183
- 184
- 185
- 186
- 187
- 188
- 189
- 190
- 191
- 192
- 193
- 194
- 195
- 196
- 197
- 198
- 199
- 200
- 201
- 202
- 203
- 204
- 205
- 206
- 207
- 208
- 209
- 210
- 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
- 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
- 382
- 383
- 384
- 385
- 386
- 387
- 388
- 389
- 390
- 391
- 392
- 393
- 394
- 395
- 396
- 397
- 398
- 399
- 400
- 401
- 402
- 403
- 404
- 405
- 406
- 407
- 408
- 409
- 410
- 411
- 412
- 413
- 414
- 415
- 416
- 417
- 418
- 419
- 420
- 421
- 422
- 423
- 424
- 425
- 426
- 427
- 428
- 429
- 430
- 431
- 432
- 433
- 434
- 435
- 436
- 437
- 438
- 439
- 440
- 441
- 442
quill-video.js
import { Quill } from '@vueup/vue-quill';
const BlockEmbed = Quill.import('blots/block/embed');
const Link = Quill.import('formats/link');
const ATTRIBUTES = ['height', 'width'];
class Video extends BlockEmbed {
static create(value) {
let node = super.create();
node.setAttribute('controls', 'controls');
node.setAttribute('playsinline', 'true');
node.setAttribute('webkit-playsinline', 'true');
node.setAttribute('type', 'video/mp4');
node.setAttribute('poster', value.poster);
node.setAttribute('src', this.sanitize(value.url));
return node;
}
static formats(domNode) {
return ATTRIBUTES.reduce((formats, attribute) => {
if (domNode.hasAttribute(attribute)) {
formats[attribute] = domNode.getAttribute(attribute);
}
return formats;
}, {});
}
static sanitize(url) {
return Link.sanitize(url);
}
static value(domNode) {
return {
url: domNode.getAttribute('src'),
poster: domNode.getAttribute('poster'),
};
}
format(name, value) {
if (ATTRIBUTES.indexOf(name) > -1) {
if (value) {
this.domNode.setAttribute(name, value);
} else {
this.domNode.removeAttribute(name);
}
} else {
super.format(name, value);
}
}
html() {
const { video } = this.value();
return `${video}">${video}`;
}
}
Video.blotName = 'video';
Video.className = 'ql-video';
Video.tagName = 'video';
export default Video;
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 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
quill-image.js
import { Quill } from '@vueup/vue-quill';
var BlockEmbed = Quill.import('blots/block/embed');
class ImageBlot extends BlockEmbed {
static create(value) {
let node = super.create();
node.setAttribute('src', value.url);
node.setAttribute('id', value.id);
return node;
}
static value(node) {
return {
url: node.getAttribute('src'),
id: node.getAttribute('id'),
};
}
}
ImageBlot.blotName = 'image';
ImageBlot.tagName = 'img';
export default ImageBlot;
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23