updates...

This commit is contained in:
chenos 2021-07-18 17:16:57 +08:00
parent 057dc8406a
commit 63538cf240
15 changed files with 364 additions and 261 deletions

View File

@ -1,68 +0,0 @@
import { DatabaseOutlined } from '@ant-design/icons';
import { SchemaRenderer } from '../../../';
import Modal from 'antd/lib/modal/Modal';
import React from 'react';
import { useState } from 'react';
import schema from './schema';
import { Button, Dropdown, Menu } from 'antd';
import { useMemo } from 'react';
import { createForm } from '@formily/core';
export default () => {
const form = useMemo(
() =>
createForm({
initialValues: {
title: '数据表名称',
},
}),
[],
);
const [visible, setVisible] = useState(false);
return (
<>
<Button
onClick={() => {
setVisible(true);
}}
type={'primary'}
>
<DatabaseOutlined />
</Button>
<Modal
title={
<>
<Dropdown
overlay={
<Menu
onClick={(info) => {
form.setValues({
title: `数据表${info.key}`,
});
}}
>
<Menu.Item key={1}>1</Menu.Item>
<Menu.Item key={2}>2</Menu.Item>
<Menu.Divider></Menu.Divider>
<Menu.Item key={5}></Menu.Item>
</Menu>
}
>
<span style={{cursor: 'pointer'}}>3</span>
</Dropdown>
</>
}
visible={visible}
onOk={async () => {
await form.submit();
setVisible(false);
}}
onCancel={() => {
setVisible(false);
}}
>
<SchemaRenderer form={form} schema={schema} />
</Modal>
</>
);
};

View File

@ -1,49 +0,0 @@
import { ISchema } from "@formily/react";
export default {
type: 'object',
properties: {
form: {
type: 'void',
"x-component": 'FormLayout',
"x-component-props": {
layout: 'vertical',
},
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: '内容',
},
},
],
}
}
},
},
} as ISchema;

View File

@ -1,56 +0,0 @@
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;

View File

@ -0,0 +1,45 @@
import { SchemaRenderer } from '@nocobase/client/lib';
import React from 'react';
export default () => {
const schema = {
type: 'array',
name: 'collections',
'x-component': 'DatabaseCollection',
'x-component-props': {},
default: [
{
name: 'test1',
title: '数据表 1',
},
{
name: 'test2',
title: '数据表 2',
},
],
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: [],
},
},
};
return <SchemaRenderer schema={schema} />;
};

View File

@ -23,7 +23,7 @@ import './style.less';
import { uid } from '@formily/shared';
import { ISchema } from '@formily/react';
import DataTableConfig from './DataTableConfig';
import Database from './datatable';
function LayoutWithMenu({ schema }) {
const match = useRouteMatch<any>();
@ -53,7 +53,7 @@ function LayoutWithMenu({ schema }) {
selectedKeys: [activeKey].filter(Boolean),
}}
/>
<DataTableConfig />
<Database />
</Layout.Header>
<Layout>
<Layout.Sider

View File

