RichText.vue 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196
  1. <template>
  2. <Editor
  3. v-bind="$attrs"
  4. :id="id"
  5. ref="richText"
  6. :value="value"
  7. :class="['rich-text', isBorder ? 'is-border' : '']"
  8. :init="init"
  9. @input="updateValue"
  10. v-on="$listeners"
  11. />
  12. </template>
  13. <script>
  14. import 'tinymce/tinymce';
  15. import Editor from '@tinymce/tinymce-vue';
  16. import 'tinymce/icons/default/icons';
  17. import 'tinymce/themes/silver';
  18. // 引入富文本编辑器主题的js和css
  19. import 'tinymce/themes/silver/theme.min';
  20. import 'tinymce/skins/ui/oxide/skin.min.css';
  21. // 扩展插件
  22. import 'tinymce/plugins/image';
  23. import 'tinymce/plugins/link';
  24. // import 'tinymce/plugins/code';
  25. // import 'tinymce/plugins/table';
  26. import 'tinymce/plugins/lists';
  27. // import 'tinymce/plugins/wordcount'; // 字数统计插件
  28. import 'tinymce/plugins/media'; // 插入视频插件
  29. // import 'tinymce/plugins/template'; // 模板插件
  30. // import 'tinymce/plugins/fullscreen'; // 全屏插件
  31. // import 'tinymce/plugins/paste';
  32. // import 'tinymce/plugins/preview'; // 预览插件
  33. import 'tinymce/plugins/hr';
  34. import 'tinymce/plugins/autoresize'; // 自动调整大小插件
  35. import 'tinymce/plugins/ax_wordlimit'; // 字数限制插件
  36. import { getRandomNumber } from '@/utils';
  37. import { fileUpload } from '@/api/app';
  38. export default {
  39. name: 'RichText',
  40. components: {
  41. Editor,
  42. },
  43. inheritAttrs: false,
  44. props: {
  45. inline: {
  46. type: Boolean,
  47. default: false,
  48. },
  49. placeholder: {
  50. type: String,
  51. default: '输入内容',
  52. },
  53. value: {
  54. type: String,
  55. required: true,
  56. },
  57. height: {
  58. type: [Number, String],
  59. default: 110,
  60. },
  61. isBorder: {
  62. type: Boolean,
  63. default: false,
  64. },
  65. wordLimit: {
  66. type: Number,
  67. default: 500,
  68. },
  69. },
  70. data() {
  71. return {
  72. id: getRandomNumber(),
  73. init: {
  74. inline: this.inline,
  75. language_url: `${process.env.BASE_URL}tinymce/langs/zh_CN.js`,
  76. placeholder: this.placeholder,
  77. language: 'zh_CN',
  78. skin_url: `${process.env.BASE_URL}tinymce/skins/ui/oxide`,
  79. // height: this.height,
  80. content_css: `${process.env.BASE_URL}tinymce/skins/content/index.css`,
  81. min_height: 52,
  82. width: '100%',
  83. autoresize_bottom_margin: 0,
  84. plugins: 'link lists image hr media autoresize ax_wordlimit',
  85. /* eslint-disable max-len */
  86. toolbar:
  87. 'fontselect fontsizeselect forecolor backcolor | underline | bold italic strikethrough alignleft aligncenter alignright | bullist numlist | image media | link blockquote hr',
  88. menubar: false,
  89. branding: false,
  90. statusbar: false,
  91. // 字数限制
  92. ax_wordlimit_num: this.wordLimit,
  93. ax_wordlimit_callback(editor) {
  94. editor.execCommand('undo');
  95. },
  96. /**
  97. * 图片上传自定义逻辑函数
  98. * @param {object} blobInfo 文件数据
  99. * @param {Function} success 成功回调函数
  100. * @param {Function} fail 失败回调函数
  101. */
  102. images_upload_handler(blobInfo, success, fail) {
  103. let file = blobInfo.blob();
  104. const formData = new FormData();
  105. formData.append(file.name, file, file.name);
  106. fileUpload('Mid', formData)
  107. .then(({ file_info_list }) => {
  108. if (file_info_list.length > 0) {
  109. success(file_info_list[0].file_url);
  110. } else {
  111. fail('上传失败');
  112. }
  113. })
  114. .catch(() => {
  115. fail('上传失败');
  116. });
  117. },
  118. file_picker_types: 'media', // 文件上传类型
  119. /**
  120. * 文件上传自定义逻辑函数
  121. * @param {Function} callback
  122. * @param {String} value
  123. * @param {object} meta
  124. */
  125. file_picker_callback(callback, value, meta) {
  126. if (meta.filetype === 'media') {
  127. let filetype = '.mp3, .mp4';
  128. let input = document.createElement('input');
  129. input.setAttribute('type', 'file');
  130. input.setAttribute('accept', filetype);
  131. input.click();
  132. input.addEventListener('change', () => {
  133. let file = input.files[0];
  134. const formData = new FormData();
  135. formData.append(file.name, file, file.name);
  136. fileUpload('Mid', formData)
  137. .then(({ file_info_list }) => {
  138. if (file_info_list.length > 0) {
  139. callback(file_info_list[0].file_url);
  140. } else {
  141. callback('');
  142. }
  143. })
  144. .catch(() => {
  145. callback('');
  146. });
  147. });
  148. }
  149. },
  150. },
  151. };
  152. },
  153. methods: {
  154. updateValue(data) {
  155. this.$emit('update:value', data);
  156. },
  157. },
  158. };
  159. </script>
  160. <style lang="scss" scoped>
  161. .rich-text {
  162. :deep + .tox {
  163. .tox-sidebar-wrap {
  164. border: 1px solid $fill-color;
  165. border-radius: 4px;
  166. &:hover {
  167. border-color: #c0c4cc;
  168. }
  169. }
  170. &.tox-tinymce {
  171. border-width: 0;
  172. border-radius: 0;
  173. .tox-edit-area__iframe {
  174. background-color: $fill-color;
  175. }
  176. }
  177. &:not(.tox-tinymce-inline) .tox-editor-header {
  178. box-shadow: none;
  179. }
  180. }
  181. :deep &.is-border + .tox.tox-tinymce {
  182. border-width: 2px;
  183. border-radius: 10px;
  184. }
  185. }
  186. </style>