This commit is contained in:
coward
2024-07-05 14:48:09 +08:00
parent 7f07c29a23
commit 2c1450990b
205 changed files with 23058 additions and 6 deletions

9
web/src/store/index.ts Normal file
View File

@@ -0,0 +1,9 @@
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,89 @@
import { defineStore } from "pinia";
import {
type appType,
store,
getConfig,
storageLocal,
deviceDetection,
responsiveStorageNameSpace
} from "../utils";
export const useAppStore = defineStore({
id: "pure-app",
state: (): appType => ({
sidebar: {
opened:
storageLocal().getItem<StorageConfigs>(
`${responsiveStorageNameSpace()}layout`
)?.sidebarStatus ?? getConfig().SidebarStatus,
withoutAnimation: false,
isClickCollapse: false
},
// 这里的layout用于监听容器拖拉后恢复对应的导航模式
layout:
storageLocal().getItem<StorageConfigs>(
`${responsiveStorageNameSpace()}layout`
)?.layout ?? getConfig().Layout,
device: deviceDetection() ? "mobile" : "desktop",
// 浏览器窗口的可视区域大小
viewportSize: {
width: document.documentElement.clientWidth,
height: document.documentElement.clientHeight
}
}),
getters: {
getSidebarStatus(state) {
return state.sidebar.opened;
},
getDevice(state) {
return state.device;
},
getViewportWidth(state) {
return state.viewportSize.width;
},
getViewportHeight(state) {
return state.viewportSize.height;
}
},
actions: {
TOGGLE_SIDEBAR(opened?: boolean, resize?: string) {
const layout = storageLocal().getItem<StorageConfigs>(
`${responsiveStorageNameSpace()}layout`
);
if (opened && resize) {
this.sidebar.withoutAnimation = true;
this.sidebar.opened = true;
layout.sidebarStatus = true;
} else if (!opened && resize) {
this.sidebar.withoutAnimation = true;
this.sidebar.opened = false;
layout.sidebarStatus = false;
} else if (!opened && !resize) {
this.sidebar.withoutAnimation = false;
this.sidebar.opened = !this.sidebar.opened;
this.sidebar.isClickCollapse = !this.sidebar.opened;
layout.sidebarStatus = this.sidebar.opened;
}
storageLocal().setItem(`${responsiveStorageNameSpace()}layout`, layout);
},
async toggleSideBar(opened?: boolean, resize?: string) {
await this.TOGGLE_SIDEBAR(opened, resize);
},
toggleDevice(device: string) {
this.device = device;
},
setLayout(layout) {
this.layout = layout;
},
setViewportSize(size) {
this.viewportSize = size;
},
setSortSwap(val) {
this.sortSwap = val;
}
}
});
export function useAppStoreHook() {
return useAppStore(store);
}

View File

@@ -0,0 +1,50 @@
import { defineStore } from "pinia";
import {
store,
getConfig,
storageLocal,
responsiveStorageNameSpace
} from "../utils";
export const useEpThemeStore = defineStore({
id: "pure-epTheme",
state: () => ({
epThemeColor:
storageLocal().getItem<StorageConfigs>(
`${responsiveStorageNameSpace()}layout`
)?.epThemeColor ?? getConfig().EpThemeColor,
epTheme:
storageLocal().getItem<StorageConfigs>(
`${responsiveStorageNameSpace()}layout`
)?.theme ?? getConfig().Theme
}),
getters: {
getEpThemeColor(state) {
return state.epThemeColor;
},
/** 用于mix导航模式下hamburger-svg的fill属性 */
fill(state) {
if (state.epTheme === "light") {
return "#409eff";
} else {
return "#fff";
}
}
},
actions: {
setEpThemeColor(newColor: string): void {
const layout = storageLocal().getItem<StorageConfigs>(
`${responsiveStorageNameSpace()}layout`
);
this.epTheme = layout?.theme;
this.epThemeColor = newColor;
if (!layout) return;
layout.epThemeColor = newColor;
storageLocal().setItem(`${responsiveStorageNameSpace()}layout`, layout);
}
}
});
export function useEpThemeStoreHook() {
return useEpThemeStore(store);
}

View File

