编辑器的功能,主要的需求是。编辑器做标注功能。由于是vue2项目,所以用的编辑器是 vue-quill-editor,这个插件不进行维护了,但是正常使用还是可以的,主要是会涉及到一个报错 [Deprecation] Listener added for a ‘DOMNodeInserted’ mutation event. Support for this event type has been removed, and this event will no longer be fired. See for more information. 这个就是quill的插件报错,去找到 _this.domNode.addEventListener('DOMNodeInserted', function () { }); 修改为:new MutationObserver(() => {}).observe(_this.domNode, { childList: true }); 这样报错就没有了。
直接上代码吧:
npm install vue-quill-editor --save
main.js
import Vue from 'vue'
import App from './App.vue'
import VueQuillEditor from 'vue-quill-editor'
// require styles
import 'quill/dist/quill.core.css'
import 'quill/dist/quill.snow.css'
import 'quill/dist/quill.bubble.css'
Vue.use(VueQuillEditor, /* { default global options } */)
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
Vue.use(ElementUI);
Vue.config.productionTip = false
new Vue({
render: h => h(App),
}).$mount('#app')
// 组件中使用
<template>
<div class="container">
<quill-editor
ref="myQuillEditor"
v-model="content"
:options="editorOption"
@blur="onEditorBlur($event)"
@focus="onEditorFocus($event)"
@change="onEditorChange($event)"
style="height: 600px"
></quill-editor>
</el-row>
<el-dialog :visible.sync="dialogVisible" title="添加标注" :before-close="cancelAnnotation">
<el-form ref="markForm" :model="markForm" label-width="100px">
<el-form-item label="标注词:">
<el-input disabled v-model="markForm.keyworkd"></el-input>
</el-form-item>
<el-form-item label="建议词:">
<el-input v-model="markForm.suggest"></el-input>
</el-form-item>
<el-form-item label="问题描述:">
<el-input type="textarea" v-model="markForm.problemDescription"></el-input>
</el-form-item>
<!-- 其他表单项 -->
</el-form>
<span slot="footer" class="dialog-footer">
<el-button size="mini" @click="cancelAnnotation">取消</el-button>
<el-button size="mini" type="primary" @click="addAnnotation">确定</el-button>
</span>
</el-dialog>
<el-button
v-if="isAddButton"
class="add-button"
ref="addButton"
type="primary"
@click="addButton"
size="mini"
>添加标注</el-button>
</div>
</template>
<script>
export default {
data() {
return {
content: '<h3 style="text-align: start;">故事背景</h3><p style="text-align: start;">在遥远的艾斯特拉王国,时间并非一成不变,而是由古老传说中的“时光之钥”所掌控。这把神秘的钥匙,剧说是创世之初遗落的碎片,拥有逆转时光、重塑历史的无上力量。然而,这股力量若落入邪恶之手,将引发不可预知的灾难。数千年来,时光之钥被分为数块碎片,散落于世界的各个角落,守护着世界的平衡与秩序。</p><h3 style="text-align: start;">主要人物</h3><ul><li style="text-align: start;">艾莉娅:一位拥有敏锐直觉与温柔心灵的年轻女学者,对历史与神话充满无限好奇。她欧染间发现了一本古老的手稿,里面记载了时光之钥的传说,从此踏上了寻找钥匙碎片的征途。</li><li style="text-align: start;">莱恩:前王室骑士团成员,因一个误会而被流放。他武艺高强,性格孤傲但内心深处渴望救赎。艾莉娅的出现让他重新找到了战斗的意义,决定加入她的行列,保护她免受一切威胁。</li><li style="text-align: start;">凯尔:来自异世界的旅人,拥有操控元素的神奇能力。他因一次意外穿越到艾斯特拉,被艾莉娅和莱恩的勇气与决心所吸引,决定帮助他们寻找时光之钥碎片,同时寻找回到自己世界的方法。</li></ul><h3 style="text-align: start;">故事梗概</h3><h4 style="text-align: start;">第一章:启程与谜团</h4><p style="text-indent: 2em; text-align: start;">艾莉娅在一次图书馆的深夜探险中,意外发现了一哥尘封的手稿,上面详细记载了时光之钥的传说及其碎片的线索。意识到这份力量的重要性,她决定踏上寻找碎片的旅程。在旅途中,她先后遇到了莱恩和凯尔,三人因各自的信念与目的而结伴。他们首先来到了古老的遗迹——遗忘之城,那里隐藏着第一块时光之钥碎片的线索。通过解开一系列复杂的机关与谜题,他们成功获得了第一块碎片,但也因此引起了暗中窥视的邪恶势力的注意。</p><h4 style="text-align: start;">第二章:挑战与试炼</h4><p style="text-indent: 2em; text-align: start;">随着旅程的深入,三人遭遇了越来越多的挑战与危险。他们不仅要面对自然界的考研,如穿越迷雾森林、攀登险峻的山峰,还要应对来自邪恶势力的追击与阻挠。在一次激烈的战斗中,莱恩为了保护艾莉娅身受重伤,这让他深刻反思自己过去的错误与选择。而凯尔则利用自己的元素之力,在关键时刻扭转战局,展现了非凡的能力与智慧。通过这些经历,三人之间的友谊与信任日益加深,彼此成为了不可分割的伙伴。</p><h4 style="text-align: start;">第三章:真相与抉择</h4><p style="text-align: start;"> 在接近最后一块时光之钥碎片的过程中,他们逐渐揭开了时光之钥背后隐藏的真相。原来,时光之钥的力量并非无限制地操控时间,而是引导人们正视历史、珍惜现在、展望未来。邪恶势力之所以渴望得到它,是为了改变对他们不利的过去,从而逃避应有的惩罚与责任。面对这一残酷的真相,艾莉娅、莱恩和凯尔必须做出抉择:是继续追寻力量,还是用它来维护世界的平衡与正义?</p><h4 style="text-align: start;">第四章:决战与牺牲</h4><p style="text-align: start;">在最终的决战中,三人与邪恶势力展开了激烈的交锋。邪恶势力的首领试图利用时光之钥的力量改变历史,但艾莉娅等人坚决反对。在一场惊心动魄的对决后,凯尔利用自己的特殊能力,将时光之钥的碎片重新组合,释放出了一股强大的力量。然而,这股力量并非用于战斗,而是用来揭示真相、唤醒人们的良知与勇气。最终,邪恶势力在众人的联合反抗下彻底覆灭,时光之钥也化为了点点星光,融入了大地之中,象征着时间的不可逆转与生命的无限可能。</p><h4 style="text-align: start;">第五章:和平与新生</h4><p style="text-align: start;">战争结束后,艾斯特拉王国迎来了久违的和平与繁荣。艾莉娅、莱恩和凯尔也因为他们的英勇与智慧而被誉为英雄。但对他们而言,更重要的是这段旅程中收获的友谊、成长与自我认知。艾莉娅继续她的学术研究,致力于传承与发扬古代文化的精髓;莱恩则选择留在王城,用自己的剑与勇气保护这片土地与人民;而凯尔则在找到返回自己世界的方法后,带着对艾斯特拉的深深眷恋与祝福离开了。</p><p style="text-align: start;">《时光之钥》不仅是一部关于冒险与战痘的故事,更是一次心灵的洗礼与成长的旅程。它教会我们珍惜时间、勇于探索、团结合作、坚守正义,并在反思与成长中不断前行。在这个充满奇迹与未知的世界里,每个人都是自己命运的主宰者,而真正的力量则源自内心的信念与减持。</p>',
programIdeologicalConnotation: '这是思想内涵',
dialogVisible: false,
isAddButton: false,
quillReady: false, // 新增标志位,表示 Quill 编辑器是否已准备好
editorOption: {
// 占位配置
placeholder: '',
modules: {
toolbar:{
container: [
['bold', 'italic', 'underline', 'strike'], //加粗,斜体,下划线,删除线
[{ header: 1 }, { header: 2 }], // 标题
[{ list: 'ordered' }, { list: 'bullet' }], // 列表
[{ indent: '-1' }, { indent: '+1' }], // 缩进
[{ color: [] }, { background: [] }], // 字体颜色,字体背景颜色
[{ align: [] }], // 对齐方式
['clean'], // 清除文本格式
]
}
}
},
psoAmendmentRecords: [
{
"id": 1,
"contentId": 1,
"videoId": null,
"inPoint": 2,
"outPoint": 4,
"problemDescription": "用词不当",
"imageUrl": null,
"suggest": "测试",
"auditor": "zenglingqi",
"auditType": 1,
"auditStage": 1,
"deleted": 0,
"createTime":"2024-09-04 10:17:15",
"updateTime":"2024-09-04 10:17:15",
"keyworkd": "背景",
},
{
"id": 2,
"contentId": 1,
"videoId": null,
"inPoint": 6,
"outPoint": 8,
"problemDescription": "用词不当",
"imageUrl": null,
"suggest": "测试",
"auditor": "zenglingqi",
"auditType": 1,
"auditStage": 1,
"deleted": 0,
"createTime":"2024-09-04 10:17:15",
"updateTime":"2024-09-04 10:17:15",
"keyworkd": "遥远",
},
{
"id": 3,
"contentId": 1,
"videoId": null,
"inPoint": 220,
"outPoint": 222,
"problemDescription": "用词不当",
"imageUrl": null,
"suggest": "测试",
"auditor": "zenglingqi",
"auditType": 1,
"auditStage": 1,
"deleted": 0,
"createTime":"2024-09-04 10:17:15",
"updateTime":"2024-09-04 10:17:15",
"keyworkd": "此踏",
},
{
"id": 4,
"contentId": 1,
"videoId": null,
"inPoint": 300,
"outPoint": 302,
"problemDescription": "用词不当",
"imageUrl": null,
"suggest": "测试",
"auditor": "zenglingqi",
"auditType": 1,
"auditStage": 1,
"deleted": 0,
"createTime":"2024-09-04 10:17:15",
"updateTime":"2024-09-04 10:17:15",
"keyworkd": "她的",
},
{
"id": 5,
"contentId": 1,
"videoId": null,
"inPoint": 500,
"outPoint": 502,
"problemDescription": "用词不当",
"imageUrl": null,
"suggest": "测试",
"auditor": "zenglingqi",
"auditType": 1,
"auditStage": 1,
"deleted": 0,
"createTime":"2024-09-04 10:17:15",
"updateTime":"2024-09-04 10:17:15",
"keyworkd": "遇到",
},
{
"id": 6,
"contentId": 1,
"videoId": null,
"inPoint": 1000,
"outPoint": 1002,
"problemDescription": "用词不当",
"imageUrl": null,
"suggest": "测试",
"auditor": "zenglingqi",
"auditType": 1,
"auditStage": 1,
"deleted": 0,
"createTime":"2024-09-04 10:17:15",
"updateTime":"2024-09-04 10:17:15",
"keyworkd": "决战",
},
{
"id": 7,
"contentId": 1,
"videoId": null,
"inPoint": 2001,
"outPoint": 2003,
"problemDescription": "用词不当",
"imageUrl": null,
"suggest": "测试",
"auditor": "zenglingqi",
"auditType": 1,
"auditStage": 1,
"deleted": 0,
"createTime":"2024-09-04 10:17:15",
"updateTime":"2024-09-04 10:17:15",
"keyworkd": "旅途",
},
{
"id": 8,
"contentId": 1,
"videoId": null,
"inPoint": 2501,
"outPoint": 2503,
"problemDescription": "用词不当",
"imageUrl": null,
"suggest": "测试",
"auditor": "zenglingqi",
"auditType": 1,
"auditStage": 1,
"deleted": 0,
"createTime":"2024-09-04 10:17:15",
"updateTime":"2024-09-04 10:17:15",
"keyworkd": "牺牲",
},
{
"id": 9,
"contentId": 1,
"videoId": null,
"inPoint": 2799,
"outPoint": 2802,
"problemDescription": "用词不当",
"imageUrl": null,
"suggest": "测试",
"auditor": "zenglingqi",
"auditType": 1,
"auditStage": 1,
"deleted": 0,
"createTime":"2024-09-04 10:17:15",
"updateTime":"2024-09-04 10:17:15",
"keyworkd": "艾丽娅",
}
],
psoAiaResult: [
{
"id": 1,
"contentId": 1,
"videoId": null,
"inPoint": 2,
"outPoint": 4,
"problemDescription": "用词不当",
"imageUrl": null,
"suggest": "测试",
"auditor": "zenglingqi",
"auditType": 1,
"auditStage": 1,
"deleted": 0,
"createTime":"2024-09-04 10:17:15",
"updateTime":"2024-09-04 10:17:15",
"keyworkd": "背景",
},
{
"id": 2,
"contentId": 1,
"videoId": null,
"inPoint": 10,
"outPoint": 12,
"problemDescription": "用词不当",
"imageUrl": null,
"suggest": "测试",
"auditor": "zenglingqi",
"auditType": 1,
"auditStage": 1,
"deleted": 0,
"createTime":"2024-09-04 10:17:15",
"updateTime":"2024-09-04 10:17:15",
"keyworkd": "背景",
},
{
"id": 3,
"contentId": 1,
"videoId": null,
"inPoint": 2600,
"outPoint": 2602,
"problemDescription": "用词不当",
"imageUrl": null,
"suggest": "测试",
"auditor": "zenglingqi",
"auditType": 1,
"auditStage": 1,
"deleted": 0,
"createTime":"2024-09-04 10:17:15",
"updateTime":"2024-09-04 10:17:15",
"keyworkd": "背景",
},
], // 故事梗概机审结果
picAmendmentRecords: [], // 思想内涵修改建议
picAiaResult: [], // 思想内涵机审结果
activeName: 'first',
storyActiveName: 'first',
connotationActiveName: 'third',
markForm: {
keyworkd: '', // 关键词
suggest: '', // 建议词
problemDescription: '', // 问题描述
inPoint: '', // 关键词索引开始位置
outPoint: '' // 关键词索引结束位置
},
};
},
created () {
// 选中取消时, 隐藏按钮
console.log('-------------123')
// document.addEventListener('selectionchange', () => {
// if(this.$refs.myQuillEditor === undefined) {
// this.isAddButton = false
// } else {
// let quill = null;
// if(this.activeName === 'first') {
// // 故事梗概
// quill = this.$refs.myQuillEditor.quill
// } else if(this.activeName === 'second') {
// quill = this.$refs.connotationEditor.quill
// }
// const range = quill.getSelection()
// if (this.isAddButton && range.length === 0) {
// this.isAddButton = false
// }
// }
// })
},
mounted() {
this.setEditorContent();
console.log('mounted-----')
// this.$nextTick(() => {
// this.quillReady = true; // 在下一个 tick 设置为 true,确保 Quill 实例已挂载
// // this.$refs.myQuillEditor.quill.root.addEventListener('mouseup', this.mouseup);
// this.$refs.myQuillEditor.quill.root.addEventListener('mouseup', this.mouseup);
// this.$refs.connotationEditor.quill.root.addEventListener('mouseup', this.mouseup);
// });
},
beforeDestroy() {
if (this.quillReady && this.$refs.myQuillEditor) {
this.$refs.myQuillEditor.quill.root.removeEventListener('mouseup', this.mouseup);
}
if (this.quillReady && this.$refs.connotationEditor) {
this.$refs.connotationEditor.quill.root.removeEventListener('mouseup', this.mouseup);
}
},
methods: {
onEditorBlur(editor) {
console.log('editor blur!', editor)
},
onEditorFocus(editor) {
console.log('editor focus!', editor)
},
onEditorChange({ quill, html, text }) {
// 这里可以获取到编辑器的内容、HTML和纯文本
// quill 是编辑器实例
// html 是编辑器内容的HTML
// text 是编辑器内容的纯文本
// console.log('editor content changed!', quill, html, text);
// 根据需要处理你的逻辑
// 例如,检查是否需要显示添加按钮
const range = quill.getSelection();
if (range && range.length === 0) {
this.isAddButton = false;
}
},
// 根据修改建议去设置标注的
setEditorContent() {
let quill = null;
if(this.activeName === 'first') {
// 故事梗概
quill = this.$refs.myQuillEditor.quill
} else if(this.activeName === 'second') {
quill = this.$refs.connotationEditor.quill
}
this.psoAiaResult.forEach((record => {
const startIndex = record.inPoint
const selectLength = record.keyworkd.length;
// quill.setSelection(startIndex, selectLength)
quill.formatText(startIndex, selectLength, {
'bold': true,
'color': 'red'
});
}))
this.psoAmendmentRecords.forEach((record => {
const startIndex = record.inPoint
const selectLength = record.keyworkd.length;
// quill.setSelection(startIndex, selectLength)
quill.formatText(startIndex, selectLength, {
'bold': true,
'color': 'red'
});
}))
},
// mouseup (e) {
// if (!this.quillReady) return; // 如果 Quill 编辑器未准备好,直接返回
// // console.log('鼠标事件')
// // 未选中
// let quill = null;
// if(this.activeName === 'first') {
// quill = this.$refs.myQuillEditor.quill;
// } else {
// quill = this.$refs.connotationEditor.quill;
// }
// const range = quill.getSelection();
// if(range.length === 0) return
// // 插入交互
// this.insetAffix(e)
// },
// insetAffix(e) {
// this.isAddButton = true
// this.$nextTick(() => {
// const { clientX, clientY } = e
// const addButton = this.$refs.addButton.$el;
// const offset = 5
// addButton.style.cssText = `top: ${clientY + offset}px; left: ${clientX + offset}px; z-index: 10`
// })
// },
// addButton() {
// if (!this.quillReady) return; // 如果 Quill 编辑器未准备好,直接返回
// // 添加标注弹出层
// this.dialogVisible = true
// this.markForm = {
// keyworkd: '', // 关键词
// suggest: '', // 建议词
// problemDescription: '', // 问题描述
// inPoint: '', // 关键词索引开始位置
// outPoint: '' // 关键词的索引结束位置
// }
// // 需要拿到选中的文字
// let quill = null;
// if(this.activeName === 'first') {
// quill = this.$refs.myQuillEditor.quill;
// } else {
// quill = this.$refs.connotationEditor.quill;
// }
// // const quill = this.$refs.myQuillEditor.quill
// const range = quill.getSelection()
// const text = quill.getText(range.index, range.length); // 选中的文字
// console.log(range,text)
// this.markForm.keyworkd = text;
// this.markForm.inPoint = range.index
// this.markForm.outPoint = range.index + range.length
// },
// addAnnotation() {
// this.dialogVisible = false;
// // 此时应该取消选区
// let quill = null;
// if(this.activeName === 'first') {
// quill = this.$refs.myQuillEditor.quill;
// } else {
// quill = this.$refs.connotationEditor.quill;
// }
// // const quill = this.$refs.myQuillEditor.quill
// const startIndex = this.markForm.inPoint
// const selectLength = this.markForm.keyworkd.length;
// // quill.setSelection(startIndex, selectLength)
// quill.formatText(startIndex, selectLength, {
// 'bold': true,
// 'color': 'red'
// });
// this.isAddButton = false
// },
// cancelAnnotation() {
// this.dialogVisible = false
// this.isAddButton = false
// },
// 定位
storyOutlineLocation(row, event, column) {
// console.log('row',row)
let quill = null;
if(this.activeName === 'first') {
quill = this.$refs.myQuillEditor.quill;
} else {
quill = this.$refs.connotationEditor.quill;
}
// const quill = this.$refs.myQuillEditor.quill
// 设置光标位置到指定位置
// quill.setSelection(row.inPoint, row.keyworkd.length);
// 获取当前光标位置相对于编辑器容器的位置
const bounds = quill.getBounds(row.inPoint);
// 获取编辑器的滚动容器
const scrollContainer = quill.scrollingContainer;
// 计算需要滚动的偏移量
const scrollTop = scrollContainer.scrollTop;
const scrollLeft = scrollContainer.scrollLeft;
// 考虑编辑器的滚动容器和内容的位置
const padding = 10; // 偏移量,可根据需要调整
const targetTop = bounds.top + scrollTop - padding;
const targetLeft = bounds.left + scrollLeft;
// 滚动到指定位置
scrollContainer.scrollTop = targetTop;
scrollContainer.scrollLeft = targetLeft;
},
editClick(row) {
console.log('编辑人审建议',row)
this.dialogVisible = true;
this.markForm.keyworkd = row.keyworkd;
this.markForm.inPoint = row.inPoint;
this.markForm.suggest = row.suggest;
this.markForm.problemDescription = row.problemDescription;
}
},
computed: {
editor() {
if(this.activeName === 'first') {
return this.$refs.myQuillEditor.quill
} else {
return this.$refs.connotationEditor.quill
}
}
},
}
</script>
<style scoped>
.ql-editor {
height: 300px !important;
}
.add-button {
position: fixed;
top: 0;
left: 0;
z-index: -1;
}
.container {
display: flex;
}
.annotation-button {
position: absolute;
padding: 5px 10px;
background-color: #007bff;
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
z-index: 1000; /* 确保按钮在其他内容之上 */
}
</style>