低代码/无代码平台开发实战:从拖拽引擎到企业级平台架构

前言

💡 痛点: 企业重复开发相似的 CRUD 页面?业务人员需求排队等开发?定制化需求变更频繁导致维护成本爆炸?传统开发模式无法快速响应市场变化?

🎯 解决方案: 基于可视化拖拽引擎 + 插件化架构 + 自定义组件体系,构建企业级低代码平台,让业务人员能自助搭建应用,开发者专注平台能力建设。
#mermaid-svg-C1ZZis6RuWKKh5us{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-C1ZZis6RuWKKh5us .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-C1ZZis6RuWKKh5us .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-C1ZZis6RuWKKh5us .error-icon{fill:#552222;}#mermaid-svg-C1ZZis6RuWKKh5us .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-C1ZZis6RuWKKh5us .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-C1ZZis6RuWKKh5us .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-C1ZZis6RuWKKh5us .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-C1ZZis6RuWKKh5us .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-C1ZZis6RuWKKh5us .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-C1ZZis6RuWKKh5us .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-C1ZZis6RuWKKh5us .marker{fill:#333333;stroke:#333333;}#mermaid-svg-C1ZZis6RuWKKh5us .marker.cross{stroke:#333333;}#mermaid-svg-C1ZZis6RuWKKh5us svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-C1ZZis6RuWKKh5us p{margin:0;}#mermaid-svg-C1ZZis6RuWKKh5us .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-C1ZZis6RuWKKh5us .cluster-label text{fill:#333;}#mermaid-svg-C1ZZis6RuWKKh5us .cluster-label span{color:#333;}#mermaid-svg-C1ZZis6RuWKKh5us .cluster-label span p{background-color:transparent;}#mermaid-svg-C1ZZis6RuWKKh5us .label text,#mermaid-svg-C1ZZis6RuWKKh5us span{fill:#333;color:#333;}#mermaid-svg-C1ZZis6RuWKKh5us .node rect,#mermaid-svg-C1ZZis6RuWKKh5us .node circle,#mermaid-svg-C1ZZis6RuWKKh5us .node ellipse,#mermaid-svg-C1ZZis6RuWKKh5us .node polygon,#mermaid-svg-C1ZZis6RuWKKh5us .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-C1ZZis6RuWKKh5us .rough-node .label text,#mermaid-svg-C1ZZis6RuWKKh5us .node .label text,#mermaid-svg-C1ZZis6RuWKKh5us .image-shape .label,#mermaid-svg-C1ZZis6RuWKKh5us .icon-shape .label{text-anchor:middle;}#mermaid-svg-C1ZZis6RuWKKh5us .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-C1ZZis6RuWKKh5us .rough-node .label,#mermaid-svg-C1ZZis6RuWKKh5us .node .label,#mermaid-svg-C1ZZis6RuWKKh5us .image-shape .label,#mermaid-svg-C1ZZis6RuWKKh5us .icon-shape .label{text-align:center;}#mermaid-svg-C1ZZis6RuWKKh5us .node.clickable{cursor:pointer;}#mermaid-svg-C1ZZis6RuWKKh5us .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-C1ZZis6RuWKKh5us .arrowheadPath{fill:#333333;}#mermaid-svg-C1ZZis6RuWKKh5us .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-C1ZZis6RuWKKh5us .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-C1ZZis6RuWKKh5us .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-C1ZZis6RuWKKh5us .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-C1ZZis6RuWKKh5us .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-C1ZZis6RuWKKh5us .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-C1ZZis6RuWKKh5us .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-C1ZZis6RuWKKh5us .cluster text{fill:#333;}#mermaid-svg-C1ZZis6RuWKKh5us .cluster span{color:#333;}#mermaid-svg-C1ZZis6RuWKKh5us div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-C1ZZis6RuWKKh5us .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-C1ZZis6RuWKKh5us rect.text{fill:none;stroke-width:0;}#mermaid-svg-C1ZZis6RuWKKh5us .icon-shape,#mermaid-svg-C1ZZis6RuWKKh5us .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-C1ZZis6RuWKKh5us .icon-shape p,#mermaid-svg-C1ZZis6RuWKKh5us .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-C1ZZis6RuWKKh5us .icon-shape .label rect,#mermaid-svg-C1ZZis6RuWKKh5us .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-C1ZZis6RuWKKh5us .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-C1ZZis6RuWKKh5us .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-C1ZZis6RuWKKh5us :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 部署层
扩展层
后端服务层
前端引擎层
可视化编辑器

拖拽画布/属性面板
组件库

基础组件/业务组件
Schema 引擎

