Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

A vue page that loops through multiple quill components cannot get the quill instance of the current operation #479

Open
4 tasks done
lvzhuhen opened this issue Aug 24, 2022 · 0 comments

Comments

@lvzhuhen
Copy link

Describe the bug

A vue page that loops through multiple quill components cannot get the quill instance of the current operation

Reproduction

components
VueQuillEditor.vue

<script> import { quillEditor, Quill } from 'vue-quill-editor' import 'quill/dist/quill.core.css' import 'quill/dist/quill.snow.css' import 'quill/dist/quill.bubble.css' // 设置字体大小 const fontSizeStyle = Quill.import('attributors/style/size') // 引入这个后会把样式写在style上 fontSizeStyle.whitelist = ['12px', '14px', '16px', '18px', '20px', '24px', '28px', '32px', '36px'] Quill.register(fontSizeStyle, true) // 设置字体样式 const Font = Quill.import('attributors/style/font') // 引入这个后会把样式写在style上 const fonts = [ 'SimSun', 'SimHei', 'Microsoft-YaHei', 'KaiTi', 'FangSong' ] Font.whitelist = fonts // 将字体加入到白名单 Quill.register(Font, true) // 工具栏 const toolbarOptions = [ ['bold', 'italic', 'underline', 'strike'], // 加粗 斜体 下划线 删除线 -----['bold', 'italic', 'underline', 'strike'] [{ color: [] }, { background: [] }], // 字体颜色、字体背景颜色-----[{ color: [] }, { background: [] }] [{ align: [] }], // 对齐方式-----[{ align: [] }] [{ size: fontSizeStyle.whitelist }], // 字体大小-----[{ size: ['small', false, 'large', 'huge'] }] [{ font: fonts }], // 字体种类-----[{ font: [] }] [{ header: [1, 2, 3, 4, 5, 6, false] }], // 标题 [{ direction: 'ltl' }], // 文本方向-----[{'direction': 'rtl'}] [{ direction: 'rtl' }], // 文本方向-----[{'direction': 'rtl'}] [{ indent: '-1' }, { indent: '+1' }], // 缩进-----[{ indent: '-1' }, { indent: '+1' }] [{ list: 'ordered' }, { list: 'bullet' }], // 有序、无序列表-----[{ list: 'ordered' }, { list: 'bullet' }] [{ script: 'sub' }, { script: 'super' }], // 上标/下标-----[{ script: 'sub' }, { script: 'super' }] ['blockquote', 'code-block'], // 引用 代码块-----['blockquote', 'code-block'] ['clean'], // 清除文本格式-----['clean'] ['link', 'image', 'video'] // 链接、图片、视频-----['link', 'image', 'video'] ] export default { name: 'VueQuillEditor', components: { quillEditor }, props: { value: { type: [Number, Object, Array, String], default: '' }, callbackParam: { type: [Number, Object, Array, String], default: '' }, quillEditorId: { type: String, required: true } }, data () { return { html: this.value, editorOption: { placeholder: '请在这里输入', modules: { toolbar: { container: toolbarOptions, handlers: { image: this.handleImgUpload } } } }, isShow: false, uploadUrl: process.env.VUE_APP_BASE_API + '/' + process.env.VUE_APP_GATEWAY_RULENGINE_URL + `/upload/file` // 服务器上传地址 } }, computed: { //当前富文本实例 editor() { debugger return this.$refs[this.quillEditorId].quill } }, watch: { html: { handler (newValue) { this.$emit('quillContentInput', newValue, this.callbackParam) }, deep: true }, value: { handler (newValue, oldValue) { if (newValue !== oldValue) this.html = newValue }, deep: true } }, mounted () { this.initMounted() }, methods: { randomId(len) { var chars = "ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678"; var tempLen = chars.length, tempStr = ""; for (var i = 0; i < len; ++i) { tempStr += chars.charAt(Math.floor(Math.random() * tempLen)); } return tempStr; }, initMounted () { setTimeout(() => { this.isShow = true }, 100) }, handleImgUpload () { const { quill } = this.$refs.mwQuillEditor // SelectImages({ // visible: true, // multi: true, // showButton: true, // maxMulti: 18, // success: (data, filPath) => { // for (const item of data) { // const length = quill.getSelection(true).index // // 获取光标所在位置 // // 插入图片,res为服务器返回的图片链接地址 // quill.insertEmbed(length, 'image', filPath + item) // // 调整光标到最后 // quill.setSelection(length + 1) // } // } // }) }, beforeUpload(file) { const isLt1M = file.size / 1024 / 1024 < 1 if (!isLt1M) { this.$message.error('上传图片大小不能超过 1MB!') } return isLt1M }, uploadSuccess(res) { // 获取富文本组件实例 debugger // let quill = this.$refs[this.quillEditorId].quill // let quill = this.$refs.mwQuillEditor.quill let quill = this.editor debugger // 如果上传成功 if (res.code === 200 && res.data) { // 获取光标所在位置 let length = quill.selection.savedRange.index debugger // 插入图片,res为服务器返回的图片链接地址 quill.insertEmbed(length, 'image', res.data) // 调整光标到最后 quill.setSelection(length + 1) } else { // 提示信息,需引入Message this.$message.error('图片插入失败!') } } } } </script> <style lang="scss"> .vue-quill-editor { .quill-editor{ line-height: normal; .ql-container.ql-snow{ line-height: normal !important; height: 200px !important; font-size:14px; } .ql-snow { .ql-tooltip[data-mode=link]::before { content: "请输入链接地址:"; } .ql-tooltip.ql-editing a.ql-action::after { border-right: 0px; content: '保存'; padding-right: 0px; } .ql-tooltip[data-mode=video]::before { content: "请输入视频地址:"; } .ql-picker.ql-size { .ql-picker-label[data-value="12px"]::before, .ql-picker-item[data-value="12px"]::before { content: '12px'; } .ql-picker-label[data-value="14px"]::before, .ql-picker-item[data-value="14px"]::before { content: '14px'; } .ql-picker-label[data-value="16px"]::before, .ql-picker-item[data-value="16px"]::before { content: '16px'; } .ql-picker-label[data-value="18px"]::before, .ql-picker-item[data-value="18px"]::before { content: '18px'; } .ql-picker-label[data-value="20px"]::before, .ql-picker-item[data-value="20px"]::before { content: '20px'; } .ql-picker-label[data-value="24px"]::before, .ql-picker-item[data-value="24px"]::before { content: '24px'; } .ql-picker-label[data-value="28px"]::before, .ql-picker-item[data-value="28px"]::before { content: '28px'; } .ql-picker-label[data-value="32px"]::before, .ql-picker-item[data-value="32px"]::before { content: '32px'; } .ql-picker-label[data-value="36px"]::before, .ql-picker-item[data-value="36px"]::before { content: '36px'; } } .ql-picker.ql-header { .ql-picker-label::before, .ql-picker-item::before { content: '文本'; } .ql-picker-label[data-value="1"]::before, .ql-picker-item[data-value="1"]::before { content: '标题1'; } .ql-picker-label[data-value="2"]::before, .ql-picker-item[data-value="2"]::before { content: '标题2'; } .ql-picker-label[data-value="3"]::before, .ql-picker-item[data-value="3"]::before { content: '标题3'; } .ql-picker-label[data-value="4"]::before, .ql-picker-item[data-value="4"]::before { content: '标题4'; } .ql-picker-label[data-value="5"]::before, .ql-picker-item[data-value="5"]::before { content: '标题5'; } .ql-picker-label[data-value="6"]::before, .ql-picker-item[data-value="6"]::before { content: '标题6'; } } .ql-picker.ql-font{ .ql-picker-label[data-value="SimSun"]::before, .ql-picker-item[data-value="SimSun"]::before { content: "宋体"; font-family: "SimSun" !important; } .ql-picker-label[data-value="SimHei"]::before, .ql-picker-item[data-value="SimHei"]::before { content: "黑体"; font-family: "SimHei"; } .ql-picker-label[data-value="Microsoft-YaHei"]::before, .ql-picker-item[data-value="Microsoft-YaHei"]::before { content: "微软雅黑"; font-family: "Microsoft YaHei"; } .ql-picker-label[data-value="KaiTi"]::before, .ql-picker-item[data-value="KaiTi"]::before { content: "楷体"; font-family: "KaiTi" !important; } .ql-picker-label[data-value="FangSong"]::before, .ql-picker-item[data-value="FangSong"]::before { content: "仿宋"; font-family: "FangSong"; } } } .ql-align-center{ text-align: center; } .ql-align-right{ text-align: right; } .ql-align-left{ text-align: left; } } } </style>

