vue实战----富文本编辑器。Tinymce

Tinymce  如何嵌入vue 不说了

代码核心是对Tinymce 默认功能封装。  此篇文件  主要是针对富文本编辑器的样式改造和 一个拓展按钮功能实现

Tinymce  很强,他有丰富的APi和对用户开放的接口

我们可以通过对外开放接口 定制自己需要的功能。直接上代码 

使用代码

引入

import Tinymce from '@comp/widgets/TinymceVue'

import { defaultStyles } from '@util/tinymceConfig'



html 片段
      <tinymce id="tinymce-area" ref="tinymceArea" v-model="content" :width="942" style="width: 950px;margin: 0 auto;float: left;" @onChoose="isInsertModalShow=true"></tinymce>

tinymceConfig .js

//样式重构处理。
export const defaultStyles = `
    .area {
      position: relative;
      display: inline-block;
      text-indent: 0;
    }
    .area button {
      position: relative;
      margin-left: 1px;
      top: -1px;
      display:inline-block;
      padding: 4px 30px;
      border: none;
      border-bottom: 1px solid #333;
      width: auto;
      min-width: 120px;
      vertical-align: bottom;
      background-color: #FFF3C8;
      color: #F38F1B;
      font-size: 14px;
      text-align: center;
    }
    .area .remove {
      display: none;
      position: absolute;
      right: -6px;
      bottom: 20px;
      width: 14px;
      height: 14px;
      background: url('/static/img/close.png') 0 0 no-repeat;
      color: #fff;
      cursor: pointer;
    }
    .area:hover .remove {
      display: inline-block;
      font-size: 12px;
    }
  `
// p {
//   display: block;
//   -webkit-margin-before: 1em;
//   -webkit-margin-after: 1em;
//   -webkit-margin-start: 0px;
//   -webkit-margin-end: 0px;
// }

// .b1{white-space-collapsing:preserve;}
// .b2{margin: 0.7875in 1.0236111in 0.7875in 1.0236111in;}
// .p1{text-align:start;hyphenate:auto;font-family:Times New Roman;font-size:16pt;}
// .p2{text-indent:2.2326388in;text-align:justify;hyphenate:auto;font-family:宋体;font-size:22pt;}
// .p3{text-align:center;hyphenate:auto;font-family:Times New Roman;font-size:22pt;}
// .p4{text-align:center;hyphenate:auto;font-family:宋体;font-size:12pt;}
// .p5{text-align:start;hyphenate:auto;font-family:宋体;font-size:16pt;}
// .p6{text-align:justify;hyphenate:auto;font-family:宋体;font-size:12pt;}
// .p7{text-indent:3.6006944in;text-align:start;hyphenate:auto;font-family:宋体;font-size:12pt;}
// .p8{text-align:justify;hyphenate:auto;font-family:宋体;font-size:10pt;}
// .p9{text-indent:0.29166666in;text-align:justify;hyphenate:auto;font-family:宋体;font-size:10pt;}
// .p10{text-indent:0.30208334in;text-align:justify;hyphenate:auto;font-family:宋体;font-size:10pt;}
// .p11{text-indent:0.29166666in;text-align:start;hyphenate:auto;font-family:Times New Roman;font-size:10pt;}
// .p12{text-indent:0.30208334in;text-align:justify;hyphenate:auto;font-family:Times New Roman;font-size:10pt;}
// .p13{text-indent:0.5833333in;text-align:justify;hyphenate:auto;font-family:宋体;font-size:10pt;}
// .p14{text-indent:0.29166666in;text-align:start;hyphenate:auto;font-family:宋体;font-size:10pt;}
// .p15{text-indent:0.40138888in;text-align:justify;hyphenate:auto;font-family:宋体;font-size:10pt;}
// .p16{text-indent:0.47430557in;text-align:justify;hyphenate:auto;font-family:宋体;font-size:10pt;}
// .p17{text-align:justify;hyphenate:auto;font-family:Times New Roman;font-size:10pt;}
// .p18{text-align:center;hyphenate:auto;font-family:Times New Roman;font-size:16pt;}
// .p19{text-align:start;hyphenate:auto;font-family:Times New Roman;font-size:10pt;}
// .p20{text-indent:0.29166666in;text-align:justify;hyphenate:auto;font-family:Times New Roman;font-size:10pt;}
// .p21{text-indent:0.29166666in;margin-left:0.29166666in;text-align:justify;hyphenate:auto;font-family:宋体;font-size:10pt;}
// .p22{text-indent:0.5833333in;margin-left:0.29166666in;text-align:justify;hyphenate:auto;font-family:宋体;font-size:10pt;}
// .p23{text-indent:0.875in;margin-left:0.29166666in;text-align:justify;hyphenate:auto;font-family:宋体;font-size:10pt;}
// .p24{margin-right:-0.011805556in;text-align:justify;hyphenate:auto;font-family:宋体;font-size:10pt;}
// .s1{font-family:宋体;font-weight:bold;}
// .s2{color:red;text-decoration:underline;}
// .s3{font-weight:bold;}
// .s4{font-weight:bold;color:red;text-decoration:underline;}
// .s5{font-family:宋体;}
// .s6{text-decoration:underline;}
// .s7{color:black;}
// .s8{font-family:Times New Roman;color:red;text-decoration:underline;}
// .s9{color:black;text-decoration:underline;}
// .s10{color:red;}
// .s11{font-family:Times New Roman;color:black;}
// .s12{font-family:宋体;font-size:12pt;color:red;text-decoration:underline;}
// .s13{font-weight:bold;color:black;}
// .s14{font-family:宋体;color:red;text-decoration:underline;}
// .s15{font-family:宋体;color:black;}
// .td1{width:3.0194445in;padding-start:0.019444445in;padding-end:0.019444445in;border-bottom:thin solid black;border-left:thin solid black;border-right:thin solid black;border-top:thin solid black;}
// .td2{width:3.025in;padding-start:0.019444445in;padding-end:0.019444445in;border-bottom:thin solid black;border-left:thin solid black;border-right:thin solid black;border-top:thin solid black;}
// .r1{height:1.4631945in;keep-together:always;}
// .r2{height:1.0145833in;keep-together:always;}
// .t1{table-layout:fixed;border-collapse:collapse;border-spacing:0;}