@@ -0,0 +1,146 @@
import { defineStore } from "pinia";
import {
type multiType,
type positionType,
store,
isUrl,
isEqual,
isNumber,
isBoolean,
getConfig,
routerArrays,
storageLocal,
responsiveStorageNameSpace
} from "../utils";
import { usePermissionStoreHook } from "./permission";
export const useMultiTagsStore = defineStore({
id: "pure-multiTags",
state: () => ({
// 存储标签页信息(路由信息)
multiTags: storageLocal().getItem<StorageConfigs>(
`${responsiveStorageNameSpace()}configure`
)?.multiTagsCache
? storageLocal().getItem<StorageConfigs>(
`${responsiveStorageNameSpace()}tags`
)
: [
...routerArrays,
...usePermissionStoreHook().flatteningRoutes.filter(
v => v?.meta?.fixedTag
)
],
multiTagsCache: storageLocal().getItem<StorageConfigs>(
`${responsiveStorageNameSpace()}configure`
)?.multiTagsCache
}),
getters: {
getMultiTagsCache(state) {
return state.multiTagsCache;
}
},
actions: {
multiTagsCacheChange(multiTagsCache: boolean) {
this.multiTagsCache = multiTagsCache;
if (multiTagsCache) {
storageLocal().setItem(
`${responsiveStorageNameSpace()}tags`,
this.multiTags
);
} else {
storageLocal().removeItem(`${responsiveStorageNameSpace()}tags`);
}
},
tagsCache(multiTags) {
this.getMultiTagsCache &&
storageLocal().setItem(
`${responsiveStorageNameSpace()}tags`,
multiTags
);
},
handleTags<T>(
mode: string,
value?: T | multiType,
position?: positionType
): T {
switch (mode) {
case "equal":
this.multiTags = value;
this.tagsCache(this.multiTags);
break;
case "push":
{
const tagVal = value as multiType;
// 不添加到标签页
if (tagVal?.meta?.hiddenTag) return;
// 如果是外链无需添加信息到标签页
if (isUrl(tagVal?.name)) return;
// 如果title为空拒绝添加空信息到标签页
if (tagVal?.meta?.title.length === 0) return;
// showLink:false 不添加到标签页
if (isBoolean(tagVal?.meta?.showLink) && !tagVal?.meta?.showLink)
return;
const tagPath = tagVal.path;
// 判断tag是否已存在
const tagHasExits = this.multiTags.some(tag => {
return tag.path === tagPath;
});
// 判断tag中的query键值是否相等
const tagQueryHasExits = this.multiTags.some(tag => {
return isEqual(tag?.query, tagVal?.query);
});
// 判断tag中的params键值是否相等
const tagParamsHasExits = this.multiTags.some(tag => {
return isEqual(tag?.params, tagVal?.params);
});
if (tagHasExits && tagQueryHasExits && tagParamsHasExits) return;
// 动态路由可打开的最大数量
const dynamicLevel = tagVal?.meta?.dynamicLevel ?? -1;
if (dynamicLevel > 0) {
if (
this.multiTags.filter(e => e?.path === tagPath).length >=
dynamicLevel
) {
// 如果当前已打开的动态路由数大于dynamicLevel替换第一个动态路由标签
const index = this.multiTags.findIndex(
item => item?.path === tagPath
);
index !== -1 && this.multiTags.splice(index, 1);
}
}
this.multiTags.push(value);
this.tagsCache(this.multiTags);
if (
getConfig()?.MaxTagsLevel &&
isNumber(getConfig().MaxTagsLevel)
) {
if (this.multiTags.length > getConfig().MaxTagsLevel) {
this.multiTags.splice(1, 1);
}
}
}
break;
case "splice":
if (!position) {
const index = this.multiTags.findIndex(v => v.path === value);
if (index === -1) return;
this.multiTags.splice(index, 1);
} else {
this.multiTags.splice(position?.startIndex, position?.length);
}
this.tagsCache(this.multiTags);
return this.multiTags;
case "slice":
return this.multiTags.slice(-1);
}
}
}
});
export function useMultiTagsStoreHook() {
return useMultiTagsStore(store);
}

View File

@@ -0,0 +1,75 @@
import { defineStore } from "pinia";
import {
type cacheType,
store,
debounce,
ascending,
getKeyList,
filterTree,
constantMenus,
filterNoPermissionTree,
formatFlatteningRoutes
} from "../utils";
import { useMultiTagsStoreHook } from "./multiTags";
export const usePermissionStore = defineStore({
id: "pure-permission",
state: () => ({
// 静态路由生成的菜单
constantMenus,
// 整体路由生成的菜单(静态、动态)
wholeMenus: [],
// 整体路由(一维数组格式)
flatteningRoutes: [],
// 缓存页面keepAlive
cachePageList: []
}),
actions: {
/** 组装整体路由生成的菜单 */
handleWholeMenus(routes: any[]) {
this.wholeMenus = filterNoPermissionTree(
filterTree(ascending(this.constantMenus.concat(routes)))
);
this.flatteningRoutes = formatFlatteningRoutes(
this.constantMenus.concat(routes)
);
},
cacheOperate({ mode, name }: cacheType) {
const delIndex = this.cachePageList.findIndex(v => v === name);
switch (mode) {
case "refresh":
this.cachePageList = this.cachePageList.filter(v => v !== name);
break;
case "add":
this.cachePageList.push(name);
break;
case "delete":
delIndex !== -1 && this.cachePageList.splice(delIndex, 1);
break;
}
/** 监听缓存页面是否存在于标签页,不存在则删除 */
debounce(() => {
let cacheLength = this.cachePageList.length;
const nameList = getKeyList(useMultiTagsStoreHook().multiTags, "name");
while (cacheLength > 0) {
nameList.findIndex(v => v === this.cachePageList[cacheLength - 1]) ===
-1 &&
this.cachePageList.splice(
this.cachePageList.indexOf(this.cachePageList[cacheLength - 1]),
1
);
cacheLength--;
}
})();
},
/** 清空缓存页面 */
clearAllCachePage() {
this.wholeMenus = [];
this.cachePageList = [];
}
}
});
export function usePermissionStoreHook() {
return usePermissionStore(store);
}

