feat: add association tab
This commit is contained in:
parent
32774657d7
commit
629a4de173
@ -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: '时间',
|
||||
|
@ -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'), {}]);
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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 (
|
||||
<>
|
||||
<Select disabled={disabled} notFoundContent={loading ? <Spin/> : undefined} allowClear loading={loading} value={value} onChange={onChange}>
|
||||
{!loading && data.map(item => (<Select.Option value={item[valueField]}>{item[labelField]}</Select.Option>))}
|
||||
<Select
|
||||
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>
|
||||
</>
|
||||
);
|
||||
|
@ -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 {
|
||||
|
@ -130,7 +130,7 @@ function toFlat(items = []): Array<any> {
|
||||
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 (
|
||||
<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}/>
|
||||
</a>
|
||||
);
|
||||
|
@ -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) => {
|
||||
<SchemaForm
|
||||
colon={true}
|
||||
layout={'vertical'}
|
||||
initialValues={data}
|
||||
// 暂时先这么处理,如果有 associatedKey 注入表单里
|
||||
initialValues={{associatedKey, ...data}}
|
||||
actions={actions}
|
||||
schema={{
|
||||
type: 'object',
|
||||
|
@ -45,6 +45,7 @@ export default function ViewFactory(props: ViewProps) {
|
||||
associatedName,
|
||||
associatedKey,
|
||||
resourceName,
|
||||
resourceTarget,
|
||||
viewName,
|
||||
mode,
|
||||
reference,
|
||||
@ -59,9 +60,9 @@ export default function ViewFactory(props: ViewProps) {
|
||||
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) {
|
||||
return <Spin/>;
|
||||
|
@ -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',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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 = {
|
||||
|
Loading…
Reference in New Issue
Block a user