1
0

feat: initial commit

This commit is contained in:
gin
2026-05-07 18:39:00 +08:00
commit cdee21ee8e
653 changed files with 63946 additions and 0 deletions
+9
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 };
+68
View File
@@ -0,0 +1,68 @@
import { store } from "@/store";
import { appType } from "./types";
import { defineStore } from "pinia";
import { getConfig, responsiveStorageNameSpace } from "@/config";
import { deviceDetection, storageLocal } from "@pureadmin/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"
}),
getters: {
getSidebarStatus(state) {
return state.sidebar.opened;
},
getDevice(state) {
return state.device;
}
},
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;
}
}
});
export function useAppStoreHook() {
return useAppStore(store);
}
+49
View File
@@ -0,0 +1,49 @@
import { store } from "@/store";
import { defineStore } from "pinia";
import { storageLocal } from "@pureadmin/utils";
import { getConfig, responsiveStorageNameSpace } from "@/config";
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 if (state.epTheme === "yellow") {
return "#d25f00";
} 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);
}
+124
View File
@@ -0,0 +1,124 @@
import { defineStore } from "pinia";
import { store } from "@/store";
import { routerArrays } from "@/layout/types";
import { multiType, positionType } from "./types";
import { responsiveStorageNameSpace } from "@/config";
import { isEqual, isBoolean, isUrl, storageLocal } from "@pureadmin/utils";
export const useMultiTagsStore = defineStore({
id: "pure-multiTags",
state: () => ({
// 存储标签页信息(路由信息)
multiTags: storageLocal().getItem<StorageConfigs>(
`${responsiveStorageNameSpace()}configure`
)?.multiTagsCache
? storageLocal().getItem<StorageConfigs>(
`${responsiveStorageNameSpace()}tags`
)
: [...routerArrays],
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);
}
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);
}
@@ -0,0 +1,64 @@
import { defineStore } from "pinia";
import { store } from "@/store";
import { cacheType } from "./types";
import { constantMenus } from "@/router";
import { useMultiTagsStoreHook } from "./multiTags";
import { debounce, getKeyList } from "@pureadmin/utils";
import { ascending, filterTree, filterNoPermissionTree } from "@/router/utils";
export const usePermissionStore = defineStore({
id: "ag-permission",
state: () => ({
// 静态路由生成的菜单
constantMenus,
// 整体路由生成的菜单(静态、动态)
wholeMenus: [],
// 缓存页面keepAlive
cachePageList: []
}),
actions: {
/** 组装整体路由生成的菜单 */
handleWholeMenus(routes: any[]) {
this.wholeMenus = filterNoPermissionTree(
filterTree(ascending(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);
}
@@ -0,0 +1,38 @@
import { defineStore } from "pinia";
import { store } from "@/store";
import { setType } from "./types";
import { getConfig } from "@/config";
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);
}
+48
View File
@@ -0,0 +1,48 @@
import { RouteRecordName } from "vue-router";
import { CurrentUserInfoDTO, DictionaryData } from "../../api/common/login";
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;
};
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 = {
username?: string;
roles?: Array<string>;
verifyCode?: string;
/** 字典ListMap 用于下拉框直接展示 */
dictionaryList: Record<string, Array<DictionaryData>>;
/** 字典MapMap 用于匹配值展示 */
dictionaryMap: Record<string, Record<string, DictionaryData>>;
currentUserInfo?: CurrentUserInfoDTO;
};
+92
View File
@@ -0,0 +1,92 @@
import { defineStore } from "pinia";
import { store } from "@/store";
import { userType } from "./types";
import { routerArrays } from "@/layout/types";
import { router, resetRouter } from "@/router";
import { storageSession } from "@pureadmin/utils";
import { useMultiTagsStoreHook } from "@/store/modules/multiTags";
import { removeToken, sessionKey } from "@/utils/auth";
import { DictionaryData, TokenDTO } from "@/api/common/login";
import { storageLocal } from "@pureadmin/utils";
const dictionaryListKey = "ag-dictionary-list";
const dictionaryMapKey = "ag-dictionary-map";
export const useUserStore = defineStore({
id: "ag-user",
state: (): userType => ({
// 用户名
username:
storageSession().getItem<TokenDTO>(sessionKey)?.currentUser.userInfo
.username ?? "",
// 页面级别权限
roles: storageSession().getItem<TokenDTO>(sessionKey)?.currentUser.roleKey
? [storageSession().getItem<TokenDTO>(sessionKey)?.currentUser.roleKey]
: [],
verifyCode: "",
dictionaryList:
storageLocal().getItem<Record<string, Array<DictionaryData>>>(
dictionaryListKey
) ?? {},
dictionaryMap:
storageLocal().getItem<Record<string, Record<string, DictionaryData>>>(
dictionaryMapKey
) ?? {},
currentUserInfo:
storageSession().getItem<TokenDTO>(sessionKey)?.currentUser.userInfo ?? {}
}),
actions: {
/** 存储用户名 */
SET_USERNAME(username: string) {
/** TODO 这里不是应该再进一步存到sessionStorage中吗 */
this.username = username;
},
/** 存储角色 */
SET_ROLES(roles: Array<string>) {
this.roles = roles;
},
/** 存储系统内的字典值 并拆分为Map形式和List形式 */
SET_DICTIONARY(dictionary: Record<string, Array<DictionaryData>>) {
/** 由于localStorage不能存储Map对象,所以用Obj来装载数据 */
const dictionaryMapTmp: Record<
string,
Record<string, DictionaryData>
> = {};
for (const obj in dictionary) {
dictionaryMapTmp[obj] = dictionary[obj].reduce((map, dict) => {
map[dict.value] = dict;
return map;
}, {});
}
/** 将字典分成List形式和Map形式 List便于下拉框展示 Map便于匹配值 */
this.dictionaryList = dictionary;
this.dictionaryMap = dictionaryMapTmp;
storageLocal().setItem<Record<string, Array<DictionaryData>>>(
dictionaryListKey,
dictionary
);
storageLocal().setItem<Record<string, Record<string, DictionaryData>>>(
dictionaryMapKey,
dictionaryMapTmp
);
},
/** 前端登出(不调用接口) */
logOut() {
this.username = "";
this.roles = [];
removeToken();
useMultiTagsStoreHook().handleTags("equal", [...routerArrays]);
resetRouter();
router.push("/login");
}
}
});
export function useUserStoreHook() {
return useUserStore(store);
}