updates
This commit is contained in:
parent
46e4089140
commit
8b34b84a5e
329
packages/client/src/blocks/DesignableSchemaField/index.tsx
Normal file
329
packages/client/src/blocks/DesignableSchemaField/index.tsx
Normal file
@ -0,0 +1,329 @@
|
||||
import React, {
|
||||
createContext,
|
||||
useCallback,
|
||||
useContext,
|
||||
useMemo,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { createForm } from '@formily/core';
|
||||
import {
|
||||
Field,
|
||||
ISchema,
|
||||
observer,
|
||||
Schema,
|
||||
createSchemaField,
|
||||
FormProvider,
|
||||
useField,
|
||||
useFieldSchema,
|
||||
} from '@formily/react';
|
||||
import { observable } from '@formily/reactive';
|
||||
import { uid, clone } from '@formily/shared';
|
||||
import { ArrayCollapse, ArrayTable, FormLayout } from '@formily/antd';
|
||||
|
||||
import { Space, Card } from 'antd';
|
||||
import { Action, useLogin, useRegister, useSubmit } from '../action';
|
||||
import { AddNew } from '../add-new';
|
||||
import { Cascader } from '../cascader';
|
||||
import { Checkbox } from '../checkbox';
|
||||
import { ColorSelect } from '../color-select';
|
||||
import { DatabaseField } from '../database-field';
|
||||
import { DatePicker } from '../date-picker';
|
||||
import { DrawerSelect } from '../drawer-select';
|
||||
import { Filter } from '../filter';
|
||||
import { Form } from '../form';
|
||||
import { Grid } from '../grid';
|
||||
import { IconPicker } from '../icon-picker';
|
||||
import { Input } from '../input';
|
||||
import { InputNumber } from '../input-number';
|
||||
import { Markdown } from '../markdown';
|
||||
import { Menu } from '../menu';
|
||||
import { Password } from '../password';
|
||||
import { Radio } from '../radio';
|
||||
import { Select } from '../select';
|
||||
import { Table } from '../table';
|
||||
import { Tabs } from '../tabs';
|
||||
import { TimePicker } from '../time-picker';
|
||||
import { Upload } from '../upload';
|
||||
import { FormItem } from '../form-item';
|
||||
|
||||
export const BlockContext = createContext({ dragRef: null });
|
||||
|
||||
const Div = (props) => <div {...props} />;
|
||||
|
||||
export const scope = {
|
||||
useLogin,
|
||||
useRegister,
|
||||
useSubmit,
|
||||
};
|
||||
|
||||
export const components = {
|
||||
Div,
|
||||
Space,
|
||||
Card,
|
||||
|
||||
ArrayCollapse,
|
||||
ArrayTable,
|
||||
FormLayout,
|
||||
FormItem,
|
||||
|
||||
Action,
|
||||
AddNew,
|
||||
Cascader,
|
||||
Checkbox,
|
||||
ColorSelect,
|
||||
DatabaseField,
|
||||
DatePicker,
|
||||
DrawerSelect,
|
||||
Filter,
|
||||
Form,
|
||||
Grid,
|
||||
IconPicker,
|
||||
Input,
|
||||
InputNumber,
|
||||
Markdown,
|
||||
Menu,
|
||||
Password,
|
||||
Radio,
|
||||
Select,
|
||||
Table,
|
||||
Tabs,
|
||||
TimePicker,
|
||||
Upload,
|
||||
};
|
||||
|
||||
export function registerScope(scopes) {
|
||||
Object.keys(scopes).forEach((key) => {
|
||||
scope[key] = scopes[key];
|
||||
});
|
||||
}
|
||||
|
||||
export function registerComponents(values) {
|
||||
Object.keys(values).forEach((key) => {
|
||||
components[key] = values[key];
|
||||
});
|
||||
}
|
||||
|
||||
export interface DesignableContextProps {
|
||||
schema: Schema;
|
||||
refresh: () => void;
|
||||
}
|
||||
|
||||
export const DesignableContext = createContext<DesignableContextProps>({
|
||||
schema: null,
|
||||
refresh: null,
|
||||
});
|
||||
|
||||
export function pathToArray(path): string[] {
|
||||
if (Array.isArray(path)) {
|
||||
return [...path];
|
||||
}
|
||||
|
||||
if (typeof path === 'string') {
|
||||
return path.split('.');
|
||||
}
|
||||
}
|
||||
|
||||
export function findPropertyByPath(schema: Schema, path?: any): Schema {
|
||||
if (!path) {
|
||||
return schema;
|
||||
}
|
||||
const arr = pathToArray(path);
|
||||
let property = schema;
|
||||
while (arr.length) {
|
||||
const name = arr.shift();
|
||||
property = property.properties[name];
|
||||
if (!property) {
|
||||
console.error('property does not exist.');
|
||||
break;
|
||||
}
|
||||
}
|
||||
return property;
|
||||
}
|
||||
|
||||
export function addPropertyBefore(target: Schema, data: ISchema) {
|
||||
Object.keys(target.parent.properties).forEach((name) => {
|
||||
if (name === target.name) {
|
||||
target.parent.addProperty(data.name, data);
|
||||
}
|
||||
const property = target.parent.properties[name];
|
||||
property.parent.removeProperty(property.name);
|
||||
target.parent.addProperty(property.name, property.toJSON());
|
||||
});
|
||||
}
|
||||
|
||||
export function addPropertyAfter(target: Schema, data: ISchema) {
|
||||
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(data.name, data);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function useDesignable(path?: any) {
|
||||
const { schema, refresh } = useContext(DesignableContext);
|
||||
const schemaPath = path || useSchemaPath();
|
||||
const currentSchema = findPropertyByPath(schema, schemaPath);
|
||||
console.log('useDesignable', { schema, schemaPath, currentSchema });
|
||||
return {
|
||||
schema: currentSchema,
|
||||
refresh,
|
||||
appendChild: (property: ISchema, targetPath?: any): Schema => {
|
||||
let target = currentSchema;
|
||||
if (targetPath) {
|
||||
target = findPropertyByPath(schema, targetPath);
|
||||
}
|
||||
if (!target) {
|
||||
console.error('target schema does not exist.');
|
||||
return;
|
||||
}
|
||||
if (!property.name) {
|
||||
property.name = uid();
|
||||
}
|
||||
target.addProperty(property.name, property);
|
||||
// BUG: 空 properties 时,addProperty 无反应。
|
||||
const tmp = { name: uid() };
|
||||
addPropertyAfter(target, tmp);
|
||||
target.parent.removeProperty(tmp.name);
|
||||
refresh();
|
||||
return target.properties[property.name];
|
||||
},
|
||||
insertAfter: (property: ISchema, targetPath?: any): Schema => {
|
||||
let target = currentSchema;
|
||||
if (targetPath) {
|
||||
target = findPropertyByPath(schema, targetPath);
|
||||
}
|
||||
if (!target) {
|
||||
console.error('target schema does not exist.');
|
||||
return;
|
||||
}
|
||||
if (!property.name) {
|
||||
property.name = uid();
|
||||
}
|
||||
addPropertyAfter(target, property);
|
||||
refresh();
|
||||
return target.parent.properties[property.name];
|
||||
},
|
||||
insertBefore(property: ISchema, targetPath?: any): Schema {
|
||||
let target = currentSchema;
|
||||
if (targetPath) {
|
||||
target = findPropertyByPath(schema, targetPath);
|
||||
}
|
||||
if (!target) {
|
||||
console.error('target schema does not exist.');
|
||||
return;
|
||||
}
|
||||
if (!property.name) {
|
||||
property.name = uid();
|
||||
}
|
||||
addPropertyBefore(target, property);
|
||||
refresh();
|
||||
return target.parent.properties[property.name];
|
||||
},
|
||||
remove(targetPath?: any) {
|
||||
let target = currentSchema;
|
||||
if (targetPath) {
|
||||
target = findPropertyByPath(schema, targetPath);
|
||||
}
|
||||
if (!target) {
|
||||
console.error('target schema does not exist.');
|
||||
return;
|
||||
}
|
||||
target.parent.removeProperty(target.name);
|
||||
refresh();
|
||||
return target;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function useSchemaPath() {
|
||||
const schema = useFieldSchema();
|
||||
const path = [schema.name];
|
||||
let parent = schema.parent;
|
||||
while (parent) {
|
||||
if (!parent.name) {
|
||||
break;
|
||||
}
|
||||
path.unshift(parent.name);
|
||||
parent = parent.parent;
|
||||
}
|
||||
return [...path];
|
||||
}
|
||||
|
||||
console.log({ scope, components });
|
||||
|
||||
export const createDesignableSchemaField = (options) => {
|
||||
const SchemaField = createSchemaField(options);
|
||||
|
||||
const DesignableSchemaField = (props) => {
|
||||
const schema = useMemo(() => new Schema(props.schema), [props.schema]);
|
||||
const [, refresh] = useState(0);
|
||||
if (props.designable === false) {
|
||||
return <SchemaField schema={schema} />;
|
||||
}
|
||||
return (
|
||||
<DesignableContext.Provider
|
||||
value={{
|
||||
schema,
|
||||
refresh: () => {
|
||||
refresh(Math.random());
|
||||
props.onRefresh && props.onRefresh(schema);
|
||||
},
|
||||
}}
|
||||
>
|
||||
<SchemaField schema={schema} />
|
||||
</DesignableContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
return DesignableSchemaField;
|
||||
};
|
||||
|
||||
export const DesignableSchemaField = createDesignableSchemaField({
|
||||
scope,
|
||||
components,
|
||||
});
|
||||
|
||||
export interface SchemaRendererProps {
|
||||
schema: ISchema;
|
||||
form?: any;
|
||||
designable?: boolean;
|
||||
onRefresh?: any;
|
||||
onlyRenderProperties?: boolean;
|
||||
}
|
||||
|
||||
export const SchemaRenderer = (props: SchemaRendererProps) => {
|
||||
const form = useMemo(() => props.form || createForm({}), []);
|
||||
|
||||
const schema = useMemo(() => {
|
||||
let s = props.schema;
|
||||
if (props.onlyRenderProperties) {
|
||||
s = {
|
||||
type: 'object',
|
||||
properties: s.properties,
|
||||
};
|
||||
} else if (s.name) {
|
||||
s = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
[s.name]: s,
|
||||
},
|
||||
};
|
||||
}
|
||||
return s;
|
||||
}, []);
|
||||
|
||||
console.log('SchemaRenderer', schema, props.schema);
|
||||
|
||||
return (
|
||||
<FormProvider form={form}>
|
||||
<DesignableSchemaField
|
||||
onRefresh={props.onRefresh}
|
||||
designable={props.designable}
|
||||
schema={schema}
|
||||
/>
|
||||
</FormProvider>
|
||||
);
|
||||
};
|
@ -47,13 +47,13 @@ export const BlockContext = createContext({ dragRef: null });
|
||||
|
||||
const Div = (props) => <div {...props} />;
|
||||
|
||||
const scope = {
|
||||
export const scope = {
|
||||
useLogin,
|
||||
useRegister,
|
||||
useSubmit,
|
||||
};
|
||||
|
||||
const components = {
|
||||
export const components = {
|
||||
Div,
|
||||
Space,
|
||||
Card,
|
||||
|
@ -27,7 +27,7 @@ group:
|
||||
* desc: 可以通过配置 `useAction` 来处理操作逻辑
|
||||
*/
|
||||
import React from 'react';
|
||||
import { SchemaBlock, registerScope } from '../';
|
||||
import { SchemaRenderer, registerScope } from '../';
|
||||
|
||||
function useCustomAction() {
|
||||
return {
|
||||
@ -52,7 +52,7 @@ const schema = {
|
||||
};
|
||||
|
||||
export default () => {
|
||||
return <SchemaBlock schema={schema} />
|
||||
return <SchemaRenderer schema={schema} />
|
||||
}
|
||||
```
|
||||
|
||||
@ -60,7 +60,7 @@ export default () => {
|
||||
|
||||
```tsx
|
||||
import React from 'react';
|
||||
import { SchemaBlock } from '../';
|
||||
import { SchemaRenderer } from '../';
|
||||
|
||||
const schema = {
|
||||
type: 'void',
|
||||
@ -73,7 +73,7 @@ const schema = {
|
||||
};
|
||||
|
||||
export default () => {
|
||||
return <SchemaBlock schema={schema} />
|
||||
return <SchemaRenderer schema={schema} />
|
||||
}
|
||||
```
|
||||
|
||||
@ -81,7 +81,7 @@ export default () => {
|
||||
|
||||
```tsx
|
||||
import React from 'react';
|
||||
import { SchemaBlock } from '../';
|
||||
import { SchemaRenderer } from '../';
|
||||
|
||||
const schema = {
|
||||
type: 'void',
|
||||
@ -94,7 +94,7 @@ const schema = {
|
||||
};
|
||||
|
||||
export default () => {
|
||||
return <SchemaBlock schema={schema} />
|
||||
return <SchemaRenderer schema={schema} />
|
||||
}
|
||||
```
|
||||
|
||||
@ -102,7 +102,7 @@ export default () => {
|
||||
|
||||
```tsx
|
||||
import React from 'react';
|
||||
import { SchemaBlock } from '../';
|
||||
import { SchemaRenderer } from '../';
|
||||
|
||||
const schema = {
|
||||
type: 'void',
|
||||
@ -110,7 +110,7 @@ const schema = {
|
||||
title: '按钮',
|
||||
'x-component': 'Action',
|
||||
properties: {
|
||||
drawer1: {
|
||||
popover1: {
|
||||
type: 'void',
|
||||
title: '弹窗标题',
|
||||
'x-component': 'Action.Popover',
|
||||
@ -126,7 +126,7 @@ const schema = {
|
||||
};
|
||||
|
||||
export default () => {
|
||||
return <SchemaBlock schema={schema} />
|
||||
return <SchemaRenderer schema={schema} />
|
||||
}
|
||||
```
|
||||
|
||||
@ -134,7 +134,7 @@ export default () => {
|
||||
|
||||
```tsx
|
||||
import React from 'react';
|
||||
import { SchemaBlock } from '../';
|
||||
import { SchemaRenderer } from '../';
|
||||
|
||||
const schema = {
|
||||
type: 'void',
|
||||
@ -179,7 +179,7 @@ const schema = {
|
||||
};
|
||||
|
||||
export default () => {
|
||||
return <SchemaBlock schema={schema} />
|
||||
return <SchemaRenderer schema={schema} />
|
||||
}
|
||||
```
|
||||
|
||||
@ -187,7 +187,7 @@ export default () => {
|
||||
|
||||
```tsx
|
||||
import React from 'react';
|
||||
import { SchemaBlock } from '../';
|
||||
import { SchemaRenderer } from '../';
|
||||
|
||||
const schema = {
|
||||
type: 'void',
|
||||
@ -207,15 +207,16 @@ const schema = {
|
||||
};
|
||||
|
||||
export default () => {
|
||||
return <SchemaBlock schema={schema} />
|
||||
return <SchemaRenderer schema={schema} />
|
||||
}
|
||||
```
|
||||
|
||||
## Action.Container - 指定容器内打开
|
||||
|
||||
```tsx
|
||||
import React from 'react';
|
||||
import { SchemaBlock } from '../';
|
||||
import React, { useRef } from 'react';
|
||||
import { SchemaRenderer } from '../';
|
||||
import { ActionContext } from './';
|
||||
|
||||
const schema = {
|
||||
type: 'void',
|
||||
@ -223,17 +224,15 @@ const schema = {
|
||||
title: '按钮',
|
||||
'x-component': 'Action',
|
||||
properties: {
|
||||
drawer1: {
|
||||
container1: {
|
||||
type: 'void',
|
||||
title: '页面标题',
|
||||
'x-component': 'Action.Container',
|
||||
'x-component-props': {
|
||||
container: '#container'
|
||||
},
|
||||
properties: {
|
||||
input: {
|
||||
type: 'string',
|
||||
title: '字段',
|
||||
'x-designable-bar': 'FormItem.DesignableBar',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input',
|
||||
}
|
||||
@ -243,11 +242,17 @@ const schema = {
|
||||
};
|
||||
|
||||
export default () => {
|
||||
const ref = useRef();
|
||||
console.log('containerRef2222', ref)
|
||||
return (
|
||||
<div>
|
||||
<SchemaBlock schema={schema} />
|
||||
<ActionContext.Provider value={{
|
||||
containerRef: ref,
|
||||
}}>
|
||||
<SchemaRenderer schema={schema} />
|
||||
</ActionContext.Provider>
|
||||
<div style={{padding: '8px 0'}}>目标容器:</div>
|
||||
<div id={'container'} style={{ border: '1px dashed #ebedf1', background: '#fafafa', padding: 24 }}/>
|
||||
<div ref={ref} id={'container'} style={{ border: '1px dashed #ebedf1', background: '#fafafa', padding: 24 }}/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@ -257,7 +262,7 @@ export default () => {
|
||||
|
||||
```tsx
|
||||
import React from 'react';
|
||||
import { SchemaBlock } from '../';
|
||||
import { SchemaRenderer } from '../';
|
||||
|
||||
const schema = {
|
||||
type: 'void',
|
||||
@ -303,7 +308,6 @@ const schema = {
|
||||
};
|
||||
|
||||
export default () => {
|
||||
return <SchemaBlock schema={schema} />
|
||||
return <SchemaRenderer schema={schema} />
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -1,12 +1,4 @@
|
||||
import React, { useContext, useState } from 'react';
|
||||
import {
|
||||
Input,
|
||||
FormItem,
|
||||
FormButtonGroup,
|
||||
Submit,
|
||||
Password,
|
||||
} from '@formily/antd';
|
||||
import { createForm } from '@formily/core';
|
||||
import React, { createContext, useContext, useState } from 'react';
|
||||
import {
|
||||
useForm,
|
||||
FormProvider,
|
||||
@ -21,56 +13,16 @@ import {
|
||||
import { Button, Dropdown, Menu, Popover, Space } from 'antd';
|
||||
import { Link, useHistory, LinkProps } from 'react-router-dom';
|
||||
import Drawer from '../../components/Drawer';
|
||||
import { SchemaBlock } from '../';
|
||||
import { SchemaRenderer, useDesignable } from '../';
|
||||
import ReactDOM from 'react-dom';
|
||||
import get from 'lodash/get';
|
||||
import {
|
||||
DesignableSchemaContext,
|
||||
RefreshDesignableSchemaContext,
|
||||
} from '../SchemaField';
|
||||
import {
|
||||
MenuOutlined,
|
||||
GroupOutlined,
|
||||
PlusOutlined,
|
||||
LinkOutlined,
|
||||
AppstoreAddOutlined,
|
||||
EditOutlined,
|
||||
DeleteOutlined,
|
||||
ArrowRightOutlined,
|
||||
SettingOutlined,
|
||||
ArrowUpOutlined,
|
||||
ArrowDownOutlined,
|
||||
LoadingOutlined,
|
||||
} from '@ant-design/icons';
|
||||
import { MenuOutlined } from '@ant-design/icons';
|
||||
import classNames from 'classnames';
|
||||
import { useMount } from 'ahooks';
|
||||
import { uid } from '@formily/shared';
|
||||
|
||||
import './style.less';
|
||||
|
||||
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);
|
||||
|
||||
return {
|
||||
refresh,
|
||||
schema,
|
||||
};
|
||||
}
|
||||
|
||||
export function useDefaultAction() {
|
||||
return {
|
||||
run() {},
|
||||
@ -121,6 +73,8 @@ export type ActionType = React.FC<ActionProps> & {
|
||||
Link?: React.FC<LinkProps>;
|
||||
URL?: React.FC<any>;
|
||||
Page?: React.FC<any>;
|
||||
Container?: React.FC<any>;
|
||||
Popover?: React.FC<any>;
|
||||
Drawer?: React.FC<any>;
|
||||
Modal?: React.FC<any>;
|
||||
Dropdown?: React.FC<any>;
|
||||
@ -141,13 +95,40 @@ function useDesignableBar() {
|
||||
};
|
||||
}
|
||||
|
||||
export const ActionContext = createContext({ containerRef: null });
|
||||
|
||||
export const Action: ActionType = observer((props) => {
|
||||
const { useAction = useDefaultAction, ...others } = props;
|
||||
const { containerRef } = useContext(ActionContext);
|
||||
const field = useField();
|
||||
const { schema } = useSchemaQuery();
|
||||
const schema = useFieldSchema();
|
||||
const { run } = useAction();
|
||||
const { DesignableBar } = useDesignableBar();
|
||||
|
||||
const renderContainer = () => {
|
||||
let childSchema = null;
|
||||
if (schema.properties) {
|
||||
const key = Object.keys(schema.properties).shift();
|
||||
const current = schema.properties[key];
|
||||
childSchema = current;
|
||||
}
|
||||
if (childSchema && childSchema['x-component'] === 'Action.Container') {
|
||||
containerRef &&
|
||||
ReactDOM.render(
|
||||
<div>
|
||||
<SchemaRenderer schema={childSchema} onlyRenderProperties />
|
||||
</div>,
|
||||
containerRef.current,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
useMount(() => {
|
||||
renderContainer();
|
||||
});
|
||||
|
||||
let childSchema = null;
|
||||
|
||||
if (schema.properties) {
|
||||
const key = Object.keys(schema.properties).shift();
|
||||
const current = schema.properties[key];
|
||||
@ -162,7 +143,7 @@ export const Action: ActionType = observer((props) => {
|
||||
{...childSchema['x-component-props']}
|
||||
content={
|
||||
<div>
|
||||
<SchemaBlock schema={childSchema} onlyRenderProperties />
|
||||
<SchemaRenderer schema={childSchema} onlyRenderProperties />
|
||||
</div>
|
||||
}
|
||||
>
|
||||
@ -170,6 +151,7 @@ export const Action: ActionType = observer((props) => {
|
||||
</Popover>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Button
|
||||
{...others}
|
||||
@ -184,25 +166,14 @@ export const Action: ActionType = observer((props) => {
|
||||
content: () => {
|
||||
return (
|
||||
<div>
|
||||
<SchemaBlock schema={childSchema} onlyRenderProperties />
|
||||
<SchemaRenderer schema={childSchema} onlyRenderProperties />
|
||||
</div>
|
||||
);
|
||||
},
|
||||
});
|
||||
}
|
||||
if (childSchema['x-component'] === 'Action.Container') {
|
||||
const el = document.createElement('div');
|
||||
const target = document.querySelector(
|
||||
childSchema['x-component-props']?.['container'],
|
||||
);
|
||||
target.childNodes.forEach((child) => child.remove());
|
||||
target.appendChild(el);
|
||||
ReactDOM.render(
|
||||
<div>
|
||||
<SchemaBlock schema={childSchema} onlyRenderProperties />
|
||||
</div>,
|
||||
el,
|
||||
);
|
||||
renderContainer();
|
||||
}
|
||||
}}
|
||||
>
|
||||
@ -227,9 +198,26 @@ Action.URL = observer((props) => {
|
||||
);
|
||||
});
|
||||
|
||||
Action.Container = ({ children }) => {
|
||||
return children;
|
||||
};
|
||||
|
||||
Action.Popover = ({ children }) => {
|
||||
return children;
|
||||
};
|
||||
|
||||
Action.Drawer = ({ children }) => {
|
||||
return children;
|
||||
};
|
||||
|
||||
Action.Modal = ({ children }) => {
|
||||
return children;
|
||||
};
|
||||
|
||||
Action.DesignableBar = () => {
|
||||
const field = useField();
|
||||
const { schema, refresh } = useSchemaQuery();
|
||||
const schema = useFieldSchema();
|
||||
const { insertAfter } = useDesignable();
|
||||
const [visible, setVisible] = useState(false);
|
||||
return (
|
||||
<div className={classNames('designable-bar', { active: visible })}>
|
||||
@ -253,12 +241,21 @@ Action.DesignableBar = () => {
|
||||
schema.title = '按钮文案被修改了';
|
||||
field.setTitle('按钮文案被修改了');
|
||||
schema.properties.drawer1.title = '抽屉标题文案被修改了';
|
||||
refresh();
|
||||
setVisible(false);
|
||||
}}
|
||||
>
|
||||
点击修改按钮文案
|
||||
</Menu.Item>
|
||||
<Menu.Item
|
||||
onClick={() => {
|
||||
insertAfter({
|
||||
name: uid(),
|
||||
'x-component': 'Input',
|
||||
});
|
||||
}}
|
||||
>
|
||||
insertAfter
|
||||
</Menu.Item>
|
||||
</Menu>
|
||||
}
|
||||
>
|
||||
|
@ -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 <Spin />;
|
||||
}
|
||||
|
||||
console.log({ data });
|
||||
|
||||
return (
|
||||
<Dropdown
|
||||
overlay={
|
||||
<Menu>
|
||||
<Menu.SubMenu
|
||||
key={`table-tables`}
|
||||
style={{ minWidth: 150 }}
|
||||
title={'新建表单'}
|
||||
>
|
||||
<Menu.ItemGroup key={`table-tables-itemgroup`} title={'所属数据表'}>
|
||||
{data.map((item, index) => {
|
||||
return (
|
||||
<Menu.Item
|
||||
key={`table-${index}`}
|
||||
style={{ minWidth: 150 }}
|
||||
onClick={() => {
|
||||
const rowData = row(form());
|
||||
insertBefore(rowData);
|
||||
}}
|
||||
>
|
||||
{item.title}
|
||||
</Menu.Item>
|
||||
);
|
||||
})}
|
||||
</Menu.ItemGroup>
|
||||
<Menu.Divider></Menu.Divider>
|
||||
<Menu.Item
|
||||
style={{ minWidth: 150 }}
|
||||
onClick={() => {
|
||||
FormDialog('新建数据表', () => {
|
||||
return <SchemaField schema={dbschema} />;
|
||||
})
|
||||
.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);
|
||||
}}
|
||||
>
|
||||
新增数据表
|
||||
</Menu.Item>
|
||||
</Menu.SubMenu>
|
||||
<Menu.SubMenu
|
||||
key={`form-tables`}
|
||||
style={{ minWidth: 150 }}
|
||||
title={'新建表格'}
|
||||
>
|
||||
<Menu.ItemGroup key={`form-tables-itemgroup`} title={'所属数据表'}>
|
||||
{data.map((item, index) => {
|
||||
return (
|
||||
<Menu.Item
|
||||
key={`form-${index}`}
|
||||
style={{ minWidth: 150 }}
|
||||
onClick={() => {
|
||||
const rowData = row(table());
|
||||
console.log({ rowData });
|
||||
insertBefore(rowData);
|
||||
}}
|
||||
>
|
||||
{item.title}
|
||||
</Menu.Item>
|
||||
);
|
||||
})}
|
||||
</Menu.ItemGroup>
|
||||
<Menu.Divider></Menu.Divider>
|
||||
<Menu.Item
|
||||
style={{ minWidth: 150 }}
|
||||
onClick={() => {
|
||||
FormDialog('新建数据表', () => {
|
||||
return <SchemaField schema={dbschema} />;
|
||||
})
|
||||
.open({
|
||||
initialValues: {
|
||||
// aaa: '123',
|
||||
},
|
||||
})
|
||||
.then(() => {
|
||||
const items = [...data];
|
||||
items.push({ title: '数据表4' });
|
||||
mutate(items);
|
||||
const rowData = row(table());
|
||||
insertBefore(rowData);
|
||||
});
|
||||
}}
|
||||
>
|
||||
新建数据表
|
||||
</Menu.Item>
|
||||
</Menu.SubMenu>
|
||||
<Menu.Item
|
||||
onClick={() => {
|
||||
const rowData = row(markdown());
|
||||
insertBefore(rowData);
|
||||
}}
|
||||
>
|
||||
新建 Markdown
|
||||
</Menu.Item>
|
||||
<Menu.Item>新建 Markdown</Menu.Item>
|
||||
</Menu>
|
||||
}
|
||||
>
|
||||
@ -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 (
|
||||
<Dropdown
|
||||
overlay={
|
||||
<Menu>
|
||||
<Menu.Item onClick={insertBeforeHandle} style={{ minWidth: 150 }}>
|
||||
字段1
|
||||
</Menu.Item>
|
||||
<Menu.Item onClick={insertBeforeHandle}>字段2</Menu.Item>
|
||||
<Menu.Item onClick={insertBeforeHandle}>字段3</Menu.Item>
|
||||
<Menu.Divider />
|
||||
<Menu.SubMenu title={'新增字段'}>
|
||||
<Menu.Item onClick={insertBeforeHandle} style={{ minWidth: 150 }}>
|
||||
单行文本
|
||||
</Menu.Item>
|
||||
<Menu.Item onClick={insertBeforeHandle}>多行文本</Menu.Item>
|
||||
<Menu.Item onClick={insertBeforeHandle}>电子邮箱</Menu.Item>
|
||||
<Menu.Item onClick={insertBeforeHandle}>手机号</Menu.Item>
|
||||
<Menu.Item onClick={insertBeforeHandle}>数字</Menu.Item>
|
||||
</Menu.SubMenu>
|
||||
<Menu.Item>字段 1</Menu.Item>
|
||||
</Menu>
|
||||
}
|
||||
>
|
||||
|
@ -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 (
|
||||
<SchemaBlock schema={schema} />
|
||||
<SchemaRenderer schema={schema} />
|
||||
);
|
||||
};
|
||||
```
|
||||
|
@ -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 (
|
||||
<SchemaBlock schema={schema} />
|
||||
<SchemaRenderer schema={schema} />
|
||||
);
|
||||
};
|
||||
```
|
||||
@ -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 (
|
||||
<SchemaBlock schema={schema} />
|
||||
<SchemaRenderer schema={schema} />
|
||||
);
|
||||
};
|
||||
```
|
||||
|
@ -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 (
|
||||
<SchemaBlock schema={schema} />
|
||||
<SchemaRenderer schema={schema} />
|
||||
);
|
||||
};
|
||||
```
|
||||
|
@ -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 <SchemaBlock schema={schema} />
|
||||
return <SchemaRenderer schema={schema} />
|
||||
}
|
||||
```
|
||||
|
@ -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 (
|
||||
<SchemaBlock schema={schema} />
|
||||
<SchemaRenderer schema={schema} />
|
||||
);
|
||||
};
|
||||
```
|
||||
@ -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 (
|
||||
<SchemaBlock schema={schema} />
|
||||
<SchemaRenderer schema={schema} />
|
||||
);
|
||||
};
|
||||
```
|
||||
|
@ -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 (
|
||||
<div className={'designable-form-item'}>
|
||||
<FormilyFormItem {...props} />
|
||||
{dragRef && <div ref={dragRef}>Drag</div>}
|
||||
{/* {dragRef && <div ref={dragRef}>Drag</div>} */}
|
||||
<DesignableBar/>
|
||||
</div>
|
||||
);
|
||||
@ -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 (
|
||||
<div className={classNames('designable-bar', { active: visible })}>
|
||||
<span
|
||||
@ -45,6 +46,8 @@ FormItem.DesignableBar = () => {
|
||||
}}
|
||||
className={classNames('designable-bar-actions', { active: visible })}
|
||||
>
|
||||
<Space size={'small'}>
|
||||
<DragOutlined ref={dragRef} />
|
||||
<Dropdown
|
||||
trigger={['click']}
|
||||
visible={visible}
|
||||
@ -55,6 +58,7 @@ FormItem.DesignableBar = () => {
|
||||
<Menu>
|
||||
<Menu.Item
|
||||
onClick={(e) => {
|
||||
field.title = uid();
|
||||
setVisible(false);
|
||||
}}
|
||||
>
|
||||
@ -63,8 +67,9 @@ FormItem.DesignableBar = () => {
|
||||
</Menu>
|
||||
}
|
||||
>
|
||||
<MenuOutlined />
|
||||
<MenuOutlined/>
|
||||
</Dropdown>
|
||||
</Space>
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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 (
|
||||
<>
|
||||
<Menu.Item
|
||||
onClick={() => {
|
||||
addBlock(
|
||||
{},
|
||||
{
|
||||
insertBefore: true,
|
||||
},
|
||||
);
|
||||
setActive(false);
|
||||
}}
|
||||
icon={<ArrowUpOutlined />}
|
||||
@ -31,7 +23,6 @@ export const DesignableBar = (props) => {
|
||||
</Menu.Item>
|
||||
<Menu.Item
|
||||
onClick={() => {
|
||||
addBlock();
|
||||
setActive(false);
|
||||
}}
|
||||
icon={<ArrowDownOutlined />}
|
||||
@ -41,7 +32,6 @@ export const DesignableBar = (props) => {
|
||||
<Menu.Divider />
|
||||
<Menu.Item
|
||||
onClick={() => {
|
||||
removeBlock();
|
||||
setActive(false);
|
||||
}}
|
||||
icon={<DeleteOutlined />}
|
||||
|
@ -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<Formily.Core.Models.Field>();
|
||||
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 (
|
||||
<div
|
||||
data-type={'block'}
|
||||
ref={mergeRefs([dropRef, previewRef])}
|
||||
className={classNames('block', { 'top-half': onTopHalf, hover: isOver })}
|
||||
// style={{ textAlign: 'center', lineHeight: '60px', background: '#f1f1f1' }}
|
||||
>
|
||||
<BlockContext.Provider value={{ dragRef }}>
|
||||
{children}
|
||||
</BlockContext.Provider>
|
||||
{/* {DesignableBarComponent && (
|
||||
<DefaultActionBar DesignableBarComponent={DesignableBarComponent} dragRef={dragRef} />
|
||||
)} */}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
// function overlay() {
|
||||
// return <Menu>
|
||||
// <Menu.Item
|
||||
// onClick={() => {
|
||||
// addBlock(
|
||||
// {},
|
||||
// {
|
||||
// insertBefore: 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>
|
||||
// }
|
||||
|
||||
function Overlay(props) {
|
||||
return (
|
||||
<>
|
||||
<Menu.Item>在上方插入区块1</Menu.Item>
|
||||
<Menu.Item>在下方插入区块2</Menu.Item>
|
||||
<Menu.Divider />
|
||||
<Menu.Item>删除区块</Menu.Item>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
const DefaultActionBar = ({ DesignableBarComponent, dragRef }) => {
|
||||
const { addBlock, removeBlock } = useSchemaQuery();
|
||||
const [active, setActive] = useState(false);
|
||||
// return <MenuOutlined className={'draggable'} ref={dragRef} />;
|
||||
return (
|
||||
<div className={classNames('action-bar', { active })}>
|
||||
<Dropdown
|
||||
overlayStyle={{ minWidth: 200 }}
|
||||
trigger={['click']}
|
||||
visible={active}
|
||||
onVisibleChange={setActive}
|
||||
overlay={<Menu>{DesignableBarComponent ? <DesignableBarComponent /> : <Overlay />}</Menu>}
|
||||
>
|
||||
<MenuOutlined className={'draggable'} ref={dragRef} />
|
||||
</Dropdown>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Block;
|
@ -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<HTMLDivElement>();
|
||||
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<any>(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 (
|
||||
<>
|
||||
<div data-type={'col'} className={'col'} style={{ width: `${size * 100}%` }}>
|
||||
{children}
|
||||
</div>
|
||||
<Col.Divider />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
Col.Divider = (props) => {
|
||||
const { onDragEnd, resizable = true } = props;
|
||||
const { isDragging, dragRef } = useColResizer({ onDragEnd });
|
||||
const { isOver, dropRef } = useDrop({
|
||||
accept: 'grid',
|
||||
data: { },
|
||||
});
|
||||
return (
|
||||
<div
|
||||
data-type={'col-divider'}
|
||||
className={classNames('col-divider', { hover: isOver, resizable })}
|
||||
style={{ width: '24px' }}
|
||||
ref={mergeRefs(resizable ? [dropRef, dragRef] : [dropRef])}
|
||||
></div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Col;
|
@ -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 (
|
||||
<div className={'grid'}>
|
||||
<DragDropProvider
|
||||
gridRef={ref}
|
||||
onDrop={(event: Event) => {
|
||||
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);
|
||||
}}
|
||||
>
|
||||
<Row.Divider style={{ marginTop: -24 }} />
|
||||
{children}
|
||||
</DragDropProvider>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
Grid.Row = Row;
|
||||
Grid.Col = Col;
|
||||
Grid.Block = Block;
|
||||
|
||||
export default Grid;
|
@ -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 (
|
||||
<>
|
||||
<div
|
||||
data-type={'row'}
|
||||
ref={dropRef}
|
||||
className={classNames('row', { hover: isOver, 'top-half': onTopHalf })}
|
||||
style={{ margin: '0 -24px', display: 'flex' }}
|
||||
>
|
||||
<Col.Divider resizable={false} />
|
||||
{children}
|
||||
</div>
|
||||
<Row.Divider />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
Row.Divider = (props) => {
|
||||
const { style = {}, position } = props;
|
||||
const { isOver, dropRef } = useDrop({
|
||||
accept: 'grid',
|
||||
data: { position },
|
||||
});
|
||||
return (
|
||||
<div
|
||||
data-type={'row-divider'}
|
||||
ref={dropRef}
|
||||
className={classNames('row-divider', { hover: isOver })}
|
||||
style={{ ...style, height: '24px' }}
|
||||
></div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Row;
|
120
packages/client/src/blocks/grid/demos/demo1.tsx
Normal file
120
packages/client/src/blocks/grid/demos/demo1.tsx
Normal file
@ -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 (
|
||||
<FormProvider form={form}>
|
||||
<DesignableSchemaField schema={schema} />
|
||||
</FormProvider>
|
||||
);
|
||||
};
|
@ -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 (
|
||||
<div
|
||||
ref={dropRef as any}
|
||||
style={{
|
||||
textAlign: 'center',
|
||||
lineHeight: '100px',
|
||||
margin: 24,
|
||||
border: isOver ? '1px solid red' : '1px solid #ddd',
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
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 <Button ref={mergeRefs<any>([dragRef, previewRef])}>拖拽1</Button>;
|
||||
}
|
||||
|
||||
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 <Button ref={mergeRefs<any>([dragRef, previewRef])}>拖拽2</Button>;
|
||||
}
|
||||
|
||||
export default () => {
|
||||
return (
|
||||
<DragDropProvider>
|
||||
<Space style={{ marginBottom: 12 }}>
|
||||
<Dragable />
|
||||
<Dragable2 />
|
||||
</Space>
|
||||
<DropZone
|
||||
options={{
|
||||
accept: 'box',
|
||||
data: { a: 'a' },
|
||||
shallow: true,
|
||||
}}
|
||||
>
|
||||
Drop Zone1
|
||||
<DropZone
|
||||
options={{
|
||||
accept: 'box',
|
||||
data: { b: 'b' },
|
||||
// shallow: true,
|
||||
}}
|
||||
>
|
||||
Drop Zone2
|
||||
</DropZone>
|
||||
<DropZone
|
||||
options={{
|
||||
accept: 'box2',
|
||||
data: { c: 'c' },
|
||||
// shallow: true,
|
||||
}}
|
||||
>
|
||||
Drop Zone3
|
||||
</DropZone>
|
||||
Drop Zone1
|
||||
</DropZone>
|
||||
</DragDropProvider>
|
||||
);
|
||||
};
|
@ -1,18 +0,0 @@
|
||||
import React from 'react';
|
||||
import { Row, Col } from '../';
|
||||
|
||||
export default () => {
|
||||
return (
|
||||
<div>
|
||||
<Row onColResize={(e) => {
|
||||
console.log(e.data);
|
||||
}}>
|
||||
{[1, 2, 3].map((index) => (
|
||||
<Col size={1 / 3}>
|
||||
<div style={{textAlign: 'center', lineHeight: '60px', background: '#f1f1f1'}}>col {index}</div>
|
||||
</Col>
|
||||
))}
|
||||
</Row>
|
||||
</div>
|
||||
);
|
||||
};
|
@ -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': (
|
||||
<div
|
||||
style={{
|
||||
padding: 24,
|
||||
textAlign: 'center',
|
||||
background: 'rgb(241, 241, 241)',
|
||||
}}
|
||||
>
|
||||
block11
|
||||
</div>
|
||||
),
|
||||
},
|
||||
},
|
||||
},
|
||||
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': (
|
||||
<div
|
||||
style={{
|
||||
padding: 24,
|
||||
textAlign: 'center',
|
||||
background: 'rgb(241, 241, 241)',
|
||||
}}
|
||||
>
|
||||
block21
|
||||
</div>
|
||||
),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
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': (
|
||||
<div
|
||||
style={{
|
||||
padding: 24,
|
||||
textAlign: 'center',
|
||||
background: 'rgb(241, 241, 241)',
|
||||
}}
|
||||
>
|
||||
block211
|
||||
</div>
|
||||
),
|
||||
},
|
||||
},
|
||||
},
|
||||
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': (
|
||||
<div
|
||||
style={{
|
||||
padding: 24,
|
||||
textAlign: 'center',
|
||||
background: 'rgb(241, 241, 241)',
|
||||
}}
|
||||
>
|
||||
block221
|
||||
</div>
|
||||
),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
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': (
|
||||
<div
|
||||
style={{
|
||||
padding: 24,
|
||||
textAlign: 'center',
|
||||
background: 'rgb(241, 241, 241)',
|
||||
}}
|
||||
>
|
||||
block311
|
||||
</div>
|
||||
),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default () => {
|
||||
return <SchemaBlock schema={schema} />;
|
||||
};
|
@ -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<HTMLDivElement>();
|
||||
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<any>(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 };
|
||||
}
|
@ -73,62 +73,4 @@ group:
|
||||
|
||||
- 100%
|
||||
|
||||
## 代码演示
|
||||
|
||||
### useDrag & useDrop
|
||||
|
||||
<code src="./demos/demo4.tsx"/>
|
||||
|
||||
### useColResize
|
||||
|
||||
<code src="./demos/demo5.tsx"/>
|
||||
|
||||
### Grid
|
||||
|
||||
<code src="./demos/demo6.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` 方法可以简化配置。
|
||||
|
||||
### useDrag & useDrop
|
||||
|
||||
拖拽 hooks
|
||||
|
||||
原生态的
|
||||
|
||||
### useDrop
|
||||
|
||||
### useColResize
|
||||
<code src="./demos/demo1.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 (
|
||||
<div
|
||||
ref={dropRef}
|
||||
className={cls('nb-grid-row-divider', { hover: isOver })}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
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 (
|
||||
<div
|
||||
data-type={'col-divider'}
|
||||
ref={mergeRefs([dragRef, dropRef])}
|
||||
style={{ width: 24 }}
|
||||
className={cls('nb-grid-col-divider', {
|
||||
resizable,
|
||||
hover: isOver,
|
||||
dragging: isDragging,
|
||||
})}
|
||||
></div>
|
||||
);
|
||||
};
|
||||
|
||||
export const Grid: any = observer((props) => {
|
||||
const schema = useFieldSchema();
|
||||
const { insertBefore, insertAfter, remove } = useDesignable();
|
||||
const ref = useRef();
|
||||
return (
|
||||
<DragDropProvider>
|
||||
<GridContext.Provider value={{ ref }}>
|
||||
<div ref={ref} className={'nb-grid'}>
|
||||
<RowDivider
|
||||
onDrop={(e) => {
|
||||
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 (
|
||||
<>
|
||||
<div style={{ display: 'flex' }} className={'nb-grid-row'}>
|
||||
<RecursionField name={property.name} schema={property} />
|
||||
</div>
|
||||
<RowDivider
|
||||
onDrop={(e) => {
|
||||
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,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</GridContext.Provider>
|
||||
</DragDropProvider>
|
||||
);
|
||||
});
|
||||
|
||||
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 (
|
||||
<ColumnSizeContext.Provider value={len}>
|
||||
{schema.mapProperties((property, key, index) => {
|
||||
return (
|
||||
<>
|
||||
<ColDivider
|
||||
resizable={index > 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);
|
||||
}}
|
||||
/>
|
||||
<RecursionField name={property.name} schema={property} />
|
||||
</>
|
||||
);
|
||||
})}
|
||||
<ColDivider />
|
||||
</ColumnSizeContext.Provider>
|
||||
);
|
||||
});
|
||||
|
||||
const schema = getSchemaByPath(field.address.segments);
|
||||
Grid.Col = observer((props) => {
|
||||
const field = useField();
|
||||
const width = field.componentProps['width'];
|
||||
const size = useContext(ColumnSizeContext);
|
||||
return (
|
||||
<div
|
||||
style={{ width: `calc(${width || 100 / size}% - 24px / ${size})` }}
|
||||
className={'nb-grid-col'}
|
||||
>
|
||||
{props.children}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
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 (
|
||||
<GridBlockContext.Provider value={{ dragRef }}>
|
||||
<div
|
||||
ref={mergeRefs([previewRef, dropRef])}
|
||||
className={cls('nb-grid-block', {
|
||||
'top-half': onTopHalf,
|
||||
hover: isOver,
|
||||
dragging: isDragging,
|
||||
})}
|
||||
>
|
||||
{props.children}
|
||||
</div>
|
||||
</GridBlockContext.Provider>
|
||||
);
|
||||
});
|
||||
|
@ -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;
|
||||
}
|
||||
.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;
|
||||
}
|
||||
|
@ -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(), []);
|
||||
|
||||
|
@ -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 (
|
||||
<SchemaBlock schema={schema} />
|
||||
<SchemaRenderer schema={schema} />
|
||||
);
|
||||
};
|
||||
```
|
||||
@ -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 (
|
||||
<div style={{width: 200}}><SchemaBlock schema={schema} /></div>
|
||||
<div style={{width: 200}}><SchemaRenderer schema={schema} /></div>
|
||||
);
|
||||
};
|
||||
```
|
||||
```
|
||||
|
||||
### 混合菜单
|
||||
|
||||
```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 (
|
||||
<div>
|
||||
<Layout>
|
||||
<Layout.Header>
|
||||
<MenuContainerContext.Provider value={{
|
||||
sideMenuRef: ref,
|
||||
}}>
|
||||
<SchemaRenderer schema={schema} />
|
||||
</MenuContainerContext.Provider>
|
||||
</Layout.Header>
|
||||
<Layout>
|
||||
<Layout.Sider ref={ref} theme={'light'} width={200}>
|
||||
</Layout.Sider>
|
||||
<Layout.Content>
|
||||
{activeKey}
|
||||
</Layout.Content>
|
||||
</Layout>
|
||||
</Layout>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### 设计器模式
|
||||
|
||||
```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 (
|
||||
<div>
|
||||
<Layout>
|
||||
<Layout.Header>
|
||||
<MenuContainerContext.Provider value={{
|
||||
sideMenuRef: ref,
|
||||
}}>
|
||||
<SchemaRenderer schema={schema} />
|
||||
</MenuContainerContext.Provider>
|
||||
</Layout.Header>
|
||||
<Layout>
|
||||
<Layout.Sider ref={ref} theme={'light'} width={200}>
|
||||
</Layout.Sider>
|
||||
<Layout.Content>
|
||||
{activeKey}
|
||||
</Layout.Content>
|
||||
</Layout>
|
||||
</Layout>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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 (
|
||||
<SchemaBlock schema={schema} />
|
||||
<SchemaRenderer schema={schema} />
|
||||
);
|
||||
};
|
||||
```
|
||||
|
@ -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 (
|
||||
<SchemaBlock schema={schema} />
|
||||
<SchemaRenderer schema={schema} />
|
||||
);
|
||||
};
|
||||
```
|
||||
|
@ -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: {
|
||||
|
@ -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 (
|
||||
<Layout>
|
||||
<Layout.Header
|
||||
style={{
|
||||
height: '45px',
|
||||
lineHeight: '45px',
|
||||
padding: 0,
|
||||
display: 'flex',
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
width: 200,
|
||||
fontSize: 24,
|
||||
fontWeight: 200,
|
||||
letterSpacing: 3,
|
||||
textAlign: 'center',
|
||||
color: '#fff',
|
||||
}}
|
||||
>
|
||||
NocoBase
|
||||
</div>
|
||||
<SchemaBlock designable={false} schema={topMenuSchema} />
|
||||
|
||||
<Database />
|
||||
</Layout.Header>
|
||||
<Layout>
|
||||
{sideMenuSchema && (
|
||||
<Layout.Sider theme={'light'} width={200}>
|
||||
<SchemaBlock designable={false} schema={sideMenuSchema} />
|
||||
<Layout.Header>
|
||||
<MenuContainerContext.Provider value={{
|
||||
sideMenuRef: ref,
|
||||
}}>
|
||||
<SchemaRenderer schema={schema} />
|
||||
</MenuContainerContext.Provider>
|
||||
</Layout.Header>
|
||||
<Layout>
|
||||
<Layout.Sider ref={ref} theme={'light'} width={200}>
|
||||
</Layout.Sider>
|
||||
)}
|
||||
<Layout.Content>
|
||||
{pageTitle && <PageHeader title={pageTitle} ghost={false} />}
|
||||
<div style={{ margin: 24 }}>
|
||||
{/* {history.location.pathname} */}
|
||||
<Content activeKey={activeKey} />
|
||||
</div>
|
||||
</Layout.Content>
|
||||
<Layout.Content>
|
||||
{location.pathname}
|
||||
<Content activeKey={activeKey}/>
|
||||
</Layout.Content>
|
||||
</Layout>
|
||||
</Layout>
|
||||
</Layout>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
function Content({ activeKey }) {
|
||||
@ -414,7 +207,7 @@ function Content({ activeKey }) {
|
||||
return <Spin />;
|
||||
}
|
||||
|
||||
return <SchemaBlock schema={data} />;
|
||||
return <SchemaRenderer schema={data} />;
|
||||
}
|
||||
|
||||
export function AdminLayout({ route, children }: any) {
|
||||
@ -432,29 +225,8 @@ export function AdminLayout({ route, children }: any) {
|
||||
}
|
||||
|
||||
return (
|
||||
<DesignableProvider
|
||||
schema={
|
||||
new Schema(
|
||||
data.name
|
||||
? {
|
||||
type: 'object',
|
||||
properties: {
|
||||
[data.name]: data,
|
||||
},
|
||||
}
|
||||
: data,
|
||||
)
|
||||
}
|
||||
>
|
||||
{(s) => {
|
||||
// console.log('DesignableProvider', s.properties.item2.title);
|
||||
return (
|
||||
<LayoutWithMenu activeMenuItemKey={match.params.name} schema={s} />
|
||||
);
|
||||
}}
|
||||
</DesignableProvider>
|
||||
<LayoutWithMenu schema={data}/>
|
||||
);
|
||||
// return <LayoutWithMenu activeMenuItemKey={match.params.name} schema={data} />;
|
||||
}
|
||||
|
||||
export default AdminLayout;
|
||||
|
Loading…
Reference in New Issue
Block a user