Angular7+Ckeditor5基础功能整合

主要是实现两个功能:图片上传和保存文章内容至服务端

仅为笔记,仅供参考


环境参数

  • @angular/cli 7.3.2
  • macOS 10.14.3
  • nodejs v9.0.0
  • 其他信息详见package.json

环境搭建

首先,使用angular脚手架初始化项目(需要先全局安装 @angular/cli)

npm install -g @angular/cli
ng new ckeditor-angular

其次,安装 Ckeditor5 相关依赖,进入项目根目录

npm install --save @ckeditor/ckeditor5-angular
npm install --save @ckeditor/ckeditor5-build-classic
npm install --save @ckeditor/ckeditor5-ui

前两个是必须的,第三个是为了编写保存功能插件而安装的

安装完毕后使用编辑器打开项目,我使用的是 Intellij IDEA

在 src/app/app.module.ts 中添加 CKEditorModule,如下

为了演示方便,将 src/app/app.component.htmlsrc/app/app.component.css 删除,html内容在 src/app/app.component.ts 中编写,第一版代码具体代码如下

// src/app/app.component.ts
import {Component} from '@angular/core';
import * as ClassicEditor from '@ckeditor/ckeditor5-build-classic';

@Component({
  selector: 'app-root',
  template: `
    
    
  `
})
export class AppComponent {
  public Editor = ClassicEditor;
}

输入 ng serve 查看效果

至此环境搭建完毕


对Ckeditor进行配置

完成两项配置:1,中文化;2,自己配置按钮的数量类型

中文化

顺着包路径 node_modules/@ckeditor/ckeditor5-build-classic/build/translations一路找下去就会发现有一大堆地区语言的js文件,其中就有 zh-cn.js,将其加入到ts文件中,同时添加配置对象config,传入到 组件 ckeditor 中,app.component.ts修改如下

import {Component} from '@angular/core';
import * as ClassicEditor from '@ckeditor/ckeditor5-build-classic';
import '@ckeditor/ckeditor5-build-classic/build/translations/zh-cn.js';

@Component({
  selector: 'app-root',
  template: `
    
    
  `
})
export class AppComponent {
  public Editor = ClassicEditor;
  // 配置语言
  public config = {
    language: 'zh-cn'
  };
}

效果如下


配置按钮

这个也是在那个 config 对象中配置,就添加上传图片的按钮和加粗的按钮吧,中间再填个一个分隔符,配置如下

//....
export class AppComponent {
  public Editor = ClassicEditor;
  public config = {
    // 配置语言
    language: 'zh-cn',
    toolbar: ['imageUpload', '|', 'bold']
  };
}

效果如下

更多按钮请参考官网文档


添加上传图片的功能

搭建测试文件服务器

首先,先搭建一个简单的提供文件上传下载功能的nodejs服务器

这里就在根目录下新建文件夹 file-server,进入其中,安装express等依赖

# 在 file-server 文件夹中
npm init
npm install --save express
npm install --save express-fileupload
npm install --save body-parser 

新建app.js文件,向其中填写基础代码;新建uploads文件夹,作为上传文件的文件夹

const express = require('express');
const fileUpload = require('express-fileupload');
const app = express();
const path = require('path');

const PORT = 8000;

// 用于解析post请求
const bodyParser = require('body-parser')
app.use(bodyParser.json());
app.post('/upload/content', function(req, res) {
  console.log(req.body)
  // 就返回提交的数据
  res.send(req.body)
})

// 处理上传文件的插件
app.use(fileUpload());

// 处理静态资环
app.use('/uploads',express.static('uploads'))

// 处理上传文件的逻辑
app.post('/upload', function(req, res) {
  let sampleFile;
  let uploadPath;

  if (Object.keys(req.files).length == 0) {
    res.status(400).send('No files were uploaded.');
    return;
  }

  console.log('req.files >>>', req.files); // eslint-disable-line

  // 这里提交的文件表单名称为 file
  sampleFile = req.files.file;

  // 为文件名打一个时间戳,保证不重名
  const filename = (new Date().getTime()) + sampleFile.name;

  uploadPath = path.join(__dirname, 'uploads', filename);

  sampleFile.mv(uploadPath, function(err) {
    if (err) {
      return res.status(500).send(err);
    }

  res.send({default: path.join('uploads', filename)})
  });
});

