From 629a4de173f2b1aca0c329f2a54b3fc8084d875b Mon Sep 17 00:00:00 2001 From: chenos Date: Fri, 25 Dec 2020 16:15:58 +0800 Subject: [PATCH] feat: add association tab --- packages/app/src/api/collections/example.ts | 14 +++ packages/app/src/api/index.ts | 14 +-- .../app/src/components/actions/Create.tsx | 7 +- .../app/src/components/actions/Filter.tsx | 7 +- .../app/src/components/actions/Update.tsx | 7 +- .../form.fields/remote-select/index.tsx | 22 +++- .../CollectionLoader/CollectionTabPane.tsx | 9 +- .../app/src/components/views/Field/index.tsx | 4 +- .../src/components/views/Form/DrawerForm.tsx | 5 +- packages/app/src/components/views/index.tsx | 5 +- .../src/collections/tabs.ts | 105 ++++++++++++++++-- .../src/models/collection.ts | 15 +++ .../plugin-pages/src/actions/getCollection.ts | 30 ++--- 13 files changed, 195 insertions(+), 49 deletions(-) diff --git a/packages/app/src/api/collections/example.ts b/packages/app/src/api/collections/example.ts index 8bd99f6a9..aa1063839 100644 --- a/packages/app/src/api/collections/example.ts +++ b/packages/app/src/api/collections/example.ts @@ -2,6 +2,7 @@ import { TableOptions } from '@nocobase/database'; export default { title: '示例', + name: 'examples', showInDataMenu: true, // createdBy: true, // updatedBy: true, @@ -9,6 +10,7 @@ export default { { interface: 'string', title: '单行文本', + name: 'title', component: { showInTable: true, showInDetail: true, @@ -163,6 +165,18 @@ export default { showInForm: true, }, }, + { + interface: 'linkTo', + type: 'belongsToMany', + title: '关联1', + target: 'examples', + labelField: 'title', + component: { + showInTable: true, + showInDetail: true, + showInForm: true, + }, + }, { interface: 'time', title: '时间', diff --git a/packages/app/src/api/index.ts b/packages/app/src/api/index.ts index b3ee0bf61..521adbc69 100644 --- a/packages/app/src/api/index.ts +++ b/packages/app/src/api/index.ts @@ -37,6 +37,9 @@ const api = Api.create({ }, }); +api.resourcer.use(associated); +api.resourcer.registerActionHandlers({...actions.common, ...actions.associate}); + api.resourcer.use(async (ctx: actions.Context, next) => { const token = ctx.get('Authorization').replace(/^Bearer\s+/gi, ''); // console.log('user check', ctx.action.params.actionName); @@ -59,8 +62,8 @@ api.resourcer.use(async (ctx: actions.Context, next) => { }); api.resourcer.use(async (ctx: actions.Context, next) => { - const { resourceName, fields = {} } = ctx.action.params; - const table = ctx.db.getTable(resourceName); + const { resourceField, resourceName, fields = {} } = ctx.action.params; + const table = ctx.db.getTable(resourceField ? resourceField.options.target : resourceName); // ctx.state.developerMode = {[Op.not]: null}; ctx.state.developerMode = false; if (table && table.hasField('developerMode') && ctx.state.developerMode === false) { @@ -89,13 +92,13 @@ api.resourcer.use(async (ctx: actions.Context, next) => { }); api.resourcer.use(async (ctx: actions.Context, next) => { - const { resourceName, viewName, filter } = ctx.action.params; + const { resourceField, resourceName, viewName, filter } = ctx.action.params; // TODO: 需要补充默认视图的情况 let view: any; if (viewName) { view = await ctx.db.getModel('views').findOne({ where: { - collection_name: resourceName, + collection_name: resourceField ? resourceField.options.target : resourceName, name: viewName, } }); @@ -109,9 +112,6 @@ api.resourcer.use(async (ctx: actions.Context, next) => { await next(); }); -api.resourcer.use(associated); -api.resourcer.registerActionHandlers({...actions.common, ...actions.associate}); - api.registerPlugin('plugin-collections', [path.resolve(__dirname, '../../../plugin-collections'), {}]); api.registerPlugin('plugin-pages', [path.resolve(__dirname, '../../../plugin-pages'), {}]); api.registerPlugin('plugin-users', [path.resolve(__dirname, '../../../plugin-users'), {}]); diff --git a/packages/app/src/components/actions/Create.tsx b/packages/app/src/components/actions/Create.tsx index 1aecd56e9..a533b9a2a 100644 --- a/packages/app/src/components/actions/Create.tsx +++ b/packages/app/src/components/actions/Create.tsx @@ -6,12 +6,13 @@ export function Create(props) { console.log(props); const { title, viewName, collection_name } = props.schema; const { activeTab = {}, item = {}, associatedName, associatedKey } = props; - const { association } = activeTab; + const { associationField } = activeTab; const params = {}; - if (association) { - params['resourceName'] = association; + if (associationField && associationField.target) { + params['resourceName'] = associationField.name; + params['resourceTarget'] = associationField.target; params['associatedName'] = associatedName; params['associatedKey'] = associatedKey; } else { diff --git a/packages/app/src/components/actions/Filter.tsx b/packages/app/src/components/actions/Filter.tsx index 9dc00fd8e..1503c1c4c 100644 --- a/packages/app/src/components/actions/Filter.tsx +++ b/packages/app/src/components/actions/Filter.tsx @@ -8,12 +8,13 @@ export function Filter(props) { const [visible, setVisible] = useState(false); const { title, viewName, collection_name } = props.schema; const { activeTab = {}, item = {}, associatedName, associatedKey } = props; - const { association } = activeTab; + const { associationField } = activeTab; const params = {}; - if (association) { - params['resourceName'] = association; + if (associationField && associationField.target) { + params['resourceName'] = associationField.target; + params['resourceTarget'] = associationField.target; params['associatedName'] = associatedName; params['associatedKey'] = associatedKey; } else { diff --git a/packages/app/src/components/actions/Update.tsx b/packages/app/src/components/actions/Update.tsx index e837fd522..d5510f4fa 100644 --- a/packages/app/src/components/actions/Update.tsx +++ b/packages/app/src/components/actions/Update.tsx @@ -5,12 +5,13 @@ import ViewFactory from '@/components/views'; export function Update(props) { const { title, viewName, resourceName, collection_name } = props.schema; const { resourceKey, activeTab = {}, item = {} } = props; - const { association } = activeTab; + const { associationField } = activeTab; const params = {}; - if (association) { - params['resourceName'] = association; + if (associationField && associationField.target) { + params['resourceName'] = associationField.target; + params['resourceTarget'] = associationField.target; params['associatedName'] = resourceName; params['associatedKey'] = item.itemId; } else { diff --git a/packages/app/src/components/form.fields/remote-select/index.tsx b/packages/app/src/components/form.fields/remote-select/index.tsx index 07bbaa1dc..103aefb8f 100644 --- a/packages/app/src/components/form.fields/remote-select/index.tsx +++ b/packages/app/src/components/form.fields/remote-select/index.tsx @@ -14,18 +14,34 @@ import api from '@/api-client'; import { Spin } from '@nocobase/client' function RemoteSelectComponent(props) { - const { value, onChange, disabled, resourceName, associatedKey, labelField, valueField } = props; + const { value, onChange, disabled, resourceName, associatedKey, filter, labelField, valueField = 'id', objectValue } = props; const { data = [], loading = true } = useRequest(() => { return api.resource(resourceName).list({ associatedKey, + filter, }); }, { refreshDeps: [resourceName, associatedKey] }); return ( <> - : undefined} + allowClear + loading={loading} + value={value && typeof value === 'object' ? value[valueField] : value} + onChange={(value, option) => { + if (value === null || typeof value === 'undefined') { + onChange(undefined); + return; + } + // @ts-ignore + const item = option.item; + onChange(objectValue ? item : value); + }} + > + {!loading && data.map(item => ({item[labelField]}))} ); diff --git a/packages/app/src/components/pages/CollectionLoader/CollectionTabPane.tsx b/packages/app/src/components/pages/CollectionLoader/CollectionTabPane.tsx index c19a7e3c0..d09ff5120 100644 --- a/packages/app/src/components/pages/CollectionLoader/CollectionTabPane.tsx +++ b/packages/app/src/components/pages/CollectionLoader/CollectionTabPane.tsx @@ -5,13 +5,14 @@ import { useRequest, request, Spin } from '@nocobase/client'; export function CollectionTabPane(props) { const { loading, pageInfo = {}, activeTab = {}, item = {} } = props; - const { viewName, association, collection_name, field = {} } = activeTab; - const { sourceKey = 'id' } = field; + const { viewName, associationField = {}, collection_name, field = {} } = activeTab; + const { name, target, sourceKey = 'id' } = associationField || {}; const params = {}; - if (association) { - params['resourceName'] = association; + if (target) { + params['resourceName'] = name; + params['resourceTarget'] = target; params['associatedName'] = collection_name; params['associatedKey'] = pageInfo[sourceKey] || item.itemId; } else { diff --git a/packages/app/src/components/views/Field/index.tsx b/packages/app/src/components/views/Field/index.tsx index 25b219ace..b670daa6e 100644 --- a/packages/app/src/components/views/Field/index.tsx +++ b/packages/app/src/components/views/Field/index.tsx @@ -130,7 +130,7 @@ function toFlat(items = []): Array { export function DataSourceField(props: any) { const { schema: { dataSource = [] }, value } = props; const items = toFlat(dataSource); - console.log(items); + // console.log(items); if (isEmpty(value)) { return null; } @@ -243,7 +243,7 @@ export function AttachmentFieldItem(props: any) { exclude: ['.png', '.jpg', '.jpeg', '.gif'] }) return ( - + e.stopPropagation()} className={'attachment-field-item'} target={'_blank'} href={url}> {title} ); diff --git a/packages/app/src/components/views/Form/DrawerForm.tsx b/packages/app/src/components/views/Form/DrawerForm.tsx index 5be26b1de..bdce618d1 100644 --- a/packages/app/src/components/views/Form/DrawerForm.tsx +++ b/packages/app/src/components/views/Form/DrawerForm.tsx @@ -36,7 +36,7 @@ export const DrawerForm = forwardRef((props: any, ref) => { const [resourceKey, setResourceKey] = useState(props.resourceKey); const [visible, setVisible] = useState(false); const name = associatedName ? `${associatedName}.${resourceName}` : resourceName; - const { data, run, loading } = useRequest((resourceKey) => { + const { data = {}, run, loading } = useRequest((resourceKey) => { setResourceKey(resourceKey); return api.resource(name).get({ resourceKey, @@ -89,7 +89,8 @@ export const DrawerForm = forwardRef((props: any, ref) => { ; diff --git a/packages/plugin-collections/src/collections/tabs.ts b/packages/plugin-collections/src/collections/tabs.ts index c92d0ed04..29dbd23e9 100644 --- a/packages/plugin-collections/src/collections/tabs.ts +++ b/packages/plugin-collections/src/collections/tabs.ts @@ -64,23 +64,114 @@ export default { showInDetail: true, showInForm: true, "x-linkages": [ + // { + // "type": "value:visible", + // "target": "association", + // "condition": "{{ $self.value === 'association' }}" + // }, { - "type": "value:visible", - "target": "association", - "condition": "{{ $self.value === 'association' }}" + type: "value:visible", + target: "associationField", + condition: "{{ $self.value === 'association' }}" + }, + // { + // type: "value:schema", + // target: "association", + // condition: "{{ $self.value === 'association' }}", + // schema: { + // "x-component-props": { + // "associatedKey": "{{ $form.values && $form.values.associatedKey }}" + // }, + // }, + // }, + { + type: "value:schema", + target: "associationField", + condition: "{{ $self.value === 'association' }}", + schema: { + "x-component-props": { + associatedKey: "{{ $form.values && $form.values.associatedKey }}" + }, + }, + }, + ], + }, + }, + // { + // interface: 'string', + // type: 'string', + // name: 'association', + // title: '相关数据', + // component: { + // type: 'remoteSelect', + // showInDetail: true, + // showInForm: true, + // 'x-component-props': { + // resourceName: 'collections.fields', + // labelField: 'title', + // valueField: 'name', + // filter: { + // interface: 'linkTo', + // }, + // }, + // }, + // }, + { + interface: 'linkTo', + type: 'belongsTo', + name: 'associationField', + target: 'fields', + title: '相关数据表', + labelField: 'title', + // valueField: 'name', + component: { + type: 'remoteSelect', + showInDetail: true, + showInForm: true, + 'x-component-props': { + resourceName: 'collections.fields', + labelField: 'title', + // valueField: 'name', + objectValue: true, + filter: { + interface: 'linkTo', + }, + }, + "x-linkages": [ + { + type: "value:visible", + target: "viewName", + condition: "{{ !!$self.value }}" + }, + { + type: "value:schema", + target: "viewName", + condition: "{{ !!$self.value }}", + schema: { + "x-component-props": { + associatedKey: "{{ $self.value.target }}" + }, + }, }, ], }, }, { interface: 'string', - type: 'virtual', - name: 'association', - title: '相关数据表', + type: 'string', + name: 'viewName', + title: '视图', + labelField: 'title', + // valueField: 'name', component: { - type: 'drawerSelect', + type: 'remoteSelect', showInDetail: true, showInForm: true, + 'x-component-props': { + resourceName: 'collections.views', + labelField: 'title', + valueField: 'name', + }, }, }, { diff --git a/packages/plugin-collections/src/models/collection.ts b/packages/plugin-collections/src/models/collection.ts index c1cbe4707..771364eec 100644 --- a/packages/plugin-collections/src/models/collection.ts +++ b/packages/plugin-collections/src/models/collection.ts @@ -166,6 +166,7 @@ export class CollectionModel extends BaseModel { } const Model = this.database.getModel(key); let ids = []; + const View = this.database.getModel('views'); for (const index in data[key]) { if (key === 'fields') { ids = await Field.import(data[key], { @@ -204,6 +205,20 @@ export class CollectionModel extends BaseModel { }, options); } if (model) { + if (key === 'tabs') { + let associationField; + if (item.association) { + associationField = await Field.findOne({ + where: { + name: item.association, + collection_name: collection.name, + }, + }); + await model.updateAssociations({ + associationField: associationField.id, + }); + } + } ids.push(model.id); } } diff --git a/packages/plugin-pages/src/actions/getCollection.ts b/packages/plugin-pages/src/actions/getCollection.ts index 9d6c0c272..467720426 100644 --- a/packages/plugin-pages/src/actions/getCollection.ts +++ b/packages/plugin-pages/src/actions/getCollection.ts @@ -16,13 +16,17 @@ export default async (ctx, next) => { plain: true, }); collection.setDataValue('defaultViewName', defaultView.get('name')); - const tabs = await collection.getTabs({ - where: { + const options = Tab.parseApiJson({ + filter: { enabled: true, developerMode: ctx.state.developerMode, }, - order: [['sort', 'asc']], - }) as Model[]; + fields: { + appends: ['associationField'], + }, + sort: ['sort'], + }); + const tabs = await collection.getTabs(options) as Model[]; const tabItems = []; for (const tab of tabs) { const itemTab = { @@ -31,15 +35,15 @@ export default async (ctx, next) => { if (itemTab.type === 'details' && !itemTab.viewName) { itemTab.viewName = 'details'; } - if (itemTab.type == 'association') { - itemTab.field = await collection.getFields({ - where: { - name: itemTab.association, - }, - limit: 1, - plain: true, - }); - } + // if (itemTab.type == 'association') { + // itemTab.field = await collection.getFields({ + // where: { + // name: itemTab.association, + // }, + // limit: 1, + // plain: true, + // }); + // } tabItems.push(itemTab); } ctx.body = {