机缘巧合,刚好公司准备做一个新闻版块。
在网上找了很多富文本插件感觉都跟angular4不大契合,最终踩坑CKEditor,这款强到爆炸得富文本插件!
发现国内几乎没有分享这方面踩坑得文章,所以最终决定把这段踩坑血泪史写下来,供大家参考。
CKEditor官网: https://www.ckeditor.com
CKEditor插件列表: https://ckeditor.com/cke4/addons/plugins/all
国内被墙了,各位大佬搭把梯子吧!脱坑必备!
1.安装ng2-ckeditor插件(因为我得angular版本是4.2.6所以就没有安装最新版本了):
npm install ng2-ckeditor@1.1.9 --save
2.引入module.ts你懂得
import {CKEditorModule} from 'ng2-ckeditor';
@NgModule({
imports: [
xxx..,
CKEditorModule,
]
3.将下载得ckeditor包解压放到assert目录下:assert/ckeditor,并在index.html中引用它
<script src="assets/ckeditor/ckeditor.js">script>
4.component.html
<ckeditor [(ngModel)]="article.content"
debounce="500"
[config]="config">
ckeditor>
5.component.ts配置
config: any = {
// 文件上传路径
filebrowserUploadUrl :`/api-admin/admin/public/flashUpload`,
// 图片上传后端url
filebrowserImageUploadUrl:`/api-admin/admin/public/imageUpload`,
// flash上传后端url
filebrowserFlashUploadUrl:`/api-admin/admin/public/flashUpload`,
// audio上传url
filebrowserAudioUploadUrl:`/api-admin/admin/public/flashUpload`,
// 其他插件,字数统计,提示信息
extraPlugins:`widget,html5video,emojione,video,audio,
image2,wordcount,notification,notificationaggregator`,
};
// 注意: 需要注意得是在官网下载得full压缩包并未含有
// html5video,emojione,video,audio,image2,wordcount等插件,需要自行配置!!!
// 我在此处只配置了各种文件上传得链接,主要配置在assert/ckeditor/config.js中
6.assert/ckeditor/config.js配置
CKEDITOR.editorConfig = function (config) {
/**
* 插件语言
* @type {string}
*/
config.language = 'zh-cn';
/**
* 插件字体
* @type {string}
*/
config.font_names = '宋体/宋体;黑体/黑体;仿宋/仿宋_GB2312;楷体/楷体_GB2312;隶书/隶书;幼圆/幼圆;微软雅黑/微软雅黑;宋体/SimSun;'
+ '新宋体/NSimSun;仿宋_GB2312/FangSong_GB2312;楷体_GB2312/KaiTi_GB2312;黑体/SimHei;微软雅黑/Microsoft YaHei;幼圆/YouYuan;华文彩云/STCaiyun;华文行楷/STXingkai;'
+ '方正舒体/FZShuTi;方正姚体/FZYaoti;Arial;Comic Sans MS;Courier New;Georgia;Lucida Sans Unicode;Tahoma;Times New Roman;Trebuchet MS;Verdana;';
/**
* 插件默认字体
* @type {string}
*/
config.font_defaultLabel = '宋体';
/**
* 设置工具栏显示模式
* @type {string}
*/
config.toolbar = 'Full';
/**
* 工具栏全显示模式
* @type {[null,null,null,string,null,null,null,null,null,string,null,null,null]}
*/
config.toolbar_Full = [
{
name: 'document', items: [
'Source', '-', 'NewPage', 'DocProps', 'Preview', 'Print', '-', 'Templates'
]
},
{
name: 'clipboard', items: [
'Cut', 'Copy', 'Paste', 'PasteText', 'PasteFromWord', '-', 'Undo', 'Redo'
]
},
{
name: 'editing', items: [
'Find', 'Replace', '-', 'SelectAll', '-', 'SpellChecker', 'Scayt'
]
},
'/',
{
name: 'forms', items: [
'Form', 'Checkbox', 'Radio', 'TextField', 'Textarea', 'Select', 'Button', 'ImageButton', 'HiddenField'
]
},
{
name: 'basicstyles', items: [
'Bold', 'Italic', 'Underline', 'Strike', 'Subscript', 'Superscript', '-', 'RemoveFormat'
]
},
{
name: 'links', items: [
'Link', 'Unlink', 'Anchor'
]
},
{
name: 'paragraph', items:
['NumberedList', 'BulletedList', '-', 'Outdent', 'Indent', '-', 'Blockquote', 'CreateDiv',
'-', 'JustifyLeft', 'JustifyCenter', 'JustifyRight', 'JustifyBlock', '-', 'BidiLtr', 'BidiRtl'
]
},
// todo 在这里设定需要的插件 ...
// ,'Flash','Smiley','Video' Video是用得国产插件
{
name: 'insert', items: [
'Image', 'Html5video', 'Table', 'HorizontalRule', 'Emojione', 'Video', 'Audio', 'Image2','SpecialChar', 'PageBreak', 'Iframe'
]
},
'/',
{
name: 'styles', items: [
'Styles', 'Format', 'Font', 'FontSize'
]
},
{
name: 'colors', items: [
'TextColor', 'BGColor'
]
},
{
name: 'tools', items: [
'Maximize', 'ShowBlocks', '-'
// ,'About'
]},
];
/**
* 基础模式插件列表
* @type {[null]}
*/
config.toolbar_Basic = [['Bold', 'Italic', '-', 'NumberedList', 'BulletedList', '-', 'Link', 'Unlink', '-'
// , 'About'
]];
/**
* 工具栏是否可以被收缩
* @type {boolean}
*/
config.toolbarCanCollapse = true;
/**
* 工具栏默认是否展开
* @type {boolean}
*/
config.toolbarStartupExpanded = true;
/**
* 取消 “拖拽以改变尺寸”功能 plugins/resize/plugin.js
* @type {boolean}
*/
config.resize_enabled = true;
/**
* 取消文章体得html头
* @type {boolean}
*/
config.fullPage = false;
/**
* 图片展示方式
* @type {string}
*/
config.removeDialogTabs = 'image:advanced;image:advanced';
/**
* 禁止图片上传完毕后自动填充长宽
* @type {boolean}
*/
config.image_prefillDimensions = false;
/**
* 图片展示文字
*/
config.image_previewText = '';
/**
* 字符统计插件
* @type {{showParagraphs: boolean, showWordCount: boolean, showCharCount: boolean, countSpacesAsChars: boolean, countHTML: boolean, maxWordCount: number, maxCharCount: number, filter: *}}
*/
config.wordcount = {
// 段落统计
showParagraphs: true,
// 词数统计
showWordCount: true,
// 字数统计
showCharCount: true,
// 将空格计入字符
countSpacesAsChars: false,
// 统计html
countHTML: false,
// 最大词数 -1代表无上限
maxWordCount: -1,
// 最大字数 -1代表无上限
maxCharCount: -1,
// 移除关键词统计 类似mediaembed 节点之类,可以设置不统计
filter: new CKEDITOR.htmlParser.filter({
elements: {
div: function (element) {
if (element.attributes.class == 'mediaembed') {
return false;
}
}
}
})
};
// 更多配置请参见官网;
// .cke_dialog_ui_html{height:350px;overflow:auto;}
7.后端接口
因为考虑到大家可能使用不同得后端服务这里就说一下upload接口得返回值吧;
// 成功:{"uploaded":1,"fileName":"文件名.文件格式","url":"上传成功后得资源路径url"}
// 失败: {"uploaded":0,"error":{"message":"资源上传错误得原因..."}}
做Java得同学们我就附赠一小块后端实现代码吧(为了方便演示就没有刻意封装了,大神勿喷....)
/**
* CKEditor上传图片接口
* @param request
* @return
* @throws IOException
* @Author Jack
*/
@RequestMapping(value = "/imageUpload")
public String imageUpload(HttpServletRequest request) throws IOException {
boolean uploadFlag = false;
// 创建一个通用的多部分解析器
CommonsMultipartResolver multipartResolver = new CommonsMultipartResolver(request.getSession()
.getServletContext());
// 图片名称
String fileName = null;
// 判断 request 是否有文件上传,即多部分请求
if (multipartResolver.isMultipart(request)) {
// 转换成多部分request
MultipartHttpServletRequest multiRequest = (MultipartHttpServletRequest) request;
// 取得request中的所有文件名
Iterator<String> iter = multiRequest.getFileNames();
// 定义存库相对路径名称
String relativeName = "";
// 功能文件夹,注意:代码只能创建一级文件夹,需先在服务器创建文件夹目录
String tmpPath = AdminConstant.ADMIN_FILE_PATH;
// 时间文件夹
String folderYMD = new DateTime().toString("yyyyMMdd") + "/";
while (iter.hasNext()) {
// 取得上传文件
MultipartFile file = multiRequest.getFile(iter.next());
if (file != null) {
// 取得当前上传文件的文件名称
String myFileName = file.getOriginalFilename();
// 如果名称不为“”,说明该文件存在,否则说明该文件不存在
if (myFileName.trim() != "") {
// 获得图片的原始名称
String originalFilename = file.getOriginalFilename();
// 获得图片后缀名称,如果后缀不为图片格式,则不上传
String suffix = originalFilename.substring(originalFilename.lastIndexOf(".")).toLowerCase();
if (!fileTypes.contains(suffix)) {
continue;
}
InputStream is = file.getInputStream();
byte[] picData = IOUtils.toByteArray(is);
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(picData);
if (picData != null && picData.length > 0) {
try {
// 真实文件名,DigestUtils.md5Hex方法可以根据文件byte[]生成唯一哈希!!!(节省服务器内存)
String realName = DigestUtils.md5Hex(picData) + suffix;
// 存库相对文件路径
relativeName = tmpPath + folderYMD + realName;
// 文件在图片服务器的路径前缀
String directory = UPLOAD_PATH + tmpPath + folderYMD;
String imageContextPath = FILE_PATH + UPLOAD_PATH + relativeName;
// ftp上传文件
uploadFlag = ftpUtil.uploadInputStreamFile(byteArrayInputStream, realName, directory);
if (uploadFlag) {
return "{\"uploaded\":1,\"fileName\":\""+relativeName+"\",\"url\":\"" + imageContextPath + "\"}";
} else {
return "{\"uploaded\":0,\"error\":{\"message\":\"upload file is not success!\"}}";
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
}
return "{\"uploaded\":0,\"error\":{\"message\":\"upload file is not success!\"}}";
}
8.CKEditor不愧是大厂神作!说了这么多我们来看看最终效果吧![手动滑稽]