新增前端vue

This commit is contained in:
2025-11-26 13:55:01 +08:00
parent ae391f1b94
commit ffd5a6ad66
781 changed files with 83348 additions and 0 deletions

View File

@@ -0,0 +1,10 @@
import type { App } from 'vue';
import { createPinia } from 'pinia';
const store = createPinia();
export function setupStore(app: App<Element>) {
app.use(store);
}
export { store };

View File

@@ -0,0 +1,110 @@
import type {
ProjectConfig,
HeaderSetting,
MenuSetting,
TransitionSetting,
MultiTabsSetting,
} from '@jeesite/types/config';
import type { BeforeMiniState } from '@jeesite/types/store';
import { defineStore } from 'pinia';
import { store } from '@jeesite/core/store';
import { ThemeEnum } from '@jeesite/core/enums/appEnum';
import { APP_DARK_MODE_KEY, PROJ_CFG_KEY } from '@jeesite/core/enums/cacheEnum';
import { Persistent } from '@jeesite/core/utils/cache/persistent';
import { darkMode } from '@jeesite/core/settings/designSetting';
import { resetRouter } from '@jeesite/core/router';
import { deepMerge } from '@jeesite/core/utils';
import { switchSkin } from '@jeesite/core/api/sys/login';
interface AppState {
darkMode?: ThemeEnum;
// Page loading status
pageLoading: boolean;
// project config
projectConfig: ProjectConfig | null;
// When the window shrinks, remember some states, and restore these states when the window is restored
beforeMiniInfo: BeforeMiniState;
}
let timeId: TimeoutHandle;
export const useAppStore = defineStore('app', {
state: (): AppState => ({
darkMode: undefined,
pageLoading: false,
projectConfig: Persistent.getLocal(PROJ_CFG_KEY),
beforeMiniInfo: {},
}),
getters: {
getPageLoading(): boolean {
return this.pageLoading;
},
getDarkMode(): 'light' | 'dark' | string {
return this.darkMode || localStorage.getItem(APP_DARK_MODE_KEY) || darkMode;
},
getBeforeMiniInfo(): BeforeMiniState {
return this.beforeMiniInfo;
},
getProjectConfig(): ProjectConfig {
return this.projectConfig || ({} as ProjectConfig);
},
getHeaderSetting(): HeaderSetting {
return this.getProjectConfig.headerSetting;
},
getMenuSetting(): MenuSetting {
return this.getProjectConfig.menuSetting;
},
getTransitionSetting(): TransitionSetting {
return this.getProjectConfig.transitionSetting;
},
getMultiTabsSetting(): MultiTabsSetting {
return this.getProjectConfig.multiTabsSetting;
},
},
actions: {
setPageLoading(loading: boolean): void {
this.pageLoading = loading;
},
setDarkMode(mode: ThemeEnum): void {
this.darkMode = mode;
localStorage.setItem(APP_DARK_MODE_KEY, mode);
switchSkin();
},
setBeforeMiniInfo(state: BeforeMiniState): void {
this.beforeMiniInfo = state;
},
setProjectConfig(config: DeepPartial<ProjectConfig>): void {
this.projectConfig = deepMerge(this.projectConfig || {}, config);
Persistent.setLocal(PROJ_CFG_KEY, this.projectConfig);
},
async resetAllState() {
resetRouter();
// Persistent.clearAll(); 退出时不清理本地存储
},
async setPageLoadingAction(loading: boolean): Promise<void> {
if (loading) {
clearTimeout(timeId);
// Prevent flicker
timeId = setTimeout(() => {
this.setPageLoading(loading);
}, 50);
} else {
this.setPageLoading(loading);
clearTimeout(timeId);
}
},
},
});
// Need to be used outside the setup
export function useAppStoreWithOut() {
return useAppStore(store);
}

View File