JSON 配置解析
API 网关

统一接口管理
流程引擎

BPMN 工作流
数据模型

动态 Schema
插件系统

自定义组件/钩子
组件市场

共享生态
多环境部署

开发/测试/生产
版本管理

Git 集成/回滚


一、可视化拖拽引擎核心架构

1.1 Schema 驱动的设计思想

typescript 复制代码
// schema 核心类型定义
interface ComponentSchema {
  id: string;                    // 组件唯一 ID
  type: string;                  // 组件类型 button/input/table
  props: Record<string, any>;    // 组件属性
  style: React.CSSProperties;    // 内联样式
  events: EventConfig[];         // 事件配置
  children?: ComponentSchema[];  // 子组件(容器类)
}

interface EventConfig {
  trigger: 'click' | 'change' | 'submit';
  action: 'api' | 'navigation' | 'script';
  config: Record<string, any>;
}

interface PageSchema {
  version: string;
  pageId: string;
  name: string;
  components: ComponentSchema[];
  globalSettings: {
    apiBaseUrl: string;
    authConfig: AuthConfig;
    theme: ThemeConfig;
  };
}

// Schema 示例:一个完整的表单页面
const formPageSchema: PageSchema = {
  version: '1.0.0',
  pageId: 'user-create-form',
  name: '用户创建表单',
  components: [
    {
      id: 'root-container',
      type: 'container',
      props: { layout: 'vertical', gap: 16 },
      style: { padding: '24px' },
      events: [],
      children: [
        {
          id: 'username-input',
          type: 'input',
          props: {
            label: '用户名',
            placeholder: '请输入用户名',
            required: true,
            validator: 'username',
          },
          style: {},
          events: [
            {
              trigger: 'change',
              action: 'api',
              config: {
                url: '/api/validate/username',
                method: 'POST',
                debounce: 500,
              },
            },
          ],
        },
        {
          id: 'submit-btn',
          type: 'button',
          props: { text: '提交', type: 'primary' },
          style: {},
          events: [
            {
              trigger: 'click',
              action: 'api',
              config: {
                url: '/api/users',
                method: 'POST',
                onSuccess: 'navigate:/users/list',
              },
            },
          ],
        },
      ],
    },
  ],
  globalSettings: {
    apiBaseUrl: 'https://api.example.com',
    authConfig: { type: 'jwt', tokenKey: 'Authorization' },
    theme: { primaryColor: '#1890ff' },
  },
};

1.2 拖拽引擎实现

typescript 复制代码
// drag-engine.ts - 拖拽引擎核心
import { ref, reactive, computed } from 'vue'; // 或 React 版本

export class DragEngine {
  // 画布状态
  private canvasState = reactive({
    components: [] as ComponentSchema[],
    selectedId: null as string | null,
    dragging: null as DragItem | null,
  });

  // 拖拽开始
  onDragStart(event: DragEvent, componentType: string, defaultProps: Record<string, any>) {
    const dragItem: DragItem = {
      type: 'new',
      componentType,
      defaultProps,
      offsetX: event.offsetX,
      offsetY: event.offsetY,
    };
    event.dataTransfer?.setData('application/json', JSON.stringify(dragItem));
    this.canvasState.dragging = dragItem;
  }

  // 拖拽进入画布
  onDragOver(event: DragEvent) {
    event.preventDefault();
    event.dataTransfer!.dropEffect = 'copy';
    // 计算预览位置
    this.updateDropIndicator(event.clientX, event.clientY);
  }

  // 放置组件
  onDrop(event: DragEvent, parentId?: string) {
    event.preventDefault();
    const dragData = event.dataTransfer?.getData('application/json');
    if (!dragData) return;

    const dragItem: DragItem = JSON.parse(dragData);
    const newComponent = this.createComponent(dragItem, parentId);

    // 计算放置位置
    const position = this.calculateDropPosition(event.clientX, event.clientY, parentId);

    // 插入组件树
    this.insertComponent(newComponent, position);

    // 选中新组件
    this.selectComponent(newComponent.id);

    this.canvasState.dragging = null;
  }

