发布v2.0.0分支

This commit is contained in:
zhangtao
2025-07-13 19:31:23 +08:00
parent 4150a39713
commit 6a8ca3becc
342 changed files with 39441 additions and 14737 deletions
-139
View File
@@ -1,139 +0,0 @@
import { defineStore, createPinia } from "pinia";
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
import { getCurrentUserInfo } from "@/api/system/user";
import { getInitConfig } from "@/api/system/config";
import { getNoticeListAvailable } from '@/api/system/notice'
import { getInitDict } from '@/api/system/dict'
// pinia-plugin-persistedstate 持久化存储官方文档:https://prazdevs.github.io/pinia-plugin-persistedstate/zh/guide/
const store = createPinia()
store.use(piniaPluginPersistedstate)
export const useNoticeStore = defineStore("notice", {
state: () => ({
noticeList: [], // 存储系统配置
total: 0, // 存储系统配置
isNoticeLoaded: false, // 标记配置是否已加载
}),
actions: {
async getNotice() {
const response = await getNoticeListAvailable();
const { status_code, data } = response.data;
if (status_code === 200) {
this.noticeList = data.items;
this.total = data.total;
this.isNoticeLoaded = true;
}
},
clearUserInfo() {
this.noticeList = [];
this.total = 0;
this.isNoticeLoaded = false;
},
},
persist: true
});
export const useUserStore = defineStore("user", {
state: () => ({
basicInfo: {},
routeList: [],
hasGetRoute: false,
}),
actions: {
async getUserInfo() {
const response = await getCurrentUserInfo();
const result = response.data;
const routers = result.data.menus;
delete result.data.menus;
this.setRoute(routers);
this.basicInfo = { ...this.basicInfo, ...result.data };
},
setRoute(routers) {
this.routeList = routers;
this.hasGetRoute = true;
},
setAvatar(avatar) {
this.basicInfo = { ...this.basicInfo, avatar };
},
clearUserInfo() {
this.basicInfo = {};
this.routeList = [];
this.hasGetRoute = false;
},
},
persist: true // 当前 store 启用了持久化
});
export const useConfigStore = defineStore("config", {
state: () => ({
configData: {}, // 存储系统配置
isConfigLoaded: false, // 标记配置是否已加载
}),
actions: {
async getConfig() {
const response = await getInitConfig();
const { status_code, data } = response.data;
if (status_code === 200) {
data.forEach((item) => {
this.configData[item.config_key] = item.config_value;
})
this.isConfigLoaded = true;
}
},
getConfigValue(key) {
return this.configData[key] || null;
},
},
persist: true
});
export const useDictStore = defineStore('dict', {
state: () => ({
dictObj: {},
isLoaded: false,
}),
getters: {
getDictObjData() {
return this.dictObj
}
},
actions: {
// 批量获取字典数据
async getDict(types) {
try {
for (const type of types) {
const response = await getInitDict(type)
const result = response.data;
if (result.status_code === 200) {
// 格式化数据
this.dictObj[type] = result.data;
this.isLoaded = true;
}
}
} catch (error) {
console.error("获取字典数据失败", error);
}
},
getDictLabel(datas, value) {
const result = datas.find((item) => item.dict_value === value);
const dict_data = {
id: result.id,
dict_value: result.dict_value,
dict_label: result.dict_label,
dict_type: result.dict_type,
css_class: result.css_class,
list_class: result.list_class,
is_default: result.is_default,
}
return dict_data;
}
},
persist: true
})
+23
View File
@@ -0,0 +1,23 @@
import type { App } from "vue";
import { createPinia } from "pinia";
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
const store = createPinia();
store.use(piniaPluginPersistedstate)
// 全局注册 store
export function setupStore(app: App<Element>) {
app.use(store);
}
export * from "./modules/app.store";
export * from "./modules/permission.store";
export * from "./modules/settings.store";
export * from "./modules/tags-view.store";
export * from "./modules/user.store";
export * from "./modules/dict.store";
export * from "./modules/lock.store";
export * from "./modules/config.store";
export * from "./modules/notice.store";
export { store };
+107
View File
@@ -0,0 +1,107 @@
import { defaultSettings } from "@/settings";
// 导入 Element Plus 中英文语言包
import zhCn from "element-plus/es/locale/lang/zh-cn";
import en from "element-plus/es/locale/lang/en";
import { store } from "@/store";
import { DeviceEnum } from "@/enums/settings/device.enum";
import { SidebarStatus } from "@/enums/settings/layout.enum";
export const useAppStore = defineStore("app", () => {
// 设备类型
const device = useStorage("device", DeviceEnum.DESKTOP);
// 布局大小
const size = useStorage("size", defaultSettings.size);
// 语言
const language = useStorage("language", defaultSettings.language);
// 侧边栏状态
const sidebarStatus = useStorage("sidebarStatus", SidebarStatus.CLOSED);
const sidebar = reactive({
opened: sidebarStatus.value === SidebarStatus.OPENED,
withoutAnimation: false,
});
// 顶部菜单激活路径
const activeTopMenuPath = useStorage("activeTopMenuPath", "");
/**
* 根据语言标识读取对应的语言包
*/
const locale = computed(() => {
if (language?.value == "en") {
return en;
} else {
return zhCn;
}
});
// 切换侧边栏
function toggleSidebar() {
sidebar.opened = !sidebar.opened;
sidebarStatus.value = sidebar.opened ? SidebarStatus.OPENED : SidebarStatus.CLOSED;
}
// 关闭侧边栏
function closeSideBar() {
sidebar.opened = false;
sidebarStatus.value = SidebarStatus.CLOSED;
}
// 打开侧边栏
function openSideBar() {
sidebar.opened = true;
sidebarStatus.value = SidebarStatus.OPENED;
}
// 切换设备
function toggleDevice(val: string) {
device.value = val;
}
/**
* 改变布局大小
*
* @param val 布局大小 default | small | large
*/
function changeSize(val: string) {
size.value = val;
}
/**
* 切换语言
*
* @param val
*/
function changeLanguage(val: string) {
language.value = val;
}
/**
* 混合模式顶部切换
*/
function activeTopMenu(val: string) {
activeTopMenuPath.value = val;
}
return {
device,
sidebar,
language,
locale,
size,
activeTopMenu,
toggleDevice,
changeSize,
changeLanguage,
toggleSidebar,
closeSideBar,
openSideBar,
activeTopMenuPath,
};
});
/**
* 用于在组件外部(如在Pinia Store 中)使用 Pinia 提供的 store 实例。
* 官方文档解释了如何在组件外部使用 Pinia Store:
* https://pinia.vuejs.org/core-concepts/outside-component-usage.html#using-a-store-outside-of-a-component
*/
export function useAppStoreHook() {
return useAppStore(store);
}
@@ -0,0 +1,42 @@
import { store } from "@/store";
import ConfigAPI, { ConfigTable } from "@/api/system/config";
interface ConfigState {
sys_git_code: ConfigTable;
sys_help_doc: ConfigTable;
sys_keep_record: ConfigTable;
sys_login_background: ConfigTable;
sys_web_clause: ConfigTable;
sys_web_copyright: ConfigTable;
sys_web_description: ConfigTable;
sys_web_favicon: ConfigTable;
sys_web_logo: ConfigTable;
sys_web_privacy: ConfigTable;
sys_web_title: ConfigTable;
sys_web_version: ConfigTable;
}
export const useConfigStore = defineStore("config", {
state: () => ({
configData: {} as ConfigState, // 存储系统配置
isConfigLoaded: false, // 标记配置是否已加载
}),
actions: {
async getConfig() {
const response = await ConfigAPI.getInitConfig();
response.data.data.forEach((item: ConfigTable) => {
// 确保所有配置项都正确映射到 configData
if (item.config_value !== undefined) {
this.configData[item.config_key as keyof ConfigState] = item;
}
});
this.isConfigLoaded = true;
}
},
persist: true
});
export function useConfigStoreHook() {
return useConfigStore(store);
}
+50
View File
@@ -0,0 +1,50 @@
import { store } from "@/store";
import DictAPI, { DictDataTable } from "@/api/system/dict";
export const useDictStore = defineStore("dict", {
state: () => ({
dictData: {} as Record<string, DictDataTable[]>,
isLoaded: false,
}),
getters: {
getDictData(): Record<string, DictDataTable[]> {
return this.dictData;
},
},
actions: {
// 批量获取字典数据
async getDict(types: string[]) {
try {
for (const type of types) {
const response = await DictAPI.getInitDict(type);
this.dictData[type] = response.data.data as unknown as DictDataTable[];
this.isLoaded = true;
}
} catch (error) {
console.error("获取字典数据失败", error);
}
},
getDictLabel(type: string, value: string) {
const result = this.dictData[type].find((item) => item.dict_value === value);
if (!result) {
return value;
}
const dict_data = {
id: result.id,
dict_value: result.dict_value,
dict_label: result.dict_label,
dict_type: result.dict_type,
css_class: result.css_class,
list_class: result.list_class,
is_default: result.is_default,
};
return dict_data;
},
},
persist: true,
});
export function useDictStoreHook() {
return useDictStore(store);
}
+48
View File
@@ -0,0 +1,48 @@
import { defineStore } from 'pinia'
import { store } from '../index'
interface lockInfo {
isLock?: boolean
password?: string
}
interface LockState {
lockInfo: lockInfo
}
export const useLockStore = defineStore('lock', {
state: (): LockState => {
return {
lockInfo: {
// isLock: false, // 是否锁定屏幕
// password: '' // 锁屏密码
}
}
},
getters: {
getLockInfo(): lockInfo {
return this.lockInfo
}
},
actions: {
setLockInfo(lockInfo: lockInfo) {
this.lockInfo = lockInfo
},
resetLockInfo() {
this.lockInfo = {}
},
unLock(password: string) {
if (this.lockInfo?.password === password) {
this.resetLockInfo()
return true
} else {
return false
}
}
},
persist: true
})
export const useLockStoreWithOut = () => {
return useLockStore(store)
}
@@ -0,0 +1,29 @@
import { store } from "@/store";
import NoticeAPI, { NoticeTable } from "@/api/system/notice";
export const useNoticeStore = defineStore("notice", {
state: () => ({
noticeList: [] as NoticeTable[], // 存储系统配置
total: 0, // 存储系统配置
isNoticeLoaded: false, // 标记配置是否已加载
}),
actions: {
async getNotice() {
const response = await NoticeAPI.getNoticeListAvailable();
this.noticeList = response.data.data.items;
this.total = response.data.data.total;
this.isNoticeLoaded = true;
},
clearUserInfo() {
this.noticeList = [];
this.total = 0;
this.isNoticeLoaded = false;
},
},
persist: true
});
export function useNoticeStoreHook() {
return useNoticeStore(store);
}
@@ -0,0 +1,224 @@
import type { RouteRecordRaw } from "vue-router";
import router, { constantRoutes } from "@/router";
import { store, useUserStore } from "@/store";
import { listToTree } from "@/utils/common";
import { MenuTable } from "@/api/system/menu";
const modules = import.meta.glob("../../views/**/**.vue");
const Layout = () => import("@/layouts/index.vue");
export interface Meta {
/** 【目录】只有一个子路由是否始终显示 */
alwaysShow?: boolean;
/** 是否隐藏(true-是 false-否) */
hidden?: boolean;
/** ICON */
icon?: string;
/** 【菜单】是否开启页面缓存 */
keepAlive?: boolean;
/** 路由title */
title?: string;
/** 排序 */
order?: number;
/** 参数 */
params?: string;
/** 是否固定路由 */
affix?: boolean;
}
// 修改 component 类型,使其能接受动态导入函数
export interface RouteVO {
/** 子路由列表 */
children: RouteVO[];
/** 组件路径或组件导入函数 */
component?: string | (() => Promise<Component>);
/** 路由属性 */
meta?: Meta;
/** 路由名称 */
name?: string;
/** 路由路径 */
path?: string;
/** 跳转链接 */
redirect?: string;
}
export const generator = (routers: MenuTable[]): RouteVO[] => {
return routers.map((item) => {
const currentRouter: RouteVO = {
children: [],
path: item.route_path,
name: item.route_name,
component: item.component_path,
redirect: item.redirect,
meta: {
title: item.title,
icon: item.icon || undefined,
keepAlive: item.keep_alive,
hidden: item.hidden,
order: item.order,
alwaysShow: item.always_show,
params: item.params,
affix: item.affix,
},
};
if (item.children && item.children.length > 0) {
currentRouter.children = item.children ? generator(item.children) : [];
}
return currentRouter;
});
};
// router.beforeEach(async (to) => {
// const accessToken = Auth.getAccessToken();
// const userStore = useUserStore();
// if (!accessToken && to.name !== "Login") {
// return { name: "Login" };
// } else if (accessToken && to.name === "Login") {
// return { name: "Index" };
// } else if (accessToken && !userStore.hasGetRoute) {
// await userStore.getUserInfo();
// const routersTree = listToTree(userStore.routeList);
// const routerMap = generator(routersTree);
// constantRoutes[0].children = [...(constantRoutes[0].children || []), ...routerMap].filter(Boolean) as RouteRecordRaw[];
// router.addRoute(constantRoutes[0]);
// return to.fullPath;
// }
// });
export const usePermissionStore = defineStore("permission", () => {
// 存储所有路由,包括静态路由和动态路由
const routes = ref<RouteRecordRaw[]>([]);
// 混合模式左侧菜单路由
const sideMenuRoutes = ref<RouteRecordRaw[]>([]);
// 路由是否加载完成
const routesLoaded = ref(false);
/**
* 获取后台动态路由数据,解析并注册到全局路由
*
* @returns Promise<RouteRecordRaw[]> 解析后的动态路由列表
*/
async function generateRoutes() {
try {
const userStore = useUserStore();
// 确保获取用户信息和路由列表
if (!userStore.hasGetRoute) {
await userStore.getUserInfo();
}
const routersTree = listToTree(userStore.routeList);
const routerMap = generator(routersTree);
// 从用户绑定的路由列表获取数据
const dynamicRoutes = parseDynamicRoutes(routerMap);
// 重置路由,移除旧的动态路由
resetRouter();
// 重新添加所有路由
constantRoutes.forEach(route => {
router.addRoute(route);
});
routes.value = [...constantRoutes, ...dynamicRoutes];
routesLoaded.value = true;
console.log("✅ 动态路由:", (dynamicRoutes));
return dynamicRoutes;
} catch (error) {
console.error("❌ 生成路由失败:", error);
// 即使失败也要设置状态,避免无限重试
routesLoaded.value = false;
throw error;
}
}
/**
* 根据父菜单路径设置侧边菜单
*
* @param parentPath 父菜单的路径,用于查找对应的菜单项
*/
const updateSideMenu = (parentPath: string) => {
const matchedItem = routes.value.find((item) => item.path === parentPath);
if (matchedItem && matchedItem.children) {
sideMenuRoutes.value = matchedItem.children;
}
};
/**
* 重置路由
*/
const resetRouter = () => {
// 创建常量路由名称集合,用于O(1)时间复杂度的查找
const constantRouteNames = new Set(constantRoutes.map((route) => route.name).filter(Boolean));
// 从 router 实例中移除动态路由
routes.value.forEach((route) => {
if (route.name && !constantRouteNames.has(route.name)) {
router.removeRoute(route.name);
}
});
// 重置为仅包含常量路由
routes.value = [...constantRoutes];
sideMenuRoutes.value = [];
routesLoaded.value = false;
};
return {
routes,
sideMenuRoutes,
routesLoaded,
generateRoutes,
updateSideMenu,
resetRouter,
};
});
/**
* 解析后端返回的路由数据并转换为 Vue Router 兼容的路由配置
*
* @param rawRoutes 后端返回的原始路由数据
* @returns 解析后的路由集合
*/
const parseDynamicRoutes = (rawRoutes: RouteVO[]): RouteRecordRaw[] => {
const parsedRoutes: RouteRecordRaw[] = [];
rawRoutes.forEach((route) => {
const normalizedRoute = { ...route } as RouteRecordRaw;
// http://localhost:5180/#/dashboard/workplace
// http://localhost:5180/#/dashboard/workplace
// 处理组件路径
normalizedRoute.component =
!normalizedRoute.component
? Layout
: modules[`../../views/${normalizedRoute.component}.vue`] ||
modules["../../views/error/404.vue"];
// 递归解析子路由
if (normalizedRoute.children) {
normalizedRoute.children = parseDynamicRoutes(route.children);
}
parsedRoutes.push(normalizedRoute);
});
return parsedRoutes;
};
/**
* 导出此hook函数用于在非组件环境(如其他store、工具函数等)中获取权限store实例
*
* 在组件中可直接使用usePermissionStore(),但在组件外部需要传入store实例
* 此函数简化了这个过程,避免每次都手动传入store参数
*/
export function usePermissionStoreHook() {
return usePermissionStore(store);
}
@@ -0,0 +1,160 @@
import { defaultSettings } from "@/settings";
import { SidebarColor, ThemeMode } from "@/enums/settings/theme.enum";
import type { LayoutMode } from "@/enums/settings/layout.enum";
import { applyTheme, generateThemeColors, toggleDarkMode, toggleSidebarColor } from "@/utils/theme";
import { SETTINGS_KEYS } from "@/constants";
// 🎯 设置项类型定义
interface SettingsState {
// 界面显示设置
settingsVisible: boolean;
showTagsView: boolean;
showAppLogo: boolean;
showWatermark: boolean;
// 布局设置
layout: LayoutMode;
sidebarColorScheme: string;
// 主题设置
theme: ThemeMode;
themeColor: string;
}
// 🎯 可变更的设置项类型
type MutableSetting = Exclude<keyof SettingsState, "settingsVisible">;
type SettingValue<K extends MutableSetting> = SettingsState[K];
export const useSettingsStore = defineStore("setting", () => {
// 🎯 基础设置 - 非持久化
const settingsVisible = ref<boolean>(false);
// 🎯 持久化设置 - 使用分组常量
const showTagsView = useStorage<boolean>(
SETTINGS_KEYS.SHOW_TAGS_VIEW,
defaultSettings.showTagsView
);
const showAppLogo = useStorage<boolean>(SETTINGS_KEYS.SHOW_APP_LOGO, defaultSettings.showAppLogo);
const showWatermark = useStorage<boolean>(
SETTINGS_KEYS.SHOW_WATERMARK,
defaultSettings.showWatermark
);
const sidebarColorScheme = useStorage<string>(
SETTINGS_KEYS.SIDEBAR_COLOR_SCHEME,
defaultSettings.sidebarColorScheme
);
const layout = useStorage<LayoutMode>(SETTINGS_KEYS.LAYOUT, defaultSettings.layout as LayoutMode);
const themeColor = useStorage<string>(SETTINGS_KEYS.THEME_COLOR, defaultSettings.themeColor);
const theme = useStorage<ThemeMode>(SETTINGS_KEYS.THEME, defaultSettings.theme);
// 🎯 设置项映射
const settingsMap = {
showTagsView,
showAppLogo,
showWatermark,
sidebarColorScheme,
layout,
} as const;
// 🎯 监听器 - 主题变化
watch(
[theme, themeColor],
([newTheme, newThemeColor]) => {
toggleDarkMode(newTheme === ThemeMode.DARK);
const colors = generateThemeColors(newThemeColor, newTheme);
applyTheme(colors);
},
{ immediate: true }
);
// 🎯 监听器 - 侧边栏配色方案变化
watch(
[sidebarColorScheme],
([newSidebarColorScheme]) => {
toggleSidebarColor(newSidebarColorScheme === SidebarColor.CLASSIC_BLUE);
},
{ immediate: true }
);
// 🎯 统一的设置更新方法 - 类型安全
function updateSetting<K extends keyof typeof settingsMap>(key: K, value: SettingValue<K>): void {
const setting = settingsMap[key];
if (setting) {
(setting as Ref<any>).value = value;
}
}
// 🎯 主题相关的专用更新方法
function updateTheme(newTheme: ThemeMode): void {
theme.value = newTheme;
}
function updateThemeColor(newColor: string): void {
themeColor.value = newColor;
}
function updateSidebarColorScheme(newScheme: string): void {
sidebarColorScheme.value = newScheme;
}
function updateLayout(newLayout: LayoutMode): void {
layout.value = newLayout;
}
// 🎯 设置面板显示控制
function toggleSettingsPanel(): void {
settingsVisible.value = !settingsVisible.value;
}
function showSettingsPanel(): void {
settingsVisible.value = true;
}
function hideSettingsPanel(): void {
settingsVisible.value = false;
}
// 🎯 批量重置设置
function resetSettings(): void {
showTagsView.value = defaultSettings.showTagsView;
showAppLogo.value = defaultSettings.showAppLogo;
showWatermark.value = defaultSettings.showWatermark;
sidebarColorScheme.value = defaultSettings.sidebarColorScheme;
layout.value = defaultSettings.layout as LayoutMode;
themeColor.value = defaultSettings.themeColor;
theme.value = defaultSettings.theme;
}
return {
// 状态
settingsVisible,
showTagsView,
showAppLogo,
showWatermark,
sidebarColorScheme,
layout,
themeColor,
theme,
// 更新方法
updateSetting,
updateTheme,
updateThemeColor,
updateSidebarColorScheme,
updateLayout,
// 面板控制
toggleSettingsPanel,
showSettingsPanel,
hideSettingsPanel,
// 重置功能
resetSettings,
};
});
@@ -0,0 +1,257 @@
export const useTagsViewStore = defineStore("tagsView", () => {
const visitedViews = ref<TagView[]>([]);
const cachedViews = ref<string[]>([]);
const router = useRouter();
const route = useRoute();
/**
* 添加已访问视图到已访问视图列表中
*/
function addVisitedView(view: TagView) {
// 如果已经存在于已访问的视图列表中或者是重定向地址,则不再添加
if (view.path.startsWith("/redirect")) {
return;
}
if (visitedViews.value.some((v) => v.name === view.name)) {
return;
}
// 如果视图是固定的(affix),则在已访问的视图列表的开头添加
if (view.affix) {
visitedViews.value.unshift(view);
} else {
// 如果视图不是固定的,则在已访问的视图列表的末尾添加
visitedViews.value.push(view);
}
}
/**
* 添加缓存视图到缓存视图列表中
*/
function addCachedView(view: TagView) {
const viewName = view.name;
// 如果缓存视图名称已经存在于缓存视图列表中,则不再添加
if (cachedViews.value.includes(viewName)) {
return;
}
// 如果视图需要缓存(keepAlive),则将其路由名称添加到缓存视图列表中
if (view.keepAlive) {
cachedViews.value.push(viewName);
}
}
/**
* 从已访问视图列表中删除指定的视图
*/
function delVisitedView(view: TagView) {
return new Promise((resolve) => {
for (const [i, v] of visitedViews.value.entries()) {
// 找到与指定视图路径匹配的视图,在已访问视图列表中删除该视图
if (v.path === view.path) {
visitedViews.value.splice(i, 1);
break;
}
}
resolve([...visitedViews.value]);
});
}
function delCachedView(view: TagView) {
const viewName = view.name;
return new Promise((resolve) => {
const index = cachedViews.value.indexOf(viewName);
if (index > -1) {
cachedViews.value.splice(index, 1);
}
resolve([...cachedViews.value]);
});
}
function delOtherVisitedViews(view: TagView) {
return new Promise((resolve) => {
visitedViews.value = visitedViews.value.filter((v) => {
return v?.affix || v.path === view.path;
});
resolve([...visitedViews.value]);
});
}
function delOtherCachedViews(view: TagView) {
const viewName = view.name as string;
return new Promise((resolve) => {
const index = cachedViews.value.indexOf(viewName);
if (index > -1) {
cachedViews.value = cachedViews.value.slice(index, index + 1);
} else {
// if index = -1, there is no cached tags
cachedViews.value = [];
}
resolve([...cachedViews.value]);
});
}
function updateVisitedView(view: TagView) {
for (let v of visitedViews.value) {
if (v.path === view.path) {
v = Object.assign(v, view);
break;
}
}
}
function addView(view: TagView) {
addVisitedView(view);
addCachedView(view);
}
function delView(view: TagView) {
return new Promise((resolve) => {
delVisitedView(view);
delCachedView(view);
resolve({
visitedViews: [...visitedViews.value],
cachedViews: [...cachedViews.value],
});
});
}
function delOtherViews(view: TagView) {
return new Promise((resolve) => {
delOtherVisitedViews(view);
delOtherCachedViews(view);
resolve({
visitedViews: [...visitedViews.value],
cachedViews: [...cachedViews.value],
});
});
}
function delLeftViews(view: TagView) {
return new Promise((resolve) => {
const currIndex = visitedViews.value.findIndex((v) => v.path === view.path);
if (currIndex === -1) {
return;
}
visitedViews.value = visitedViews.value.filter((item, index) => {
if (index >= currIndex || item?.affix) {
return true;
}
const cacheIndex = cachedViews.value.indexOf(item.name);
if (cacheIndex > -1) {
cachedViews.value.splice(cacheIndex, 1);
}
return false;
});
resolve({
visitedViews: [...visitedViews.value],
});
});
}
function delRightViews(view: TagView) {
return new Promise((resolve) => {
const currIndex = visitedViews.value.findIndex((v) => v.path === view.path);
if (currIndex === -1) {
return;
}
visitedViews.value = visitedViews.value.filter((item, index) => {
if (index <= currIndex || item?.affix) {
return true;
}
});
resolve({
visitedViews: [...visitedViews.value],
});
});
}
function delAllViews() {
return new Promise((resolve) => {
const affixTags = visitedViews.value.filter((tag) => tag?.affix);
visitedViews.value = affixTags;
cachedViews.value = [];
resolve({
visitedViews: [...visitedViews.value],
cachedViews: [...cachedViews.value],
});
});
}
function delAllVisitedViews() {
return new Promise((resolve) => {
const affixTags = visitedViews.value.filter((tag) => tag?.affix);
visitedViews.value = affixTags;
resolve([...visitedViews.value]);
});
}
function delAllCachedViews() {
return new Promise((resolve) => {
cachedViews.value = [];
resolve([...cachedViews.value]);
});
}
/**
* 关闭当前tagView
*/
function closeCurrentView() {
const tags: TagView = {
name: route.name as string,
title: route.meta.title as string,
path: route.path,
fullPath: route.fullPath,
affix: route.meta?.affix,
keepAlive: route.meta?.keepAlive,
query: route.query,
};
delView(tags).then((res: any) => {
if (isActive(tags)) {
toLastView(res.visitedViews, tags);
}
});
}
function isActive(tag: TagView) {
return tag.path === route.path;
}
function toLastView(visitedViews: TagView[], view?: TagView) {
const latestView = visitedViews.slice(-1)[0];
if (latestView && latestView.fullPath) {
router.push(latestView.fullPath);
} else {
// now the default is to redirect to the home page if there is no tags-view,
// you can adjust it according to your needs.
if (view?.name === "Dashboard") {
// to reload home page
router.replace("/redirect" + view.fullPath);
} else {
router.push("/");
}
}
}
return {
visitedViews,
cachedViews,
addVisitedView,
addCachedView,
delVisitedView,
delCachedView,
delOtherVisitedViews,
delOtherCachedViews,
updateVisitedView,
addView,
delView,
delOtherViews,
delLeftViews,
delRightViews,
delAllViews,
delAllVisitedViews,
delAllCachedViews,
closeCurrentView,
isActive,
toLastView,
};
});
+125
View File
@@ -0,0 +1,125 @@
import { store, useTagsViewStore, usePermissionStoreHook } from "@/store";
import AuthAPI, {type LoginFormData } from "@/api/system/auth";
import UserAPI, {type UserInfo } from "@/api/system/user";
import type { MenuTable } from "@/api/system/menu";
import { Auth } from "@/utils/auth";
export const useUserStore = defineStore("user", {
state: () => ({
basicInfo: {} as UserInfo,
routeList: [] as MenuTable[],
hasGetRoute: false,
// 记住我状态
rememberMe: ref(Auth.getRememberMe()),
}),
getters: {
getBasicInfo: (state) => state.basicInfo,
getRouteList: (state) => state.routeList,
getHasGetRoute: (state) => state.hasGetRoute,
},
actions: {
// 获取用户信息
async getUserInfo() {
const response = await UserAPI.getCurrentUserInfo();
const routers = response.data.data.menus;
delete response.data.data.menus;
this.setRoute(routers);
this.basicInfo = { ...this.basicInfo, ...response.data.data };
},
setRoute(routers: any) {
this.routeList = routers;
this.hasGetRoute = true;
},
setAvatar(avatar: string) {
this.basicInfo = { ...this.basicInfo, avatar };
},
clearUserInfo() {
this.basicInfo = {};
this.routeList = [];
this.hasGetRoute = false;
},
// 登录
login(LoginFormData: LoginFormData) {
return new Promise<void>((resolve, reject) => {
AuthAPI.login(LoginFormData)
.then((response) => {
// 保存记住我状态和token
this.rememberMe = LoginFormData.remember;
Auth.setTokens(response.data.data.access_token, response.data.data.refresh_token, this.rememberMe);
resolve();
})
.catch((error) => {
reject(error);
});
});
},
// 登出
logout() {
return new Promise<void>((resolve, reject) => {
AuthAPI.logout({token: Auth.getAccessToken()})
.then(() => {
// 重置所有系统状态
this.resetAllState();
resolve();
})
.catch((error) => {
reject(error);
});
});
},
// 重置所有状态
resetAllState() {
// 重置用户状态
Auth.clearAuth();
// 重置路由
usePermissionStoreHook().resetRouter();
// 清除标签视图
useTagsViewStore().delAllViews();
return Promise.resolve();
},
// 刷新token
refreshToken() {
const refreshToken = Auth.getRefreshToken();
if (!refreshToken) {
return Promise.reject(new Error("没有有效的刷新令牌"));
}
return new Promise<void>((resolve, reject) => {
AuthAPI.refreshToken({refresh_token: refreshToken})
.then((data) => {
// 更新令牌,保持当前记住我状态
Auth.setTokens(data.access_token, data.refresh_token, Auth.getRememberMe());
resolve();
})
.catch((error) => {
console.log(" refreshToken 刷新失败", error);
reject(error);
});
});
}
},
persist: true
});
/**
* 在组件外部使用UserStore的钩子函数
* @see https://pinia.vuejs.org/core-concepts/outside-component-usage.html
*/
export function useUserStoreHook() {
return useUserStore(store);
}