mirror of
https://gitee.com/nocobase/nocobase.git
synced 2025-05-05 13:39:24 +08:00
Merge branch 'main' into next
This commit is contained in:
commit
e1e2f7a83c
@ -7,9 +7,9 @@
|
|||||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { filter, unionBy, uniq } from 'lodash';
|
||||||
import type { CollectionFieldOptions, GetCollectionFieldPredicate } from '../../data-source';
|
import type { CollectionFieldOptions, GetCollectionFieldPredicate } from '../../data-source';
|
||||||
import { Collection } from '../../data-source/collection/Collection';
|
import { Collection } from '../../data-source/collection/Collection';
|
||||||
import _, { filter, unionBy, uniq } from 'lodash';
|
|
||||||
|
|
||||||
export class InheritanceCollectionMixin extends Collection {
|
export class InheritanceCollectionMixin extends Collection {
|
||||||
protected parentCollectionsName: string[];
|
protected parentCollectionsName: string[];
|
||||||
@ -22,6 +22,7 @@ export class InheritanceCollectionMixin extends Collection {
|
|||||||
protected parentCollectionFields: Record<string, CollectionFieldOptions[]> = {};
|
protected parentCollectionFields: Record<string, CollectionFieldOptions[]> = {};
|
||||||
protected allCollectionsInheritChain: string[];
|
protected allCollectionsInheritChain: string[];
|
||||||
protected inheritCollectionsChain: string[];
|
protected inheritCollectionsChain: string[];
|
||||||
|
protected inheritChain: string[];
|
||||||
protected foreignKeyFields: CollectionFieldOptions[];
|
protected foreignKeyFields: CollectionFieldOptions[];
|
||||||
|
|
||||||
getParentCollectionsName() {
|
getParentCollectionsName() {
|
||||||
@ -233,6 +234,43 @@ export class InheritanceCollectionMixin extends Collection {
|
|||||||
return this.inheritCollectionsChain;
|
return this.inheritCollectionsChain;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取所有祖先数据表和后代数据表,不包括兄弟表。用于下面这些地方:
|
||||||
|
* - 筛选区块链接数据区块时使用
|
||||||
|
*/
|
||||||
|
getInheritChain() {
|
||||||
|
if (this.inheritChain) {
|
||||||
|
return this.inheritChain.slice();
|
||||||
|
}
|
||||||
|
|
||||||
|
const ancestorChain = this.getInheritCollectionsChain();
|
||||||
|
const descendantNames = this.getChildrenCollectionsName();
|
||||||
|
|
||||||
|
// 构建最终的链,首先包含祖先链(包括自身)
|
||||||
|
const inheritChain = [...ancestorChain];
|
||||||
|
|
||||||
|
// 再添加直接后代及其后代,但不包括兄弟表
|
||||||
|
const addDescendants = (names: string[]) => {
|
||||||
|
for (const name of names) {
|
||||||
|
if (!inheritChain.includes(name)) {
|
||||||
|
inheritChain.push(name);
|
||||||
|
const childCollection = this.collectionManager.getCollection<InheritanceCollectionMixin>(name);
|
||||||
|
if (childCollection) {
|
||||||
|
// 递归添加每个后代的后代
|
||||||
|
const childrenNames = childCollection.getChildrenCollectionsName();
|
||||||
|
addDescendants(childrenNames);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 从当前集合的直接后代开始添加
|
||||||
|
addDescendants(descendantNames);
|
||||||
|
|
||||||
|
this.inheritChain = inheritChain;
|
||||||
|
return this.inheritChain;
|
||||||
|
}
|
||||||
|
|
||||||
getAllFields(predicate?: GetCollectionFieldPredicate) {
|
getAllFields(predicate?: GetCollectionFieldPredicate) {
|
||||||
if (this.allFields) {
|
if (this.allFields) {
|
||||||
return this.allFields.slice();
|
return this.allFields.slice();
|
||||||
|
@ -0,0 +1,189 @@
|
|||||||
|
import { Application } from '@nocobase/client';
|
||||||
|
import { CollectionManager } from '../../../data-source/collection/CollectionManager';
|
||||||
|
import { InheritanceCollectionMixin } from '../InheritanceCollectionMixin';
|
||||||
|
|
||||||
|
describe('InheritanceCollectionMixin', () => {
|
||||||
|
let app: Application;
|
||||||
|
let collectionManager: CollectionManager;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
app = new Application({
|
||||||
|
dataSourceManager: {
|
||||||
|
collectionMixins: [InheritanceCollectionMixin],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
collectionManager = app.getCollectionManager();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getInheritChain', () => {
|
||||||
|
it('should return itself when there are no ancestors or descendants', () => {
|
||||||
|
const options = {
|
||||||
|
name: 'test',
|
||||||
|
fields: [{ name: 'field1', interface: 'input' }],
|
||||||
|
};
|
||||||
|
|
||||||
|
collectionManager.addCollections([options]);
|
||||||
|
const collection = collectionManager.getCollection<InheritanceCollectionMixin>('test');
|
||||||
|
|
||||||
|
const inheritChain = collection.getInheritChain();
|
||||||
|
expect(inheritChain).toEqual(['test']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return a chain including all ancestor tables', () => {
|
||||||
|
// 创建三代数据表结构:grandparent -> parent -> child
|
||||||
|
const grandparentOptions = {
|
||||||
|
name: 'grandparent',
|
||||||
|
fields: [{ name: 'field1', interface: 'input' }],
|
||||||
|
};
|
||||||
|
const parentOptions = {
|
||||||
|
name: 'parent',
|
||||||
|
inherits: ['grandparent'],
|
||||||
|
fields: [{ name: 'field2', interface: 'input' }],
|
||||||
|
};
|
||||||
|
const childOptions = {
|
||||||
|
name: 'child',
|
||||||
|
inherits: ['parent'],
|
||||||
|
fields: [{ name: 'field3', interface: 'input' }],
|
||||||
|
};
|
||||||
|
|
||||||
|
// 先将所有集合添加到 collectionManager
|
||||||
|
collectionManager.addCollections([grandparentOptions, parentOptions, childOptions]);
|
||||||
|
|
||||||
|
// 获取最终的集合实例以调用方法
|
||||||
|
const child = collectionManager.getCollection<InheritanceCollectionMixin>('child');
|
||||||
|
|
||||||
|
// 测试 child 的继承链包含所有祖先表
|
||||||
|
const inheritChain = child.getInheritChain();
|
||||||
|
expect(inheritChain).toContain('child');
|
||||||
|
expect(inheritChain).toContain('parent');
|
||||||
|
expect(inheritChain).toContain('grandparent');
|
||||||
|
expect(inheritChain.length).toBe(3);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should include all descendant tables, but not sibling tables', () => {
|
||||||
|
// 创建具有兄弟和后代关系的数据表结构
|
||||||
|
// parent (祖先表)
|
||||||
|
// |-- child1 (子表)
|
||||||
|
// | |-- grandChild1 (孙表1)
|
||||||
|
// | |-- grandChild2 (孙表2)
|
||||||
|
// |-- child2 (兄弟表)
|
||||||
|
// |-- grandChild3 (兄弟的子表,不应该包括在测试集合的继承链中)
|
||||||
|
|
||||||
|
const collections = [
|
||||||
|
{
|
||||||
|
name: 'parent',
|
||||||
|
fields: [{ name: 'parentField', interface: 'input' }],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'child1',
|
||||||
|
inherits: ['parent'],
|
||||||
|
fields: [{ name: 'child1Field', interface: 'input' }],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'child2',
|
||||||
|
inherits: ['parent'],
|
||||||
|
fields: [{ name: 'child2Field', interface: 'input' }],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'grandChild1',
|
||||||
|
inherits: ['child1'],
|
||||||
|
fields: [{ name: 'grandChild1Field', interface: 'input' }],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'grandChild2',
|
||||||
|
inherits: ['child1'],
|
||||||
|
fields: [{ name: 'grandChild2Field', interface: 'input' }],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'grandChild3',
|
||||||
|
inherits: ['child2'],
|
||||||
|
fields: [{ name: 'grandChild3Field', interface: 'input' }],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
// 一次性添加所有集合
|
||||||
|
collectionManager.addCollections(collections);
|
||||||
|
|
||||||
|
// 获取要测试的集合实例
|
||||||
|
const child1 = collectionManager.getCollection<InheritanceCollectionMixin>('child1');
|
||||||
|
|
||||||
|
// 测试 child1 的继承链
|
||||||
|
const child1InheritChain = child1.getInheritChain();
|
||||||
|
|
||||||
|
// 应该包含自身、父表和子表
|
||||||
|
expect(child1InheritChain).toContain('child1');
|
||||||
|
expect(child1InheritChain).toContain('parent');
|
||||||
|
expect(child1InheritChain).toContain('grandChild1');
|
||||||
|
expect(child1InheritChain).toContain('grandChild2');
|
||||||
|
|
||||||
|
// 不应该包含兄弟表及其子表
|
||||||
|
expect(child1InheritChain).not.toContain('child2');
|
||||||
|
expect(child1InheritChain).not.toContain('grandChild3');
|
||||||
|
|
||||||
|
// 检查总数量是否正确 (parent, child1, grandChild1, grandChild2)
|
||||||
|
expect(child1InheritChain.length).toBe(4);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should properly handle multiple inheritance', () => {
|
||||||
|
// 创建多重继承的数据表结构
|
||||||
|
// parent1 parent2
|
||||||
|
// \ /
|
||||||
|
// \ /
|
||||||
|
// child
|
||||||
|
// |
|
||||||
|
// grandChild
|
||||||
|
|
||||||
|
const collections = [
|
||||||
|
{
|
||||||
|
name: 'parent1',
|
||||||
|
fields: [{ name: 'parent1Field', interface: 'input' }],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'parent2',
|
||||||
|
fields: [{ name: 'parent2Field', interface: 'input' }],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'child',
|
||||||
|
inherits: ['parent1', 'parent2'],
|
||||||
|
fields: [{ name: 'childField', interface: 'input' }],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'grandChild',
|
||||||
|
inherits: ['child'],
|
||||||
|
fields: [{ name: 'grandChildField', interface: 'input' }],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
// 一次性添加所有集合
|
||||||
|
collectionManager.addCollections(collections);
|
||||||
|
|
||||||
|
// 获取要测试的集合实例
|
||||||
|
const child = collectionManager.getCollection<InheritanceCollectionMixin>('child');
|
||||||
|
const grandChild = collectionManager.getCollection<InheritanceCollectionMixin>('grandChild');
|
||||||
|
|
||||||
|
// 测试 child 的继承链
|
||||||
|
const childInheritChain = child.getInheritChain();
|
||||||
|
|
||||||
|
// 应该包含自身、两个父表和子表
|
||||||
|
expect(childInheritChain).toContain('child');
|
||||||
|
expect(childInheritChain).toContain('parent1');
|
||||||
|
expect(childInheritChain).toContain('parent2');
|
||||||
|
expect(childInheritChain).toContain('grandChild');
|
||||||
|
|
||||||
|
// 检查总数量是否正确 (child, parent1, parent2, grandChild)
|
||||||
|
expect(childInheritChain.length).toBe(4);
|
||||||
|
|
||||||
|
// 测试 grandChild 的继承链
|
||||||
|
const grandChildInheritChain = grandChild.getInheritChain();
|
||||||
|
|
||||||
|
// 应该包含自身及所有祖先表
|
||||||
|
expect(grandChildInheritChain).toContain('grandChild');
|
||||||
|
expect(grandChildInheritChain).toContain('child');
|
||||||
|
expect(grandChildInheritChain).toContain('parent1');
|
||||||
|
expect(grandChildInheritChain).toContain('parent2');
|
||||||
|
|
||||||
|
// 检查总数量是否正确 (grandChild, child, parent1, parent2)
|
||||||
|
expect(grandChildInheritChain.length).toBe(4);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -29,7 +29,7 @@ export function useDataSourceManager() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取当前 collection 继承链路上的所有 collection
|
* 获取当前 collection 继承链路上的所有 collection(不包括兄弟表)
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export function useAllCollectionsInheritChainGetter() {
|
export function useAllCollectionsInheritChainGetter() {
|
||||||
@ -39,7 +39,7 @@ export function useAllCollectionsInheritChainGetter() {
|
|||||||
return dm
|
return dm
|
||||||
?.getDataSource(customDataSource)
|
?.getDataSource(customDataSource)
|
||||||
?.collectionManager?.getCollection<InheritanceCollectionMixin>(collectionName)
|
?.collectionManager?.getCollection<InheritanceCollectionMixin>(collectionName)
|
||||||
?.getAllCollectionsInheritChain();
|
?.getInheritChain();
|
||||||
},
|
},
|
||||||
[dm],
|
[dm],
|
||||||
);
|
);
|
||||||
|
@ -67,6 +67,108 @@ describe('getSupportFieldsByAssociation', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('getSupportFieldsByForeignKey', () => {
|
describe('getSupportFieldsByForeignKey', () => {
|
||||||
|
it('should return foreign key fields matching both name and target collection', () => {
|
||||||
|
const filterBlockCollection = {
|
||||||
|
fields: [
|
||||||
|
{ id: 1, name: 'field1', type: 'hasMany', foreignKey: 'fk1', target: 'collection1' },
|
||||||
|
{ id: 2, name: 'field2', type: 'hasMany', foreignKey: 'fk2', target: 'collection2' },
|
||||||
|
{ id: 3, name: 'field3', type: 'hasMany', foreignKey: 'fk3', target: 'collection3' },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const block = {
|
||||||
|
foreignKeyFields: [
|
||||||
|
{ id: 1, name: 'fk1', collectionName: 'collection1' },
|
||||||
|
{ id: 2, name: 'fk2', collectionName: 'collection2' },
|
||||||
|
{ id: 3, name: 'fk3', collectionName: 'collection3' },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = getSupportFieldsByForeignKey(filterBlockCollection as any, block as any);
|
||||||
|
|
||||||
|
expect(result).toEqual([
|
||||||
|
{ id: 1, name: 'fk1', collectionName: 'collection1' },
|
||||||
|
{ id: 2, name: 'fk2', collectionName: 'collection2' },
|
||||||
|
{ id: 3, name: 'fk3', collectionName: 'collection3' },
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should not return foreign key fields when target collection doesn't match", () => {
|
||||||
|
const filterBlockCollection = {
|
||||||
|
fields: [
|
||||||
|
{ id: 1, name: 'field1', type: 'hasMany', foreignKey: 'fk1', target: 'collection1' },
|
||||||
|
{ id: 2, name: 'field2', type: 'hasMany', foreignKey: 'fk2', target: 'collectionX' }, // target不匹配
|
||||||
|
{ id: 3, name: 'field3', type: 'hasMany', foreignKey: 'fk3', target: 'collection3' },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const block = {
|
||||||
|
foreignKeyFields: [
|
||||||
|
{ id: 1, name: 'fk1', collectionName: 'collection1' },
|
||||||
|
{ id: 2, name: 'fk2', collectionName: 'collection2' }, // 与field2的target不匹配
|
||||||
|
{ id: 3, name: 'fk3', collectionName: 'collection3' },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = getSupportFieldsByForeignKey(filterBlockCollection as any, block as any);
|
||||||
|
|
||||||
|
expect(result).toEqual([
|
||||||
|
{ id: 1, name: 'fk1', collectionName: 'collection1' },
|
||||||
|
{ id: 3, name: 'fk3', collectionName: 'collection3' },
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should filter out belongsTo type fields', () => {
|
||||||
|
const filterBlockCollection = {
|
||||||
|
fields: [
|
||||||
|
{ id: 1, name: 'field1', type: 'hasMany', foreignKey: 'fk1', target: 'collection1' },
|
||||||
|
{ id: 2, name: 'field2', type: 'belongsTo', foreignKey: 'fk2', target: 'collection2' }, // belongsTo类型
|
||||||
|
{ id: 3, name: 'field3', type: 'hasMany', foreignKey: 'fk3', target: 'collection3' },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const block = {
|
||||||
|
foreignKeyFields: [
|
||||||
|
{ id: 1, name: 'fk1', collectionName: 'collection1' },
|
||||||
|
{ id: 2, name: 'fk2', collectionName: 'collection2' },
|
||||||
|
{ id: 3, name: 'fk3', collectionName: 'collection3' },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = getSupportFieldsByForeignKey(filterBlockCollection as any, block as any);
|
||||||
|
|
||||||
|
expect(result).toEqual([
|
||||||
|
{ id: 1, name: 'fk1', collectionName: 'collection1' },
|
||||||
|
{ id: 3, name: 'fk3', collectionName: 'collection3' },
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle when both name and target collection match', () => {
|
||||||
|
const filterBlockCollection = {
|
||||||
|
fields: [
|
||||||
|
{ id: 1, name: 'field1', type: 'hasMany', foreignKey: 'fk1', target: 'collection1' },
|
||||||
|
{ id: 2, name: 'field2', type: 'hasOne', foreignKey: 'fk2', target: 'collection2' },
|
||||||
|
{ id: 3, name: 'field3', type: 'hasMany', foreignKey: 'fk3', target: 'wrongCollection' }, // 目标表不匹配
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const block = {
|
||||||
|
foreignKeyFields: [
|
||||||
|
{ id: 1, name: 'fk1', collectionName: 'collection1' },
|
||||||
|
{ id: 2, name: 'fk2', collectionName: 'collection2' },
|
||||||
|
{ id: 3, name: 'fk3', collectionName: 'collection3' }, // 与field3的target不匹配
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = getSupportFieldsByForeignKey(filterBlockCollection as any, block as any);
|
||||||
|
|
||||||
|
expect(result).toEqual([
|
||||||
|
{ id: 1, name: 'fk1', collectionName: 'collection1' },
|
||||||
|
{ id: 2, name: 'fk2', collectionName: 'collection2' },
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 保留原有的通用测试用例
|
||||||
it("should return all foreign key fields matching the filter block collection's foreign key properties", () => {
|
it("should return all foreign key fields matching the filter block collection's foreign key properties", () => {
|
||||||
const filterBlockCollection = {
|
const filterBlockCollection = {
|
||||||
fields: [
|
fields: [
|
||||||
|
@ -49,10 +49,14 @@ export const getSupportFieldsByAssociation = (inheritCollectionsChain: string[],
|
|||||||
|
|
||||||
export const getSupportFieldsByForeignKey = (filterBlockCollection: Collection, block: DataBlock) => {
|
export const getSupportFieldsByForeignKey = (filterBlockCollection: Collection, block: DataBlock) => {
|
||||||
return block.foreignKeyFields?.filter((foreignKeyField) => {
|
return block.foreignKeyFields?.filter((foreignKeyField) => {
|
||||||
return filterBlockCollection.fields.some(
|
return filterBlockCollection.fields.some((field) => {
|
||||||
(field) => field.type !== 'belongsTo' && field.foreignKey === foreignKeyField.name,
|
return (
|
||||||
|
field.type !== 'belongsTo' &&
|
||||||
|
field.foreignKey === foreignKeyField.name && // 1. 外键字段的 name 要一致
|
||||||
|
field.target === foreignKeyField.collectionName // 2. 关系字段的目标表要和外键的数据表一致
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -193,19 +197,21 @@ export const useFilterAPI = () => {
|
|||||||
|
|
||||||
const doFilter = useCallback(
|
const doFilter = useCallback(
|
||||||
(
|
(
|
||||||
value: any | ((target: FilterTarget['targets'][0], block: DataBlock) => any),
|
value: any | ((target: FilterTarget['targets'][0], block: DataBlock, sourceKey?: string) => any),
|
||||||
field: string | ((target: FilterTarget['targets'][0], block: DataBlock) => string) = 'id',
|
field: string | ((target: FilterTarget['targets'][0], block: DataBlock) => string) = 'id',
|
||||||
operator: string | ((target: FilterTarget['targets'][0]) => string) = '$eq',
|
operator: string | ((target: FilterTarget['targets'][0]) => string) = '$eq',
|
||||||
) => {
|
) => {
|
||||||
|
const currentBlock = dataBlocks.find((block) => block.uid === fieldSchema.parent['x-uid']);
|
||||||
dataBlocks.forEach((block) => {
|
dataBlocks.forEach((block) => {
|
||||||
|
let key = field as string;
|
||||||
const target = targets.find((target) => target.uid === block.uid);
|
const target = targets.find((target) => target.uid === block.uid);
|
||||||
if (!target) return;
|
if (!target) return;
|
||||||
|
|
||||||
if (_.isFunction(value)) {
|
if (_.isFunction(value)) {
|
||||||
value = value(target, block);
|
value = value(target, block, getSourceKey(currentBlock, target.field));
|
||||||
}
|
}
|
||||||
if (_.isFunction(field)) {
|
if (_.isFunction(field)) {
|
||||||
field = field(target, block);
|
key = field(target, block);
|
||||||
}
|
}
|
||||||
if (_.isFunction(operator)) {
|
if (_.isFunction(operator)) {
|
||||||
operator = operator(target);
|
operator = operator(target);
|
||||||
@ -219,7 +225,7 @@ export const useFilterAPI = () => {
|
|||||||
storedFilter[uid] = {
|
storedFilter[uid] = {
|
||||||
$and: [
|
$and: [
|
||||||
{
|
{
|
||||||
[field]: {
|
[key]: {
|
||||||
[operator]: value,
|
[operator]: value,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -248,7 +254,7 @@ export const useFilterAPI = () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
[dataBlocks, targets, uid],
|
[dataBlocks, targets, uid, fieldSchema],
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -268,3 +274,8 @@ export const isInFilterFormBlock = (fieldSchema: Schema) => {
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function getSourceKey(currentBlock: DataBlock, field: string) {
|
||||||
|
const associationField = currentBlock?.associatedFields?.find((item) => item.foreignKey === field);
|
||||||
|
return associationField?.sourceKey || field?.split?.('.')?.[1];
|
||||||
|
}
|
||||||
|
@ -54,3 +54,47 @@ export const hasEmptyValue = (objOrArr: object | any[]) => {
|
|||||||
export const nextTick = (fn: () => void) => {
|
export const nextTick = (fn: () => void) => {
|
||||||
setTimeout(fn);
|
setTimeout(fn);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通用树节点深度优先遍历函数
|
||||||
|
* @param {Object|Array} tree - 要遍历的树结构
|
||||||
|
* @param {Function} callback - 遍历每个节点时执行的回调函数,返回真值时停止遍历并返回当前节点
|
||||||
|
* @param {Object} options - 配置选项
|
||||||
|
* @param {string|Function} options.childrenKey - 子节点的属性名,默认为'children',也可以是一个函数
|
||||||
|
* @returns {any|undefined} - 找到的节点或undefined
|
||||||
|
*/
|
||||||
|
export function treeFind<T = any>(
|
||||||
|
tree: T | T[],
|
||||||
|
callback: (node: T) => boolean,
|
||||||
|
options: {
|
||||||
|
childrenKey?: string | ((node: T) => T[] | undefined);
|
||||||
|
} = {},
|
||||||
|
): T | undefined {
|
||||||
|
if (!tree) return undefined;
|
||||||
|
|
||||||
|
const { childrenKey = 'children' } = options;
|
||||||
|
|
||||||
|
// 处理根节点是数组的情况
|
||||||
|
const nodes = Array.isArray(tree) ? [...tree] : [tree];
|
||||||
|
|
||||||
|
// 深度优先搜索
|
||||||
|
for (const node of nodes) {
|
||||||
|
// 对当前节点调用回调函数
|
||||||
|
if (callback(node)) {
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取子节点
|
||||||
|
const children = typeof childrenKey === 'function' ? childrenKey(node) : (node as any)[childrenKey];
|
||||||
|
|
||||||
|
// 递归处理子节点
|
||||||
|
if (Array.isArray(children) && children.length > 0) {
|
||||||
|
const found = treeFind(children, callback, options);
|
||||||
|
if (found !== undefined) {
|
||||||
|
return found;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user