集成第三方库
本指南解释了如何将第三方组件库集成到 VTJ 低代码框架中。VTJ 采用基于物料的架构,封装现有的组件库,使其在可视化设计器中可用,同时保留其原有功能。
集成架构概览
VTJ 的集成系统采用分层方法,通过声明式描述符将第三方库转换为物料。这种架构实现了无缝集成,无需修改原始库代码。
graph TD
A[第三方组件库] --> B[物料描述符<br>MaterialDescription]
B --> C[VTJ物料<br>Material]
C --> D[可视化设计器]
核心概念
集成系统依赖于三种主要的类型定义:
- Material:完整的库封装,包含版本、分类和组件描述
- MaterialDescription:单个组件架构,定义 props、events、slots 和 snippets
- Dependencie:库脚本、样式和资源的加载配置
物料描述符结构
每个集成组件都需要一个 MaterialDescription,作为第三方库和 VTJ 设计系统之间的桥梁。
基础描述符模板
import type { MaterialDescription } from "@vtj/core";
const componentDescriptor: MaterialDescription = {
name: "ComponentName", // VTJ 中的组件标识符
label: "Display Name", // 人类可读的名称
categoryId: "category-id", // 设计器面板中的分类
doc: "https://library-docs-url", // 文档链接
alias: "OriginalName", // 原始导出名称(可选)
parent: "ParentComponent", // 父级命名空间(可选)
props: [
{
name: "propName",
label: "Property Label",
title: "Description tooltip",
defaultValue: "default-value",
setters: "StringSetter", // 该属性的编辑器组件
options: ["option1", "option2"], // 可用选项
type: ["string"], // 数据类型验证
},
],
events: ["click", "change"], // 支持的事件
slots: ["default", "header"], // 可用的插槽
snippet: {
// 初始拖放模板
name: "ComponentName",
props: {
/* initial props */
},
},
};
属性 Setter 类型
VTJ 为常见属性类型提供了内置的 setters:
| Setter 名称 | 用途 | 使用场景 |
|---|---|---|
StringSetter | 文本输入 | 通用字符串属性 |
BooleanSetter | 复选框切换 | 二进制标志 |
SelectSetter | 下拉选择 | 枚举选项 |
NumberSetter | 数字输入 | 数值 |
IconSetter | 图标选择器 | 图标选择 |
ObjectSetter | JSON 编辑器 | 复杂对象 |
ColorSetter | 颜色选择器 | 颜色值 |
分步集成指南
第一步:准备库依赖
首先,确保第三方库已安装并在你的项目中可用:
npm install element-plus --save
创建依赖配置条目:
const elementPlusDependency: Dependencie = {
package: "element-plus",
version: "2.13.0",
library: "ElementPlus", // 全局变量名称
localeLibrary: "ElementPlusLocaleZh", // 语言包(可选)
urls: [
"https://unpkg.com/element-plus/dist/index.full.js",
"https://unpkg.com/element-plus/dist/index.css",
],
assetsUrl: "/path/to/material-config.js", // 物料描述符
assetsLibrary: "ElementPlusMaterial", // 导出的物料名称
};
第二步:定义物料注册表
创建导出所有组件描述符的物料注册表文件:
import type {
Material,
MaterialCategory,
MaterialDescription,
} from "@vtj/core";
import { version } from "../version";
import { setPackageName } from "../shared";
// 导入单个组件描述符
import button from "./button";
import form from "./form";
import table from "./table";
const name = "element-plus";
const components: MaterialDescription[] = [button, form, table].flat();
const categories: MaterialCategory[] = [
{ id: "base", category: "基础组件" },
{ id: "form", category: "表单组件" },
{ id: "data", category: "数据展示" },
];
const material: Material = {
name,
version,
label: "Element+",
library: "ElementPlusMaterial", // 导出的库名称
order: 2, // 面板中的显示顺序
categories,
components: setPackageName(components, name),
};
export default material;
第三步:创建组件描述符
为每个组件创建一个描述符文件,定义其属性、事件和插槽:
import type { MaterialDescription } from "@vtj/core";
import { size, type } from "../shared"; // 可复用的属性辅助函数
const button: MaterialDescription = {
name: "ElButton",
label: "按钮",
categoryId: "base",
doc: "https://element-plus.org/zh-CN/component/button.html",
props: [
size("size"), // 可复用的 size 属性
type("type"), // 可复用的 type 属性
{
name: "plain",
title: "是否为朴素按钮",
defaultValue: false,
setters: "BooleanSetter",
},
{
name: "loading",
title: "是否为加载中状态",
defaultValue: false,
setters: "BooleanSetter",
},
{
name: "icon",
title: "图标组件",
defaultValue: undefined,
setters: "IconSetter",
},
],
events: ["click"],
slots: ["default", "loading", "icon"],
snippet: {
name: "ElButton",
children: "按钮",
props: {
type: "primary",
},
},
};
export default button;
第四步:配置构建流程
在 package.json 中为你的物料包添加构建配置:
{
"scripts": {
"build:element": "vue-tsc && cross-env BUILD_TYPE=element vite build"
},
"devDependencies": {
"element-plus": "~2.13.0",
"@vtj/core": "workspace:~"
}
}
第五步:在 Provider 中注册
在你的应用程序中,向 VTJ provider 注册物料:
import { createProvider, createModules } from "@vtj/web";
import elementPlusMaterial from "@vtj/materials/src/element";
import antdMaterial from "@vtj/materials/src/antdv";
const { provider, onReady } = createProvider({
nodeEnv: process.env.NODE_ENV,
modules: createModules({
materials: [elementPlusMaterial, antdMaterial],
}),
// ... 其他配置
});
集成示例
示例 1:Element Plus 集成
Element Plus 已完全集成,包含 80 多个组件,按类别组织:
// 组件分类
const categories: MaterialCategory[] = [
{ id: "base", category: "基础组件" },
{ id: "layout", category: "排版布局" },
{ id: "form", category: "表单组件" },
{ id: "data", category: "数据展示" },
{ id: "nav", category: "导航" },
{ id: "other", category: "其他" },
];
// 示例组件
const components = [
button,
input,
form,
table,
dialog,
// ... 70+ 更多组件
];
Element Plus 使用共享工具函数,如
size()和type(),在组件间一致地定义通用属性,从而减少重复。
示例 2:Ant Design Vue 集成
Ant Design Vue 集成展示了如何处理组件组和命名空间组件:
const button: MaterialDescription = {
name: "AButton",
label: "按钮",
categoryId: "base",
props: [size("size"), type("type")],
snippet: {
name: "AButton",
props: { type: "primary" },
},
};
const buttonGroup: MaterialDescription = {
name: "AButtonGroup",
parent: "Button", // 父级命名空间
alias: "Group", // 实际导出名称
childIncludes: ["AButton"], // 仅允许按钮
label: "按钮组",
categoryId: "base",
};
export default [button, buttonGroup];
示例 3:ECharts 集成
图表库需要特殊处理复杂的配置对象:
const chart: MaterialDescription = {
name: "XChart",
label: "图表",
categoryId: "base",
props: [
{
name: "option",
title: "ECharts option",
setters: "ObjectSetter", // 用于复杂配置的 JSON 编辑器
},
{
name: "width",
setters: ["StringNumber"],
},
{
name: "height",
setters: ["StringNumber"],
},
],
events: [
"highlight",
"downplay",
"selectchanged",
"legendselectchanged",
"datazoom",
"rendered",
"finished",
],
snippet: {
props: {
width: "100%",
height: "400px",
option: {
xAxis: {
type: "category",
data: ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"],
},
yAxis: { type: "value" },
series: [
{
data: [150, 230, 224, 218, 135, 147, 260],
type: "line",
},
],
},
},
},
};
高级配置
处理组件命名空间
对于在命名空间下导出组件的库(例如 Button.Group),使用 parent 和 alias 属性:
const namespacedComponent: MaterialDescription = {
name: "ANestedComponent", // VTJ 内部名称
parent: "ParentComponent", // 命名空间路径
alias: "Nested", // 实际属性名称
label: "嵌套组件",
// 组件将通过以下方式访问:ParentComponent.Nested
};
创建可复用的属性辅助函数
将常见的属性定义提取到工具函数中:
// shared/props.ts
export function size(name: string = "size"): MaterialProp {
return {
name,
title: "尺寸",
defaultValue: "default",
setters: "SelectSetter",
options: ["default", "large", "small"],
};
}
export function type(name: string = "type"): MaterialProp {
return {
name,
title: "类型",
defaultValue: "default",
setters: "SelectSetter",
options: ["default", "primary", "success", "warning", "danger", "info"],
};
}
// 在描述符中使用
const button: MaterialDescription = {
name: "Button",
props: [
size("size"), // 可复用的 size 属性
type("type"), // 可复用的 type 属性
],
};
组件约束
控制组件在设计器中的放置位置:
const listItem: MaterialDescription = {
name: "ListItem",
childIncludes: false, // 不能包含其他组件
parentIncludes: ["List"], // 只能在 List 组件内部
};
const container: MaterialDescription = {
name: "Container",
childIncludes: true, // 可以包含任何组件
parentIncludes: false, // 没有父级限制
};
Snippet 模板
使用 snippets 定义初始组件状态:
const form: MaterialDescription = {
name: "Form",
label: "表单",
snippet: {
name: "ElForm",
props: {
model: "formData",
labelWidth: "120px",
},
children: [
{
name: "ElFormItem",
props: { label: "用户名" },
children: [
{
name: "ElInput",
props: { vModel: "formData.username" },
},
],
},
{
name: "ElFormItem",
props: { label: "密码" },
children: [
{
name: "ElInput",
props: {
type: "password",
vModel: "formData.password",
},
},
],
},
],
},
};
平台特定注意事项
VTJ 支持多个平台,具有不同的库要求:
| 平台 | 支持的库 | 备注 |
|---|---|---|
| Web | Element Plus, Ant Design Vue, Vant, ECharts | 完整功能支持 |
| H5 Mobile | Vant, uni-ui components | 触控优化 |
| uni-app | uni-ui, uni-app 内置 | 跨平台移动端 |
| Pro | 所有 web 库 + 自定义物料 | 企业功能 |
UniApp 集成
对于 uni-app 平台,使用特定的物料配置:
// packages/materials/src/uni-app/index.ts
const uniAppMaterial: Material = {
name: "uni-app",
label: "UniApp",
library: "UniAppMaterial",
order: 10,
categories: [
{ id: "view", category: "视图容器" },
{ id: "content", category: "基础内容" },
{ id: "form", category: "表单组件" },
],
components: [
view,
text,
image,
input,
button,
// ... uni-app 特定组件
],
};
依赖管理
依赖解析器处理库资源的加载和映射:
// 依赖配置解析
const {
scripts, // JavaScript URL
css, // CSS URL
materials, // 物料配置 URL
libraryExports, // 全局库名称
materialExports, // 物料导出名称
libraryMap, // 每个库的 URL 映射
materialMapLibrary, // 物料到库的映射
} = parseDeps(dependencies, basePath, isDev);
依赖解析器会自动在开发模式下删除
.prod后缀,并向资源 URL 添加版本缓存破坏参数。
构建与部署
资源生成
使用配置的脚本构建物料包:
# 构建所有物料包
npm run build
# 构建特定库的物料
npm run build:element
npm run build:antdv
npm run build:charts
部署配置
为生产环境配置静态资源路径:
import { createDevTools } from "@vtj/pro/vite";
export default defineConfig({
base: "/sub-directory/", // 应用程序基础路径
plugins: [
createDevTools({
staticBase: "/sub-directory/", // 匹配应用基础路径
}),
],
});
最佳实践
- 使用共享工具:利用
size()、type()和其他共享辅助函数来保持一致性 - 全面的属性:使用适当的 setters 定义所有相关属性,以获得最佳的设计器体验
- 事件文档:包含所有支持的事件,以便在设计器中正确绑定事件
- 插槽定义:为容器组件显式声明插槽
- 有意义的 Snippets:提供有用的默认模板以进行快速原型设计
- 文档链接:引用官方库文档以方便开发者
- 类型安全:使用从
@vtj/core导出的 TypeScript 接口进行类型检查
故障排除
| 问题 | 解决方案 |
|---|---|
| 组件未在设计器中显示 | 检查 categoryId 是否匹配定义的分类,验证 hidden: false |
| 属性不可编辑 | 确保 setters 配置正确,检查 setter 注册 |
| 事件未触发 | 验证事件名称是否匹配库发出的事件,检查事件侦听器绑定 |
| 样式缺失 | 确认 CSS URL 在依赖配置中,验证资源加载 |
| 组件渲染时崩溃 | 检查组件版本兼容性,验证属性类型定义 |
后续步骤
- 创建自定义物料组件:学习构建完全自定义的组件
- 物料架构配置:深入了解架构定义选项
- 插件系统开发:使用插件扩展设计器功能
- 自定义 Setters 和属性编辑器:构建专用的属性编辑器
参考资料
- 开源代码仓库:gitee.com/newgateway/…