基于 Vue3 的低代码平台架构设计:篇一

#lowcode

目录

1. 核心理念

Amis 类似,我们采用 JSON 配置驱动的方式来构建页面。主要包含以下核心概念:

  • Schema 驱动渲染
    • 通过 JSON 描述页面结构和行为
  • 组件注册机制
    • 灵活的组件注册和扩展系统
  • 数据域管理
    • 独立的数据作用域
    • 建议使用 pinia
  • 表达式引擎
    • 处理动态数据和条件判断
  • 事件系统
    • 统一的事件处理机制

2. 技术架构图

让我用 Mermaid 来绘制架构图:



  graph TD
    A[Schema JSON] --> B[Schema Parser]
    B --> C[Component Factory]
    C --> D[Runtime Renderer]
    D --> E[页面实例]
    
    F[组件库] --> C
    G[表达式引擎] --> D
    H[数据域] --> D
    I[事件系统] --> D
    
    subgraph 核心引擎
    B
    C
    D
    end
    
    subgraph 扩展系统
    F
    G
    H
    I
    end

3. 渲染引擎

渲染引擎是整个框架的核心,负责解析 Schema 并渲染组件。以下是核心渲染器的示例代码:

3.1. types/schema.ts

// types/schema.ts
interface Schema {
  type: string;
  props?: Record<string, any>;
  children?: Schema[];
  data?: Record<string, any>;
}

3.2. components/Renderer.vue


<script setup lang="ts">
import { computed, provide, inject } from 'vue';
import { resolveComponent } from 'vue';

const props = defineProps<{
  schema: Schema;
  data?: Record<string, any>;
}>();

// 数据域管理
const parentData = inject('scopeData', {});
const currentData = computed(() => ({
  ...parentData,
  ...props.data
}));
provide('scopeData', currentData);

// 解析组件
const resolveSchemaComponent = (type: string) => {
  // 1. 查找注册的自定义组件
  // 2. 回退到 Vue 内置组件
  return resolveComponent(type) || resolveComponent('div');
};

// 处理属性
const resolveProps = (props: Record<string, any>) => {
  return Object.entries(props).reduce((acc, [key, value]) => {
    // 处理表达式
    acc[key] = typeof value === 'string' && value.startsWith('${') 
      ? evaluateExpression(value, currentData.value)
      : value;
    return acc;
  }, {});
};
</script>

<template>
  <component
    :is="resolveSchemaComponent(schema.type)"
    v-bind="resolveProps(schema.props || {})"
  >
    <template v-if="schema.children">
      <Renderer
        v-for="(child, index) in schema.children"
        :key="index"
        :schema="child"
      />
    </template>
  </component>
</template>

4. 组件注册系统

结合异步组件来做,系统本身只提供部分核心组件

// core/registry.ts
interface ComponentMeta {
  component: Component;
  schema?: Record<string, any>;
  validations?: Record<string, Function>;
}

class ComponentRegistry {
  private components: Map<string, ComponentMeta> = new Map();

  register(type: string, meta: ComponentMeta) {
    this.components.set(type, meta);
  }

  get(type: string): ComponentMeta | undefined {
    return this.components.get(type);
  }

  // 批量注册
  registerMultiple(components: Record<string, ComponentMeta>) {
    Object.entries(components).forEach(([type, meta]) => {
      this.register(type, meta);
    });
  }
}

export const registry = new ComponentRegistry();

5. 表达式引擎

请结合使用 loadsh 里面的各类工具类函数

// core/expression.ts
export function evaluateExpression(expr: string, data: Record<string, any>) {
  // 移除 ${} 包裹
  const script = expr.slice(2, -1).trim();
  
  // 构建安全的执行环境
  const context = {
    ...data,
    // 内置函数
    FORMAT: (val: any, format: string) => format(val),
    IF: (condition: boolean, then: any, else_: any) => condition ? then : else_,
  };

  try {
    // 使用 Function 构造器创建沙箱环境
    const fn = new Function(...Object.keys(context), `return ${script}`);
    return fn(...Object.values(context));
  } catch (e) {
    console.error('Expression evaluation failed:', expr, e);
    return undefined;
  }
}

