feat: association field block (#493)
* feat: association field block * feat: association details block * feat: template add resource name * feat: add association calendar * fix: update yarn.lock * fix: remove useAssociationNames * fix: restore useFilterByTk logic * feat: client doc * fix: resolveNocobasePackagesAlias * fix: input textarea readpretty * feat: styling * fix: oho & obo * fix: field-summary component remove to collection manager * fix: translation * feat: improve code * fix(audit-logs): skip when collection does not exist * feat: m2m * fix: improve code * fix: title field Co-authored-by: chenos <chenlinxh@gmail.com>
This commit is contained in:
parent
d831a9b889
commit
b91ca4420b
@ -2,6 +2,7 @@
|
||||
|
||||
const dotenv = require('dotenv');
|
||||
const { resolve } = require('path');
|
||||
const { existsSync } = require('fs');
|
||||
|
||||
const env = {
|
||||
APP_PACKAGE_ROOT: 'app',
|
||||
@ -20,6 +21,12 @@ if ('v18' === process.version.split('.').shift()) {
|
||||
process.env.NODE_OPTIONS = '--openssl-legacy-provider';
|
||||
}
|
||||
|
||||
if (!process.env.APP_ENV_PATH && process.argv[2] && process.argv[2] === 'test') {
|
||||
if (existsSync(resolve(process.cwd(), '.env.test'))) {
|
||||
process.env.APP_ENV_PATH = '.env.test';
|
||||
}
|
||||
}
|
||||
|
||||
dotenv.config({
|
||||
path: resolve(process.cwd(), process.env.APP_ENV_PATH || '.env'),
|
||||
});
|
||||
|
@ -39,6 +39,7 @@ const useResource = (props: UseResourceProps) => {
|
||||
const api = useAPIClient();
|
||||
const association = useAssociation(props);
|
||||
const sourceId = useSourceId?.();
|
||||
|
||||
const field = useField<Field>();
|
||||
if (block === 'TableField') {
|
||||
const options = {
|
||||
@ -60,6 +61,7 @@ const useResource = (props: UseResourceProps) => {
|
||||
if (sourceId) {
|
||||
return api.resource(resource, sourceId);
|
||||
}
|
||||
|
||||
return api.resource(resource, record[association?.sourceKey || 'id']);
|
||||
};
|
||||
|
||||
@ -150,7 +152,8 @@ export const useFilterByTk = () => {
|
||||
return recordIndex;
|
||||
}
|
||||
}
|
||||
if (assoc) {
|
||||
|
||||
if (assoc) {
|
||||
const association = getCollectionField(assoc);
|
||||
return record?.[association.targetKey || 'id'];
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ import { useField } from '@formily/react';
|
||||
import { Spin } from 'antd';
|
||||
import React, { createContext, useContext, useEffect, useMemo } from 'react';
|
||||
import { BlockProvider, useBlockRequestContext } from './BlockProvider';
|
||||
import { useCollectionManager } from '../collection-manager';
|
||||
|
||||
export const FormBlockContext = createContext<any>({});
|
||||
|
||||
|
@ -61,7 +61,7 @@ function getFormValues(filterByTk, field, form, fieldNames, getField, resource)
|
||||
if (fieldNames.includes(key)) {
|
||||
const collectionField = getField(key);
|
||||
if (filterByTk) {
|
||||
if (collectionField.interface === 'subTable') {
|
||||
if (['subTable', 'o2m'].includes(collectionField.interface)) {
|
||||
values[key] = form.values[key];
|
||||
continue;
|
||||
}
|
||||
@ -70,7 +70,7 @@ function getFormValues(filterByTk, field, form, fieldNames, getField, resource)
|
||||
}
|
||||
}
|
||||
const items = form.values[key];
|
||||
if (collectionField.interface === 'linkTo') {
|
||||
if (['linkTo', 'm2o', 'm2m'].includes(collectionField.interface)) {
|
||||
const targetKey = collectionField.targetKey || 'id';
|
||||
if (resource instanceof TableFieldResource) {
|
||||
if (Array.isArray(items)) {
|
||||
@ -121,35 +121,39 @@ export const useCreateActionProps = () => {
|
||||
const values = getFormValues(filterByTk, field, form, fieldNames, getField, resource);
|
||||
actionField.data = field.data || {};
|
||||
actionField.data.loading = true;
|
||||
await resource.create({
|
||||
values: {
|
||||
...values,
|
||||
...overwriteValues,
|
||||
...assignedValues,
|
||||
},
|
||||
});
|
||||
actionField.data.loading = false;
|
||||
__parent?.service?.refresh?.();
|
||||
setVisible?.(false);
|
||||
if (!onSuccess?.successMessage) {
|
||||
return;
|
||||
}
|
||||
if (onSuccess?.manualClose) {
|
||||
Modal.success({
|
||||
title: compile(onSuccess?.successMessage),
|
||||
onOk: async () => {
|
||||
await form.reset();
|
||||
if (onSuccess?.redirecting && onSuccess?.redirectTo) {
|
||||
if (isURL(onSuccess.redirectTo)) {
|
||||
window.location.href = onSuccess.redirectTo;
|
||||
} else {
|
||||
history.push(onSuccess.redirectTo);
|
||||
}
|
||||
}
|
||||
try {
|
||||
await resource.create({
|
||||
values: {
|
||||
...values,
|
||||
...overwriteValues,
|
||||
...assignedValues,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
message.success(compile(onSuccess?.successMessage));
|
||||
actionField.data.loading = false;
|
||||
__parent?.service?.refresh?.();
|
||||
setVisible?.(false);
|
||||
if (!onSuccess?.successMessage) {
|
||||
return;
|
||||
}
|
||||
if (onSuccess?.manualClose) {
|
||||
Modal.success({
|
||||
title: compile(onSuccess?.successMessage),
|
||||
onOk: async () => {
|
||||
await form.reset();
|
||||
if (onSuccess?.redirecting && onSuccess?.redirectTo) {
|
||||
if (isURL(onSuccess.redirectTo)) {
|
||||
window.location.href = onSuccess.redirectTo;
|
||||
} else {
|
||||
history.push(onSuccess.redirectTo);
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
} else {
|
||||
message.success(compile(onSuccess?.successMessage));
|
||||
}
|
||||
} catch (error) {
|
||||
actionField.data.loading = false;
|
||||
}
|
||||
},
|
||||
};
|
||||
@ -281,39 +285,43 @@ export const useUpdateActionProps = () => {
|
||||
const values = getFormValues(filterByTk, field, form, fieldNames, getField, resource);
|
||||
actionField.data = field.data || {};
|
||||
actionField.data.loading = true;
|
||||
await resource.update({
|
||||
filterByTk,
|
||||
values: {
|
||||
...values,
|
||||
...overwriteValues,
|
||||
...assignedValues,
|
||||
},
|
||||
});
|
||||
actionField.data.loading = false;
|
||||
__parent?.service?.refresh?.();
|
||||
if (!(resource instanceof TableFieldResource)) {
|
||||
__parent?.__parent?.service?.refresh?.();
|
||||
}
|
||||
setVisible?.(false);
|
||||
if (!onSuccess?.successMessage) {
|
||||
return;
|
||||
}
|
||||
if (onSuccess?.manualClose) {
|
||||
Modal.success({
|
||||
title: compile(onSuccess?.successMessage),
|
||||
onOk: async () => {
|
||||
await form.reset();
|
||||
if (onSuccess?.redirecting && onSuccess?.redirectTo) {
|
||||
if (isURL(onSuccess.redirectTo)) {
|
||||
window.location.href = onSuccess.redirectTo;
|
||||
} else {
|
||||
history.push(onSuccess.redirectTo);
|
||||
}
|
||||
}
|
||||
try {
|
||||
await resource.update({
|
||||
filterByTk,
|
||||
values: {
|
||||
...values,
|
||||
...overwriteValues,
|
||||
...assignedValues,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
message.success(compile(onSuccess?.successMessage));
|
||||
actionField.data.loading = false;
|
||||
__parent?.service?.refresh?.();
|
||||
if (!(resource instanceof TableFieldResource)) {
|
||||
__parent?.__parent?.service?.refresh?.();
|
||||
}
|
||||
setVisible?.(false);
|
||||
if (!onSuccess?.successMessage) {
|
||||
return;
|
||||
}
|
||||
if (onSuccess?.manualClose) {
|
||||
Modal.success({
|
||||
title: compile(onSuccess?.successMessage),
|
||||
onOk: async () => {
|
||||
await form.reset();
|
||||
if (onSuccess?.redirecting && onSuccess?.redirectTo) {
|
||||
if (isURL(onSuccess.redirectTo)) {
|
||||
window.location.href = onSuccess.redirectTo;
|
||||
} else {
|
||||
history.push(onSuccess.redirectTo);
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
} else {
|
||||
message.success(compile(onSuccess?.successMessage));
|
||||
}
|
||||
} catch (error) {
|
||||
actionField.data.loading = false;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
@ -2,7 +2,7 @@ import { PlusOutlined } from '@ant-design/icons';
|
||||
import { ArrayTable } from '@formily/antd';
|
||||
import { ISchema, useForm } from '@formily/react';
|
||||
import { uid } from '@formily/shared';
|
||||
import { Button, Dropdown, Menu, Typography } from 'antd';
|
||||
import { Button, Dropdown, Menu } from 'antd';
|
||||
import { cloneDeep } from 'lodash';
|
||||
import React, { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
@ -50,8 +50,8 @@ const getSchema = (schema: IField, record: any, compile): ISchema => {
|
||||
type: 'void',
|
||||
'x-component': 'FieldSummary',
|
||||
'x-component-props': {
|
||||
schemaKey: schema.name
|
||||
}
|
||||
schemaKey: schema.name,
|
||||
},
|
||||
},
|
||||
// @ts-ignore
|
||||
...properties,
|
||||
@ -120,9 +120,10 @@ const useCreateCollectionField = () => {
|
||||
|
||||
recursiveChildren(form?.values?.children);
|
||||
|
||||
if (['o2o', 'o2m', 'm2o', 'm2m', 'linkTo'].includes(form?.values?.interface) && title) {
|
||||
if (['obo', 'oho', 'o2o', 'o2m', 'm2o', 'm2m', 'linkTo'].includes(form?.values?.interface) && title) {
|
||||
form.setValuesIn('reverseField.uiSchema.title', title);
|
||||
}
|
||||
|
||||
await run();
|
||||
await refreshCM();
|
||||
},
|
||||
@ -155,9 +156,11 @@ export const AddFieldAction = () => {
|
||||
return (
|
||||
option.children.length > 0 && (
|
||||
<Menu.ItemGroup key={option.label} title={compile(option.label)}>
|
||||
{option.children.map((child) => {
|
||||
return <Menu.Item key={child.name}>{compile(child.title)}</Menu.Item>;
|
||||
})}
|
||||
{option.children
|
||||
.filter((child) => !['o2o', 'subTable'].includes(child.name))
|
||||
.map((child) => {
|
||||
return <Menu.Item key={child.name}>{compile(child.title)}</Menu.Item>;
|
||||
})}
|
||||
</Menu.ItemGroup>
|
||||
)
|
||||
);
|
||||
@ -169,7 +172,11 @@ export const AddFieldAction = () => {
|
||||
{t('Add field')}
|
||||
</Button>
|
||||
</Dropdown>
|
||||
<SchemaComponent schema={schema} components={{ ...components, ArrayTable }} scope={{ createOnly: true, useCreateCollectionField }} />
|
||||
<SchemaComponent
|
||||
schema={schema}
|
||||
components={{ ...components, ArrayTable }}
|
||||
scope={{ createOnly: true, useCreateCollectionField }}
|
||||
/>
|
||||
</ActionContext.Provider>
|
||||
);
|
||||
};
|
||||
|
@ -8,6 +8,7 @@ import { SchemaComponent, useActionContext, useCompile } from '../../schema-comp
|
||||
import { useCollectionManager } from '../hooks/useCollectionManager';
|
||||
import { DataSourceContext } from '../sub-table';
|
||||
import { AddSubFieldAction } from './AddSubFieldAction';
|
||||
import { FieldSummary } from './components/FieldSummary';
|
||||
import { EditSubFieldAction } from './EditSubFieldAction';
|
||||
import { collectionSchema } from './schemas/collections';
|
||||
|
||||
@ -185,6 +186,7 @@ export const ConfigurationTable = () => {
|
||||
components={{
|
||||
AddSubFieldAction,
|
||||
EditSubFieldAction,
|
||||
FieldSummary,
|
||||
}}
|
||||
scope={{
|
||||
useDestroySubField,
|
||||
|
@ -0,0 +1,39 @@
|
||||
import { css } from '@emotion/css';
|
||||
import { observer } from '@formily/react';
|
||||
import { Tag } from 'antd';
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useCompile } from '../../../schema-component';
|
||||
import { useCollectionManager } from '../../hooks';
|
||||
|
||||
export const FieldSummary = observer((props: any) => {
|
||||
const { schemaKey } = props;
|
||||
const { getInterface } = useCollectionManager();
|
||||
const compile = useCompile();
|
||||
const { t } = useTranslation();
|
||||
const schema = getInterface(schemaKey);
|
||||
|
||||
if (!schema) return null;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={css`
|
||||
background: #f6f6f6;
|
||||
margin-bottom: 24px;
|
||||
padding: 16px;
|
||||
`}
|
||||
>
|
||||
<div className={css``}>{t('Field interface')}: <Tag>{compile(schema.title)}</Tag></div>
|
||||
{schema.description ? (
|
||||
<div
|
||||
className={css`
|
||||
margin-top: 8px;
|
||||
color: rgba(0, 0, 0, 0.45);
|
||||
`}
|
||||
>
|
||||
{compile(schema.description)}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
});
|
@ -17,7 +17,7 @@ export function registerGroupLabel(key: string, label: string) {
|
||||
groupLabels[key] = label;
|
||||
}
|
||||
|
||||
Object.keys(types).filter((type) => !['subTable'].includes(type)).forEach((type) => {
|
||||
Object.keys(types).forEach((type) => {
|
||||
const schema = types[type];
|
||||
registerField(schema.group || 'others', type, { order: 0, ...schema });
|
||||
});
|
||||
@ -27,7 +27,7 @@ registerGroupLabel('choices', '{{t("Choices")}}');
|
||||
registerGroupLabel('media', '{{t("Media")}}');
|
||||
registerGroupLabel('datetime', '{{t("Date & Time")}}');
|
||||
registerGroupLabel('relation', '{{t("Relation")}}');
|
||||
registerGroupLabel('advance', '{{t("Advance type")}}');
|
||||
registerGroupLabel('advanced', '{{t("Advanced type")}}');
|
||||
registerGroupLabel('systemInfo', '{{t("System info")}}');
|
||||
registerGroupLabel('others', '{{t("Others")}}');
|
||||
|
||||
|
@ -4,7 +4,7 @@ import { IField } from './types';
|
||||
export const formula: IField = {
|
||||
name: 'formula',
|
||||
type: 'object',
|
||||
group: 'advance',
|
||||
group: 'advanced',
|
||||
order: 1,
|
||||
title: '{{t("Formula")}}',
|
||||
description: '{{t("Formula description")}}',
|
||||
|
@ -86,17 +86,17 @@ export const linkTo: IField = {
|
||||
'x-component': 'Select',
|
||||
'x-disabled': '{{ !createOnly }}',
|
||||
},
|
||||
through: {
|
||||
type: 'string',
|
||||
title: '{{t("Junction collection")}}',
|
||||
'x-disabled': '{{ !createOnly }}',
|
||||
'x-reactions': ['{{useAsyncDataSource(loadCollections)}}'],
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Select',
|
||||
'x-component-props': {
|
||||
placeholder: '{{t("Leave it blank, unless you need a custom intermediate table")}}',
|
||||
},
|
||||
},
|
||||
// through: {
|
||||
// type: 'string',
|
||||
// title: '{{t("Junction collection")}}',
|
||||
// 'x-disabled': '{{ !createOnly }}',
|
||||
// 'x-reactions': ['{{useAsyncDataSource(loadCollections)}}'],
|
||||
// 'x-decorator': 'FormItem',
|
||||
// 'x-component': 'Select',
|
||||
// 'x-component-props': {
|
||||
// placeholder: '{{t("Leave it blank, unless you need a custom intermediate table")}}',
|
||||
// },
|
||||
// },
|
||||
// 'reverseField.uiSchema.title': {
|
||||
// type: 'string',
|
||||
// title: '{{t("Reverse field display name")}}',
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { ISchema } from '@formily/react';
|
||||
import { uid } from '@formily/shared';
|
||||
import { cloneDeep } from 'lodash';
|
||||
import { defaultProps, recordPickerSelector, recordPickerViewer } from './properties';
|
||||
import { defaultProps, recordPickerSelector, recordPickerViewer, relationshipType } from './properties';
|
||||
import { IField } from './types';
|
||||
|
||||
export const m2m: IField = {
|
||||
@ -77,20 +77,7 @@ export const m2m: IField = {
|
||||
},
|
||||
properties: {
|
||||
...defaultProps,
|
||||
type: {
|
||||
type: 'string',
|
||||
title: '{{t("Relationship type")}}',
|
||||
required: true,
|
||||
'x-disabled': true,
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Select',
|
||||
enum: [
|
||||
{ label: "{{t('One to one')}}", value: 'hasOne' },
|
||||
{ label: "{{t('One to many')}}", value: 'hasMany' },
|
||||
{ label: "{{t('Many to one')}}", value: 'belongsTo' },
|
||||
{ label: "{{t('Many to many')}}", value: 'belongsToMany' },
|
||||
],
|
||||
},
|
||||
type: relationshipType,
|
||||
grid: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid',
|
||||
@ -119,13 +106,13 @@ export const m2m: IField = {
|
||||
through: {
|
||||
type: 'string',
|
||||
title: '{{t("Through collection")}}',
|
||||
description: '{{ t("Generated automatically if left blank") }}',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-disabled': '{{ !createOnly }}',
|
||||
'x-reactions': ['{{useAsyncDataSource(loadCollections)}}'],
|
||||
'x-component': 'Select',
|
||||
'x-component-props': {
|
||||
allowClear: true,
|
||||
placeholder: '留空时,自动生成中间表'
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { ISchema } from '@formily/react';
|
||||
import { cloneDeep } from 'lodash';
|
||||
import { recordPickerSelector, recordPickerViewer } from './properties';
|
||||
import { recordPickerSelector, recordPickerViewer, relationshipType } from './properties';
|
||||
import { IField } from './types';
|
||||
|
||||
export const m2o: IField = {
|
||||
@ -73,20 +73,7 @@ export const m2o: IField = {
|
||||
description:
|
||||
"{{t('Randomly generated and can be modified. Support letters, numbers and underscores, must start with an letter.')}}",
|
||||
},
|
||||
type: {
|
||||
type: 'string',
|
||||
title: '{{t("Relationship type")}}',
|
||||
required: true,
|
||||
'x-disabled': true,
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Select',
|
||||
enum: [
|
||||
{ label: "{{t('One to one')}}", value: 'hasOne' },
|
||||
{ label: "{{t('One to many')}}", value: 'hasMany' },
|
||||
{ label: "{{t('Many to one')}}", value: 'belongsTo' },
|
||||
{ label: "{{t('Many to many')}}", value: 'belongsToMany' },
|
||||
],
|
||||
},
|
||||
type: relationshipType,
|
||||
grid: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid',
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { ISchema } from '@formily/react';
|
||||
import { relationshipType } from './properties';
|
||||
import { IField } from './types';
|
||||
|
||||
export const o2m: IField = {
|
||||
@ -111,20 +112,7 @@ export const o2m: IField = {
|
||||
description:
|
||||
"{{t('Randomly generated and can be modified. Support letters, numbers and underscores, must start with an letter.')}}",
|
||||
},
|
||||
type: {
|
||||
type: 'string',
|
||||
title: '{{t("Relationship type")}}',
|
||||
required: true,
|
||||
'x-disabled': true,
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Select',
|
||||
enum: [
|
||||
{ label: "{{t('One to one')}}", value: 'hasOne' },
|
||||
{ label: "{{t('One to many')}}", value: 'hasMany' },
|
||||
{ label: "{{t('Many to one')}}", value: 'belongsTo' },
|
||||
{ label: "{{t('Many to many')}}", value: 'belongsToMany' },
|
||||
],
|
||||
},
|
||||
type: relationshipType,
|
||||
grid: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid',
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { ISchema } from '@formily/react';
|
||||
import { cloneDeep } from 'lodash';
|
||||
import { recordPickerSelector, recordPickerViewer } from './properties';
|
||||
import { recordPickerSelector, recordPickerViewer, relationshipType } from './properties';
|
||||
import { IField } from './types';
|
||||
|
||||
export const o2o: IField = {
|
||||
@ -27,9 +27,9 @@ export const o2o: IField = {
|
||||
},
|
||||
},
|
||||
reverseField: {
|
||||
interface: 'm2o',
|
||||
interface: 'obo',
|
||||
type: 'belongsTo',
|
||||
title: '{{t("One to one")}}',
|
||||
// title: '{{t("One to one (belongs to)")}}',
|
||||
// name,
|
||||
uiSchema: {
|
||||
// title,
|
||||
@ -74,20 +74,7 @@ export const o2o: IField = {
|
||||
description:
|
||||
"{{t('Randomly generated and can be modified. Support letters, numbers and underscores, must start with an letter.')}}",
|
||||
},
|
||||
type: {
|
||||
type: 'string',
|
||||
title: '{{t("Relationship type")}}',
|
||||
required: true,
|
||||
'x-disabled': true,
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Select',
|
||||
enum: [
|
||||
{ label: "{{t('One to one')}}", value: 'hasOne' },
|
||||
{ label: "{{t('One to many')}}", value: 'hasMany' },
|
||||
{ label: "{{t('Many to one')}}", value: 'belongsTo' },
|
||||
{ label: "{{t('Many to many')}}", value: 'belongsToMany' },
|
||||
],
|
||||
},
|
||||
type: relationshipType,
|
||||
grid: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid',
|
||||
@ -183,3 +170,339 @@ export const o2o: IField = {
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
export const oho: IField = {
|
||||
name: 'oho',
|
||||
type: 'object',
|
||||
group: 'relation',
|
||||
order: 3,
|
||||
title: '{{t("One to one (has one)")}}',
|
||||
description: '{{t("One to one description")}}',
|
||||
isAssociation: true,
|
||||
default: {
|
||||
type: 'hasOne',
|
||||
// name,
|
||||
uiSchema: {
|
||||
// title,
|
||||
'x-component': 'RecordPicker',
|
||||
'x-component-props': {
|
||||
// mode: 'tags',
|
||||
multiple: false,
|
||||
fieldNames: {
|
||||
label: 'id',
|
||||
value: 'id',
|
||||
},
|
||||
},
|
||||
},
|
||||
reverseField: {
|
||||
interface: 'obo',
|
||||
type: 'belongsTo',
|
||||
// title: '{{t("One to one (belongs to)")}}',
|
||||
// name,
|
||||
uiSchema: {
|
||||
// title,
|
||||
'x-component': 'RecordPicker',
|
||||
'x-component-props': {
|
||||
// mode: 'tags',
|
||||
multiple: false,
|
||||
fieldNames: {
|
||||
label: 'id',
|
||||
value: 'id',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
schemaInitialize(schema: ISchema, { readPretty }) {
|
||||
if (readPretty) {
|
||||
schema['properties'] = {
|
||||
viewer: cloneDeep(recordPickerViewer),
|
||||
};
|
||||
} else {
|
||||
schema['properties'] = {
|
||||
selector: cloneDeep(recordPickerSelector),
|
||||
};
|
||||
}
|
||||
},
|
||||
properties: {
|
||||
'uiSchema.title': {
|
||||
type: 'string',
|
||||
title: '{{t("Field display name")}}',
|
||||
required: true,
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input',
|
||||
},
|
||||
name: {
|
||||
type: 'string',
|
||||
title: '{{t("Field name")}}',
|
||||
required: true,
|
||||
'x-disabled': '{{ !createOnly }}',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input',
|
||||
description:
|
||||
"{{t('Randomly generated and can be modified. Support letters, numbers and underscores, must start with an letter.')}}",
|
||||
},
|
||||
type: relationshipType,
|
||||
grid: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid',
|
||||
properties: {
|
||||
row1: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Row',
|
||||
properties: {
|
||||
col11: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Col',
|
||||
properties: {
|
||||
source: {
|
||||
type: 'void',
|
||||
title: '{{t("Source collection")}}',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'SourceCollection',
|
||||
'x-disabled': true,
|
||||
},
|
||||
},
|
||||
},
|
||||
col12: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Col',
|
||||
properties: {
|
||||
target: {
|
||||
type: 'string',
|
||||
title: '{{t("Target collection")}}',
|
||||
required: true,
|
||||
'x-reactions': ['{{useAsyncDataSource(loadCollections)}}'],
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Select',
|
||||
'x-disabled': '{{ !createOnly }}',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
row2: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Row',
|
||||
properties: {
|
||||
col21: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Col',
|
||||
properties: {
|
||||
sourceKey: {
|
||||
type: 'void',
|
||||
title: '{{t("Source key")}}',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'SourceKey',
|
||||
},
|
||||
},
|
||||
},
|
||||
col22: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Col',
|
||||
properties: {
|
||||
foreignKey: {
|
||||
type: 'string',
|
||||
title: '{{t("Foreign key")}}',
|
||||
required: true,
|
||||
default: '{{ useNewId("f_") }}',
|
||||
description:
|
||||
"{{t('Randomly generated and can be modified. Support letters, numbers and underscores, must start with an letter.')}}",
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input',
|
||||
'x-disabled': '{{ !createOnly }}',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
filterable: {
|
||||
nested: true,
|
||||
children: [
|
||||
// {
|
||||
// name: 'id',
|
||||
// title: '{{t("Exists")}}',
|
||||
// operators: [
|
||||
// { label: '{{t("exists")}}', value: '$exists', noValue: true },
|
||||
// { label: '{{t("not exists")}}', value: '$notExists', noValue: true },
|
||||
// ],
|
||||
// schema: {
|
||||
// title: '{{t("Exists")}}',
|
||||
// type: 'string',
|
||||
// 'x-component': 'Input',
|
||||
// },
|
||||
// },
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
export const obo: IField = {
|
||||
name: 'obo',
|
||||
type: 'object',
|
||||
group: 'relation',
|
||||
order: 3,
|
||||
title: '{{t("One to one (belongs to)")}}',
|
||||
description: '{{t("One to one description")}}',
|
||||
isAssociation: true,
|
||||
default: {
|
||||
type: 'belongsTo',
|
||||
// name,
|
||||
uiSchema: {
|
||||
// title,
|
||||
'x-component': 'RecordPicker',
|
||||
'x-component-props': {
|
||||
// mode: 'tags',
|
||||
multiple: false,
|
||||
fieldNames: {
|
||||
label: 'id',
|
||||
value: 'id',
|
||||
},
|
||||
},
|
||||
},
|
||||
reverseField: {
|
||||
interface: 'oho',
|
||||
type: 'hasOne',
|
||||
// name,
|
||||
uiSchema: {
|
||||
// title,
|
||||
'x-component': 'RecordPicker',
|
||||
'x-component-props': {
|
||||
// mode: 'tags',
|
||||
multiple: true,
|
||||
fieldNames: {
|
||||
label: 'id',
|
||||
value: 'id',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
schemaInitialize(schema: ISchema, { readPretty }) {
|
||||
if (readPretty) {
|
||||
schema['properties'] = {
|
||||
viewer: cloneDeep(recordPickerViewer),
|
||||
};
|
||||
} else {
|
||||
schema['properties'] = {
|
||||
selector: cloneDeep(recordPickerSelector),
|
||||
};
|
||||
}
|
||||
},
|
||||
properties: {
|
||||
'uiSchema.title': {
|
||||
type: 'string',
|
||||
title: '{{t("Field display name")}}',
|
||||
required: true,
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input',
|
||||
},
|
||||
name: {
|
||||
type: 'string',
|
||||
title: '{{t("Field name")}}',
|
||||
required: true,
|
||||
'x-disabled': '{{ !createOnly }}',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input',
|
||||
description:
|
||||
"{{t('Randomly generated and can be modified. Support letters, numbers and underscores, must start with an letter.')}}",
|
||||
},
|
||||
type: relationshipType,
|
||||
grid: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid',
|
||||
properties: {
|
||||
row1: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Row',
|
||||
properties: {
|
||||
col11: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Col',
|
||||
properties: {
|
||||
source: {
|
||||
type: 'void',
|
||||
title: '{{t("Source collection")}}',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'SourceCollection',
|
||||
'x-disabled': true,
|
||||
},
|
||||
},
|
||||
},
|
||||
col12: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Col',
|
||||
properties: {
|
||||
target: {
|
||||
type: 'string',
|
||||
title: '{{t("Target collection")}}',
|
||||
required: true,
|
||||
'x-reactions': ['{{useAsyncDataSource(loadCollections)}}'],
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Select',
|
||||
'x-disabled': '{{ !createOnly }}',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
row2: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Row',
|
||||
properties: {
|
||||
col21: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Col',
|
||||
properties: {
|
||||
foreignKey: {
|
||||
type: 'string',
|
||||
title: '{{t("Foreign key")}}',
|
||||
required: true,
|
||||
default: '{{ useNewId("f_") }}',
|
||||
description:
|
||||
"{{t('Randomly generated and can be modified. Support letters, numbers and underscores, must start with an letter.')}}",
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input',
|
||||
'x-disabled': '{{ !createOnly }}',
|
||||
},
|
||||
},
|
||||
},
|
||||
col22: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Col',
|
||||
properties: {
|
||||
targetKey: {
|
||||
type: 'void',
|
||||
title: '{{t("Target key")}}',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'TargetKey',
|
||||
'x-disabled': '{{ !createOnly }}',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
filterable: {
|
||||
nested: true,
|
||||
children: [
|
||||
// {
|
||||
// name: 'id',
|
||||
// title: '{{t("Exists")}}',
|
||||
// operators: [
|
||||
// { label: '{{t("exists")}}', value: '$exists', noValue: true },
|
||||
// { label: '{{t("not exists")}}', value: '$notExists', noValue: true },
|
||||
// ],
|
||||
// schema: {
|
||||
// title: '{{t("Exists")}}',
|
||||
// type: 'string',
|
||||
// 'x-component': 'Input',
|
||||
// },
|
||||
// },
|
||||
],
|
||||
},
|
||||
};
|
||||
|
@ -29,6 +29,21 @@ export const type: ISchema = {
|
||||
],
|
||||
};
|
||||
|
||||
export const relationshipType: ISchema ={
|
||||
type: 'string',
|
||||
title: '{{t("Relationship type")}}',
|
||||
required: true,
|
||||
'x-disabled': true,
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Select',
|
||||
enum: [
|
||||
{ label: "{{t('HasOne')}}", value: 'hasOne' },
|
||||
{ label: "{{t('HasMany')}}", value: 'hasMany' },
|
||||
{ label: "{{t('BelongsTo')}}", value: 'belongsTo' },
|
||||
{ label: "{{t('BelongsToMany')}}", value: 'belongsToMany' },
|
||||
],
|
||||
};
|
||||
|
||||
export const dateTimeProps: { [key: string]: ISchema } = {
|
||||
'uiSchema.x-component-props.dateFormat': {
|
||||
type: 'string',
|
||||
|
@ -456,8 +456,8 @@ export default {
|
||||
"By month": "By month",
|
||||
"By field": "By field",
|
||||
"By custom date": "By custom date",
|
||||
"Advance": "Advance",
|
||||
"Advance type": "Advance type",
|
||||
"Advanced": "Advanced",
|
||||
"Advanced type": "Advanced",
|
||||
"End": "End",
|
||||
"Trigger context": "Trigger context",
|
||||
"Node result": "Node result",
|
||||
@ -538,4 +538,6 @@ export default {
|
||||
"User": "User",
|
||||
"Field": "Field",
|
||||
"Field value changes": "Field value changes",
|
||||
"One to one (has one)": "One to one (has one)",
|
||||
"One to one (belongs to)": "One to one (belongs to)",
|
||||
}
|
||||
|
@ -172,9 +172,10 @@ export default {
|
||||
"Foreign key 1": "外键1",
|
||||
"Foreign key 2": "外键2",
|
||||
"One to one description": "用于创建一对一关系,比如一个用户会有一套个人资料。",
|
||||
"One to many description": "用于创建一对多关系,比如一个国家会有多个城市。作为字段存在时,它是一个子表格用于显示目标数据表的数据。创建后,会在目标数据表里自动生成一个 Many to one 字段。",
|
||||
"Many to one description": "用于创建多对一关系,比如一个城市只能属于一个国家,一个国家可以有多个城市。作为字段存在时,它是一个下拉选择用于选择目标数据表的数据。创建后,会在目标数据表里自动生成一个 One to many 字段。",
|
||||
"One to many description": "用于创建一对多关系,比如一个国家会有多个城市。作为字段存在时,它是一个子表格用于显示目标数据表的数据。创建后,会在目标数据表里自动生成一个多对一字段。",
|
||||
"Many to one description": "用于创建多对一关系,比如一个城市只能属于一个国家,一个国家可以有多个城市。作为字段存在时,它是一个下拉选择用于选择目标数据表的数据。创建后,会在目标数据表里自动生成一个多对一字段。",
|
||||
"Many to many description": "用于创建多对多关系,比如一个学生会有多个老师,一个老师也会有多个学生。作为字段存在时,它是一个下拉选择用于选择目标数据表的数据。",
|
||||
"Generated automatically if left blank": "留空时,自动生成中间表",
|
||||
"Add filter": "添加筛选条件",
|
||||
"Add filter group": "添加筛选分组",
|
||||
"is": "等于",
|
||||
@ -493,8 +494,8 @@ export default {
|
||||
'By field': '数据表字段',
|
||||
'By custom date': '自定义时间',
|
||||
|
||||
'Advance': '高级模式',
|
||||
'Advance type': '高级类型',
|
||||
'Advanced': '高级模式',
|
||||
'Advanced type': '高级类型',
|
||||
|
||||
'End': '结束',
|
||||
|
||||
@ -593,4 +594,6 @@ export default {
|
||||
"Select": "选择",
|
||||
"Select Field": "选择字段",
|
||||
"Field value changes": "变更记录",
|
||||
"One to one (has one)": "一对一(has one)",
|
||||
"One to one (belongs to)": "一对一(belongs to)"
|
||||
}
|
||||
|
@ -33,6 +33,7 @@ export const CalendarDesigner = () => {
|
||||
const defaultFilter = fieldSchema?.['x-decorator-props']?.params?.filter || {};
|
||||
const options = useOptions();
|
||||
const fieldNames = fieldSchema?.['x-decorator-props']?.['fieldNames'] || {};
|
||||
const defaultResource = fieldSchema?.['x-decorator-props']?.resource;
|
||||
return (
|
||||
<GeneralSchemaDesigner template={template} title={title || name}>
|
||||
<SchemaSettings.SelectItem
|
||||
@ -129,7 +130,7 @@ export const CalendarDesigner = () => {
|
||||
}}
|
||||
/>
|
||||
<SchemaSettings.Divider />
|
||||
<SchemaSettings.Template componentName={'Calendar'} collectionName={name} />
|
||||
<SchemaSettings.Template componentName={'Calendar'} collectionName={name} resourceName={defaultResource} />
|
||||
<SchemaSettings.Divider />
|
||||
<SchemaSettings.Remove
|
||||
removeParentsIfNoChildren
|
||||
|
@ -1,21 +0,0 @@
|
||||
import { observer, RecursionField, useFieldSchema } from '@formily/react';
|
||||
import { Space, Typography } from 'antd';
|
||||
import React from 'react';
|
||||
import { useCollectionManager } from '../../../collection-manager/hooks';
|
||||
import { useCompile } from '../../hooks';
|
||||
|
||||
export const FieldSummary = observer((props: any) => {
|
||||
const { schemaKey } = props;
|
||||
const { getInterface } = useCollectionManager();
|
||||
const compile = useCompile();
|
||||
const schema = getInterface(schemaKey);
|
||||
|
||||
if (!schema) return (null);
|
||||
|
||||
return (
|
||||
<div style={{marginBottom: '22px'}}>
|
||||
<Typography.Title level={3} style={{ margin: '0 0 10px 0' }}>{compile(schema.title)}</Typography.Title>
|
||||
{ schema.description ? (<Typography.Text type="secondary">{compile(schema.description)}</Typography.Text>) : (null)}
|
||||
</div>
|
||||
)
|
||||
});
|
@ -1 +0,0 @@
|
||||
export * from './FieldSummary';
|
@ -20,10 +20,11 @@ export const FormDesigner = () => {
|
||||
const { dn } = useDesignable();
|
||||
const { t } = useTranslation();
|
||||
const { visible } = useActionContext();
|
||||
const defaultResource = fieldSchema?.['x-decorator-props']?.resource;
|
||||
return (
|
||||
<GeneralSchemaDesigner template={template} title={title || name}>
|
||||
{/* <SchemaSettings.Template componentName={'FormItem'} collectionName={name} /> */}
|
||||
<SchemaSettings.FormItemTemplate componentName={'FormItem'} collectionName={name} />
|
||||
<SchemaSettings.FormItemTemplate componentName={'FormItem'} collectionName={name} resourceName={defaultResource} />
|
||||
<SchemaSettings.Divider />
|
||||
<SchemaSettings.Remove
|
||||
removeParentsIfNoChildren
|
||||
@ -38,6 +39,8 @@ export const FormDesigner = () => {
|
||||
export const ReadPrettyFormDesigner = () => {
|
||||
const { name, title } = useCollection();
|
||||
const template = useSchemaTemplate();
|
||||
const fieldSchema = useFieldSchema();
|
||||
const defaultResource = fieldSchema?.['x-decorator-props']?.resource;
|
||||
return (
|
||||
<GeneralSchemaDesigner template={template} title={title || name}>
|
||||
{/* <SchemaSettings.Template componentName={'ReadPrettyForm'} collectionName={name} /> */}
|
||||
@ -45,6 +48,7 @@ export const ReadPrettyFormDesigner = () => {
|
||||
insertAdjacentPosition={'beforeEnd'}
|
||||
componentName={'ReadPrettyFormItem'}
|
||||
collectionName={name}
|
||||
resourceName={defaultResource}
|
||||
/>
|
||||
<SchemaSettings.Divider />
|
||||
<SchemaSettings.Remove
|
||||
@ -69,6 +73,7 @@ export const DetailsDesigner = () => {
|
||||
const sortFields = useSortFields(name);
|
||||
const defaultFilter = fieldSchema?.['x-decorator-props']?.params?.filter || {};
|
||||
const defaultSort = fieldSchema?.['x-decorator-props']?.params?.sort || [];
|
||||
const defaultResource = fieldSchema?.['x-decorator-props']?.resource;
|
||||
const sort = defaultSort?.map((item: string) => {
|
||||
return item.startsWith('-')
|
||||
? {
|
||||
@ -204,7 +209,7 @@ export const DetailsDesigner = () => {
|
||||
service.run({ ...service.params?.[0], sort: sortArr });
|
||||
}}
|
||||
/>
|
||||
<SchemaSettings.Template componentName={'Details'} collectionName={name} />
|
||||
<SchemaSettings.Template componentName={'Details'} collectionName={name} resourceName={defaultResource} />
|
||||
<SchemaSettings.Divider />
|
||||
<SchemaSettings.Remove
|
||||
removeParentsIfNoChildren
|
||||
|
@ -35,5 +35,4 @@ export * from './tabs';
|
||||
export * from './time-picker';
|
||||
export * from './tree-select';
|
||||
export * from './upload';
|
||||
export * from './field-summary';
|
||||
import './index.less';
|
||||
|
@ -38,16 +38,17 @@ ReadPretty.TextArea = (props) => {
|
||||
const { autop = true, ellipsis, text } = props;
|
||||
const html = (
|
||||
<div
|
||||
style={{lineHeight: 1.612}}
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: HTMLEncode(value).split('\n').join('<br/>'),
|
||||
}}
|
||||
/>
|
||||
);
|
||||
console.log('value', value);
|
||||
|
||||
const content = ellipsis ?
|
||||
(<EllipsisWithTooltip ellipsis={ellipsis} popoverContent={autop ? html : value}>
|
||||
{text}
|
||||
</EllipsisWithTooltip>) : value || html;
|
||||
{text || value}
|
||||
</EllipsisWithTooltip>) : (autop ? html : value);
|
||||
return (
|
||||
<div className={cls(prefixCls, props.className)} style={props.style}>
|
||||
{props.addonBefore}
|
||||
|
@ -41,6 +41,9 @@ const schema = {
|
||||
'x-component': 'Input.TextArea',
|
||||
'x-component-props': {
|
||||
ellipsis: true,
|
||||
style: {
|
||||
width: '100px'
|
||||
}
|
||||
},
|
||||
},
|
||||
read3: {
|
||||
|
@ -17,6 +17,7 @@ export const KanbanDesigner = () => {
|
||||
const { t } = useTranslation();
|
||||
const { dn } = useDesignable();
|
||||
const defaultFilter = fieldSchema?.['x-decorator-props']?.params?.filter || {};
|
||||
const defaultResource = fieldSchema?.['x-decorator-props']?.resource;
|
||||
const template = useSchemaTemplate();
|
||||
return (
|
||||
<GeneralSchemaDesigner template={template} title={title || name}>
|
||||
@ -51,7 +52,7 @@ export const KanbanDesigner = () => {
|
||||
}}
|
||||
/>
|
||||
<SchemaSettings.Divider />
|
||||
<SchemaSettings.Template componentName={'Kanban'} collectionName={name} />
|
||||
<SchemaSettings.Template componentName={'Kanban'} collectionName={name} resourceName={defaultResource} />
|
||||
<SchemaSettings.Divider />
|
||||
<SchemaSettings.Remove
|
||||
removeParentsIfNoChildren
|
||||
|
@ -6,6 +6,8 @@ import { ReadPretty as InputReadPretty } from '../input';
|
||||
import { MarkdownVoid } from './Markdown.Void';
|
||||
import { convertToText, markdown } from './util';
|
||||
|
||||
import './style.less';
|
||||
|
||||
export const Markdown: any = connect(
|
||||
AntdInput.TextArea,
|
||||
mapProps((props: any, field) => {
|
||||
|
@ -1,3 +1,7 @@
|
||||
.nb-markdown {
|
||||
line-height: 1.612;
|
||||
}
|
||||
|
||||
.nb-markdown > *:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
@ -65,7 +65,7 @@ export const TableColumnDesigner = (props) => {
|
||||
dn.refresh();
|
||||
}}
|
||||
/>
|
||||
{collectionField?.interface === 'linkTo' && (
|
||||
{['linkTo', 'm2m', 'm2o', 'obo', 'oho'].includes(collectionField?.interface) && (
|
||||
<SchemaSettings.SelectItem
|
||||
title={t('Title field')}
|
||||
options={options}
|
||||
|
@ -20,6 +20,7 @@ export const TableBlockDesigner = () => {
|
||||
const { dn } = useDesignable();
|
||||
const defaultFilter = fieldSchema?.['x-decorator-props']?.params?.filter || {};
|
||||
const defaultSort = fieldSchema?.['x-decorator-props']?.params?.sort || [];
|
||||
const defaultResource = fieldSchema?.['x-decorator-props']?.resource;
|
||||
const sort = defaultSort?.map((item: string) => {
|
||||
return item.startsWith('-')
|
||||
? {
|
||||
@ -200,7 +201,7 @@ export const TableBlockDesigner = () => {
|
||||
}}
|
||||
/>
|
||||
<SchemaSettings.Divider />
|
||||
<SchemaSettings.Template componentName={'Table'} collectionName={name} />
|
||||
<SchemaSettings.Template componentName={'Table'} collectionName={name} resourceName={defaultResource} />
|
||||
<SchemaSettings.Divider />
|
||||
<SchemaSettings.Remove
|
||||
removeParentsIfNoChildren
|
||||
|
@ -52,7 +52,7 @@ SchemaInitializer.Button = observer((props: SchemaInitializerButtonProps) => {
|
||||
return (
|
||||
Component && (
|
||||
<SchemaInitializerItemContext.Provider
|
||||
key={`item-${indexA}`}
|
||||
key={`${item.key || 'item'}-${indexA}`}
|
||||
value={{
|
||||
index: indexA,
|
||||
item,
|
||||
@ -164,7 +164,6 @@ SchemaInitializer.Item = (props: SchemaInitializerItemProps) => {
|
||||
);
|
||||
}
|
||||
if (item.type === 'subMenu') {
|
||||
console.log('item.key', item.key);
|
||||
return (
|
||||
<Menu.SubMenu
|
||||
// @ts-ignore
|
||||
@ -192,8 +191,8 @@ SchemaInitializer.Item = (props: SchemaInitializerItemProps) => {
|
||||
return (
|
||||
<Menu.SubMenu
|
||||
// @ts-ignore
|
||||
eventKey={eventKey ? `${eventKey}-${index}` : index}
|
||||
key={eventKey ? `${eventKey}-${index}` : index}
|
||||
eventKey={eventKey ? `${eventKey}-${index}` : info.key}
|
||||
key={info.key}
|
||||
title={compile(children)}
|
||||
icon={typeof icon === 'string' ? <Icon type={icon as string} /> : icon}
|
||||
>
|
||||
@ -203,12 +202,13 @@ SchemaInitializer.Item = (props: SchemaInitializerItemProps) => {
|
||||
}
|
||||
return (
|
||||
<Menu.Item
|
||||
eventKey={eventKey ? `${eventKey}-${index}` : index}
|
||||
// {...others}
|
||||
key={info.key}
|
||||
eventKey={eventKey ? `${eventKey}-${index}` : info.key}
|
||||
icon={typeof icon === 'string' ? <Icon type={icon as string} /> : icon}
|
||||
onClick={(opts) => {
|
||||
onClick({ ...opts, item: info });
|
||||
}}
|
||||
{...others}
|
||||
>
|
||||
{compile(children)}
|
||||
</Menu.Item>
|
||||
|
@ -5,9 +5,71 @@ import { gridRowColWrap } from '../utils';
|
||||
|
||||
const useRelationFields = () => {
|
||||
const { fields } = useCollection();
|
||||
return fields
|
||||
.filter((field) => ['linkTo', 'subTable'].includes(field.interface))
|
||||
const relationFields = fields
|
||||
.filter((field) => ['linkTo', 'subTable', 'o2m', 'm2m', 'obo', 'oho', 'o2o', 'm2o'].includes(field.interface))
|
||||
.map((field) => {
|
||||
if (['hasOne', 'belongsTo'].includes(field.type)) {
|
||||
return {
|
||||
key: field.name,
|
||||
type: 'subMenu',
|
||||
title: field?.uiSchema?.title || field.name,
|
||||
children: [
|
||||
{
|
||||
key: `${field.name}_details`,
|
||||
type: 'item',
|
||||
title: '{{t("Details")}}',
|
||||
field,
|
||||
component: 'RecordReadPrettyAssociationFormBlockInitializer',
|
||||
},
|
||||
// {
|
||||
// key: `${field.name}_form`,
|
||||
// type: 'item',
|
||||
// title: '{{t("Form")}}',
|
||||
// field,
|
||||
// component: 'RecordAssociationFormBlockInitializer',
|
||||
// },
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
if (['hasMany', 'belongsToMany'].includes(field.type)) {
|
||||
return {
|
||||
key: field.name,
|
||||
type: 'subMenu',
|
||||
title: field?.uiSchema?.title || field.name,
|
||||
children: [
|
||||
{
|
||||
key: `${field.name}_table`,
|
||||
type: 'item',
|
||||
title: '{{t("Table")}}',
|
||||
field,
|
||||
component: 'RecordAssociationBlockInitializer',
|
||||
},
|
||||
{
|
||||
key: `${field.name}_details`,
|
||||
type: 'item',
|
||||
title: '{{t("Details")}}',
|
||||
field,
|
||||
component: 'RecordAssociationDetailsBlockInitializer',
|
||||
},
|
||||
{
|
||||
key: `${field.name}_form`,
|
||||
type: 'item',
|
||||
title: '{{t("Form")}}',
|
||||
field,
|
||||
component: 'RecordAssociationFormBlockInitializer',
|
||||
},
|
||||
{
|
||||
key: `${field.name}_calendar`,
|
||||
type: 'item',
|
||||
title: '{{t("Calendar")}}',
|
||||
field,
|
||||
component: 'RecordAssociationCalendarBlockInitializer',
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
key: field.name,
|
||||
type: 'item',
|
||||
@ -16,6 +78,7 @@ const useRelationFields = () => {
|
||||
component: 'RecordAssociationBlockInitializer',
|
||||
};
|
||||
}) as any;
|
||||
return relationFields;
|
||||
};
|
||||
|
||||
export const RecordBlockInitializers = (props: any) => {
|
||||
|
@ -663,6 +663,7 @@ export const RecordReadPrettyFormBlockInitializer = (props) => {
|
||||
<SchemaInitializer.Item
|
||||
icon={<FormOutlined />}
|
||||
{...others}
|
||||
key={'123'}
|
||||
onClick={async ({ item }) => {
|
||||
if (item.template) {
|
||||
const s = await getTemplateSchemaByMode(item);
|
||||
@ -699,34 +700,256 @@ export const RecordReadPrettyFormBlockInitializer = (props) => {
|
||||
);
|
||||
};
|
||||
|
||||
export const RecordAssociationBlockInitializer = (props) => {
|
||||
export const RecordAssociationFormBlockInitializer = (props) => {
|
||||
const { item, onCreateBlockSchema, componentType, createBlockSchema, insert, ...others } = props;
|
||||
const { getTemplateSchemaByMode } = useSchemaTemplateManager();
|
||||
const field = item.field;
|
||||
const collection = field.target;
|
||||
const resource = `${field.collectionName}.${field.name}`;
|
||||
return (
|
||||
<SchemaInitializer.Item
|
||||
icon={<FormOutlined />}
|
||||
{...others}
|
||||
onClick={async ({ item }) => {
|
||||
|
||||
const action = ['hasOne', 'belongsTo'].includes(field.type) ? 'get' : null;
|
||||
const actionInitializers = ['hasOne', 'belongsTo'].includes(field.type) ? 'UpdateFormActionInitializers' : 'CreateFormActionInitializers';
|
||||
|
||||
if (item.template) {
|
||||
const s = await getTemplateSchemaByMode(item);
|
||||
if (item.template.componentName === 'FormItem') {
|
||||
const blockSchema = createFormBlockSchema({
|
||||
collection,
|
||||
resource,
|
||||
association: resource,
|
||||
action,
|
||||
useSourceId: '{{ useSourceIdFromParentRecord }}',
|
||||
useParams: '{{ useParamsFromRecord }}',
|
||||
actionInitializers,
|
||||
template: s,
|
||||
});
|
||||
if (item.mode === 'reference') {
|
||||
blockSchema['x-template-key'] = item.template.key;
|
||||
}
|
||||
insert(blockSchema);
|
||||
} else {
|
||||
insert(s);
|
||||
}
|
||||
} else {
|
||||
insert(
|
||||
createFormBlockSchema({
|
||||
collection,
|
||||
resource,
|
||||
association: resource,
|
||||
action,
|
||||
useSourceId: '{{ useSourceIdFromParentRecord }}',
|
||||
useParams: '{{ useParamsFromRecord }}',
|
||||
actionInitializers,
|
||||
}),
|
||||
);
|
||||
}
|
||||
}}
|
||||
items={useRecordCollectionDataSourceItems('FormItem', item, collection, resource)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const RecordReadPrettyAssociationFormBlockInitializer = (props) => {
|
||||
const { item, onCreateBlockSchema, componentType, createBlockSchema, insert, ...others } = props;
|
||||
const { getTemplateSchemaByMode } = useSchemaTemplateManager();
|
||||
|
||||
const field = item.field;
|
||||
const collection = field.target;
|
||||
const resource = `${field.collectionName}.${field.name}`;
|
||||
return (
|
||||
<SchemaInitializer.Item
|
||||
icon={<FormOutlined />}
|
||||
{...others}
|
||||
onClick={async ({ item }) => {
|
||||
if (item.template) {
|
||||
const s = await getTemplateSchemaByMode(item);
|
||||
if (item.template.componentName === 'ReadPrettyFormItem') {
|
||||
const blockSchema = createReadPrettyFormBlockSchema({
|
||||
collection,
|
||||
resource,
|
||||
association: resource,
|
||||
action: 'get',
|
||||
useSourceId: '{{ useSourceIdFromParentRecord }}',
|
||||
useParams: '{{ useParamsFromRecord }}',
|
||||
template: s,
|
||||
});
|
||||
if (item.mode === 'reference') {
|
||||
blockSchema['x-template-key'] = item.template.key;
|
||||
}
|
||||
insert(blockSchema);
|
||||
} else {
|
||||
insert(s);
|
||||
}
|
||||
} else {
|
||||
insert(
|
||||
createReadPrettyFormBlockSchema({
|
||||
collection,
|
||||
resource,
|
||||
association: resource,
|
||||
action: 'get',
|
||||
useSourceId: '{{ useSourceIdFromParentRecord }}',
|
||||
useParams: '{{ useParamsFromRecord }}',
|
||||
}),
|
||||
);
|
||||
}
|
||||
}}
|
||||
items={useRecordCollectionDataSourceItems('ReadPrettyFormItem', item, collection, resource)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const RecordAssociationDetailsBlockInitializer = (props) => {
|
||||
const { item, onCreateBlockSchema, componentType, createBlockSchema, insert, ...others } = props;
|
||||
const { getTemplateSchemaByMode } = useSchemaTemplateManager();
|
||||
const { getCollection } = useCollectionManager();
|
||||
const field = item.field;
|
||||
const collection = getCollection(field.target);
|
||||
const resource = `${field.collectionName}.${field.name}`;
|
||||
return (
|
||||
<SchemaInitializer.Item
|
||||
icon={<FormOutlined />}
|
||||
{...others}
|
||||
onClick={async ({ item }) => {
|
||||
if (item.template) {
|
||||
const s = await getTemplateSchemaByMode(item);
|
||||
insert(s);
|
||||
} else {
|
||||
insert(
|
||||
createDetailsBlockSchema({
|
||||
collection: field.target,
|
||||
resource,
|
||||
association: resource,
|
||||
rowKey: collection.filterTargetKey || 'id',
|
||||
}),
|
||||
);
|
||||
}
|
||||
}}
|
||||
items={useRecordCollectionDataSourceItems('Details', item, field.target, resource)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export const RecordAssociationCalendarBlockInitializer = (props) => {
|
||||
const { item, onCreateBlockSchema, componentType, createBlockSchema, insert, ...others } = props;
|
||||
const { getTemplateSchemaByMode } = useSchemaTemplateManager();
|
||||
const { t } = useTranslation();
|
||||
const options = useContext(SchemaOptionsContext);
|
||||
const { getCollection } = useCollectionManager();
|
||||
const field = item.field;
|
||||
const collection = getCollection(field.target);
|
||||
const resource = `${field.collectionName}.${field.name}`;
|
||||
|
||||
return (
|
||||
<SchemaInitializer.Item
|
||||
icon={<TableOutlined />}
|
||||
{...others}
|
||||
onClick={async ({ item }) => {
|
||||
console.log('RecordAssociationBlockInitializer', item);
|
||||
const field = item.field;
|
||||
const collection = getCollection(field.target);
|
||||
if (item.template) {
|
||||
const s = await getTemplateSchemaByMode(item);
|
||||
insert(s);
|
||||
} else {
|
||||
const stringFields = collection?.fields
|
||||
?.filter((field) => field.type === 'string')
|
||||
?.map((field) => {
|
||||
return {
|
||||
label: field?.uiSchema?.title,
|
||||
value: field.name,
|
||||
};
|
||||
});
|
||||
const dateFields = collection?.fields
|
||||
?.filter((field) => field.type === 'date')
|
||||
?.map((field) => {
|
||||
return {
|
||||
label: field?.uiSchema?.title,
|
||||
value: field.name,
|
||||
};
|
||||
});
|
||||
const values = await FormDialog(t('Create calendar block'), () => {
|
||||
return (
|
||||
<SchemaComponentOptions scope={options.scope} components={{ ...options.components }}>
|
||||
<FormLayout layout={'vertical'}>
|
||||
<SchemaComponent
|
||||
schema={{
|
||||
properties: {
|
||||
title: {
|
||||
title: t('Title field'),
|
||||
enum: stringFields,
|
||||
required: true,
|
||||
'x-component': 'Select',
|
||||
'x-decorator': 'FormItem',
|
||||
},
|
||||
start: {
|
||||
title: t('Start date field'),
|
||||
enum: dateFields,
|
||||
required: true,
|
||||
default: 'createdAt',
|
||||
'x-component': 'Select',
|
||||
'x-decorator': 'FormItem',
|
||||
},
|
||||
end: {
|
||||
title: t('End date field'),
|
||||
enum: dateFields,
|
||||
'x-component': 'Select',
|
||||
'x-decorator': 'FormItem',
|
||||
},
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</FormLayout>
|
||||
</SchemaComponentOptions>
|
||||
);
|
||||
}).open({
|
||||
initialValues: {},
|
||||
});
|
||||
insert(
|
||||
createTableBlockSchema({
|
||||
rowKey: collection.filterTargetKey,
|
||||
createCalendarBlockSchema({
|
||||
collection: field.target,
|
||||
resource: `${field.collectionName}.${field.name}`,
|
||||
association: `${field.collectionName}.${field.name}`,
|
||||
resource,
|
||||
association: resource,
|
||||
fieldNames: {
|
||||
...values,
|
||||
},
|
||||
}),
|
||||
);
|
||||
// if (item.template) {
|
||||
// const s = await getTemplateSchemaByMode(item);
|
||||
// insert(s);
|
||||
// } else {
|
||||
// insert(createTableBlockSchema({ collection: item.name }));
|
||||
// }
|
||||
}
|
||||
}}
|
||||
// items={useRecordCollectionDataSourceItems('Table')}
|
||||
items={useRecordCollectionDataSourceItems('Calendar', item, field.target, resource)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export const RecordAssociationBlockInitializer = (props) => {
|
||||
const { item, onCreateBlockSchema, componentType, createBlockSchema, insert, ...others } = props;
|
||||
const { getTemplateSchemaByMode } = useSchemaTemplateManager();
|
||||
const { getCollection } = useCollectionManager();
|
||||
const field = item.field;
|
||||
const collection = getCollection(field.target);
|
||||
const resource = `${field.collectionName}.${field.name}`;
|
||||
return (
|
||||
<SchemaInitializer.Item
|
||||
icon={<TableOutlined />}
|
||||
{...others}
|
||||
onClick={async ({ item }) => {
|
||||
if (item.template) {
|
||||
const s = await getTemplateSchemaByMode(item);
|
||||
insert(s);
|
||||
} else {
|
||||
insert(
|
||||
createTableBlockSchema({
|
||||
rowKey: collection.filterTargetKey,
|
||||
collection: field.target,
|
||||
resource,
|
||||
association: resource,
|
||||
}),
|
||||
);
|
||||
}
|
||||
}}
|
||||
items={useRecordCollectionDataSourceItems('Table', item, field.target, resource)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
@ -197,11 +197,11 @@ export const useCurrentSchema = (action: string, key: string, find = findSchema,
|
||||
};
|
||||
};
|
||||
|
||||
export const useRecordCollectionDataSourceItems = (componentName) => {
|
||||
export const useRecordCollectionDataSourceItems = (componentName, item = null, collectionName = null, resourceName = null) => {
|
||||
const { t } = useTranslation();
|
||||
const collection = useCollection();
|
||||
const { getTemplatesByCollection } = useSchemaTemplateManager();
|
||||
const templates = getTemplatesByCollection(collection.name).filter((template) => {
|
||||
const templates = getTemplatesByCollection(collectionName || collection.name, resourceName || collectionName || collection.name).filter((template) => {
|
||||
return componentName && template.componentName === componentName;
|
||||
});
|
||||
if (!templates.length) {
|
||||
@ -210,15 +210,17 @@ export const useRecordCollectionDataSourceItems = (componentName) => {
|
||||
const index = 0;
|
||||
return [
|
||||
{
|
||||
key: `${collectionName || componentName}_table_blank`,
|
||||
type: 'item',
|
||||
name: collection.name,
|
||||
title: t('Blank block'),
|
||||
item,
|
||||
},
|
||||
{
|
||||
type: 'divider',
|
||||
},
|
||||
{
|
||||
key: `${componentName}_table_subMenu_${index}_copy`,
|
||||
key: `${collectionName || componentName}_table_subMenu_${index}_copy`,
|
||||
type: 'subMenu',
|
||||
name: 'copy',
|
||||
title: t('Duplicate template'),
|
||||
@ -230,12 +232,13 @@ export const useRecordCollectionDataSourceItems = (componentName) => {
|
||||
mode: 'copy',
|
||||
name: collection.name,
|
||||
template,
|
||||
item,
|
||||
title: templateName || t('Untitled'),
|
||||
};
|
||||
}),
|
||||
},
|
||||
{
|
||||
key: `${componentName}_table_subMenu_${index}_ref`,
|
||||
key: `${collectionName || componentName}_table_subMenu_${index}_ref`,
|
||||
type: 'subMenu',
|
||||
name: 'ref',
|
||||
title: t('Reference template'),
|
||||
@ -247,6 +250,7 @@ export const useRecordCollectionDataSourceItems = (componentName) => {
|
||||
mode: 'reference',
|
||||
name: collection.name,
|
||||
template,
|
||||
item,
|
||||
title: templateName || t('Untitled'),
|
||||
};
|
||||
}),
|
||||
@ -266,7 +270,7 @@ export const useCollectionDataSourceItems = (componentName) => {
|
||||
children: collections
|
||||
?.filter((item) => !item.inherit)
|
||||
?.map((item, index) => {
|
||||
const templates = getTemplatesByCollection(item.name).filter((template) => {
|
||||
const templates = getTemplatesByCollection(item.name, item.name).filter((template) => {
|
||||
return componentName && template.componentName === componentName;
|
||||
});
|
||||
if (!templates.length) {
|
||||
@ -412,6 +416,7 @@ export const createFormBlockSchema = (options) => {
|
||||
template,
|
||||
...others
|
||||
} = options;
|
||||
console.log('createFormBlockSchema', options);
|
||||
const resourceName = resource || association || collection;
|
||||
const schema: ISchema = {
|
||||
type: 'void',
|
||||
|
@ -117,7 +117,7 @@ export const SchemaSettings: React.FC<SchemaSettingsProps> & SchemaSettingsNeste
|
||||
};
|
||||
|
||||
SchemaSettings.Template = (props) => {
|
||||
const { componentName, collectionName } = props;
|
||||
const { componentName, collectionName, resourceName } = props;
|
||||
const { t } = useTranslation();
|
||||
const { dn, setVisible, template, fieldSchema } = useSchemaSettings();
|
||||
const api = useAPIClient();
|
||||
@ -178,6 +178,7 @@ SchemaSettings.Template = (props) => {
|
||||
sdn.loadAPIClientEvents();
|
||||
const { key } = await saveAsTemplate({
|
||||
collectionName,
|
||||
resourceName,
|
||||
componentName,
|
||||
name: values.name,
|
||||
uid: fieldSchema['x-uid'],
|
||||
@ -232,7 +233,8 @@ const findBlockTemplateSchema = (fieldSchema) => {
|
||||
};
|
||||
|
||||
SchemaSettings.FormItemTemplate = (props) => {
|
||||
const { insertAdjacentPosition = 'afterBegin', componentName, collectionName } = props;
|
||||
const { insertAdjacentPosition = 'afterBegin', componentName, collectionName, resourceName } = props;
|
||||
console.log('SchemaSettings.Template', props);
|
||||
const { t } = useTranslation();
|
||||
const { dn, setVisible, template, fieldSchema } = useSchemaSettings();
|
||||
const api = useAPIClient();
|
||||
@ -312,6 +314,7 @@ SchemaSettings.FormItemTemplate = (props) => {
|
||||
sdn.loadAPIClientEvents();
|
||||
const { key } = await saveAsTemplate({
|
||||
collectionName,
|
||||
resourceName,
|
||||
componentName,
|
||||
name: values.name,
|
||||
uid: gridSchema['x-uid'],
|
||||
|
@ -119,10 +119,11 @@ export const useSchemaTemplateManager = () => {
|
||||
getTemplateById(key) {
|
||||
return templates?.find((template) => template.key === key);
|
||||
},
|
||||
getTemplatesByCollection(collectionName: string) {
|
||||
const items = templates?.filter?.((template) => template.collectionName === collectionName);
|
||||
getTemplatesByCollection(collectionName: string, resourceName: string = null) {
|
||||
const items = templates?.filter?.((template) => (!template.resourceName && template.collectionName === collectionName) || (template.resourceName && template.resourceName === resourceName));
|
||||
return items || [];
|
||||
},
|
||||
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -1,11 +1,11 @@
|
||||
import React from "react";
|
||||
import { css } from "@emotion/css";
|
||||
import { InputNumber, Select } from "antd";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Cron } from 'react-js-cron';
|
||||
|
||||
import CronZhCN from './locale/Cron.zh-CN';
|
||||
|
||||
|
||||
const languages = {
|
||||
'zh-CN': CronZhCN,
|
||||
};
|
||||
@ -18,7 +18,7 @@ const RepeatOptions = [
|
||||
{ value: 86400_000, text: 'By day', unitText: 'Days' },
|
||||
{ value: 604800_000, text: 'By week', unitText: 'Weeks' },
|
||||
// { value: 18144_000_000, text: 'By 30 days' },
|
||||
{ value: 'cron', text: 'Advance' }
|
||||
{ value: 'cron', text: 'Advanced' }
|
||||
];
|
||||
|
||||
function getNumberOption(v) {
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { Database } from '../../database';
|
||||
import { mockDatabase } from '..';
|
||||
import { Database } from '../../database';
|
||||
import { FormulaField } from '../../fields';
|
||||
|
||||
describe('formula field', () => {
|
||||
@ -13,10 +13,13 @@ describe('formula field', () => {
|
||||
await db.close();
|
||||
});
|
||||
|
||||
it('add formula field with old table, already has data.', async() => {
|
||||
it('add formula field with old table, already has data.', async () => {
|
||||
const Test = db.collection({
|
||||
name: 'tests',
|
||||
fields: [{ type: 'float', name: 'price' }, { type: 'float', name: 'count' }],
|
||||
fields: [
|
||||
{ type: 'float', name: 'price' },
|
||||
{ type: 'float', name: 'count' },
|
||||
],
|
||||
});
|
||||
|
||||
await db.sync();
|
||||
@ -42,7 +45,11 @@ describe('formula field', () => {
|
||||
const expression = 'price*count';
|
||||
const Test = db.collection({
|
||||
name: 'tests',
|
||||
fields: [{ type: 'float', name: 'price' }, { type: 'float', name: 'count' }, {name: 'sum', type: 'formula', expression}],
|
||||
fields: [
|
||||
{ type: 'float', name: 'price' },
|
||||
{ type: 'float', name: 'count' },
|
||||
{ name: 'sum', type: 'formula', expression },
|
||||
],
|
||||
});
|
||||
|
||||
await db.sync();
|
||||
@ -58,5 +65,5 @@ describe('formula field', () => {
|
||||
test.set('count', '6');
|
||||
await test.save();
|
||||
expect(test.get('sum')).toEqual(sumField.caculate(expression, test.toJSON()));
|
||||
})
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -1,7 +1,7 @@
|
||||
import lodash from 'lodash';
|
||||
import type { SequelizeHooks } from 'sequelize/types/lib/hooks';
|
||||
import Database from './database';
|
||||
import { Model } from './model';
|
||||
import type { SequelizeHooks } from 'sequelize/types/lib/hooks';
|
||||
|
||||
const { hooks } = require('sequelize/lib/hooks');
|
||||
|
||||
@ -60,6 +60,7 @@ export class ModelHook {
|
||||
buildSequelizeHook(type) {
|
||||
return async (...args: any[]) => {
|
||||
const modelName = this.findModelName(args);
|
||||
|
||||
if (modelName) {
|
||||
// emit model event
|
||||
await this.database.emitAsync(`${modelName}.${type}`, ...args);
|
||||
|
@ -59,7 +59,7 @@ export abstract class SingleRelationRepository extends RelationRepository {
|
||||
}
|
||||
|
||||
async findOne(options?: SingleRelationFindOption): Promise<Model<any>> {
|
||||
return this.find(options);
|
||||
return this.find({ ...options, filterByTk: null } as any);
|
||||
}
|
||||
|
||||
@transaction()
|
||||
@ -85,6 +85,10 @@ export abstract class SingleRelationRepository extends RelationRepository {
|
||||
transaction,
|
||||
});
|
||||
|
||||
if (!target) {
|
||||
throw new Error('The record does not exist');
|
||||
}
|
||||
|
||||
await updateModelByValues(target, options?.values, {
|
||||
...lodash.omit(options, 'values'),
|
||||
transaction,
|
||||
|
@ -6,6 +6,7 @@ export function registerCli(app: Application) {
|
||||
require('./db-clean').default(app);
|
||||
require('./db-sync').default(app);
|
||||
require('./install').default(app);
|
||||
require('./migrator').default(app);
|
||||
require('./start').default(app);
|
||||
require('./upgrade').default(app);
|
||||
|
||||
|
12
packages/core/server/src/commands/migrator.ts
Normal file
12
packages/core/server/src/commands/migrator.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import Application from '../application';
|
||||
|
||||
export default (app: Application) => {
|
||||
app
|
||||
.command('migrator')
|
||||
.action(async (opts) => {
|
||||
await app.start();
|
||||
console.log('migrating...');
|
||||
await app.db.migrator.runAsCLI(process.argv.slice(3));
|
||||
await app.stop();
|
||||
});
|
||||
};
|
@ -18,7 +18,7 @@ export function dataWrapping() {
|
||||
|
||||
if (!ctx.body) {
|
||||
if (ctx.action.actionName == 'get') {
|
||||
ctx.status = 404;
|
||||
ctx.status = 200;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,42 +1,44 @@
|
||||
import Database from '@nocobase/database';
|
||||
import Application from '@nocobase/server';
|
||||
import { LOG_TYPE_CREATE } from '../constants';
|
||||
|
||||
export async function afterCreate(model, options) {
|
||||
const db = model.constructor.database as Database;
|
||||
const collection = db.getCollection(model.constructor.name);
|
||||
if (!collection.options.logging) {
|
||||
return;
|
||||
}
|
||||
const transaction = options.transaction;
|
||||
const AuditLog = db.getCollection('auditLogs');
|
||||
const currentUserId = options?.context?.state?.currentUser?.id;
|
||||
try {
|
||||
const changes = [];
|
||||
const changed = model.changed();
|
||||
if (changed) {
|
||||
changed.forEach((key: string) => {
|
||||
const field = collection.findField((field) => {
|
||||
return field.name === key || field.options.field === key;
|
||||
});
|
||||
if (field && !field.options.hidden) {
|
||||
changes.push({
|
||||
field: field.options,
|
||||
after: model.get(key),
|
||||
});
|
||||
}
|
||||
});
|
||||
export function afterCreate(app: Application) {
|
||||
return async (model, options) => {
|
||||
const db = app.db;
|
||||
const collection = db.getCollection(model.constructor.name);
|
||||
if (!collection || !collection.options.logging) {
|
||||
return;
|
||||
}
|
||||
await AuditLog.repository.create({
|
||||
values: {
|
||||
type: LOG_TYPE_CREATE,
|
||||
collectionName: model.constructor.name,
|
||||
recordId: model.get(model.constructor.primaryKeyAttribute),
|
||||
createdAt: model.get('createdAt'),
|
||||
userId: currentUserId,
|
||||
changes,
|
||||
},
|
||||
transaction,
|
||||
hooks: false,
|
||||
});
|
||||
} catch (error) {}
|
||||
const transaction = options.transaction;
|
||||
const AuditLog = db.getCollection('auditLogs');
|
||||
const currentUserId = options?.context?.state?.currentUser?.id;
|
||||
try {
|
||||
const changes = [];
|
||||
const changed = model.changed();
|
||||
if (changed) {
|
||||
changed.forEach((key: string) => {
|
||||
const field = collection.findField((field) => {
|
||||
return field.name === key || field.options.field === key;
|
||||
});
|
||||
if (field && !field.options.hidden) {
|
||||
changes.push({
|
||||
field: field.options,
|
||||
after: model.get(key),
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
await AuditLog.repository.create({
|
||||
values: {
|
||||
type: LOG_TYPE_CREATE,
|
||||
collectionName: model.constructor.name,
|
||||
recordId: model.get(model.constructor.primaryKeyAttribute),
|
||||
createdAt: model.get('createdAt'),
|
||||
userId: currentUserId,
|
||||
changes,
|
||||
},
|
||||
transaction,
|
||||
hooks: false,
|
||||
});
|
||||
} catch (error) {}
|
||||
};
|
||||
}
|
||||
|
@ -1,45 +1,47 @@
|
||||
import Database from '@nocobase/database';
|
||||
import Application from '@nocobase/server';
|
||||
import { LOG_TYPE_DESTROY } from '../constants';
|
||||
|
||||
export async function afterDestroy(model, options) {
|
||||
const db = model.constructor.database as Database;
|
||||
const collection = db.getCollection(model.constructor.name);
|
||||
if (!collection.options.logging) {
|
||||
return;
|
||||
}
|
||||
const transaction = options.transaction;
|
||||
const AuditLog = db.getCollection('auditLogs');
|
||||
const currentUserId = options?.context?.state?.currentUser?.id;
|
||||
try {
|
||||
const changes = [];
|
||||
Object.keys(model.get()).forEach((key: string) => {
|
||||
const field = collection.findField((field) => {
|
||||
return field.name === key || field.options.field === key;
|
||||
});
|
||||
if (field) {
|
||||
changes.push({
|
||||
field: field.options,
|
||||
before: model.get(key),
|
||||
export function afterDestroy(app: Application) {
|
||||
return async (model, options) => {
|
||||
const db = app.db;
|
||||
const collection = db.getCollection(model.constructor.name);
|
||||
if (!collection || !collection.options.logging) {
|
||||
return;
|
||||
}
|
||||
const transaction = options.transaction;
|
||||
const AuditLog = db.getCollection('auditLogs');
|
||||
const currentUserId = options?.context?.state?.currentUser?.id;
|
||||
try {
|
||||
const changes = [];
|
||||
Object.keys(model.get()).forEach((key: string) => {
|
||||
const field = collection.findField((field) => {
|
||||
return field.name === key || field.options.field === key;
|
||||
});
|
||||
}
|
||||
});
|
||||
await AuditLog.repository.create({
|
||||
values: {
|
||||
type: LOG_TYPE_DESTROY,
|
||||
collectionName: model.constructor.name,
|
||||
recordId: model.get(model.constructor.primaryKeyAttribute),
|
||||
userId: currentUserId,
|
||||
changes,
|
||||
},
|
||||
transaction,
|
||||
hooks: false,
|
||||
});
|
||||
// if (!options.transaction) {
|
||||
// await transaction.commit();
|
||||
// }
|
||||
} catch (error) {
|
||||
// if (!options.transaction) {
|
||||
// await transaction.rollback();
|
||||
// }
|
||||
}
|
||||
if (field) {
|
||||
changes.push({
|
||||
field: field.options,
|
||||
before: model.get(key),
|
||||
});
|
||||
}
|
||||
});
|
||||
await AuditLog.repository.create({
|
||||
values: {
|
||||
type: LOG_TYPE_DESTROY,
|
||||
collectionName: model.constructor.name,
|
||||
recordId: model.get(model.constructor.primaryKeyAttribute),
|
||||
userId: currentUserId,
|
||||
changes,
|
||||
},
|
||||
transaction,
|
||||
hooks: false,
|
||||
});
|
||||
// if (!options.transaction) {
|
||||
// await transaction.commit();
|
||||
// }
|
||||
} catch (error) {
|
||||
// if (!options.transaction) {
|
||||
// await transaction.rollback();
|
||||
// }
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -1,54 +1,56 @@
|
||||
import Database from '@nocobase/database';
|
||||
import Application from '@nocobase/server';
|
||||
import { LOG_TYPE_UPDATE } from '../constants';
|
||||
|
||||
export async function afterUpdate(model, options) {
|
||||
const db = model.constructor.database as Database;
|
||||
const collection = db.getCollection(model.constructor.name);
|
||||
if (!collection.options.logging) {
|
||||
return;
|
||||
}
|
||||
const changed = model.changed();
|
||||
if (!changed) {
|
||||
return;
|
||||
}
|
||||
const transaction = options.transaction;
|
||||
const AuditLog = db.getCollection('auditLogs');
|
||||
const currentUserId = options?.context?.state?.currentUser?.id;
|
||||
const changes = [];
|
||||
changed.forEach((key: string) => {
|
||||
const field = collection.findField((field) => {
|
||||
return field.name === key || field.options.field === key;
|
||||
});
|
||||
if (field && !field.options.hidden) {
|
||||
changes.push({
|
||||
field: field.options,
|
||||
after: model.get(key),
|
||||
before: model.previous(key),
|
||||
});
|
||||
export function afterUpdate(app: Application) {
|
||||
return async (model, options) => {
|
||||
const db = app.db;
|
||||
const collection = db.getCollection(model.constructor.name);
|
||||
if (!collection || !collection.options.logging) {
|
||||
return;
|
||||
}
|
||||
});
|
||||
if (!changes.length) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await AuditLog.repository.create({
|
||||
values: {
|
||||
type: LOG_TYPE_UPDATE,
|
||||
collectionName: model.constructor.name,
|
||||
recordId: model.get(model.constructor.primaryKeyAttribute),
|
||||
createdAt: model.get('updatedAt'),
|
||||
userId: currentUserId,
|
||||
changes,
|
||||
},
|
||||
transaction,
|
||||
hooks: false,
|
||||
const changed = model.changed();
|
||||
if (!changed) {
|
||||
return;
|
||||
}
|
||||
const transaction = options.transaction;
|
||||
const AuditLog = db.getCollection('auditLogs');
|
||||
const currentUserId = options?.context?.state?.currentUser?.id;
|
||||
const changes = [];
|
||||
changed.forEach((key: string) => {
|
||||
const field = collection.findField((field) => {
|
||||
return field.name === key || field.options.field === key;
|
||||
});
|
||||
if (field && !field.options.hidden) {
|
||||
changes.push({
|
||||
field: field.options,
|
||||
after: model.get(key),
|
||||
before: model.previous(key),
|
||||
});
|
||||
}
|
||||
});
|
||||
// if (!options.transaction) {
|
||||
// await transaction.commit();
|
||||
// }
|
||||
} catch (error) {
|
||||
// if (!options.transaction) {
|
||||
// await transaction.rollback();
|
||||
// }
|
||||
}
|
||||
if (!changes.length) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await AuditLog.repository.create({
|
||||
values: {
|
||||
type: LOG_TYPE_UPDATE,
|
||||
collectionName: model.constructor.name,
|
||||
recordId: model.get(model.constructor.primaryKeyAttribute),
|
||||
createdAt: model.get('updatedAt'),
|
||||
userId: currentUserId,
|
||||
changes,
|
||||
},
|
||||
transaction,
|
||||
hooks: false,
|
||||
});
|
||||
// if (!options.transaction) {
|
||||
// await transaction.commit();
|
||||
// }
|
||||
} catch (error) {
|
||||
// if (!options.transaction) {
|
||||
// await transaction.rollback();
|
||||
// }
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -4,9 +4,9 @@ import { afterCreate, afterDestroy, afterUpdate } from './hooks';
|
||||
|
||||
export default class PluginActionLogs extends Plugin {
|
||||
async beforeLoad() {
|
||||
this.db.on('afterCreate', afterCreate);
|
||||
this.db.on('afterUpdate', afterUpdate);
|
||||
this.db.on('afterDestroy', afterDestroy);
|
||||
this.db.on('afterCreate', afterCreate(this.app));
|
||||
this.db.on('afterUpdate', afterUpdate(this.app));
|
||||
this.db.on('afterDestroy', afterDestroy(this.app));
|
||||
}
|
||||
|
||||
async load() {
|
||||
|
@ -0,0 +1,16 @@
|
||||
import { Migration } from '@nocobase/database';
|
||||
|
||||
export default class AlertSubTableMigration extends Migration {
|
||||
versionRange = '<=0.7.0-alpha.83';
|
||||
|
||||
async up() {
|
||||
const repository = this.context.db.getRepository('fields');
|
||||
const fields = await repository.find();
|
||||
for (const field of fields) {
|
||||
if (field.get('interface') === 'subTable') {
|
||||
field.set('interface', 'o2m');
|
||||
await field.save();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -9,6 +9,7 @@ import {
|
||||
beforeCreateForReverseField,
|
||||
beforeInitOptions
|
||||
} from './hooks';
|
||||
import AlertSubTableMigration from './migrations/20220613103214-alert-sub-table';
|
||||
import { CollectionModel, FieldModel } from './models';
|
||||
|
||||
export class CollectionManagerPlugin extends Plugin {
|
||||
@ -18,6 +19,11 @@ export class CollectionManagerPlugin extends Plugin {
|
||||
FieldModel,
|
||||
});
|
||||
|
||||
this.db.addMigration({
|
||||
name: 'collection-manager/20220613103214-alert-sub-table',
|
||||
migration: AlertSubTableMigration,
|
||||
});
|
||||
|
||||
this.app.db.registerRepositories({
|
||||
CollectionRepository,
|
||||
});
|
||||
@ -244,7 +250,7 @@ export class CollectionManagerPlugin extends Plugin {
|
||||
const collection: Collection = ctx.db.getCollection(collectionName);
|
||||
if (collection) {
|
||||
for (const [, field] of collection.fields) {
|
||||
if (field.options.interface === 'subTable') {
|
||||
if (['subTable', 'o2m'].includes(field.options.interface)) {
|
||||
updateAssociationValues.push(field.name);
|
||||
}
|
||||
}
|
||||
@ -255,7 +261,7 @@ export class CollectionManagerPlugin extends Plugin {
|
||||
const collection: Collection = ctx.db.getCollection(association?.target);
|
||||
if (collection) {
|
||||
for (const [, field] of collection.fields) {
|
||||
if (field.options.interface === 'subTable') {
|
||||
if (['subTable', 'o2m'].includes(field.options.interface)) {
|
||||
updateAssociationValues.push(field.name);
|
||||
}
|
||||
}
|
||||
|
@ -21,6 +21,10 @@ export default defineCollection({
|
||||
type: 'string',
|
||||
name: 'associationName',
|
||||
},
|
||||
{
|
||||
type: 'string',
|
||||
name: 'resourceName',
|
||||
},
|
||||
{
|
||||
type: 'belongsTo',
|
||||
name: 'uiSchema',
|
||||
|
@ -52,7 +52,7 @@ export default {
|
||||
},
|
||||
},
|
||||
{
|
||||
interface: 'linkTo',
|
||||
interface: 'm2m',
|
||||
type: 'belongsToMany',
|
||||
name: 'roles',
|
||||
target: 'roles',
|
||||
|
Loading…
Reference in New Issue
Block a user