app.listen(PORT, function() {
  console.log('Express server listening on port ', PORT); // eslint-disable-line
});

启动app.js,监听8000端口;在uplaods文件夹中放入一个图片文件,通过浏览器可以访问,代表成功


前后端打通

不做任何配置的话前端是无法跨域访问后端文件服务器的,这里需要配置angular的server

在ckeditor-angular根目录下新建文件 proxy.config.json,进行如下配置

{
  "/uploads/*": {
    "target": "http://localhost:8000",
    "secure": false,
    "changeOrigin": true,
    "logLevel": "debug"
  },
  "/upload/*": {
    "target": "http://localhost:8000",
    "secure": false,
    "changeOrigin": true,
    "logLevel": "debug"
  }
}

第一个是GET图片的所有请求,第二个是POST图片的所有请求

重启 ng serve,但是需要让其读取这个配置文件

ng serve --proxy-config proxy.config.json

修改 app.component.ts 内容,让其访问图片

//...
@Component({
  selector: 'app-root',
  template: `
    
    
  `
})
//...

效果如下,访问成功


编写上传图片插件

在 app.component.ts 同级目录下创建文件 fileupload.ckeditor.ts ,在其中编写上传插件,大体框架如下

/**
 * 编写上传逻辑
 */
export class FileUploadAdapter {

  constructor(loader) {
    // 初始化
  }
  upload() {
    // 上传文件
  }
  abort() {
    // 上传失败
  }
}

/**
 * 工厂方法,提供给ckeditor调用
 */
export function CustomUploadAdapterPlugin(editor) {
  editor.plugins.get('FileRepository').createUploadAdapter = (loader) => {
    return new FileUploadAdapter(loader);
  };
}

最后,将第二个函数添加到 app.component.ts 中的config对象中(这里重构了一下,将config对象的初始化放到constructor中),部分代码如下

// .........
  constructor(private http: HttpClient) {
    this.config = {
      // 配置语言
      language: 'zh-cn',
      toolbar: ['imageUpload', '|', 'bold'],
      // 在这里添加
      extraPlugins: [CustomUploadAdapterPlugin]
    };
  }
// ........

首先,这个第二个函数中的 editorloader 是ckeditor注入的,和我们使用者无关;其次,由于它们没有什么关系,而angular发送http模块初始化也是有angular管理和注入的,和我们无关,所有说要在fileupload.ckeditor.ts中使用angular的模块就有点麻烦。使用函数参数传入的方法貌似也不好使,貌似this作用域改变了。

我给出的解决方案是将其作为config对象的一个参数传入,然后在 fileupload.ckeditor.ts 文件中通过 editor.config.get() 来获得,app.component.ts文件如下,记得在 app.module.ts 中import添加 HttpClientModule

当然,也可以自己编写service将http的功能封装起来注入其中

import {Component} from '@angular/core';
import * as ClassicEditor from '@ckeditor/ckeditor5-build-classic';
import '@ckeditor/ckeditor5-build-classic/build/translations/zh-cn.js';
import {HttpClient} from '@angular/common/http';
import {CustomUploadAdapterPlugin} from './fileupload.ckeditor';

@Component({
  selector: 'app-root',
  template: `
    
    
  `
})
export class AppComponent {
  public Editor = ClassicEditor;
  public config

  constructor(private http: HttpClient) {
    this.config = {
      // 配置语言
      language: 'zh-cn',
      toolbar: ['imageUpload', '|', 'bold'],
      // 注入http
      http,
      extraPlugins: [CustomUploadAdapterPlugin]
    };
  }
}

fileupload.ckeditor.ts 完整代码如下

