下面是参与贡献 Farris UI 的共享指南,请在反馈issue
和Pull Request
之前,花费几分钟阅读以下内容。
你也可以根据自己的实践经验,自由的通过Pull Request
修改完善这个指南。
如下图所示,在 Farris Vue 项目首页,点击右上脚的「Fork」按钮,创建属于自己的项目副本。
个人项目
中,创建新组件特性分支。如下图所示,首先进入个人命名空间
下的 Farris Vue 项目,然后点击左侧的「main」分支下拉框,在弹出的分支面板中点击「新建分支」创建新组件特性分支。
需要注意的是,特性分支以feature/{新特性}
的方式命名。
git clone https://gitee.com/farris-design/farris-vue.git
同步 Farris Vue 主项目代码 目前 Farris Vue 项目正处于活跃发展中,每天都会合并新特性PR,提交代码前,可以点击下方的刷新按钮,将个人命名空间下的 Farris Vue 项目与主项目同步一致。
安装依赖包 在本地运行项目前,请先执行以下命令,检查环境中是否已经安装 yarn。
yarn -v
如果未得到yarn
版本信息,请参考安装 yarn.
然后在 farris-vue 目录下使用yarn
安装依赖包。
cd farris-vue
yarn
cd packages/ui-vue
然后,同步npx
执行vite
命令编译预览项目。
例如:
npx vite dev --open=#designer-canvas/drag-over
需要说明的是--open
参数,其后的值为预览demo路由,#designer-canvas/drag-over
是可视化低代码设计器画布demo的路由。
更多示例demo路由,请参见:packages/ui-vue/src/app.vue
中的routes变量。
farris vue 组件代码保持在packages/ui-vue/components
目录下,开发者可以在此目录为新组件创建存储目录。
以下是组件目录结构。
input-group
├── test // 单元测试代码目录
| └── input-group.spec.tsx
├── src // 源代码目录
| ├── components // 子组件元代码目录
| | ├── appended-button.component.tsx // 附加按钮组件
| | └── text-edit.component.tsx // 文本编辑器组件
| ├── composition // 组件的可复用逻辑
| | ├── types.ts // 组合式Api返利值接口类型
| | ├── use-append-button.ts // 实现组件特性「附加按钮」的组合式Api
| | ├── use-clear.ts // 实现组件特性「清空文本」的组合式Api
| | ├── use-password.ts // 实现组件特性「显示密码」的组合式Api
| | └── use-text-box.ts // 实现组件特性「文本框」的组合式Api
| ├── designer // 设计器组件
| | └── input-group.design.component.ts // 用于可视化设计器画布的设计时组件
| ├── schema // 元组件 schema 描述目录
| | ├── input-group.schema.json // 以Json-Schema模式描述的统一UI组件JSON结构
| | └── schema-mapper.ts // schema 与 props 映射关系
| ├── input-group.component.tsx // 组件代码
| └── input-group.props.ts // 定义组件Api
└── index.ts // 组件入口文件
如果你成功领取了项目Issue,请通过Gitee推荐的「fork + pull request」的方式贡献代码。 为了保证项目代码质量,我们指定了详细的编码风格指南。 为了你的PR可以顺利通过代码审查,请在编码前认真阅读以下编码指南:
首先,在组件源代码目录下,建立名称为schema
的目录,在其下以{组件名}.schema.json
命名组件schema描述文件。
我们采用JSON Schema规范描述组件schema结构。
JSON Schema规范参见:JSON Schema 规范(中文版)
需要注意的是,除了遵循JSON Schema规范描述组件schema之外,还需要遵循以下约定。
https://farris-design.gitee.io/{组件名}.schema.json
作为命名空间,标识组件schema。{组件名}
采用「kebab-case」命名方法,例如:button-edit
、splitter-panel
等。 "$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://farris-design.gitee.io/button-edit.schema.json",
"title": "button-edit",
在properties
节点下描述组件schema结构,要求所有组件必须具有id
、type
两个属性。
其中,必须指定type
属性等默认值,即其default
属性的值为「kebab-case」命名规范的组件名。
例如:
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://farris-design.gitee.io/button-edit.schema.json",
"title": "button-edit",
"description": "A Farris Input Component",
"type": "object",
"properties": {
"id": {
"description": "The unique identifier for a Buttton Edit component",
"type": "string"
},
"type": {
"description": "The type string of Button Edit component",
"type": "string",
"default": "button-edit"
}
},
"required": [
"id",
"type"
]
}
同理,采用以上规则描述完整组件的属性结构,在default
属性中描述每个属性的默认值。
完成的button-edit
组件schema结构,参见:Button Edit 组件 Schema 结构
默认情况下,开发者可以按照与组件props
结构一致的方式描述组件的Schema结构。
因为Schema结构拥有抽象描述组件,并广泛的应用与可视化设计器、属性编辑器、解释渲染引擎、代码生成引擎等公共组件,其结构具有普遍性,与组件props
结构会出现结构性差异。
开发者需要实现schema-mapper.ts
进行结构映射。
例如:
在组件schema结构中采用apperance属性描述组件的自定义class和自定义style,在组件的props中使用customClass描述自定义class。
schema结构片段
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://farris-design.gitee.io/button-edit.schema.json",
"title": "button-edit",
"description": "A Farris Input Component",
"type": "object",
"properties": {
"appearance": {
"description": "",
"type": "object",
"properties": {
"class": {
"type": "string"
},
"style": {
"type": "string"
}
},
"default": {}
}
}
}
props结构片段
export const buttonEditProps = {
/**
* 组件自定义样式
*/
customClass: { type: String, default: '' }
};
此时需要在schema
目录下,新建文件名为schema-mapper.ts
的映射文件。
其内容为:
import { MapperFunction, resolveAppearance } from '../../../dynamic-resolver';
export const schemaMapper = new Map<string, string | MapperFunction>([
['appearance', resolveAppearance]
]);
对于apperance
属性,项目内提供了默认映射方法,可以引用dynamic-resolver
下的resolveAppearance
方法。
对于仅属性名不一样的值映射,可以直接在Map
对象中声明schema属性名和props属性名。
例如,在section
组件的schema中使用expanded
描述展开状况,在props中使用expandStatus
描述展开状态。
对于复杂的映射关系,可以使用匿名函数完成映射,例如:在section
组件的schema中使用title
描述标题,在props中使用mainTitle
描述标题。
完整示例如下:
import { resolveAppearance, MapperFunction } from '../../../dynamic-resolver';
export const schemaMapper = new Map<string, string | MapperFunction>([
['appearance', resolveAppearance],
['expanded', 'expandStatus'],
['title', (key: string, value: any) => ({ 'mainTitle': value })]
]);
设计时组件与组件本身不同,其仅应用于低代码可视化设计器,可以在其中实现「添加子元素」、「配置属性」等可视化设计器独有的交互。 在本项目中,需要独立提供设计时组件。设计时组件可以是精简交互的完整组件。
首先,在组件源代码目录,建立目录名为designer
的目录,存储设计时组件源代码。
然后,以{组件名}.design.component.tsx
命名,创建设计时组件文件。其中,组件名遵循「kebab-case」命名规范。
需要注意的是,除实现设计时组件展现和交互逻辑外,需要额外引入支持可视化设计器拖拽布局的结构。
以基本布局容器组件content-container
为例:
elementRef
的Ref
类型对象,并将器绑定至可拖拽布局的容器。drag-container
class。dragref
属性,其值为{组件标识}-container
,在项目中可以通过designItemContext.schema.id
或者组件标识。
此部分示例代码片段如下:export default defineComponent({
name: 'FContentContainerDesign',
props: contentContainerProps,
emits: [],
setup(props: ContentContainerPropsType, context: SetupContext) {
const elementRef = ref();
const designItemContext = inject<DesignerItemContext>('design-item-context') as DesignerItemContext;
return () => {
return (
<div ref={elementRef} class="drag-container" dragref={`${designItemContext.schema.id}-container`}>
{context.slots.default && context.slots.default()}
</div>
);
};
}
});
接下来,需要定义可视化拖拽校验规则,在校验规则中,验证那些类型的元素允许拖入当前容器中。
这部分的内容具有很强的自定义色彩,可以参考下面的示例代码,实现UseDesignerRules
接口。
此部分内容为缺省内容,对可以容纳任何组件的容器,可以不提供。
以基本布局容器组件content-container
为例,其「可视化拖拽校验规则」参见:use-designer-rules.ts
创建供可视化设计器使用设计时组件实例。
项目提供了创建可视化设计时组件实例的公共方法,可以使用designer-canvas
下的use-designer-component
创建设计时组件实例。
改方法需要接受三个参数:组件Html元素、可视化拓展元素上下文、可视化拖拽校验规则,其中:
组件Html元素 - 由开发者自行声明。
可视化拓展元素上下文 - 由开发者调用inject
方法从上下文环境中,使用依赖注入获取。
可视化拖拽校验规则 - 为缺省参数。
开发者创建完设计时组件实例后,需要在onMounted
事件将其记录至组件元素,需要使用expose
方法将其公开。
此部分示例代码片段如下:
const elementRef = ref();
const designItemContext = inject<DesignerItemContext>('design-item-context') as DesignerItemContext;
const designerRulesComposition = useDesignerRules(designItemContext.schema, designItemContext.parent?.schema);
const componentInstance = useDesignerComponent(elementRef, designItemContext, designerRulesComposition);
onMounted(() => {
elementRef.value.componentInstance = componentInstance;
});
context.expose(componentInstance.value);
content-container
为例的完整设计时组件代码import { SetupContext, defineComponent, inject, onMounted, ref } from 'vue';
import { ContentContainerPropsType, contentContainerProps } from '../content-container.props';
import { useDesignerRules } from './use-designer-rules';
import { DesignerItemContext } from '../../../designer-canvas/src/types';
import { useDesignerComponent } from '../../../designer-canvas/src/composition/use-designer-component';
export default defineComponent({
name: 'FContentContainerDesign',
props: contentContainerProps,
emits: [],
setup(props: ContentContainerPropsType, context: SetupContext) {
const elementRef = ref();
const designItemContext = inject<DesignerItemContext>('design-item-context') as DesignerItemContext;
const designerRulesComposition = useDesignerRules(designItemContext.schema, designItemContext.parent?.schema);
const componentInstance = useDesignerComponent(elementRef, designItemContext, designerRulesComposition);
onMounted(() => {
elementRef.value.componentInstance = componentInstance;
});
context.expose(componentInstance.value);
return () => {
return (
<div ref={elementRef} class="drag-container" dragref={`${designItemContext.schema.id}-container`}>
{context.slots.default && context.slots.default()}
</div>
);
};
}
});
在低代码场景下,低代码引擎需要接受组件的Schema结构,动态解释执行代码,将shema结构转换为组件。
此时需要组件提供可以由schema结构解释创建props的服务。
开发者需要在组件的props文件中,创建propsResolver
对象。
开发者可以使用dynamic-resolver
中提供的createPropsResolver
创建props解析器。
createPropsResolver
方法接受三个参数:组件props对象、组件schema结构、组件schema结构与props映射对象,其中:
组件props对象 - 由开发者声明组件时自行声明。
组件schema结构 - 由开发者在步骤「3」中创建。
组件schema结构与props映射对象 - 为缺省参数,由开发者在步骤「4」中根据需要创建。
以content-container
为例完整输出propsResolver
的示例代码
import { ExtractPropTypes } from 'vue';
import { createPropsResolver } from '../../dynamic-resolver';
import { schemaMapper } from './schema/schema-mapper';
import contentContainerSchema from './schema/content-container.schema.json';
export const contentContainerProps = {
customClass: { type: String, default: '' }
} as Record<string, any>;
export type ContentContainerPropsType = ExtractPropTypes<typeof contentContainerProps>;
export const propsResolver = createPropsResolver<ContentContainerPropsType>(contentContainerProps, contentContainerSchema, schemaMapper);
完成以上步骤后,开发者需要在index.ts
文件中注册输出组件。
install
方法中,使用app.component
方法全局注册组件。register
方法中,为低代码解析引擎提供组件及其解析Props服务。registerDesinger
方法中,为低代码可视化设计器提供设计时组件及其解析Props服务。其中解析Props服务可以在低代码解析引擎和可视化设计器中复用。以content-container
为例完整注册示例代码
import type { App } from 'vue';
import ContentContainer from './src/content-container.component';
import ContentContainerDesign from './src/designer/content-container.design.component';
import { propsResolver } from './src/content-container.props';
export * from './src/content-container.props';
export { ContentContainer, ContentContainerDesign };
export default {
install(app: App): void {
app.component(ContentContainer.name, ContentContainer);
},
register(componentMap: Record<string, any>, propsResolverMap: Record<string, any>): void {
componentMap['content-container'] = ContentContainer;
propsResolverMap['content-container'] = propsResolver;
},
registerDesigner(componentMap: Record<string, any>, propsResolverMap: Record<string, any>): void {
componentMap['content-container'] = ContentContainerDesign;
propsResolverMap['content-container'] = propsResolver;
}
};
开发者声明注册插件后,需要在项目源代码根目录的index.ts
文件中使用。
import { App } from 'vue';
import ContentContainer from './content-container';
export default {
install(app: App): void {
app.use(ContentContainer);
}
};
完整示例源代码参见组件库index.ts
开发者声明注册插件后,需要在可视化设计器中使用组件。
编辑designer-canvas
下的maps.ts
文件,增加以下内容:
import FContentContainer from '../../../content-container';
const componentMap: Record<string, any> = {};
const componentPropsConverter: Record<string, any> = {};
FContentContainer.registerDesigner(componentMap, componentPropsConverter);
export { componentMap, componentPropsConverter };
完整示例源代码参见设计器组件库
开发者完成设计时组件后,需要在工具箱组件注册元组件。
编辑designer-canvas/src/components/toolbox.json
文件,添加工具箱选项。
需要注意的是,选项的type
属性为注册组件时使用的key
。
例如:
[
{
"type": "container",
"name": "容器类控件",
"items": [
{
"id": "Tab",
"type": "Tab",
"name": "标签页区域",
"category": "container"
},
{
"id": "HtmlTemplate",
"type": "HtmlTemplate",
"name": "模版容器",
"category": "container"
},
{
"id": "ContentContainer",
"type": "content-container",
"name": "容器",
"category": "container"
}
]
}
]
完整工具箱注册代码请参见Designer Canvas Toolbox
恭喜各位开发者,通过以上步骤,你已经完成了开发组件的全部步骤。 下面可以制作示例页面了。
所有 Farris Vue 组件均在项目跟目录下的demo
目录中提供示例程序。
对于低代码可视化拓展元组件,可以在packages/ui-vue/demos/designer-canvas
目录下添加示例代码。
drag-over.json
制作低代码页面母版。示例Designer Canvas Demo - 页面Schema结构
drag-over.vue
将其中引用的drag-over.json
改为步骤1中新建的页面Schema结构文件。packages/ui-vue/src/app.vue
中,引入步骤2创建的组件,并注册路由。参见:app.vue
npx vite dev --open=#{步骤3中注册的路由路径}
我们欢迎你通过提交PR参与项目贡献,在你计划提交PR前,请先阅读以下注意事项:
在你提交PR之前请确保已经开启了一个Issue并认领了它,我们只接收与认领Issue关联的PR。如果你打算实现一个比较大的特性,在开启新的Issue前最好先与项目管理者进行充分讨论。
在没有十足把握时,尽量提交小规格的PR。不要在一个PR中修复多于一个bug或实现多于一个新特性,以便于更容易被接受。提交两个小规模的PR,会比提交一个大规模修改的PR要好。
当你提交新特性,或者修改已有特性时,请包含相应的测试代码,以便于确认组件新的交互特性。
在提交PR前端请先执行Rebase以便于保持干净的历史提交记录。
我们提供了PR模板,请在提交PR时安装模板要求提供「修改的内容」、「管理的PR」、「测试用例」、「界面预览」等相关内容。
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。