  // 创建组件实例
  private createComponent(item: DragItem, parentId?: string): ComponentSchema {
    const id = `comp_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
    return {
      id,
      type: item.componentType,
      props: { ...item.defaultProps },
      style: {},
      events: [],
      children: isContainer(item.componentType) ? [] : undefined,
    };
  }

  // 移动组件
  moveComponent(componentId: string, newParentId: string, index: number) {
    const component = this.findComponent(componentId);
    if (!component) return;

    // 从原位置移除
    this.removeComponent(componentId);

    // 插入新位置
    this.insertComponent(component, { parentId: newParentId, index });
  }

  // 更新组件属性
  updateComponentProps(componentId: string, props: Record<string, any>) {
    const component = this.findComponent(componentId);
    if (component) {
      component.props = { ...component.props, ...props };
      this.emitChange();
    }
  }

  // 撤销/重做
  private history: PageSchema[] = [];
  private historyIndex = -1;

  undo() {
    if (this.historyIndex > 0) {
      this.historyIndex--;
      this.restoreState(this.history[this.historyIndex]);
    }
  }

  redo() {
    if (this.historyIndex < this.history.length - 1) {
      this.historyIndex++;
      this.restoreState(this.history[this.historyIndex]);
    }
  }

  private emitChange() {
    // 保存历史
    this.history = this.history.slice(0, this.historyIndex + 1);
    this.history.push(this.exportSchema());
    this.historyIndex++;

    // 触发变更事件
    this.onChange?.(this.exportSchema());
  }
}

1.3 画布渲染器

tsx 复制代码
// canvas-renderer.tsx - Schema 到 React 组件的渲染器
import React from 'react';

interface CanvasRendererProps {
  schema: PageSchema;
  previewMode: boolean;
  onComponentClick?: (componentId: string) => void;
}

export const CanvasRenderer: React.FC<CanvasRendererProps> = ({
  schema,
  previewMode,
  onComponentClick,
}) => {
  // 递归渲染组件树
  const renderComponent = (component: ComponentSchema): React.ReactNode => {
    const Component = componentRegistry.get(component.type);
    if (!Component) {
      return <div key={component.id}>Unknown component: {component.type}</div>;
    }

    // 处理子组件
    const children = component.children?.map(renderComponent);

    // 绑定事件
    const eventHandlers = bindEvents(component.events, previewMode);

    return (
      <ComponentWrapper
        key={component.id}
        componentId={component.id}
        selected={!previewMode && schema.selectedComponentId === component.id}
        onClick={() => !previewMode && onComponentClick?.(component.id)}
      >
        <Component
          {...component.props}
          style={component.style}
          {...eventHandlers}
        >
          {children}
        </Component>
      </ComponentWrapper>
    );
  };

  return <div className="canvas-container">{schema.components.map(renderComponent)}</div>;
};

// 事件绑定
function bindEvents(events: EventConfig[], previewMode: boolean) {
  if (!previewMode || events.length === 0) return {};

  const handlers: Record<string, () => void> = {};

  for (const event of events) {
    if (event.trigger === 'click') {
      handlers.onClick = () => executeAction(event.action, event.config);
    } else if (event.trigger === 'change') {
      handlers.onChange = (value: any) => executeAction(event.action, event.config, value);
    }
  }

  return handlers;
}

// 执行动作
async function executeAction(
  action: string,
  config: Record<string, any>,
  payload?: any
) {
  switch (action) {
    case 'api':
      await apiClient.request({
        url: config.url,
        method: config.method,
        data: payload,
      });
      break;
    case 'navigation':
      router.push(config.url);
      break;
    case 'script':
      // 执行自定义脚本(沙箱环境)
      await executeSandboxedScript(config.code, payload);
      break;
  }
}

二、插件化架构设计

2.1 插件系统核心接口

typescript 复制代码
// plugin-system.ts
export interface LowCodePlugin {
  name: string;
  version: string;
  author?: string;
  description?: string;

  // 生命周期钩子
  init?(context: PluginContext): void | Promise<void>;
  destroy?(): void | Promise<void>;

  // 扩展点
  registerComponents?(): ComponentDefinition[];
  registerActions?(): ActionDefinition[];
  registerTemplates?(): TemplateDefinition[];
  registerThemes?(): ThemeDefinition[];

  // 事件钩子
  onBeforeSave?(schema: PageSchema): PageSchema | Promise<PageSchema>;
  onAfterLoad?(schema: PageSchema): void;
  onComponentAdd?(component: ComponentSchema): void;
  onComponentUpdate?(component: ComponentSchema, changes: Partial<ComponentSchema>): void;
}

export class PluginManager {
  private plugins: Map<string, LowCodePlugin> = new Map();
  private context: PluginContext;

  constructor() {
    this.context = {
      registerComponent: this.registerComponent.bind(this),
      registerAction: this.registerAction.bind(this),
      emit: this.emit.bind(this),
      on: this.on.bind(this),
    };
  }

  // 注册插件
  async register(plugin: LowCodePlugin): Promise<void> {
    if (this.plugins.has(plugin.name)) {
      throw new Error(`Plugin ${plugin.name} already registered`);
    }

    // 初始化插件
    await plugin.init?.(this.context);

    // 注册扩展
    if (plugin.registerComponents) {
      const components = plugin.registerComponents();
      components.forEach((comp) => componentRegistry.register(comp));
    }

    if (plugin.registerActions) {
      const actions = plugin.registerActions();
      actions.forEach((action) => actionRegistry.register(action));
    }

    this.plugins.set(plugin.name, plugin);
  }

  // 卸载插件
  async unregister(pluginName: string): Promise<void> {
    const plugin = this.plugins.get(pluginName);
    if (!plugin) return;

    await plugin.destroy?.();
    this.plugins.delete(pluginName);
  }

  // 触发钩子
  async triggerHook(hookName: string, ...args: any[]): Promise<any> {
    for (const plugin of this.plugins.values()) {
      const hook = (plugin as any)[hookName];
      if (typeof hook === 'function') {
        await hook(...args);
      }
    }
  }
}

2.2 自定义组件插件示例

typescript 复制代码
// plugins/advanced-chart-plugin.ts
import { LowCodePlugin } from '../plugin-system';

export class AdvancedChartPlugin implements LowCodePlugin {
  name = 'advanced-chart';
  version = '1.0.0';
  author = 'LowCode Team';
  description = '高级图表组件(折线图/柱状图/饼图)';

  registerComponents() {
    return [
      {
        type: 'line-chart',
        name: '折线图',
        icon: 'line-chart',
        category: 'charts',
        defaultProps: {
          title: '折线图',
          dataSource: { type: 'static', data: [] },
          xField: 'date',
          yField: 'value',
          color: '#1890ff',
          showLegend: true,
        },
        propSchema: {
          title: { type: 'string', label: '图表标题' },
          dataSource: { type: 'dataSource', label: '数据源' },
          xField: { type: 'string', label: 'X轴字段' },
          yField: { type: 'string', label: 'Y轴字段' },
          color: { type: 'color', label: '线条颜色' },
          showLegend: { type: 'boolean', label: '显示图例' },
        },
        events: ['click', 'dataZoom'],
        render: LineChartComponent,
      },
      {
        type: 'bar-chart',
        name: '柱状图',
        icon: 'bar-chart',
        category: 'charts',
        defaultProps: {
          title: '柱状图',
          dataSource: { type: 'static', data: [] },
          xField: 'category',
          yField: 'value',
          color: '#52c41a',
        },
        propSchema: {
          title: { type: 'string', label: '图表标题' },
          dataSource: { type: 'dataSource', label: '数据源' },
          xField: { type: 'string', label: 'X轴字段' },
          yField: { type: 'string', label: 'Y轴字段' },
          color: { type: 'color', label: '柱状颜色' },
        },
        events: ['click', 'dataZoom'],
        render: BarChartComponent,
      },
    ];
  }

  registerActions() {
    return [
      {
        name: 'fetch-chart-data',
        description: '获取图表数据',
        configSchema: {
          apiUrl: { type: 'string', required: true },
          refreshInterval: { type: 'number', default: 0 },
        },
        execute: async (config: any) => {
          const data = await fetch(config.apiUrl).then((r) => r.json());
          return { success: true, data };
        },
      },
    ];
  }

  async init(context) {
    console.log('Advanced Chart Plugin initialized');
    // 可以在这里注册全局样式、初始化第三方库等
  }
}

2.3 插件市场与动态加载

typescript 复制代码
// plugin-market.ts
export class PluginMarket {
  private registryUrl = 'https://plugins.lowcode.com';

  // 从市场安装插件
  async install(pluginId: string): Promise<void> {
    // 1. 从市场获取插件元数据
    const metadata = await this.fetchPluginMetadata(pluginId);

    // 2. 检查兼容性
    if (!this.checkCompatibility(metadata)) {
      throw new Error(`Plugin ${pluginId} is not compatible with current version`);
    }

    // 3. 下载插件包
    const pluginUrl = `${this.registryUrl}/plugins/${pluginId}/download`;
    const pluginCode = await this.downloadPlugin(pluginUrl);

    // 4. 验证插件签名(安全)
    if (!this.verifySignature(pluginCode, metadata.signature)) {
      throw new Error('Plugin signature verification failed');
    }

    // 5. 沙箱执行插件代码
    const plugin = await this.loadPluginInSandbox(pluginCode);

    // 6. 注册到插件管理器
    await pluginManager.register(plugin);

    // 7. 保存到本地存储
    await this.savePlugin(pluginId, pluginCode, metadata);
  }

  // 沙箱加载插件
  private async loadPluginInSandbox(code: string): Promise<LowCodePlugin> {
    const sandbox = {
      console,
      fetch: this.sandboxedFetch.bind(this),
      localStorage: this.sandboxedStorage,
      // 禁止访问敏感 API
      // window, document 等仅提供受限访问
    };

    const fn = new Function('sandbox', `with (sandbox) { ${code} }`);
    const pluginFactory = fn(sandbox);

    return pluginFactory();
  }

  // 获取已安装插件列表
  async getInstalledPlugins(): Promise<InstalledPlugin[]> {
    const plugins = localStorage.getItem('installed_plugins');
    return plugins ? JSON.parse(plugins) : [];
  }
}

三、数据模型与动态表单

3.1 动态数据模型

typescript 复制代码
// data-model.ts
export interface DataModel {
  id: string;
  name: string;
  displayName: string;
  fields: DataField[];
  relations?: ModelRelation[];
  indexes?: DataIndex[];
}

export interface DataField {
  name: string;
  displayName: string;
  type: 'string' | 'number' | 'boolean' | 'date' | 'datetime' | 'json' | 'file' | 'relation';
  required: boolean;
  unique?: boolean;
  defaultValue?: any;
  validation?: ValidationRule[];
  ui?: {
    widget: 'input' | 'textarea' | 'select' | 'radio' | 'checkbox' | 'date-picker' | 'upload';
    options?: { label: string; value: any }[];
    placeholder?: string;
  };
}

// 动态表单生成
export function generateFormFromModel(model: DataModel): ComponentSchema {
  const formItems = model.fields.map((field) => {
    const formItem: ComponentSchema = {
      id: `field_${field.name}`,
      type: getWidgetType(field),
      props: {
        label: field.displayName,
        field: field.name,
        required: field.required,
        placeholder: field.ui?.placeholder,
        ...(field.ui?.options ? { options: field.ui.options } : {}),
      },
      style: {},
      events: [],
    };
    return formItem;
  });

  return {
    version: '1.0.0',
    pageId: `form_${model.id}`,
    name: `${model.displayName}表单`,
    components: [
      {
        id: `form_${model.id}`,
        type: 'form',
        props: {
          model: model.id,
          fields: model.fields.map((f) => f.name),
        },
        style: {},
        events: [
          {
            trigger: 'submit',
            action: 'api',
            config: {
              url: `/api/data/${model.id}`,
              method: 'POST',
            },
          },
        ],
        children: formItems,
      },
    ],
    globalSettings: {
      apiBaseUrl: '',
      authConfig: { type: 'jwt' },
      theme: {},
    },
  };
}

function getWidgetType(field: DataField): string {
  const widgetMap: Record<string, string> = {
    input: 'input',
    textarea: 'textarea',
    select: 'select',
    radio: 'radio-group',
    checkbox: 'checkbox-group',
    'date-picker': 'date-picker',
    upload: 'upload',
  };
  return widgetMap[field.ui?.widget || 'input'] || 'input';
}

3.2 动态列表/表格生成

typescript 复制代码
// data-table-generator.ts
export function generateTableFromModel(model: DataModel): ComponentSchema {
  const columns = model.fields.map((field) => ({
    title: field.displayName,
    dataIndex: field.name,
    key: field.name,
    render: getColumnRender(field),
  }));

  return {
    version: '1.0.0',
    pageId: `table_${model.id}`,
    name: `${model.displayName}列表`,
    components: [
      {
        id: `table_${model.id}`,
        type: 'table',
        props: {
          columns,
          dataSource: {
            type: 'api',
            url: `/api/data/${model.id}`,
            method: 'GET',
            params: {},
          },
          pagination: true,
          pageSize: 20,
          rowKey: 'id',
          actions: [
            { label: '编辑', action: 'navigate', url: `/edit/:id` },
            { label: '删除', action: 'api', url: `/api/data/${model.id}/:id`, method: 'DELETE' },
          ],
        },
        style: {},
        events: [],
      },
    ],
    globalSettings: {
      apiBaseUrl: '',
      authConfig: { type: 'jwt' },
      theme: {},
    },
  };
}

四、流程引擎集成

4.1 BPMN 流程设计器

typescript 复制代码
// bpmn-designer.ts
export interface WorkflowDefinition {
  id: string;
  name: string;
  description?: string;
  bpmnXml: string;  // BPMN 2.0 XML
  nodes: WorkflowNode[];
  transitions: WorkflowTransition[];
}

export interface WorkflowNode {
  id: string;
  type: 'start' | 'end' | 'userTask' | 'serviceTask' | 'exclusiveGateway' | 'inclusiveGateway';
  name: string;
  position: { x: number; y: number };
  config: {
    assignee?: string;         // 用户任务审批人
    serviceUrl?: string;        // 服务任务 API
    condition?: string;         // 网关条件表达式
    formId?: string;           // 关联表单
  };
}

export class WorkflowEngine {
  // 启动流程实例
  async startProcess(definitionId: string, formData: Record<string, any>): Promise<string> {
    const instanceId = `inst_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;

    await db.workflowInstances.create({
      id: instanceId,
      definitionId,
      status: 'running',
      formData,
      startTime: new Date(),
      currentNodes: [this.getStartNode(definitionId)],
    });

    // 执行第一个节点
    await this.executeNode(instanceId, this.getStartNode(definitionId));

    return instanceId;
  }

