diff --git a/packages/client/src/components/admin-layout/datatable.ts b/packages/client/src/components/admin-layout/datatable.ts new file mode 100644 index 000000000..7b8dde280 --- /dev/null +++ b/packages/client/src/components/admin-layout/datatable.ts @@ -0,0 +1,56 @@ + +export const schema = { + type: 'void', + name: 'action1', + // title: 'ModalForm', + 'x-component': 'Action', + 'x-component-props': { + type: 'primary', + icon: 'DatabaseOutlined', + }, + properties: { + modal1: { + type: 'void', + title: '对话框标题', + 'x-decorator': 'Form', + 'x-component': 'Action.Modal', + properties: { + title: { + type: 'string', + title: '数据表名称', + required: true, + 'x-decorator': 'FormItem', + 'x-component': 'Input', + }, + fields: { + type: 'array', + title: '字段', + 'x-decorator': 'FormItem', + 'x-component': 'DatabaseField', + default: [ + { + id: 1, + interface: 'string', + dataType: 'string', + name: 'title', + ui: { + title: '标题', + }, + }, + { + id: 2, + dataType: 'text', + interface: 'textarea', + name: 'content', + ui: { + title: '内容', + }, + }, + ], + }, + }, + }, + }, +}; + +export default schema; diff --git a/packages/client/src/components/admin-layout/index.tsx b/packages/client/src/components/admin-layout/index.tsx index 4fe3e2342..79b185b3b 100644 --- a/packages/client/src/components/admin-layout/index.tsx +++ b/packages/client/src/components/admin-layout/index.tsx @@ -21,6 +21,10 @@ import { SchemaRenderer } from '../../schemas'; import { useRequest } from 'ahooks'; import './style.less'; +import { uid } from '@formily/shared'; +import { ISchema } from '@formily/react'; +import datatable from './datatable'; + function LayoutWithMenu({ schema }) { const match = useRouteMatch(); const location = useLocation(); @@ -33,7 +37,7 @@ function LayoutWithMenu({ schema }) { console.log({ match }); return ( - + + result?.data, @@ -70,7 +75,31 @@ function Content({ activeKey }) { return ; } - return ; + const schema: ISchema = { + type: 'void', + name: uid(), + 'x-component': 'Grid', + properties: { + [`row_${uid()}`]: { + type: 'void', + 'x-component': 'Grid.Row', + properties: { + [`col_${uid()}`]: { + type: 'void', + 'x-component': 'Grid.Col', + properties: { + [uid()]: { + type: 'void', + 'x-component': 'AddNew.BlockItem', + }, + }, + }, + }, + }, + }, + }; + + return ; } export function AdminLayout({ route }: any) { diff --git a/packages/client/src/components/route-schema-renderer/index.tsx b/packages/client/src/components/route-schema-renderer/index.tsx index d0911b37f..1e6d37e21 100644 --- a/packages/client/src/components/route-schema-renderer/index.tsx +++ b/packages/client/src/components/route-schema-renderer/index.tsx @@ -6,6 +6,10 @@ import { SchemaRenderer } from '../../schemas'; import { useForm } from '@formily/react'; import { useHistory } from 'react-router-dom'; +function Div(props) { + return
+} + export function useLogin() { const form = useForm(); const history = useHistory(); @@ -41,7 +45,7 @@ export function RouteSchemaRenderer({ route }) { } return (
- +
); } diff --git a/packages/client/src/components/schema-renderer/index.tsx b/packages/client/src/components/schema-renderer/index.tsx index 692d884c5..d91b4814d 100644 --- a/packages/client/src/components/schema-renderer/index.tsx +++ b/packages/client/src/components/schema-renderer/index.tsx @@ -161,8 +161,8 @@ export function useDesignable(path?: any) { const fieldSchema = useFieldSchema(); const currentSchema = findPropertyByPath(schema, schemaPath) || ({} as Schema); - console.log({ currentSchema }) - // console.log('useDesignable', { schema, schemaPath, currentSchema }); + console.log({ currentSchema }); + // console.log('useDesignable', { schema, schemaPath, currentSchema }); const options = useContext(SchemaOptionsContext); const DesignableBar = get(options.components, currentSchema['x-designable-bar']) || (() => null); @@ -184,6 +184,9 @@ export function useDesignable(path?: any) { } if (target['key']) { property['parentKey'] = target['key']; + if (!property['key']) { + property['key'] = uid(); + } } const properties = {}; properties[property.name] = property; @@ -211,6 +214,9 @@ export function useDesignable(path?: any) { } if (target['key']) { property['parentKey'] = target['key']; + if (!property['key']) { + property['key'] = uid(); + } } target.addProperty(property.name, property); // BUG: 空 properties 时,addProperty 无反应。 @@ -232,8 +238,12 @@ export function useDesignable(path?: any) { if (!property.name) { property.name = uid(); } + console.log('target.parentKey', target); if (target['parentKey']) { property['parentKey'] = target['parentKey']; + if (!property['key']) { + property['key'] = uid(); + } } addPropertyAfter(target, property); refresh(); @@ -253,6 +263,9 @@ export function useDesignable(path?: any) { } if (target['parentKey']) { property['parentKey'] = target['parentKey']; + if (!property['key']) { + property['key'] = uid(); + } } addPropertyBefore(target, property); refresh(); diff --git a/packages/client/src/schemas/action/index.tsx b/packages/client/src/schemas/action/index.tsx index 2c79f889d..27832cfad 100644 --- a/packages/client/src/schemas/action/index.tsx +++ b/packages/client/src/schemas/action/index.tsx @@ -18,9 +18,11 @@ import './style.less'; import { uid } from '@formily/shared'; import cls from 'classnames'; import { MenuOutlined } from '@ant-design/icons'; +import { FormLayout } from '@formily/antd'; +import IconPicker from '../../components/icon-picker'; export const Action: any = observer((props: any) => { - const { useAction = useDefaultAction, ...others } = props; + const { useAction = useDefaultAction, icon, ...others } = props; const { run } = useAction(); const { schema, DesignableBar } = useDesignable(); const [visible, setVisible] = useState(false); @@ -28,12 +30,13 @@ export const Action: any = observer((props: any) => { const isDropdownOrPopover = child && ['Action.Dropdown', 'Action.Popover'].includes(child['x-component']); - console.log({ DesignableBar, schema }) - return ( + console.log({ DesignableBar, schema }); + return ( {!isDropdownOrPopover && ( + + + ); }; @@ -65,6 +76,9 @@ export const Menu: any = observer((props: any) => { const { schema } = useDesignable(); const [selectedKey, setSelectedKey] = useState(null); const ref = useRef(); + const path = useSchemaPath(); + const child = schema.properties && schema.properties[selectedKey]; + const isSubMenu = child && child['x-component'] === 'Menu.SubMenu'; useMount(() => { if (mode !== 'mix') { @@ -74,8 +88,17 @@ export const Menu: any = observer((props: any) => { if (sideMenuElement && ref.current) { sideMenuElement.querySelector(':scope > div').appendChild(ref.current); } + sideMenuElement.style.display = isSubMenu ? 'block' : 'none'; }); + useEffect(() => { + const sideMenuElement = sideMenuRef && (sideMenuRef.current as HTMLElement); + if (!sideMenuElement) { + return; + } + sideMenuElement.style.display = isSubMenu ? 'block' : 'none'; + }, [selectedKey]); + return ( { }} > - 新增 - }> + + + {mode === 'mix' && (
{ ); }); +Menu.AddNew = observer((props: any) => { + const { appendChild } = useDesignable(props.path); + return ( + + { + const data = appendChild({ + type: 'void', + title: uid(), + 'x-component': 'Menu.Item', + 'x-designable-bar': 'Menu.DesignableBar', + }); + if (data['key']) { + await request('ui_schemas:create', { + method: 'post', + data: data.toJSON(), + }); + } + }} + > + 新建菜单 + + { + const data = appendChild({ + type: 'void', + key: uid(), + title: uid(), + 'x-component': 'Menu.SubMenu', + 'x-designable-bar': 'Menu.DesignableBar', + }); + if (data['key']) { + await request('ui_schemas:create', { + method: 'post', + data: data.toJSON(), + }); + } + }} + > + 新建菜单组 + + + } + > + {props.children} + + } + /> + ); +}); + Menu.Divider = observer(AntdMenu.Divider); Menu.Item = observer((props: any) => { @@ -125,18 +205,54 @@ Menu.Item = observer((props: any) => { ); }); -Menu.Action = observer((props: any) => { +Menu.Link = observer((props: any) => { const { icon } = props; const { schema, DesignableBar } = useDesignable(); + return ( + } + eventKey={schema.name} + key={schema.name} + > + {schema.title} + + + ); +}); + +Menu.URL = observer((props: any) => { + const { icon } = props; + const { schema, DesignableBar } = useDesignable(); + return ( + } + eventKey={schema.name} + key={schema.name} + > + + {schema.title} + + + + ); +}); + +Menu.Action = observer((props: any) => { + const { icon, useAction = useDefaultAction, ...others } = props; + const { schema, DesignableBar } = useDesignable(); const [visible, setVisible] = useState(false); + const { run } = useAction(); return ( } - onClick={() => { + onClick={async () => { + await run(); setVisible(true); }} > @@ -210,37 +326,53 @@ Menu.DesignableBar = (props) => { 修改标题 { + onClick={async () => { const s = insertAfter({ type: 'void', - key: uid(), title: uid(), 'x-component': 'Menu.SubMenu', }); + console.log('s.s.s.s', s); + if (s['key']) { + await request('ui_schemas:create', { + method: 'post', + data: s.toJSON(), + }); + } }} > 新建分组 { + onClick={async () => { const s = insertAfter({ type: 'void', - key: uid(), title: uid(), 'x-component': 'Menu.Item', }); + if (s['key']) { + await request('ui_schemas:create', { + method: 'post', + data: s.toJSON(), + }); + } }} > 新建菜单 { + onClick={async () => { const s = appendChild({ type: 'void', - key: uid(), title: uid(), 'x-component': 'Menu.Item', }); + if (s['key']) { + await request('ui_schemas:create', { + method: 'post', + data: s.toJSON(), + }); + } }} > 在菜单组里新增 diff --git a/packages/client/src/schemas/menu/style.less b/packages/client/src/schemas/menu/style.less index ca356c60a..857560733 100644 --- a/packages/client/src/schemas/menu/style.less +++ b/packages/client/src/schemas/menu/style.less @@ -74,4 +74,17 @@ height: 100%; margin: 0 -20px; } +} + +.nb-menu-add-new { + .ant-menu-item-group-title { + line-height: 46px; + padding: 0; + cursor: pointer; + .nb-add-new-icon { + height: 46px; + line-height: 46px; + width: 46px; + } + } } \ No newline at end of file diff --git a/packages/client/src/schemas/table/demos/demo1.tsx b/packages/client/src/schemas/table/demos/demo1.tsx index 89411b5a6..7245b7b8b 100644 --- a/packages/client/src/schemas/table/demos/demo1.tsx +++ b/packages/client/src/schemas/table/demos/demo1.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { SchemaRenderer } from '../..'; -import { Table } from '../'; +import { Table, useTableCreateAction } from '../'; import { uid } from '@formily/shared'; import { createForm } from '@formily/core'; import { ISchema, observer } from '@formily/react'; @@ -8,9 +8,9 @@ import { ISchema, observer } from '@formily/react'; function useAction() { return { async run() { - alert('自定义') - } - } + alert('自定义'); + }, + }; } const schema: ISchema = { @@ -93,8 +93,8 @@ const schema: ISchema = { }, }, }, - } - } + }, + }, }, }, }, @@ -109,35 +109,23 @@ const schema: ISchema = { drawer1: { type: 'void', title: '抽屉标题', + 'x-decorator': 'Form', 'x-component': 'Action.Modal', - 'x-component-props': {}, + 'x-component-props': { + useOkAction: '{{ Table.useTableCreateAction }}', + }, properties: { - [uid()]: { - name: 'form', - type: 'object', - 'x-component': 'Form', - properties: { - field1: { - type: 'string', - title: '字段1', - 'x-decorator': 'FormItem', - 'x-component': 'Input', - }, - field2: { - type: 'string', - title: '字段2', - 'x-decorator': 'FormItem', - 'x-component': 'Input', - }, - action: { - type: 'void', - title: '提交', - 'x-component': 'Action', - 'x-component-props': { - useAction: '{{ Table.useTableCreateAction }}', - }, - }, - }, + field1: { + type: 'string', + title: '字段1', + 'x-decorator': 'FormItem', + 'x-component': 'Input', + }, + field2: { + type: 'string', + title: '字段2', + 'x-decorator': 'FormItem', + 'x-component': 'Input', }, }, }, @@ -154,22 +142,6 @@ const schema: ISchema = { }, }, }, - // [`a_${uid()}`]: { - // type: 'void', - // 'x-component': 'Table.ActionBar', - // 'x-component-props': { - // align: 'bottom', - // }, - // properties: { - // pagination: { - // type: 'void', - // 'x-component': 'Table.Pagination', - // 'x-component-props': { - // defaultPageSize: 5, - // }, - // }, - // }, - // }, [`a_${uid()}`]: { type: 'void', 'x-component': 'Table.ActionBar', @@ -256,7 +228,6 @@ const schema: ISchema = { type: 'void', name: 'action1', title: '查看', - 'x-component': 'Action', 'x-designable-bar': 'Table.Action.DesignableBar', properties: { @@ -318,38 +289,26 @@ const schema: ISchema = { drawer1: { type: 'void', title: '编辑表单', + "x-decorator": 'Form', + 'x-decorator-props': { + useValues: '{{ Table.useTableRow }}', + }, 'x-component': 'Action.Modal', - 'x-component-props': {}, + 'x-component-props': { + useOkAction: '{{ Table.useTableUpdateAction }}', + }, properties: { - [uid()]: { - name: 'form', - type: 'void', - 'x-component': 'Form', - 'x-component-props': { - useValues: '{{ Table.useTableRow }}', - }, - properties: { - field1: { - type: 'string', - title: '字段1', - 'x-decorator': 'FormItem', - 'x-component': 'Input', - }, - field2: { - type: 'string', - title: '字段2', - 'x-decorator': 'FormItem', - 'x-component': 'Input', - }, - action: { - type: 'void', - title: '提交', - 'x-component': 'Action', - 'x-component-props': { - useAction: '{{ Table.useTableUpdateAction }}', - }, - }, - }, + field1: { + type: 'string', + title: '字段1', + 'x-decorator': 'FormItem', + 'x-component': 'Input', + }, + field2: { + type: 'string', + title: '字段2', + 'x-decorator': 'FormItem', + 'x-component': 'Input', }, }, }, @@ -371,7 +330,7 @@ const schema: ISchema = { [uid()]: { type: 'void', title: '操作 1', - 'x-component': 'Action', + 'x-component': 'Menu.Action', 'x-component-props': { useAction, disabled: true, @@ -380,7 +339,7 @@ const schema: ISchema = { [uid()]: { type: 'void', title: '操作 2', - 'x-component': 'Action', + 'x-component': 'Menu.Action', 'x-default-action': true, 'x-component-props': { useAction, @@ -390,8 +349,7 @@ const schema: ISchema = { type: 'void', name: 'action1', title: '查看', - 'x-component': 'Action', - // 'x-default-action': true, + 'x-component': 'Menu.Action', 'x-designable-bar': 'Table.Action.DesignableBar', properties: { drawer1: { @@ -446,44 +404,32 @@ const schema: ISchema = { type: 'void', name: 'action1', title: '修改', - 'x-component': 'Action', + 'x-component': 'Menu.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': {}, + 'x-component-props': { + useOkAction: '{{ Table.useTableUpdateAction }}', + }, properties: { - [uid()]: { - name: 'form', - type: 'void', - 'x-component': 'Form', - 'x-component-props': { - useValues: '{{ Table.useTableRow }}', - }, - properties: { - field1: { - type: 'string', - title: '字段1', - 'x-decorator': 'FormItem', - 'x-component': 'Input', - }, - field2: { - type: 'string', - title: '字段2', - 'x-decorator': 'FormItem', - 'x-component': 'Input', - }, - action: { - type: 'void', - title: '提交', - 'x-component': 'Action', - 'x-component-props': { - useAction: '{{ Table.useTableUpdateAction }}', - }, - }, - }, + field1: { + type: 'string', + title: '字段1', + 'x-decorator': 'FormItem', + 'x-component': 'Input', + }, + field2: { + type: 'string', + title: '字段2', + 'x-decorator': 'FormItem', + 'x-component': 'Input', }, }, }, @@ -492,7 +438,7 @@ const schema: ISchema = { [uid()]: { type: 'void', title: '删除', - 'x-component': 'Action', + 'x-component': 'Menu.Action', 'x-component-props': { useAction: '{{ Table.useTableDestroyAction }}', }, @@ -511,7 +457,7 @@ const form = createForm({ export default observer(() => { return (
- +
); }); diff --git a/packages/client/src/schemas/table/index.tsx b/packages/client/src/schemas/table/index.tsx index 398018e4a..ec34214e9 100644 --- a/packages/client/src/schemas/table/index.tsx +++ b/packages/client/src/schemas/table/index.tsx @@ -227,9 +227,11 @@ export function useTableCreateAction() { export function useTableUpdateAction() { const { field, refresh, params } = useTableContext(); const [,setVisible] = useContext(VisibleContext); + const ctx = useContext(TableRowContext); const form = useForm(); return { async run() { + field.value[ctx.index] = form.values; setVisible && setVisible(false); refresh(); }, diff --git a/packages/plugin-ui-schema/src/actions/getTree.ts b/packages/plugin-ui-schema/src/actions/getTree.ts index 92683baaf..7c207cd5b 100644 --- a/packages/plugin-ui-schema/src/actions/getTree.ts +++ b/packages/plugin-ui-schema/src/actions/getTree.ts @@ -2,14 +2,34 @@ import { Model, ModelCtor } from '@nocobase/database'; import { actions } from '@nocobase/actions'; export default async (ctx: actions.Context, next: actions.Next) => { - const { resourceKey } = ctx.action.params; + const { resourceKey, filter } = ctx.action.params; const UISchema = ctx.db.getModel('ui_schemas'); - const schema = await UISchema.findByPk(resourceKey); - const property = schema.toProperty(); - const properties = await schema.getProperties(); - if (Object.keys(properties).length) { - property.properties = properties; + if (resourceKey) { + const schema = await UISchema.findByPk(resourceKey); + const property = schema.toProperty(); + const properties = await schema.getProperties(); + if (Object.keys(properties).length) { + property.properties = properties; + } + ctx.body = property; + } else { + const schemas = await UISchema.findAll(UISchema.parseApiJson({ + filter, + })); + let properties = {}; + for (const schema of schemas) { + const property = schema.toProperty(); + const properties = await schema.getProperties(); + if (Object.keys(properties).length) { + property.properties = properties; + } + properties[property.name] = property; + } + ctx.body = { + type: 'object', + properties, + } } - ctx.body = property; + await next(); } diff --git a/packages/plugin-ui-schema/src/models/ui-schema.ts b/packages/plugin-ui-schema/src/models/ui-schema.ts index 8c54fef1d..b1db0f3ca 100644 --- a/packages/plugin-ui-schema/src/models/ui-schema.ts +++ b/packages/plugin-ui-schema/src/models/ui-schema.ts @@ -42,7 +42,7 @@ export class UISchema extends Model { toProperty() { const options = this.get('options') || {}; - const data = _.omit(this.toJSON(), ['created_at', 'updated_at', 'options', 'parentKey']); + const data = _.omit(this.toJSON(), ['created_at', 'updated_at', 'options']); return { ...data, ...options }; }