This commit is contained in:
chenos 2021-07-20 23:19:54 +08:00
parent 97ca17bc10
commit e8399e167e
17 changed files with 1020 additions and 140 deletions

View File

@ -17,7 +17,7 @@ import {
useHistory,
Redirect,
} from 'react-router-dom';
import { SchemaRenderer } from '../../schemas';
import { CollectionContextProvider, PageTitleContextProvider, SchemaRenderer, usePageTitleContext } from '../../schemas';
import { useRequest } from 'ahooks';
import './style.less';
@ -30,6 +30,7 @@ function LayoutWithMenu({ schema }) {
const location = useLocation();
const sideMenuRef = useRef();
const [activeKey, setActiveKey] = useState(match.params.name);
const [,setPageTitle] = usePageTitleContext();
const onSelect = (info) => {
console.log('LayoutWithMenu', info);
if (!info.schema) {
@ -39,6 +40,9 @@ function LayoutWithMenu({ schema }) {
setActiveKey(null);
} else {
setActiveKey(info.schema.key);
if (info.schema.title) {
setPageTitle(info.schema.title);
}
}
};
console.log({ match });
@ -98,7 +102,13 @@ export function AdminLayout({ route }: any) {
return <Spin />;
}
return <LayoutWithMenu schema={data} />;
return (
<CollectionContextProvider>
<PageTitleContextProvider>
<LayoutWithMenu schema={data} />
</PageTitleContextProvider>
</CollectionContextProvider>
);
}
export default AdminLayout;

View File

@ -46,8 +46,10 @@ import { TimePicker } from '../../schemas/time-picker';
import { Upload } from '../../schemas/upload';
import { FormItem } from '../../schemas/form-item';
import { BlockItem } from '../../schemas/block-item';
import { CardItem } from '../../schemas/card-item';
import { DragAndDrop } from '../../schemas/drag-and-drop';
import { TreeSelect } from '../../schemas/tree-select';
import { Page } from '../../schemas/page';
export const BlockContext = createContext({ dragRef: null });
@ -59,11 +61,14 @@ interface DesignableContextProps {
}
export const SchemaField = createSchemaField({
scope: {},
scope: {
Table,
},
components: {
Card,
Div,
Space,
Page,
ArrayCollapse,
ArrayTable,
@ -73,6 +78,7 @@ export const SchemaField = createSchemaField({
DragAndDrop,
BlockItem,
CardItem,
FormItem,
Action,
@ -173,6 +179,14 @@ function setKeys(schema: ISchema, parentKey = null) {
});
}
export function useSchemaComponent(component: string) {
if (!component) {
return null;
}
const options = useContext(SchemaOptionsContext);
return get(options.components, component);
}
export function useDesignable(path?: any) {
const { schema = new Schema({}), refresh } = useContext(DesignableContext);
const schemaPath = path || useSchemaPath();

View File

@ -46,9 +46,17 @@ import {
DownOutlined,
DatabaseOutlined,
} from '@ant-design/icons';
import { createSchema, useDesignable, useSchemaPath } from '../';
import {
createOrUpdateCollection,
createSchema,
request,
useCollectionContext,
useDesignable,
useSchemaPath,
} from '../';
import { uid } from '@formily/shared';
import { request } from '../../demos/api';
import { useRequest } from 'ahooks';
import { SchemaField } from '../../components/schema-renderer';
const generateGridBlock = (schema: ISchema) => {
const name = schema.name || uid();
@ -68,6 +76,10 @@ const generateGridBlock = (schema: ISchema) => {
};
};
const isGrid = (schema: Schema) => {
return schema['x-component'] === 'Grid';
};
const isGridBlock = (schema: Schema) => {
if (schema.parent['x-component'] !== 'Grid.Col') {
return false;
@ -83,14 +95,519 @@ const isGridBlock = (schema: Schema) => {
return true;
};
function generateCardItemSchema(component) {
const defaults = {
Markdown: {
type: 'string',
default: '这是一段演示文字',
'x-designable-bar': 'Markdown.DesignableBar',
'x-decorator': 'CardItem',
'x-read-pretty': true,
'x-component': 'Markdown',
'x-component-props': {
savedInSchema: true,
},
},
Table: {
type: 'array',
'x-designable-bar': 'Table.DesignableBar',
'x-decorator': 'CardItem',
'x-component': 'Table',
default: [
{ key: uid(), field1: uid(), field2: uid() },
{ key: uid(), field1: uid(), field2: uid() },
{ key: uid(), field1: uid(), field2: uid() },
{ key: uid(), field1: uid(), field2: uid() },
{ key: uid(), field1: uid(), field2: uid() },
{ key: uid(), field1: uid(), field2: uid() },
{ key: uid(), field1: uid(), field2: uid() },
{ key: uid(), field1: uid(), field2: uid() },
{ key: uid(), field1: uid(), field2: uid() },
{ key: uid(), field1: uid(), field2: uid() },
{ key: uid(), field1: uid(), field2: uid() },
{ key: uid(), field1: uid(), field2: uid() },
],
'x-component-props': {
rowKey: 'key',
},
properties: {
[`action_bar_${uid()}`]: {
type: 'void',
'x-component': 'Table.ActionBar',
properties: {
action1: {
type: 'void',
name: 'action1',
title: '筛选',
'x-component': 'Action',
properties: {
popover1: {
type: 'void',
title: '弹窗标题',
'x-decorator': 'Form',
'x-component': 'Action.Popover',
'x-component-props': {},
properties: {
filter: {
name: 'filter',
type: 'object',
'x-component': 'Filter',
properties: {
column1: {
type: 'void',
title: '字段1',
'x-component': 'Filter.Column',
'x-component-props': {
operations: [
{ label: '等于', value: 'eq' },
{ label: '不等于', value: 'ne' },
],
},
properties: {
field1: {
type: 'string',
'x-component': 'Input',
},
},
},
column2: {
type: 'void',
title: '字段2',
'x-component': 'Filter.Column',
'x-component-props': {
operations: [
{ label: '大于', value: 'gt' },
{ label: '小于', value: 'lt' },
{
label: '非空',
value: 'notNull',
noValue: true,
},
],
},
properties: {
field1: {
type: 'number',
'x-component': 'InputNumber',
},
},
},
},
},
},
},
},
},
action2: {
type: 'void',
name: 'action1',
title: '新增',
'x-component': 'Action',
'x-designable-bar': 'Action.DesignableBar',
properties: {
drawer1: {
type: 'void',
title: '抽屉标题',
'x-decorator': 'Form',
'x-component': 'Action.Modal',
'x-component-props': {
useOkAction: '{{ Table.useTableCreateAction }}',
},
properties: {
field1: {
type: 'string',
title: '字段1',
'x-decorator': 'FormItem',
'x-component': 'Input',
},
field2: {
type: 'string',
title: '字段2',
'x-decorator': 'FormItem',
'x-component': 'Input',
},
},
},
},
},
action3: {
type: 'void',
name: 'action1',
title: '删除',
'x-component': 'Action',
'x-component-props': {
useAction: '{{ Table.useTableDestroyAction }}',
},
},
},
},
[`a_${uid()}`]: {
type: 'void',
'x-component': 'Table.ActionBar',
'x-component-props': {
align: 'bottom',
},
properties: {
pagination: {
type: 'void',
'x-component': 'Table.Pagination',
'x-component-props': {},
},
},
},
[`column_${uid()}`]: {
type: 'void',
title: '排序',
'x-component': 'Table.Column',
properties: {
sort: {
type: 'void',
'x-component': 'Table.SortHandle',
},
},
},
[`column_${uid()}`]: {
type: 'void',
title: '序号',
'x-component': 'Table.Column',
properties: {
index: {
type: 'void',
'x-component': 'Table.Index',
},
},
},
[`column_${uid()}`]: {
type: 'void',
title: '字段1',
'x-component': 'Table.Column',
'x-component-props': {
// title: 'z1',
},
'x-designable-bar': 'Table.Column.DesignableBar',
properties: {
field1: {
type: 'string',
required: true,
'x-read-pretty': true,
'x-decorator-props': {
feedbackLayout: 'popover',
},
'x-decorator': 'FormItem',
'x-component': 'Input',
},
},
},
[`column_${uid()}`]: {
type: 'void',
title: '字段2',
'x-component': 'Table.Column',
'x-designable-bar': 'Table.Column.DesignableBar',
properties: {
field2: {
type: 'string',
// title: '字段2',
required: true,
'x-read-pretty': true,
'x-decorator-props': {
feedbackLayout: 'popover',
},
'x-decorator': 'FormItem',
'x-component': 'Input',
},
},
},
[`column_${uid()}`]: {
type: 'void',
title: '操作',
'x-component': 'Table.Column',
properties: {
[uid()]: {
type: 'void',
name: 'action1',
title: '查看',
'x-component': 'Action',
'x-designable-bar': 'Table.Action.DesignableBar',
properties: {
drawer1: {
type: 'void',
title: '查看',
'x-component': 'Action.Modal',
'x-component-props': {},
properties: {
[uid()]: {
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',
},
},
},
},
},
},
},
},
},
[uid()]: {
type: 'void',
name: 'action1',
title: '修改',
'x-component': 'Action',
'x-designable-bar': 'Table.Action.DesignableBar',
properties: {
drawer1: {
type: 'void',
title: '编辑表单',
'x-decorator': 'Form',
'x-decorator-props': {
useValues: '{{ Table.useTableRow }}',
},
'x-component': 'Action.Modal',
'x-component-props': {
useOkAction: '{{ Table.useTableUpdateAction }}',
},
properties: {
field1: {
type: 'string',
title: '字段1',
'x-decorator': 'FormItem',
'x-component': 'Input',
},
field2: {
type: 'string',
title: '字段2',
'x-decorator': 'FormItem',
'x-component': 'Input',
},
},
},
},
},
[uid()]: {
type: 'void',
title: '删除',
'x-component': 'Action',
'x-component-props': {
useAction: '{{ Table.useTableDestroyAction }}',
},
},
},
},
},
},
Form: {
type: 'void',
name: uid(),
'x-decorator': 'CardItem',
'x-component': 'Form',
'x-designable-bar': 'Form.DesignableBar',
properties: {
[uid()]: {
type: 'void',
'x-component': 'Grid',
'x-component-props': {
addNewComponent: 'AddNew.FormItem',
},
},
},
},
};
return defaults[component];
}
function generateFormItemSchema(component) {
const defaults = {
Markdown: {
type: 'string',
title: uid(),
'x-designable-bar': 'Markdown.DesignableBar',
'x-decorator': 'FormItem',
'x-component': 'Markdown',
'x-component-props': {},
},
};
return defaults[component];
}
const dbSchema = {
type: 'object',
properties: {
title: {
type: 'string',
title: '数据表名称',
required: true,
'x-decorator': 'FormItem',
'x-component': 'Input',
},
name: {
type: 'string',
title: '数据表标识',
'x-decorator': 'FormItem',
'x-component': 'Input',
'x-read-pretty': true,
},
fields: {
type: 'array',
title: '数据表字段',
'x-decorator': 'FormItem',
'x-component': 'DatabaseField',
default: [],
},
},
};
export const AddNew = () => null;
AddNew.CardItem = observer((props: any) => {
const { ghost, defaultAction } = props;
const { schema, insertBefore, insertAfter, appendChild } = useDesignable();
const path = useSchemaPath();
const { data: collections = [], loading, run } = useRequest('collections:findAll', {
formatResult: (result) => result?.data,
});
console.log({ collections });
return (
<Dropdown
overlayStyle={{
minWidth: 200,
}}
onVisibleChange={(visible) => {
if (visible) {
run();
}
console.log('onVisibleChange', visible);
}}
placement={'bottomCenter'}
overlay={
<Menu
onClick={async (info) => {
let data: ISchema;
if (['addNewTable', 'addNewForm'].includes(info.key)) {
const values = await FormDialog(`新建数据表`, () => {
return (
<FormLayout layout={'vertical'}>
<SchemaField schema={dbSchema} />
</FormLayout>
);
}).open({
initialValues: {
name: `t_${uid()}`,
fields: [],
},
});
await createOrUpdateCollection(values);
data = generateCardItemSchema(
info.key === 'addNewTable' ? 'Table' : 'Form',
);
} else if (info.key !== 'Markdown') {
const keys = info.key.split('.');
const component = keys.shift();
const tableName = keys.join('.');
data = generateCardItemSchema(component);
console.log('info.keyPath', component, tableName);
} else {
data = generateCardItemSchema(info.key);
}
if (schema['key']) {
data['key'] = uid();
}
if (isGridBlock(schema)) {
path.pop();
path.pop();
data = generateGridBlock(data);
} else if (isGrid(schema)) {
data = generateGridBlock(data);
}
if (data) {
let s;
if (isGrid(schema)) {
s = appendChild(data, [...path]);
} else if (defaultAction === 'insertAfter') {
s = insertAfter(data, [...path]);
} else {
s = insertBefore(data, [...path]);
}
await createSchema(s);
}
}}
>
<Menu.SubMenu key={'Table'} title={'新建表格'}>
<Menu.ItemGroup key={'table-select'} title={'选择数据表'}>
{collections.map((item) => (
<Menu.Item key={`Table.${item.name}`}>{item.title}</Menu.Item>
))}
</Menu.ItemGroup>
<Menu.Divider></Menu.Divider>
<Menu.Item key={'addNewTable'}></Menu.Item>
</Menu.SubMenu>
<Menu.SubMenu key={'Form'} title={'新建表单'}>
<Menu.ItemGroup key={'form-select'} title={'选择数据表'}>
{collections.map((item) => (
<Menu.Item key={`Form.${item.name}`}>{item.title}</Menu.Item>
))}
</Menu.ItemGroup>
<Menu.Divider></Menu.Divider>
<Menu.Item key={'addNewForm'}></Menu.Item>
</Menu.SubMenu>
<Menu.Item key={'Markdown'}> Markdown</Menu.Item>
<Menu.SubMenu key={'Ref'} title={'引用模板'}>
<Menu.ItemGroup key={'form-select'} title={'选择模板'}>
<Menu.Item key={'Ref.name1'}>1</Menu.Item>
</Menu.ItemGroup>
<Menu.Divider></Menu.Divider>
<Menu.Item key={'addNewRef'}></Menu.Item>
</Menu.SubMenu>
</Menu>
}
>
{ghost ? (
<PlusOutlined />
) : (
<Button block type={'dashed'} icon={<PlusOutlined />}></Button>
)}
</Dropdown>
);
});
AddNew.BlockItem = observer((props: any) => {
const { ghost, defaultAction } = props;
const { schema, insertBefore, insertAfter } = useDesignable();
const path = useSchemaPath();
return (
<Dropdown
overlayStyle={{
minWidth: 200,
}}
placement={'bottomCenter'}
overlay={
<Menu>
<Menu.SubMenu title={'新建表格'}>
@ -113,7 +630,7 @@ AddNew.BlockItem = observer((props: any) => {
type: 'void',
title: uid(),
'x-designable-bar': 'BlockItem.DesignableBar',
'x-decorator': 'BlockItem',
'x-decorator': 'CardItem',
'x-component': 'Markdown',
};
if (schema['key']) {
@ -141,7 +658,11 @@ AddNew.BlockItem = observer((props: any) => {
</Menu>
}
>
{ghost ? <PlusOutlined /> : <Button icon={<PlusOutlined />}></Button>}
{ghost ? (
<PlusOutlined />
) : (
<Button block type={'dashed'} icon={<PlusOutlined />}></Button>
)}
</Dropdown>
);
});
@ -152,6 +673,10 @@ AddNew.FormItem = observer((props: any) => {
const path = useSchemaPath();
return (
<Dropdown
overlayStyle={{
minWidth: 200,
}}
placement={'bottomCenter'}
overlay={
<Menu>
<Menu.ItemGroup title={'选择已有字段'}>
@ -159,25 +684,24 @@ AddNew.FormItem = observer((props: any) => {
style={{
minWidth: 150,
}}
onClick={() => {
let data: ISchema = {
type: 'void',
title: uid(),
'x-designable-bar': 'FormItem.DesignableBar',
'x-decorator': 'FormItem',
'x-component': 'Markdown',
};
onClick={async () => {
let data: ISchema = generateFormItemSchema('Markdown');
if (schema['key']) {
data['key'] = uid();
}
if (isGridBlock(schema)) {
path.pop();
path.pop();
data = generateGridBlock(data);
}
if (data) {
let s;
if (defaultAction === 'insertAfter') {
insertAfter(data, [...path]);
s = insertAfter(data, [...path]);
} else {
insertBefore(data, [...path]);
s = insertBefore(data, [...path]);
}
await createSchema(s);
}
}}
>
@ -219,7 +743,11 @@ AddNew.FormItem = observer((props: any) => {
</Menu>
}
>
{ghost ? <PlusOutlined /> : <Button icon={<PlusOutlined />}></Button>}
{ghost ? (
<PlusOutlined />
) : (
<Button block type={'dashed'} icon={<PlusOutlined />}></Button>
)}
</Dropdown>
);
});

View File

@ -113,17 +113,6 @@ export const BlockItem: any = observer((props) => {
return React.createElement(uid ? DraggableBlock : Block, props);
});
// export const BlockItem: any = connect((props) => {
// const { children, ...others } = props;
// const { DesignableBar } = useDesignableBar();
// return (
// <div {...others} style={{marginBottom: 24}} className={'designable-form-item'}>
// {children}
// <DesignableBar />
// </div>
// );
// });
BlockItem.DesignableBar = () => {
const field = useField();
const { schema, refresh } = useDesignable();

View File

@ -0,0 +1,32 @@
import React, { useContext, useState } from 'react';
import {
connect,
mapProps,
mapReadPretty,
observer,
SchemaOptionsContext,
useField,
useFieldSchema,
} from '@formily/react';
import { FormItem as FormilyFormItem } from '@formily/antd';
import { Card, Dropdown, Menu, Space } from 'antd';
import classNames from 'classnames';
import { MenuOutlined, DragOutlined } from '@ant-design/icons';
// import './style.less';
import get from 'lodash/get';
import { GridBlockContext } from '../grid';
import { uid } from '@formily/shared';
import { useDesignable } from '../';
import { AddNew } from '../add-new';
import { BlockItem } from '../block-item';
import { DraggableBlockContext } from '../../components/drag-and-drop';
export const CardItem: any = connect((props) => {
return (
<BlockItem>
<Card bordered={false} {...props}>
{props.children}
</Card>
</BlockItem>
);
});

View File

@ -39,7 +39,7 @@ import Modal from 'antd/lib/modal/Modal';
import { clone, cloneDeep, get } from 'lodash';
import { useEffect } from 'react';
import { useRequest } from 'ahooks';
import { createOrUpdateCollection, deleteCollection } from '..';
import { createOrUpdateCollection, deleteCollection, useCollectionContext } from '..';
export const DatabaseCollection = observer((props) => {
const field = useField<Formily.Core.Models.ArrayField>();
@ -49,6 +49,7 @@ export const DatabaseCollection = observer((props) => {
const [activeIndex, setActiveIndex] = useState(0);
const form = useForm();
const [newValue, setNewValue] = useState('');
const { refresh } = useCollectionContext();
const { run, loading } = useRequest('collections:findAll', {
formatResult: (result) => result?.data,
@ -125,6 +126,7 @@ export const DatabaseCollection = observer((props) => {
setOpen(false);
setNewValue('');
await createOrUpdateCollection(data);
await refresh();
}}
/>
</div>
@ -166,6 +168,7 @@ export const DatabaseCollection = observer((props) => {
}
if (item.name) {
await deleteCollection(item.name);
await refresh();
}
}}
/>
@ -188,6 +191,7 @@ export const DatabaseCollection = observer((props) => {
await form.validate(`${field.address.entire}.${activeIndex}.*`);
delete field.value[activeIndex]['unsaved'];
await createOrUpdateCollection(field.value[activeIndex]);
await refresh();
message.success('保存成功');
} catch (error) {}
}}

View File

@ -8,14 +8,18 @@ import {
SchemaExpressionScopeContext,
FormProvider,
ISchema,
useField,
} from '@formily/react';
import { useSchemaPath, SchemaField, useDesignable } from '../';
import { useSchemaPath, SchemaField, useDesignable, removeSchema } from '../';
import get from 'lodash/get';
import { Dropdown, Menu } from 'antd';
import { MenuOutlined } from '@ant-design/icons';
import { Dropdown, Menu, Space } from 'antd';
import { MenuOutlined, DragOutlined } from '@ant-design/icons';
import cls from 'classnames';
import { FormLayout } from '@formily/antd';
import './style.less';
import AddNew from '../add-new';
import { DraggableBlockContext } from '../../components/drag-and-drop';
import { isGridRowOrCol } from '../grid';
export const Form: any = observer((props: any) => {
const { useValues = () => ({}), ...others } = props;
@ -61,21 +65,59 @@ export const Form: any = observer((props: any) => {
Form.__Decorator = ({ children }) => children;
Form.DesignableBar = (props) => {
const { active } = props;
Form.DesignableBar = observer((props) => {
const field = useField();
const { schema, refresh, deepRemove } = useDesignable();
const [visible, setVisible] = useState(false);
const { dragRef } = useContext(DraggableBlockContext);
return (
<div className={cls('designable-bar', { active })}>
<div className={'designable-bar-actions'}>
<Dropdown
overlay={
<Menu>
<Menu.Item></Menu.Item>
</Menu>
}
>
<MenuOutlined />
</Dropdown>
</div>
<div className={cls('designable-bar', { active: visible })}>
<span
onClick={(e) => {
e.stopPropagation();
}}
className={cls('designable-bar-actions', { active: visible })}
>
<Space size={'small'}>
<AddNew.CardItem defaultAction={'insertAfter'} ghost />
{dragRef && <DragOutlined ref={dragRef} />}
<Dropdown
trigger={['click']}
visible={visible}
onVisibleChange={(visible) => {
setVisible(visible);
}}
overlay={
<Menu>
<Menu.Item
key={'update'}
onClick={() => {
field.readPretty = false;
}}
>
Markdown
</Menu.Item>
<Menu.Divider />
<Menu.Item
key={'delete'}
onClick={async () => {
const removed = deepRemove();
// console.log({ removed })
const last = removed.pop();
if (isGridRowOrCol(last)) {
await removeSchema(last);
}
}}
>
</Menu.Item>
</Menu>
}
>
<MenuOutlined />
</Dropdown>
</Space>
</span>
</div>
);
};
});

View File

@ -14,6 +14,9 @@ const schema: ISchema = {
type: 'void',
name: uid(),
'x-component': 'Grid',
"x-component-props": {
addNewComponent: 'AddNew.CardItem',
},
properties: {
[`row_${uid()}`]: {
type: 'void',

View File

@ -30,6 +30,7 @@ import {
useDragDropUID,
DragDropManagerProvider,
} from '../../components/drag-and-drop';
import { useSchemaComponent } from '../../components/schema-renderer';
const ColumnSizeContext = createContext(null);
@ -37,7 +38,7 @@ export const GridBlockContext = createContext({
dragRef: null,
});
function isGridRowOrCol(schema: ISchema) {
export function isGridRowOrCol(schema: ISchema) {
return ['Grid.Row', 'Grid.Col'].includes(schema['x-component']);
}
@ -81,11 +82,14 @@ const ColDivider = (props: any) => {
);
};
export const Grid: any = observer((props) => {
export const Grid: any = observer((props: any) => {
const { addNewComponent } = props;
const AddNewComponent = useSchemaComponent(addNewComponent);
const ref = useRef();
const schema = useFieldSchema();
// const schema = useFieldSchema();
const gridPath = useSchemaPath();
const {
schema,
schema: designableSchema,
insertAfter,
prepend,
@ -162,6 +166,7 @@ export const Grid: any = observer((props) => {
</>
);
})}
{AddNewComponent && <AddNewComponent />}
</div>
</DragDropManagerProvider>
);
@ -169,9 +174,10 @@ export const Grid: any = observer((props) => {
Grid.Row = observer((props) => {
const field = useField();
const schema = useFieldSchema();
// const schema = useFieldSchema();
const rowPath = useSchemaPath();
const {
schema,
schema: designableSchema,
refresh,
insertAfter,

View File

@ -1,18 +1,50 @@
import { ISchema } from '@formily/react';
import React from 'react';
import { ISchema, observer, Schema, useField } from '@formily/react';
import constate from 'constate';
import { useState } from 'react';
import { createContext } from 'react';
import { extend } from 'umi-request';
import { isVoidField } from '@formily/core';
import { useRequest } from 'ahooks';
export * from '../components/schema-renderer';
export function mapReadPretty(component, readPrettyProps) {
return function (target) {
return observer(
(props) => {
const field = useField()
if (!isVoidField(field) && field?.pattern === 'readPretty') {
return React.createElement(component, {
...readPrettyProps,
...props,
})
}
return React.createElement(target, props)
},
{
forwardRef: true,
}
)
};
}
export const VisibleContext = createContext(null);
export const DesignableBarContext = createContext(null);
const [PageTitleContextProvider, usePageTitleContext] = constate(() => {
return useState(null);
});
export { PageTitleContextProvider, usePageTitleContext };
export function useDefaultAction() {
return {
async run () {}
}
async run() {},
};
}
import { extend } from 'umi-request';
export const request = extend({
prefix: 'http://localhost:23003/api/',
timeout: 1000,
@ -31,7 +63,7 @@ export async function deleteCollection(name) {
params: {
filter: {
name,
}
},
},
});
}
@ -44,7 +76,7 @@ export async function createSchema(schema: ISchema) {
method: 'post',
data: schema.toJSON(),
});
};
}
export async function updateSchema(schema: ISchema) {
if (!schema['key']) {
@ -52,9 +84,9 @@ export async function updateSchema(schema: ISchema) {
}
return await request(`ui_schemas:update/${schema.key}`, {
method: 'post',
data: schema.toJSON(),
data: Schema.isSchemaInstance(schema) ? schema.toJSON() : schema,
});
};
}
export async function removeSchema(schema: ISchema) {
if (!schema['key']) {
@ -65,7 +97,15 @@ export async function removeSchema(schema: ISchema) {
params: {
filter: {
key: schema['key'],
}
},
},
});
}
const [CollectionContextProvider, useCollectionContext] = constate(() => {
return useRequest('collections:findAll', {
formatResult: (result) => result?.data,
});
});
export { CollectionContextProvider, useCollectionContext };

View File

@ -33,7 +33,6 @@ const schema = {
type: 'object',
properties: {
input: {
interface: 'string',
type: 'string',
title: `编辑模式`,
name: 'name1',
@ -76,3 +75,58 @@ export default () => {
);
};
```
### Markdown.DesignableBar
```tsx
import React from 'react';
import { SchemaRenderer } from '../';
const schema1 = {
type: 'object',
properties: {
markdown1: {
type: 'string',
title: `简易Markdown`,
default: '这是一段示例文案',
'x-read-pretty': true,
'x-decorator': 'CardItem',
'x-component': 'Markdown',
'x-designable-bar': 'Markdown.DesignableBar',
'x-component-props': {
savedInSchema: true,
// ellipsis: true,
},
},
},
};
const schema2 = {
type: 'object',
properties: {
markdown1: {
type: 'string',
title: `简易Markdown`,
default: '这是一段示例文案',
'x-read-pretty': true,
'x-decorator': 'FormItem',
'x-component': 'Markdown',
'x-designable-bar': 'Markdown.DesignableBar',
'x-component-props': {
},
},
},
};
export default () => {
return (
<div>
<h3>区块项</h3>
<SchemaRenderer schema={schema1} />
<h3>表单项</h3>
<SchemaRenderer schema={schema2} />
</div>
);
};
```

View File

@ -1,15 +1,53 @@
import React from 'react';
import { connect, mapProps, mapReadPretty, useFieldSchema } from '@formily/react';
import { Input as AntdInput } from 'antd';
import {
connect,
mapProps,
mapReadPretty,
observer,
useField,
useFieldSchema,
} from '@formily/react';
import { Button, Dropdown, Input as AntdInput, Menu, Space } from 'antd';
import { InputProps, TextAreaProps } from 'antd/lib/input';
import { Display } from '../display';
import { LoadingOutlined } from '@ant-design/icons';
import { LoadingOutlined, MenuOutlined, DragOutlined } from '@ant-design/icons';
import micromark from 'micromark';
import { useDesignable } from '../../components/schema-renderer';
import { useContext, useState } from 'react';
import AddNew from '../add-new';
import cls from 'classnames';
import { DraggableBlockContext } from '../../components/drag-and-drop';
import { uid } from '@formily/shared';
import { removeSchema, updateSchema } from '..';
import { isGridRowOrCol } from '../grid';
export const Markdown = connect(
export const Markdown: any = connect(
(props) => {
const schema = useFieldSchema();
return <AntdInput.TextArea defaultValue={schema.name} {...props}/>
const { schema } = useDesignable();
const field = useField<any>();
const { savedInSchema, ...others } = props;
console.log('Markdown', props);
return savedInSchema ? (
<div className={'mb-markdown'} style={{ position: 'relative' }}>
<AntdInput.TextArea autoSize={{ minRows: 3 }} {...others} />
<Button
style={{ position: 'absolute', bottom: 5, right: 5 }}
type={'primary'}
onClick={async () => {
field.readPretty = true;
schema['default'] = field.value;
await updateSchema({
key: schema['key'],
default: field.value,
});
}}
>
</Button>
</div>
) : (
<AntdInput.TextArea autoSize={{ minRows: 1 }} {...others} />
);
},
mapProps((props: any, field) => {
return {
@ -26,6 +64,7 @@ export const Markdown = connect(
};
}),
mapReadPretty((props) => {
console.log('Markdown', props);
let text = props.value;
let value = (
<div dangerouslySetInnerHTML={{ __html: micromark(text || '') }} />
@ -34,4 +73,61 @@ export const Markdown = connect(
}),
);
Markdown.DesignableBar = observer((props) => {
const field = useField();
const { schema, refresh, deepRemove } = useDesignable();
const [visible, setVisible] = useState(false);
const { dragRef } = useContext(DraggableBlockContext);
return (
<div className={cls('designable-bar', { active: visible })}>
<span
onClick={(e) => {
e.stopPropagation();
}}
className={cls('designable-bar-actions', { active: visible })}
>
<Space size={'small'}>
<AddNew.CardItem defaultAction={'insertAfter'} ghost />
{dragRef && <DragOutlined ref={dragRef} />}
<Dropdown
trigger={['click']}
visible={visible}
onVisibleChange={(visible) => {
setVisible(visible);
}}
overlay={
<Menu>
<Menu.Item
key={'update'}
onClick={() => {
field.readPretty = false;
}}
>
Markdown
</Menu.Item>
<Menu.Divider />
<Menu.Item
key={'delete'}
onClick={async () => {
const removed = deepRemove();
// console.log({ removed })
const last = removed.pop();
if (isGridRowOrCol(last)) {
await removeSchema(last);
}
}}
>
</Menu.Item>
</Menu>
}
>
<MenuOutlined />
</Dropdown>
</Space>
</span>
</div>
);
});
export default Markdown;

View File

@ -1,71 +1,59 @@
import { ISchema } from '@formily/react';
import { uid } from '@formily/shared';
export const defaultSchemas = {
MixMenu: {
type: 'void',
'x-component': 'Menu',
'x-designable-bar': 'Menu.DesignableBar',
'x-component-props': {
sideMenuRef: '{{ sideMenuRef }}',
mode: 'mix',
theme: 'dark',
// onSelect: '{{ onSelect }}',
export function generateDefaultSchema(component) {
const defaultSchemas: { [key: string]: ISchema } = {
MixMenu: {
type: 'void',
'x-component': 'Menu',
'x-designable-bar': 'Menu.DesignableBar',
'x-component-props': {
sideMenuRef: '{{ sideMenuRef }}',
mode: 'mix',
theme: 'dark',
// onSelect: '{{ onSelect }}',
},
},
},
'Menu.Item': {
type: 'void',
'x-designable-bar': 'Menu.DesignableBar',
'x-component': 'Menu.Item',
},
'Menu.Link': {
type: 'void',
'x-designable-bar': 'Menu.DesignableBar',
'x-component': 'Menu.Link',
properties: {
[uid()]: {
type: 'void',
name: uid(),
'x-component': 'Grid',
properties: {
[`row_${uid()}`]: {
type: 'void',
'x-component': 'Grid.Row',
'x-component-props': {
locked: true,
},
properties: {
[`col_${uid()}`]: {
type: 'void',
'x-component': 'Grid.Col',
properties: {
[uid()]: {
type: 'void',
'x-component': 'AddNew.BlockItem',
},
},
'Menu.Item': {
type: 'void',
'x-designable-bar': 'Menu.DesignableBar',
'x-component': 'Menu.Item',
},
'Menu.Link': {
type: 'void',
'x-designable-bar': 'Menu.DesignableBar',
'x-component': 'Menu.Link',
properties: {
[uid()]: {
type: 'void',
'x-component': 'Page',
properties: {
[uid()]: {
type: 'void',
'x-component': 'Grid',
'x-component-props': {
addNewComponent: 'AddNew.CardItem',
},
},
},
},
}
}
},
'Menu.URL': {
type: 'void',
'x-designable-bar': 'Menu.DesignableBar',
'x-component': 'Menu.URL',
},
'Menu.SubMenu': {
type: 'void',
'x-designable-bar': 'Menu.DesignableBar',
'x-component': 'Menu.SubMenu',
},
'Menu.Action': {
type: 'void',
'x-designable-bar': 'Menu.DesignableBar',
'x-component': 'Menu.Action',
},
};
export default defaultSchemas;
},
},
'Menu.URL': {
type: 'void',
'x-designable-bar': 'Menu.DesignableBar',
'x-component': 'Menu.URL',
},
'Menu.SubMenu': {
type: 'void',
'x-designable-bar': 'Menu.DesignableBar',
'x-component': 'Menu.SubMenu',
},
'Menu.Action': {
type: 'void',
'x-designable-bar': 'Menu.DesignableBar',
'x-component': 'Menu.Action',
},
};
return defaultSchemas[component];
}

View File

@ -62,7 +62,7 @@ import './style.less';
import { Link } from 'react-router-dom';
import { findPropertyByPath, getSchemaPath, useSchemaPath } from '../../';
import { request } from '../';
import defaultSchemas from './defaultSchemas';
import { generateDefaultSchema } from './defaultSchemas';
import _, { cloneDeep, get, isNull } from 'lodash';
import { FormDialog, FormItem, FormLayout, Input } from '@formily/antd';
import deepmerge from 'deepmerge';
@ -369,7 +369,7 @@ Menu.AddNew = observer((props: any) => {
</FormLayout>
);
}).open();
const defaults = cloneDeep(defaultSchemas[info.key]);
const defaults = generateDefaultSchema(info.key);
const data = appendChild(deepmerge(defaults, values));
await createSchema(data);
}}
@ -568,7 +568,7 @@ Menu.DesignableBar = (props) => {
insertBefore,
appendChild,
};
const defaults = cloneDeep(defaultSchemas[type]);
const defaults = generateDefaultSchema(type);
const data = methods[method](deepmerge(defaults, values));
await createSchema(data);
}}
@ -711,11 +711,12 @@ Menu.DesignableBar = (props) => {
insertBefore,
appendChild,
};
const data = schema.toJSON();
remove();
const source = methods[values.method](
schema.toJSON(),
data,
values.path,
);
remove();
await updateSchema(source);
}}
>

View File

@ -0,0 +1,18 @@
import React from 'react';
import { PageHeader as AntdPageHeader } from 'antd';
import { observer } from '@formily/react';
import { usePageTitleContext } from '..';
export const Page = observer((props) => {
const { children, ...others } = props;
const [pageTitle] = usePageTitleContext();
return (
<>
<AntdPageHeader ghost={false} title={pageTitle} {...others} />
<div style={{margin: 24}}>
{children}
</div>
</>
);
});

View File

@ -25,7 +25,7 @@ import constate from 'constate';
import useRequest from '@ahooksjs/use-request';
import { BaseResult } from '@ahooksjs/use-request/lib/types';
import { uid, clone } from '@formily/shared';
import { MenuOutlined } from '@ant-design/icons';
import { MenuOutlined, DragOutlined } from '@ant-design/icons';
import {
SortableHandle,
SortableContainer,
@ -34,6 +34,8 @@ import {
import cls from 'classnames';
import { getSchemaPath, useDesignable, useSchemaPath, VisibleContext } from '../';
import './style.less';
import { DraggableBlockContext } from '../../components/drag-and-drop';
import AddNew from '../add-new';
interface TableRowProps {
index: number;
@ -632,4 +634,56 @@ Table.Action.DesignableBar = () => {
</span>
</div>
);
};
};
Table.DesignableBar = observer((props) => {
const field = useField();
const { schema, refresh, deepRemove } = useDesignable();
const [visible, setVisible] = useState(false);
const { dragRef } = useContext(DraggableBlockContext);
return (
<div className={cls('designable-bar', { active: visible })}>
<span
onClick={(e) => {
e.stopPropagation();
}}
className={cls('designable-bar-actions', { active: visible })}
>
<Space size={'small'}>
<AddNew.CardItem defaultAction={'insertAfter'} ghost />
{dragRef && <DragOutlined ref={dragRef} />}
<Dropdown
trigger={['click']}
visible={visible}
onVisibleChange={(visible) => {
setVisible(visible);
}}
overlay={
<Menu>
<Menu.Item
key={'update'}
onClick={() => {
field.readPretty = false;
}}
>
Markdown
</Menu.Item>
<Menu.Divider />
<Menu.Item
key={'delete'}
onClick={() => {
deepRemove();
}}
>
</Menu.Item>
</Menu>
}
>
<MenuOutlined />
</Dropdown>
</Space>
</span>
</div>
);
});

View File

@ -1,5 +1,6 @@
import _ from 'lodash';
import { Model } from '@nocobase/database';
import deepmerge from 'deepmerge';
export class UISchema extends Model {
static async create(value?: any, options?: any): Promise<any> {
@ -33,7 +34,7 @@ export class UISchema extends Model {
];
const attrs = _.pick(data, keys);
const options = _.omit(data, keys);
return { ...attrs, options: {...options, ...opts} };
return { ...attrs, options: deepmerge(opts, options) };
}
static properties2children(properties = []) {