quill.js 汉化处理

1,793 阅读1分钟

关于 Quill

Quill 是一个富文本编辑器库,API 驱动,高自由度定制化。

但 Quill 界面都是英文的,面向普通用户不太友好,需要显示成中文。

默认效果如下:

image.png

想要的效果如下:

image.png

汉化处理

常用功能中需要转换成中文的有4个部分:

placeholder

这个很简单,加个配置项

new Quill(this.$refs.editor, {
  placeholder: '请输入内容'
})

image.png

picker

字号和标题这里英文就很明显,需要覆盖 quill 的样式来实现。

image.png

.ql-snow.ql-toolbar {
  .ql-picker.ql-header {
    width: 70px;
    .ql-picker-label::before,
    .ql-picker-item::before {
      content: '普通';
    }
    .ql-picker-label[data-value='1']::before,
    .ql-picker-item[data-value='1']::before {
      content: '标题一';
    }
    .ql-picker-label[data-value='2']::before,
    .ql-picker-item[data-value='2']::before {
      content: '标题二';
    }
    .ql-picker-label[data-value='3']::before,
    .ql-picker-item[data-value='3']::before {
      content: '标题三';
    }
    .ql-picker-label[data-value='4']::before,
    .ql-picker-item[data-value='4']::before {
      content: '标题四';
    }
    .ql-picker-label[data-value='5']::before,
    .ql-picker-item[data-value='5']::before {
      content: '标题五';
    }
    .ql-picker-label[data-value='6']::before,
    .ql-picker-item[data-value='6']::before {
      content: '标题六';
    }
  }
  .ql-picker.ql-size {
    width: 70px;
    .ql-picker-label::before,
    .ql-picker-item::before {
      content: '普通';
    }
    .ql-picker-label[data-value='small']::before,
    .ql-picker-item[data-value='small']::before {
      content: '小';
    }
    .ql-picker-label[data-value='large']::before,
    .ql-picker-item[data-value='large']::before {
      content: '中';
    }
    .ql-picker-label[data-value='huge']::before,
    .ql-picker-item[data-value='huge']::before {
      content: '大';
    }
  }
}

image.png

title

其实 Quill 默认是没有 title 的,但格式操作图标不给个提示难免让用户困惑,有必要加个 title 提示下每个图标的含义。

image.png

加 title 就要麻烦点了,需要遍历所有图标设置 title 属性。

const TOOLBAR_TILTE = {
  'ql-bold': ['加粗'],
  'ql-italic': ['倾斜'],
  'ql-underline': ['下划线'],
  'ql-strike': ['删除线'],
  'ql-blockquote': ['块引用'],
  'ql-script': ['下标', '上标'],
  'ql-list': ['编号列表', '项目符号列表'],
  'ql-indent': ['减少缩进量', '增加缩进量'],
  'ql-align': ['对齐', '左对齐', '居中', '右对齐', '两端对齐'],
  'ql-color': ['字体颜色'],
  'ql-background': ['文本突出显示颜色'],
  'ql-size': ['字号'],
  'ql-header': ['标题'],
  'ql-link': ['链接'],
  'ql-image': ['图片'],
  'ql-clean': ['清除样式']
}
for (let key in TOOLBAR_TILTE) {
  Array.from(
    this.$refs.bounds.querySelectorAll(
      `.ql-toolbar .ql-formats > .${key}`
    )
  ).forEach((item, index) => {
    if (key === 'ql-align') {
      Array.from(item.querySelectorAll('[role=button]')).forEach(
        (role, rIndex) => {
          role.title = TOOLBAR_TILTE[key][rIndex]
        }
      )
    } else {
      item.title = TOOLBAR_TILTE[key][index]
    }
  })
}

tooltip

主要是链接的编辑会用到这个组件,也是通过覆盖 quill 的样式来实现。

image.png

.ql-snow .ql-tooltip::before {
  content: '链接';
}
.ql-snow .ql-tooltip a.ql-action::after {
  content: '编辑';
}
.ql-snow .ql-tooltip.ql-editing a.ql-action::after {
  content: '保存';
}
.ql-snow .ql-tooltip a.ql-remove::before {
  content: '移除';
}

image.png

完整实现

把 Quill 封装成一个 Vue 组件,想要的使用效果就是 <Quill v-model="foo" />,简单明了。

首先需要安装 quill

npm i quill --save

Quill.vue

<template>
  <div ref="bounds" class="quill-editor">
    <div ref="editor"></div>
  </div>
</template>

<script>
import 'quill/dist/quill.core.css'
import 'quill/dist/quill.snow.css'
import Quill from 'quill'