  // 执行节点
  private async executeNode(instanceId: string, nodeId: string): Promise<void> {
    const node = this.getNode(nodeId);
    const instance = await this.getInstance(instanceId);

    switch (node.type) {
      case 'start':
        // 启动节点,直接到下一个
        await this.moveToNextNodes(instanceId, nodeId);
        break;

      case 'userTask':
        // 创建待办任务
        await this.createTask(instanceId, node);
        break;

      case 'serviceTask':
        // 调用外部服务
        const result = await this.callService(node.config.serviceUrl, instance.formData);
        instance.variables = { ...instance.variables, ...result };
        await this.moveToNextNodes(instanceId, nodeId);
        break;

      case 'exclusiveGateway':
        // 排他网关,计算条件
        const condition = this.evaluateCondition(node.config.condition, instance);
        const nextNode = this.getNextNodeByCondition(nodeId, condition);
        await this.moveToNextNodes(instanceId, nextNode);
        break;
    }
  }

  // 完成用户任务
  async completeTask(taskId: string, formData: Record<string, any>): Promise<void> {
    const task = await db.tasks.findOne(taskId);
    const instance = await this.getInstance(task.instanceId);

    // 更新实例数据
    instance.formData = { ...instance.formData, ...formData };
    await db.workflowInstances.update(instance.id, instance);

    // 移动到下一个节点
    await this.moveToNextNodes(instance.id, task.nodeId);
  }
}

