签入版本
CI / lint (push) Canceled after 0s
CI / test (push) Canceled after 0s

This commit is contained in:
2026-09-16 10:08:07 +08:00
commit fda0fe1a53
820 changed files with 97186 additions and 0 deletions
+10
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 };
+136
View File
@@ -0,0 +1,136 @@
import { toRaw, unref } from 'vue';
import { defineStore } from 'pinia';
import { RouteRecordRaw } from 'vue-router';
import { store } from '@/store';
import { asyncRoutes, constantRouter } from '@/router/index';
import { generatorDynamicRouter } from '@/router/generator-routers';
import { useProjectSetting } from '@/hooks/setting/useProjectSetting';
interface TreeHelperConfig {
id: string;
children: string;
pid: string;
}
const DEFAULT_CONFIG: TreeHelperConfig = {
id: 'id',
children: 'children',
pid: 'pid',
};
const getConfig = (config: Partial<TreeHelperConfig>) => Object.assign({}, DEFAULT_CONFIG, config);
export interface IAsyncRouteState {
menus: RouteRecordRaw[];
routers: any[];
addRouters: any[];
keepAliveComponents: string[];
isDynamicAddedRoute: boolean;
}
function filter<T = any>(
tree: T[],
func: (n: T) => boolean,
config: Partial<TreeHelperConfig> = {},
): T[] {
config = getConfig(config);
const children = config.children as string;
function listFilter(list: T[]) {
return list
.map((node: any) => ({ ...node }))
.filter((node) => {
node[children] = node[children] && listFilter(node[children]);
return func(node) || (node[children] && node[children].length);
});
}
return listFilter(tree);
}
export const useAsyncRouteStore = defineStore({
id: 'app-async-route',
state: (): IAsyncRouteState => ({
menus: [],
routers: constantRouter,
addRouters: [],
keepAliveComponents: [],
// Whether the route has been dynamically added
isDynamicAddedRoute: false,
}),
getters: {
getRouters() {
return toRaw(this.addRouters);
},
getMenus(): RouteRecordRaw[] {
return this.menus;
},
getIsDynamicAddedRoute(): boolean {
return this.isDynamicAddedRoute;
},
},
actions: {
//从缓存列表删除路由
removeKeepAliveComponents(compNames) {
if (!compNames || !compNames.length) return;
this.keepAliveComponents = this.keepAliveComponents.filter(
(item) => !compNames.includes(item),
);
},
setDynamicAddedRoute(added: boolean) {
this.isDynamicAddedRoute = added;
},
// 设置动态路由
setRouters(routers) {
this.addRouters = routers;
this.routers = constantRouter.concat(routers);
},
setMenus(menus) {
// 设置动态路由
this.menus = menus;
},
setKeepAliveComponents(compNames) {
// 设置需要缓存的组件
this.keepAliveComponents = compNames;
},
async generateRoutes(data) {
let accessedRouters;
const permissionsList = data.permissions || [];
const routeFilter = (route) => {
const { meta } = route;
const { permissions, authEvery } = meta || {};
if (!permissions) return true;
if (authEvery === true) {
return permissionsList.every((item) => permissions.includes(item.value));
}
return permissionsList.some((item) => permissions.includes(item.value));
};
const { getPermissionMode } = useProjectSetting();
const permissionMode = unref(getPermissionMode);
if (permissionMode === 'BACK') {
// 动态获取菜单
try {
accessedRouters = await generatorDynamicRouter();
} catch (error) {
console.log(error);
}
} else {
try {
//过滤账户是否拥有某一个权限,并将菜单从加载列表移除
accessedRouters = filter(asyncRoutes, routeFilter);
} catch (error) {
console.log(error);
}
}
accessedRouters = accessedRouters.filter(routeFilter);
this.setRouters(accessedRouters);
this.setMenus(accessedRouters);
return toRaw(accessedRouters);
},
},
});
// Need to be used outside the setup
export function useAsyncRouteStoreWidthOut() {
return useAsyncRouteStore(store);
}
+40
View File
@@ -0,0 +1,40 @@
import { defineStore } from 'pinia';
import { store } from '@/store';
import designSetting from '@/settings/designSetting';
const { darkTheme, appTheme, appThemeList } = designSetting;
interface DesignSettingState {
//深色主题
darkTheme: boolean;
//系统风格
appTheme: string;
//系统内置风格
appThemeList: string[];
}
export const useDesignSettingStore = defineStore({
id: 'app-design-setting',
state: (): DesignSettingState => ({
darkTheme,
appTheme,
appThemeList,
}),
getters: {
getDarkTheme(): boolean {
return this.darkTheme;
},
getAppTheme(): string {
return this.appTheme;
},
getAppThemeList(): string[] {
return this.appThemeList;
},
},
actions: {},
});
// Need to be used outside the setup
export function useDesignSettingWithOut() {
return useDesignSettingStore(store);
}
+31
View File
@@ -0,0 +1,31 @@
import { defineStore } from 'pinia';
import { IS_LOCKSCREEN } from '@/store/mutation-types';
import { storage } from '@/utils/Storage';
// 长时间不操作默认锁屏时间
const initTime = 60 * 60;
const isLock = storage.get(IS_LOCKSCREEN, false);
export type ILockscreenState = {
isLock: boolean; // 是否锁屏
lockTime: number;
};
export const useLockscreenStore = defineStore({
id: 'app-lockscreen',
state: (): ILockscreenState => ({
isLock: isLock === true, // 是否锁屏
lockTime: isLock == 'true' ? initTime : 0,
}),
getters: {},
actions: {
setLock(payload) {
this.isLock = payload;
storage.set(IS_LOCKSCREEN, this.isLock);
},
setLockTime(payload = initTime) {
this.lockTime = payload;
},
},
});
+34
View File
@@ -0,0 +1,34 @@
import { defineStore } from 'pinia';
import { getOssConfig } from '@/api/common';
interface OssConfigState {
ossConfig: any; // 根据实际数据类型定义接口
}
export const useOssConfigStore = defineStore('ossConfig', {
state: (): OssConfigState => ({
ossConfig: null,
}),
getters: {
getConfig() {
return this.ossConfig;
},
},
actions: {
setOssConfigState(info) {
this.ossConfig = info;
},
setOssConfig() {
// return new Promise((resolve, reject) => {
// getOssConfig()
// .then((res) => {
// this.setOssConfigState(res);
// resolve(res);
// })
// .catch((error) => {
// reject(error);
// });
// });
},
},
});
+115
View File
@@ -0,0 +1,115 @@
import { defineStore } from 'pinia';
import { store } from '@/store';
import projectSetting from '@/settings/projectSetting';
import type {
IbodySetting,
IheaderSetting,
ImenuSetting,
ImultiTabsSetting,
IcrumbsSetting,
} from '/#/config';
const {
navMode,
navTheme,
headerSetting,
bodySetting,
showFooter,
menuSetting,
multiTabsSetting,
crumbsSetting,
permissionMode,
isPageAnimate,
pageAnimateType,
isProjectSetting,
isWaterMark,
} = projectSetting;
interface ProjectSettingState {
navMode: string; //导航模式
navTheme: string; //导航风格
headerSetting: IheaderSetting; //顶部设置
bodySetting: IbodySetting; //主体区域
showFooter: boolean; //页脚
menuSetting: ImenuSetting; //菜单配置
multiTabsSetting: ImultiTabsSetting; //多标签
crumbsSetting: IcrumbsSetting; //面包屑
permissionMode: string; //权限模式
isPageAnimate: boolean; //是否开启路由动画
pageAnimateType: string; //路由动画类型
isProjectSetting: boolean; //显示项目配置
isWaterMark: boolean; //显示水印
}
export const useProjectSettingStore = defineStore({
id: 'app-project-setting',
state: (): ProjectSettingState => ({
navMode: navMode,
navTheme,
headerSetting,
bodySetting,
showFooter,
menuSetting,
multiTabsSetting,
crumbsSetting,
permissionMode,
isPageAnimate,
pageAnimateType,
isProjectSetting,
isWaterMark,
}),
getters: {
getNavMode(): string {
return this.navMode;
},
getNavTheme(): string {
return this.navTheme;
},
getHeaderSetting(): object {
return this.headerSetting;
},
getBodySetting(): object {
return this.bodySetting;
},
getShowFooter(): boolean {
return this.showFooter;
},
getMenuSetting(): object {
return this.menuSetting;
},
getMultiTabsSetting(): object {
return this.multiTabsSetting;
},
getCrumbsSetting(): object {
return this.multiTabsSetting;
},
getPermissionMode(): string {
return this.permissionMode;
},
getIsPageAnimate(): boolean {
return this.isPageAnimate;
},
getPageAnimateType(): string {
return this.pageAnimateType;
},
getIsProjectSetting(): boolean {
return this.isProjectSetting;
},
getIsWaterMark(): boolean {
return this.isWaterMark;
},
},
actions: {
setNavTheme(value: string): void {
this.navTheme = value;
},
setMenuSetting(values: object): void {
this.menuSetting = Object.assign(this.menuSetting, values);
},
},
});
// Need to be used outside the setup
export function useProjectSettingStoreWithOut() {
return useProjectSettingStore(store);
}
+156
View File
@@ -0,0 +1,156 @@
import { defineStore } from 'pinia';
import { RouteLocationNormalized } from 'vue-router';
import { useAsyncRouteStore } from '@/store/modules/asyncRoute';
import { storage } from '@/utils/Storage';
import { TABS_ROUTES } from '@/store/mutation-types';
// 不需要出现在标签页中的路由
const whiteList = ['Redirect', 'Login'];
export type RouteItem = Partial<RouteLocationNormalized> & {
fullPath: string;
path: string;
name: string;
hash: string;
meta: object;
params: object;
query: object;
};
export type ITabsViewState = {
tabsList: RouteItem[]; // 标签页
};
//过滤当前路由
function filterCurrentRoute(list: any[], activeKey) {
return list.filter((item) => item.fullPath != activeKey);
}
//保留固定路由
function retainAffixRoute(list: any[]) {
return list.filter((item) => item?.meta?.affix ?? false);
}
//筛序最新的
function filterNewTabs(sliceTabs: any[], tabsList: any[], activeKey = '') {
const pathList: string[] = [];
for (const item of sliceTabs) {
const affix = item?.meta?.affix ?? false;
if (!affix) {
pathList.push(item.fullPath);
}
}
return tabsList.filter(
(item) => item.fullPath === activeKey || !pathList.includes(item.fullPath),
);
}
export const useTabsViewStore = defineStore({
id: 'app-tabs-view',
state: (): ITabsViewState => ({
tabsList: [],
}),
getters: {
getTabList(): RouteLocationNormalized[] {
return this.tabsList;
},
},
actions: {
//查找设置缓存name
findKeepAliveNames(list) {
const names = [];
list.forEach((item) => {
if (item.meta?.keepAlive) {
names.push(item.name);
}
});
return names;
},
//从缓存中删除关闭的路由
delKeepAliveNames(list) {
const asyncRouteStore = useAsyncRouteStore();
const names = this.findKeepAliveNames(list);
asyncRouteStore.removeKeepAliveComponents(names);
},
// 初始化标签页
initTabs(routes) {
this.tabsList = routes;
},
// 添加标签页
addTabs(route): boolean {
if (whiteList.includes(route.name)) return false;
const isExists = this.tabsList.some((item) => item.fullPath == route.fullPath);
if (!isExists) {
this.tabsList.push(route);
} else {
//如果是存在的路由,则刷新信息
const _index = this.tabsList.findIndex((item) => item.fullPath == route.fullPath);
this.tabsList[_index] = route;
}
return true;
},
// 关闭左侧
closeLeftTabs(route, activeKey) {
const index = this.tabsList.findIndex((item) => item.fullPath == route.value.fullPath);
if (index > 0) {
const leftTabs = this.tabsList.slice(0, index);
const newLeftTabs = filterCurrentRoute(leftTabs, activeKey);
this.delKeepAliveNames(newLeftTabs);
this.tabsList = filterNewTabs(newLeftTabs, this.tabsList, activeKey);
}
},
// 关闭右侧
closeRightTabs(route, activeKey) {
const index = this.tabsList.findIndex((item) => item.fullPath == route.value.fullPath);
if (index >= 0 && index < this.tabsList.length - 1) {
const rightTabs = this.tabsList.slice(index + 1, this.tabsList.length);
const newRightTabs = filterCurrentRoute(rightTabs, activeKey);
this.delKeepAliveNames(newRightTabs);
this.tabsList = filterNewTabs(newRightTabs, this.tabsList, activeKey);
}
},
// 关闭其他
closeOtherTabs(route, activeKey) {
const newTabsList = this.tabsList.filter(
(item) =>
([route.value.fullPath, activeKey].includes(item.fullPath) || item?.meta?.affix) ?? false,
);
this.delKeepAliveNames(newTabsList);
this.tabsList = newTabsList;
},
// 关闭当前页
closeCurrentTab(route) {
const index = this.tabsList.findIndex((item) => item.fullPath == route.value.fullPath);
if (index != -1) {
this.delKeepAliveNames([this.tabsList[index]]);
this.tabsList.splice(index, 1);
}
},
// 关闭全部
closeAllTabs() {
this.delKeepAliveNames(this.tabsList);
this.tabsList = retainAffixRoute(this.tabsList);
},
// 设置标题
async setTabTitle(title: string, route: RouteLocationNormalized) {
this.tabsList.forEach((item) => {
if (item.fullPath === route.fullPath) {
item.meta.title = title;
}
});
storage.set(TABS_ROUTES, JSON.stringify(this.tabsList));
},
// 设置状态
async setTabState(params, route: RouteLocationNormalized) {
this.tabsList.forEach((item) => {
if (item.fullPath === route.fullPath) {
item.meta = {
...item.meta,
...params,
};
}
});
storage.set(TABS_ROUTES, JSON.stringify(this.tabsList));
},
},
});
+121
View File
@@ -0,0 +1,121 @@
import { defineStore } from 'pinia';
import { createStorage } from '@/utils/Storage';
import { store } from '@/store';
import { ACCESS_TOKEN, CURRENT_USER, IS_LOCKSCREEN } from '@/store/mutation-types';
import { ResultEnum } from '@/enums/httpEnum';
const Storage = createStorage({ storage: localStorage });
import { getUserInfo, login, login2, logout } from '@/api/common/user';
import { storage } from '@/utils/Storage';
export interface IUserState {
token: string;
username: string;
welcome: string;
avatar: string;
permissions: any[];
info: any;
}
export const useUserStore = defineStore({
id: 'app-user',
state: (): IUserState => ({
token: Storage.get(ACCESS_TOKEN, ''),
username: '',
welcome: '',
avatar: '',
permissions: [],
info: Storage.get(CURRENT_USER, {}),
}),
getters: {
getToken(): string {
return this.token;
},
getAvatar(): string {
return this.avatar;
},
getNickname(): string {
return this.username;
},
getPermissions(): [any][] {
return this.permissions;
},
getUserInfo(): object {
return this.info;
},
},
actions: {
setToken(token: string) {
this.token = token;
},
setAvatar(avatar: string) {
this.avatar = avatar;
},
setPermissions(permissions) {
this.permissions = permissions;
},
setUserInfo(info) {
this.info = info;
},
// 登录
async login(userInfo) {
try {
const response = await login(userInfo);
// const response = await login2(userInfo);
const { code, data } = response;
if (code === ResultEnum.SUCCESS) {
const ex = 7 * 24 * 60 * 60 * 1000;
storage.set(ACCESS_TOKEN, data.access_token, ex);
storage.set(IS_LOCKSCREEN, false);
this.setToken(data.access_token);
}
return Promise.resolve(response);
} catch (e) {
return Promise.reject(e);
}
},
// 获取用户信息
GetInfo() {
return new Promise((resolve, reject) => {
getUserInfo()
.then((res) => {
const result = res;
if (result.permissions && result.permissions.length) {
const permissionsList = result.permissions;
this.setPermissions(permissionsList);
}
this.setUserInfo(result);
this.setAvatar(result.avatar);
resolve(res);
})
.catch((error) => {
reject(error);
});
});
},
// 登出
async logout() {
return new Promise((resolve, reject) => {
logout()
.then((res) => {
this.setPermissions([]);
this.setUserInfo('');
storage.remove(ACCESS_TOKEN);
storage.remove(CURRENT_USER);
storage.clear();
resolve('');
})
.catch((error) => {
reject(error);
});
});
},
},
});
// Need to be used outside the setup
export function useUserStoreWidthOut() {
return useUserStore(store);
}
+5
View File
@@ -0,0 +1,5 @@
export const ACCESS_TOKEN = 'ACCESS-TOKEN'; // 用户token
export const CURRENT_USER = 'CURRENT-USER'; // 当前用户信息
export const IS_LOCKSCREEN = 'IS-LOCKSCREEN'; // 是否锁屏
export const TABS_ROUTES = 'TABS-ROUTES'; // 标签页
export const FIRST_ROUTE = 'FIRST-ROUTE'; // 菜单第一个路由
+82
View File
@@ -0,0 +1,82 @@
import type { PiniaPluginContext } from 'pinia';
interface CustomObject {
[key: string]: {
[key: string]: any;
};
}
export interface StorageApi {
getItem: (key: string) => string | null;
setItem: (key: string, value: any) => void;
removeItem: (key: string) => void;
}
export interface ReducerOptionsArray {
[key: string]: string[] | boolean;
}
export interface PersistedOptions {
stroage?: StorageApi;
reducer?: string[] | ReducerOptionsArray;
name?: string;
}
function formateReducer(value: PersistedOptions['reducer'], ctx: PiniaPluginContext): CustomObject {
const state = ctx.pinia.state.value;
if (!value) return state;
const saveState: CustomObject = {};
if (Array.isArray(value)) {
value.forEach((moduleName) => {
saveState[moduleName] = state[moduleName];
});
} else if (typeof value === 'object' && !Array.isArray(value)) {
// 遍历 reducer
Object.keys(value).forEach((moduleName) => {
// 添加模块
saveState[moduleName] = {};
const moduleValue = value[moduleName]; // 模块值
const stateModule = state[moduleName]; // 状态值
if (typeof moduleValue === 'boolean') {
stateModule && (saveState[moduleName] = stateModule);
} else if (Array.isArray(moduleValue)) {
moduleValue.forEach((item) => {
stateModule && (saveState[moduleName][item] = stateModule[item]);
});
}
});
}
return saveState;
}
/**
* 持久化存储
* @param options
* @returns
*/
export function createPersistedState(
options?: PersistedOptions,
): (ctx: PiniaPluginContext) => object {
// 存储方式
const stroage = options?.stroage || window.localStorage;
// 前缀
const name = options?.name || 'vuex-pinia';
function piniaPlugin(ctx: PiniaPluginContext) {
// this subscription will be removed after the component is unmounted, so we need need to fill in the second parameter
try {
ctx.store.$subscribe(() => {
const sv = JSON.parse(stroage.getItem(name) as string) || {};
const reducer = formateReducer(options?.reducer, ctx);
stroage.setItem(name, JSON.stringify(Object.assign(sv, reducer)));
}, true);
console.log(ctx);
const rv = JSON.parse(stroage.getItem(name) as string);
return rv && rv[ctx.store.$id];
} catch (error) {
console.log(error);
}
}
return piniaPlugin;
}
+12
View File
@@ -0,0 +1,12 @@
import { IAsyncRouteState } from '@/store/modules/asyncRoute';
import { IUserState } from '@/store/modules/user';
import { ILockscreenState } from '@/store/modules/lockscreen';
import { ITabsViewState } from '@/store/modules/tabsView';
export interface IStore {
asyncRoute: IAsyncRouteState;
user: IUserState;
lockscreen: ILockscreenState;
tabsView: ITabsViewState;
count: number;
}