Appearance
ProForm 表单
TIP
- ProForm 是基于 Shineout Form 封装的配置驱动表单组件,通过 JSON Schema 声明式地生成完整表单,无需手写 JSX。它是 low-code 体系的核心表单引擎
js
// 设置全局回车不去触发表单校验 + 提交
import { setConfig } from "@lite-code/pro-shineout";
setConfig({
enterPressTouchSubmit: false,
});基本使用
基于配置化的方式渲染
tsx
tsx
import { ProForm } from "@lite-code/pro-shineout";
export default () => {
const [form] = ProForm.useForm(); // 获取实例
return (
<ProForm
column={2}
form={form} // 传递即可
defaultValue={{
input: "123456",
rangeInput: [1, 10],
inputNumber: 90,
inputMore: "168,238,367",
select: 1,
switch: false,
radio: 1,
checkbox: [1],
slider: 7,
selectMore: [1, 2],
cascader: ["jiangsu", "nanjing"],
datePicker: "2025-03-24",
datePickerRangeStart: "2025-03-24",
datePickerRangeEnd: "2026-07-23",
treeSelect: ["2-2"],
}}
schema={[
{
type: "Block",
props: {
title: "基础组件",
},
},
{
type: "Input",
name: "input",
label: "输入框",
props: {
rules: [
// @ts-ignore
(val, values, callback) => {
console.log("val", val, values);
if (val === "100") {
callback(new Error("不能是100"));
} else {
callback(true);
}
},
],
},
},
{
type: "RangeInput",
name: "rangeInput",
label: "区间输入框",
required: true,
tooltip: "通常在定制一些区间范围比较有用",
props: {
startProps: {
min: 0,
},
endProps: {
max: 100,
},
},
},
{
type: "InputMore",
name: "inputMore",
label: "多条输入框",
required: true,
tooltip: "点击右侧icon,多条数据,换行区分",
props: {
max: 10,
title: "多条数据",
modalPlaceholder: "多条数据,换行区分",
},
},
{
type: "InputNumber",
name: "inputNumber",
label: "数字输入框",
props: {
min: 1,
max: 999,
},
},
{
type: "Select",
name: "select",
label: "下拉选",
props: {
data: [
{ label: "选项1", value: 1 },
{ label: "选项2", value: 2 },
],
},
},
{
type: "Select",
name: "selectMore",
label: "下拉多选",
props: {
multiple: true,
data: [
{ label: "选项1", value: 1 },
{ label: "选项2", value: 2 },
],
},
},
{
type: "RadioGroup",
name: "radio",
label: "单选按钮组",
props: {
data: [
{ label: "选项1", value: 1 },
{ label: "选项2", value: 2 },
],
},
},
{
type: "CheckGroup",
name: "checkbox",
label: "复选框1",
props: {
data: [
{ label: "选项1", value: 1 },
{ label: "选项2", value: 2 },
],
},
},
{
type: "Block",
props: {
title: "其他组件",
},
},
{
type: "Rate",
name: "rate",
label: "评分",
},
{
type: "Switch",
name: "switch",
label: "开关切换",
},
{
type: "Slider",
name: "slider",
label: "滑块组件",
props: {
formatValue: false,
formatScale: false,
},
},
{
type: "TreeSelect",
name: "treeSelect",
label: "下拉树组件",
props: {
defaultExpandAll: true,
keygen: "id",
renderItem: "title",
multiple: true,
compressed: true,
mode: 2,
data: [
{
id: "1",
title: "1",
children: [
{
id: "1-1",
title: "1-1",
children: [
{ id: "1-1-1", title: "1-1-1" },
{ id: "1-1-2", title: "1-1-2" },
],
},
{ id: "1-2", title: "1-2" },
],
},
{
id: "2",
title: "2",
children: [
{ id: "2-1", title: "2-1" },
{ id: "2-2", title: "2-2" },
],
},
{ id: "3", title: "3", children: [{ id: "3-1", title: "3-1" }] },
],
},
},
{
type: "Cascader",
name: "cascader",
label: "级联选择器",
props: {
data: [
{
value: "jiangsu",
label: "江苏省",
children: [
{
value: "nanjing",
label: "南京市",
},
],
},
{
value: "anhui",
label: "安徽省",
children: [
{
value: "hefei",
label: "合肥市",
},
],
},
],
},
},
{
type: "Block",
props: {
title: "日期组件",
},
},
{
type: "DatePicker",
name: "datePicker",
label: "选择日期",
},
{
type: "DatePicker",
label: "选择日期范围",
props: {
range: true,
name: ["datePickerRangeStart", "datePickerRangeEnd"],
},
},
{
type: "TextArea",
name: "textarea",
label: "大文本",
tip: "这是一段描述信息",
span: 24,
props: {
rows: 2,
},
},
{
type: "UploadFile",
name: "files",
label: "文件上传",
span: 24, // 占满一行
props: {
action: "//jsonplaceholder.typicode.com/posts",
accept: "image/*",
name: "file",
// @ts-ignore
onSuccess: (_, file) => file.name,
style: { width: 300 },
text: "点击上传",
},
},
]}
onChange={(vs: any) => {
console.log("onChange", vs, form?.getValue());
}}
/>
);
};
卡片表单
设置 onSubmit 可成为卡片提交表单
tsx
tsx
import { ProForm } from "@lite-code/pro-shineout";
import { Button } from "shineout";
const sleep = () => new Promise((res) => setTimeout(res, 1000));
export default () => {
return (
<ProForm
column={2}
height={450}
defaultValue={{
input: "123456",
inputNumber: 2323,
select: 1,
radio: 1,
rate: 3,
checkbox: [1],
}}
schema={[
{
type: "Input",
name: "input",
label: "输入框",
required: true,
},
{
type: "InputNumber",
name: "inputNumber",
label: "数字输入框",
props: {
min: 1,
max: 999,
},
},
{
type: "Select",
name: "select",
label: "下拉选",
props: {
data: [
{ label: "选项1", value: 1 },
{ label: "选项2", value: 2 },
],
},
},
{
type: "CheckGroup",
name: "checkbox",
label: "复选框",
props: {
data: [
{ label: "选项1", value: 1 },
{ label: "选项2", value: 2 },
],
},
},
{
type: "TreeSelect",
name: "treeSelect",
label: "下拉树组件",
props: {
defaultExpandAll: true,
data: [
{
label: "Trunk",
value: "node1",
selectable: false,
children: [
{
value: "node2",
label: "Leaf1",
},
],
},
{
label: "Trunk2",
value: "node3",
selectable: false,
children: [
{
value: "node4",
label: "Leaf2",
},
{
value: "node5",
label: "Leaf3",
},
],
},
],
},
},
{
type: "Cascader",
name: "cascader",
label: "级联选择器",
props: {
data: [
{
value: "jiangsu",
label: "江苏省",
children: [
{
value: "nanjing",
label: "南京市",
},
],
},
{
value: "anhui",
label: "安徽省",
children: [
{
value: "hefei",
label: "合肥市",
},
],
},
],
},
},
{
type: "DatePicker",
name: "datePicker",
label: "选择日期",
},
{
type: "DatePicker",
name: "datePickerRange",
label: "选择日期范围",
props: {
range: true,
},
},
{
type: "TextArea",
name: "textarea",
label: "大文本",
tip: "这是一段描述信息",
span: 24,
required: true,
props: {
rows: 2,
},
},
]}
title="提交"
onSubmit={async (v) => {
await sleep();
console.log(v);
}}
footerProps={{
textAlign: "center",
extra: <Button>生成创建表脚本</Button>,
}}
/>
);
};
灵活布局
使用 span 来控制布局
tsx
tsx
import { ProForm } from '@lite-code/pro-shineout'
export default () => {
return (
<ProForm
column={3}
schema={[
{
type: 'Input',
name: 'input',
label: '输入框',
required: true,
},
{
type: 'InputNumber',
name: 'inputNumber',
label: '数字输入框',
span: 16,
props: {
min: 1,
max: 999,
},
},
{
type: 'Select',
name: 'selectMore',
label: '下拉多选',
span: 12,
props: {
multiple: true,
data: [{ label: '选项1', value: 1 }],
},
},
{
type: 'TreeSelect',
name: 'treeSelect',
label: '下拉树组件',
span: 12,
props: {
defaultExpandAll: true,
data: [
{
label: 'Trunk',
value: 'node1',
selectable: false,
children: [
{
value: 'node2',
label: 'Leaf1',
},
],
},
{
label: 'Trunk2',
value: 'node3',
selectable: false,
children: [
{
value: 'node4',
label: 'Leaf2',
},
{
value: 'node5',
label: 'Leaf3',
},
],
},
],
},
},
{
type: 'Select',
name: 'select',
label: '下拉选',
span: 24,
props: {
data: [
{ label: '选项1', value: 1 },
{ label: '选项2', value: 2 },
],
},
tip: '这是一段描述信息',
},
{
type: 'Cascader',
name: 'cascader',
label: '级联选择器',
props: {
data: [
{
value: 'jiangsu',
label: '江苏省',
children: [
{
value: 'nanjing',
label: '南京市',
},
],
},
{
value: 'anhui',
label: '安徽省',
children: [
{
value: 'hefei',
label: '合肥市',
},
],
},
],
},
},
{
type: 'DatePicker',
name: 'datePicker',
label: '选择日期',
},
{
type: 'DatePicker',
name: 'datePickerRange',
label: '选择日期范围',
props: {
range: true,
},
},
]}
/>
)
}
基础联动
使用 effect + visible 来处理简单的联动
tsx
tsx
import { ProForm } from "@lite-code/pro-shineout";
export default () => {
return (
<ProForm
style={{
width: 300
}}
schema={[
{
type: "RadioGroup",
name: "sex",
label: "性别",
props: {
data: [
{
label: "男",
value: 0,
},
{
label: "女",
value: 1,
},
],
},
},
{
type: "InputNumber",
name: "age",
label: "年龄",
effect: ["sex"],
visible: (datum) => {
return datum.get("sex") === 0;
},
},
{
type: "RadioGroup",
name: "type",
label: "类型",
props: {
onChange() {
this.set({ dateRange: [] });
},
data: [
{
label: "日",
value: 0,
},
{
label: "周",
value: 1,
},
{
label: "月",
value: 2,
},
],
},
},
{
type: "DatePicker",
name: "dateRange",
label: "日期范围",
effect: ["type"],
extendProps(formInstance) {
return {
range: true,
// 设置类型联动
type: ["day", "week", "month"][
formInstance?.getValue?.().type || 0
],
};
},
},
]}
/>
);
};
子表单
适用于 3-4 个字段的场景
tsx
tsx
import { ProForm } from "@lite-code/pro-shineout";
import { Message } from "shineout";
export default () => {
return (
<ProForm
defaultValue={{
options: [
{
label: "选项1",
value: "1001",
},
{
label: "选项2",
value: "1002",
},
],
}}
schema={[
{
type: "FormList",
name: "options",
label: "设置选项 (可拖动调整顺序)",
props: {
leastOne: true,
disabled: false,
draggable: true,
actions: [
{
label: "设置",
onClick: (index: number) => {
Message.show(`扩展设置: ${index}`);
},
},
],
defaultAddValue: {
label: "新选项",
},
schema: [
{
type: "Input",
name: "label",
label: "属性名",
props: {
onChange(v: any, _: any, idx: number) {
console.log("name change", v, this, idx);
},
},
},
{
type: "Input",
name: "value",
label: "属性值",
},
],
},
},
]}
/>
);
};
分层表单
适用有层级关系的对象数据格式
tsx
tsx
import { ProForm, Anchor } from "@lite-code/pro-shineout";
const schema = [
{
type: "FieldSet",
name: "baseInfo",
label: "基础信息",
props: {
column: 2,
schema: [
{
type: "Input",
name: "input",
label: "输入框",
},
{
type: "RangeInput",
name: "rangeInput",
label: "区间输入框",
required: true,
tooltip: "通常在定制一些区间范围比较有用",
props: {
startProps: {
min: 0,
},
endProps: {
max: 100,
},
},
},
{
type: "InputMore",
name: "inputMore",
label: "多条输入框",
required: true,
tooltip: "点击右侧icon,多条数据,换行区分",
props: {
max: 10,
title: "多条数据",
modalPlaceholder: "多条数据,换行区分",
},
},
{
type: "InputNumber",
name: "inputNumber",
label: "数字输入框",
props: {
min: 1,
max: 999,
},
},
{
type: "Input",
name: "input",
label: "输入框",
},
{
type: "RangeInput",
name: "rangeInput",
label: "区间输入框",
required: true,
tooltip: "通常在定制一些区间范围比较有用",
props: {
startProps: {
min: 0,
},
endProps: {
max: 100,
},
},
},
{
type: "InputMore",
name: "inputMore",
label: "多条输入框",
required: true,
tooltip: "点击右侧icon,多条数据,换行区分",
props: {
max: 10,
title: "多条数据",
modalPlaceholder: "多条数据,换行区分",
},
},
{
type: "InputNumber",
name: "inputNumber",
label: "数字输入框",
props: {
min: 1,
max: 999,
},
},
],
actions: [
{
label: "更多设置",
icon: "setting",
onClick(formInstance: any) {
console.log("设置", formInstance);
},
},
],
},
},
{
type: "FieldSet",
name: "picker",
label: "下拉选择器",
props: {
column: 2,
schema: [
{
type: "Select",
name: "select",
label: "下拉选",
props: {
data: [
{ label: "选项1", value: 1 },
{ label: "选项2", value: 2 },
],
},
},
{
type: "TreeSelect",
name: "treeSelect",
label: "下拉树组件",
props: {
defaultExpandAll: true,
keygen: "id",
renderItem: "title",
multiple: true,
compressed: true,
mode: 2,
data: [
{
id: "1",
title: "1",
children: [
{
id: "1-1",
title: "1-1",
children: [
{ id: "1-1-1", title: "1-1-1" },
{ id: "1-1-2", title: "1-1-2" },
],
},
{ id: "1-2", title: "1-2" },
],
},
{
id: "2",
title: "2",
children: [
{ id: "2-1", title: "2-1" },
{ id: "2-2", title: "2-2" },
],
},
{
id: "3",
title: "3",
children: [{ id: "3-1", title: "3-1" }],
},
],
},
},
{
type: "Cascader",
name: "cascader",
label: "级联选择器",
props: {
data: [
{
value: "jiangsu",
label: "江苏省",
children: [
{
value: "nanjing",
label: "南京市",
},
],
},
{
value: "anhui",
label: "安徽省",
children: [
{
value: "hefei",
label: "合肥市",
},
],
},
],
},
},
{
type: "DatePicker",
name: "datePicker",
label: "选择日期",
},
{
type: "DatePicker",
label: "选择日期范围",
props: {
range: true,
name: ["datePickerRangeStart", "datePickerRangeEnd"],
},
},
],
actions: [
{
label: "更多设置",
icon: "setting",
onClick(formInstance: any) {
console.log("设置", formInstance);
},
},
],
},
},
{
type: "FieldSet",
name: "tools",
label: "工具栏配置",
props: {
schema: [
{
type: "InputNumber",
name: "width",
label: "宽度",
},
{
type: "FormList",
name: "menus",
label: "列集合",
props: {
defaultAddValue: {
label: "新选项",
icon: "setting",
},
actions: [
{
label: "设置",
onClick: (index: any) => {
console.log(index);
},
},
],
schema: [
{
type: "Input",
name: "label",
label: "属性值",
},
{
type: "Input",
name: "icon",
label: "图标",
},
],
},
},
{
type: "TextArea",
name: "textarea",
label: "大文本",
tip: "这是一段描述信息",
span: 24,
props: {
rows: 2,
},
},
],
actions: [
{
label: "更多设置",
icon: "setting",
onClick(formInstance: any) {
console.log("设置", formInstance);
},
},
],
},
},
{
type: "FieldSet",
name: "operation",
label: "操作按钮配置",
props: {
schema: [
{
type: "InputNumber",
name: "width",
label: "宽度",
},
{
type: "FormList",
name: "menus",
label: "列集合",
props: {
defaultAddValue: {
label: "新选项",
icon: "setting",
},
actions: [
{
label: "设置",
onClick: (index: any) => {
console.log(index);
},
},
],
schema: [
{
type: "Input",
name: "label",
label: "属性值",
},
{
type: "Input",
name: "icon",
label: "图标",
},
],
},
},
{
type: "TextArea",
name: "textarea",
label: "大文本",
tip: "这是一段描述信息",
span: 24,
style: {
marginBottom: 220,
},
props: {
rows: 2,
},
},
],
actions: [
{
label: "更多设置",
icon: "setting",
onClick(formInstance: any) {
console.log("设置", formInstance);
},
},
],
},
},
];
export default () => {
return (
<div style={{ display: "flex", gap: 20 }}>
<div style={{ width: 120 }}>
<Anchor
items={schema.map((i) => {
return {
key: i.name,
title: i.label,
};
})}
offsetTop={20}
container={() =>
document.querySelector(".anchor-form-container")?.parentElement ||
null
}
/>
</div>
<ProForm
height={800}
title="分层表单"
className="anchor-form-container"
defaultValue={{
baseInfo: {
input: "123456",
inputMore: "1,2,3",
rangeInput: [1, 10],
inputNumber: 2323,
},
picker: {
select: 1,
selectMore: [1, 2],
cascader: ["jiangsu", "nanjing"],
treeSelect: ["2-2"],
datePicker: "2025-03-24",
datePickerRangeStart: "2025-03-24",
datePickerRangeEnd: "2026-07-23",
},
tools: {
width: 200,
menus: [
{
label: "查看",
icon: "preview",
},
{
label: "删除",
icon: "delete",
},
],
textarea: "结合 Anchor 组件可以快速实现电梯表单的效果",
},
operation: {
width: 200,
menus: [
{
label: "查看",
icon: "preview",
},
{
label: "删除",
icon: "delete",
},
],
textarea: "结合 Anchor 组件可以快速实现电梯表单的效果",
},
}}
schema={schema}
onSubmit={async (vs) => {
await new Promise((res) => setTimeout(res, 1000));
console.log(vs);
}}
/>
</div>
);
};
自定义渲染
支持 itemRender 扩展渲染
tsx
tsx
import { ProForm } from '@lite-code/pro-shineout'
export default () => (
<ProForm
schema={[
{
type: 'Input',
name: 'name',
label: '姓名',
itemRender(dom) {
return (
<div
style={{
border: '2px dashed #1a7af9',
borderRadius: 4,
padding: 10,
paddingBottom: 0,
}}
>
{dom}
</div>
)
},
},
]}
/>
)
注册自定义组件
使用 widget 注册组件
tsx
tsx
import { Input } from 'shineout'
import { ProForm } from '@lite-code/pro-shineout'
import { useState } from 'react'
const CountInput = ({ onChange, maxLength = 20, ...rest }: any) => {
const [count, setCount] = useState(0)
return (
<Input.Group>
<Input
placeholder="请输入"
{...rest}
trim
delay={0}
maxLength={maxLength}
onChange={(v: string) => {
setCount(v.length)
onChange(v)
}}
/>
<b>
{count}/{maxLength}
</b>
</Input.Group>
)
}
export default () => (
<ProForm
widget={{
CountInput,
}}
schema={[
{
type: 'CountInput',
label: '计数输入框',
name: 'countInput',
required: true,
},
]}
/>
)
异步选择器
使用 widget 传入 axios
tsx
tsx
import axios from "axios";
import { ProForm } from "@lite-code/pro-shineout";
export default () => (
<ProForm
widget={{
axios, // 使用业务内部封装的统一请求api
}}
title="异步数据源"
onSubmit={(values) => {
console.log(values);
}}
schema={[
{
type: "AsyncSelect",
label: "异步下拉选",
name: "select",
required: true,
props: {
method: "GET",
url: "http://111.229.112.107:3003/mock/option1",
// requestParams: (form: any) => ({}), // 入参
// onMount: (data) => console.log(data), // 请求完毕回调
},
},
{
type: "AsyncTreeSelect",
label: "异步树下拉选",
name: "tree",
required: true,
props: {
method: "GET",
url: "http://111.229.112.107:3003/mock/option2",
defaultExpandAll: true,
keygen: "id",
renderItem: "title",
compressed: true,
mode: 2,
// requestParams: (form: any) => ({}), // 入参
// onMount: (data) => console.log(data), // 请求完毕回调
},
},
]}
/>
);
API
ProFormProps 继承 Form 额外的属性描述
| 属性名 | 描述 | 类型 | 默认值 | 是否必需 |
|---|---|---|---|---|
| schema | 表单项模型 | ProFormItemProps[] | 无 | 是 |
| column | 等份布局 | 1 | 2 | 3 | 4 | 1 | 否 |
| title | 标题 | string | 无 | 否 |
| onSubmit | 提交事件 | (values: Object): void | 无 | 否 |
| okText | 提交按钮文案 | string | 提交 | 否 |
| widget | 自定义组件 | Object | 无 | 否 |
ProFormItemProps 继承 Form.Item 额外的属性描述
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| name | string | - | 字段名(必填,同时作为 Form.Field 的 name) |
| label | ReactNode | - | 字段标签 |
| type | string | Function | - | 组件类型名或自定义渲染函数(必填) |
| span | number | 24 | 占据的栅格数(24 为满行) |
| props | AllComponentProps | {} | 传递给表单组件的属性 |
| required | boolean | false | 是否必填,自动生成校验规则 |
| rules | Rule[] | - | 自定义校验规则 |
| effect | "all" | string[] | - | 依赖的字段名列表,字段变化时触发重渲染 |
| visible | (datum) => boolean | - | 根据表单值控制是否显示 |
| extendProps | (form, formListIndex) => object | - | 动态计算额外 props,接收表单实例和 FormList 行索引 |
| itemRender | (dom, form) => ReactElement | - | 自定义表单项渲染 |
| autoSearch | boolean | false | 值变化时自动触发搜索 |
| tooltip | string | - | 标签提示信息 |
| gridStyle | CSSProperties | - | 栅格容器样式 |
| defaultValue | any | - | 字段默认值 |