@ -18,20 +18,20 @@
// }
// }
.ant-collapse {
border: 1px solid #d9d9d9 !important;
border-bottom: 0 !important;
> .ant-collapse-item {
border-bottom: 1px solid #d9d9d9 !important;
}
.ant-collapse-content {
border-top: 1px solid #d9d9d9 !important;
}
.ant-collapse-content > .ant-collapse-content-box {
padding: 24px !important;
padding-bottom: 0 !important;
}
}
// .ant-collapse {
// border: 1px solid #d9d9d9 !important;
// border-bottom: 0 !important;
// > .ant-collapse-item {
// border-bottom: 1px solid #d9d9d9 !important;
// }
// .ant-collapse-content {
// border-top: 1px solid #d9d9d9 !important;
// }
// .ant-collapse-content > .ant-collapse-content-box {
// padding: 24px !important;
// padding-bottom: 0 !important;
// }
// }
.database-sider {
background: #fafafa;

View File

@ -27,7 +27,7 @@ import { AddNew } from '../../schemas/add-new';
import { Cascader } from '../../schemas/cascader';
import { Checkbox } from '../../schemas/checkbox';
import { ColorSelect } from '../../schemas/color-select';
import { DatabaseField } from '../../schemas/database-field';
import { DatabaseField, DatabaseCollection } from '../../schemas/database-field';
import { DatePicker } from '../../schemas/date-picker';
import { Filter } from '../../schemas/filter';
import { Form } from '../../schemas/form';
@ -79,6 +79,7 @@ export const SchemaField = createSchemaField({
Checkbox,
ColorSelect,
DatabaseField,
DatabaseCollection,
DatePicker,
Filter,
Form,

View File

@ -1,55 +1,46 @@
import React from 'react';
import { createForm } from '@formily/core';
import { SchemaRenderer } from '../../';
import { observer, connect, useField } from '@formily/react';
import { observer, connect, useField, ISchema } from '@formily/react';
const schema = {
type: 'void',
name: 'action1',
title: 'ModalForm',
'x-component': 'Action',
const schema: ISchema = {
type: 'array',
name: 'collections',
'x-component': 'DatabaseCollection',
'x-component-props': {},
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',
'x-component': 'DatabaseField',
default: [
{
id: 1,
interface: 'string',
dataType: 'string',
name: 'title',
ui: {
title: '标题',
},
},
{
id: 2,
dataType: 'text',
interface: 'textarea',
name: 'content',
ui: {
title: '内容',
},
},
],
},
},
default: [
{
name: 'test1',
title: '数据表 1',
},
},
{
name: 'test2',
title: '数据表 2',
}
],
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: [],
}
}
};
const form = createForm();

View File

