未验证 提交 ac9eee1c 编写于 作者: 徒言 提交者: GitHub

Merge pull request #420 from alibaba/dev

1.4.1 -1.4.4 一些bug fix

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'}
[!] 修复了几个声明文件上的类型注释错误
/**
* 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;
......@@ -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
#### 新功能
......@@ -14,15 +35,12 @@
- [!] fix 了列表元素上下移动后校验信息展示有误的问题(一个简单 fix,还需后续优化)
- [!] fix 了列表中非 string 类型的元素类型校验一直不通过的问题
- [!] 修复了 range 元素的校验,清空后不再会通不过校验
- [!] 修复了 hidden 是表达式时,即使隐藏也还是会被校验的问题
- [!] fix 了 min = 0 时,校验信息错误的问题
#### 其他
- [!] fix 了 watch 的声明类型错误的问题
- [!] fix 了部分情况 disabled 状态变化后展示无反馈的问题
- [!] antd 升级 4.16.0 Collapse.Panel 样式变更导致对象展示奇怪,修复兼容性
### 1.3.4
......
{
"name": "form-render",
"version": "1.4.0",
"version": "1.4.4",
"description": "通过 JSON Schema 生成标准 Form,常用于自定义搭建配置界面生成",
"repository": {
"type": "git",
......
......@@ -55,7 +55,7 @@ const RenderList = ({
newItem,
...displayList.slice(idx),
];
onItemChange(dataPath, newList);
onItemChange(dataPath, JSON.parse(JSON.stringify(newList)));
};
const deleteItem = idx => {
......
......@@ -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;
......@@ -47,16 +47,14 @@ export interface FormInstance {
syncStuff: (any) => void;
}
export type WatchProperties =
| {
[path: string]: (value: any) => any;
}
| {
[path: string]: {
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,
......@@ -224,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;
......
......@@ -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;
}
......@@ -420,7 +424,7 @@ export function parseSingleExpression(func, formData = {}, dataPath) {
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) {
} else if (
typeof key === 'string' &&
key.toLowerCase().indexOf('props') > -1
) {
// 有可能叫 xxxProps
const propsObj = schema[key];
if (isObject(propsObj)) {
Object.keys(propsObj).forEach(k => {
......@@ -495,9 +504,6 @@ export const parseAllExpression = (_schema, formData, dataPath) => {
);
});
}
} else if (isObject(value)) {
// TODO: dataPath 这边要处理一下,否则rootValue类的没有效果
schema[key] = parseAllExpression(value, formData, dataPath);
}
});
return schema;
......@@ -679,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) {
......@@ -754,6 +760,7 @@ export const getDescriptorFromSchema = ({ schema, isRequired = true }) => {
}
return false;
},
type: 'array',
message: '${title}必填',
};
singleResult = rangeValidator;
......@@ -775,7 +782,7 @@ export const getDescriptorFromSchema = ({ schema, isRequired = true }) => {
let requiredRule;
if (isRequired && schema.required === true) {
requiredRule = { required: true, type: singleResult.type };
requiredRule = { required: true, type: singleResult.type || 'string' };
}
if (schema.rules) {
......@@ -1137,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);
}
......
......@@ -5,6 +5,7 @@ import {
isPathRequired,
generateDataSkeleton,
parseAllExpression,
schemaContainsExpression,
getArray,
} from './utils';
import { defaultValidateMessagesCN } from './validateMessageCN';
......@@ -22,12 +23,16 @@ export const validateAll = ({
validateMessages = {},
}) => {
// Check: 添加了这个逻辑,不知道性能是否会变坏
const _schema = parseAllExpression(schema, formData, '#');
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,
isRequired,
}).fields;
window.descriptor = descriptor;
// console.log(descriptor, '&&&& descriptor', formData);
let touchVerifyList = [];
......
......@@ -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={<span style={{ fontSize: 16, fontWeight: 500 }}>{title}</span>}
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.
先完成此消息的编辑!
想要评论请 注册