1.0.12 • Published 5 years ago
design-editor v1.0.12
Design Editor
布局编辑器(React)
想法来源于EmailHQ项目,该项目目前使用的邮件模板编辑器是GrapesJS,GrapesJS是一个开源的、多用途的Web Builder框架,它结合了不同的工具和特性,目的是帮助用户在不了解任何编码的情况下构建HTML模板,适合于内容编辑,但是其功能太过复杂(文档简陋),需要进行定制的内容太多,所以使用体验不是很好(过于复杂且专业)。
基于此需求,我用React实现了一个布局编辑器,参照原型为unlayer(一个商业软件,基于服务收费,最高399刀/月)。
该项目完成后,能应用于多个有此需求场景的公司项目中。
使用说明
npm i design-editor
- 由于使用了
iconfont
,需要拷贝node_modules/design-editor/dist/sources
到自己项目的Server目录下; - 由于使用了tinymce导致包体积比较庞大,所以将
tinymce
改为peerDependencies
依赖,可以自己配置externals
外部依赖。如果是本地配置tinymce
,需要自己布署其资源文件,从node_modules/design-editor/dist/skins
拷贝即可;使用cdn的话则可以直接在页面引入,不需要关心资源文件。 - 以下几个模块也改为
peerDependencies
依赖"react": ">=16.0.0",` "react-dom": ">=16.0.0", "classnames": ">=2.0.0", "tinymce": ">=4.9.2", "immutable": ">=3.8.1", "immutable-undo": ">=2.0.0", "mobx": ">=4.6.0", "mobx-react": ">=5.3.6"`
- mentions动态字段提示功能支持Button与Text组件,通过输入#触发,输入之后替换成[keyword],支持键盘上、下、回车操作;
- 图片上传需要自己提供后端服务。
- 支持撤销重做( Ctrl+Z Ctrl+Y )
- 20190930,优化打包体积
20190930,全面支持
TypeScript
,提供了d.ts接口声明属性
属性名 功能 imageUploadUrl 提供图片上传地址 mentions 提供动态字段提示列表 {key,title} (填充key值) enableUndoRedo 是否启动Ctrl+Z Ctrl+Y撤消重做(默认true) contents 默认值为'button','divider','html','image','text','social',可以通过此参数定制需要的内置默认组件 回调方法
方法名 功能 参数 返回值 onRef 用于获取编辑器instance 编辑器instance 无 onUpload 图片上传完成处理数据格式 服务端返回的数据 实际图片地址 onUploadError 捕获图片上传失败异常信息 error: { message: string, errorStack: string } 无 instance方法
方法名 功能 参数 返回值 export 将当前内容转换成html导出 无 html:string getData 获取当前内容的原始数据rawData 无 rawData:Object setData 将原始数据设置回编辑器 rawData:Object 无 undo 撤销 无 无 redo 重做 无 无 关于Content组件扩展
在编码前的设计阶段,我就构想了Content扩展,包括Content图标,标题,编辑区如何展示,如何提供属性编辑器列表等等。 扩展方式如下(以Video为例):
import React from 'react';
import DesignEditor, { Extension, PropertyWidget, PropertyGroup } from 'design-editor';
const { Space, Align, Input, Switch } = PropertyWidget;
class Video extends Extension {
getIconClass() {
return 'icon icon-video';
}
getContentType() {
return 'video';
}
getLabel() {
return 'Video';
}
toHtml(data) {
const { url, containerPadding, textAlign, fullWidth } = data;
const videoStyle = fullWidth ? ` width: 100% ` : ` maxWidth: 100% `;
return `<div style="padding:${containerPadding}">
<div style="text-align:${textAlign}">
<video controls src="${url}" style="${videoStyle}" />
</div>
</div>`;
}
getInitialAttribute() {
return {
containerPadding: '10px',
textAlign: 'center',
fullWidth: false,
url: ''
};
}
getProperties(values, update) {
const { url, textAlign, containerPadding, fullWidth } = values;
return <React.Fragment>
<PropertyGroup title="LINK">
<Input title="Video URL" value={url} attribute="url" desc="Add a YouTube or Vimeo URL to automatically generate a preview image. The image will link to the provided URL." onUpdate={update} />
</PropertyGroup>
<PropertyGroup title="SPACING">
<Switch title="Full Width" checked={fullWidth} attribute="fullWidth" onUpdate={update} />
<Align title="Align" align={textAlign} onUpdate={update} />
</PropertyGroup>
<PropertyGroup title="GENERAL">
<Space title="Container Padding" value={containerPadding} attribute="containerPadding" onUpdate={update} />
</PropertyGroup>
</React.Fragment>
}
render() {
const { url, containerPadding, textAlign, fullWidth } = this.props;
const videoStyle = fullWidth ? { width: '100%' } : { maxWidth: '100%' };
return <div className="ds_content_video"
style={{
padding: containerPadding,
}}
>
<div style={{
textAlign
}}>
{url ? <video controls src={url} style={videoStyle} /> : <p><i className="icon icon-play-button"></i></p>}
</div>
</div>;
}
}
export default Video;
然后,直接将Video组件放置于DesignEditor组件内部即可,如有多个扩展,显示时会按照放置顺序进行输出:
<DesignEditor
imageUploadUrl="http://localhost:3001/NewUserFeedback/upload"
mentions={[
{ key: 'key', title: 'title' },
]}
onUpload={data => data.fileUrl}
onUploadError={error => console.log('5555', error.message)}
onRef={(obj) => { instance = obj; window.instance = obj; }}>
<ExtensionGroup title="Custom Group">
<Video />
</ExtensionGroup>
</DesignEditor>
之所以继承自Extension类,是因为需要规范几个方法,如下所示:
Extension方法
方法名 | 功能 | 参数 | 返回值 |
---|---|---|---|
getIconClass | 提供扩展图标样式 | 无 | iconClass:string |
getLabel | 提供扩展标题 | 无 | label:string |
getContentType | 提供扩展类型名称(需要保证唯一,除button divider html image text social外) | 无 | contentType:string |
toHtml | 提供toHtml转换功能 | 扩展的所有属性 | 根据属性生成扩展html片段 |
getInitialAttribute | 提供初始属性对象 | 无 | Attribute:Object |
getProperties | 提供属性编辑器片段 | (values: Object 属性对象, update:(key, value) => {} 更新方法) | ReactNode |
render | 提供渲染片段 | props: { ...所有扩展的属性, focus: boolean 编辑区域中是否选中当前扩展 } | ReactNode |
如果觉得默认组件内置的toHtml片段满足不了需求或是需要更多属性编辑,可以在继承自原有组件的基础上加入自己个性化的东西
属性编辑组件列表
内置一些属性编辑组件如下:
组件 | 功能 | 使用示例 |
---|---|---|
Link | 配置链接 | <Link link={link} linkType={linkType} title="Button Link" onUpdate={update} /> |
Colors | 配置四项颜色,color+backgroundColor+hoverColor+hoverBackgroundColor(可选) | <Colors title="Colors" colors={{ color, backgroundColor, hoverColor, hoverBackgroundColor }} onUpdate={update} /> |
Align | 对齐 | <Align align={textAlign} onUpdate={update} /> |
LineHeight | 行高 | <LineHeight lineHeight={lineHeight} onUpdate={update} /> |
BorderRadius | 圆角 | <BorderRadius borderRadius={borderRadius} onUpdate={update} /> |
Color | 颜色 | <Color title="Color" value={color} attribute="color" onUpdate={update} /> |
Switch | toggle开关 | <Switch title="Full Width" checked={fullWidth} attribute="fullWidth" onUpdate={update} /> |
Space | 四周空间配置,用于margin padding等 | <Space title="Padding" value={padding} attribute="padding" onUpdate={update} /> |
Slide | 滑块 | <Slide title="Width" attribute="width" value={width} onUpdate={update} /> |
Line | 边框效果配置,包括边框样式颜色与粗细 | <Line title="Line" lineWidth={lineWidth} lineStyle={lineStyle} lineColor={lineColor} onUpdate={update} /> |
HtmlEditor | Html源码编辑 | <HtmlEditor style={{ margin: '-15px -20px' }} value={html} onChange={(value) => { update('html', value) }} /> |
Input | 普通输入框,参见Image的Url | <Input addOn="URL" onChange={(e) => { onUpdate('link', e.target.value) }} value={link} /> <Input title="Video URL" value={url} attribute="url" desc="Add a YouTube or Vimeo URL to automatically generate a preview image. The image will link to the provided URL." onUpdate={update} /> |
ImageEditor | 图片上传组件 | <ImageEditor key={values._meta.guid} attribute="url" onUpdate={update} /> |
NumberItem | 左右加减操作数字 | <NumberItem title="Content Width" value={width} attribute="width" onUpdate={onUpdate} /> |
Font | 字体选择 | <Font title="Font Family" fontFamily={fontFamily} onUpdate={onUpdate} /> |
若有其它需求,需要另行开发。