This commit is contained in:
chenos 2021-07-23 18:08:44 +08:00
parent e21a9ab57d
commit 3223d95d01
16 changed files with 548 additions and 196 deletions

View File

@ -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} />;
};

View File

@ -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}

View File

@ -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 });

View File

@ -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

View File

@ -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;

View 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;
}

View File

@ -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>
))}

View File

@ -14,3 +14,10 @@
background: #1890ff;
}
}
.ant-dropdown.all-fields {
.ant-dropdown-menu {
max-height: 300px;
overflow: auto;
}
}

View File

@ -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) {

View File

@ -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(() => {

View File

@ -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: (

View File

@ -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]}>

View File

@ -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> &nbsp;&nbsp;<Switch size={'small'} defaultChecked/></Menu.Item>
<Menu.Item>
&nbsp;&nbsp;
<Switch size={'small'} defaultChecked />
</Menu.Item>
)}
<Menu.Divider />
<Menu.Item></Menu.Item>
</Menu>

View File

@ -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;
}
}

View File

@ -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,
});
}
}
}

View File

@ -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;
}