lots of updates
This commit is contained in:
parent
63538cf240
commit
2606f28059
@ -7,16 +7,7 @@ export default () => {
|
|||||||
name: 'collections',
|
name: 'collections',
|
||||||
'x-component': 'DatabaseCollection',
|
'x-component': 'DatabaseCollection',
|
||||||
'x-component-props': {},
|
'x-component-props': {},
|
||||||
default: [
|
default: [],
|
||||||
{
|
|
||||||
name: 'test1',
|
|
||||||
title: '数据表 1',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'test2',
|
|
||||||
title: '数据表 2',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
properties: {
|
properties: {
|
||||||
title: {
|
title: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
|
@ -99,7 +99,7 @@ export const SchemaField = createSchemaField({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const DesignableContext = createContext<DesignableContextProps>(null);
|
export const DesignableContext = createContext<DesignableContextProps>({});
|
||||||
|
|
||||||
export function useSchema(path?: any) {
|
export function useSchema(path?: any) {
|
||||||
const { schema, refresh } = useContext(DesignableContext);
|
const { schema, refresh } = useContext(DesignableContext);
|
||||||
@ -124,10 +124,14 @@ export function findPropertyByPath(schema: Schema, path?: any): Schema {
|
|||||||
let property = schema;
|
let property = schema;
|
||||||
while (arr.length) {
|
while (arr.length) {
|
||||||
const name = arr.shift();
|
const name = arr.shift();
|
||||||
|
if (!property.properties) {
|
||||||
|
console.warn('property does not exist.');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
property = property.properties[name];
|
property = property.properties[name];
|
||||||
if (!property) {
|
if (!property) {
|
||||||
console.error('property does not exist.');
|
console.warn('property does not exist.');
|
||||||
break;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return property;
|
return property;
|
||||||
@ -168,7 +172,7 @@ function setKeys(schema: ISchema, parentKey = null) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function useDesignable(path?: any) {
|
export function useDesignable(path?: any) {
|
||||||
const { schema, refresh } = useContext(DesignableContext);
|
const { schema = new Schema({}), refresh } = useContext(DesignableContext);
|
||||||
const schemaPath = path || useSchemaPath();
|
const schemaPath = path || useSchemaPath();
|
||||||
const fieldSchema = useFieldSchema();
|
const fieldSchema = useFieldSchema();
|
||||||
const currentSchema =
|
const currentSchema =
|
||||||
|
@ -185,18 +185,20 @@ Action.Drawer = observer((props: any) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
Action.Dropdown = observer((props: any) => {
|
Action.Dropdown = observer((props: any) => {
|
||||||
|
const { buttonProps = {}, ...others } = props;
|
||||||
const { schema } = useDesignable();
|
const { schema } = useDesignable();
|
||||||
const componentProps = schema.parent['x-component-props'] || {};
|
const componentProps = schema.parent['x-component-props'] || {};
|
||||||
return (
|
return (
|
||||||
<Dropdown
|
<Dropdown
|
||||||
trigger={['click']}
|
trigger={['click']}
|
||||||
|
{...others}
|
||||||
overlay={
|
overlay={
|
||||||
<Menu>
|
<Menu>
|
||||||
<RecursionField schema={schema} onlyRenderProperties />
|
<RecursionField schema={schema} onlyRenderProperties />
|
||||||
</Menu>
|
</Menu>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Button {...componentProps}>{schema.title || schema.parent.title}</Button>
|
<Button {...buttonProps} {...componentProps}>{schema.title || schema.parent.title}</Button>
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -94,11 +94,11 @@ const Block = (props) => {
|
|||||||
<div
|
<div
|
||||||
onMouseEnter={(e) => {
|
onMouseEnter={(e) => {
|
||||||
setActive(true);
|
setActive(true);
|
||||||
console.log('e.onMouseEnter', new Date().toString());
|
// console.log('e.onMouseEnter', new Date().toString());
|
||||||
}}
|
}}
|
||||||
onMouseLeave={(e) => {
|
onMouseLeave={(e) => {
|
||||||
setActive(false);
|
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 })}
|
className={cls('nb-grid-block', 'designable-form-item', { active })}
|
||||||
>
|
>
|
||||||
|
@ -8,6 +8,7 @@ import {
|
|||||||
Schema,
|
Schema,
|
||||||
useFieldSchema,
|
useFieldSchema,
|
||||||
useForm,
|
useForm,
|
||||||
|
FormConsumer,
|
||||||
} from '@formily/react';
|
} from '@formily/react';
|
||||||
import { ArrayCollapse, FormLayout } from '@formily/antd';
|
import { ArrayCollapse, FormLayout } from '@formily/antd';
|
||||||
import { uid } from '@formily/shared';
|
import { uid } from '@formily/shared';
|
||||||
@ -21,6 +22,9 @@ import {
|
|||||||
Select,
|
Select,
|
||||||
Divider,
|
Divider,
|
||||||
Input,
|
Input,
|
||||||
|
Badge,
|
||||||
|
message,
|
||||||
|
Spin,
|
||||||
} from 'antd';
|
} from 'antd';
|
||||||
import { options, interfaces } from './interfaces';
|
import { options, interfaces } from './interfaces';
|
||||||
import {
|
import {
|
||||||
@ -32,7 +36,7 @@ import {
|
|||||||
import cls from 'classnames';
|
import cls from 'classnames';
|
||||||
import './style.less';
|
import './style.less';
|
||||||
import Modal from 'antd/lib/modal/Modal';
|
import Modal from 'antd/lib/modal/Modal';
|
||||||
import { get } from 'lodash';
|
import { clone, cloneDeep, get } from 'lodash';
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import { useRequest } from 'ahooks';
|
import { useRequest } from 'ahooks';
|
||||||
import { createOrUpdateCollection, deleteCollection } from '..';
|
import { createOrUpdateCollection, deleteCollection } from '..';
|
||||||
@ -46,19 +50,33 @@ export const DatabaseCollection = observer((props) => {
|
|||||||
const form = useForm();
|
const form = useForm();
|
||||||
const [newValue, setNewValue] = useState('');
|
const [newValue, setNewValue] = useState('');
|
||||||
|
|
||||||
useRequest('collections:list?sort=-created_at', {
|
const { run, loading } = useRequest('collections:findAll', {
|
||||||
formatResult: (result) => result?.data,
|
formatResult: (result) => result?.data,
|
||||||
onSuccess(data) {
|
onSuccess(data) {
|
||||||
field.setValue(data);
|
field.setValue(data);
|
||||||
console.log('onSuccess', data);
|
console.log('onSuccess', data);
|
||||||
},
|
},
|
||||||
|
manual: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Button
|
<Button
|
||||||
onClick={() => {
|
className={'nb-database-config'}
|
||||||
|
style={{
|
||||||
|
height: 46,
|
||||||
|
borderRadius: 0,
|
||||||
|
}}
|
||||||
|
onClick={async () => {
|
||||||
setVisible(true);
|
setVisible(true);
|
||||||
|
await run();
|
||||||
|
if (field.value?.length === 0) {
|
||||||
|
field.push({
|
||||||
|
name: `t_${uid()}`,
|
||||||
|
unsaved: true,
|
||||||
|
fields: [],
|
||||||
|
});
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
type={'primary'}
|
type={'primary'}
|
||||||
>
|
>
|
||||||
@ -68,6 +86,7 @@ export const DatabaseCollection = observer((props) => {
|
|||||||
title={
|
title={
|
||||||
<div style={{ textAlign: 'center' }}>
|
<div style={{ textAlign: 'center' }}>
|
||||||
<Select
|
<Select
|
||||||
|
loading={loading}
|
||||||
value={activeIndex}
|
value={activeIndex}
|
||||||
style={{ minWidth: 300, textAlign: 'center' }}
|
style={{ minWidth: 300, textAlign: 'center' }}
|
||||||
onChange={(value) => {
|
onChange={(value) => {
|
||||||
@ -97,8 +116,9 @@ export const DatabaseCollection = observer((props) => {
|
|||||||
}}
|
}}
|
||||||
onSearch={async (value) => {
|
onSearch={async (value) => {
|
||||||
const data = {
|
const data = {
|
||||||
name: uid(),
|
name: `t_${uid()}`,
|
||||||
title: value,
|
title: value,
|
||||||
|
fields: [],
|
||||||
};
|
};
|
||||||
field.push(data);
|
field.push(data);
|
||||||
setActiveIndex(field.value.length - 1);
|
setActiveIndex(field.value.length - 1);
|
||||||
@ -114,7 +134,12 @@ export const DatabaseCollection = observer((props) => {
|
|||||||
>
|
>
|
||||||
{field.value?.map((item, index) => {
|
{field.value?.map((item, index) => {
|
||||||
return (
|
return (
|
||||||
<Select.Option value={index} label={item.title || '未命名'}>
|
<Select.Option
|
||||||
|
value={index}
|
||||||
|
label={`${item.title || '未命名'}${
|
||||||
|
item.unsaved ? ' (未保存)' : ''
|
||||||
|
}`}
|
||||||
|
>
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
@ -122,17 +147,26 @@ export const DatabaseCollection = observer((props) => {
|
|||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{item.title || '未命名'}
|
{item.title || '未命名'} {item.unsaved ? '(未保存)' : ''}
|
||||||
<DeleteOutlined
|
<DeleteOutlined
|
||||||
onClick={async (e) => {
|
onClick={async (e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
field.remove(index);
|
field.remove(index);
|
||||||
|
if (field.value?.length === 0) {
|
||||||
|
field.push({
|
||||||
|
name: `t_${uid()}`,
|
||||||
|
unsaved: true,
|
||||||
|
fields: [],
|
||||||
|
});
|
||||||
|
}
|
||||||
if (activeIndex === index) {
|
if (activeIndex === index) {
|
||||||
setActiveIndex(0);
|
setActiveIndex(0);
|
||||||
} else if (activeIndex > index) {
|
} else if (activeIndex > index) {
|
||||||
setActiveIndex(activeIndex - 1);
|
setActiveIndex(activeIndex - 1);
|
||||||
}
|
}
|
||||||
await deleteCollection(item.name);
|
if (item.name) {
|
||||||
|
await deleteCollection(item.name);
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -146,30 +180,38 @@ export const DatabaseCollection = observer((props) => {
|
|||||||
onCancel={() => {
|
onCancel={() => {
|
||||||
setVisible(false);
|
setVisible(false);
|
||||||
}}
|
}}
|
||||||
|
okText={'保存'}
|
||||||
|
cancelText={'关闭'}
|
||||||
onOk={async () => {
|
onOk={async () => {
|
||||||
try {
|
try {
|
||||||
form.clearErrors();
|
form.clearErrors();
|
||||||
await form.validate(`${field.address.entire}.${activeIndex}.*`);
|
await form.validate(`${field.address.entire}.${activeIndex}.*`);
|
||||||
setVisible(false);
|
delete field.value[activeIndex]['unsaved'];
|
||||||
await createOrUpdateCollection(field.value[activeIndex]);
|
await createOrUpdateCollection(field.value[activeIndex]);
|
||||||
console.log(
|
message.success('保存成功');
|
||||||
`${field.address.entire}.${activeIndex}.*`,
|
|
||||||
field.value[activeIndex],
|
|
||||||
);
|
|
||||||
} catch (error) {}
|
} catch (error) {}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<FormLayout layout={'vertical'}>
|
{loading ? (
|
||||||
<RecursionField
|
<Spin />
|
||||||
name={activeIndex}
|
) : (
|
||||||
schema={
|
<FormLayout layout={'vertical'}>
|
||||||
new Schema({
|
<RecursionField
|
||||||
type: 'object',
|
name={activeIndex}
|
||||||
properties: schema.properties,
|
schema={
|
||||||
})
|
new Schema({
|
||||||
}
|
type: 'object',
|
||||||
/>
|
properties: schema.properties,
|
||||||
</FormLayout>
|
})
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
{/* <FormConsumer>
|
||||||
|
{form => (
|
||||||
|
<pre>{JSON.stringify(form.values, null, 2)}</pre>
|
||||||
|
)}
|
||||||
|
</FormConsumer> */}
|
||||||
|
</FormLayout>
|
||||||
|
)}
|
||||||
</Modal>
|
</Modal>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@ -177,8 +219,13 @@ export const DatabaseCollection = observer((props) => {
|
|||||||
|
|
||||||
export const DatabaseField: any = observer((props) => {
|
export const DatabaseField: any = observer((props) => {
|
||||||
const field = useField<Formily.Core.Models.ArrayField>();
|
const field = useField<Formily.Core.Models.ArrayField>();
|
||||||
console.log('DatabaseField', field.value);
|
useEffect(()=> {
|
||||||
|
if (!field.value) {
|
||||||
|
field.setValue([]);
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
const [activeKey, setActiveKey] = useState(null);
|
const [activeKey, setActiveKey] = useState(null);
|
||||||
|
console.log('DatabaseField', field);
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Collapse
|
<Collapse
|
||||||
@ -191,12 +238,16 @@ export const DatabaseField: any = observer((props) => {
|
|||||||
>
|
>
|
||||||
{field.value?.map((item, index) => {
|
{field.value?.map((item, index) => {
|
||||||
const schema = interfaces.get(item.interface);
|
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 (
|
return (
|
||||||
<Collapse.Panel
|
<Collapse.Panel
|
||||||
header={
|
header={
|
||||||
<>
|
<>
|
||||||
{(item.ui && item.ui.title) || (
|
{(item.uiSchema && item.uiSchema.title) || (
|
||||||
<i style={{ color: 'rgba(0, 0, 0, 0.25)' }}>未命名</i>
|
<i style={{ color: 'rgba(0, 0, 0, 0.25)' }}>未命名</i>
|
||||||
)}{' '}
|
)}{' '}
|
||||||
<Tag>{schema.title}</Tag>
|
<Tag>{schema.title}</Tag>
|
||||||
@ -206,16 +257,18 @@ export const DatabaseField: any = observer((props) => {
|
|||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
extra={[
|
extra={[
|
||||||
|
<Badge count={errors.length} />,
|
||||||
<DeleteOutlined
|
<DeleteOutlined
|
||||||
onClick={() => {
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
field.remove(index);
|
field.remove(index);
|
||||||
}}
|
}}
|
||||||
/>,
|
/>,
|
||||||
]}
|
]}
|
||||||
key={item.id}
|
key={item.key}
|
||||||
>
|
>
|
||||||
<RecursionField
|
<RecursionField
|
||||||
key={`${item.id}_${index}`}
|
key={`${item.key}_${index}`}
|
||||||
name={index}
|
name={index}
|
||||||
schema={
|
schema={
|
||||||
new Schema({
|
new Schema({
|
||||||
@ -252,14 +305,14 @@ export const DatabaseField: any = observer((props) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const data = {
|
const data = {
|
||||||
...schema.default,
|
...cloneDeep(schema.default),
|
||||||
id: uid(),
|
key: uid(),
|
||||||
name: uid(),
|
name: `f_${uid()}`,
|
||||||
interface: info.key,
|
interface: info.key,
|
||||||
};
|
};
|
||||||
field.push(data);
|
field.push(data);
|
||||||
setActiveKey(data.id);
|
setActiveKey(data.key);
|
||||||
console.log('info.key', info.key, schema);
|
console.log('info.key', field.value);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{options.map((option) => (
|
{options.map((option) => (
|
||||||
|
@ -8,7 +8,7 @@ export const select: ISchema = {
|
|||||||
default: {
|
default: {
|
||||||
dataType: 'string',
|
dataType: 'string',
|
||||||
// name,
|
// name,
|
||||||
ui: {
|
uiSchema: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
// title,
|
// title,
|
||||||
'x-component': 'Select',
|
'x-component': 'Select',
|
||||||
@ -16,7 +16,7 @@ export const select: ISchema = {
|
|||||||
} as ISchema,
|
} as ISchema,
|
||||||
},
|
},
|
||||||
properties: {
|
properties: {
|
||||||
'ui.title': {
|
'uiSchema.title': {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
required: true,
|
required: true,
|
||||||
title: '字段名称',
|
title: '字段名称',
|
||||||
@ -42,7 +42,7 @@ export const select: ISchema = {
|
|||||||
{ label: 'Text', value: 'text' },
|
{ label: 'Text', value: 'text' },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
'ui.enum': {
|
'uiSchema.enum': {
|
||||||
type: 'array',
|
type: 'array',
|
||||||
title: '可选项',
|
title: '可选项',
|
||||||
'x-decorator': 'FormItem',
|
'x-decorator': 'FormItem',
|
||||||
@ -132,7 +132,7 @@ export const select: ISchema = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
'ui.required': {
|
'uiSchema.required': {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
title: '必填',
|
title: '必填',
|
||||||
'x-decorator': 'FormItem',
|
'x-decorator': 'FormItem',
|
||||||
|
@ -8,14 +8,14 @@ export const string: ISchema = {
|
|||||||
default: {
|
default: {
|
||||||
dataType: 'string',
|
dataType: 'string',
|
||||||
// name,
|
// name,
|
||||||
ui: {
|
uiSchema: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
// title,
|
// title,
|
||||||
'x-component': 'Input',
|
'x-component': 'Input',
|
||||||
} as ISchema,
|
} as ISchema,
|
||||||
},
|
},
|
||||||
properties: {
|
properties: {
|
||||||
'ui.title': {
|
'uiSchema.title': {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
title: '字段名称',
|
title: '字段名称',
|
||||||
required: true,
|
required: true,
|
||||||
@ -41,7 +41,7 @@ export const string: ISchema = {
|
|||||||
{ label: 'Text', value: 'text' },
|
{ label: 'Text', value: 'text' },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
'ui.required': {
|
'uiSchema.required': {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
title: '必填',
|
title: '必填',
|
||||||
'x-decorator': 'FormItem',
|
'x-decorator': 'FormItem',
|
||||||
|
@ -7,7 +7,7 @@ export const subTable: ISchema = {
|
|||||||
group: 'relation',
|
group: 'relation',
|
||||||
default: {
|
default: {
|
||||||
// name,
|
// name,
|
||||||
ui: {
|
uiSchema: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
// title,
|
// title,
|
||||||
'x-component': 'Select',
|
'x-component': 'Select',
|
||||||
@ -15,7 +15,7 @@ export const subTable: ISchema = {
|
|||||||
} as ISchema,
|
} as ISchema,
|
||||||
},
|
},
|
||||||
properties: {
|
properties: {
|
||||||
'ui.title': {
|
'uiSchema.title': {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
required: true,
|
required: true,
|
||||||
title: '字段名称',
|
title: '字段名称',
|
||||||
|
@ -8,14 +8,14 @@ export const textarea: ISchema = {
|
|||||||
default: {
|
default: {
|
||||||
dataType: 'text',
|
dataType: 'text',
|
||||||
// name,
|
// name,
|
||||||
ui: {
|
uiSchema: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
// title,
|
// title,
|
||||||
'x-component': 'Input.TextArea',
|
'x-component': 'Input.TextArea',
|
||||||
} as ISchema,
|
} as ISchema,
|
||||||
},
|
},
|
||||||
properties: {
|
properties: {
|
||||||
'ui.title': {
|
'uiSchema.title': {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
required: true,
|
required: true,
|
||||||
title: '字段名称',
|
title: '字段名称',
|
||||||
@ -41,7 +41,7 @@ export const textarea: ISchema = {
|
|||||||
{ label: 'Text', value: 'text' },
|
{ label: 'Text', value: 'text' },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
'ui.required': {
|
'uiSchema.required': {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
title: '必填',
|
title: '必填',
|
||||||
'x-decorator': 'FormItem',
|
'x-decorator': 'FormItem',
|
||||||
|
@ -1,3 +1,16 @@
|
|||||||
.ant-collapse.empty {
|
.ant-collapse.empty {
|
||||||
border: 0;
|
border: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.nb-database-config {
|
||||||
|
height: 46px;
|
||||||
|
border-radius: 0px;
|
||||||
|
background: none;
|
||||||
|
border: 0;
|
||||||
|
&:hover {
|
||||||
|
background: #1890ff;
|
||||||
|
}
|
||||||
|
&:active {
|
||||||
|
background: #1890ff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -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) {
|
export async function removeSchema(schema: ISchema) {
|
||||||
if (!schema['key']) {
|
if (!schema['key']) {
|
||||||
return;
|
return;
|
||||||
|
@ -32,17 +32,34 @@ import {
|
|||||||
} from 'antd';
|
} from 'antd';
|
||||||
import { uid } from '@formily/shared';
|
import { uid } from '@formily/shared';
|
||||||
import cls from 'classnames';
|
import cls from 'classnames';
|
||||||
import { useDesignable } from '../../components/schema-renderer';
|
import {
|
||||||
import { MenuOutlined, PlusOutlined } from '@ant-design/icons';
|
SchemaField,
|
||||||
|
SchemaRenderer,
|
||||||
|
useDesignable,
|
||||||
|
} from '../../components/schema-renderer';
|
||||||
|
import {
|
||||||
|
MenuOutlined,
|
||||||
|
PlusOutlined,
|
||||||
|
GroupOutlined,
|
||||||
|
LinkOutlined,
|
||||||
|
} from '@ant-design/icons';
|
||||||
import { IconPicker } from '../../components/icon-picker';
|
import { IconPicker } from '../../components/icon-picker';
|
||||||
import { createSchema, removeSchema, useDefaultAction, VisibleContext } from '..';
|
import {
|
||||||
|
createSchema,
|
||||||
|
removeSchema,
|
||||||
|
updateSchema,
|
||||||
|
useDefaultAction,
|
||||||
|
VisibleContext,
|
||||||
|
} from '..';
|
||||||
import { useMount } from 'ahooks';
|
import { useMount } from 'ahooks';
|
||||||
import './style.less';
|
import './style.less';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import { findPropertyByPath, useSchemaPath } from '@nocobase/client/lib';
|
import { findPropertyByPath, useSchemaPath } from '@nocobase/client/lib';
|
||||||
import { request } from '../';
|
import { request } from '../';
|
||||||
import defaultSchemas from './defaultSchemas';
|
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);
|
export const MenuModeContext = createContext(null);
|
||||||
|
|
||||||
@ -62,8 +79,8 @@ const SideMenu = (props: any) => {
|
|||||||
<AntdMenu mode={'inline'} onSelect={onSelect}>
|
<AntdMenu mode={'inline'} onSelect={onSelect}>
|
||||||
<RecursionField schema={child} onlyRenderProperties />
|
<RecursionField schema={child} onlyRenderProperties />
|
||||||
<Menu.AddNew key={uid()} path={[...path, selectedKey]}>
|
<Menu.AddNew key={uid()} path={[...path, selectedKey]}>
|
||||||
<Button block type={'dashed'}>
|
<Button className={'nb-add-new-menu-item'} block type={'dashed'}>
|
||||||
<PlusOutlined className={'nb-add-new-icon'} />
|
<PlusOutlined />
|
||||||
</Button>
|
</Button>
|
||||||
</Menu.AddNew>
|
</Menu.AddNew>
|
||||||
</AntdMenu>
|
</AntdMenu>
|
||||||
@ -109,13 +126,20 @@ export const Menu: any = observer((props: any) => {
|
|||||||
setSelectedKey(info.key);
|
setSelectedKey(info.key);
|
||||||
}
|
}
|
||||||
const selectedSchema = schema.properties[info.key];
|
const selectedSchema = schema.properties[info.key];
|
||||||
console.log({ selectedSchema })
|
console.log({ selectedSchema });
|
||||||
onSelect && onSelect({ ...info, schema: selectedSchema });
|
onSelect && onSelect({ ...info, schema: selectedSchema });
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<RecursionField schema={schema} onlyRenderProperties />
|
<RecursionField schema={schema} onlyRenderProperties />
|
||||||
<Menu.AddNew key={uid()} path={path}>
|
<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>
|
</Menu.AddNew>
|
||||||
</AntdMenu>
|
</AntdMenu>
|
||||||
{mode === 'mix' && (
|
{mode === 'mix' && (
|
||||||
@ -126,7 +150,8 @@ export const Menu: any = observer((props: any) => {
|
|||||||
const keyPath = [selectedKey, ...info.keyPath];
|
const keyPath = [selectedKey, ...info.keyPath];
|
||||||
const selectedSchema = findPropertyByPath(schema, keyPath);
|
const selectedSchema = findPropertyByPath(schema, keyPath);
|
||||||
console.log('keyPath', keyPath, selectedSchema);
|
console.log('keyPath', keyPath, selectedSchema);
|
||||||
onSelect && onSelect({ ...info, keyPath, schema: selectedSchema });
|
onSelect &&
|
||||||
|
onSelect({ ...info, keyPath, schema: selectedSchema });
|
||||||
}}
|
}}
|
||||||
selectedKey={selectedKey}
|
selectedKey={selectedKey}
|
||||||
sideMenuRef={sideMenuRef}
|
sideMenuRef={sideMenuRef}
|
||||||
@ -240,39 +265,117 @@ Menu.SubMenu = observer((props: any) => {
|
|||||||
|
|
||||||
Menu.AddNew = observer((props: any) => {
|
Menu.AddNew = observer((props: any) => {
|
||||||
const { appendChild } = useDesignable(props.path);
|
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 (
|
return (
|
||||||
<AntdMenu.ItemGroup
|
<AntdMenu.ItemGroup
|
||||||
className={'nb-menu-add-new'}
|
className={'nb-menu-add-new'}
|
||||||
title={
|
title={
|
||||||
<Dropdown
|
<Dropdown
|
||||||
overlay={
|
overlay={
|
||||||
<AntdMenu>
|
<AntdMenu
|
||||||
<AntdMenu.Item
|
onClick={async (info) => {
|
||||||
onClick={async () => {
|
console.log({ info });
|
||||||
const data = appendChild({
|
const values = await FormDialog(schemas[info.key].title, () => {
|
||||||
...defaultSchemas['Menu.Link'],
|
return (
|
||||||
title: uid(),
|
<FormLayout layout={'vertical'}>
|
||||||
});
|
<SchemaField schema={schemas[info.key].schema} />
|
||||||
await createSchema(data);
|
</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>
|
||||||
<AntdMenu.Item
|
<AntdMenu.Item key={'Menu.SubMenu'} icon={<GroupOutlined />}>
|
||||||
onClick={async () => {
|
新建菜单分组
|
||||||
const data = appendChild({
|
</AntdMenu.Item>
|
||||||
...defaultSchemas['Menu.SubMenu'],
|
<AntdMenu.Item key={'Menu.URL'} icon={<LinkOutlined />}>
|
||||||
title: uid(),
|
添加自定义链接
|
||||||
});
|
|
||||||
await createSchema(data);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
新建菜单组
|
|
||||||
</AntdMenu.Item>
|
</AntdMenu.Item>
|
||||||
</AntdMenu>
|
</AntdMenu>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<a>{props.children}</a>
|
{props.children}
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
@ -280,9 +383,131 @@ Menu.AddNew = observer((props: any) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
Menu.DesignableBar = (props) => {
|
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 field = useField();
|
||||||
const [visible, setVisible] = useState(false);
|
const [visible, setVisible] = useState(false);
|
||||||
const { schema, remove, refresh, insertAfter, appendChild } = useDesignable();
|
const { schema, remove, refresh, insertAfter, appendChild } = useDesignable();
|
||||||
|
const formConfig = schemas[schema['x-component']];
|
||||||
|
console.log({ formConfig, schema })
|
||||||
return (
|
return (
|
||||||
<div className={cls('designable-bar', { active: visible })}>
|
<div className={cls('designable-bar', { active: visible })}>
|
||||||
<div
|
<div
|
||||||
@ -304,17 +529,39 @@ Menu.DesignableBar = (props) => {
|
|||||||
overlay={
|
overlay={
|
||||||
<AntdMenu>
|
<AntdMenu>
|
||||||
<AntdMenu.Item
|
<AntdMenu.Item
|
||||||
onClick={() => {
|
onClick={async () => {
|
||||||
const title = uid();
|
const initialValues = {};
|
||||||
field.componentProps['icon'] = 'DeleteOutlined';
|
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'] || {};
|
schema['x-component-props'] || {};
|
||||||
schema['x-component-props']['icon'] = 'DeleteOutlined';
|
schema['x-component-props']['icon'] = icon;
|
||||||
schema.title = title;
|
field.componentProps['icon'] = icon;
|
||||||
refresh();
|
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>
|
||||||
<AntdMenu.Item
|
<AntdMenu.Item
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
|
@ -1,3 +1,8 @@
|
|||||||
|
.ant-layout-header {
|
||||||
|
height: 46px;
|
||||||
|
line-height: 46px;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.ant-menu-horizontal {
|
.ant-menu-horizontal {
|
||||||
width: 100%;
|
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);
|
||||||
|
}
|
@ -3,6 +3,19 @@ import { actions, middlewares } from '@nocobase/actions';
|
|||||||
import { sort } from '@nocobase/actions/src/actions/common';
|
import { sort } from '@nocobase/actions/src/actions/common';
|
||||||
import { cloneDeep, omit } from 'lodash';
|
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) => {
|
export const createOrUpdate = async (ctx: actions.Context, next: actions.Next) => {
|
||||||
const { values } = ctx.action.params;
|
const { values } = ctx.action.params;
|
||||||
const Collection = ctx.db.getModel('collections');
|
const Collection = ctx.db.getModel('collections');
|
||||||
@ -16,6 +29,7 @@ export const createOrUpdate = async (ctx: actions.Context, next: actions.Next) =
|
|||||||
} else {
|
} else {
|
||||||
await collection.update(values);
|
await collection.update(values);
|
||||||
}
|
}
|
||||||
|
await collection.updateAssociations(values);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log('error.errors', error.errors)
|
console.log('error.errors', error.errors)
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,10 @@ export default {
|
|||||||
title: '数据表配置',
|
title: '数据表配置',
|
||||||
model: 'Collection',
|
model: 'Collection',
|
||||||
fields: [
|
fields: [
|
||||||
|
{
|
||||||
|
type: 'sort',
|
||||||
|
name: 'sort',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
type: 'uid',
|
type: 'uid',
|
||||||
name: 'name',
|
name: 'name',
|
||||||
|
@ -6,6 +6,11 @@ export default {
|
|||||||
title: '字段配置',
|
title: '字段配置',
|
||||||
model: 'Field',
|
model: 'Field',
|
||||||
fields: [
|
fields: [
|
||||||
|
{
|
||||||
|
type: 'sort',
|
||||||
|
name: 'sort',
|
||||||
|
scope: ['parentKey'],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
type: 'uid',
|
type: 'uid',
|
||||||
name: 'key',
|
name: 'key',
|
||||||
|
@ -2,5 +2,39 @@ import _ from 'lodash';
|
|||||||
import { Model } from '@nocobase/database';
|
import { Model } from '@nocobase/database';
|
||||||
|
|
||||||
export class Collection extends Model {
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,5 +2,48 @@ import _ from 'lodash';
|
|||||||
import { Model } from '@nocobase/database';
|
import { Model } from '@nocobase/database';
|
||||||
|
|
||||||
export class Field extends Model {
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,21 +2,19 @@ import path from 'path';
|
|||||||
import { Application } from '@nocobase/server';
|
import { Application } from '@nocobase/server';
|
||||||
import { registerModels, Table } from '@nocobase/database';
|
import { registerModels, Table } from '@nocobase/database';
|
||||||
import * as models from './models';
|
import * as models from './models';
|
||||||
import { createOrUpdate } from './actions';
|
import { createOrUpdate, findAll } from './actions';
|
||||||
|
|
||||||
export default async function (this: Application, options = {}) {
|
export default async function (this: Application, options = {}) {
|
||||||
const database = this.database;
|
const database = this.database;
|
||||||
registerModels(models);
|
registerModels(models);
|
||||||
|
|
||||||
database.import({
|
database.import({
|
||||||
directory: path.resolve(__dirname, 'collections'),
|
directory: path.resolve(__dirname, 'collections'),
|
||||||
});
|
});
|
||||||
|
|
||||||
database.getModel('fields').beforeCreate((model) => {
|
database.getModel('fields').beforeCreate((model) => {
|
||||||
if (!model.get('name')) {
|
if (!model.get('name')) {
|
||||||
model.set('name', model.get('key'));
|
model.set('name', model.get('key'));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
this.resourcer.registerActionHandler('collections:findAll', findAll);
|
||||||
this.resourcer.registerActionHandler('collections:createOrUpdate', createOrUpdate);
|
this.resourcer.registerActionHandler('collections:createOrUpdate', createOrUpdate);
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,9 @@ const flatToNested = new FlatToNested({
|
|||||||
export default async (ctx: actions.Context, next: actions.Next) => {
|
export default async (ctx: actions.Context, next: actions.Next) => {
|
||||||
const { resourceKey } = ctx.action.params;
|
const { resourceKey } = ctx.action.params;
|
||||||
const Route = ctx.db.getModel('routes');
|
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()));
|
const data = flatToNested.convert(routes.map(route => route.toProps()));
|
||||||
ctx.body = data.routes;
|
ctx.body = data.routes;
|
||||||
await next();
|
await next();
|
||||||
|
@ -5,6 +5,11 @@ export default {
|
|||||||
title: '路由表',
|
title: '路由表',
|
||||||
model: 'Route',
|
model: 'Route',
|
||||||
fields: [
|
fields: [
|
||||||
|
{
|
||||||
|
type: 'sort',
|
||||||
|
name: 'sort',
|
||||||
|
scope: ['parentKey'],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
type: 'uid',
|
type: 'uid',
|
||||||
name: 'key',
|
name: 'key',
|
||||||
|
@ -21,6 +21,14 @@ export class UISchema extends Model {
|
|||||||
return 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 {
|
static toAttributes(value = {}): any {
|
||||||
const data = _.cloneDeep(value);
|
const data = _.cloneDeep(value);
|
||||||
const keys = [
|
const keys = [
|
||||||
@ -53,6 +61,15 @@ export class UISchema extends Model {
|
|||||||
return { ...data, ...options };
|
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() {
|
async getProperties() {
|
||||||
const properties = {};
|
const properties = {};
|
||||||
const children: UISchema[] = await this.getChildren({
|
const children: UISchema[] = await this.getChildren({
|
||||||
|
Loading…
Reference in New Issue
Block a user