export default {
  width: '824',
  height: '400',
  'theme-advanced-resizing': false,
  // content_css: 'ui/tinymce/myContent.css',
  // content_style: styles,
  // document_base_url: 'http://localhost:2333/',
  // relative_urls: false,
  skin: false,
  statusbar: false,
  // height: 300,
  toolbar: 'undo redo | formatselect fontselect fontsizeselect | bold italic underline strikethrough forecolor backcolor | alignleft aligncenter alignright alignjustify  | numlist bullist outdent indent  | removeformat | table | InsertElements',
  menu: {},
  font_formats: '微软雅黑=微软雅黑,Microsoft YaHei;宋体=宋体, SimSun;仿宋=仿宋;仿宋_GB2312=FangSong_GB2312;新宋体=新宋体;黑体=黑体, SimHei;隶书=隶书, SimLi;楷体=楷体, SimKai;楷体_GB2312=KaiTi_GB2312;幼圆=幼圆;andale mono=andale mono;arial=arial, helvetica,sans-serif;arial black=arial black,avant garde;comic sans ms=comic sans ms;impact=impact,chicago;Arial=Arial;Verdana=Verdana;Georgia=Georgia;Times New Roman=Times New Roman;Trebuchet MS=Trebuchet MS;Courier New=Courier New;Impact=Impact;Comic Sans MS=Comic Sans MS;Calibri=Calibri',
  fontsize_formats: '初号=42pt 小初=36pt 一号=26pt 小一=24pt 二号=22pt 小二=18pt 三号=16pt 小三=15pt 四号=14pt 小四=12pt 五号=10.5pt 小五=9pt 六号=7.5pt 小六=6.5pt 七号=5.5pt 八号=5pt 5pt 5.5pt 6.5pt 7.5pt 8pt 9pt 10pt 10.5pt 11pt 12pt 14pt 16pt 18pt 20pt 24pt 36pt 48pt 72pt',
  block_formats: 'Paragraph=p;Heading 1=h1;Heading 2=h2;Heading 3=h3;Heading 4=h4;Heading 5=h5;Heading 6=h6'
}

 

 

代码如下

Tinymce.vue

<template>
  <div style="overflow-y: auto;" :style="{height: height+'px'}">
    <div :style="{width: width + 'px'}" style="margin: 0 auto;">
      <textarea :id="id" />
    </div>
  </div>
</template>

<script>
// Import TinyMCE
import tinymce from 'tinymce/tinymce'
// A theme is also required
import 'tinymce/themes/modern/theme'

// Any plugins you want to use has to be imported
import 'tinymce/plugins/paste'
import 'tinymce/plugins/autoresize'
import 'tinymce/plugins/textcolor'
import 'tinymce/plugins/wordcount'
import 'tinymce/plugins/fullscreen'
import 'tinymce/plugins/table'