4.2 可视化流程设计器

tsx 复制代码
// workflow-designer.tsx
import React, { useState } from 'react';
import ReactFlow, {
  Node,
  Edge,
  addEdge,
  Connection,
  useNodesState,
  useEdgesState,
} from 'reactflow';

const nodeTypes = {
  start: StartNode,
  end: EndNode,
  userTask: UserTaskNode,
  serviceTask: ServiceTaskNode,
  gateway: GatewayNode,
};

export const WorkflowDesigner: React.FC<{ definitionId: string }> = ({ definitionId }) => {
  const [nodes, setNodes, onNodesChange] = useNodesState([]);
  const [edges, setEdges, onEdgesChange] = useEdgesState([]);

  const onConnect = (connection: Connection) => {
    setEdges((eds) => addEdge({ ...connection, type: 'smoothstep' }, eds));
  };

  const addNode = (type: string, position: { x: number; y: number }) => {
    const newNode: Node = {
      id: `node_${Date.now()}`,
      type,
      position,
      data: { label: `${type} node` },
    };
    setNodes((nds) => [...nds, newNode]);
  };

  const saveWorkflow = async () => {
    const definition: WorkflowDefinition = {
      id: definitionId,
      name: 'New Workflow',
      nodes: nodes.map((n) => ({
        id: n.id,
        type: n.type as any,
        name: n.data.label,
        position: n.position,
        config: n.data.config || {},
      })),
      transitions: edges.map((e) => ({
        id: e.id,
        source: e.source,
        target: e.target,
        condition: e.data?.condition,
      })),
      bpmnXml: generateBPMN(nodes, edges),
    };

    await apiClient.post('/api/workflows', definition);
  };

  return (
    <div className="workflow-designer">
      <div className="toolbar">
        <button onClick={() => addNode('start', { x: 100, y: 100 })}>开始节点</button>
        <button onClick={() => addNode('userTask', { x: 300, y: 100 })}>用户任务</button>
        <button onClick={() => addNode('serviceTask', { x: 500, y: 100 })}>服务任务</button>
        <button onClick={() => addNode('gateway', { x: 400, y: 250 })}>网关</button>
        <button onClick={() => addNode('end', { x: 700, y: 100 })}>结束节点</button>
        <button onClick={saveWorkflow}>保存流程</button>
      </div>

      <ReactFlow
        nodes={nodes}
        edges={edges}
        onNodesChange={onNodesChange}
        onEdgesChange={onEdgesChange}
        onConnect={onConnect}
        nodeTypes={nodeTypes}
        fitView
      />
    </div>
  );
};