baseQuestion.vue

<!-- 选项 start -->
<el-row>
<el-form-item v-if="questiondata.questionTypeId==='1' || questiondata.questionTypeId==='2'" label="选项" class="choice-el-form-item" prop="choiceList">
  <template v-for="(item, index) in optionsAll">
    <div :key="index" class="choice-item">
      <label slot="label">{{ String.fromCharCode(64 + (index + 1)) }}</label>
      <vue-quill-editor :value="questiondata.choiceList[index].value" :callback-param="index" @quillContentInput="optionQuillContent"></vue-quill-editor>
      <el-button v-if="index > 1"  class="mini-btn" type="danger" icon="el-icon-delete" @click.prevent="removeOptionItem(index)" circle></el-button>
    </div>
  </template>
</el-form-item>
<el-form-item v-if="questiondata.questionTypeId==='1'||questiondata.questionTypeId==='2'" class="little-item">
  <el-button type="primary" class="margin-bottom-20" @click="addItem" plain>添加选项</el-button>
</el-form-item>
</el-row>
<!-- 选项 end -->

<!--正确答案 start -->
<el-form-item v-if="questiondata.questionTypeId==='1'" label="正确答案" prop="questionAnswer">
  <el-select v-model="questiondata.questionAnswer" placeholder="请选择正确答案">
    <el-option
      v-for="(item, index) in optionsAll"
      :key="index"
      :label="String.fromCharCode(64 + (index + 1))"
      :value="String.fromCharCode(64 + (index + 1))"
    />
  </el-select>
