import { type ActiveRecord, type Pagination, QuerySort, resolveDynamicDomain } from '@oinone/kunlun-engine'; import type { ExpressionRunParam } from '@oinone/kunlun-expression'; import { SYSTEM_MODULE_NAME } from '@oinone/kunlun-meta'; import { GQL } from '@oinone/kunlun-request'; import { http, IQueryPageResult } from '@oinone/kunlun-service'; import { CastHelper, GraphqlHelper, type OioTreeNode, TreeHelper, TreeNode, uniqueKeyGenerator } from '@oinone/kunlun-shared'; import { cloneDeep } from 'lodash-es'; import type { CardCascaderItemData, TreeData, TreeNodeMetadata } from '../../typing'; type ResponseBody = { label: string; metadataKey: string; value: string; isLeaf: boolean; }; export interface TreeNodeResponseBody { label: string; value: string; key: string; parentKeys: string[]; metadataKey: string; isLeaf: boolean; } function buildMetadataGQLParameter( key: string | undefined, metadata: TreeNodeMetadata | undefined, options?: { disableSelfReferences?: boolean; expressionParameters?: ExpressionRunParam; } ) { if (!metadata) { return ''; } let { filter } = metadata; const expressionParameters = options?.expressionParameters; if (filter && expressionParameters) { filter = resolveDynamicDomain( filter, expressionParameters.activeRecord || expressionParameters.activeRecords?.[0] || {}, expressionParameters.rootRecord, expressionParameters.openerRecord ); } return `${key ? `${key}: ` : ''} { ${GraphqlHelper.buildStringGQLParameter('key', metadata.key)} ${GraphqlHelper.buildStringGQLParameter('model', metadata.model)} ${GraphqlHelper.buildStringGQLParameter('relModel', metadata.references?.model)} ${GraphqlHelper.buildStringGQLParameter('relField', metadata.references?.fieldName)} ${GraphqlHelper.buildStringGQLParameter('icon', metadata.icon)} ${GraphqlHelper.buildNotStringGQLParameter('translate', metadata.translate)} ${ options?.disableSelfReferences ? '' : GraphqlHelper.buildStringGQLParameter('selfRelField', metadata.selfReferences?.fieldName) } ${GraphqlHelper.buildStringGQLParameter('filter', filter, true)} ${GraphqlHelper.buildStringGQLParameter('label', metadata.label)} ${GraphqlHelper.buildNotStringGQLParameter( 'labelFields', GraphqlHelper.serializableStringArray(metadata.labelFields || []) )} ${GraphqlHelper.buildNotStringGQLParameter( 'searchFields', GraphqlHelper.serializableStringArray(metadata.searchFields || []) )} ${GraphqlHelper.buildNotStringGQLParameter('expandEndLevel', metadata.expandEndLevel)} }`; } export interface TreeFetchChildrenOptions { disableSelfReferences?: boolean; selfFilter?: string; filter?: string; selfDomain?: string; domain?: string; selfSort?: { orders: QuerySort[] }; sort?: { orders: QuerySort[] }; expressionParameters?: ExpressionRunParam; } export class TreeService { /** * 获取下一个需要查询的元数据定义 * @param node 当前选中节点 */ public static getNextMetadata( node: OioTreeNode ): { isRoot: boolean; nextMetadata: TreeNodeMetadata | undefined } { const isRoot = !node.parent; const { metadata } = node.value; let nextMetadata: TreeNodeMetadata | undefined; if (isRoot) { nextMetadata = metadata; } else { nextMetadata = metadata.child; } return { isRoot, nextMetadata }; } public static async fetchChildren( node: OioTreeNode, options?: TreeFetchChildrenOptions ): Promise> { const { metadata, data, pagination } = node.value; let { nextMetadata } = TreeService.getNextMetadata(node); let currentMetadata: TreeNodeMetadata | undefined; const isRoot = !node.parent; if (!isRoot) { currentMetadata = metadata; } if (currentMetadata && options) { let { selfFilter } = options; if (selfFilter) { currentMetadata = cloneDeep(currentMetadata); const metadataFilter = currentMetadata.filter; if (metadataFilter) { selfFilter = `(${metadataFilter}) and (${selfFilter})`; } currentMetadata.filter = selfFilter; } } if (nextMetadata && options) { let { filter } = options; if (filter) { nextMetadata = cloneDeep(nextMetadata); const metadataFilter = nextMetadata.filter; if (metadataFilter) { filter = `(${metadataFilter}) and (${filter})`; } nextMetadata.filter = filter; } } let value = ''; if (data && Object.keys(data).length) { value = GraphqlHelper.serializableObject(data); } const { searchValue: keywords, parentNode } = node.value as CardCascaderItemData; if (isRoot && parentNode) { currentMetadata = parentNode.value.metadata; const parentData = parentNode.value.data; if (parentData && Object.keys(parentData).length) { value = GraphqlHelper.serializableObject(parentData); } } const gql = `query { uiTreeNodeQuery { fetchChildren ( ${GraphqlHelper.buildStringGQLParameter( 'keywords', keywords ? GraphqlHelper.serializableString(keywords) : undefined )} ${GraphqlHelper.buildNotStringGQLParameter('currentNode', value ? `{ value: "${value}" }` : undefined)} ${buildMetadataGQLParameter('selfMetadata', currentMetadata, options)} ${buildMetadataGQLParameter('nextMetadata', nextMetadata, { expressionParameters: options?.expressionParameters })} ${buildMetadataGQLParameter('afterNextMetadata', nextMetadata?.child, { expressionParameters: options?.expressionParameters })} pagination: { currentPage: ${pagination!.current}, size: ${pagination!.pageSize} } ) { content { label value metadataKey isLeaf icon } currentPage totalPages totalElements } } } `; const result = await http.query>(SYSTEM_MODULE_NAME.BASE, gql, undefined, { batch: true }); return result.data.uiTreeNodeQuery.fetchChildren; } public static async fetchAll( metadataList: TreeNodeMetadata[], options?: { expressionParameters?: ExpressionRunParam; } ): Promise { const gql = `query { uiTreeNodeQuery { fetchAll ( metadataList: [${metadataList.map((v) => buildMetadataGQLParameter('', v, options)).join(',')}] ) { label value key parentKeys metadataKey isLeaf } } } `; const result = await http.query(SYSTEM_MODULE_NAME.BASE, gql); return result.data.uiTreeNodeQuery.fetchAll; } public static async fetchExpandEndLevel( metadataList: TreeNodeMetadata[], options?: { expressionParameters?: ExpressionRunParam; } ): Promise { const gql = `query { uiTreeNodeQuery { fetchExpandEndLevel ( metadataList: [${metadataList.map((v) => buildMetadataGQLParameter('', v, options)).join(',')}] ) { label value key parentKeys metadataKey isLeaf } } } `; const result = await http.query(SYSTEM_MODULE_NAME.BASE, gql); return result.data.uiTreeNodeQuery.fetchExpandEndLevel; } public static async queryKeywords4Tree( keywords: string, metadataList: TreeNodeMetadata[], options?: { expressionParameters?: ExpressionRunParam; } ): Promise { const gql = `query { uiTreeNodeQuery { queryKeywords4Tree ( keywords: "${GraphqlHelper.serializableString(keywords)}" metadataList: [${metadataList.map((v) => buildMetadataGQLParameter('', v, options)).join(',')}] ) { label value key parentKeys metadataKey isLeaf } } } `; const result = await http.query(SYSTEM_MODULE_NAME.BASE, gql); return result.data.uiTreeNodeQuery.queryKeywords4Tree; } public static async queryKeywords4InnerSelfTree( keywords: string, node: OioTreeNode ): Promise { const { metadata, parentNode } = node.value; let value = ''; if (parentNode) { const { data } = parentNode.value; if (data && Object.keys(data).length) { value = GraphqlHelper.serializableObject(data); } } const gql = `query { uiTreeNodeQuery { queryKeywords4InnerSelfTree ( keywords: "${GraphqlHelper.serializableString(keywords)}" ${GraphqlHelper.buildNotStringGQLParameter('parentNode', value ? `{ value: "${value}" }` : undefined)} ${buildMetadataGQLParameter('currentNodeMetadata', metadata)} ${buildMetadataGQLParameter('nextNodeMetadata', metadata.child)} ) { label value key parentKeys metadataKey isLeaf } } } `; const result = await http.query(SYSTEM_MODULE_NAME.BASE, gql); return result.data.uiTreeNodeQuery.queryKeywords4InnerSelfTree; } public static async reverselyQuery( values: ActiveRecord[], metadataList: TreeNodeMetadata[], options?: { expressionParameters?: ExpressionRunParam; } ): Promise { return GQL.query('uiTreeNodeQuery', 'reverselyQuery') .buildRequest((builder) => builder .objectParameter( 'leafNodes', `[${values.map((v) => `{ value: "${GraphqlHelper.serializableObject(v)}" }`).join(', ')}]` ) .objectParameter( 'metadataList', `[${metadataList.map((v) => buildMetadataGQLParameter('', v, options)).join(',')}]` ) ) .buildResponse((builder) => builder.parameter('label', 'value', 'key', 'parentKeys', 'metadataKey', 'isLeaf')) .request(SYSTEM_MODULE_NAME.BASE); } public static async reverselyQueryWithSize( values: ActiveRecord[], metadataList: TreeNodeMetadata[], options?: { pageSize?: number; expressionParameters?: ExpressionRunParam; disabledIsLeaf?: boolean; } ): Promise { return GQL.query('uiTreeNodeQuery', 'reverselyQueryWithSize') .buildRequest((builder) => builder .numberParameter('size', options?.pageSize || 50) .objectParameter( 'leafNodes', `[${values.map((v) => `{ value: "${GraphqlHelper.serializableObject(v)}" }`).join(', ')}]` ) .objectParameter( 'metadataList', `[${metadataList.map((v) => buildMetadataGQLParameter('', v, options)).join(',')}]` ) ) .buildResponse((builder) => { builder.parameter('label', 'value', 'key', 'parentKeys', 'metadataKey'); if (!options?.disabledIsLeaf) { builder.parameter('isLeaf'); } }) .request(SYSTEM_MODULE_NAME.BASE); } public static convertTreeByResponseBody( treeDefinition: TreeNodeMetadata, list: TreeNodeResponseBody[], options: { defaultPagination?: Pagination; generatorKey?(item: TreeNodeResponseBody, value: V): string; computeNodeTitle(value: V): string; } ): { nodes: OioTreeNode[]; expandedKeys: string[] } { const expandedKeys: string[] = []; const isUsingPagination = !!options.defaultPagination; const generatorKey = options.generatorKey || ((item) => item.key || uniqueKeyGenerator()); const nodes = CastHelper.cast[]>( TreeHelper.convert>( list, (v, value) => { const key = v.key || generatorKey(v, value); expandedKeys.push(key); return key; }, (v) => v.parentKeys, (v) => { const { label, metadataKey, value } = v; if (metadataKey) { let metadata: TreeNodeMetadata | undefined = treeDefinition; while (metadata) { if (metadata.key === metadataKey) { return { label, metadata, data: JSON.parse(value), pagination: isUsingPagination ? { ...options.defaultPagination } : undefined } as V; } metadata = metadata.child; } } return undefined; }, (key, value, parent) => { const node = CastHelper.cast>(TreeNode.newInstance(key, value, CastHelper.cast(parent))); if (value) { node.title = options.computeNodeTitle(value); } node.loaded = true; return node; }, (node, value) => { node.title = options.computeNodeTitle(value); return node; } ) ); return { nodes, expandedKeys }; } }