forked from gin/simple-template
feat: initial commit
This commit is contained in:
@@ -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 };
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
@@ -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);
|
||||
}
|
||||
Reference in New Issue
Block a user