tinymceSimple.vue 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362
  1. <template>
  2. <div class="tinymce-box">
  3. <client-only>
  4. <editor id="tinymce" class="doc-cnt" v-model="contents" :init="init"></editor>
  5. </client-only>
  6. <div class="editor-custom-btn-container">
  7. <editorImage color="#2973fd" ref="editorImage" class="editor-upload-btn" @successCBK="imageSuccessCBK"/>
  8. </div>
  9. </div>
  10. </template>
  11. <script>
  12. import editorImage from './editorImage'
  13. import editor from '@tinymce/tinymce-vue';
  14. import plugins from "./plugins"; // plugins
  15. let tinymce;
  16. // 在客户端环境下引入
  17. if (process.client) {
  18. tinymce = require('tinymce/tinymce');
  19. require('tinymce/themes/silver');
  20. require('tinymce/icons/default');
  21. require('./formatpainter'); //格式刷
  22. require('tinymce/plugins/image');// 插入上传图片插件
  23. require('tinymce/plugins/imagetools');// 插入上传图片插件
  24. require('tinymce/plugins/media');// 插入视频插件
  25. require('tinymce/plugins/table');// 插入表格插件
  26. require('tinymce/plugins/lists');// 列表插件
  27. require('tinymce/plugins/link');//超链接插件
  28. require('tinymce/plugins/wordcount');// 字数统计插件
  29. require('tinymce/plugins/emoticons');// 插入表情插件
  30. require('tinymce/plugins/fullscreen');
  31. require('tinymce/plugins/code');
  32. require('tinymce/plugins/paste');
  33. require('tinymce/plugins/advlist');
  34. require('tinymce/plugins/autolink');
  35. require('tinymce/plugins/autosave');
  36. require('tinymce/plugins/codesample');
  37. require('tinymce/plugins/colorpicker');
  38. require('tinymce/plugins/contextmenu');
  39. require('tinymce/plugins/directionality');
  40. require('tinymce/plugins/nonbreaking');
  41. require('tinymce/plugins/insertdatetime');
  42. require('tinymce/plugins/preview');
  43. require('tinymce/plugins/charmap');
  44. require('tinymce/plugins/save');
  45. require('tinymce/plugins/searchreplace');
  46. require('tinymce/plugins/spellchecker');
  47. require('tinymce/plugins/tabfocus');
  48. require('tinymce/plugins/table');
  49. require('tinymce/plugins/template');
  50. require('tinymce/plugins/textcolor');
  51. require('tinymce/plugins/visualblocks');
  52. require('tinymce/plugins/textpattern');
  53. require('tinymce/plugins/visualchars');
  54. require('tinymce/plugins/wordcount');
  55. require('tinymce/plugins/anchor');
  56. require('tinymce/plugins/hr');
  57. require('tinymce/plugins/link');
  58. // require('tinymce/plugins/toc'); //标题大纲
  59. require('tinymce/plugins/noneditable');
  60. // require('tinymce/plugins/pagebreak'); //分页符
  61. }
  62. export default {
  63. name: "tinymce-editor",
  64. components:{editor, editorImage},
  65. // 接收引用此组件的值
  66. props: ['modifyContent'],
  67. data() {
  68. return {
  69. uploaded:false,//有没有上传完成
  70. //初始化配置
  71. init: {
  72. placeholder: "请输入文本",
  73. language_url: '/plugins/shop_server/static/tinymce/langs/zh_CN.js',// 语言包的路径
  74. language: 'zh_CN',//语言
  75. emoticons_database_url: '/plugins/shop_server/static/tinymce/emoticons/js/emojis.js',
  76. skin_url: '/plugins/shop_server/static/tinymce/skins/ui/oxide',// skin路径
  77. min_height: 400,//编辑器高度
  78. width: 1125,
  79. branding: false,//是否禁用“Powered by TinyMCE”
  80. elementpath: false, //隐藏底栏的元素路径
  81. menubar: false,//顶部菜单栏显示
  82. object_resizing: false,// 是否禁用表格图片大小调整
  83. autosave_ask_before_unload:false, // 去除关闭/刷新网页时弹出对话框
  84. plugins: plugins,
  85. toolbar: ['code undo redo restoredraft | formatselect fontsizeselect | forecolor backcolor formatting alignment formatpainter lineheight link emoticons | searchreplace wordcount |'],
  86. toolbar_groups: {
  87. formatting: {
  88. text: '文字格式',
  89. tooltip: 'Formatting',
  90. items: 'bold italic underline strikethrough | superscript subscript | removeformat',
  91. },
  92. alignment: {
  93. icon: 'align-left',
  94. tooltip: 'alignment',
  95. items: 'alignleft aligncenter alignright alignjustify | outdent indent ',
  96. },
  97. insertion: {
  98. text: "插入",
  99. tooltip: 'insertion',
  100. items: 'codesample table insertdatetime anchor | charmap hr',
  101. },
  102. },
  103. body_class: "panel-body",
  104. end_container_on_empty_block: true,
  105. powerpaste_word_import: "clean",
  106. code_dialog_height: 450,
  107. code_dialog_width: 1000,
  108. contextmenu: "link copy ", //右键菜单
  109. advlist_bullet_styles: "square",
  110. advlist_number_styles: "default",
  111. imagetools_cors_hosts: ["www.tinymce.com", "codepen.io"],
  112. // 自定义字号
  113. fontsize_formats: '12px 14px 16px 18px 20px 22px 24px 28px 36px 48px 56px 72px',
  114. // 自定义字体集
  115. theme_advanced_fonts:
  116. "宋体=宋体;微软雅黑=微软雅黑;新宋体=新宋体;Courier New=courier new,courier,monospace;AkrutiKndPadmini=Akpdmi-n",
  117. paste_data_images: false, // 允许粘贴图像
  118. // 自定义字体
  119. font_formats: `
  120. 微软雅黑=微软雅黑;
  121. 宋体=宋体;
  122. 黑体=黑体;
  123. 仿宋=仿宋;
  124. 楷体=楷体;
  125. 隶书=隶书;
  126. 幼圆=幼圆;
  127. 思源黑体CN=思源黑体CN;
  128. Andale Mono=andale mono,times;
  129. Arial=arial, helvetica,
  130. sans-serif;
  131. Arial Black=arial black, avant garde;
  132. Book Antiqua=book antiqua,palatino;
  133. Comic Sans MS=comic sans ms,sans-serif;
  134. Courier New=courier new,courier;
  135. Georgia=georgia,palatino;
  136. Helvetica=helvetica;
  137. Impact=impact,chicago;
  138. Symbol=symbol;
  139. Tahoma=tahoma,arial,helvetica,sans-serif;
  140. Terminal=terminal,monaco;
  141. Times New Roman=times new roman,times;
  142. Trebuchet MS=trebuchet ms,geneva;
  143. Verdana=verdana,geneva;
  144. Webdings=webdings;
  145. Wingdings=wingdings,zapf dingbats`,
  146. content_style: `
  147. * { padding:0; margin:0; font-family: "Noto Sans SC";}
  148. html, body { height:100%; }
  149. img { max-width:100%; display:inline-block;height:auto; }
  150. a { text-decoration: none; }
  151. iframe { width: 100%; }
  152. p { line-height:1.6; margin: 0px; font-size: 16px; font-family:微软雅黑}
  153. table { word-wrap:break-word; word-break:break-all; max-width:100%; border:none; border-color:#999; }
  154. .mce-object-iframe { width:100%; box-sizing:border-box; margin:0; padding:0; }
  155. body, td, pre {
  156. font-family: Verdana, Arial, Helvetica, sans-serif;
  157. font-size: 12px;
  158. }
  159. `,
  160. block_formats: 'Paragraph=p;Header 1=h1;Header 2=h2;Header 3=h3;Heading 4=h4;',
  161. formats:{
  162. p:{block:'p',attributes: { 'class': `micro_release_content` }},
  163. },
  164. default_link_target: "_blank",
  165. link_title: false,
  166. convert_urls: false, // 图片上传路径为绝对路径
  167. remove_script_host: false,
  168. paste_word_valid_elements: "*[*]",
  169. paste_convert_word_fake_lists: false,
  170. paste_webkit_styles: "all",
  171. paste_merge_formats: true,
  172. nonbreaking_force_tab: false,
  173. paste_auto_cleanup_on_paste: false,
  174. forced_root_block: "p",
  175. forced_root_block_attrs: {
  176. 'class': 'micro-panel-body',
  177. },
  178. force_p_newlines: true,
  179. importcss_append: true,
  180. tabfocus_elements: ":prev,:next",
  181. // 本地图片上传配置
  182. images_upload_handler: (blobInfo, success, failure) => {
  183. // 上传图片拿到url
  184. let fd = new FormData();
  185. fd.append('file', blobInfo.blob(), blobInfo.filename());
  186. this.uploadImg(fd, 'img').then((resVideo)=> {
  187. if(resVideo) {
  188. success(resVideo)
  189. }else {
  190. failure("上传失败")
  191. }
  192. });
  193. },
  194. // 本地媒体上传配置
  195. file_picker_types: 'media',
  196. file_picker_callback: (callback, value, meta)=> {
  197. if(meta.filetype == 'media'){
  198. let input = document.createElement('input');//创建一个隐藏的input
  199. input.setAttribute('type', 'file');
  200. input.setAttribute('accept', '.mp4,.mov,.avi');
  201. let that = this;
  202. input.onchange = function(){
  203. let file = this.files[0];//选取第一个文件
  204. that.uploadImg(file, 'video').then((resVideo)=> {
  205. callback(resVideo, { title: file.name,width:'100%',height:'100%' }) //将url显示在弹框输入框中
  206. }); // 上传视频拿到url
  207. };
  208. //触发点击
  209. input.click();
  210. }
  211. },
  212. save_onsavecallback: ()=> {
  213. // 触发保存事件
  214. this.$emit('save')
  215. },
  216. setup: (editor) => {
  217. const _this = this;
  218. // 定义按钮,
  219. editor.ui.registry.addButton('outline', {
  220. // text: '大纲',
  221. icon: "toc",
  222. tooltip: '大纲',
  223. onAction: () => {
  224. this.showOutline(editor)
  225. }
  226. });
  227. editor.ui.registry.addButton('manyimg', {
  228. icon: "gallery",
  229. tooltip: '多图',
  230. onAction: () => {
  231. this.opUpload()
  232. }
  233. });
  234. editor.ui.registry.addButton('fileupload', {
  235. icon: "non-breaking",
  236. tooltip: '上传附件',
  237. onAction: () => {
  238. this.fileUpload()
  239. }
  240. });
  241. // 监听编辑器失焦事件
  242. // editor.on('blur', function () {
  243. // _this.$emit('saveDraft')
  244. // });
  245. // editor.on('keydown', function (e) {
  246. // if (e.keyCode == 83 && (navigator.platform.match("Mac") ? e.metaKey : e.ctrlKey)){
  247. // _this.$emit('save')
  248. // }
  249. // });
  250. },
  251. },
  252. // 绑定的内容
  253. contents: this.modifyContent
  254. }
  255. },
  256. mounted(){ // 用process.client判断一下环境再执行
  257. this.$nextTick(()=>{
  258. if (process.client) {
  259. window.tinymce.init({});
  260. }
  261. })
  262. },
  263. watch: {
  264. modifyContent(newValue) {
  265. this.contents = newValue
  266. },
  267. contents(newValue) {
  268. this.$emit('writeContent', newValue);
  269. },
  270. },
  271. methods: {
  272. opUpload() {
  273. this.$refs.editorImage.opUpload('img')
  274. },
  275. fileUpload() {
  276. this.$refs.editorImage.opUpload('file')
  277. },
  278. showOutline(editor) {
  279. // editor.insertContent('&nbsp;<b>It\'s my button!</b>&nbsp;')
  280. this.$emit('showOutline')
  281. },
  282. uploadImg(file,type) {
  283. return new Promise((resolve, reject) => {
  284. let fd = new FormData();
  285. let loading = "loading"
  286. if(type === 'img') {
  287. fd = file;
  288. loading = "";
  289. }else if(type ==='video') {
  290. fd.append("upload_type", "videos");
  291. fd.append("file", file);
  292. }
  293. this.fun.$post(this.fun.getRealUrl("upload.uploadPic"), fd, loading,'', {
  294. headers: { "Content-Type": "multipart/form-data" }
  295. }).then((res) => {
  296. if(res.result == 1) {
  297. resolve(res.data.img_url)
  298. }else {
  299. reject('');
  300. console.log("上传出错")
  301. }
  302. }).catch(() => {
  303. reject('');
  304. console.log("上传出错")
  305. });
  306. })
  307. },
  308. imageSuccessCBK(obj) {
  309. console.log(obj);
  310. if(obj.uploadType === 'img') {
  311. obj.arr.forEach(v => {
  312. window.tinymce.get("tinymce").insertContent(`<img class="wscnph" style="display: inline-block;" src="${v.url}" />`)
  313. })
  314. }else if(obj.uploadType === 'file'){
  315. obj.arr.forEach(v => {
  316. v.url = v.url.replace(/http:/gi, "https:");
  317. window.tinymce.get("tinymce").insertContent(`<a target="_blank" class="pc-file" download="${v.name}" href="${v.url}"><i class="iconfont icon-all_link"> </i>${v.name}</a>`)
  318. })
  319. }else {
  320. obj.arr.forEach(v => {
  321. window.tinymce.get("tinymce").insertContent(`<video class="pc-video" src="${v.url}" ></video>`)
  322. })
  323. }
  324. }
  325. }
  326. }
  327. </script>
  328. <style lang="scss" rel="stylesheet/scss" scoped>
  329. .tinymce-box {
  330. line-height: normal;
  331. padding: 0 10px;
  332. }
  333. .tinymce-box:deep(.tox-fullscreen) {
  334. z-index: 10000;
  335. }
  336. .doc-cnt{min-height:400px;overflow:auto;font-size:16px;padding:0.5em;}
  337. .doc-cnt:focus{outline:0;}
  338. .doc-cnt p{text-indent:0;}
  339. </style>
  340. <style>
  341. .tox .tox-dialog__body-nav .tox-dialog__body-nav-item:nth-child(3) {
  342. display: none!important;
  343. }
  344. .tox-tinymce {
  345. border: none!important;
  346. }
  347. .tox-tinymce .tox-editor {
  348. border: none;
  349. }
  350. .tox-notifications-container {
  351. display: none;
  352. }
  353. .tox .tox-statusbar {
  354. border: none!important;
  355. }
  356. </style>