export class FileUploadAdapter {
  loader;
  http;
  editor;
  constructor(loader, editor) {
    this.loader = loader;
    this.editor = editor;
    // 获取传入的http模块
    this.http = this.editor.config.get('http');
  }
  upload() {
    const data = new FormData();
    // 表单name为file,和后端app.js中的 req.files.file 相对应
    data.append('file', this.loader.file);
    return new Promise((resolve, reject) => {

      this.http.post(
        '/upload',
        data)
        .subscribe(
          (resp) => {
            console.log(resp)
            resolve({
              default: resp.default
            });
          },
          (err) => reject(err));
    });
  }
  abort() {
  }
}
export function CustomUploadAdapterPlugin(editor) {
  editor.plugins.get('FileRepository').createUploadAdapter = (loader) => {
    // 将 editor 对象也注入其中
    return new FileUploadAdapter(loader, editor);
  };
}

对于FileUploadAdapter,有几个注意点

1.不要自作多情设置header Content-Type:multipart/form-data

2.注意返回给ckeditor的resolve传入的对象中 default 属性对应图片的路径

进行测试,上传成功,同时查看 file-server/uploads中有刚才上传的文件


至此文件上传服务编写完毕


编写保存功能

这个需求是在ckeditor的按钮列表中添加保存选项,点击后将数据传输至服务器,这里的测试服务器也使用file-server

编写功能按钮

新建文件 savefile.ckeditor.ts,这其中编写如下代码

import ButtonView from '@ckeditor/ckeditor5-ui/src/button/buttonview';
/**
 *  添加保存文件的功能
 */
export function SaveFilePlugin(editor) {
  // 注册一个名字是 save 按钮
  editor.ui.componentFactory.add( 'save', locale => {
    const view = new ButtonView( locale );
    // 由于没有图标,这里使用文字按钮
    view.set( {
      label: '保存',
      withText: true,
    } );
    // 点击时执行指令
    view.on( 'execute', () => {
      console.log('执行保存指令');
    } );
    return view;
  } );
}

修改 app.component.ts,部分如下

// .......
  constructor(private http: HttpClient) {
    this.config = {
      language: 'zh-cn',
      // 添加一个 save 按钮
      toolbar: ['imageUpload', '|', 'bold', '|', 'save'],
      http,
      // 导入 SaveFilePlugin
      extraPlugins: [CustomUploadAdapterPlugin, SaveFilePlugin]
    };
  }
// ......

测试如下图,点击按钮后控制台有如下输出


获取内容

这里可以使用 ngModel 获取 ckeditor中的内容,注意要在app.module.ts中import添加 FormsModule 模块

app.component.ts 修改部分如下

// ......
@Component({
  selector: 'app-root',
  template: `
    
    
  `
})
export class AppComponent {
  public Editor = ClassicEditor;
  public config
  article = {content: ''};
// ......
}

前后端打通

我希望可以在 app.component.ts 中实现上传文章的逻辑;这里可以使用闭包的技术将这个函数注入到 savefile.ckeditor.ts 文件的 SaveFilePlugin 中,同时保留this的作用域,如下

app.component.ts 修改部分如下

//.....
constructor(private http: HttpClient) {
    this.config = {
      // 配置语言
      language: 'zh-cn',
      toolbar: ['imageUpload', '|', 'bold', '|', 'save'],
      http,
      // 还是将其添加到config中,使用 editor.config.get 可以获取
      uploadContent: () => this.uploadContent(),
      extraPlugins: [CustomUploadAdapterPlugin, SaveFilePlugin]
    };
  }

  /**
   * 上传文件的逻辑
   */
  uploadContent() {
    this.http.post('/upload/content', this.article)
      .subscribe((resp) => {
        console.log(resp);
      }, (err) => {
        console.log(err);
      });
  }
//.....

savefile.ckeditor.ts 修改部分如下

// .....
    view.on( 'execute', () => {
      const func = editor.config.get('uploadContent');
      // 这里获取了传入的闭包,并执行
      func();
    } );
// .....

点击按钮进行上传,查看后台服务器输出:

总结

完整代码

踩了不少的坑,总结一下,但是官方文档还是很重要的

  • Rich text editor component for Angular 2+
  • Custom image upload adapter

你可能感兴趣的:(javascript)