| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196 |
- <template>
- <div class="main-container">
- <QuillEditor ref="myQuillEditorRef"
- theme="snow"
- v-model:content="data.content"
- :options="data.editorOption"
- contentType="delta"
- @update:content="setValue()"
- />
- </div>
- </template>
- <script setup lang="ts">
- import {QuillEditor, Quill, Delta} from '@vueup/vue-quill'
- import { reactive, onMounted, ref, toRaw, watch, defineProps } from 'vue'
- import '@vueup/vue-quill/dist/vue-quill.snow.css'
- import emitter from '@/utils/emitter'
- import dayjs from 'dayjs'
- import useApis from "@/apis";
- import {useRoute} from "vue-router";
- import {useStore} from "@/store";
- import type {engagementVO} from "@/types/vo.js";
- import {ElMessage} from "element-plus";
- const apis = useApis()
- const store = useStore()
- const route = useRoute();
- const assignmentId = Number(route.params.assignmentId);
- const myQuillEditorRef = ref()
- const data = reactive({
- content: {},
- editorOption: {
- modules: {
- toolbar: [
- ['bold', 'italic', 'underline', 'strike'],
- [{ 'size': ['small', false, 'large', 'huge'] }],
- [{ 'font': [] }],
- [{ 'align': [] }],
- [{ 'list': 'ordered' }, { 'list': 'bullet' }],
- [{ 'indent': '-1' }, { 'indent': '+1' }],
- [{ 'header': 1 }, { 'header': 2 }],
- ['image'],
- [{ 'direction': 'rtl' }],
- [{ 'color': [] }, { 'background': [] }]
- ]
- },
- placeholder: '请输入内容...'
- }
- })
- // 抛出更改内容
- const setValue = () => {
- const html = toRaw(myQuillEditorRef.value).getHTML()
- emitter.emit('get-html', html)
- const text = toRaw(myQuillEditorRef.value).getText()
- emitter.emit('get-text', text)
- const content1 = toRaw(myQuillEditorRef.value).getContents()
- emitter.emit('get-quill-content', content1)
- }
- /**
- * 合并所有insert内容,因为存在attribute的缘故,原内容被分割成很多个insert
- * @param ops
- */
- const processOps = (ops) => {
- let combinedInsert = '';
- ops.forEach(item => {
- if (item.insert) {
- combinedInsert += item.insert;
- }
- });
- return combinedInsert
- }
- //初始化Quill及行为监控
- const initQuill = () => {
- // 获取quill
- const quill = toRaw(myQuillEditorRef.value).getQuill()
- // 加入工具栏
- if (myQuillEditorRef.value) {
- quill.getModule('toolbar')
- }
- // 文本变化监控
- quill.on(Quill.events.TEXT_CHANGE, (...args) => {
- const [delta, oldDelta, source] = args;
- const deleteOps = delta.ops.filter(op => 'delete' in op);
- // 直接将 ops 数组中的属性合并到一个对象
- const opsObj = {};
- delta.ops.forEach(op => {
- Object.assign(opsObj, op);
- });
- delete opsObj.attributes;
- //有bug,当replace时,既有delete又有insert,此时会被误判为删除操作
- if(deleteOps.length > 0){ //删除操作
- // 获取删除操作的位置和长度
- const deleteStart = delta.ops.find(op => 'retain' in op).retain || 0; // 删除起始位置
- const deleteLength = delta.ops.find(op => 'delete' in op).delete; // 删除长度
- const oldContent = processOps(args["1"].ops)// 原内容
- // 提取被删除的具体内容
- const deletedContent = oldContent.slice(deleteStart, deleteStart + deleteLength);
- emitter.emit('change-record', {
- type: "delete",
- retain: 0,
- ...opsObj,
- oldContent,
- deletedContent,
- docLength: quill.getLength() - 1,
- letterCount: (quill.getText().match(/[a-zA-Z\d\u4e00-\u9fa5]/g) || []).length
- })
- } else {
- emitter.emit('change-record', {
- type: "insert",
- retain: 0,
- ...opsObj,
- docLength: quill.getLength() - 1,
- letterCount: (quill.getText().match(/[a-zA-Z\d\u4e00-\u9fa5]/g) || []).length,
- key: args["0"].ops.find(op => 'insert' in op).insert || null
- })
- }
- });
- // 光标变化监控
- quill.on(Quill.events.SELECTION_CHANGE, (...args) => {
- emitter.emit('change-record', {
- type: "cursor_move",
- retain: args["0"].index, // 将 index 属性重命名为 retain
- length: args["0"].length, // 保留 length 属性
- time: dayjs(new Date()).format("YYYY-MM-DD HH:mm:ss.SSS")
- });
- });
- }
- const fetchData = () => {
- apis.getEngagement(store.user.id, assignmentId)
- .then(res => {
- const engagementData = res.data.data;
- const engagement: engagementVO = Object.assign({}, engagementData);
- // data.content = engagement.textContent
- // const rawEditor = toRaw(myQuillEditorRef.value);
- // rawEditor.setContents(data.content); // 强制设置内容
- if(engagement.quillContent != null){
- data.content = new Delta(JSON.parse(engagement.quillContent));
- const rawEditor = toRaw(myQuillEditorRef.value);
- rawEditor.setContents(data.content); // 强制设置内容
- }
- })
- .catch(err => {
- console.log(err)
- })
- }
- // 初始化编辑器
- onMounted(() => {
- initQuill()
- fetchData()
- });
- </script>
- <script lang="ts">
- export default {
- name: "Editor"
- }
- </script>
- <style scoped lang="scss">
- // 调整样式
- .main-container {
- height: 80vh;
- border: solid 2px rgba(199, 199, 199, 0.3);
- }
- .main-container :deep(.ql-container) {
- height: 95%;
- max-height: 95%;
- background: #fff;
- }
- .main-container :deep(.ql-toolbar) {
- height: 5%;
- background: #fff;
- min-height: 40px;
- }
- .main-container :deep(.ql-formats) {
- height: 21px;
- line-height: 21px;
- }
- </style>
|