五、企业级功能

5.1 权限控制

typescript 复制代码
// permission-system.ts
export interface PermissionPolicy {
  role: string;
  permissions: Permission[];
}

export interface Permission {
  resource: string;      // 资源类型:page/component/data/api
  action: 'create' | 'read' | 'update' | 'delete' | '*';
  conditions?: PermissionCondition[];
}

export class PermissionManager {
  // 检查权限
  async checkPermission(
    user: User,
    resource: string,
    action: string,
    context?: Record<string, any>
  ): Promise<boolean> {
    const policies = await this.getUserPolicies(user);

    for (const policy of policies) {
      for (const perm of policy.permissions) {
        if (this.matchPermission(perm, resource, action, context)) {
          // 检查条件
          if (perm.conditions) {
            const conditionResult = await this.evaluateConditions(
              perm.conditions,
              user,
              context
            );
            if (!conditionResult) continue;
          }
          return true;
        }
      }
    }

    return false;
  }

  // 数据行级权限
  async applyRowLevelSecurity(
    query: DataQuery,
    user: User
  ): Promise<DataQuery> {
    const rlsPolicies = await this.getRowLevelPolicies(user);

    for (const policy of rlsPolicies) {
      query.where = {
        AND: [query.where, policy.condition],
      };
    }

    return query;
  }
}

