ckeditorcss
vue-quill-editorhtml
<template> <div :style="containerStyle" class="rich-text-editor"> <div v-if="textInput" class="rich-text-editor-wrap"> <div v-show="!isFocus && !isFullScreen" class="preview-container"> <el-input v-model="textValue" :placeholder="placeholder" @focus="showEditor" ></el-input> </div> <div v-show="isFocus || isFullScreen"> <textarea :id="editorId"></textarea> </div> </div> <div v-else class="rich-text-editor-wrap"> <textarea :id="editorId"></textarea> </div> </div> </template>
<script> import { CKEDITOR } from ''@/util/ckeditor.js'' let count = 0 export default { props: { width: { type: String, default: ''100%'' }, height: { type: String, default: ''100%'' }, textInput: { type: Boolean, default: false }, autoSaveKey: { type: String, default: '''' }, value: { type: String, default: '''' }, // 工具欄:Basic、Full、Common toolbar: { type: [String, Array], default: ''Common'' }, placeholder: { type: String, default: '''' }, readonly: { type: Boolean, default: false } }, watch: { editor: { handler (val) { if (val === null) { this.initRichText() } } }, value (val) { if (this.itemValue !== val) { this.itemValue = val this.setValue() } } }, data () { return { editorId: ''rich-text-editor-'' + ++count, itemValue: this.value, editor: null, copyPopover: null, copyValue: '''', isFocus: false, textValue: '''', // 是否全屏 isFullScreen: false } }, computed: { containerStyle () { let style = {} if (this.width.indexOf(''%'') < 0 && !this.textInput) { style.minWidth = this.width } if (this.height.indexOf(''%'') < 0 && !this.textInput) { style.minHeight = this.height } return style } }, methods: { initRichText () { let $ = window.jQuery let config = { height: this.height, width: this.width, autoSaveKey: this.autoSaveKey, placeholder: this.placeholder, readOnly: this.readonly } if (this.isTextInput === true) { config.startupFocus = true } if (this.toolbar !== ''Common'') { config.toolbar = this.toolbar } let editor = CKEDITOR.replace(this.editorId, config) this.editor = editor let self = this if (this.editor && this.editor !== null) { // CKEDITOR.on(''instanceReady'', function (e) { // 設置值 // 加載完畢事件 editor.on(''loaded'', () => { this.$emit(''loaded'', editor) }) // 設置值 if (this.itemValue) { this.setValue() } // 內容改變事件 editor.on(''change'', function (evt) { let value = evt.editor.getData() let textValue = evt.editor.document.getBody().getText() self.handleChange(value, textValue) }) // 粘貼事件 editor.on(''paste'', function (evt) { let data = evt.data let type = data.type let value = data.dataValue if (type === ''html'') { // html內容 try { let editor = evt.editor let $obj = $(value) if ($obj.is(''img'')) { let src = $obj.attr(''src'') let maxWidth = window.editorImageMaxWidth $(''<img/>'') .attr(''src'', src) .load(function () { let width = this.width if (maxWidth && width > maxWidth) { $obj.css(''width'', maxWidth + ''px'') $obj.css(''height'', ''auto'') } editor.insertHtml($obj.prop(''outerHTML'')) }) return false } } catch (e) { console.error(e) } } value && self.createCopyPopover(value) }) // 鍵盤輸入事件 editor.on(''key'', function (evt) { self.removeCopyPopover() }) // 最大化最小化事件 editor.on(''maximize'', function (evt) { self.isFullScreen = !self.isFullScreen if (window.fullScreenEditor) { window.fullScreenEditor = null } else { window.fullScreenEditor = evt.editor } }) // 失去焦點事件 editor.on(''blur'', function (evt) { // self.validate(''blur'') if ( window.fullScreenEditor == null || window.fullScreenEditor === undefined ) { self.$emit(''blur'') } self.isFocus = false }) // 得到焦點事件 editor.on(''focus'', function (evt) { self.isFocus = true }) // }) } }, showEditor () { this.isFocus = true this.$nextTick(() => { if (this.editor && this.editor !== null) { this.editor.focus() } }) }, 木易楊@: handleChange (value, textValue) { this.itemValue = value this.textValue = textValue this.validate(''change'') // 判斷是否爲所有空格 let newValue = '''' if ( !textValue.trim() && !value.includes(''<img'') && !value.includes(''<table'') ) { newValue = '''' } else { newValue = value } this.$emit(''input'', value) this.$emit(''change'', newValue) }, setValue () { this.editor.setData(this.itemValue) this.textValue = this.getText() }, 木易楊@: getText () { if (!this.itemValue) { return '''' } if (this.editor.document) { try { return this.editor.document.getBody().getText() } catch (e) { console.info(e) } } let html = this.itemValue if (!html.startsWith(''<'')) { html = ''<div>'' + html + ''</div>'' } return window.jQuery(html).text() }, getEditorDom () { return document.getElementById(''cke_'' + this.editorId) }, getOffsetTop (obj) { obj = obj || this.getEditorDom() let offsetTop = obj.offsetTop || 0 return offsetTop === 0 ? obj.parentNode ? this.getOffsetTop(obj.parentNode) : 0 : offsetTop }, getOffsetLeft (obj) { obj = obj || this.getEditorDom() let offsetLeft = obj.offsetLeft || 0 return offsetLeft === 0 ? obj.parentNode ? this.getOffsetLeft(obj.parentNode) : 0 : offsetLeft }, getCenterPosition () { let offsetTop = this.getOffsetTop() let getOffsetLeft = this.getOffsetLeft() let height = this.getEditorDom().offsetHeight let width = this.getEditorDom().offsetWidth return { top: offsetTop + height / 2, left: getOffsetLeft + width / 2 } }, getEditorDocument () { return this.editor.document.$ }, createCopyPopover (value) { this.removeCopyPopover() let div = document.createElement(''div'') div.innerHTML = ` <ul> <li class="source-text-li"><i class="source-text"></i><span>保留源格式</span></li> <li class="plain-text-li"><i class="plain-text"></i><span>保留純文本</span></li> </ul> ` div.className = ''cke_popover'' let ckeInner = this.getEditorDom().querySelector(''.cke_inner'') div.style.position = ''absolute'' div.style.top = ''50%'' div.style.left = ''50%'' ckeInner.appendChild(div) this.copyPopover = div this.copyValue = value // 監聽事件 let self = this div .querySelector(''.source-text-li'') .addEventListener(''click'', function () { self.removeCopyPopover() }) div.querySelector(''.plain-text-li'').addEventListener(''click'', function () { self.removeCopyPopover() self.editor.execCommand(''undo'') div = document.createElement(''div'') div.innerHTML = self.copyValue self.editor.insertHtml(div.innerText) }) }, removeCopyPopover () { if (this.copyPopover != null) { this.copyPopover.remove() this.copyPopover = null } }, getParentVNode (vnode, tagName) { if (!vnode) { vnode = this } if (!vnode.$vnode || !vnode.$vnode.componentOptions) { return null } let tag = vnode.$vnode.componentOptions.tag if (tag === tagName) { return vnode } if (!vnode.$parent) { return null } return this.getParentVNode(vnode.$parent, tagName) }, getElFormItem (vnode) { return this.getParentVNode(null, ''el-form-item'') }, getElForm (vnode) { return this.getParentVNode(null, ''el-form'') }, validate (eventName) { let elFormItem = this.getElFormItem() if (elFormItem) { let elForm = this.getElForm(elFormItem) let prop = elFormItem.prop if (prop && elForm.rules) { for (let name in elForm.rules) { if (name === prop) { let isNeddValidate = false elForm.rules[name].forEach(rule => { if (rule.trigger === eventName) { isNeddValidate = true } }) isNeddValidate && elForm.validateField(prop) } } } } } }, mounted () { this.initRichText() } } </script>
<style lang="less"> /* 去除路徑顯示 */ .cke_path { display: none; } .cke_popover { font-size: 13px; ul { background: #fff; list-style: none; border: 1px solid #ccc; border-bottom: none; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; li { padding: 5px; border-bottom: 1px solid #ccc; cursor: pointer; height: 20px; line-height: 20px; > span { vertical-align: middle; } i { display: inline-block; width: 15px; height: 15px; vertical-align: middle; background-size: 15px 15px; margin-right: 5px; } i.plain-text { background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAkElEQVQ4T+3TPQ4BYRCH8d8egiO4gUqzhUajcwiVOIQLyN5CLRqi1WodgUvIJitZrx0iS7fTzswzn/9MS8ta5ksBG/Q+QG+YPWJSwAUF1shxrALHOGCJOQYR4IQprgGgjy2GEaCstK+cTR2Urgl2EaA+fgR4WtG7K3SA5j94+bG/LXGFUVLujEXaws/F9LU47xkkIBGRZtDbAAAAAElFTkSuQmCC); } i.source-text { background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABHUlEQVQ4T6XTsSuGURTH8c+LFUUZKIsyCKNdSozYTUoZRBZKIrEoWSx2k8IqKX+CEItJGSVMDOjqvk/X7cHw3ul5zjnP957fOb+nosZTqfF7KeAJW9iP0Do0xudXfKABGxhFc8ilgD0s4THp6rsIz0msFfNYzgF9uMokreITa1l8CGc5oBfXSWGQ8BDfO6KEaroHNzmgH5cJYAyLGMA4jpJcUZvOoGgrFh5iBvc4x3ACKJWQAjqxgikcYALduIuQUkA6xLDOerzEdc1hGwsRUNSmEqq6wq5PMZi0fIt2tOENf85gBF3YTQCzaMEFjn8DrGMnM1KZ04ORNjGdrzFY+SQ67D3OoCkSwiyClcOZjE4MHf2wctlt/8Zq/hu/ABKzNxFN8ClKAAAAAElFTkSuQmCC); } } li:hover { background-color: #eee; } } } </style>
<rich-text-editor width="81%" v-model="formModel.march" :toolbar="toolbar"></rich-text-editor> data(){ return{ toolbar: [ { name: ''basicstyles'', items: [''Bold'', ''Italic'', ''Underline'', ''Strike'', ''Subscript'', ''Superscript'', ''RemoveFormat''] }, { name: ''editing'', items: [''Find'', ''Replace'', ''SelectAll''] }, { name: ''document'', items: [''Source'', ''DocProps'', ''Preview''] }, { name: ''paragraph'', items: [''NumberedList'', ''Outdent'', ''Indent'', ''Blockquote'', ''CreateDiv'', ''JustifyLeft'', ''JustifyCenter'', ''JustifyRight'', ''JustifyBlock'', ''BidiLtr'', ''BidiRtl''] }, { name: ''forms'', items: [ ''Form'', ''Checkbox'', ''Radio'', ''TextField'', ''Textarea'', ''Select'' ] }, { name: ''links'', items: [ ''Link'', ''Unlink'', ''Anchor'', ''uploadbutton'' ] }, { name: ''insert'', items: [ ''Table'', ''HorizontalRule'', ''Smiley'', ''SpecialChar'', ''PageBreak'' ] }, { name: ''styles'', items: [ ''Styles'', ''Format'', ''Font'', ''FontSize'' ] }, { name: ''clipboard'', items: [''Cut'', ''Copy'', ''Paste'', ''PasteText'', ''Undo'', ''Redo''] }, { name: ''colors'', items: [''TextColor'', ''BGColor''] } ] } }
工具欄的定義英漢對照說明: Source = 源碼模式 Save = 保存(提交表單) NewPage = 新建 Preview = 預覽 - = 分割線 Templates = 模板 Cut = 剪切 Copy = 複製 Paste = 粘貼 PasteText = 粘貼爲無格式文本 PasteFromWord = 從 MS WORD 粘貼 Print = 打印 SpellChecker = 拼寫檢查 Scayt = 即時拼寫檢查 Undo = 撤銷 Redo = 重作 Find = 查找 Replace = 替換 SelectAll = 全選 RemoveFormat = 清除格式 Form = 表單 Checkbox = 複選框 Radio = 單選框 TextField = 單行文本 Textarea = 多行文本 Select = 列表/菜單 Button = 按鈕 ImageButton = 圖片按鈕 HiddenField = 隱藏域 Bold = 加粗 Italic = 傾斜 Underline = 下劃線 Strike = 刪除線 Subscript = 下標 Superscript = 上標 NumberedList = 編號列表 BulletedList = 項目列表 Outdent = 減小縮進量 Indent = 增長縮進量 Blockquote = 塊引用 CreateDiv = 建立DIV容器 JustifyLeft = 左對齊 JustifyCenter = 居中 JustifyRight = 右對齊 JustifyBlock = 兩端對齊 BidiLtr = 文字方向從左到右 BidiRtl = 文字方向從右到左 Link = 插入/編輯超連接(上傳文件) Unlink = 取消超連接 Anchor = 插入/編輯錨點連接 Image = 圖像(上傳) Flash = 動畫(上傳) Table = 表格 HorizontalRule = 插入水平線 Smiley = 插入表情 SpecialChar = 插入特殊符號 PageBreak = 插入分頁符 Styles = 樣式快捷方式 Format = 文本格式 Font = 字體 FontSize = 文字大小 TextColor = 文字顏色 BGColor = 背景顏色 Maximize = 全屏編輯模式 ShowBlocks = 顯示區塊 About = 顯示關於
本文內容圖片來自https://www.zhihu.com/questio...vue