yy-forms 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.fatherrc.js +37 -0
- package/CHANGELOG.md +254 -0
- package/LICENSE +21 -0
- package/README.md +99 -0
- package/dist/index.d.ts +145 -0
- package/dist/index.esm.js +4006 -0
- package/dist/index.js +4041 -0
- package/es/Provider.js +248 -0
- package/es/index.d.ts +145 -0
- package/es/index.js +44 -0
- package/es/settings/index.js +975 -0
- package/es/styles/atom.less +1134 -0
- package/es/styles/index.less +358 -0
- package/es/transformer/form-render.js +75 -0
- package/es/utils/context.js +3 -0
- package/es/utils/hooks.js +48 -0
- package/es/utils/index.js +706 -0
- package/es/utils/mapping.js +31 -0
- package/es/utils/serialize.js +276 -0
- package/es/widgets/htmlInput.js +20 -0
- package/es/widgets/idInput.js +23 -0
- package/es/widgets/index.js +5 -0
- package/es/widgets/jsonInput.js +24 -0
- package/es/widgets/list.js +24 -0
- package/es/widgets/percentSlider.js +89 -0
- package/package.json +53 -0
- package/src/Provider.jsx +239 -0
- package/src/components/Canvas/core/RenderChildren.jsx +18 -0
- package/src/components/Canvas/core/RenderField.jsx +129 -0
- package/src/components/Canvas/core/Wrapper.jsx +298 -0
- package/src/components/Canvas/core/Wrapper.less +57 -0
- package/src/components/Canvas/core/index.jsx +171 -0
- package/src/components/Canvas/index.jsx +178 -0
- package/src/components/Settings/GlobalSettings.jsx +48 -0
- package/src/components/Settings/ItemSettings.jsx +143 -0
- package/src/components/Settings/index.jsx +75 -0
- package/src/components/Settings/index.less +25 -0
- package/src/components/Sidebar/Element.jsx +80 -0
- package/src/components/Sidebar/Element.less +18 -0
- package/src/components/Sidebar/index.jsx +47 -0
- package/src/components/Sidebar/index.less +23 -0
- package/src/i18next/index.ts +14 -0
- package/src/i18next/locales/enUS.json +60 -0
- package/src/i18next/locales/resources.ts +7 -0
- package/src/i18next/locales/zhCN.json +3 -0
- package/src/index.d.ts +145 -0
- package/src/index.js +45 -0
- package/src/settings/index.js +1058 -0
- package/src/styles/atom.less +1134 -0
- package/src/styles/index.less +358 -0
- package/src/transformer/form-render.js +65 -0
- package/src/utils/context.js +4 -0
- package/src/utils/hooks.js +35 -0
- package/src/utils/index.js +678 -0
- package/src/utils/mapping.js +29 -0
- package/src/utils/serialize.js +368 -0
- package/src/widgets/htmlInput.jsx +24 -0
- package/src/widgets/idInput.jsx +27 -0
- package/src/widgets/index.js +6 -0
- package/src/widgets/jsonInput.jsx +29 -0
- package/src/widgets/list.jsx +28 -0
- package/src/widgets/percentSlider.jsx +74 -0
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
.field-wrapper {
|
|
2
|
+
padding: 20px 8px 0 8px;
|
|
3
|
+
border: 1px dashed #bbb;
|
|
4
|
+
margin: 0 0 8px 0;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
// 统一编辑区滚动条样式
|
|
8
|
+
.field-wrapper::-webkit-scrollbar {
|
|
9
|
+
width: 5px;
|
|
10
|
+
background-color: #0002;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.field-wrapper::-webkit-scrollbar-thumb {
|
|
14
|
+
background: #0004;
|
|
15
|
+
border-radius: 5px;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.dnd-container > .field-wrapper {
|
|
19
|
+
margin: 0;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.fr-generator-container .dnd-container > .field-wrapper > div.fr-field {
|
|
23
|
+
margin: 0;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.pointer-move {
|
|
27
|
+
position: absolute;
|
|
28
|
+
top: -2px;
|
|
29
|
+
left: -2px;
|
|
30
|
+
height: 24px;
|
|
31
|
+
width: 24px;
|
|
32
|
+
color: #fff;
|
|
33
|
+
background-color: #409eff;
|
|
34
|
+
cursor: move;
|
|
35
|
+
display: flex;
|
|
36
|
+
align-items: center;
|
|
37
|
+
justify-content: center;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.pointer-wrapper {
|
|
41
|
+
position: absolute;
|
|
42
|
+
z-index: 20;
|
|
43
|
+
bottom: -2px;
|
|
44
|
+
right: -2px;
|
|
45
|
+
height: 24px;
|
|
46
|
+
border-top-left-radius: 8px;
|
|
47
|
+
background: #409eff;
|
|
48
|
+
display: flex;
|
|
49
|
+
justify-content: center;
|
|
50
|
+
align-items: center;
|
|
51
|
+
padding: 0 4px;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.pointer {
|
|
55
|
+
color: #fff;
|
|
56
|
+
padding: 0 4px;
|
|
57
|
+
}
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import FormRender, { useForm } from 'form-render';
|
|
2
|
+
import React, { useEffect } from 'react';
|
|
3
|
+
import { useTranslation } from 'react-i18next';
|
|
4
|
+
import { dataToFlatten, flattenToData } from '../../../utils';
|
|
5
|
+
import { useStore } from '../../../utils/hooks';
|
|
6
|
+
import RenderChildren from './RenderChildren';
|
|
7
|
+
import RenderField from './RenderField';
|
|
8
|
+
import Wrapper from './Wrapper';
|
|
9
|
+
|
|
10
|
+
const PreviewFR = ({ schema, data }) => {
|
|
11
|
+
const form = useForm();
|
|
12
|
+
const { flatten, widgets, mapping, methods, userProps, onFlattenChange } = useStore();
|
|
13
|
+
const renderSchema = userProps.transformer.to(schema);
|
|
14
|
+
|
|
15
|
+
useEffect(() => {
|
|
16
|
+
form.setValues(data);
|
|
17
|
+
}, []);
|
|
18
|
+
|
|
19
|
+
return (
|
|
20
|
+
<FormRender
|
|
21
|
+
schema={renderSchema}
|
|
22
|
+
form={form}
|
|
23
|
+
widgets={widgets}
|
|
24
|
+
mapping={mapping}
|
|
25
|
+
methods={methods}
|
|
26
|
+
watch={{
|
|
27
|
+
'#': formData => {
|
|
28
|
+
onFlattenChange(dataToFlatten(flatten, formData), 'data');
|
|
29
|
+
},
|
|
30
|
+
}}
|
|
31
|
+
/>
|
|
32
|
+
);
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const FR = ({ id = '#', preview, displaySchema }) => {
|
|
36
|
+
const { flatten, frProps = {} } = useStore();
|
|
37
|
+
const { t } = useTranslation();
|
|
38
|
+
if (preview) {
|
|
39
|
+
const data = flattenToData(flatten);
|
|
40
|
+
return <PreviewFR schema={displaySchema} data={data} />;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const { column } = frProps;
|
|
44
|
+
const item = flatten[id];
|
|
45
|
+
if (!item) return null;
|
|
46
|
+
|
|
47
|
+
const { schema } = item;
|
|
48
|
+
const displayType = schema.displayType || frProps.displayType;
|
|
49
|
+
const isObj = schema.type === 'object';
|
|
50
|
+
const isList =
|
|
51
|
+
schema.type === 'array' && schema.enum === undefined && !!schema.items;
|
|
52
|
+
const isComplex = isObj || isList;
|
|
53
|
+
const width = schema['width'];
|
|
54
|
+
let containerClass = `fr-field w-100 ${isComplex ? 'fr-field-complex' : ''} ${
|
|
55
|
+
schema.className || ''
|
|
56
|
+
}`;
|
|
57
|
+
let labelClass = 'fr-label mb2';
|
|
58
|
+
let contentClass = 'fr-content';
|
|
59
|
+
|
|
60
|
+
let columnStyle = {};
|
|
61
|
+
if (!isComplex && width) {
|
|
62
|
+
columnStyle = {
|
|
63
|
+
width,
|
|
64
|
+
paddingRight: '12px',
|
|
65
|
+
};
|
|
66
|
+
} else if (!isComplex && column > 1) {
|
|
67
|
+
columnStyle = {
|
|
68
|
+
width: `calc(100% /${column})`,
|
|
69
|
+
paddingRight: '12px',
|
|
70
|
+
};
|
|
71
|
+
} else if ('flex' === schema?.theme) {
|
|
72
|
+
columnStyle.width = width;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// 如果传入自定义样式则覆盖使用,object 外层样式使用 schema.style,内层样式使用 schema.props.style
|
|
76
|
+
if ('object' === typeof schema?.style) {
|
|
77
|
+
columnStyle = {
|
|
78
|
+
...columnStyle,
|
|
79
|
+
...schema.style,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
switch (schema.type) {
|
|
84
|
+
case 'object':
|
|
85
|
+
if (schema.title) {
|
|
86
|
+
containerClass += ' ba b--black-20 pt4 pr3 pb2 relative mt3 mb4'; // object的margin bottom由内部元素撑起
|
|
87
|
+
labelClass += ' fr-label-object bg-white absolute ph2 top-upper left-1'; // fr-label-object 无默认style,只是占位用于使用者样式覆盖
|
|
88
|
+
}
|
|
89
|
+
containerClass += ' fr-field-object'; // object的margin bottom由内部元素撑起
|
|
90
|
+
if (schema.title) {
|
|
91
|
+
contentClass += ' ml3'; // 缩进
|
|
92
|
+
}
|
|
93
|
+
break;
|
|
94
|
+
case 'array':
|
|
95
|
+
if (isList) {
|
|
96
|
+
labelClass += ' mt2 mb3';
|
|
97
|
+
}
|
|
98
|
+
break;
|
|
99
|
+
case 'boolean':
|
|
100
|
+
if (schema['widget'] !== 'switch') {
|
|
101
|
+
if (schema.title) {
|
|
102
|
+
labelClass += ' ml2';
|
|
103
|
+
labelClass = labelClass.replace('mb2', 'mb0');
|
|
104
|
+
}
|
|
105
|
+
contentClass += ' flex items-center'; // checkbox高度短,需要居中对齐
|
|
106
|
+
containerClass += ' flex items-center flex-row-reverse justify-end';
|
|
107
|
+
}
|
|
108
|
+
break;
|
|
109
|
+
default:
|
|
110
|
+
if (displayType === 'row') {
|
|
111
|
+
labelClass = labelClass.replace('mb2', 'mb0');
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
// 横排时
|
|
115
|
+
const isCheckBox = schema.type === 'boolean' && schema['widget'] !== 'switch';
|
|
116
|
+
if (displayType === 'row' && !isComplex && !isCheckBox) {
|
|
117
|
+
containerClass += ' flex items-center';
|
|
118
|
+
labelClass += ' flex-shrink-0 fr-label-row';
|
|
119
|
+
labelClass = labelClass.replace('mb2', 'mb0');
|
|
120
|
+
contentClass += ' flex-grow-1 relative';
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// 横排的checkbox
|
|
124
|
+
if (displayType === 'row' && isCheckBox) {
|
|
125
|
+
contentClass += ' flex justify-end pr2';
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const fieldProps = {
|
|
129
|
+
$id: id,
|
|
130
|
+
item,
|
|
131
|
+
labelClass,
|
|
132
|
+
contentClass,
|
|
133
|
+
isComplex,
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
const childrenElement =
|
|
137
|
+
item.children && item.children.length > 0 ? (
|
|
138
|
+
<ul className={`flex flex-wrap pl0`} style={schema?.props?.style}>
|
|
139
|
+
<RenderChildren children={item.children} />
|
|
140
|
+
</ul>
|
|
141
|
+
) : null;
|
|
142
|
+
|
|
143
|
+
const isEmpty = Object.keys(flatten).length < 2; // 只有一个根元素 # 的情况
|
|
144
|
+
if (isEmpty) {
|
|
145
|
+
return (
|
|
146
|
+
<Wrapper style={columnStyle} $id={id} item={item}>
|
|
147
|
+
<div
|
|
148
|
+
className={`${containerClass} h-100 f4 black-40 flex items-center justify-center`}
|
|
149
|
+
>
|
|
150
|
+
{t('点击/拖拽左侧栏的组件进行添加')}
|
|
151
|
+
</div>
|
|
152
|
+
</Wrapper>
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return (
|
|
157
|
+
<Wrapper style={columnStyle} $id={id} item={item}>
|
|
158
|
+
<div className={containerClass}>
|
|
159
|
+
<RenderField {...fieldProps}>
|
|
160
|
+
{(isObj || isList) && (
|
|
161
|
+
<Wrapper $id={id} item={item} inside>
|
|
162
|
+
{childrenElement || <div className="h2" />}
|
|
163
|
+
</Wrapper>
|
|
164
|
+
)}
|
|
165
|
+
</RenderField>
|
|
166
|
+
</div>
|
|
167
|
+
</Wrapper>
|
|
168
|
+
);
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
export default FR;
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import { Button, Input, message, Modal } from 'antd';
|
|
2
|
+
import copyTOClipboard from 'copy-text-to-clipboard';
|
|
3
|
+
import { useTranslation } from 'react-i18next';
|
|
4
|
+
import React, { useEffect } from 'react';
|
|
5
|
+
import {
|
|
6
|
+
idToSchema,
|
|
7
|
+
isObject,
|
|
8
|
+
looseJsonParse,
|
|
9
|
+
schemaToState,
|
|
10
|
+
} from '../../utils';
|
|
11
|
+
import { useGlobal, useSet, useStore } from '../../utils/hooks';
|
|
12
|
+
import FR from './core';
|
|
13
|
+
|
|
14
|
+
const { TextArea } = Input;
|
|
15
|
+
|
|
16
|
+
const Canvas = ({ onSelect }) => {
|
|
17
|
+
const { t } = useTranslation();
|
|
18
|
+
const setGlobal = useGlobal();
|
|
19
|
+
const {
|
|
20
|
+
userProps,
|
|
21
|
+
displaySchema,
|
|
22
|
+
displaySchemaString,
|
|
23
|
+
preview,
|
|
24
|
+
selected,
|
|
25
|
+
flatten,
|
|
26
|
+
onChange,
|
|
27
|
+
onSchemaChange,
|
|
28
|
+
} = useStore();
|
|
29
|
+
const [local, setState] = useSet({
|
|
30
|
+
showModal: false,
|
|
31
|
+
showModal2: false,
|
|
32
|
+
schemaForImport: '',
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
const { transformer, extraButtons = [] } = userProps;
|
|
36
|
+
|
|
37
|
+
const toggleModal = () => setState({ showModal: !local.showModal });
|
|
38
|
+
const toggleModal2 = () => setState({ showModal2: !local.showModal2 });
|
|
39
|
+
|
|
40
|
+
const onTextareaChange = e => {
|
|
41
|
+
setState({ schemaForImport: e.target.value });
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const importSchema = () => {
|
|
45
|
+
try {
|
|
46
|
+
const value = transformer.from(looseJsonParse(local.schemaForImport));
|
|
47
|
+
setGlobal(() => ({
|
|
48
|
+
selected: undefined,
|
|
49
|
+
...schemaToState(value),
|
|
50
|
+
}));
|
|
51
|
+
onChange(value.formData || {});
|
|
52
|
+
onSchemaChange(value);
|
|
53
|
+
} catch (error) {
|
|
54
|
+
console.error(error, 'catch');
|
|
55
|
+
message.info(t('格式不对哦,请重新尝试')); // 可以加个格式哪里不对的提示
|
|
56
|
+
}
|
|
57
|
+
toggleModal2();
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const copySchema = () => {
|
|
61
|
+
copyTOClipboard(displaySchemaString);
|
|
62
|
+
message.info(t('复制成功'));
|
|
63
|
+
toggleModal();
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const clearSchema = () => {
|
|
67
|
+
const schema = {
|
|
68
|
+
type: 'object',
|
|
69
|
+
properties: {},
|
|
70
|
+
};
|
|
71
|
+
setGlobal({
|
|
72
|
+
schema,
|
|
73
|
+
formData: {},
|
|
74
|
+
selected: undefined,
|
|
75
|
+
});
|
|
76
|
+
onChange({});
|
|
77
|
+
onSchemaChange(schema);
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
useEffect(() => {
|
|
81
|
+
if (!onSelect) return;
|
|
82
|
+
onSelect(idToSchema(flatten, selected));
|
|
83
|
+
}, [selected]);
|
|
84
|
+
|
|
85
|
+
const _extraButtons = Array.isArray(extraButtons) ? extraButtons : [];
|
|
86
|
+
const _showDefaultBtns = _extraButtons.filter(item => !isObject(item));
|
|
87
|
+
const _extraBtns = _extraButtons.filter(item => isObject(item));
|
|
88
|
+
|
|
89
|
+
const getDefaultBtnText = (text, defaultText, index) => {
|
|
90
|
+
if (typeof index === 'number') {
|
|
91
|
+
if (Array.isArray(text)) return text[index];
|
|
92
|
+
return defaultText[index];
|
|
93
|
+
}
|
|
94
|
+
if (typeof text === 'string') return text;
|
|
95
|
+
return defaultText;
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
return (
|
|
99
|
+
<div className="mid-layout pr2">
|
|
100
|
+
<div className="mv2 mh1">
|
|
101
|
+
{_showDefaultBtns[0] !== false && (
|
|
102
|
+
<Button
|
|
103
|
+
className="mr2 mb1"
|
|
104
|
+
onClick={() => {
|
|
105
|
+
setGlobal({ selected: '#', preview: !preview });
|
|
106
|
+
}}
|
|
107
|
+
>
|
|
108
|
+
{getDefaultBtnText(
|
|
109
|
+
_showDefaultBtns[0],
|
|
110
|
+
[t('开始编辑'), t('最终展示')],
|
|
111
|
+
Number(!preview)
|
|
112
|
+
)}
|
|
113
|
+
</Button>
|
|
114
|
+
)}
|
|
115
|
+
{_showDefaultBtns[1] !== false && (
|
|
116
|
+
<Button className="mr2" onClick={clearSchema}>
|
|
117
|
+
{getDefaultBtnText(_showDefaultBtns[1], t('清空'))}
|
|
118
|
+
</Button>
|
|
119
|
+
)}
|
|
120
|
+
{_showDefaultBtns[2] !== false && (
|
|
121
|
+
<Button className="mr2" onClick={toggleModal2}>
|
|
122
|
+
{getDefaultBtnText(_showDefaultBtns[2], t('导入'))}
|
|
123
|
+
</Button>
|
|
124
|
+
)}
|
|
125
|
+
{_showDefaultBtns[3] !== false && (
|
|
126
|
+
<Button type="primary" className="mr2" onClick={toggleModal}>
|
|
127
|
+
{getDefaultBtnText(_showDefaultBtns[3], t('导出schema'))}
|
|
128
|
+
</Button>
|
|
129
|
+
)}
|
|
130
|
+
{_extraBtns.map((item, idx) => {
|
|
131
|
+
return (
|
|
132
|
+
<Button key={idx.toString()} className="mr2" {...item}>
|
|
133
|
+
{item.text || item.children}
|
|
134
|
+
</Button>
|
|
135
|
+
);
|
|
136
|
+
})}
|
|
137
|
+
</div>
|
|
138
|
+
<div className={`dnd-container ${preview ? 'preview' : 'edit'}`}>
|
|
139
|
+
<div style={{ height: preview ? 33 : 0 }}></div>
|
|
140
|
+
<FR preview={preview} displaySchema={displaySchema} />
|
|
141
|
+
</div>
|
|
142
|
+
<Modal
|
|
143
|
+
open={local.showModal}
|
|
144
|
+
onOk={copySchema}
|
|
145
|
+
onCancel={toggleModal}
|
|
146
|
+
okText={t('复制')}
|
|
147
|
+
cancelText={t('取消')}
|
|
148
|
+
>
|
|
149
|
+
<div className="mt3">
|
|
150
|
+
<TextArea
|
|
151
|
+
style={{ fontSize: 12 }}
|
|
152
|
+
value={displaySchemaString}
|
|
153
|
+
autoSize={{ minRows: 10, maxRows: 30 }}
|
|
154
|
+
/>
|
|
155
|
+
</div>
|
|
156
|
+
</Modal>
|
|
157
|
+
<Modal
|
|
158
|
+
open={local.showModal2}
|
|
159
|
+
okText={t('导入')}
|
|
160
|
+
cancelText={t('取消')}
|
|
161
|
+
onOk={importSchema}
|
|
162
|
+
onCancel={toggleModal2}
|
|
163
|
+
>
|
|
164
|
+
<div className="mt3">
|
|
165
|
+
<TextArea
|
|
166
|
+
style={{ fontSize: 12 }}
|
|
167
|
+
value={local.schemaForImport}
|
|
168
|
+
placeholder={t('贴入需要导入的schema,模样可点击导出schema参考')}
|
|
169
|
+
onChange={onTextareaChange}
|
|
170
|
+
autoSize={{ minRows: 10, maxRows: 30 }}
|
|
171
|
+
/>
|
|
172
|
+
</div>
|
|
173
|
+
</Modal>
|
|
174
|
+
</div>
|
|
175
|
+
);
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
export default Canvas;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import FormRender, { useForm } from 'form-render';
|
|
2
|
+
import React, { useEffect, useState } from 'react';
|
|
3
|
+
import { defaultGlobalSettings } from '../../settings';
|
|
4
|
+
import { useGlobal, useStore } from '../../utils/hooks';
|
|
5
|
+
|
|
6
|
+
export default function GlobalSettings({ widgets }) {
|
|
7
|
+
const form = useForm();
|
|
8
|
+
const [innerUpdate, setInnerUpdate] = useState(false);
|
|
9
|
+
const {
|
|
10
|
+
widgets: globalWidgets,
|
|
11
|
+
frProps,
|
|
12
|
+
userProps = {},
|
|
13
|
+
mapping,
|
|
14
|
+
} = useStore();
|
|
15
|
+
const setGlobal = useGlobal();
|
|
16
|
+
const globalSettings = userProps.globalSettings || defaultGlobalSettings;
|
|
17
|
+
|
|
18
|
+
const onDataChange = value => {
|
|
19
|
+
setInnerUpdate(!!Object.keys(value).length);
|
|
20
|
+
setGlobal({ frProps: value });
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
useEffect(() => {
|
|
24
|
+
if (innerUpdate) {
|
|
25
|
+
setInnerUpdate(false);
|
|
26
|
+
} else {
|
|
27
|
+
form.setValues(frProps);
|
|
28
|
+
}
|
|
29
|
+
}, [frProps]);
|
|
30
|
+
|
|
31
|
+
useEffect(() => {
|
|
32
|
+
setGlobal({ settingsForm: form });
|
|
33
|
+
}, []);
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<div style={{ paddingRight: 24 }}>
|
|
37
|
+
<FormRender
|
|
38
|
+
form={form}
|
|
39
|
+
schema={globalSettings}
|
|
40
|
+
watch={{
|
|
41
|
+
'#': v => onDataChange(v),
|
|
42
|
+
}}
|
|
43
|
+
widgets={{ ...globalWidgets, ...widgets }}
|
|
44
|
+
mapping={mapping}
|
|
45
|
+
/>
|
|
46
|
+
</div>
|
|
47
|
+
);
|
|
48
|
+
}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import FormRender, { useForm } from 'form-render';
|
|
2
|
+
import React, { useEffect, useState, useRef } from 'react';
|
|
3
|
+
import {
|
|
4
|
+
advancedElements,
|
|
5
|
+
baseCommonSettings,
|
|
6
|
+
defaultCommonSettings,
|
|
7
|
+
defaultSettings,
|
|
8
|
+
elements,
|
|
9
|
+
layouts,
|
|
10
|
+
} from '../../settings';
|
|
11
|
+
import { isObject, mergeInOrder } from '../../utils';
|
|
12
|
+
import { useGlobal, useStore } from '../../utils/hooks';
|
|
13
|
+
import { getWidgetName } from '../../utils/mapping';
|
|
14
|
+
import * as frgWidgets from '../../widgets';
|
|
15
|
+
|
|
16
|
+
export default function ItemSettings({ widgets }) {
|
|
17
|
+
const setGlobal = useGlobal();
|
|
18
|
+
const form = useForm();
|
|
19
|
+
const isReady = useRef(false);
|
|
20
|
+
const {
|
|
21
|
+
selected,
|
|
22
|
+
flatten,
|
|
23
|
+
onItemChange,
|
|
24
|
+
onItemErrorChange,
|
|
25
|
+
userProps = {},
|
|
26
|
+
widgets: globalWidgets,
|
|
27
|
+
mapping: globalMapping,
|
|
28
|
+
} = useStore();
|
|
29
|
+
|
|
30
|
+
const { settings, commonSettings, hideId, validation, transformer } =
|
|
31
|
+
userProps;
|
|
32
|
+
const [settingSchema, setSettingSchema] = useState({});
|
|
33
|
+
|
|
34
|
+
const _widgets = {
|
|
35
|
+
...globalWidgets,
|
|
36
|
+
...frgWidgets,
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const getWidgetList = (settings, commonSettings) => {
|
|
40
|
+
return settings.reduce((widgetList, setting) => {
|
|
41
|
+
if (!Array.isArray(setting.widgets)) return widgetList;
|
|
42
|
+
const basicWidgets = setting.widgets.map(item => {
|
|
43
|
+
const baseItemSettings = {};
|
|
44
|
+
if (item.schema.type === 'array' && item.schema.items) {
|
|
45
|
+
baseItemSettings.items = {
|
|
46
|
+
type: 'object',
|
|
47
|
+
hidden: '{{true}}',
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
return {
|
|
51
|
+
...item,
|
|
52
|
+
widget:
|
|
53
|
+
item.widget ||
|
|
54
|
+
item.schema.widget ||
|
|
55
|
+
getWidgetName(item.schema, globalMapping),
|
|
56
|
+
setting: mergeInOrder(
|
|
57
|
+
baseCommonSettings,
|
|
58
|
+
commonSettings,
|
|
59
|
+
baseItemSettings,
|
|
60
|
+
item.setting
|
|
61
|
+
),
|
|
62
|
+
};
|
|
63
|
+
});
|
|
64
|
+
return [...widgetList, ...basicWidgets];
|
|
65
|
+
}, []);
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const onDataChange = (value = {}) => {
|
|
69
|
+
try {
|
|
70
|
+
if (selected === '#' || !isReady.current || !value.$id) return;
|
|
71
|
+
const item = {
|
|
72
|
+
...flatten[selected],
|
|
73
|
+
schema: transformer.fromSetting(value),
|
|
74
|
+
};
|
|
75
|
+
onItemChange(selected, item, 'schema');
|
|
76
|
+
} catch (error) {
|
|
77
|
+
console.error(error, 'catch');
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
useEffect(() => {
|
|
82
|
+
// setting 该显示什么的计算,要把选中组件的 schema 和它对应的 widgets 的整体 schema 进行拼接
|
|
83
|
+
try {
|
|
84
|
+
isReady.current = false;
|
|
85
|
+
const item = flatten[selected];
|
|
86
|
+
if (!item || selected === '#') return;
|
|
87
|
+
// 算 widgetList
|
|
88
|
+
const _settings = Array.isArray(settings)
|
|
89
|
+
? [
|
|
90
|
+
...settings,
|
|
91
|
+
{ widgets: [...elements, ...advancedElements, ...layouts] },
|
|
92
|
+
] // TODO: 不是最优解
|
|
93
|
+
: defaultSettings;
|
|
94
|
+
const _commonSettings = isObject(commonSettings)
|
|
95
|
+
? commonSettings
|
|
96
|
+
: defaultCommonSettings;
|
|
97
|
+
const widgetList = getWidgetList(_settings, _commonSettings);
|
|
98
|
+
const widgetName = getWidgetName(item.schema, globalMapping);
|
|
99
|
+
const element = widgetList.find(e => e.widget === widgetName) || {}; // 有可能会没有找到
|
|
100
|
+
const properties = { ...element.setting };
|
|
101
|
+
|
|
102
|
+
if (hideId) delete properties.$id;
|
|
103
|
+
|
|
104
|
+
setTimeout(() => {
|
|
105
|
+
setSettingSchema({
|
|
106
|
+
type: 'object',
|
|
107
|
+
displayType: 'column',
|
|
108
|
+
properties,
|
|
109
|
+
});
|
|
110
|
+
const value = transformer.toSetting(item.schema);
|
|
111
|
+
form.setValues(value);
|
|
112
|
+
onDataChange(form.getValues());
|
|
113
|
+
validation && form.submit();
|
|
114
|
+
isReady.current = true;
|
|
115
|
+
}, 0);
|
|
116
|
+
} catch (error) {
|
|
117
|
+
isReady.current = true;
|
|
118
|
+
console.error(error);
|
|
119
|
+
}
|
|
120
|
+
}, [selected]);
|
|
121
|
+
|
|
122
|
+
useEffect(() => {
|
|
123
|
+
validation && onItemErrorChange(form?.errorFields);
|
|
124
|
+
}, [validation, form?.errorFields]);
|
|
125
|
+
|
|
126
|
+
useEffect(() => {
|
|
127
|
+
setGlobal({ settingsForm: form });
|
|
128
|
+
}, []);
|
|
129
|
+
|
|
130
|
+
return (
|
|
131
|
+
<div style={{ paddingRight: 24 }}>
|
|
132
|
+
<FormRender
|
|
133
|
+
form={form}
|
|
134
|
+
schema={settingSchema}
|
|
135
|
+
widgets={{ ..._widgets, ...widgets }}
|
|
136
|
+
mapping={globalMapping}
|
|
137
|
+
watch={{
|
|
138
|
+
'#': v => setTimeout(() => onDataChange(v), 0),
|
|
139
|
+
}}
|
|
140
|
+
/>
|
|
141
|
+
</div>
|
|
142
|
+
);
|
|
143
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { RightOutlined } from '@ant-design/icons';
|
|
2
|
+
import { Tabs } from 'antd';
|
|
3
|
+
import React, { useEffect } from 'react';
|
|
4
|
+
import { useTranslation } from 'react-i18next';
|
|
5
|
+
import { useSet, useStore } from '../../utils/hooks';
|
|
6
|
+
import GlobalSettings from './GlobalSettings';
|
|
7
|
+
import './index.less';
|
|
8
|
+
import ItemSettings from './ItemSettings';
|
|
9
|
+
|
|
10
|
+
const { TabPane } = Tabs;
|
|
11
|
+
|
|
12
|
+
export default function Settings({ widgets }) {
|
|
13
|
+
const { t } = useTranslation();
|
|
14
|
+
const [state, setState] = useSet({
|
|
15
|
+
tabsKey: 'globalSettings',
|
|
16
|
+
showRight: true,
|
|
17
|
+
showItemSettings: false,
|
|
18
|
+
});
|
|
19
|
+
const { selected, userProps = {} } = useStore();
|
|
20
|
+
const { tabsKey, showRight, showItemSettings } = state;
|
|
21
|
+
|
|
22
|
+
const toggleRight = () => setState({ showRight: !showRight });
|
|
23
|
+
|
|
24
|
+
const ToggleIcon = () => (
|
|
25
|
+
<div
|
|
26
|
+
className="absolute top-0 left-0 pointer"
|
|
27
|
+
style={{ height: 30, width: 30, padding: '8px 0 0 8px' }}
|
|
28
|
+
onClick={toggleRight}
|
|
29
|
+
>
|
|
30
|
+
<RightOutlined style={{color: '#666'}} />
|
|
31
|
+
</div>
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
const HideRightArrow = () => (
|
|
35
|
+
<div
|
|
36
|
+
className="absolute right-0 top-0 h2 flex-center"
|
|
37
|
+
style={{ width: 40, transform: 'rotate(180deg)' }}
|
|
38
|
+
>
|
|
39
|
+
<ToggleIcon />
|
|
40
|
+
</div>
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
// 如果没有选中任何item,或者是选中了根节点,object、list的内部,显示placeholder
|
|
44
|
+
useEffect(() => {
|
|
45
|
+
if ((selected && selected[0] === '0') || selected === '#' || !selected) {
|
|
46
|
+
setState({ tabsKey: 'globalSettings', showItemSettings: false });
|
|
47
|
+
} else {
|
|
48
|
+
setState({ tabsKey: 'itemSettings', showItemSettings: true });
|
|
49
|
+
}
|
|
50
|
+
}, [selected]);
|
|
51
|
+
|
|
52
|
+
const globalSettingHide =
|
|
53
|
+
userProps.globalSettings === null ||
|
|
54
|
+
(userProps.globalSettings && !Object.keys(userProps.globalSettings).length);
|
|
55
|
+
|
|
56
|
+
return showRight ? (
|
|
57
|
+
<div className="right-layout relative pl2">
|
|
58
|
+
<ToggleIcon />
|
|
59
|
+
<Tabs activeKey={tabsKey} onChange={key => setState({ tabsKey: key })}>
|
|
60
|
+
{showItemSettings && (
|
|
61
|
+
<TabPane tab={t("组件配置")} key="itemSettings">
|
|
62
|
+
<ItemSettings widgets={widgets} />
|
|
63
|
+
</TabPane>
|
|
64
|
+
)}
|
|
65
|
+
{!globalSettingHide && (
|
|
66
|
+
<TabPane tab={t("表单配置")} key="globalSettings">
|
|
67
|
+
<GlobalSettings widgets={widgets} />
|
|
68
|
+
</TabPane>
|
|
69
|
+
)}
|
|
70
|
+
</Tabs>
|
|
71
|
+
</div>
|
|
72
|
+
) : (
|
|
73
|
+
<HideRightArrow />
|
|
74
|
+
);
|
|
75
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
.fr-generator-container .right-layout {
|
|
2
|
+
flex-shrink: 0;
|
|
3
|
+
width: 8rem;
|
|
4
|
+
padding-top: 24px;
|
|
5
|
+
display: flex;
|
|
6
|
+
flex-direction: column;
|
|
7
|
+
height: 100%;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
// 统一左右编辑区滚动条样式
|
|
11
|
+
.fr-generator-container .right-layout ::-webkit-scrollbar {
|
|
12
|
+
width: 5px;
|
|
13
|
+
background-color: #0002;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.fr-generator-container .right-layout ::-webkit-scrollbar-thumb {
|
|
17
|
+
background: #0004;
|
|
18
|
+
border-radius: 5px;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
@media screen and (min-width: 60em) {
|
|
22
|
+
.fr-generator-container .right-layout {
|
|
23
|
+
width: 16rem;
|
|
24
|
+
}
|
|
25
|
+
}
|