import { type ActiveRecord, ActiveRecordExtendKeys, type ActiveRecords, ActiveRecordsOperator, EventKeys, getRefreshParameters, type IPopupInstance, type IPopupWidget, isRelationField, PopupManager, RelationUpdateType, type RuntimeAction, type RuntimeViewAction, SubmitValue } from '@oinone/kunlun-engine'; import type { ExpressionRunParam } from '@oinone/kunlun-expression'; import { ActionContextType, deepClone, ViewType } from '@oinone/kunlun-meta'; import { BooleanHelper, CallChaining, ObjectUtils, Optional } from '@oinone/kunlun-shared'; import { ActiveRecordsWidget, type ActiveRecordsWidgetProps, type OioAnyViewState, useInjectMetaContext, useProviderMetaContext, Widget, type WidgetSubjection } from '@oinone/kunlun-vue-widget'; import { isArray, isFunction, isNil } from 'lodash-es'; import { computed } from 'vue'; import { REFRESH_FORM_DATA, validatorCallChainingCallAfterFn } from '../../basic/constant'; import { type ClickResult, type PopupEventHandle, type PopupEventHandles, PopupScene, type PopupSubmitOptions, type PopupSubmitParameters, PopupSubmitType } from '../../typing'; import { executeMapping } from '../../util'; export interface PopupWidgetProps extends ActiveRecordsWidgetProps { mountedVisible?: boolean; } /** * 弹出层组件,如弹出框、抽屉等 */ export abstract class PopupWidget extends ActiveRecordsWidget implements IPopupWidget { @Widget.SubContext(REFRESH_FORM_DATA) protected reloadFormData$!: WidgetSubjection; private handlers: PopupEventHandle = { cancel: [], ok: [] }; public initialize(props: Props) { super.initialize(props); this.mountedVisible = Optional.ofNullable(props.mountedVisible).orElse(true)!; return this; } @Widget.Reactive() @Widget.Inject() protected openerDataSource: ActiveRecord[] | undefined; @Widget.Reactive() @Widget.Inject() protected openerActiveRecords: ActiveRecord[] | undefined; @Widget.Reactive() protected get popupSubmitType(): PopupSubmitType { return (this.getDsl().submitType?.toLowerCase?.() as PopupSubmitType) || PopupSubmitType.current; } @Widget.Reactive() protected get viewType(): ViewType { return this.getDsl().type as unknown as ViewType; } @Widget.Reactive() protected get maskClosable(): boolean { return BooleanHelper.toBoolean(this.getDsl().maskClosable) || false; } @Widget.Reactive() protected get mask(): boolean { return BooleanHelper.toBoolean(this.getDsl().mask) ?? true; } @Widget.Reactive() protected get closable(): boolean { return BooleanHelper.toBoolean(this.getDsl().closable) ?? true; } @Widget.Reactive() protected currentRelationUpdateType: RelationUpdateType | undefined; @Widget.Reactive() @Widget.Provide() public get relationUpdateType(): RelationUpdateType { return ( this.currentRelationUpdateType || (this.getDsl().relationUpdateType?.toLowerCase?.() as RelationUpdateType) || RelationUpdateType.default ); } protected abstract getPopupScene(): PopupScene | string; @Widget.Reactive() @Widget.Provide() protected get popupScene(): string { return this.getPopupScene(); } protected get action(): RuntimeViewAction | undefined { return this.metadataRuntimeContext.viewAction; } @Widget.Reactive() @Widget.Provide() public get dataSource(): ActiveRecord[] | undefined { return this.getCurrentDataSource() || undefined; } public reloadDataSource(records: ActiveRecords | undefined) { this.setCurrentDataSource(records); } @Widget.Reactive() @Widget.Provide() public get activeRecords(): ActiveRecord[] | undefined { return this.getCurrentActiveRecords(); } public reloadActiveRecords(records: ActiveRecords | undefined) { this.setCurrentActiveRecords(records); } public flushDataSource() { // do nothing } /** * 这里某个组件需要知道自己在某个特定组件中,进行特殊逻辑处理,暂时先使用这种方式进行识别 * @protected */ @Widget.Reactive() @Widget.Provide() protected isCard = false; @Widget.Provide() protected currentMountedCallChaining: CallChaining | undefined; @Widget.Reactive() @Widget.Provide() public get mountedCallChaining(): CallChaining | undefined { return this.currentMountedCallChaining; } @Widget.Reactive() @Widget.Inject('refreshCallChaining') protected parentRefreshCallChaining: CallChaining | undefined; @Widget.Reactive() protected currentRefreshCallChaining: CallChaining | undefined; @Widget.Reactive() @Widget.Provide() public get refreshCallChaining(): CallChaining | undefined { return this.currentRefreshCallChaining || this.parentRefreshCallChaining; } @Widget.Reactive() @Widget.Provide() protected submitCallChaining: CallChaining | undefined; @Widget.Reactive() @Widget.Provide() protected validatorCallChaining: CallChaining | undefined; /** * 置空cols属性 */ @Widget.Reactive() @Widget.Provide() public cols: number | undefined; /** * 置空rowIndex属性 */ @Widget.Reactive() @Widget.Provide() public rowIndex: number | undefined; @Widget.Reactive() private visible = false; @Widget.Reactive() private mountedVisible = false; /** * 是否显示切换全屏按钮 */ @Widget.Reactive() protected get enabledFullScreen() { return Optional.ofNullable(BooleanHelper.toBoolean(this.getDsl().enabledFullScreen)).orElse(true); } /** * 是否显示切换窗口类型按钮 */ @Widget.Reactive() protected get showPopupToggle() { return Optional.ofNullable(BooleanHelper.toBoolean(this.getDsl().showPopupToggle)).orElse(true); } /** * 是否显示上一条、下一条数据切换 */ @Widget.Reactive() protected get showQuickToggle() { if (this.action?.contextType === ActionContextType.Single) { return Optional.ofNullable(BooleanHelper.toBoolean(this.getDsl().showQuickToggle)).orElse(true); } return false; } @Widget.Method() public async onOk() { await this.onNotify('ok'); } @Widget.Method() public async onCancel() { await this.onNotify('cancel'); } @Widget.Reactive() public get isVisible(): boolean { return this.visible; } /** * @deprecated please extends widget override onVisibleChange */ public beforeClose; @Widget.Method() public async onVisibleChange(visible: boolean, triggerAction?: Action) { if (!visible && isFunction(this.beforeClose)) { const canClose = await this.beforeClose(triggerAction); if (!canClose) { return; } } this.visible = visible; const popupInstance = this.findPopupInstance(); if (popupInstance) { PopupManager.INSTANCE.notifyHandler(visible ? EventKeys.open : EventKeys.close, popupInstance, triggerAction); } } @Widget.Method() @Widget.Inject('onSubmit') public onSubmitToParent: ((parameters: PopupSubmitParameters) => ClickResult) | undefined; @Widget.Method() @Widget.Inject('onCancel') public onCancelToParent: ((parameters: PopupSubmitParameters) => ClickResult) | undefined; @Widget.Method() @Widget.Provide('onSubmit') private async onSubmitProvider(options?: PopupSubmitOptions) { if (isFunction(this.onSubmitToParent)) { let finalParameters: PopupSubmitParameters; if (options?.parameters) { finalParameters = options.parameters; } else { switch (this.popupSubmitType) { case PopupSubmitType.current: { let result: ActiveRecords = []; if (this.submitCallChaining) { result = (await this.submitCallChaining.syncCall())?.records || []; } let finalShowRecords: ActiveRecord[] = deepClone(this.activeRecords) || []; const viewAction = this.action; if (viewAction) { const { model, resModel } = viewAction; if (model && resModel && model !== resModel) { finalShowRecords = ActiveRecordsOperator.repairRecordsNullable(result) || []; } } finalParameters = { showRecords: finalShowRecords, submitRecords: result }; break; } case PopupSubmitType.all: { const result: ActiveRecord[] = deepClone(this.dataSource) || []; finalParameters = { showRecords: result, submitRecords: result }; break; } } } Optional.ofNullable(options) .map>((v) => v!.mapping!) .filter((v) => !!Object.keys(v).length) .ifPresent((mapping) => { finalParameters = this.executeMapping(finalParameters, mapping); }); return this.onSubmitToParent(finalParameters); } PopupManager.INSTANCE.dispose(this.getHandle(), options?.action); return false; } @Widget.Method() @Widget.Provide('onCancel') private async onCancelProvider() { if (isFunction(this.onCancelToParent)) { let result: ActiveRecords = []; if (this.submitCallChaining) { result = (await this.submitCallChaining.syncCall())?.records || []; } return this.onCancelToParent({ showRecords: this.activeRecords || [], submitRecords: result }); } PopupManager.INSTANCE.dispose(this.getHandle()); return false; } @Widget.Reactive() protected get listViewTotalPage() { return this.openerDataSource?.length || 0; } @Widget.Reactive() protected listViewRowNumber = 1; /** * 计算当前的数据是表格的第一行 */ protected computedRowNumber() { if (!this.openerDataSource?.length || !this.showQuickToggle) { return; } const __draftId = this.action?.resView?.initialValue?.[0]?.__draftId; if (!__draftId) { return; } const index = this.openerDataSource.findIndex((item) => item.__draftId === __draftId); if (index >= 0) { this.listViewRowNumber = index + 1; } } /** * 上一行、下一行切换 */ @Widget.Method() protected async onChangeRowNumber(currentPage: number) { this.listViewRowNumber = currentPage; const activeRecord = deepClone(this.openerDataSource?.[this.listViewRowNumber - 1]); if (!activeRecord) { return; } this.reloadActiveRecords(activeRecord); this.reloadDataSource(activeRecord); if (this.action?.resView) { this.action.resView.initialValue = this.activeRecords; } this.mountedCallChaining?.syncCall(); } protected isSameModel(): boolean { const { model, field, viewAction } = this.metadataRuntimeContext; let isSameModel = false; let originModel = viewAction?.model; if (isNil(originModel) && field && isRelationField(field)) { originModel = field.references; } if (originModel) { isSameModel = model.model === originModel; } return isSameModel; } protected executeMapping(parameters: PopupSubmitParameters, mapping: Record): PopupSubmitParameters { const isSameModel = this.isSameModel(); let { showRecords, submitRecords } = parameters; showRecords = this.$$executeMapping(showRecords, mapping, isSameModel); if (isArray(submitRecords)) { submitRecords = this.$$executeMapping(submitRecords, mapping, isSameModel); } else { [submitRecords] = this.$$executeMapping([submitRecords], mapping, isSameModel); } return { showRecords, submitRecords }; } protected $$executeMapping( records: ActiveRecord[], mapping: Record, isSameModel: boolean ): ActiveRecord[] { return records.map((record) => { const mappingExecuteParameters: ExpressionRunParam = { activeRecords: records, rootRecord: this.rootData?.[0] || {}, openerRecord: this.openerActiveRecords?.[0] || {}, scene: this.action?.name || '', activeRecord: record }; let target: ActiveRecord; if (isSameModel) { target = record; } else { target = {}; Object.values(ActiveRecordExtendKeys).forEach((key) => { ObjectUtils.safeSetter(target, record, key); }); } executeMapping(mappingExecuteParameters, target, mapping); return target; }); } private onNotify(type: keyof PopupEventHandles) { this.handlers[type]?.forEach((handle) => handle?.()); } public on(type: keyof PopupEventHandles, handler: Function) { let queue = this.handlers[type]; if (!queue) { queue = []; this.handlers[type] = queue; } queue.push(handler); } protected findPopupInstance(): IPopupInstance | undefined { const key = this.getHandle(); return PopupManager.INSTANCE.getInstances(this.getPopupScene()).find((v) => v.key === key); } protected $$initViewState(state: OioAnyViewState): void { if (!state.popupScene) { state.popupScene = this.popupScene; } } protected $$beforeCreated() { super.$$beforeCreated(); const metaContext = useInjectMetaContext(); useProviderMetaContext({ ...metaContext, parentHandle: computed(() => this.getHandle()), slotName: computed(() => undefined) }); let popupInstance = this.findPopupInstance(); if (!popupInstance) { popupInstance = { type: this.popupScene, key: this.getHandle(), initialConfig: { action: this.action }, widget: this }; PopupManager.INSTANCE.push(popupInstance, false); } } protected $$created() { super.$$created(); const popupInstance = this.findPopupInstance(); if (popupInstance) { PopupManager.INSTANCE.notifyHandler(EventKeys.created, popupInstance); } } protected $$beforeMount() { super.$$beforeMount(); this.computedRowNumber(); this.currentMountedCallChaining = new CallChaining(); this.currentRefreshCallChaining = new CallChaining(); this.submitCallChaining = new CallChaining(); this.validatorCallChaining = new CallChaining(); } protected $$mounted() { super.$$mounted(); this.currentRefreshCallChaining?.callBefore( (...args) => { const { refreshParent } = getRefreshParameters(args); if (refreshParent) { this.parentRefreshCallChaining?.syncCall(); return false; } return true; }, { immutable: true } ); this.submitCallChaining?.callAfter( (args, callBeforeResult, { status, results }) => { const result = results[0]; if (status && result) { return new SubmitValue(ActiveRecordsOperator.repairRecords(result.records)); } return new SubmitValue([]); }, { force: true, immutable: false } ); this.validatorCallChaining?.callAfter(validatorCallChainingCallAfterFn, { force: true, immutable: false }); if (this.mountedVisible) { this.onVisibleChange(true); } } public dispose(force = false) { super.dispose(force); const popupInstance = this.findPopupInstance(); if (popupInstance) { PopupManager.INSTANCE.notifyHandler(EventKeys.dispose, popupInstance); } } @Widget.Method() protected allMounted() { this.mountedCallChaining?.syncCall(); } }