feat: add association tab
This commit is contained in:
parent
32774657d7
commit
629a4de173
@ -2,6 +2,7 @@ import { TableOptions } from '@nocobase/database';
|
|||||||
|
|
||||||
export default {
|
export default {
|
||||||
title: '示例',
|
title: '示例',
|
||||||
|
name: 'examples',
|
||||||
showInDataMenu: true,
|
showInDataMenu: true,
|
||||||
// createdBy: true,
|
// createdBy: true,
|
||||||
// updatedBy: true,
|
// updatedBy: true,
|
||||||
@ -9,6 +10,7 @@ export default {
|
|||||||
{
|
{
|
||||||
interface: 'string',
|
interface: 'string',
|
||||||
title: '单行文本',
|
title: '单行文本',
|
||||||
|
name: 'title',
|
||||||
component: {
|
component: {
|
||||||
showInTable: true,
|
showInTable: true,
|
||||||
showInDetail: true,
|
showInDetail: true,
|
||||||
@ -163,6 +165,18 @@ export default {
|
|||||||
showInForm: true,
|
showInForm: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
interface: 'linkTo',
|
||||||
|
type: 'belongsToMany',
|
||||||
|
title: '关联1',
|
||||||
|
target: 'examples',
|
||||||
|
labelField: 'title',
|
||||||
|
component: {
|
||||||
|
showInTable: true,
|
||||||
|
showInDetail: true,
|
||||||
|
showInForm: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
interface: 'time',
|
interface: 'time',
|
||||||
title: '时间',
|
title: '时间',
|
||||||
|
@ -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) => {
|
api.resourcer.use(async (ctx: actions.Context, next) => {
|
||||||
const token = ctx.get('Authorization').replace(/^Bearer\s+/gi, '');
|
const token = ctx.get('Authorization').replace(/^Bearer\s+/gi, '');
|
||||||
// console.log('user check', ctx.action.params.actionName);
|
// 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) => {
|
api.resourcer.use(async (ctx: actions.Context, next) => {
|
||||||
const { resourceName, fields = {} } = ctx.action.params;
|
const { resourceField, resourceName, fields = {} } = ctx.action.params;
|
||||||
const table = ctx.db.getTable(resourceName);
|
const table = ctx.db.getTable(resourceField ? resourceField.options.target : resourceName);
|
||||||
// ctx.state.developerMode = {[Op.not]: null};
|
// ctx.state.developerMode = {[Op.not]: null};
|
||||||
ctx.state.developerMode = false;
|
ctx.state.developerMode = false;
|
||||||
if (table && table.hasField('developerMode') && 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) => {
|
api.resourcer.use(async (ctx: actions.Context, next) => {
|
||||||
const { resourceName, viewName, filter } = ctx.action.params;
|
const { resourceField, resourceName, viewName, filter } = ctx.action.params;
|
||||||
// TODO: 需要补充默认视图的情况
|
// TODO: 需要补充默认视图的情况
|
||||||
let view: any;
|
let view: any;
|
||||||
if (viewName) {
|
if (viewName) {
|
||||||
view = await ctx.db.getModel('views').findOne({
|
view = await ctx.db.getModel('views').findOne({
|
||||||
where: {
|
where: {
|
||||||
collection_name: resourceName,
|
collection_name: resourceField ? resourceField.options.target : resourceName,
|
||||||
name: viewName,
|
name: viewName,
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -109,9 +112,6 @@ api.resourcer.use(async (ctx: actions.Context, next) => {
|
|||||||
await 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-collections', [path.resolve(__dirname, '../../../plugin-collections'), {}]);
|
||||||
api.registerPlugin('plugin-pages', [path.resolve(__dirname, '../../../plugin-pages'), {}]);
|
api.registerPlugin('plugin-pages', [path.resolve(__dirname, '../../../plugin-pages'), {}]);
|
||||||
api.registerPlugin('plugin-users', [path.resolve(__dirname, '../../../plugin-users'), {}]);
|
api.registerPlugin('plugin-users', [path.resolve(__dirname, '../../../plugin-users'), {}]);
|
||||||
|
@ -6,12 +6,13 @@ export function Create(props) {
|
|||||||
console.log(props);
|
console.log(props);
|
||||||
const { title, viewName, collection_name } = props.schema;
|
const { title, viewName, collection_name } = props.schema;
|
||||||
const { activeTab = {}, item = {}, associatedName, associatedKey } = props;
|
const { activeTab = {}, item = {}, associatedName, associatedKey } = props;
|
||||||
const { association } = activeTab;
|
const { associationField } = activeTab;
|
||||||
|
|
||||||
const params = {};
|
const params = {};
|
||||||
|
|
||||||
if (association) {
|
if (associationField && associationField.target) {
|
||||||
params['resourceName'] = association;
|
params['resourceName'] = associationField.name;
|
||||||
|
params['resourceTarget'] = associationField.target;
|
||||||
params['associatedName'] = associatedName;
|
params['associatedName'] = associatedName;
|
||||||
params['associatedKey'] = associatedKey;
|
params['associatedKey'] = associatedKey;
|
||||||
} else {
|
} else {
|
||||||
|
@ -8,12 +8,13 @@ export function Filter(props) {
|
|||||||
const [visible, setVisible] = useState(false);
|
const [visible, setVisible] = useState(false);
|
||||||
const { title, viewName, collection_name } = props.schema;
|
const { title, viewName, collection_name } = props.schema;
|
||||||
const { activeTab = {}, item = {}, associatedName, associatedKey } = props;
|
const { activeTab = {}, item = {}, associatedName, associatedKey } = props;
|
||||||
const { association } = activeTab;
|
const { associationField } = activeTab;
|
||||||
|
|
||||||
const params = {};
|
const params = {};
|
||||||
|
|
||||||
if (association) {
|
if (associationField && associationField.target) {
|
||||||
params['resourceName'] = association;
|
params['resourceName'] = associationField.target;
|
||||||
|
params['resourceTarget'] = associationField.target;
|
||||||
params['associatedName'] = associatedName;
|
params['associatedName'] = associatedName;
|
||||||
params['associatedKey'] = associatedKey;
|
params['associatedKey'] = associatedKey;
|
||||||
} else {
|
} else {
|
||||||
|
@ -5,12 +5,13 @@ import ViewFactory from '@/components/views';
|
|||||||
export function Update(props) {
|
export function Update(props) {
|
||||||
const { title, viewName, resourceName, collection_name } = props.schema;
|
const { title, viewName, resourceName, collection_name } = props.schema;
|
||||||
const { resourceKey, activeTab = {}, item = {} } = props;
|
const { resourceKey, activeTab = {}, item = {} } = props;
|
||||||
const { association } = activeTab;
|
const { associationField } = activeTab;
|
||||||
|
|
||||||
const params = {};
|
const params = {};
|
||||||
|
|
||||||
if (association) {
|
if (associationField && associationField.target) {
|
||||||
params['resourceName'] = association;
|
params['resourceName'] = associationField.target;
|
||||||
|
params['resourceTarget'] = associationField.target;
|
||||||
params['associatedName'] = resourceName;
|
params['associatedName'] = resourceName;
|
||||||
params['associatedKey'] = item.itemId;
|
params['associatedKey'] = item.itemId;
|
||||||
} else {
|
} else {
|
||||||
|
@ -14,18 +14,34 @@ import api from '@/api-client';
|
|||||||
import { Spin } from '@nocobase/client'
|
import { Spin } from '@nocobase/client'
|
||||||
|
|
||||||
function RemoteSelectComponent(props) {
|
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(() => {
|
const { data = [], loading = true } = useRequest(() => {
|
||||||
return api.resource(resourceName).list({
|
return api.resource(resourceName).list({
|
||||||
associatedKey,
|
associatedKey,
|
||||||
|
filter,
|
||||||
});
|
});
|
||||||
}, {
|
}, {
|
||||||
refreshDeps: [resourceName, associatedKey]
|
refreshDeps: [resourceName, associatedKey]
|
||||||
});
|
});
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Select disabled={disabled} notFoundContent={loading ? <Spin/> : undefined} allowClear loading={loading} value={value} onChange={onChange}>
|
<Select
|
||||||
{!loading && data.map(item => (<Select.Option value={item[valueField]}>{item[labelField]}</Select.Option>))}
|
disabled={disabled}
|
||||||
|
notFoundContent={loading ? <Spin/> : 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 => (<Select.Option item={item} value={item[valueField]}>{item[labelField]}</Select.Option>))}
|
||||||
</Select>
|
</Select>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
@ -5,13 +5,14 @@ import { useRequest, request, Spin } from '@nocobase/client';
|
|||||||
|
|
||||||
export function CollectionTabPane(props) {
|
export function CollectionTabPane(props) {
|
||||||
const { loading, pageInfo = {}, activeTab = {}, item = {} } = props;
|
const { loading, pageInfo = {}, activeTab = {}, item = {} } = props;
|
||||||
const { viewName, association, collection_name, field = {} } = activeTab;
|
const { viewName, associationField = {}, collection_name, field = {} } = activeTab;
|
||||||
const { sourceKey = 'id' } = field;
|
const { name, target, sourceKey = 'id' } = associationField || {};
|
||||||
|
|
||||||
const params = {};
|
const params = {};
|
||||||
|
|
||||||
if (association) {
|
if (target) {
|
||||||
params['resourceName'] = association;
|
params['resourceName'] = name;
|
||||||
|
params['resourceTarget'] = target;
|
||||||
params['associatedName'] = collection_name;
|
params['associatedName'] = collection_name;
|
||||||
params['associatedKey'] = pageInfo[sourceKey] || item.itemId;
|
params['associatedKey'] = pageInfo[sourceKey] || item.itemId;
|
||||||
} else {
|
} else {
|
||||||
|
@ -130,7 +130,7 @@ function toFlat(items = []): Array<any> {
|
|||||||
export function DataSourceField(props: any) {
|
export function DataSourceField(props: any) {
|
||||||
const { schema: { dataSource = [] }, value } = props;
|
const { schema: { dataSource = [] }, value } = props;
|
||||||
const items = toFlat(dataSource);
|
const items = toFlat(dataSource);
|
||||||
console.log(items);
|
// console.log(items);
|
||||||
if (isEmpty(value)) {
|
if (isEmpty(value)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -243,7 +243,7 @@ export function AttachmentFieldItem(props: any) {
|
|||||||
exclude: ['.png', '.jpg', '.jpeg', '.gif']
|
exclude: ['.png', '.jpg', '.jpeg', '.gif']
|
||||||
})
|
})
|
||||||
return (
|
return (
|
||||||
<a className={'attachment-field-item'} target={'_blank'} href={url}>
|
<a onClick={(e) => e.stopPropagation()} className={'attachment-field-item'} target={'_blank'} href={url}>
|
||||||
<img style={{marginRight: 5}} height={20} alt={title} title={title} src={img}/>
|
<img style={{marginRight: 5}} height={20} alt={title} title={title} src={img}/>
|
||||||
</a>
|
</a>
|
||||||
);
|
);
|
||||||
|
@ -36,7 +36,7 @@ export const DrawerForm = forwardRef((props: any, ref) => {
|
|||||||
const [resourceKey, setResourceKey] = useState(props.resourceKey);
|
const [resourceKey, setResourceKey] = useState(props.resourceKey);
|
||||||
const [visible, setVisible] = useState(false);
|
const [visible, setVisible] = useState(false);
|
||||||
const name = associatedName ? `${associatedName}.${resourceName}` : resourceName;
|
const name = associatedName ? `${associatedName}.${resourceName}` : resourceName;
|
||||||
const { data, run, loading } = useRequest((resourceKey) => {
|
const { data = {}, run, loading } = useRequest((resourceKey) => {
|
||||||
setResourceKey(resourceKey);
|
setResourceKey(resourceKey);
|
||||||
return api.resource(name).get({
|
return api.resource(name).get({
|
||||||
resourceKey,
|
resourceKey,
|
||||||
@ -89,7 +89,8 @@ export const DrawerForm = forwardRef((props: any, ref) => {
|
|||||||
<SchemaForm
|
<SchemaForm
|
||||||
colon={true}
|
colon={true}
|
||||||
layout={'vertical'}
|
layout={'vertical'}
|
||||||
initialValues={data}
|
// 暂时先这么处理,如果有 associatedKey 注入表单里
|
||||||
|
initialValues={{associatedKey, ...data}}
|
||||||
actions={actions}
|
actions={actions}
|
||||||
schema={{
|
schema={{
|
||||||
type: 'object',
|
type: 'object',
|
||||||
|
@ -45,6 +45,7 @@ export default function ViewFactory(props: ViewProps) {
|
|||||||
associatedName,
|
associatedName,
|
||||||
associatedKey,
|
associatedKey,
|
||||||
resourceName,
|
resourceName,
|
||||||
|
resourceTarget,
|
||||||
viewName,
|
viewName,
|
||||||
mode,
|
mode,
|
||||||
reference,
|
reference,
|
||||||
@ -59,9 +60,9 @@ export default function ViewFactory(props: ViewProps) {
|
|||||||
mode
|
mode
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
return api.resource(resourceName).getView(params);
|
return api.resource(resourceTarget||resourceName).getView(params);
|
||||||
}, {
|
}, {
|
||||||
refreshDeps: [associatedName, resourceName, viewName],
|
refreshDeps: [associatedName, resourceTarget, resourceName, viewName],
|
||||||
});
|
});
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return <Spin/>;
|
return <Spin/>;
|
||||||
|
@ -64,23 +64,114 @@ export default {
|
|||||||
showInDetail: true,
|
showInDetail: true,
|
||||||
showInForm: true,
|
showInForm: true,
|
||||||
"x-linkages": [
|
"x-linkages": [
|
||||||
|
// {
|
||||||
|
// "type": "value:visible",
|
||||||
|
// "target": "association",
|
||||||
|
// "condition": "{{ $self.value === 'association' }}"
|
||||||
|
// },
|
||||||
{
|
{
|
||||||
"type": "value:visible",
|
type: "value:visible",
|
||||||
"target": "association",
|
target: "associationField",
|
||||||
"condition": "{{ $self.value === 'association' }}"
|
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',
|
interface: 'string',
|
||||||
type: 'virtual',
|
type: 'string',
|
||||||
name: 'association',
|
name: 'viewName',
|
||||||
title: '相关数据表',
|
title: '视图',
|
||||||
|
labelField: 'title',
|
||||||
|
// valueField: 'name',
|
||||||
component: {
|
component: {
|
||||||
type: 'drawerSelect',
|
type: 'remoteSelect',
|
||||||
showInDetail: true,
|
showInDetail: true,
|
||||||
showInForm: true,
|
showInForm: true,
|
||||||
|
'x-component-props': {
|
||||||
|
resourceName: 'collections.views',
|
||||||
|
labelField: 'title',
|
||||||
|
valueField: 'name',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -166,6 +166,7 @@ export class CollectionModel extends BaseModel {
|
|||||||
}
|
}
|
||||||
const Model = this.database.getModel(key);
|
const Model = this.database.getModel(key);
|
||||||
let ids = [];
|
let ids = [];
|
||||||
|
const View = this.database.getModel('views');
|
||||||
for (const index in data[key]) {
|
for (const index in data[key]) {
|
||||||
if (key === 'fields') {
|
if (key === 'fields') {
|
||||||
ids = await Field.import(data[key], {
|
ids = await Field.import(data[key], {
|
||||||
@ -204,6 +205,20 @@ export class CollectionModel extends BaseModel {
|
|||||||
}, options);
|
}, options);
|
||||||
}
|
}
|
||||||
if (model) {
|
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);
|
ids.push(model.id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,13 +16,17 @@ export default async (ctx, next) => {
|
|||||||
plain: true,
|
plain: true,
|
||||||
});
|
});
|
||||||
collection.setDataValue('defaultViewName', defaultView.get('name'));
|
collection.setDataValue('defaultViewName', defaultView.get('name'));
|
||||||
const tabs = await collection.getTabs({
|
const options = Tab.parseApiJson({
|
||||||
where: {
|
filter: {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
developerMode: ctx.state.developerMode,
|
developerMode: ctx.state.developerMode,
|
||||||
},
|
},
|
||||||
order: [['sort', 'asc']],
|
fields: {
|
||||||
}) as Model[];
|
appends: ['associationField'],
|
||||||
|
},
|
||||||
|
sort: ['sort'],
|
||||||
|
});
|
||||||
|
const tabs = await collection.getTabs(options) as Model[];
|
||||||
const tabItems = [];
|
const tabItems = [];
|
||||||
for (const tab of tabs) {
|
for (const tab of tabs) {
|
||||||
const itemTab = {
|
const itemTab = {
|
||||||
@ -31,15 +35,15 @@ export default async (ctx, next) => {
|
|||||||
if (itemTab.type === 'details' && !itemTab.viewName) {
|
if (itemTab.type === 'details' && !itemTab.viewName) {
|
||||||
itemTab.viewName = 'details';
|
itemTab.viewName = 'details';
|
||||||
}
|
}
|
||||||
if (itemTab.type == 'association') {
|
// if (itemTab.type == 'association') {
|
||||||
itemTab.field = await collection.getFields({
|
// itemTab.field = await collection.getFields({
|
||||||
where: {
|
// where: {
|
||||||
name: itemTab.association,
|
// name: itemTab.association,
|
||||||
},
|
// },
|
||||||
limit: 1,
|
// limit: 1,
|
||||||
plain: true,
|
// plain: true,
|
||||||
});
|
// });
|
||||||
}
|
// }
|
||||||
tabItems.push(itemTab);
|
tabItems.push(itemTab);
|
||||||
}
|
}
|
||||||
ctx.body = {
|
ctx.body = {
|
||||||
|
Loading…
Reference in New Issue
Block a user