lots of updates

This commit is contained in:
chenos 2021-07-19 18:33:03 +08:00
parent 63538cf240
commit 2606f28059
22 changed files with 570 additions and 105 deletions

View File

@ -7,16 +7,7 @@ export default () => {
name: 'collections',
'x-component': 'DatabaseCollection',
'x-component-props': {},
default: [
{
name: 'test1',
title: '数据表 1',
},
{
name: 'test2',
title: '数据表 2',
},
],
default: [],
properties: {
title: {
type: 'string',

View File

@ -99,7 +99,7 @@ export const SchemaField = createSchemaField({
},
});
export const DesignableContext = createContext<DesignableContextProps>(null);
export const DesignableContext = createContext<DesignableContextProps>({});
export function useSchema(path?: any) {
const { schema, refresh } = useContext(DesignableContext);
@ -124,10 +124,14 @@ export function findPropertyByPath(schema: Schema, path?: any): Schema {
let property = schema;
while (arr.length) {
const name = arr.shift();
if (!property.properties) {
console.warn('property does not exist.');
return null;
}
property = property.properties[name];
if (!property) {
console.error('property does not exist.');
break;
console.warn('property does not exist.');
return null;
}
}
return property;
@ -168,7 +172,7 @@ function setKeys(schema: ISchema, parentKey = null) {
}
export function useDesignable(path?: any) {
const { schema, refresh } = useContext(DesignableContext);
const { schema = new Schema({}), refresh } = useContext(DesignableContext);
const schemaPath = path || useSchemaPath();
const fieldSchema = useFieldSchema();
const currentSchema =

View File

@ -185,18 +185,20 @@ Action.Drawer = observer((props: any) => {
});
Action.Dropdown = observer((props: any) => {
const { buttonProps = {}, ...others } = props;
const { schema } = useDesignable();
const componentProps = schema.parent['x-component-props'] || {};
return (
<Dropdown
trigger={['click']}
{...others}
overlay={
<Menu>
<RecursionField schema={schema} onlyRenderProperties />
</Menu>
}
>
<Button {...componentProps}>{schema.title || schema.parent.title}</Button>
<Button {...buttonProps} {...componentProps}>{schema.title || schema.parent.title}</Button>
</Dropdown>
);
});

View File

@ -94,11 +94,11 @@ const Block = (props) => {
<div
onMouseEnter={(e) => {
setActive(true);
console.log('e.onMouseEnter', new Date().toString());
// console.log('e.onMouseEnter', new Date().toString());
}}
onMouseLeave={(e) => {
setActive(false);
console.log('e.onMouseLeave', new Date().toString());
// console.log('e.onMouseLeave', new Date().toString());
}}
className={cls('nb-grid-block', 'designable-form-item', { active })}
>

View File

@ -8,6 +8,7 @@ import {
Schema,
useFieldSchema,
useForm,
FormConsumer,
} from '@formily/react';
import { ArrayCollapse, FormLayout } from '@formily/antd';
import { uid } from '@formily/shared';
@ -21,6 +22,9 @@ import {
Select,
Divider,
Input,
Badge,
message,
Spin,
} from 'antd';
import { options, interfaces } from './interfaces';
import {
@ -32,7 +36,7 @@ import {
import cls from 'classnames';
import './style.less';
import Modal from 'antd/lib/modal/Modal';
import { get } from 'lodash';
import { clone, cloneDeep, get } from 'lodash';
import { useEffect } from 'react';
import { useRequest } from 'ahooks';
import { createOrUpdateCollection, deleteCollection } from '..';
@ -46,19 +50,33 @@ export const DatabaseCollection = observer((props) => {
const form = useForm();
const [newValue, setNewValue] = useState('');
useRequest('collections:list?sort=-created_at', {
const { run, loading } = useRequest('collections:findAll', {
formatResult: (result) => result?.data,
onSuccess(data) {
field.setValue(data);
console.log('onSuccess', data);
},
manual: true,
});
return (
<div>
<Button
onClick={() => {
className={'nb-database-config'}
style={{
height: 46,
borderRadius: 0,
}}
onClick={async () => {
setVisible(true);
await run();
if (field.value?.length === 0) {
field.push({
name: `t_${uid()}`,
unsaved: true,
fields: [],
});
}
}}
type={'primary'}
>
@ -68,6 +86,7 @@ export const DatabaseCollection = observer((props) => {
title={
<div style={{ textAlign: 'center' }}>
<Select
loading={loading}
value={activeIndex}
style={{ minWidth: 300, textAlign: 'center' }}
onChange={(value) => {
@ -97,8 +116,9 @@ export const DatabaseCollection = observer((props) => {
}}
onSearch={async (value) => {
const data = {
name: uid(),
name: `t_${uid()}`,
title: value,
fields: [],
};
field.push(data);
setActiveIndex(field.value.length - 1);
@ -114,7 +134,12 @@ export const DatabaseCollection = observer((props) => {
>
{field.value?.map((item, index) => {
return (
<Select.Option value={index} label={item.title || '未命名'}>
<Select.Option
value={index}
label={`${item.title || '未命名'}${
item.unsaved ? ' (未保存)' : ''
}`}
>
<div
style={{
display: 'flex',
@ -122,17 +147,26 @@ export const DatabaseCollection = observer((props) => {
alignItems: 'center',
}}
>
{item.title || '未命名'}
{item.title || '未命名'} {item.unsaved ? '(未保存)' : ''}
<DeleteOutlined
onClick={async (e) => {
e.stopPropagation();
field.remove(index);
if (field.value?.length === 0) {
field.push({
name: `t_${uid()}`,
unsaved: true,
fields: [],
});
}
if (activeIndex === index) {
setActiveIndex(0);
} else if (activeIndex > index) {
setActiveIndex(activeIndex - 1);
}
if (item.name) {
await deleteCollection(item.name);
}
}}
/>
</div>
@ -146,19 +180,21 @@ export const DatabaseCollection = observer((props) => {
onCancel={() => {
setVisible(false);
}}
okText={'保存'}
cancelText={'关闭'}
onOk={async () => {
try {
form.clearErrors();
await form.validate(`${field.address.entire}.${activeIndex}.*`);
setVisible(false);
delete field.value[activeIndex]['unsaved'];
await createOrUpdateCollection(field.value[activeIndex]);
console.log(
`${field.address.entire}.${activeIndex}.*`,
field.value[activeIndex],
);
message.success('保存成功');
} catch (error) {}
}}
>
{loading ? (
<Spin />
) : (
<FormLayout layout={'vertical'}>
<RecursionField
name={activeIndex}
@ -169,7 +205,13 @@ export const DatabaseCollection = observer((props) => {
})
}
/>
{/* <FormConsumer>
{form => (
<pre>{JSON.stringify(form.values, null, 2)}</pre>
)}
</FormConsumer> */}
</FormLayout>
)}
</Modal>
</div>
);
@ -177,8 +219,13 @@ export const DatabaseCollection = observer((props) => {
export const DatabaseField: any = observer((props) => {
const field = useField<Formily.Core.Models.ArrayField>();
console.log('DatabaseField', field.value);
useEffect(()=> {
if (!field.value) {
field.setValue([]);
}
}, [])
const [activeKey, setActiveKey] = useState(null);
console.log('DatabaseField', field);
return (
<div>
<Collapse
@ -191,12 +238,16 @@ export const DatabaseField: any = observer((props) => {
>
{field.value?.map((item, index) => {
const schema = interfaces.get(item.interface);
console.log({ schema });
const path = field.address.concat(index);
const errors = field.form.queryFeedbacks({
type: 'error',
address: `*(${path},${path}.*)`,
});
return (
<Collapse.Panel
header={
<>
{(item.ui && item.ui.title) || (
{(item.uiSchema && item.uiSchema.title) || (
<i style={{ color: 'rgba(0, 0, 0, 0.25)' }}></i>
)}{' '}
<Tag>{schema.title}</Tag>
@ -206,16 +257,18 @@ export const DatabaseField: any = observer((props) => {
</>
}
extra={[
<Badge count={errors.length} />,
<DeleteOutlined
onClick={() => {
onClick={(e) => {
e.stopPropagation();
field.remove(index);
}}
/>,
]}
key={item.id}
key={item.key}
>
<RecursionField
key={`${item.id}_${index}`}
key={`${item.key}_${index}`}
name={index}
schema={
new Schema({
@ -252,14 +305,14 @@ export const DatabaseField: any = observer((props) => {
return;
}
const data = {
...schema.default,
id: uid(),
name: uid(),
...cloneDeep(schema.default),
key: uid(),
name: `f_${uid()}`,
interface: info.key,
};
field.push(data);
setActiveKey(data.id);
console.log('info.key', info.key, schema);
setActiveKey(data.key);
console.log('info.key', field.value);
}}
>
{options.map((option) => (

View File

@ -8,7 +8,7 @@ export const select: ISchema = {
default: {
dataType: 'string',
// name,
ui: {
uiSchema: {
type: 'string',
// title,
'x-component': 'Select',
@ -16,7 +16,7 @@ export const select: ISchema = {
} as ISchema,
},
properties: {
'ui.title': {
'uiSchema.title': {
type: 'string',
required: true,
title: '字段名称',
@ -42,7 +42,7 @@ export const select: ISchema = {
{ label: 'Text', value: 'text' },
],
},
'ui.enum': {
'uiSchema.enum': {
type: 'array',
title: '可选项',
'x-decorator': 'FormItem',
@ -132,7 +132,7 @@ export const select: ISchema = {
},
},
},
'ui.required': {
'uiSchema.required': {
type: 'string',
title: '必填',
'x-decorator': 'FormItem',

View File

@ -8,14 +8,14 @@ export const string: ISchema = {
default: {
dataType: 'string',
// name,
ui: {
uiSchema: {
type: 'string',
// title,
'x-component': 'Input',
} as ISchema,
},
properties: {
'ui.title': {
'uiSchema.title': {
type: 'string',
title: '字段名称',
required: true,
@ -41,7 +41,7 @@ export const string: ISchema = {
{ label: 'Text', value: 'text' },
],
},
'ui.required': {
'uiSchema.required': {
type: 'string',
title: '必填',
'x-decorator': 'FormItem',

View File

@ -7,7 +7,7 @@ export const subTable: ISchema = {
group: 'relation',
default: {
// name,
ui: {
uiSchema: {
type: 'string',
// title,
'x-component': 'Select',
@ -15,7 +15,7 @@ export const subTable: ISchema = {
} as ISchema,
},
properties: {
'ui.title': {
'uiSchema.title': {
type: 'string',
required: true,
title: '字段名称',

View File

@ -8,14 +8,14 @@ export const textarea: ISchema = {
default: {
dataType: 'text',
// name,
ui: {
uiSchema: {
type: 'string',
// title,
'x-component': 'Input.TextArea',
} as ISchema,
},
properties: {
'ui.title': {
'uiSchema.title': {
type: 'string',
required: true,
title: '字段名称',
@ -41,7 +41,7 @@ export const textarea: ISchema = {
{ label: 'Text', value: 'text' },
],
},
'ui.required': {
'uiSchema.required': {
type: 'string',
title: '必填',
'x-decorator': 'FormItem',

View File

@ -1,3 +1,16 @@
.ant-collapse.empty {
border: 0;
}
.nb-database-config {
height: 46px;
border-radius: 0px;
background: none;
border: 0;
&:hover {
background: #1890ff;
}
&:active {
background: #1890ff;
}
}

View File

@ -46,6 +46,16 @@ export async function createSchema(schema: ISchema) {
});
};
export async function updateSchema(schema: ISchema) {
if (!schema['key']) {
return;
}
return await request(`ui_schemas:update/${schema.key}`, {
method: 'post',
data: schema.toJSON(),
});
};
export async function removeSchema(schema: ISchema) {
if (!schema['key']) {
return;

View File

@ -32,17 +32,34 @@ import {
} from 'antd';
import { uid } from '@formily/shared';
import cls from 'classnames';
import { useDesignable } from '../../components/schema-renderer';
import { MenuOutlined, PlusOutlined } from '@ant-design/icons';
import {
SchemaField,
SchemaRenderer,
useDesignable,
} from '../../components/schema-renderer';
import {
MenuOutlined,
PlusOutlined,
GroupOutlined,
LinkOutlined,
} from '@ant-design/icons';
import { IconPicker } from '../../components/icon-picker';
import { createSchema, removeSchema, useDefaultAction, VisibleContext } from '..';
import {
createSchema,
removeSchema,
updateSchema,
useDefaultAction,
VisibleContext,
} from '..';
import { useMount } from 'ahooks';
import './style.less';
import { Link } from 'react-router-dom';
import { findPropertyByPath, useSchemaPath } from '@nocobase/client/lib';
import { request } from '../';
import defaultSchemas from './defaultSchemas';
import { get } from 'lodash';
import _, { cloneDeep, get, isNull } from 'lodash';
import { FormDialog, FormItem, FormLayout, Input } from '@formily/antd';
import deepmerge from 'deepmerge';
export const MenuModeContext = createContext(null);
@ -62,8 +79,8 @@ const SideMenu = (props: any) => {
<AntdMenu mode={'inline'} onSelect={onSelect}>
<RecursionField schema={child} onlyRenderProperties />
<Menu.AddNew key={uid()} path={[...path, selectedKey]}>
<Button block type={'dashed'}>
<PlusOutlined className={'nb-add-new-icon'} />
<Button className={'nb-add-new-menu-item'} block type={'dashed'}>
<PlusOutlined />
</Button>
</Menu.AddNew>
</AntdMenu>
@ -109,13 +126,20 @@ export const Menu: any = observer((props: any) => {
setSelectedKey(info.key);
}
const selectedSchema = schema.properties[info.key];
console.log({ selectedSchema })
console.log({ selectedSchema });
onSelect && onSelect({ ...info, schema: selectedSchema });
}}
>
<RecursionField schema={schema} onlyRenderProperties />
<Menu.AddNew key={uid()} path={path}>
<PlusOutlined className={'nb-add-new-icon'} />
{/* <PlusOutlined className={'nb-add-new-icon'} /> */}
<Button
className={`nb-add-new-menu-item menu-mode-${mode === 'mix' ? 'horizontal' : mode}`}
block
type={mode == 'inline' ? 'dashed' : 'primary'}
>
<PlusOutlined />
</Button>
</Menu.AddNew>
</AntdMenu>
{mode === 'mix' && (
@ -126,7 +150,8 @@ export const Menu: any = observer((props: any) => {
const keyPath = [selectedKey, ...info.keyPath];
const selectedSchema = findPropertyByPath(schema, keyPath);
console.log('keyPath', keyPath, selectedSchema);
onSelect && onSelect({ ...info, keyPath, schema: selectedSchema });
onSelect &&
onSelect({ ...info, keyPath, schema: selectedSchema });
}}
selectedKey={selectedKey}
sideMenuRef={sideMenuRef}
@ -240,39 +265,117 @@ Menu.SubMenu = observer((props: any) => {
Menu.AddNew = observer((props: any) => {
const { appendChild } = useDesignable(props.path);
const schemas = {
'Menu.Link': {
icon: <MenuOutlined />,
title: '新建菜单项',
schema: {
type: 'object',
properties: {
title: {
type: 'string',
title: '菜单项名称',
required: true,
'x-decorator': 'FormItem',
'x-component': 'Input',
},
'x-component-props.icon': {
type: 'string',
title: '图标',
'x-decorator': 'FormItem',
'x-component': 'IconPicker',
},
},
},
},
'Menu.SubMenu': {
icon: <GroupOutlined />,
title: '新建菜单分组',
schema: {
type: 'object',
properties: {
title: {
type: 'string',
title: '分组名称',
required: true,
'x-decorator': 'FormItem',
'x-component': 'Input',
},
'x-component-props.icon': {
type: 'string',
title: '图标',
'x-decorator': 'FormItem',
'x-component': 'IconPicker',
},
},
},
},
'Menu.URL': {
icon: <LinkOutlined />,
title: '添加自定义链接',
schema: {
type: 'object',
properties: {
title: {
type: 'string',
title: '链接名称',
required: true,
'x-decorator': 'FormItem',
'x-component': 'Input',
},
'x-component-props.icon': {
type: 'string',
title: '图标',
'x-decorator': 'FormItem',
'x-component': 'IconPicker',
},
'x-component-props.href': {
type: 'string',
title: '自定义链接',
required: true,
'x-decorator': 'FormItem',
'x-component': 'Input',
},
},
},
},
};
return (
<AntdMenu.ItemGroup
className={'nb-menu-add-new'}
title={
<Dropdown
overlay={
<AntdMenu>
<AntdMenu.Item
onClick={async () => {
const data = appendChild({
...defaultSchemas['Menu.Link'],
title: uid(),
});
<AntdMenu
onClick={async (info) => {
console.log({ info });
const values = await FormDialog(schemas[info.key].title, () => {
return (
<FormLayout layout={'vertical'}>
<SchemaField schema={schemas[info.key].schema} />
</FormLayout>
);
}).open();
const defaults = cloneDeep(defaultSchemas[info.key]);
const data = appendChild(deepmerge(defaults, values));
await createSchema(data);
}}
>
<AntdMenu.Item key={'Menu.Link'} icon={<MenuOutlined />}>
</AntdMenu.Item>
<AntdMenu.Item
onClick={async () => {
const data = appendChild({
...defaultSchemas['Menu.SubMenu'],
title: uid(),
});
await createSchema(data);
}}
>
<AntdMenu.Item key={'Menu.SubMenu'} icon={<GroupOutlined />}>
</AntdMenu.Item>
<AntdMenu.Item key={'Menu.URL'} icon={<LinkOutlined />}>
</AntdMenu.Item>
</AntdMenu>
}
>
<a>{props.children}</a>
{props.children}
</Dropdown>
}
/>
@ -280,9 +383,131 @@ Menu.AddNew = observer((props: any) => {
});
Menu.DesignableBar = (props) => {
const schemas = {
'Menu.Action': {
icon: <MenuOutlined />,
title: '修改菜单项',
schema: {
type: 'object',
properties: {
title: {
type: 'string',
title: '菜单项名称',
required: true,
'x-decorator': 'FormItem',
'x-component': 'Input',
},
'x-component-props.icon': {
type: 'string',
title: '图标',
'x-decorator': 'FormItem',
'x-component': 'IconPicker',
},
},
},
},
'Menu.Item': {
icon: <MenuOutlined />,
title: '修改菜单项',
schema: {
type: 'object',
properties: {
title: {
type: 'string',
title: '菜单项名称',
required: true,
'x-decorator': 'FormItem',
'x-component': 'Input',
},
'x-component-props.icon': {
type: 'string',
title: '图标',
'x-decorator': 'FormItem',
'x-component': 'IconPicker',
},
},
},
},
'Menu.Link': {
icon: <MenuOutlined />,
title: '修改菜单项',
schema: {
type: 'object',
properties: {
title: {
type: 'string',
title: '菜单项名称',
required: true,
'x-decorator': 'FormItem',
'x-component': 'Input',
},
'x-component-props.icon': {
type: 'string',
title: '图标',
'x-decorator': 'FormItem',
'x-component': 'IconPicker',
},
},
},
},
'Menu.SubMenu': {
icon: <GroupOutlined />,
title: '修改菜单分组',
schema: {
type: 'object',
properties: {
title: {
type: 'string',
title: '分组名称',
required: true,
'x-decorator': 'FormItem',
'x-component': 'Input',
},
'x-component-props.icon': {
type: 'string',
title: '图标',
'x-decorator': 'FormItem',
'x-component': 'IconPicker',
},
},
},
},
'Menu.URL': {
icon: <LinkOutlined />,
title: '修改自定义链接',
schema: {
type: 'object',
properties: {
title: {
type: 'string',
title: '链接名称',
required: true,
'x-decorator': 'FormItem',
'x-component': 'Input',
},
'x-component-props.icon': {
type: 'string',
title: '图标',
'x-decorator': 'FormItem',
'x-component': 'IconPicker',
},
'x-component-props.href': {
type: 'string',
title: '自定义链接',
required: true,
'x-decorator': 'FormItem',
'x-component': 'Input',
},
},
},
},
};
const field = useField();
const [visible, setVisible] = useState(false);
const { schema, remove, refresh, insertAfter, appendChild } = useDesignable();
const formConfig = schemas[schema['x-component']];
console.log({ formConfig, schema })
return (
<div className={cls('designable-bar', { active: visible })}>
<div
@ -304,17 +529,39 @@ Menu.DesignableBar = (props) => {
overlay={
<AntdMenu>
<AntdMenu.Item
onClick={() => {
const title = uid();
field.componentProps['icon'] = 'DeleteOutlined';
onClick={async () => {
const initialValues = {};
Object.keys(formConfig.schema.properties).forEach((name) => {
_.set(initialValues, name, get(schema, name));
});
const values = await FormDialog(formConfig.title, () => {
return (
<FormLayout layout={'vertical'}>
<SchemaField schema={formConfig.schema} />
</FormLayout>
);
}).open({
initialValues,
});
if (values.title) {
schema.title = values.title;
}
const icon = _.get(values, 'x-component-props.icon') || null;
schema['x-component-props'] =
schema['x-component-props'] || {};
schema['x-component-props']['icon'] = 'DeleteOutlined';
schema.title = title;
schema['x-component-props']['icon'] = icon;
field.componentProps['icon'] = icon;
refresh();
await updateSchema(schema);
// const title = uid();
// field.componentProps['icon'] = 'DeleteOutlined';
// schema['x-component-props']['icon'] = 'DeleteOutlined';
// schema.title = title;
// refresh();
}}
>
{formConfig.title}
</AntdMenu.Item>
<AntdMenu.Item
onClick={async () => {

View File

@ -1,3 +1,8 @@
.ant-layout-header {
height: 46px;
line-height: 46px;
padding: 0;
}
.ant-menu-horizontal {
width: 100%;
@ -88,3 +93,21 @@
}
}
}
.nb-add-new-menu-item.menu-mode-horizontal {
height: 46px;
border-radius: 0px;
background: none;
border: 0;
&:hover {
background: #1890ff;
}
&:active {
background: #1890ff;
}
}
.nb-add-new-menu-item.menu-mode-inline {
margin: 0 14px;
width: calc(100% - 28px);
}

View File

@ -3,6 +3,19 @@ import { actions, middlewares } from '@nocobase/actions';
import { sort } from '@nocobase/actions/src/actions/common';
import { cloneDeep, omit } from 'lodash';
export const findAll = async (ctx: actions.Context, next: actions.Next) => {
const Collection = ctx.db.getModel('collections');
const collections = await Collection.findAll(Collection.parseApiJson({
sort: '-created_at',
}));
const data = [];
for (const collection of collections) {
data.push(await collection.toProps());
}
ctx.body = data;
await next();
}
export const createOrUpdate = async (ctx: actions.Context, next: actions.Next) => {
const { values } = ctx.action.params;
const Collection = ctx.db.getModel('collections');
@ -16,6 +29,7 @@ export const createOrUpdate = async (ctx: actions.Context, next: actions.Next) =
} else {
await collection.update(values);
}
await collection.updateAssociations(values);
} catch (error) {
console.log('error.errors', error.errors)
}

View File

@ -5,6 +5,10 @@ export default {
title: '数据表配置',
model: 'Collection',
fields: [
{
type: 'sort',
name: 'sort',
},
{
type: 'uid',
name: 'name',

View File

@ -6,6 +6,11 @@ export default {
title: '字段配置',
model: 'Field',
fields: [
{
type: 'sort',
name: 'sort',
scope: ['parentKey'],
},
{
type: 'uid',
name: 'key',

View File

@ -2,5 +2,39 @@ import _ from 'lodash';
import { Model } from '@nocobase/database';
export class Collection extends Model {
static async create(value?: any, options?: any): Promise<any> {
// console.log({ value });
const attributes = this.toAttributes(value);
// @ts-ignore
const model: Model = await super.create(attributes, options);
return model;
}
static toAttributes(value = {}): any {
const data = _.cloneDeep(value);
const keys = [
...Object.keys(this.rawAttributes),
...Object.keys(this.associations),
];
const attrs = _.pick(data, keys);
const options = _.omit(data, keys);
return { ...attrs, options };
}
async toProps() {
const json = this.toJSON();
const data: any = _.omit(json, ['options', 'created_at', 'updated_at']);
const options = json['options'] || {};
const fields = await this.getNestedFields();
return { ...data, ...options, fields }
}
async getNestedFields() {
const fields = await this.getFields();
const items = [];
for (const field of fields) {
items.push(await field.toProps());
}
return items;
}
}

View File

@ -2,5 +2,48 @@ import _ from 'lodash';
import { Model } from '@nocobase/database';
export class Field extends Model {
static async create(value?: any, options?: any): Promise<any> {
// console.log({ value });
const attributes = this.toAttributes(value);
// @ts-ignore
const model: Model = await super.create(attributes, options);
return model;
}
static toAttributes(value = {}): any {
const data = _.cloneDeep(value);
const keys = [
...Object.keys(this.rawAttributes),
...Object.keys(this.associations),
];
const attrs = _.pick(data, keys);
const options = _.omit(data, keys);
return { ...attrs, options };
}
async toProps() {
const json = this.toJSON();
const data: any = _.omit(json, ['options', 'created_at', 'updated_at']);
const options = json['options'] || {};
const fields = await this.getNestedFields();
const props = { ...data, ...options };
if (fields.length) {
props['children'] = fields;
}
const uiSchema = await this.getUiSchema();
if (uiSchema) {
// props['uiSchema1'] = uiSchema;
props['uiSchema'] = await uiSchema.toJSONSchema();
}
return props;
}
async getNestedFields() {
const fields = await this.getChildren();
const items = [];
for (const field of fields) {
items.push(await field.toProps());
}
return items;
}
}

View File

@ -2,21 +2,19 @@ import path from 'path';
import { Application } from '@nocobase/server';
import { registerModels, Table } from '@nocobase/database';
import * as models from './models';
import { createOrUpdate } from './actions';
import { createOrUpdate, findAll } from './actions';
export default async function (this: Application, options = {}) {
const database = this.database;
registerModels(models);
database.import({
directory: path.resolve(__dirname, 'collections'),
});
database.getModel('fields').beforeCreate((model) => {
if (!model.get('name')) {
model.set('name', model.get('key'));
}
});
this.resourcer.registerActionHandler('collections:findAll', findAll);
this.resourcer.registerActionHandler('collections:createOrUpdate', createOrUpdate);
}

View File

@ -11,7 +11,9 @@ const flatToNested = new FlatToNested({
export default async (ctx: actions.Context, next: actions.Next) => {
const { resourceKey } = ctx.action.params;
const Route = ctx.db.getModel('routes');
const routes = await Route.findAll();
const routes = await Route.findAll(Route.parseApiJson({
sort: 'sort',
}));
const data = flatToNested.convert(routes.map(route => route.toProps()));
ctx.body = data.routes;
await next();

View File

@ -5,6 +5,11 @@ export default {
title: '路由表',
model: 'Route',
fields: [
{
type: 'sort',
name: 'sort',
scope: ['parentKey'],
},
{
type: 'uid',
name: 'key',

View File

@ -21,6 +21,14 @@ export class UISchema extends Model {
return model;
}
async update(key?: any, value?: any, options?: any): Promise<any> {
if (typeof key === 'object') {
const attributes = UISchema.toAttributes(key);
return super.update(attributes, value, options);
}
return super.update(key, value, options);
}
static toAttributes(value = {}): any {
const data = _.cloneDeep(value);
const keys = [
@ -53,6 +61,15 @@ export class UISchema extends Model {
return { ...data, ...options };
}
async toJSONSchema() {
const schema = this.toProperty();
const properties = await this.getProperties();
if (Object.keys(properties).length) {
schema['properties'] = properties;
}
return schema;
}
async getProperties() {
const properties = {};
const children: UISchema[] = await this.getChildren({