@ -7,17 +7,177 @@ import {
ISchema,
Schema,
useFieldSchema,
useForm,
} from '@formily/react';
import { ArrayCollapse } from '@formily/antd';
import { ArrayCollapse, FormLayout } from '@formily/antd';
import { uid } from '@formily/shared';
import '@formily/antd/lib/form-tab/style';
import { Collapse, Button, Dropdown, Menu, Tag } from 'antd';
import {
Collapse,
Button,
Dropdown,
Menu,
Tag,
Select,
Divider,
Input,
} from 'antd';
import { options, interfaces } from './interfaces';
import { DeleteOutlined } from '@ant-design/icons';
import {
DeleteOutlined,
DatabaseOutlined,
PlusOutlined,
CloseOutlined,
} from '@ant-design/icons';
import cls from 'classnames';
import './style.less';
import Modal from 'antd/lib/modal/Modal';
import { get } from 'lodash';
import { useEffect } from 'react';
import { useRequest } from 'ahooks';
import { createOrUpdateCollection, deleteCollection } from '..';
export const DatabaseCollection = observer((props) => {
const field = useField<Formily.Core.Models.ArrayField>();
const [visible, setVisible] = useState(false);
const [open, setOpen] = useState(false);
const schema = useFieldSchema();
const [activeIndex, setActiveIndex] = useState(0);
const form = useForm();
const [newValue, setNewValue] = useState('');
useRequest('collections:list?sort=-created_at', {
formatResult: (result) => result?.data,
onSuccess(data) {
field.setValue(data);
console.log('onSuccess', data);
},
});
return (
<div>
<Button
onClick={() => {
setVisible(true);
}}
type={'primary'}
>
<DatabaseOutlined />
</Button>
<Modal
title={
<div style={{ textAlign: 'center' }}>
<Select
value={activeIndex}
style={{ minWidth: 300, textAlign: 'center' }}
onChange={(value) => {
setActiveIndex(value as any);
}}
open={open}
onDropdownVisibleChange={setOpen}
optionLabelProp={'label'}
dropdownRender={(menu) => {
return (
<div>
{menu}
<Divider style={{ margin: '5px 0 4px' }} />
<div
style={{
cursor: 'pointer',
padding: '5px 12px',
}}
>
<Input.Search
size={'middle'}
placeholder={'新增数据表'}
enterButton={<PlusOutlined />}
value={newValue}
onChange={(e) => {
setNewValue(e.target.value);
}}
onSearch={async (value) => {
const data = {
name: uid(),
title: value,
};
field.push(data);
setActiveIndex(field.value.length - 1);
setOpen(false);
setNewValue('');
await createOrUpdateCollection(data);
}}
/>
</div>
</div>
);
}}
>
{field.value?.map((item, index) => {
return (
<Select.Option value={index} label={item.title || '未命名'}>
<div
style={{
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
}}
>
{item.title || '未命名'}
<DeleteOutlined
onClick={async (e) => {
e.stopPropagation();
field.remove(index);
if (activeIndex === index) {
setActiveIndex(0);
} else if (activeIndex > index) {
setActiveIndex(activeIndex - 1);
}
await deleteCollection(item.name);
}}
/>
</div>
</Select.Option>
);
})}
</Select>
</div>
}
visible={visible}
onCancel={() => {
setVisible(false);
}}
onOk={async () => {
try {
form.clearErrors();
await form.validate(`${field.address.entire}.${activeIndex}.*`);
setVisible(false);
await createOrUpdateCollection(field.value[activeIndex]);
console.log(
`${field.address.entire}.${activeIndex}.*`,
field.value[activeIndex],
);
} catch (error) {}
}}
>
<FormLayout layout={'vertical'}>
<RecursionField
name={activeIndex}
schema={
new Schema({
type: 'object',
properties: schema.properties,
})
}
/>
</FormLayout>
</Modal>
</div>
);
});
export const DatabaseField: any = observer((props) => {
const field = useField<Formily.Core.Models.ArrayField>();
console.log('DatabaseField', field.componentProps);
console.log('DatabaseField', field.value);
const [activeKey, setActiveKey] = useState(null);
return (
<div>
@ -26,6 +186,7 @@ export const DatabaseField: any = observer((props) => {
onChange={(key) => {
setActiveKey(key);
}}
className={cls({ empty: !field.value?.length })}
accordion
>
{field.value?.map((item, index) => {
@ -35,9 +196,13 @@ export const DatabaseField: any = observer((props) => {
<Collapse.Panel
header={
<>
{(item.ui && item.ui.title) || <i style={{color: 'rgba(0, 0, 0, 0.25)'}}></i>}{' '}
{(item.ui && item.ui.title) || (
<i style={{ color: 'rgba(0, 0, 0, 0.25)' }}></i>
)}{' '}
<Tag>{schema.title}</Tag>
<span style={{ color: 'rgba(0, 0, 0, 0.25)', fontSize: 14 }}>{item.name}</span>
<span style={{ color: 'rgba(0, 0, 0, 0.25)', fontSize: 14 }}>
{item.name}
</span>
</>
}
extra={[
@ -97,13 +262,11 @@ export const DatabaseField: any = observer((props) => {
console.log('info.key', info.key, schema);
}}
>
{options.map(option => (
{options.map((option) => (
<Menu.ItemGroup title={option.label}>
{
option.children.map(item => (
<Menu.Item key={item.name}>{item.title}</Menu.Item>
))
}
{option.children.map((item) => (
<Menu.Item key={item.name}>{item.title}</Menu.Item>
))}
</Menu.ItemGroup>
))}
</Menu>

View File

@ -0,0 +1,3 @@
.ant-collapse.empty {
border: 0;
}

View File

@ -21,7 +21,7 @@ import {
import './style.less';
import cls from 'classnames';
import { createSchema, useDesignable, useSchemaPath } from '../';
import { createSchema, removeSchema, useDesignable, useSchemaPath } from '../';
import {
useDrag,
useDrop,
@ -37,6 +37,10 @@ export const GridBlockContext = createContext({
dragRef: null,
});
function isGridRowOrCol(schema: ISchema) {
return ['Grid.Row', 'Grid.Col'].includes(schema['x-component']);
}
const RowDivider = ({ name, onDrop }) => {
const uid = useDragDropUID();
const { isOver, dropRef } = useDrop({
@ -111,7 +115,11 @@ export const Grid: any = observer((props) => {
});
await createSchema(data);
console.log('prepend', data);
deepRemove(path);
const removed = deepRemove(path);
const last = removed.pop();
if (isGridRowOrCol(last)) {
await removeSchema(last);
}
}}
/>
{schema.mapProperties((property, key, index) => {
@ -143,8 +151,12 @@ export const Grid: any = observer((props) => {
[...gridPath, key],
);
await createSchema(data);
console.log('insertAfter', data);
deepRemove(path);
const removed = deepRemove(path);
const last = removed.pop();
if (isGridRowOrCol(last)) {
await removeSchema(last);
}
console.log('insertAfter', data, removed);
}}
/>
</>
@ -185,7 +197,11 @@ Grid.Row = observer((props) => {
});
await createSchema(data);
const path = [...e.dragItem.path];
deepRemove(path);
const removed = deepRemove(path);
const last = removed.pop();
if (isGridRowOrCol(last)) {
await removeSchema(last);
}
}}
/>
{schema.mapProperties((property, key, index) => {
@ -207,10 +223,14 @@ Grid.Row = observer((props) => {
},
[...rowPath, key],
);
console.log('ColDivider', data)
console.log('ColDivider', data);
await createSchema(data);
const path = [...e.dragItem.path];
deepRemove(path);
const removed = deepRemove(path);
const last = removed.pop();
if (isGridRowOrCol(last)) {
await removeSchema(last);
}
}}
onDragEnd={(e) => {
designableSchema.mapProperties((s, key, index) => {

View File

@ -18,6 +18,23 @@ export const request = extend({
timeout: 1000,
});
export async function createOrUpdateCollection(data: any) {
return await request('collections:createOrUpdate', {
method: 'post',
data,
});
}
export async function deleteCollection(name) {
await request('collections:destroy', {
method: 'post',
params: {
filter: {
name,
}
},
});
}
export async function createSchema(schema: ISchema) {
if (!schema['key']) {

View File

@ -0,0 +1,24 @@
import { Model, ModelCtor } from '@nocobase/database';
import { actions, middlewares } from '@nocobase/actions';
import { sort } from '@nocobase/actions/src/actions/common';
import { cloneDeep, omit } from 'lodash';
export const createOrUpdate = async (ctx: actions.Context, next: actions.Next) => {
const { values } = ctx.action.params;
const Collection = ctx.db.getModel('collections');
let collection;
if (values.name) {
collection = await Collection.findByPk(values.name);
}
try {
if (!collection) {
collection = await Collection.create(values);
} else {
await collection.update(values);
}
} catch (error) {
console.log('error.errors', error.errors)
}
ctx.body = collection;
}

View File

@ -2,6 +2,7 @@ import path from 'path';
import { Application } from '@nocobase/server';
import { registerModels, Table } from '@nocobase/database';
import * as models from './models';
import { createOrUpdate } from './actions';
export default async function (this: Application, options = {}) {
const database = this.database;
@ -16,4 +17,6 @@ export default async function (this: Application, options = {}) {
model.set('name', model.get('key'));
}
});
this.resourcer.registerActionHandler('collections:createOrUpdate', createOrUpdate);
}

View File

@ -18,14 +18,19 @@ export const create = async (ctx: actions.Context, next: actions.Next) => {
payload: 'replace',
},
);
await actions.common.create(ctx, async () => {});
await actions.common.create(ctx, async () => { });
const sticky = values['__prepend__'];
const targetKey = values['__insertAfter__'] || values['__insertBefore__'];
if (targetKey) {
if (sticky || targetKey) {
console.log({
associatedKey: values.parentKey,
resourceKey: ctx.body.key,
values: {
values: sticky ? {
field: 'sort',
sticky: true,
} : {
field: 'sort',
insertAfter: !!values['__insertAfter__'],
target: {
key: targetKey,
},
@ -35,7 +40,11 @@ export const create = async (ctx: actions.Context, next: actions.Next) => {
{
associatedKey: values.parentKey,
resourceKey: ctx.body.key,
values: {
values: sticky ? {
field: 'sort',
sticky: true,
target: {},
} : {
field: 'sort',
insertAfter: !!values['__insertAfter__'],
target: {
@ -47,8 +56,8 @@ export const create = async (ctx: actions.Context, next: actions.Next) => {
payload: 'replace',
},
);
await middlewares.associated(ctx, async () => {});
await sort(ctx, async () => {});
await middlewares.associated(ctx, async () => { });
await sort(ctx, async () => { });
}
await next();
};