import type { DslDefinition } from '@oinone/kunlun-dsl'; import { $systemMajorConfig, CurrentLanguage, formateLanguage, initI18n, LanguageType, type MajorConfig, type MultiTabsApplicationHomepageConfig, MultiTabsRuntimeManifestMergedConfigManager, OioProvider, queryResourceDateTimeFormat, type ReloadMainViewCallChainingParameters, type ReloadMaskCallChainingParameters, ROOT_HANDLE, type RuntimeViewAction, translateValueByKey, useLanguage, ViewActionCache, type ViewActionQueryParameter } from '@oinone/kunlun-engine'; import { ViewActionTarget } from '@oinone/kunlun-meta'; import { isNotPermission, setSessionPath, useSessionPath } from '@oinone/kunlun-request'; import { useMatched } from '@oinone/kunlun-router'; import { CallChaining } from '@oinone/kunlun-shared'; import { distinctUntilChanged, Subscription } from '@oinone/kunlun-state'; import { DEFAULT_PREFIX } from '@oinone/kunlun-theme'; import { emptyHomepageModelName, getUnauthorizedAction, MenuService, ModuleService, replaceStanderMainView, type RuntimeMenu, TopBarService, unauthorizedActionName, urlHomepageModelName } from '@oinone/kunlun-vue-admin-layout'; import { OioNotification } from '@oinone/kunlun-vue-ui-antd'; import { ZH_CN_CODE } from '@oinone/kunlun-vue-ui-common'; import { Widget } from '@oinone/kunlun-vue-widget'; import { nextTick, type VNode } from 'vue'; import { MetadataViewWidget } from '../basic'; import { TeleportWidget } from '../components/teleport'; import { seekViewMask } from '../tags'; import { TranslateBox } from '../view/translate'; import DefaultMetadataMainView from './DefaultMetadataMainView.vue'; import { MetadataMainViewLifecycle } from './lifecycle'; import { MultiTabsContainerWidget } from './multi-tabs'; import { MULTI_TABS_CONTAINER_HANDLE, MULTI_TABS_TELEPORT_HANDLE } from './multi-tabs/named'; /** *

元数据主视图

*

* 该视图组件提供mask渲染以及首个元数据上下文初始化的处理逻辑 * metadataHandle、rootHandle、parentHandle将从这里向下提供一个根节点,是整个视图渲染的起点 *

*

* 该视图组件并不会提供除了渲染挂载以外的其他数据,比如model、module等信息将从MainViewWidget向下提供,这样处理将更能凸显MainViewWidget组件的通用性 *

*/ export class DefaultMetadataMainViewWidget extends MetadataViewWidget { private matchedSub: Subscription | undefined; private $systemMajorConfig!: Subscription; protected pairwiseRoutePage: { oldPage: ViewActionQueryParameter | undefined; newPage: ViewActionQueryParameter | undefined; } = { oldPage: undefined, newPage: undefined }; @Widget.Reactive() @Widget.Provide() protected reloadMaskCallChaining = new CallChaining(); @Widget.Reactive() @Widget.Provide() protected reloadMainViewCallChaining = new CallChaining(); @Widget.Reactive() protected maskTemplate: DslDefinition | undefined; @Widget.Reactive() protected loading = true; protected currentRuntimeViewAction: RuntimeViewAction | undefined; @Widget.Reactive() @Widget.Provide() protected mainViewLoading = false; @Widget.Reactive() protected translateToolBox: VNode | undefined; protected setMainViewLoading(loading: boolean) { this.mainViewLoading = loading; this.multiTabsContainerWidget?.setLoading(loading); } protected multiTabsTeleportWidget: TeleportWidget | undefined; protected multiTabsContainerWidget: MultiTabsContainerWidget | undefined; public initialize(props) { super.initialize(props); this.setComponent(DefaultMetadataMainView); this.metadataHandle = ROOT_HANDLE; this.rootHandle = ROOT_HANDLE; if (MultiTabsRuntimeManifestMergedConfigManager.isEnabled()) { this.createMultiTabsContainerWidget(); } return this; } private arePropertiesEqual(oldProp: T | string | undefined, newProp: T | string | undefined) { if (oldProp && newProp) { const old = typeof oldProp === 'string' ? oldProp : JSON.stringify(oldProp); const _new = typeof newProp === 'string' ? newProp : JSON.stringify(newProp); return old === _new; } return false; } /** * 监听路由变化 * 1: 渲染mask * 2: 初始化上下文P */ private watchRoute() { let oldSegmentParams; this.matchedSub = useMatched() .getMatched$() .pipe( distinctUntilChanged((x, y) => { oldSegmentParams = x.segmentParams; return JSON.stringify(x.segmentParams) === JSON.stringify(y.segmentParams); }) ) .subscribe((matched) => { const { page } = matched.segmentParams; this.pairwiseRoutePage = { oldPage: oldSegmentParams?.page, newPage: page }; if (!page) { return; } this.setMainViewLoading(true); this.reloadPage(oldSegmentParams?.page, page) .catch((e) => { console.error('reload page error.', e); }) .finally(() => { this.loading = false; this.setMainViewLoading(false); }); }); } protected async reloadPage( oldPage: ViewActionQueryParameter | undefined, newPage: ViewActionQueryParameter ): Promise { const { module: moduleName, model, action, target, path } = newPage; if (!moduleName) { throw new Error('Invalid module'); } if (!model) { throw new Error('Invalid view action model'); } if (!action) { throw new Error('Invalid view action name'); } // fixme @zbh 20230410 国际化迁移 await CurrentLanguage.refreshLocalStorage(); if (OioProvider.getConfig().enableI18n !== false) { await initI18n(moduleName); const langCode = await CurrentLanguage.get(); const resLangCode = OioProvider.getConfig().language; if (formateLanguage(langCode?.code) !== formateLanguage(resLangCode)) { if (OioProvider.getConfig().extend?.toolboxTranslation) { this.translateToolBox = TranslateBox.getWidget(); } else { this.translateToolBox = undefined; } } } let runtimeViewAction: RuntimeViewAction; /** * 当前模块没有绑定首页,所以是不存在首页的模型 */ if (model === emptyHomepageModelName) { runtimeViewAction = getUnauthorizedAction({ moduleName, model, target: ViewActionTarget.Router, title: '无首页', name: action }); await ViewActionCache.set(runtimeViewAction); } else if (model === urlHomepageModelName) { window.location.replace(decodeURI(action)); return; } else { /** * 查询action对应的页面,如果当前页面没有权限,就展示"无权限"的视图 */ try { runtimeViewAction = await this.fetchRuntimeViewAction(model, action, path); queryResourceDateTimeFormat(); } catch (e) { if (isNotPermission(e)) { runtimeViewAction = getUnauthorizedAction({ moduleName, model, target, name: action }); await ViewActionCache.set(runtimeViewAction); } else { throw e; } } } if (!this.arePropertiesEqual(this.currentRuntimeViewAction, runtimeViewAction)) { await this.beforeRender(runtimeViewAction, oldPage, newPage); this.initRuntimeContext(runtimeViewAction); await this.renderMask(runtimeViewAction, oldPage, newPage); this.currentRuntimeViewAction = runtimeViewAction; } this.loading = false; return this.renderMainView(runtimeViewAction, oldPage, newPage); } protected async fetchRuntimeViewAction( model: string, action: string | undefined, path: string | undefined ): Promise { let runtimeViewAction = this.runtimeContext?.viewAction; if (model && action) { if (!runtimeViewAction || runtimeViewAction.model !== model || runtimeViewAction.name !== action) { if (path) { runtimeViewAction = await useSessionPath(path, () => ViewActionCache.getOrThrow(model, action)); } else { runtimeViewAction = await ViewActionCache.getOrThrow(model, action); } } } else { runtimeViewAction = undefined; } if (!runtimeViewAction) { OioNotification.error(translateValueByKey('错误'), translateValueByKey('页面初始化异常')); throw new Error('Page initialization error'); } setSessionPath(runtimeViewAction.sessionPath); return runtimeViewAction; } /** * 初始化运行时上下文 * @param viewAction 跳转动作 * @protected */ protected initRuntimeContext(viewAction: RuntimeViewAction): { isInit: boolean; isRefresh: boolean } { if (!this.runtimeContext) { this.initContextByViewAction(viewAction); return { isInit: true, isRefresh: false }; } const lastedViewAction = this.runtimeContext?.viewAction; if (!lastedViewAction || lastedViewAction.model !== viewAction.model || lastedViewAction.name !== viewAction.name) { this.initContextByViewAction(viewAction); return { isInit: false, isRefresh: true }; } return { isInit: false, isRefresh: false }; } /** * 渲染前 * @param viewAction 跳转动作 * @param oldPage 旧url参数 * @param newPage 新url参数 * @protected */ protected async beforeRender( viewAction: RuntimeViewAction, oldPage: ViewActionQueryParameter | undefined, newPage: ViewActionQueryParameter ) { const { module: moduleName } = newPage; const moduleClassPrefix = `${DEFAULT_PREFIX}-module`; document.body.className = `${moduleClassPrefix} ${moduleClassPrefix}-${moduleName}`; const moduleDisplayName = viewAction.resModuleDefinition?.displayName || viewAction.moduleDefinition?.displayName; const menus = (await MenuService.queryMenus(moduleName)) as RuntimeMenu[]; const treeNodes = MenuService.convert(menus); MenuService.sort(treeNodes); const selectedMenuItem = MenuService.findSelectedMenuItemByAction(treeNodes, viewAction.name); const title = selectedMenuItem?.value?.title || ModuleService.generatorViewTitle(viewAction); const titleArray: string[] = []; if (moduleDisplayName) { titleArray.push(translateValueByKey(moduleDisplayName)); } if (title) { titleArray.push(translateValueByKey(title)); } if (titleArray.length) { document.title = titleArray.join(' - '); } MetadataMainViewLifecycle.notifyBeforeRender(viewAction, oldPage, newPage); } /** * 渲染mask * @param viewAction 跳转动作 * @param oldPage 旧url参数 * @param newPage 新url参数 */ public async renderMask( viewAction: RuntimeViewAction, oldPage: ViewActionQueryParameter | undefined, newPage: ViewActionQueryParameter ): Promise { const { module: moduleName, model, action } = newPage; let finalMaskTemplate: DslDefinition = seekViewMask(viewAction, moduleName); /** * 当前用户没有该视图没有权限 * * 但是用户自定义了当前页面的主内容区域mask,所以自定义的mask还是会被渲染出来的,需要将用户自定义的mask,改成`main-view` */ if (viewAction.resView?.name === unauthorizedActionName) { finalMaskTemplate = replaceStanderMainView(finalMaskTemplate); } const preMaskTemplate = JSON.stringify(this.maskTemplate, (key, value) => { if (key === '__index') { return undefined; } return value; }); if (!this.arePropertiesEqual(preMaskTemplate, finalMaskTemplate)) { this.maskTemplate = finalMaskTemplate; } return nextTick(() => { const reloadMaskParameters: ReloadMaskCallChainingParameters = { module: moduleName, model, action, viewName: viewAction.resViewName, viewType: viewAction.resViewType, target: viewAction.target, previousPage: oldPage, currentPage: newPage }; this.reloadMaskCallChaining.call(reloadMaskParameters); }); } /** * 渲染MainView * @param viewAction 跳转动作 * @param oldPage 旧url参数 * @param newPage 新url参数 * @protected */ protected async renderMainView( viewAction: RuntimeViewAction, oldPage: ViewActionQueryParameter | undefined, newPage: ViewActionQueryParameter ): Promise { return nextTick(() => { const { module: moduleName, model, action, target } = newPage; const reloadMainViewParameters: ReloadMainViewCallChainingParameters = { handle: this.currentHandle, module: moduleName, model, action, viewName: viewAction.resViewName, viewType: viewAction.resViewType, target, extension: viewAction.resView?.extension, previousPage: oldPage, currentPage: newPage }; return this.reloadMainViewCallChaining?.syncCall(reloadMainViewParameters); }); } protected createMultiTabsContainerWidget() { const { metadataHandle } = this; if (!metadataHandle) { throw new Error('Invalid metadata handle'); } const teleportWidget: TeleportWidget | undefined = this.createWidget( new TeleportWidget(MULTI_TABS_TELEPORT_HANDLE), 'multiTabs', { teleport: 'body', disabled: true } ); this.multiTabsTeleportWidget = teleportWidget; this.multiTabsContainerWidget = teleportWidget.createWidget( new MultiTabsContainerWidget(MULTI_TABS_CONTAINER_HANDLE), undefined, { metadataHandle, rootHandle: this.rootHandle, automatic: true, internal: true, reloadMainViewCallChaining: this.reloadMainViewCallChaining, loading: this.mainViewLoading } ); } protected watchMultiTabTheme() { this.$systemMajorConfig = $systemMajorConfig .pipe( distinctUntilChanged((pre, next) => { return ( this.multiTabThemeDistinct(pre, next) && pre.extend?.toolboxTranslation === next.extend?.toolboxTranslation ); }) ) .subscribe(() => { this.reloadPage(this.pairwiseRoutePage.oldPage, this.pairwiseRoutePage.newPage!); }); } protected multiTabThemeDistinct(pre: MajorConfig, next: MajorConfig) { return ( pre.extend?.systemStyleConfig?.multiTabConfig?.inline === next.extend?.systemStyleConfig?.multiTabConfig?.inline && (pre.extend?.systemStyleConfig?.multiTabConfig?.homepage as MultiTabsApplicationHomepageConfig)?.enabled === (next.extend?.systemStyleConfig?.multiTabConfig?.homepage as MultiTabsApplicationHomepageConfig)?.enabled && (pre.extend?.systemStyleConfig?.multiTabConfig?.homepage as MultiTabsApplicationHomepageConfig)?.autoInvisible === (next.extend?.systemStyleConfig?.multiTabConfig?.homepage as MultiTabsApplicationHomepageConfig) ?.autoInvisible && pre.multiTabTheme?.inline === next.multiTabTheme?.inline ); } protected async $$mounted() { super.$$mounted(); const lang = await TopBarService.getCurrentLang(); const currentLanguageCode = lang?.code || ZH_CN_CODE; useLanguage(currentLanguageCode as LanguageType); this.watchRoute(); this.watchMultiTabTheme(); } protected $$unmounted() { super.$$unmounted(); this.matchedSub?.unsubscribe(); this.$systemMajorConfig && this.$systemMajorConfig.unsubscribe(); } }