improve code

This commit is contained in:
chenos 2021-07-04 22:04:15 +08:00
parent 5e181c3ade
commit eb31861b54
20 changed files with 900 additions and 926 deletions

View File

@ -20,7 +20,7 @@ import { observable } from '@formily/reactive';
import { uid, clone } from '@formily/shared';
import { ArrayCollapse, ArrayTable, FormLayout } from '@formily/antd';
import { Space, Card, Modal } from 'antd';
import { Space, Card, Modal, Spin } from 'antd';
import { Action, useLogin, useRegister, useSubmit } from '../action';
import { AddNew } from '../add-new';
import { Cascader } from '../cascader';
@ -45,6 +45,8 @@ import { Tabs } from '../tabs';
import { TimePicker } from '../time-picker';
import { Upload } from '../upload';
import { FormItem } from '../form-item';
import { BlockItem } from '../block-item';
import { DragAndDrop } from '../drag-and-drop';
import { CodeOutlined } from '@ant-design/icons';
import Editor from '@monaco-editor/react';
@ -60,13 +62,17 @@ export const scope = {
};
export const components = {
Card,
Div,
Space,
Card,
ArrayCollapse,
ArrayTable,
FormLayout,
DragAndDrop,
BlockItem,
FormItem,
Action,
@ -171,7 +177,7 @@ export function useDesignable(path?: any) {
const currentSchema = findPropertyByPath(schema, schemaPath);
console.log('useDesignable', { schema, schemaPath, currentSchema });
return {
schema: (currentSchema || {} as any),
schema: currentSchema || ({} as Schema),
refresh,
prepend: (property: ISchema, targetPath?: any): Schema => {
let target = currentSchema;
@ -192,7 +198,7 @@ export function useDesignable(path?: any) {
current.parent.removeProperty(current.name);
properties[current.name] = current.toJSON();
});
console.log({ properties }, target.properties)
console.log({ properties }, target.properties);
target.setProperties(properties);
refresh();
return target.properties[property.name];
@ -263,7 +269,6 @@ export function useDesignable(path?: any) {
return;
}
s.parent.removeProperty(s.name);
console.log('s.parent.properties', s.name, s.parent.properties)
if (Object.keys(s.parent.properties || {}).length === 0) {
remove(s.parent);
}
@ -337,7 +342,12 @@ const CodePreview = ({ schema }) => {
return (
<>
<CodeOutlined onClick={() => setVisible(true)} />
<Modal width={'50%'} onCancel={() => setVisible(false)} visible={visible}>
<Modal
width={'50%'}
onOk={() => setVisible(false)}
onCancel={() => setVisible(false)}
visible={visible}
>
<Editor
height="60vh"
defaultLanguage="json"

View File

@ -1,73 +0,0 @@
import { uid } from '@formily/shared';
export default () => ({
type: 'void',
name: `form_${uid()}`,
'x-decorator': 'Card',
'x-component': 'Form',
properties: {
grid: {
type: 'void',
'x-component': 'Grid',
properties: {
[`gr_${uid()}`]: {
type: 'void',
'x-component': 'Grid.Row',
properties: {
[`gc_${uid()}`]: {
type: 'void',
'x-component': 'Grid.Col',
'x-component-props': {
size: 1,
},
properties: {
[`gb_${uid()}`]: {
type: 'void',
'x-component': 'Grid.Block',
properties: {
[`gbn_${uid()}`]: {
type: 'void',
'x-component': 'AddNew.FormItem',
},
},
},
},
},
},
},
},
},
actions: {
type: 'void',
// 'x-decorator': 'Div',
'x-component': 'Space',
properties: {
submit: {
type: 'void',
'x-component': 'Action',
'x-component-props': {
// block: true,
type: 'primary',
useAction: '{{ useLogin }}',
style: {
// width: '100%',
},
},
title: '提交',
},
reset: {
type: 'void',
'x-component': 'Action',
'x-component-props': {
// block: true,
useAction: '{{ useLogin }}',
style: {
// width: '100%',
},
},
title: '重置',
},
},
},
},
})

View File

@ -1,5 +1,5 @@
---
title: AddNew - 新增区块
title: AddNew - 新增
nav:
title: 组件
path: /client
@ -9,18 +9,34 @@ group:
path: /client/components
---
# AddNew - 新增区块
# AddNew - 新增
## 新增普通区块
可用于新增区块 AddNew 或表单项 AddNew.FormItem支持常规布局也支持 Grid 布局。
## 代码演示
### 基本使用
```tsx
import React from 'react';
import { ISchema } from '@formily/react';
import { uid } from '@formily/shared';
import { SchemaRenderer } from '../';
const schema = {
const schema: ISchema = {
type: 'object',
properties: {
[uid()]: {
type: 'void',
'x-component': 'DragAndDrop',
properties: {
[uid()]: {
type: 'void',
name: 'addnew',
'x-component': 'AddNew',
}
}
},
},
};
export default () => {
@ -28,16 +44,79 @@ export default () => {
}
```
## 新增表单区块
### Grid 布局的 AddNew
```tsx
import React from 'react';
import { ISchema } from '@formily/react';
import { uid } from '@formily/shared';
import { SchemaRenderer } from '../';
const schema = {
const schema: ISchema = {
type: 'void',
name: 'addnew',
'x-component': 'AddNew.FormItem',
name: uid(),
'x-component': 'Grid',
properties: {
[`row_${uid()}`]: {
type: 'void',
'x-component': 'Grid.Row',
properties: {
[`col_${uid()}`]: {
type: 'void',
'x-component': 'Grid.Col',
'x-component-props': {
width: 30,
},
properties: {
[uid()]: {
type: 'void',
'x-component': 'AddNew',
},
},
},
},
},
},
};
export default () => {
return <SchemaRenderer schema={schema} />
}
```
### Grid 布局的 AddNew.FormItem
```tsx
import React from 'react';
import { ISchema } from '@formily/react';
import { uid } from '@formily/shared';
import { SchemaRenderer } from '../';
const schema: ISchema = {
type: 'void',
name: uid(),
'x-component': 'Grid',
properties: {
[`row_${uid()}`]: {
type: 'void',
'x-component': 'Grid.Row',
properties: {
[`col_${uid()}`]: {
type: 'void',
'x-component': 'Grid.Col',
'x-component-props': {
width: 30,
},
properties: {
[uid()]: {
type: 'void',
'x-component': 'AddNew.FormItem',
},
},
},
},
},
},
};
export default () => {

View File

@ -15,6 +15,7 @@ import {
useFieldSchema,
RecursionField,
Schema,
ISchema,
} from '@formily/react';
import {
Menu,
@ -26,6 +27,8 @@ import {
Modal,
Button,
Spin,
Switch,
Checkbox,
} from 'antd';
import {
MenuOutlined,
@ -46,46 +49,84 @@ import {
import { useDesignable, useSchemaPath } from '../DesignableSchemaField';
import { uid } from '@formily/shared';
export const AddNew: any = observer((props) => {
const { schema, insertBefore } = useDesignable();
const path = useSchemaPath();
if (schema.parent['x-component'] === 'Grid.Block') {
path.pop(); // block
path.pop(); // col
path.pop(); // row
const generateGridBlock = (schema: ISchema) => {
const name = schema.name || uid();
return {
type: 'void',
name: uid(),
'x-component': 'Grid.Row',
properties: {
[uid()]: {
type: 'void',
'x-component': 'Grid.Col',
properties: {
[name]: schema,
},
},
},
};
};
const isGridBlock = (schema: Schema) => {
if (schema.parent['x-component'] !== 'Grid.Col') {
return false;
}
// Grid.Col 里有多少 Block
if (Object.keys(schema.parent.properties).length > 1) {
return false;
}
// 有多少 Grid.Row
if (Object.keys(schema.parent.parent.properties).length > 1) {
return false;
}
return true;
};
export const AddNew: any = observer((props: any) => {
const { ghost, defaultAction } = props;
const { schema, insertBefore, insertAfter } = useDesignable();
const path = useSchemaPath();
return (
<Dropdown
overlay={
<Menu>
<Menu.SubMenu title={'新建表格'}>
<Menu.ItemGroup title={'选择数据表'}>
<Menu.Item>1</Menu.Item>
</Menu.ItemGroup>
<Menu.Divider></Menu.Divider>
<Menu.Item></Menu.Item>
</Menu.SubMenu>
<Menu.SubMenu title={'新建表单'}>
<Menu.ItemGroup title={'选择数据表'}>
<Menu.Item>1</Menu.Item>
</Menu.ItemGroup>
<Menu.Divider></Menu.Divider>
<Menu.Item></Menu.Item>
</Menu.SubMenu>
<Menu.Item
onClick={() => {
insertBefore({
type: 'void',
name: uid(),
"x-component": 'Grid.Row',
properties: {
[uid()]: {
type: 'void',
"x-component": 'Grid.Col',
properties: {
[uid()]: {
type: 'void',
"x-component": 'Grid.Block',
properties: {
[uid()]: {
let data: ISchema = {
type: 'void',
title: uid(),
'x-designable-bar': 'FormItem.DesignableBar',
"x-decorator": 'FormItem',
'x-designable-bar': 'BlockItem.DesignableBar',
'x-decorator': 'BlockItem',
'x-component': 'Markdown',
};
console.log('isGridBlock(schema)', isGridBlock(schema));
if (isGridBlock(schema)) {
path.pop();
path.pop();
data = generateGridBlock(data);
}
if (data) {
if (defaultAction === 'insertAfter') {
insertAfter(data, [...path]);
} else {
insertBefore(data, [...path]);
}
}
},
},
},
},
}, [...path]);
}}
>
Markdown
@ -93,59 +134,85 @@ export const AddNew: any = observer((props) => {
</Menu>
}
>
<Button icon={<PlusOutlined />}></Button>
{ghost ? <PlusOutlined /> : <Button icon={<PlusOutlined />}></Button>}
</Dropdown>
);
});
AddNew.FormItem = observer((props) => {
const { schema, insertBefore } = useDesignable();
AddNew.FormItem = observer((props: any) => {
const { ghost, defaultAction } = props;
const { schema, insertBefore, insertAfter } = useDesignable();
const path = useSchemaPath();
if (schema.parent['x-component'] === 'Grid.Block') {
path.pop(); // block
path.pop(); // col
path.pop(); // row
}
return (
<Dropdown
overlay={
<Menu>
<Menu.ItemGroup title={'选择已有字段'}>
<Menu.Item
style={{
minWidth: 150,
}}
onClick={() => {
insertBefore({
type: 'void',
name: uid(),
"x-component": 'Grid.Row',
properties: {
[uid()]: {
type: 'void',
"x-component": 'Grid.Col',
properties: {
[uid()]: {
type: 'void',
"x-component": 'Grid.Block',
properties: {
[uid()]: {
let data: ISchema = {
type: 'void',
title: uid(),
'x-designable-bar': 'FormItem.DesignableBar',
"x-decorator": 'FormItem',
'x-decorator': 'FormItem',
'x-component': 'Markdown',
};
if (isGridBlock(schema)) {
path.pop();
path.pop();
data = generateGridBlock(data);
}
if (data) {
if (defaultAction === 'insertAfter') {
insertAfter(data, [...path]);
} else {
insertBefore(data, [...path]);
}
}
},
},
},
},
}, [...path]);
}}
>
1
<Checkbox /> 1
</Menu.Item>
<Menu.Item>
<Checkbox /> 2
</Menu.Item>
<Menu.Item>
<Checkbox /> 3
</Menu.Item>
<Menu.Item>
<Checkbox /> 4
</Menu.Item>
<Menu.Item>
<Checkbox /> 5
</Menu.Item>
<Menu.Item>
<Checkbox /> 6
</Menu.Item>
<Menu.Item>
<Checkbox /> 7
</Menu.Item>
</Menu.ItemGroup>
<Menu.Divider />
<Menu.SubMenu title={'新增字段'}>
<Menu.Item
style={{
minWidth: 150,
}}
>
</Menu.Item>
<Menu.Item></Menu.Item>
<Menu.Item></Menu.Item>
<Menu.Item></Menu.Item>
<Menu.Item></Menu.Item>
</Menu.SubMenu>
</Menu>
}
>
<Button icon={<PlusOutlined />}></Button>
{ghost ? <PlusOutlined /> : <Button icon={<PlusOutlined />}></Button>}
</Dropdown>
);
});

View File

@ -1,8 +0,0 @@
import { uid } from '@formily/shared';
export default () => ({
name: `markdown_${uid()}`,
type: 'string',
'x-component': 'Markdown',
'x-component-props': {},
});

View File

@ -1,86 +0,0 @@
import { uid } from '@formily/shared';
export default () => ({
name: `t_${uid()}`,
type: 'array',
'x-component': 'Table',
default: [{ field1: 'a', field2: 'b' }, { field1: 'a', field2: 'b' }],
'x-component-props': {},
properties: {
[`a_${uid()}`]: {
type: 'void',
'x-component': 'Table.ActionBar',
properties: {
action1: {
type: 'object',
title: '按钮1',
'x-component': 'Table.BulkAction.Popover',
'x-component-props': {},
properties: {},
},
action2: {
type: 'void',
title: '按钮2',
'x-component': 'Table.BulkAction',
'x-component-props': {},
},
},
},
[`a_${uid()}`]: {
type: 'void',
'x-component': 'Table.ActionBar',
'x-component-props': {
align: 'bottom',
},
properties: {
pagination: {
type: 'void',
'x-component': 'Table.Pagination',
'x-component-props': {
defaultCurrent: 1,
total: 50,
},
},
},
},
[`c_${uid()}`]: {
type: 'void',
title: '字段1',
'x-component': 'Table.Column',
'x-component-props': {
title: 'z1',
width: 100,
// DesignableBar: 'Table.Column.DesignableBar',
},
'x-designable-bar': 'Table.Column.DesignableBar',
properties: {
field1: {
type: 'string',
required: true,
'x-decorator-props': {
feedbackLayout: 'popover',
},
'x-decorator': 'FormItem',
'x-component': 'Input',
},
},
},
[`c_${uid()}`]: {
type: 'void',
title: '字段2',
'x-component': 'Table.Column',
properties: {
field2: {
type: 'string',
title: '字段2',
required: true,
'x-decorator-props': {
feedbackLayout: 'popover',
},
'x-decorator': 'FormItem',
'x-component': 'Input',
},
},
},
},
});

View File

@ -0,0 +1,172 @@
import React, { createContext, useContext, useState } from 'react';
import {
connect,
mapProps,
mapReadPretty,
observer,
SchemaOptionsContext,
useField,
useFieldSchema,
} from '@formily/react';
import { FormItem as FormilyFormItem } from '@formily/antd';
import { Dropdown, Menu, Space } from 'antd';
import classNames from 'classnames';
import { MenuOutlined, DragOutlined } from '@ant-design/icons';
import './style.less';
import get from 'lodash/get';
import { GridBlockContext } from '../grid';
import { uid } from '@formily/shared';
import { useDesignable, useSchemaPath } from '../DesignableSchemaField';
import { AddNew } from '../add-new';
import { Card } from 'antd';
import {
mergeRefs,
DraggableBlockContext,
useBlockDragAndDrop,
useDragDropUID,
} from '../../components/drag-and-drop';
import cls from 'classnames';
function Blank() {
return null;
}
function useDesignableBar() {
const schema = useFieldSchema();
const options = useContext(SchemaOptionsContext);
const DesignableBar = get(options.components, schema['x-designable-bar']);
return {
DesignableBar: DesignableBar || Blank,
};
}
const DraggableBlock = (props) => {
const { children, ...others } = props;
const { DesignableBar } = useDesignableBar();
const { isDragging, dragRef, previewRef, isOver, onTopHalf, dropRef } =
useBlockDragAndDrop();
const schema = useFieldSchema();
const [active, setActive] = useState(false);
return (
<DraggableBlockContext.Provider value={{ dragRef }}>
<div
onMouseEnter={(e) => {
setActive(true);
console.log('e.onMouseEnter', new Date().toString());
}}
onMouseMove={(event) => {
let dropElement = document.elementFromPoint(
event.clientX,
event.clientY,
);
const dropIds = [];
while (dropElement) {
if (!dropElement.getAttribute) {
dropElement = dropElement.parentNode as HTMLElement;
continue;
}
const dropId = dropElement.getAttribute('data-drop-id');
if (dropId) {
dropIds.push(dropId);
}
// if (dropId && dropId !== schema.name) {
// setActive(false);
// break;
// }
dropElement = dropElement.parentNode as HTMLElement;
}
if (dropIds.length > 0) {
setActive(dropIds[0] === schema.name);
}
console.log('e.onMouseMove', dropIds, schema.name);
}}
onMouseLeave={(e) => {
setActive(false);
console.log('e.onMouseLeave', new Date().toString());
}}
ref={mergeRefs([previewRef, dropRef])}
className={cls('nb-grid-block', 'designable-form-item', {
'top-half': onTopHalf,
hover: isOver,
active,
dragging: isDragging,
})}
style={{ marginBottom: 24 }}
>
{children}
<DesignableBar />
</div>
</DraggableBlockContext.Provider>
);
};
const Block = (props) => {
return (
<div className={cls('nb-grid-block', 'designable-form-item')}>
{props.children}
</div>
);
};
export const BlockItem: any = observer((props) => {
const uid = useDragDropUID();
return React.createElement(uid ? DraggableBlock : Block, props);
});
// export const BlockItem: any = connect((props) => {
// const { children, ...others } = props;
// const { DesignableBar } = useDesignableBar();
// return (
// <div {...others} style={{marginBottom: 24}} className={'designable-form-item'}>
// {children}
// <DesignableBar />
// </div>
// );
// });
BlockItem.DesignableBar = () => {
const field = useField();
const { schema, refresh } = useDesignable();
const [visible, setVisible] = useState(false);
const { dragRef } = useContext(DraggableBlockContext);
return (
<div className={classNames('designable-bar', { active: visible })}>
<span
onClick={(e) => {
e.stopPropagation();
}}
className={classNames('designable-bar-actions', { active: visible })}
>
<Space size={'small'}>
<AddNew defaultAction={'insertAfter'} ghost />
{dragRef && <DragOutlined ref={dragRef} />}
<Dropdown
trigger={['click']}
visible={visible}
onVisibleChange={(visible) => {
setVisible(visible);
}}
overlay={
<Menu>
<Menu.Item
onClick={(e) => {
const title = uid();
schema.title = title;
field.title = title;
setVisible(false);
refresh();
}}
>
</Menu.Item>
</Menu>
}
>
<MenuOutlined />
</Dropdown>
</Space>
</span>
</div>
);
};

View File

@ -0,0 +1,43 @@
.designable-form-item {
position: relative;
&.active {
> .designable-bar {
display: block;
}
}
> .designable-bar {
z-index: 1;
pointer-events: none;
display: none;
position: absolute;
top: 0;
right: -0;
left: -0;
bottom: -0;
border-radius: 2px;
border: 2px solid #1890ff;
&.active {
display: block;
}
.designable-bar-actions {
pointer-events: auto;
position: absolute;
right: 0;
line-height: 1rem;
color: #fff;
z-index: 10;
padding: 0 3px;
.ant-space {
gap: 1px !important;
}
.anticon {
width: 16px;
height: 16px;
vertical-align: top;
line-height: 16px;
font-size: 10px;
background-color: #1890ff;
}
}
}
}

View File

@ -0,0 +1,16 @@
import React from 'react';
import { observer, useFieldSchema } from '@formily/react';
import {
DragDropManagerProvider,
} from './../../components/drag-and-drop';
export const DragAndDrop = observer((props) => {
const schema = useFieldSchema();
console.log('DragAndDrop')
return (
<DragDropManagerProvider uid={schema.name}>
<div className={'nb-dnd'}>{props.children}</div>
</DragDropManagerProvider>
)
});

View File

@ -1,37 +1,31 @@
import React, { useContext, useState } from 'react';
import { connect, mapProps, mapReadPretty, SchemaOptionsContext, useField, useFieldSchema } from '@formily/react';
import {
connect,
mapProps,
mapReadPretty,
observer,
SchemaOptionsContext,
useField,
useFieldSchema,
} from '@formily/react';
import { FormItem as FormilyFormItem } from '@formily/antd';
import { Dropdown, Menu, Space } from 'antd';
import classNames from 'classnames';
import { MenuOutlined, DragOutlined } from '@ant-design/icons';
import './style.less';
// import './style.less';
import get from 'lodash/get';
import { GridBlockContext } from '../grid';
import { uid } from '@formily/shared';
import { useDesignable } from '../DesignableSchemaField';
function Blank() {
return null;
}
function useDesignableBar() {
const schema = useFieldSchema();
const options = useContext(SchemaOptionsContext);
const DesignableBar = get(options.components, schema['x-designable-bar']);
return {
DesignableBar: DesignableBar || Blank,
};
}
import { AddNew } from '../add-new';
import { BlockItem } from '../block-item';
import { DraggableBlockContext } from '../../components/drag-and-drop';
export const FormItem: any = connect((props) => {
const { DesignableBar } = useDesignableBar();
return (
<div className={'designable-form-item'}>
<BlockItem>
<FormilyFormItem {...props} />
{/* {dragRef && <div ref={dragRef}>Drag</div>} */}
<DesignableBar/>
</div>
</BlockItem>
);
});
@ -39,7 +33,7 @@ FormItem.DesignableBar = () => {
const field = useField();
const { schema, refresh } = useDesignable();
const [visible, setVisible] = useState(false);
const { dragRef } = useContext(GridBlockContext);
const { dragRef } = useContext(DraggableBlockContext);
return (
<div className={classNames('designable-bar', { active: visible })}>
<span
@ -49,7 +43,8 @@ FormItem.DesignableBar = () => {
className={classNames('designable-bar-actions', { active: visible })}
>
<Space size={'small'}>
<DragOutlined ref={dragRef} />
<AddNew.FormItem defaultAction={'insertAfter'} ghost />
{dragRef && <DragOutlined ref={dragRef} />}
<Dropdown
trigger={['click']}
visible={visible}
@ -72,7 +67,7 @@ FormItem.DesignableBar = () => {
</Menu>
}
>
<MenuOutlined/>
<MenuOutlined />
</Dropdown>
</Space>
</span>

View File

@ -1,43 +0,0 @@
import React, { useState } from 'react';
import classNames from 'classnames';
import { useField, observer, RecursionField, Schema } from '@formily/react';
import { Dropdown, Menu } from 'antd';
import {
MenuOutlined,
ArrowUpOutlined,
ArrowDownOutlined,
DeleteOutlined,
} from '@ant-design/icons';
export const DesignableBar = (props) => {
const [active, setActive] = useState(false);
return (
<>
<Menu.Item
onClick={() => {
setActive(false);
}}
icon={<ArrowUpOutlined />}
>
</Menu.Item>
<Menu.Item
onClick={() => {
setActive(false);
}}
icon={<ArrowDownOutlined />}
>
</Menu.Item>
<Menu.Divider />
<Menu.Item
onClick={() => {
setActive(false);
}}
icon={<DeleteOutlined />}
>
</Menu.Item>
</>
);
};

View File

@ -57,37 +57,27 @@ export default () => {
```tsx
import React from 'react';
import { SchemaRenderer } from '../';
import { uid } from '@formily/shared';
const schema = {
type: 'void',
name: 'item3',
name: uid(),
'x-decorator': 'Card',
'x-component': 'Form',
properties: {
grid: {
[uid()]: {
type: 'void',
title: 'aa',
'x-component': 'Grid',
properties: {
row1: {
[`row_${uid()}`]: {
type: 'void',
'x-component': 'Grid.Row',
properties: {
col1: {
[`col_${uid()}`]: {
type: 'void',
'x-component': 'Grid.Col',
'x-component-props': {
size: 1 / 2,
},
properties: {
block11: {
type: 'void',
'x-component': 'Grid.Block',
'x-component-props': {
title: 'block11',
},
properties: {
field1: {
[`block_${uid()}`]: {
type: 'string',
required: true,
title: '字段1',
@ -96,24 +86,11 @@ const schema = {
},
},
},
},
},
col2: {
[`col_${uid()}`]: {
type: 'void',
'x-component': 'Grid.Col',
'x-component-props': {
size: 1 / 2,
isLast: true,
},
properties: {
block21: {
type: 'void',
'x-component': 'Grid.Block',
'x-component-props': {
title: 'block21',
},
properties: {
field2: {
[`block_${uid()}`]: {
type: 'string',
required: true,
title: '字段2',
@ -126,100 +103,6 @@ const schema = {
},
},
},
row2: {
type: 'void',
'x-component': 'Grid.Row',
properties: {
col21: {
type: 'void',
'x-component': 'Grid.Col',
'x-component-props': {
size: 1 / 3,
},
properties: {
block211: {
type: 'void',
'x-component': 'Grid.Block',
'x-component-props': {
title: 'block211',
},
properties: {
field3: {
type: 'string',
required: true,
title: '字段3',
'x-decorator': 'FormItem',
'x-component': 'Input',
},
},
},
},
},
col22: {
type: 'void',
'x-component': 'Grid.Col',
'x-component-props': {
size: 2 / 3,
isLast: true,
},
properties: {
block221: {
type: 'void',
'x-component': 'Grid.Block',
'x-component-props': {
title: 'block221',
},
properties: {
field4: {
type: 'string',
required: true,
title: '字段4',
'x-decorator': 'FormItem',
'x-component': 'Input',
},
},
},
},
},
},
},
row3: {
type: 'void',
'x-component': 'Grid.Row',
"x-component-props": {
isLast: true,
},
properties: {
col31: {
type: 'void',
'x-component': 'Grid.Col',
'x-component-props': {
size: 1,
isLast: true,
},
properties: {
block311: {
type: 'void',
'x-component': 'Grid.Block',
'x-component-props': {
title: 'block311',
},
properties: {
field5: {
type: 'string',
required: true,
title: '字段5',
'x-decorator': 'FormItem',
'x-component': 'Input',
},
},
},
},
},
},
},
},
},
actions: {
type: 'void',
// 'x-decorator': 'Div',
@ -271,197 +154,49 @@ import { uid } from '@formily/shared';
const schema = {
type: 'void',
name: `form_${uid()}`,
name: uid(),
'x-decorator': 'Card',
'x-component': 'Form',
'x-designable-bar': 'Form.DesignableBar',
properties: {
grid: {
[uid()]: {
type: 'void',
'x-component': 'Grid',
properties: {
row1: {
[`row_${uid()}`]: {
type: 'void',
'x-component': 'Grid.Row',
properties: {
col1: {
[`col_${uid()}`]: {
type: 'void',
'x-component': 'Grid.Col',
'x-component-props': {
size: 1 / 2,
},
properties: {
block11: {
type: 'void',
'x-component': 'Grid.Block',
'x-component-props': {
title: 'block11',
},
'x-designable-bar': 'Input.DesignableBar',
properties: {
field1: {
[`block_${uid()}`]: {
type: 'string',
required: true,
title: '字段1',
'x-designable-bar': 'FormItem.DesignableBar',
'x-decorator': 'FormItem',
'x-designable-bar': 'FormItem.DesignableBar',
'x-component': 'Input',
},
},
},
},
},
col2: {
[`col_${uid()}`]: {
type: 'void',
'x-component': 'Grid.Col',
'x-component-props': {
size: 1 / 2,
isLast: true,
},
properties: {
block21: {
type: 'void',
'x-component': 'Grid.Block',
'x-component-props': {
title: 'block21',
},
'x-designable-bar': 'Input.DesignableBar',
properties: {
field2: {
[`block_${uid()}`]: {
type: 'string',
required: true,
title: '字段2',
'x-designable-bar': 'FormItem.DesignableBar',
'x-decorator': 'FormItem',
'x-component': 'Input',
},
},
},
},
},
},
},
row2: {
type: 'void',
'x-component': 'Grid.Row',
properties: {
col21: {
type: 'void',
'x-component': 'Grid.Col',
'x-component-props': {
size: 1 / 3,
},
properties: {
block211: {
type: 'void',
'x-component': 'Grid.Block',
'x-component-props': {
title: 'block211',
},
'x-designable-bar': 'Input.DesignableBar',
properties: {
field3: {
type: 'string',
required: true,
title: '字段3',
'x-designable-bar': 'FormItem.DesignableBar',
'x-decorator': 'FormItem',
'x-component': 'Input',
},
},
},
},
},
col22: {
type: 'void',
'x-component': 'Grid.Col',
'x-component-props': {
size: 2 / 3,
isLast: true,
},
properties: {
block221: {
type: 'void',
'x-component': 'Grid.Block',
'x-component-props': {
title: 'block221',
},
'x-designable-bar': 'Input.DesignableBar',
properties: {
field4: {
type: 'string',
required: true,
title: '字段4',
'x-designable-bar': 'FormItem.DesignableBar',
'x-decorator': 'FormItem',
'x-component': 'Input',
},
},
},
},
},
},
},
row3: {
type: 'void',
'x-component': 'Grid.Row',
"x-component-props": {
isLast: true,
},
properties: {
col31: {
type: 'void',
'x-component': 'Grid.Col',
'x-component-props': {
size: 1,
isLast: true,
},
properties: {
block311: {
type: 'void',
'x-component': 'Grid.Block',
'x-component-props': {
title: 'block311',
},
properties: {
field5: {
type: 'string',
required: true,
title: '字段5',
'x-designable-bar': 'FormItem.DesignableBar',
'x-decorator': 'FormItem',
'x-component': 'Input',
},
},
},
},
},
},
},
[`gr_${uid()}`]: {
type: 'void',
'x-component': 'Grid.Row',
properties: {
[`gc_${uid()}`]: {
type: 'void',
'x-component': 'Grid.Col',
'x-component-props': {
size: 1,
},
properties: {
[`gb_${uid()}`]: {
type: 'void',
'x-component': 'Grid.Block',
properties: {
[`gbn_${uid()}`]: {
type: 'void',
'x-component': 'AddNew.FormItem',
},
},
},
},
},
},
},
},
},
actions: {
@ -497,7 +232,7 @@ const schema = {
},
},
},
};
}
export default () => {
return (

View File

@ -1,29 +1,82 @@
import React, { useMemo } from 'react';
import React, { useContext, useMemo, useRef, useState } from 'react';
import { createForm } from '@formily/core';
import { FormProvider, Schema, useFieldSchema } from '@formily/react';
import { SchemaOptionsContext, Schema, useFieldSchema } from '@formily/react';
import { SchemaRenderer, useDesignable } from '../DesignableSchemaField';
import { DesignableBar } from './DesignableBar';
import get from 'lodash/get';
import { Dropdown, Menu } from 'antd';
import {
MenuOutlined,
ArrowUpOutlined,
ArrowDownOutlined,
DeleteOutlined,
} from '@ant-design/icons';
import { useMouseEvents } from 'beautiful-react-hooks';
import cls from 'classnames';
import './style.less';
function Blank() {
return null;
}
function useDesignableBar() {
const schema = useFieldSchema();
const options = useContext(SchemaOptionsContext);
const DesignableBar = get(options.components, schema['x-designable-bar']);
return {
DesignableBar: DesignableBar || Blank,
};
}
export const Form = (props) => {
const schema = useFieldSchema();
const { schema: designableSchema, refresh } = useDesignable();
return <SchemaRenderer onRefresh={(subSchema: Schema) => {
const { DesignableBar } = useDesignableBar();
const ref = useRef();
const [active, setActive] = useState(false);
const { onMouseEnter, onMouseLeave, onMouseMove } = useMouseEvents(ref);
onMouseEnter((e: React.MouseEvent) => {
setActive(true);
});
onMouseLeave((e: React.MouseEvent) => {
setActive(false);
});
onMouseMove((e: React.MouseEvent) => {
});
return (
<div ref={ref} className={'nb-form'}>
<SchemaRenderer
onRefresh={(subSchema: Schema) => {
designableSchema.properties = subSchema.properties;
refresh();
}} schema={schema.toJSON()} onlyRenderProperties/>
// return (
// <FormProvider form={form}>
// <DesignableSchemaField
// schema={{
// type: 'object',
// properties: {
// ...schema,
// },
// }}
// />
// </FormProvider>
// );
}}
schema={schema.toJSON()}
onlyRenderProperties
/>
<DesignableBar active={active}/>
</div>
);
};
Form.DesignableBar = DesignableBar;
Form.DesignableBar = (props) => {
const { active } = props;
return (
<div className={cls('designable-bar', { active })}>
<div className={'designable-bar-actions'}>
<Dropdown
overlay={
<Menu>
<Menu.Item></Menu.Item>
</Menu>
}
>
<MenuOutlined />
</Dropdown>
</div>
</div>
);
};

View File

@ -0,0 +1,45 @@
.nb-form {
position: relative;
padding: 24px;
margin: -24px;
&.active {
> .designable-bar {
display: block;
}
}
> .designable-bar {
z-index: 1;
pointer-events: none;
display: none;
position: absolute;
top: 0;
right: -0;
left: -0;
bottom: -0;
border-radius: 2px;
border: 2px solid #1890ff;
&.active {
display: block;
}
.designable-bar-actions {
pointer-events: auto;
position: absolute;
right: 0;
line-height: 1rem;
color: #fff;
z-index: 10;
padding: 0 3px;
.ant-space {
gap: 1px !important;
}
.anticon {
width: 16px;
height: 16px;
vertical-align: top;
line-height: 16px;
font-size: 10px;
background-color: #1890ff;
}
}
}
}

View File

@ -8,12 +8,7 @@ import {
RecursionField,
useField,
} from '@formily/react';
import { Input } from '../../input';
import { FormItem } from '../../form-item';
import { createDesignableSchemaField } from '../../DesignableSchemaField';
import { createForm } from '@formily/core';
import { Grid } from '../';
import { Card } from 'antd';
import { SchemaRenderer } from '../../';
const schema: ISchema = {
type: 'void',
@ -30,10 +25,6 @@ const schema: ISchema = {
'x-component-props': {
width: 30,
},
properties: {
[`block_${uid()}`]: {
type: 'void',
'x-component': 'Grid.Block',
properties: {
[uid()]: {
type: 'string',
@ -42,12 +33,6 @@ const schema: ISchema = {
'x-decorator': 'FormItem',
'x-component': 'Input',
},
},
},
[`block_${uid()}`]: {
type: 'void',
'x-component': 'Grid.Block',
properties: {
[uid()]: {
type: 'string',
title: uid(),
@ -57,18 +42,12 @@ const schema: ISchema = {
},
},
},
},
},
[`col_${uid()}`]: {
type: 'void',
'x-component': 'Grid.Col',
'x-component-props': {
width: 70,
},
properties: {
[`block_${uid()}`]: {
type: 'void',
'x-component': 'Grid.Block',
properties: {
[uid()]: {
type: 'string',
@ -81,8 +60,6 @@ const schema: ISchema = {
},
},
},
},
},
[`row_${uid()}`]: {
type: 'void',
'x-component': 'Grid.Row',
@ -90,10 +67,6 @@ const schema: ISchema = {
[`col_${uid()}`]: {
type: 'void',
'x-component': 'Grid.Col',
properties: {
[`block_${uid()}`]: {
type: 'void',
'x-component': 'Grid.Block',
properties: {
[uid()]: {
type: 'string',
@ -106,8 +79,6 @@ const schema: ISchema = {
},
},
},
},
},
[`row_${uid()}`]: {
type: 'void',
'x-component': 'Grid.Row',
@ -115,15 +86,16 @@ const schema: ISchema = {
[`col_${uid()}`]: {
type: 'void',
'x-component': 'Grid.Col',
properties: {
[`block_${uid()}`]: {
type: 'void',
'x-component': 'Grid.Block',
properties: {
[uid()]: {
type: 'void',
name: uid(),
'x-decorator': 'Card',
'x-decorator': 'FormItem',
'x-designable-bar': 'FormItem.DesignableBar',
'x-component': 'Card',
properties: {
[uid()]: {
type: 'void',
'x-component': 'Grid',
properties: {
[`row_${uid()}`]: {
@ -136,10 +108,6 @@ const schema: ISchema = {
'x-component-props': {
width: 30,
},
properties: {
[`block_${uid()}`]: {
type: 'void',
'x-component': 'Grid.Block',
properties: {
[uid()]: {
type: 'string',
@ -150,18 +118,12 @@ const schema: ISchema = {
},
},
},
},
},
[`col_${uid()}`]: {
type: 'void',
'x-component': 'Grid.Col',
'x-component-props': {
width: 70,
},
properties: {
[`block_${uid()}`]: {
type: 'void',
'x-component': 'Grid.Block',
properties: {
[uid()]: {
type: 'string',
@ -174,8 +136,6 @@ const schema: ISchema = {
},
},
},
},
},
[`row_${uid()}`]: {
type: 'void',
'x-component': 'Grid.Row',
@ -183,10 +143,6 @@ const schema: ISchema = {
[`col_${uid()}`]: {
type: 'void',
'x-component': 'Grid.Col',
properties: {
[`block_${uid()}`]: {
type: 'void',
'x-component': 'Grid.Block',
properties: {
[uid()]: {
type: 'string',
@ -199,8 +155,6 @@ const schema: ISchema = {
},
},
},
},
},
[`row_${uid()}`]: {
type: 'void',
'x-component': 'Grid.Row',
@ -208,10 +162,6 @@ const schema: ISchema = {
[`col_${uid()}`]: {
type: 'void',
'x-component': 'Grid.Col',
properties: {
[`block_${uid()}`]: {
type: 'void',
'x-component': 'Grid.Block',
properties: {
[uid()]: {
type: 'string',
@ -233,30 +183,10 @@ const schema: ISchema = {
},
},
},
},
},
};
const DesignableSchemaField = createDesignableSchemaField({
components: {
Grid,
Input,
FormItem,
Card,
},
});
const form = createForm();
export default () => {
return (
<FormProvider form={form}>
<DesignableSchemaField schema={{
type: 'object',
properties: {
[schema.name]: schema,
}
}} />
</FormProvider>
<SchemaRenderer schema={schema} />
);
};

View File

@ -22,13 +22,14 @@ import './style.less';
import cls from 'classnames';
import { useDesignable, useSchemaPath } from '../DesignableSchemaField';
import { DragDropManagerProvider, useColResizer } from './hooks';
import { useDrag, useDrop, mergeRefs } from './hooks';
export const GridContext = createContext({
ref: null,
gridName: null,
});
import {
useDrag,
useDrop,
mergeRefs,
useColResizer,
useDragDropUID,
DragDropManagerProvider,
} from './../../components/drag-and-drop';
const ColumnSizeContext = createContext(null);
@ -37,10 +38,10 @@ export const GridBlockContext = createContext({
});
const RowDivider = ({ name, onDrop }) => {
const { gridName } = useContext(GridContext);
const uid = useDragDropUID();
const { isOver, dropRef } = useDrop({
uid: `row_divider_${name}`,
accept: gridName,
accept: uid,
shallow: true,
onDrop,
});
@ -54,11 +55,11 @@ const RowDivider = ({ name, onDrop }) => {
const ColDivider = (props: any) => {
const { name, onDragEnd, resizable, onDrop } = props;
const { gridName } = useContext(GridContext);
const uid = useDragDropUID();
const { isDragging, dragRef } = useColResizer({ onDragEnd });
const { isOver, dropRef } = useDrop({
uid: `col_divider_${name}`,
accept: gridName,
accept: uid,
shallow: true,
onDrop,
});
@ -87,8 +88,7 @@ export const Grid: any = observer((props) => {
deepRemove,
} = useDesignable();
return (
<DragDropManagerProvider>
<GridContext.Provider value={{ ref, gridName: schema.name }}>
<DragDropManagerProvider uid={schema.name}>
<div ref={ref} className={'nb-grid'}>
<RowDivider
key={`${schema.name}_0`}
@ -147,7 +147,6 @@ export const Grid: any = observer((props) => {
);
})}
</div>
</GridContext.Provider>
</DragDropManagerProvider>
);
});
@ -244,52 +243,44 @@ Grid.Col = observer((props) => {
);
});
Grid.Block = observer((props) => {
const schema = useFieldSchema();
const ctx = useContext(GridContext);
const path = useSchemaPath();
const { isDragging, dragRef, previewRef } = useDrag({
type: ctx.gridName,
onDragStart() {
console.log('onDragStart');
},
onDragEnd(event) {
console.log('onDragEnd', event.data);
},
onDrag(event) {
// console.log('onDrag');
},
item: {
path,
schema: schema.toJSON(),
},
});
const { isOver, onTopHalf, dropRef } = useDrop({
uid: schema.name,
accept: ctx.gridName,
data: {},
canDrop: !isDragging,
});
useEffect(() => {
if (ctx.ref && ctx.ref.current) {
(ctx.ref.current as HTMLElement).className = isDragging
? 'nb-grid dragging'
: 'nb-grid';
}
console.log('ctx.ref.current');
}, [isDragging]);
return (
<GridBlockContext.Provider value={{ dragRef }}>
<div
ref={mergeRefs([previewRef, dropRef])}
className={cls('nb-grid-block', {
'top-half': onTopHalf,
hover: isOver,
dragging: isDragging,
})}
>
{props.children}
</div>
</GridBlockContext.Provider>
);
});
// Grid.Block = observer((props) => {
// const schema = useFieldSchema();
// const uid = useDragDropUID();
// const path = useSchemaPath();
// const { isDragging, dragRef, previewRef } = useDrag({
// type: uid,
// onDragStart() {
// console.log('onDragStart');
// },
// onDragEnd(event) {
// console.log('onDragEnd', event.data);
// },
// onDrag(event) {
// // console.log('onDrag');
// },
// item: {
// path,
// schema: schema.toJSON(),
// },
// });
// const { isOver, onTopHalf, dropRef } = useDrop({
// uid: schema.name,
// accept: uid,
// data: {},
// canDrop: !isDragging,
// });
// return (
// <GridBlockContext.Provider value={{ dragRef }}>
// <div
// ref={mergeRefs([previewRef, dropRef])}
// className={cls('nb-grid-block', {
// 'top-half': onTopHalf,
// hover: isOver,
// dragging: isDragging,
// })}
// >
// {props.children}
// </div>
// </GridBlockContext.Provider>
// );
// });

View File

@ -35,8 +35,11 @@ body.dragging {
}
}
.nb-grid.dragging {
.nb-grid {
position: relative;
}
body.dragging {
.nb-grid-col-divider {
&.hover {
background: #e6f7ff;

View File

@ -1,11 +1,16 @@
import React, { createContext, useContext, useEffect, useRef } from 'react';
import { useState } from 'react';
import { useMouseEvents, useWillUnmount } from 'beautiful-react-hooks';
import { useField } from '@formily/react';
import { useField, useFieldSchema } from '@formily/react';
import { useMount } from 'ahooks';
import constate from 'constate';
import { useSchemaPath } from '../../blocks';
export function useDragDropManager() {
export const DraggableBlockContext = createContext({
dragRef: null,
});
export function useDragDropManager({ uid }) {
const [drag, setDrag] = useState(null);
const [drop, setDrop] = useState(null);
const [drops, setDrops] = useState({});
@ -17,19 +22,18 @@ export function useDragDropManager() {
const getDrop = (dropId) => {
return drops[dropId];
};
return { drag, drop, drops, setDrag, addDrop, setDrop, getDrop };
return { uid, drag, drop, drops, setDrag, addDrop, setDrop, getDrop };
}
const [DragDropManagerProvider, useDragDropManagerContext] =
constate(useDragDropManager);
export { DragDropManagerProvider, useDragDropManagerContext };
export function useDragDropUID() {
const { uid } = useDragDropManagerContext();
return uid;
}
// export const DragDropManagerContext = createContext<any>({
// drag: null,
// drops: {},
// name: `${Math.random()}`,
// });
export { DragDropManagerProvider, useDragDropManagerContext };
export function mergeRefs<T = any>(
refs: Array<React.MutableRefObject<T> | React.LegacyRef<T>>,
@ -289,7 +293,7 @@ export function useDrop(options) {
if (!drag || drag.type !== accept) {
return;
}
console.log('drag.dropIds', drag.dropIds, dropId)
console.log('drag.dropIds', drag.dropIds, dropId);
if (drag.dropIds && drag.dropIds.includes(dropId)) {
const top = event.clientY - dropRef.current.getBoundingClientRect().top;
const onTop = top < dropRef.current.clientHeight / 2;
@ -380,7 +384,11 @@ export function useColResizer(options?: any) {
const els = parent.querySelectorAll(':scope > .nb-grid-col');
const size = [];
const gap = dragRef.current.clientWidth;
console.log('parent.clientWidth', parent.clientWidth, dragRef.current.clientWidth );
console.log(
'parent.clientWidth',
parent.clientWidth,
dragRef.current.clientWidth,
);
els.forEach((el: HTMLDivElement) => {
const w = (100 * el.clientWidth) / parent.clientWidth;
const w2 =
@ -411,3 +419,40 @@ export function useColResizer(options?: any) {
return { isDragging, dragOffset, dragRef, columns };
}
export function useBlockDragAndDrop() {
const schema = useFieldSchema();
const uid = useDragDropUID();
const path = useSchemaPath();
const { isDragging, dragRef, previewRef } = useDrag({
type: uid,
onDragStart() {
console.log('onDragStart');
},
onDragEnd(event) {
console.log('onDragEnd', event.data);
},
onDrag(event) {
// console.log('onDrag');
},
item: {
path,
schema: schema.toJSON(),
},
});
const { isOver, onTopHalf, dropRef } = useDrop({
uid: schema.name,
accept: uid,
data: {},
canDrop: !isDragging,
});
return {
isDragging,
dragRef,
previewRef,
isOver,
onTopHalf,
dropRef,
};
}

View File

@ -10,7 +10,7 @@ group:
path: /client
---
<code src="./demos/demo1.tsx"/>
<!-- <code src="./demos/demo1.tsx"/> -->