refactor: view collection options (#1643)
* feat: add schema prefix to view only if view name exists * chore: view-collection name without schema prefix * chore: skip list views already connected * chore: comment * fix: update field error * refactor: collection edit can not config fields * fix: viewName set * fix: transaction * refactor: viewname * chore: collection view name * refactor: viewname * chore: remove rename view collection name * refactor: loading wthen action submjit --------- Co-authored-by: katherinehhh <katherine_15995@163.com>
This commit is contained in:
parent
914260ed7e
commit
fff11df2ba
@ -1,6 +1,6 @@
|
|||||||
import { DownOutlined, PlusOutlined } from '@ant-design/icons';
|
import { DownOutlined, PlusOutlined } from '@ant-design/icons';
|
||||||
import { ArrayTable } from '@formily/antd';
|
import { ArrayTable } from '@formily/antd';
|
||||||
import { ISchema, useForm } from '@formily/react';
|
import { ISchema, useField, useForm } from '@formily/react';
|
||||||
import { uid } from '@formily/shared';
|
import { uid } from '@formily/shared';
|
||||||
import { Button, Dropdown, Menu } from 'antd';
|
import { Button, Dropdown, Menu } from 'antd';
|
||||||
import { cloneDeep } from 'lodash';
|
import { cloneDeep } from 'lodash';
|
||||||
@ -28,7 +28,7 @@ const getSchema = (schema, category, compile): ISchema => {
|
|||||||
properties['defaultValue']['x-decorator'] = 'FormItem';
|
properties['defaultValue']['x-decorator'] = 'FormItem';
|
||||||
}
|
}
|
||||||
const initialValue: any = {
|
const initialValue: any = {
|
||||||
name: schema.name !== 'view' ? `t_${uid()}` : null,
|
name: `t_${uid()}`,
|
||||||
template: schema.name,
|
template: schema.name,
|
||||||
view: schema.name === 'view',
|
view: schema.name === 'view',
|
||||||
category,
|
category,
|
||||||
@ -194,9 +194,12 @@ const useCreateCollection = (schema?: any) => {
|
|||||||
const { refreshCM } = useCollectionManager();
|
const { refreshCM } = useCollectionManager();
|
||||||
const ctx = useActionContext();
|
const ctx = useActionContext();
|
||||||
const { refresh } = useResourceActionContext();
|
const { refresh } = useResourceActionContext();
|
||||||
const { resource, collection } = useResourceContext();
|
const { resource } = useResourceContext();
|
||||||
|
const field = useField();
|
||||||
return {
|
return {
|
||||||
async run() {
|
async run() {
|
||||||
|
field.data = field.data || {};
|
||||||
|
field.data.loading = true;
|
||||||
await form.submit();
|
await form.submit();
|
||||||
const values = cloneDeep(form.values);
|
const values = cloneDeep(form.values);
|
||||||
if (schema?.events?.beforeSubmit) {
|
if (schema?.events?.beforeSubmit) {
|
||||||
@ -218,6 +221,7 @@ const useCreateCollection = (schema?: any) => {
|
|||||||
});
|
});
|
||||||
ctx.setVisible(false);
|
ctx.setVisible(false);
|
||||||
await form.reset();
|
await form.reset();
|
||||||
|
field.data.loading = false;
|
||||||
refresh();
|
refresh();
|
||||||
await refreshCM();
|
await refreshCM();
|
||||||
},
|
},
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { PlusOutlined } from '@ant-design/icons';
|
import { PlusOutlined } from '@ant-design/icons';
|
||||||
import { ArrayTable } from '@formily/antd';
|
import { ArrayTable } from '@formily/antd';
|
||||||
import { useForm } from '@formily/react';
|
import { useForm, useField } from '@formily/react';
|
||||||
import { uid } from '@formily/shared';
|
import { uid } from '@formily/shared';
|
||||||
import { Button, Dropdown, Menu } from 'antd';
|
import { Button, Dropdown, Menu } from 'antd';
|
||||||
import { cloneDeep } from 'lodash';
|
import { cloneDeep } from 'lodash';
|
||||||
@ -136,9 +136,12 @@ const useCreateCollectionField = () => {
|
|||||||
const ctx = useActionContext();
|
const ctx = useActionContext();
|
||||||
const { refresh } = useResourceActionContext();
|
const { refresh } = useResourceActionContext();
|
||||||
const { resource } = useResourceContext();
|
const { resource } = useResourceContext();
|
||||||
|
const field = useField();
|
||||||
return {
|
return {
|
||||||
async run() {
|
async run() {
|
||||||
await form.submit();
|
await form.submit();
|
||||||
|
field.data = field.data || {};
|
||||||
|
field.data.loading = true;
|
||||||
const values = cloneDeep(form.values);
|
const values = cloneDeep(form.values);
|
||||||
if (values.autoCreateReverseField) {
|
if (values.autoCreateReverseField) {
|
||||||
} else {
|
} else {
|
||||||
@ -148,6 +151,7 @@ const useCreateCollectionField = () => {
|
|||||||
await resource.create({ values });
|
await resource.create({ values });
|
||||||
ctx.setVisible(false);
|
ctx.setVisible(false);
|
||||||
await form.reset();
|
await form.reset();
|
||||||
|
field.data.loading = false;
|
||||||
refresh();
|
refresh();
|
||||||
await refreshCM();
|
await refreshCM();
|
||||||
},
|
},
|
||||||
|
@ -21,7 +21,7 @@ const getInterfaceOptions = (data, type) => {
|
|||||||
return interfaceOptions.filter((v) => v.children.length > 0);
|
return interfaceOptions.filter((v) => v.children.length > 0);
|
||||||
};
|
};
|
||||||
const PreviewCom = (props) => {
|
const PreviewCom = (props) => {
|
||||||
const { name, sources, viewName, schema } = props;
|
const { databaseView, viewName,sources, schema } = props;
|
||||||
const { data: fields } = useContext(ResourceActionContext);
|
const { data: fields } = useContext(ResourceActionContext);
|
||||||
const api = useAPIClient();
|
const api = useAPIClient();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@ -47,10 +47,10 @@ const PreviewCom = (props) => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
setSourceFields(data);
|
setSourceFields(data);
|
||||||
}, [sources, name]);
|
}, [sources, databaseView]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (name) {
|
if (databaseView) {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
api
|
api
|
||||||
.resource(`dbViews`)
|
.resource(`dbViews`)
|
||||||
@ -72,7 +72,7 @@ const PreviewCom = (props) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [name]);
|
}, [databaseView]);
|
||||||
|
|
||||||
const handleFieldChange = (record, index) => {
|
const handleFieldChange = (record, index) => {
|
||||||
dataSource.splice(index, 1, record);
|
dataSource.splice(index, 1, record);
|
||||||
@ -175,7 +175,7 @@ const PreviewCom = (props) => {
|
|||||||
const item = dataSource[index];
|
const item = dataSource[index];
|
||||||
return (
|
return (
|
||||||
<Input
|
<Input
|
||||||
defaultValue={record?.uiSchema?.title}
|
defaultValue={record?.uiSchema?.title||text}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
handleFieldChange({ ...item, uiSchema: { ...item?.uiSchema, title: e.target.value } }, index)
|
handleFieldChange({ ...item, uiSchema: { ...item?.uiSchema, title: e.target.value } }, index)
|
||||||
}
|
}
|
||||||
@ -204,7 +204,7 @@ const PreviewCom = (props) => {
|
|||||||
scroll={{ y: 300 }}
|
scroll={{ y: 300 }}
|
||||||
pagination={false}
|
pagination={false}
|
||||||
rowClassName="editable-row"
|
rowClassName="editable-row"
|
||||||
key={name}
|
key={viewName}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
@ -7,7 +7,7 @@ import { useAPIClient } from '../../../api-client';
|
|||||||
import { useCollectionManager } from '../../hooks/useCollectionManager';
|
import { useCollectionManager } from '../../hooks/useCollectionManager';
|
||||||
|
|
||||||
export const PreviewTable = (props) => {
|
export const PreviewTable = (props) => {
|
||||||
const { name, viewName, schema, fields } = props;
|
const { databaseView, schema, viewName, fields } = props;
|
||||||
const [previewColumns, setPreviewColumns] = useState([]);
|
const [previewColumns, setPreviewColumns] = useState([]);
|
||||||
const [previewData, setPreviewData] = useState([]);
|
const [previewData, setPreviewData] = useState([]);
|
||||||
const compile = useCompile();
|
const compile = useCompile();
|
||||||
@ -17,10 +17,10 @@ export const PreviewTable = (props) => {
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const form = useForm();
|
const form = useForm();
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (name) {
|
if (databaseView) {
|
||||||
getPreviewData();
|
getPreviewData();
|
||||||
}
|
}
|
||||||
}, [name]);
|
}, [databaseView]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const pColumns = formatPreviewColumns(fields);
|
const pColumns = formatPreviewColumns(fields);
|
||||||
@ -96,7 +96,7 @@ export const PreviewTable = (props) => {
|
|||||||
columns={previewColumns}
|
columns={previewColumns}
|
||||||
dataSource={previewData}
|
dataSource={previewData}
|
||||||
scroll={{ x: 1000, y: 300 }}
|
scroll={{ x: 1000, y: 300 }}
|
||||||
key={name}
|
key={viewName}
|
||||||
/>,
|
/>,
|
||||||
]}
|
]}
|
||||||
</div>
|
</div>
|
||||||
|
@ -3,7 +3,6 @@ import { ICollectionTemplate } from './types';
|
|||||||
import { PreviewFields } from './components/PreviewFields';
|
import { PreviewFields } from './components/PreviewFields';
|
||||||
import { PreviewTable } from './components/PreviewTable';
|
import { PreviewTable } from './components/PreviewTable';
|
||||||
|
|
||||||
|
|
||||||
export const view: ICollectionTemplate = {
|
export const view: ICollectionTemplate = {
|
||||||
name: 'view',
|
name: 'view',
|
||||||
title: '{{t("Connect to database view")}}',
|
title: '{{t("Connect to database view")}}',
|
||||||
@ -21,7 +20,8 @@ export const view: ICollectionTemplate = {
|
|||||||
'x-decorator': 'FormItem',
|
'x-decorator': 'FormItem',
|
||||||
'x-component': 'Input',
|
'x-component': 'Input',
|
||||||
},
|
},
|
||||||
name: {
|
|
||||||
|
databaseView: {
|
||||||
title: '{{t("Connect to database view")}}',
|
title: '{{t("Connect to database view")}}',
|
||||||
type: 'single',
|
type: 'single',
|
||||||
required: true,
|
required: true,
|
||||||
@ -30,12 +30,37 @@ export const view: ICollectionTemplate = {
|
|||||||
'x-reactions': ['{{useAsyncDataSource(loadDBViews)}}'],
|
'x-reactions': ['{{useAsyncDataSource(loadDBViews)}}'],
|
||||||
'x-disabled': '{{ !createOnly }}',
|
'x-disabled': '{{ !createOnly }}',
|
||||||
},
|
},
|
||||||
|
name: {
|
||||||
|
type: 'string',
|
||||||
|
title: '{{t("Collection name")}}',
|
||||||
|
required: true,
|
||||||
|
'x-disabled': '{{ !createOnly }}',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'Input',
|
||||||
|
'x-validator': 'uid',
|
||||||
|
description:
|
||||||
|
"{{t('Randomly generated and can be modified. Support letters, numbers and underscores, must start with an letter.')}}",
|
||||||
|
'x-reactions': {
|
||||||
|
dependencies: ['databaseView'],
|
||||||
|
when: '{{isPG}}',
|
||||||
|
fulfill: {
|
||||||
|
state: {
|
||||||
|
initialValue: '{{$deps[0]&&$deps[0].match(/^([^_]+)_(.*)$/)?.[2]}}',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
otherwise: {
|
||||||
|
state: {
|
||||||
|
value: null,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
'x-hidden': true,
|
'x-hidden': true,
|
||||||
'x-reactions': {
|
'x-reactions': {
|
||||||
dependencies: ['name'],
|
dependencies: ['databaseView'],
|
||||||
when: "{{isPG}}",
|
when: '{{isPG}}',
|
||||||
fulfill: {
|
fulfill: {
|
||||||
state: {
|
state: {
|
||||||
value: "{{$deps[0].split('_')?.[0]}}",
|
value: "{{$deps[0].split('_')?.[0]}}",
|
||||||
@ -52,8 +77,8 @@ export const view: ICollectionTemplate = {
|
|||||||
type: 'string',
|
type: 'string',
|
||||||
'x-hidden': true,
|
'x-hidden': true,
|
||||||
'x-reactions': {
|
'x-reactions': {
|
||||||
dependencies: ['name'],
|
dependencies: ['databaseView'],
|
||||||
when: "{{isPG}}",
|
when: '{{isPG}}',
|
||||||
fulfill: {
|
fulfill: {
|
||||||
state: {
|
state: {
|
||||||
value: '{{$deps[0].match(/^([^_]+)_(.*)$/)?.[2]}}',
|
value: '{{$deps[0].match(/^([^_]+)_(.*)$/)?.[2]}}',
|
||||||
@ -80,6 +105,7 @@ export const view: ICollectionTemplate = {
|
|||||||
fields: {
|
fields: {
|
||||||
type: 'array',
|
type: 'array',
|
||||||
'x-component': PreviewFields,
|
'x-component': PreviewFields,
|
||||||
|
'x-visible': '{{ createOnly }}',
|
||||||
'x-reactions': {
|
'x-reactions': {
|
||||||
dependencies: ['name'],
|
dependencies: ['name'],
|
||||||
fulfill: {
|
fulfill: {
|
||||||
@ -91,9 +117,10 @@ export const view: ICollectionTemplate = {
|
|||||||
},
|
},
|
||||||
preview: {
|
preview: {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
|
'x-visible': '{{ createOnly }}',
|
||||||
'x-component': PreviewTable,
|
'x-component': PreviewTable,
|
||||||
'x-reactions': {
|
'x-reactions': {
|
||||||
dependencies: ['name','fields'],
|
dependencies: ['name', 'fields'],
|
||||||
fulfill: {
|
fulfill: {
|
||||||
schema: {
|
schema: {
|
||||||
'x-component-props': '{{$form.values}}', //任意层次属性都支持表达式
|
'x-component-props': '{{$form.values}}', //任意层次属性都支持表达式
|
||||||
|
@ -47,6 +47,25 @@ SELECT * FROM numbers;
|
|||||||
const response = await agent.resource('dbViews').list();
|
const response = await agent.resource('dbViews').list();
|
||||||
expect(response.status).toBe(200);
|
expect(response.status).toBe(200);
|
||||||
expect(response.body.data.find((item) => item.name === testViewName)).toBeTruthy();
|
expect(response.body.data.find((item) => item.name === testViewName)).toBeTruthy();
|
||||||
|
|
||||||
|
await app.db.getCollection('collections').repository.create({
|
||||||
|
values: {
|
||||||
|
name: testViewName,
|
||||||
|
view: true,
|
||||||
|
schema: app.db.inDialect('postgres') ? 'public' : undefined,
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
name: 'numbers',
|
||||||
|
type: 'integer',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
context: {},
|
||||||
|
});
|
||||||
|
|
||||||
|
const response2 = await agent.resource('dbViews').list();
|
||||||
|
expect(response2.status).toBe(200);
|
||||||
|
expect(response2.body.data.find((item) => item.name === testViewName)).toBeFalsy();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should query views data', async () => {
|
it('should query views data', async () => {
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import Database, { Repository, ViewCollection } from '@nocobase/database';
|
import Database, { Repository, ViewCollection } from '@nocobase/database';
|
||||||
import Application from '@nocobase/server';
|
import Application from '@nocobase/server';
|
||||||
import { createApp } from '../index';
|
import { createApp } from '../index';
|
||||||
|
import { uid } from '@nocobase/utils';
|
||||||
|
|
||||||
describe('view collection', function () {
|
describe('view collection', function () {
|
||||||
let db: Database;
|
let db: Database;
|
||||||
@ -27,8 +28,67 @@ describe('view collection', function () {
|
|||||||
await app.destroy();
|
await app.destroy();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should save view collection in difference schema', async () => {
|
||||||
|
if (!db.inDialect('postgres')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const viewName = 'test_view';
|
||||||
|
const dbSchema = db.options.schema || 'public';
|
||||||
|
const randomSchema = `s_${uid(6)}`;
|
||||||
|
await db.sequelize.query(`CREATE SCHEMA IF NOT EXISTS ${randomSchema};`);
|
||||||
|
await db.sequelize.query(`CREATE OR REPLACE VIEW ${dbSchema}.${viewName} AS select 1+1 as "view_1"`);
|
||||||
|
await db.sequelize.query(`CREATE OR REPLACE VIEW ${randomSchema}.${viewName} AS select 1+1 as "view_2"`);
|
||||||
|
|
||||||
|
await collectionRepository.create({
|
||||||
|
values: {
|
||||||
|
name: viewName,
|
||||||
|
view: true,
|
||||||
|
fields: [{ type: 'string', name: 'view_1' }],
|
||||||
|
schema: dbSchema,
|
||||||
|
},
|
||||||
|
context: {},
|
||||||
|
});
|
||||||
|
|
||||||
|
const viewCollection = db.getCollection(viewName);
|
||||||
|
expect(viewCollection).toBeInstanceOf(ViewCollection);
|
||||||
|
|
||||||
|
let err;
|
||||||
|
try {
|
||||||
|
await collectionRepository.create({
|
||||||
|
values: {
|
||||||
|
name: viewName,
|
||||||
|
view: true,
|
||||||
|
fields: [{ type: 'string', name: 'view_2' }],
|
||||||
|
schema: randomSchema,
|
||||||
|
},
|
||||||
|
context: {},
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
err = e;
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(err).toBeTruthy();
|
||||||
|
|
||||||
|
await collectionRepository.create({
|
||||||
|
values: {
|
||||||
|
name: `${randomSchema}_${viewName}`,
|
||||||
|
view: true,
|
||||||
|
viewName: 'test_view',
|
||||||
|
fields: [{ type: 'string', name: 'view_2' }],
|
||||||
|
schema: randomSchema,
|
||||||
|
},
|
||||||
|
context: {},
|
||||||
|
});
|
||||||
|
|
||||||
|
const otherSchemaView = db.getCollection(`${randomSchema}_${viewName}`);
|
||||||
|
expect(otherSchemaView.options.viewName).toBe(viewName);
|
||||||
|
expect(otherSchemaView.options.schema).toBe(randomSchema);
|
||||||
|
});
|
||||||
|
|
||||||
it('should support view with dot field', async () => {
|
it('should support view with dot field', async () => {
|
||||||
const dropViewSQL = `DROP VIEW IF EXISTS test_view`;
|
const dropViewSQL = `DROP VIEW IF EXISTS test_view`;
|
||||||
|
|
||||||
await db.sequelize.query(dropViewSQL);
|
await db.sequelize.query(dropViewSQL);
|
||||||
const viewSQL = `CREATE VIEW test_view AS select 1+1 as "dot.results"`;
|
const viewSQL = `CREATE VIEW test_view AS select 1+1 as "dot.results"`;
|
||||||
await db.sequelize.query(viewSQL);
|
await db.sequelize.query(viewSQL);
|
||||||
|
@ -1,16 +1,5 @@
|
|||||||
import { Database } from '@nocobase/database';
|
import { Database } from '@nocobase/database';
|
||||||
|
|
||||||
export function beforeCreateForViewCollection(db: Database) {
|
export function beforeCreateForViewCollection(db: Database) {
|
||||||
return async (model, { transaction, context }) => {
|
return async (model, { transaction, context }) => {};
|
||||||
if (model.get('viewSQL')) {
|
|
||||||
const name = model.get('name');
|
|
||||||
const sql = model.get('viewSQL');
|
|
||||||
|
|
||||||
await db.sequelize.query(`CREATE OR REPLACE VIEW "${name}" AS ${sql}`, {
|
|
||||||
transaction,
|
|
||||||
});
|
|
||||||
|
|
||||||
model.set('viewName', name);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
@ -27,14 +27,30 @@ export default {
|
|||||||
|
|
||||||
await next();
|
await next();
|
||||||
},
|
},
|
||||||
async list(ctx, next) {
|
|
||||||
|
list: async function (ctx, next) {
|
||||||
const db = ctx.app.db as Database;
|
const db = ctx.app.db as Database;
|
||||||
const dbViews = await db.queryInterface.listViews();
|
const dbViews = await db.queryInterface.listViews();
|
||||||
ctx.body = dbViews.map((dbView) => {
|
|
||||||
return {
|
const viewCollections = Array.from(db.collections.values()).filter((collection) => collection.isView());
|
||||||
...dbView,
|
|
||||||
};
|
ctx.body = dbViews
|
||||||
});
|
.map((dbView) => {
|
||||||
|
return {
|
||||||
|
...dbView,
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.filter((dbView) => {
|
||||||
|
// if view is connected, skip
|
||||||
|
return !viewCollections.find((collection) => {
|
||||||
|
const viewName = dbView.name;
|
||||||
|
const schema = dbView.schema;
|
||||||
|
|
||||||
|
const collectionViewName = collection.options.viewName || collection.options.name;
|
||||||
|
|
||||||
|
return collectionViewName === viewName && collection.options.schema === schema;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
await next();
|
await next();
|
||||||
},
|
},
|
||||||
@ -46,7 +62,9 @@ export default {
|
|||||||
const limit = 1 * pageSize;
|
const limit = 1 * pageSize;
|
||||||
|
|
||||||
const sql = `SELECT *
|
const sql = `SELECT *
|
||||||
FROM ${ctx.app.db.utils.quoteTable(ctx.app.db.utils.addSchema(filterByTk, schema))} LIMIT ${limit} OFFSET ${offset}`;
|
FROM ${ctx.app.db.utils.quoteTable(
|
||||||
|
ctx.app.db.utils.addSchema(filterByTk, schema),
|
||||||
|
)} LIMIT ${limit} OFFSET ${offset}`;
|
||||||
|
|
||||||
ctx.body = await ctx.app.db.sequelize.query(sql, { type: 'SELECT' });
|
ctx.body = await ctx.app.db.sequelize.query(sql, { type: 'SELECT' });
|
||||||
await next();
|
await next();
|
||||||
|
@ -11,7 +11,7 @@ import {
|
|||||||
afterCreateForReverseField,
|
afterCreateForReverseField,
|
||||||
beforeCreateForReverseField,
|
beforeCreateForReverseField,
|
||||||
beforeDestroyForeignKey,
|
beforeDestroyForeignKey,
|
||||||
beforeInitOptions
|
beforeInitOptions,
|
||||||
} from './hooks';
|
} from './hooks';
|
||||||
|
|
||||||
import { InheritedCollection } from '@nocobase/database';
|
import { InheritedCollection } from '@nocobase/database';
|
||||||
@ -277,16 +277,22 @@ export class CollectionManagerPlugin extends Plugin {
|
|||||||
for (const field of castArray(fields)) {
|
for (const field of castArray(fields)) {
|
||||||
if (field.get('source')) {
|
if (field.get('source')) {
|
||||||
const [collectionSource, fieldSource] = field.get('source').split('.');
|
const [collectionSource, fieldSource] = field.get('source').split('.');
|
||||||
|
// find original field
|
||||||
const collectionField = this.app.db.getCollection(collectionSource).getField(fieldSource);
|
const collectionField = this.app.db.getCollection(collectionSource).getField(fieldSource);
|
||||||
|
|
||||||
const newOptions = {};
|
const newOptions = {};
|
||||||
|
|
||||||
|
// write original field options
|
||||||
lodash.merge(newOptions, collectionField.options);
|
lodash.merge(newOptions, collectionField.options);
|
||||||
|
|
||||||
|
// merge with current field options
|
||||||
lodash.mergeWith(newOptions, field.get(), (objValue, srcValue) => {
|
lodash.mergeWith(newOptions, field.get(), (objValue, srcValue) => {
|
||||||
if (srcValue === null) {
|
if (srcValue === null) {
|
||||||
return objValue;
|
return objValue;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// set final options
|
||||||
field.set('options', newOptions);
|
field.set('options', newOptions);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user