updates
This commit is contained in:
parent
e21a9ab57d
commit
3223d95d01
@ -1,5 +1,6 @@
|
||||
import { SchemaRenderer } from '../../';
|
||||
import React from 'react';
|
||||
import { FormItem } from '@formily/antd';
|
||||
|
||||
export default () => {
|
||||
const schema = {
|
||||
@ -32,5 +33,5 @@ export default () => {
|
||||
},
|
||||
},
|
||||
};
|
||||
return <SchemaRenderer schema={schema} />;
|
||||
return <SchemaRenderer components={{ FormItem }} schema={schema} />;
|
||||
};
|
||||
|
@ -69,10 +69,11 @@ export function createRouteSwitch(options: RouteSwitchOptions) {
|
||||
value={{ ...options.components, ...props.components }}
|
||||
>
|
||||
<Switch>
|
||||
{routes.map((route) => {
|
||||
{routes.map((route, index) => {
|
||||
if (route.type == 'redirect') {
|
||||
return (
|
||||
<Redirect
|
||||
key={index}
|
||||
to={route.to}
|
||||
push={route.push}
|
||||
from={route.from}
|
||||
@ -87,6 +88,7 @@ export function createRouteSwitch(options: RouteSwitchOptions) {
|
||||
}
|
||||
return (
|
||||
<Route
|
||||
key={index}
|
||||
path={route.path}
|
||||
exact={route.exact}
|
||||
strict={route.strict}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import request from 'umi-request';
|
||||
import { request } from './schemas';
|
||||
|
||||
export interface ResourceOptions {
|
||||
resourceName: string;
|
||||
@ -33,7 +33,7 @@ export class Resource {
|
||||
|
||||
list(options: ListOptions = {}) {
|
||||
const { resourceName } = this.options;
|
||||
return request(`/api/${resourceName}:list`);
|
||||
return request(`${resourceName}:list`);
|
||||
}
|
||||
|
||||
get(options: GetOptions = {}) {
|
||||
@ -42,19 +42,30 @@ export class Resource {
|
||||
if (!resourceKey) {
|
||||
return Promise.resolve({ data: {} });
|
||||
}
|
||||
return request(`/api/${resourceName}:get/${resourceKey}`);
|
||||
return request(`${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'}`;
|
||||
const url = `${resourceName}:${resourceKey ? `update/${resourceKey}` : 'create'}`;
|
||||
return request(url, {
|
||||
method: 'post',
|
||||
data: values,
|
||||
});
|
||||
}
|
||||
|
||||
destroy(filter: any) {
|
||||
const { resourceName } = this.options;
|
||||
const url = `${resourceName}:destroy`;
|
||||
return request(url, {
|
||||
method: 'get',
|
||||
params: {
|
||||
filter
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
static make(options: string | Resource | ResourceOptions) {
|
||||
if (typeof options === 'string') {
|
||||
return new Resource({ resourceName: options });
|
||||
|
@ -13,7 +13,7 @@ import {
|
||||
} from '@formily/react';
|
||||
import { Button, Dropdown, Menu, Popover, Space, Drawer, Modal } from 'antd';
|
||||
import { Link, useHistory, LinkProps } from 'react-router-dom';
|
||||
import { useDesignable, VisibleContext, useDefaultAction } from '..';
|
||||
import { useDesignable, VisibleContext, useDefaultAction, useVisible } from '..';
|
||||
import './style.less';
|
||||
import { uid } from '@formily/shared';
|
||||
import cls from 'classnames';
|
||||
@ -25,8 +25,9 @@ import { useSchemaComponent } from '../../components/schema-renderer';
|
||||
export const Action: any = observer((props: any) => {
|
||||
const { useAction = useDefaultAction, icon, ...others } = props;
|
||||
const { run } = useAction();
|
||||
const field = useField();
|
||||
const { schema, DesignableBar } = useDesignable();
|
||||
const [visible, setVisible] = useState(false);
|
||||
const { visible, setVisible } = useVisible(field.address.entire);
|
||||
const child = Object.values(schema.properties || {}).shift();
|
||||
const isDropdownOrPopover =
|
||||
child &&
|
||||
@ -74,6 +75,7 @@ Action.Modal = observer((props: any) => {
|
||||
} = props;
|
||||
const { schema } = useDesignable();
|
||||
const [visible, setVisible] = useContext(VisibleContext);
|
||||
console.log('VisibleContext', visible);
|
||||
const form = useForm();
|
||||
const { run: runOk } = useOkAction();
|
||||
const { run: runCancel } = useCancelAction();
|
||||
@ -81,7 +83,7 @@ Action.Modal = observer((props: any) => {
|
||||
return (
|
||||
<Modal
|
||||
title={schema.title}
|
||||
destroyOnClose
|
||||
// destroyOnClose
|
||||
maskClosable
|
||||
footer={
|
||||
isFormDecorator
|
||||
|
@ -64,6 +64,8 @@ import { cloneDeep } from 'lodash';
|
||||
import { options } from '../database-field/interfaces';
|
||||
import { useDisplayFieldsContext } from '../form';
|
||||
|
||||
import './style.less';
|
||||
|
||||
const generateGridBlock = (schema: ISchema) => {
|
||||
const name = schema.name || uid();
|
||||
return {
|
||||
@ -117,17 +119,17 @@ function generateCardItemSchema(component) {
|
||||
'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() },
|
||||
// { 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',
|
||||
rowKey: 'id',
|
||||
dragSort: true,
|
||||
showIndex: true,
|
||||
},
|
||||
@ -232,45 +234,34 @@ function generateCardItemSchema(component) {
|
||||
'x-component': 'Menu.Action',
|
||||
'x-designable-bar': 'Table.Action.DesignableBar',
|
||||
properties: {
|
||||
drawer1: {
|
||||
[uid()]: {
|
||||
type: 'void',
|
||||
title: '查看',
|
||||
'x-component': 'Action.Modal',
|
||||
'x-component-props': {},
|
||||
'x-component-props': {
|
||||
bodyStyle: {
|
||||
background: '#f0f2f5',
|
||||
paddingTop: 0,
|
||||
},
|
||||
},
|
||||
properties: {
|
||||
[uid()]: {
|
||||
type: 'void',
|
||||
'x-component': 'Tabs',
|
||||
properties: {
|
||||
tab1: {
|
||||
[uid()]: {
|
||||
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',
|
||||
'x-component': 'Grid',
|
||||
'x-component-props': {
|
||||
addNewComponent: 'AddNew.PaneItem',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -282,14 +273,13 @@ function generateCardItemSchema(component) {
|
||||
},
|
||||
[uid()]: {
|
||||
type: 'void',
|
||||
name: 'action1',
|
||||
title: '修改',
|
||||
title: '编辑',
|
||||
'x-component': 'Menu.Action',
|
||||
'x-designable-bar': 'Table.Action.DesignableBar',
|
||||
properties: {
|
||||
drawer1: {
|
||||
[uid()]: {
|
||||
type: 'void',
|
||||
title: '编辑表单',
|
||||
title: '编辑数据',
|
||||
'x-decorator': 'Form',
|
||||
'x-decorator-props': {
|
||||
useValues: '{{ Table.useTableRow }}',
|
||||
@ -299,17 +289,12 @@ function generateCardItemSchema(component) {
|
||||
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',
|
||||
'x-component': 'Grid',
|
||||
'x-component-props': {
|
||||
addNewComponent: 'AddNew.FormItem',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -327,48 +312,6 @@ function generateCardItemSchema(component) {
|
||||
},
|
||||
},
|
||||
},
|
||||
[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',
|
||||
},
|
||||
},
|
||||
},
|
||||
[uid()]: {
|
||||
type: 'void',
|
||||
title: '字段2',
|
||||
'x-component': 'Table.Column',
|
||||
'x-component-props': {
|
||||
// title: 'z1',
|
||||
},
|
||||
'x-designable-bar': 'Table.Column.DesignableBar',
|
||||
properties: {
|
||||
field2: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
'x-read-pretty': true,
|
||||
'x-decorator-props': {
|
||||
feedbackLayout: 'popover',
|
||||
},
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Form: {
|
||||
@ -679,7 +622,7 @@ AddNew.FormItem = observer((props: any) => {
|
||||
placement={'bottomCenter'}
|
||||
overlay={
|
||||
<Menu>
|
||||
<Menu.ItemGroup title={`要展示的字段`}>
|
||||
<Menu.ItemGroup className={'display-fields'} title={`要展示的字段`}>
|
||||
{resource?.fields?.map((field) => (
|
||||
<Menu.Item key={field.key}>
|
||||
<FieldSwitch
|
||||
@ -723,7 +666,11 @@ AddNew.FormItem = observer((props: any) => {
|
||||
))}
|
||||
</Menu.ItemGroup>
|
||||
<Menu.Divider />
|
||||
<Menu.SubMenu title={'新建字段'}>
|
||||
<Menu.SubMenu
|
||||
popupClassName={'add-new-fields-popup'}
|
||||
className={'sub-menu-add-new-fields'}
|
||||
title={'新建字段'}
|
||||
>
|
||||
{options.map(
|
||||
(option) =>
|
||||
option.children.length > 0 && (
|
||||
@ -827,4 +774,163 @@ AddNew.FormItem = observer((props: any) => {
|
||||
);
|
||||
});
|
||||
|
||||
AddNew.PaneItem = observer((props: any) => {
|
||||
const { ghost, defaultAction } = props;
|
||||
const { schema, insertBefore, insertAfter, appendChild } = useDesignable();
|
||||
const path = useSchemaPath();
|
||||
const { resource = {}, refresh } = useResourceContext();
|
||||
const [visible, setVisible] = useState(false);
|
||||
const { displayFields = [] } = useDisplayFieldsContext();
|
||||
console.log({ displayFields });
|
||||
return (
|
||||
<Dropdown
|
||||
trigger={['click']}
|
||||
visible={visible}
|
||||
onVisibleChange={setVisible}
|
||||
overlayStyle={{
|
||||
minWidth: 200,
|
||||
}}
|
||||
placement={'bottomCenter'}
|
||||
overlay={
|
||||
<Menu>
|
||||
<Menu.SubMenu title={'新建卡片'}>
|
||||
<Menu.Item
|
||||
onClick={async () => {
|
||||
let data: ISchema = {
|
||||
type: 'void',
|
||||
name: uid(),
|
||||
'x-decorator': 'CardItem',
|
||||
'x-component': 'Form',
|
||||
'x-read-pretty': true,
|
||||
'x-component-props': {},
|
||||
'x-designable-bar': 'Form.DesignableBar',
|
||||
properties: {
|
||||
[uid()]: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid',
|
||||
'x-component-props': {
|
||||
addNewComponent: 'AddNew.FormItem',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
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);
|
||||
}
|
||||
setVisible(false);
|
||||
}}
|
||||
style={{ minWidth: 150 }}
|
||||
>
|
||||
阅读模式
|
||||
</Menu.Item>
|
||||
<Menu.Item
|
||||
onClick={async () => {
|
||||
let data: ISchema = {
|
||||
type: 'void',
|
||||
name: uid(),
|
||||
'x-decorator': 'CardItem',
|
||||
'x-component': 'Form',
|
||||
'x-component-props': {
|
||||
showDefaultButtons: true,
|
||||
},
|
||||
'x-designable-bar': 'Form.DesignableBar',
|
||||
properties: {
|
||||
[uid()]: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid',
|
||||
'x-component-props': {
|
||||
addNewComponent: 'AddNew.FormItem',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
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);
|
||||
}
|
||||
setVisible(false);
|
||||
}}
|
||||
>
|
||||
编辑模式
|
||||
</Menu.Item>
|
||||
</Menu.SubMenu>
|
||||
<Menu.SubMenu title={'要展示的相关数据'}>
|
||||
<Menu.Item style={{ minWidth: 150 }}>日志</Menu.Item>
|
||||
<Menu.Item>评论</Menu.Item>
|
||||
</Menu.SubMenu>
|
||||
<Menu.Item
|
||||
onClick={async () => {
|
||||
let data: ISchema = {
|
||||
key: uid(),
|
||||
type: 'void',
|
||||
default: '这是一段演示文字,**支持使用 Markdown 语法**',
|
||||
'x-designable-bar': 'Markdown.Void.DesignableBar',
|
||||
'x-decorator': 'CardItem',
|
||||
'x-read-pretty': true,
|
||||
'x-component': 'Markdown.Void',
|
||||
};
|
||||
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);
|
||||
}
|
||||
setVisible(false);
|
||||
}}
|
||||
>
|
||||
添加说明文字
|
||||
</Menu.Item>
|
||||
</Menu>
|
||||
}
|
||||
>
|
||||
{ghost ? (
|
||||
<PlusOutlined />
|
||||
) : (
|
||||
<Button block type={'dashed'} icon={<PlusOutlined />}></Button>
|
||||
)}
|
||||
</Dropdown>
|
||||
);
|
||||
});
|
||||
|
||||
export default AddNew;
|
||||
|
10
packages/client/src/schemas/add-new/style.less
Normal file
10
packages/client/src/schemas/add-new/style.less
Normal file
@ -0,0 +1,10 @@
|
||||
.ant-dropdown-menu-item-group.display-fields {
|
||||
.ant-dropdown-menu-item-group-list {
|
||||
overflow: auto;
|
||||
max-height: 300px;
|
||||
}
|
||||
}
|
||||
.add-new-fields-popup .ant-dropdown-menu {
|
||||
overflow: auto;
|
||||
max-height: 300px;
|
||||
}
|
@ -145,6 +145,7 @@ export const DatabaseCollection = observer((props) => {
|
||||
{field.value?.map((item, index) => {
|
||||
return (
|
||||
<Select.Option
|
||||
key={index}
|
||||
value={index}
|
||||
label={`${item.title || '未命名'}${
|
||||
item.unsaved ? ' (未保存)' : ''
|
||||
@ -254,6 +255,7 @@ export const DatabaseField: any = observer((props) => {
|
||||
type: 'error',
|
||||
address: `*(${path},${path}.*)`,
|
||||
});
|
||||
console.log('item.key', item.key);
|
||||
return (
|
||||
<Collapse.Panel
|
||||
header={
|
||||
@ -268,8 +270,9 @@ export const DatabaseField: any = observer((props) => {
|
||||
</>
|
||||
}
|
||||
extra={[
|
||||
<Badge count={errors.length} />,
|
||||
<Badge key={'1'} count={errors.length} />,
|
||||
<DeleteOutlined
|
||||
key={'2'}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
field.remove(index);
|
||||
@ -305,6 +308,7 @@ export const DatabaseField: any = observer((props) => {
|
||||
</Collapse>
|
||||
<Dropdown
|
||||
placement={'bottomCenter'}
|
||||
overlayClassName={'all-fields'}
|
||||
overlayStyle={{
|
||||
minWidth: 200,
|
||||
}}
|
||||
@ -327,9 +331,9 @@ export const DatabaseField: any = observer((props) => {
|
||||
}}
|
||||
>
|
||||
{options.map(
|
||||
(option) =>
|
||||
(option, groupIndex) =>
|
||||
option.children.length > 0 && (
|
||||
<Menu.ItemGroup title={option.label}>
|
||||
<Menu.ItemGroup key={groupIndex} title={option.label}>
|
||||
{option.children.map((item) => (
|
||||
<Menu.Item key={item.name}>{item.title}</Menu.Item>
|
||||
))}
|
||||
|
@ -14,3 +14,10 @@
|
||||
background: #1890ff;
|
||||
}
|
||||
}
|
||||
|
||||
.ant-dropdown.all-fields {
|
||||
.ant-dropdown-menu {
|
||||
max-height: 300px;
|
||||
overflow: auto;
|
||||
}
|
||||
}
|
||||
|
@ -22,7 +22,7 @@ import {
|
||||
ResourceContextProvider,
|
||||
} from '../';
|
||||
import get from 'lodash/get';
|
||||
import { Button, Dropdown, Menu, Space } from 'antd';
|
||||
import { Button, Dropdown, Menu, message, Space } from 'antd';
|
||||
import { MenuOutlined, DragOutlined } from '@ant-design/icons';
|
||||
import cls from 'classnames';
|
||||
import { FormLayout } from '@formily/antd';
|
||||
@ -38,6 +38,7 @@ import { DesignableBar } from './DesignableBar';
|
||||
import { FieldDesignableBar } from './Field.DesignableBar';
|
||||
import { createContext } from 'react';
|
||||
import { BlockItem } from '../block-item';
|
||||
import { Resource } from '../../resource';
|
||||
|
||||
const [DisplayFieldsContextProvider, useDisplayFieldsContext] = constate(() => {
|
||||
const [displayFields, setDisplayFields] = useState([]);
|
||||
@ -46,12 +47,12 @@ const [DisplayFieldsContextProvider, useDisplayFieldsContext] = constate(() => {
|
||||
displayFields,
|
||||
setDisplayFields,
|
||||
addDisplayField(fieldName) {
|
||||
setDisplayFields(fields => fields.concat(fieldName));
|
||||
setDisplayFields((fields) => fields.concat(fieldName));
|
||||
},
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
export { DisplayFieldsContextProvider, useDisplayFieldsContext }
|
||||
export { DisplayFieldsContextProvider, useDisplayFieldsContext };
|
||||
|
||||
export const Form: any = observer((props: any) => {
|
||||
const {
|
||||
@ -60,12 +61,12 @@ export const Form: any = observer((props: any) => {
|
||||
resourceName,
|
||||
...others
|
||||
} = props;
|
||||
const { schema } = useDesignable();
|
||||
const initialValues = useValues();
|
||||
const form = useMemo(() => {
|
||||
console.log('Form.useMemo', initialValues);
|
||||
return createForm({ initialValues });
|
||||
return createForm({ initialValues, readPretty: schema['x-read-pretty'] });
|
||||
}, []);
|
||||
const { schema } = useDesignable();
|
||||
const path = useSchemaPath();
|
||||
const scope = useContext(SchemaExpressionScopeContext);
|
||||
const content = (
|
||||
@ -102,6 +103,9 @@ export const Form: any = observer((props: any) => {
|
||||
onClick={async () => {
|
||||
const values = await form.submit();
|
||||
console.log({ values });
|
||||
await Resource.make(resourceName).save(values);
|
||||
message.success('保存成功');
|
||||
await form.reset();
|
||||
}}
|
||||
type={'primary'}
|
||||
>
|
||||
@ -121,9 +125,7 @@ export const Form: any = observer((props: any) => {
|
||||
return resourceName ? (
|
||||
// @ts-ignore
|
||||
<ResourceContextProvider resourceName={resourceName}>
|
||||
<DisplayFieldsContextProvider>
|
||||
{content}
|
||||
</DisplayFieldsContextProvider>
|
||||
<DisplayFieldsContextProvider>{content}</DisplayFieldsContextProvider>
|
||||
</ResourceContextProvider>
|
||||
) : (
|
||||
content
|
||||
@ -141,7 +143,7 @@ Form.Field = observer((props: any) => {
|
||||
const { resource = {} } = useResourceContext();
|
||||
useEffect(() => {
|
||||
if (fieldName) {
|
||||
addDisplayField(fieldName);
|
||||
addDisplayField && addDisplayField(fieldName);
|
||||
}
|
||||
}, [fieldName]);
|
||||
if (!fieldName) {
|
||||
|
@ -32,6 +32,21 @@ export function mapReadPretty(component: any, readPrettyProps?: any) {
|
||||
}
|
||||
|
||||
export const VisibleContext = createContext(null);
|
||||
|
||||
export function useVisible(name, defaultValue = false) {
|
||||
const [visible, setVisible] = useState(defaultValue);
|
||||
return { visible, setVisible };
|
||||
// const [visible, setVisible] = useCookieState(`${name}-visible`, {
|
||||
// defaultValue: defaultValue ? 'true' : null,
|
||||
// });
|
||||
// return {
|
||||
// visible: visible === 'true' ? true : false,
|
||||
// setVisible: (value) => {
|
||||
// setVisible(value ? 'true' : null);
|
||||
// },
|
||||
// }
|
||||
}
|
||||
|
||||
export const DesignableBarContext = createContext(null);
|
||||
|
||||
const [PageTitleContextProvider, usePageTitleContext] = constate(() => {
|
||||
|
@ -24,7 +24,6 @@ type ComposedInput = React.FC<InputProps> & {
|
||||
export const Input: ComposedInput = connect(
|
||||
AntdInput,
|
||||
mapProps((props, field) => {
|
||||
console.log('fieldfieldfieldfieldfield', field.address.entire);
|
||||
return {
|
||||
...props,
|
||||
suffix: (
|
||||
|
@ -55,6 +55,7 @@ import {
|
||||
removeSchema,
|
||||
updateSchema,
|
||||
useDefaultAction,
|
||||
useVisible,
|
||||
VisibleContext,
|
||||
} from '..';
|
||||
import { useMount } from 'ahooks';
|
||||
@ -205,7 +206,8 @@ Menu.Link = observer((props: any) => {
|
||||
eventKey={schema.name}
|
||||
key={schema.name}
|
||||
>
|
||||
<Link to={props.to}>{schema.title}</Link>
|
||||
{schema.title}
|
||||
{/* <Link to={props.to || schema.name}>{schema.title}</Link> */}
|
||||
<DesignableBar />
|
||||
</AntdMenu.Item>
|
||||
);
|
||||
@ -232,7 +234,8 @@ Menu.URL = observer((props: any) => {
|
||||
Menu.Action = observer((props: any) => {
|
||||
const { icon, useAction = useDefaultAction, ...others } = props;
|
||||
const { schema, DesignableBar } = useDesignable();
|
||||
const [visible, setVisible] = useState(false);
|
||||
const field = useField();
|
||||
const { visible, setVisible } = useVisible(field.address.entire);
|
||||
const { run } = useAction();
|
||||
return (
|
||||
<VisibleContext.Provider value={[visible, setVisible]}>
|
||||
|
@ -1,4 +1,10 @@
|
||||
import React, { createContext, useContext, useRef, useState } from 'react';
|
||||
import React, {
|
||||
createContext,
|
||||
useContext,
|
||||
useEffect,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react';
|
||||
import {
|
||||
useFieldSchema,
|
||||
Schema,
|
||||
@ -22,7 +28,7 @@ import {
|
||||
Select,
|
||||
Switch,
|
||||
} from 'antd';
|
||||
import { findIndex, get, set } from 'lodash';
|
||||
import { cloneDeep, findIndex, get, set } from 'lodash';
|
||||
import constate from 'constate';
|
||||
import useRequest from '@ahooksjs/use-request';
|
||||
import { uid, clone } from '@formily/shared';
|
||||
@ -39,9 +45,11 @@ import {
|
||||
} from 'react-sortable-hoc';
|
||||
import cls from 'classnames';
|
||||
import {
|
||||
createSchema,
|
||||
getSchemaPath,
|
||||
removeSchema,
|
||||
ResourceContextProvider,
|
||||
updateSchema,
|
||||
useCollectionContext,
|
||||
useDesignable,
|
||||
useResourceContext,
|
||||
@ -53,6 +61,8 @@ import { DraggableBlockContext } from '../../components/drag-and-drop';
|
||||
import AddNew from '../add-new';
|
||||
import { isGridRowOrCol } from '../grid';
|
||||
import { options } from '../database-field/interfaces';
|
||||
import { Resource } from '../../resource';
|
||||
import { useDisplayFieldsContext } from '../form';
|
||||
|
||||
interface TableRowProps {
|
||||
index: number;
|
||||
@ -131,10 +141,13 @@ function useTableActionBars() {
|
||||
return findActionBars(schema);
|
||||
}
|
||||
|
||||
const ResourceFieldContext = createContext(null);
|
||||
|
||||
function useTableColumns(props?: any) {
|
||||
const { schema, designable } = useDesignable();
|
||||
// const schema = useFieldSchema();
|
||||
const { dataSource } = props || {};
|
||||
const { dataSource, rowKey = 'id' } = props || {};
|
||||
const { resource = {} } = useResourceContext();
|
||||
|
||||
function findColumns(schema: Schema): Schema[] {
|
||||
return schema.reduceProperties((columns, current) => {
|
||||
@ -148,21 +161,35 @@ function useTableColumns(props?: any) {
|
||||
const columns = findColumns(schema).map((item) => {
|
||||
const columnProps = item['x-component-props'] || {};
|
||||
console.log(item);
|
||||
const resourceField = columnProps.fieldName
|
||||
? resource?.fields?.find((item) => item.name === columnProps.fieldName)
|
||||
: null;
|
||||
|
||||
return {
|
||||
title: <RecursionField name={item.name} schema={item} onlyRenderSelf />,
|
||||
title: (
|
||||
<ResourceFieldContext.Provider value={resourceField}>
|
||||
<RecursionField name={item.name} schema={item} onlyRenderSelf />
|
||||
</ResourceFieldContext.Provider>
|
||||
),
|
||||
dataIndex: item.name,
|
||||
...columnProps,
|
||||
render(value, record, recordIndex) {
|
||||
const index = dataSource.indexOf(record);
|
||||
const index = findIndex(
|
||||
dataSource,
|
||||
(item) => item[rowKey] === record[rowKey],
|
||||
);
|
||||
console.log({ dataSource, record, index });
|
||||
return (
|
||||
<TableRowContext.Provider
|
||||
value={{
|
||||
index: index,
|
||||
data: record,
|
||||
}}
|
||||
>
|
||||
<RecursionField schema={item} name={index} onlyRenderProperties />
|
||||
</TableRowContext.Provider>
|
||||
<ResourceFieldContext.Provider value={resourceField}>
|
||||
<TableRowContext.Provider
|
||||
value={{
|
||||
index: index,
|
||||
data: record,
|
||||
}}
|
||||
>
|
||||
<Table.Cell schema={item} />
|
||||
</TableRowContext.Provider>
|
||||
</ResourceFieldContext.Provider>
|
||||
);
|
||||
},
|
||||
};
|
||||
@ -198,6 +225,7 @@ function useTableColumns(props?: any) {
|
||||
|
||||
function AddColumn() {
|
||||
const [visible, setVisible] = useState(false);
|
||||
const { appendChild } = useDesignable();
|
||||
const { resource = {}, refresh } = useResourceContext();
|
||||
return (
|
||||
<Dropdown
|
||||
@ -206,7 +234,7 @@ function AddColumn() {
|
||||
onVisibleChange={setVisible}
|
||||
overlay={
|
||||
<Menu>
|
||||
<Menu.ItemGroup title={'要展示的字段'}>
|
||||
<Menu.ItemGroup className={'display-fields'} title={'要展示的字段'}>
|
||||
{resource?.fields?.map((field) => (
|
||||
<Menu.Item style={{ minWidth: 150 }}>
|
||||
<div
|
||||
@ -215,15 +243,29 @@ function AddColumn() {
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
}}
|
||||
onClick={async () => {
|
||||
const data = appendChild({
|
||||
type: 'void',
|
||||
'x-component': 'Table.Column',
|
||||
'x-component-props': {
|
||||
fieldName: field.name,
|
||||
},
|
||||
'x-designable-bar': 'Table.Column.DesignableBar',
|
||||
});
|
||||
await createSchema(data);
|
||||
}}
|
||||
>
|
||||
<span>{field.uiSchema.title}</span>
|
||||
<Switch size={'small'} defaultChecked />
|
||||
<Switch size={'small'} />
|
||||
</div>
|
||||
</Menu.Item>
|
||||
))}
|
||||
</Menu.ItemGroup>
|
||||
<Menu.Divider />
|
||||
<Menu.SubMenu title={'新增字段'}>
|
||||
<Menu.SubMenu
|
||||
popupClassName={'add-new-fields-popup'}
|
||||
title={'新增字段'}
|
||||
>
|
||||
{options.map((option) => (
|
||||
<Menu.ItemGroup title={option.label}>
|
||||
{option.children.map((item) => (
|
||||
@ -266,26 +308,31 @@ export function useTableIndex() {
|
||||
|
||||
export function useTableDestroyAction() {
|
||||
const ctx = useContext(TableRowContext);
|
||||
const { field, selectedRowKeys, setSelectedRowKeys, refresh } =
|
||||
const { resource, field, selectedRowKeys, setSelectedRowKeys, refresh } =
|
||||
useTableContext();
|
||||
const rowKey = field.componentProps.rowKey || 'id';
|
||||
|
||||
return {
|
||||
async run() {
|
||||
if (ctx && typeof ctx.index !== 'undefined') {
|
||||
field.remove(ctx.index);
|
||||
refresh();
|
||||
// field.remove(ctx.index);
|
||||
await resource.destroy({
|
||||
[rowKey]: ctx.data[rowKey],
|
||||
});
|
||||
await refresh();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!selectedRowKeys.length) {
|
||||
return;
|
||||
}
|
||||
while (selectedRowKeys.length) {
|
||||
const key = selectedRowKeys.shift();
|
||||
const index = findIndex(field.value, (item) => item[rowKey] === key);
|
||||
field.remove(index);
|
||||
}
|
||||
await resource.destroy({
|
||||
[`${rowKey}.in`]: selectedRowKeys,
|
||||
});
|
||||
// while (selectedRowKeys.length) {
|
||||
// const key = selectedRowKeys.shift();
|
||||
// const index = findIndex(field.value, (item) => item[rowKey] === key);
|
||||
// field.remove(index);
|
||||
// }
|
||||
refresh();
|
||||
setSelectedRowKeys([]);
|
||||
},
|
||||
@ -305,16 +352,18 @@ export function useTableFilterAction() {
|
||||
}
|
||||
|
||||
export function useTableCreateAction() {
|
||||
const { field, run: exec, params } = useTableContext();
|
||||
const { resource, field, run: exec, params, refresh } = useTableContext();
|
||||
const [, setVisible] = useContext(VisibleContext);
|
||||
const form = useForm();
|
||||
return {
|
||||
async run() {
|
||||
setVisible && setVisible(false);
|
||||
field.push({
|
||||
key: uid(),
|
||||
...clone(form.values),
|
||||
});
|
||||
await resource.save(form.values);
|
||||
// await refresh();
|
||||
// field.push({
|
||||
// key: uid(),
|
||||
// ...clone(form.values),
|
||||
// });
|
||||
form.reset();
|
||||
exec({ ...params, page: 1 });
|
||||
},
|
||||
@ -322,14 +371,16 @@ export function useTableCreateAction() {
|
||||
}
|
||||
|
||||
export function useTableUpdateAction() {
|
||||
const { field, refresh, params } = useTableContext();
|
||||
const { resource, 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);
|
||||
await resource.save(form.values, {
|
||||
resourceKey: form.values[field.componentProps.rowKey || 'id'],
|
||||
});
|
||||
refresh();
|
||||
},
|
||||
};
|
||||
@ -342,6 +393,7 @@ const [TableContextProvider, useTableContext] = constate(() => {
|
||||
const [selectedRowKeys, setSelectedRowKeys] = useState(
|
||||
defaultSelectedRowKeys || [],
|
||||
);
|
||||
const resource = Resource.make(field.componentProps.resourceName);
|
||||
console.log({ defaultSelectedRowKeys });
|
||||
const pagination = usePaginationProps();
|
||||
const response = useRequest<{
|
||||
@ -349,6 +401,21 @@ const [TableContextProvider, useTableContext] = constate(() => {
|
||||
total: number;
|
||||
}>((params = {}) => {
|
||||
return new Promise((resolve) => {
|
||||
resource.list({}).then((res) => {
|
||||
field.setValue(res?.data || []);
|
||||
resolve({
|
||||
list: res?.data || [],
|
||||
total: res?.meta?.count || res?.data?.length,
|
||||
});
|
||||
console.log(
|
||||
'field.componentProps.resourceName',
|
||||
field.componentProps.resourceName,
|
||||
res,
|
||||
);
|
||||
});
|
||||
|
||||
return;
|
||||
|
||||
const dataSource = Array.isArray(field.value) ? field.value.slice() : [];
|
||||
if (pagination) {
|
||||
const {
|
||||
@ -375,6 +442,7 @@ const [TableContextProvider, useTableContext] = constate(() => {
|
||||
...(response.params[0] || {}),
|
||||
};
|
||||
return {
|
||||
resource,
|
||||
...response,
|
||||
params,
|
||||
field,
|
||||
@ -440,7 +508,10 @@ const TableContainer = observer((props) => {
|
||||
type: 'checkbox',
|
||||
selectedRowKeys,
|
||||
renderCell: (checked, record, _, originNode) => {
|
||||
const index = dataSource.indexOf(record);
|
||||
const index = findIndex(
|
||||
dataSource,
|
||||
(item) => item[rowKey] === record[rowKey],
|
||||
);
|
||||
return (
|
||||
<TableRowContext.Provider
|
||||
value={{
|
||||
@ -571,13 +642,79 @@ function useDesignableBar() {
|
||||
};
|
||||
}
|
||||
|
||||
Table.Column = observer((props) => {
|
||||
const schema = useFieldSchema();
|
||||
const field = useField();
|
||||
const { DesignableBar } = useDesignableBar();
|
||||
Table.Cell = observer((props: any) => {
|
||||
const ctx = useContext(TableRowContext);
|
||||
const schema = props.schema;
|
||||
const resourceField = useContext(ResourceFieldContext);
|
||||
return (
|
||||
<RecursionField
|
||||
schema={
|
||||
!resourceField
|
||||
? schema
|
||||
: new Schema({
|
||||
type: 'void',
|
||||
properties: {
|
||||
[resourceField.name]: {
|
||||
...resourceField.uiSchema,
|
||||
title: undefined,
|
||||
'x-read-pretty': true,
|
||||
'x-decorator-props': {
|
||||
feedbackLayout: 'popover',
|
||||
},
|
||||
'x-decorator': 'FormilyFormItem',
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
name={ctx.index}
|
||||
onlyRenderProperties
|
||||
/>
|
||||
);
|
||||
// const { fieldName } = props;
|
||||
// const { schema } = useDesignable();
|
||||
// const path = getSchemaPath(schema);
|
||||
// const { addDisplayField } = useDisplayFieldsContext();
|
||||
// const { resource = {} } = useResourceContext();
|
||||
// useEffect(() => {
|
||||
// if (fieldName) {
|
||||
// addDisplayField && addDisplayField(fieldName);
|
||||
// }
|
||||
// }, [fieldName]);
|
||||
// if (!fieldName) {
|
||||
// return null;
|
||||
// }
|
||||
// const field = resource?.fields?.find((item) => item.name === fieldName);
|
||||
// if (!field) {
|
||||
// return null;
|
||||
// }
|
||||
// const name = useContext(FormFieldUIDContext);
|
||||
// const title = schema['title'] || field.uiSchema['title'];
|
||||
// return (
|
||||
// <RecursionField
|
||||
// name={name}
|
||||
// schema={
|
||||
// new Schema({
|
||||
// 'x-path': path,
|
||||
// type: 'void',
|
||||
// properties: {
|
||||
// [field.name]: {
|
||||
// ...field.uiSchema,
|
||||
// title,
|
||||
// 'x-decorator': 'FormilyFormItem',
|
||||
// },
|
||||
// },
|
||||
// })
|
||||
// }
|
||||
// />
|
||||
// );
|
||||
});
|
||||
|
||||
Table.Column = observer((props: any) => {
|
||||
const resourceField = useContext(ResourceFieldContext) || {};
|
||||
const { schema, DesignableBar } = useDesignable();
|
||||
return (
|
||||
<div className={'nb-table-column'}>
|
||||
{field.title}
|
||||
{schema.title || resourceField?.uiSchema?.title}
|
||||
<DesignableBar />
|
||||
</div>
|
||||
);
|
||||
@ -605,52 +742,28 @@ Table.Column.DesignableBar = () => {
|
||||
overlay={
|
||||
<Menu>
|
||||
<Menu.Item
|
||||
onClick={(e) => {
|
||||
onClick={async (e) => {
|
||||
const title = uid();
|
||||
field.title = title;
|
||||
schema.title = title;
|
||||
refresh();
|
||||
await updateSchema({
|
||||
key: schema['key'],
|
||||
title: title,
|
||||
});
|
||||
setVisible(false);
|
||||
}}
|
||||
>
|
||||
点击修改按钮文案
|
||||
编辑列
|
||||
</Menu.Item>
|
||||
<Menu.Item
|
||||
onClick={() => {
|
||||
remove();
|
||||
console.log('Table.Column.DesignableBar', { schema });
|
||||
onClick={async () => {
|
||||
const s = remove();
|
||||
await removeSchema(s);
|
||||
}}
|
||||
>
|
||||
删除列
|
||||
</Menu.Item>
|
||||
<Menu.Item
|
||||
onClick={() => {
|
||||
const name = uid();
|
||||
insertAfter({
|
||||
name: `column_${name}`,
|
||||
type: 'void',
|
||||
title: `字段 ${name}`,
|
||||
'x-component': 'Table.Column',
|
||||
'x-component-props': {
|
||||
// title: 'z1',
|
||||
},
|
||||
'x-designable-bar': 'Table.Column.DesignableBar',
|
||||
properties: {
|
||||
[name]: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
// 'x-read-pretty': true,
|
||||
'x-decorator-props': {
|
||||
feedbackLayout: 'popover',
|
||||
},
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input',
|
||||
},
|
||||
},
|
||||
});
|
||||
}}
|
||||
>
|
||||
插入列
|
||||
</Menu.Item>
|
||||
</Menu>
|
||||
}
|
||||
>
|
||||
@ -927,9 +1040,12 @@ Table.Action.DesignableBar = () => {
|
||||
{inActionBar ? (
|
||||
<Menu.Item>放置在右侧</Menu.Item>
|
||||
) : (
|
||||
<Menu.Item>点击表格行时触发 <Switch size={'small'} defaultChecked/></Menu.Item>
|
||||
<Menu.Item>
|
||||
点击表格行时触发
|
||||
<Switch size={'small'} defaultChecked />
|
||||
</Menu.Item>
|
||||
)}
|
||||
|
||||
|
||||
<Menu.Divider />
|
||||
<Menu.Item>删除</Menu.Item>
|
||||
</Menu>
|
||||
|
@ -30,9 +30,10 @@ export const createOrUpdate = async (ctx: actions.Context, next: actions.Next) =
|
||||
await collection.update(values);
|
||||
}
|
||||
await collection.updateAssociations(values);
|
||||
await collection.migrate();
|
||||
} catch (error) {
|
||||
console.log('error.errors', error.errors)
|
||||
throw error;
|
||||
}
|
||||
|
||||
ctx.body = collection;
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,16 @@
|
||||
import _ from 'lodash';
|
||||
import { Model } from '@nocobase/database';
|
||||
import { Model, TableOptions } from '@nocobase/database';
|
||||
|
||||
export interface LoadOptions {
|
||||
reset?: boolean;
|
||||
where?: any;
|
||||
skipExisting?: boolean;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export interface MigrateOptions {
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export class Collection extends Model {
|
||||
static async create(value?: any, options?: any): Promise<any> {
|
||||
@ -37,4 +48,66 @@ export class Collection extends Model {
|
||||
}
|
||||
return items;
|
||||
}
|
||||
|
||||
/**
|
||||
* DOTO:
|
||||
* - database.table 初始化可能存在一些缺陷
|
||||
* - 是否需要考虑关系数据的重载?
|
||||
*
|
||||
* @param opts
|
||||
*/
|
||||
async loadTableOptions(opts: any = {}) {
|
||||
const options = await this.toProps();
|
||||
console.log(JSON.stringify(options, null, 2));
|
||||
const table = this.database.table(options);
|
||||
return table;
|
||||
}
|
||||
|
||||
/**
|
||||
* 迁移
|
||||
*/
|
||||
async migrate(options: MigrateOptions = {}) {
|
||||
const { isNewRecord } = options;
|
||||
const table = await this.loadTableOptions(options);
|
||||
// 如果不是新增数据,force 必须为 false
|
||||
if (!isNewRecord) {
|
||||
return await table.sync({
|
||||
force: false,
|
||||
alter: {
|
||||
drop: false,
|
||||
}
|
||||
});
|
||||
}
|
||||
// TODO: 暂时加了个 collectionSync 解决 collection.create 的数据不清空问题
|
||||
// @ts-ignore
|
||||
const sync = this.sequelize.options.collectionSync;
|
||||
return await table.sync(sync || {
|
||||
force: false,
|
||||
alter: {
|
||||
drop: false,
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO:需要考虑是初次加载还是重载
|
||||
*
|
||||
* @param options
|
||||
*/
|
||||
static async load(options: LoadOptions = {}) {
|
||||
const { skipExisting = false, reset = false, where = {}, transaction } = options;
|
||||
const collections = await this.findAll({
|
||||
transaction,
|
||||
where,
|
||||
});
|
||||
for (const collection of collections) {
|
||||
if (skipExisting && this.database.isDefined(collection.get('name'))) {
|
||||
continue;
|
||||
}
|
||||
await collection.loadTableOptions({
|
||||
transaction,
|
||||
reset,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -26,7 +26,7 @@ export class Field extends Model {
|
||||
const data: any = _.omit(json, ['options', 'created_at', 'updated_at']);
|
||||
const options = json['options'] || {};
|
||||
const fields = await this.getNestedFields();
|
||||
const props = { ...data, ...options };
|
||||
const props = { type: json['dataType'], ...data, ...options };
|
||||
if (fields.length) {
|
||||
props['children'] = fields;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user