View File

@@ -0,0 +1,36 @@
import { defineStore } from "pinia";
import { type setType, store, getConfig } from "../utils";
export const useSettingStore = defineStore({
id: "pure-setting",
state: (): setType => ({
title: getConfig().Title,
fixedHeader: getConfig().FixedHeader,
hiddenSideBar: getConfig().HiddenSideBar
}),
getters: {
getTitle(state) {
return state.title;
},
getFixedHeader(state) {
return state.fixedHeader;
},
getHiddenSideBar(state) {
return state.hiddenSideBar;
}
},
actions: {
CHANGE_SETTING({ key, value }) {
if (Reflect.has(this, key)) {
this[key] = value;
}
},
changeSetting(data) {
this.CHANGE_SETTING(data);
}
}
});
export function useSettingStoreHook() {
return useSettingStore(store);
}

View File

@@ -0,0 +1,102 @@
import { defineStore } from "pinia";
import {
type userType,
store,
router,
resetRouter,
routerArrays,
storageLocal
} from "../utils";
import {
type UserResult,
type RefreshTokenResult,
getLogin,
refreshTokenApi
} from "@/api/user";
import { useMultiTagsStoreHook } from "./multiTags";
import { type DataInfo, setToken, removeToken, userKey } from "@/utils/auth";
export const useUserStore = defineStore({
id: "pure-user",
state: (): userType => ({
// 头像
avatar: storageLocal().getItem<DataInfo<number>>(userKey)?.avatar ?? "",
// 用户名
username: storageLocal().getItem<DataInfo<number>>(userKey)?.username ?? "",
// 昵称
nickname: storageLocal().getItem<DataInfo<number>>(userKey)?.nickname ?? "",
// 页面级别权限
roles: storageLocal().getItem<DataInfo<number>>(userKey)?.roles ?? [],
// 是否勾选了登录页的免登录
isRemembered: false,
// 登录页的免登录存储几天默认7天
loginDay: 7
}),
actions: {
/** 存储头像 */
SET_AVATAR(avatar: string) {
this.avatar = avatar;
},
/** 存储用户名 */
SET_USERNAME(username: string) {
this.username = username;
},
/** 存储昵称 */
SET_NICKNAME(nickname: string) {
this.nickname = nickname;
},
/** 存储角色 */
SET_ROLES(roles: Array<string>) {
this.roles = roles;
},
/** 存储是否勾选了登录页的免登录 */
SET_ISREMEMBERED(bool: boolean) {
this.isRemembered = bool;
},
/** 设置登录页的免登录存储几天 */
SET_LOGINDAY(value: number) {
this.loginDay = Number(value);
},
/** 登入 */
async loginByUsername(data) {
return new Promise<UserResult>((resolve, reject) => {
getLogin(data)
.then(data => {
if (data?.success) setToken(data.data);
resolve(data);
})
.catch(error => {
reject(error);
});
});
},
/** 前端登出(不调用接口) */
logOut() {
this.username = "";
this.roles = [];
removeToken();
useMultiTagsStoreHook().handleTags("equal", [...routerArrays]);
resetRouter();
router.push("/login");
},
/** 刷新`token` */
async handRefreshToken(data) {
return new Promise<RefreshTokenResult>((resolve, reject) => {
refreshTokenApi(data)
.then(data => {
if (data) {
setToken(data.data);
resolve(data);
}
})
.catch(error => {
reject(error);
});
});
}
}
});
export function useUserStoreHook() {
return useUserStore(store);
}

46
web/src/store/types.ts Normal file
View File

@@ -0,0 +1,46 @@
import type { RouteRecordName } from "vue-router";
export type cacheType = {
mode: string;
name?: RouteRecordName;
};
export type positionType = {
startIndex?: number;
length?: number;
};
export type appType = {
sidebar: {
opened: boolean;
withoutAnimation: boolean;
// 判断是否手动点击Collapse
isClickCollapse: boolean;
};
layout: string;
device: string;
viewportSize: { width: number; height: number };
};
export type multiType = {
path: string;
name: string;
meta: any;
query?: object;
params?: object;
};
export type setType = {
title: string;
fixedHeader: boolean;
hiddenSideBar: boolean;
};
export type userType = {
avatar?: string;
username?: string;
nickname?: string;
roles?: Array<string>;
isRemembered?: boolean;
loginDay?: number;
};

28
web/src/store/utils.ts Normal file
View File

@@ -0,0 +1,28 @@
export { store } from "@/store";
export { routerArrays } from "@/layout/types";
export { router, resetRouter, constantMenus } from "@/router";
export { getConfig, responsiveStorageNameSpace } from "@/config";
export {
ascending,
filterTree,
filterNoPermissionTree,
formatFlatteningRoutes
} from "@/router/utils";
export {
isUrl,
isEqual,
isNumber,
debounce,
isBoolean,
getKeyList,
storageLocal,
deviceDetection
} from "@pureadmin/utils";
export type {
setType,
appType,
userType,
multiType,
cacheType,
positionType
} from "./types";