</el-form-item>
<el-form-item v-else-if="questiondata.questionTypeId==='2'" label="正确答案" prop="multiQuestionAnswer">
  <el-checkbox-group v-model="questiondata.multiQuestionAnswer" class="multi-checkbox">
    <el-checkbox
      v-for="(item, index) in optionsAll"
      :key="index"
      :label="String.fromCharCode(64 + (index + 1))" @change="refreshElement"
    >{{ String.fromCharCode(64 + (index + 1)) }}</el-checkbox>
  </el-checkbox-group>
</el-form-item>
<el-form-item v-else-if="questiondata.questionTypeId==='3'" label="正确答案" prop="questionAnswer">
  <el-select v-model="questiondata.questionAnswer" placeholder="请选择正确答案">
    <el-option label="正确" value="1" />
    <el-option label="错误" value="0" />
  </el-select>
</el-form-item>
<el-form-item v-else-if="questiondata.questionTypeId >= '4' && questiondata.questionTypeId <= '7'" label="参考答案" prop="questionAnswer">
  <vue-quill-editor :value="questiondata.questionAnswer" @quillContentInput="answerQuillContent"></vue-quill-editor>
</el-form-item>
<!--正确答案 end -->
<script> import VueQuillEditor from './VueQuillEditor.vue' export default { name: 'BaseQuestion', components: { VueQuillEditor }, // 使用 props 进行接收 props: { questiondata: { type: Object, default() { return {} } }, /** * 对‘子组件接收的数据名称’进行声明,需要标明接收数据的类型‘Array’ */ optionsAll: { type: Array, default() { return []; } }, childRule: { type: Object, default() { return {} } } }, data() { return {} }, methods: { /** * el-checkbox子组件中,选中时不会及时刷新页面,造成没有选中的假象。这时,需要强制刷新一下组件 */ refreshElement() { this.$forceUpdate() }, validateTitle(rule, value, callback) { if (!value) { callback(new Error('请输入试题内容')) } callback() }, validateQuestionAnswer(rule, value, callback) { if (!value) { let str = '' if (this.questiondata.questionTypeId === '1' || this.questiondata.questionTypeId === '3') { str = '选择试题' } else { str = '输入参考' } callback(new Error(`请${str}答案`)) } callback() }, validateMultiQuestionAnswer(rule, value, callback) { if (!value || value.length < 1) { callback(new Error('请至少选择一个选项')) } callback() },
// 删除选项
removeOptionItem(index) {
  if (index !== -1) {
    let delChoice = this.questiondata.choiceList.splice(index, 1)[0]
    if (this.questiondata.questionTypeId === '1') {
      // 单选题,删除选项时,如果被删除的选项正好是答案项,则同时把答案置空
      if (this.questiondata.questionAnswer === delChoice.id) {
        this.questiondata.questionAnswer = ''
      }
    }
    // 多选题,删除选项时,如果被删除的选项正好是答案项,则同时删除对应的答案选项
    if (this.questiondata.questionTypeId === '2') {
      this.questiondata.multiQuestionAnswer.remove(delChoice.id)
    }
    // 重置选项列表编号:A,B,C..., 如:[{id:A,value:''}, {id:B,value:''}, {id:C,value:''}, {id:D,value:''}]
    this.questiondata.choiceList.forEach((item, index) => {
      item.id = String.fromCharCode(64 + (index + 1))
    })

    // 强制刷新
    this.$forceUpdate()
  }
},
// 增加选项
addItem() {
  if (this.questiondata.choiceList.length >= 6) {
    return this.$modal.msgWarning('最多只能添加6个选项')
  } else {
    let val = ''
    if (this.questiondata.choiceList.length === 2) {
      val = '2'
    } else if (this.questiondata.choiceList.length === 3) {
      val = '3'
    } else if (this.questiondata.choiceList.length === 4) {
      val = '4'
    } else if (this.questiondata.choiceList.length === 5) {
      val = '5'
    }
    this.questiondata.choiceList.push({
      id: val,
      value: ''
    })
    // 强制刷新
    this.$forceUpdate()
  }
},
quillContent(quillContent) {
  this.questiondata.title = quillContent
},
optionQuillContent(quillContent, callbackParam) {
  this.questiondata.choiceList[callbackParam].value = quillContent
},
answerQuillContent(quillContent) {
  this.questiondata.questionAnswer = quillContent
}

}

}
</script>

