@@ -253,12 +241,21 @@ Action.DesignableBar = () => {
schema.title = '按钮文案被修改了';
field.setTitle('按钮文案被修改了');
schema.properties.drawer1.title = '抽屉标题文案被修改了';
- refresh();
setVisible(false);
}}
>
点击修改按钮文案
+
{
+ insertAfter({
+ name: uid(),
+ 'x-component': 'Input',
+ });
+ }}
+ >
+ insertAfter
+
}
>
diff --git a/packages/client/src/blocks/add-new/index.tsx b/packages/client/src/blocks/add-new/index.tsx
index d673bb569..c9d47927e 100644
--- a/packages/client/src/blocks/add-new/index.tsx
+++ b/packages/client/src/blocks/add-new/index.tsx
@@ -44,327 +44,12 @@ import {
DatabaseOutlined,
} from '@ant-design/icons';
-import { uid } from '@formily/shared';
-import {
- DesignableSchemaContext,
- RefreshDesignableSchemaContext,
- SchemaField,
-} from '../SchemaField';
-
-import { SchemaBlock } from '../';
-
-import table from './table';
-import markdown from './markdown';
-import form from './form';
-import { useRequest } from 'ahooks';
-
-const row = (schema) => {
- const component = schema['x-component'];
- return {
- type: 'void',
- name: `gr_${uid()}`,
- '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()}`]: schema,
- },
- },
- },
- },
- },
- };
-};
-
-export function removeProperty(property: Schema) {
- property.parent.removeProperty(property.name);
-}
-
-export function addPropertyBefore(target, prop) {
- Object.keys(target.parent.properties).forEach((name) => {
- if (name === target.name) {
- target.parent.addProperty(prop.name, prop);
- }
- const property = target.parent.properties[name];
- property.parent.removeProperty(property.name);
- target.parent.addProperty(property.name, property.toJSON());
- });
-}
-
-export function addPropertyAfter(target, prop) {
- Object.keys(target.parent.properties).forEach((name) => {
- const property = target.parent.properties[name];
- property.parent.removeProperty(property.name);
- target.parent.addProperty(property.name, property.toJSON());
- if (name === target.name) {
- target.parent.addProperty(prop.name, prop);
- }
- });
-}
-
-export function useSchemaQuery(segments?: any[]) {
- const context = useContext(DesignableSchemaContext);
- const refresh = useContext(RefreshDesignableSchemaContext);
- const fieldSchema = useFieldSchema();
- const field = useField();
-
- const getSchemaByPath = (path) => {
- let s: Schema = context;
- const names = [...path];
- // names.shift();
- while (s && names.length) {
- const name = names.shift();
- s = s.properties[name];
- }
- return s;
- };
-
- const schema = getSchemaByPath(segments || field.address.segments);
-
- console.log({ context, schema });
-
- return {
- refresh,
- schema,
- appendChild(data) {
- schema.addProperty(data.name, data);
- refresh();
- },
- insertAfter(data) {
- addPropertyAfter(schema, data);
- refresh();
- },
- insertBefore(data) {
- addPropertyBefore(schema, data);
- refresh();
- },
- push(data) {
- addPropertyBefore(schema, data);
- },
- remove() {
- removeProperty(schema);
- refresh();
- },
- };
-}
-
export const AddNew: any = observer((props) => {
- const field = useField();
-
- const segments = [...field.address.segments];
- segments.pop();
- segments.pop();
- segments.pop();
- // segments.pop();
-
- const { insertBefore, refresh, schema } = useSchemaQuery(segments);
- console.log('field.address.segments', segments, schema);
-
- const {
- data = [],
- loading,
- mutate,
- } = useRequest(() => {
- return Promise.resolve([
- { title: '数据表1' },
- { title: '数据表2' },
- { title: '数据表3' },
- ]);
- });
-
- const [visible, setVisible] = useState(false);
-
- const dbschema = {
- type: 'object',
- properties: {
- layout: {
- type: 'void',
- 'x-component': 'FormLayout',
- 'x-component-props': {
- layout: 'vertical',
- },
- properties: {
- input: {
- type: 'string',
- title: '数据表名称',
- required: true,
- 'x-decorator': 'FormItem',
- 'x-component': 'Input',
- },
- array: {
- type: 'array',
- title: '数据表字段',
- 'x-component': 'ArrayCollapse',
- 'x-component-props': {
- accordion: true,
- },
- // maxItems: 3,
- 'x-decorator': 'FormItem',
- items: {
- type: 'object',
- 'x-component': 'ArrayCollapse.CollapsePanel',
- 'x-component-props': {
- header: '字段',
- },
- properties: {
- index: {
- type: 'void',
- 'x-component': 'ArrayCollapse.Index',
- },
- input: {
- type: 'string',
- 'x-decorator': 'FormItem',
- title: 'Input',
- required: true,
- 'x-component': 'Input',
- },
- remove: {
- type: 'void',
- 'x-component': 'ArrayCollapse.Remove',
- },
- moveUp: {
- type: 'void',
- 'x-component': 'ArrayCollapse.MoveUp',
- },
- moveDown: {
- type: 'void',
- 'x-component': 'ArrayCollapse.MoveDown',
- },
- },
- },
- properties: {
- addition: {
- type: 'void',
- title: '添加字段',
- 'x-component': 'ArrayCollapse.Addition',
- },
- },
- },
- },
- },
- },
- };
-
- if (loading) {
- return
;
- }
-
- console.log({ data });
-
return (
-
-
- {data.map((item, index) => {
- return (
- {
- const rowData = row(form());
- insertBefore(rowData);
- }}
- >
- {item.title}
-
- );
- })}
-
-
- {
- FormDialog('新建数据表', () => {
- return ;
- })
- .open({
- initialValues: {
- // aaa: '123',
- },
- })
- .then(() => {
- const items = [...data];
- items.push({ title: '数据表5' });
- mutate(items);
- const rowData = row(form());
- insertBefore(rowData);
- });
- // const rowData = row(table());
- // insertBefore(rowData);
- }}
- >
- 新增数据表
-
-
-
-
- {data.map((item, index) => {
- return (
- {
- const rowData = row(table());
- console.log({ rowData });
- insertBefore(rowData);
- }}
- >
- {item.title}
-
- );
- })}
-
-
- {
- FormDialog('新建数据表', () => {
- return ;
- })
- .open({
- initialValues: {
- // aaa: '123',
- },
- })
- .then(() => {
- const items = [...data];
- items.push({ title: '数据表4' });
- mutate(items);
- const rowData = row(table());
- insertBefore(rowData);
- });
- }}
- >
- 新建数据表
-
-
- {
- const rowData = row(markdown());
- insertBefore(rowData);
- }}
- >
- 新建 Markdown
-
+ 新建 Markdown
}
>
@@ -374,47 +59,11 @@ export const AddNew: any = observer((props) => {
});
AddNew.FormItem = observer((props) => {
- const field = useField();
-
- const segments = [...field.address.segments];
- segments.pop();
- segments.pop();
- segments.pop();
-
- const { insertBefore, refresh, schema } = useSchemaQuery(segments);
-
- const insertBeforeHandle = () => {
- const rowData = row({
- type: 'string',
- // required: true,
- name: `f_${uid()}`,
- title: `字段${uid()}`,
- 'x-decorator': 'FormItem',
- 'x-designable-bar': 'FormItem.DesignableBar',
- 'x-component': 'Input',
- });
- console.log({ rowData });
- insertBefore(rowData);
- };
return (
-
- 字段1
-
- 字段2
- 字段3
-
-
-
- 单行文本
-
- 多行文本
- 电子邮箱
- 手机号
- 数字
-
+ 字段 1
}
>
diff --git a/packages/client/src/blocks/cascader/index.md b/packages/client/src/blocks/cascader/index.md
index b563b2f52..107baa382 100644
--- a/packages/client/src/blocks/cascader/index.md
+++ b/packages/client/src/blocks/cascader/index.md
@@ -18,7 +18,7 @@ group:
* title: 省市区级联
*/
import React from 'react';
-import { SchemaBlock } from '../';
+import { SchemaRenderer } from '../';
const options = [
{
@@ -94,7 +94,7 @@ const schema = {
export default () => {
return (
-
+
);
};
```
diff --git a/packages/client/src/blocks/checkbox/index.md b/packages/client/src/blocks/checkbox/index.md
index 72d8c604d..bd9360af1 100644
--- a/packages/client/src/blocks/checkbox/index.md
+++ b/packages/client/src/blocks/checkbox/index.md
@@ -20,7 +20,7 @@ group:
* title: 勾选
*/
import React from 'react';
-import { SchemaBlock } from '../';
+import { SchemaRenderer } from '../';
const schema = {
type: 'object',
@@ -53,7 +53,7 @@ const schema = {
export default () => {
return (
-
+
);
};
```
@@ -65,7 +65,7 @@ export default () => {
* title: 组
*/
import React from 'react';
-import { SchemaBlock } from '../';
+import { SchemaRenderer } from '../';
const options = [
{
@@ -111,7 +111,7 @@ const schema = {
export default () => {
return (
-
+
);
};
```
diff --git a/packages/client/src/blocks/color-select/index.md b/packages/client/src/blocks/color-select/index.md
index ea7896de1..0724433ed 100644
--- a/packages/client/src/blocks/color-select/index.md
+++ b/packages/client/src/blocks/color-select/index.md
@@ -16,7 +16,7 @@ group:
* title: 颜色选择器
*/
import React from 'react';
-import { SchemaBlock } from '../';
+import { SchemaRenderer } from '../';
const schema = {
type: 'object',
@@ -59,7 +59,7 @@ const schema = {
export default () => {
return (
-
+
);
};
```
diff --git a/packages/client/src/blocks/database-field/index.md b/packages/client/src/blocks/database-field/index.md
index fb41f9343..1f13270c1 100644
--- a/packages/client/src/blocks/database-field/index.md
+++ b/packages/client/src/blocks/database-field/index.md
@@ -13,7 +13,7 @@ group:
```tsx
import React from 'react';
-import { SchemaBlock } from '../';
+import { SchemaRenderer } from '../';
const schema = {
type: 'object',
@@ -71,6 +71,6 @@ const schema = {
};
export default () => {
- return
+ return
}
```
diff --git a/packages/client/src/blocks/date-picker/index.md b/packages/client/src/blocks/date-picker/index.md
index e972c6516..ed4624bf8 100644
--- a/packages/client/src/blocks/date-picker/index.md
+++ b/packages/client/src/blocks/date-picker/index.md
@@ -20,7 +20,7 @@ group:
* title: 日期选择器
*/
import React from 'react';
-import { SchemaBlock } from '../';
+import { SchemaRenderer } from '../';
const schema = {
type: 'object',
@@ -53,7 +53,7 @@ const schema = {
export default () => {
return (
-
+
);
};
```
@@ -65,7 +65,7 @@ export default () => {
* title: 日期时间选择
*/
import React from 'react';
-import { SchemaBlock } from '../';
+import { SchemaRenderer } from '../';
const schema = {
type: 'object',
@@ -104,7 +104,7 @@ const schema = {
export default () => {
return (
-
+
);
};
```
diff --git a/packages/client/src/blocks/form-item/index.tsx b/packages/client/src/blocks/form-item/index.tsx
index 1d1de632e..f5c9e9261 100644
--- a/packages/client/src/blocks/form-item/index.tsx
+++ b/packages/client/src/blocks/form-item/index.tsx
@@ -1,12 +1,13 @@
import React, { useContext, useState } from 'react';
import { connect, mapProps, mapReadPretty, SchemaOptionsContext, useField, useFieldSchema } from '@formily/react';
import { FormItem as FormilyFormItem } from '@formily/antd';
-import { Dropdown, Menu } from 'antd';
+import { Dropdown, Menu, Space } from 'antd';
import classNames from 'classnames';
-import { MenuOutlined } from '@ant-design/icons';
+import { MenuOutlined, DragOutlined } from '@ant-design/icons';
import './style.less';
import get from 'lodash/get';
-import { BlockContext } from '../SchemaField';
+import { GridBlockContext } from '../grid';
+import { uid } from '@formily/shared';
function Blank() {
return null;
@@ -24,11 +25,10 @@ function useDesignableBar() {
export const FormItem: any = connect((props) => {
const { DesignableBar } = useDesignableBar();
- const { dragRef } = useContext(BlockContext);
return (
- {dragRef &&
Drag
}
+ {/* {dragRef &&
Drag
} */}
);
@@ -37,6 +37,7 @@ export const FormItem: any = connect((props) => {
FormItem.DesignableBar = () => {
const field = useField();
const [visible, setVisible] = useState(false);
+ const { dragRef } = useContext(GridBlockContext);
return (
{
}}
className={classNames('designable-bar-actions', { active: visible })}
>
+
+
{
}
>
-
+
+
);
diff --git a/packages/client/src/blocks/form-item/style.less b/packages/client/src/blocks/form-item/style.less
index 4336df692..6f4b86ea9 100644
--- a/packages/client/src/blocks/form-item/style.less
+++ b/packages/client/src/blocks/form-item/style.less
@@ -24,12 +24,19 @@
position: absolute;
right: 0;
line-height: 1rem;
- background-color: #1890ff;
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;
}
}
}
diff --git a/packages/client/src/blocks/form/DesignableBar.tsx b/packages/client/src/blocks/form/DesignableBar.tsx
index 27d9dce83..9cf72c589 100644
--- a/packages/client/src/blocks/form/DesignableBar.tsx
+++ b/packages/client/src/blocks/form/DesignableBar.tsx
@@ -2,7 +2,6 @@ import React, { useState } from 'react';
import classNames from 'classnames';
import { useField, observer, RecursionField, Schema } from '@formily/react';
import { Dropdown, Menu } from 'antd';
-import { useSchemaQuery } from '../grid';
import {
MenuOutlined,
ArrowUpOutlined,
@@ -11,18 +10,11 @@ import {
} from '@ant-design/icons';
export const DesignableBar = (props) => {
- const { addBlock, removeBlock } = useSchemaQuery();
const [active, setActive] = useState(false);
return (
<>
{
- addBlock(
- {},
- {
- insertBefore: true,
- },
- );
setActive(false);
}}
icon={}
@@ -31,7 +23,6 @@ export const DesignableBar = (props) => {
{
- addBlock();
setActive(false);
}}
icon={}
@@ -41,7 +32,6 @@ export const DesignableBar = (props) => {
{
- removeBlock();
setActive(false);
}}
icon={}
diff --git a/packages/client/src/blocks/grid/Block.tsx b/packages/client/src/blocks/grid/Block.tsx
deleted file mode 100644
index bd5f8a2fc..000000000
--- a/packages/client/src/blocks/grid/Block.tsx
+++ /dev/null
@@ -1,141 +0,0 @@
-import React, { useState, useContext, createContext } from 'react';
-import { useDrag, mergeRefs, useDrop } from './DND';
-import classNames from 'classnames';
-import {
- useField,
- observer,
- RecursionField,
- Schema,
- SchemaOptionsContext,
-} from '@formily/react';
-import { Dropdown, Menu } from 'antd';
-import { useSchemaQuery } from '.';
-import {
- MenuOutlined,
- ArrowUpOutlined,
- ArrowDownOutlined,
- DeleteOutlined,
-} from '@ant-design/icons';
-import get from 'lodash/get';
-import { BlockContext } from '../SchemaField';
-
-export const Block = observer((props: any) => {
- const { children, title, DesignableBar } = props;
- const field = useField();
- const { isDragging, dragRef, previewRef } = useDrag({
- type: 'grid',
- onDragStart() {
- // console.log('onDragStart');
- },
- onDragEnd(event) {
- console.log('onDragEnd', event);
- },
- onDrag(event) {
- // console.log('onDrag');
- },
- item: {
- path: field.address.segments,
- },
- });
- console.log('ActionBar', DesignableBar);
- const { isOver, onTopHalf, dropRef } = useDrop({
- accept: 'grid',
- data: {},
- canDrop: !isDragging,
- });
-
- const options = useContext(SchemaOptionsContext);
-
- const DesignableBarComponent = get(options.components, DesignableBar);
-
- console.log({ DesignableBarComponent, DesignableBar });
-
- const { addBlock } = useSchemaQuery();
-
- console.log({ children });
- return (
-
-
- {children}
-
- {/* {DesignableBarComponent && (
-
- )} */}
-
- );
-});
-
-// function overlay() {
-// return
-// }
-
-function Overlay(props) {
- return (
- <>
- 在上方插入区块1
- 在下方插入区块2
-
- 删除区块
- >
- );
-}
-
-const DefaultActionBar = ({ DesignableBarComponent, dragRef }) => {
- const { addBlock, removeBlock } = useSchemaQuery();
- const [active, setActive] = useState(false);
- // return ;
- return (
-
- {DesignableBarComponent ? : }}
- >
-
-
-
- );
-};
-
-export default Block;
diff --git a/packages/client/src/blocks/grid/Col.tsx b/packages/client/src/blocks/grid/Col.tsx
deleted file mode 100644
index 0539b4100..000000000
--- a/packages/client/src/blocks/grid/Col.tsx
+++ /dev/null
@@ -1,100 +0,0 @@
-import React, { cloneElement, useRef, useState } from 'react';
-import { useMouseEvents } from 'beautiful-react-hooks';
-import { mergeRefs, useDrop } from './DND';
-import classNames from 'classnames';
-
-export function useColResizer(options?: any) {
- const { onDragStart, onDrag, onDragEnd } = options || {};
- const dragRef = useRef();
- const [dragOffset, setDragOffset] = useState({ left: 0, top: 0 });
- const { onMouseDown } = useMouseEvents(dragRef);
- const { onMouseMove, onMouseUp } = useMouseEvents();
- const [isDragging, setIsDragging] = useState(false);
- const [columns, setColumns] = useState(options.columns || []);
- const [initial, setInitial] = useState(null);
-
- onMouseDown((event: React.MouseEvent) => {
- if (event.button !== 0) {
- return;
- }
- const prev = dragRef.current.previousElementSibling as HTMLDivElement;
- const next = dragRef.current.nextElementSibling as HTMLDivElement;
- if (!prev || !next) {
- return;
- }
- setIsDragging(true);
- if (!initial) {
- setInitial({
- offset: event.clientX,
- prevWidth: prev.style.width,
- nextWidth: next.style.width,
- });
- }
- });
-
- onMouseUp((event: React.MouseEvent) => {
- if (!isDragging) {
- return;
- }
- const parent = dragRef.current.parentElement;
- const els = parent.querySelectorAll('.col');
- const size = [];
- els.forEach((el: HTMLDivElement) => {
- const w = el.clientWidth / parent.clientWidth;
- size.push(w);
- el.style.width = `${100 * w}%`;
- });
- console.log(size);
- setIsDragging(false);
- setInitial(null);
- // @ts-ignore
- event.data = { size };
- onDragEnd && onDragEnd(event);
- });
-
- onMouseMove((event: React.MouseEvent) => {
- if (!isDragging) {
- return;
- }
- const offset = event.clientX - initial.offset;
- // dragRef.current.style.transform = `translateX(${event.clientX - initialOffset}px)`;
- const prev = dragRef.current.previousElementSibling as HTMLDivElement;
- const next = dragRef.current.nextElementSibling as HTMLDivElement;
- prev.style.width = `calc(${initial.prevWidth} + ${offset}px)`;
- next.style.width = `calc(${initial.nextWidth} - ${offset}px)`;
- // console.log('dragRef.current.nextSibling', prev.style.width);
- });
-
- return { isDragging, dragOffset, dragRef, columns };
-}
-
-export const Col: any = (props) => {
- const { size, children, position = {}, isLast } = props;
- return (
- <>
-
- {children}
-
-
- >
- );
-};
-
-Col.Divider = (props) => {
- const { onDragEnd, resizable = true } = props;
- const { isDragging, dragRef } = useColResizer({ onDragEnd });
- const { isOver, dropRef } = useDrop({
- accept: 'grid',
- data: { },
- });
- return (
-
- );
-};
-
-export default Col;
diff --git a/packages/client/src/blocks/grid/Grid.tsx b/packages/client/src/blocks/grid/Grid.tsx
deleted file mode 100644
index 10641b248..000000000
--- a/packages/client/src/blocks/grid/Grid.tsx
+++ /dev/null
@@ -1,88 +0,0 @@
-import React, { cloneElement, useRef } from 'react';
-import { Col } from './Col';
-import { Row } from './Row';
-import { Block } from './Block';
-import { DragDropProvider } from './DND';
-import { useSchemaQuery } from './';
-
-type Event = React.MouseEvent & {
- dropElement: HTMLElement;
- onTopHalf?: boolean;
- dragItem?: any;
-};
-
-export const Grid = (props) => {
- const { children, onDrop } = props;
- const ref = useRef();
- const { schema, moveTo, refresh } = useSchemaQuery();
- return (
-
- {
- const el = event.dropElement;
- const type = el.getAttribute('data-type');
- const getIndex = (el) => {
- const type = el.getAttribute('data-type');
- return Array.prototype.indexOf.call(
- el.parentNode.querySelectorAll(`.${type}`),
- el,
- );
- };
- let position: any = { type };
- if (type === 'row') {
- // position.rowIndex = getIndex(el);
- position = {
- type: 'row-divider',
- rowDividerIndex: getIndex(
- event.onTopHalf ? el.previousSibling : el.nextSibling,
- ),
- };
- }
- if (type === 'row-divider') {
- position.rowDividerIndex = getIndex(el);
- }
- if (type === 'col-divider') {
- position.colDividerIndex = getIndex(el);
- position.rowIndex = getIndex(el.parentNode);
- }
- if (type === 'block') {
- const rowNode = el.parentNode.parentNode;
- position.blockIndex = getIndex(el);
- position.colIndex = getIndex(el.parentNode);
- position.rowIndex = getIndex(rowNode);
- const colsize = rowNode.querySelectorAll('.col').length;
- if (colsize === 1) {
- position = {
- type: 'row-divider',
- rowDividerIndex: getIndex(
- event.onTopHalf
- ? rowNode.previousSibling
- : rowNode.nextSibling,
- ),
- };
- } else {
- position.type = 'block-divider';
- position.blockDividerIndex = getIndex(el);
- if (!event.onTopHalf) {
- position.blockDividerIndex += 1
- }
- }
- }
- onDrop && onDrop(event);
- moveTo(event.dragItem.path, position);
- console.log('onDrop', position, event.dragItem);
- }}
- >
-
- {children}
-
-
- );
-};
-
-Grid.Row = Row;
-Grid.Col = Col;
-Grid.Block = Block;
-
-export default Grid;
diff --git a/packages/client/src/blocks/grid/Row.tsx b/packages/client/src/blocks/grid/Row.tsx
deleted file mode 100644
index d9a06fce5..000000000
--- a/packages/client/src/blocks/grid/Row.tsx
+++ /dev/null
@@ -1,46 +0,0 @@
-import React, { cloneElement, useRef } from 'react';
-import classNames from 'classnames';
-import { Col } from './Col';
-import { DragDropProvider, useDrop } from './DND';
-import { useField } from '@formily/react';
-
-export const Row = (props) => {
- const { children, onColResize, position = {}, isLast } = props;
- const { isOver, onTopHalf, dropRef } = useDrop({
- accept: 'grid',
- data: {},
- shallow: true,
- });
- return (
- <>
-
-
- {children}
-
-
- >
- );
-};
-
-Row.Divider = (props) => {
- const { style = {}, position } = props;
- const { isOver, dropRef } = useDrop({
- accept: 'grid',
- data: { position },
- });
- return (
-
- );
-};
-
-export default Row;
diff --git a/packages/client/src/blocks/grid/demos/demo1.tsx b/packages/client/src/blocks/grid/demos/demo1.tsx
new file mode 100644
index 000000000..d2a2817e6
--- /dev/null
+++ b/packages/client/src/blocks/grid/demos/demo1.tsx
@@ -0,0 +1,120 @@
+import React from 'react';
+import { uid } from '@formily/shared';
+import {
+ observer,
+ ISchema,
+ FormProvider,
+ useFieldSchema,
+ 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 '../';
+
+const schema: ISchema = {
+ type: 'object',
+ properties: {
+ [uid()]: {
+ type: 'void',
+ '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: {
+ [`block_${uid()}`]: {
+ type: 'void',
+ 'x-component': 'Grid.Block',
+ properties: {
+ [uid()]: {
+ type: 'string',
+ title: uid(),
+ 'x-designable-bar': 'FormItem.DesignableBar',
+ 'x-decorator': 'FormItem',
+ 'x-component': 'Input',
+ },
+ },
+ },
+ },
+ },
+ [`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',
+ title: uid(),
+ 'x-designable-bar': 'FormItem.DesignableBar',
+ 'x-decorator': 'FormItem',
+ 'x-component': 'Input',
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ [`row_${uid()}`]: {
+ type: 'void',
+ 'x-component': 'Grid.Row',
+ properties: {
+ [`col_${uid()}`]: {
+ type: 'void',
+ 'x-component': 'Grid.Col',
+ properties: {
+ [`block_${uid()}`]: {
+ type: 'void',
+ 'x-component': 'Grid.Block',
+ properties: {
+ [uid()]: {
+ type: 'string',
+ title: uid(),
+ 'x-designable-bar': 'FormItem.DesignableBar',
+ 'x-decorator': 'FormItem',
+ 'x-component': 'Input',
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+};
+
+const DesignableSchemaField = createDesignableSchemaField({
+ components: {
+ Grid,
+ Input,
+ FormItem,
+ },
+});
+
+const form = createForm();
+
+export default () => {
+ return (
+
+
+
+ );
+};
diff --git a/packages/client/src/blocks/grid/demos/demo4.tsx b/packages/client/src/blocks/grid/demos/demo4.tsx
deleted file mode 100644
index 267cbdeec..000000000
--- a/packages/client/src/blocks/grid/demos/demo4.tsx
+++ /dev/null
@@ -1,91 +0,0 @@
-import React, { createContext, useContext, useEffect, useRef } from 'react';
-import { useDrag, useDrop, DragDropProvider, mergeRefs } from '../';
-import { Button, Space } from 'antd';
-
-function DropZone({ options, children }) {
- const { isOver, dropRef } = useDrop(options);
- return (
-
- {children}
-
- );
-}
-
-function Dragable() {
- const { isDragging, dragRef, previewRef } = useDrag({
- type: 'box',
- onDragStart() {
- console.log('onDragStart');
- },
- onDragEnd(event) {
- console.log('onDragEnd', event.data);
- },
- onDrag(event) {
- // console.log('onDrag');
- },
- });
- return ;
-}
-
-function Dragable2() {
- const { isDragging, dragRef, previewRef } = useDrag({
- type: 'box2',
- onDragStart() {
- console.log('onDragStart');
- },
- onDragEnd(event) {
- console.log('onDragEnd', event.data);
- },
- onDrag(event) {
- // console.log('onDrag');
- },
- });
- return ;
-}
-
-export default () => {
- return (
-
-
-
-
-
-
- Drop Zone1
-
- Drop Zone2
-
-
- Drop Zone3
-
- Drop Zone1
-
-
- );
-};
diff --git a/packages/client/src/blocks/grid/demos/demo5.tsx b/packages/client/src/blocks/grid/demos/demo5.tsx
deleted file mode 100644
index 2f7f2b385..000000000
--- a/packages/client/src/blocks/grid/demos/demo5.tsx
+++ /dev/null
@@ -1,18 +0,0 @@
-import React from 'react';
-import { Row, Col } from '../';
-
-export default () => {
- return (
-
-
{
- console.log(e.data);
- }}>
- {[1, 2, 3].map((index) => (
-
- col {index}
-
- ))}
-
-
- );
-};
diff --git a/packages/client/src/blocks/grid/demos/demo6.tsx b/packages/client/src/blocks/grid/demos/demo6.tsx
deleted file mode 100644
index a704f54c7..000000000
--- a/packages/client/src/blocks/grid/demos/demo6.tsx
+++ /dev/null
@@ -1,167 +0,0 @@
-import React from 'react';
-import { SchemaBlock } from '../../';
-import { ISchema } from '@formily/json-schema';
-
-const schema: ISchema = {
- type: 'object',
- properties: {
- grid: {
- type: 'void',
- title: 'aa',
- 'x-component': 'Grid',
- properties: {
- row1: {
- type: 'void',
- 'x-component': 'Grid.Row',
- properties: {
- col1: {
- type: 'void',
- 'x-component': 'Grid.Col',
- 'x-component-props': {
- size: 1 / 2,
- },
- properties: {
- block11: {
- type: 'void',
- 'x-component': 'Grid.Block',
- 'x-content': (
-
- block11
-
- ),
- },
- },
- },
- col2: {
- 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-content': (
-
- block21
-
- ),
- },
- },
- },
- },
- },
- 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-content': (
-
- block211
-
- ),
- },
- },
- },
- col22: {
- type: 'void',
- 'x-component': 'Grid.Col',
- 'x-component-props': {
- size: 2 / 3,
- isLast: true,
- },
- properties: {
- block221: {
- type: 'void',
- 'x-component': 'Grid.Block',
- 'x-content': (
-
- block221
-
- ),
- },
- },
- },
- },
- },
- 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-content': (
-
- block311
-
- ),
- },
- },
- },
- },
- },
- },
- },
- },
-};
-
-export default () => {
- return ;
-};
diff --git a/packages/client/src/blocks/grid/DND.tsx b/packages/client/src/blocks/grid/hooks.tsx
similarity index 80%
rename from packages/client/src/blocks/grid/DND.tsx
rename to packages/client/src/blocks/grid/hooks.tsx
index 50dd164f9..f5ebbacea 100644
--- a/packages/client/src/blocks/grid/DND.tsx
+++ b/packages/client/src/blocks/grid/hooks.tsx
@@ -99,6 +99,7 @@ export function useDrag(options?: any) {
};
const wrap = document.createElement('div');
+ wrap.className = 'drag-container';
wrap.style.position = 'absolute';
wrap.style.pointerEvents = 'none';
wrap.style.opacity = '0.7';
@@ -325,3 +326,70 @@ export function useDrop(options) {
dropRef,
};
}
+
+export function useColResizer(options?: any) {
+ const { onDragStart, onDrag, onDragEnd } = options || {};
+ const dragRef = useRef();
+ const [dragOffset, setDragOffset] = useState({ left: 0, top: 0 });
+ const { onMouseDown } = useMouseEvents(dragRef);
+ const { onMouseMove, onMouseUp } = useMouseEvents();
+ const [isDragging, setIsDragging] = useState(false);
+ const [columns, setColumns] = useState(options.columns || []);
+ const [initial, setInitial] = useState(null);
+
+ onMouseDown((event: React.MouseEvent) => {
+ if (event.button !== 0) {
+ return;
+ }
+ const prev = dragRef.current.previousElementSibling as HTMLDivElement;
+ const next = dragRef.current.nextElementSibling as HTMLDivElement;
+ if (!prev || !next) {
+ return;
+ }
+ setIsDragging(true);
+ if (!initial) {
+ setInitial({
+ offset: event.clientX,
+ prevWidth: prev.style.width,
+ nextWidth: next.style.width,
+ });
+ }
+ });
+
+ onMouseUp((event: React.MouseEvent) => {
+ if (!isDragging) {
+ return;
+ }
+ const parent = dragRef.current.parentElement;
+ const els = parent.querySelectorAll(':scope > .nb-grid-col');
+ const size = [];
+ els.forEach((el: HTMLDivElement) => {
+ const w = (100 * el.clientWidth) / parent.clientWidth;
+ const w2 =
+ (100 * (el.clientWidth + 24 + 24 / els.length)) / parent.clientWidth;
+ size.push(w2);
+ el.style.width = `${w}%`;
+ });
+ console.log({ size });
+ setIsDragging(false);
+ setInitial(null);
+ // @ts-ignore
+ event.data = { size };
+ onDragEnd && onDragEnd(event);
+ });
+
+ onMouseMove((event: React.MouseEvent) => {
+ if (!isDragging) {
+ return;
+ }
+ const offset = event.clientX - initial.offset;
+ // dragRef.current.style.transform = `translateX(${event.clientX - initialOffset}px)`;
+ const prev = dragRef.current.previousElementSibling as HTMLDivElement;
+ const next = dragRef.current.nextElementSibling as HTMLDivElement;
+ prev.style.width = `calc(${initial.prevWidth} + ${offset}px)`;
+ next.style.width = `calc(${initial.nextWidth} - ${offset}px)`;
+ // console.log('dragRef.current.nextSibling', prev.style.width);
+ });
+
+ return { isDragging, dragOffset, dragRef, columns };
+}
diff --git a/packages/client/src/blocks/grid/index.md b/packages/client/src/blocks/grid/index.md
index 6df26a8c1..007a4d9e3 100644
--- a/packages/client/src/blocks/grid/index.md
+++ b/packages/client/src/blocks/grid/index.md
@@ -73,62 +73,4 @@ group:
- 100%
-## 代码演示
-
-### useDrag & useDrop
-
-
-
-### useColResize
-
-
-
-### Grid
-
-
-
-## API 说明
-
-### Grid
-
-只能在同一个 Grid 里拖拽布局
-
-### Grid.Row
-
-行
-
-### Grid.Column
-
-列
-
-### Grid.Block
-
-区块
-
-### BlockOptions
-
-```ts
-interface BlockOptions {
- rowOrder: number;
- columnOrder: number;
- blockOrder: number;
-}
-```
-
-- rowOrder:第几行
-- columnOrder:第几列
-- blockOrder:某单元格内部区块排序
-
-### blocks2properties
-
-原始 schema 需要至少 grid->row->col->block->custom 五层嵌套,写起来非常繁琐,`blocks2properties` 方法可以简化配置。
-
-### useDrag & useDrop
-
-拖拽 hooks
-
-原生态的
-
-### useDrop
-
-### useColResize
+
\ No newline at end of file
diff --git a/packages/client/src/blocks/grid/index.tsx b/packages/client/src/blocks/grid/index.tsx
index 3fec8a012..11f88a2be 100644
--- a/packages/client/src/blocks/grid/index.tsx
+++ b/packages/client/src/blocks/grid/index.tsx
@@ -1,252 +1,237 @@
-import React, { useContext, createContext, useState } from 'react';
+import React, {
+ FC,
+ CSSProperties,
+ useRef,
+ createContext,
+ useContext,
+ useEffect,
+} from 'react';
+// import { DndProvider, useDrag, useDragDropManager } from 'react-dnd';
+// import { HTML5Backend } from 'react-dnd-html5-backend';
+import { uid } from '@formily/shared';
import {
- Schema,
+ observer,
ISchema,
+ FormProvider,
useFieldSchema,
- useForm,
+ RecursionField,
useField,
} from '@formily/react';
-import { uid } from '@formily/shared';
import './style.less';
+import cls from 'classnames';
-import {
- DesignableSchemaContext,
- RefreshDesignableSchemaContext,
-} from '../SchemaField';
+import { useDesignable, useSchemaPath } from '../DesignableSchemaField';
+import { useColResizer } from './hooks';
+import { useDrag, useDrop, DragDropProvider, mergeRefs } from './hooks';
-export function removeProperty(property: Schema) {
- property.parent.removeProperty(property.name);
-}
+export const GridContext = createContext({
+ ref: null,
+});
-export function addPropertyBefore(target, prop) {
- Object.keys(target.parent.properties).forEach((name) => {
- if (name === target.name) {
- target.parent.addProperty(prop.name, prop);
- }
- const property = target.parent.properties[name];
- property.parent.removeProperty(property.name);
- target.parent.addProperty(property.name, property.toJSON());
+const ColumnSizeContext = createContext(null);
+
+export const GridBlockContext = createContext({
+ dragRef: null,
+});
+
+const RowDivider = ({ onDrop }) => {
+ const { isOver, dropRef } = useDrop({
+ accept: 'grid',
+ onDrop,
});
-}
-
-export function addPropertyAfter(target, prop) {
- Object.keys(target.parent.properties).forEach((name) => {
- const property = target.parent.properties[name];
- property.parent.removeProperty(property.name);
- target.parent.addProperty(property.name, property.toJSON());
- if (name === target.name) {
- target.parent.addProperty(prop.name, prop);
- }
- });
-}
-
-export const getSchemaAddressSegments = (schema: Schema) => {
- if (!schema) {
- return [];
- }
- const segments = [schema.name];
- if (schema.parent && schema.parent.name) {
- segments.unshift(...getSchemaAddressSegments(schema.parent));
- }
- return segments;
+ return (
+
+ );
};
-export function useSchemaQuery() {
- const context = useContext(DesignableSchemaContext);
- const refresh = useContext(RefreshDesignableSchemaContext);
- const fieldSchema = useFieldSchema();
+const ColDivider = (props: any) => {
+ const { onDragEnd, resizable } = props;
+ const { isDragging, dragRef } = useColResizer({ onDragEnd });
+ const { isOver, dropRef } = useDrop({
+ accept: 'grid',
+ data: {},
+ });
+ return (
+
+ );
+};
+
+export const Grid: any = observer((props) => {
+ const schema = useFieldSchema();
+ const { insertBefore, insertAfter, remove } = useDesignable();
+ const ref = useRef();
+ return (
+
+
+
+
{
+ const blockSchema = e.dragItem.schema;
+ const path = [...e.dragItem.path];
+ path.pop();
+ remove(path);
+ insertBefore({
+ type: 'void',
+ "x-component": 'Grid.Row',
+ properties: {
+ [uid()]: {
+ type: 'void',
+ "x-component": 'Grid.Col',
+ properties: {
+ [blockSchema.name]: blockSchema,
+ },
+ },
+ },
+ });
+ }}
+ />
+ {schema.mapProperties((property) => {
+ return (
+ <>
+
+
+
+ {
+ const blockSchema = e.dragItem.schema;
+ const path = [...e.dragItem.path];
+ path.pop();
+ remove(path);
+ insertAfter({
+ type: 'void',
+ "x-component": 'Grid.Row',
+ properties: {
+ [uid()]: {
+ type: 'void',
+ "x-component": 'Grid.Col',
+ properties: {
+ [blockSchema.name]: blockSchema,
+ },
+ },
+ },
+ });
+ }}
+ />
+ >
+ );
+ })}
+
+
+
+ );
+});
+
+Grid.Row = observer((props) => {
const field = useField();
- const form = useForm();
+ const schema = useFieldSchema();
+ const { schema: designableSchema, refresh } = useDesignable();
+ const len = Object.keys(schema.properties || {}).length;
+ return (
+
+ {schema.mapProperties((property, key, index) => {
+ return (
+ <>
+ 0}
+ onDragEnd={(e) => {
+ schema.mapProperties((s, key, index) => {
+ field.query(`.${schema.name}.${key}`).take((f) => {
+ f.componentProps['width'] = e.data.size[index];
+ });
+ s['x-component-props'] = s['x-component-props'] || {};
+ s['x-component-props']['width'] = e.data.size[index];
+ return s;
+ });
+ designableSchema.mapProperties((s, key, index) => {
+ s['x-component-props'] = s['x-component-props'] || {};
+ s['x-component-props']['width'] = e.data.size[index];
+ return s;
+ });
- const getSchemaByPath = (path) => {
- let s: Schema = context;
- const names = [...path];
- while (names.length) {
- s = s.properties[names.shift()];
- }
- return s;
- };
+ refresh();
+ console.log('e.data', designableSchema);
+ }}
+ />
+
+ >
+ );
+ })}
+
+
+ );
+});
- const schema = getSchemaByPath(field.address.segments);
+Grid.Col = observer((props) => {
+ const field = useField();
+ const width = field.componentProps['width'];
+ const size = useContext(ColumnSizeContext);
+ return (
+
+ {props.children}
+
+ );
+});
- const getPropertyByPosition = (position) => {
- if (position.type === 'row-divider') {
- const names = Object.keys(schema.properties);
- const isOver = position.rowDividerIndex > names.length - 1;
- const index = isOver ? names.length - 1 : position.rowDividerIndex;
- const name = names[index];
- const property = schema.properties[name];
- const addProperty = isOver ? addPropertyAfter : addPropertyBefore;
- return (data) => {
- return addProperty(property, {
- type: 'void',
- name: `r_${uid()}`,
- 'x-component': 'Grid.Row',
- properties: {
- [`c_${uid()}`]: {
- type: 'void',
- 'x-component': 'Grid.Col',
- 'x-component-props': {
- size: 1,
- },
- properties: {
- [data.name]: data,
- },
- },
- },
- });
- };
- }
- const rowNames = Object.keys(schema.properties);
- const rowName = rowNames[position.rowIndex];
- const row = schema.properties[rowName];
- if (position.type === 'col-divider') {
- const names = Object.keys(row.properties);
- const isOver = position.colDividerIndex > names.length - 1;
- const index = isOver ? names.length - 1 : position.colDividerIndex;
- const name = names[index];
- const property = row.properties[name];
- const addProperty = isOver ? addPropertyAfter : addPropertyBefore;
- const count = Object.keys(row.properties).length + 1;
- return (data) => {
- const other = 1 - 1 / count;
- Object.keys(row.properties).forEach((name) => {
- const prop = row.properties[name];
- const segments = getSchemaAddressSegments(prop);
- form.setFieldState(segments.join('.'), (state) => {
- state.componentProps.size = other * state.componentProps.size;
- console.log({ state }, other * state.componentProps.size);
- });
- });
- addProperty(property, {
- type: 'void',
- name: `c_${uid()}`,
- 'x-component': 'Grid.Col',
- 'x-component-props': {
- size: 1 / count,
- },
- properties: {
- [data.name]: data,
- },
- });
- };
- }
- const colNames = Object.keys(row.properties);
- const colName = colNames[position.colIndex];
- const col = row.properties[colName];
- if (position.type === 'block-divider') {
- const names = Object.keys(col.properties);
- const isOver = position.blockDividerIndex > names.length - 1;
- const index = isOver ? names.length - 1 : position.blockDividerIndex;
- const name = names[index];
- const property = col.properties[name];
- const addProperty = isOver ? addPropertyAfter : addPropertyBefore;
- return (data) => {
- return addProperty(property, data);
- };
- }
- };
-
- return {
- schema,
- fieldSchema,
- refresh,
- removeBlock: () => {
- if (Object.keys(schema.parent.parent.properties).length === 1) {
- removeProperty(schema.parent.parent);
- } else if (Object.keys(schema.parent.properties).length === 1) {
- removeProperty(schema.parent);
- const cols = [];
- let allSize = 0;
- Object.keys(schema.parent.parent.properties).forEach((name) => {
- const prop = schema.parent.parent.properties[name];
- const segments = getSchemaAddressSegments(prop);
- cols.push(segments);
- form.setFieldState(segments.join('.'), (state) => {
- allSize += state.componentProps.size;
- });
- return;
- });
- for (const segments of cols) {
- form.setFieldState(segments.join('.'), (state) => {
- state.componentProps.size = state.componentProps.size / allSize;
- });
- }
- }
- refresh();
+Grid.Block = observer((props) => {
+ const schema = useFieldSchema();
+ const ctx = useContext(GridContext);
+ const path = useSchemaPath();
+ const { isDragging, dragRef, previewRef } = useDrag({
+ type: 'grid',
+ onDragStart() {
+ console.log('onDragStart');
},
- addBlock: (data?: any, options?: any) => {
- const { insertBefore = false } = options || {};
- data = {
- type: 'void',
- name: `b_${uid()}`,
- 'x-component': 'Grid.Block',
- };
- const addProperty = insertBefore ? addPropertyBefore : addPropertyAfter;
- if (Object.keys(schema.parent.parent.properties).length === 1) {
- addProperty(schema.parent.parent, {
- type: 'void',
- name: `r_${uid()}`,
- 'x-component': 'Grid.Row',
- properties: {
- [`c_${uid()}`]: {
- type: 'void',
- 'x-component': 'Grid.Col',
- 'x-component-props': {
- size: 1,
- },
- properties: {
- [data.name]: data,
- },
- },
- },
- });
- } else {
- addProperty(schema, data);
- }
- refresh();
+ onDragEnd(event) {
+ console.log('onDragEnd', event.data);
},
- moveTo: (path, position) => {
- const source = getSchemaByPath(path);
- const insert = getPropertyByPosition(position);
- if (!insert) {
- return;
- }
-
- // 只有一列时,删除当前行
- if (Object.keys(source.parent.parent.properties).length === 1) {
- source.parent.parent.parent.removeProperty(source.parent.parent.name);
- }
- // 某列只有一个区块时删除当前列
- else if (Object.keys(source.parent.properties).length === 1) {
- source.parent.parent.removeProperty(source.parent.name);
- const cols = [];
- let allSize = 0;
- Object.keys(source.parent.parent.properties).forEach((name) => {
- const prop = source.parent.parent.properties[name];
- const segments = getSchemaAddressSegments(prop);
- cols.push(segments);
- form.setFieldState(segments.join('.'), (state) => {
- allSize += state.componentProps.size;
- });
- return;
- });
- for (const segments of cols) {
- form.setFieldState(segments.join('.'), (state) => {
- state.componentProps.size = state.componentProps.size / allSize;
- });
- }
- } else {
- source.parent.removeProperty(source.name);
- }
- insert(source.toJSON());
- refresh();
+ onDrag(event) {
+ // console.log('onDrag');
},
- };
-}
-
-export * from './DND';
-export * from './Row';
-export * from './Col';
-export * from './Grid';
-export * from './Block';
+ item: {
+ path,
+ schema: schema.toJSON(),
+ },
+ });
+ const { isOver, onTopHalf, dropRef } = useDrop({
+ accept: 'grid',
+ 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 (
+
+
+ {props.children}
+
+
+ );
+});
diff --git a/packages/client/src/blocks/grid/style.less b/packages/client/src/blocks/grid/style.less
index 696731796..8b731fc94 100644
--- a/packages/client/src/blocks/grid/style.less
+++ b/packages/client/src/blocks/grid/style.less
@@ -1,114 +1,81 @@
-.col-divider {
- position: relative;
- &:last-child {
- &.resizable:hover {
- cursor: auto;
- &::after {
- display: none;
- }
- }
- &.resizable.hover {
- &::after {
- display: block !important;
- }
- }
- }
- &.resizable {
- cursor: col-resize;
- }
- &.hover {
- cursor: grab !important;
- }
- &.resizable:hover,
- &.hover {
- &::after {
- content: '';
- display: block;
- position: absolute;
- top: 0;
- left: 50%;
- transform: translateX(-50%);
- height: 100%;
- width: 12px;
- background: #e6f7ff;
+.nb-grid-col-divider {
+ width: 24px;
+}
+.nb-grid-row {
+ margin: 0px -24px;
+}
+
+.nb-grid {
+ .nb-grid-col-divider {
+ &.resizable {
+ cursor: col-resize;
}
}
}
-.row-divider {
+.nb-grid.dragging {
position: relative;
- &.hover {
- &::after {
- content: '';
- display: block;
- position: absolute;
- left: 0;
- top: 50%;
- transform: translateY(-50%);
- height: 12px;
- width: 100%;
+ .nb-grid-col-divider {
+ // background-color: #ddd;
+ // opacity: 0.5;
+ &.resizable {
+ cursor: col-resize;
+ }
+ &.hover {
background: #e6f7ff;
}
}
- &:last-child {
- margin-bottom: -24px;
- }
-}
-
-.row {
- position: relative;
- &.hover {
- &::after {
- content: '';
- display: block;
- position: absolute;
- left: 24px;
- bottom: -18px;
- height: 12px;
- right: 24px;
- background: #e6f7ff;
- }
- &.top-half {
- &::after {
- top: -18px;
- bottom: auto;
- }
- }
- }
-}
-
-.block {
- position: relative;
- margin-bottom: 24px;
- &:last-child {
- margin-bottom: 0;
- }
- &.hover {
- &::after {
- content: '';
- display: block;
- position: absolute;
- left: 0;
- bottom: -18px;
- height: 12px;
- width: 100%;
- background: #e6f7ff;
- }
- &.top-half {
- &::after {
- top: -18px;
- bottom: auto;
- }
- }
- }
- > .action-bar {
+ .nb-grid-row-divider {
+ // background-color: #ddd;
+ height: 24px;
+ width: 100%;
position: absolute;
- top: 5px;
- right: 8px;
- z-index: 222;
+ z-index: 20;
+ // opacity: 0.5;
+ transform: translateY(-100%);
+ &.hover {
+ background: #e6f7ff;
+ }
+ }
+ .designable-bar {
+ display: none;
+ }
+ .nb-grid-block {
+ position: relative;
+ &.hover {
+ &::after {
+ background: #e6f7ff;
+ content: '';
+ display: block;
+ position: absolute;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ height: 12px;
+ z-index: 30;
+ }
+ &.top-half {
+ &::after {
+ top: 0;
+ bottom: auto;
+ }
+ }
+ }
}
}
-.draggable {
- cursor: grab !important;
-}
\ No newline at end of file
+.nb-grid-block.dragging {
+ > div > .designable-bar {
+ display: block !important;
+ }
+}
+
+.drag-container {
+ .nb-grid-block.dragging .designable-bar {
+ display: none !important;
+ }
+}
+
+.anticon-drag {
+ cursor: grab;
+}
diff --git a/packages/client/src/blocks/index.tsx b/packages/client/src/blocks/index.tsx
index a9a7d3ea0..cddc4bb1f 100644
--- a/packages/client/src/blocks/index.tsx
+++ b/packages/client/src/blocks/index.tsx
@@ -5,6 +5,8 @@ import { DesignableSchemaField, SchemaField } from './SchemaField';
export * from './SchemaField';
+export { SchemaRenderer, useDesignable, registerComponents, registerScope } from './DesignableSchemaField';
+
export const SchemaBlock = ({ schema, onlyRenderProperties = false, designable = true }) => {
const form = useMemo(() => createForm(), []);
diff --git a/packages/client/src/blocks/menu/index.md b/packages/client/src/blocks/menu/index.md
index c38fde6a4..7ee1ac063 100644
--- a/packages/client/src/blocks/menu/index.md
+++ b/packages/client/src/blocks/menu/index.md
@@ -22,7 +22,7 @@ group:
* title: 横向菜单
*/
import React from 'react';
-import { SchemaBlock } from '../';
+import { SchemaRenderer } from '../';
const schema = {
type: 'object',
@@ -33,6 +33,7 @@ const schema = {
'x-designable-bar': 'Menu.DesignableBar',
'x-component-props': {
mode: 'horizontal',
+ theme: 'dark',
},
properties: {
item1: {
@@ -47,24 +48,53 @@ const schema = {
},
item3: {
type: 'void',
- title: '菜单组',
+ title: '菜单组3',
'x-component': 'Menu.SubMenu',
properties: {
- item4: {
+ item5: {
type: 'void',
- title: `子菜单1`,
+ title: `子菜单5`,
+ 'x-component': 'Menu.SubMenu',
+ properties: {
+ item8: {
+ type: 'void',
+ title: `子菜单8`,
+ 'x-component': 'Menu.Item',
+ },
+ item9: {
+ type: 'void',
+ title: `子菜单9`,
+ 'x-component': 'Menu.Item',
+ },
+ },
+ },
+ }
+ },
+ item4: {
+ type: 'void',
+ title: '菜单组4',
+ 'x-component': 'Menu.SubMenu',
+ properties: {
+ item6: {
+ type: 'void',
+ title: `子菜单6`,
+ 'x-component': 'Menu.Item',
+ },
+ item7: {
+ type: 'void',
+ title: `子菜单7`,
'x-component': 'Menu.Item',
},
}
},
},
- }
- }
-};
+ },
+ },
+}
export default () => {
return (
-
+
);
};
```
@@ -76,7 +106,7 @@ export default () => {
* title: 竖向菜单
*/
import React from 'react';
-import { SchemaBlock } from '../';
+import { SchemaRenderer } from '../';
const schema = {
type: 'object',
@@ -90,23 +120,7 @@ const schema = {
mode: 'inline',
},
properties: {
- item3: {
- type: 'void',
- title: '菜单组',
- 'x-component': 'Menu.SubMenu',
- properties: {
- item4: {
- type: 'void',
- title: `子菜单1`,
- 'x-component': 'Menu.Item',
- },
- item5: {
- type: 'void',
- title: `子菜单2`,
- 'x-component': 'Menu.Item',
- },
- }
- },
+
item1: {
type: 'void',
title: `菜单1`,
@@ -117,6 +131,40 @@ const schema = {
title: `菜单2`,
'x-component': 'Menu.Item',
},
+ item3: {
+ type: 'void',
+ title: '菜单组3',
+ 'x-component': 'Menu.SubMenu',
+ properties: {
+ item4: {
+ type: 'void',
+ title: `子菜单4`,
+ 'x-component': 'Menu.Item',
+ },
+ item5: {
+ type: 'void',
+ title: `子菜单5`,
+ 'x-component': 'Menu.Item',
+ },
+ }
+ },
+ item4: {
+ type: 'void',
+ title: '菜单组4',
+ 'x-component': 'Menu.SubMenu',
+ properties: {
+ item6: {
+ type: 'void',
+ title: `子菜单6`,
+ 'x-component': 'Menu.Item',
+ },
+ item7: {
+ type: 'void',
+ title: `子菜单7`,
+ 'x-component': 'Menu.Item',
+ },
+ }
+ },
},
},
},
@@ -124,7 +172,234 @@ const schema = {
export default () => {
return (
-
+
);
};
-```
\ No newline at end of file
+```
+
+### 混合菜单
+
+```tsx
+import React, { useRef, useState } from 'react';
+import { SchemaRenderer } from '../';
+import { MenuContainerContext } from './';
+import { Layout } from 'antd';
+
+export default () => {
+ const ref = useRef();
+
+ const [activeKey, setActiveKey] = useState('item3');
+
+ const schema = {
+ type: 'object',
+ properties: {
+ menu1: {
+ type: 'void',
+ 'x-component': 'Menu',
+ 'x-component-props': {
+ defaultSelectedKeys: [activeKey],
+ mode: 'mix',
+ theme: 'dark',
+ onSelect(info) {
+ setActiveKey(info.key);
+ console.log({ info })
+ },
+ },
+ properties: {
+ item1: {
+ type: 'void',
+ title: `菜单1`,
+ 'x-component': 'Menu.Item',
+ },
+ item2: {
+ type: 'void',
+ title: `菜单2`,
+ 'x-component': 'Menu.Item',
+ },
+ item3: {
+ type: 'void',
+ title: '菜单组3',
+ 'x-component': 'Menu.SubMenu',
+ properties: {
+ item4: {
+ type: 'void',
+ title: `子菜单4`,
+ 'x-component': 'Menu.Item',
+ },
+ item5: {
+ type: 'void',
+ title: `子菜单5`,
+ 'x-component': 'Menu.SubMenu',
+ properties: {
+ item8: {
+ type: 'void',
+ title: `子菜单8`,
+ 'x-component': 'Menu.Item',
+ },
+ item9: {
+ type: 'void',
+ title: `子菜单9`,
+ 'x-component': 'Menu.Item',
+ },
+ },
+ },
+ }
+ },
+ item4: {
+ type: 'void',
+ title: '菜单组4',
+ 'x-component': 'Menu.SubMenu',
+ properties: {
+ item6: {
+ type: 'void',
+ title: `子菜单6`,
+ 'x-component': 'Menu.Item',
+ },
+ item7: {
+ type: 'void',
+ title: `子菜单7`,
+ 'x-component': 'Menu.Item',
+ },
+ }
+ },
+ },
+ },
+ },
+ }
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+ {activeKey}
+
+
+
+
+ )
+}
+```
+
+### 设计器模式
+
+```tsx
+import React, { useRef, useState } from 'react';
+import { SchemaRenderer } from '../';
+import { MenuContainerContext } from './';
+import { Layout } from 'antd';
+
+export default () => {
+ const ref = useRef();
+
+ const [activeKey, setActiveKey] = useState('item3');
+
+ const schema = {
+ type: 'object',
+ properties: {
+ menu1: {
+ type: 'void',
+ 'x-component': 'Menu',
+ 'x-designable-bar': 'Menu.DesignableBar',
+ 'x-component-props': {
+ defaultSelectedKeys: [activeKey],
+ mode: 'mix',
+ theme: 'dark',
+ onSelect(info) {
+ setActiveKey(info.key);
+ console.log({ info })
+ },
+ },
+ properties: {
+ item1: {
+ type: 'void',
+ title: `菜单1`,
+ 'x-component': 'Menu.Item',
+ },
+ item2: {
+ type: 'void',
+ title: `菜单2`,
+ 'x-component': 'Menu.Item',
+ },
+ item3: {
+ type: 'void',
+ title: '菜单组3',
+ 'x-component': 'Menu.SubMenu',
+ properties: {
+ item34: {
+ type: 'void',
+ title: `子菜单4`,
+ 'x-component': 'Menu.Item',
+ },
+ item5: {
+ type: 'void',
+ title: `子菜单5`,
+ 'x-component': 'Menu.SubMenu',
+ properties: {
+ item8: {
+ type: 'void',
+ title: `子菜单8`,
+ 'x-component': 'Menu.Item',
+ },
+ item9: {
+ type: 'void',
+ title: `子菜单9`,
+ 'x-component': 'Menu.Item',
+ },
+ },
+ },
+ }
+ },
+ item4: {
+ type: 'void',
+ title: '菜单组4',
+ 'x-component': 'Menu.SubMenu',
+ properties: {
+ item6: {
+ type: 'void',
+ title: `子菜单6`,
+ 'x-component': 'Menu.Item',
+ },
+ item7: {
+ type: 'void',
+ title: `子菜单7`,
+ 'x-component': 'Menu.Item',
+ },
+ }
+ },
+ },
+ },
+ },
+ }
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+ {activeKey}
+
+
+
+
+ )
+}
+```
diff --git a/packages/client/src/blocks/menu/index.tsx b/packages/client/src/blocks/menu/index.tsx
index 3c8802a53..9198449bc 100644
--- a/packages/client/src/blocks/menu/index.tsx
+++ b/packages/client/src/blocks/menu/index.tsx
@@ -16,6 +16,8 @@ import {
RecursionField,
Schema,
SchemaOptionsContext,
+ FormProvider,
+ useForm,
} from '@formily/react';
import {
Menu as AntdMenu,
@@ -28,14 +30,7 @@ import {
Button,
} from 'antd';
import get from 'lodash/get';
-import { FormDialog, FormLayout } from '@formily/antd';
import { uid } from '@formily/shared';
-// import { useSchemaQuery } from '../grid';
-import {
- DesignableSchemaContext,
- RefreshDesignableSchemaContext,
- SchemaField,
-} from '../SchemaField';
import {
MenuOutlined,
GroupOutlined,
@@ -53,6 +48,12 @@ import './style.less';
import { Icon } from '../icon-picker';
import { useHistory } from 'react-router-dom';
import cls from 'classnames';
+import ReactDOM from 'react-dom';
+import { useMount } from 'ahooks';
+import { useDesignable, SchemaRenderer, SchemaBlock } from '../';
+import { Router } from "react-router";
+
+import { useLifecycle } from 'beautiful-react-hooks';
export type MenuType = React.FC & {
Item?: React.FC;
@@ -64,108 +65,127 @@ export type MenuType = React.FC & {
Url?: React.FC;
};
+export const MenuContainerContext = createContext({
+ sideMenuRef: null,
+});
+
+export const MenuContext = createContext({
+ mode: null,
+ designableBar: null,
+});
+
function Blank() {
return null;
}
function useDesignableBar() {
- const schema = useFieldSchema();
-
- let s = schema;
- let DesignableBarName;
- while (s.parent) {
- if (s.parent['x-component'] === 'Menu') {
- DesignableBarName = s.parent['x-designable-bar'];
- break;
- }
- s = s.parent;
- }
+ const { designableBar } = useContext(MenuContext);
const options = useContext(SchemaOptionsContext);
- const DesignableBar = DesignableBarName ? get(options.components, DesignableBarName) : null;
+ const DesignableBar = designableBar
+ ? get(options.components, designableBar)
+ : null;
return {
DesignableBar: DesignableBar || Blank,
};
}
-export function removeProperty(property: Schema) {
- property.parent.removeProperty(property.name);
-}
-
-export function addPropertyBefore(target, prop) {
- Object.keys(target.parent.properties).forEach((name) => {
- if (name === target.name) {
- target.parent.addProperty(prop.name, prop);
+export const Menu: MenuType = observer((props) => {
+ const { onSelect, mode, defaultSelectedKeys, ...others } = props;
+ const { sideMenuRef } = useContext(MenuContainerContext);
+ const schema = useFieldSchema();
+ const { schema: designableSchema, refresh } = useDesignable();
+ const designableBar = schema['x-designable-bar'];
+ const history = useHistory();
+ const renderSideMenu = (selectedKey) => {
+ if (!selectedKey) {
+ return;
}
- const property = target.parent.properties[name];
- property.parent.removeProperty(property.name);
- target.parent.addProperty(property.name, property.toJSON());
- });
-}
-
-export function addPropertyAfter(target, prop) {
- Object.keys(target.parent.properties).forEach((name) => {
- const property = target.parent.properties[name];
- property.parent.removeProperty(property.name);
- target.parent.addProperty(property.name, property.toJSON());
- if (name === target.name) {
- target.parent.addProperty(prop.name, prop);
+ if ((mode as any) !== 'mix') {
+ return;
}
- });
-}
-
-export function useSchemaQuery(segments?: any[]) {
- const context = useContext(DesignableSchemaContext);
- const refresh = useContext(RefreshDesignableSchemaContext);
- const fieldSchema = useFieldSchema();
- const field = useField();
-
- const getSchemaByPath = (path) => {
- let s: Schema = context;
- const names = [...path];
- console.log('names', [...names], path, context);
- // names.shift();
- while (s && names.length) {
- const name = names.shift();
- s = s.properties[name];
+ if (!sideMenuRef || !sideMenuRef.current) {
+ return;
}
- return s;
+ const properties = schema.properties[selectedKey].properties;
+ console.log({ selectedKey, properties });
+ sideMenuRef.current.style.display = properties ? 'block' : 'none';
+ const newProps = {};
+ Object.keys(properties || {}).forEach((name) => {
+ newProps[name] = properties[name].toJSON();
+ });
+ ReactDOM.render(
+ properties ? (
+
+ {
+ const selected = designableSchema.properties[selectedKey];
+ const diff = subSchema.properties[`${schema.name}.${selectedKey}`];
+ Object.keys(selected.properties).forEach((name) => {
+ selected.properties[name].parent.removeProperty(name);
+ });
+ Object.keys(diff.properties).forEach((name) => {
+ if (name.endsWith('-add-new')) {
+ return;
+ }
+ const current = diff.properties[name];
+ selected.addProperty(current.name, current.toJSON());
+ });
+ refresh();
+ }}
+ schema={{
+ type: 'void',
+ name: `${schema.name}.${selectedKey}`,
+ 'x-component': 'Menu',
+ 'x-designable-bar': designableBar,
+ 'x-component-props': {
+ mode: 'inline',
+ onSelect,
+ },
+ properties: {
+ ...newProps,
+ [`${uid()}-add-new`]: {
+ type: 'void',
+ 'x-component': 'Menu.AddNew',
+ },
+ },
+ }}
+ />
+ ) : null,
+ sideMenuRef.current,
+ );
};
-
- const schema = getSchemaByPath(segments || field.address.segments);
-
- return {
- refresh,
- schema,
- appendChild(data) {
- schema.addProperty(data.name, data);
- refresh();
- },
- insertAfter(data) {
- addPropertyAfter(schema, data);
- refresh();
- },
- insertBefore(data) {
- addPropertyBefore(schema, data);
- refresh();
- },
- push(data) {
- addPropertyBefore(schema, data);
- },
- remove() {
- removeProperty(schema);
- refresh();
- },
- };
-}
+ useMount(() => {
+ const defaultSelectedKey = defaultSelectedKeys
+ ? defaultSelectedKeys[0]
+ : null;
+ renderSideMenu(defaultSelectedKey);
+ });
+ return (
+
+ {
+ console.log('info.key', info.key);
+ renderSideMenu(info.key);
+ onSelect && onSelect(info);
+ }}
+ mode={(mode as any) === 'mix' ? 'horizontal' : mode}
+ />
+
+ );
+});
const AddNewAction = () => {
- const field = useField();
- const segments = [...field.address.segments];
- // add new 节点是后加的,并不在菜单树上,要通过父节点处理 add 逻辑
- segments.pop();
- const { appendChild } = useSchemaQuery(segments);
+ const { insertBefore } = useDesignable();
return (
{
trigger={['click']}
overlay={
- {
- FormDialog(`新建菜单`, () => {
- return (
-
-
-
-
-
-
- );
- })
- .open({})
- .then((data) => {
- appendChild({
- name: `m_${uid()}`,
- type: 'void',
- title: data.title,
- 'x-component': 'Menu.Item',
- 'x-component-props': {
- icon: data.icon,
- },
- });
- });
- }}
- style={{ minWidth: 150 }}
- >
+ {
+ insertBefore({
+ type: 'void',
+ title: uid(),
+ "x-component": 'Menu.Item',
+ })
+ }} style={{ minWidth: 150 }}>
新建菜单
- {
- FormDialog(`新建菜单组`, () => {
- return (
-
-
-
-
-
-
- );
- })
- .open({})
- .then((data) => {
- appendChild({
- name: `m_${uid()}`,
- type: 'void',
- title: data.title,
- 'x-component': 'Menu.SubMenu',
- 'x-component-props': {
- icon: data.icon,
- },
- properties: {
- [`m_${uid()}`]: {
- type: 'void',
- title: `菜单 ${uid()}`,
- 'x-component': 'Menu.Item',
- },
- },
- });
- });
- }}
- >
+
新建分组
@@ -271,419 +217,24 @@ const AddNewAction = () => {
);
};
-const SettingAction = () => {
- const field = useField();
- const [visible, setVisible] = useState(false);
- const { schema, refresh, remove, insertBefore, insertAfter, appendChild } =
- useSchemaQuery();
- const text =
- schema['x-component'] === 'Menu.SubMenu' ? '当前菜单组' : '当前菜单';
- return (
-
-
{
- e.stopPropagation();
- e.preventDefault();
- }}
- className={'designable-bar-actions'}
- >
-
{
- setVisible(visible);
- }}
- trigger={['click']}
- overlay={
-
- {
- FormDialog('编辑菜单', () => {
- return (
-
-
-
-
-
- {/*
- 扩展文案
- */}
-
- );
- })
- .open({
- initialValues: {
- title: schema.title,
- icon: schema['x-component-props']?.['icon'],
- },
- })
- .then((data) => {
- schema.title = data.title;
- const componentProps = schema['x-component-props'] || {};
- componentProps['icon'] = data.icon;
- field.setTitle(data.title);
- field.setComponentProps(componentProps);
- refresh();
- });
- }}
- >
- 编辑菜单
-
-
- 移动到
-
-
- } title={`${text}前`}>
- {
- FormDialog(`新建菜单`, () => {
- return (
-
-
-
-
-
-
- );
- })
- .open({})
- .then((data) => {
- insertBefore({
- name: `m_${uid()}`,
- type: 'void',
- title: data.title,
- 'x-component': 'Menu.Item',
- 'x-component-props': {
- icon: data.icon,
- },
- });
- });
- }}
- style={{ minWidth: 150 }}
- >
- 新建菜单
-
- {
- FormDialog(`新建菜单组`, () => {
- return (
-
-
-
-
-
-
- );
- })
- .open({})
- .then((data) => {
- insertBefore({
- name: `m_${uid()}`,
- type: 'void',
- title: data.title,
- 'x-component': 'Menu.SubMenu',
- 'x-component-props': {
- icon: data.icon,
- },
- properties: {
- [`m_${uid()}`]: {
- type: 'void',
- title: `菜单 ${uid()}`,
- 'x-component': 'Menu.Item',
- },
- },
- });
- });
- }}
- >
- 新建分组
-
-
- 添加链接
-
-
- }
- title={`${text}后`}
- >
- {
- FormDialog(`新建菜单`, () => {
- return (
-
-
-
-
-
-
- );
- })
- .open({})
- .then((data) => {
- insertAfter(
- new Schema({
- name: `m_${uid()}`,
- type: 'void',
- title: data.title,
- 'x-component': 'Menu.Item',
- 'x-component-props': {
- icon: data.icon,
- },
- }),
- );
- });
- }}
- style={{ minWidth: 150 }}
- >
- 新建菜单
-
- {
- FormDialog(`新建菜单组`, () => {
- return (
-
-
-
-
-
-
- );
- })
- .open({})
- .then((data) => {
- insertAfter(
- new Schema({
- name: `m_${uid()}`,
- type: 'void',
- title: data.title,
- 'x-component': 'Menu.SubMenu',
- 'x-component-props': {
- icon: data.icon,
- },
- properties: {
- [`m_${uid()}`]: {
- type: 'void',
- title: `菜单 ${uid()}`,
- 'x-component': 'Menu.Item',
- },
- },
- }),
- );
- });
- }}
- >
- 新建分组
-
-
- 添加链接
-
-
- {schema['x-component'] === 'Menu.SubMenu' ? (
- }
- title={`${text}里`}
- >
- {
- FormDialog(`新建菜单`, () => {
- return (
-
-
-
-
-
-
- );
- })
- .open({})
- .then((data) => {
- appendChild(
- new Schema({
- name: `m_${uid()}`,
- type: 'void',
- title: data.title,
- 'x-component': 'Menu.Item',
- 'x-component-props': {
- icon: data.icon,
- },
- }),
- );
- });
- }}
- style={{ minWidth: 150 }}
- >
- 新建菜单
-
- {
- FormDialog(`新建菜单组`, () => {
- return (
-
-
-
-
-
-
- );
- })
- .open({})
- .then((data) => {
- appendChild(
- new Schema({
- name: `m_${uid()}`,
- type: 'void',
- title: data.title,
- 'x-component': 'Menu.SubMenu',
- 'x-component-props': {
- icon: data.icon,
- },
- properties: {
- [`m_${uid()}`]: {
- type: 'void',
- title: `菜单 ${uid()}`,
- 'x-component': 'Menu.Item',
- },
- },
- }),
- );
- });
- }}
- >
- 新建分组
-
-
- 添加链接
-
-
- ) : null}
-
- {
- Modal.confirm({
- title: '删除菜单',
- content: '确认删除此菜单项吗?',
- onOk: remove,
- });
- }}
- >
- 删除菜单
-
-
- }
- >
-
-
-
-
- );
-};
-
-export const Menu: MenuType = observer((props) => {
- const { hideSubMenu, ...others } = props;
- // const schema = useFieldSchema();
- // console.log({ schema }, 'Menu');
- return ;
-});
-
Menu.AddNew = observer((props) => {
- const field = useField();
- const { schema } = useSchemaQuery();
- console.log('AddNew', field.address.segments);
- return (
+ const { designableBar } = useContext(MenuContext);
+ return designableBar ? (
}
>
- );
+ ) : null;
});
Menu.Url = observer((props) => {
const field = useField();
- const { schema, refresh } = useSchemaQuery();
+ const schema = useFieldSchema();
+ const { DesignableBar } = useDesignableBar();
return (
{
}}
icon={props.icon ? : undefined}
>
- {field.title}
+ {field.title}
+
);
});
@@ -700,11 +252,11 @@ Menu.Url = observer((props) => {
Menu.Link = observer((props) => {
const history = useHistory();
const field = useField();
- const { schema, refresh } = useSchemaQuery();
+ const schema = useFieldSchema();
+ const { DesignableBar } = useDesignableBar();
return (
{
}}
icon={props.icon ? : undefined}
>
- {field.title}
+ {field.title}
+
);
});
Menu.Item = observer((props) => {
const field = useField();
+ const schema = useFieldSchema();
const { DesignableBar } = useDesignableBar();
- const { schema, refresh } = useSchemaQuery();
return (
{
- const el = e.domEvent.target as HTMLElement;
- console.log(
- 'onMouseEnter',
- el.offsetTop,
- el.offsetLeft,
- el.clientWidth,
- el.clientHeight,
- );
- }}
- onMouseLeave={() => {
- console.log('onMouseLeave');
- }}
schema={schema}
// @ts-ignore
eventKey={schema.name}
@@ -750,408 +290,13 @@ Menu.Item = observer((props) => {
);
});
-Menu.DesignableBar = (props) => {
- const field = useField();
- const [visible, setVisible] = useState(false);
- const { schema, refresh, remove, insertBefore, insertAfter, appendChild } =
- useSchemaQuery();
- const text =
- schema['x-component'] === 'Menu.SubMenu' ? '当前菜单组' : '当前菜单';
- return (
-
-
{
- e.stopPropagation();
- e.preventDefault();
- }}
- className={'designable-bar-actions'}
- >
-
{
- setVisible(visible);
- }}
- trigger={['click']}
- overlay={
-
- {
- FormDialog('编辑菜单', () => {
- return (
-
-
-
-
-
- {/*
- 扩展文案
- */}
-
- );
- })
- .open({
- initialValues: {
- title: schema.title,
- icon: schema['x-component-props']?.['icon'],
- },
- })
- .then((data) => {
- schema.title = data.title;
- const componentProps = schema['x-component-props'] || {};
- componentProps['icon'] = data.icon;
- field.setTitle(data.title);
- field.setComponentProps(componentProps);
- refresh();
- });
- }}
- >
- 编辑菜单
-
-
- 移动到
-
-
- } title={`${text}前`}>
- {
- FormDialog(`新建菜单`, () => {
- return (
-
-
-
-
-
-
- );
- })
- .open({})
- .then((data) => {
- insertBefore({
- name: `m_${uid()}`,
- type: 'void',
- title: data.title,
- 'x-component': 'Menu.Item',
- 'x-component-props': {
- icon: data.icon,
- },
- });
- });
- }}
- style={{ minWidth: 150 }}
- >
- 新建菜单
-
- {
- FormDialog(`新建菜单组`, () => {
- return (
-
-
-
-
-
-
- );
- })
- .open({})
- .then((data) => {
- insertBefore({
- name: `m_${uid()}`,
- type: 'void',
- title: data.title,
- 'x-component': 'Menu.SubMenu',
- 'x-component-props': {
- icon: data.icon,
- },
- properties: {
- [`m_${uid()}`]: {
- type: 'void',
- title: `菜单 ${uid()}`,
- 'x-component': 'Menu.Item',
- },
- },
- });
- });
- }}
- >
- 新建分组
-
-
- 添加链接
-
-
- }
- title={`${text}后`}
- >
- {
- FormDialog(`新建菜单`, () => {
- return (
-
-
-
-
-
-
- );
- })
- .open({})
- .then((data) => {
- insertAfter(
- new Schema({
- name: `m_${uid()}`,
- type: 'void',
- title: data.title,
- 'x-component': 'Menu.Item',
- 'x-component-props': {
- icon: data.icon,
- },
- }),
- );
- });
- }}
- style={{ minWidth: 150 }}
- >
- 新建菜单
-
- {
- FormDialog(`新建菜单组`, () => {
- return (
-
-
-
-
-
-
- );
- })
- .open({})
- .then((data) => {
- insertAfter(
- new Schema({
- name: `m_${uid()}`,
- type: 'void',
- title: data.title,
- 'x-component': 'Menu.SubMenu',
- 'x-component-props': {
- icon: data.icon,
- },
- properties: {
- [`m_${uid()}`]: {
- type: 'void',
- title: `菜单 ${uid()}`,
- 'x-component': 'Menu.Item',
- },
- },
- }),
- );
- });
- }}
- >
- 新建分组
-
-
- 添加链接
-
-
- {schema['x-component'] === 'Menu.SubMenu' ? (
- }
- title={`${text}里`}
- >
- {
- FormDialog(`新建菜单`, () => {
- return (
-
-
-
-
-
-
- );
- })
- .open({})
- .then((data) => {
- appendChild(
- new Schema({
- name: `m_${uid()}`,
- type: 'void',
- title: data.title,
- 'x-component': 'Menu.Item',
- 'x-component-props': {
- icon: data.icon,
- },
- }),
- );
- });
- }}
- style={{ minWidth: 150 }}
- >
- 新建菜单
-
- {
- FormDialog(`新建菜单组`, () => {
- return (
-
-
-
-
-
-
- );
- })
- .open({})
- .then((data) => {
- appendChild(
- new Schema({
- name: `m_${uid()}`,
- type: 'void',
- title: data.title,
- 'x-component': 'Menu.SubMenu',
- 'x-component-props': {
- icon: data.icon,
- },
- properties: {
- [`m_${uid()}`]: {
- type: 'void',
- title: `菜单 ${uid()}`,
- 'x-component': 'Menu.Item',
- },
- },
- }),
- );
- });
- }}
- >
- 新建分组
-
-
- 添加链接
-
-
- ) : null}
-
- {
- Modal.confirm({
- title: '删除菜单',
- content: '确认删除此菜单项吗?',
- onOk: remove,
- });
- }}
- >
- 删除菜单
-
-
- }
- >
-
-
-
-
- );
-};
-
Menu.SubMenu = observer((props) => {
const { DesignableBar } = useDesignableBar();
const schema = useFieldSchema();
- let s = schema;
- let hideSubMenu;
- while (s.parent) {
- if (s.parent['x-component'] === 'Menu') {
- hideSubMenu = s.parent['x-component-props']?.['hideSubMenu'];
- break;
- }
- s = s.parent;
- }
- if (hideSubMenu) {
- return ;
- }
- return (
+ const { mode } = useContext(MenuContext);
+ return mode === 'mix' ? (
+
+ ) : (
{
Menu.Divider = observer(AntdMenu.Divider);
+function useDesigner() {
+ const field = useField();
+}
+
+Menu.DesignableBar = (props) => {
+ const field = useField();
+ const fieldSchema = useFieldSchema();
+ const [visible, setVisible] = useState(false);
+ const { schema, remove, refresh } = useDesignable();
+ return (
+
+
{
+ e.stopPropagation();
+ e.preventDefault();
+ }}
+ className={'designable-bar-actions'}
+ >
+
{
+ // setVisible(visible);
+ // }}
+ trigger={['click']}
+ overlay={
+
+ {
+ const title = uid();
+ field.title = title;
+ field.componentProps['icon'] = 'DeleteOutlined';
+ schema['x-component-props'] = schema['x-component-props'] || {};
+ schema['x-component-props']['icon'] = 'DeleteOutlined';
+ schema.title = title;
+ fieldSchema.title = title;
+ fieldSchema['x-component-props'] = fieldSchema['x-component-props'] || {};
+ fieldSchema['x-component-props']['icon'] = 'DeleteOutlined';
+ refresh();
+ }}
+ >
+ 修改标题
+
+ {
+ Modal.confirm({
+ title: '删除菜单',
+ content: '确认删除此菜单项吗?',
+ onOk: () => {
+ remove();
+ },
+ });
+ }}
+ >
+ 删除菜单
+
+
+ }
+ >
+
+
+
+
+ );
+};
+
export default Menu;
diff --git a/packages/client/src/blocks/menu/style.less b/packages/client/src/blocks/menu/style.less
index e2e2d4c08..a30b0bbb5 100644
--- a/packages/client/src/blocks/menu/style.less
+++ b/packages/client/src/blocks/menu/style.less
@@ -1,18 +1,19 @@
-.ant-menu-dark.ant-menu-horizontal {
+.ant-menu-horizontal {
width: 100%;
}
-.ant-menu-dark .ant-menu-item-active .designable-bar {
+
+.ant-menu-item-active .designable-bar {
display: inline-block;
}
-.ant-menu-dark .ant-menu-item-active:hover .designable-bar,
-.ant-menu-dark .ant-menu-item-active.ant-menu-item-selected .designable-bar {
+.ant-menu-item-active:hover .designable-bar,
+.ant-menu-item-active.ant-menu-item-selected .designable-bar {
display: inline-block;
}
-.ant-menu-light .ant-menu-submenu-active > .ant-menu-submenu-title .designable-bar,
-.ant-menu-light .ant-menu-item-active .designable-bar {
+.ant-menu-submenu-active > .ant-menu-submenu-title .designable-bar,
+.ant-menu-item-active .designable-bar {
display: inline-block;
}
diff --git a/packages/client/src/blocks/time-picker/index.md b/packages/client/src/blocks/time-picker/index.md
index aafc26cd6..0b2d4a167 100644
--- a/packages/client/src/blocks/time-picker/index.md
+++ b/packages/client/src/blocks/time-picker/index.md
@@ -20,7 +20,7 @@ group:
* title: 日期选择器
*/
import React from 'react';
-import { SchemaBlock } from '../';
+import { SchemaRenderer } from '../';
const schema = {
type: 'object',
@@ -51,7 +51,7 @@ const schema = {
export default () => {
return (
-
+
);
};
```
diff --git a/packages/client/src/blocks/upload/index.md b/packages/client/src/blocks/upload/index.md
index 3cde8ccb2..3b87560c3 100644
--- a/packages/client/src/blocks/upload/index.md
+++ b/packages/client/src/blocks/upload/index.md
@@ -20,7 +20,7 @@ import React from 'react';
import { Button } from 'antd'
import { UploadOutlined, InboxOutlined } from '@ant-design/icons'
import Upload from './';
-import { SchemaBlock, registerComponents } from '../';
+import { SchemaRenderer, registerComponents } from '../';
const NormalUpload = (props) => {
return (
@@ -69,7 +69,7 @@ const schema = {
export default () => {
return (
-
+
);
};
```
diff --git a/packages/client/src/demos/api/blocks-getSchema/menu.ts b/packages/client/src/demos/api/blocks-getSchema/menu.ts
index 7a2ab036f..2ee0d9b47 100644
--- a/packages/client/src/demos/api/blocks-getSchema/menu.ts
+++ b/packages/client/src/demos/api/blocks-getSchema/menu.ts
@@ -4,8 +4,10 @@ export default {
type: 'void',
name: `m_${uid()}`,
'x-component': 'Menu',
- 'x-decorator': 'Menu.Designable',
+ 'x-designable-bar': 'Menu.DesignableBar',
'x-component-props': {
+ mode: 'mix',
+ theme: 'dark',
},
properties: {
item2: {
diff --git a/packages/client/src/templates/admin-layout/index.tsx b/packages/client/src/templates/admin-layout/index.tsx
index 7f5af2030..6c9ce918e 100644
--- a/packages/client/src/templates/admin-layout/index.tsx
+++ b/packages/client/src/templates/admin-layout/index.tsx
@@ -1,4 +1,4 @@
-import React, { useContext, useEffect, useState } from 'react';
+import React, { useContext, useEffect, useRef, useState } from 'react';
import {
Button,
Spin,
@@ -22,11 +22,11 @@ import {
refreshGlobalAction,
RouteComponentContext,
} from '../../';
-import { SchemaBlock } from '../../blocks';
+import { SchemaBlock, SchemaRenderer } from '../../blocks';
import { useRequest } from 'ahooks';
import cloneDeep from 'lodash/cloneDeep';
import { Schema } from '@formily/react';
-import { DesignableProvider } from '../../blocks/SchemaField';
+import { DesignableContext } from '../../blocks/SchemaField';
import { uid } from '@formily/shared';
import {
DatabaseOutlined,
@@ -36,6 +36,7 @@ import {
import { Tabs } from 'antd';
import '@formily/antd/esm/array-collapse/style';
import './style.less';
+import { MenuContainerContext } from '../../blocks/menu';
function LogoutButton() {
const history = useHistory();
@@ -163,242 +164,34 @@ function Database() {
);
}
-function useMenuSchema({ schema, selectedKey }) {
- const [activeTopKey, setActiveTopKey] = useState(selectedKey);
- let topMenuSchema = new Schema(cloneDeep(schema.toJSON()));
- topMenuSchema =
- topMenuSchema.properties[Object.keys(topMenuSchema.properties)[0]];
- const [activeKey, setActiveKey] = useState(selectedKey);
- console.log({ activeKey, topMenuSchema });
- topMenuSchema['x-component-props']['hideSubMenu'] = true;
- topMenuSchema['x-component-props']['mode'] = 'horizontal';
- topMenuSchema['x-component-props']['theme'] = 'dark';
-
- function findLastSelected(activeKey) {
- function find(schema: Schema) {
- return schema.reduceProperties((selected, current) => {
- if (current.name === activeKey) {
- return [...selected, current];
- }
- if (current.properties) {
- return [...selected, ...find(current)];
- }
- return [...selected];
- }, []);
- }
-
- // const topMenuSchema = new Schema(cloneDeep(schema.toJSON()));
-
- let selected = find(topMenuSchema).shift() as Schema;
-
- console.log({ topMenuSchema, selected, schema });
-
- if (selected && selected.properties) {
- const findChild = (properties) => {
- const keys = Object.keys(properties || {});
- const firstKey = keys.shift();
- if (firstKey) {
- selected = properties[firstKey];
- findChild(properties[firstKey].properties);
- }
- };
- findChild(selected.properties);
- }
-
- return selected;
+function LayoutWithMenu({ schema }) {
+ const location = useLocation();
+ const ref = useRef();
+ const [activeKey, setActiveKey] = useState('item3');
+ schema['x-component-props']['defaultSelectedKeys'] = [activeKey];
+ schema['x-component-props']['onSelect'] = (info) => {
+ console.log('LayoutWithMenu', schema)
+ setActiveKey(info.key);
}
-
- function find(schema: Schema) {
- return schema.reduceProperties((selected, current) => {
- if (current.name === activeKey) {
- return [...selected, current];
- }
- if (current.properties) {
- return [...selected, ...find(current)];
- }
- return [...selected];
- }, []);
- }
-
- let selected = (find(topMenuSchema).shift() as Schema) || new Schema({});
-
- const [pageTitle, setPageTitle] = useState(selected.title);
- console.log({ selected, pageTitle }, selected.title);
-
- useEffect(() => {
- setPageTitle(selected.title);
- }, [selected]);
-
- useEffect(() => {
- setActiveKey(selectedKey);
- }, [selectedKey]);
-
- let s = selected;
-
- let properties = null;
-
- const selectedKeys = [s.name];
-
- let sideMenuKey = null;
-
- function getAddress(schema: Schema) {
- const segments = [];
-
- segments.unshift(schema.name);
-
- while (schema.parent) {
- segments.unshift(schema.parent.name);
- schema = schema.parent;
- }
-
- return segments.join('.');
- }
-
- while (s.parent) {
- if (s.parent['x-component'] === 'Menu') {
- sideMenuKey = getAddress(s);
- if (s['x-component'] === 'Menu.SubMenu') {
- properties = s.properties;
- }
- break;
- }
- selectedKeys.push(s.parent.name);
- s = s.parent;
- }
-
- console.log({ selectedKeys });
-
- if (properties && selectedKeys.length === 1) {
- const findChild = (properties) => {
- const keys = Object.keys(properties || {});
- const firstKey = keys.shift();
- if (firstKey) {
- selectedKeys.push(firstKey);
- findChild(properties[firstKey].properties);
- }
- };
- findChild(properties);
- selectedKey = selectedKeys[selectedKeys.length - 1];
- }
-
- topMenuSchema['x-component-props']['onSelect'] = (info) => {
- console.log('onSelect', info.item.props.schema);
- // setActiveSchema(info.item.props.schema || {});
- // setPageTitle(info.item.props.schema.title);
- setActiveTopKey(info.key);
- // setActiveKey(info.key);
- const selected = findLastSelected(info.key);
- console.log('selected', selected.name);
- setActiveKey(selected.name);
- setPageTitle(selected.title);
- };
-
- let sideMenuSchema = null;
- if (properties) {
- properties['add_new'] = new Schema({
- type: 'void',
- name: `m_${uid()}`,
- 'x-component': 'Menu.AddNew',
- });
- sideMenuSchema = new Schema({
- type: 'void',
- name: sideMenuKey,
- 'x-component': 'Menu',
- 'x-component-props': {
- mode: 'inline',
- // selectedKeys,
- defaultSelectedKeys: selectedKeys,
- defaultOpenKeys: selectedKeys,
- onSelect(info) {
- console.log('onSelect', info.item.props.schema);
- setPageTitle(info.item.props.schema.title);
- setActiveKey(info.key);
- },
- },
- properties,
- }).toJSON();
- }
-
- // const sideMenuSchema = properties
- // ? new Schema({
- // type: 'void',
- // name: sideMenuKey,
- // 'x-component': 'Menu',
- // 'x-component-props': {
- // mode: 'inline',
- // // selectedKeys,
- // defaultSelectedKeys: selectedKeys,
- // defaultOpenKeys: selectedKeys,
- // onSelect(info) {
- // console.log('onSelect', info.item.props.schema);
- // setPageTitle(info.item.props.schema.title);
- // setActiveKey(info.key);
- // },
- // },
- // properties,
- // }).toJSON()
- // : null;
-
- topMenuSchema['x-component-props']['defaultSelectedKeys'] = selectedKeys;
- topMenuSchema['x-component-props']['defaultOpenKeys'] = selectedKeys;
-
- return {
- pageTitle,
- topMenuSchema,
- sideMenuSchema,
- selectedKeys,
- activeKey,
- };
-}
-
-function LayoutWithMenu({ schema, activeMenuItemKey }) {
- const { activeKey, pageTitle, topMenuSchema, sideMenuSchema } = useMenuSchema(
- { schema, selectedKey: activeMenuItemKey },
- );
- const history = useHistory();
-
return (
-
-
- NocoBase
-
-
-
-
-
-
- {sideMenuSchema && (
-
-
+
+
+
+
+
+
+
- )}
-
- {pageTitle && }
-
- {/* {history.location.pathname} */}
-
-
-
+
+ {location.pathname}
+
+
+
-
- );
+ )
}
function Content({ activeKey }) {
@@ -414,7 +207,7 @@ function Content({ activeKey }) {
return ;
}
- return ;
+ return ;
}
export function AdminLayout({ route, children }: any) {
@@ -432,29 +225,8 @@ export function AdminLayout({ route, children }: any) {
}
return (
-
- {(s) => {
- // console.log('DesignableProvider', s.properties.item2.title);
- return (
-
- );
- }}
-
+
);
- // return ;
}
export default AdminLayout;