schema-form是一个基于JSON Schema标准的构建表单。
- 符合最新的 JSON Schema 7.0 标准。
- 基于但不限于 antd-design 基础组件库。
- 目前定义 8 种小部件,可动态注册。
- 可自定义小部件满足各种业务需求。
- 支持自定义校验,自定义错误信息文本
- 基于 Grid 栅格系统,根据 ui.grid 配置自动完成布局。
- 代码以
schema.
开头的表示 JSON Schema 对象属性 - 代码以
ui.
开头的表示 UI 属性 - 所有的部件都以对象路径的方式检索,格式为
a.b[0].c
npm install ks-schema-form
或者
yarn add ks-schema-form
如果使用antd库,
import {SF} from 'ks-schema-form/lib/antd';
如果使用KPC库
import {SF} from 'ks-schema-form/lib/kpc';
开始
import React from 'react';
import 'antd/dist/antd.css';
import {FormProperty, WidgetProperty, SF, Schema} from 'ks-schema-form/lib/antd';
function App() {
const schema: Schema = {
properties: {
name: {
type: "string",
title: "姓名",
},
email: {
type: "string",
format: "email",
title: "邮箱",
},
},
ui: {
layout: "inline",
actions: [
{
text: "搜索",
onClick: function(this: FormProperty, values: IFormData) {
console.log(values);
}
},
]
}
}
return (
<div>
<SF schema={schema} />
</div>
)
}
通过 cdn.jsdelivr.net/npm/ks-schema-form/dist/ 引入最新版,建议使用固定版本号,例如:cdn.jsdelivr.net/npm/ks-schema-form@1.0.5/dist/
使用 antd 引入 sf.antd.min.js,使用 kpc 引入 sf.kpc.min.js
antd.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Schema Form Example</title>
<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/antd/dist/antd.min.css" />
</head>
<body>
<div id="app"></div>
<script src="https://cdn.jsdelivr.net/npm/react/umd/react.development.js"></script>
<script src="https://cdn.jsdelivr.net/npm/react-dom/umd/react-dom.development.js"></script>
<script src="https://cdn.jsdelivr.net/npm/babel-standalone/babel.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/antd/dist/antd.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/ks-schema-form@1.0.6/dist/sf.antd.min.js"></script>
<script type="text/babel">
const { SF } = sf;
function App() {
const schema = {
properties: {
name: {
type: "string",
title: "姓名",
default: "john",
ui: {
placeholder: "请输入姓名",
allowClear: true,
}
},
sex: {
type: "string",
title: "性别",
ui: {
widget: "select",
placeholder: "请选择性别",
style: {width: "150px"},
options: [
{label: "男", value: "男"},
{label: "女", value: "女"},
]
}
},
age: {
type: "number",
title: "年龄",
ui: {
style: {width: "100px"},
placeholder: "年龄",
}
},
province: {
type: "string",
title: "省份",
default: "hb",
ui: {
widget: "select",
style: {width: "150px"},
placeholder: "请选择省份",
options: [
{label: "北京", value: "bj"},
{label: "黑龙江", value: "hlj"},
{label: "湖北", value: "hb"},
]
}
},
},
ui: {
layout: "inline",
actions: [
{
text: "搜索",
style: {width: "100px"},
onClick: function(values) {
console.log(values);
alert(JSON.stringify(values));
}
},
{
text: "导出",
style: {width: "100px"},
type: "default",
onClick: function(values) {
console.log(values);
alert(JSON.stringify(values));
}
},
]
}
}
return (
<div style={{margin: "40px 50px"}}>
<SF schema={schema} />
</div>
)
}
ReactDOM.render(<App />, document.getElementById('app'));
</script>
</body>
</html>
kpc.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Schema Form Example</title>
<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/kpc/dist/kpc.css" />
</head>
<body>
<div id="app"></div>
<script src="https://cdn.jsdelivr.net/npm/react/umd/react.development.js"></script>
<script src="https://cdn.jsdelivr.net/npm/react-dom/umd/react-dom.development.js"></script>
<script src="https://cdn.jsdelivr.net/npm/babel-standalone/babel.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/kpc/dist/kpc.react.js"></script>
<script src="https://cdn.jsdelivr.net/npm/ks-schema-form@1.0.6/dist/sf.kpc.min.js"></script>
<script type="text/babel">
const { SF, FormProperty } = sf;
function App() {
const sf = React.useRef(new FormProperty());
const submit = () => {
sf.current.validates()
console.log(sf.current.getValues());
}
const schema = {
properties: {
name: {
type: "string",
title: "姓名",
minLength: 3,
maxLength: 10,
default: "john",
},
email: {
type: "string",
format: "email",
title: "邮箱",
},
},
required: ["name", "email", "age"],
ui: {
grid: {labelWidth: "100px"},
}
}
return (
<div style={{margin: "40px 50px", width: "800px"}}>
<SF schema={schema} ref={sf} />
<button onClick={submit} style={{width:"200px",height:"30px",marginLeft:"100px",marginTop:"20px"}}>提交</button>
</div>
)
}
ReactDOM.render(<App />, document.getElementById('app'));
</script>
</body>
</html>
antd
- string
- textarea
- search
- password
- boolean
- checkbox
- checkgroup
- datepicker
- rangepicker
- number
- object
- radiogroup
- slider
- custom
kpc
- string
表单组件
参数 | 说明 | 类型 | 默认值 |
---|---|---|---|
schema | 必填项 JSON Schema | Schema | - |
使用 ref 获取表单的 FormProperty 以与表单交互。
表单状态管理工具,允许在外部与表单交互。
属性/方法 | 说明 | 类型 | 默认值 |
---|---|---|---|
schema | 表单的 schema 对象,已去除 $ref。 | () => Schema |
- |
getValues | 获取表单当前值 | () => IFormData |
- |
setValues | 设置表单当前值,触发表单的 ui.onChange事件 | (values: IFormData) => void |
- |
resetValues | 重置为初始值 schema.default , 触发表单的 ui.onChange事件 | () => void |
- |
validates | 触发 ajv 验证,同时触发表单和所有部件的ui.onValidate事件 | () => void |
- |
getProperty | 根据对象路径获取部件的 WidgetProperty 以与部件交互 | (path: string) => WidgetProperty |
- |
getValue | 根据对象路径获取部件的当前值 | (path: string) => SFValue |
- |
部件状态管理工具,允许在外部与部件交互。
属性/方法 | 说明 | 类型 | 默认值 |
---|---|---|---|
value | 获取部件的当前值 | () => SFValue |
- |
setValue | 设置部件的当前值 | (value: SFValue) => void |
- |
propertyName | 获取部件的在 schema 中的键值 | () => string |
- |
reset | 重置部件的当前值 schema.default,优先级高于表单的 schema.default | () => void |
- |
setError | 设置部件的错误信息 | (error: string) => void |
- |
resetError | 清除部件的错误信息 | () => void |
- |
setSchema | 设置当前部件的 schema 信息, 部件的 schema 仅包含表单 schema 中对应该部件的声明部分 | (schema: Schema) => void |
- |
setUI | 设置当前部件的 ui 信息 | <UI extends SCUI>(ui: UI) => void |
- |
path | 获取当前部件的对象路径 | () => string |
- |
formProperty | 获取表单 formProperty | () => FormProperty |
- |
部件注册管理工具
属性/方法 | 说明 | 类型 | 默认值 |
---|---|---|---|
register | 根据名称注册部件,解析 schema 时会根据 ui.widget 和 schema.type 查找对应名称的部件,如果没有 ui.widget 字段则会使用 schema.type 字段去查找,如果名称未注册则会使用 widgetRegistry.setDefault 设置的默认部件 | (name: string, widget: WidgetType) => void |
- |
setDefault | 设置默认部件,如果使用未注册的部件,默认使用此值 | (widget: WidgetType) => void |
- |
get | 根据名称获取部件,缺省则返回默认值 | (name: string) => WidgetType |
- |
registrySF | 注册 sf 表单,每个组件库都应该注册自己的 form 组件,例如 lib/antd/form.tsx 会调用该方法自动注册 | (name: string, sf: WidgetType) => void |
- |
getSF | 获取 sf 表单,未注册会显式抛出异常 | (name: string) => WidgetType | - |
Widget<UI extends SCUI> extends React.Component<WidgetProps<UI>>
所有的 class 部件都应该继承此类,以使用 widgetProperty工具。
useWidget<UI extends SCUI>(props: WidgetProps<UI>)
所有的 function 部件都应该使用此 hook ,以使用 widgetProperty 工具。
JSON Schema 语法解析工具
属性/方法 | 说明 | 类型 | 默认值 |
---|---|---|---|
getSchema | 根据对象路径获取 schema 节点 | (schema: Schema, path: string) => SchemaNode |
- |
ignoreNode | 深度搜索时忽略的节点列表,默认忽略 ui 节点 | (key: string, node: SchemaNode) => boolean |
- |
eliminateRef | 将 schema 中的 $ref 语法替换成对应的 schema,递归调用,支持 $ref 路径和 $id 的引用方式,不支持跨文件引用,$ref 路径必须符合 JSON Schema 标准。 | (schema: Schema, node: SchemaNode = schema) => void |
- |
parseRef | 解析 $ref | (schema: Schema, ref: string) => void |
- |
searchSchemaByRef | 根据 $ref 路径搜索 | - | - |
searchSchemaById | 根据 $ref $id 搜索 | - | - |
deepSearchByRef | 根据 $ref 路径深度优先搜索 | - | - |
deepSearchById | 根据 $ref $id 深度优先搜索 | - | - |
在符合 JSON Schema 标准的基础上加入 ui 配置,每个部件的 ui 配置都不相同,但共同继承 SCUI,ui 节点包括 grid 部件配置、自定义错误信息配置、公共事件等。
export interface Schema extends SchemaNumber, SchemaString, SchemaObject, SchemaArray {
[key: string]: any;
$schema?: string;
type?: SchemaType;
/* Structuring a complex schema */
definitions?: {
[key: string]: Schema;
}
$id?: string;
$ref?: string;
/* Generic keywords */
title?: string;
description?: string;
default?: any;
examples?: any[];
$comment?: string;
readOnly?: boolean;
enum?: SchemaEnumType;
const?: any;
/* Media: string-encoding non-JSON data */
contentMediaType?: string;
contentEncoding?: '7bit' | '8bit' | 'binary' | 'quoted-printable' | 'base64';
/* Combining schemas */
allOf?: Schema[];
anyOf?: Schema[];
oneOf?: Schema[];
not?: Schema;
/* Applying subschemas conditionally */
if?: Schema;
then?: Schema;
else?: Schema;
ui?: SCUI;
}
export interface SCUI {
[key: string]: any;
widget?: string;
style?: CSSProperties;
className?: string;
grid?: SCGrid;
errors?: {
[keyword: string]: string;
};
onValidate?: (values: any) => boolean;
}
export interface SCGrid {
hideLabel?: boolean;
labelAlign?: 'left' | 'right' | 'center';
labelCol?: number;
labelWidth?: string;
labelStyle?: CSSProperties;
controlCol?: number;
controlOffset?: number;
controlWidth?: string;
controlStyle?: CSSProperties;
}
配置项 | 说明 | 类型 | 默认值 |
---|---|---|---|
type | 数据类型,如果缺省 ui.widget 则会使用这个字段查找部件 | `'number' | 'string' |
title | 解析为 FormItem 的 label | string | - |
description | 暂不支持 | string | - |
default | 解析为部件的默认值,如果出现在顶层,则解析为表单的默认值。 | any | - |
readOnly | 暂不支持 | boolean | - |
ui | 表单或部件的 ui 配置,最顶部的 ui 会解析为表单配置,节点的 ui 会解析为部件的 ui 配置 | SCUI |
- |
$ref 的值必须以 # 开头,后面跟 schema 的路径。
schema: Schema = {
properties: {
name: {
type: 'string',
title: '姓名',
maxLength: 20,
},
alias: {
$ref: "#/properties/name",
title: "别名",
},
}
}
使用 $id 定位 ref,$id 值必须符合 JSON Schema 规范,即以 # 开头
schema: Schema = {
properties: {
name: {
type: 'string',
title: '姓名',
maxLength: 20,
$id: "#name",
},
alias: {
$ref: "#name",
title: "别名",
},
}
}
表单或部件的 ui 配置,每个部件的 ui 配置都不相同,下面时所有部件都支持的公共配置。
配置项 | 说明 | 类型 | 默认值 |
---|---|---|---|
widget | 部件类型,缺省会使用 schema.type | string | - |
grid | 栅格布局配置 | SCGrid |
- |
errors | 自定义错误信息配置, JSON Schema 校验过程中会生产一组错误信息,每一个错误都有一个固定的 keyword 来表示,使用 ui.errors 可覆盖默认的配置。 | ||
onValidate | 表单校验时触发,必须返回true,否则导致验证失败 | - | - |
style | 部件或表单样式 | - | - |
className | 部件或表单类名 | - | - |
export interface SCFormUI extends SCUI {
layout?: 'horizontal' | 'vertical' | 'inline';
colon?: boolean;
onChange?: (values: IFormData) => void;
actions?: IBtnOption[],
}
schema 根节点下面的 ui 配置为整个表单的ui配置,例如监听整个表单值改变。
schema: Schema = {
properties: {
email: {...}
}
ui: {
layout: "inline",
onChange: function(values: IFormData) {
console.log("values改变:" + values);
}
}
}
自定义错误信息配置
schema: Schema = {
properties: {
email: {
type: 'string',
title: '邮箱',
format: 'email',
maxLength: 20,
ui: {
errors: {
required: '必填项'
}
}
}
}
}
schema: Schema = {
properties: {
email: {
type: 'string',
title: '邮箱',
format: 'email',
maxLength: 20,
ui: {
onValidate: function(this: WidgetProperty) {
if(!/^a/.test(this.value)) {
this.setError("必须以a开头");
return false;
}
return true;
}
}
}
}
}
ui 配置中所有的函数都自动绑定this为当前 WidgetProperty ,暂不支持异步校验。
表单或者部件布局配置。
export interface SCGrid {
hideLabel?: boolean;
labelAlign?: 'left' | 'right' | 'center';
labelCol?: number;
labelWidth?: string;
labelStyle?: CSSProperties;
controlCol?: number;
controlOffset?: number;
controlWidth?: string;
controlStyle?: CSSProperties;
}
配置项 | 说明 | 类型 | 默认值 |
---|---|---|---|
hideLabel | 隐藏部件的 label | boolean | false |
labelAlign | label 对齐方式 | 'left','center','right' |
'right' |
labelCol | label 所占的栅格数 | number | - |
labelWidth | label 宽度,可以被 labelStyle.width 覆盖 | string | 'auto' |
labelStyle | 配置 label 的样式 | CSSProperties | - |
controlCol | 部件 control 所占的栅格数 | number | - |
controlOffset | 部件 control 栅格左移数 | number | - |
controlWidth | 部件 control 的宽度,可以被 controlStyle.width 覆盖 | string | 'auto' |
controlStyle | 部件 control 的样式 | CSSProperties | - |
表单的 ui.grid 会作用于所有子部件,每个部件也可以定义自己的 ui.grid 配置。
schema: Schema = {
properties: {
email: {
type: 'string',
title: '邮箱',
format: 'email',
maxLength: 20,
ui: {
grid: {
labelAlign: "left",
labelWidth: "120px",
}
}
}
},
ui: {
grid: {
labelAlign: "right",
labelWidth: "100px",
}
}
}
string 类型默认使用 input 组件,可配置 ui.widget 使用其他组件。
ui 配置参考
interface StringWidgetUI extends SCUI {
placeholder?: string;
disabled?: boolean;
autocomplete?: 'on' | 'off';
autofocus?: boolean;
addonBefore?: string | ReactNode;
addonAfter?: string | ReactNode;
prefix?: string | ReactNode;
suffix?: string | ReactNode;
onChange?: (val: string) => void;
onFocus?: (e: FocusEvent) => void;
onBlur?: (e: FocusEvent) => void;
onEnter?: (e: KeyboardEvent) => void;
}
使用 sting 部件,无需配置 ui.widget。
如下示例:姓名输入联动,自动回填到 alias 部件。
schema: Schema = {
properties: {
name: {
type: "string",
title: "姓名",
maxLength: 10,
ui: {
placeholder: "请输入姓名",
onChange: function(this: WidgetProperty, val: string) {
this.formProperty.getProperty("alias").setValue(val);
}
}
},
alias: {
type: 'string',
title: '别名',
ui: {
placeholder: "请输入别名",
}
},
}
}
number 类型默认使用 InputNumber 组件,可配置 ui.widget 使用其他组件。
ui 配置参考
interface NumberWidgetUI extends SCUI {
placeholder?: string;
disabled?: boolean;
autofocus?: boolean;
formatter?: (val?: number | string) => string;
parser?: (val?: string) => number;
precision?: number;
decimalSeparator?: string;
step?: number | string;
onChange?: (val?: string | number) => void;
onEnter?: (e: KeyboardEvent) => void;
}
使用 number 部件无需配置 ui.widget.
schema: Schema = {
properties: {
age: {
type: "number",
title: "姓名",
minimum: 10,
maximum: 20,
},
}
}
boolean 类型默认使用 switch 组件,可通过 ui.widget 配置其他组件。
ui 配置参考
interface BooleanWidgetUI extends SCUI {
disabled?: boolean;
autoFocus?: boolean;
checkedChildren?: string | ReactNode;
unCheckedChildren?: string | ReactNode;
onChange?: (checked: boolean) => void;
}
使用 boolean 部件无需配置 ui.widget.
schema: Schema = {
properties: {
isMarital: {
type: "boolean",
title: "已婚",
default: false,
},
}
}
ui 配置参考
interface SelectWidgetUI extends SCUI {
mode?: 'multiple' | 'tags';
allowClear?: boolean;
placeholder?: string;
disabled?: boolean;
autofocus?: boolean;
filterOption?: boolean; // 如果为true,默认启用全拼搜索label字段
showSearch?: boolean;
options?: {label: string; value: SFValue;}[];
onChange?: (val: string) => void;
onFocus?: (e: FocusEvent) => void;
onBlur?: (e: FocusEvent) => void;
onSearch?: (e: SFValue) => void;
}
使用 select 部件,需配置 ui.widget = "select"。
schema: Schema = {
properties: {
province: {
type: "string",
title: "省份",
ui: {
widget: "select",
placeholder: "请选择省份",
options: [
{label: "北京", value: "bj"},
{label: "天津", value: "tj"},
{label: "上海", value: "sh"},
]
}
},
isp: {
type: 'array',
title: '运营商',
ui: {
widget: "select",
placeholder: "请选择运营商",
mode: "multiple",
options: [
{label: "移动", value: "cm"},
{label: "联通", value: "um"},
{label: "电信", value: "cn"},
]
}
},
layer: {
type: "number",
title: "层级",
ui: {
widget: "select",
placeholder: "请选择层级",
optiosn: [
{label: "上层", value: 0},
{label: "中层", value: 1},
{label: "下层", value: 2},
]
}
}
}
}
object 类型默认使用 card 组件,可配置 ui.widget 使用其他组件。
ui 配置参考
interface ObjectWidgetUI extends SCUI {
bordered?: boolean;
hoverable?: boolean;
headStyle?: CSSProperties;
bodyStyle?: CSSProperties;
}
object 可支持嵌套,object 也可配置 ui.grid,并且对该类型下的所有子部件都生效。
schema: Schema = {
properties: {
name: {
type: "string",
}
address: {
type: "object",
title: "详细地址",
properties: {
province: {
type: "string",
title: "省份",
default: "hb",
ui: {
widget: "select",
options: [
{label: "北京", value: "bj"},
{label: "黑龙江", value: "hlj"},
{label: "湖北", value: "hb"},
]
}
},
city: {
type: "string",
title: "城市",
},
zone: {
type: "string",
title: "地区"
},
},
required: ["province", "city"],
ui: {
grid: {
labelWidth: "70px"
}
},
},
}
}
ui 配置参考
interface CheckBoxGroupWidgetUI extends SCUI {
disabled?: boolean;
options?: (string | CheckboxOptionType)[];
onChange?: (checkedValue: CheckboxValueType[]) => void;
}
使用 select 部件,需配置 ui.widget = "checkbox.group"。
schema: Schema = {
properties: {
province: {
type: "array",
title: "省份",
default: "hb",
ui: {
widget: "checkbox.group",
options: [
{label: "北京", value: "bj"},
{label: "黑龙江", value: "hlj"},
{label: "湖北", value: "hb"},
]
}
},
}
}
custom 部件支持自定义渲染。
ui 配置参考
interface CustomWidgetUI extends SCUI {
render?: (widgetProperty: WidgetProperty) => ReactNode;
}
使用 custom 部件,需配置 ui.widget = "custom"。
schema: Schema = {
properties: {
custom: {
type: "string",
title: "自定义",
ui: {
widget: "custom",
render: (widgetProperty: WidgetProperty) => {
const { ui } = widgetProperty;
return <div>我是自定义内容, {ui.title}</div>
}
}
}
}
}