Vue项目中tinymce富文本的安装以及配置
对于目前网上存在的许多富文本插件中,个人还是觉得tinymce相对比较强大一些。在使用配置的过程中,可能会出现配置不完全,导致使用不了的情况,下面把我个人使用的经验分享给大家,希望对你们有所帮助。
TinyMCE兼容性
PC浏览器兼容性
TinyMCE使用JS封装行为来规范各个浏览器之间的渲染行为,以确保统一的用户体验。我们主要关注的浏览器包括Chrome、Firefox、IE和Safari。
未列出的浏览器可能意味着TinyMCE未在该浏览器上进行过全面测试,可以试试首页的示例DEMO,用以确定是否兼容。(国产浏览器大都用的是Chrome的核,兼容完全没问题)
浏览器 | Windows | Mac | GNU/Linux |
---|---|---|---|
Chrome | Yes | Yes | Yes |
Firefox | Yes | Yes | Yes |
Safari | - | Yes | - |
Edge | Yes | - | - |
IE 11 | Yes | - | - |
IE 8-10 | No | - | - |
因为TinyMCE v5用到了很多现代浏览器才支持的标准,所以IE10及以下全部阵亡。TinyMCE作为一个商业性的开源编辑器,更新频率是可以保证的,所以完全支持以上浏览器的最新版本。
移动端浏览器兼容性
系统 | 浏览器 | 设备 |
---|---|---|
Android 6,7,8 | Chrome | 手机 |
iOS 10,11 | Safari | iPhone/iPad |
一、安装环境
安装tinymce插件命令
vue-cli版本:3.x+
安装tinymce
npm install [email protected] -S
安装tinymce-vue
npm install @tinymce/[email protected] -S
我这边用的版本是:
tinymce-vue:3.2.8
tinymce:5.10.3
注:vue2中不能使用@tinymce/tinymce-vue为4以上的版本
vue-cli版本:2.x的安装
安装tinymce
npm install tinymce -S
安装tinymce-vue
npm install @tinymce/[email protected] -S
二、复制静态文件到public目录
先在public新建tinymce目录,然后到node_modules目录下找到一个tinymce文件夹,复制到public的tinymce目录下;

