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.
Files changed (62) hide show
  1. package/.fatherrc.js +37 -0
  2. package/CHANGELOG.md +254 -0
  3. package/LICENSE +21 -0
  4. package/README.md +99 -0
  5. package/dist/index.d.ts +145 -0
  6. package/dist/index.esm.js +4006 -0
  7. package/dist/index.js +4041 -0
  8. package/es/Provider.js +248 -0
  9. package/es/index.d.ts +145 -0
  10. package/es/index.js +44 -0
  11. package/es/settings/index.js +975 -0
  12. package/es/styles/atom.less +1134 -0
  13. package/es/styles/index.less +358 -0
  14. package/es/transformer/form-render.js +75 -0
  15. package/es/utils/context.js +3 -0
  16. package/es/utils/hooks.js +48 -0
  17. package/es/utils/index.js +706 -0
  18. package/es/utils/mapping.js +31 -0
  19. package/es/utils/serialize.js +276 -0
  20. package/es/widgets/htmlInput.js +20 -0
  21. package/es/widgets/idInput.js +23 -0
  22. package/es/widgets/index.js +5 -0
  23. package/es/widgets/jsonInput.js +24 -0
  24. package/es/widgets/list.js +24 -0
  25. package/es/widgets/percentSlider.js +89 -0
  26. package/package.json +53 -0
  27. package/src/Provider.jsx +239 -0
  28. package/src/components/Canvas/core/RenderChildren.jsx +18 -0
  29. package/src/components/Canvas/core/RenderField.jsx +129 -0
  30. package/src/components/Canvas/core/Wrapper.jsx +298 -0
  31. package/src/components/Canvas/core/Wrapper.less +57 -0
  32. package/src/components/Canvas/core/index.jsx +171 -0
  33. package/src/components/Canvas/index.jsx +178 -0
  34. package/src/components/Settings/GlobalSettings.jsx +48 -0
  35. package/src/components/Settings/ItemSettings.jsx +143 -0
  36. package/src/components/Settings/index.jsx +75 -0
  37. package/src/components/Settings/index.less +25 -0
  38. package/src/components/Sidebar/Element.jsx +80 -0
  39. package/src/components/Sidebar/Element.less +18 -0
  40. package/src/components/Sidebar/index.jsx +47 -0
  41. package/src/components/Sidebar/index.less +23 -0
  42. package/src/i18next/index.ts +14 -0
  43. package/src/i18next/locales/enUS.json +60 -0
  44. package/src/i18next/locales/resources.ts +7 -0
  45. package/src/i18next/locales/zhCN.json +3 -0
  46. package/src/index.d.ts +145 -0
  47. package/src/index.js +45 -0
  48. package/src/settings/index.js +1058 -0
  49. package/src/styles/atom.less +1134 -0
  50. package/src/styles/index.less +358 -0
  51. package/src/transformer/form-render.js +65 -0
  52. package/src/utils/context.js +4 -0
  53. package/src/utils/hooks.js +35 -0
  54. package/src/utils/index.js +678 -0
  55. package/src/utils/mapping.js +29 -0
  56. package/src/utils/serialize.js +368 -0
  57. package/src/widgets/htmlInput.jsx +24 -0
  58. package/src/widgets/idInput.jsx +27 -0
  59. package/src/widgets/index.js +6 -0
  60. package/src/widgets/jsonInput.jsx +29 -0
  61. package/src/widgets/list.jsx +28 -0
  62. 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
+ }