tinymceEditor.vue 15 KB

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