import {
type ActiveRecord,
getRefreshParameters,
getStaticRelationField,
isRelationField,
isStaticRelationField,
ModelCache,
type Pagination,
RefreshCallChainingScope
} from '@oinone/kunlun-engine';
import { Expression, type ExpressionRunParam } from '@oinone/kunlun-expression';
import { Condition } from '@oinone/kunlun-request';
import { DEFAULT_FALSE_CONDITION, DEFAULT_TRUE_CONDITION } from '@oinone/kunlun-service';
import { BooleanHelper, CallChaining, Optional, type ReturnPromise, uniqueKeyGenerator } from '@oinone/kunlun-shared';
import type { OioTreeNode } from '@oinone/kunlun-vue-ui-antd';
import { isAllInvisible, Widget } from '@oinone/kunlun-vue-widget';
import { isNil, toInteger } from 'lodash-es';
import { BaseElementWidget } from '../../basic';
import { FETCH_DATA_WIDGET_PRIORITY } from '../../basic/constant';
import { type TreeNodeResponseBody, TreeService } from '../../service';
import type { CardCascaderItemData, TreeData, TreeNodeMetadata, TreeRefreshCallChainingParameters } from '../../typing';
import { FetchUtil, TreeUtils } from '../../util';
type ResponseBody = {
label?: string;
metadata: TreeNodeMetadata;
value: ActiveRecord;
isLeaf: boolean;
};
/**
*
抽象树基类
*
* 该组件作为Tree数据结构的基本抽象,仅用于定义数据获取,数据回填,数据结构处理等功能,与具体展示组件无关
*
*/
export abstract class AbstractTreeWidget extends BaseElementWidget {
protected defaultPagination = {
current: 1,
pageSize: 50
} as Pagination;
@Widget.Reactive()
protected treeDefinition: TreeNodeMetadata | undefined;
@Widget.Reactive()
@Widget.Inject()
protected mountedCallChaining: CallChaining | undefined;
@Widget.Reactive()
@Widget.Inject('refreshCallChaining')
protected parentRefreshCallChaining: CallChaining | undefined;
@Widget.Reactive()
protected currentRefreshCallChaining: CallChaining | undefined;
@Widget.Reactive()
@Widget.Provide()
protected get refreshCallChaining(): CallChaining | undefined {
return this.currentRefreshCallChaining;
}
public initialize(props) {
super.initialize(props);
this.treeDefinition = TreeUtils.convert(props.template);
return this;
}
@Widget.Reactive()
public get allInvisible() {
return false;
}
@Widget.Reactive()
public get invisible() {
if (!this.treeDefinition) {
return true;
}
return super.invisible;
}
protected childrenInvisibleProcess(): boolean {
const children = this.getChildren();
if (children.length) {
return isAllInvisible(children);
}
return false;
}
protected getTreeMetadataList(): TreeNodeMetadata[] {
const metadataList: TreeNodeMetadata[] = [];
let target = this.treeDefinition;
while (target) {
metadataList.push(target);
target = target.child;
}
return metadataList;
}
protected loadIdempotentKey: Record = {};
/**
* 加载指定节点的子节点(展开时懒加载)
* @param node
*/
@Widget.Method()
public async loadData(node: OioTreeNode) {
this.loadIdempotentKey[node.key] = uniqueKeyGenerator();
const idempotentKey = this.loadIdempotentKey[node.key];
return this.loadNode(node, async () => {
const results = await this.fetchData(node);
if (idempotentKey === this.loadIdempotentKey[node.key]) {
this.fillChildren(node, results);
delete this.loadIdempotentKey[node.key];
}
return results;
});
}
/**
* 加载指定节点的更多子节点
* @param node
*/
@Widget.Method()
public async loadMoreData(node: OioTreeNode) {
const { parent } = node;
if (!parent) {
return [];
}
parent.value.pagination!.current++;
this.loadIdempotentKey[node.key] = uniqueKeyGenerator();
const idempotentKey = this.loadIdempotentKey[node.key];
return this.loadNode(parent, async () => {
const results = await this.fetchData(parent);
if (idempotentKey === this.loadIdempotentKey[node.key]) {
this.fillChildren(parent, results);
delete this.loadIdempotentKey[node.key];
}
return results;
});
}
@Widget.Method()
public onSelected(node: OioTreeNode, selected: boolean): ReturnPromise {
if (selected) {
this.onNodeSelected(node);
} else {
this.onNodeUnselected(node);
}
}
protected onNodeSelected(node: OioTreeNode): ReturnPromise {}
protected onNodeUnselected(node: OioTreeNode): ReturnPromise {}
@Widget.Method()
public onChecked(node: OioTreeNode, checked: boolean): ReturnPromise {
if (checked) {
this.onNodeChecked(node);
} else {
this.onNodeUnchecked(node);
}
}
protected onNodeChecked(node: OioTreeNode): ReturnPromise {}
protected onNodeUnchecked(node: OioTreeNode): ReturnPromise {}
@Widget.Reactive()
protected get enableSearch() {
return BooleanHelper.toBoolean(this.getDsl().enableSearch);
}
/**
* @deprecated
*/
@Widget.Reactive()
public get isSkipRefreshSelf() {
return this.getDsl().refresh === 'once';
}
public async fetchData(node: OioTreeNode, disableSelfReferences?: boolean): Promise {
const result = await TreeService.fetchChildren(node, {
disableSelfReferences
});
const pagination = node.value.pagination!;
pagination.total = toInteger(result.totalElements);
pagination.totalPageSize = toInteger(result.totalPages);
const results: ResponseBody[] = [];
const rootMetadata = node.value.metadata;
result.content?.forEach((v) => {
const metadata = TreeUtils.findMetadataByKey(rootMetadata, v.metadataKey);
if (metadata) {
results.push({
metadata,
value: JSON.parse(v.value),
isLeaf: v.isLeaf,
label: v.label
});
}
});
return results;
}
public fillChildren(node: OioTreeNode, results: ResponseBody[]) {
const length = results.length;
if (!length) {
node.isLeaf = true;
return;
}
results.forEach((v) => {
const { label, metadata, value } = v;
const key = this.generatorKey(metadata.key, value);
const newNode = this.generatorNewTreeNode(node, key, label, metadata, value);
Optional.ofNullable(v.isLeaf).ifPresent((isLeaf) => {
newNode.isLeaf = isLeaf;
});
node.children.push(newNode);
});
}
public generatorKey(metadataKey: string, data: ActiveRecord) {
const pks = this.model.pks || [];
if (pks.length) {
return `${metadataKey}:${pks.map((pk) => data[pk]).join('#')}`;
}
let { __draftId } = data;
if (!__draftId) {
__draftId = uniqueKeyGenerator();
data.__draftId = __draftId;
}
return __draftId;
}
protected generatorNewTreeNode(
parent: OioTreeNode,
key: string,
title: string | undefined,
metadata: TreeNodeMetadata | undefined,
data: ActiveRecord
): OioTreeNode {
const value = {
label: title,
metadata,
data,
pagination: { ...this.defaultPagination }
} as V;
return {
key,
title: this.computeNodeTitle(value),
value,
parent,
children: [],
isLeaf: this.isLeafPredict(metadata)
};
}
protected isLeafPredict(node: TreeNodeMetadata | undefined) {
if (!node) {
return false;
}
const { child, selfReferences } = node;
let isLeaf = !child || !child.references;
if (!isLeaf) {
return false;
}
if (isLeaf) {
isLeaf = !selfReferences;
}
return isLeaf;
}
protected convertTreeByTreeSearchResponseBody(list: TreeNodeResponseBody[]) {
const { treeDefinition, defaultPagination } = this;
if (!treeDefinition) {
throw new Error('Invalid tree definition');
}
return TreeService.convertTreeByResponseBody(treeDefinition, list, {
defaultPagination,
generatorKey: (item, value) => {
const { data } = value;
if (data) {
return this.generatorKey(item.metadataKey, data);
}
return uniqueKeyGenerator();
},
computeNodeTitle: this.computeNodeTitle.bind(this)
});
}
protected computeNodeTitle(val: V): string {
const { label, metadata, data } = val;
if (!isNil(label)) {
return label;
}
let title = metadata.label;
if (title && data) {
title = this.executeExpression(data, title, '-');
if (isNil(title)) {
title = '-';
}
} else {
title = '-';
}
return title;
}
/**
* 加载状态调用函数
* @param node
* @param fn
* @param args
* @protected
*/
public async loadNode(node: OioTreeNode, fn: (...args) => R, ...args): Promise {
if (node.parent) {
const result = fn(...args);
return result;
}
return super.load(fn, ...args);
}
public executeExpression(activeRecord: ActiveRecord, expression: string, errorValue?: T): T | string | undefined {
return Expression.run(
{
activeRecords: [activeRecord],
rootRecord: this.rootData?.[0] || {},
openerRecord: this.openerActiveRecords?.[0] || {},
scene: this.scene
} as ExpressionRunParam,
expression,
errorValue
);
}
protected abstract mountedProcess(): ReturnPromise;
protected abstract refreshProcess(): ReturnPromise;
protected $$beforeMount() {
super.$$beforeMount();
this.currentRefreshCallChaining = new CallChaining();
}
protected $$mounted() {
super.$$mounted();
this.mountedCallChaining?.hook(
this.path,
async () => {
await this.mountedProcess();
},
FETCH_DATA_WIDGET_PRIORITY
);
let isRefreshParent = false;
this.currentRefreshCallChaining?.callBefore(
(...args) => {
const { refreshParent } = getRefreshParameters(args);
if (refreshParent) {
isRefreshParent = true;
this.parentRefreshCallChaining?.syncCall(...(args || []));
return false;
}
return true;
},
{ immutable: true }
);
this.currentRefreshCallChaining?.hook(
this.path,
async (args, callBeforeResult) => {
if (callBeforeResult === false) {
return;
}
const refreshParameters = getRefreshParameters(args) as TreeRefreshCallChainingParameters;
if (!refreshParameters.isSkipRefreshSelf && !this.isSkipRefreshSelf) {
await this.refreshProcess();
}
},
FETCH_DATA_WIDGET_PRIORITY
);
this.parentRefreshCallChaining?.hook(
this.path,
async (args, callBeforeResult) => {
if (callBeforeResult === false) {
return;
}
const refreshParameters = getRefreshParameters(args) as TreeRefreshCallChainingParameters;
if (!refreshParameters.isSkipRefreshSelf && !this.isSkipRefreshSelf) {
if (refreshParameters.scope !== RefreshCallChainingScope.search) {
await this.refreshProcess();
}
}
if (isRefreshParent) {
refreshParameters.refreshParent = false;
isRefreshParent = false;
this.currentRefreshCallChaining?.syncCall(refreshParameters, ...((args || []).slice(1) || []));
}
},
FETCH_DATA_WIDGET_PRIORITY
);
}
protected $$unmounted() {
super.$$unmounted();
this.mountedCallChaining?.unhook(this.path);
this.parentRefreshCallChaining?.unhook(this.path);
}
/**
* 选中节点(搜索)
* @param node 选中节点
*/
protected async onSelectedForSearch(node: OioTreeNode): Promise {
const { metadata, data } = node.value;
if (!metadata || !data) {
return false;
}
const { model, search } = metadata;
if (!search) {
return false;
}
const modelField = await TreeUtils.getModelFieldMetadata(search, model);
if (modelField && isRelationField(modelField)) {
const condition = new Condition(DEFAULT_TRUE_CONDITION);
let targetCondition: Condition | undefined;
const activeTreeContext = {};
const res = await TreeUtils.consumerReferenceModelField(
model,
modelField,
(originFields, targetFields, index, reverse) => {
const originField = originFields[index];
const targetField = targetFields[index];
let key: string;
let value: unknown;
if (isStaticRelationField(originField)) {
value = getStaticRelationField(originField);
key = targetField;
} else if (isStaticRelationField(targetField)) {
if (reverse) {
return;
}
value = getStaticRelationField(targetField);
key = originField;
} else {
key = targetField;
value = data[originField];
}
const splitKeys = key.split('.');
if (splitKeys.length === 1) {
activeTreeContext[key] = value;
} else {
splitKeys.reduce((context, key, index) => {
if (index === splitKeys.length - 1) {
context[key] = value;
} else if (!context[key]) {
context[key] = {};
}
return context[key];
}, activeTreeContext);
}
if (targetCondition) {
targetCondition.and(TreeUtils.newCondition(key, value));
} else {
targetCondition = TreeUtils.newCondition(key, value);
}
}
);
if (res) {
if (targetCondition) {
condition.and(targetCondition);
}
this.setRuntimeFilter(condition);
this.rootRuntimeContext.view.context = {
...(this.rootRuntimeContext.view.context || {}),
activeTreeContext,
rootConditionBodyData: activeTreeContext
};
const refreshParameters: TreeRefreshCallChainingParameters = {
isSkipRefreshSelf: true,
refreshParent: false,
currentPage: 1
};
this.parentRefreshCallChaining?.syncCall(refreshParameters);
return true;
}
/**
* 搜索条件异常时,不显示任何数据
*/
this.setRuntimeFilter(new Condition(DEFAULT_FALSE_CONDITION));
const refreshParameters: TreeRefreshCallChainingParameters = {
isSkipRefreshSelf: true,
refreshParent: false,
currentPage: 1
};
this.parentRefreshCallChaining?.syncCall(refreshParameters);
}
return false;
}
/**
* 选中节点(查询)
* @param node 选中节点
* @protected
*/
protected async onSelectedForQuery(node: OioTreeNode): Promise {
const { metadata, data } = node.value;
if (!metadata || !data) {
this.setRuntimeFilter(undefined);
return false;
}
const { model } = metadata;
const modelDefinition = await ModelCache.get(model);
if (!modelDefinition) {
this.setRuntimeFilter(undefined);
return false;
}
const uniqueObject = FetchUtil.generatorUniqueObject(modelDefinition, data);
const condition = FetchUtil.generatorSimpleCondition(uniqueObject);
if (!condition) {
this.setRuntimeFilter(undefined);
return false;
}
this.setRuntimeFilter(condition);
const refreshParameters: TreeRefreshCallChainingParameters = {
isSkipRefreshSelf: true,
refreshParent: false,
currentPage: 1
};
this.currentRefreshCallChaining?.syncCall(refreshParameters);
return true;
}
/**
* 移除选中
* @protected
*/
protected async onUnselected() {
this.setRuntimeFilter(undefined);
const refreshParameters: TreeRefreshCallChainingParameters = {
isSkipRefreshSelf: true,
refreshParent: false,
currentPage: 1
};
this.parentRefreshCallChaining?.syncCall(refreshParameters);
}
protected setRuntimeFilter(filter: string | Condition | undefined) {
const view = this.metadataRuntimeContext.view;
if (view) {
if (filter instanceof Condition) {
filter = filter.toString();
} else if (!filter) {
filter = undefined;
}
view.filter = filter;
}
}
}