index.vue 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277
  1. <template>
  2. <div :class="{fullscreen:fullscreen}" class="tinymce-container" :style="{width:containerWidth}">
  3. <textarea :id="tinymceId" class="tinymce-textarea" />
  4. <!--<div class="editor-custom-btn-container">
  5. <editorImage color="#1890ff" class="editor-upload-btn" @successCBK="imageSuccessCBK" />
  6. </div>-->
  7. </div>
  8. </template>
  9. <script>
  10. /**
  11. * docs:
  12. * https://panjiachen.github.io/vue-element-admin-site/feature/component/rich-editor.html#tinymce
  13. */
  14. // import editorImage from './components/EditorImage'
  15. import plugins from './plugins'
  16. // import toolbar from './toolbar'
  17. import load from './dynamicLoadScript'
  18. import { uploadImg } from '@/api/qiniu'
  19. // why use this cdn, detail see https://github.com/PanJiaChen/tinymce-all-in-one
  20. const tinymceCDN = 'https://cdn.jsdelivr.net/npm/tinymce-all-in-one@4.9.3/tinymce.min.js'
  21. export default {
  22. name: 'Tinymce',
  23. // components: { editorImage },
  24. props: {
  25. id: {
  26. type: String,
  27. default: function() {
  28. return 'vue-tinymce-' + +new Date() + ((Math.random() * 1000).toFixed(0) + '')
  29. }
  30. },
  31. value: {
  32. type: String,
  33. default: ''
  34. },
  35. toolbar: {
  36. type: Array,
  37. required: false,
  38. default() {
  39. return []
  40. // return ['removeformat undo redo | bullist numlist | outdent indent | forecolor | fullscreen code', 'bold italic blockquote | h2 p media link | alignleft aligncenter alignright | fontsizeselect | fontselect'] // 修改后 第一步
  41. }
  42. },
  43. menubar: {
  44. type: String,
  45. default: 'file edit insert view format table'
  46. },
  47. height: {
  48. type: [Number, String],
  49. required: false,
  50. default: 360
  51. },
  52. width: {
  53. type: [Number, String],
  54. required: false,
  55. default: 'auto'
  56. }
  57. },
  58. data() {
  59. return {
  60. hasChange: false,
  61. hasInit: false,
  62. tinymceId: this.id,
  63. fullscreen: false,
  64. languageTypeList: {
  65. 'en': 'en',
  66. 'zh': 'zh_CN',
  67. 'es': 'es_MX',
  68. 'ja': 'ja'
  69. }
  70. }
  71. },
  72. computed: {
  73. containerWidth() {
  74. const width = this.width
  75. if (/^[\d]+(\.[\d]+)?$/.test(width)) { // matches `100`, `'100'`
  76. return `${width}px`
  77. }
  78. return width
  79. }
  80. },
  81. watch: {
  82. value(val) {
  83. if (!this.hasChange && this.hasInit) {
  84. this.$nextTick(() =>
  85. window.tinymce.get(this.tinymceId).setContent(val || ''))
  86. }
  87. }
  88. },
  89. mounted() {
  90. this.init()
  91. },
  92. activated() {
  93. if (window.tinymce) {
  94. this.initTinymce()
  95. }
  96. },
  97. deactivated() {
  98. this.destroyTinymce()
  99. },
  100. destroyed() {
  101. this.destroyTinymce()
  102. },
  103. methods: {
  104. init() {
  105. // dynamic load tinymce from cdn
  106. load(tinymceCDN, (err) => {
  107. if (err) {
  108. this.$message.error(err.message)
  109. return
  110. }
  111. this.initTinymce()
  112. })
  113. },
  114. initTinymce() {
  115. const _this = this
  116. window.tinymce.init({
  117. // images_upload_url: '/static/image/',
  118. // images_upload_base_path: '/static/image',
  119. selector: `#${this.tinymceId}`,
  120. language: this.languageTypeList['zh'],
  121. height: this.height,
  122. body_class: 'panel-body ',
  123. object_resizing: false,
  124. // toolbar: this.toolbar.length > 0 ? this.toolbar : toolbar,
  125. menubar: this.menubar,
  126. plugins: plugins,
  127. end_container_on_empty_block: true,
  128. powerpaste_word_import: 'clean',
  129. code_dialog_height: 450,
  130. code_dialog_width: 1000,
  131. advlist_bullet_styles: 'square',
  132. advlist_number_styles: 'default',
  133. imagetools_cors_hosts: ['www.tinymce.com', 'codepen.io'],
  134. default_link_target: '_blank',
  135. toolbar: ['searchreplace bold italic underline strikethrough alignleft aligncenter alignright outdent indent blockquote undo redo removeformat subscript superscript code codesample fontsizeselect fontselect', 'hr bullist numlist link image charmap preview anchor pagebreak insertdatetime media table emoticons forecolor backcolor fullscreen'],
  136. fontsize_formats: '0pt 8pt 10pt 12pt 14pt 18pt 24pt 36pt',
  137. link_title: false,
  138. image_dimensions: false,
  139. content_style: 'div {margin: 0px; border:0px ; padding: 0px}',
  140. nonbreaking_force_tab: true, // inserting nonbreaking space &nbsp; need Nonbreaking Space Plugin
  141. init_instance_callback: editor => {
  142. if (_this.value) {
  143. editor.setContent(_this.value)
  144. }
  145. _this.hasInit = true
  146. editor.on('NodeChange Change KeyUp SetContent', () => {
  147. this.hasChange = true
  148. this.$emit('input', editor.getContent())
  149. })
  150. },
  151. setup(editor) {
  152. editor.on('FullscreenStateChanged', (e) => {
  153. _this.fullscreen = e.state
  154. })
  155. },
  156. images_upload_handler(blobInfo, success, failure, progress) {
  157. progress(0)
  158. // const token = _this.$store.getters.token
  159. const formData = new FormData()
  160. const image = blobInfo.blob()
  161. // formData.append('token', response.data.qiniu_token)
  162. formData.append('key', '1111')
  163. formData.append('image', image, image.name)
  164. console.log(blobInfo.blob())
  165. uploadImg(formData).then(response => {
  166. success(response.data.url)
  167. // const url = response.data.qiniu_url
  168. // upload(formData).then(() => {
  169. // success(url)
  170. // progress(100)
  171. // })
  172. }).catch(err => {
  173. failure('出现未知问题,刷新页面,或者联系程序员')
  174. console.log(err)
  175. })
  176. },
  177. // it will try to keep these URLs intact
  178. // https://www.tiny.cloud/docs-3x/reference/configuration/Configuration3x@convert_urls/
  179. // https://stackoverflow.com/questions/5196205/disable-tinymce-absolute-to-relative-url-conversions
  180. convert_urls: false
  181. // 整合七牛上传
  182. // images_dataimg_filter(img) {
  183. // setTimeout(() => {
  184. // const $image = $(img);
  185. // $image.removeAttr('width');
  186. // $image.removeAttr('height');
  187. // if ($image[0].height && $image[0].width) {
  188. // $image.attr('data-wscntype', 'image');
  189. // $image.attr('data-wscnh', $image[0].height);
  190. // $image.attr('data-wscnw', $image[0].width);
  191. // $image.addClass('wscnph');
  192. // }
  193. // }, 0);
  194. // return img
  195. // },
  196. // images_upload_handler(blobInfo, success, failure, progress) {
  197. // progress(0);
  198. // const token = _this.$store.getters.token;
  199. // getToken(token).then(response => {
  200. // const url = response.data.qiniu_url;
  201. // const formData = new FormData();
  202. // formData.append('token', response.data.qiniu_token);
  203. // formData.append('key', response.data.qiniu_key);
  204. // formData.append('file', blobInfo.blob(), url);
  205. // upload(formData).then(() => {
  206. // success(url);
  207. // progress(100);
  208. // })
  209. // }).catch(err => {
  210. // failure('出现未知问题,刷新页面,或者联系程序员')
  211. // console.log(err);
  212. // });
  213. // },
  214. })
  215. },
  216. destroyTinymce() {
  217. const tinymce = window.tinymce.get(this.tinymceId)
  218. if (this.fullscreen) {
  219. tinymce.execCommand('mceFullScreen')
  220. }
  221. if (tinymce) {
  222. tinymce.destroy()
  223. }
  224. },
  225. setContent(value) {
  226. window.tinymce.get(this.tinymceId).setContent(value)
  227. },
  228. getContent() {
  229. window.tinymce.get(this.tinymceId).getContent()
  230. },
  231. imageSuccessCBK(arr) {
  232. arr.forEach(v => window.tinymce.get(this.tinymceId).insertContent(`<img class="wscnph" src="${v.url}" >`))
  233. }
  234. }
  235. }
  236. </script>
  237. <style lang="scss" scoped>
  238. .tinymce-container {
  239. position: relative;
  240. line-height: normal;
  241. }
  242. .tinymce-container {
  243. ::v-deep {
  244. .mce-fullscreen {
  245. z-index: 10000;
  246. }
  247. }
  248. }
  249. .tinymce-textarea {
  250. visibility: hidden;
  251. z-index: -1;
  252. }
  253. .editor-custom-btn-container {
  254. position: absolute;
  255. right: 4px;
  256. top: 4px;
  257. /*z-index: 2005;*/
  258. }
  259. .fullscreen .editor-custom-btn-container {
  260. z-index: 10000;
  261. position: fixed;
  262. }
  263. .editor-upload-btn {
  264. display: inline-block;
  265. }
  266. </style>