tinymce 默认是英文界面,所以还需要下载一个中文语言包。
在public目录下新建langs文件夹,在里面新建zh_CN.js文件处理中文,(可以到官网下载中文语言包 zh_CN.js)代码如下
:
tinymce.addI18n('zh_CN',{
"Redo": "恢复",
"Undo": "撤销",
"Cut": "剪切",
"Copy": "复制",
"Paste": "粘贴",
"Select all": "全选",
"New document": "新建文档",
"Ok": "确定",
"Cancel": "取消",
"Visual aids": "网格线",
"Bold": "粗体",
"Italic": "斜体",
"Underline": "下划线",
"Strikethrough": "删除线",
"Superscript": "上标",
"Subscript": "下标",
"Clear formatting": "清除格式",
"Align left": "左对齐",
"Align center": "居中",
"Align right": "右对齐",
"Justify": "两端对齐",
"Bullet list": "符号列表",
"Numbered list": "数字列表",
"Decrease indent": "减少缩进",
"Increase indent": "增加缩进",
"Close": "关闭",
"Formats": "格式",
"Your browser doesn't support direct access to the clipboard. Please use the Ctrl+X\/C\/V keyboard shortcuts instead.": "当前浏览器不支持访问剪贴板,请使用快捷键Ctrl+X/C/V复制粘贴",
"Headers": "标题",
"Header 1": "标题1",
"Header 2": "标题2",
"Header 3": "标题3",
"Header 4": "标题4",
"Header 5": "标题5",
"Header 6": "标题6",
"Headings": "标题",
"Heading 1": "标题1",
"Heading 2": "标题2",
"Heading 3": "标题3",
"Heading 4": "标题4",
"Heading 5": "标题5",
"Heading 6": "标题6",
"Preformatted": "预格式化",
"Div": "Div区块",
"Pre": "预格式文本",
"Code": "代码",
"Paragraph": "段落",
"Blockquote": "引用",
"Inline": "文本",
"Blocks": "区块",
"Paste is now in plain text mode. Contents will now be pasted as plain text until you toggle this option off.": "当前为纯文本粘贴模式,再次点击可以回到普通粘贴模式。",
"Fonts": "字体",
"Font Sizes": "字号",
"Class": "Class",
"Browse for an image": "浏览图像",
"OR": "或",
"Drop an image here": "拖放一张图片文件至此",
"Upload": "上传",
"Block": "块",
"Align": "对齐",
"Default": "默认",
"Circle": "空心圆",
"Disc": "实心圆",
"Square": "方块",
"Lower Alpha": "小写英文字母",
"Lower Greek": "小写希腊字母",
"Lower Roman": "小写罗马字母",
"Upper Alpha": "大写英文字母",
"Upper Roman": "大写罗马字母",
"Anchor...": "锚点...",
"Name": "名称",
"Id": "id",
"Id should start with a letter, followed only by letters, numbers, dashes, dots, colons or underscores.": "id应该以字母开头,后跟字母、数字、横线、点、冒号或下划线。",
"You have unsaved changes are you sure you want to navigate away?": "你对文档的修改尚未保存,确定离开吗?",
"Restore last draft": "恢复上次的草稿",
"Special characters...": "特殊字符...",
"Source code": "HTML源码",
"Insert\/Edit code sample": "插入/编辑代码示例",
"Language": "语言",
"Code sample...": "代码示例...",
"Color Picker": "选取颜色",
"R": "R",
"G": "G",
"B": "B",
"Left to right": "从左到右",
"Right to left": "从右到左",
"Emoticons...": "表情符号...",
"Metadata and Document Properties": "元数据和文档属性",
"Title": "标题",
"Keywords": "关键词",
"Description": "描述",
"Robots": "机器人",
"Author": "作者",
"Encoding": "编码",
"Fullscreen": "全屏",
"Action": "操作",
"Shortcut": "快捷键",
"Help": "帮助",
"Address": "地址",
"Focus to menubar": "移动焦点到菜单栏",
"Focus to toolbar": "移动焦点到工具栏",
"Focus to element path": "移动焦点到元素路径",
"Focus to contextual toolbar": "移动焦点到上下文菜单",
"Insert link (if link plugin activated)": "插入链接 (如果链接插件已激活)",
"Save (if save plugin activated)": "保存(如果保存插件已激活)",
"Find (if searchreplace plugin activated)": "查找(如果查找替换插件已激活)",
"Plugins installed ({0}):": "已安装插件 ({0}):",
"Premium plugins:": "优秀插件:",
"Learn more...": "了解更多...",
"You are using {0}": "你正在使用 {0}",
"Plugins": "插件",
"Handy Shortcuts": "快捷键",
"Horizontal line": "水平分割线",
"Insert\/edit image": "插入/编辑图片",
"Image description": "图片描述",
"Source": "地址",
"Dimensions": "大小",
"Constrain proportions": "保持宽高比",
"General": "常规",
"Advanced": "高级",
"Style": "样式",
"Vertical space": "垂直边距",
"Horizontal space": "水平边距",
"Border": "边框",
"Insert image": "插入图片",
"Image...": "图片...",
"Image list": "图片列表",
"Rotate counterclockwise": "逆时针旋转",
"Rotate clockwise": "顺时针旋转",
"Flip vertically": "垂直翻转",
"Flip horizontally": "水平翻转",
"Edit image": "编辑图片",
"Image options": "图片选项",
"Zoom in": "放大",
"Zoom out": "缩小",
"Crop": "裁剪",
"Resize": "调整大小",
"Orientation": "方向",
"Brightness": "亮度",
"Sharpen": "锐化",
"Contrast": "对比度",
"Color levels": "色阶",
"Gamma": "伽马值",
"Invert": "反转",
"Apply": "应用",
"Back": "后退",
"Insert date\/time": "插入日期/时间",
"Date\/time": "日期/时间",
"Insert\/Edit Link": "插入/编辑链接",
"Insert\/edit link": "插入/编辑链接",
"Text to display": "显示文字",
"Url": "地址",
"Open link in...": "链接打开方式...",
"Current window": "当前窗口打开",
"None": "在当前窗口/框架打开",
"New window": "在新窗口打开",
"Remove link": "删除链接",
"Anchors": "锚点",
"Link...": "链接...",
"Paste or type a link": "粘贴或输入链接",
"The URL you entered seems to be an email address. Do you want to add the required mailto: prefix?": "你所填写的URL地址为邮件地址,需要加上mailto:前缀吗?",
"The URL you entered seems to be an external link. Do you want to add the required http:\/\/ prefix?": "你所填写的URL地址属于外部链接,需要加上http://:前缀吗?",
"Link list": "链接列表",
"Insert video": "插入视频",
"Insert\/edit video": "插入/编辑视频",
"Insert\/edit media": "插入/编辑媒体",
"Alternative source": "替代资源",
"Alternative image URL": "资源备用地址",
"Media poster (Image URL)": "封面(图片地址)",
"Paste your embed code below:": "将内嵌代码粘贴在下面:",
"Embed": "内嵌",
"Media...": "多媒体...",
"Nonbreaking space": "不间断空格",
"Page break": "分页符",
"Paste as text": "粘贴为文本",
"Preview": "预览",
"Print...": "打印...",
"Save": "保存",
"Find": "查找",
"Replace with": "替换为",
"Replace": "替换",
"Replace all": "替换全部",
"Previous": "上一个",
"Next": "下一个",
"Find and replace...": "查找并替换...",
"Could not find the specified string.": "未找到搜索内容。",
"Match case": "区分大小写",
"Find whole words only": "全单词匹配",
"Spell check": "拼写检查",
"Ignore": "忽略",
"Ignore all": "忽略全部",
"Finish": "完成",
"Add to Dictionary": "添加到字典",
"Insert table": "插入表格",
"Table properties": "表格属性",
"Delete table": "删除表格",
"Cell": "单元格",
"Row": "行",
"Column": "列",
"Cell properties": "单元格属性",
"Merge cells": "合并单元格",
"Split cell": "拆分单元格",
"Insert row before": "在上方插入",
"Insert row after": "在下方插入",
"Delete row": "删除行",
"Row properties": "行属性",
"Cut row": "剪切行",
"Copy row": "复制行",
"Paste row before": "粘贴到上方",
"Paste row after": "粘贴到下方",
"Insert column before": "在左侧插入",
"Insert column after": "在右侧插入",
"Delete column": "删除列",
"Cols": "列",
"Rows": "行",
"Width": "宽",
"Height": "高",
"Cell spacing": "单元格外间距",
"Cell padding": "单元格内边距",
"Show caption": "显示标题",
"Left": "左对齐",
"Center": "居中",
"Right": "右对齐",
"Cell type": "单元格类型",
"Scope": "范围",
"Alignment": "对齐方式",
"H Align": "水平对齐",
"V Align": "垂直对齐",
"Top": "顶部对齐",
"Middle": "垂直居中",
"Bottom": "底部对齐",
"Header cell": "表头单元格",
"Row group": "行组",
"Column group": "列组",
"Row type": "行类型",
"Header": "表头",
"Body": "表体",
"Footer": "表尾",
"Border color": "边框颜色",
"Insert template...": "插入模板...",
"Templates": "模板",
"Template": "模板",
"Text color": "文字颜色",
"Background color": "背景色",
"Custom...": "自定义...",
"Custom color": "自定义颜色",
"No color": "无",
"Remove color": "删除颜色",
"Table of Contents": "目录",
"Show blocks": "显示区块边框",
"Show invisible characters": "显示不可见字符",
"Word count": "字数统计",
"Words: {0}": "字数:{0}",
"{0} words": "{0} 个字",
"File": "文件",
"Edit": "编辑",
"Insert": "插入",
"View": "查看",
"Format": "格式",
"Table": "表格",
"Tools": "工具",
"Powered by {0}": "Powered by {0}",
"Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help": "在编辑区按ALT+F9打开菜单,按ALT+F10打开工具栏,按ALT+0查看帮助",
"Image title": "图片标题",
"Border width": "边框宽度",
"Border style": "边框样式",
"Error": "错误",
"Warn": "警告",
"Valid": "有效",
"To open the popup, press Shift+Enter": "此快捷为软回车(插入<br>)",
"Rich Text Area. Press ALT-0 for help.": "编辑区. 按Alt+0键打开帮助",
"System Font": "默认字体",
"Failed to upload image: {0}": "图片上传失败: {0}",
"Failed to load plugin: {0} from url {1}": "插件加载失败: {0} - {1}",
"Failed to load plugin url: {0}": "插件加载失败: {0}",
"Failed to initialize plugin: {0}": "插件初始化失败: {0}",
"example": "示例",
"Search": "查找",
"All": "全部",
"Currency": "货币",
"Text": "文本",
"Quotations": "引用",
"Mathematical": "数学运算符",
"Extended Latin": "拉丁语扩充",
"Symbols": "符号",
"Arrows": "箭头",
"User Defined": "自定义",
"dollar sign": "美元",
"currency sign": "货币",
"euro-currency sign": "欧元",
"colon sign": "冒号",
"cruzeiro sign": "克鲁赛罗币",
"french franc sign": "法郎",
"lira sign": "里拉",
"mill sign": "密尔",
"naira sign": "奈拉",
"peseta sign": "比塞塔",
"rupee sign": "卢比",
"won sign": "韩元",
"new sheqel sign": "新谢克尔",
"dong sign": "越南盾",
"kip sign": "老挝基普",
"tugrik sign": "图格里克",
"drachma sign": "德拉克马",
"german penny symbol": "德国便士",
"peso sign": "比索",
"guarani sign": "瓜拉尼",
"austral sign": "澳元",
"hryvnia sign": "格里夫尼亚",
"cedi sign": "塞地",
"livre tournois sign": "里弗弗尔",
"spesmilo sign": "一千spesoj的货币符号,该货币未使用",
"tenge sign": "坚戈",
"indian rupee sign": "印度卢比",
"turkish lira sign": "土耳其里拉",
"nordic mark sign": "北欧马克",
"manat sign": "马纳特",
"ruble sign": "卢布",
"yen character": "日元",
"yuan character": "人民币元",
"yuan character, in hong kong and taiwan": "元的繁体字",
"yen\/yuan character variant one": "元(大写)",
"Loading emoticons...": "正在加载表情文字...",
"Could not load emoticons": "不能加载表情文字",
"People": "人类",
"Animals and Nature": "动物和自然",
"Food and Drink": "食物和饮品",
"Activity": "活动",
"Travel and Places": "旅游和地点",
"Objects": "物件",
"Flags": "旗帜",
"Characters": "字数",
"Characters (no spaces)": "字数(不含空格)",
"Error: Form submit field collision.": "错误: 表单提交字段冲突.",
"Error: No form element found.": "错误: 未找到可用的form.",
"Update": "更新",
"Color swatch": "颜色样本",
"Turquoise": "青绿",
"Green": "绿色",
"Blue": "蓝色",
"Purple": "紫色",
"Navy Blue": "海军蓝",
"Dark Turquoise": "深蓝绿色",
"Dark Green": "暗绿",
"Medium Blue": "中蓝",
"Medium Purple": "中紫",
"Midnight Blue": "深蓝",
"Yellow": "黄色",
"Orange": "橙色",
"Red": "红色",
"Light Gray": "浅灰",
"Gray": "灰色",
"Dark Yellow": "暗黄",
"Dark Orange": "暗橙",
"Dark Red": "暗红",
"Medium Gray": "中灰",
"Dark Gray": "深灰",
"Black": "黑色",
"White": "白色",
"Switch to or from fullscreen mode": "切换全屏模式",
"Open help dialog": "打开帮助对话框",
"history": "历史",
"styles": "样式",
"formatting": "格式化",
"alignment": "对齐",
"indentation": "缩进",
"permanent pen": "记号笔",
"comments": "注释",
"Anchor": "锚点",
"Special character": "特殊字符",
"Code sample": "代码示例",
"Color": "颜色",
"Emoticons": "表情",
"Document properties": "文档属性",
"Image": "图片",
"Insert link": "插入链接",
"Target": "目标",
"Link": "链接",
"Poster": "封面",
"Media": "音视频",
"Print": "打印",
"Prev": "上一个",
"Find and replace": "查找并替换",
"Whole words": "全字匹配",
"Spellcheck": "拼写检查",
"Caption": "标题",
"Insert template": "插入模板",
//以下为补充汉化内容 by 莫若卿
"Code view": "代码区域",
"Select...": "选择...",
"Format Painter": "格式刷",
"No templates defined.": "无内置模板",
"Special character...": "特殊字符...",
"Open link": "打开链接",
"None": "无",
"Count": "统计",
"Document": "整个文档",
"Selection": "选取部分",
"Words": "字词数",
"{0} characters": "{0} 个字符",
"Alternative source URL": "替代资源地址",
"Alternative description": "替代说明文字",
"Accessibility": "可访问性",
"Image is decorative": "仅用于装饰",
//5.6新增
"Line height": "行高",
"Cut column": "剪切列",
"Copy column": "复制列",
"Paste column before": "粘贴到前方",
"Paste column after": "粘贴到后方",
"Copy column": "复制列",
//帮助窗口内的文字
"Version": "版本",
"Keyboard Navigation": "键盘导航",
"Open popup menu for split buttons": "该组合键的作用是软回车(插入br)",
});
找到public目录下index.html文件,在body中添加
<script src="/tinymce/tinymce.min.js"></script>
三、页面使用
在页面的使用有两种方式
方式1:纯净版组件:
<template>
<!-- 富文本 -->
<div>
<editor v-model="content" :init="init" :disabled="disabled"></editor>
</div>
</template>
<script>
import tinymce from "tinymce/tinymce";
import Editor from "@tinymce/tinymce-vue";
import "tinymce/icons/default/icons";
import "tinymce/themes/silver";
import "tinymce/plugins/image";
import "tinymce/plugins/media";
import "tinymce/plugins/table";
import "tinymce/plugins/lists";
import "tinymce/plugins/contextmenu";
import "tinymce/plugins/wordcount";
import "tinymce/plugins/colorpicker";
import "tinymce/plugins/textcolor";
import "tinymce/plugins/preview";
import "tinymce/plugins/code";
import "tinymce/plugins/link";
import "tinymce/plugins/advlist";
import "tinymce/plugins/codesample";
import "tinymce/plugins/hr";
import "tinymce/plugins/fullscreen";
import "tinymce/plugins/textpattern";
import "tinymce/plugins/searchreplace";
import "tinymce/plugins/autolink";
import "tinymce/plugins/directionality";
import "tinymce/plugins/visualblocks";
import "tinymce/plugins/visualchars";
import "tinymce/plugins/template";
import "tinymce/plugins/charmap";
import "tinymce/plugins/nonbreaking";
import "tinymce/plugins/insertdatetime";
import "tinymce/plugins/imagetools";
import "tinymce/plugins/autosave";
import "tinymce/plugins/autoresize";
export default {
components: {
Editor
},
props: {
value: {
type: String,
default: ""
},
disabled: {
type: Boolean,
default: false
},
plugins: {
type: [String, Array],
default:
"preview searchreplace autolink directionality visualblocks visualchars fullscreen image link media template code codesample table charmap hr nonbreaking insertdatetime advlist lists wordcount imagetools textpattern autosave autoresize"
},
toolbar: {
type: [String, Array],
default:
"code undo redo restoredraft | cut copy paste pastetext | forecolor backcolor bold italic underline strikethrough link codesample | alignleft aligncenter alignright alignjustify outdent indent formatpainter | \
styleselect formatselect fontselect fontsizeselect | bullist numlist | blockquote subscript superscript removeformat | \
table image media charmap hr pagebreak insertdatetime | fullscreen preview"
}
},
data() {
return {
//初始化配置
init: {
//menubar: true, // 菜单栏显隐
language_url: "/static/tinymce/langs/zh_CN.js",
//language_url: '../../static/tinymce/langs/zh_CN.js', // vue-cli2.x
language: "zh_CN",
skin_url: "/static/tinymce/skins/ui/oxide",
//skin_url: '../../static/tinymce/skins/ui/oxide', // vue-cli2.x
//content_css: '../../static/tinymce/skins/content/default/content.css',// vue-cli2.x
height: 770,
min_height: 770,
max_height: 770,
toolbar_mode: "wrap",
plugins: this.plugins,
toolbar: this.toolbar,
content_style: "p {margin: 5px 0;}",
fontsize_formats: "12px 14px 16px 18px 24px 36px 48px 56px 72px",
font_formats:
"微软雅黑=Microsoft YaHei,Helvetica Neue,PingFang SC,sans-serif;苹果苹方=PingFang SC,Microsoft YaHei,sans-serif;宋体=simsun,serif;仿宋体=FangSong,serif;黑体=SimHei,sans-serif;Arial=arial,helvetica,sans-serif;Arial Black=arial black,avant garde;Book Antiqua=book antiqua,palatino;",
branding: false,
// 图片上传
images_upload_handler: (blobInfo, success, failure) => {
// const img = 'data:image/jpeg;base64,' + blobInfo.base64()
// success(img)
const formData = new FormData()
formData.append('file', blobInfo.blob())
reserveTableFoodDescribe(formData).then(res => {
if (res.code === '10000') {
const file = res.data
success(file.url)
return
}
failure('上传失败')
}).catch(() => {
failure('上传出错')
})
}
},
content: this.value
};
},
mounted() {
tinymce.init({});
},
methods: {
},
watch: {
value(newValue) {
this.content = newValue;
},
content(newValue) {
this.$emit("input", newValue);
}
}
};
</script>
组件使用
import Editor from "@/components/imcoder-tinymce";
components: { Editor },
<editor v-model="yourContent"></editor>
方式2:二次封装版:
直接新建一个PEditor组件,代码如下:
处理了tab切换的缓存问题(适用于jeecg-boot项目)
<template>
<div class="tinymce-editor">
<editor v-if="!reloading" v-model="myValue" :init="init" :disabled="disabled" @onClick="onClick"></editor>
</div>
</template>
<script>
import tinymce from 'tinymce/tinymce'
import Editor from '@tinymce/tinymce-vue'
import 'tinymce/themes/silver/theme'
import 'tinymce/plugins/print'
import 'tinymce/plugins/preview'
import 'tinymce/plugins/searchreplace'
import 'tinymce/plugins/autolink'
import 'tinymce/plugins/directionality'
import 'tinymce/plugins/visualblocks'
import 'tinymce/plugins/visualchars'
import 'tinymce/plugins/fullscreen'
import 'tinymce/plugins/template'
import 'tinymce/plugins/codesample'
import 'tinymce/plugins/charmap'
import 'tinymce/plugins/autoresize'
import 'tinymce/plugins/hr'
import 'tinymce/plugins/pagebreak'
import 'tinymce/plugins/nonbreaking'
import 'tinymce/plugins/anchor'
import 'tinymce/plugins/insertdatetime'
import 'tinymce/plugins/advlist'
import 'tinymce/plugins/imagetools'
import 'tinymce/plugins/textpattern'
import 'tinymce/plugins/help'
import 'tinymce/plugins/autosave'
import 'tinymce/plugins/code'
import '../../../public/tinymce/plugins/searchreplace/plugin.min'
import '../../../public/tinymce/plugins/layout/plugin.min'
import '../../../public/tinymce/plugins/letterspacing/plugin.min'
import '../../../public/tinymce/plugins/indent2em/plugin.min'
import '../../../public/tinymce/plugins/formatpainter/plugin.min'
import 'tinymce/plugins/image'
import 'tinymce/plugins/link'
import 'tinymce/plugins/media'
import 'tinymce/plugins/table'
import 'tinymce/plugins/lists'
import 'tinymce/plugins/contextmenu'
import 'tinymce/plugins/wordcount'
import 'tinymce/plugins/colorpicker'
import 'tinymce/plugins/textcolor'
import 'tinymce/plugins/fullscreen'
import 'tinymce/icons/default'
import { uploadAction, getFileAccessHttpUrl } from '@/api/manage'
import { getVmParentByName } from '@/utils/util'
export default {
components: {
Editor
},
props: {
value: {
type: String,
required: false
},
triggerChange: {
type: Boolean,
default: false,
required: false
},
disabled: {
type: Boolean,
default: false
},
plugins: {
type: [String, Array],
default:
'print preview searchreplace autolink directionality visualblocks visualchars fullscreen image link media template codesample table charmap hr pagebreak nonbreaking anchor insertdatetime advlist lists wordcount imagetools textpattern help autosave indent2em autoresize formatpainter letterspacing layout searchreplace code'
},
toolbar: {
type: [String, Array],
default:
'code undo redo restoredraft | cut copy paste pastetext | forecolor backcolor| bold italic underline strikethrough | link anchor | alignleft aligncenter alignright alignjustify | outdent indent |indent2em lineheight letterspacing|' +
' formatselect | fontselect | fontsizeselect | bullist numlist | blockquote subscript superscript|' +
' table |image media |hr pagebreak | charmap |insertdatetime |print |preview fullscreen code|layout|searchreplace| formatpainter removeformat',
branding: false
},
maxHeight: {
type: Number,
default:0,
required: false
}
},
data() {
return {
//初始化配置
init: {
language_url: '/tinymce/langs/zh_CN.js',
language: 'zh_CN',
skin_url: '/tinymce/skins/lightgray',
height: 300,
plugins: this.plugins,
toolbar: this.toolbar,
max_height: this.maxHeight,
branding: false,
menubar: false,
toolbar_drawer: false,
lineheight_formats: '1 1.1 1.2 1.3 1.4 1.5 2 28pt 30pt 31pt 32pt',
fontsize_formats: '12px 14px 16px 18px 20px 21px 24px 36px 48px 56px 72px',
font_formats:
'仿宋_GB2312=仿宋_GB2312;楷体_GB2312=楷体_GB2312;黑体=SimHei;隶书=隶书;幼圆=幼圆;仿宋体=FangSong;宋体=simsun,serif;微软雅黑=Microsoft YaHei,Helvetica Neue,PingFang SC;苹果苹方=PingFang SC,Microsoft YaHei;',
images_upload_handler: (blobInfo, success) => {
let formData = new FormData()
formData.append('file', blobInfo.blob(), blobInfo.filename())
formData.append('biz', 'jeditor')
formData.append('jeditor', '1')
uploadAction(window._CONFIG['domianURL'] + '/sys/common/upload', formData).then((res) => {
if (res.success) {
if (res.message == 'local') {
const img = 'data:image/jpeg;base64,' + blobInfo.base64()
success(img)
} else {
let img = getFileAccessHttpUrl(res.message)
success(img)
}
}
})
}
},
myValue: this.value,
reloading: false
}
},
mounted() {
this.initATabsChangeAutoReload()
},
methods: {
reload() {
this.reloading = true
this.$nextTick(() => (this.reloading = false))
},
onClick(e) {
this.$emit('onClick', e, tinymce)
},
//可以添加一些自己的自定义事件,如清空内容
clear() {
this.myValue = ''
},
/**
* 自动判断父级是否是 <a-tabs/> 组件,然后添加事件监听,自动触发reload()
*
* 由于 tabs 组件切换会导致 tinymce 无法输入,
* 只有重新加载才能使用(无论是vue版的还是jQuery版tinymce都有这个通病)
*/
initATabsChangeAutoReload() {
// 获取父级
let tabs = getVmParentByName(this, 'ATabs')
let tabPane = getVmParentByName(this, 'ATabPane')
if (tabs && tabPane) {
// 用户自定义的 key
let currentKey = tabPane.$vnode.key
// 添加事件监听
tabs.$on('change', (key) => {
// 切换到自己时执行reload
if (currentKey === key) {
this.reload()
}
})
} else {
//update--begin--autor:wangshuai-----date:20200724------for:富文本编辑器切换tab无法修改------
let tabLayout = getVmParentByName(this, 'TabLayout')
tabLayout.excuteCallback(() => {
this.reload()
})
//update--begin--autor:wangshuai-----date:20200724------for:文本编辑器切换tab无法修改------
}
}
},
watch: {
value(newValue) {
this.myValue = newValue == null ? '' : newValue
},
myValue(newValue) {
if (this.triggerChange) {
this.$emit('change', newValue)
} else {
this.$emit('input', newValue)
}
}
}
}
</script>
<style scoped>
</style>
最后送上配置完成之后的效果图:
四、tinymce的三种模式
tinymce 有三种模式可选:经典模式(classic,默认)、内联模式(inline)、沉浸模式(Distraction-free)。
经典模式
经典模式是最常见的,也就是工具栏搭配输入区域,通过工具栏的按钮插入、修改、格式化内容,我们也选用这种模式作为业务的主要模式,代码就是上面封装的组件案例,这里不过多赘述。
再介绍一下其他两种模式:内联模式和沉浸模式
内联模式
js如下:
tinymce.init({
selector: '#tinydemo', //容器,可使用css选择器
language:'zh_CN', //调用放在langs文件夹内的语言包
toolbar: false, //隐藏工具栏
menubar: false, //隐藏菜单栏
inline: true, //开启内联模式
plugins: [ 'quickbars','link','table' ], //选择需加载的插件
//选中时出现的快捷工具,与插件有依赖关系
quickbars_selection_toolbar: 'bold italic forecolor | link blockquote quickimage',
});
内联模式最大的好处,是内容完全继承自外层定义的样式,真正实现了"所见即所得"。但要注意,请小心使用杀伤力比较大的自定义CSS,因为这样可能会覆盖TinyMCE控件的CSS,导致编辑器显示异常。上面的代码高亮框本来是黑色主题,被TinyMCE的插件CSS污染成了白色主题……😂
沉浸模式
在该模式下,编辑器以轻巧的方式呈现。此模式提供了将常用的链接、图像、表格等以快速插入的方式。有关该模式的例子,请参考本文档首页。
此模式最简代码:
tinymce.init({
selector: 'div.tinymce',
plugins: [ 'quickbars' ],
toolbar: false,
menubar: false,
inline: true
});
所谓‘沉浸模式’感觉就是TinyMCE强行将inline模式解释成了另一种风格。我们也不要揭穿它,假装不知道就好了。
后两种模式也各有特点,但是与我们实际的应用场景不太匹配,故不做介绍。有兴趣可以自行了解。
五、页面多个编辑器的配置
如果一个页面中需要多个编辑器,有两种方法:
一个是结合className选择器,使用一次 tinymce 的 init 方法,生成多个实例,多个实例会共用一套配置;
示例代码:
<template>
<div class="default-tinymce">
<h2>编辑器1</h2>
<textarea class="editor"></textarea>
<h2>编辑器2</h2>
<textarea class="editor"></textarea>
</div>
</template>
<script>
import Tinymce from 'tinymce'
export default {
mounted () {
Tinymce.init({
selector: '.editor'
})
}
}
</script>
另一种是,需要几个编辑器,便使用tinymce 的 init 方法生成几种实例,生成的实例彼此无关
示例代码:
<template>
<div class="default-tinymce">
<h2>编辑器1</h2>
<textarea class="editor1"></textarea>
<h2>编辑器2</h2>
<textarea class="editor2"></textarea>
</div>
</template>
<script>
import Tinymce from 'tinymce'
export default {
mounted () {
Tinymce.init({
selector: '.editor1'
})
Tinymce.init({
selector: '.editor2'
})
}
}
</script>
六、编辑器配置说明
1.自定义编辑器UI
theme 和 skin 我们直接使用默认的即可,无需折腾。
从上面的示例中,我们也知道基本上所有东西我们都可以去自定义的,比如隐藏不使用的menubar、调整按钮在toolbar中的位置等。
tinymce.init({
selector: '#editor',
// 隐藏menubar
menubar: false,
// 隐藏 statusbar
statusbar: false,
// 隐藏品牌标识
branding: false,
// 设置最大宽高
max_height: 500,
max_width: 500,
// 设置最小宽高
min_height: 100,
min_width: 400
})
最后对于tinymce富文本插件的配置,下面有它的中文开发文档地址,大家可以根据自己的需求进行相应的配置。
相关链接:
TinyMCE菜单配置详解。
官方文档
2.如何使用自定义的图片上传?
官方有提供一个上传的url配置,但是需要后台配合返回指定的数据格式。
这里我们还是希望可以自定义参数,这样才不对后台产生影响,实现images_upload_handler函数即可,如果没有实现的话,默认是使用base64格式的图片。
images_upload_handler: function(blobInfo, success, failure) {
const maxSize = 2
const blob = blobInfo.blob()
// 判断图片大小
if (blob.size / 1024 / 1024 > maxSize) {
failure('图片大小不能超过' + maxSize + 'MB')
return
}
// 组装图片信息
const formData = new FormData()
formData.append('file', blob, blobInfo.fileName)
// 调用上传图片接口
upload(formData).then(res => {
// 将返回的全路径赋值给success回调函数即可
success(res.data.fileUrl)
}).catch(() => {
failure('文件上传失败,请重试')
})
}
图片太大,如何在编辑器内的限制图片大小?
tiny自带配置可设置编辑区内的css,使用content_style及content_css皆可,本例使用content_style演示如何限制图片大小:
tinymce.init({
selector: '#tinydemo',
content_style: "img {max-width:100%;}"
});
3.使用插件
除了默认的配置,如果想拓展编辑器的功能,tinymce提供的方式就是插件。
官方的插件很丰富,文档也很详细,大多数情况下,我只需要官方提供的插件即可。
激活插件也很简单,只需要做了相应配置即可。
比如,我们想要拓展一个查看编辑器内容HTML源码的功能,只需要如下配置:
tinymce.init({
selector: '#editor',
plugins: 'code'
})
在menubar的位置,多了一个Tool的分组,分组里有一个source code的项,点击即可查看源码
如果我们不习惯使用menubar,而是习惯使用toolbar,我们只需要如下配置:
tinymce.init({
selector: '#editor',
toolbar: 'code',
plugins: 'code'
})
toolbar位置上多了一个查看源码的图标,点击是相同的功能
有时候,这些基础的功能可能满足不了我们的需求,我们可能对这些插件进行配置。
tinymce.init({
selector: '#editor',
toolbar: "numlist bullist",
plugins: 'lists'
})
示例中,我们配置了list组件,它支持无序(ul)列表和有序列表的功能,但是列表样式是固定的,我们可以引入新的插件advlist,并配置
tinymce.init({
selector: '#editor',
toolbar: 'bullist numlist',
plugins: 'lists advlist'
})
此时,我们发现我们可以选择样式了,有多种样式供我们选择,如果我们想固定某种样式,我们可以这样配置:
tinymce.init({
selector: '#editor',
toolbar: 'bullist numlist',
plugins: 'lists advlist',
advlist_number_styles: 'lower-alpha'
})
我们发现可选项只有一个了
每个插件有哪些配置项、配置参数和配置值,官方文档中都有详细的描述
这些配置不太重要,一般一次配置之后就不会有改动。
4.配置插件库
这里会写一下编辑器用到的插件的介绍、配置、和使用,太简单的插件和没用到的插件不在此说明。
Advanced Code Editor
此插件为付费插件
此插件可美化代码预览时候的视图,并且还有收起/展开标签的功能。配置此插件后,需要移除code插件。
tinymce.init({
selector: "textarea", // change this value according to your HTML
plugins: "advcode",
toolbar: "code"
});
AutoLink
此插件可在输入形如「www.qq.com」的链接时,自动将文本转化为超链接,空格和换行键都可以触发
tinymce.init({
selector: "textarea", // change this value according to your HTML
plugins: "autolink"
});
nonbreaking
键盘的Tab键,默认是切换到下一个元素的focus。此可以改变这个默认行为。
需要注意的是,table插件中也有改变改默认行为的代码,所以要在table插件之后引用该插件。
tinymce.init({
selector: "textarea", // change this value according to your HTML
plugins: "nonbreaking",
// 此配置会改变默认行为,会在光标之后添加三个空格。
nonbreaking_force_tab: true
});
autosave
此插件会自动保存编辑器的内容到localStorage。会同时存储两个字段,一个是内容,一个是存储时间。
可以配置自动保存的时间间隔,一般以秒为单位;也可以配置内容存储的过期时间,一般以分钟为单位,超过这个时间段,数据会从localStorage中被清除。
tinymce.init({
selector: "textarea", // change this value according to your HTML
plugins: "autosave",
// 自动保存的时间间隔
autosave_interval: '30s',
// 自动保存的数据存储的最大时间
autosave_retention: '30m'
});
powerpaste
此插件为付费插件
此插件可以保留剪切板内容的样式、文档结构。
tinymce.init({
selector: "textarea", // change this value according to your HTML
plugins: "powerpaste",
/**
* 粘贴前是否保留文本样式
* @param 'clean' 不保留
* @parma 'merge' 保留
* @parma 'prompt' 询问用户
*/
powerpaste_word_import: 'prompt',
powerpaste_html_import: 'prompt',
/**
* 在粘贴到富文本之前,可以修改粘贴的内容。内容已经DOM格式化
* @param plugin
* @param args
* @returns {Promise<void>}
*/
paste_postprocess (plugin, args) {}
});
5.修改默认样式
改变格式工具的默认行为
TinyMCE提供给开发人员接口,能够覆盖格式工具的默认行为,将自定义行为添加到源码中输出。
当用户单击编辑器中粗体按钮时,编辑器会按默认操作应用文本样式——也就是加粗,TinyMCE内建一个文本格式化引擎,它允许你去指定该触发行为进行什么操作。例如,你可以定义粗体按钮被单击的行为是文字变红而不是加粗。
内置格式
TinyMCE允许重写的内置格式,如下表所示:
- alignleft
- aligncenter
- alignright
- alignjustify
- bold
- italic
- underline
- strikethrough
- forecolor
- hilitecolor
- fontname
- fontsize
- blockquote
- removeformat
- p
- h1, h2, h3, h4, h5, h6
- div
- address
- pre
- div
- code
- dt, dd
- samp
一些内置格式fontsize、fontname、forecolor、hilitecolor,它们的值使用变量%value代替。此变量将替换为用户选择的值。
你一定会觉得这是在说神马,我怎么完全看不懂!我刚读这部分文档的时候也是云里雾里,直到看完才明白官方要表达神马,所以,我在这里插入个例子,亲自试一下就明白了。
tinymce.init({
selector: '#tinydemo',
plugins:'code',
toolbar:'undo redo forecolor bold italic | alignleft aligncenter alignright | code',
formats: {
alignright: {selector : 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li,table,img', classes : 'right'},
bold: {inline:'span', styles:{color:'#f00'} },
forecolor: {inline : 'span', styles : {color:'%value',fontWeight:'700'} },
custom_format: {block : 'h1', attributes : {title:'Header'}, styles : {color:'red'} }
}
});
效果如下:
内置格式的参数
看完例子,再理解下面这些参数就容易很多了。
名称 | 说明 |
---|---|
inline | 包裹选中文本的内联元素名称,比如span,加粗改变颜色默认都在外围插个span,然后再加个style="" |
block | 指定一个块元素,如h1,当前焦点外围的块元素将被其替换。比如焦点在p标签内,当你使用格式工具指定为“标题1”时,p将会替换成h1。 |
selector | 利用CSS3选择器中所选则内容的查找匹配的内容(如表中的奇数行)。 |
classes | 值为以空格分隔的class列表,如:class1 class2。简单说就是给选中的块加自定义class,如上例的右对齐。 |
styles | 值为包含名称/值的一个对象。将自定义css样式置入到选中内容中,如上例的加粗和颜色。 |
attributes | 值为包含名称/值的一个对象。给html标签添加自定义属性。 |
exact | 可设值为:true,此选项将禁用样式合并功能,用于解决一些css继承问题,例如下划线、删除线。默认为false。 |
wrapper | 指定当前格式是块元素,例如div或blockquote。 |
schema:照顾老旧浏览器
该选项可指定生成的代码是html4还是html5标准。
该值默认是html5,可选值还有:html4、html5-strict。
html5模式是完整的HTML5规范,它兼容旧的HTML4。html5-strict是HTML5的严格模式,它只允许HTML5规范的元素,不包括已经被移除标准的元素。html4模式则是包括完整的HTML4过渡规范。在需要兼容老旧浏览器时,可能会用到该选项。
七、业务上一些常用的API
<textarea id="tinydemo">这里是默认文字。</textarea>
<input type="button" onclick="initTiny()" value="创建编辑器" >
<input type="button" onclick="getContent()" value="获得内容" >
<input type="button" onclick="setContent()" value="设置内容" >
<input type="button" onclick="goEnd()" value="光标放最后" >
<input type="button" onclick="insertContent()" value="插入内容" >
<input type="button" onclick="saveContent()" value="同步内容到textarea" >
<input type="button" onclick="boldContent()" value="加粗文本" >
<input type="button" onclick="colorContent()" value="标红文字" >
<input type="button" onclick="fontSize()" value="文字大小" >
<input type="button" onclick="getText()" value="获得纯文本" >
<input type="button" onclick="copyContent()" value="复制选中文字" >
<input type="button" onclick="pasteContent()" value="粘贴" >
<input type="button" onclick="mceImage()" value="打开图片对话框" >
<input type="button" onclick="indent2em()" value="自定义首行缩进" >
<input type="button" onclick="hideTiny()" value="隐藏编辑器" >
<input type="button" onclick="showTiny()" value="显示编辑器" >
<input type="button" onclick="destroyTiny()" value="销毁编辑器" >
var tinyID='tinydemo';
function initTiny(){
tinymce.init({
selector: '#'+tinyID,
language:'zh_CN',
plugins:'link image indent2em',
auto_focus: true,
});
}
function destroyTiny(){
tinyMCE.editors[tinyID].destroy();
//tinyMCE.editors[tinyID].remove();
}
function getContent(){
var cnt = tinyMCE.editors[tinyID].getContent();
console.log(cnt);
alert(cnt);
}
function getText(){
var cnt = tinyMCE.editors[tinyID].getContent({ format: 'text' });
console.log(cnt);
alert(cnt);
}
function goEnd(){
editor = tinyMCE.editors[tinyID];
editor.execCommand('selectAll');
editor.selection.getRng().collapse(false);
editor.focus();
}
function setContent(){ tinyMCE.editors[tinyID].setContent('设置内容'); }
function insertContent(){ tinyMCE.editors[tinyID].insertContent('<b>插入内容</b>'); }
function saveContent(){ tinyMCE.editors[tinyID].save(); }
function showTiny(){ tinyMCE.editors[tinyID].show(); }
function hideTiny(){ tinyMCE.editors[tinyID].hide(); }
function boldContent(){ tinyMCE.editors[tinyID].execCommand('bold'); }
function colorContent(){ tinyMCE.editors[tinyID].execCommand('ForeColor',false,'#f33'); }
function fontSize(){ tinyMCE.editors[tinyID].execCommand('fontSize',false,'24px'); }
function copyContent(){ tinyMCE.editors[tinyID].execCommand('copy'); }
function pasteContent(){ tinyMCE.editors[tinyID].execCommand('paste'); }
function mceImage(){ tinyMCE.editors[tinyID].execCommand('mceImage'); }
function indent2em(){ tinyMCE.editors[tinyID].execCommand('indent2em'); }