@@ -0,0 +1,74 @@
import type { ErrorLogInfo } from '@jeesite/types/store';
import { defineStore } from 'pinia';
import { store } from '@jeesite/core/store';
import { formatToDateTime } from '@jeesite/core/utils/dateUtil';
import projectSetting from '@jeesite/core/settings/projectSetting';
import { ErrorTypeEnum } from '@jeesite/core/enums/exceptionEnum';
export interface ErrorLogState {
errorLogInfoList: Nullable<ErrorLogInfo[]>;
errorLogListCount: number;
}
export const useErrorLogStore = defineStore('app-error-log', {
state: (): ErrorLogState => ({
errorLogInfoList: null,
errorLogListCount: 0,
}),
getters: {
getErrorLogInfoList(): ErrorLogInfo[] {
return this.errorLogInfoList || [];
},
getErrorLogListCount(): number {
return this.errorLogListCount;
},
},
actions: {
addErrorLogInfo(info: ErrorLogInfo) {
const item = {
...info,
time: formatToDateTime(new Date()),
};
this.errorLogInfoList = [item, ...(this.errorLogInfoList || [])];
this.errorLogListCount += 1;
},
setErrorLogListCount(count: number): void {
this.errorLogListCount = count;
},
/**
* Triggered after ajax request error
* @param error
* @returns
*/
addAjaxErrorInfo(error) {
const { useErrorHandle } = projectSetting;
if (!useErrorHandle) {
return;
}
const errInfo: Partial<ErrorLogInfo> = {
message: error.message,
type: ErrorTypeEnum.AJAX,
};
if (error.response) {
const { config: { url = '', data: params = '', method = 'get', headers = {} } = {}, data = {} } =
error.response;
errInfo.url = url;
errInfo.name = 'Ajax Error!';
errInfo.file = '-';
errInfo.stack = JSON.stringify(data);
errInfo.detail = JSON.stringify({ params, method, headers });
}
this.addErrorLogInfo(errInfo as ErrorLogInfo);
},
},
});
// Need to be used outside the setup
export function useErrorLogStoreWithOut() {
return useErrorLogStore(store);
}

View File

@@ -0,0 +1,54 @@
import type { LocaleSetting, LocaleType } from '@jeesite/types/config';
import { defineStore } from 'pinia';
import { store } from '@jeesite/core/store';
import { LOCALE_KEY } from '@jeesite/core/enums/cacheEnum';
import { createLocalStorage } from '@jeesite/core/utils/cache';
import { localeSetting } from '@jeesite/core/settings/localeSetting';
const ls = createLocalStorage();
const lsLocaleSetting = (ls.get(LOCALE_KEY) || localeSetting) as LocaleSetting;
interface LocaleState {
localInfo: LocaleSetting;
}
export const useLocaleStore = defineStore('app-locale', {
state: (): LocaleState => ({
localInfo: lsLocaleSetting,
}),
getters: {
getShowPicker(): boolean {
return !!this.localInfo?.showPicker;
},
getLocale(): LocaleType {
return this.localInfo?.locale ?? 'zh_CN';
},
},
actions: {
/**
* Set up multilingual information and cache
* @param info multilingual info
*/
setLocaleInfo(info: Partial<LocaleSetting>) {
this.localInfo = { ...this.localInfo, ...info };
ls.set(LOCALE_KEY, this.localInfo);
},
/**
* Initialize multilingual information and load the existing configuration from the local cache
*/
initLocale() {
this.setLocaleInfo({
...localeSetting,
...this.localInfo,
});
},
},
});
// Need to be used outside the setup
export function useLocaleStoreWithOut() {
return useLocaleStore(store);
}

View File

@@ -0,0 +1,59 @@
import type { LockInfo } from '@jeesite/types/store';
import { defineStore } from 'pinia';
import { LOCK_INFO_KEY } from '@jeesite/core/enums/cacheEnum';
import { Persistent } from '@jeesite/core/utils/cache/persistent';
import { useUserStore } from './user';
interface LockState {
lockInfo: Nullable<LockInfo>;
}
export const useLockStore = defineStore('app-lock', {
state: (): LockState => ({
lockInfo: Persistent.getLocal(LOCK_INFO_KEY),
}),
getters: {
getLockInfo(): Nullable<LockInfo> {
return this.lockInfo;
},
},
actions: {
setLockInfo(info: LockInfo) {
this.lockInfo = Object.assign({}, this.lockInfo, info);
Persistent.setLocal(LOCK_INFO_KEY, this.lockInfo, true);
},
resetLockInfo() {
Persistent.removeLocal(LOCK_INFO_KEY, true);
this.lockInfo = null;
},
// Unlock
async unLock(password?: string) {
const userStore = useUserStore();
if (this.lockInfo?.pwd === password) {
this.resetLockInfo();
return true;
}
const tryLogin = async () => {
try {
const username = userStore.getUserInfo?.loginCode;
const res = await userStore.login({
username: username,
password: password!,
goHome: false,
mode: 'none',
});
if (res.result == 'true') {
this.resetLockInfo();
return true;
}
} catch (error: any) {
console.log(error);
}
return false;
};
return await tryLogin();
},
},
});

