提交 b3aec8c3 编写于 作者: Y yangxue05

Merge remote-tracking branch 'upstream/master'

# Conflicts:
#	packages/form-render/src/core/RenderChildren/RenderList/TableList.js
......@@ -16,7 +16,6 @@ assignees: FateRiddle, siyi98
- react:
- form-render:
- antd:
- funsion(如果用的是fusion):
**2.问题描述**
......
......@@ -104,7 +104,7 @@
<tr>
<td width="160" align="center">
<img
src="https://www.agtech.com/public/asset/yabologo-6f9d76c5cbb0b954b9939a8cac3a0cb1.png"
src="https://qpluspicture.oss-cn-beijing.aliyuncs.com/up/hU257o.png"
width="160"
/>
</td>
......
/**
* transform: true
* defaultShowCode: true
*/
import React, { useState, useEffect } from 'react';
import { Button, Space, message } from 'antd';
import FormRender, { useForm } from 'form-render';
import { fakeApi, delay } from './advanced/utils';
import RichTextEditor from '../../widgets/RichText/src';
const Demo = () => {
const form = useForm();
const [schema, setSchema] = useState({});
const getRemoteData = () => {
fakeApi('xxx/getForm').then(_ => {
form.setValues({ input1: 'hello world', select1: 'c' });
});
};
const test4 = {
type: 'object',
properties: {
COLS: {
type: 'array',
title: '字段配置',
items: {
type: 'object',
title: 'SERVICE_COL_DEF',
properties: {
SHOW: {
type: 'boolean',
title: '是否显示',
required: true,
},
SORT: {
type: 'number',
title: '排序',
required: true,
},
},
},
},
},
};
const test3 = {
type: 'object',
properties: {
select1: {
title: '单选',
type: 'number',
enum: [1, 2, 3],
enumNames: ['选项1', '选项2', '选项3'],
},
select: {
hidden: '{{formData.select1 === 1}}',
required: true,
title: '单选',
type: 'number',
enum: [1, 2, 3],
enumNames: ['选项1', '选项2', '选项3'],
},
StayRange: {
title: '停留时间段',
type: 'range',
// bind: ['StayStartTime', 'StayEndTime'],
format: 'date',
},
LivedCountries: {
required: true,
type: 'array',
items: {
type: 'object',
properties: {
LivedCountry: {
title: '国家/地区',
type: 'string',
},
LivedAddressCN: {
title: '详细地址',
type: 'string',
},
LivedAddressEN: {
title: '详细地址(英文)',
type: 'string',
},
test: {
title: '测试 bind',
type: 'string',
bind: 'testA',
},
LivedTime: {
title: '停留期',
type: 'range',
bind: ['LivedStartTime', 'LivedEndTime'],
placeholder: ['开始时间', '结束时间'],
format: 'date',
},
},
},
},
},
};
const test = {
// displayType: 'row',
type: 'object',
properties: {
readLevel: {
title: '查询权限等级',
type: 'number',
format: 'int',
default: 0,
min: 0,
max: 3,
},
objectName: {
title: '对象',
description: '这是一个对象类型',
type: 'object',
properties: {
objectName: {
title: '对象',
description: '这是一个对象类型',
type: 'object',
properties: {
inputName: {
title: '简单输入框',
type: 'string',
},
radioName: {
title: '单选radio',
type: 'string',
enum: ['a', 'b', 'c'],
enumNames: ['', '', ''],
widget: 'radio',
default: null,
},
multiSelect: {
title: '多选',
description: '下拉多选',
type: 'array',
items: {
type: 'string',
},
default: ['B', 'D'],
enum: ['A', 'B', 'C'],
enumNames: ['杭州', '武汉', '湖州'],
widget: 'multiSelect',
},
},
},
},
},
boxes: {
title: '',
description: 'checkbox',
type: 'array',
items: {
type: 'string',
},
default: ['A', 'B'],
enum: ['A', 'B', 'C', 'D'],
enumNames: ['杭州', '武汉', '湖州', '贵阳'],
widget: 'checkboxes',
},
},
};
const test1 = {
displayType: 'column',
type: 'object',
properties: {
dateName: {
title: '时间选择',
type: 'string',
format: 'date',
required: true,
},
selectName: {
title: '单选',
type: 'string',
enum: ['a', 'b', 'c'],
enumNames: ['', '', ''],
widget: 'select',
},
inputName: {
title: '简单输入框',
type: 'string',
required: true,
},
inputName2: {
title: '简单输入框',
type: 'string',
},
inputName3: {
title: '简单输入框',
type: 'string',
},
listName: {
title: '对象数组',
description: '对象数组嵌套功能',
type: 'array',
items: {
type: 'object',
properties: {
selectName: {
title: '单选',
type: 'string',
enum: ['a', 'b', 'c'],
enumNames: ['', '', ''],
widget: 'select',
},
inputName: {
title: '简单输入框',
type: 'string',
},
activityDesc: {
title: '活动简介',
type: 'string',
format: 'textarea',
widget: 'richText',
},
objectName: {
title: '对象',
description: '这是一个对象类型',
type: 'object',
properties: {
radioName: {
title: '单选radio',
type: 'string',
enum: ['a', 'b', 'c'],
enumNames: ['', '', ''],
widget: 'radio',
},
dateName: {
title: '时间选择',
type: 'string',
format: 'date',
},
inputName: {
title: '简单输入框',
type: 'string',
},
},
},
},
},
},
},
};
const test2 = {
type: 'object',
properties: {
audio_on_mic_limit: {
className: 'my-className',
title: '同时支持连麦人数',
default: '1',
enum: ['1', '2', '3'],
enumNames: ['1人', '2人', '3人'],
type: 'string',
widget: 'radio',
labelWidth: 145,
},
},
displayType: 'row',
};
// const onMount = () => {
// setSchema(test);
// };
useEffect(() => {
setSchema(test4);
}, []);
const onFinish = (data, errors) => {
if (errors.length > 0) {
message.error(
'校验未通过:' + JSON.stringify(errors.map(item => item.name))
);
} else {
fakeApi('xxx/submit', data).then(_ => message.success('提交成功!'));
}
};
const onValuesChange = (a, b) => {
console.log(a, b);
};
return (
<div>
<FormRender
form={form}
schema={schema}
widgets={{
richText: RichTextEditor,
}}
debug
theme="1"
// onMount={onMount}
onFinish={onFinish}
onValuesChange={onValuesChange}
/>
<Space>
<Button onClick={getRemoteData}>加载服务端数据</Button>
<Button type="primary" onClick={form.submit}>
提交(见console)
</Button>
</Space>
</div>
);
};
export default Demo;
/**
* transform: true
* defaultShowCode: true
*/
import React, { useState, useEffect } from 'react';
import { Button, Space, message } from 'antd';
import FormRender, { useForm } from 'form-render';
import { fakeApi, delay } from './advanced/utils';
import RichTextEditor from '../../widgets/RichText/src';
const Demo = () => {
const form = useForm();
const [schema, setSchema] = useState({});
const getRemoteData = () => {
fakeApi('xxx/getForm').then(_ => {
form.setValues({ input1: 'hello world', select1: 'c' });
});
};
const test1 = {
displayType: 'column',
type: 'object',
properties: {
sellerId: {
title: '简单输入框',
type: 'string',
},
memberOrderFeeIdentification: {
required: true,
title: '商家费用比例',
description:
'菲住邀约此项必须填写:如商家给联盟结算底价为85.0%,则费用比例录入应该为:15.0%',
type: 'string',
props: {
addonAfter: '%',
},
rules: [
{
pattern: '^[1]?[0-9]+\\.[0-9]?$',
message: '商家费用比例需保留一位小数,例如15.0',
},
],
hidden: "{{formData.sellerId != '1'}}",
},
},
};
useEffect(() => {
setSchema(test1);
}, []);
const onFinish = (data, errors) => {
if (errors.length > 0) {
message.error(
'校验未通过:' + JSON.stringify(errors.map(item => item.name))
);
} else {
fakeApi('xxx/submit', data).then(_ => message.success('提交成功!'));
}
};
const onValuesChange = (a, b) => {
console.log(a, b);
};
return (
<div>
<FormRender
form={form}
schema={schema}
widgets={{
richText: RichTextEditor,
}}
debug
theme="1"
// onMount={onMount}
onFinish={onFinish}
onValuesChange={onValuesChange}
/>
<Space>
<Button onClick={getRemoteData}>加载服务端数据</Button>
<Button type="primary" onClick={form.submit}>
提交(见console)
</Button>
</Space>
</div>
);
};
export default Demo;
import React from 'react';
import { Button } from 'antd';
import FormRender, { useForm } from 'form-render';
const Ha = props => {
console.log(props, 'poadshahahahah --asd-fs-df-s-sd-f');
return 'haha';
};
const schema = {
type: 'object',
properties: {
listName: {
title: 'list0',
description: '对象数组嵌套功能',
type: 'array',
min: 2,
max: 3,
props: {
hideTitle: true,
hideMove: true,
hideDelete: true,
addBtnProps: {
type: 'primary',
children: 'nisadf',
},
buttons: [
{
html: '测试1',
callback: 'cb1',
},
{
html: '<span style="color:red">测试2<span>',
callback: 'cb2',
},
],
},
items: {
type: 'object',
widget: 'Ha',
properties: {
input1: {
title: '简单输入框',
type: 'string',
required: true,
},
select1: {
title: '单选',
type: 'string',
enum: ['a', 'b', 'c'],
enumNames: ['', '', ''],
},
},
},
},
listName1: {
title: 'list1',
description: '对象数组嵌套功能',
type: 'array',
widget: 'list1',
min: 2,
max: 3,
props: {
hideTitle: true,
hideMove: true,
hideDelete: true,
buttons: [
{
html: '测试1',
callback: 'cb1',
},
{
html: '<span style="color:red">测试2<span>',
callback: 'cb2',
},
],
},
items: {
type: 'object',
properties: {
input1: {
title: '简单输入框',
type: 'string',
required: true,
},
select1: {
title: '单选',
type: 'string',
enum: ['a', 'b', 'c'],
enumNames: ['', '', ''],
},
},
},
},
listName2: {
title: 'list2',
description: '对象数组嵌套功能',
type: 'array',
widget: 'list2',
items: {
type: 'object',
properties: {
input1: {
title: '简单输入框',
type: 'string',
required: true,
},
select1: {
title: '单选',
type: 'string',
enum: ['a', 'b', 'c'],
enumNames: ['', '', ''],
},
},
},
props: {
hideMove: true,
hideDelete: true,
buttons: [
{
html: '测试1',
callback: 'cb1',
},
{
html: '<span style="color:red">测试2<span>',
callback: 'cb2',
},
],
},
},
listName3: {
title: '对象数组',
description: '对象数组嵌套功能',
type: 'array',
widget: 'list3',
items: {
type: 'object',
properties: {
input1: {
title: '简单输入框',
type: 'string',
required: true,
},
select1: {
title: '单选',
type: 'string',
enum: ['a', 'b', 'c'],
enumNames: ['', '', ''],
},
},
},
props: {
hideMove: true,
hideTitle: true,
hideDelete: true,
buttons: [
{
html: '测试1',
callback: 'cb1',
},
{
html: '<span style="color:red">测试2<span>',
callback: 'cb2',
},
],
},
},
listName4: {
title: '对象数组',
description: '对象数组嵌套功能',
type: 'array',
widget: 'list4',
items: {
type: 'object',
properties: {
input1: {
title: '简单输入框',
type: 'string',
required: true,
},
select1: {
title: '单选',
type: 'string',
enum: ['a', 'b', 'c'],
enumNames: ['', '', ''],
},
},
},
props: {
buttons: [
{
html: '测试1',
callback: 'cb1',
},
{
html: '<span style="color:red">测试2<span>',
callback: 'cb2',
},
],
},
},
},
};
window.cb1 = ({ value = [], onChange, schema }) => {
console.log(schema);
onChange([
...value,
{ input1: '123', select1: 'b' },
{ input1: '122', select1: 'c' },
]);
};
const Demo = () => {
const form = useForm();
const onFinish = (formData, errorFields) => {
if (errorFields.length > 0) {
alert('errorFields:' + JSON.stringify(errorFields));
} else {
alert('formData:' + JSON.stringify(formData, null, 2));
}
};
return (
<div>
<FormRender
debug
form={form}
schema={schema}
onFinish={onFinish}
widgets={{ Ha }}
/>
<Button type="primary" onClick={form.submit}>
提交
</Button>
</div>
);
};
export default Demo;
......@@ -268,7 +268,7 @@ const Demo = () => {
const onMount = () => {
form.setSchemaByPath('obj1.select1', {
enum: ['nouth', 'south', 'east', 'west'],
enum: ['east', 'south', 'west', 'north'],
enumNames: ['', '', '西', ''],
});
};
......
......@@ -65,6 +65,7 @@ const schema = {
};
const SiteInput = props => {
console.log('widget props:', props);
return <Input addonBefore="http://" addonAfter=".com" {...props} />;
};
......@@ -74,6 +75,7 @@ const Demo = () => {
return (
<div>
<Form
readOnly
form={form}
schema={schema}
widgets={{ site: SiteInput }}
......@@ -91,9 +93,7 @@ export default Demo;
可以看到自定义组件的写法十分直观,事实上很多 antd 的组件都是可以直接拿来作为自定义组件使用(内置组件中就有 Input, InputNumber, Checkbox 和 Switch)
## createWidget
自定义组件接收以下 Props:
## 自定义组件收到的 props
- **disabled**:是否禁止输入
- **readOnly**:是否只读
......@@ -106,20 +106,63 @@ export default Demo;
- **addons.dataPath**: 目前数据所在的 path,例如"a.b[2].c[0].d",string 类型。
- **addons.dataIndex**: 如果 dataPath 不包含数组,则为 [], 如果 dataPath 包含数组,例如"a.b[2].c[0].d",则为 [2,0]。是自上到下所有经过的数组的 index 按顺序存放的一个数组类型
对高阶组件熟悉的同学,`form-render` 内置了 `createWidget` 方法,支持用类似于 `redux``connect` 的语法快速生产自定义组件:
## antd 组件改造成自定义组件
大多数情况下,antd 的组件可以拿来即用。但有时组件的 props 并不是约定的 value/onChange, 例如 Checkbox 的情况,value 值对应的是 checked,此时只需要少量改动即可:
```js
import { Checkbox } from 'antd';
import { createWidget } from 'form-render';
const MyCheckBox = createWidget(({ value }) => {
return {
checked: value,
};
})(Checkbox);
const MyCheckBox = (({ value, ...rest }) => {
return <Checkbox checked={value} {...rest} />
}
```
## 只读模式下的自定义组件
只读模式下,默认会渲染内置的 html 组件,但有时 html 组件并不能满足一个自定义组件在只读模式下需要的展示,此时可使用`readOnlyWidget`字段来指定只读模式下的展示。
```json
{
"type": "object",
"properties": {
"string": {
"title": "网址输入自定义组件",
"type": "string",
"widget": "site",
"readOnlyWidget": "siteText"
}
}
}
```
如果你打算在一个自定义组件里通过 readOnly 参数判断条件展示,既是说,site 组件已经写了只读和非只读情况下的渲染
```js
const SiteInput = ({ readOnly, value, ...rest }) => {
if (readOnly) return <div>{`https://${value}.com`}</div>;
return (
<Input addonBefore="http://" addonAfter=".com" value={value} {...rest} />
);
};
```
`createWidget` 是一个高阶组件,它接收组件 props,允许用户对齐做修改并返回真正需要的 props。使用 `createWidget` 与直接写自定义组件的工作量基本是一样的,只是一个语法糖,喜欢的同学可以使用
此时可以指定 `readOnlyWidget``widget` 为同一个组件:
```json
{
"type": "object",
"properties": {
"string": {
"title": "网址输入自定义组件",
"type": "string",
"widget": "site",
"readOnlyWidget": "site"
}
}
}
```
## 最佳实践
......
......@@ -20,3 +20,7 @@ FormRender 1.0 与之前一样,对组件库的口子都是开着的,外部
### 4、下拉选择框的选项希望从服务端拿到。是否有简单实现方法?
可以在 `onMount` 中使用 `form.setSchemaByPath` 来实现,见[文档样例](/advanced/form-methods#%E4%BE%8B-4%EF%BC%9A%E6%9C%8D%E5%8A%A1%E7%AB%AF%E5%8A%A0%E8%BD%BD%E9%80%89%E6%8B%A9%E6%A1%86%E7%9A%84%E9%80%89%E9%A1%B9)
### 5、 只读模式下,默认的渲染不能满足要求我想定制怎么办?
参见[只读模式下的自定义组件](/advanced/widget#%E5%8F%AA%E8%AF%BB%E6%A8%A1%E5%BC%8F%E4%B8%8B%E7%9A%84%E8%87%AA%E5%AE%9A%E4%B9%89%E7%BB%84%E4%BB%B6)
......@@ -79,3 +79,21 @@ export const expression = {
},
required: [],
};
export const titleTrick = {
displayType: 'row',
type: 'object',
properties: {
inputName1: {
title: '简单输入框',
type: 'string',
width: '50%',
},
desc: {
type: 'html',
bind: false,
default: "补充说明 <span style='color:red'>hello<span>",
width: '50%',
},
},
};
......@@ -33,6 +33,7 @@ toc: content
| hideTitle | boolean | 只支持“list1”,隐藏 title,展示更紧凑 |
| hideDelete | boolean | 隐藏删除按钮 |
| hideAdd | boolean | 隐藏新增/复制按钮 |
| hideMove | boolean | 隐藏上下移动 item 的按钮 |
| buttons | array | 下详 (注 1) |
**itemProps**
......
......@@ -6,7 +6,7 @@ group:
toc: content
---
# 书写规范
# schema 规范
## 概述
......@@ -121,6 +121,16 @@ export default () => <FR schema={basic} />;
- 类型:`string`
- 详细:表单的标题信息,作为 label 展示,注意 title 为""时占位,title 不写时不占位
如下例,通过选择性不使用 title,达到展示效果
```jsx
import React from 'react';
import FR from '../demo/FR';
import { titleTrick } from '../json/schema';
export default () => <FR schema={titleTrick} />;
```
### description
- 类型:`string`
......@@ -349,6 +359,19 @@ readOnly=true 的情况,FormRender 默认使用 html 组件渲染。特殊情
}
```
### extra
- 类型:string
- 详细:用于在元素下展示更多说明信息,extra 可以是 html string,也可以是纯文案,会展示在元素下面一行紧贴
```json
{
"title": "单选",
"type": "string",
"extra": "<a href='xxx'>详细规范</a>"
}
```
### properties
只在对象组件(type: object)中使用,`properties` 用于包裹对象的子属性:
......
......@@ -32,6 +32,17 @@ const Demo = () => {
api: { title: 'api', type: 'string' },
},
},
{
text: '姓名',
name: 'name',
schema: {
title: '输入框',
type: 'string',
},
setting: {
maxLength: { title: '最长字数', type: 'number' },
},
},
{
text: 'object',
name: 'object',
......@@ -43,15 +54,17 @@ const Demo = () => {
setting: {},
},
{
text: '姓名',
name: 'name',
text: 'array',
name: 'array',
schema: {
title: '输入框',
type: 'string',
},
setting: {
maxLength: { title: '最长字数', type: 'number' },
title: '数组',
type: 'array',
items: {
type: 'object',
properties: {},
},
},
setting: {},
},
],
},
......
# Changelog
### 1.4.4
- [!] 修复了 hidden = true 是做了多余的对类型的判断的问题
### 1.4.3
- [!] 修复了 range 元素的校验,清空后不再会通不过校验
- [!] 修复了 hidden 是表达式时,即使隐藏也还是会被校验的问题
- [!] 修复了复制后的元素与原元素值同步的问题
### 1.4.2
- [!] 修复日期使用 props/format 造成校验不通过的问题
### 1.4.1
- [!] 去掉了 1.4.0 版本带来的一堆 error 的 console.log
- [!] antd 升级 4.16.0 Collapse.Panel 样式变更导致对象展示奇怪,修复兼容性
- [!] 日期组件的 format 可以在 props 里自定义设置,例如 props: {format: 'YY/MM/DD'}
- [!] 修复了几个声明文件上的类型注释错误
### 1.4.0
#### 新功能
- [+] 区别了不写 title(不占位) 和 title = '' (占位)的展示,给展示带来灵活性
- [+] 列表添加了上下移动 item 的功能,同时新增 hideMove 字段用于隐藏上下移动
- [+] 列表添加了 `addBtnProps` 属性,用于自定义“新增一条”按钮的样式和文案
- [+] 新增 extra 字段,用于描述补充文案,类似于 antd form 的 extra,详见“协议/schema 规范”
- [+] list2 组件增加了复制功能
#### 校验
- [!] fix 了列表元素上下移动后校验信息展示有误的问题(一个简单 fix,还需后续优化)
- [!] fix 了列表中非 string 类型的元素类型校验一直不通过的问题
- [!] fix 了 min = 0 时,校验信息错误的问题
#### 其他
- [!] fix 了 watch 的声明类型错误的问题
- [!] fix 了部分情况 disabled 状态变化后展示无反馈的问题
### 1.3.4
- [!] 修复复杂(带数组方法)的表达式解析错误的问题
......
{
"name": "form-render",
"version": "1.3.4",
"version": "1.4.5-beta.0",
"description": "通过 JSON Schema 生成标准 Form,常用于自定义搭建配置界面生成",
"repository": {
"type": "git",
......
......@@ -3,7 +3,12 @@ import React from 'react';
import Core from '../../index';
import { Button, Space, Popconfirm } from 'antd';
// import ArrowDown from '../../../components/ArrowDown';
import { DeleteOutlined, CopyOutlined } from '@ant-design/icons';
import {
DeleteOutlined,
CopyOutlined,
ArrowUpOutlined,
ArrowDownOutlined,
} from '@ant-design/icons';
const CardList = ({
displayList = [],
......@@ -13,10 +18,22 @@ const CardList = ({
deleteItem,
copyItem,
addItem,
moveItemUp,
moveItemDown,
displayType,
getFieldsProps,
}) => {
const { props = {}, itemProps } = schema;
let addBtnProps = {
type: 'dashed',
children: '新增一条',
};
if (props.addBtnProps && typeof props.addBtnProps === 'object') {
addBtnProps = { ...addBtnProps, ...props.addBtnProps };
}
return (
<>
<div className="fr-card-list">
......@@ -50,17 +67,25 @@ const CardList = ({
onClick={() => copyItem(idx)}
/>
)}
{!props.hideMove && (
<>
<ArrowUpOutlined
style={{ fontSize: 16, marginLeft: 4 }}
onClick={() => moveItemUp(idx)}
/>
<ArrowDownOutlined
style={{ fontSize: 16, marginLeft: 4 }}
onClick={() => moveItemDown(idx)}
/>
</>
)}
</Space>
</div>
);
})}
</div>
<div style={{ marginTop: displayList.length > 0 ? 0 : 8 }}>
{!props.hideAdd && (
<Button type="dashed" onClick={addItem}>
新增一条
</Button>
)}
{!props.hideAdd && <Button onClick={addItem} {...addBtnProps} />}
{Array.isArray(props.buttons)
? props.buttons.map((item, idx) => {
const { callback, text, html } = item;
......
......@@ -5,6 +5,7 @@ import { useSet } from '../../../hooks';
import { getDataPath, getKeyFromPath, getDisplayValue } from '../../../utils';
import { Button, Table, Drawer, Space, Popconfirm } from 'antd';
// import ArrowDown from '../../../components/ArrowDown';
import { ArrowUpOutlined, ArrowDownOutlined } from '@ant-design/icons';
import ErrorMessage from '../../RenderField/ErrorMessage';
const FIELD_LENGTH = 170;
......@@ -72,7 +73,7 @@ const DrawerList = ({
title: '操作',
key: '$action',
fixed: 'right',
width: 60,
width: 120,
render: (value, record, idx) => {
const index = (value && value.$idx) || 0;
return (
......@@ -88,12 +89,18 @@ const DrawerList = ({
<a>删除</a>
</Popconfirm>
)}
{/* <ArrowUp height={18} width={24} onClick={() => moveItemUp(index)} />
<ArrowDown
height={18}
width={24}
onClick={() => moveItemDown(index)}
/> */}
{!props.hideMove && (
<>
<ArrowUpOutlined
style={{ color: '#1890ff', fontSize: 16, marginLeft: 4 }}
onClick={() => moveItemUp(idx)}
/>
<ArrowDownOutlined
style={{ color: '#1890ff', fontSize: 16, marginLeft: 4 }}
onClick={() => moveItemDown(idx)}
/>
</>
)}
</Space>
);
},
......
import React from 'react';
import Core from '../../index';
import { Button, Popconfirm } from 'antd';
import { DeleteOutlined, CopyOutlined } from '@ant-design/icons';
import {
DeleteOutlined,
CopyOutlined,
ArrowUpOutlined,
ArrowDownOutlined,
} from '@ant-design/icons';
const SimpleList = ({
schema,
......@@ -11,9 +16,21 @@ const SimpleList = ({
deleteItem,
addItem,
copyItem,
moveItemUp,
moveItemDown,
getFieldsProps,
}) => {
const { props = {}, itemProps } = schema;
let addBtnProps = {
type: 'dashed',
children: '新增一条',
};
if (props.addBtnProps && typeof props.addBtnProps === 'object') {
addBtnProps = { ...addBtnProps, ...props.addBtnProps };
}
return (
<div className="fr-list-1">
{displayList.map((item, idx) => {
......@@ -42,16 +59,24 @@ const SimpleList = ({
onClick={() => copyItem(idx)}
/>
)}
{!props.hideMove && (
<>
<ArrowUpOutlined
style={{ fontSize: 16, marginLeft: 8 }}
onClick={() => moveItemUp(idx)}
/>
<ArrowDownOutlined
style={{ fontSize: 16, marginLeft: 8 }}
onClick={() => moveItemDown(idx)}
/>
</>
)}
</div>
</div>
);
})}
<div style={{ marginTop: displayList.length > 0 ? 0 : 8 }}>
{!props.hideAdd && (
<Button type="dashed" onClick={addItem}>
新增一条
</Button>
)}
{!props.hideAdd && <Button onClick={addItem} {...addBtnProps} />}
{Array.isArray(props.buttons)
? props.buttons.map((item, idx) => {
const { callback, text, html } = item;
......
......@@ -2,6 +2,7 @@
import React from 'react';
import Core from '../../index';
import { Button, Table, Popconfirm, Space } from 'antd';
import { ArrowUpOutlined, ArrowDownOutlined } from '@ant-design/icons';
// import ArrowDown from '../../../components/ArrowDown';
const FIELD_LENGTH = 170;
......@@ -13,9 +14,12 @@ const TableList = ({
deleteItem,
copyItem,
addItem,
moveItemUp,
moveItemDown,
flatten,
schema,
listData,
changeList,
}) => {
const { props = {}, itemProps } = schema;
......@@ -66,7 +70,7 @@ const TableList = ({
title: '操作',
key: '$action',
fixed: 'right',
width: 60,
width: 120,
render: (value, record, idx) => {
return (
<Space>
......@@ -81,6 +85,18 @@ const TableList = ({
<a>删除</a>
</Popconfirm>
)}
{!props.hideMove && (
<>
<ArrowUpOutlined
style={{ color: '#1890ff', fontSize: 16, marginLeft: 4 }}
onClick={() => moveItemUp(idx)}
/>
<ArrowDownOutlined
style={{ color: '#1890ff', fontSize: 16, marginLeft: 4 }}
onClick={() => moveItemDown(idx)}
/>
</>
)}
</Space>
);
},
......
......@@ -3,8 +3,9 @@ import React from 'react';
import Core from '../../index';
import { Button, Popconfirm, Table } from 'antd';
import { useVT } from 'virtualizedtableforantd4';
import { ArrowUpOutlined, ArrowDownOutlined } from '@ant-design/icons';
const FIELD_LENGTH = 120;
const FIELD_LENGTH = 170;
const VirtualList = ({
displayList = [],
......@@ -12,6 +13,8 @@ const VirtualList = ({
children,
deleteItem,
addItem,
moveItemUp,
moveItemDown,
flatten,
schema,
listData,
......@@ -61,7 +64,7 @@ const VirtualList = ({
title: '操作',
key: '$action',
fixed: 'right',
width: 80,
width: 120,
render: (value, record, idx) => {
return (
<>
......@@ -75,6 +78,18 @@ const VirtualList = ({
<a>删除</a>
</Popconfirm>
)}
{!props.hideMove && (
<>
<ArrowUpOutlined
style={{ color: '#1890ff', fontSize: 16, marginLeft: 4 }}
onClick={() => moveItemUp(idx)}
/>
<ArrowDownOutlined
style={{ color: '#1890ff', fontSize: 16, marginLeft: 4 }}
onClick={() => moveItemDown(idx)}
/>
</>
)}
{Array.isArray(itemProps.buttons)
? itemProps.buttons.map((item, idx) => {
const { callback, text, html } = item;
......
......@@ -55,7 +55,7 @@ const RenderList = ({
newItem,
...displayList.slice(idx),
];
onItemChange(dataPath, newList);
onItemChange(dataPath, JSON.parse(JSON.stringify(newList)));
};
const deleteItem = idx => {
......@@ -75,6 +75,8 @@ const RenderList = ({
newList[idx] = itemAbove;
newList[idx - 1] = currentItem;
onItemChange(dataPath, newList);
// TODO: 这块懒了,之后要处理一下
removeTouched(`${dataPath}[${idx}]`);
};
const moveItemDown = idx => {
......@@ -85,6 +87,8 @@ const RenderList = ({
newList[idx] = itemBelow;
newList[idx + 1] = currentItem;
onItemChange(dataPath, newList);
// TODO: 这块懒了,之后要处理一下
removeTouched(`${dataPath}[${idx}]`);
};
let itemSchema = {
......
......@@ -4,7 +4,7 @@ import { defaultWidgetNameList } from '../../widgets/antd';
import { useTools } from '../../hooks';
import { transformProps } from '../../createWidget';
import { isObjType, isListType } from '../../utils';
import { isObjType, isListType, isObject } from '../../utils';
// import { Input } from 'antd';
// import Map from '../../widgets/antd/map';
......@@ -67,9 +67,11 @@ const ExtendedWidget = ({
value,
children,
disabled,
readOnly,
...schema.props,
};
if (schema.type === 'string' && typeof schema.max === 'number') {
widgetProps.maxLength = schema.max;
}
......@@ -84,6 +86,12 @@ const ExtendedWidget = ({
widgetProps = { ...widgetProps, ...schema.props };
}
// 支持 addonAfter 为自定义组件的情况
if(isObject(widgetProps.addonAfter) && widgetProps.addonAfter.widget) {
const AddonAfterWidget = widgets[widgetProps.addonAfter.widget];
widgetProps.addonAfter = <AddonAfterWidget {...schema}/>;
}
// 避免传组件不接受的props,按情况传多余的props
// const isExternalWidget = defaultWidgetNameList.indexOf(widgetName) === -1; // 是否是外部组件
widgetProps.addons = {
......@@ -107,6 +115,9 @@ const areEqual = (prev, current) => {
if (prev.readOnly !== current.readOnly) {
return false;
}
if (prev.disabled !== current.disabled) {
return false;
}
if (
JSON.stringify(prev.value) === JSON.stringify(current.value) &&
JSON.stringify(prev.schema) === JSON.stringify(current.schema)
......
import React from 'react';
import { useTools } from '../../hooks';
import './Extra.less';
const Extra = ({ schema }) => {
const { extra } = schema;
if (!extra) return null;
// widget 这个api也可以不对外
const { widgets } = useTools();
const widgetName = extra.widget;
const Widget = widgets[widgetName];
if (Widget) return <Widget schema={schema} />;
let __html = '';
if (typeof extra === 'string') {
__html = extra;
}
// 内部BU使用的口子,这个api不对外,也没有必要
if (typeof extra === 'object' && extra.text) {
__html = extra.text;
}
return (
__html && (
<div
className="fr-form-item-extra"
dangerouslySetInnerHTML={{ __html }}
></div>
)
);
};
export default Extra;
.fr-container {
.fr-form-item-extra {
clear: both;
color: rgba(0, 0, 0, 0.45);
font-size: 14px;
line-height: 1.5715;
min-height: 24px;
}
}
......@@ -12,35 +12,37 @@ const Title = ({ labelClass, labelStyle, schema, displayType }) => {
return (
<div className={labelClass} style={labelStyle}>
<label
className={`fr-label-title ${
isCheckBoxType(schema, readOnly) || _displayType === 'column'
? 'no-colon'
: ''
}`} // checkbox不带冒号
title={title}
>
{required === true && <span className="fr-label-required"> *</span>}
<span
className={`${isObjType ? 'b' : ''} ${
_displayType === 'column' ? 'flex-none' : ''
}`}
{title ? (
<label
className={`fr-label-title ${
isCheckBoxType(schema, readOnly) || _displayType === 'column'
? 'no-colon'
: ''
}`} // checkbox不带冒号
title={title}
>
{title}
</span>
{description &&
(_displayType === 'row' ? (
<span className="fr-tooltip-toggle" aria-label={description}>
<i className="fr-tooltip-icon" />
<div className="fr-tooltip-container">
<i className="fr-tooltip-triangle" />
{description}
</div>
</span>
) : _displayType === 'inline' ? null : (
<span className="fr-desc ml2">(&nbsp;{description}&nbsp;)</span>
))}
</label>
{required === true && <span className="fr-label-required"> *</span>}
<span
className={`${isObjType ? 'b' : ''} ${
_displayType === 'column' ? 'flex-none' : ''
}`}
>
{title}
</span>
{description &&
(_displayType === 'row' ? (
<span className="fr-tooltip-toggle" aria-label={description}>
<i className="fr-tooltip-icon" />
<div className="fr-tooltip-container">
<i className="fr-tooltip-triangle" />
{description}
</div>
</span>
) : _displayType === 'inline' ? null : (
<span className="fr-desc ml2">(&nbsp;{description}&nbsp;)</span>
))}
</label>
) : null}
</div>
);
};
......
......@@ -3,6 +3,7 @@ import { useStore, useStore2, useTools } from '../../hooks';
import useDebouncedCallback from '../../useDebounce';
import { getValueByPath, isCheckBoxType, isObjType } from '../../utils';
import ErrorMessage from './ErrorMessage';
import Extra from './Extra';
import FieldTitle from './Title';
import ExtendedWidget from './ExtendedWidget';
......@@ -81,7 +82,7 @@ const RenderField = props => {
style: labelStyle,
};
const _showTitle = !hideTitle && !!_schema.title;
const _showTitle = !hideTitle && typeof _schema.title === 'string';
// TODO: 这块最好能判断上一层是list1,
if (hideTitle && _schema.title) {
_schema.placeholder = _schema.placeholder || _schema.title;
......@@ -116,6 +117,7 @@ const RenderField = props => {
{_showTitle && <div {...placeholderTitleProps} />}
<div className={contentClass} style={contentStyle}>
<ExtendedWidget {...widgetProps} />
<Extra {...widgetProps} />
<ErrorMessage {...messageProps} />
</div>
</>
......@@ -138,6 +140,7 @@ const RenderField = props => {
message={errorMessage}
title={_showTitle ? titleElement : undefined}
/>
<Extra {...widgetProps} />
</div>
);
}
......@@ -150,6 +153,7 @@ const RenderField = props => {
style={contentStyle}
>
<ExtendedWidget {...widgetProps} />
<Extra {...widgetProps} />
<ErrorMessage {...messageProps} />
</div>
</>
......
......@@ -25,7 +25,14 @@ export const createWidget = (mapProps, extraSchema) => Component => props => {
};
export const transformProps = props => {
const { onChange, value, defaultValue, schema: ownSchema, ...rest } = props;
const {
onChange,
value,
defaultValue,
schema: ownSchema,
readOnly,
...rest
} = props;
const schema = { ...ownSchema };
const { trigger, valuePropName } = schema || {};
const controlProps = {};
......@@ -50,7 +57,7 @@ export const transformProps = props => {
// TODO: 之后 ui:xx 会舍去
const usefulPropsFromSchema = {
disabled: schema.disabled || schema['ui:disabled'],
readOnly: schema.readOnly || schema['ui:readonly'],
readOnly: schema.readOnly || schema['ui:readonly'] || readOnly,
hidden: schema.hidden || schema['ui:hidden'],
};
......
......@@ -25,7 +25,7 @@ export interface FormInstance {
touchKey: (key: string) => void;
onItemChange: (path: string, value: any) => void;
setValueByPath: (path: string, value: any) => void;
getSchemaByPath: (path: string, value: any) => void;
getSchemaByPath: (path: string) => object;
setSchemaByPath: (path: string, value: any) => void;
setValues: (formData: any) => void;
getValues: () => any;
......@@ -41,14 +41,20 @@ export interface FormInstance {
setErrorFields: (error: Error[]) => void;
removeErrorField: (path: string) => void;
removeTouched: (path: string) => void;
changeTouchedKeys: (pathArray: string[]) => void;
isEditing: boolean;
setEditing: (status: boolean) => void;
syncStuff: (any) => void;
}
export interface WatchProperties {
[path: string]: (value: any) => any;
}
export type WatchProperties = {
[path: string]:
| {
handler: (value: any) => void;
immediate?: boolean;
}
| ((value: any) => void);
};
export interface FRProps {
/** 表单 schema */
......
......@@ -56,7 +56,7 @@ function App({
console.error('form 为必填 props,<FormRender /> 没有接收到 form 属性!');
}
const _column = schema.column || column;
const _column = (schema && schema.column) || column;
const {
onItemChange,
setEditing,
......@@ -73,6 +73,7 @@ function App({
setErrorFields,
removeErrorField,
removeTouched,
changeTouchedKeys,
syncStuff,
...valuesThatWillChange
} = form;
......@@ -160,6 +161,7 @@ function App({
setErrorFields,
removeErrorField,
removeTouched,
changeTouchedKeys,
}),
[]
);
......@@ -222,13 +224,7 @@ function App({
<div>{'errorFields:' + JSON.stringify(form.errorFields)}</div>
<div>{'touchedKeys:' + JSON.stringify(form.touchedKeys)}</div>
<div>{'allTouched:' + JSON.stringify(form.allTouched)}</div>
<div>{'isEditting:' + JSON.stringify(form.isEditing)}</div>
<div>
{'isValidating:' + JSON.stringify(form.isValidating)}
</div>
<div>
{'isSubmitting:' + JSON.stringify(form.isSubmitting)}
</div>
<div>{'descriptor:' + JSON.stringify(window.descriptor)}</div>
</div>
) : null}
{watchList.length > 0
......
......@@ -13,10 +13,10 @@ export const processData = (data, flatten) => {
// 2. 去掉list里面所有的空值
_data = removeEmptyItemFromList(_data);
// 3. 去掉hidden的元素
_data = removeHiddenFromResult(_data, flatten);
// 3. 去掉 hidden = true 的元素
// _data = removeHiddenFromResult(_data, flatten);
// 3. 去掉所有的 undefined
// 4. 去掉所有的 undefined
_data = cleanEmpty(_data);
return _data;
......
......@@ -2,7 +2,7 @@
import { useEffect, useRef, useMemo, useState } from 'react';
import { validateAll } from './validator';
import { useSet } from './hooks';
import { set, sortedUniqBy, merge } from 'lodash-es';
import { set, sortedUniqBy } from 'lodash-es';
import { processData, transformDataWithBind2 } from './processData';
import { generateDataSkeleton, flattenSchema, clone } from './utils';
......@@ -61,9 +61,8 @@ export const useForm = props => {
const formData = dataFromOutside ? _formData : innerData;
// 生成一个基础结构,确保对象内的必填元素也被校验。
// _data.current = merge(generateDataSkeleton(schemaRef.current), formData);
_data.current = useMemo(() => {
return merge(generateDataSkeleton(schemaRef.current), formData);
return generateDataSkeleton(schemaRef.current, formData);
}, [JSON.stringify(formData), JSON.stringify(schemaRef.current)]);
_touchedKeys.current = touchedKeys;
......@@ -100,6 +99,10 @@ export const useForm = props => {
setState({ touchedKeys: newTouch });
};
const changeTouchedKeys = newTouchedKeys => {
setState({ touchedKeys: newTouchedKeys });
};
// 为了兼容 0.x
// useEffect(() => {
// // 如果是外部数据,submit没有收束,无校验
......@@ -265,24 +268,28 @@ export const useForm = props => {
});
}
if (typeof beforeFinishRef.current === 'function') {
return Promise.resolve(processData(_data.current, flatten)).then(res => {
return Promise.resolve(processData(_data.current, flatten)).then(
res => {
setState({
isValidating: true,
isSubmitting: false,
outsideValidating: true,
submitData: res,
});
return errors;
}
);
}
return Promise.resolve(processData(_data.current, flatten)).then(
res => {
setState({
isValidating: true,
isSubmitting: false,
outsideValidating: true,
isValidating: false,
isSubmitting: true,
submitData: res,
});
return errors;
});
}
return Promise.resolve(processData(_data.current, flatten)).then(res => {
setState({
isValidating: false,
isSubmitting: true,
submitData: res,
});
return errors;
});
}
);
})
.catch(err => {
// 不应该走到这边的
......@@ -333,6 +340,7 @@ export const useForm = props => {
// methods
touchKey,
removeTouched,
changeTouchedKeys,
onItemChange,
setValueByPath: onItemChange, // 单个
getSchemaByPath,
......
......@@ -280,7 +280,11 @@ export function getFormat(format) {
break;
default:
// dateTime
dateFormat = 'YYYY-MM-DD';
if (typeof format === 'string') {
dateFormat = format;
} else {
dateFormat = 'YYYY-MM-DD';
}
}
return dateFormat;
}
......@@ -414,13 +418,13 @@ export function parseSingleExpression(func, formData = {}, dataPath) {
const funcBody = func.substring(2, func.length - 2);
const str = `
return ${funcBody
.replaceAll('formData', JSON.stringify(formData))
.replaceAll('rootValue', JSON.stringify(parent))}`;
.replace(/formData/g, JSON.stringify(formData))
.replace(/rootValue/g, JSON.stringify(parent))}`;
try {
return Function(str)();
} catch (error) {
console.log(error);
console.log(error, func, dataPath);
return func;
}
// const funcBody = func.substring(2, func.length - 2);
......@@ -475,16 +479,21 @@ export const parseAllExpression = (_schema, formData, dataPath) => {
const schema = clone(_schema);
Object.keys(schema).forEach(key => {
const value = schema[key];
if (isExpression(value)) {
if (isObject(value)) {
// TODO: dataPath 这边要处理一下,否则rootValue类的没有效果
schema[key] = parseAllExpression(value, formData, dataPath);
} else if (isExpression(value)) {
schema[key] = parseSingleExpression(value, formData, dataPath);
console.log(
formData.materialType,
dataPath,
parseSingleExpression(value, formData, dataPath)
);
}
// 有可能叫 xxxProps
if (typeof key === 'string' && key.toLowerCase().indexOf('props') > -1) {
// console.log(
// formData.materialType,
// dataPath,
// parseSingleExpression(value, formData, dataPath)
// );
} else if (
typeof key === 'string' &&
key.toLowerCase().indexOf('props') > -1
) {
// 有可能叫 xxxProps
const propsObj = schema[key];
if (isObject(propsObj)) {
Object.keys(propsObj).forEach(k => {
......@@ -676,7 +685,7 @@ export const removeEmptyItemFromList = formData => {
export const getDescriptorFromSchema = ({ schema, isRequired = true }) => {
let result = {};
let singleResult = {};
if (schema.hidden === true) return result;
if (schema.hidden === true) return { validator: () => true };
if (isObjType(schema)) {
result.type = 'object';
if (isRequired && schema.required === true) {
......@@ -699,10 +708,10 @@ export const getDescriptorFromSchema = ({ schema, isRequired = true }) => {
if (isRequired && schema.required === true) {
result.required = true;
}
if (schema.min) {
if (typeof schema.min === 'number') {
result.min = schema.min;
}
if (schema.max) {
if (typeof schema.max === 'number') {
result.max = schema.max;
}
result.defaultField = { type: 'object', fields: {} }; // 目前就默认只有object类型的 TODO:
......@@ -740,13 +749,18 @@ export const getDescriptorFromSchema = ({ schema, isRequired = true }) => {
validator: (rule, value) => {
if (!value) return true;
if (Array.isArray(value)) {
if (value[0] && value[1]) {
// range组件点击clear,会变成 ['','']
if (
typeof value[0] === 'string' &&
typeof value[1] === 'string'
) {
return true;
}
return false;
}
return false;
},
type: 'array',
message: '${title}必填',
};
singleResult = rangeValidator;
......@@ -768,7 +782,7 @@ export const getDescriptorFromSchema = ({ schema, isRequired = true }) => {
let requiredRule;
if (isRequired && schema.required === true) {
requiredRule = { required: true };
requiredRule = { required: true, type: singleResult.type || 'string' };
}
if (schema.rules) {
......@@ -889,20 +903,34 @@ export const isPathRequired = (path, schema) => {
}
};
export const generateDataSkeleton = schema => {
// _path 只供内部递归使用
export const generateDataSkeleton = (schema, formData, _path = '') => {
let result = {};
let _formData = clone(formData);
if (isObjType(schema)) {
if (_formData === undefined || typeof _formData !== 'object') {
_formData = {};
}
Object.keys(schema.properties).forEach(key => {
const childSchema = schema.properties[key];
const childResult = generateDataSkeleton(childSchema);
const childData = _formData[key];
const childResult = generateDataSkeleton(
childSchema,
childData,
`${_path}.${key}`
);
result[key] = childResult;
});
} else if (schema.default !== undefined) {
result = clone(schema.default);
} else if (schema.type === 'boolean') {
result = false;
} else if (_formData !== undefined) {
result = clone(_formData);
} else {
result = undefined;
if (schema.default !== undefined) {
result = clone(schema.default);
} else if (schema.type === 'boolean') {
result = false;
} else {
result = undefined;
}
}
return result;
};
......@@ -915,10 +943,10 @@ export const translateMessage = (msg, schema) => {
msg = msg.replace('${title}', schema.title);
msg = msg.replace('${type}', schema.format || schema.type);
// 兼容代码
if (schema.min) {
if (typeof schema.min === 'number') {
msg = msg.replace('${min}', schema.min);
}
if (schema.max) {
if (typeof schema.max === 'number') {
msg = msg.replace('${max}', schema.max);
}
if (schema.rules) {
......@@ -1116,7 +1144,7 @@ export const cleanEmpty = obj => {
export const removeHiddenFromResult = (data, flatten) => {
Object.keys(flatten).forEach(key => {
const hidden = flatten[key].schema && flatten[key].schema.hidden === true; // TODO: 有表达式的情况
const hidden = flatten[key].schema && flatten[key].schema.hidden === true; // Remark: 有表达式的情况, 暂时不去掉了(有业务反而是希望留下的),就去掉 hidden = true 的
if (get(data, key) !== undefined && hidden) {
set(data, key, undefined);
}
......
......@@ -4,6 +4,8 @@ import {
formatPathFromValidator,
isPathRequired,
generateDataSkeleton,
parseAllExpression,
schemaContainsExpression,
getArray,
} from './utils';
import { defaultValidateMessagesCN } from './validateMessageCN';
......@@ -20,11 +22,17 @@ export const validateAll = ({
locale = 'cn',
validateMessages = {},
}) => {
if (Object.keys(schema).length === 0) return Promise.resolve();
// Check: 添加了这个逻辑,不知道性能是否会变坏
let _schema = schema;
if (schemaContainsExpression(schema, false)) {
_schema = parseAllExpression(schema, formData, '#');
}
if (Object.keys(_schema).length === 0) return Promise.resolve();
const descriptor = getDescriptorFromSchema({
schema,
schema: _schema,
isRequired,
}).fields;
window.descriptor = descriptor;
// console.log(descriptor, '&&&& descriptor', formData);
let touchVerifyList = [];
......@@ -33,7 +41,7 @@ export const validateAll = ({
// 因为要整个构建validator在list的情况太复杂了,所以required单独拿出来处理,但是这边有不少单独处理逻辑,例如message
if (!isRequired) {
touchedKeys.forEach(key => {
const keyRequired = isPathRequired(key, schema);
const keyRequired = isPathRequired(key, _schema);
const val = get(formData, key);
const nullValue = [undefined, null, ''].indexOf(val) > -1; // 注意 0 不是
const isEmptyMultiSelect = Array.isArray(val) && val.length === 0;
......
......@@ -41,5 +41,9 @@ export default ({ onChange, format, value, style, ...rest }) => {
dateParams = { ...dateParams, ...rest };
if (dateFormat === format) {
dateParams.format = format;
}
return <DatePicker {...dateParams} />;
};
......@@ -50,6 +50,10 @@ const DateRange = ({ onChange, format, value, style, ...rest }) => {
dateParams = { ...dateParams, ...rest };
if (dateFormat === format) {
dateParams.format = format;
}
return <RangePicker {...dateParams} />;
};
......
......@@ -47,7 +47,9 @@ export default function map({ children, title, ...rest }) {
<div className="w-100">
<Collapse activeKey={collapsed ? [] : ['1']} onChange={toggle}>
<Panel
header={<div style={{ fontSize: 16, fontWeight: 500 }}>{title}</div>}
header={
<span style={{ fontSize: 16, fontWeight: 500 }}>{title}</span>
}
key="1"
className="fr-collapse-object"
>
......
{
"name": "fr-generator",
"version": "2.0.3",
"version": "2.1.2",
"scripts": {
"build": "father-build",
"prepare": "npm run build",
......
......@@ -73,8 +73,9 @@ const RenderField = ({
const usefulWidgetProps = transformProps({
value: data || schema.default,
checked: data,
disabled: schema['disabled'],
readOnly: schema['readOnly'],
disabled: schema.disabled,
readOnly: schema.readOnly,
format: schema.format,
onChange,
schema,
...schema['props'],
......
......@@ -11,8 +11,8 @@ import {
flattenToData,
looseJsonParse,
isObject,
oldSchemaToNew,
newSchemaToOld,
schemaToState,
} from './utils';
import { Ctx, StoreCtx } from './context';
import FR from './FR';
......@@ -90,28 +90,12 @@ function Wrapper(
setState({ schemaForImport: e.target.value });
};
// 收口点 propsSchema 到 schema 的转换(一共就3个入口:defaultValue,importSchema,setValue)
// TODO: 3个入口可能还是太多了,是不是考虑在外面裹一层
// TODO2: 导入这边看看会不会传一个乱写的schema就crash
const importSchema = () => {
try {
const value = transformFrom(looseJsonParse(local.schemaForImport));
let _isNewVersion = true;
if (value && value.propsSchema) {
_isNewVersion = false;
}
const schema = oldSchemaToNew(value);
setGlobal(state => ({
schema,
formData: {},
setGlobal(() => ({
selected: undefined,
isNewVersion: _isNewVersion,
frProps: {
...state.frProps,
column: schema.column,
displayType: schema.displayType,
labelWidth: schema.labelWidth,
},
...schemaToState(value),
}));
} catch (error) {
message.info('格式不对哦,请重新尝试'); // 可以加个格式哪里不对的提示
......@@ -141,29 +125,12 @@ function Wrapper(
const getValue = () => displaySchema;
// 收口点 propsSchema 到 schema
// setValue 外部用于修改大schema,叫setSchema比较合适
// TODO: 这次顶层的props传递改动和整理后,确保这个api还是正确的
const setValue = value => {
try {
// TODO: 这里默认使用setValue的同学不使用ui:Schema
let _isNewVersion = true;
if (value && value.propsSchema) {
_isNewVersion = false;
}
const schema = oldSchemaToNew(value);
setGlobal(state => ({
...state,
schema,
formData: {},
selected: undefined,
isNewVersion: _isNewVersion,
frProps: {
...state.frProps,
column: schema.column,
displayType: schema.displayType,
labelWidth: schema.labelWidth,
},
...schemaToState(value),
}));
} catch (error) {
console.error(error);
......
......@@ -10,7 +10,7 @@ import list from './widgets/antd/list';
import './atom.less';
import './Main.less';
import 'antd/dist/antd.less';
import { oldSchemaToNew } from './utils';
import { schemaToState } from './utils';
const DEFAULT_SCHEMA = {
type: 'object',
......@@ -48,9 +48,7 @@ function App(props, ref) {
const frwRef = ref || useRef();
const [state, setState] = useSet({
formData: {},
frProps: {
displayType: 'row',
}, // form-render 的全局props等
frProps: {}, // form-render 的全局 props 等
hovering: undefined, // 目前没有用到
isNewVersion: true, // 用schema字段,还是用propsSchema字段,这是一个问题
preview: false, // preview = false 是编辑模式
......@@ -61,21 +59,7 @@ function App(props, ref) {
// 收口点 propsSchema 到 schema 的转换 (一共3处,其他两个是 importSchema 和 setValue,在 FRWrapper 文件)
useEffect(() => {
const schema = defaultValue ? transformFrom(defaultValue) : DEFAULT_SCHEMA;
if (!schema) return;
if (schema.propsSchema) {
setState({ isNewVersion: false });
} else {
setState({ isNewVersion: true });
}
setState({
schema: oldSchemaToNew(schema), // 旧的转新的,新的不变
formData: schema.formData || {},
frProps: {
column: schema.column,
displayType: schema.displayType,
labelWidth: schema.labelWidth,
},
});
if (schema) setState(schemaToState(schema));
}, [defaultValue]);
const {
......
......@@ -6,12 +6,9 @@ import { useStore, useGlobal } from '../hooks';
export default function GlobalSettings() {
const form = useForm();
const [innerUpdate, setInnerUpdate] = useState(false);
const { widgets, frProps, userProps } = useStore();
const { widgets, frProps, userProps = {} } = useStore();
const setGlobal = useGlobal();
const globalSettings =
userProps && userProps.globalSettings
? userProps.globalSettings
: defaultGlobalSettings;
const globalSettings = userProps.globalSettings || defaultGlobalSettings;
const onDataChange = value => {
setInnerUpdate(true);
......
......@@ -5,6 +5,7 @@ import IdInput from '../widgets/antd/idInput';
import PercentSlider from '../widgets/antd/percentSlider';
import {
defaultSettings,
baseCommonSettings,
defaultCommonSettings,
elements,
advancedElements,
......@@ -38,14 +39,28 @@ export default function ItemSettings() {
const getWidgetList = (settings, commonSettings) => {
return settings.reduce((widgetList, setting) => {
if (!Array.isArray(setting.widgets)) return widgetList;
const basicWidgets = setting.widgets.map(item => ({
...item,
widget:
item.widget ||
item.schema.widget ||
getWidgetName(item.schema, defaultMapping),
setting: { ...commonSettings, ...item.setting },
}));
const basicWidgets = setting.widgets.map(item => {
const baseItemSettings = {};
if (item.schema.type === 'array') {
baseItemSettings.items = {
type: 'object',
hidden: '{{true}}',
};
}
return {
...item,
widget:
item.widget ||
item.schema.widget ||
getWidgetName(item.schema, defaultMapping),
setting: {
...baseCommonSettings,
...commonSettings,
...baseItemSettings,
...item.setting,
},
};
});
return [...widgetList, ...basicWidgets];
}, []);
};
......@@ -69,7 +84,7 @@ export default function ItemSettings() {
// setting 该显示什么的计算,要把选中组件的 schema 和它对应的 widgets 的整体 schema 进行拼接
try {
const item = flatten[selected];
if (!item) return;
if (!item || selected === '#') return;
setReady(false);
// 算 widgetList
const _settings = Array.isArray(settings)
......@@ -88,13 +103,16 @@ export default function ItemSettings() {
if (hideId) delete properties.$id;
form.setValues(item.schema);
setSettingSchema({
type: 'object',
displayType: 'column',
properties,
});
setTimeout(() => setReady(true), 0);
form.setValues(item.schema);
setTimeout(() => {
setReady(true);
onDataChange(form.getValues());
}, 0);
} catch (error) {
console.log(error);
}
......
// 只需写配置,方便可扩展
export const baseCommonSettings = {
title: {
title: '标题',
type: 'string',
},
type: {
title: '类型',
type: 'string',
hidden: '{{true}}',
},
widget: {
title: '组件',
type: 'string',
hidden: '{{true}}',
},
format: {
title: '格式',
type: 'string',
hidden: '{{true}}',
},
};
export const defaultCommonSettings = {
$id: {
title: 'ID',
......@@ -322,7 +344,6 @@ export const elements = [
name: 'checkboxes',
schema: {
title: '多选',
description: '点击多选',
type: 'array',
widget: 'checkboxes',
items: {
......@@ -424,7 +445,7 @@ export const advancedElements = [
export const layouts = [
{
text: 'object',
text: '对象',
name: 'object',
schema: {
title: '对象',
......@@ -445,6 +466,10 @@ export const layouts = [
},
},
setting: {
items: {
type: 'object',
hidden: '{{true}}',
},
min: {
title: '最小长度',
type: 'number',
......@@ -486,6 +511,10 @@ export const layouts = [
},
},
setting: {
items: {
type: 'object',
hidden: '{{true}}',
},
min: {
title: '最小长度',
type: 'number',
......@@ -531,6 +560,10 @@ export const layouts = [
},
},
setting: {
items: {
type: 'object',
hidden: '{{true}}',
},
min: {
title: '最小长度',
type: 'number',
......@@ -572,6 +605,10 @@ export const layouts = [
},
},
setting: {
items: {
type: 'object',
hidden: '{{true}}',
},
min: {
title: '最小长度',
type: 'number',
......@@ -694,6 +731,7 @@ export const defaultGlobalSettings = {
displayType: {
title: '标签展示模式',
type: 'string',
default: 'row',
enum: ['row', 'column'],
enumNames: ['同行', '单独一行'],
widget: 'radio',
......
......@@ -228,6 +228,12 @@ export function idToSchema(flatten, id = '#', final = false) {
if (final) {
schema.$id && delete schema.$id;
}
if (schema.type === 'array') {
if (!schema.items) schema.items = {}
if (!schema.items.type) {
schema.items.type = 'object'
}
}
if (item.children.length > 0) {
item.children.forEach(child => {
let childId = child;
......@@ -588,6 +594,22 @@ export const newSchemaToOld = setting => {
return setting;
};
export const schemaToState = value => {
const schema = oldSchemaToNew(value);
const frProps = Object.keys(schema).reduce((rst, cur) => {
if (['type', 'properties'].includes(cur)) return rst;
return { ...rst, [cur]: schema[cur] };
}, {});
const isNewVersion = !(value && value.propsSchema);
return {
schema,
frProps,
formData: schema.formData || {},
isNewVersion,
};
};
export function defaultGetValueFromEvent(valuePropName, ...args) {
const event = args[0];
if (event && event.target && valuePropName in event.target) {
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册