5.2 版本管理

typescript 复制代码
// version-control.ts
export class VersionManager {
  // 保存版本
  async saveVersion(
    pageId: string,
    schema: PageSchema,
    comment: string
  ): Promise<Version> {
    const version: Version = {
      id: `ver_${Date.now()}`,
      pageId,
      schema: JSON.parse(JSON.stringify(schema)), // 深拷贝
      comment,
      author: currentUser.id,
      createdAt: new Date(),
      versionNumber: await this.getNextVersionNumber(pageId),
    };

    await db.versions.create(version);
    return version;
  }

  // 版本对比
  async compareVersions(versionId1: string, versionId2: string): Promise<DiffResult> {
    const v1 = await db.versions.findById(versionId1);
    const v2 = await db.versions.findById(versionId2);

    return {
      added: jsonDiff.getAdded(v1.schema, v2.schema),
      removed: jsonDiff.getRemoved(v1.schema, v2.schema),
      changed: jsonDiff.getChanged(v1.schema, v2.schema),
    };
  }

  // 回滚到指定版本
  async rollback(versionId: string): Promise<void> {
    const version = await db.versions.findById(versionId);
    const page = await db.pages.findById(version.pageId);

    // 保存当前版本为历史版本
    await this.saveVersion(page.id, page.schema, `Rollback to ${versionId}`);

    // 恢复版本
    page.schema = version.schema;
    await db.pages.update(page.id, page);
  }

  // Git 集成(高级功能)
  async commitToGit(pageId: string, message: string): Promise<void> {
    const page = await db.pages.findById(pageId);
    const repoPath = `/repos/${page.appId}`;

    // 初始化 Git 仓库(如果不存在)
    if (!fs.existsSync(path.join(repoPath, '.git'))) {
      await git.init(repoPath);
    }

    // 写入文件
    const filePath = path.join(repoPath, `${page.name}.json`);
    fs.writeFileSync(filePath, JSON.stringify(page.schema, null, 2));

    // 提交
    await git.add(repoPath, filePath);
    await git.commit(repoPath, message);
  }
}

六、性能优化

6.1 Schema 懒加载与分片

typescript 复制代码
// schema-optimization.ts
export class SchemaOptimizer {
  // 大页面分片加载
  async splitSchema(schema: PageSchema): Promise<SchemaChunk[]> {
    const chunks: SchemaChunk[] = [];
    const components = schema.components;

    // 按容器组件分片
    const containers = components.filter((c) => c.children);
    for (const container of containers) {
      chunks.push({
        id: `chunk_${container.id}`,
        components: [container, ...container.children!],
        priority: this.calculatePriority(container),
      });
    }

    // 非容器组件作为独立分片
    const standalone = components.filter((c) => !c.children);
    if (standalone.length > 0) {
      chunks.push({
        id: 'chunk_standalone',
        components: standalone,
        priority: 0,
      });
    }

    return chunks.sort((a, b) => a.priority - b.priority);
  }