import '../../../static/langs/zh_CN'

import config from '@util/tinymceConfig'
import safeStr from '@util/xss'

let content = ''
export default {
  name: 'Tinymce',
  props: {
    id: {
      type: String,
      required: true
    },
    // htmlClass: {
    //   default: '',
    //   type: String
    // },
    width: {
      type: Number,
      default: 1000
    },
    styles: {
      type: String,
      default: ''
    },
    value: {
      type: String,
      default: ''
    },
    plugins: {
      default: () => {
        return [
          // 'advlist autolink lists link image charmap print preview hr anchor pagebreak',
          // 'searchreplace wordcount visualblocks visualchars code fullscreen',
          // 'insertdatetime media nonbreaking save table contextmenu directionality',
          'textcolor wordcount fullscreen autoresize table'
        ]
      },
      type: Array
    },
    otherOptions: { default: () => {}, type: Object }
  },
  data () {
    return {
      // content: '',
      editor: null,
      checkerTimeout: null,
      height: 500,
      isTyping: false
    }
  },

  watch: {
    value: function (newValue) {
      if (!this.isTyping) {
        if (this.editor !== null) {
          this.editor.setContent(newValue)
          this.submitNewContent()
        } else {
          content = newValue
        }
      }
    }
  },

  mounted () {
    content = this.value
    if (this.editor) {
      this.editor.destroy()
    }

    this.height = this.$app.remote.getCurrentWindow().getSize()[1] - 200
    // this.$app.remote.getCurrentWindow().on('resize', () => {
    //   this.height = this.$app.remote.getCurrentWindow().getSize()[1] - 200
    // })
  },
  beforeDestroy () {
    content = null
    this.editor.destroy()
    this.editor = null
  },

  methods: {
    // 外部调用
    insertElem ({ id, elementName, elementKey }) {
      // contenteditable="false" 不可编辑
      let dom = `<span class="area ${id}" contenteditable="false">
          <button data-id="${id}" data-key="${elementKey}">
            ${safeStr(elementName)}
          </button>
          <span class="remove">.</span>
        </span>`
      // disabled="disabled" 去掉该button属性
      this.editor.insertContent(dom)
      this.saveRecentContent()
    },
    insertValueElem ({ id, elementName, elementKey, value }) {
      // contenteditable="false" 不可编辑
      let dom = `<span class="area valuearea ${id}" contenteditable="false">
          <button data-id="${id}" data-key="${elementKey}">
            ${safeStr(value || '')}
          </button>
          <span class="remove">.</span>
        </span>`
      // disabled="disabled" 去掉该button属性
      this.editor.insertContent(dom)
      this.saveRecentContent()
    },
    init (styles = '') {
      let options = {
        selector: '#' + this.id,
        width: this.width + '',
        // height: this.height + '',
        content_style: styles,
        content_css: process.env === 'development' ? '/static/myTinymceSkin/content.min.css' : 'static/myTinymceSkin/content.min.css',
        plugins: this.plugins,
        autoresize_min_height: this.height - 57,
        init_instance_callback: this.initEditor,
        setup: this.setupEditor
      }
      tinymce.init(this._assign(config, options, this.otherOptions))
    },
    initEditor (editor) {
      this.editor = editor

      // editor.on('KeyUp', (e) => {
      //   this.submitNewContent()
      // })
      // editor.on('Change', (e) => {
      //   if (this.editor.getContent() !== this.value) {
      //     this.submitNewContent()
      //   }
      //   this.$emit('editorChange', e)
      // })
      editor.on('init', () => {
        editor.setContent(content)
        // this.$emit('input', this.content)
        this.saveRecentContent()
      })

      editor.on('Click', (e) => {
        if (e.target.className.split(' ').indexOf('remove') > -1) {
          let removeNode = e.target.parentNode
          if (!!window.ActiveXObject || 'ActiveXObject' in window || (/Trident\/7\./).test(navigator.userAgent)) { // IE或IE11
            removeNode.parentNode.removeChild(removeNode)
          } else {
            removeNode.remove()
          }
          const id = removeNode.getElementsByTagName('button')[0].getAttribute('data-id')
          this.$emit('remove', id)
          this.submitNewContent()
        }
      })

      editor.on('dblClick', (e) => {
        let id = e.target.getAttribute('data-id') || ''
        if (id) {
          this.$emit('onDblclick', id)
        }
      })

      this.$emit('editorInit', editor)
    },
    setupEditor (editor) {
      this.editor = editor
      editor.addButton('InsertElements', {
        // icon: 'insertdatetime',
        image: process.env.TARGET === 'web' ? '/static/image/insert.png' : './static/image/insert.png',
        text: ' 插入要素',
        tooltip: '插入要素',
        onclick: () => {
          this.$emit('onChoose')
        }
      })
    },
    submitNewContent () {
      this.isTyping = true
      if (this.checkerTimeout !== null) {
        clearTimeout(this.checkerTimeout)
        this.checkerTimeout = setTimeout(() => {
          this.isTyping = false
        }, 300)
      }

      // this.$emit('input', this.editor.getContent())
      this.saveRecentContent()
    },
    setNewContent (newValue = '') {
      this.editor.setContent(newValue)
    },
    saveRecentContent () {
      this.$emit('input', this.editor.getContent())
    }
  }
}
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
/*@import '~tinymce/skins/lightgray/skin.min.css';*/
@import '../../assets/myTinymceSkin/skin.min.css';