View File

@@ -0,0 +1,320 @@
import type { RouteLocationNormalized, RouteLocationRaw, Router } from 'vue-router';
import { toRaw, unref } from 'vue';
import { defineStore } from 'pinia';
import { store } from '@jeesite/core/store';
import { useGo, useRedo } from '@jeesite/core/hooks/web/usePage';
import { Persistent } from '@jeesite/core/utils/cache/persistent';
import { PageEnum } from '@jeesite/core/enums/pageEnum';
import { PAGE_NOT_FOUND_ROUTE, REDIRECT_ROUTE } from '@jeesite/core/router/routes/basic';
import { getRawRoute } from '@jeesite/core/utils';
import { MULTIPLE_TABS_KEY } from '@jeesite/core/enums/cacheEnum';
import projectSetting from '@jeesite/core/settings/projectSetting';
import { useUserStore } from '@jeesite/core/store/modules/user';
export interface MultipleTabState {
cacheTabList: Set<string>;
tabList: RouteLocationNormalized[];
lastDragEndIndex: number;
}
// function handleGotoPage(router: Router) {
// const go = useGo(router);
// go(unref(router.currentRoute).path, true);
// }
const cacheTab = projectSetting.multiTabsSetting.cache;
export const useMultipleTabStore = defineStore('app-multiple-tab', {
state: (): MultipleTabState => ({
// Tabs that need to be cached
cacheTabList: new Set(),
// multiple tab list
tabList: cacheTab ? Persistent.getLocal(MULTIPLE_TABS_KEY) || [] : [],
// Index of the last moved tab
lastDragEndIndex: 0,
}),
getters: {
getTabList(): RouteLocationNormalized[] {
return this.tabList;
},
getCachedTabList(): string[] {
return Array.from(this.cacheTabList);
},
getLastDragEndIndex(): number {
return this.lastDragEndIndex;
},
},
actions: {
/**
* Update the cache according to the currently opened tabs
*/
async updateCacheTab() {
const cacheMap: Set<string> = new Set();
for (const tab of this.tabList) {
const item = getRawRoute(tab);
// Ignore the cache
const needCache = !item.meta?.ignoreKeepAlive;
if (!needCache) {
continue;
}
const name = item.name as string;
cacheMap.add(name);
}
this.cacheTabList = cacheMap;
},
/**
* Refresh tabs
*/
async refreshPage(router: Router) {
const { currentRoute } = router;
const route = unref(currentRoute);
const name = route.name;
const findTab = this.getCachedTabList.find((item) => item === name);
if (findTab) {
this.cacheTabList.delete(findTab);
}
const redo = useRedo(router);
await redo();
},
clearCacheTabs(): void {
this.cacheTabList = new Set();
},
resetState(): void {
this.tabList = [];
this.clearCacheTabs();
},
goToPage(router: Router) {
const go = useGo(router);
const len = this.tabList.length;
const { path } = unref(router.currentRoute);
let toPath: PageEnum | string = PageEnum.BASE_HOME;
if (len > 0) {
const page = this.tabList[len - 1];
const p = page.fullPath || page.path;
if (p) {
toPath = p;
}
}
// Jump to the current page and report an error
path !== toPath && go(toPath as PageEnum, true);
},
async addTab(route: RouteLocationNormalized) {
const { path, name, fullPath, params, query } = getRawRoute(route);
// 404 The page does not need to add a tab
if (
path === PageEnum.ERROR_PAGE ||
path === PageEnum.BASE_LOGIN ||
!name ||
[REDIRECT_ROUTE.name, PAGE_NOT_FOUND_ROUTE.name].includes(name as string)
) {
return;
}
let updateIndex = -1;
// Existing pages, do not add tabs repeatedly
const tabHasExits = this.tabList.some((tab, index) => {
updateIndex = index;
return (tab.fullPath || tab.path) === (fullPath || path);
});
// If the tab already exists, perform the update operation
if (tabHasExits) {
const curTab = toRaw(this.tabList)[updateIndex];
if (!curTab) {
return;
}
curTab.params = params || curTab.params;
curTab.query = query || curTab.query;
curTab.fullPath = fullPath || curTab.fullPath;
this.tabList.splice(updateIndex, 1, curTab);
} else {
// Add tab
this.tabList.push(route);
}
await this.updateCacheTab();
cacheTab && Persistent.setLocal(MULTIPLE_TABS_KEY, this.tabList);
},
async closeTab(tab: RouteLocationNormalized, router: Router) {
const getToTarget = (tabItem: RouteLocationNormalized) => {
const { params, path, query } = tabItem;
return {
params: params || {},
path,
query: query || {},
};
};
const close = (route: RouteLocationNormalized) => {
const { path, fullPath, meta: { affix } = {} } = route;
if (affix) {
return;
}
const index = this.tabList.findIndex((item) => (item.fullPath || item.path) === (fullPath || path));
index !== -1 && this.tabList.splice(index, 1);
};
const { currentRoute, replace } = router;
const { path, fullPath } = unref(currentRoute);
if ((fullPath || path) !== (tab.fullPath || tab.path)) {
// Closed is not the activation tab
close(tab);
return;
}
// Closed is activated atb
let toTarget: RouteLocationRaw = {};
const index = this.tabList.findIndex((item) => (item.fullPath || item.path) === (fullPath || path));
// If the current is the leftmost tab
if (index === 0) {
// There is only one tab, then jump to the homepage, otherwise jump to the right tab
if (this.tabList.length === 1) {
const userStore = useUserStore();
toTarget = userStore.getUserInfo.homePath || PageEnum.BASE_HOME;
} else {
// Jump to the right tab
const page = this.tabList[index + 1];
toTarget = getToTarget(page);
}
} else {
// Close the current tab
const page = this.tabList[index - 1];
toTarget = getToTarget(page);
}
close(currentRoute.value);
await replace(toTarget);
},
// Close according to key
async closeTabByKey(key: string, router: Router) {
const index = this.tabList.findIndex((item) => (item.fullPath || item.path) === key);
index !== -1 && this.closeTab(this.tabList[index], router);
},
// Sort the tabs
async sortTabs(oldIndex: number, newIndex: number) {
const currentTab = this.tabList[oldIndex];
this.tabList.splice(oldIndex, 1);
this.tabList.splice(newIndex, 0, currentTab);
this.lastDragEndIndex = this.lastDragEndIndex + 1;
},
// Close the tab on the right and jump
async closeLeftTabs(route: RouteLocationNormalized, router: Router) {
const index = this.tabList.findIndex((item) => (item.fullPath || item.path) === (route.fullPath || route.path));
if (index > 0) {
const leftTabs = this.tabList.slice(0, index);
const pathList: string[] = [];
for (const item of leftTabs) {
const affix = item?.meta?.affix ?? false;
if (!affix) {
pathList.push(item.fullPath || item.path);
}
}
await this.bulkCloseTabs(pathList);
}
await this.updateCacheTab();
// handleGotoPage(router);
},
// Close the tab on the left and jump
async closeRightTabs(route: RouteLocationNormalized, router: Router) {
const index = this.tabList.findIndex((item) => (item.fullPath || item.path) === (route.fullPath || route.path));
if (index >= 0 && index < this.tabList.length - 1) {
const rightTabs = this.tabList.slice(index + 1, this.tabList.length);
const pathList: string[] = [];
for (const item of rightTabs) {
const affix = item?.meta?.affix ?? false;
if (!affix) {
pathList.push(item.fullPath || item.path);
}
}
await this.bulkCloseTabs(pathList);
}
await this.updateCacheTab();
// handleGotoPage(router);
},
async closeAllTab(router: Router) {
this.tabList = this.tabList.filter((item) => item?.meta?.affix ?? false);
this.clearCacheTabs();
this.goToPage(router);
},
/**
* Close other tabs
*/
async closeOtherTabs(route: RouteLocationNormalized, router: Router) {
const closePathList = this.tabList.map((item) => item.fullPath || item.path);
const pathList: string[] = [];
for (const fullPath of closePathList) {
if (fullPath !== (route.fullPath || route.path)) {
const closeItem = this.tabList.find((item) => (item.fullPath || item.path) === fullPath);
if (!closeItem) {
continue;
}
const affix = closeItem?.meta?.affix ?? false;
if (!affix) {
pathList.push(closeItem.fullPath || closeItem.path);
}
}
}
await this.bulkCloseTabs(pathList);
await this.updateCacheTab();
// handleGotoPage(router);
},
/**
* Close tabs in bulk
*/
async bulkCloseTabs(pathList: string[]) {
this.tabList = this.tabList.filter((item) => !pathList.includes(item.fullPath || item.path));
},
/**
* Set tab's title
*/
async setTabTitle(title: string, route: RouteLocationNormalized) {
const findTab = this.getTabList.find((item) => item === route);
if (findTab) {
findTab.meta.title = title;
await this.updateCacheTab();
}
},
/**
* replace tab's path
*/
async updateTabPath(fullPath: string, route: RouteLocationNormalized) {
const findTab = this.getTabList.find((item) => item === route);
if (findTab) {
findTab.fullPath = fullPath;
findTab.path = fullPath;
await this.updateCacheTab();
}
},
},
});
// Need to be used outside the setup
export function useMultipleTabWithOutStore() {
return useMultipleTabStore(store);
}