  // 懒加载非关键组件
  async lazyLoadComponents(pageId: string, visibleArea: DOMRect): Promise<void> {
    const schema = await this.getSchema(pageId);
    const allComponents = schema.components;

    for (const component of allComponents) {
      const element = document.getElementById(component.id);
      if (element) {
        const rect = element.getBoundingClientRect();
        if (this.isInViewport(rect, visibleArea)) {
          await this.loadComponentAssets(component);
        }
      }
    }
  }
}

6.2 渲染性能优化

typescript 复制代码
// render-optimization.tsx
import React, { memo, useMemo, useCallback } from 'react';

// 组件记忆化
export const MemoizedComponent = memo(({ schema, ...props }: ComponentProps) => {
  return <BaseComponent schema={schema} {...props} />;
}, (prevProps, nextProps) => {
  // 自定义比较函数
  return (
    prevProps.schema.id === nextProps.schema.id &&
    prevProps.schema.version === nextProps.schema.version &&
    prevProps.previewMode === nextProps.previewMode
  );
});

// 虚拟滚动(大列表)
export function VirtualList({ items, itemHeight, containerHeight }: VirtualListProps) {
  const [scrollTop, setScrollTop] = useState(0);

  const startIndex = Math.floor(scrollTop / itemHeight);
  const endIndex = Math.min(
    startIndex + Math.ceil(containerHeight / itemHeight) + 1,
    items.length
  );

  const visibleItems = useMemo(() => {
    return items.slice(startIndex, endIndex).map((item, index) => ({
      ...item,
      index: startIndex + index,
      style: {
        position: 'absolute' as const,
        top: (startIndex + index) * itemHeight,
        height: itemHeight,
      },
    }));
  }, [items, startIndex, endIndex]);

  return (
    <div
      style={{ height: containerHeight, overflow: 'auto' }}
      onScroll={(e) => setScrollTop(e.currentTarget.scrollTop)}
    >
      <div style={{ height: items.length * itemHeight, position: 'relative' }}>
        {visibleItems.map((item) => (
          <div key={item.id} style={item.style}>
            {item.content}
          </div>
        ))}
      </div>
    </div>
  );
}

七、Checklist 总结

复制代码
□ 前端引擎
  □ Schema 驱动架构(PageSchema/ComponentSchema)
  □ 拖拽引擎(DragEngine - 拖拽/放置/移动)
  □ 画布渲染器(递归渲染/事件绑定)
  □ 属性面板(动态表单生成)
  □ 撤销/重做(History 管理)

□ 插件系统
  □ 插件接口定义(LowCodePlugin)
  □ 插件管理器(注册/卸载/生命周期)
  □ 扩展点(组件/动作/模板/主题)
  □ 事件钩子(onBeforeSave/onAfterLoad)
  □ 插件市场(安装/签名验证/沙箱执行)

□ 数据模型
  □ 动态数据模型定义(DataModel/DataField)
  □ 动态表单生成(generateFormFromModel)
  □ 动态表格生成(generateTableFromModel)
  □ 数据校验(ValidationRule)
  □ 关联关系(ModelRelation)

□ 流程引擎
  □ BPMN 流程设计器(ReactFlow)
  □ 流程定义(WorkflowDefinition)
  □ 流程引擎(启动/执行/完成任务)
  □ 节点类型(开始/结束/用户任务/服务任务/网关)
  □ 条件网关(条件表达式计算)

□ 企业级功能
  □ 权限控制(RBAC + 行级权限)
  □ 版本管理(保存/对比/回滚)
  □ Git 集成(提交/分支/合并)
  □ 多环境部署(开发/测试/生产)
  □ 审计日志(操作记录)

□ 性能优化
  □ Schema 分片加载
  □ 懒加载非关键组件
  □ 组件记忆化(React.memo)
  □ 虚拟滚动(大列表)
  □ 代码分割(Dynamic Import)

总结

一句话总结: 低代码平台 = Schema 驱动引擎 + 插件化架构 + 可视化设计器,通过拖拽生成 JSON Schema,再由渲染器动态渲染成应用,让业务人员自助搭建,开发者专注平台能力。

核心技术栈:

模块 技术选型 关键点
前端框架 React/Vue 3 + TypeScript 组件化/类型安全
拖拽引擎 dnd-kit/react-dnd 拖拽状态管理
流程设计器 ReactFlow 节点/边/自定义样式
Schema 存储 JSON + IndexedDB/PostgreSQL 版本管理/离线支持
插件系统 微前端/qiankun 隔离/动态加载
后端 Node.js/Go + PostgreSQL 高并发/事务支持

下一步推荐:

  • AI 辅助低代码(自然语言生成页面)
  • WebAssembly 高性能组件渲染
  • 跨端低代码(Web + 小程序 + App)