Skip to content

Latest commit

 

History

History
370 lines (297 loc) · 10.5 KB

插件开发文档.md

File metadata and controls

370 lines (297 loc) · 10.5 KB

插件开发

插件又名自定义图层,可以进行自由扩展,插件文件目录结构:

|-pluginName 插件文件夹名称
│  ├─Options.tsx 设置区域的配置
│  ├─Layer.tsx 图层模块
│  ├─LayerData.ts 图层对应的初始化数据
│  ├─types.ts ts类型描述
│  ├─config.ts 插件的配置文件
│  └─index.ts 入口

插件必须在 plugins/index.ts 中引入和添加到 layers 数组中

插件文件说明

types.ts

插件的数据 TS 类型定义,所有的图层都需要继承BaseLayertype是必须要定义的字段,必须和 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]; // 圆角
}

Layer.tsx

插件渲染模块,该模块会在渲染内核中执行,组件参数为 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;
}

LayerData.ts

顾名思义,这个数据是为了创建插件数据使用的。我们只需要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.ts

选中元素的时候,右侧会显示参数修改面板,这里的 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: '修改二维码',
});

config.ts

插件的配置文件,后续这个配置文件会进行扩展

const config = {
  version: '1.0.0', // 版本号
  pid: 'qrcode', // 插件id,必须和LayerData中的type保持一致
};

export default config;

index.ts

以上模块开发好之后,我们需要将这些模块暴露出去给外部使用。所以定义了一个 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('作者还没时间搞,等你贡献代码');
}