refactor
This commit is contained in:
parent
7d6dd556f1
commit
75cd158a27
@ -10,7 +10,7 @@ title: '@nocobase/plugin-notifications'
|
||||
|
||||
暂时只实现了核心三步骤:
|
||||
|
||||
- 通知模板:包括主题、内容、接收人配置等等
|
||||
- 通知模板:包括主题、内容、接收人配置、发送服务等等
|
||||
- 通知服务:可以是短信、邮件等等
|
||||
- 通知日志:记录通知状态
|
||||
|
||||
|
@ -30,7 +30,7 @@
|
||||
"@typescript-eslint/eslint-plugin": "^4.9.1",
|
||||
"@typescript-eslint/parser": "^4.8.2",
|
||||
"@umijs/plugin-antd": "^0.9.1",
|
||||
"antd": "^4.15.4",
|
||||
"antd": "^4.16.3",
|
||||
"dotenv": "^8.2.0",
|
||||
"dumi": "^1.1.16",
|
||||
"eslint": "^7.4.0",
|
||||
|
@ -18,9 +18,9 @@
|
||||
},
|
||||
"gitHead": "f0b335ac30f29f25c95d7d137655fa64d8d67f1e",
|
||||
"dependencies": {
|
||||
"@formily/antd": "^2.0.0-beta.54",
|
||||
"@formily/core": "^2.0.0-beta.54",
|
||||
"@formily/react": "^2.0.0-beta.54",
|
||||
"@formily/antd": "^2.0.0-beta.72",
|
||||
"@formily/core": "^2.0.0-beta.72",
|
||||
"@formily/react": "^2.0.0-beta.72",
|
||||
"ahooks": "^2.10.2",
|
||||
"axios": "^0.21.1",
|
||||
"beautiful-react-hooks": "^0.35.0",
|
||||
|
132
packages/client/src/blocks/SchemaField.tsx
Normal file
132
packages/client/src/blocks/SchemaField.tsx
Normal file
@ -0,0 +1,132 @@
|
||||
import React, { createContext, useState } from 'react';
|
||||
import {
|
||||
FormItem,
|
||||
FormLayout,
|
||||
FormButtonGroup,
|
||||
Submit,
|
||||
ArrayTable,
|
||||
ArrayCollapse,
|
||||
} from '@formily/antd';
|
||||
import { createForm } from '@formily/core';
|
||||
import {
|
||||
Schema,
|
||||
ISchema,
|
||||
useForm,
|
||||
FormProvider,
|
||||
createSchemaField,
|
||||
observer,
|
||||
useFieldSchema,
|
||||
} from '@formily/react';
|
||||
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';
|
||||
|
||||
const Div = (props) => <div {...props} />;
|
||||
|
||||
const scope = {
|
||||
useLogin,
|
||||
useRegister,
|
||||
useSubmit,
|
||||
};
|
||||
|
||||
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 const SchemaField = createSchemaField({
|
||||
scope,
|
||||
components,
|
||||
});
|
||||
|
||||
export const DesignableSchemaContext = createContext<Schema>(new Schema({}));
|
||||
export const RefreshDesignableSchemaContext = createContext(null);
|
||||
|
||||
export function DesignableProvider(props) {
|
||||
const { schema } = props;
|
||||
const [, refresh] = useState(0);
|
||||
return (
|
||||
<RefreshDesignableSchemaContext.Provider
|
||||
value={() => {
|
||||
refresh(Math.random());
|
||||
}}
|
||||
>
|
||||
<DesignableSchemaContext.Provider value={schema}>
|
||||
{props.children(schema)}
|
||||
</DesignableSchemaContext.Provider>
|
||||
{/* <pre>{JSON.stringify(schema.toJSON(), null, 2)}</pre> */}
|
||||
</RefreshDesignableSchemaContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
export function DesignableSchemaField(props: { schema?: ISchema }) {
|
||||
return (
|
||||
<DesignableProvider schema={new Schema(props.schema)}>
|
||||
{(s) => <SchemaField schema={s} />}
|
||||
</DesignableProvider>
|
||||
);
|
||||
}
|
309
packages/client/src/blocks/action/index.md
Normal file
309
packages/client/src/blocks/action/index.md
Normal file
@ -0,0 +1,309 @@
|
||||
---
|
||||
title: Action - 操作
|
||||
nav:
|
||||
title: 组件
|
||||
path: /client
|
||||
group:
|
||||
order: 1
|
||||
title: 区块 - Blocks
|
||||
path: /client/blocks
|
||||
---
|
||||
|
||||
# Action - 操作
|
||||
|
||||
操作有三类:
|
||||
|
||||
- 常规操作
|
||||
- 弹出层操作,弹出层有 Drawer、Modal、Popover
|
||||
- 指定容器内操作 Container
|
||||
- 下拉菜单,用于收纳多种操作
|
||||
- 跳转操作
|
||||
|
||||
## Action - 常规操作
|
||||
|
||||
```tsx
|
||||
/**
|
||||
* title: 按钮操作
|
||||
* desc: 可以通过配置 `useAction` 来处理操作逻辑
|
||||
*/
|
||||
import React from 'react';
|
||||
import { SchemaBlock, registerScope } from '../';
|
||||
|
||||
function useCustomAction() {
|
||||
return {
|
||||
run() {
|
||||
alert('这是自定义的操作逻辑');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
registerScope({
|
||||
useCustomAction,
|
||||
});
|
||||
|
||||
const schema = {
|
||||
type: 'void',
|
||||
name: 'action1',
|
||||
title: '按钮',
|
||||
'x-component': 'Action',
|
||||
'x-component-props': {
|
||||
useAction: '{{ useCustomAction }}'
|
||||
},
|
||||
};
|
||||
|
||||
export default () => {
|
||||
return <SchemaBlock schema={schema} />
|
||||
}
|
||||
```
|
||||
|
||||
## Action.Link - 内链跳转
|
||||
|
||||
```tsx
|
||||
import React from 'react';
|
||||
import { SchemaBlock } from '../';
|
||||
|
||||
const schema = {
|
||||
type: 'void',
|
||||
name: 'action1',
|
||||
title: '按钮',
|
||||
'x-component': 'Action.Link',
|
||||
'x-component-props': {
|
||||
to: 'abc',
|
||||
},
|
||||
};
|
||||
|
||||
export default () => {
|
||||
return <SchemaBlock schema={schema} />
|
||||
}
|
||||
```
|
||||
|
||||
## Action.URL - 外链跳转
|
||||
|
||||
```tsx
|
||||
import React from 'react';
|
||||
import { SchemaBlock } from '../';
|
||||
|
||||
const schema = {
|
||||
type: 'void',
|
||||
name: 'action1',
|
||||
title: '按钮',
|
||||
'x-component': 'Action.URL',
|
||||
'x-component-props': {
|
||||
url: 'https://www.nocobase.com/',
|
||||
},
|
||||
};
|
||||
|
||||
export default () => {
|
||||
return <SchemaBlock schema={schema} />
|
||||
}
|
||||
```
|
||||
|
||||
## Action.Popover - 打开气泡
|
||||
|
||||
```tsx
|
||||
import React from 'react';
|
||||
import { SchemaBlock } from '../';
|
||||
|
||||
const schema = {
|
||||
type: 'void',
|
||||
name: 'action1',
|
||||
title: '按钮',
|
||||
'x-component': 'Action',
|
||||
properties: {
|
||||
drawer1: {
|
||||
type: 'void',
|
||||
title: '弹窗标题',
|
||||
'x-component': 'Action.Popover',
|
||||
'x-component-props': {},
|
||||
properties: {
|
||||
input: {
|
||||
type: 'string',
|
||||
'x-component': 'Input',
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default () => {
|
||||
return <SchemaBlock schema={schema} />
|
||||
}
|
||||
```
|
||||
|
||||
## Action.Drawer - 打开抽屉
|
||||
|
||||
```tsx
|
||||
import React from 'react';
|
||||
import { SchemaBlock } from '../';
|
||||
|
||||
const schema = {
|
||||
type: 'void',
|
||||
name: 'action1',
|
||||
title: '按钮',
|
||||
'x-component': 'Action',
|
||||
properties: {
|
||||
drawer1: {
|
||||
type: 'void',
|
||||
title: '抽屉标题',
|
||||
'x-component': 'Action.Drawer',
|
||||
'x-component-props': {},
|
||||
properties: {
|
||||
input: {
|
||||
type: 'string',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input',
|
||||
},
|
||||
action2: {
|
||||
type: 'void',
|
||||
title: '打开二级抽屉',
|
||||
// 'x-decorator': 'FormItem',
|
||||
'x-component': 'Action',
|
||||
properties: {
|
||||
drawer1: {
|
||||
type: 'void',
|
||||
title: '二级抽屉标题',
|
||||
'x-component': 'Action.Drawer',
|
||||
'x-component-props': {},
|
||||
properties: {
|
||||
input: {
|
||||
type: 'string',
|
||||
'x-component': 'Input',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default () => {
|
||||
return <SchemaBlock schema={schema} />
|
||||
}
|
||||
```
|
||||
|
||||
## Action.Modal - 打开对话框
|
||||
|
||||
```tsx
|
||||
import React from 'react';
|
||||
import { SchemaBlock } from '../';
|
||||
|
||||
const schema = {
|
||||
type: 'void',
|
||||
name: 'action1',
|
||||
title: '按钮',
|
||||
'x-component': 'Action',
|
||||
properties: {
|
||||
drawer1: {
|
||||
type: 'void',
|
||||
title: '弹窗标题',
|
||||
'x-component': 'Action.Modal',
|
||||
'x-component-props': {},
|
||||
properties: {
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default () => {
|
||||
return <SchemaBlock schema={schema} />
|
||||
}
|
||||
```
|
||||
|
||||
## Action.Container - 指定容器内打开
|
||||
|
||||
```tsx
|
||||
import React from 'react';
|
||||
import { SchemaBlock } from '../';
|
||||
|
||||
const schema = {
|
||||
type: 'void',
|
||||
name: 'action1',
|
||||
title: '按钮',
|
||||
'x-component': 'Action',
|
||||
properties: {
|
||||
drawer1: {
|
||||
type: 'void',
|
||||
title: '页面标题',
|
||||
'x-component': 'Action.Container',
|
||||
'x-component-props': {
|
||||
container: '#container'
|
||||
},
|
||||
properties: {
|
||||
input: {
|
||||
type: 'string',
|
||||
title: '字段',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input',
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default () => {
|
||||
return (
|
||||
<div>
|
||||
<SchemaBlock schema={schema} />
|
||||
<div style={{padding: '8px 0'}}>目标容器:</div>
|
||||
<div id={'container'} style={{ border: '1px dashed #ebedf1', background: '#fafafa', padding: 24 }}/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
## Action.DesignableBar - 按钮配置操作栏
|
||||
|
||||
```tsx
|
||||
import React from 'react';
|
||||
import { SchemaBlock } from '../';
|
||||
|
||||
const schema = {
|
||||
type: 'void',
|
||||
name: 'action1',
|
||||
title: '按钮',
|
||||
'x-component': 'Action',
|
||||
'x-designable-bar': 'Action.DesignableBar',
|
||||
properties: {
|
||||
drawer1: {
|
||||
type: 'void',
|
||||
title: '抽屉标题',
|
||||
'x-component': 'Action.Drawer',
|
||||
'x-component-props': {},
|
||||
properties: {
|
||||
input: {
|
||||
type: 'string',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input',
|
||||
},
|
||||
action2: {
|
||||
type: 'void',
|
||||
title: '打开二级抽屉',
|
||||
// 'x-decorator': 'FormItem',
|
||||
'x-component': 'Action',
|
||||
properties: {
|
||||
drawer1: {
|
||||
type: 'void',
|
||||
title: '二级抽屉标题',
|
||||
'x-component': 'Action.Drawer',
|
||||
'x-component-props': {},
|
||||
properties: {
|
||||
input: {
|
||||
type: 'string',
|
||||
'x-component': 'Input',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default () => {
|
||||
return <SchemaBlock schema={schema} />
|
||||
}
|
||||
```
|
||||
|
272
packages/client/src/blocks/action/index.tsx
Normal file
272
packages/client/src/blocks/action/index.tsx
Normal file
@ -0,0 +1,272 @@
|
||||
import React, { useContext, useState } from 'react';
|
||||
import {
|
||||
Input,
|
||||
FormItem,
|
||||
FormButtonGroup,
|
||||
Submit,
|
||||
Password,
|
||||
} from '@formily/antd';
|
||||
import { createForm } from '@formily/core';
|
||||
import {
|
||||
useForm,
|
||||
FormProvider,
|
||||
createSchemaField,
|
||||
observer,
|
||||
useFieldSchema,
|
||||
RecursionField,
|
||||
SchemaOptionsContext,
|
||||
Schema,
|
||||
useField,
|
||||
} from '@formily/react';
|
||||
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 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 classNames from 'classnames';
|
||||
|
||||
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() {},
|
||||
};
|
||||
}
|
||||
|
||||
export function useSubmit() {
|
||||
const form = useForm();
|
||||
const run = async () => {
|
||||
await form.submit();
|
||||
console.log(form.values);
|
||||
};
|
||||
return {
|
||||
run,
|
||||
};
|
||||
}
|
||||
|
||||
export function useLogin() {
|
||||
const form = useForm();
|
||||
const history = useHistory();
|
||||
const run = async () => {
|
||||
await form.submit();
|
||||
history.push('/admin');
|
||||
console.log(form.values);
|
||||
};
|
||||
return {
|
||||
run,
|
||||
};
|
||||
}
|
||||
|
||||
export function useRegister() {
|
||||
const form = useForm();
|
||||
const run = async () => {
|
||||
await form.submit();
|
||||
console.log(form.values);
|
||||
};
|
||||
return {
|
||||
run,
|
||||
};
|
||||
}
|
||||
|
||||
export interface ActionProps {
|
||||
useAction?: any;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export type ActionType = React.FC<ActionProps> & {
|
||||
Link?: React.FC<LinkProps>;
|
||||
URL?: React.FC<any>;
|
||||
Page?: React.FC<any>;
|
||||
Drawer?: React.FC<any>;
|
||||
Modal?: React.FC<any>;
|
||||
Dropdown?: React.FC<any>;
|
||||
DesignableBar?: React.FC<any>;
|
||||
};
|
||||
|
||||
function Blank({ children }) {
|
||||
return children;
|
||||
}
|
||||
|
||||
function useDesignableBar() {
|
||||
const schema = useFieldSchema();
|
||||
const options = useContext(SchemaOptionsContext);
|
||||
const DesignableBar = get(options.components, schema['x-designable-bar']);
|
||||
|
||||
return {
|
||||
DesignableBar: DesignableBar || Blank,
|
||||
};
|
||||
}
|
||||
|
||||
export const Action: ActionType = observer((props) => {
|
||||
const { useAction = useDefaultAction, ...others } = props;
|
||||
const field = useField();
|
||||
const { schema } = useSchemaQuery();
|
||||
const { run } = useAction();
|
||||
const { DesignableBar } = useDesignableBar();
|
||||
let childSchema = null;
|
||||
if (schema.properties) {
|
||||
const key = Object.keys(schema.properties).shift();
|
||||
const current = schema.properties[key];
|
||||
childSchema = current;
|
||||
}
|
||||
console.log({ schema });
|
||||
if (childSchema && childSchema['x-component'] === 'Action.Popover') {
|
||||
return (
|
||||
<Popover
|
||||
trigger={['click']}
|
||||
title={childSchema.title}
|
||||
{...childSchema['x-component-props']}
|
||||
content={
|
||||
<div>
|
||||
<SchemaBlock schema={childSchema} onlyRenderProperties />
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<Button {...others}>{field.title}</Button>
|
||||
</Popover>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<DesignableBar>
|
||||
<Button
|
||||
{...others}
|
||||
onClick={async () => {
|
||||
if (!childSchema) {
|
||||
return await run();
|
||||
}
|
||||
if (childSchema['x-component'] === 'Action.Drawer') {
|
||||
Drawer.open({
|
||||
title: childSchema.title,
|
||||
...(childSchema['x-component-props'] || {}),
|
||||
content: () => {
|
||||
return (
|
||||
<div>
|
||||
<SchemaBlock 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,
|
||||
);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{field.title}
|
||||
</Button>
|
||||
</DesignableBar>
|
||||
);
|
||||
});
|
||||
|
||||
Action.Link = observer((props) => {
|
||||
const schema = useFieldSchema();
|
||||
return <Link {...props}>{schema.title}</Link>;
|
||||
});
|
||||
|
||||
Action.URL = observer((props) => {
|
||||
const { url, ...others } = props;
|
||||
const schema = useFieldSchema();
|
||||
return (
|
||||
<a target={'_blank'} href={url} {...others}>
|
||||
{schema.title}
|
||||
</a>
|
||||
);
|
||||
});
|
||||
|
||||
Action.DesignableBar = (props) => {
|
||||
const field = useField();
|
||||
const { schema, refresh } = useSchemaQuery();
|
||||
const [visible, setVisible] = useState(false);
|
||||
return (
|
||||
<div className={'designable'}>
|
||||
<span
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
className={classNames('designable-bar', { active: visible })}
|
||||
>
|
||||
<Dropdown
|
||||
trigger={['click']}
|
||||
visible={visible}
|
||||
onVisibleChange={(visible) => {
|
||||
setVisible(visible);
|
||||
}}
|
||||
overlay={
|
||||
<Menu>
|
||||
<Menu.Item
|
||||
onClick={(e) => {
|
||||
console.log({ field, schema });
|
||||
schema.title = '按钮文案被修改了';
|
||||
field.setTitle('按钮文案被修改了');
|
||||
schema.properties.drawer1.title = '抽屉标题文案被修改了';
|
||||
refresh();
|
||||
setVisible(false);
|
||||
}}
|
||||
>
|
||||
点击修改按钮文案
|
||||
</Menu.Item>
|
||||
</Menu>
|
||||
}
|
||||
>
|
||||
<MenuOutlined />
|
||||
</Dropdown>
|
||||
</span>
|
||||
{props.children}
|
||||
</div>
|
||||
);
|
||||
};
|
21
packages/client/src/blocks/action/style.less
Normal file
21
packages/client/src/blocks/action/style.less
Normal file
@ -0,0 +1,21 @@
|
||||
.designable {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
&:hover {
|
||||
.designable-bar {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
.designable-bar {
|
||||
display: none;
|
||||
background: #fff;
|
||||
z-index: 2;
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
&.active {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
73
packages/client/src/blocks/add-new/form.ts
Normal file
73
packages/client/src/blocks/add-new/form.ts
Normal file
@ -0,0 +1,73 @@
|
||||
import { uid } from '@formily/shared';
|
||||
|
||||
export default () => ({
|
||||
type: 'void',
|
||||
name: `form_${uid()}`,
|
||||
'x-decorator': 'Card',
|
||||
'x-component': 'Form',
|
||||
properties: {
|
||||
grid: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid',
|
||||
properties: {
|
||||
[`gr_${uid()}`]: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Row',
|
||||
properties: {
|
||||
[`gc_${uid()}`]: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Col',
|
||||
'x-component-props': {
|
||||
size: 1,
|
||||
},
|
||||
properties: {
|
||||
[`gb_${uid()}`]: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Block',
|
||||
properties: {
|
||||
[`gbn_${uid()}`]: {
|
||||
type: 'void',
|
||||
'x-component': 'AddNew.FormItem',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
actions: {
|
||||
type: 'void',
|
||||
// 'x-decorator': 'Div',
|
||||
'x-component': 'Space',
|
||||
properties: {
|
||||
submit: {
|
||||
type: 'void',
|
||||
'x-component': 'Action',
|
||||
'x-component-props': {
|
||||
// block: true,
|
||||
type: 'primary',
|
||||
useAction: '{{ useLogin }}',
|
||||
style: {
|
||||
// width: '100%',
|
||||
},
|
||||
},
|
||||
title: '提交',
|
||||
},
|
||||
reset: {
|
||||
type: 'void',
|
||||
'x-component': 'Action',
|
||||
'x-component-props': {
|
||||
// block: true,
|
||||
useAction: '{{ useLogin }}',
|
||||
style: {
|
||||
// width: '100%',
|
||||
},
|
||||
},
|
||||
title: '重置',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
46
packages/client/src/blocks/add-new/index.md
Normal file
46
packages/client/src/blocks/add-new/index.md
Normal file
@ -0,0 +1,46 @@
|
||||
---
|
||||
title: AddNew - 新增区块
|
||||
nav:
|
||||
title: 组件
|
||||
path: /client
|
||||
group:
|
||||
order: 1
|
||||
title: 区块 - Blocks
|
||||
path: /client/blocks
|
||||
---
|
||||
|
||||
# AddNew - 新增区块
|
||||
|
||||
## 新增普通区块
|
||||
|
||||
```tsx
|
||||
import React from 'react';
|
||||
import { SchemaBlock } from '../';
|
||||
|
||||
const schema = {
|
||||
type: 'void',
|
||||
name: 'addnew',
|
||||
'x-component': 'AddNew',
|
||||
};
|
||||
|
||||
export default () => {
|
||||
return <SchemaBlock schema={schema} />
|
||||
}
|
||||
```
|
||||
|
||||
## 新增表单区块
|
||||
|
||||
```tsx
|
||||
import React from 'react';
|
||||
import { SchemaBlock } from '../';
|
||||
|
||||
const schema = {
|
||||
type: 'void',
|
||||
name: 'addnew',
|
||||
'x-component': 'AddNew.FormItem',
|
||||
};
|
||||
|
||||
export default () => {
|
||||
return <SchemaBlock schema={schema} />
|
||||
}
|
||||
```
|
429
packages/client/src/blocks/add-new/index.tsx
Normal file
429
packages/client/src/blocks/add-new/index.tsx
Normal file
@ -0,0 +1,429 @@
|
||||
import React, {
|
||||
Children,
|
||||
createContext,
|
||||
useContext,
|
||||
useEffect,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { FormDialog, FormLayout } from '@formily/antd';
|
||||
import {
|
||||
connect,
|
||||
observer,
|
||||
mapProps,
|
||||
mapReadPretty,
|
||||
useField,
|
||||
useFieldSchema,
|
||||
RecursionField,
|
||||
Schema,
|
||||
} from '@formily/react';
|
||||
import {
|
||||
Menu,
|
||||
MenuProps,
|
||||
MenuItemProps,
|
||||
SubMenuProps,
|
||||
DividerProps,
|
||||
Dropdown,
|
||||
Modal,
|
||||
Button,
|
||||
Spin,
|
||||
} from 'antd';
|
||||
import {
|
||||
MenuOutlined,
|
||||
GroupOutlined,
|
||||
PlusOutlined,
|
||||
LinkOutlined,
|
||||
AppstoreAddOutlined,
|
||||
EditOutlined,
|
||||
DeleteOutlined,
|
||||
ArrowRightOutlined,
|
||||
SettingOutlined,
|
||||
ArrowUpOutlined,
|
||||
ArrowDownOutlined,
|
||||
LoadingOutlined,
|
||||
DownOutlined,
|
||||
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',
|
||||
'x-component-props': {
|
||||
DesignableBar: `${component}.DesignableBar`,
|
||||
},
|
||||
'x-designable-bar': `${component}.DesignableBar`,
|
||||
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>
|
||||
}
|
||||
>
|
||||
<Button icon={<PlusOutlined />}></Button>
|
||||
</Dropdown>
|
||||
);
|
||||
});
|
||||
|
||||
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-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>
|
||||
}
|
||||
>
|
||||
<Button icon={<PlusOutlined />}></Button>
|
||||
</Dropdown>
|
||||
);
|
||||
});
|
||||
|
||||
export default AddNew;
|
8
packages/client/src/blocks/add-new/markdown.ts
Normal file
8
packages/client/src/blocks/add-new/markdown.ts
Normal file
@ -0,0 +1,8 @@
|
||||
import { uid } from '@formily/shared';
|
||||
|
||||
export default () => ({
|
||||
name: `markdown_${uid()}`,
|
||||
type: 'string',
|
||||
'x-component': 'Markdown',
|
||||
'x-component-props': {},
|
||||
});
|
86
packages/client/src/blocks/add-new/table.ts
Normal file
86
packages/client/src/blocks/add-new/table.ts
Normal file
@ -0,0 +1,86 @@
|
||||
import { uid } from '@formily/shared';
|
||||
|
||||
export default () => ({
|
||||
name: `t_${uid()}`,
|
||||
type: 'array',
|
||||
'x-component': 'Table',
|
||||
default: [{ field1: 'a', field2: 'b' }, { field1: 'a', field2: 'b' }],
|
||||
'x-component-props': {},
|
||||
properties: {
|
||||
[`a_${uid()}`]: {
|
||||
type: 'void',
|
||||
'x-component': 'Table.ActionBar',
|
||||
properties: {
|
||||
action1: {
|
||||
type: 'object',
|
||||
title: '按钮1',
|
||||
'x-component': 'Table.BulkAction.Popover',
|
||||
'x-component-props': {},
|
||||
properties: {},
|
||||
},
|
||||
action2: {
|
||||
type: 'void',
|
||||
title: '按钮2',
|
||||
'x-component': 'Table.BulkAction',
|
||||
'x-component-props': {},
|
||||
},
|
||||
},
|
||||
},
|
||||
[`a_${uid()}`]: {
|
||||
type: 'void',
|
||||
'x-component': 'Table.ActionBar',
|
||||
'x-component-props': {
|
||||
align: 'bottom',
|
||||
},
|
||||
properties: {
|
||||
pagination: {
|
||||
type: 'void',
|
||||
'x-component': 'Table.Pagination',
|
||||
'x-component-props': {
|
||||
defaultCurrent: 1,
|
||||
total: 50,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
[`c_${uid()}`]: {
|
||||
type: 'void',
|
||||
title: '字段1',
|
||||
'x-component': 'Table.Column',
|
||||
'x-component-props': {
|
||||
title: 'z1',
|
||||
width: 100,
|
||||
// DesignableBar: 'Table.Column.DesignableBar',
|
||||
},
|
||||
'x-designable-bar': 'Table.Column.DesignableBar',
|
||||
properties: {
|
||||
field1: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
'x-decorator-props': {
|
||||
feedbackLayout: 'popover',
|
||||
},
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input',
|
||||
},
|
||||
},
|
||||
},
|
||||
[`c_${uid()}`]: {
|
||||
type: 'void',
|
||||
title: '字段2',
|
||||
'x-component': 'Table.Column',
|
||||
properties: {
|
||||
field2: {
|
||||
type: 'string',
|
||||
title: '字段2',
|
||||
required: true,
|
||||
'x-decorator-props': {
|
||||
feedbackLayout: 'popover',
|
||||
},
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
@ -1,19 +0,0 @@
|
||||
---
|
||||
title: Calendar - 日历
|
||||
nav:
|
||||
title: 组件
|
||||
path: /client
|
||||
group:
|
||||
order: 2
|
||||
title: Blocks
|
||||
path: /client/blocks
|
||||
---
|
||||
|
||||
# Calendar - 日历
|
||||
|
||||
## 代码演示
|
||||
|
||||
### 基本使用
|
||||
|
||||
### 设计器模式
|
||||
|
100
packages/client/src/blocks/cascader/index.md
Normal file
100
packages/client/src/blocks/cascader/index.md
Normal file
@ -0,0 +1,100 @@
|
||||
---
|
||||
title: Cascader - 级联选择
|
||||
nav:
|
||||
title: 组件
|
||||
path: /client
|
||||
group:
|
||||
order: 1
|
||||
title: 区块 - Blocks
|
||||
path: /client/blocks
|
||||
---
|
||||
|
||||
# Cascader - 级联选择
|
||||
|
||||
## 省市区级联
|
||||
|
||||
```tsx
|
||||
/**
|
||||
* title: 省市区级联
|
||||
*/
|
||||
import React from 'react';
|
||||
import { SchemaBlock } from '../';
|
||||
|
||||
const options = [
|
||||
{
|
||||
value: 'zhejiang',
|
||||
label: 'Zhejiang',
|
||||
children: [
|
||||
{
|
||||
value: 'hangzhou',
|
||||
label: 'Hangzhou',
|
||||
children: [
|
||||
{
|
||||
value: 'xihu',
|
||||
label: 'West Lake',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
value: 'jiangsu',
|
||||
label: 'Jiangsu',
|
||||
children: [
|
||||
{
|
||||
value: 'nanjing',
|
||||
label: 'Nanjing',
|
||||
children: [
|
||||
{
|
||||
value: 'zhonghuamen',
|
||||
label: 'Zhong Hua Men',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const schema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
input: {
|
||||
type: 'string',
|
||||
title: `编辑模式`,
|
||||
name: 'name1',
|
||||
enum: options,
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Cascader',
|
||||
'x-component-props': {
|
||||
changeOnSelect: true,
|
||||
},
|
||||
'x-reactions': {
|
||||
target: 'read',
|
||||
fulfill: {
|
||||
state: {
|
||||
value: '{{$self.value}}',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
read: {
|
||||
type: 'string',
|
||||
title: `阅读模式`,
|
||||
enum: options,
|
||||
name: 'name2',
|
||||
'x-read-pretty': true,
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Cascader',
|
||||
'x-component-props': {
|
||||
changeOnSelect: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default () => {
|
||||
return (
|
||||
<SchemaBlock schema={schema} />
|
||||
);
|
||||
};
|
||||
```
|
@ -1,11 +1,12 @@
|
||||
import React from 'react'
|
||||
import { connect, mapReadPretty, mapProps } from '@formily/react'
|
||||
import { Cascader as AntdCascader } from 'antd'
|
||||
import { Descriptions } from '../descriptions'
|
||||
import { Display } from '../display'
|
||||
import { LoadingOutlined } from '@ant-design/icons'
|
||||
|
||||
export const Cascader = connect(
|
||||
(props) => {
|
||||
console.log('props.title', props.title);
|
||||
return <AntdCascader {...props}/>
|
||||
},
|
||||
mapProps(
|
||||
@ -24,7 +25,7 @@ export const Cascader = connect(
|
||||
}
|
||||
}
|
||||
),
|
||||
mapReadPretty(Descriptions.Cascader)
|
||||
mapReadPretty(Display.Cascader)
|
||||
)
|
||||
|
||||
export default Cascader
|
@ -1,19 +0,0 @@
|
||||
---
|
||||
title: Chart - 图表
|
||||
nav:
|
||||
title: 组件
|
||||
path: /client
|
||||
group:
|
||||
order: 2
|
||||
title: Blocks
|
||||
path: /client/blocks
|
||||
---
|
||||
|
||||
# Chart - 图表
|
||||
|
||||
## 代码演示
|
||||
|
||||
### 基本使用
|
||||
|
||||
### 设计器模式
|
||||
|
117
packages/client/src/blocks/checkbox/index.md
Normal file
117
packages/client/src/blocks/checkbox/index.md
Normal file
@ -0,0 +1,117 @@
|
||||
---
|
||||
title: Checkbox - 多选框
|
||||
nav:
|
||||
title: 组件
|
||||
path: /client
|
||||
group:
|
||||
order: 1
|
||||
title: 区块 - Blocks
|
||||
path: /client/blocks
|
||||
---
|
||||
|
||||
# Checkbox - 多选框
|
||||
|
||||
## 代码演示
|
||||
|
||||
### 勾选
|
||||
|
||||
```tsx
|
||||
/**
|
||||
* title: 勾选
|
||||
*/
|
||||
import React from 'react';
|
||||
import { SchemaBlock } from '../';
|
||||
|
||||
const schema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
input: {
|
||||
interface: 'string',
|
||||
type: 'string',
|
||||
title: `编辑模式`,
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Checkbox',
|
||||
'x-reactions': {
|
||||
target: 'read',
|
||||
fulfill: {
|
||||
state: {
|
||||
value: '{{$self.value}}',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
read: {
|
||||
interface: 'string',
|
||||
type: 'string',
|
||||
title: `阅读模式`,
|
||||
'x-read-pretty': true,
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Checkbox',
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
export default () => {
|
||||
return (
|
||||
<SchemaBlock schema={schema} />
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
### 组
|
||||
|
||||
```tsx
|
||||
/**
|
||||
* title: 组
|
||||
*/
|
||||
import React from 'react';
|
||||
import { SchemaBlock } from '../';
|
||||
|
||||
const options = [
|
||||
{
|
||||
label: '选项1',
|
||||
value: 1,
|
||||
color: 'red',
|
||||
},
|
||||
{
|
||||
label: '选项2',
|
||||
value: 2,
|
||||
color: 'blue',
|
||||
},
|
||||
];
|
||||
|
||||
const schema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
input: {
|
||||
type: 'string',
|
||||
title: `编辑模式`,
|
||||
enum: options,
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Checkbox.Group',
|
||||
'x-reactions': {
|
||||
target: 'read',
|
||||
fulfill: {
|
||||
state: {
|
||||
value: '{{$self.value}}',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
read: {
|
||||
type: 'string',
|
||||
title: `阅读模式`,
|
||||
enum: options,
|
||||
'x-read-pretty': true,
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Checkbox.Group',
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default () => {
|
||||
return (
|
||||
<SchemaBlock schema={schema} />
|
||||
);
|
||||
};
|
||||
```
|
@ -2,7 +2,6 @@ import React from 'react';
|
||||
import { connect, mapProps, mapReadPretty, useField } from '@formily/react';
|
||||
import { Checkbox as AntdCheckbox, Tag } from 'antd';
|
||||
import { CheckboxProps, CheckboxGroupProps } from 'antd/lib/checkbox';
|
||||
import { Descriptions } from '../descriptions';
|
||||
import uniq from 'lodash/uniq';
|
||||
import { CheckOutlined, CloseOutlined } from '@ant-design/icons';
|
||||
|
57
packages/client/src/blocks/color-select/index.md
Normal file
57
packages/client/src/blocks/color-select/index.md
Normal file
@ -0,0 +1,57 @@
|
||||
---
|
||||
title: ColorSelect - 颜色选择器
|
||||
nav:
|
||||
title: 组件
|
||||
path: /client
|
||||
group:
|
||||
order: 1
|
||||
title: 区块 - Blocks
|
||||
path: /client/blocks
|
||||
---
|
||||
|
||||
# ColorSelect - 颜色选择器
|
||||
|
||||
```tsx
|
||||
/**
|
||||
* title: 颜色选择器
|
||||
*/
|
||||
import React from 'react';
|
||||
import { SchemaBlock } from '../';
|
||||
|
||||
const schema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
input: {
|
||||
interface: 'icon',
|
||||
type: 'string',
|
||||
title: `编辑模式`,
|
||||
name: 'name1',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'ColorSelect',
|
||||
'x-reactions': {
|
||||
target: 'read',
|
||||
fulfill: {
|
||||
state: {
|
||||
value: '{{$self.value}}',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
read: {
|
||||
interface: 'icon',
|
||||
type: 'string',
|
||||
title: `阅读模式`,
|
||||
name: 'name2',
|
||||
'x-read-pretty': true,
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'ColorSelect',
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default () => {
|
||||
return (
|
||||
<SchemaBlock schema={schema} />
|
||||
);
|
||||
};
|
||||
```
|
@ -1,7 +1,6 @@
|
||||
import React from 'react'
|
||||
import { connect, mapProps, mapReadPretty } from '@formily/react'
|
||||
import { Tag, Select } from 'antd'
|
||||
import { Descriptions } from '../descriptions'
|
||||
import { LoadingOutlined } from '@ant-design/icons'
|
||||
|
||||
const colors = {
|
76
packages/client/src/blocks/database-field/index.md
Normal file
76
packages/client/src/blocks/database-field/index.md
Normal file
@ -0,0 +1,76 @@
|
||||
---
|
||||
title: DatabaseField - 数据表字段
|
||||
nav:
|
||||
title: 组件
|
||||
path: /client
|
||||
group:
|
||||
order: 1
|
||||
title: 区块 - Blocks
|
||||
path: /client/blocks
|
||||
---
|
||||
|
||||
# DatabaseField - 数据表字段
|
||||
|
||||
```tsx
|
||||
import React from 'react';
|
||||
import { SchemaBlock } from '../';
|
||||
|
||||
const schema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
array: {
|
||||
type: 'array',
|
||||
// title: '数据表字段',
|
||||
'x-component': 'DatabaseField.ArrayCollapse',
|
||||
'x-component-props': {
|
||||
accordion: true,
|
||||
},
|
||||
// maxItems: 3,
|
||||
'x-decorator': 'FormItem',
|
||||
items: {
|
||||
type: 'object',
|
||||
'x-component': 'DatabaseField.ArrayCollapse.CollapsePanel',
|
||||
'x-component-props': {
|
||||
header: '字段',
|
||||
},
|
||||
properties: {
|
||||
index: {
|
||||
type: 'void',
|
||||
'x-component': 'DatabaseField.ArrayCollapse.Index',
|
||||
},
|
||||
input: {
|
||||
type: 'string',
|
||||
'x-decorator': 'FormItem',
|
||||
title: 'Input',
|
||||
required: true,
|
||||
'x-component': 'Input',
|
||||
},
|
||||
remove: {
|
||||
type: 'void',
|
||||
'x-component': 'DatabaseField.ArrayCollapse.Remove',
|
||||
},
|
||||
moveUp: {
|
||||
type: 'void',
|
||||
'x-component': 'DatabaseField.ArrayCollapse.MoveUp',
|
||||
},
|
||||
moveDown: {
|
||||
type: 'void',
|
||||
'x-component': 'DatabaseField.ArrayCollapse.MoveDown',
|
||||
},
|
||||
},
|
||||
},
|
||||
properties: {
|
||||
addition: {
|
||||
type: 'void',
|
||||
title: '添加字段',
|
||||
'x-component': 'DatabaseField.ArrayCollapse.Addition',
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
export default () => {
|
||||
return <SchemaBlock schema={schema} />
|
||||
}
|
||||
```
|
9
packages/client/src/blocks/database-field/index.tsx
Normal file
9
packages/client/src/blocks/database-field/index.tsx
Normal file
@ -0,0 +1,9 @@
|
||||
import { observer, connect } from '@formily/react';
|
||||
import React from 'react';
|
||||
import { ArrayCollapse } from '@formily/antd';
|
||||
import '@formily/antd/lib/form-tab/style';
|
||||
|
||||
export const DatabaseField: any = () => null;
|
||||
|
||||
DatabaseField.ArrayCollapse = ArrayCollapse;
|
||||
|
110
packages/client/src/blocks/date-picker/index.md
Normal file
110
packages/client/src/blocks/date-picker/index.md
Normal file
@ -0,0 +1,110 @@
|
||||
---
|
||||
title: DatePicker - 日期选择器
|
||||
nav:
|
||||
title: 组件
|
||||
path: /client
|
||||
group:
|
||||
order: 1
|
||||
title: 区块 - Blocks
|
||||
path: /client/blocks
|
||||
---
|
||||
|
||||
# DatePicker - 日期选择器
|
||||
|
||||
## 代码演示
|
||||
|
||||
### 日期选择器
|
||||
|
||||
```tsx
|
||||
/**
|
||||
* title: 日期选择器
|
||||
*/
|
||||
import React from 'react';
|
||||
import { SchemaBlock } from '../';
|
||||
|
||||
const schema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
input: {
|
||||
interface: 'icon',
|
||||
type: 'string',
|
||||
title: `编辑模式`,
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'DatePicker',
|
||||
'x-reactions': {
|
||||
target: 'read',
|
||||
fulfill: {
|
||||
state: {
|
||||
value: '{{$self.value}}',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
read: {
|
||||
interface: 'icon',
|
||||
type: 'string',
|
||||
title: `阅读模式`,
|
||||
'x-read-pretty': true,
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'DatePicker',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default () => {
|
||||
return (
|
||||
<SchemaBlock schema={schema} />
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
### 日期时间选择
|
||||
|
||||
```tsx
|
||||
/**
|
||||
* title: 日期时间选择
|
||||
*/
|
||||
import React from 'react';
|
||||
import { SchemaBlock } from '../';
|
||||
|
||||
const schema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
input: {
|
||||
interface: 'icon',
|
||||
type: 'string',
|
||||
title: `编辑模式`,
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'DatePicker',
|
||||
'x-component-props': {
|
||||
showTime: true,
|
||||
},
|
||||
'x-reactions': {
|
||||
target: 'read',
|
||||
fulfill: {
|
||||
state: {
|
||||
value: '{{$self.value}}',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
read: {
|
||||
interface: 'icon',
|
||||
type: 'string',
|
||||
title: `阅读模式`,
|
||||
'x-read-pretty': true,
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'DatePicker',
|
||||
'x-component-props': {
|
||||
showTime: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default () => {
|
||||
return (
|
||||
<SchemaBlock schema={schema} />
|
||||
);
|
||||
};
|
||||
```
|
@ -5,7 +5,7 @@ import {
|
||||
DatePickerProps as AntdDatePickerProps,
|
||||
RangePickerProps,
|
||||
} from 'antd/lib/date-picker'
|
||||
import { Descriptions } from '../descriptions'
|
||||
import { Display } from '../display'
|
||||
import { formatMomentValue, momentable } from '@formily/antd/esm/__builtins__'
|
||||
|
||||
type DatePickerProps<PickerProps> = Exclude<
|
||||
@ -52,13 +52,13 @@ const mapDateFormat = function () {
|
||||
export const DatePicker: ComposedDatePicker = connect(
|
||||
AntdDatePicker,
|
||||
mapProps(mapDateFormat()),
|
||||
mapReadPretty(Descriptions.DatePicker)
|
||||
mapReadPretty(Display.DatePicker)
|
||||
)
|
||||
|
||||
DatePicker.RangePicker = connect(
|
||||
AntdDatePicker.RangePicker,
|
||||
mapProps(mapDateFormat()),
|
||||
mapReadPretty(Descriptions.DateRangePicker)
|
||||
mapReadPretty(Display.DateRangePicker)
|
||||
)
|
||||
|
||||
export default DatePicker
|
@ -1,8 +0,0 @@
|
||||
import React from 'react';
|
||||
import { FormBlock } from '../form';
|
||||
|
||||
export function DescriptionsBlock(props: any) {
|
||||
return <FormBlock readPretty={true} {...props} />
|
||||
};
|
||||
|
||||
export default DescriptionsBlock;
|
@ -339,7 +339,7 @@ const TimeRangePicker: React.FC<TimeRangePickerProps> = (props) => {
|
||||
)
|
||||
}
|
||||
|
||||
export const Descriptions = {
|
||||
export const Display = {
|
||||
Input,
|
||||
URL,
|
||||
TextArea,
|
||||
@ -356,5 +356,4 @@ export const Descriptions = {
|
||||
usePlaceholder,
|
||||
}
|
||||
|
||||
export default Descriptions;
|
||||
|
||||
export default Display;
|
12
packages/client/src/blocks/drawer-select/index.md
Normal file
12
packages/client/src/blocks/drawer-select/index.md
Normal file
@ -0,0 +1,12 @@
|
||||
---
|
||||
title: DrawerSelect - 抽屉选择器
|
||||
nav:
|
||||
title: 组件
|
||||
path: /client
|
||||
group:
|
||||
order: 1
|
||||
title: 区块 - Blocks
|
||||
path: /client/blocks
|
||||
---
|
||||
|
||||
# DrawerSelect - 抽屉选择器
|
10
packages/client/src/blocks/filter/index.md
Normal file
10
packages/client/src/blocks/filter/index.md
Normal file
@ -0,0 +1,10 @@
|
||||
---
|
||||
title: Filter - 筛选器
|
||||
nav:
|
||||
title: 组件
|
||||
path: /client
|
||||
group:
|
||||
order: 1
|
||||
title: 区块 - Blocks
|
||||
path: /client/blocks
|
||||
---
|
53
packages/client/src/blocks/form/ActionBar.tsx
Normal file
53
packages/client/src/blocks/form/ActionBar.tsx
Normal file
@ -0,0 +1,53 @@
|
||||
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,
|
||||
ArrowDownOutlined,
|
||||
DeleteOutlined,
|
||||
} from '@ant-design/icons';
|
||||
|
||||
export const ActionBar = (props) => {
|
||||
const { addBlock, removeBlock } = useSchemaQuery();
|
||||
const [active, setActive] = useState(false);
|
||||
return (
|
||||
<>
|
||||
<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>
|
||||
</>
|
||||
);
|
||||
};
|
53
packages/client/src/blocks/form/DesignableBar.tsx
Normal file
53
packages/client/src/blocks/form/DesignableBar.tsx
Normal file
@ -0,0 +1,53 @@
|
||||
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,
|
||||
ArrowDownOutlined,
|
||||
DeleteOutlined,
|
||||
} 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 />}
|
||||
>
|
||||
在上方插入区块
|
||||
</Menu.Item>
|
||||
<Menu.Item
|
||||
onClick={() => {
|
||||
addBlock();
|
||||
setActive(false);
|
||||
}}
|
||||
icon={<ArrowDownOutlined />}
|
||||
>
|
||||
在下方插入区块
|
||||
</Menu.Item>
|
||||
<Menu.Divider />
|
||||
<Menu.Item
|
||||
onClick={() => {
|
||||
removeBlock();
|
||||
setActive(false);
|
||||
}}
|
||||
icon={<DeleteOutlined />}
|
||||
>
|
||||
删除区块
|
||||
</Menu.Item>
|
||||
</>
|
||||
);
|
||||
};
|
@ -1,62 +0,0 @@
|
||||
import request from 'umi-request';
|
||||
|
||||
export interface ResourceOptions {
|
||||
resourceName: string;
|
||||
associatedKey?: any;
|
||||
associatedName?: string;
|
||||
resourceKey?: any;
|
||||
}
|
||||
|
||||
export interface GetOptions {
|
||||
resourceKey?: any;
|
||||
appends?: string[];
|
||||
}
|
||||
|
||||
export interface SaveOptions {
|
||||
resourceKey?: any;
|
||||
}
|
||||
|
||||
export class Resource {
|
||||
|
||||
protected options: ResourceOptions;
|
||||
|
||||
constructor(options: string | ResourceOptions) {
|
||||
if (typeof options === 'string') {
|
||||
this.options = { resourceName: options }
|
||||
} else {
|
||||
this.options = options;
|
||||
}
|
||||
}
|
||||
|
||||
get(options: GetOptions = {}) {
|
||||
const resourceKey = options.resourceKey || this.options.resourceKey;
|
||||
const { resourceName } = this.options;
|
||||
if (!resourceKey) {
|
||||
return Promise.resolve({ data: {} });
|
||||
}
|
||||
return request(`/api/${resourceName}:get/${resourceKey}`);
|
||||
}
|
||||
|
||||
save(values: any, options: SaveOptions = {}) {
|
||||
const resourceKey = options.resourceKey || this.options.resourceKey;
|
||||
const { resourceName } = this.options;
|
||||
const url = `/api/${resourceName}:${resourceKey ? `update/${resourceKey}` : 'create'}`;
|
||||
return request(url, {
|
||||
method: 'post',
|
||||
data: values,
|
||||
});
|
||||
}
|
||||
|
||||
static make(options: string | Resource | ResourceOptions) {
|
||||
if (typeof options === 'string') {
|
||||
return new Resource({ resourceName: options });
|
||||
}
|
||||
if (options instanceof Resource) {
|
||||
return options;
|
||||
}
|
||||
if (typeof options === 'object' && options.resourceName) {
|
||||
return new Resource(options);
|
||||
}
|
||||
throw 'resource 参数错误';
|
||||
}
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
import { Input, FormItem } from '@formily/antd';
|
||||
import { createSchemaField } from '@formily/react';
|
||||
|
||||
export const SchemaField = createSchemaField({
|
||||
components: {
|
||||
Input,
|
||||
FormItem,
|
||||
},
|
||||
scope: {},
|
||||
});
|
||||
|
||||
export default SchemaField;
|
@ -1,35 +0,0 @@
|
||||
/**
|
||||
* title: 基本使用
|
||||
*/
|
||||
import React from 'react';
|
||||
import { FormBlock } from '@nocobase/client';
|
||||
|
||||
const fields = [
|
||||
{
|
||||
interface: 'string',
|
||||
type: 'string',
|
||||
title: `单行文本`,
|
||||
name: 'username',
|
||||
required: true,
|
||||
default: 'abcdefg',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input',
|
||||
'x-component-props': {
|
||||
placeholder: 'please enter',
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
export default () => {
|
||||
return (
|
||||
<FormBlock
|
||||
resource={'users'}
|
||||
fields={fields}
|
||||
effects={{
|
||||
onFormValuesChange(form) {
|
||||
console.log('aaaa', form.values);
|
||||
},
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
@ -1,37 +0,0 @@
|
||||
/**
|
||||
* title: 更新数据表单
|
||||
* desc: 设置 `resource` 的 `resourceKey` 参数
|
||||
*/
|
||||
import React from 'react';
|
||||
import { FormBlock } from '@nocobase/client';
|
||||
|
||||
export default () => {
|
||||
return (
|
||||
<FormBlock
|
||||
resource={{
|
||||
resourceName: 'users',
|
||||
resourceKey: 1,
|
||||
}}
|
||||
fields={[
|
||||
{
|
||||
interface: 'string',
|
||||
type: 'string',
|
||||
title: `单行文本`,
|
||||
name: 'username',
|
||||
required: true,
|
||||
default: 'abcdefg',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input',
|
||||
'x-component-props': {
|
||||
placeholder: 'please enter',
|
||||
},
|
||||
},
|
||||
]}
|
||||
effects={{
|
||||
onFormValuesChange(form) {
|
||||
console.log('aaaa', form.values);
|
||||
},
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
@ -1,64 +0,0 @@
|
||||
import React from 'react';
|
||||
import { FormBlock, Drawer } from '@nocobase/client';
|
||||
|
||||
export default () => {
|
||||
const items = [
|
||||
{ id: 1, username: '文本一' },
|
||||
{ id: 2, username: '文本二' },
|
||||
];
|
||||
return (
|
||||
<ul>
|
||||
{items.map((item) => (
|
||||
<li>
|
||||
<a
|
||||
onClick={() => {
|
||||
Drawer.open({
|
||||
title: 'Form',
|
||||
content: ({ resolve, closeWithConfirm }) => (
|
||||
<FormBlock
|
||||
initialValues={item}
|
||||
resource={{
|
||||
resourceName: 'users',
|
||||
resourceKey: item.id,
|
||||
}}
|
||||
fields={[
|
||||
{
|
||||
interface: 'string',
|
||||
type: 'string',
|
||||
title: `单行文本`,
|
||||
name: 'username',
|
||||
required: true,
|
||||
default: 'aa',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input',
|
||||
'x-component-props': {},
|
||||
},
|
||||
]}
|
||||
effects={{
|
||||
onFormValuesChange(form) {
|
||||
if (form.modified && closeWithConfirm) {
|
||||
closeWithConfirm(true);
|
||||
}
|
||||
console.log('aaaa', form);
|
||||
},
|
||||
}}
|
||||
onSuccess={() => {
|
||||
return new Promise((r) => {
|
||||
setTimeout(() => {
|
||||
r(null);
|
||||
resolve();
|
||||
}, 2000);
|
||||
});
|
||||
}}
|
||||
/>
|
||||
),
|
||||
});
|
||||
}}
|
||||
>
|
||||
{item.username}
|
||||
</a>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
);
|
||||
};
|
@ -1,65 +0,0 @@
|
||||
import React from 'react';
|
||||
import { Drawer, FormBlock } from '@nocobase/client';
|
||||
|
||||
export default () => {
|
||||
const items = [
|
||||
{ id: 1, username: '文本一' },
|
||||
{ id: 2, username: '文本二' },
|
||||
];
|
||||
return (
|
||||
<ul>
|
||||
{items.map((item) => (
|
||||
<li>
|
||||
<a
|
||||
onClick={() => {
|
||||
Drawer.open({
|
||||
title: '详情',
|
||||
content: ({ resolve, closeWithConfirm }) => (
|
||||
<FormBlock
|
||||
readPretty
|
||||
initialValues={item}
|
||||
resource={{
|
||||
resourceName: 'users',
|
||||
resourceKey: item.id,
|
||||
}}
|
||||
fields={[
|
||||
{
|
||||
interface: 'string',
|
||||
type: 'string',
|
||||
title: `单行文本`,
|
||||
name: 'username',
|
||||
required: true,
|
||||
default: 'aa',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input',
|
||||
'x-component-props': {},
|
||||
},
|
||||
]}
|
||||
effects={{
|
||||
onFormValuesChange(form) {
|
||||
if (form.modified && closeWithConfirm) {
|
||||
closeWithConfirm(true);
|
||||
}
|
||||
console.log('aaaa', form);
|
||||
},
|
||||
}}
|
||||
onSuccess={() => {
|
||||
return new Promise((r) => {
|
||||
setTimeout(() => {
|
||||
r(null);
|
||||
resolve();
|
||||
}, 2000);
|
||||
});
|
||||
}}
|
||||
/>
|
||||
),
|
||||
});
|
||||
}}
|
||||
>
|
||||
{item.username}
|
||||
</a>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
);
|
||||
};
|
@ -1,94 +1,502 @@
|
||||
---
|
||||
title: Form - 表单
|
||||
nav:
|
||||
title: 组件
|
||||
path: /client
|
||||
title: 组件
|
||||
path: /client
|
||||
group:
|
||||
order: 2
|
||||
title: Blocks
|
||||
order: 1
|
||||
title: 区块 - Blocks
|
||||
path: /client/blocks
|
||||
---
|
||||
|
||||
# Form - 表单
|
||||
|
||||
## 示例
|
||||
## 代码演示
|
||||
|
||||
### 基本使用
|
||||
### 常规表单
|
||||
|
||||
<code src="./demos/demo1.tsx"/>
|
||||
```tsx
|
||||
import React from 'react';
|
||||
import { SchemaBlock } from '../';
|
||||
|
||||
### 更新数据表单
|
||||
|
||||
<code src="./demos/demo2.tsx"/>
|
||||
|
||||
### 抽屉表单
|
||||
|
||||
<code src="./demos/demo3.tsx"/>
|
||||
|
||||
### 阅读模式
|
||||
|
||||
<code src="./demos/demo4.tsx"/>
|
||||
|
||||
## Props API
|
||||
|
||||
可能包括四部分:
|
||||
|
||||
- IFormLayoutProps:表单布局相关参数
|
||||
- IFormProps:用于初始化 form 实例
|
||||
- ISchemaField:表单字段配置
|
||||
- ResourceOptions:用于资源定位
|
||||
|
||||
暂时只提供了一些核心的参数
|
||||
|
||||
### resource <Badge>ResourceOptions</Badge>
|
||||
|
||||
这部分参数还有待改进
|
||||
|
||||
```ts
|
||||
export interface ResourceOptions {
|
||||
resourceName: string;
|
||||
associatedKey?: string;
|
||||
associatedName?: string;
|
||||
resourceKey?: string;
|
||||
}
|
||||
```
|
||||
|
||||
### fields <Badge>ISchemaField</Badge>
|
||||
|
||||
字段配置
|
||||
|
||||
```ts
|
||||
{
|
||||
interface: 'string',
|
||||
type: 'string',
|
||||
title: `单行文本`,
|
||||
name: 'username',
|
||||
required: true,
|
||||
component: {
|
||||
type: 'string',
|
||||
default: 'aa',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input',
|
||||
'x-component-props': {},
|
||||
const schema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
form: {
|
||||
type: 'object',
|
||||
'x-component': 'Form',
|
||||
properties: {
|
||||
username: {
|
||||
type: 'string',
|
||||
title: '用户名',
|
||||
required: true,
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input',
|
||||
},
|
||||
password: {
|
||||
type: 'string',
|
||||
title: '密码',
|
||||
required: true,
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Password',
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
export default () => {
|
||||
return (
|
||||
<SchemaBlock schema={schema} />
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
### readPretty
|
||||
### 栅格布局表单
|
||||
|
||||
详情展示
|
||||
```tsx
|
||||
import React from 'react';
|
||||
import { SchemaBlock } from '../';
|
||||
|
||||
### initialValues <Badge>IFormProps</Badge>
|
||||
const schema = {
|
||||
type: 'void',
|
||||
name: 'item3',
|
||||
'x-decorator': 'Card',
|
||||
'x-component': 'Form',
|
||||
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-component-props': {
|
||||
title: 'block11',
|
||||
},
|
||||
properties: {
|
||||
field1: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
title: '字段1',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
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',
|
||||
},
|
||||
properties: {
|
||||
field2: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
title: '字段2',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
row2: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Row',
|
||||
properties: {
|
||||
col21: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Col',
|
||||
'x-component-props': {
|
||||
size: 1 / 3,
|
||||
},
|
||||
properties: {
|
||||
block211: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Block',
|
||||
'x-component-props': {
|
||||
title: 'block211',
|
||||
},
|
||||
properties: {
|
||||
field3: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
title: '字段3',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
col22: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Col',
|
||||
'x-component-props': {
|
||||
size: 2 / 3,
|
||||
isLast: true,
|
||||
},
|
||||
properties: {
|
||||
block221: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Block',
|
||||
'x-component-props': {
|
||||
title: 'block221',
|
||||
},
|
||||
properties: {
|
||||
field4: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
title: '字段4',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
row3: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Row',
|
||||
"x-component-props": {
|
||||
isLast: true,
|
||||
},
|
||||
properties: {
|
||||
col31: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Col',
|
||||
'x-component-props': {
|
||||
size: 1,
|
||||
isLast: true,
|
||||
},
|
||||
properties: {
|
||||
block311: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Block',
|
||||
'x-component-props': {
|
||||
title: 'block311',
|
||||
},
|
||||
properties: {
|
||||
field5: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
title: '字段5',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
actions: {
|
||||
type: 'void',
|
||||
// 'x-decorator': 'Div',
|
||||
'x-component': 'Space',
|
||||
properties: {
|
||||
submit: {
|
||||
type: 'void',
|
||||
'x-component': 'Action',
|
||||
'x-component-props': {
|
||||
// block: true,
|
||||
type: 'primary',
|
||||
useAction: '{{ useLogin }}',
|
||||
style: {
|
||||
// width: '100%',
|
||||
},
|
||||
},
|
||||
title: '提交',
|
||||
},
|
||||
reset: {
|
||||
type: 'void',
|
||||
'x-component': 'Action',
|
||||
'x-component-props': {
|
||||
// block: true,
|
||||
useAction: '{{ useLogin }}',
|
||||
style: {
|
||||
// width: '100%',
|
||||
},
|
||||
},
|
||||
title: '重置',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
初始值
|
||||
export default () => {
|
||||
return (
|
||||
<SchemaBlock schema={schema} />
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
### effects <Badge>IFormProps</Badge>
|
||||
### 表单设计器
|
||||
|
||||
支持以下 Effect Hooks
|
||||
```tsx
|
||||
import React from 'react';
|
||||
import { SchemaBlock } from '../';
|
||||
import { uid } from '@formily/shared';
|
||||
|
||||
- [Form Effect Hooks](https://core.formilyjs.org/api/entry/form-effect-hooks)
|
||||
- [Field Effect Hooks](https://core.formilyjs.org/api/entry/field-effect-hooks)
|
||||
const schema = {
|
||||
type: 'void',
|
||||
name: `form_${uid()}`,
|
||||
'x-decorator': 'Card',
|
||||
'x-component': 'Form',
|
||||
properties: {
|
||||
grid: {
|
||||
type: 'void',
|
||||
'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-component-props': {
|
||||
title: 'block11',
|
||||
},
|
||||
'x-designable-bar': 'Input.DesignableBar',
|
||||
properties: {
|
||||
field1: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
title: '字段1',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
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-designable-bar': 'Input.DesignableBar',
|
||||
properties: {
|
||||
field2: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
title: '字段2',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
row2: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Row',
|
||||
properties: {
|
||||
col21: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Col',
|
||||
'x-component-props': {
|
||||
size: 1 / 3,
|
||||
},
|
||||
properties: {
|
||||
block211: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Block',
|
||||
'x-component-props': {
|
||||
title: 'block211',
|
||||
},
|
||||
'x-designable-bar': 'Input.DesignableBar',
|
||||
properties: {
|
||||
field3: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
title: '字段3',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
col22: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Col',
|
||||
'x-component-props': {
|
||||
size: 2 / 3,
|
||||
isLast: true,
|
||||
},
|
||||
properties: {
|
||||
block221: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Block',
|
||||
'x-component-props': {
|
||||
title: 'block221',
|
||||
},
|
||||
'x-designable-bar': 'Input.DesignableBar',
|
||||
properties: {
|
||||
field4: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
title: '字段4',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
row3: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Row',
|
||||
"x-component-props": {
|
||||
isLast: true,
|
||||
},
|
||||
properties: {
|
||||
col31: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Col',
|
||||
'x-component-props': {
|
||||
size: 1,
|
||||
isLast: true,
|
||||
},
|
||||
properties: {
|
||||
block311: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Block',
|
||||
'x-component-props': {
|
||||
title: 'block311',
|
||||
},
|
||||
properties: {
|
||||
field5: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
title: '字段5',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
[`gr_${uid()}`]: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Row',
|
||||
properties: {
|
||||
[`gc_${uid()}`]: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Col',
|
||||
'x-component-props': {
|
||||
size: 1,
|
||||
},
|
||||
properties: {
|
||||
[`gb_${uid()}`]: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Block',
|
||||
properties: {
|
||||
[`gbn_${uid()}`]: {
|
||||
type: 'void',
|
||||
'x-component': 'AddNew.FormItem',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
actions: {
|
||||
type: 'void',
|
||||
// 'x-decorator': 'Div',
|
||||
'x-component': 'Space',
|
||||
properties: {
|
||||
submit: {
|
||||
type: 'void',
|
||||
'x-component': 'Action',
|
||||
'x-component-props': {
|
||||
// block: true,
|
||||
type: 'primary',
|
||||
useAction: '{{ useLogin }}',
|
||||
style: {
|
||||
// width: '100%',
|
||||
},
|
||||
},
|
||||
title: '提交',
|
||||
},
|
||||
reset: {
|
||||
type: 'void',
|
||||
'x-component': 'Action',
|
||||
'x-component-props': {
|
||||
// block: true,
|
||||
useAction: '{{ useLogin }}',
|
||||
style: {
|
||||
// width: '100%',
|
||||
},
|
||||
},
|
||||
title: '重置',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
### onSuccess
|
||||
|
||||
数据提交成功的回调函数
|
||||
export default () => {
|
||||
return (
|
||||
<SchemaBlock schema={schema} />
|
||||
);
|
||||
};
|
||||
```
|
@ -1,67 +1,23 @@
|
||||
import React, { useEffect, useMemo } from 'react';
|
||||
import { Form as Formily, FormButtonGroup, Submit, Reset } from '@formily/antd';
|
||||
import React, { useMemo } from 'react';
|
||||
import { createForm } from '@formily/core';
|
||||
import { SchemaField, fields2properties, parseEffects } from '../../';
|
||||
import { Resource, ResourceOptions } from '../../resource';
|
||||
import { FormProvider, useFieldSchema } from '@formily/react';
|
||||
import { DesignableSchemaField } from '../SchemaField';
|
||||
import { DesignableBar } from './DesignableBar';
|
||||
|
||||
export interface FormBlockProps {
|
||||
effects?: any;
|
||||
fields?: any;
|
||||
initialValues?: any;
|
||||
onSuccess?: any;
|
||||
resource: string | Resource | ResourceOptions;
|
||||
readPretty?: boolean;
|
||||
[key: string]: any;
|
||||
}
|
||||
export const Form = (props) => {
|
||||
const form = useMemo(() => createForm(), []);
|
||||
const schema = useFieldSchema();
|
||||
|
||||
export const FormBlock = (props: FormBlockProps) => {
|
||||
const { initialValues, fields = [], effects, onSuccess, readPretty } = props;
|
||||
const form = useMemo(
|
||||
() =>
|
||||
createForm({
|
||||
readPretty,
|
||||
initialValues,
|
||||
effects: (form) => parseEffects(effects, form),
|
||||
}),
|
||||
[],
|
||||
);
|
||||
const resource = Resource.make(props.resource);
|
||||
useEffect(() => {
|
||||
if (!initialValues) {
|
||||
resource.get().then(({ data }) => {
|
||||
form.setInitialValues(data);
|
||||
});
|
||||
}
|
||||
}, [initialValues]);
|
||||
return (
|
||||
<Formily
|
||||
form={form}
|
||||
layout={'vertical'}
|
||||
onAutoSubmit={async (values) => {
|
||||
console.log(form.values, values);
|
||||
try {
|
||||
const { data } = await resource.save(values);
|
||||
if (onSuccess) {
|
||||
return onSuccess(data);
|
||||
}
|
||||
return data;
|
||||
} catch (error) {}
|
||||
}}
|
||||
>
|
||||
<SchemaField
|
||||
<FormProvider form={form}>
|
||||
<DesignableSchemaField
|
||||
schema={{
|
||||
type: 'object',
|
||||
properties: fields2properties(fields),
|
||||
properties: schema.properties,
|
||||
}}
|
||||
/>
|
||||
{!readPretty && (
|
||||
<FormButtonGroup>
|
||||
<Submit>提交</Submit>
|
||||
<Reset>取消</Reset>
|
||||
</FormButtonGroup>
|
||||
)}
|
||||
</Formily>
|
||||
</FormProvider>
|
||||
);
|
||||
};
|
||||
|
||||
export default FormBlock;
|
||||
Form.DesignableBar = DesignableBar;
|
||||
|
@ -1,13 +1,25 @@
|
||||
import React, { useState } from 'react';
|
||||
import React, { useState, useContext } from 'react';
|
||||
import { useDrag, mergeRefs, useDrop } from './DND';
|
||||
import classNames from 'classnames';
|
||||
import { useField } from '@formily/react';
|
||||
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 {
|
||||
MenuOutlined,
|
||||
ArrowUpOutlined,
|
||||
ArrowDownOutlined,
|
||||
DeleteOutlined,
|
||||
} from '@ant-design/icons';
|
||||
import get from 'lodash/get';
|
||||
|
||||
export const Block = (props) => {
|
||||
const { children, title } = props;
|
||||
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',
|
||||
@ -24,12 +36,19 @@ export const Block = (props) => {
|
||||
path: field.address.segments,
|
||||
},
|
||||
});
|
||||
console.log('useDrag', 'previewElement', 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();
|
||||
return (
|
||||
<div
|
||||
@ -38,19 +57,67 @@ export const Block = (props) => {
|
||||
className={classNames('block', { 'top-half': onTopHalf, hover: isOver })}
|
||||
// style={{ textAlign: 'center', lineHeight: '60px', background: '#f1f1f1' }}
|
||||
>
|
||||
<ActionBar dragRef={dragRef}/>
|
||||
<div
|
||||
style={{ textAlign: 'center', lineHeight: '60px', background: '#f1f1f1' }}
|
||||
>
|
||||
{children} {field.path.entire}
|
||||
</div>
|
||||
{DesignableBarComponent && (
|
||||
<DefaultActionBar DesignableBarComponent={DesignableBarComponent} dragRef={dragRef} />
|
||||
)}
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
||||
const ActionBar = ({ dragRef }) => {
|
||||
// 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
|
||||
@ -58,40 +125,7 @@ const ActionBar = ({ dragRef }) => {
|
||||
trigger={['click']}
|
||||
visible={active}
|
||||
onVisibleChange={setActive}
|
||||
overlay={
|
||||
<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>
|
||||
}
|
||||
overlay={<Menu>{DesignableBarComponent ? <DesignableBarComponent /> : <Overlay />}</Menu>}
|
||||
>
|
||||
<MenuOutlined className={'draggable'} ref={dragRef} />
|
||||
</Dropdown>
|
||||
|
@ -1,113 +0,0 @@
|
||||
.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.row-divider {
|
||||
position: relative;
|
||||
&.hover {
|
||||
&::after {
|
||||
content: '';
|
||||
display: block;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
height: 12px;
|
||||
width: 100%;
|
||||
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 {
|
||||
position: absolute;
|
||||
top: 5px;
|
||||
right: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.draggable {
|
||||
cursor: grab !important;
|
||||
}
|
@ -1,6 +1,5 @@
|
||||
import React from 'react';
|
||||
import { Row, Col } from '../';
|
||||
import './demo5.less';
|
||||
|
||||
export default () => {
|
||||
return (
|
||||
|
@ -1,36 +1,8 @@
|
||||
import { Grid, Row, Col, Block, SchemaFieldWithDesigner } from '../';
|
||||
import './demo5.less';
|
||||
import { Card } from 'antd';
|
||||
import classNames from 'classnames';
|
||||
import React, { useMemo } from 'react';
|
||||
import {
|
||||
FormProvider,
|
||||
FormConsumer,
|
||||
useField,
|
||||
useFieldSchema,
|
||||
ISchema,
|
||||
Schema,
|
||||
} from '@formily/react';
|
||||
import { createForm } from '@formily/core';
|
||||
import React from 'react';
|
||||
import { SchemaBlock } from '../../';
|
||||
import { ISchema } from '@formily/json-schema';
|
||||
|
||||
function Designer(props: { schema?: ISchema }) {
|
||||
const form = useMemo(() => createForm({}), []);
|
||||
const { schema } = props;
|
||||
return (
|
||||
<div>
|
||||
<FormProvider form={form}>
|
||||
<SchemaFieldWithDesigner schema={schema} />
|
||||
{/* <FormConsumer>
|
||||
{(form) => {
|
||||
return <div>{JSON.stringify(form.values, null, 2)}</div>;
|
||||
}}
|
||||
</FormConsumer> */}
|
||||
</FormProvider>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const schema = new Schema({
|
||||
const schema: ISchema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
grid: {
|
||||
@ -52,9 +24,17 @@ const schema = new Schema({
|
||||
block11: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Block',
|
||||
'x-component-props': {
|
||||
title: 'block11',
|
||||
},
|
||||
'x-content': (
|
||||
<div
|
||||
style={{
|
||||
padding: 24,
|
||||
textAlign: 'center',
|
||||
background: 'rgb(241, 241, 241)',
|
||||
}}
|
||||
>
|
||||
block11
|
||||
</div>
|
||||
),
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -72,6 +52,17 @@ const schema = new Schema({
|
||||
'x-component-props': {
|
||||
title: 'block21',
|
||||
},
|
||||
'x-content': (
|
||||
<div
|
||||
style={{
|
||||
padding: 24,
|
||||
textAlign: 'center',
|
||||
background: 'rgb(241, 241, 241)',
|
||||
}}
|
||||
>
|
||||
block21
|
||||
</div>
|
||||
),
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -91,9 +82,17 @@ const schema = new Schema({
|
||||
block211: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Block',
|
||||
'x-component-props': {
|
||||
title: 'block211',
|
||||
},
|
||||
'x-content': (
|
||||
<div
|
||||
style={{
|
||||
padding: 24,
|
||||
textAlign: 'center',
|
||||
background: 'rgb(241, 241, 241)',
|
||||
}}
|
||||
>
|
||||
block211
|
||||
</div>
|
||||
),
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -108,9 +107,17 @@ const schema = new Schema({
|
||||
block221: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Block',
|
||||
'x-component-props': {
|
||||
title: 'block221',
|
||||
},
|
||||
'x-content': (
|
||||
<div
|
||||
style={{
|
||||
padding: 24,
|
||||
textAlign: 'center',
|
||||
background: 'rgb(241, 241, 241)',
|
||||
}}
|
||||
>
|
||||
block221
|
||||
</div>
|
||||
),
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -119,7 +126,7 @@ const schema = new Schema({
|
||||
row3: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Row',
|
||||
"x-component-props": {
|
||||
'x-component-props': {
|
||||
isLast: true,
|
||||
},
|
||||
properties: {
|
||||
@ -134,9 +141,17 @@ const schema = new Schema({
|
||||
block311: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Block',
|
||||
'x-component-props': {
|
||||
title: 'block311',
|
||||
},
|
||||
'x-content': (
|
||||
<div
|
||||
style={{
|
||||
padding: 24,
|
||||
textAlign: 'center',
|
||||
background: 'rgb(241, 241, 241)',
|
||||
}}
|
||||
>
|
||||
block311
|
||||
</div>
|
||||
),
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -145,64 +160,8 @@ const schema = new Schema({
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
};
|
||||
|
||||
export default () => {
|
||||
return (
|
||||
<Card>
|
||||
<Designer
|
||||
schema={schema}
|
||||
/>
|
||||
{/* <Grid
|
||||
onDrop={(e) => {
|
||||
console.log('onDrop', e, e.data);
|
||||
}}
|
||||
>
|
||||
<Row
|
||||
onColResize={(e) => {
|
||||
console.log(e.data);
|
||||
}}
|
||||
>
|
||||
{[1, 2, 3].map((index) => (
|
||||
<Col size={1 / 3}>
|
||||
<Block>col {index}</Block>
|
||||
</Col>
|
||||
))}
|
||||
</Row>
|
||||
<Row
|
||||
onColResize={(e) => {
|
||||
console.log(e.data);
|
||||
}}
|
||||
>
|
||||
{[4, 5, 6].map((index) => (
|
||||
<Col size={1 / 3}>
|
||||
<Block>col {index}</Block>
|
||||
</Col>
|
||||
))}
|
||||
</Row>
|
||||
<Row
|
||||
onColResize={(e) => {
|
||||
console.log(e.data);
|
||||
}}
|
||||
>
|
||||
{[7, 8].map((index) => (
|
||||
<Col size={1 / 3}>
|
||||
<Block>col {index}</Block>
|
||||
</Col>
|
||||
))}
|
||||
</Row>
|
||||
<Row
|
||||
onColResize={(e) => {
|
||||
console.log(e.data);
|
||||
}}
|
||||
>
|
||||
{[9].map((index) => (
|
||||
<Col size={1}>
|
||||
<Block>col {index}</Block>
|
||||
</Col>
|
||||
))}
|
||||
</Row>
|
||||
</Grid> */}
|
||||
</Card>
|
||||
);
|
||||
return <SchemaBlock schema={schema} />;
|
||||
};
|
||||
|
@ -73,16 +73,7 @@ group:
|
||||
|
||||
- 100%
|
||||
|
||||
|
||||
## 代码演示
|
||||
<!--
|
||||
### 基本用法
|
||||
|
||||
<code src="./demos/demo2.tsx"/>
|
||||
|
||||
### 内嵌区块
|
||||
|
||||
<code src="./demos/demo3.tsx"/> -->
|
||||
|
||||
### useDrag & useDrop
|
||||
|
||||
|
@ -7,31 +7,12 @@ import {
|
||||
useField,
|
||||
} from '@formily/react';
|
||||
import { uid } from '@formily/shared';
|
||||
import { SchemaField } from '../../fields';
|
||||
import set from 'lodash/set';
|
||||
import './style.less';
|
||||
|
||||
export const SchemaDesignerContext = createContext<Schema>(new Schema({}));
|
||||
export const SchemaRefreshContext = createContext(null);
|
||||
|
||||
export function SchemaFieldWithDesigner(props: { schema?: ISchema }) {
|
||||
function Container(props) {
|
||||
const { schema } = props;
|
||||
const [, refresh] = useState(0);
|
||||
return (
|
||||
<SchemaRefreshContext.Provider
|
||||
value={() => {
|
||||
refresh(Math.random());
|
||||
}}
|
||||
>
|
||||
<SchemaDesignerContext.Provider value={schema}>
|
||||
<SchemaField schema={schema} />
|
||||
</SchemaDesignerContext.Provider>
|
||||
{/* <pre>{JSON.stringify(schema.toJSON(), null, 2)}</pre> */}
|
||||
</SchemaRefreshContext.Provider>
|
||||
);
|
||||
}
|
||||
return <Container schema={new Schema(props.schema)} />;
|
||||
}
|
||||
import {
|
||||
DesignableSchemaContext,
|
||||
RefreshDesignableSchemaContext,
|
||||
} from '../SchemaField';
|
||||
|
||||
export function removeProperty(property: Schema) {
|
||||
property.parent.removeProperty(property.name);
|
||||
@ -71,8 +52,8 @@ export const getSchemaAddressSegments = (schema: Schema) => {
|
||||
};
|
||||
|
||||
export function useSchemaQuery() {
|
||||
const context = useContext(SchemaDesignerContext);
|
||||
const refresh = useContext(SchemaRefreshContext);
|
||||
const context = useContext(DesignableSchemaContext);
|
||||
const refresh = useContext(RefreshDesignableSchemaContext);
|
||||
const fieldSchema = useFieldSchema();
|
||||
const field = useField();
|
||||
const form = useForm();
|
||||
@ -126,15 +107,15 @@ export function useSchemaQuery() {
|
||||
const name = names[index];
|
||||
const property = row.properties[name];
|
||||
const addProperty = isOver ? addPropertyAfter : addPropertyBefore;
|
||||
const count = Object.keys(row.properties).length+1;
|
||||
const count = Object.keys(row.properties).length + 1;
|
||||
return (data) => {
|
||||
const other = 1-(1/count);
|
||||
Object.keys(row.properties).forEach(name => {
|
||||
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 => {
|
||||
form.setFieldState(segments.join('.'), (state) => {
|
||||
state.componentProps.size = other * state.componentProps.size;
|
||||
console.log({state}, other * state.componentProps.size);
|
||||
console.log({ state }, other * state.componentProps.size);
|
||||
});
|
||||
});
|
||||
addProperty(property, {
|
||||
@ -142,7 +123,7 @@ export function useSchemaQuery() {
|
||||
name: `c_${uid()}`,
|
||||
'x-component': 'Grid.Col',
|
||||
'x-component-props': {
|
||||
size: 1/count,
|
||||
size: 1 / count,
|
||||
},
|
||||
properties: {
|
||||
[data.name]: data,
|
||||
@ -177,18 +158,18 @@ export function useSchemaQuery() {
|
||||
removeProperty(schema.parent);
|
||||
const cols = [];
|
||||
let allSize = 0;
|
||||
Object.keys(schema.parent.parent.properties).forEach(name => {
|
||||
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 => {
|
||||
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;
|
||||
form.setFieldState(segments.join('.'), (state) => {
|
||||
state.componentProps.size = state.componentProps.size / allSize;
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -219,7 +200,7 @@ export function useSchemaQuery() {
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
});
|
||||
} else {
|
||||
addProperty(schema, data);
|
||||
}
|
||||
@ -241,18 +222,18 @@ export function useSchemaQuery() {
|
||||
source.parent.parent.removeProperty(source.parent.name);
|
||||
const cols = [];
|
||||
let allSize = 0;
|
||||
Object.keys(source.parent.parent.properties).forEach(name => {
|
||||
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 => {
|
||||
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;
|
||||
form.setFieldState(segments.join('.'), (state) => {
|
||||
state.componentProps.size = state.componentProps.size / allSize;
|
||||
});
|
||||
}
|
||||
} else {
|
||||
|
@ -1,143 +1,114 @@
|
||||
.drop-column {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: -12px;
|
||||
height: calc(100% - 24px);
|
||||
width: 24px;
|
||||
// border: 1px dashed rgb(232, 232, 232);
|
||||
&.active {
|
||||
.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;
|
||||
width: 12px;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
left: 5px;
|
||||
background: #e6f7ff;
|
||||
}
|
||||
}
|
||||
&.last {
|
||||
left: auto;
|
||||
right: -24px;
|
||||
}
|
||||
}
|
||||
.drop-block {
|
||||
height: 12px;
|
||||
position: absolute;
|
||||
bottom: -12px;
|
||||
width: 100%;
|
||||
left: 0;
|
||||
// background: #e6f7ff;
|
||||
// pointer-events: none;
|
||||
&.active {
|
||||
background: #e6f7ff;
|
||||
}
|
||||
}
|
||||
// .ant-formily-item + .drop-block {
|
||||
// position: absolute;
|
||||
// bottom: 12px;
|
||||
// width: 100%;
|
||||
// }
|
||||
.grid-row {
|
||||
position: relative;
|
||||
|
||||
&.active {
|
||||
&::after {
|
||||
content: '';
|
||||
display: block;
|
||||
height: 12px;
|
||||
position: absolute;
|
||||
bottom: 5px;
|
||||
width: 100%;
|
||||
width: 12px;
|
||||
background: #e6f7ff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.grid-block {
|
||||
.row-divider {
|
||||
position: relative;
|
||||
padding-bottom: 1px;
|
||||
&.active {
|
||||
&.hover {
|
||||
&::after {
|
||||
content: '';
|
||||
display: block;
|
||||
height: 12px;
|
||||
position: absolute;
|
||||
bottom: 5px;
|
||||
left: 0;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
height: 12px;
|
||||
width: 100%;
|
||||
background: #e6f7ff;
|
||||
}
|
||||
}
|
||||
|
||||
&.grid-block--Input {
|
||||
&: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;
|
||||
.drop-block {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}rgb(172, 172, 172)
|
||||
.grid-block-actions {
|
||||
position: absolute;
|
||||
top: 5px;
|
||||
right: 5px;
|
||||
z-index: 2;
|
||||
display: none;
|
||||
}
|
||||
> .action-bar {
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
top: 5px;
|
||||
z-index: 2;
|
||||
display: none;
|
||||
&.active {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
&:hover,
|
||||
&.hover {
|
||||
> .action-bar {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.grid-block .ant-card {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.drop-row {
|
||||
height: 12px;
|
||||
// background: #e6f7ff;
|
||||
position: absolute;
|
||||
bottom: 5px;
|
||||
width: 100%;
|
||||
// background: #e6f7ff;
|
||||
&.active {
|
||||
background: #e6f7ff;
|
||||
}
|
||||
|
||||
&.first {
|
||||
top: -17px;
|
||||
z-index: 2;
|
||||
bottom: auto;
|
||||
right: 8px;
|
||||
z-index: 222;
|
||||
}
|
||||
}
|
||||
|
||||
.draggable {
|
||||
cursor: move !important; /* fallback: no `url()` support or images disabled */
|
||||
cursor: -webkit-grab !important; /* Chrome 1-21, Safari 4+ */
|
||||
cursor: -moz-grab !important; /* Firefox 1.5-26 */
|
||||
cursor: grab !important; /* W3C standards syntax, should come least */
|
||||
}
|
||||
|
||||
.draggable:active {
|
||||
cursor: -webkit-grabbing;
|
||||
cursor: -moz-grabbing;
|
||||
cursor: grabbing;
|
||||
}
|
||||
tr.drop-over-downward td {
|
||||
border-bottom: 1px dashed #1890ff;
|
||||
}
|
||||
|
||||
tr.drop-over-upward td {
|
||||
border-top: 1px dashed #1890ff;
|
||||
cursor: grab !important;
|
||||
}
|
@ -1,338 +0,0 @@
|
||||
import React, { useContext, createContext, useState } from 'react';
|
||||
import { Schema, useFieldSchema, useForm } from '@formily/react';
|
||||
import { uid } from '@formily/shared';
|
||||
import { SchemaField } from '../../fields';
|
||||
import set from 'lodash/set';
|
||||
|
||||
export const SchemaDesignerContext = createContext<Schema>(new Schema({}));
|
||||
export const SchemaRefreshContext = createContext(null);
|
||||
|
||||
export function SchemaFieldWithDesigner(props) {
|
||||
function Container(props) {
|
||||
const { schema } = props;
|
||||
const [, refresh] = useState(0);
|
||||
return (
|
||||
<SchemaRefreshContext.Provider
|
||||
value={() => {
|
||||
refresh(Math.random());
|
||||
}}
|
||||
>
|
||||
<SchemaDesignerContext.Provider value={schema}>
|
||||
<SchemaField schema={schema} />
|
||||
</SchemaDesignerContext.Provider>
|
||||
</SchemaRefreshContext.Provider>
|
||||
);
|
||||
}
|
||||
return <Container schema={new Schema(props.schema)} />;
|
||||
}
|
||||
|
||||
export const getFullPaths = (schema: Schema) => {
|
||||
if (!schema) {
|
||||
return [];
|
||||
}
|
||||
const paths = [schema.name];
|
||||
if (schema.parent && schema.parent.name) {
|
||||
paths.unshift(...getFullPaths(schema.parent));
|
||||
}
|
||||
return paths;
|
||||
};
|
||||
|
||||
export function useSchema() {
|
||||
const context = useContext(SchemaDesignerContext);
|
||||
const refresh = useContext(SchemaRefreshContext);
|
||||
const fieldSchema = useFieldSchema();
|
||||
const paths = getFullPaths(fieldSchema);
|
||||
let schema: Schema = context;
|
||||
const names = [...paths];
|
||||
while (names.length) {
|
||||
schema = schema.properties[names.shift()];
|
||||
}
|
||||
return { schema, fieldSchema, refresh };
|
||||
}
|
||||
|
||||
export function getSchema(context) {
|
||||
return (paths) => {
|
||||
const fullPaths = Array.isArray(paths) ? paths : getFullPaths(paths);
|
||||
let s: Schema = context;
|
||||
const names = [...fullPaths];
|
||||
while (names.length) {
|
||||
s = s.properties[names.shift()];
|
||||
}
|
||||
return s;
|
||||
};
|
||||
}
|
||||
|
||||
export function addPropertyBefore(target, prop) {
|
||||
Object.keys(target.parent.properties).forEach((name) => {
|
||||
if (name === target.name) {
|
||||
target.parent.addProperty(prop.name, prop);
|
||||
}
|
||||
const property = target.parent.properties[name];
|
||||
property.parent.removeProperty(property.name);
|
||||
target.parent.addProperty(property.name, property.toJSON());
|
||||
});
|
||||
}
|
||||
|
||||
export function addPropertyAfter(target, prop) {
|
||||
Object.keys(target.parent.properties).forEach((name) => {
|
||||
const property = target.parent.properties[name];
|
||||
property.parent.removeProperty(property.name);
|
||||
target.parent.addProperty(property.name, property.toJSON());
|
||||
if (name === target.name) {
|
||||
target.parent.addProperty(prop.name, prop);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function useSchemaQuery() {
|
||||
const context = useContext(SchemaDesignerContext);
|
||||
const form = useForm();
|
||||
const { schema, refresh } = useSchema();
|
||||
|
||||
return {
|
||||
resizeColumn() {
|
||||
function getPrevProperty() {
|
||||
const names = Object.keys(schema.parent.properties);
|
||||
const index = names.indexOf(schema.name as string);
|
||||
return schema.parent.properties[names[index-1]];
|
||||
}
|
||||
console.log('onMouseDown', schema.name);
|
||||
console.log('onMouseDown', schema.parent.properties);
|
||||
console.log('onMouseDown', getFullPaths(getPrevProperty()));
|
||||
form.setFieldState(getFullPaths(getPrevProperty()).join('.'), (state) => {
|
||||
const span = state.componentProps.span;
|
||||
let width = state.componentProps.width||50;
|
||||
width -= 1;
|
||||
state.componentProps = {
|
||||
span: 1*span - 1,
|
||||
width,
|
||||
style: {
|
||||
flex: `0 0 ${width}%`,
|
||||
maxWidth: `${width}%`,
|
||||
},
|
||||
};
|
||||
});
|
||||
console.log('onMouseDown', getFullPaths(schema));
|
||||
form.setFieldState(getFullPaths(schema).join('.'), (state) => {
|
||||
const span = state.componentProps.span;
|
||||
console.log({span})
|
||||
let width = state.componentProps.width||50;
|
||||
width += 1;
|
||||
state.componentProps = {
|
||||
span: 1*span + 1,
|
||||
style: {
|
||||
flex: `0 0 ${width}%`,
|
||||
maxWidth: `${width}%`,
|
||||
},
|
||||
};
|
||||
});
|
||||
refresh();
|
||||
},
|
||||
removeBlock() {
|
||||
schema.parent.removeProperty(schema.name);
|
||||
refresh();
|
||||
},
|
||||
addBlock: (data, up = false) => {
|
||||
const blockSchema = block({
|
||||
type: 'string',
|
||||
title: `Block ${uid()}`,
|
||||
required: true,
|
||||
'x-read-pretty': false,
|
||||
// 'x-decorator': 'FormItem',
|
||||
'x-component': 'Hello',
|
||||
rowOrder: 2,
|
||||
columnOrder: 1,
|
||||
blockOrder: 1,
|
||||
});
|
||||
if ('Grid.Column' === schema.parent['x-component']) {
|
||||
if (Object.keys(schema.parent.parent.properties).length > 1) {
|
||||
up
|
||||
? addPropertyBefore(schema, blockSchema)
|
||||
: addPropertyAfter(schema, blockSchema);
|
||||
} else {
|
||||
const rowSchema = row(column(blockSchema));
|
||||
up
|
||||
? addPropertyAfter(schema.parent.parent, rowSchema)
|
||||
: addPropertyAfter(schema.parent.parent, rowSchema);
|
||||
}
|
||||
}
|
||||
console.log('x-component', schema.parent['x-component']);
|
||||
refresh();
|
||||
// schema.parent['x-component']
|
||||
},
|
||||
insertAfter: (sourcePath, targetPath) => {
|
||||
const source = getSchema(context)(sourcePath);
|
||||
const target = getSchema(context)(targetPath);
|
||||
if (!source || !target) {
|
||||
return;
|
||||
}
|
||||
console.log({
|
||||
sourcePath,
|
||||
source,
|
||||
target,
|
||||
targetPath,
|
||||
sourceParentproperties: source.parent.properties,
|
||||
});
|
||||
const names = [];
|
||||
Object.keys(target.parent.properties).forEach((name) => {
|
||||
// if (names.includes(source.name)) {
|
||||
// return;
|
||||
// }
|
||||
// names.push(name);
|
||||
const property = target.parent.properties[name];
|
||||
property.parent.removeProperty(property.name);
|
||||
target.parent.addProperty(property.name, property.toJSON());
|
||||
if (name === target.name) {
|
||||
// names.push(source.name);
|
||||
source.parent.removeProperty(source.name);
|
||||
console.log('source.parent.properties', source.parent.properties);
|
||||
target.parent.addProperty(source.name, source.toJSON());
|
||||
}
|
||||
});
|
||||
},
|
||||
insertAfterWithAddRow: (sourcePath, targetPath) => {
|
||||
const source = getSchema(context)(sourcePath);
|
||||
const target = getSchema(context)(targetPath);
|
||||
const rowSchema = row(column(source.toJSON()));
|
||||
source.parent.removeProperty(source.name);
|
||||
Object.keys(target.parent.properties).forEach((name) => {
|
||||
const property = target.parent.properties[name];
|
||||
property.parent.removeProperty(property.name);
|
||||
target.parent.addProperty(property.name, property.toJSON());
|
||||
if (name === target.name) {
|
||||
target.parent.addProperty(rowSchema.name, rowSchema);
|
||||
}
|
||||
});
|
||||
},
|
||||
insertBeforeWithAddRow: (sourcePath, targetPath) => {
|
||||
const source = getSchema(context)(sourcePath);
|
||||
const target = getSchema(context)(targetPath);
|
||||
const rowSchema = row(column(source.toJSON()));
|
||||
source.parent.removeProperty(source.name);
|
||||
Object.keys(target.parent.properties).forEach((name) => {
|
||||
if (name === target.name) {
|
||||
target.parent.addProperty(rowSchema.name, rowSchema);
|
||||
}
|
||||
const property = target.parent.properties[name];
|
||||
property.parent.removeProperty(property.name);
|
||||
target.parent.addProperty(property.name, property.toJSON());
|
||||
});
|
||||
},
|
||||
appendToRowWithAddColumn: (sourcePath, targetPath) => {
|
||||
const source = getSchema(context)(sourcePath);
|
||||
const target = getSchema(context)(targetPath);
|
||||
const colSchema = column(source.toJSON());
|
||||
source.parent.removeProperty(source.name);
|
||||
const len = Object.keys(target.properties).length + 1;
|
||||
target.addProperty(colSchema.name, colSchema);
|
||||
console.log('target.properties', target.properties);
|
||||
Object.keys(target.properties).forEach((name) => {
|
||||
const prop = target.properties[name];
|
||||
form.setFieldState(getFullPaths(prop).join('.'), (state) => {
|
||||
state.componentProps = {
|
||||
span: 24 / len,
|
||||
};
|
||||
});
|
||||
});
|
||||
},
|
||||
insertBeforeWithAddColumn: (sourcePath, targetPath) => {
|
||||
const source = getSchema(context)(sourcePath);
|
||||
const target = getSchema(context)(targetPath);
|
||||
const colSchema = column(source.toJSON());
|
||||
source.parent.removeProperty(source.name);
|
||||
const len = Object.keys(target.parent.properties).length + 1;
|
||||
console.log('x-component-props.span', 24 / len);
|
||||
Object.keys(target.parent.properties).forEach((name) => {
|
||||
if (name === target.name) {
|
||||
target.parent.addProperty(colSchema.name, colSchema);
|
||||
}
|
||||
const property = target.parent.properties[name];
|
||||
property.parent.removeProperty(property.name);
|
||||
const json = property.toJSON();
|
||||
target.parent.addProperty(property.name, json);
|
||||
});
|
||||
Object.keys(target.parent.properties).forEach((name) => {
|
||||
const prop = target.parent.properties[name];
|
||||
form.setFieldState(getFullPaths(prop).join('.'), (state) => {
|
||||
state.componentProps = {
|
||||
span: 24 / len,
|
||||
};
|
||||
});
|
||||
});
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export const grid = (...rows: any[]) => {
|
||||
const rowProperties = {};
|
||||
rows.forEach((row, index) => {
|
||||
set(row, 'x-component-props.rowOrder', index);
|
||||
rowProperties[row.name] = row;
|
||||
});
|
||||
const name = `g_${uid()}`;
|
||||
return {
|
||||
type: 'void',
|
||||
name,
|
||||
'x-component': 'Grid',
|
||||
'x-component-props': {},
|
||||
properties: rowProperties,
|
||||
};
|
||||
};
|
||||
|
||||
export const row = (...cols: any[]) => {
|
||||
const rowName = `r_${uid()}`;
|
||||
const colsProperties = {};
|
||||
cols.forEach((col, index) => {
|
||||
set(col, 'x-component-props.columnOrder', index);
|
||||
set(col, 'x-component-props.span', 24 / cols.length);
|
||||
colsProperties[col.name] = col;
|
||||
});
|
||||
return {
|
||||
type: 'void',
|
||||
name: rowName,
|
||||
'x-component': 'Grid.Row',
|
||||
'x-component-props': {},
|
||||
properties: colsProperties,
|
||||
};
|
||||
};
|
||||
|
||||
export const column = (...blocks: any[]) => {
|
||||
const colName = `c_${uid()}`;
|
||||
const properties = {};
|
||||
blocks.forEach((item) => {
|
||||
properties[item.name] = item;
|
||||
});
|
||||
return {
|
||||
name: colName,
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Column',
|
||||
'x-read-pretty': true,
|
||||
'x-component-props': {
|
||||
labelCol: 6,
|
||||
wrapperCol: 10,
|
||||
span: 24,
|
||||
},
|
||||
properties,
|
||||
};
|
||||
};
|
||||
|
||||
export const block = (...fields: any[]) => {
|
||||
const blockName = `b_${uid()}`;
|
||||
const properties = {};
|
||||
fields.forEach((item) => {
|
||||
const name = item.name || `f_${uid()}`;
|
||||
// item.title = `${item.title} ${name}`;
|
||||
properties[name] = item;
|
||||
});
|
||||
const lastComponentType = fields[fields.length - 1]['x-component'];
|
||||
return {
|
||||
name: blockName,
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Block',
|
||||
'x-component-props': {
|
||||
lastComponentType,
|
||||
},
|
||||
'x-read-pretty': false,
|
||||
properties,
|
||||
};
|
||||
};
|
57
packages/client/src/blocks/icon-picker/index.md
Normal file
57
packages/client/src/blocks/icon-picker/index.md
Normal file
@ -0,0 +1,57 @@
|
||||
---
|
||||
title: IconPicker - 图标选择器
|
||||
nav:
|
||||
title: 组件
|
||||
path: /client
|
||||
group:
|
||||
order: 1
|
||||
title: 区块 - Blocks
|
||||
path: /client/blocks
|
||||
---
|
||||
|
||||
# IconPicker - 图标选择器
|
||||
|
||||
```tsx
|
||||
/**
|
||||
* title: 图标
|
||||
*/
|
||||
import React from 'react';
|
||||
import { SchemaBlock } from '../';
|
||||
|
||||
const schema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
input: {
|
||||
interface: 'icon',
|
||||
type: 'string',
|
||||
title: `编辑模式`,
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'IconPicker',
|
||||
'x-reactions': {
|
||||
target: 'read',
|
||||
fulfill: {
|
||||
state: {
|
||||
value: '{{$self.value}}',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
read: {
|
||||
interface: 'icon',
|
||||
type: 'string',
|
||||
title: `阅读模式`,
|
||||
'x-read-pretty': true,
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'IconPicker',
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
export default () => {
|
||||
return (
|
||||
<div>
|
||||
<SchemaBlock schema={schema} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
```
|
@ -2,7 +2,78 @@ import React, { useState } from 'react';
|
||||
import { connect, mapProps, mapReadPretty } from '@formily/react';
|
||||
import { Input, Popover, Button } from 'antd';
|
||||
import { LoadingOutlined, CloseOutlined } from '@ant-design/icons';
|
||||
import { icons, hasIcon, Icon } from '../../components/Icon';
|
||||
import {
|
||||
createFromIconfontCN,
|
||||
UserOutlined,
|
||||
TeamOutlined,
|
||||
DatabaseOutlined,
|
||||
DashboardOutlined,
|
||||
SettingOutlined,
|
||||
TableOutlined,
|
||||
MenuOutlined,
|
||||
HistoryOutlined,
|
||||
NotificationOutlined,
|
||||
} from '@ant-design/icons';
|
||||
|
||||
import * as antIcons from '@ant-design/icons';
|
||||
|
||||
export const IconFont = createFromIconfontCN({
|
||||
scriptUrl: ['//at.alicdn.com/t/font_2261954_u9jzwc44ug.js'],
|
||||
});
|
||||
|
||||
export const icons = new Map<string, any>();
|
||||
|
||||
export function registerIcon(type: string, icon: any = IconFont) {
|
||||
icons.set(type.toLowerCase(), icon);
|
||||
}
|
||||
|
||||
export function hasIcon(type: string) {
|
||||
if (!type) {
|
||||
return false;
|
||||
}
|
||||
return icons.has(type.toLowerCase());
|
||||
}
|
||||
|
||||
export function registerIcons(components) {
|
||||
Object.keys(components).forEach((type) => {
|
||||
registerIcon(type, components[type]);
|
||||
});
|
||||
}
|
||||
|
||||
Object.keys(antIcons).forEach((name) => {
|
||||
if (name.endsWith('Outlined')) {
|
||||
registerIcon(name, antIcons[name]);
|
||||
}
|
||||
});
|
||||
|
||||
registerIcons({
|
||||
// HistoryOutlined,
|
||||
// MenuOutlined,
|
||||
// TableOutlined,
|
||||
// SettingOutlined,
|
||||
// TeamOutlined,
|
||||
// UserOutlined,
|
||||
// DatabaseOutlined,
|
||||
// DashboardOutlined,
|
||||
// NotificationOutlined,
|
||||
});
|
||||
|
||||
interface IconProps {
|
||||
type: string;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export function Icon(props: IconProps) {
|
||||
const { type = '', ...restProps } = props;
|
||||
if (type && icons.has(type.toLowerCase())) {
|
||||
const IconComponent = icons.get(type.toLowerCase());
|
||||
if (IconComponent === IconFont) {
|
||||
return <IconFont type={type} />;
|
||||
}
|
||||
return <IconComponent {...restProps} />;
|
||||
}
|
||||
return <IconFont type={type} />;
|
||||
}
|
||||
|
||||
function IconField(props: any) {
|
||||
const { value, onChange } = props;
|
||||
@ -17,7 +88,9 @@ function IconField(props: any) {
|
||||
setVisible(val);
|
||||
}}
|
||||
content={
|
||||
<div style={{width: '26em', maxHeight: '20em', overflowY: 'auto'}}>
|
||||
<div
|
||||
style={{ width: '26em', maxHeight: '20em', overflowY: 'auto' }}
|
||||
>
|
||||
{[...icons.keys()].map((key) => (
|
||||
<span
|
||||
style={{ fontSize: 18, marginRight: 10, cursor: 'pointer' }}
|
@ -1,90 +0,0 @@
|
||||
---
|
||||
title: Block
|
||||
nav:
|
||||
title: 组件
|
||||
path: /client
|
||||
group:
|
||||
order: 2
|
||||
title: Blocks
|
||||
path: /client/blocks
|
||||
---
|
||||
|
||||
# 区块
|
||||
|
||||
## 示例
|
||||
|
||||
```tsx
|
||||
import React from 'react';
|
||||
import { createBlock, GridBlock } from '@nocobase/client';
|
||||
|
||||
function Hello({ name }) {
|
||||
return <div>Hello {name}</div>;
|
||||
}
|
||||
|
||||
const Block = createBlock({
|
||||
components: {
|
||||
Hello,
|
||||
GridBlock,
|
||||
},
|
||||
});
|
||||
|
||||
const blocks = [
|
||||
{
|
||||
name: 'gb1',
|
||||
'x-component': 'Hello',
|
||||
'x-row': 0,
|
||||
'x-column': 0,
|
||||
'x-sort': 1,
|
||||
},
|
||||
{
|
||||
name: 'gb2',
|
||||
'x-component': 'Hello',
|
||||
'x-row': 0,
|
||||
'x-column': 0,
|
||||
'x-sort': 2,
|
||||
},
|
||||
{
|
||||
name: 'gb3',
|
||||
'x-component': 'Hello',
|
||||
'x-row': 0,
|
||||
'x-column': 1,
|
||||
'x-sort': 0,
|
||||
},
|
||||
{
|
||||
name: 'gb4',
|
||||
'x-component': 'Hello',
|
||||
'x-row': 1,
|
||||
'x-column': 0,
|
||||
'x-sort': 0,
|
||||
},
|
||||
{
|
||||
name: 'gb5',
|
||||
'x-component': 'Hello',
|
||||
'x-row': 2,
|
||||
'x-column': 0,
|
||||
'x-sort': 0,
|
||||
},
|
||||
];
|
||||
|
||||
export default () => {
|
||||
return (
|
||||
<div>
|
||||
<Block x-component={'Hello'} name={'demo1'}/>
|
||||
<Block x-component={'Hello'} name={'demo2'}/>
|
||||
<Block x-component={'GridBlock'} blocks={blocks}/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
### createBlock
|
||||
|
||||
- components 区块组件
|
||||
|
||||
创建区块
|
||||
|
||||
### Block
|
||||
|
||||
- x-component
|
@ -1,46 +1,36 @@
|
||||
import React, { createContext, useContext } from 'react';
|
||||
import React, { useMemo } from 'react';
|
||||
import { createForm } from '@formily/core';
|
||||
import { FormProvider } from '@formily/react';
|
||||
import { DesignableSchemaField, SchemaField } from './SchemaField';
|
||||
|
||||
export * from './form';
|
||||
export * from './descriptions';
|
||||
export * from './table';
|
||||
export * from './SchemaField';
|
||||
|
||||
export const BlockContext = createContext({});
|
||||
export const SchemaBlock = ({ schema, onlyRenderProperties = false, designable = true }) => {
|
||||
const form = useMemo(() => createForm(), []);
|
||||
|
||||
export interface BlockProps {
|
||||
name: string;
|
||||
blocks?: any;
|
||||
'x-component'?: any;
|
||||
[key: string]: any;
|
||||
}
|
||||
let s = schema;
|
||||
|
||||
export interface CreateBlockOptions {
|
||||
components?: any;
|
||||
}
|
||||
|
||||
export function createBlock(options: CreateBlockOptions) {
|
||||
const { components } = options;
|
||||
function Block(props: BlockProps) {
|
||||
const { ['x-component']: component } = props;
|
||||
const Component = component ? components[component] : null;
|
||||
if (!Component) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<BlockContext.Provider value={components}>
|
||||
<Component {...props}/>
|
||||
</BlockContext.Provider>
|
||||
)
|
||||
if (onlyRenderProperties) {
|
||||
s = {
|
||||
type: 'object',
|
||||
properties: schema.properties,
|
||||
};
|
||||
} else if (schema.name) {
|
||||
s = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
[schema.name]: schema,
|
||||
},
|
||||
};
|
||||
}
|
||||
return Block;
|
||||
}
|
||||
|
||||
export function useBlock(props: any) {
|
||||
const components = useContext(BlockContext);
|
||||
const { 'x-component': component } = props;
|
||||
|
||||
const Component = component ? components[component] : null;
|
||||
|
||||
return {
|
||||
Component,
|
||||
}
|
||||
}
|
||||
return (
|
||||
<FormProvider form={form}>
|
||||
{designable ? (
|
||||
<DesignableSchemaField schema={s} />
|
||||
) : (
|
||||
<SchemaField schema={s} />
|
||||
)}
|
||||
</FormProvider>
|
||||
);
|
||||
};
|
||||
|
109
packages/client/src/blocks/input-number/index.md
Normal file
109
packages/client/src/blocks/input-number/index.md
Normal file
@ -0,0 +1,109 @@
|
||||
---
|
||||
title: InputNumber - 数字框
|
||||
nav:
|
||||
title: 组件
|
||||
path: /client
|
||||
group:
|
||||
order: 1
|
||||
title: 区块 - Blocks
|
||||
path: /client/blocks
|
||||
---
|
||||
|
||||
# InputNumber - 数字框
|
||||
|
||||
### 数字框
|
||||
|
||||
```tsx
|
||||
/**
|
||||
* title: 数字框
|
||||
*/
|
||||
import React from 'react';
|
||||
import { SchemaBlock } from '../';
|
||||
|
||||
const schema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
input: {
|
||||
type: 'number',
|
||||
title: `编辑模式`,
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'InputNumber',
|
||||
'x-reactions': {
|
||||
target: 'read',
|
||||
fulfill: {
|
||||
state: {
|
||||
value: '{{$self.value}}',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
read: {
|
||||
interface: 'number',
|
||||
type: 'number',
|
||||
title: `阅读模式`,
|
||||
'x-read-pretty': true,
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'InputNumber',
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
export default () => {
|
||||
return (
|
||||
<SchemaBlock schema={schema} />
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
### 百分比
|
||||
|
||||
```tsx
|
||||
/**
|
||||
* title: 百分比
|
||||
*/
|
||||
import React from 'react';
|
||||
import { SchemaBlock } from '../';
|
||||
|
||||
const schema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
input: {
|
||||
type: 'number',
|
||||
title: `编辑模式`,
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'InputNumber',
|
||||
'x-component-props': {
|
||||
placeholder: 'please enter',
|
||||
formatter: value => `${value}%`,
|
||||
parser: value => value.replace('%', ''),
|
||||
},
|
||||
'x-reactions': {
|
||||
target: 'read',
|
||||
fulfill: {
|
||||
state: {
|
||||
value: '{{$self.value}}',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
read: {
|
||||
type: 'number',
|
||||
title: `阅读模式`,
|
||||
'x-read-pretty': true,
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'InputNumber',
|
||||
'x-component-props': {
|
||||
placeholder: 'please enter',
|
||||
formatter: value => `${value}%`,
|
||||
parser: value => value.replace('%', ''),
|
||||
},
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
export default () => {
|
||||
return (
|
||||
<SchemaBlock schema={schema} />
|
||||
);
|
||||
};
|
||||
```
|
@ -1,10 +1,10 @@
|
||||
import { connect, mapReadPretty } from '@formily/react'
|
||||
import { InputNumber as AntdNumber } from 'antd'
|
||||
import { Descriptions } from '../descriptions'
|
||||
import { Display } from '../display'
|
||||
|
||||
export const InputNumber = connect(
|
||||
AntdNumber,
|
||||
mapReadPretty(Descriptions.InputNumber)
|
||||
mapReadPretty(Display.InputNumber)
|
||||
)
|
||||
|
||||
export default InputNumber
|
69
packages/client/src/blocks/input/DesignableBar.tsx
Normal file
69
packages/client/src/blocks/input/DesignableBar.tsx
Normal file
@ -0,0 +1,69 @@
|
||||
import React, { useState } from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { useField, observer, RecursionField, Schema } from '@formily/react';
|
||||
import { Dropdown, Menu, Switch } from 'antd';
|
||||
import { useSchemaQuery } from '../grid';
|
||||
import {
|
||||
MenuOutlined,
|
||||
ArrowUpOutlined,
|
||||
ArrowDownOutlined,
|
||||
DeleteOutlined,
|
||||
} from '@ant-design/icons';
|
||||
|
||||
export const DesignableBar = (props) => {
|
||||
const { schema, addBlock, removeBlock, refresh } = useSchemaQuery();
|
||||
const [active, setActive] = useState(false);
|
||||
const field = useField();
|
||||
return (
|
||||
<>
|
||||
<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={() => {}}>
|
||||
<Switch
|
||||
onChange={(checked) => {
|
||||
const key = Object.keys(schema.properties).shift();
|
||||
schema.properties[key].required = checked;
|
||||
refresh();
|
||||
field.query(key).take((target: Formily.Core.Models.Field) => {
|
||||
target.required = checked;
|
||||
});
|
||||
}}
|
||||
checkedChildren="必填"
|
||||
unCheckedChildren="非必填"
|
||||
/>
|
||||
</Menu.Item>
|
||||
<Menu.Divider />
|
||||
<Menu.Item
|
||||
onClick={() => {
|
||||
removeBlock();
|
||||
setActive(false);
|
||||
}}
|
||||
icon={<DeleteOutlined />}
|
||||
>
|
||||
删除区块
|
||||
</Menu.Item>
|
||||
</>
|
||||
);
|
||||
};
|
260
packages/client/src/blocks/input/index.md
Normal file
260
packages/client/src/blocks/input/index.md
Normal file
@ -0,0 +1,260 @@
|
||||
---
|
||||
title: Input - 输入框
|
||||
nav:
|
||||
title: 组件
|
||||
path: /client
|
||||
group:
|
||||
order: 1
|
||||
title: 区块 - Blocks
|
||||
path: /client/blocks
|
||||
---
|
||||
|
||||
# Input - 输入框
|
||||
|
||||
输入框是非常常用的控件,参数可以组合成许多字段,如单行文本、多行文本、手机号、邮箱、网址等。
|
||||
|
||||
## 代码演示
|
||||
|
||||
### 单行文本
|
||||
|
||||
```tsx
|
||||
/**
|
||||
* title: 单行文本
|
||||
*/
|
||||
import React from 'react';
|
||||
import { SchemaBlock } from '../';
|
||||
|
||||
const schema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
input: {
|
||||
type: 'string',
|
||||
title: `编辑模式`,
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input',
|
||||
'x-reactions': {
|
||||
target: 'read',
|
||||
fulfill: {
|
||||
state: {
|
||||
value: '{{$self.value}}',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
read: {
|
||||
interface: 'string',
|
||||
type: 'string',
|
||||
title: `阅读模式`,
|
||||
'x-read-pretty': true,
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export default () => {
|
||||
return (
|
||||
<SchemaBlock schema={schema} />
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
### 多行文本
|
||||
|
||||
```tsx
|
||||
/**
|
||||
* title: 多行文本
|
||||
* desc: 用于多行输入,使用 `Input.TextArea` 组件,`ellipsis=true` 时超出隐藏
|
||||
*/
|
||||
import React from 'react';
|
||||
import { SchemaBlock } from '../';
|
||||
|
||||
const schema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
input: {
|
||||
interface: 'string',
|
||||
type: 'string',
|
||||
title: `编辑模式`,
|
||||
name: 'name1',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input.TextArea',
|
||||
'x-reactions': {
|
||||
target: '*(read1,read2)',
|
||||
fulfill: {
|
||||
state: {
|
||||
value: '{{$self.value}}',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
read1: {
|
||||
interface: 'string',
|
||||
type: 'string',
|
||||
title: `阅读模式`,
|
||||
'x-read-pretty': true,
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input.TextArea',
|
||||
},
|
||||
read2: {
|
||||
interface: 'string',
|
||||
type: 'string',
|
||||
title: `阅读模式(超出隐藏)`,
|
||||
'x-read-pretty': true,
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input.TextArea',
|
||||
'x-component-props': {
|
||||
ellipsis: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
export default () => {
|
||||
return (
|
||||
<SchemaBlock schema={schema} />
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
### 电子邮箱
|
||||
|
||||
```tsx
|
||||
/**
|
||||
* title: 电子邮箱
|
||||
* desc: 加上邮箱验证 `x-validator=email`
|
||||
*/
|
||||
import React from 'react';
|
||||
import { SchemaBlock } from '../';
|
||||
|
||||
const schema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
input: {
|
||||
interface: 'string',
|
||||
type: 'string',
|
||||
title: `编辑模式`,
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input',
|
||||
'x-validator': 'email',
|
||||
'x-reactions': {
|
||||
target: 'read',
|
||||
fulfill: {
|
||||
state: {
|
||||
value: '{{$self.value}}',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
read: {
|
||||
interface: 'string',
|
||||
type: 'string',
|
||||
title: `阅读模式`,
|
||||
'x-read-pretty': true,
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input',
|
||||
'x-validator': 'email',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export default () => {
|
||||
return (
|
||||
<SchemaBlock schema={schema} />
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
### 手机号
|
||||
|
||||
```tsx
|
||||
/**
|
||||
* title: 手机号
|
||||
* desc: 加上手机号验证 `x-validator=phone`
|
||||
*/
|
||||
import React from 'react';
|
||||
import { SchemaBlock } from '../';
|
||||
|
||||
const schema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
input: {
|
||||
interface: 'string',
|
||||
type: 'string',
|
||||
title: `编辑模式`,
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input',
|
||||
'x-validator': 'phone',
|
||||
'x-reactions': {
|
||||
target: 'name2',
|
||||
fulfill: {
|
||||
state: {
|
||||
value: '{{$self.value}}',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
read: {
|
||||
interface: 'string',
|
||||
type: 'string',
|
||||
title: `阅读模式`,
|
||||
'x-read-pretty': true,
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input',
|
||||
'x-validator': 'phone',
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
export default () => {
|
||||
return (
|
||||
<SchemaBlock schema={schema} />
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
### 网址
|
||||
|
||||
```tsx
|
||||
/**
|
||||
* title: 网址
|
||||
* desc: 加上 URL 验证 `x-validator=url`
|
||||
*/
|
||||
import React from 'react';
|
||||
import { SchemaBlock } from '../';
|
||||
|
||||
const schema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
input: {
|
||||
type: 'string',
|
||||
title: `编辑模式`,
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input.URL',
|
||||
'x-validator': 'url',
|
||||
'x-reactions': {
|
||||
target: 'read',
|
||||
fulfill: {
|
||||
state: {
|
||||
value: '{{$self.value}}',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
read: {
|
||||
type: 'string',
|
||||
title: `阅读模式`,
|
||||
'x-read-pretty': true,
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input.URL',
|
||||
'x-validator': 'url',
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
export default () => {
|
||||
return (
|
||||
<SchemaBlock schema={schema} />
|
||||
);
|
||||
};
|
||||
```
|
@ -2,12 +2,14 @@ import React from 'react'
|
||||
import { connect, mapProps, mapReadPretty } from '@formily/react'
|
||||
import { Input as AntdInput } from 'antd'
|
||||
import { InputProps, TextAreaProps } from 'antd/lib/input'
|
||||
import { Descriptions } from '../descriptions'
|
||||
import { LoadingOutlined } from '@ant-design/icons'
|
||||
import { Display } from '../display';
|
||||
import { DesignableBar } from './DesignableBar';
|
||||
|
||||
type ComposedInput = React.FC<InputProps> & {
|
||||
TextArea?: React.FC<TextAreaProps>
|
||||
URL?: React.FC<InputProps>
|
||||
DesignableBar?: React.FC<any>
|
||||
}
|
||||
|
||||
export const Input: ComposedInput = connect(
|
||||
@ -26,10 +28,11 @@ export const Input: ComposedInput = connect(
|
||||
),
|
||||
}
|
||||
}),
|
||||
mapReadPretty(Descriptions.Input)
|
||||
mapReadPretty(Display.Input)
|
||||
)
|
||||
|
||||
Input.TextArea = connect(AntdInput.TextArea, mapReadPretty(Descriptions.TextArea))
|
||||
Input.URL = connect(AntdInput, mapReadPretty(Descriptions.URL))
|
||||
Input.DesignableBar = DesignableBar;
|
||||
Input.TextArea = connect(AntdInput.TextArea, mapReadPretty(Display.TextArea))
|
||||
Input.URL = connect(AntdInput, mapReadPretty(Display.URL))
|
||||
|
||||
export default Input
|
@ -1,19 +0,0 @@
|
||||
---
|
||||
title: Kanban - 看板
|
||||
nav:
|
||||
title: 组件
|
||||
path: /client
|
||||
group:
|
||||
order: 2
|
||||
title: Blocks
|
||||
path: /client/blocks
|
||||
---
|
||||
|
||||
# Kanban - 看板
|
||||
|
||||
## 代码演示
|
||||
|
||||
### 基本使用
|
||||
|
||||
### 设计器模式
|
||||
|
68
packages/client/src/blocks/markdown/index.md
Normal file
68
packages/client/src/blocks/markdown/index.md
Normal file
@ -0,0 +1,68 @@
|
||||
---
|
||||
title: Markdown 编辑器
|
||||
nav:
|
||||
title: 组件
|
||||
path: /client
|
||||
group:
|
||||
order: 1
|
||||
title: 区块 - Blocks
|
||||
path: /client/blocks
|
||||
---
|
||||
|
||||
# Markdown 编辑器
|
||||
|
||||
## 代码演示
|
||||
|
||||
### Markdown 编辑器
|
||||
|
||||
```tsx
|
||||
import React from 'react';
|
||||
import { SchemaBlock } from '../';
|
||||
|
||||
const schema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
input: {
|
||||
interface: 'string',
|
||||
type: 'string',
|
||||
title: `编辑模式`,
|
||||
name: 'name1',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Markdown',
|
||||
'x-reactions': {
|
||||
target: '*(read1,read2)',
|
||||
fulfill: {
|
||||
state: {
|
||||
value: '{{$self.value}}',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
read1: {
|
||||
interface: 'string',
|
||||
type: 'string',
|
||||
title: `阅读模式`,
|
||||
'x-read-pretty': true,
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Markdown',
|
||||
},
|
||||
read2: {
|
||||
interface: 'string',
|
||||
type: 'string',
|
||||
title: `阅读模式(超出隐藏)`,
|
||||
'x-read-pretty': true,
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Markdown',
|
||||
'x-component-props': {
|
||||
ellipsis: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default () => {
|
||||
return (
|
||||
<SchemaBlock schema={schema} />
|
||||
);
|
||||
};
|
||||
```
|
@ -2,7 +2,7 @@ import React from 'react';
|
||||
import { connect, mapProps, mapReadPretty } from '@formily/react';
|
||||
import { Input as AntdInput } from 'antd';
|
||||
import { InputProps, TextAreaProps } from 'antd/lib/input';
|
||||
import { Descriptions } from '../descriptions';
|
||||
import { Display } from '../display';
|
||||
import { LoadingOutlined } from '@ant-design/icons';
|
||||
import micromark from 'micromark';
|
||||
|
||||
@ -27,7 +27,7 @@ export const Markdown = connect(
|
||||
let value = (
|
||||
<div dangerouslySetInnerHTML={{ __html: micromark(text || '') }} />
|
||||
);
|
||||
return <Descriptions.TextArea {...props} text={text} value={value} />;
|
||||
return <Display.TextArea {...props} text={text} value={value} />;
|
||||
}),
|
||||
);
|
||||
|
130
packages/client/src/blocks/menu/index.md
Normal file
130
packages/client/src/blocks/menu/index.md
Normal file
@ -0,0 +1,130 @@
|
||||
---
|
||||
title: Menu - 菜单
|
||||
nav:
|
||||
title: 组件
|
||||
path: /client
|
||||
group:
|
||||
order: 1
|
||||
title: 区块 - Blocks
|
||||
path: /client/blocks
|
||||
---
|
||||
|
||||
# Menu - 菜单
|
||||
|
||||
需要 antd v4.16+ 支持,在此之前的 Menu.Item 不支持 Fragment 包裹。
|
||||
|
||||
## 代码演示
|
||||
|
||||
### 横向菜单
|
||||
|
||||
```tsx
|
||||
/**
|
||||
* title: 横向菜单
|
||||
*/
|
||||
import React from 'react';
|
||||
import { SchemaBlock } from '../';
|
||||
|
||||
const schema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
menu1: {
|
||||
type: 'void',
|
||||
'x-component': 'Menu',
|
||||
// 'x-decorator': 'Menu.Designable',
|
||||
'x-component-props': {
|
||||
mode: 'horizontal',
|
||||
},
|
||||
properties: {
|
||||
item1: {
|
||||
type: 'void',
|
||||
title: `菜单1`,
|
||||
'x-component': 'Menu.Item',
|
||||
},
|
||||
item2: {
|
||||
type: 'void',
|
||||
title: `菜单2`,
|
||||
'x-component': 'Menu.Item',
|
||||
},
|
||||
item3: {
|
||||
type: 'void',
|
||||
title: '菜单组',
|
||||
'x-component': 'Menu.SubMenu',
|
||||
properties: {
|
||||
item4: {
|
||||
type: 'void',
|
||||
title: `子菜单1`,
|
||||
'x-component': 'Menu.Item',
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default () => {
|
||||
return (
|
||||
<SchemaBlock schema={schema} />
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
### 竖向菜单
|
||||
|
||||
```tsx
|
||||
/**
|
||||
* title: 竖向菜单
|
||||
*/
|
||||
import React from 'react';
|
||||
import { SchemaBlock } from '../';
|
||||
|
||||
const schema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
menu1: {
|
||||
type: 'void',
|
||||
name: 'name1',
|
||||
'x-component': 'Menu',
|
||||
// 'x-decorator': 'Menu.Designable',
|
||||
'x-component-props': {
|
||||
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`,
|
||||
'x-component': 'Menu.Item',
|
||||
},
|
||||
item2: {
|
||||
type: 'void',
|
||||
title: `菜单2`,
|
||||
'x-component': 'Menu.Item',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export default () => {
|
||||
return (
|
||||
<SchemaBlock schema={schema} />
|
||||
);
|
||||
};
|
||||
```
|
751
packages/client/src/blocks/menu/index.tsx
Normal file
751
packages/client/src/blocks/menu/index.tsx
Normal file
@ -0,0 +1,751 @@
|
||||
import React, {
|
||||
Children,
|
||||
createContext,
|
||||
useContext,
|
||||
useEffect,
|
||||
useState,
|
||||
} from 'react';
|
||||
import {
|
||||
connect,
|
||||
observer,
|
||||
mapProps,
|
||||
mapReadPretty,
|
||||
useField,
|
||||
useFieldSchema,
|
||||
RecursionField,
|
||||
Schema,
|
||||
} from '@formily/react';
|
||||
import {
|
||||
Menu as AntdMenu,
|
||||
MenuProps,
|
||||
MenuItemProps,
|
||||
SubMenuProps,
|
||||
DividerProps,
|
||||
Dropdown,
|
||||
Modal,
|
||||
Button,
|
||||
} from 'antd';
|
||||
import get from 'lodash/get';
|
||||
import { FormDialog, FormLayout } from '@formily/antd';
|
||||
import { uid } from '@formily/shared';
|
||||
// import { useSchemaQuery } from '../grid';
|
||||
import {
|
||||
DesignableSchemaContext,
|
||||
RefreshDesignableSchemaContext,
|
||||
SchemaField,
|
||||
} from '../SchemaField';
|
||||
import {
|
||||
MenuOutlined,
|
||||
GroupOutlined,
|
||||
PlusOutlined,
|
||||
LinkOutlined,
|
||||
AppstoreAddOutlined,
|
||||
EditOutlined,
|
||||
DeleteOutlined,
|
||||
ArrowRightOutlined,
|
||||
SettingOutlined,
|
||||
ArrowUpOutlined,
|
||||
ArrowDownOutlined,
|
||||
} from '@ant-design/icons';
|
||||
import './style.less';
|
||||
import { Icon } from '../icon-picker';
|
||||
import {
|
||||
useHistory,
|
||||
} from 'react-router-dom';
|
||||
|
||||
export type MenuType = React.FC<MenuProps & { hideSubMenu?: boolean }> & {
|
||||
Item?: React.FC<MenuItemProps>;
|
||||
SubMenu?: React.FC<SubMenuProps>;
|
||||
Divider?: React.FC<DividerProps>;
|
||||
Designable?: React.FC<any>;
|
||||
AddNew?: React.FC<any>;
|
||||
Link?: React.FC<MenuItemProps>;
|
||||
Url?: React.FC<MenuItemProps & { url: string }>;
|
||||
};
|
||||
|
||||
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];
|
||||
console.log('names', [...names], path, context)
|
||||
// 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,
|
||||
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();
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const AddNewAction = () => {
|
||||
const field = useField();
|
||||
const segments = [...field.address.segments];
|
||||
// add new 节点是后加的,并不在菜单树上,要通过父节点处理 add 逻辑
|
||||
segments.pop();
|
||||
const { appendChild } = useSchemaQuery(segments);
|
||||
return (
|
||||
<Dropdown
|
||||
overlayStyle={{
|
||||
minWidth: 150,
|
||||
}}
|
||||
trigger={['click']}
|
||||
overlay={
|
||||
<AntdMenu>
|
||||
<AntdMenu.Item
|
||||
onClick={() => {
|
||||
FormDialog(`新建菜单`, () => {
|
||||
return (
|
||||
<FormLayout layout={'vertical'}>
|
||||
<SchemaField>
|
||||
<SchemaField.String
|
||||
name="title"
|
||||
required
|
||||
title="名称"
|
||||
x-decorator="FormItem"
|
||||
x-component="Input"
|
||||
/>
|
||||
<SchemaField.String
|
||||
name="icon"
|
||||
title="图标"
|
||||
x-decorator="FormItem"
|
||||
x-component="IconPicker"
|
||||
/>
|
||||
</SchemaField>
|
||||
</FormLayout>
|
||||
);
|
||||
})
|
||||
.open({})
|
||||
.then((data) => {
|
||||
appendChild({
|
||||
name: `m_${uid()}`,
|
||||
type: 'void',
|
||||
title: data.title,
|
||||
'x-component': 'Menu.Item',
|
||||
'x-component-props': {
|
||||
icon: data.icon,
|
||||
},
|
||||
});
|
||||
});
|
||||
}}
|
||||
style={{ minWidth: 150 }}
|
||||
>
|
||||
<MenuOutlined /> 新建菜单
|
||||
</AntdMenu.Item>
|
||||
<AntdMenu.Item
|
||||
onClick={() => {
|
||||
FormDialog(`新建菜单组`, () => {
|
||||
return (
|
||||
<FormLayout layout={'vertical'}>
|
||||
<SchemaField>
|
||||
<SchemaField.String
|
||||
name="title"
|
||||
required
|
||||
title="名称"
|
||||
x-decorator="FormItem"
|
||||
x-component="Input"
|
||||
/>
|
||||
<SchemaField.String
|
||||
name="icon"
|
||||
title="图标"
|
||||
x-decorator="FormItem"
|
||||
x-component="IconPicker"
|
||||
/>
|
||||
</SchemaField>
|
||||
</FormLayout>
|
||||
);
|
||||
})
|
||||
.open({})
|
||||
.then((data) => {
|
||||
appendChild({
|
||||
name: `m_${uid()}`,
|
||||
type: 'void',
|
||||
title: data.title,
|
||||
'x-component': 'Menu.SubMenu',
|
||||
'x-component-props': {
|
||||
icon: data.icon,
|
||||
},
|
||||
properties: {
|
||||
[`m_${uid()}`]: {
|
||||
type: 'void',
|
||||
title: `菜单 ${uid()}`,
|
||||
'x-component': 'Menu.Item',
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
}}
|
||||
>
|
||||
<GroupOutlined /> 新建分组
|
||||
</AntdMenu.Item>
|
||||
<AntdMenu.Item>
|
||||
<LinkOutlined /> 添加链接
|
||||
</AntdMenu.Item>
|
||||
</AntdMenu>
|
||||
}
|
||||
>
|
||||
<Button block type={'dashed'} icon={<PlusOutlined />}></Button>
|
||||
</Dropdown>
|
||||
);
|
||||
};
|
||||
|
||||
const SettingAction = () => {
|
||||
const field = useField();
|
||||
const { schema, refresh, remove, insertBefore, insertAfter, appendChild } =
|
||||
useSchemaQuery();
|
||||
const text = schema['x-component'] === 'Menu.SubMenu' ? '当前菜单组' : '当前菜单';
|
||||
return (
|
||||
<div
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
}}
|
||||
className={'designable-action'}
|
||||
>
|
||||
<Dropdown
|
||||
overlayStyle={{
|
||||
minWidth: 150,
|
||||
}}
|
||||
trigger={['click']}
|
||||
overlay={
|
||||
<AntdMenu>
|
||||
<AntdMenu.Item
|
||||
onClick={() => {
|
||||
FormDialog('编辑菜单', () => {
|
||||
return (
|
||||
<FormLayout layout={'vertical'}>
|
||||
<SchemaField>
|
||||
<SchemaField.String
|
||||
name="title"
|
||||
required
|
||||
title="菜单名称"
|
||||
x-decorator="FormItem"
|
||||
x-component="Input"
|
||||
/>
|
||||
<SchemaField.String
|
||||
name=".icon"
|
||||
title="图标"
|
||||
x-decorator="FormItem"
|
||||
x-component="IconPicker"
|
||||
/>
|
||||
</SchemaField>
|
||||
{/* <FormDialog.Footer>
|
||||
<span style={{ marginLeft: 4 }}>扩展文案</span>
|
||||
</FormDialog.Footer> */}
|
||||
</FormLayout>
|
||||
);
|
||||
})
|
||||
.open({
|
||||
initialValues: {
|
||||
title: schema.title,
|
||||
icon: schema['x-component-props']?.['icon'],
|
||||
},
|
||||
})
|
||||
.then((data) => {
|
||||
schema.title = data.title;
|
||||
const componentProps = schema['x-component-props'] || {};
|
||||
componentProps['icon'] = data.icon;
|
||||
field.setTitle(data.title);
|
||||
field.setComponentProps(componentProps);
|
||||
refresh();
|
||||
});
|
||||
}}
|
||||
>
|
||||
<EditOutlined /> 编辑菜单
|
||||
</AntdMenu.Item>
|
||||
<AntdMenu.Item>
|
||||
<ArrowRightOutlined /> 移动到
|
||||
</AntdMenu.Item>
|
||||
<AntdMenu.Divider />
|
||||
<AntdMenu.SubMenu icon={<ArrowUpOutlined />} title={`${text}前`}>
|
||||
<AntdMenu.Item
|
||||
onClick={() => {
|
||||
FormDialog(`新建菜单`, () => {
|
||||
return (
|
||||
<FormLayout layout={'vertical'}>
|
||||
<SchemaField>
|
||||
<SchemaField.String
|
||||
name="title"
|
||||
required
|
||||
title="名称"
|
||||
x-decorator="FormItem"
|
||||
x-component="Input"
|
||||
/>
|
||||
<SchemaField.String
|
||||
name="icon"
|
||||
title="图标"
|
||||
x-decorator="FormItem"
|
||||
x-component="IconPicker"
|
||||
/>
|
||||
</SchemaField>
|
||||
</FormLayout>
|
||||
);
|
||||
})
|
||||
.open({})
|
||||
.then((data) => {
|
||||
insertBefore({
|
||||
name: `m_${uid()}`,
|
||||
type: 'void',
|
||||
title: data.title,
|
||||
'x-component': 'Menu.Item',
|
||||
'x-component-props': {
|
||||
icon: data.icon,
|
||||
},
|
||||
});
|
||||
});
|
||||
}}
|
||||
style={{ minWidth: 150 }}
|
||||
>
|
||||
<MenuOutlined /> 新建菜单
|
||||
</AntdMenu.Item>
|
||||
<AntdMenu.Item
|
||||
onClick={() => {
|
||||
FormDialog(`新建菜单组`, () => {
|
||||
return (
|
||||
<FormLayout layout={'vertical'}>
|
||||
<SchemaField>
|
||||
<SchemaField.String
|
||||
name="title"
|
||||
required
|
||||
title="名称"
|
||||
x-decorator="FormItem"
|
||||
x-component="Input"
|
||||
/>
|
||||
<SchemaField.String
|
||||
name="icon"
|
||||
title="图标"
|
||||
x-decorator="FormItem"
|
||||
x-component="IconPicker"
|
||||
/>
|
||||
</SchemaField>
|
||||
</FormLayout>
|
||||
);
|
||||
})
|
||||
.open({})
|
||||
.then((data) => {
|
||||
insertBefore({
|
||||
name: `m_${uid()}`,
|
||||
type: 'void',
|
||||
title: data.title,
|
||||
'x-component': 'Menu.SubMenu',
|
||||
'x-component-props': {
|
||||
icon: data.icon,
|
||||
},
|
||||
properties: {
|
||||
[`m_${uid()}`]: {
|
||||
type: 'void',
|
||||
title: `菜单 ${uid()}`,
|
||||
'x-component': 'Menu.Item',
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
}}
|
||||
>
|
||||
<GroupOutlined /> 新建分组
|
||||
</AntdMenu.Item>
|
||||
<AntdMenu.Item>
|
||||
<LinkOutlined /> 添加链接
|
||||
</AntdMenu.Item>
|
||||
</AntdMenu.SubMenu>
|
||||
<AntdMenu.SubMenu icon={<ArrowDownOutlined />} title={`${text}后`}>
|
||||
<AntdMenu.Item
|
||||
onClick={() => {
|
||||
FormDialog(`新建菜单`, () => {
|
||||
return (
|
||||
<FormLayout layout={'vertical'}>
|
||||
<SchemaField>
|
||||
<SchemaField.String
|
||||
name="title"
|
||||
required
|
||||
title="名称"
|
||||
x-decorator="FormItem"
|
||||
x-component="Input"
|
||||
/>
|
||||
<SchemaField.String
|
||||
name="icon"
|
||||
title="图标"
|
||||
x-decorator="FormItem"
|
||||
x-component="IconPicker"
|
||||
/>
|
||||
</SchemaField>
|
||||
</FormLayout>
|
||||
);
|
||||
})
|
||||
.open({})
|
||||
.then((data) => {
|
||||
insertAfter(
|
||||
new Schema({
|
||||
name: `m_${uid()}`,
|
||||
type: 'void',
|
||||
title: data.title,
|
||||
'x-component': 'Menu.Item',
|
||||
'x-component-props': {
|
||||
icon: data.icon,
|
||||
},
|
||||
}),
|
||||
);
|
||||
});
|
||||
}}
|
||||
style={{ minWidth: 150 }}
|
||||
>
|
||||
<MenuOutlined /> 新建菜单
|
||||
</AntdMenu.Item>
|
||||
<AntdMenu.Item
|
||||
onClick={() => {
|
||||
FormDialog(`新建菜单组`, () => {
|
||||
return (
|
||||
<FormLayout layout={'vertical'}>
|
||||
<SchemaField>
|
||||
<SchemaField.String
|
||||
name="title"
|
||||
required
|
||||
title="名称"
|
||||
x-decorator="FormItem"
|
||||
x-component="Input"
|
||||
/>
|
||||
<SchemaField.String
|
||||
name="icon"
|
||||
title="图标"
|
||||
x-decorator="FormItem"
|
||||
x-component="IconPicker"
|
||||
/>
|
||||
</SchemaField>
|
||||
</FormLayout>
|
||||
);
|
||||
})
|
||||
.open({})
|
||||
.then((data) => {
|
||||
insertAfter(
|
||||
new Schema({
|
||||
name: `m_${uid()}`,
|
||||
type: 'void',
|
||||
title: data.title,
|
||||
'x-component': 'Menu.SubMenu',
|
||||
'x-component-props': {
|
||||
icon: data.icon,
|
||||
},
|
||||
properties: {
|
||||
[`m_${uid()}`]: {
|
||||
type: 'void',
|
||||
title: `菜单 ${uid()}`,
|
||||
'x-component': 'Menu.Item',
|
||||
},
|
||||
},
|
||||
}),
|
||||
);
|
||||
});
|
||||
}}
|
||||
>
|
||||
<GroupOutlined /> 新建分组
|
||||
</AntdMenu.Item>
|
||||
<AntdMenu.Item>
|
||||
<LinkOutlined /> 添加链接
|
||||
</AntdMenu.Item>
|
||||
</AntdMenu.SubMenu>
|
||||
{schema['x-component'] === 'Menu.SubMenu' ? (
|
||||
<AntdMenu.SubMenu icon={<ArrowRightOutlined />} title={`${text}里`}>
|
||||
<AntdMenu.Item
|
||||
onClick={() => {
|
||||
FormDialog(`新建菜单`, () => {
|
||||
return (
|
||||
<FormLayout layout={'vertical'}>
|
||||
<SchemaField>
|
||||
<SchemaField.String
|
||||
name="title"
|
||||
required
|
||||
title="名称"
|
||||
x-decorator="FormItem"
|
||||
x-component="Input"
|
||||
/>
|
||||
<SchemaField.String
|
||||
name="icon"
|
||||
title="图标"
|
||||
x-decorator="FormItem"
|
||||
x-component="IconPicker"
|
||||
/>
|
||||
</SchemaField>
|
||||
</FormLayout>
|
||||
);
|
||||
})
|
||||
.open({})
|
||||
.then((data) => {
|
||||
appendChild(
|
||||
new Schema({
|
||||
name: `m_${uid()}`,
|
||||
type: 'void',
|
||||
title: data.title,
|
||||
'x-component': 'Menu.Item',
|
||||
'x-component-props': {
|
||||
icon: data.icon,
|
||||
},
|
||||
}),
|
||||
);
|
||||
});
|
||||
}}
|
||||
style={{ minWidth: 150 }}
|
||||
>
|
||||
<MenuOutlined /> 新建菜单
|
||||
</AntdMenu.Item>
|
||||
<AntdMenu.Item
|
||||
onClick={() => {
|
||||
FormDialog(`新建菜单组`, () => {
|
||||
return (
|
||||
<FormLayout layout={'vertical'}>
|
||||
<SchemaField>
|
||||
<SchemaField.String
|
||||
name="title"
|
||||
required
|
||||
title="名称"
|
||||
x-decorator="FormItem"
|
||||
x-component="Input"
|
||||
/>
|
||||
<SchemaField.String
|
||||
name="icon"
|
||||
title="图标"
|
||||
x-decorator="FormItem"
|
||||
x-component="IconPicker"
|
||||
/>
|
||||
</SchemaField>
|
||||
</FormLayout>
|
||||
);
|
||||
})
|
||||
.open({})
|
||||
.then((data) => {
|
||||
appendChild(
|
||||
new Schema({
|
||||
name: `m_${uid()}`,
|
||||
type: 'void',
|
||||
title: data.title,
|
||||
'x-component': 'Menu.SubMenu',
|
||||
'x-component-props': {
|
||||
icon: data.icon,
|
||||
},
|
||||
properties: {
|
||||
[`m_${uid()}`]: {
|
||||
type: 'void',
|
||||
title: `菜单 ${uid()}`,
|
||||
'x-component': 'Menu.Item',
|
||||
},
|
||||
},
|
||||
}),
|
||||
);
|
||||
});
|
||||
}}
|
||||
>
|
||||
<GroupOutlined /> 新建分组
|
||||
</AntdMenu.Item>
|
||||
<AntdMenu.Item>
|
||||
<LinkOutlined /> 添加链接
|
||||
</AntdMenu.Item>
|
||||
</AntdMenu.SubMenu>
|
||||
) : null}
|
||||
<AntdMenu.Divider />
|
||||
<AntdMenu.Item
|
||||
onClick={() => {
|
||||
Modal.confirm({
|
||||
title: '删除菜单',
|
||||
content: '确认删除此菜单项吗?',
|
||||
onOk: remove,
|
||||
});
|
||||
}}
|
||||
>
|
||||
<DeleteOutlined /> 删除菜单
|
||||
</AntdMenu.Item>
|
||||
</AntdMenu>
|
||||
}
|
||||
>
|
||||
<MenuOutlined />
|
||||
</Dropdown>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const Menu: MenuType = observer((props) => {
|
||||
const { hideSubMenu, ...others } = props;
|
||||
// const schema = useFieldSchema();
|
||||
// console.log({ schema }, 'Menu');
|
||||
return <AntdMenu {...others} />;
|
||||
});
|
||||
|
||||
Menu.AddNew = observer((props) => {
|
||||
const field = useField();
|
||||
const { schema } = useSchemaQuery();
|
||||
console.log('AddNew', field.address.segments);
|
||||
return (
|
||||
<AntdMenu.ItemGroup
|
||||
key="add"
|
||||
className={'menu-add'}
|
||||
title={<AddNewAction />}
|
||||
></AntdMenu.ItemGroup>
|
||||
);
|
||||
});
|
||||
|
||||
Menu.Url = observer((props) => {
|
||||
const field = useField();
|
||||
const { schema, refresh } = useSchemaQuery();
|
||||
return (
|
||||
<AntdMenu.Item
|
||||
{...props}
|
||||
schema={schema}
|
||||
// @ts-ignore
|
||||
eventKey={schema.name}
|
||||
key={schema.name}
|
||||
onClick={(e) => {
|
||||
window.open(props.url);
|
||||
}}
|
||||
icon={props.icon ? <Icon type={props.icon as string} /> : undefined}
|
||||
>
|
||||
{field.title} <SettingAction />
|
||||
</AntdMenu.Item>
|
||||
);
|
||||
});
|
||||
|
||||
Menu.Link = observer((props) => {
|
||||
const history = useHistory();
|
||||
const field = useField();
|
||||
const { schema, refresh } = useSchemaQuery();
|
||||
return (
|
||||
<AntdMenu.Item
|
||||
{...props}
|
||||
schema={schema}
|
||||
// @ts-ignore
|
||||
eventKey={schema.name}
|
||||
key={schema.name}
|
||||
onClick={(e) => {
|
||||
history.push(schema.name as string);
|
||||
}}
|
||||
icon={props.icon ? <Icon type={props.icon as string} /> : undefined}
|
||||
>
|
||||
{field.title} <SettingAction />
|
||||
</AntdMenu.Item>
|
||||
);
|
||||
});
|
||||
|
||||
Menu.Item = observer((props) => {
|
||||
const field = useField();
|
||||
const { schema, refresh } = useSchemaQuery();
|
||||
return (
|
||||
<AntdMenu.Item
|
||||
{...props}
|
||||
schema={schema}
|
||||
// @ts-ignore
|
||||
eventKey={schema.name}
|
||||
key={schema.name}
|
||||
icon={props.icon ? <Icon type={props.icon as string} /> : undefined}
|
||||
>
|
||||
{field.title} <SettingAction />
|
||||
</AntdMenu.Item>
|
||||
);
|
||||
});
|
||||
|
||||
// Menu
|
||||
Menu.Designable = (props) => {
|
||||
return props.children;
|
||||
};
|
||||
|
||||
Menu.SubMenu = observer((props) => {
|
||||
const schema = useFieldSchema();
|
||||
let s = schema;
|
||||
let hideSubMenu;
|
||||
while (s.parent) {
|
||||
if (s.parent['x-component'] === 'Menu') {
|
||||
hideSubMenu = s.parent['x-component-props']?.['hideSubMenu'];
|
||||
break;
|
||||
}
|
||||
s = s.parent;
|
||||
}
|
||||
if (hideSubMenu) {
|
||||
return <Menu.Item {...props} />;
|
||||
}
|
||||
return (
|
||||
<AntdMenu.SubMenu
|
||||
{...props}
|
||||
// @ts-ignore
|
||||
schema={schema}
|
||||
title={
|
||||
<>
|
||||
{schema.title} <SettingAction />
|
||||
</>
|
||||
}
|
||||
eventKey={schema.name}
|
||||
key={schema.name}
|
||||
icon={
|
||||
props.icon ? (
|
||||
<RecursionField
|
||||
name={schema.name}
|
||||
schema={
|
||||
new Schema({
|
||||
type: 'string',
|
||||
'x-read-pretty': true,
|
||||
default: props.icon,
|
||||
'x-component': 'IconPicker',
|
||||
})
|
||||
}
|
||||
onlyRenderProperties
|
||||
/>
|
||||
) : undefined
|
||||
}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
Menu.Divider = observer(AntdMenu.Divider);
|
||||
|
||||
export default Menu;
|
34
packages/client/src/blocks/menu/style.less
Normal file
34
packages/client/src/blocks/menu/style.less
Normal file
@ -0,0 +1,34 @@
|
||||
.designable-action {
|
||||
position: absolute;
|
||||
right: 16px;
|
||||
display: none;
|
||||
z-index: 10;
|
||||
}
|
||||
.ant-menu-dark.ant-menu-horizontal {
|
||||
width: 100%;
|
||||
}
|
||||
.ant-menu-dark .ant-menu-item-active .designable-action {
|
||||
background-color: #001529;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.ant-menu-dark .ant-menu-item-active:hover .designable-action,
|
||||
.ant-menu-dark .ant-menu-item-active.ant-menu-item-selected .designable-action {
|
||||
background-color: #1890ff;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.ant-menu-light .ant-menu-submenu-active > .ant-menu-submenu-title .designable-action,
|
||||
.ant-menu-light .ant-menu-item-active .designable-action {
|
||||
background-color: #fff;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.ant-menu-sub.ant-menu-inline .ant-menu-submenu-active > .ant-menu-submenu-title .designable-action,
|
||||
.ant-menu-sub.ant-menu-inline .ant-menu-item-active .designable-action {
|
||||
background-color: #fafafa;
|
||||
}
|
||||
|
||||
.ant-menu-light .ant-menu-item-selected .designable-action {
|
||||
background-color: #e6f7ff !important;
|
||||
}
|
54
packages/client/src/blocks/password/index.md
Normal file
54
packages/client/src/blocks/password/index.md
Normal file
@ -0,0 +1,54 @@
|
||||
---
|
||||
title: Password - 密码
|
||||
nav:
|
||||
title: 组件
|
||||
path: /client
|
||||
group:
|
||||
order: 1
|
||||
title: 区块 - Blocks
|
||||
path: /client/blocks
|
||||
---
|
||||
|
||||
# Password - 密码
|
||||
|
||||
## 代码演示
|
||||
|
||||
### 密码框
|
||||
|
||||
```tsx
|
||||
import React from 'react';
|
||||
import { SchemaBlock } from '../';
|
||||
|
||||
const schema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
input: {
|
||||
type: 'string',
|
||||
title: `编辑模式`,
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Password',
|
||||
'x-reactions': {
|
||||
target: 'read',
|
||||
fulfill: {
|
||||
state: {
|
||||
value: '{{$self.value}}',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
read: {
|
||||
type: 'string',
|
||||
title: `阅读模式`,
|
||||
'x-read-pretty': true,
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Password',
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
export default () => {
|
||||
return (
|
||||
<SchemaBlock schema={schema} />
|
||||
);
|
||||
};
|
||||
```
|
72
packages/client/src/blocks/radio/index.md
Normal file
72
packages/client/src/blocks/radio/index.md
Normal file
@ -0,0 +1,72 @@
|
||||
---
|
||||
title: Radio - 单选框
|
||||
nav:
|
||||
title: 组件
|
||||
path: /client
|
||||
group:
|
||||
order: 1
|
||||
title: 区块 - Blocks
|
||||
path: /client/blocks
|
||||
---
|
||||
|
||||
# Radio - 单选框
|
||||
|
||||
## 代码演示
|
||||
|
||||
### 单选框
|
||||
|
||||
```tsx
|
||||
/**
|
||||
* title: 单选框
|
||||
*/
|
||||
import React from 'react';
|
||||
import { SchemaBlock } from '../';
|
||||
|
||||
const options = [
|
||||
{
|
||||
label: '选项1',
|
||||
value: 1,
|
||||
color: 'red',
|
||||
},
|
||||
{
|
||||
label: '选项2',
|
||||
value: 2,
|
||||
color: 'blue',
|
||||
},
|
||||
];
|
||||
|
||||
const schema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
input: {
|
||||
type: 'string',
|
||||
title: `编辑模式`,
|
||||
enum: options,
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Radio.Group',
|
||||
'x-reactions': {
|
||||
target: 'read',
|
||||
fulfill: {
|
||||
state: {
|
||||
value: '{{$self.value}}',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
read: {
|
||||
type: 'string',
|
||||
title: `阅读模式`,
|
||||
enum: options,
|
||||
'x-read-pretty': true,
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Radio.Group',
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
export default () => {
|
||||
return (
|
||||
<SchemaBlock schema={schema} />
|
||||
);
|
||||
};
|
||||
```
|
273
packages/client/src/blocks/select/index.md
Normal file
273
packages/client/src/blocks/select/index.md
Normal file
@ -0,0 +1,273 @@
|
||||
---
|
||||
title: Select - 选择器
|
||||
nav:
|
||||
title: 组件
|
||||
path: /client
|
||||
group:
|
||||
order: 1
|
||||
title: 区块 - Blocks
|
||||
path: /client/blocks
|
||||
---
|
||||
|
||||
# Select - 选择器
|
||||
|
||||
## 基本用法
|
||||
|
||||
```tsx
|
||||
import React from 'react';
|
||||
import { SchemaBlock } from '../';
|
||||
|
||||
const options = [
|
||||
{
|
||||
label: '选项1',
|
||||
value: 1,
|
||||
color: 'red',
|
||||
},
|
||||
{
|
||||
label: '选项2',
|
||||
value: 2,
|
||||
color: 'blue',
|
||||
},
|
||||
];
|
||||
|
||||
const schema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
input: {
|
||||
interface: 'string',
|
||||
type: 'string',
|
||||
title: `编辑模式`,
|
||||
name: 'name1',
|
||||
enum: options,
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Select',
|
||||
'x-reactions': {
|
||||
target: 'read',
|
||||
fulfill: {
|
||||
state: {
|
||||
value: '{{$self.value}}',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
read: {
|
||||
interface: 'string',
|
||||
type: 'string',
|
||||
title: `阅读模式`,
|
||||
enum: options,
|
||||
name: 'name2',
|
||||
'x-read-pretty': true,
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Select',
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
export default () => {
|
||||
return (
|
||||
<SchemaBlock schema={schema}/>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
## 多选
|
||||
|
||||
```tsx
|
||||
import React from 'react';
|
||||
import { SchemaBlock } from '../';
|
||||
|
||||
const options = [
|
||||
{
|
||||
label: '选项1',
|
||||
value: 1,
|
||||
color: 'red',
|
||||
},
|
||||
{
|
||||
label: '选项2',
|
||||
value: 2,
|
||||
color: 'blue',
|
||||
},
|
||||
];
|
||||
|
||||
const schema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
input: {
|
||||
interface: 'string',
|
||||
type: 'string',
|
||||
title: `编辑模式`,
|
||||
name: 'name1',
|
||||
enum: options,
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Select',
|
||||
'x-component-props': {
|
||||
mode: 'tags',
|
||||
},
|
||||
'x-reactions': {
|
||||
target: 'read',
|
||||
fulfill: {
|
||||
state: {
|
||||
value: '{{$self.value}}',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
read: {
|
||||
interface: 'string',
|
||||
type: 'string',
|
||||
title: `阅读模式`,
|
||||
enum: options,
|
||||
'x-read-pretty': true,
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Select',
|
||||
'x-component-props': {
|
||||
mode: 'tags',
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
export default () => {
|
||||
return (
|
||||
<SchemaBlock schema={schema} />
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
## 分组
|
||||
|
||||
```tsx
|
||||
import React from 'react';
|
||||
import { SchemaBlock } from '../';
|
||||
|
||||
const dataSource = [
|
||||
{
|
||||
label: 'Manager',
|
||||
children: [
|
||||
{value: 'jack', label: 'Jack'},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Engineer',
|
||||
children: [
|
||||
{value: 'lucy', label: 'Lucy'},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const schema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
input: {
|
||||
interface: 'select',
|
||||
type: 'number',
|
||||
title: `编辑模式`,
|
||||
enum: dataSource,
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Select',
|
||||
'x-component-props': {
|
||||
placeholder: 'please enter',
|
||||
},
|
||||
'x-reactions': {
|
||||
target: 'read',
|
||||
fulfill: {
|
||||
state: {
|
||||
value: '{{$self.value}}',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
read: {
|
||||
interface: 'select',
|
||||
type: 'number',
|
||||
title: `阅读模式`,
|
||||
required: true,
|
||||
enum: dataSource,
|
||||
'x-read-pretty': true,
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Select',
|
||||
'x-component-props': {
|
||||
placeholder: 'please enter',
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default () => {
|
||||
return (
|
||||
<SchemaBlock schema={schema}/>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
## 值为对象
|
||||
|
||||
```tsx
|
||||
import React from 'react';
|
||||
import { SchemaBlock } from '../';
|
||||
|
||||
const dataSource = [
|
||||
{
|
||||
id: 1,
|
||||
title: '标题1',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: '标题2',
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
title: '标题3',
|
||||
},
|
||||
];
|
||||
|
||||
const schema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
input: {
|
||||
interface: 'select',
|
||||
type: 'string',
|
||||
title: `编辑模式`,
|
||||
enum: dataSource,
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Select.Object',
|
||||
'x-component-props': {
|
||||
placeholder: 'please enter',
|
||||
labelField: 'title',
|
||||
valueField: 'id',
|
||||
labelInValue: true,
|
||||
},
|
||||
'x-reactions': {
|
||||
target: 'name2',
|
||||
fulfill: {
|
||||
state: {
|
||||
value: '{{$self.value}}',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
read: {
|
||||
interface: 'select',
|
||||
type: 'string',
|
||||
title: `阅读模式`,
|
||||
required: true,
|
||||
enum: dataSource,
|
||||
'x-read-pretty': true,
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Select.Object',
|
||||
'x-component-props': {
|
||||
placeholder: 'please enter',
|
||||
labelField: 'title',
|
||||
valueField: 'id',
|
||||
labelInValue: true,
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default () => {
|
||||
return (
|
||||
<SchemaBlock schema={schema} />
|
||||
);
|
||||
};
|
||||
```
|
@ -7,7 +7,7 @@ import { SelectProps } from 'antd/lib/select';
|
||||
import { isArr, isValid } from '@formily/shared';
|
||||
import { Field } from '@formily/core/esm/models/Field';
|
||||
import { useAction } from '../../state';
|
||||
import { Descriptions } from '../descriptions';
|
||||
import { Display } from '../display';
|
||||
import { useRequest } from 'ahooks';
|
||||
import uniq from 'lodash/uniq';
|
||||
|
||||
@ -179,7 +179,7 @@ Select.Object = connect(
|
||||
};
|
||||
},
|
||||
),
|
||||
mapReadPretty(Descriptions.ObjectSelect),
|
||||
mapReadPretty(Display.ObjectSelect),
|
||||
);
|
||||
|
||||
export default Select;
|
53
packages/client/src/blocks/table/ColumnDesignableBar.tsx
Normal file
53
packages/client/src/blocks/table/ColumnDesignableBar.tsx
Normal file
@ -0,0 +1,53 @@
|
||||
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,
|
||||
ArrowDownOutlined,
|
||||
DeleteOutlined,
|
||||
} from '@ant-design/icons';
|
||||
|
||||
export const ColumnDesignableBar = (props) => {
|
||||
const { addBlock, removeBlock } = useSchemaQuery();
|
||||
const [active, setActive] = useState(false);
|
||||
return (
|
||||
<>
|
||||
<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>
|
||||
</>
|
||||
);
|
||||
};
|
53
packages/client/src/blocks/table/DesignableBar.tsx
Normal file
53
packages/client/src/blocks/table/DesignableBar.tsx
Normal file
@ -0,0 +1,53 @@
|
||||
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,
|
||||
ArrowDownOutlined,
|
||||
DeleteOutlined,
|
||||
} 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 />}
|
||||
>
|
||||
在上方插入区块
|
||||
</Menu.Item>
|
||||
<Menu.Item
|
||||
onClick={() => {
|
||||
addBlock();
|
||||
setActive(false);
|
||||
}}
|
||||
icon={<ArrowDownOutlined />}
|
||||
>
|
||||
在下方插入区块
|
||||
</Menu.Item>
|
||||
<Menu.Divider />
|
||||
<Menu.Item
|
||||
onClick={() => {
|
||||
removeBlock();
|
||||
setActive(false);
|
||||
}}
|
||||
icon={<DeleteOutlined />}
|
||||
>
|
||||
删除区块
|
||||
</Menu.Item>
|
||||
</>
|
||||
);
|
||||
};
|
@ -1,57 +0,0 @@
|
||||
import React from 'react';
|
||||
import { TableBlock } from '@nocobase/client';
|
||||
|
||||
export default () => {
|
||||
return (
|
||||
<div>
|
||||
<TableBlock
|
||||
resource={'users'}
|
||||
initialValues={[
|
||||
{ input: '文本1', input2: 'a' },
|
||||
{ input: '文本2', input2: 'b' },
|
||||
{ input: '文本3', input2: 'c' },
|
||||
]}
|
||||
details={[
|
||||
{
|
||||
name: 'tab1',
|
||||
title: 'Tab1',
|
||||
blocks: [],
|
||||
},
|
||||
{
|
||||
name: 'tab2',
|
||||
title: 'Tab2',
|
||||
blocks: [],
|
||||
},
|
||||
]}
|
||||
fields={[
|
||||
{
|
||||
interface: 'string',
|
||||
type: 'string',
|
||||
title: `单行文本`,
|
||||
name: 'input',
|
||||
required: true,
|
||||
default: 'abcdefg',
|
||||
'x-decorator': 'Column',
|
||||
'x-component': 'Input',
|
||||
'x-component-props': {
|
||||
placeholder: 'please enter',
|
||||
},
|
||||
},
|
||||
{
|
||||
interface: 'string',
|
||||
type: 'string',
|
||||
title: `单行文本2`,
|
||||
name: 'input2',
|
||||
required: true,
|
||||
default: 'abcdefg',
|
||||
'x-decorator': 'Column',
|
||||
'x-component': 'Input',
|
||||
'x-component-props': {
|
||||
placeholder: 'please enter',
|
||||
},
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
@ -4,18 +4,106 @@ nav:
|
||||
title: 组件
|
||||
path: /client
|
||||
group:
|
||||
order: 2
|
||||
title: Blocks
|
||||
order: 1
|
||||
title: 区块 - Blocks
|
||||
path: /client/blocks
|
||||
---
|
||||
|
||||
# Table - 表格
|
||||
|
||||
## 代码演示
|
||||
```tsx
|
||||
import React from 'react';
|
||||
import { SchemaBlock } from '../';
|
||||
import { uid } from '@formily/shared';
|
||||
|
||||
### 基本使用
|
||||
|
||||
<code src="./demos/demo1.tsx"/>
|
||||
|
||||
### 设计器模式
|
||||
const schema = {
|
||||
name: `t_${uid()}`,
|
||||
type: 'array',
|
||||
'x-component': 'Table',
|
||||
default: [{ field1: 'a1', field2: 'b1' }, { field1: 'a2', field2: 'b2' }],
|
||||
'x-component-props': {},
|
||||
properties: {
|
||||
[`a_${uid()}`]: {
|
||||
type: 'void',
|
||||
'x-component': 'Table.ActionBar',
|
||||
properties: {
|
||||
action1: {
|
||||
type: 'object',
|
||||
title: '按钮1',
|
||||
'x-component': 'Table.BulkAction.Popover',
|
||||
'x-component-props': {},
|
||||
properties: {},
|
||||
},
|
||||
action2: {
|
||||
type: 'void',
|
||||
title: '按钮2',
|
||||
'x-component': 'Table.BulkAction',
|
||||
'x-component-props': {},
|
||||
},
|
||||
},
|
||||
},
|
||||
[`a_${uid()}`]: {
|
||||
type: 'void',
|
||||
'x-component': 'Table.ActionBar',
|
||||
'x-component-props': {
|
||||
align: 'bottom',
|
||||
},
|
||||
properties: {
|
||||
pagination: {
|
||||
type: 'void',
|
||||
'x-component': 'Table.Pagination',
|
||||
'x-component-props': {
|
||||
defaultCurrent: 1,
|
||||
total: 50,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
[`c_${uid()}`]: {
|
||||
type: 'void',
|
||||
title: '字段1',
|
||||
'x-component': 'Table.Column',
|
||||
'x-component-props': {
|
||||
title: 'z1',
|
||||
width: 100,
|
||||
// DesignableBar: 'Table.Column.DesignableBar',
|
||||
},
|
||||
'x-designable-bar': 'Table.Column.DesignableBar',
|
||||
properties: {
|
||||
field1: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
'x-decorator-props': {
|
||||
feedbackLayout: 'popover',
|
||||
},
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input',
|
||||
},
|
||||
},
|
||||
},
|
||||
[`c_${uid()}`]: {
|
||||
type: 'void',
|
||||
title: '字段2',
|
||||
'x-component': 'Table.Column',
|
||||
properties: {
|
||||
field2: {
|
||||
type: 'string',
|
||||
title: '字段2',
|
||||
required: true,
|
||||
'x-decorator-props': {
|
||||
feedbackLayout: 'popover',
|
||||
},
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default () => {
|
||||
return (
|
||||
<SchemaBlock schema={schema} />
|
||||
);
|
||||
};
|
||||
```
|
@ -1,100 +1,108 @@
|
||||
import React from 'react';
|
||||
import { Table, Tabs, Card, Button } from 'antd';
|
||||
import { Drawer } from '../../components/Drawer';
|
||||
import { Resource, ResourceOptions } from '../../resource';
|
||||
import { useRequest } from 'ahooks';
|
||||
import { Field } from '../../';
|
||||
import {
|
||||
useFieldSchema,
|
||||
Schema,
|
||||
observer,
|
||||
RecursionField,
|
||||
useField,
|
||||
} from '@formily/react';
|
||||
import { Table as AntdTable } from 'antd';
|
||||
import { DesignableBar } from './DesignableBar';
|
||||
import { ColumnDesignableBar } from './ColumnDesignableBar';
|
||||
|
||||
export function fields2columns(fields: any[]) {
|
||||
if (!Array.isArray(fields)) {
|
||||
return [];
|
||||
function useTableColumns(props) {
|
||||
const schema = useFieldSchema();
|
||||
const { dataSource } = props;
|
||||
|
||||
function findColumns(schema: Schema): Schema[] {
|
||||
return schema.reduceProperties((columns, current) => {
|
||||
if (current['x-component'] === 'Table.Column') {
|
||||
return [...columns, current];
|
||||
}
|
||||
return [...columns, ...findColumns(current)];
|
||||
}, []);
|
||||
}
|
||||
return fields.map((field) => {
|
||||
field.dataIndex = field.name.split('.');
|
||||
field.render = (value, data, index) => {
|
||||
return (
|
||||
<Field
|
||||
schema={{
|
||||
'x-decorator': 'Column',
|
||||
'x-read-pretty': true,
|
||||
...field,
|
||||
}}
|
||||
data={{
|
||||
...data,
|
||||
__index__: index,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
return findColumns(schema).map((item) => {
|
||||
const columnProps = item['x-component-props'] || {};
|
||||
return {
|
||||
title: item.title,
|
||||
dataIndex: item.name,
|
||||
...columnProps,
|
||||
render(value, record) {
|
||||
console.log({ item });
|
||||
const index = dataSource.indexOf(record);
|
||||
item.mapProperties((s) => {
|
||||
s['title'] = undefined;
|
||||
s['x-read-pretty'] = true;
|
||||
return s;
|
||||
})
|
||||
return (
|
||||
<RecursionField schema={item} name={index} onlyRenderProperties />
|
||||
);
|
||||
},
|
||||
};
|
||||
return field;
|
||||
});
|
||||
}
|
||||
|
||||
export interface DetailOptions {
|
||||
name: string;
|
||||
title: string;
|
||||
blocks?: any;
|
||||
}
|
||||
function useTableActionBars() {
|
||||
const schema = useFieldSchema();
|
||||
|
||||
export interface TableBlockProps {
|
||||
fields?: any;
|
||||
details?: Array<DetailOptions>;
|
||||
initialValues?: any;
|
||||
resource: string | Resource | ResourceOptions;
|
||||
[key: string]: any;
|
||||
}
|
||||
function findActionBars(schema: Schema) {
|
||||
const actionBars = {
|
||||
top: [],
|
||||
bottom: [],
|
||||
};
|
||||
return schema.reduceProperties((bars, current) => {
|
||||
if (current['x-component'] === 'Table.ActionBar') {
|
||||
const align = current['x-component-props']?.['align'] || 'top';
|
||||
bars[align].push(current);
|
||||
|
||||
export function TableBlock(props: TableBlockProps) {
|
||||
const { fields = [], details = [], initialValues } = props;
|
||||
const columns = fields2columns(fields);
|
||||
const resource = Resource.make(props.resource);
|
||||
const { data, loading } = useRequest(() => {
|
||||
if (initialValues) {
|
||||
return Promise.resolve({
|
||||
list: initialValues,
|
||||
return bars;
|
||||
}
|
||||
|
||||
const nested = findActionBars(current);
|
||||
|
||||
Object.keys(nested).forEach((align) => {
|
||||
bars[align].push(...nested[align]);
|
||||
});
|
||||
}
|
||||
return resource.list();
|
||||
});
|
||||
console.log({ columns, data });
|
||||
return (
|
||||
<Table
|
||||
loading={loading}
|
||||
dataSource={data?.list}
|
||||
columns={columns}
|
||||
onRow={(data, index) => {
|
||||
return {
|
||||
onClick(e) {
|
||||
Drawer.open({
|
||||
bodyStyle: {
|
||||
padding: 0,
|
||||
},
|
||||
content: ({ resolve }) => {
|
||||
const tabList = details.map((detail) => {
|
||||
return {
|
||||
key: detail.name,
|
||||
tab: detail.title,
|
||||
};
|
||||
});
|
||||
return (
|
||||
<div>
|
||||
<Card
|
||||
bordered={false}
|
||||
title={'详情页面'}
|
||||
tabList={tabList}
|
||||
tabProps={{ size: 'small' }}
|
||||
>
|
||||
aaa
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
});
|
||||
},
|
||||
};
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
return bars;
|
||||
}, actionBars);
|
||||
}
|
||||
|
||||
return findActionBars(schema);
|
||||
}
|
||||
|
||||
export default TableBlock;
|
||||
function useTableDetails() {
|
||||
const schema = useFieldSchema();
|
||||
|
||||
function findDetails(schema: Schema) {
|
||||
return schema.reduceProperties((items, current) => {
|
||||
if (current['x-component'] === 'Table.Details') {
|
||||
return [...items, current];
|
||||
}
|
||||
return [...items, ...findDetails(current)];
|
||||
}, []);
|
||||
}
|
||||
|
||||
return findDetails(schema);
|
||||
}
|
||||
|
||||
export const Table: any = observer((props) => {
|
||||
const field = useField<Formily.Core.Models.ArrayField>();
|
||||
const dataSource = field.value;
|
||||
const columns = useTableColumns({ dataSource });
|
||||
const actionBars = useTableActionBars();
|
||||
const details = useTableDetails();
|
||||
console.log({ columns, actionBars, details });
|
||||
return (
|
||||
<>
|
||||
<AntdTable columns={columns} dataSource={dataSource} />
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
Table.Column = () => null;
|
||||
Table.Column.DesignableBar = ColumnDesignableBar;
|
||||
Table.DesignableBar = DesignableBar;
|
||||
|
74
packages/client/src/blocks/tabs/index.md
Normal file
74
packages/client/src/blocks/tabs/index.md
Normal file
@ -0,0 +1,74 @@
|
||||
---
|
||||
title: Tabs - 标签页
|
||||
nav:
|
||||
title: 组件
|
||||
path: /client
|
||||
group:
|
||||
order: 1
|
||||
title: 区块 - Blocks
|
||||
path: /client/blocks
|
||||
---
|
||||
|
||||
# Tabs - 标签页
|
||||
|
||||
## 代码演示
|
||||
|
||||
### 基本使用
|
||||
|
||||
```tsx
|
||||
/**
|
||||
* title: 基本使用
|
||||
*/
|
||||
import React from 'react';
|
||||
import { SchemaBlock } from '../';
|
||||
|
||||
const schema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
tabs: {
|
||||
type: 'void',
|
||||
'x-component': 'Tabs',
|
||||
properties: {
|
||||
tab1: {
|
||||
type: 'void',
|
||||
'x-component': 'Tabs.TabPane',
|
||||
'x-component-props': {
|
||||
tab: 'Tab1',
|
||||
},
|
||||
properties: {
|
||||
aaa: {
|
||||
type: 'string',
|
||||
title: 'AAA',
|
||||
'x-decorator': 'FormItem',
|
||||
required: true,
|
||||
'x-component': 'Input',
|
||||
},
|
||||
},
|
||||
},
|
||||
tab2: {
|
||||
type: 'void',
|
||||
'x-component': 'Tabs.TabPane',
|
||||
'x-component-props': {
|
||||
tab: 'Tab2',
|
||||
},
|
||||
properties: {
|
||||
bbb: {
|
||||
type: 'string',
|
||||
title: 'BBB',
|
||||
'x-decorator': 'FormItem',
|
||||
required: true,
|
||||
'x-component': 'Input',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
export default () => {
|
||||
return (
|
||||
<SchemaBlock schema={schema} />
|
||||
);
|
||||
};
|
||||
```
|
6
packages/client/src/blocks/tabs/index.tsx
Normal file
6
packages/client/src/blocks/tabs/index.tsx
Normal file
@ -0,0 +1,6 @@
|
||||
import { observer, connect } from '@formily/react';
|
||||
import React from 'react';
|
||||
import { FormTab } from '@formily/antd';
|
||||
import '@formily/antd/lib/form-tab/style';
|
||||
|
||||
export const Tabs = FormTab;
|
57
packages/client/src/blocks/time-picker/index.md
Normal file
57
packages/client/src/blocks/time-picker/index.md
Normal file
@ -0,0 +1,57 @@
|
||||
---
|
||||
title: TimePicker - 时间选择器
|
||||
nav:
|
||||
title: 组件
|
||||
path: /client
|
||||
group:
|
||||
order: 1
|
||||
title: 区块 - Blocks
|
||||
path: /client/blocks
|
||||
---
|
||||
|
||||
# TimePicker - 时间选择器
|
||||
|
||||
## 代码演示
|
||||
|
||||
### 日期选择器
|
||||
|
||||
```tsx
|
||||
/**
|
||||
* title: 日期选择器
|
||||
*/
|
||||
import React from 'react';
|
||||
import { SchemaBlock } from '../';
|
||||
|
||||
const schema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
input: {
|
||||
type: 'string',
|
||||
title: `编辑模式`,
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'TimePicker',
|
||||
'x-reactions': {
|
||||
target: 'read',
|
||||
fulfill: {
|
||||
state: {
|
||||
value: '{{$self.value}}',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
read: {
|
||||
type: 'string',
|
||||
title: `阅读模式`,
|
||||
'x-read-pretty': true,
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'TimePicker',
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
export default () => {
|
||||
return (
|
||||
<SchemaBlock schema={schema} />
|
||||
);
|
||||
};
|
||||
```
|
@ -5,7 +5,7 @@ import {
|
||||
TimePickerProps as AntdTimePickerProps,
|
||||
TimeRangePickerProps,
|
||||
} from 'antd/lib/time-picker'
|
||||
import { Descriptions } from '../descriptions'
|
||||
import { Display } from '../display'
|
||||
import { formatMomentValue, momentable } from '@formily/antd/esm/__builtins__'
|
||||
|
||||
type ComposedTimePicker = React.FC<AntdTimePickerProps> & {
|
||||
@ -32,13 +32,13 @@ const mapTimeFormat = function () {
|
||||
export const TimePicker: ComposedTimePicker = connect(
|
||||
AntdTimePicker,
|
||||
mapProps(mapTimeFormat()),
|
||||
mapReadPretty(Descriptions.TimePicker)
|
||||
mapReadPretty(Display.TimePicker)
|
||||
)
|
||||
|
||||
TimePicker.RangePicker = connect(
|
||||
AntdTimePicker.RangePicker,
|
||||
mapProps(mapTimeFormat()),
|
||||
mapReadPretty(Descriptions.TimeRangePicker)
|
||||
mapReadPretty(Display.TimeRangePicker)
|
||||
)
|
||||
|
||||
export default TimePicker
|
75
packages/client/src/blocks/upload/index.md
Normal file
75
packages/client/src/blocks/upload/index.md
Normal file
@ -0,0 +1,75 @@
|
||||
---
|
||||
title: Upload - 上传
|
||||
nav:
|
||||
title: 组件
|
||||
path: /client
|
||||
group:
|
||||
order: 1
|
||||
title: 区块 - Blocks
|
||||
path: /client/blocks
|
||||
---
|
||||
|
||||
# Upload - 上传
|
||||
|
||||
## 代码演示
|
||||
|
||||
### 上传
|
||||
|
||||
```tsx
|
||||
import React from 'react';
|
||||
import { Button } from 'antd'
|
||||
import { UploadOutlined, InboxOutlined } from '@ant-design/icons'
|
||||
import Upload from './';
|
||||
import { SchemaBlock, registerComponents } from '../';
|
||||
|
||||
const NormalUpload = (props) => {
|
||||
return (
|
||||
<Upload
|
||||
{...props}
|
||||
action="https://www.mocky.io/v2/5cc8019d300000980a055e76"
|
||||
headers={{
|
||||
authorization: 'authorization-text',
|
||||
}}
|
||||
>
|
||||
<Button icon={<UploadOutlined />}>上传文件</Button>
|
||||
</Upload>
|
||||
)
|
||||
}
|
||||
|
||||
registerComponents({
|
||||
NormalUpload,
|
||||
});
|
||||
|
||||
const schema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
input: {
|
||||
type: 'string',
|
||||
title: `编辑模式`,
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'NormalUpload',
|
||||
'x-reactions': {
|
||||
target: 'read',
|
||||
fulfill: {
|
||||
state: {
|
||||
value: '{{$self.value}}',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
read: {
|
||||
type: 'string',
|
||||
title: `阅读模式`,
|
||||
'x-read-pretty': true,
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'NormalUpload',
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
export default () => {
|
||||
return (
|
||||
<SchemaBlock schema={schema} />
|
||||
);
|
||||
};
|
||||
```
|
@ -1,12 +0,0 @@
|
||||
---
|
||||
title: ActionBar - 操作栏
|
||||
nav:
|
||||
title: 组件
|
||||
path: /client
|
||||
group:
|
||||
order: 3
|
||||
title: 其他
|
||||
path: /client/others
|
||||
---
|
||||
|
||||
# ActionBar - 操作栏
|
@ -1,17 +1,5 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
createFromIconfontCN,
|
||||
UserOutlined,
|
||||
TeamOutlined,
|
||||
DatabaseOutlined,
|
||||
DashboardOutlined,
|
||||
SettingOutlined,
|
||||
TableOutlined,
|
||||
MenuOutlined,
|
||||
HistoryOutlined,
|
||||
NotificationOutlined,
|
||||
} from '@ant-design/icons';
|
||||
|
||||
import { createFromIconfontCN } from '@ant-design/icons';
|
||||
import * as antIcons from '@ant-design/icons';
|
||||
|
||||
export const IconFont = createFromIconfontCN({
|
||||
@ -32,29 +20,17 @@ export function hasIcon(type: string) {
|
||||
}
|
||||
|
||||
export function registerIcons(components) {
|
||||
Object.keys(components).forEach(type => {
|
||||
Object.keys(components).forEach((type) => {
|
||||
registerIcon(type, components[type]);
|
||||
});
|
||||
}
|
||||
|
||||
Object.keys(antIcons).forEach(name => {
|
||||
Object.keys(antIcons).forEach((name) => {
|
||||
if (name.endsWith('Outlined')) {
|
||||
registerIcon(name, antIcons[name]);
|
||||
}
|
||||
});
|
||||
|
||||
registerIcons({
|
||||
// HistoryOutlined,
|
||||
// MenuOutlined,
|
||||
// TableOutlined,
|
||||
// SettingOutlined,
|
||||
// TeamOutlined,
|
||||
// UserOutlined,
|
||||
// DatabaseOutlined,
|
||||
// DashboardOutlined,
|
||||
// NotificationOutlined,
|
||||
});
|
||||
|
||||
interface IconProps {
|
||||
type: string;
|
||||
[key: string]: any;
|
||||
|
@ -1,155 +0,0 @@
|
||||
---
|
||||
title: MenuEditor - 菜单编辑器
|
||||
nav:
|
||||
title: 组件
|
||||
path: /client
|
||||
group:
|
||||
order: 3
|
||||
title: 其他
|
||||
path: /client/others
|
||||
---
|
||||
|
||||
# MenuEditor - 菜单编辑器
|
||||
|
||||
基于 Ant Design Menu 组件实现的菜单编辑器,考虑到可视化编辑的体验,目前只支持`竖向菜单`和`混合菜单`两种场景。
|
||||
|
||||
## 代码演示
|
||||
|
||||
### 竖向菜单
|
||||
|
||||
```tsx
|
||||
import React from 'react';
|
||||
import MenuEditor from './';
|
||||
|
||||
const items = [
|
||||
{
|
||||
icon: 'AimOutlined',
|
||||
name: 'name1',
|
||||
title: '菜单组1',
|
||||
children: [
|
||||
{
|
||||
icon: 'CiOutlined',
|
||||
name: 'name3',
|
||||
title: '子菜单1',
|
||||
},
|
||||
{
|
||||
icon: 'CiOutlined',
|
||||
name: 'name4',
|
||||
title: '子菜单2',
|
||||
},
|
||||
{
|
||||
icon: 'CiOutlined',
|
||||
name: 'name5',
|
||||
title: '子菜单组1',
|
||||
children: [
|
||||
{
|
||||
icon: 'CiOutlined',
|
||||
name: 'name6',
|
||||
title: '子菜单6',
|
||||
},
|
||||
{
|
||||
icon: 'CiOutlined',
|
||||
name: 'name7',
|
||||
title: '子菜单7',
|
||||
},
|
||||
{
|
||||
icon: 'CiOutlined',
|
||||
name: 'name8',
|
||||
title: '子菜单8',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
icon: 'CiOutlined',
|
||||
name: 'name2',
|
||||
title: '菜单1',
|
||||
},
|
||||
];
|
||||
|
||||
export default () => {
|
||||
return (
|
||||
<MenuEditor style={{ width: 256 }} items={items} mode={'vertical'}/>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### 混合菜单
|
||||
|
||||
```tsx
|
||||
import React from 'react';
|
||||
import MenuEditor from './';
|
||||
|
||||
const items = [
|
||||
{
|
||||
name: 'name1',
|
||||
title: '菜单组1',
|
||||
children: [
|
||||
{
|
||||
name: 'name3',
|
||||
title: '子菜单1',
|
||||
},
|
||||
{
|
||||
name: 'name4',
|
||||
title: '子菜单2',
|
||||
},
|
||||
{
|
||||
name: 'name5',
|
||||
title: '子菜单组1',
|
||||
children: [
|
||||
{
|
||||
name: 'name6',
|
||||
title: '子菜单6',
|
||||
},
|
||||
{
|
||||
name: 'name7',
|
||||
title: '子菜单7',
|
||||
},
|
||||
{
|
||||
name: 'name8',
|
||||
title: '子菜单8',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'name9',
|
||||
title: '菜单组2',
|
||||
children: [
|
||||
{
|
||||
name: 'name10',
|
||||
title: '子菜单10',
|
||||
},
|
||||
{
|
||||
name: 'name11',
|
||||
title: '子菜单11',
|
||||
},
|
||||
{
|
||||
name: 'name12',
|
||||
title: '子菜单12',
|
||||
},
|
||||
{
|
||||
name: 'name13',
|
||||
title: '子菜单13',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'name2',
|
||||
title: '菜单1',
|
||||
},
|
||||
];
|
||||
|
||||
export default () => {
|
||||
const [key, setKey] = React.useState('name1');
|
||||
return (
|
||||
<MenuEditor onSelect={(info) => {
|
||||
setKey(info.key);
|
||||
}} defaultValue={key} items={items} mode={'mix'}>
|
||||
<div style={{ padding: 24 }}>这是正文 {key}</div>
|
||||
</MenuEditor>
|
||||
)
|
||||
}
|
||||
```
|
@ -1,385 +0,0 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Layout, Menu, Button, Dropdown, Modal } from 'antd';
|
||||
import {
|
||||
AppstoreOutlined,
|
||||
MenuOutlined,
|
||||
SettingOutlined,
|
||||
PlusOutlined,
|
||||
DashboardOutlined,
|
||||
LinkOutlined,
|
||||
GroupOutlined,
|
||||
EditOutlined,
|
||||
ArrowRightOutlined,
|
||||
DeleteOutlined,
|
||||
} from '@ant-design/icons';
|
||||
import './style.less';
|
||||
import Icon from '../Icon';
|
||||
import { FormDialog, FormLayout } from '@formily/antd';
|
||||
import { SchemaField } from '../../fields';
|
||||
import { useDynamicList } from 'ahooks';
|
||||
|
||||
const { SubMenu } = Menu;
|
||||
|
||||
export function generateName(): string {
|
||||
return `${Math.random()
|
||||
.toString(36)
|
||||
.replace('0.', '')
|
||||
.slice(-4)
|
||||
.padStart(4, '0')}`;
|
||||
}
|
||||
|
||||
export function AddNewAction(props) {
|
||||
const { data } = props;
|
||||
const insert = (values: MenuItemOptions) => {
|
||||
props.insert && props.insert({ ...values, name: generateName() });
|
||||
};
|
||||
const prefix = data ? `在${data.title}后面` : '';
|
||||
const menu = (
|
||||
<Menu>
|
||||
<Menu.Item
|
||||
key="page"
|
||||
onClick={() => {
|
||||
FormDialog(`${prefix}新建菜单`, () => {
|
||||
return (
|
||||
<FormLayout labelCol={6} wrapperCol={10}>
|
||||
<SchemaField>
|
||||
<SchemaField.String
|
||||
name="title"
|
||||
required
|
||||
title="名称"
|
||||
x-decorator="FormItem"
|
||||
x-component="Input"
|
||||
/>
|
||||
<SchemaField.String
|
||||
name="icon"
|
||||
title="图标"
|
||||
x-decorator="FormItem"
|
||||
x-component="IconPicker"
|
||||
/>
|
||||
</SchemaField>
|
||||
</FormLayout>
|
||||
);
|
||||
})
|
||||
.open({})
|
||||
.then(insert);
|
||||
}}
|
||||
>
|
||||
<MenuOutlined /> 新建菜单
|
||||
</Menu.Item>
|
||||
<Menu.Item
|
||||
key="group"
|
||||
onClick={() => {
|
||||
FormDialog(`${prefix}新建分组`, () => {
|
||||
return (
|
||||
<FormLayout labelCol={6} wrapperCol={10}>
|
||||
<SchemaField>
|
||||
<SchemaField.String
|
||||
name="title"
|
||||
required
|
||||
title="分组名称"
|
||||
x-decorator="FormItem"
|
||||
x-component="Input"
|
||||
/>
|
||||
<SchemaField.String
|
||||
name="icon"
|
||||
title="图标"
|
||||
x-decorator="FormItem"
|
||||
x-component="IconPicker"
|
||||
/>
|
||||
</SchemaField>
|
||||
</FormLayout>
|
||||
);
|
||||
})
|
||||
.open({})
|
||||
.then((data) =>
|
||||
insert({ ...data, name: generateName(), children: [] }),
|
||||
);
|
||||
}}
|
||||
>
|
||||
<GroupOutlined /> 新建分组
|
||||
</Menu.Item>
|
||||
<Menu.Item
|
||||
key="link"
|
||||
onClick={() => {
|
||||
FormDialog(`${prefix}添加链接`, () => {
|
||||
return (
|
||||
<FormLayout labelCol={6} wrapperCol={10}>
|
||||
<SchemaField>
|
||||
<SchemaField.String
|
||||
name="title"
|
||||
required
|
||||
title="标题"
|
||||
x-decorator="FormItem"
|
||||
x-component="Input"
|
||||
/>
|
||||
<SchemaField.String
|
||||
name="link"
|
||||
title="链接"
|
||||
x-decorator="FormItem"
|
||||
x-component="Input"
|
||||
/>
|
||||
</SchemaField>
|
||||
</FormLayout>
|
||||
);
|
||||
})
|
||||
.open({})
|
||||
.then(insert);
|
||||
}}
|
||||
>
|
||||
<LinkOutlined /> 添加链接
|
||||
</Menu.Item>
|
||||
</Menu>
|
||||
);
|
||||
|
||||
return (
|
||||
<Dropdown
|
||||
trigger={['click']}
|
||||
overlay={menu}
|
||||
>
|
||||
{props.children || <PlusOutlined />}
|
||||
</Dropdown>
|
||||
);
|
||||
}
|
||||
|
||||
export function SettingAction(props) {
|
||||
const { initialValues, replace, remove } = props;
|
||||
const menu = (
|
||||
<Menu>
|
||||
<Menu.Item
|
||||
onClick={() => {
|
||||
console.log({ initialValues });
|
||||
FormDialog('编辑菜单', () => {
|
||||
return (
|
||||
<FormLayout labelCol={6} wrapperCol={10}>
|
||||
<SchemaField>
|
||||
<SchemaField.String
|
||||
name="title"
|
||||
required
|
||||
title="菜单名称"
|
||||
x-decorator="FormItem"
|
||||
x-component="Input"
|
||||
/>
|
||||
<SchemaField.String
|
||||
name="icon"
|
||||
title="图标"
|
||||
x-decorator="FormItem"
|
||||
x-component="IconPicker"
|
||||
/>
|
||||
</SchemaField>
|
||||
{/* <FormDialog.Footer>
|
||||
<span style={{ marginLeft: 4 }}>扩展文案</span>
|
||||
</FormDialog.Footer> */}
|
||||
</FormLayout>
|
||||
);
|
||||
})
|
||||
.open({
|
||||
initialValues,
|
||||
})
|
||||
.then(replace);
|
||||
}}
|
||||
key="edit"
|
||||
>
|
||||
<EditOutlined /> 编辑菜单
|
||||
</Menu.Item>
|
||||
<Menu.Item key="move">
|
||||
<ArrowRightOutlined /> 移动到
|
||||
</Menu.Item>
|
||||
<Menu.Item
|
||||
onClick={() => {
|
||||
Modal.confirm({
|
||||
title: '确定菜单',
|
||||
content: '确认删除此菜单项吗?',
|
||||
onOk: remove,
|
||||
});
|
||||
}}
|
||||
key="delete"
|
||||
>
|
||||
<DeleteOutlined /> 删除菜单
|
||||
</Menu.Item>
|
||||
</Menu>
|
||||
);
|
||||
|
||||
return (
|
||||
<Dropdown
|
||||
trigger={['click']}
|
||||
overlay={menu}
|
||||
>
|
||||
<MenuOutlined />
|
||||
</Dropdown>
|
||||
);
|
||||
}
|
||||
|
||||
export interface MenuItemOptions {
|
||||
name: string;
|
||||
title: string;
|
||||
icon: string;
|
||||
children?: MenuItemOptions[];
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export interface MenuProps {
|
||||
mode?: 'vertical' | 'mix';
|
||||
defaultValue?: string;
|
||||
items?: MenuItemOptions[];
|
||||
children?: any;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export function VerticalMenu(props: MenuProps) {
|
||||
const { items, style, onSelect } = props;
|
||||
const { list, remove, replace, insert, push, resetList } =
|
||||
useDynamicList(items);
|
||||
|
||||
useEffect(() => {
|
||||
resetList(items);
|
||||
}, [items]);
|
||||
|
||||
const renderItems = (items: MenuItemOptions[]) => {
|
||||
return items.map((item, index) => {
|
||||
if (item.children) {
|
||||
return (
|
||||
<SubMenu
|
||||
key={item.name}
|
||||
title={
|
||||
<span>
|
||||
{item.icon && <Icon type={item.icon} />}
|
||||
{item.title}{' '}
|
||||
<span
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
className={'menu-icons'}
|
||||
>
|
||||
<SettingAction
|
||||
data={item}
|
||||
initialValues={item}
|
||||
remove={() => remove(index)}
|
||||
replace={(data: MenuItemOptions) => replace(index, data)}
|
||||
/>
|
||||
<AddNewAction
|
||||
data={item}
|
||||
insert={(data: MenuItemOptions) => insert(index + 1, data)}
|
||||
/>
|
||||
</span>
|
||||
</span>
|
||||
}
|
||||
>
|
||||
{renderItems(item.children)}
|
||||
</SubMenu>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Menu.Item
|
||||
key={item.name}
|
||||
icon={item.icon && <Icon type={item.icon} />}
|
||||
>
|
||||
{item.title}{' '}
|
||||
<span
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
className={'menu-icons'}
|
||||
>
|
||||
<SettingAction
|
||||
data={item}
|
||||
initialValues={item}
|
||||
remove={() => remove(index)}
|
||||
replace={(data: MenuItemOptions) => replace(index, data)}
|
||||
/>
|
||||
<AddNewAction
|
||||
data={item}
|
||||
insert={(data: MenuItemOptions) => insert(index + 1, data)}
|
||||
/>
|
||||
</span>
|
||||
</Menu.Item>
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Menu onSelect={onSelect} style={style} mode={'inline'}>
|
||||
{renderItems(list)}
|
||||
<Menu.ItemGroup
|
||||
key="add"
|
||||
className={'menu-add'}
|
||||
title={
|
||||
<AddNewAction insert={(data: MenuItemOptions) => push(data)}>
|
||||
<Button block type={'dashed'} icon={<PlusOutlined />}></Button>
|
||||
</AddNewAction>
|
||||
}
|
||||
></Menu.ItemGroup>
|
||||
</Menu>
|
||||
);
|
||||
}
|
||||
|
||||
export function MixMenu(props: MenuProps) {
|
||||
const { items, defaultValue, children, onSelect } = props;
|
||||
const { list, insert, replace, remove, resetList } = useDynamicList(items);
|
||||
const [value, setValue] = useState<any>(defaultValue);
|
||||
const currentItem = list.find((item) => item.name === value);
|
||||
|
||||
useEffect(() => {
|
||||
resetList(items);
|
||||
}, [items]);
|
||||
|
||||
console.log({ list, currentItem });
|
||||
|
||||
return (
|
||||
<Layout>
|
||||
<Layout.Header>
|
||||
<Menu
|
||||
onSelect={(info) => {
|
||||
setValue(info.key);
|
||||
onSelect && onSelect(info);
|
||||
}}
|
||||
defaultSelectedKeys={[value]}
|
||||
theme={'dark'}
|
||||
mode={'horizontal'}
|
||||
>
|
||||
{list.map((item, index) => (
|
||||
<Menu.Item
|
||||
key={item.name}
|
||||
icon={item.icon && <Icon type={item.icon} />}
|
||||
>
|
||||
{item.title}{' '}
|
||||
<span
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
className={'menu-icons'}
|
||||
>
|
||||
<SettingAction
|
||||
data={item}
|
||||
initialValues={item}
|
||||
remove={() => remove(index)}
|
||||
replace={(data: MenuItemOptions) => replace(index, data)}
|
||||
/>
|
||||
<AddNewAction
|
||||
data={item}
|
||||
insert={(data: MenuItemOptions) => insert(index + 1, data)}
|
||||
/>
|
||||
</span>
|
||||
</Menu.Item>
|
||||
))}
|
||||
</Menu>
|
||||
</Layout.Header>
|
||||
<Layout>
|
||||
{currentItem && currentItem.children && (
|
||||
<Layout.Sider width={256}>
|
||||
<VerticalMenu onSelect={onSelect} items={currentItem.children} />
|
||||
</Layout.Sider>
|
||||
)}
|
||||
<Layout.Content>{children}</Layout.Content>
|
||||
</Layout>
|
||||
</Layout>
|
||||
);
|
||||
}
|
||||
|
||||
export default (props: MenuProps) => {
|
||||
return props.mode === 'vertical' ? (
|
||||
<VerticalMenu {...props} />
|
||||
) : (
|
||||
<MixMenu {...props} />
|
||||
);
|
||||
};
|
@ -1,58 +0,0 @@
|
||||
.ant-layout-header {
|
||||
line-height: 48px;
|
||||
height: 48px;
|
||||
}
|
||||
.ant-menu-item {
|
||||
|
||||
.menu-icons {
|
||||
margin-left: 10px;
|
||||
opacity: 0;
|
||||
// position: absolute;
|
||||
}
|
||||
&:hover {
|
||||
.menu-icons {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
&.menu-add {
|
||||
background-color: transparent !important;
|
||||
&::after {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ant-menu-inline {
|
||||
.menu-icons {
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.ant-menu-submenu-title {
|
||||
.menu-icons {
|
||||
position: absolute;
|
||||
background-color: #fff;
|
||||
display: none;
|
||||
z-index: 10;
|
||||
}
|
||||
&:hover {
|
||||
.menu-icons {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ant-menu-horizontal {
|
||||
.menu-icons {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
background: #1890ff;
|
||||
padding-left: 10px;
|
||||
height: auto;
|
||||
line-height: 1rem;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
transition: all 0.3s ease-in;
|
||||
}
|
||||
}
|
446
packages/client/src/demos/api/blocks-getSchema/item1.ts
Normal file
446
packages/client/src/demos/api/blocks-getSchema/item1.ts
Normal file
@ -0,0 +1,446 @@
|
||||
export default {
|
||||
type: 'object',
|
||||
properties: {
|
||||
grid: {
|
||||
type: 'void',
|
||||
'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-component-props': {
|
||||
title: 'block11',
|
||||
},
|
||||
properties: {
|
||||
form1: {
|
||||
type: 'void',
|
||||
name: 'item3',
|
||||
'x-decorator': 'Card',
|
||||
'x-component': 'Form',
|
||||
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-component-props': {
|
||||
title: 'block11',
|
||||
},
|
||||
properties: {
|
||||
field1: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
title: '字段1',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
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',
|
||||
},
|
||||
properties: {
|
||||
field2: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
title: '字段2',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
row2: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Row',
|
||||
properties: {
|
||||
col21: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Col',
|
||||
'x-component-props': {
|
||||
size: 1 / 3,
|
||||
},
|
||||
properties: {
|
||||
block211: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Block',
|
||||
'x-component-props': {
|
||||
title: 'block211',
|
||||
},
|
||||
properties: {
|
||||
field3: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
title: '字段3',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
col22: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Col',
|
||||
'x-component-props': {
|
||||
size: 2 / 3,
|
||||
isLast: true,
|
||||
},
|
||||
properties: {
|
||||
block221: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Block',
|
||||
'x-component-props': {
|
||||
title: 'block221',
|
||||
},
|
||||
properties: {
|
||||
field4: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
title: '字段4',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
row3: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Row',
|
||||
"x-component-props": {
|
||||
isLast: true,
|
||||
},
|
||||
properties: {
|
||||
col31: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Col',
|
||||
'x-component-props': {
|
||||
size: 1,
|
||||
isLast: true,
|
||||
},
|
||||
properties: {
|
||||
block311: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Block',
|
||||
'x-component-props': {
|
||||
title: 'block311',
|
||||
},
|
||||
properties: {
|
||||
field5: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
title: '字段5',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
actions: {
|
||||
type: 'void',
|
||||
// 'x-decorator': 'Div',
|
||||
'x-component': 'Space',
|
||||
properties: {
|
||||
submit: {
|
||||
type: 'void',
|
||||
'x-component': 'Action',
|
||||
'x-component-props': {
|
||||
// block: true,
|
||||
type: 'primary',
|
||||
useAction: '{{ useLogin }}',
|
||||
style: {
|
||||
// width: '100%',
|
||||
},
|
||||
},
|
||||
title: '提交',
|
||||
},
|
||||
reset: {
|
||||
type: 'void',
|
||||
'x-component': 'Action',
|
||||
'x-component-props': {
|
||||
// block: true,
|
||||
useAction: '{{ useLogin }}',
|
||||
style: {
|
||||
// width: '100%',
|
||||
},
|
||||
},
|
||||
title: '重置',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
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',
|
||||
},
|
||||
properties: {
|
||||
form2: {
|
||||
type: 'void',
|
||||
name: 'item3',
|
||||
'x-decorator': 'Card',
|
||||
'x-component': 'Form',
|
||||
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-component-props': {
|
||||
title: 'block11',
|
||||
},
|
||||
properties: {
|
||||
field1: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
title: '字段1',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
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',
|
||||
},
|
||||
properties: {
|
||||
field2: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
title: '字段2',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
row2: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Row',
|
||||
properties: {
|
||||
col21: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Col',
|
||||
'x-component-props': {
|
||||
size: 1 / 3,
|
||||
},
|
||||
properties: {
|
||||
block211: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Block',
|
||||
'x-component-props': {
|
||||
title: 'block211',
|
||||
},
|
||||
properties: {
|
||||
field3: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
title: '字段3',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
col22: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Col',
|
||||
'x-component-props': {
|
||||
size: 2 / 3,
|
||||
isLast: true,
|
||||
},
|
||||
properties: {
|
||||
block221: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Block',
|
||||
'x-component-props': {
|
||||
title: 'block221',
|
||||
},
|
||||
properties: {
|
||||
field4: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
title: '字段4',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
row3: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Row',
|
||||
"x-component-props": {
|
||||
isLast: true,
|
||||
},
|
||||
properties: {
|
||||
col31: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Col',
|
||||
'x-component-props': {
|
||||
size: 1,
|
||||
isLast: true,
|
||||
},
|
||||
properties: {
|
||||
block311: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Block',
|
||||
'x-component-props': {
|
||||
title: 'block311',
|
||||
},
|
||||
properties: {
|
||||
field5: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
title: '字段5',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
actions: {
|
||||
type: 'void',
|
||||
// 'x-decorator': 'Div',
|
||||
'x-component': 'Space',
|
||||
properties: {
|
||||
submit: {
|
||||
type: 'void',
|
||||
'x-component': 'Action',
|
||||
'x-component-props': {
|
||||
// block: true,
|
||||
type: 'primary',
|
||||
useAction: '{{ useLogin }}',
|
||||
style: {
|
||||
// width: '100%',
|
||||
},
|
||||
},
|
||||
title: '提交',
|
||||
},
|
||||
reset: {
|
||||
type: 'void',
|
||||
'x-component': 'Action',
|
||||
'x-component-props': {
|
||||
// block: true,
|
||||
useAction: '{{ useLogin }}',
|
||||
style: {
|
||||
// width: '100%',
|
||||
},
|
||||
},
|
||||
title: '重置',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
38
packages/client/src/demos/api/blocks-getSchema/item2.ts
Normal file
38
packages/client/src/demos/api/blocks-getSchema/item2.ts
Normal file
@ -0,0 +1,38 @@
|
||||
import { uid } from '@formily/shared';
|
||||
|
||||
export default {
|
||||
type: 'object',
|
||||
properties: {
|
||||
[`g_${uid()}`]: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid',
|
||||
properties: {
|
||||
[`gr_${uid()}`]: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Row',
|
||||
properties: {
|
||||
[`gc_${uid()}`]: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Col',
|
||||
'x-component-props': {
|
||||
size: 1,
|
||||
},
|
||||
properties: {
|
||||
[`gb_${uid()}`]: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Block',
|
||||
properties: {
|
||||
[`gbn_${uid()}`]: {
|
||||
type: 'void',
|
||||
'x-component': 'AddNew',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
63
packages/client/src/demos/api/blocks-getSchema/item22.ts
Normal file
63
packages/client/src/demos/api/blocks-getSchema/item22.ts
Normal file
@ -0,0 +1,63 @@
|
||||
export default {
|
||||
type: 'object',
|
||||
properties: {
|
||||
input: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input',
|
||||
'x-component-props': {
|
||||
placeholder: '邮箱或用户名',
|
||||
style: {
|
||||
// width: 240,
|
||||
},
|
||||
},
|
||||
},
|
||||
textarea: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Password',
|
||||
'x-component-props': {
|
||||
placeholder: '密码',
|
||||
style: {
|
||||
// width: 240,
|
||||
},
|
||||
},
|
||||
},
|
||||
actions: {
|
||||
type: 'void',
|
||||
// 'x-decorator': 'Div',
|
||||
'x-component': 'Div',
|
||||
properties: {
|
||||
submit: {
|
||||
type: 'void',
|
||||
'x-component': 'Action',
|
||||
'x-component-props': {
|
||||
block: true,
|
||||
type: 'primary',
|
||||
useAction: '{{ useLogin }}',
|
||||
style: {
|
||||
width: '100%',
|
||||
},
|
||||
},
|
||||
title: '登录',
|
||||
},
|
||||
},
|
||||
},
|
||||
registerlink: {
|
||||
type: 'void',
|
||||
'x-component': 'Div',
|
||||
properties: {
|
||||
link: {
|
||||
type: 'void',
|
||||
'x-component': 'Action.Link',
|
||||
'x-component-props': {
|
||||
to: '/register',
|
||||
},
|
||||
title: '注册账号',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
196
packages/client/src/demos/api/blocks-getSchema/item3.ts
Normal file
196
packages/client/src/demos/api/blocks-getSchema/item3.ts
Normal file
@ -0,0 +1,196 @@
|
||||
export default {
|
||||
type: 'void',
|
||||
name: 'item3',
|
||||
'x-decorator': 'Card',
|
||||
'x-component': 'Form',
|
||||
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-component-props': {
|
||||
title: 'block11',
|
||||
},
|
||||
properties: {
|
||||
field1: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
title: '字段1',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
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',
|
||||
},
|
||||
properties: {
|
||||
field2: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
title: '字段2',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
row2: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Row',
|
||||
properties: {
|
||||
col21: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Col',
|
||||
'x-component-props': {
|
||||
size: 1 / 3,
|
||||
},
|
||||
properties: {
|
||||
block211: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Block',
|
||||
'x-component-props': {
|
||||
title: 'block211',
|
||||
},
|
||||
properties: {
|
||||
field3: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
title: '字段3',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
col22: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Col',
|
||||
'x-component-props': {
|
||||
size: 2 / 3,
|
||||
isLast: true,
|
||||
},
|
||||
properties: {
|
||||
block221: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Block',
|
||||
'x-component-props': {
|
||||
title: 'block221',
|
||||
},
|
||||
properties: {
|
||||
field4: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
title: '字段4',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
row3: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Row',
|
||||
"x-component-props": {
|
||||
isLast: true,
|
||||
},
|
||||
properties: {
|
||||
col31: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Col',
|
||||
'x-component-props': {
|
||||
size: 1,
|
||||
isLast: true,
|
||||
},
|
||||
properties: {
|
||||
block311: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Block',
|
||||
'x-component-props': {
|
||||
title: 'block311',
|
||||
},
|
||||
properties: {
|
||||
field5: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
title: '字段5',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
actions: {
|
||||
type: 'void',
|
||||
// 'x-decorator': 'Div',
|
||||
'x-component': 'Space',
|
||||
properties: {
|
||||
submit: {
|
||||
type: 'void',
|
||||
'x-component': 'Action',
|
||||
'x-component-props': {
|
||||
// block: true,
|
||||
type: 'primary',
|
||||
useAction: '{{ useLogin }}',
|
||||
style: {
|
||||
// width: '100%',
|
||||
},
|
||||
},
|
||||
title: '提交',
|
||||
},
|
||||
reset: {
|
||||
type: 'void',
|
||||
'x-component': 'Action',
|
||||
'x-component-props': {
|
||||
// block: true,
|
||||
useAction: '{{ useLogin }}',
|
||||
style: {
|
||||
// width: '100%',
|
||||
},
|
||||
},
|
||||
title: '重置',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
17
packages/client/src/demos/api/blocks-getSchema/item4.ts
Normal file
17
packages/client/src/demos/api/blocks-getSchema/item4.ts
Normal file
@ -0,0 +1,17 @@
|
||||
export default {
|
||||
type: 'object',
|
||||
properties: {
|
||||
input: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input',
|
||||
'x-component-props': {
|
||||
placeholder: '邮箱或用户名',
|
||||
style: {
|
||||
// width: 240,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
92
packages/client/src/demos/api/blocks-getSchema/item5.ts
Normal file
92
packages/client/src/demos/api/blocks-getSchema/item5.ts
Normal file
@ -0,0 +1,92 @@
|
||||
export default {
|
||||
name: 'arr',
|
||||
type: 'array',
|
||||
'x-component': 'Table',
|
||||
default: [{ field1: 'a', field2: 'b' }, { field1: 'a', field2: 'b' }],
|
||||
'x-component-props': {},
|
||||
properties: {
|
||||
actionBar1: {
|
||||
type: 'void',
|
||||
'x-component': 'Table.ActionBar',
|
||||
properties: {
|
||||
action1: {
|
||||
type: 'object',
|
||||
title: '按钮1',
|
||||
'x-component': 'Table.Action.Popover',
|
||||
'x-component-props': {
|
||||
},
|
||||
properties: {
|
||||
|
||||
},
|
||||
},
|
||||
action2: {
|
||||
type: 'void',
|
||||
title: '按钮2',
|
||||
'x-component': 'Table.Action.Button',
|
||||
'x-component-props': {
|
||||
label: '按钮2',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
actionBar2: {
|
||||
type: 'void',
|
||||
'x-component': 'Table.ActionBar',
|
||||
'x-component-props': {
|
||||
align: 'bottom',
|
||||
},
|
||||
properties: {
|
||||
pagination: {
|
||||
type: 'void',
|
||||
'x-component': 'Table.Pagination',
|
||||
'x-component-props': {
|
||||
defaultCurrent: 1,
|
||||
total: 50,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
details: {
|
||||
type: 'void',
|
||||
'x-component': 'Table.Details',
|
||||
properties: {},
|
||||
},
|
||||
col1: {
|
||||
type: 'void',
|
||||
title: '字段1',
|
||||
'x-component': 'Table.Column',
|
||||
'x-component-props': {
|
||||
title: 'z1',
|
||||
width: 100,
|
||||
},
|
||||
properties: {
|
||||
field1: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
'x-decorator-props': {
|
||||
feedbackLayout: 'popover',
|
||||
},
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input',
|
||||
},
|
||||
},
|
||||
},
|
||||
col2: {
|
||||
type: 'void',
|
||||
title: '字段2',
|
||||
'x-component': 'Table.Column',
|
||||
properties: {
|
||||
field2: {
|
||||
type: 'string',
|
||||
title: '字段2',
|
||||
required: true,
|
||||
'x-decorator-props': {
|
||||
feedbackLayout: 'popover',
|
||||
},
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
63
packages/client/src/demos/api/blocks-getSchema/login.ts
Normal file
63
packages/client/src/demos/api/blocks-getSchema/login.ts
Normal file
@ -0,0 +1,63 @@
|
||||
export default {
|
||||
type: 'object',
|
||||
properties: {
|
||||
input: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input',
|
||||
'x-component-props': {
|
||||
placeholder: '邮箱或用户名',
|
||||
style: {
|
||||
// width: 240,
|
||||
},
|
||||
},
|
||||
},
|
||||
textarea: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Password',
|
||||
'x-component-props': {
|
||||
placeholder: '密码',
|
||||
style: {
|
||||
// width: 240,
|
||||
},
|
||||
},
|
||||
},
|
||||
actions: {
|
||||
type: 'void',
|
||||
// 'x-decorator': 'Div',
|
||||
'x-component': 'Div',
|
||||
properties: {
|
||||
submit: {
|
||||
type: 'void',
|
||||
'x-component': 'Action',
|
||||
'x-component-props': {
|
||||
block: true,
|
||||
type: 'primary',
|
||||
useAction: '{{ useLogin }}',
|
||||
style: {
|
||||
width: '100%',
|
||||
},
|
||||
},
|
||||
title: '登录',
|
||||
},
|
||||
},
|
||||
},
|
||||
registerlink: {
|
||||
type: 'void',
|
||||
'x-component': 'Div',
|
||||
properties: {
|
||||
link: {
|
||||
type: 'void',
|
||||
'x-component': 'Action.Link',
|
||||
'x-component-props': {
|
||||
to: '/register',
|
||||
},
|
||||
title: '注册账号',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user