6. 事件系统

  • 提供注册事件能力
  • 注意需要注册特定的上下文
// core/events.ts
type ActionType = 'ajax' | 'link' | 'dialog' | 'custom';

interface Action {
  type: ActionType;
  config: Record<string, any>;
}

class EventManager {
  private handlers: Map<ActionType, Function> = new Map();

  register(type: ActionType, handler: Function) {
    this.handlers.set(type, handler);
  }

  async execute(action: Action, context: any) {
    const handler = this.handlers.get(action.type);
    if (!handler) {
      throw new Error(`No handler registered for action type: ${action.type}`);
    }
    return handler(action.config, context);
  }
}

export const eventManager = new EventManager();

7. 示例:基础页面配置

需要考虑如何和数据模块结合,options 的来源如何获取

// 页面配置示例
const schema = {
  type: 'page',
  props: {
    title: '用户管理'
  },
  children: [
    {
      type: 'form',
      props: {
        api: '/api/users'
      },
      children: [
        {
          type: 'input',
          props: {
            name: 'username',
            label: '用户名',
            required: true
          }
        },
        {
          type: 'select',
          props: {
            name: 'role',
            label: '角色',
            options: [
              { label: '管理员', value: 'admin' },
              { label: '用户', value: 'user' }
            ]
          }
        }
      ]
    }
  ]
};

8. 示例:组件注册示例

// components/Form.vue
import { defineComponent } from 'vue';

export default defineComponent({
  name: 'Form',
  props: {
    api: String,
  },
  setup(props, { slots }) {
    // 表单逻辑实现
    return () => (
      <form>
        {slots.default?.()}
      </form>
    );
  }
});

// 注册组件
registry.register('form', {
  component: Form,
  schema: {
    type: 'object',
    properties: {
      api: { type: 'string' },
      submitText: { type: 'string', default: '提交' }
    }
  }
});

9. 示例:自定义组件

高阶组件实现的方式,类似于 amis 的 @renderer

// 自定义图表组件示例
registry.register('chart', {
  component: defineComponent({
    props: {
      type: String,
      data: Array,
      config: Object
    },
    setup(props) {
      // 集成第三方图表库
      return () => (
        <div class="chart-container">
          {/* 图表渲染逻辑 */}
        </div>
      );
    }
  }),
  schema: {
    type: 'object',
    properties: {
      type: { type: 'string', enum: ['line', 'bar', 'pie'] },
      data: { type: 'array' },
      config: { type: 'object' }
    }
  }
});

10. 示例: 数据联动

// 实现组件间的数据联动
const schema = {
  type: 'page',
  children: [
    {
      type: 'select',
      props: {
        name: 'province',
        label: '省份',
        onChange: {
          actions: [
            {
              type: 'ajax',
              config: {
                url: '/api/cities/${province}',
                target: 'cities'
              }
            }
          ]
        }
      }
    },
    {
      type: 'select',
      props: {
        name: 'city',
        label: '城市',
        source: '${cities}'
      }
    }
  ]
};

我是需要统一包在统一字段里面,比如 onEvent

11. 其他

  • 组件
    • 提供合理的默认值
    • 实现完善的类型定义
  • 性能优化
    • 使用 Vue3 的 defineAsyncComponent 实现组件懒加载
  • 扩展性设计
    • 提供插件系统
    • 支持主题定制
    • 预留适配层接口
  • 开发工具
    • 提供 Schema 验证工具
    • 开发调试工具
    • 可视化编辑器
  • 继续完善
    • 更多的内置组件
    • 更强大的表达式引擎
    • 更完善的文档和示例
    • 供更多的主题和样式选项
    • 加单元测试和端到端测试