德阳市做网站,网站模板案例,自己的网站如何做推广,好听的建筑公司名字大全效果展示 项目概述 svg flow editor 是一款流程图编辑器#xff0c;提供了一系列流程图交互、编辑所必需的功能#xff0c;支持前端研发自定义开发各种逻辑编排场景#xff0c;如流程图、ER 图、BPMN 流程等。 目前也有比较好的流程图设计框架#xff0c;但是还是难满足项目…效果展示 项目概述 svg flow editor 是一款流程图编辑器提供了一系列流程图交互、编辑所必需的功能支持前端研发自定义开发各种逻辑编排场景如流程图、ER 图、BPMN 流程等。 目前也有比较好的流程图设计框架但是还是难满足项目个性化定制BMPN.js、Jsplumb 的拓展能力不足自定义节点支持成本很高。
技术选型 本项目使用typescript与svg、canvas等技术进行搭建脱离vue、react等框架的限制使得用户更快、更轻松融合到自己的项目中在底层结合typescript使得数据类型得到更加健壮、完整的支持对图形元组使用 svg 技术进行绘制使得用户操作、底层实现更加轻松同时对其他模块背景网格、水印使用了canvas技术进行绘制。
功能规划 本项目大体功能模块如下
background 背景 背景模块支持网格绘制、水印的绘制、水印定制化配置等
graph graph 是系统交互的核心元素支持Rect矩形、Circle圆形、Ellipse椭圆、Polygon多边形、Diamond菱形、Triangle三角形、Text文本、HTMLHTML元素、Image图片、Line线等多种类型后期会考虑慢慢完善元件库
websocket websocket 是用于处理用户协同的模块
graphData graph Data 是双向绑定的数据管理模块
tools 工具模块包含图片导出、一键美化、层级处理、布局方式、元件组合、辅助线等
apis API 是外部访问内部实现执行动作、获取数据的窗口并在设计上提供了command、adapt 两个类在command中隔离内部对象通过调用adapt实现数据的处理放置用户通过command对象对内部对象进行风险操作
event 提供统一的事件处理机制支持对内部事件的监听、外部事件的注册等同时还对graph元件的统一事件进行处理例如元件的点击事件、双击事件等
history 历史记录管理模块支持 redo undo version 等历史相关操作 项目架构 项目对外暴露基础操作例如 svg 构造器、command api操作、event事件中心以及全局api通过暴露对象 sfEditor实现对内部的数据访问、对象操作等。在核心模块中需要考虑用户的使用习惯封装完整的工具类实现流程图的基本操作、拓展功能。底层依赖了svg对项目元件库的基础元件进行创作同时使用了canvas对背景网格、水印等进行绘制使用html进行页面布局并且提供了typescript的全类型支持。 在API设计的设计上采取了Command CommandAdapt 两个类实现Command中不进行用户方法的直接处理增加adapt类进行方法中转防止用户通过API直接操作核心类。Command调用 adapt 的实例方法在adapt 中获取draw、svg 等核心类进行用户的响应。 未来的功能模块规划中还是以协同为核心重点。
项目结构说明 如上图核心类在 core 中index.ts 向外暴露了APImain.ts 则是测试结果的入口文件interface是类型文件命名上基本上都是按功能模块走的。
Graph 实体类
构建 svg 对象
export class SVG {private xmlns!: string;private svg: Element;private svgID!: string;private draw: Draw; // 绘制实例private graphOption: IGraphOption | undefined;constructor(graphOption?: IGraphOption) {this.draw new Draw();this.svgID getNanoid();this.graphOption graphOption;//SVG命名空间this.xmlns graphOption?.xmlns || http://www.w3.org/2000/svg;// 1. 判断是否存在当前命名空间的svgconst svgElement this.draw.getSvg(this.xmlns);// 2. 如果存在 则保存if (svgElement) throw new Error(messageInfo.isHaveSvgElement); // 如果已经存在相同xmlns属性的svg 则报错// 3. 不存在 则创建新的 svgthis.svg this.draw.createSvg(this.xmlns, this.svgID);}// 将当前创建的svg添加到html DOM 的节点上public addTo(container: string | Element) {this.draw.addTo(container, this.svg); // 添加到指定容器this.size(); // 设置默认大小const { gridLines, waterMark, waterMarkText } this.graphOption || {};if (gridLines ! false) this.draw.gridLines(); // 绘制网格if (waterMark ! false) this.draw.waterMark(waterMarkText); // 绘制水印return this; // 返回 this 供链式调用}// 设置当前 svg 的大小public size(width?: number, height?: number) {this.svg.setAttribute(width, width?.toString() || 100%);this.svg.setAttribute(height, height?.toString() || 100%);return this;} 相关的draw方法
import { messageInfo } from ../Message;// 绘制、DOM 操作的核心类 尽量将所有的DOM操作都汇集在该类中防止多处操作DOM引起的其他问题
export class Draw {constructor() {}// 通过指定的 xmlns 获取 svgpublic getSvg(xmlns: string) {return document.querySelector(svg[xmlns${xmlns}]);}// 创建 svgpublic createSvg(xmlns: string, svgID: string) {const svg document.createElementNS(xmlns, svg);svg.setAttribute(ID, svgID);svg.setAttribute(xmlns, xmlns);svg.setAttribute(version, 1.1);svg.setAttribute(baseProfile, full);return svg;}// 将创建 svg 添加到指定容器public addTo(container: string | Element, svg: Element) {const type typeof container string;// 判断传入参数是选择器还是domlet dom type ? document.querySelector(container) : container;dom?.appendChild(svg);}// 绘制网格线public gridLines() {console.log(gridLines);}// 绘制水印public waterMark(waterMarkText?: string) {const text waterMarkText || messageInfo.waterMarkText;}// 清除网格线public clearGridLines() {}// 清除水印public clearWaterMark() {}
}构建 Rect 类
import { Common } from ./Common;
import { SVG } from ./index;// 矩形类
export class Rect extends Common {private svg: SVG; // 根元素 svgprivate rect: Element;constructor(svg: SVG, width: number, height: number) {super();this.svg svg;this.rect super.getDraw().createRect(svg.getSvgXmlns());// 设置宽高this.setAttribute(width, height);// 将当前创建的元件添加到 svg 下super.addToSvg(this);}// 独有属性设置private setAttribute(width: number, height: number) {this.rect.setAttribute(width, width.toString());this.rect.setAttribute(height, height.toString());}// 获取基本Elementpublic getElement() {return this.rect;}// 获取 xmlnspublic getXmlns() {return this.svg.getSvgXmlns();}
}抽离公共类 svg 元件具有的公共方法例如 设置位置信息、设置宽高、设置样式等还有事件处理机制都是每一个元件都拥有的方法属性因此抽离为独立的类实现 元件集成即可。
// svg 元件公共类import { IGraphAttributes } from ../../interface/Graph;
import { Draw } from ../Draw;
import { Rect } from ./Rect;// 定义元件类型
type IGraph Rect;export class Common {private draw: Draw;constructor() {this.draw new Draw();}// 设置元件IDpublic setID() {}// 获取IDpublic getID() {const element (this as unknown as IGraph).getElement();return this.draw.getID(element);}// 将创建的元件 添加到 svg 下protected addToSvg(graph: IGraph) {// 创建了基本元件后需要构建 g 分组方便处理 hover 及 click 的锚点const xmlns graph.getXmlns();const element graph.getElement();const nodeID graph.getID() as string;// 1. 获取分组const group this.draw.createGroup(element, xmlns, nodeID);// 2. 获取当前的 svg 根元素const svg this.draw.getSvg(xmlns);// 3. 初始化默认属性this.attr.call(graph, {});// 3. 将当前分组添加到根元素上this.draw.addTo(svg as Element, group);}// 设置位置public position(x: number, y: number) {const graph this as unknown as IGraph;const element graph.getElement();// 因为设置位置属性的时候不同的元素不一致因此需要建立 原型与属性的映射const { tagName } element;const attrMap: { [key: string]: string[] } {rect: [x, y],circle: [cx, cy],ellipse: [cx, cy],};element.setAttribute(attrMap[tagName][0], x.toString());element.setAttribute(attrMap[tagName][1], y.toString());// 重新渲染this.draw.updateLinkAnchorPoint(graph.getID() as string,element,graph.getXmlns());return this;}// 设置属性public attr({ stroke, fill }: IGraphAttributes) {// 设置样式const graph this as unknown as IGraph;const element graph.getElement();element.setAttribute(stroke, stroke || black);element.setAttribute(fill, fill || #F2F2F2);return this;}// 获取 draw 操作对象protected getDraw() {return this.draw;}
}实现效果 公共事件处理机制 Common.ts// 为所有的子类构造事件public click!: (_fun: Function) IGraph;public dblclick!: (_fun: Function) IGraph;public mousedown!: (_fun: Function) IGraph;public mousemove!: (_fun: Function) IGraph;public mouseup!: (_fun: Function) IGraph;public mouseover!: (_fun: Function) IGraph;public mouseout!: (_fun: Function) IGraph;// 初始化公共事件private initCommonEvent(graph: IGraph) {/*** 事件处理机制 不管用户有没有添加 click 都需要实现 addEventListener*/const eventList: IEventList {click: (e: Event, graph: IGraph) this.commonEvent.click(e, graph),};const element graph.getElement();Object.keys(eventList).forEach((eventname) {let userfun: null | Function;// ts-ignore 用户自定义事件graph[eventname] (_fun: Function | null) {userfun _fun;return graph;};// 给元素添加事件element.addEventListener(eventname, (e) {// 1. 先执行默认事件eventList[eventname](e, graph);// 在这里处理用户自定义的事件userfun userfun(e);// 阻止事件冒泡e.preventDefault();});});}
全局指令
// 暴露对外操作API 需要经过 Command Adapt的中转防止用户直接通过 Command 获取到内部对象
import { Draw } from ../Draw;
import { CommandAdapt } from ./CommandAdapt;export class Command {// 测试设置水印public executeWatermark: CommandAdapt[watermark];constructor(draw: Draw) {const adapt new CommandAdapt(draw);this.executeWatermark adapt.watermark.bind(adapt);}
}
import { Draw } from ../Draw;// Command Adapt API 操作核心库
export class CommandAdapt {private draw: Draw;constructor(draw: Draw) {this.draw draw;}public watermark() {console.log(watermark);}
}事件机制 事件处理中主要使用event Bus 实现
export class EventBusEventMap {private eventHub: Mapstring, SetFunctionconstructor() {this.eventHub new Map()}public onK extends string keyof EventMap(eventName: K,callback: EventMap[K]) {if (!eventName || typeof callback ! function) returnconst eventSet this.eventHub.get(eventName) || new Set()eventSet.add(callback)this.eventHub.set(eventName, eventSet)}public emitK extends string keyof EventMap(eventName: K,payload?: EventMap[K] extends (payload: infer P) void ? P : never) {if (!eventName) returnconst callBackSet this.eventHub.get(eventName)if (!callBackSet) returnif (callBackSet.size 1) {const callBack [...callBackSet]return callBack[0](payload)}callBackSet.forEach(callBack callBack(payload))}public offK extends string keyof EventMap(eventName: K,callback: EventMap[K]) {if (!eventName || typeof callback ! function) returnconst callBackSet this.eventHub.get(eventName)if (!callBackSet) returncallBackSet.delete(callback)}public isSubscribeK extends string keyof EventMap(eventName: K): boolean {const eventSet this.eventHub.get(eventName)return !!eventSet eventSet.size 0}
}总结 至此整体项目的框架已经跑通了包括API的封装command adapt、事件处理机制、svg元件构建本文先处理这么多事情。