mirror of
https://gitee.com/nocobase/nocobase.git
synced 2024-12-05 05:38:23 +08:00
updates
This commit is contained in:
parent
cfc04814c6
commit
5e0277d451
@ -24,6 +24,10 @@
|
||||
"ahooks": "^2.10.2",
|
||||
"axios": "^0.21.1",
|
||||
"lodash": "^4.17.21",
|
||||
"react-dnd": "^14.0.2",
|
||||
"react-dnd-html5-backend": "^14.0.0",
|
||||
"react-dnd-preview": "^6.0.2",
|
||||
"react-dnd-touch-backend": "^14.0.0",
|
||||
"react-helmet": "^6.1.0",
|
||||
"react-hooks-global-state": "^1.0.1",
|
||||
"umi-request": "^1.3.5"
|
||||
|
@ -1,34 +0,0 @@
|
||||
---
|
||||
title: Block - 区块
|
||||
nav:
|
||||
title: 组件
|
||||
path: /client
|
||||
group:
|
||||
order: 2
|
||||
title: Blocks
|
||||
path: /client/blocks
|
||||
---
|
||||
|
||||
# Block - 区块
|
||||
|
||||
```tsx
|
||||
import React from 'react';
|
||||
import BlockEditor from './';
|
||||
|
||||
const items = [
|
||||
{
|
||||
type: 'page',
|
||||
name: 'block1',
|
||||
},
|
||||
{
|
||||
type: 'page',
|
||||
name: 'block2',
|
||||
}
|
||||
]
|
||||
|
||||
export default () => {
|
||||
return (
|
||||
<BlockEditor items={items} />
|
||||
)
|
||||
}
|
||||
```
|
@ -1,94 +0,0 @@
|
||||
import React from 'react';
|
||||
import { Dropdown, Menu, Card, Button, Popover } from 'antd';
|
||||
import { useDynamicList } from 'ahooks';
|
||||
import { MenuOutlined, DeleteOutlined, PlusOutlined } from '@ant-design/icons';
|
||||
import './style.less';
|
||||
import { FormDialog, FormLayout } from '@formily/antd';
|
||||
import { SchemaField } from '../../fields';
|
||||
|
||||
export function AddNewAction(props) {
|
||||
const { insert, children } = props;
|
||||
const menu = (
|
||||
<Menu>
|
||||
<Menu.Item
|
||||
key="page"
|
||||
onClick={() => {
|
||||
FormDialog(`新建区块`, () => {
|
||||
return (
|
||||
<FormLayout labelCol={6} wrapperCol={10}>
|
||||
<SchemaField>
|
||||
<SchemaField.String
|
||||
name="name"
|
||||
required
|
||||
title="数据源"
|
||||
x-decorator="FormItem"
|
||||
x-component="Input"
|
||||
/>
|
||||
</SchemaField>
|
||||
</FormLayout>
|
||||
);
|
||||
})
|
||||
.open({})
|
||||
.then(insert);
|
||||
}}
|
||||
>
|
||||
<MenuOutlined /> 新建区块
|
||||
</Menu.Item>
|
||||
</Menu>
|
||||
);
|
||||
return <Dropdown overlay={menu}>{children || <PlusOutlined />}</Dropdown>;
|
||||
}
|
||||
|
||||
export function SettingAction(props) {
|
||||
const { remove } = props;
|
||||
const menu = (
|
||||
<Menu>
|
||||
<Menu.Item onClick={() => remove()} key="delete">
|
||||
<DeleteOutlined /> 删除区块
|
||||
</Menu.Item>
|
||||
</Menu>
|
||||
);
|
||||
return (
|
||||
<Dropdown overlay={menu}>
|
||||
<MenuOutlined />
|
||||
</Dropdown>
|
||||
);
|
||||
}
|
||||
|
||||
export function BlockItem(props) {
|
||||
const { remove, insert, replace } = props;
|
||||
return (
|
||||
<div className={'block-item'}>
|
||||
<div className={'block-item-actions'}>
|
||||
<SettingAction replace={replace} remove={remove} />
|
||||
<AddNewAction insert={insert} />
|
||||
</div>
|
||||
<div className={'block-item-body'}>
|
||||
<Card bordered={false} title={'这是测试区块'}>
|
||||
这是内容区
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default (props) => {
|
||||
const { list, push, remove, replace, insert } = useDynamicList(props.items);
|
||||
return (
|
||||
<div className={'block-list'}>
|
||||
{list.map((item, index) => (
|
||||
<BlockItem
|
||||
insert={(data) => insert(index + 1, data)}
|
||||
replace={(data) => replace(index, data)}
|
||||
remove={() => remove(index)}
|
||||
key={index}
|
||||
schema={item}
|
||||
/>
|
||||
))}
|
||||
<br />
|
||||
<AddNewAction insert={(data) => push(data)}>
|
||||
<Button block type={'dashed'} icon={<PlusOutlined />}></Button>
|
||||
</AddNewAction>
|
||||
</div>
|
||||
);
|
||||
};
|
@ -1,20 +0,0 @@
|
||||
.block-list {
|
||||
background: #f0f2f5;
|
||||
padding: 0 24px 24px;
|
||||
}
|
||||
.block-item {
|
||||
position: relative;
|
||||
padding-top: 24px;
|
||||
.block-item-actions {
|
||||
display: none;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
z-index: 1;
|
||||
}
|
||||
&:hover {
|
||||
.block-item-actions {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
}
|
@ -10,3 +10,10 @@ group:
|
||||
---
|
||||
|
||||
# Calendar - 日历
|
||||
|
||||
## 代码演示
|
||||
|
||||
### 基本使用
|
||||
|
||||
### 设计器模式
|
||||
|
||||
|
@ -11,3 +11,9 @@ group:
|
||||
|
||||
# Chart - 图表
|
||||
|
||||
## 代码演示
|
||||
|
||||
### 基本使用
|
||||
|
||||
### 设计器模式
|
||||
|
||||
|
@ -4,25 +4,27 @@
|
||||
import React from 'react';
|
||||
import { FormBlock } from '@nocobase/client';
|
||||
|
||||
const fields = [
|
||||
{
|
||||
interface: 'string',
|
||||
type: 'string',
|
||||
title: `单行文本`,
|
||||
name: 'username',
|
||||
required: true,
|
||||
default: 'abcdefg',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input',
|
||||
'x-component-props': {
|
||||
placeholder: 'please enter',
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
export default () => {
|
||||
return (
|
||||
<FormBlock
|
||||
resource={'users'}
|
||||
fields={[
|
||||
{
|
||||
interface: 'string',
|
||||
type: 'string',
|
||||
title: `单行文本`,
|
||||
name: 'username',
|
||||
required: true,
|
||||
default: 'abcdefg',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input',
|
||||
'x-component-props': {
|
||||
placeholder: 'please enter',
|
||||
},
|
||||
},
|
||||
]}
|
||||
fields={fields}
|
||||
effects={{
|
||||
onFormValuesChange(form) {
|
||||
console.log('aaaa', form.values);
|
||||
|
150
packages/client/src/blocks/grid/Drop.tsx
Normal file
150
packages/client/src/blocks/grid/Drop.tsx
Normal file
@ -0,0 +1,150 @@
|
||||
import React, { useContext } from 'react';
|
||||
import { DndProvider, useDrag, useDrop } from 'react-dnd';
|
||||
import classNames from 'classnames';
|
||||
import { useSchema } from '../../fields';
|
||||
import { AcceptContext } from './Grid';
|
||||
import { useField } from '@formily/react';
|
||||
|
||||
export function DropFirstRow() {
|
||||
const field = useField();
|
||||
const { schema } = useSchema();
|
||||
const accept = useContext(AcceptContext);
|
||||
const [{ canDrop, isOver }, drop] = useDrop(
|
||||
() => ({
|
||||
accept,
|
||||
drop: () => ({
|
||||
gridType: 'first-row',
|
||||
schema,
|
||||
segments: field.address.segments,
|
||||
}),
|
||||
collect: (monitor) => ({
|
||||
isOver: monitor.isOver(),
|
||||
canDrop: monitor.canDrop(),
|
||||
}),
|
||||
}),
|
||||
[schema],
|
||||
);
|
||||
|
||||
const active = canDrop && isOver;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classNames('drop-row', 'first', { active })}
|
||||
ref={drop}
|
||||
></div>
|
||||
);
|
||||
}
|
||||
|
||||
export function DropRow() {
|
||||
const { schema } = useSchema();
|
||||
const field = useField();
|
||||
const accept = useContext(AcceptContext);
|
||||
const [{ canDrop, isOver }, drop] = useDrop(
|
||||
() => ({
|
||||
accept,
|
||||
drop: () => ({
|
||||
gridType: 'row',
|
||||
schema,
|
||||
segments: field.address.segments,
|
||||
}),
|
||||
collect: (monitor) => ({
|
||||
isOver: monitor.isOver(),
|
||||
canDrop: monitor.canDrop(),
|
||||
}),
|
||||
}),
|
||||
[schema],
|
||||
);
|
||||
|
||||
const active = canDrop && isOver;
|
||||
|
||||
return <div className={classNames('drop-row', { active })} ref={drop}></div>;
|
||||
}
|
||||
|
||||
export function DropColumn() {
|
||||
const field = useField();
|
||||
const { schema } = useSchema();
|
||||
const accept = useContext(AcceptContext);
|
||||
const [{ canDrop, isOver }, drop] = useDrop(
|
||||
() => ({
|
||||
accept,
|
||||
drop: () => ({
|
||||
gridType: 'column',
|
||||
schema,
|
||||
segments: field.address.segments,
|
||||
}),
|
||||
collect: (monitor) => ({
|
||||
isOver: monitor.isOver(),
|
||||
canDrop: monitor.canDrop(),
|
||||
}),
|
||||
}),
|
||||
[schema],
|
||||
);
|
||||
|
||||
const active = canDrop && isOver;
|
||||
|
||||
return (
|
||||
<div className={classNames('drop-column', { active })} ref={drop}></div>
|
||||
);
|
||||
}
|
||||
|
||||
export function DropLastColumn() {
|
||||
const field = useField();
|
||||
const { schema } = useSchema();
|
||||
const accept = useContext(AcceptContext);
|
||||
const [{ canDrop, isOver }, drop] = useDrop(
|
||||
() => ({
|
||||
accept,
|
||||
drop: () => ({
|
||||
gridType: 'last-column',
|
||||
schema,
|
||||
segments: field.address.segments,
|
||||
}),
|
||||
collect: (monitor) => ({
|
||||
isOver: monitor.isOver(),
|
||||
canDrop: monitor.canDrop(),
|
||||
}),
|
||||
}),
|
||||
[schema],
|
||||
);
|
||||
|
||||
const active = canDrop && isOver;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classNames('drop-column', 'last', { active })}
|
||||
ref={drop}
|
||||
></div>
|
||||
);
|
||||
}
|
||||
|
||||
export function DropBlock({ canDrop }) {
|
||||
const { schema } = useSchema();
|
||||
const accept = useContext(AcceptContext);
|
||||
const [{ isOver }, drop] = useDrop(
|
||||
() => ({
|
||||
accept,
|
||||
drop: () => ({ schema }),
|
||||
collect: (monitor) => ({
|
||||
isOver: monitor.isOver(),
|
||||
canDrop: monitor.canDrop(),
|
||||
}),
|
||||
canDrop: () => canDrop,
|
||||
}),
|
||||
[canDrop],
|
||||
);
|
||||
|
||||
console.log({ canDrop });
|
||||
|
||||
const active = canDrop && isOver;
|
||||
|
||||
return (
|
||||
<div className={classNames('drop-block', { active })} ref={drop}></div>
|
||||
);
|
||||
}
|
||||
|
||||
export default {
|
||||
DropRow,
|
||||
DropColumn,
|
||||
DropLastColumn,
|
||||
DropBlock,
|
||||
};
|
319
packages/client/src/blocks/grid/Grid.tsx
Normal file
319
packages/client/src/blocks/grid/Grid.tsx
Normal file
@ -0,0 +1,319 @@
|
||||
import React, { createContext, useContext, useState } from 'react';
|
||||
import { Row, Col, Dropdown, Menu } from 'antd';
|
||||
import { DndProvider, useDrag, useDrop } from 'react-dnd';
|
||||
import { HTML5Backend } from 'react-dnd-html5-backend';
|
||||
import {
|
||||
DropFirstRow,
|
||||
DropRow,
|
||||
DropColumn,
|
||||
DropBlock,
|
||||
DropLastColumn,
|
||||
} from './Drop';
|
||||
import {
|
||||
CloseOutlined,
|
||||
DeleteOutlined,
|
||||
MenuOutlined,
|
||||
PlusOutlined,
|
||||
ArrowDownOutlined,
|
||||
ArrowUpOutlined,
|
||||
} from '@ant-design/icons';
|
||||
import {
|
||||
getFullPaths,
|
||||
SchemaDesignerContext,
|
||||
useSchemaQuery,
|
||||
useSchema,
|
||||
} from '../../fields';
|
||||
import { TouchBackend } from 'react-dnd-touch-backend';
|
||||
import { usePreview } from 'react-dnd-preview';
|
||||
import classNames from 'classnames';
|
||||
import './style.less';
|
||||
import { useField, useFieldSchema } from '@formily/react';
|
||||
|
||||
const Preview = () => {
|
||||
const accept = useContext(AcceptContext);
|
||||
const { item, style, display, itemType } = usePreview();
|
||||
if (itemType !== accept) {
|
||||
return null;
|
||||
}
|
||||
if (!display) {
|
||||
return null;
|
||||
}
|
||||
if (!item.ref) {
|
||||
return null;
|
||||
}
|
||||
if (!item.ref.current) {
|
||||
return null;
|
||||
}
|
||||
const el = item.ref.current as HTMLDivElement;
|
||||
console.log({ itemType });
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
...style,
|
||||
height: el.clientHeight,
|
||||
width: el.clientWidth,
|
||||
zIndex: 9999,
|
||||
opacity: 0.8,
|
||||
// left: `-${el.clientWidth}px`,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
transform: 'translate(-90%, -5%)',
|
||||
}}
|
||||
dangerouslySetInnerHTML={{ __html: el.outerHTML }}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export interface GridPorps {
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
|
||||
export interface GridRowPorps {
|
||||
children?: React.ReactNode;
|
||||
rowOrder?: number;
|
||||
}
|
||||
|
||||
export interface GridColumnPorps {
|
||||
children?: React.ReactNode;
|
||||
span?: any;
|
||||
}
|
||||
|
||||
export interface GridBlockProps {
|
||||
children?: React.ReactNode;
|
||||
lastComponentType?: string;
|
||||
}
|
||||
|
||||
export type GridComponent = React.FC<GridPorps> & {
|
||||
Row?: React.FC<GridRowPorps>;
|
||||
Column?: React.FC<GridColumnPorps>;
|
||||
Block?: React.FC<GridBlockProps>;
|
||||
};
|
||||
|
||||
export const AcceptContext = createContext(null);
|
||||
|
||||
export const Grid: GridComponent = (props) => {
|
||||
const { children } = props;
|
||||
const schema = useFieldSchema();
|
||||
const field = useField();
|
||||
console.log({ accept: schema.name, schema: schema.toJSON() });
|
||||
return (
|
||||
<div className={'grid'} style={{ marginTop: 24 }}>
|
||||
<AcceptContext.Provider value={schema.name}>
|
||||
<DndProvider
|
||||
backend={TouchBackend}
|
||||
options={{ enableMouseEvents: true }}
|
||||
>
|
||||
{children}
|
||||
<Preview />
|
||||
</DndProvider>
|
||||
</AcceptContext.Provider>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
Grid.Row = (props) => {
|
||||
const { children, rowOrder } = props;
|
||||
const { schema } = useSchema();
|
||||
const field = useField();
|
||||
const accept = useContext(AcceptContext);
|
||||
console.log({ accept });
|
||||
const [{ canDrop, isOverCurrent }, drop] = useDrop(() => ({
|
||||
accept,
|
||||
drop: (item, monitor) => {
|
||||
const didDrop = monitor.didDrop();
|
||||
if (didDrop) {
|
||||
return;
|
||||
}
|
||||
return { gridType: 'row', schema, segments: field.address.segments };
|
||||
},
|
||||
collect: (monitor) => ({
|
||||
isOver: monitor.isOver(),
|
||||
canDrop: monitor.canDrop(),
|
||||
isOverCurrent: monitor.isOver({ shallow: true }),
|
||||
}),
|
||||
}));
|
||||
|
||||
const active = canDrop && isOverCurrent;
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={drop}
|
||||
className={classNames('grid-row', `grid-row-order-${rowOrder}`, {
|
||||
active,
|
||||
})}
|
||||
>
|
||||
{rowOrder === 0 && <DropFirstRow />}
|
||||
<Row gutter={24}>{children}</Row>
|
||||
<DropLastColumn />
|
||||
{/* <DropRow /> */}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
Grid.Column = (props) => {
|
||||
const { children, span } = props;
|
||||
return (
|
||||
<Col span={span}>
|
||||
<DropColumn />
|
||||
{children}
|
||||
</Col>
|
||||
);
|
||||
};
|
||||
|
||||
interface DropResult {
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
Grid.Block = (props) => {
|
||||
const { children, lastComponentType } = props;
|
||||
const { schema, refresh } = useSchema();
|
||||
const fieldSchema = useFieldSchema();
|
||||
const field = useField();
|
||||
const accept = useContext(AcceptContext);
|
||||
const context = useContext(SchemaDesignerContext);
|
||||
const {
|
||||
insertAfter,
|
||||
insertAfterWithAddRow,
|
||||
insertBeforeWithAddRow,
|
||||
insertBeforeWithAddColumn,
|
||||
appendToRowWithAddColumn,
|
||||
} = useSchemaQuery();
|
||||
const ref = React.useRef();
|
||||
console.log({ accept });
|
||||
const [{ opacity, isDragging }, drag, preview] = useDrag(
|
||||
() => ({
|
||||
type: accept,
|
||||
item: {
|
||||
ref,
|
||||
preview,
|
||||
schema,
|
||||
},
|
||||
collect: (monitor) => ({
|
||||
isDragging: monitor.isDragging(),
|
||||
opacity: monitor.isDragging() ? 0.9 : 1,
|
||||
}),
|
||||
end: (item, monitor) => {
|
||||
const dropResult = monitor.getDropResult<DropResult>();
|
||||
if (item && dropResult) {
|
||||
if (dropResult.gridType === 'block') {
|
||||
insertAfter(field.address.segments, dropResult.segments);
|
||||
} else if (dropResult.gridType === 'row') {
|
||||
insertAfterWithAddRow(field.address.segments, dropResult.segments);
|
||||
} else if (dropResult.gridType === 'column') {
|
||||
insertBeforeWithAddColumn(
|
||||
field.address.segments,
|
||||
dropResult.segments,
|
||||
);
|
||||
} else if (dropResult.gridType === 'last-column') {
|
||||
appendToRowWithAddColumn(
|
||||
field.address.segments,
|
||||
dropResult.segments,
|
||||
);
|
||||
} else if (dropResult.gridType === 'first-row') {
|
||||
insertBeforeWithAddRow(
|
||||
field.address.segments,
|
||||
dropResult.segments,
|
||||
);
|
||||
}
|
||||
refresh();
|
||||
}
|
||||
},
|
||||
}),
|
||||
[field, schema],
|
||||
);
|
||||
|
||||
const segments = field.address.segments;
|
||||
|
||||
const [{ canDrop, isOver }, drop] = useDrop(
|
||||
() => ({
|
||||
accept,
|
||||
drop: () => {
|
||||
console.log('source.segments', segments);
|
||||
return { gridType: 'block', segments, schema };
|
||||
},
|
||||
collect: (monitor) => ({
|
||||
isOver: monitor.isOver(),
|
||||
canDrop: monitor.canDrop(),
|
||||
}),
|
||||
// hover: (item, monitor) => {
|
||||
// console.log(monitor.getSourceClientOffset());
|
||||
// },
|
||||
canDrop: () => !isDragging,
|
||||
}),
|
||||
[isDragging, schema],
|
||||
);
|
||||
|
||||
const active = canDrop && isOver;
|
||||
|
||||
drop(ref);
|
||||
|
||||
console.log({ isDragging });
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
style={{ opacity }}
|
||||
className={classNames(`grid-block`, `grid-block--${lastComponentType}`, {
|
||||
active,
|
||||
})}
|
||||
>
|
||||
<ActionBar dragRef={drag} />
|
||||
{children}
|
||||
{/* <DropBlock canDrop={!isDragging} /> */}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const ActionBar = ({ dragRef }) => {
|
||||
const { addBlock, removeBlock } = useSchemaQuery();
|
||||
const [active, setActive] = useState(false);
|
||||
return (
|
||||
<div className={classNames('action-bar', { active })}>
|
||||
<Dropdown
|
||||
overlayStyle={{ minWidth: 200 }}
|
||||
trigger={['click']}
|
||||
visible={active}
|
||||
onVisibleChange={setActive}
|
||||
overlay={
|
||||
<Menu>
|
||||
<Menu.Item
|
||||
onClick={() => {
|
||||
addBlock({}, true);
|
||||
setActive(false);
|
||||
}}
|
||||
icon={<ArrowUpOutlined />}
|
||||
>
|
||||
在上方插入区块
|
||||
</Menu.Item>
|
||||
<Menu.Item
|
||||
onClick={() => {
|
||||
addBlock({});
|
||||
setActive(false);
|
||||
}}
|
||||
icon={<ArrowDownOutlined />}
|
||||
>
|
||||
在下方插入区块
|
||||
</Menu.Item>
|
||||
<Menu.Divider />
|
||||
<Menu.Item
|
||||
onClick={() => {
|
||||
removeBlock();
|
||||
setActive(false);
|
||||
}}
|
||||
icon={<DeleteOutlined />}
|
||||
>
|
||||
删除区块
|
||||
</Menu.Item>
|
||||
</Menu>
|
||||
}
|
||||
>
|
||||
<MenuOutlined className={'draggable'} ref={dragRef} />
|
||||
</Dropdown>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Grid;
|
@ -1,15 +0,0 @@
|
||||
#components-grid-demo [class~='ant-row'] {
|
||||
.ant-col {
|
||||
// background: rgba(128, 128, 128, 0.08);
|
||||
> div {
|
||||
padding: 16px;
|
||||
border-radius: 0;
|
||||
color: #fff;
|
||||
margin-bottom: 8px;
|
||||
margin-top: 8px;
|
||||
min-height: 30px;
|
||||
text-align: center;
|
||||
background: rgba(0,146,255,.75);
|
||||
}
|
||||
}
|
||||
}
|
133
packages/client/src/blocks/grid/demos/demo2.tsx
Normal file
133
packages/client/src/blocks/grid/demos/demo2.tsx
Normal file
@ -0,0 +1,133 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import { FormProvider, FormConsumer, useField, useFieldSchema } from '@formily/react';
|
||||
import { createForm } from '@formily/core';
|
||||
import {
|
||||
SchemaFieldWithDesigner,
|
||||
registerFieldComponents,
|
||||
useSchema,
|
||||
} from '../../../fields';
|
||||
import { grid, row, column, block } from '../utils';
|
||||
import { blocks2properties } from '../utils';
|
||||
|
||||
function Designer(props) {
|
||||
const form = useMemo(() => createForm({}), []);
|
||||
const { schema } = props;
|
||||
return (
|
||||
<div>
|
||||
<FormProvider form={form}>
|
||||
<SchemaFieldWithDesigner schema={schema} />
|
||||
{/* <FormConsumer>
|
||||
{(form) => {
|
||||
return <div>{JSON.stringify(form.values, null, 2)}</div>;
|
||||
}}
|
||||
</FormConsumer> */}
|
||||
</FormProvider>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Hello(props) {
|
||||
const schema = useFieldSchema();
|
||||
return (
|
||||
<div style={{ marginBottom: 24, padding: '1rem', background: '#f9f9f9', minHeight: 50, lineHeight: '50px' }}>
|
||||
Hello {schema.title}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
registerFieldComponents({ Hello });
|
||||
const blocks = [
|
||||
{
|
||||
type: 'string',
|
||||
title: `Block 1`,
|
||||
required: true,
|
||||
'x-read-pretty': false,
|
||||
// 'x-decorator': 'FormItem',
|
||||
'x-component': 'Hello',
|
||||
rowOrder: 1,
|
||||
columnOrder: 1,
|
||||
blockOrder: 1,
|
||||
},
|
||||
{
|
||||
type: 'string',
|
||||
title: `Block 2`,
|
||||
required: true,
|
||||
'x-read-pretty': false,
|
||||
// 'x-decorator': 'FormItem',
|
||||
'x-component': 'Hello',
|
||||
rowOrder: 1,
|
||||
columnOrder: 2,
|
||||
blockOrder: 1,
|
||||
},
|
||||
{
|
||||
type: 'string',
|
||||
title: `Block 3`,
|
||||
required: true,
|
||||
'x-read-pretty': false,
|
||||
// 'x-decorator': 'FormItem',
|
||||
'x-component': 'Hello',
|
||||
rowOrder: 2,
|
||||
columnOrder: 1,
|
||||
blockOrder: 1,
|
||||
},
|
||||
{
|
||||
type: 'string',
|
||||
title: `Block 4`,
|
||||
required: true,
|
||||
'x-read-pretty': false,
|
||||
// 'x-decorator': 'FormItem',
|
||||
'x-component': 'Hello',
|
||||
rowOrder: 1,
|
||||
columnOrder: 1,
|
||||
blockOrder: 2,
|
||||
},
|
||||
{
|
||||
type: 'string',
|
||||
title: `Block 5`,
|
||||
required: true,
|
||||
'x-read-pretty': false,
|
||||
// 'x-decorator': 'FormItem',
|
||||
'x-component': 'Hello',
|
||||
rowOrder: 3,
|
||||
columnOrder: 1,
|
||||
blockOrder: 1,
|
||||
},
|
||||
{
|
||||
type: 'string',
|
||||
title: `Block 6`,
|
||||
required: true,
|
||||
'x-read-pretty': false,
|
||||
// 'x-decorator': 'FormItem',
|
||||
'x-component': 'Hello',
|
||||
rowOrder: 3,
|
||||
columnOrder: 2,
|
||||
blockOrder: 1,
|
||||
},
|
||||
];
|
||||
const schema = blocks2properties(blocks);
|
||||
|
||||
export default () => {
|
||||
console.log({schema});
|
||||
return (
|
||||
<div>
|
||||
<Designer
|
||||
schema={{
|
||||
type: 'object',
|
||||
properties: {
|
||||
layout: {
|
||||
type: 'void',
|
||||
'x-component': 'FormLayout',
|
||||
'x-component-props': {
|
||||
layout: 'vertical',
|
||||
},
|
||||
properties: {
|
||||
[schema.name]: schema,
|
||||
},
|
||||
},
|
||||
},
|
||||
}}
|
||||
/>
|
||||
{/* <pre>{JSON.stringify(schema, null, 2)}</pre> */}
|
||||
</div>
|
||||
);
|
||||
};
|
200
packages/client/src/blocks/grid/demos/demo3.tsx
Normal file
200
packages/client/src/blocks/grid/demos/demo3.tsx
Normal file
@ -0,0 +1,200 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import { FormProvider, FormConsumer, useField, useFieldSchema } from '@formily/react';
|
||||
import { createForm } from '@formily/core';
|
||||
import {
|
||||
SchemaFieldWithDesigner,
|
||||
registerFieldComponents,
|
||||
} from '../../../fields';
|
||||
import { grid, row, column, block } from '../utils';
|
||||
import { blocks2properties } from '../utils';
|
||||
import { Card } from 'antd';
|
||||
|
||||
function Designer(props) {
|
||||
const form = useMemo(() => createForm({}), []);
|
||||
const { schema } = props;
|
||||
return (
|
||||
<div>
|
||||
<FormProvider form={form}>
|
||||
<SchemaFieldWithDesigner schema={schema} />
|
||||
{/* <FormConsumer>
|
||||
{(form) => {
|
||||
return <div>{JSON.stringify(form.values, null, 2)}</div>;
|
||||
}}
|
||||
</FormConsumer> */}
|
||||
</FormProvider>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Hello(props) {
|
||||
const schema = useFieldSchema();
|
||||
return (
|
||||
<div style={{ marginBottom: 24, padding: '1rem', background: '#f9f9f9', minHeight: 50, lineHeight: '50px' }}>
|
||||
Hello {schema.title}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
registerFieldComponents({ Hello, Designer, Card });
|
||||
|
||||
const nested = blocks2properties([
|
||||
{
|
||||
type: 'string',
|
||||
title: `Nested Block 1`,
|
||||
required: true,
|
||||
'x-read-pretty': false,
|
||||
// 'x-decorator': 'FormItem',
|
||||
'x-component': 'Hello',
|
||||
rowOrder: 1,
|
||||
columnOrder: 1,
|
||||
blockOrder: 1,
|
||||
},
|
||||
{
|
||||
type: 'string',
|
||||
title: `Nested Block 2`,
|
||||
required: true,
|
||||
'x-read-pretty': false,
|
||||
// 'x-decorator': 'FormItem',
|
||||
'x-component': 'Hello',
|
||||
rowOrder: 1,
|
||||
columnOrder: 2,
|
||||
blockOrder: 1,
|
||||
},
|
||||
{
|
||||
type: 'string',
|
||||
title: `Nested Block 3`,
|
||||
required: true,
|
||||
'x-read-pretty': false,
|
||||
// 'x-decorator': 'FormItem',
|
||||
'x-component': 'Hello',
|
||||
rowOrder: 2,
|
||||
columnOrder: 1,
|
||||
blockOrder: 1,
|
||||
},
|
||||
]);
|
||||
|
||||
const blocks = [
|
||||
{
|
||||
type: 'string',
|
||||
title: `Block 1`,
|
||||
required: true,
|
||||
'x-read-pretty': false,
|
||||
// 'x-decorator': 'FormItem',
|
||||
'x-component': 'Hello',
|
||||
rowOrder: 1,
|
||||
columnOrder: 1,
|
||||
blockOrder: 1,
|
||||
},
|
||||
{
|
||||
type: 'string',
|
||||
title: `Block 2`,
|
||||
required: true,
|
||||
'x-read-pretty': false,
|
||||
// 'x-decorator': 'FormItem',
|
||||
'x-component': 'Hello',
|
||||
rowOrder: 1,
|
||||
columnOrder: 2,
|
||||
blockOrder: 1,
|
||||
},
|
||||
{
|
||||
type: 'string',
|
||||
title: `Block 3`,
|
||||
required: true,
|
||||
'x-read-pretty': false,
|
||||
// 'x-decorator': 'FormItem',
|
||||
'x-component': 'Hello',
|
||||
rowOrder: 2,
|
||||
columnOrder: 1,
|
||||
blockOrder: 1,
|
||||
},
|
||||
{
|
||||
type: 'string',
|
||||
title: `Block 4`,
|
||||
required: true,
|
||||
'x-read-pretty': false,
|
||||
// 'x-decorator': 'FormItem',
|
||||
'x-component': 'Hello',
|
||||
rowOrder: 1,
|
||||
columnOrder: 1,
|
||||
blockOrder: 2,
|
||||
},
|
||||
{
|
||||
type: 'string',
|
||||
title: `Block 5`,
|
||||
required: true,
|
||||
'x-read-pretty': false,
|
||||
// 'x-decorator': 'FormItem',
|
||||
'x-component': 'Hello',
|
||||
rowOrder: 3,
|
||||
columnOrder: 1,
|
||||
blockOrder: 1,
|
||||
},
|
||||
{
|
||||
type: 'string',
|
||||
title: `Block 6`,
|
||||
required: true,
|
||||
'x-read-pretty': false,
|
||||
// 'x-decorator': 'FormItem',
|
||||
'x-component': 'Hello',
|
||||
rowOrder: 3,
|
||||
columnOrder: 2,
|
||||
blockOrder: 1,
|
||||
},
|
||||
{
|
||||
type: 'string',
|
||||
title: `Block 7`,
|
||||
required: true,
|
||||
'x-read-pretty': false,
|
||||
'x-decorator': 'Card',
|
||||
'x-decorator-props': {
|
||||
title: '内嵌区块(只能在当前区域内部拖拽)',
|
||||
},
|
||||
'x-component': 'Designer',
|
||||
'x-component-props': {
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
layout: {
|
||||
type: 'void',
|
||||
'x-component': 'FormLayout',
|
||||
'x-component-props': {
|
||||
layout: 'vertical',
|
||||
},
|
||||
properties: {
|
||||
[nested.name]: nested,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
rowOrder: 4,
|
||||
columnOrder: 1,
|
||||
blockOrder: 1,
|
||||
},
|
||||
];
|
||||
const schema = blocks2properties(blocks);
|
||||
export default () => {
|
||||
console.log({schema});
|
||||
return (
|
||||
<div>
|
||||
<Designer
|
||||
schema={{
|
||||
type: 'object',
|
||||
properties: {
|
||||
layout: {
|
||||
type: 'void',
|
||||
'x-component': 'FormLayout',
|
||||
'x-component-props': {
|
||||
layout: 'vertical',
|
||||
},
|
||||
properties: {
|
||||
[schema.name]: schema,
|
||||
},
|
||||
},
|
||||
},
|
||||
}}
|
||||
/>
|
||||
{/* <pre>{JSON.stringify(schema, null, 2)}</pre> */}
|
||||
</div>
|
||||
);
|
||||
};
|
@ -5,72 +5,56 @@ nav:
|
||||
path: /client
|
||||
group:
|
||||
order: 2
|
||||
title: Blocks
|
||||
title: Blocks
|
||||
path: /client/blocks
|
||||
---
|
||||
|
||||
# Grid - 栅格
|
||||
|
||||
## 示例
|
||||
基于行(Row)和列(Col)来定义区块(Block)的外部框架。
|
||||
|
||||
```tsx
|
||||
import React from 'react';
|
||||
import { BlockContext, GridBlock } from '@nocobase/client';
|
||||
import './demo.less';
|
||||
## 代码演示
|
||||
|
||||
function Hello({ name }) {
|
||||
return <div>Hello {name}</div>;
|
||||
}
|
||||
### 基本用法
|
||||
|
||||
const blocks = [
|
||||
{
|
||||
name: 'gb1',
|
||||
'x-component': 'Hello',
|
||||
'x-row': 0,
|
||||
'x-column': 0,
|
||||
'x-sort': 1,
|
||||
},
|
||||
{
|
||||
name: 'gb2',
|
||||
'x-component': 'Hello',
|
||||
'x-row': 0,
|
||||
'x-column': 0,
|
||||
'x-sort': 2,
|
||||
},
|
||||
{
|
||||
name: 'gb3',
|
||||
'x-component': 'Hello',
|
||||
'x-row': 0,
|
||||
'x-column': 1,
|
||||
'x-sort': 0,
|
||||
},
|
||||
{
|
||||
name: 'gb4',
|
||||
'x-component': 'Hello',
|
||||
'x-row': 1,
|
||||
'x-column': 0,
|
||||
'x-sort': 0,
|
||||
},
|
||||
{
|
||||
name: 'gb5',
|
||||
'x-component': 'Hello',
|
||||
'x-row': 2,
|
||||
'x-column': 0,
|
||||
'x-sort': 0,
|
||||
},
|
||||
];
|
||||
<code src="./demos/demo2.tsx"/>
|
||||
|
||||
export default () => {
|
||||
return (
|
||||
<BlockContext.Provider value={{
|
||||
Hello,
|
||||
}}>
|
||||
<div id={'components-grid-demo'}>
|
||||
<GridBlock
|
||||
blocks={blocks}
|
||||
/>
|
||||
</div>
|
||||
</BlockContext.Provider >
|
||||
)
|
||||
### 内嵌区块
|
||||
|
||||
<code src="./demos/demo3.tsx"/>
|
||||
|
||||
## 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` 方法可以简化配置。
|
@ -1,49 +0,0 @@
|
||||
import React from 'react';
|
||||
import { Row, Col } from 'antd';
|
||||
import { useBlock } from '../';
|
||||
import set from 'lodash/set';
|
||||
|
||||
export function BlockItem(props: any) {
|
||||
const { Component } = useBlock(props);
|
||||
if (!Component) {
|
||||
return null;
|
||||
}
|
||||
console.log('BlockItem')
|
||||
return <Component {...props} />
|
||||
}
|
||||
|
||||
export interface GridBlockProps {
|
||||
blocks: any[];
|
||||
}
|
||||
|
||||
export function GridBlock(props: GridBlockProps) {
|
||||
const { blocks = [] } = props;
|
||||
const obj = {
|
||||
rows: [],
|
||||
};
|
||||
blocks.forEach(block => {
|
||||
const path = ['rows', block['x-row'], block['x-column'], block['x-sort']];
|
||||
console.log(path);
|
||||
set(obj, path, block);
|
||||
});
|
||||
console.log({obj})
|
||||
return (
|
||||
<div>
|
||||
{obj.rows.map((cols, rowIndex) => {
|
||||
return (
|
||||
<Row justify="space-around" align="middle" key={rowIndex}>
|
||||
{cols.map((items, colIndex) => {
|
||||
return (
|
||||
<Col span={24/cols.length} key={colIndex}>
|
||||
{items.map((item, key) => <BlockItem key={key} {...item} />) }
|
||||
</Col>
|
||||
)
|
||||
})}
|
||||
</Row>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default GridBlock;
|
143
packages/client/src/blocks/grid/style.less
Normal file
143
packages/client/src/blocks/grid/style.less
Normal file
@ -0,0 +1,143 @@
|
||||
.drop-column {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: -12px;
|
||||
height: calc(100% - 24px);
|
||||
width: 24px;
|
||||
// border: 1px dashed rgb(232, 232, 232);
|
||||
&.active {
|
||||
&::after {
|
||||
content: '';
|
||||
display: block;
|
||||
width: 12px;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
left: 5px;
|
||||
background: #e6f7ff;
|
||||
}
|
||||
}
|
||||
&.last {
|
||||
left: auto;
|
||||
right: -24px;
|
||||
}
|
||||
}
|
||||
.drop-block {
|
||||
height: 12px;
|
||||
position: absolute;
|
||||
bottom: -12px;
|
||||
width: 100%;
|
||||
left: 0;
|
||||
// background: #e6f7ff;
|
||||
// pointer-events: none;
|
||||
&.active {
|
||||
background: #e6f7ff;
|
||||
}
|
||||
}
|
||||
// .ant-formily-item + .drop-block {
|
||||
// position: absolute;
|
||||
// bottom: 12px;
|
||||
// width: 100%;
|
||||
// }
|
||||
.grid-row {
|
||||
position: relative;
|
||||
|
||||
&.active {
|
||||
&::after {
|
||||
content: '';
|
||||
display: block;
|
||||
height: 12px;
|
||||
position: absolute;
|
||||
bottom: 5px;
|
||||
width: 100%;
|
||||
background: #e6f7ff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.grid-block {
|
||||
position: relative;
|
||||
padding-bottom: 1px;
|
||||
&.active {
|
||||
&::after {
|
||||
content: '';
|
||||
display: block;
|
||||
height: 12px;
|
||||
position: absolute;
|
||||
bottom: 5px;
|
||||
width: 100%;
|
||||
background: #e6f7ff;
|
||||
}
|
||||
}
|
||||
|
||||
&.grid-block--Input {
|
||||
margin-bottom: 0;
|
||||
.drop-block {
|
||||
bottom: 0;
|
||||
}
|
||||
}rgb(172, 172, 172)
|
||||
.grid-block-actions {
|
||||
position: absolute;
|
||||
top: 5px;
|
||||
right: 5px;
|
||||
z-index: 2;
|
||||
display: none;
|
||||
}
|
||||
> .action-bar {
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
top: 5px;
|
||||
z-index: 2;
|
||||
display: none;
|
||||
&.active {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
&:hover,
|
||||
&.hover {
|
||||
> .action-bar {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.grid-block .ant-card {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.drop-row {
|
||||
height: 12px;
|
||||
// background: #e6f7ff;
|
||||
position: absolute;
|
||||
bottom: 5px;
|
||||
width: 100%;
|
||||
// background: #e6f7ff;
|
||||
&.active {
|
||||
background: #e6f7ff;
|
||||
}
|
||||
|
||||
&.first {
|
||||
top: -17px;
|
||||
z-index: 2;
|
||||
bottom: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.draggable {
|
||||
cursor: move !important; /* fallback: no `url()` support or images disabled */
|
||||
cursor: -webkit-grab !important; /* Chrome 1-21, Safari 4+ */
|
||||
cursor: -moz-grab !important; /* Firefox 1.5-26 */
|
||||
cursor: grab !important; /* W3C standards syntax, should come least */
|
||||
}
|
||||
|
||||
.draggable:active {
|
||||
cursor: -webkit-grabbing;
|
||||
cursor: -moz-grabbing;
|
||||
cursor: grabbing;
|
||||
}
|
||||
tr.drop-over-downward td {
|
||||
border-bottom: 1px dashed #1890ff;
|
||||
}
|
||||
|
||||
tr.drop-over-upward td {
|
||||
border-top: 1px dashed #1890ff;
|
||||
}
|
94
packages/client/src/blocks/grid/utils.tsx
Normal file
94
packages/client/src/blocks/grid/utils.tsx
Normal file
@ -0,0 +1,94 @@
|
||||
import set from 'lodash/set';
|
||||
import { uid } from '@formily/shared';
|
||||
|
||||
export const grid = (...rows: any[]) => {
|
||||
const rowProperties = {};
|
||||
rows.forEach((row, index) => {
|
||||
set(row, 'x-component-props.rowOrder', index);
|
||||
rowProperties[row.name] = row;
|
||||
});
|
||||
const name = `g_${uid()}`;
|
||||
return {
|
||||
type: 'void',
|
||||
name,
|
||||
'x-component': 'Grid',
|
||||
'x-component-props': {},
|
||||
properties: rowProperties,
|
||||
};
|
||||
};
|
||||
|
||||
export const row = (...cols: any[]) => {
|
||||
const rowName = `r_${uid()}`;
|
||||
const colsProperties = {};
|
||||
cols.forEach((col, index) => {
|
||||
set(col, 'x-component-props.columnOrder', index);
|
||||
set(col, 'x-component-props.span', 24 / cols.length);
|
||||
colsProperties[col.name] = col;
|
||||
});
|
||||
return {
|
||||
type: 'void',
|
||||
name: rowName,
|
||||
'x-component': 'Grid.Row',
|
||||
'x-component-props': {},
|
||||
properties: colsProperties,
|
||||
};
|
||||
};
|
||||
|
||||
export const column = (...blocks: any[]) => {
|
||||
const colName = `c_${uid()}`;
|
||||
const properties = {};
|
||||
blocks.forEach((item) => {
|
||||
properties[item.name] = item;
|
||||
});
|
||||
return {
|
||||
name: colName,
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Column',
|
||||
'x-read-pretty': true,
|
||||
'x-component-props': {
|
||||
labelCol: 6,
|
||||
wrapperCol: 10,
|
||||
span: 24,
|
||||
},
|
||||
properties,
|
||||
};
|
||||
};
|
||||
|
||||
export const block = (...fields: any[]) => {
|
||||
const blockName = `b_${uid()}`;
|
||||
const properties = {};
|
||||
fields.forEach((item) => {
|
||||
const name = item.name || `f_${uid()}`;
|
||||
properties[name] = item;
|
||||
});
|
||||
const lastComponentType = fields[fields.length - 1]['x-component'];
|
||||
return {
|
||||
name: blockName,
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Block',
|
||||
'x-component-props': {
|
||||
lastComponentType,
|
||||
},
|
||||
'x-read-pretty': false,
|
||||
properties,
|
||||
};
|
||||
};
|
||||
|
||||
export function blocks2properties(blocks: any[]) {
|
||||
const obj = {
|
||||
rows: [],
|
||||
};
|
||||
blocks.forEach(block => {
|
||||
const path = ['rows', block['rowOrder'], block['columnOrder'], block['blockOrder']];
|
||||
console.log(path);
|
||||
set(obj, path, block);
|
||||
});
|
||||
return grid(...obj.rows.filter(Boolean).map((cols) => {
|
||||
return row(
|
||||
...cols.filter(Boolean).map((items: any[]) => {
|
||||
console.log({items: items.filter(Boolean)})
|
||||
return column(...items.filter(Boolean).map(item => block(item)));
|
||||
}),
|
||||
);
|
||||
}));
|
||||
}
|
@ -3,7 +3,6 @@ import React, { createContext, useContext } from 'react';
|
||||
export * from './form';
|
||||
export * from './descriptions';
|
||||
export * from './table';
|
||||
export * from './grid';
|
||||
|
||||
export const BlockContext = createContext({});
|
||||
|
||||
|
@ -11,3 +11,9 @@ group:
|
||||
|
||||
# Kanban - 看板
|
||||
|
||||
## 代码演示
|
||||
|
||||
### 基本使用
|
||||
|
||||
### 设计器模式
|
||||
|
||||
|
@ -11,6 +11,11 @@ group:
|
||||
|
||||
# Table - 表格
|
||||
|
||||
## 示例
|
||||
## 代码演示
|
||||
|
||||
### 基本使用
|
||||
|
||||
<code src="./demos/demo1.tsx"/>
|
||||
|
||||
### 设计器模式
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Layout, Menu, Button, Dropdown } from 'antd';
|
||||
import { Layout, Menu, Button, Dropdown, Modal } from 'antd';
|
||||
import {
|
||||
AppstoreOutlined,
|
||||
MenuOutlined,
|
||||
@ -132,7 +132,12 @@ export function AddNewAction(props) {
|
||||
);
|
||||
|
||||
return (
|
||||
<Dropdown overlay={menu}>{props.children || <PlusOutlined />}</Dropdown>
|
||||
<Dropdown
|
||||
trigger={['click']}
|
||||
overlay={menu}
|
||||
>
|
||||
{props.children || <PlusOutlined />}
|
||||
</Dropdown>
|
||||
);
|
||||
}
|
||||
|
||||
@ -179,14 +184,26 @@ export function SettingAction(props) {
|
||||
<Menu.Item key="move">
|
||||
<ArrowRightOutlined /> 移动到
|
||||
</Menu.Item>
|
||||
<Menu.Item onClick={() => remove()} key="delete">
|
||||
<Menu.Item
|
||||
onClick={() => {
|
||||
Modal.confirm({
|
||||
title: '确定菜单',
|
||||
content: '确认删除此菜单项吗?',
|
||||
onOk: remove,
|
||||
});
|
||||
}}
|
||||
key="delete"
|
||||
>
|
||||
<DeleteOutlined /> 删除菜单
|
||||
</Menu.Item>
|
||||
</Menu>
|
||||
);
|
||||
|
||||
return (
|
||||
<Dropdown overlay={menu}>
|
||||
<Dropdown
|
||||
trigger={['click']}
|
||||
overlay={menu}
|
||||
>
|
||||
<MenuOutlined />
|
||||
</Dropdown>
|
||||
);
|
||||
|
@ -41,4 +41,18 @@
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ant-menu-horizontal {
|
||||
.menu-icons {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
background: #1890ff;
|
||||
padding-left: 10px;
|
||||
height: auto;
|
||||
line-height: 1rem;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
transition: all 0.3s ease-in;
|
||||
}
|
||||
}
|
@ -19,7 +19,6 @@ export function RouteSwitch(props: RouteSwitchProps) {
|
||||
<Switch>
|
||||
{routes.map((route, key) => {
|
||||
if (route.type === 'redirect') {
|
||||
console.log(route);
|
||||
return (
|
||||
<Redirect
|
||||
key={key}
|
||||
|
20
packages/client/src/fields/block/index.tsx
Normal file
20
packages/client/src/fields/block/index.tsx
Normal file
@ -0,0 +1,20 @@
|
||||
import React from 'react';
|
||||
import { connect, mapProps, mapReadPretty, useField } from '@formily/react';
|
||||
import { Card } from 'antd';
|
||||
|
||||
function Test(props) {
|
||||
return (
|
||||
<Card>Block</Card>
|
||||
)
|
||||
}
|
||||
|
||||
export const Block = connect(Test, mapProps(
|
||||
(props, field) => {
|
||||
// console.log({ props, field });
|
||||
return {
|
||||
...props,
|
||||
};
|
||||
},
|
||||
));
|
||||
|
||||
export default Block;
|
@ -102,4 +102,16 @@ export default () => {
|
||||
<Field schema={schema} data={data} />
|
||||
);
|
||||
};
|
||||
```
|
||||
```
|
||||
|
||||
## 参数说明
|
||||
|
||||
### dataRequest vs dataSource
|
||||
|
||||
- dataSource 静态选项,value 为 string 或 number。
|
||||
- dataRequest 动态选项,value 为 string 或者 number 时,需要异步获取 label;也可以是 object,无需异步获取数据。
|
||||
|
||||
### 常用配置项
|
||||
|
||||
- changeOnSelect:当此项为 true 时,点选每级菜单选项值都会发生变化
|
||||
- maxLevel:dataRequest 参数,省市区字段时可以用于配置最大选中级别
|
@ -19,7 +19,7 @@ export const Checkbox: ComposedCheckbox = connect(
|
||||
onInput: 'onChange',
|
||||
},
|
||||
(props, field) => {
|
||||
console.log({ props, field });
|
||||
// console.log({ props, field });
|
||||
return {
|
||||
...props,
|
||||
};
|
||||
|
@ -112,3 +112,17 @@ export default () => {
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
## 参数说明
|
||||
|
||||
### dateFormat
|
||||
|
||||
日期格式
|
||||
|
||||
### showTime
|
||||
|
||||
是否显示时间
|
||||
|
||||
### timeFormat
|
||||
|
||||
时间格式
|
||||
|
@ -1,6 +1,15 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import React, { createContext, useMemo, useState, useContext } from 'react';
|
||||
import { Form, FormItem, FormLayout, Space } from '@formily/antd';
|
||||
import { FormProvider, createSchemaField } from '@formily/react';
|
||||
import {
|
||||
FormProvider,
|
||||
createSchemaField,
|
||||
useField,
|
||||
useFieldSchema,
|
||||
Schema,
|
||||
useForm,
|
||||
} from '@formily/react';
|
||||
import set from 'lodash/set';
|
||||
import { uid } from '@formily/shared';
|
||||
import { createForm } from '@formily/core';
|
||||
import * as core from '@formily/core';
|
||||
import { Select } from './select';
|
||||
@ -17,6 +26,8 @@ import { Markdown } from './markdown';
|
||||
import { Password } from './password';
|
||||
import { Radio } from './radio';
|
||||
import { TimePicker } from './time-picker';
|
||||
import { Block } from './block';
|
||||
import { Grid } from '../blocks/grid/Grid';
|
||||
|
||||
export function fields2properties(fields: any) {
|
||||
const items = Array.isArray(fields) ? fields : [fields];
|
||||
@ -50,23 +61,26 @@ const fieldComponents = {
|
||||
Radio,
|
||||
Space,
|
||||
TimePicker,
|
||||
Block,
|
||||
Grid,
|
||||
Column: ({ children }) => children,
|
||||
Blank: ({ children }) => children,
|
||||
};
|
||||
|
||||
const fieldScope = {};
|
||||
|
||||
export function registerFieldComponents(components) {
|
||||
Object.keys(components).forEach(displayName => {
|
||||
Object.keys(components).forEach((displayName) => {
|
||||
fieldComponents[displayName] = components[displayName];
|
||||
});
|
||||
}
|
||||
|
||||
export function getFieldComponent(displayName: string) {
|
||||
return fieldComponents[displayName]
|
||||
return fieldComponents[displayName];
|
||||
}
|
||||
|
||||
export function registerFieldScope(scopes) {
|
||||
Object.keys(scopes).forEach(name => {
|
||||
Object.keys(scopes).forEach((name) => {
|
||||
fieldScope[name] = scopes[name];
|
||||
});
|
||||
}
|
||||
@ -76,6 +90,300 @@ export const SchemaField = createSchemaField({
|
||||
scope: fieldScope,
|
||||
});
|
||||
|
||||
export const SchemaDesignerContext = createContext<Schema>(new Schema({}));
|
||||
export const SchemaRefreshContext = createContext(null);
|
||||
|
||||
export function SchemaFieldWithDesigner(props) {
|
||||
function Container(props) {
|
||||
const { schema } = props;
|
||||
const [, refresh] = useState(0);
|
||||
return (
|
||||
<SchemaRefreshContext.Provider
|
||||
value={() => {
|
||||
refresh(Math.random());
|
||||
}}
|
||||
>
|
||||
<SchemaDesignerContext.Provider value={schema}>
|
||||
<SchemaField schema={schema} />
|
||||
</SchemaDesignerContext.Provider>
|
||||
</SchemaRefreshContext.Provider>
|
||||
);
|
||||
}
|
||||
return <Container schema={new Schema(props.schema)} />;
|
||||
}
|
||||
|
||||
export const getFullPaths = (schema: Schema) => {
|
||||
if (!schema) {
|
||||
return [];
|
||||
}
|
||||
const paths = [schema.name];
|
||||
if (schema.parent && schema.parent.name) {
|
||||
paths.unshift(...getFullPaths(schema.parent));
|
||||
}
|
||||
return paths;
|
||||
};
|
||||
|
||||
export function useSchema() {
|
||||
const context = useContext(SchemaDesignerContext);
|
||||
const refresh = useContext(SchemaRefreshContext);
|
||||
const fieldSchema = useFieldSchema();
|
||||
const paths = getFullPaths(fieldSchema);
|
||||
let schema: Schema = context;
|
||||
const names = [...paths];
|
||||
while (names.length) {
|
||||
schema = schema.properties[names.shift()];
|
||||
}
|
||||
return { schema, fieldSchema, refresh };
|
||||
}
|
||||
|
||||
export function getSchema(context) {
|
||||
return (paths) => {
|
||||
const fullPaths = Array.isArray(paths) ? paths : getFullPaths(paths);
|
||||
let s: Schema = context;
|
||||
const names = [...fullPaths];
|
||||
while (names.length) {
|
||||
s = s.properties[names.shift()];
|
||||
}
|
||||
return s;
|
||||
};
|
||||
}
|
||||
|
||||
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() {
|
||||
const context = useContext(SchemaDesignerContext);
|
||||
const form = useForm();
|
||||
const { schema, refresh } = useSchema();
|
||||
return {
|
||||
removeBlock() {
|
||||
schema.parent.removeProperty(schema.name);
|
||||
refresh();
|
||||
},
|
||||
addBlock: (data, up = false) => {
|
||||
const blockSchema = block({
|
||||
type: 'string',
|
||||
title: `Block ${uid()}`,
|
||||
required: true,
|
||||
'x-read-pretty': false,
|
||||
// 'x-decorator': 'FormItem',
|
||||
'x-component': 'Hello',
|
||||
rowOrder: 2,
|
||||
columnOrder: 1,
|
||||
blockOrder: 1,
|
||||
});
|
||||
if ('Grid.Column' === schema.parent['x-component']) {
|
||||
if (Object.keys(schema.parent.parent.properties).length > 1) {
|
||||
up
|
||||
? addPropertyBefore(schema, blockSchema)
|
||||
: addPropertyAfter(schema, blockSchema);
|
||||
} else {
|
||||
const rowSchema = row(column(blockSchema));
|
||||
up
|
||||
? addPropertyAfter(schema.parent.parent, rowSchema)
|
||||
: addPropertyAfter(schema.parent.parent, rowSchema);
|
||||
}
|
||||
}
|
||||
console.log('x-component', schema.parent['x-component']);
|
||||
refresh();
|
||||
// schema.parent['x-component']
|
||||
},
|
||||
insertAfter: (sourcePath, targetPath) => {
|
||||
const source = getSchema(context)(sourcePath);
|
||||
const target = getSchema(context)(targetPath);
|
||||
if (!source || !target) {
|
||||
return;
|
||||
}
|
||||
console.log({
|
||||
sourcePath,
|
||||
source,
|
||||
target,
|
||||
targetPath,
|
||||
sourceParentproperties: source.parent.properties,
|
||||
});
|
||||
const names = [];
|
||||
Object.keys(target.parent.properties).forEach((name) => {
|
||||
// if (names.includes(source.name)) {
|
||||
// return;
|
||||
// }
|
||||
// names.push(name);
|
||||
const property = target.parent.properties[name];
|
||||
property.parent.removeProperty(property.name);
|
||||
target.parent.addProperty(property.name, property.toJSON());
|
||||
if (name === target.name) {
|
||||
// names.push(source.name);
|
||||
source.parent.removeProperty(source.name);
|
||||
console.log('source.parent.properties', source.parent.properties);
|
||||
target.parent.addProperty(source.name, source.toJSON());
|
||||
}
|
||||
});
|
||||
},
|
||||
insertAfterWithAddRow: (sourcePath, targetPath) => {
|
||||
const source = getSchema(context)(sourcePath);
|
||||
const target = getSchema(context)(targetPath);
|
||||
const rowSchema = row(column(source.toJSON()));
|
||||
source.parent.removeProperty(source.name);
|
||||
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(rowSchema.name, rowSchema);
|
||||
}
|
||||
});
|
||||
},
|
||||
insertBeforeWithAddRow: (sourcePath, targetPath) => {
|
||||
const source = getSchema(context)(sourcePath);
|
||||
const target = getSchema(context)(targetPath);
|
||||
const rowSchema = row(column(source.toJSON()));
|
||||
source.parent.removeProperty(source.name);
|
||||
Object.keys(target.parent.properties).forEach((name) => {
|
||||
if (name === target.name) {
|
||||
target.parent.addProperty(rowSchema.name, rowSchema);
|
||||
}
|
||||
const property = target.parent.properties[name];
|
||||
property.parent.removeProperty(property.name);
|
||||
target.parent.addProperty(property.name, property.toJSON());
|
||||
});
|
||||
},
|
||||
appendToRowWithAddColumn: (sourcePath, targetPath) => {
|
||||
const source = getSchema(context)(sourcePath);
|
||||
const target = getSchema(context)(targetPath);
|
||||
const colSchema = column(source.toJSON());
|
||||
source.parent.removeProperty(source.name);
|
||||
const len = Object.keys(target.properties).length + 1;
|
||||
target.addProperty(colSchema.name, colSchema);
|
||||
console.log('target.properties', target.properties);
|
||||
Object.keys(target.properties).forEach((name) => {
|
||||
const prop = target.properties[name];
|
||||
form.setFieldState(getFullPaths(prop).join('.'), (state) => {
|
||||
state.componentProps = {
|
||||
span: 24 / len,
|
||||
};
|
||||
});
|
||||
});
|
||||
},
|
||||
insertBeforeWithAddColumn: (sourcePath, targetPath) => {
|
||||
const source = getSchema(context)(sourcePath);
|
||||
const target = getSchema(context)(targetPath);
|
||||
const colSchema = column(source.toJSON());
|
||||
source.parent.removeProperty(source.name);
|
||||
const len = Object.keys(target.parent.properties).length + 1;
|
||||
console.log('x-component-props.span', 24 / len);
|
||||
Object.keys(target.parent.properties).forEach((name) => {
|
||||
if (name === target.name) {
|
||||
target.parent.addProperty(colSchema.name, colSchema);
|
||||
}
|
||||
const property = target.parent.properties[name];
|
||||
property.parent.removeProperty(property.name);
|
||||
const json = property.toJSON();
|
||||
target.parent.addProperty(property.name, json);
|
||||
});
|
||||
Object.keys(target.parent.properties).forEach((name) => {
|
||||
const prop = target.parent.properties[name];
|
||||
form.setFieldState(getFullPaths(prop).join('.'), (state) => {
|
||||
state.componentProps = {
|
||||
span: 24 / len,
|
||||
};
|
||||
});
|
||||
});
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export const grid = (...rows: any[]) => {
|
||||
const rowProperties = {};
|
||||
rows.forEach((row, index) => {
|
||||
set(row, 'x-component-props.rowOrder', index);
|
||||
rowProperties[row.name] = row;
|
||||
});
|
||||
const name = `g_${uid()}`;
|
||||
return {
|
||||
type: 'void',
|
||||
name,
|
||||
'x-component': 'Grid',
|
||||
'x-component-props': {},
|
||||
properties: rowProperties,
|
||||
};
|
||||
};
|
||||
|
||||
export const row = (...cols: any[]) => {
|
||||
const rowName = `r_${uid()}`;
|
||||
const colsProperties = {};
|
||||
cols.forEach((col, index) => {
|
||||
set(col, 'x-component-props.columnOrder', index);
|
||||
set(col, 'x-component-props.span', 24 / cols.length);
|
||||
colsProperties[col.name] = col;
|
||||
});
|
||||
return {
|
||||
type: 'void',
|
||||
name: rowName,
|
||||
'x-component': 'Grid.Row',
|
||||
'x-component-props': {},
|
||||
properties: colsProperties,
|
||||
};
|
||||
};
|
||||
|
||||
export const column = (...blocks: any[]) => {
|
||||
const colName = `c_${uid()}`;
|
||||
const properties = {};
|
||||
blocks.forEach((item) => {
|
||||
properties[item.name] = item;
|
||||
});
|
||||
return {
|
||||
name: colName,
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Column',
|
||||
'x-read-pretty': true,
|
||||
'x-component-props': {
|
||||
labelCol: 6,
|
||||
wrapperCol: 10,
|
||||
span: 24,
|
||||
},
|
||||
properties,
|
||||
};
|
||||
};
|
||||
|
||||
export const block = (...fields: any[]) => {
|
||||
const blockName = `b_${uid()}`;
|
||||
const properties = {};
|
||||
fields.forEach((item) => {
|
||||
const name = item.name || `f_${uid()}`;
|
||||
// item.title = `${item.title} ${name}`;
|
||||
properties[name] = item;
|
||||
});
|
||||
const lastComponentType = fields[fields.length - 1]['x-component'];
|
||||
return {
|
||||
name: blockName,
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Block',
|
||||
'x-component-props': {
|
||||
lastComponentType,
|
||||
},
|
||||
'x-read-pretty': false,
|
||||
properties,
|
||||
};
|
||||
};
|
||||
|
||||
export function parseEffects(effects: any, form?: any) {
|
||||
if (!effects) {
|
||||
return;
|
||||
@ -102,15 +410,17 @@ export interface FieldProps {
|
||||
|
||||
export function Field(props: FieldProps) {
|
||||
const { schema, readPretty, data, effects } = props;
|
||||
const form = props.form || useMemo(
|
||||
() =>
|
||||
createForm({
|
||||
initialValues: data,
|
||||
readPretty,
|
||||
effects: (form) => parseEffects(effects, form),
|
||||
}),
|
||||
[],
|
||||
);
|
||||
const form =
|
||||
props.form ||
|
||||
useMemo(
|
||||
() =>
|
||||
createForm({
|
||||
initialValues: data,
|
||||
readPretty,
|
||||
effects: (form) => parseEffects(effects, form),
|
||||
}),
|
||||
[],
|
||||
);
|
||||
return (
|
||||
<div>
|
||||
<FormProvider form={form}>
|
||||
|
@ -280,4 +280,15 @@ export default () => {
|
||||
|
||||
## 参数说明
|
||||
|
||||
输入框组件暂时还没有特殊的参数,其他通用参数参考 [Field 章节](fields)。
|
||||
输入框组件暂时还没有特殊的参数,其他通用参数参考 [Field 章节](fields)。
|
||||
|
||||
常用配置参数:
|
||||
|
||||
- type
|
||||
- title
|
||||
- name
|
||||
- required
|
||||
- default
|
||||
- x-component-props.placeholder
|
||||
- x-decorator-props.description
|
||||
- x-validator
|
||||
|
@ -38,7 +38,7 @@ function Menu() {
|
||||
<ul>
|
||||
{data.map((item, index) => (
|
||||
<li key={index}>
|
||||
<Link to={item.name}>{item.title}</Link>
|
||||
<Link to={`/admin/${item.name}`}>{item.title}</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
|
@ -2,6 +2,7 @@ import React, { useContext, useEffect, useState } from 'react';
|
||||
import { Spin } from 'antd';
|
||||
import { Helmet } from 'react-helmet';
|
||||
import { usePage, BlockContext } from '../../';
|
||||
import { useRequest } from 'ahooks';
|
||||
|
||||
export function PageTemplate({ route }) {
|
||||
const { GridBlock } = useContext<any>(BlockContext);
|
||||
@ -9,11 +10,14 @@ export function PageTemplate({ route }) {
|
||||
useEffect(() => {
|
||||
setPreName(route.name);
|
||||
}, [route.name]);
|
||||
const { data = {}, loading } = usePage(route.name);
|
||||
const { data = {}, loading } = useRequest(`/api/routes:getPage?name=${route.name}`, {
|
||||
// refreshDeps: [route.name],
|
||||
});
|
||||
console.log(data.title, { loading, preName, blocks: data.blocks });
|
||||
if (loading || preName !== route.name) {
|
||||
return <Spin />;
|
||||
}
|
||||
console.log(data.title, { preName, blocks: data.blocks });
|
||||
console.log(data.title, { loading, preName, blocks: data.blocks });
|
||||
return (
|
||||
<div>
|
||||
<Helmet>
|
||||
|
Loading…
Reference in New Issue
Block a user