View File

@@ -0,0 +1,220 @@
/**
* Copyright (c) 2013-Now http://jeesite.com All rights reserved.
* No deletion without permission, or be held responsible to law.
* @author Vben、ThinkGem
*/
import type { AppRouteRecordRaw, Menu } from '@jeesite/core/router/types';
import { defineStore } from 'pinia';
import { store } from '@jeesite/core/store';
import { useI18n } from '@jeesite/core/hooks/web/useI18n';
import { useUserStore } from './user';
import { useAppStoreWithOut } from './app';
import { toRaw } from 'vue';
import { transformObjToRoute, flatMultiLevelRoutes } from '@jeesite/core/router/helper/routeHelper';
import { transformRouteToMenu } from '@jeesite/core/router/helper/menuHelper';
import projectSetting from '@jeesite/core/settings/projectSetting';
import { PermissionModeEnum } from '@jeesite/core/enums/appEnum';
import { asyncRoutes } from '@jeesite/core/router/routes';
import { ERROR_LOG_ROUTE } from '@jeesite/core/router/routes/basic';
import { filter } from '@jeesite/core/utils/helper/treeHelper';
import { authInfoApi, menuRouteApi } from '@jeesite/core/api/sys/login';
import { useMessage } from '@jeesite/core/hooks/web/useMessage';
import { PageEnum } from '@jeesite/core/enums/pageEnum';
interface PermissionState {
// Permission code list
permCodeList: string[][];
// Whether the route has been dynamically added
isDynamicAddedRoute: boolean;
// To trigger a menu update
lastBuildMenuTime: number;
// Backstage menu list
backMenuList: Menu[];
frontMenuList: Menu[];
}
export const usePermissionStore = defineStore('app-permission', {
state: (): PermissionState => ({
permCodeList: [],
// Whether the route has been dynamically added
isDynamicAddedRoute: false,
// To trigger a menu update
lastBuildMenuTime: 0,
// Backstage menu list
backMenuList: [],
// menu List
frontMenuList: [],
}),
getters: {
getPermCodeList(): string[][] {
return this.permCodeList;
},
getBackMenuList(): Menu[] {
return this.backMenuList;
},
getFrontMenuList(): Menu[] {
return this.frontMenuList;
},
getLastBuildMenuTime(): number {
return this.lastBuildMenuTime;
},
getIsDynamicAddedRoute(): boolean {
return this.isDynamicAddedRoute;
},
},
actions: {
setPermCodeList(codeList: string[][]) {
this.permCodeList = codeList;
},
setBackMenuList(list: Menu[]) {
this.backMenuList = list;
list?.length > 0 && this.setLastBuildMenuTime();
},
setFrontMenuList(list: Menu[]) {
this.frontMenuList = list;
},
setLastBuildMenuTime() {
this.lastBuildMenuTime = new Date().getTime();
},
setDynamicAddedRoute(added: boolean) {
this.isDynamicAddedRoute = added;
},
resetState(): void {
this.isDynamicAddedRoute = false;
this.permCodeList = [];
this.backMenuList = [];
this.lastBuildMenuTime = 0;
},
async changePermissionCode() {
const userStore = useUserStore();
const authInfo = await authInfoApi();
this.setPermCodeList(authInfo.stringPermissions.map((e) => e.split(':')));
userStore.setRoleList(authInfo.roles || []);
},
async buildRoutesAction(): Promise<AppRouteRecordRaw[]> {
const { t } = useI18n();
const userStore = useUserStore();
const appStore = useAppStoreWithOut();
let routes: AppRouteRecordRaw[] = [];
const roleList = toRaw(userStore.getRoleList) || [];
const { permissionMode = projectSetting.permissionMode } = appStore.getProjectConfig;
const routeFilter = (route: AppRouteRecordRaw) => {
const { meta } = route;
const { roles } = meta || {};
if (!roles) return true;
return roleList.some((role) => roles.includes(role));
};
const routeRemoveIgnoreFilter = (route: AppRouteRecordRaw) => {
const { meta } = route;
const { ignoreRoute } = meta || {};
return !ignoreRoute;
};
/**
* @description 根据设置的首页path修正routes中的affix标记固定首页
* */
const patchHomeAffix = (routes: AppRouteRecordRaw[]) => {
if (!routes || routes.length === 0) return;
let homePath: string = userStore.getUserInfo.homePath || PageEnum.BASE_HOME;
function patcher(routes: AppRouteRecordRaw[], parentPath = '') {
if (parentPath) parentPath = parentPath + '/';
routes.forEach((route: AppRouteRecordRaw) => {
const { path, children, redirect } = route;
const currentPath = path.startsWith('/') ? path : parentPath + path;
if (currentPath === homePath) {
if (redirect) {
homePath = route.redirect! as string;
} else {
route.meta = Object.assign({}, route.meta, { affix: true });
throw new Error('end');
}
}
children && children.length > 0 && patcher(children, currentPath);
});
}
try {
patcher(routes);
} catch (e) {
// 已处理完毕跳出循环
}
return;
};
switch (permissionMode) {
case PermissionModeEnum.ROLE:
routes = filter(asyncRoutes, routeFilter);
routes = routes.filter(routeFilter);
// Convert multi-level routing to level 2 routing
routes = flatMultiLevelRoutes(routes);
break;
case PermissionModeEnum.ROUTE_MAPPING:
routes = filter(asyncRoutes, routeFilter);
routes = routes.filter(routeFilter);
const menuList = transformRouteToMenu(routes, true);
routes = filter(routes, routeRemoveIgnoreFilter);
routes = routes.filter(routeRemoveIgnoreFilter);
menuList.sort((a, b) => {
return (a.meta?.orderNo || 0) - (b.meta?.orderNo || 0);
});
this.setFrontMenuList(menuList);
// Convert multi-level routing to level 2 routing
routes = flatMultiLevelRoutes(routes);
break;
// If you are sure that you do not need to do background dynamic permissions, please comment the entire judgment below
case PermissionModeEnum.BACK:
const { createMessage } = useMessage();
createMessage.loading({
content: t('sys.app.menuLoading'),
duration: 1,
});
// !Simulate to obtain permission codes from the background,
// this function may only need to be executed once, and the actual project can be put at the right time by itself
let routeList: AppRouteRecordRaw[] = [];
try {
this.changePermissionCode();
routeList = (await menuRouteApi()) as AppRouteRecordRaw[];
} catch (error) {
console.error(error);
}
// Dynamically introduce components
routeList = transformObjToRoute(routeList);
// Background routing to menu structure
const backMenuList = transformRouteToMenu(routeList);
this.setBackMenuList(backMenuList);
// remove meta.ignoreRoute item
// routeList = filter(routeList, routeRemoveIgnoreFilter);
// routeList = routeList.filter(routeRemoveIgnoreFilter);
routeList = flatMultiLevelRoutes(routeList);
routes = [...asyncRoutes, ...routeList];
break;
}
routes.push(ERROR_LOG_ROUTE);
patchHomeAffix(routes);
return routes;
},
},
});
// Need to be used outside the setup
export function usePermissionStoreWithOut() {
return usePermissionStore(store);
}

