插件又名自定义图层,可以进行自由扩展,插件文件目录结构:
|-pluginName 插件文件夹名称
│ ├─Options.tsx 设置区域的配置
│ ├─Layer.tsx 图层模块
│ ├─LayerData.ts 图层对应的初始化数据
│ ├─types.ts ts类型描述
│ ├─config.ts 插件的配置文件
│ └─index.ts 入口
插件必须在 plugins/index.ts 中引入和添加到 layers 数组中
插件的数据 TS 类型定义,所有的图层都需要继承BaseLayer
,type
是必须要定义的字段,必须和 config 中的pid
保持一致且一定
是唯一的,不能重复的。
参考二维码插件的 types.ts 定义:
import type { BaseLayer } from '@core/types/data';
// 二维码
export interface QrcodeLayer extends BaseLayer {
type: 'qrcode';
// 镜像翻转,上下,左右
flipx: boolean;
flipy: boolean;
// 宽高
width: number;
height: number;
// qrcode的内容
content: string;
lightcolor: string; // 前景色
darkcolor: string; // 背景色
cornerRadius: [number, number, number, number]; // 圆角
}
插件渲染模块,该模块会在渲染内核中执行,组件参数为 LayerProps (系统默认注入的 props),参数说明如下:
参数 | 说明 | 类型 | 默认值 | 必填 |
---|---|---|---|---|
hide | 元素是否隐藏 | boolean | false | ✔ |
lock | 元素是否锁定 | boolean | false | ✔ |
zIndex | 元素当前的层级 | number | - | ✔ |
isChild | true 表示元素已编组 | boolean | - | ✔ |
layer | 当前图层的数据 | BaseLayer | - | ✔ |
dirty | 是否需要强制更新 | boolean | - | ✔ |
parent | 父元素 | Group | - | ✔ |
store | Store 实例 | Store | - | ✔ |
env | ENV 运行环境 | 'preview','editor' | 'editor' | ✔ |
组件主要使用 Leaferjs 进行的封装,所以这里我们如果要开发自己的插件,需要先了解 Leaferjs,下面使用一个简单的示例做说明:
// 二维码插件
import React, { useEffect, useMemo } from 'react';
import { Leafer, Box, Image, Rect } from 'leafer-ui';
import { LayerProps } from '@core/types/helper';
import useLayerBaseStyle from '@core/hooks/useLayerBaseStyle';
import { QrcodeLayer } from './types';
import QRCode from 'qrcode';
import { debounce } from 'lodash';
export default function QrcodeComp(props: LayerProps) {
const layer = props.layer as QrcodeLayer;
const imgUI = useMemo<Image>(() => {
// 创建图片UI
const img = new Image({
editable: props.isChild ? false : true,
url: '',
x: layer.x,
y: layer.y,
width: layer.width,
height: layer.height,
rotation: layer.rotation,
opacity: layer.opacity,
cornerRadius: [...layer.cornerRadius],
shadow: { ...layer.shadow },
stroke: layer.border.stroke,
dashPattern: layer.border.dashPattern,
dashOffset: layer.border.dashOffset,
strokeWidth: layer.border.visible ? layer.border.strokeWidth : 0,
});
// 添加到父元素,这里的父元素可能是根元素,也可能是组元素
props.parent!.add(img as any);
// 设置ID,必填
img.id = layer.id;
// 设置zIndex
img.zIndex = props.zIndex;
return img;
}, []);
// 公共use,主要是处理阴影、zIndex、x、y、lock、hide等参数
useLayerBaseStyle(layer, imgUI as any, props.store, props.zIndex);
// 监听数据变化
useEffect(() => {
// 宽高的参数之所以没放到useLayerBaseStyle进行统一处理是因为一些组件没有宽高,在我们的BaseLayer中也没有设计宽高字段
imgUI.width = layer.width;
imgUI.height = layer.height;
// 翻转
if (layer.flipx) {
imgUI.scaleX = -1;
} else {
imgUI.scaleX = 1;
}
if (layer.flipy) {
imgUI.scaleY = -1;
} else {
imgUI.scaleY = 1;
}
//圆角
imgUI.cornerRadius = layer.cornerRadius ? [...layer.cornerRadius] : undefined;
}, [layer.width, layer.height, layer.flipx, layer.flipy, layer.cornerRadius]);
// 监听二维码参数变化
useEffect(() => {
// 二维码参数设置
const options = {
width: layer.width,
margin: 1,
color: {
light: layer.lightcolor,
dark: layer.darkcolor,
},
}
// 创建二维码
QRCode.toDataURL(layer.content || 'null', {...options}).then(url => (imgUI.url = url));
// 控制器变化的时候会触发此函数
props.store.controlScaleFuns[layer.id] = debounce(() => {
QRCode.toDataURL(layer.content || 'null', {...options}).then(url => (imgUI.url = url));
}, 500);
return () => {
// 组件销毁的时候要删除函数的引用
delete props.store.elementDragUp[layer.id];
};
}, [layer.content, layer.lightcolor, layer.darkcolor]);
// 组件销毁,移除掉画布中的元素
useEffect(() => {
return () => {
imgUI.remove();
// imgBox.destroy();
};
}, []);
return null;
}
顾名思义,这个数据是为了创建插件数据使用的。我们只需要implements
之前在 types.ts 中定义的数据类型,创建一个新的类即可
import { IBlendMode, IShadowEffect } from '@leafer-ui/interface';
import { util } from '@utils/index';
import { QrcodeLayer } from './types';
// 二维码数据类
export default class QrcodeData implements QrcodeLayer {
type: 'qrcode' = 'qrcode';
width: number = 500;
height: number = 500;
cornerRadius: [number, number, number, number] = [0, 0, 0, 0];
flipx: boolean = false;
flipy: boolean = false;
id: string = util.createID();
name: string = '图片元素';
desc: string = '描述信息';
x: number = 0;
y: number = 0;
blur: number = 0;
border: { stroke: string; dashPattern?: number[]; dashOffset?: number; strokeWidth: number; visible: boolean } = {
stroke: 'rgba(0,0,0,1)',
strokeWidth: 2,
visible: false,
};
blendMode: IBlendMode = 'normal';
opacity: number = 1;
rotation: number = 0;
shadow: IShadowEffect = { x: 0, y: 0, blur: 0, color: 'rgba(0,0,0,0.0)' };
content: string = '请输入内容';
lightcolor: string = '#ffffff';
darkcolor: string = '#000000';
_dirty: string = '1';
_lock: boolean = false;
_hide: boolean = false;
_unKeepRatio?: boolean;
_ratio?: number;
extend?: any;
constructor(params: Partial<QrcodeLayer> = {}) {
Object.assign(this, params);
}
}
LayerData 的使用:
const exLayer = exLayers.find(d => d.config.pid === '插件id');
if (exLayer) {
// 创建数据
const ndata = new exLayer.LayerData();
}
选中元素的时候,右侧会显示参数修改面板,这里的 Options 组件就是面板中的内容。我们可以自定义面板内容。另外我们
在@options/index
中预置了很多编辑区域可用的组件(颜色,布局,透明度,坐标,大小,旋转角度设置等等),可以快速协助我们做
插件的开发。
二维码插件的设置区域代码(其中只有 QrOption 组件是我们自己开发的,其他组件都是系统提供的):
import { editor } from '@stores/editor';
import { observer } from 'mobx-react';
import { Align, Opacity, Rotation, Size, Position, Filter, FlipXY, Shadow, Blur, Radius, Border } from '@options/index';
import { Tabs, TabPane } from '@douyinfe/semi-ui';
import QrOption from './QrOption';
export interface IProps {
element: any;
}
function Options(props: IProps) {
return (
<Tabs
className="optionTabs"
activeKey={editor.elementOptionType}
onChange={e => {
editor.elementOptionType = e as any;
}}
>
<TabPane tab="元素设置" itemKey="basic">
<div className={'scroll scrollBox'}>
<FlipXY />
<QrOption />
<Opacity />
<Shadow />
<Border />
<Radius />
<Position />
<Size />
<Rotation />
</div>
</TabPane>
<TabPane tab="混合模式" itemKey="colour">
<Filter />
</TabPane>
</Tabs>
);
}
export default observer(Options);
我们在开发插件的设置区域的时候,需要了解一个 editor 实例,该实例下面封装了一些方法,可以帮助我们控制视图和面板参数:
例如:
// 获取当前选中的元素
const elementData = editor.getElementData() as QrcodeLayer;
// 修改数据
elementData.content = '123456';
// 更新视图
editor.updateCanvas();
// 保存操作记录
editor.record({
type: 'update',
desc: '修改二维码',
});
插件的配置文件,后续这个配置文件会进行扩展
const config = {
version: '1.0.0', // 版本号
pid: 'qrcode', // 插件id,必须和LayerData中的type保持一致
};
export default config;
以上模块开发好之后,我们需要将这些模块暴露出去给外部使用。所以定义了一个 index.ts 文件,具体写法如下:
import Layer from './Layer';
import LayerData from './LayerData';
import Options from './Options';
import type * as types from './types';
import config from './config';
export { Layer, LayerData, Options, config };
export type { types };
这是非常重要的一步,我们在开发好一个插件之后,需要手动将其添加 plugins/index.ts 中,例如:
import * as qrcode from './qrcode';
import * as barcode from './barcode';
const layers = [qrcode, barcode];
export default layers;
我们在开发完插件后,可以在editor/components/sources/more/More.tsx
中把插件作为一个扩展添加到项目中,当然,你可以把插件
的使用入口添加到其他地方。
// 通过ID找到我们的插件
const exLayer = exLayers.find(d => d.config.pid === '插件id');
if (exLayer) {
// 创建数据
const ndata = new exLayer.LayerData();
// 插入到页面中
editor.pageData.layers.unshift(ndata);
// 更新视图
editor.updateCanvas();
// 设置选中状态
editor.store.emitControl([ndata.id]);
} else {
Toast.warning('作者还没时间搞,等你贡献代码');
}