一、业务需求
- 功能说明:富文本插件
CKEditor 5
集成七牛 JS-SDK 3.0
- 技术栈:
React + Typescript
- 说明:
- 避免代码太长,部分内容进行了简化(如七牛上传 key 的生成,图片验证等)
- 主体功能无影响,可以直接 copy 运行
- 遇到问题给我留言,看到一定回,一起学习一起加油 ヾ(◍°∇°◍)ノ゙
二、官网教程
三、开发步骤
1、Editor 组件
要点说明:
-
class UploadAdapter
:自定义插件类,可参见2、自定义 class UploadAdapter
-
MyUploadAdapterPlugin()
:方法内部直接照搬官网教程,可以根据需求在 return 前加钩子 -
editorConfiguration{}
:该对象可以添加toolbar:[ 'bold', 'italic', 'link' ]
配置参数,用以修改编辑框顶部ToolBar
,具体配置项 点击我查看官网说明
组件源码:
// @ts-nocheck
import * as React from 'react';
import CKEditor from '@ckeditor/ckeditor5-react';
import ClassicEditor from '@ckeditor/ckeditor5-build-classic';
import UploadAdapter from './UploadAdapter';
interface IProps {
value: string;
onChange: (v: string) => void;
}
const MyUploadAdapterPlugin = ( editor ) => {
editor.plugins.get( 'FileRepository' ).createUploadAdapter = ( loader ) => {
// console.info('FileRepository', loader)
return new UploadAdapter(loader);
};
};
const editorConfiguration = {
extraPlugins: [ MyUploadAdapterPlugin ]
};
const Editor = (props: IProps) => {
return (
<div className="editor">
<CKEditor
editor={ ClassicEditor }
config={ editorConfiguration }
data={ props.value }
onInit={ editor => {
// You can store the "editor" and use when it is needed.
console.log( 'Editor is ready to use!', editor );
} }
onChange={ ( event, editor ) => {
const data = editor.getData();
console.log( { event, editor, data } );
props.onChange(data);
} }
onBlur={ ( event, editor ) => {
console.log( 'Blur.', editor );
} }
onFocus={ ( event, editor ) => {
console.log( 'Focus.', editor );
} }
/>
</div>
);
}
Editor.defaultProps = {
value: '<p>Input Here!</p>'
}
export default Editor;
2、自定义 class UploadAdapter
要点说明:
-
qiniuUpload
:自定义集成七牛 SDK
的实例对象,可参见3. @utils/qiniuUpload
-
interface IUploadPromiseData{}
:default 必须为 img 上传后 url,可以增加自定义参数 -
interface IUploadAdapter{}
:-
loader: any;
和upload(): Promise<IUploadPromiseData>;
必须实现 -
token
和domain
两个参数可以根据需求进行调整,如集成到qiniuUpload
中,或在Editor 组件
中初始化class UploadAdapter
时传入 - 可以根据需求增加其他属性,如图片验证方法等
-
插件源码:
import qiniuUpload from '@utils/qiniuUpload';
import { message } from 'antd';
import { get } from '@utils/request';
import { GET_TOKEN } from '@constants/urls';
interface IUploadPromiseData{
default: string;
}
interface IUploadAdapter{
loader: any;
token: string;
domain: string;
upload(): Promise<IUploadPromiseData>;
}
class UploadAdapter implements IUploadAdapter{
public loader: any;
public token: any;
public domain: any;
constructor(loader: any) {
this.loader = loader;
}
public async upload(): Promise<any> {
const img = await this.loader.file;
if (!this.token) {
const data: any = await get(GET_TOKEN);
this.token = data.token;
this.domain = data.domain;
}
return new Promise((resolve, reject) => {
qiniuUpload.upload(img, this.token, this.domain).then(
(url) => {
resolve({ default: url })
},
(err) => {
message.error(err);
reject();
}
)
});
}
}
export default UploadAdapter;
3. @utils/qiniuUpload
要点说明:
-
const key = Math.floor(Math.random() * 1000000000000);
:这里要注意下,这个key
是文件上传后的name
,文件格式不会变,如果不指定唯一key
,上传同名文件会报错。 - 其他好像没什么需要说的了,都是抄的七牛官方教程
插件源码:
// @ts-nocheck
import * as qiniu from 'qiniu-js';
class QiniuUpload {
public token;
public domain;
public check(file: any) {
// size: kb
const sizeLimit = 200;
if (file.size / 1024 > sizeLimit) {
return `上传文件大小不能超过 ${sizeLimit} KB !`
}
return false;
}
public upload(file, token, domain){
this.token = token;
this.domain = domain;
return new Promise((resovle, reject) => {
const errMsg = this.check(file);
if (errMsg) {
reject(errMsg);
} else {
const key = Math.floor(Math.random() * 1000000000000);
const observable = qiniu.upload(file, key, token, {}, {});
this.subscription = observable.subscribe(
(next)=>{
console.info('Upload next', next)
},
(err)=>{
console.info('Upload err ', err)
reject(err);
},
(res)=>{
console.info('Upload res ', res)
resovle(domain + '/' + res.key, res);
}
)
}
})
}
public cancel() {
this.subscription.unsubscribe() // 上传取消
}
}
export default new QiniuUpload()
4、效果图


你好,我是夏堇菌,目前本职工作是产品方向。
以前做过前端开发,但是丢了一段时间,现在重新开始学。
前端学习路上一起加油 💪💪💪
网友评论