View File

@@ -0,0 +1,240 @@
/**
* Copyright (c) 2013-Now http://jeesite.com All rights reserved.
* No deletion without permission, or be held responsible to law.
* @author Vben、ThinkGem
*/
import type { UserInfo } from '@jeesite/types/store';
import type { ErrorMessageMode } from '@jeesite/types/axios';
import { defineStore } from 'pinia';
import { store } from '@jeesite/core/store';
import { RoleEnum } from '@jeesite/core/enums/roleEnum';
import { PageEnum } from '@jeesite/core/enums/pageEnum';
import { TOKEN_KEY, ROLES_KEY, USER_INFO_KEY, SESSION_TIMEOUT_KEY } from '@jeesite/core/enums/cacheEnum';
import { getAuthCache, setAuthCache } from '@jeesite/core/utils/auth';
import { loginApi, logoutApi, userInfoApi, LoginParams, LoginResult } from '@jeesite/core/api/sys/login';
// import { useI18n } from '@jeesite/core/hooks/web/useI18n';
import { useMessage } from '@jeesite/core/hooks/web/useMessage';
import { router } from '@jeesite/core/router';
import { usePermissionStore } from '@jeesite/core/store/modules/permission';
import { RouteRecordRaw } from 'vue-router';
import { PAGE_NOT_FOUND_ROUTE } from '@jeesite/core/router/routes/basic';
import { useGlobSetting } from '@jeesite/core/hooks/setting';
import logoImg from '@jeesite/assets/images/logo.png';
import { mitt, Emitter } from '@jeesite/core/utils/mitt';
const { showMessage, createConfirm } = useMessage();
interface UserState {
userInfo: Nullable<UserInfo>;
token?: string;
roleList: RoleEnum[] | string[];
sessionTimeout?: boolean;
lastUpdateTime: number;
pageCache: any;
emitter: Emitter<any>;
}
export const useUserStore = defineStore('app-user', {
state: (): UserState => ({
// user info
userInfo: null,
// token
token: undefined,
// roleList
roleList: [],
// Whether the login expired
sessionTimeout: undefined,
// Last fetch time
lastUpdateTime: 0,
// 刷新页面及销毁的缓存
pageCache: {},
// 全局事件 think gem
emitter: mitt(),
}),
getters: {
getUserInfo(): UserInfo {
return this.userInfo || getAuthCache<UserInfo>(USER_INFO_KEY) || {};
},
getToken(): string {
return this.token || getAuthCache<string>(TOKEN_KEY);
},
getRoleList(): RoleEnum[] | string[] {
return this.roleList.length > 0 ? this.roleList : getAuthCache<RoleEnum[] | string[]>(ROLES_KEY);
},
getSessionTimeout(): boolean {
return !!(this.sessionTimeout || getAuthCache<boolean>(SESSION_TIMEOUT_KEY));
},
getLastUpdateTime(): number {
return this.lastUpdateTime;
},
getPageCache(): any {
return this.pageCache;
},
getEmitter(): any {
return this.emitter;
},
},
actions: {
setToken(token: string | undefined) {
this.token = token ? token : ''; // for null or undefined value
this.lastUpdateTime = new Date().getTime();
setAuthCache(TOKEN_KEY, token);
},
setSessionTimeout(flag: boolean) {
this.sessionTimeout = flag;
setAuthCache(SESSION_TIMEOUT_KEY, flag);
},
setUserInfo(res: Recordable | null) {
const info: UserInfo = res?.user;
if (res && info) {
const { ctxPath } = useGlobSetting();
let url = info.avatarUrl || '/ctxPath/static/images/user1.jpg';
url = url.replace('/ctxPath/', ctxPath + '/');
info.avatarUrl = url || logoImg;
info.homePath = res.desktopUrl;
info.roleList = res.roleList;
info.postList = res.postList;
}
this.userInfo = info;
this.lastUpdateTime = new Date().getTime();
setAuthCache(USER_INFO_KEY, info);
},
setRoleList(roleList: RoleEnum[] | string[]) {
this.roleList = roleList;
setAuthCache(ROLES_KEY, roleList);
},
resetState() {
this.userInfo = null;
this.token = '';
this.roleList = [];
this.sessionTimeout = true;
},
setPageCache(key: string, value: any) {
this.pageCache[key] = value;
},
getPageCacheByKey(key: string, defaultValue?: any): any {
if (!this.pageCache[key] && defaultValue) {
this.pageCache[key] = defaultValue;
}
return this.pageCache[key];
},
async login(
params: LoginParams & {
goHome?: boolean;
mode?: ErrorMessageMode;
},
) {
const { goHome = true, mode, ...loginParams } = params;
const res = await loginApi(loginParams, mode);
if (res.result !== 'true') {
showMessage(res.message);
this.initPageCache(res);
return res;
}
await this.afterLoginAction(res, goHome);
return res;
},
// async afterLoginAction(goHome?: boolean): Promise<GetUserInfoModel | null> {
async afterLoginAction(res: LoginResult, goHome?: boolean) {
this.setUserInfo(res);
this.initPageCache(res);
this.setSessionTimeout(false);
const permissionStore = usePermissionStore();
if (!permissionStore.isDynamicAddedRoute) {
const routes = await permissionStore.buildRoutesAction();
routes.forEach((route) => {
router.addRoute(route as unknown as RouteRecordRaw);
});
router.addRoute(PAGE_NOT_FOUND_ROUTE as unknown as RouteRecordRaw);
permissionStore.setDynamicAddedRoute(true);
}
if (goHome) {
const currentRoute = router.currentRoute.value;
let path = currentRoute.query.redirect;
if (path !== '/') {
path = path || res.user?.homePath || PageEnum.BASE_HOME;
} else {
path = res.user?.homePath || PageEnum.BASE_HOME;
}
await router.replace(decodeURIComponent(path as string));
}
if (res['modifyPasswordTip']) {
createConfirm({
content: res['modifyPasswordTip'],
maskClosable: false,
iconType: 'info',
cancelText: '取消',
okText: '确定',
onOk: () => {
router.replace('/account/modPwd');
},
});
}
return res.user || null;
},
async getUserInfoAction() {
// if (!this.getToken) return null;
const res = await userInfoApi();
this.setUserInfo(res);
this.initPageCache(res);
// this.setRoleList(roleList);
this.setSessionTimeout(false);
return res.user;
},
initPageCache(res: LoginResult) {
this.setPageCache('demoMode', res.demoMode);
this.setPageCache('useCorpModel', res.useCorpModel);
this.setPageCache('modifyPasswordTip', res.modifyPasswordTip);
this.setPageCache('modifyPasswordMsg', res.modifyPasswordMsg);
this.setPageCache('sysCode', res.sysCode);
this.setPageCache('roleCode', res.roleCode);
this.setPageCache('postCode', res.postCode);
this.setPageCache('title', res.title);
this.setPageCache('company', res.company);
this.setPageCache('version', res.version);
this.setPageCache('year', res.year);
},
/**
* @description: logout
*/
async logout(goLogin = false) {
if (this.getToken) {
try {
await logoutApi();
} catch {
console.log('注销Token失败');
}
}
this.setToken(undefined);
this.setSessionTimeout(true);
this.setUserInfo(null);
this.setRoleList([]);
goLogin && (await router.replace(PageEnum.BASE_LOGIN));
},
/**
* @description: Confirm before logging out
*/
async confirmLoginOut() {
// const { createConfirm } = useMessage();
// const { t } = useI18n();
// createConfirm({
// iconType: 'warning',
// title: () => h('span', t('sys.app.logoutTip')),
// content: () => h('span', t('sys.app.logoutMessage')),
// onOk: async () => {
await this.logout(true);
// },
// });
},
},
});
// Global emit by think gem
export function useEmitter() {
return useUserStore().emitter;
}
// Need to be used outside the setup
export function useUserStoreWithOut() {
return useUserStore(store);
}