export default {
  name: 'QuillEditor',
  props: {
    value: Object
  },
  data() {
    return {
      editor: null
    }
  },
  mounted() {
    const options = {
      theme: 'snow',
      bounds: this.$refs.bounds,
      placeholder: '请输入内容',
      modules: {
        toolbar: {
          container: [
            [
              { size: ['small', false, 'large', 'huge'] },
              { header: [1, 2, 3, 4, 5, 6, false] }
            ],
            [
              { color: [] },
              { background: [] },
              'bold',
              'italic',
              'underline',
              'strike',
              'blockquote',
              { script: 'sub' },
              { script: 'super' }
            ],
            [
              { list: 'ordered' },
              { list: 'bullet' },
              { indent: '-1' },
              { indent: '+1' },
              { align: [] }
            ],
            ['link'],
            ['clean']
          ]
        }
      }
    }
    this.editor = new Quill(this.$refs.editor, options)
    this.editor.setContents(this.value)
    this.sinicize()
    this.change()
  },
  watch: {
    value() {
      if (
        JSON.stringify(this.value) !== JSON.stringify(this.editor.getContents())
      ) {
        this.editor.setContents(this.value)
      }
    }
  },
  methods: {
    sinicize() {
      const TOOLBAR_TILTE = {
        'ql-bold': ['加粗'],
        'ql-italic': ['倾斜'],
        'ql-underline': ['下划线'],
        'ql-strike': ['删除线'],
        'ql-blockquote': ['块引用'],
        'ql-script': ['下标', '上标'],
        'ql-list': ['编号列表', '项目符号列表'],
        'ql-indent': ['减少缩进量', '增加缩进量'],
        'ql-align': ['对齐', '左对齐', '居中', '右对齐', '两端对齐'],
        'ql-color': ['字体颜色'],
        'ql-background': ['文本突出显示颜色'],
        'ql-size': ['字号'],
        'ql-header': ['标题'],
        'ql-link': ['链接'],
        'ql-image': ['图片'],
        'ql-clean': ['清除样式']
      }
      for (let key in TOOLBAR_TILTE) {
        Array.from(
          this.$refs.bounds.querySelectorAll(
            `.ql-toolbar .ql-formats > .${key}`
          )
        ).forEach((item, index) => {
          if (key === 'ql-align') {
            Array.from(item.querySelectorAll('[role=button]')).forEach(
              (role, rIndex) => {
                role.title = TOOLBAR_TILTE[key][rIndex]
              }
            )
          } else {
            item.title = TOOLBAR_TILTE[key][index]
          }
        })
      }
    },
    change() {
      this.editor.on('text-change', () => {
        this.$emit('input', this.editor.getContents())
      })
    }
  }
}
</script>

<style lang="scss" scoped>
.quill-editor {
  ::v-deep {
    .ql-editor {
      min-height: 100px;
      max-height: 500px;
    }
    .ql-snow.ql-toolbar {
      .ql-picker.ql-header {
        width: 70px;
        .ql-picker-label::before,
        .ql-picker-item::before {
          content: '普通';
        }
        .ql-picker-label[data-value='1']::before,
        .ql-picker-item[data-value='1']::before {
          content: '标题一';
        }
        .ql-picker-label[data-value='2']::before,
        .ql-picker-item[data-value='2']::before {
          content: '标题二';
        }
        .ql-picker-label[data-value='3']::before,
        .ql-picker-item[data-value='3']::before {
          content: '标题三';
        }
        .ql-picker-label[data-value='4']::before,
        .ql-picker-item[data-value='4']::before {
          content: '标题四';
        }
        .ql-picker-label[data-value='5']::before,
        .ql-picker-item[data-value='5']::before {
          content: '标题五';
        }
        .ql-picker-label[data-value='6']::before,
        .ql-picker-item[data-value='6']::before {
          content: '标题六';
        }
      }
      .ql-picker.ql-size {
        width: 70px;
        .ql-picker-label::before,
        .ql-picker-item::before {
          content: '普通';
        }
        .ql-picker-label[data-value='small']::before,
        .ql-picker-item[data-value='small']::before {
          content: '小';
        }
        .ql-picker-label[data-value='large']::before,
        .ql-picker-item[data-value='large']::before {
          content: '中';
        }
        .ql-picker-label[data-value='huge']::before,
        .ql-picker-item[data-value='huge']::before {
          content: '大';
        }
      }
    }
    .ql-snow .ql-tooltip::before {
      content: '链接';
    }
    .ql-snow .ql-tooltip a.ql-action::after {
      content: '编辑';
    }
    .ql-snow .ql-tooltip.ql-editing a.ql-action::after {
      content: '保存';
    }
    .ql-snow .ql-tooltip a.ql-remove::before {
      content: '移除';
    }
  }
}
</style>

page.vue

<template>
  <Quill v-model="foo" />
</template>

<script>
import Quill from './Quill'

export default {
  name: 'Page',
  components: { Quill },
  data() {
    return {
      foo: null
    }
  }
}
</script>

试试效果

image.png