</style>

升级使用功能插入标签。

上述核心代码中有一句。

@onChoose="isInsertModalShow=true"

主要作用是对一个弹框的控制

我的代码引入如下

import InsertModal from '../Contract/Modals/Insert'

<insert-modal v-model="isInsertModalShow" @choiceElem="insertElem"></insert-modal>

// 插入要素事件 insertElem实现方法

insertElem (elem) {

this.$refs.tinymceArea.insertElem(elem)

},

.Insert.vue 代码如下(主要是我自己的业务代码,此处贴处理是为了告诉用户,我们可以通过这个实现可以做很多事情)

<template>
  <Modal v-model="visible" title="插入要素" :mask-closable="false" width="450" class-name="vertical-center-modal">
  	<Row style="margin-bottom: 120px;">
  	  <Col span="6" class="col-left">查找要素</Col>
  	  <Col span="18">
        <auto-comp class="insert-search" v-model.trim="elementName" placeholder="请输入关键字查找要素" style="width:240px"
          @on-search="handleSearch" @on-focus="handleSearch">
          <div class="empty" v-if="isEmpty">暂无搜索结果</div>
          <Option v-else v-for="elem in elemsList" :value="elem.elementName" :key="elem.id" @click.native="handleSelect(elem)">{{ elem.elementName }}</Option>
        </auto-comp>

      </Col>
  	</Row>
  	<div slot="footer" align="middle">
      <Button type="primary" @click="onSubmit">确定</Button>
      <Button type="ghost" @click="onCancel">取消</Button>
    </div>
  </Modal>
</template>

<script>
import AutoComp from '@comp/widgets/AutoComp'
import ModalMixin from '@comp/mixins/ModalPage'

export default {
  name: 'contract-insert-element',

  mixins: [ModalMixin],

  components: {
    AutoComp
  },

  data () {
    return {
      setting: false, // 防重复提交
      isEmpty: true,
      elementName: '',
      elem: {},
      elemsList: []
    }
  },

  methods: {
    onShow () {
      this.setting = false
    },
    handleSearch () {
      let paramsData = {
        escapeLoading: true,
        pageNo: 1,
        pageSize: 200,
        sortField: 'createdTime',
        sortType: 'desc',
        elementName: this.elementName
      }
      this.$http.post('/contractElement/selectListByPage', paramsData).then(res => {
        if (res.rtnCode === '000') {
          console.log(res)
          this.elemsList = res.data.rows || []
          this.isEmpty = this.elemsList.length === 0
        } else {
          this.$Message.error(res.rtnMsg)
        }
      }).catch((err) => {
        this.$debug(err)
      })
    },
    handleSelect (elem) {
      this.selected = true
      this.elem = elem
    },
    onSubmit () {
      // 防止重复提交
      if (this.setting === false) {
        if (this._isEmpty(this.elem) || this.elem.elementName !== this.elementName) {
          this.$Message.warning('请点击选中要素')
        } else {
          this.setting = true
          this.$emit('choiceElem', this.elem)
          this.visible = false
        }
      }
    },
    onCancel () {
      this.visible = false
    }
  }
}
</script>

<style lang='scss' scoped>
.col-left {
  padding-right: 10px;
  line-height: 30px;
  font-size: 14px;
  text-align: right;
  color: #333;

  &:after {
    content: ':'
  }
}
.empty {
  height: 30px;
  margin:10px 0 0 15px;
  color: #999;
}
</style>

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值