Files
cn-rdms-web/src/store/modules/object-context/index.ts

404 lines
11 KiB
TypeScript
Raw Normal View History

import { computed, ref } from 'vue';
import type { LocationQueryRaw, RouteLocationNormalized, RouteLocationRaw } from 'vue-router';
import { defineStore } from 'pinia';
import type { ElegantConstRoute } from '@elegant-router/types';
import {
OBJECT_CONTEXT_QUERY_KEY,
getObjectContextDomainConfigByPath,
isObjectContextEntryPath
} from '@/constants/object-context';
import { fetchGetObjectContext } from '@/service/api/object-context';
import { $t } from '@/locales';
import { SetupStoreId } from '@/enum';
import { useRouteStore } from '../route';
function createEmptyState(): App.ObjectContext.State {
return {
domainKey: '',
objectType: '',
objectId: '',
objectName: '',
objectSummary: null,
contextScopedMenus: [],
buttonCodes: [],
defaultRouteKey: '',
defaultRoutePath: '',
isReady: false
};
}
function normalizePath(path: string) {
if (!path) {
return '/';
}
return path.endsWith('/') && path !== '/' ? path.slice(0, -1) : path;
}
function isRouteMatchedByPrefix(path: string, prefix: string) {
const normalizedPath = normalizePath(path);
const normalizedPrefix = normalizePath(prefix);
return normalizedPath === normalizedPrefix || normalizedPath.startsWith(`${normalizedPrefix}/`);
}
function findDomainRootRoute(
routes: ElegantConstRoute[],
config: App.ObjectContext.DomainConfig
): ElegantConstRoute | null {
for (const route of routes) {
if (config.routePathPrefixes.some(prefix => isRouteMatchedByPrefix(route.path, prefix))) {
return route;
}
if (route.children?.length) {
const matchedChild = findDomainRootRoute(route.children, config);
if (matchedChild) {
return matchedChild;
}
}
}
return null;
}
function isEntryRoute(route: ElegantConstRoute, config: App.ObjectContext.DomainConfig) {
return route.name === config.entryRouteKey || normalizePath(route.path) === normalizePath(config.entryRoutePath);
}
function getContextMenuLabel(route: ElegantConstRoute) {
const routeName = String(route.name || route.path);
return route.meta?.i18nKey ? $t(route.meta.i18nKey) : String(route.meta?.title || routeName);
}
type ContextRouteLookupItem = {
key: string;
label: string;
routeKey: string | null;
routePath: string | null;
};
function createContextRouteLookup(
routes: ElegantConstRoute[],
lookup = new Map<string, ContextRouteLookupItem>()
): Map<string, ContextRouteLookupItem> {
routes.forEach(route => {
const routeName = route.name ? String(route.name) : '';
const routePath = route.path ? String(route.path) : '';
const item: ContextRouteLookupItem = {
key: routeName || routePath,
label: getContextMenuLabel(route),
routeKey: routeName || null,
routePath: routePath || null
};
if (routeName) {
lookup.set(routeName, item);
}
if (routePath) {
lookup.set(routePath, item);
}
route.children?.forEach(child => {
createContextRouteLookup([child], lookup);
});
});
return lookup;
}
function enrichContextMenu(
menu: App.ObjectContext.Menu,
routeLookup: Map<string, ContextRouteLookupItem>
): App.ObjectContext.Menu {
const matchedRoute =
routeLookup.get(String(menu.routeKey || '')) ||
routeLookup.get(String(menu.routePath || '')) ||
routeLookup.get(menu.key);
return {
key: matchedRoute?.key || menu.key,
label: menu.label || matchedRoute?.label || menu.key,
routeKey: menu.routeKey || matchedRoute?.routeKey || null,
routePath: menu.routePath || matchedRoute?.routePath || null,
children: menu.children?.map(child => enrichContextMenu(child, routeLookup)) || []
};
}
function getLeafRoutes(routes: ElegantConstRoute[]): ElegantConstRoute[] {
return routes.flatMap(route => {
if (route.children?.length) {
return getLeafRoutes(route.children);
}
return [route];
});
}
export const useObjectContextStore = defineStore(SetupStoreId.ObjectContext, () => {
const routeStore = useRouteStore();
const domainKey = ref<App.ObjectContext.DomainKey>('');
const objectType = ref<App.ObjectContext.ObjectType>('');
const objectId = ref('');
const objectName = ref('');
const objectSummary = ref<App.ObjectContext.Summary | null>(null);
const contextScopedMenus = ref<App.ObjectContext.Menu[]>([]);
const buttonCodes = ref<string[]>([]);
const defaultRouteKey = ref('');
const defaultRoutePath = ref('');
const isReady = ref(false);
const hasContext = computed(() => isReady.value && Boolean(domainKey.value) && Boolean(objectId.value));
function patchState(state: App.ObjectContext.State) {
domainKey.value = state.domainKey;
objectType.value = state.objectType;
objectId.value = state.objectId;
objectName.value = state.objectName;
objectSummary.value = state.objectSummary;
contextScopedMenus.value = state.contextScopedMenus;
buttonCodes.value = state.buttonCodes;
defaultRouteKey.value = state.defaultRouteKey;
defaultRoutePath.value = state.defaultRoutePath;
isReady.value = state.isReady;
}
function clearContext() {
patchState(createEmptyState());
}
function resolveDefaultRoute(
config: App.ObjectContext.DomainConfig,
domainRoutes: ElegantConstRoute[],
context: Api.ObjectContext.ContextInfo
) {
const leafRoutes = getLeafRoutes(domainRoutes);
const defaultRouteByKey = (routeKey?: string | null) => leafRoutes.find(route => route.name === routeKey);
const defaultRouteByPath = (routePath?: string | null) =>
leafRoutes.find(route => normalizePath(route.path) === normalizePath(routePath || ''));
const matchedContextByKey = defaultRouteByKey(context.defaultRouteKey);
if (matchedContextByKey?.name && matchedContextByKey.path) {
return {
defaultRouteKey: String(matchedContextByKey.name),
defaultRoutePath: matchedContextByKey.path
};
}
const matchedContextByPath = defaultRouteByPath(context.defaultRoutePath);
if (matchedContextByPath?.name && matchedContextByPath.path) {
return {
defaultRouteKey: String(matchedContextByPath.name),
defaultRoutePath: matchedContextByPath.path
};
}
const matchedFallbackByKey = defaultRouteByKey(config.fallbackDefaultRouteKey);
if (matchedFallbackByKey?.name && matchedFallbackByKey.path) {
return {
defaultRouteKey: String(matchedFallbackByKey.name),
defaultRoutePath: matchedFallbackByKey.path
};
}
const matchedFallbackByPath = defaultRouteByPath(config.fallbackDefaultRoutePath);
if (matchedFallbackByPath?.name && matchedFallbackByPath.path) {
return {
defaultRouteKey: String(matchedFallbackByPath.name),
defaultRoutePath: matchedFallbackByPath.path
};
}
const [firstLeafRoute] = leafRoutes;
if (firstLeafRoute?.name && firstLeafRoute.path) {
return {
defaultRouteKey: String(firstLeafRoute.name),
defaultRoutePath: firstLeafRoute.path
};
}
return {
defaultRouteKey: context.defaultRouteKey || config.fallbackDefaultRouteKey,
defaultRoutePath: context.defaultRoutePath || config.fallbackDefaultRoutePath
};
}
function applyContext(config: App.ObjectContext.DomainConfig, context: Api.ObjectContext.ContextInfo) {
const domainRootRoute = findDomainRootRoute(routeStore.authRoutes, config);
const domainRoutes: ElegantConstRoute[] =
domainRootRoute?.children?.filter((route: ElegantConstRoute) => !isEntryRoute(route, config)) || [];
const routeLookup = createContextRouteLookup(domainRoutes);
// 对象上下文菜单以接口返回为准,前端只补全跳转所需的本地路由信息。
const contextMenus = context.contextScopedMenus.map(menu => enrichContextMenu(menu, routeLookup));
const resolvedDefaultRoute = resolveDefaultRoute(config, domainRoutes, context);
patchState({
...context,
contextScopedMenus: contextMenus,
defaultRouteKey: resolvedDefaultRoute.defaultRouteKey,
defaultRoutePath: resolvedDefaultRoute.defaultRoutePath,
isReady: true
});
}
function getObjectIdFromRoute(route: Pick<RouteLocationNormalized, 'query'>) {
const routeObjectId = route.query?.[OBJECT_CONTEXT_QUERY_KEY];
if (Array.isArray(routeObjectId)) {
return String(routeObjectId[0] || '');
}
if (routeObjectId === null || routeObjectId === undefined) {
return '';
}
return String(routeObjectId);
}
function getContextQuery(targetObjectId = objectId.value): LocationQueryRaw {
if (!targetObjectId) {
return {};
}
return {
[OBJECT_CONTEXT_QUERY_KEY]: targetObjectId
};
}
function createEntryLocation(config: App.ObjectContext.DomainConfig): RouteLocationRaw {
return {
path: config.entryRoutePath
};
}
function createDefaultLocation(config: App.ObjectContext.DomainConfig, targetObjectId: string): RouteLocationRaw {
const query = getContextQuery(targetObjectId);
if (defaultRouteKey.value) {
return {
name: defaultRouteKey.value,
query
};
}
if (defaultRoutePath.value) {
return {
path: defaultRoutePath.value,
query
};
}
return {
path: config.fallbackDefaultRoutePath,
query
};
}
async function enterContext(config: App.ObjectContext.DomainConfig, targetObjectId: string) {
const result = await fetchGetObjectContext(config, targetObjectId);
if (!result.error && result.data) {
applyContext(config, result.data);
}
return result;
}
async function switchContext(config: App.ObjectContext.DomainConfig, targetObjectId: string) {
return enterContext(config, targetObjectId);
}
async function ensureContextByRoute(to: RouteLocationNormalized): Promise<RouteLocationRaw | null> {
const domainConfig = getObjectContextDomainConfigByPath(to.path);
if (!domainConfig) {
if (hasContext.value) {
clearContext();
}
return null;
}
const routeObjectId = getObjectIdFromRoute(to);
if (!routeObjectId) {
clearContext();
if (isObjectContextEntryPath(to.path, domainConfig)) {
return null;
}
return createEntryLocation(domainConfig);
}
const isSameContext =
hasContext.value && domainKey.value === domainConfig.domainKey && objectId.value === routeObjectId;
if (!isSameContext) {
const { error } = await enterContext(domainConfig, routeObjectId);
if (error) {
clearContext();
return createEntryLocation(domainConfig);
}
}
if (isObjectContextEntryPath(to.path, domainConfig)) {
return createDefaultLocation(domainConfig, routeObjectId);
}
return null;
}
function getMenuRouteLocation(
menu: App.ObjectContext.Menu,
targetObjectId = objectId.value
): RouteLocationRaw | null {
const query = getContextQuery(targetObjectId);
if (menu.routeKey) {
return {
name: menu.routeKey,
query
};
}
if (menu.routePath) {
return {
path: menu.routePath,
query
};
}
return null;
}
return {
domainKey,
objectType,
objectId,
objectName,
objectSummary,
contextScopedMenus,
buttonCodes,
defaultRouteKey,
defaultRoutePath,
isReady,
hasContext,
clearContext,
enterContext,
switchContext,
ensureContextByRoute,
getContextQuery,
getMenuRouteLocation
};
});