<style lang="scss" scoped> .mini-btn { height: 30px; width: 30px; padding: 0 2px; margin: 9px 0 0 8px; } </style>

addQuestion.vue

System Info

package.json
{
  "name": "igrandsoft",
  "version": "1.0.0",
  "description": "中环宏图管理系统",
  "author": "中环宏图产品组",
  "license": "MIT",
  "scripts": {
    "dev": "vue-cli-service serve",
    "build:prod": "vue-cli-service build",
    "build:stage": "vue-cli-service build --mode staging",
    "preview": "node build/index.js --preview"
  },
  "husky": {
    "hooks": {
      "pre-commit": "lint-staged"
    }
  },
  "lint-staged": {
    "src/**/*.{js,vue}": [
      "eslint --fix",
      "git add"
    ]
  },
  "keywords": [
    "vue",
    "admin",
    "dashboard",
    "element-ui",
    "boilerplate",
    "admin-template",
    "management-system"
  ],
  "repository": {
    "type": "git",
    "url": "[email protected]:igrandsoft-web/igrandsoft-web-admin.git"
  },
  "dependencies": {
    "@riophae/vue-treeselect": "0.4.0",
    "ali-oss": "^6.16.0",
    "axios": "0.24.0",
    "clipboard": "2.0.8",
    "core-js": "3.19.1",
    "echarts": "4.9.0",
    "element-ui": "2.15.6",
    "file-saver": "2.0.5",
    "fuse.js": "6.4.3",
    "highlight.js": "10.7.3",
    "js-beautify": "1.13.0",
    "js-cookie": "3.0.1",
    "jsencrypt": "3.2.1",
    "nprogress": "0.2.0",
    "qrcodejs2": "0.0.2",
    "quill": "1.3.7",
    "screenfull": "5.0.2",
    "sortablejs": "1.10.2",
    "vue": "2.6.12",
    "vue-count-to": "1.0.13",
    "vue-cropper": "0.5.5",
    "vue-meta": "2.4.0",
    "vue-quill-editor": "^3.0.6",
    "vue-router": "3.4.9",
    "vuedraggable": "2.24.3",
    "vuex": "3.6.0"
  },
  "devDependencies": {
    "@babel/eslint-parser": "^7.18.2",
    "@babel/eslint-plugin": "^7.17.7",
    "@vue/cli-plugin-babel": "4.4.6",
    "@vue/cli-plugin-eslint": "4.4.6",
    "@vue/cli-service": "4.4.6",
    "babel-eslint": "^10.1.0",
    "chalk": "4.1.0",
    "connect": "3.6.6",
    "eslint": "7.15.0",
    "eslint-plugin-vue": "7.2.0",
    "lint-staged": "10.5.3",
    "quill-image-extend-module": "^1.1.2",
    "runjs": "4.4.2",
    "sass": "1.32.13",
    "sass-loader": "10.1.1",
    "script-ext-html-webpack-plugin": "2.1.5",
    "svg-sprite-loader": "5.1.1",
    "vue-template-compiler": "2.6.12"
  },
  "browserslist": [
    "> 1%",
    "last 2 versions"
  ],
  "engines": {
    "node": ">=8.9",
    "npm": ">= 3.0.0"
  }
}

Used Package Manager

npm

Validations

  • Read the the documentation in detail.
  • Check that there isn't already an issue that reports the same bug to avoid creating a duplicate.
  • Check that this is a concrete bug. For Q&A open a GitHub Discussion or join our Discord Chat Server.
  • The provided reproduction is a minimal reproducible example of the bug.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant