feat: add web application
This commit is contained in:
@@ -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 };
|
||||
@@ -0,0 +1,141 @@
|
||||
import { toRaw, unref } from 'vue';
|
||||
import { defineStore } from 'pinia';
|
||||
import { RouteRecordRaw } from 'vue-router';
|
||||
import { store } from '@/store';
|
||||
import { asyncRoutes, constantRouter } from '@/router';
|
||||
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: {
|
||||
getMenus(): RouteRecordRaw[] {
|
||||
return this.menus;
|
||||
},
|
||||
getIsDynamicAddedRoute(): boolean {
|
||||
return this.isDynamicAddedRoute;
|
||||
},
|
||||
},
|
||||
actions: {
|
||||
getRouters() {
|
||||
return toRaw(this.addRouters);
|
||||
},
|
||||
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 } = meta || {};
|
||||
if (!permissions) return true;
|
||||
return permissionsList.some((item) => permissions.includes(item));
|
||||
};
|
||||
const { getPermissionMode } = useProjectSetting();
|
||||
const permissionMode = unref(getPermissionMode);
|
||||
const $dialog = window['$dialog'];
|
||||
if (permissionMode === 'BACK') {
|
||||
// 动态获取菜单
|
||||
try {
|
||||
accessedRouters = await generatorDynamicRouter();
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
$dialog.info({
|
||||
title: '提示',
|
||||
content: '获取动态路由失败,管理员请确认是否为角色分配菜单权限?',
|
||||
positiveText: '确定',
|
||||
onPositiveClick: () => {},
|
||||
});
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
//过滤账户是否拥有某一个权限,并将菜单从加载列表移除
|
||||
accessedRouters = filter(asyncRoutes, routeFilter);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
$dialog.info({
|
||||
title: '提示',
|
||||
content: '过滤动态路由失败,请联系管理员解决!',
|
||||
positiveText: '确定',
|
||||
onPositiveClick: () => {},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -0,0 +1,192 @@
|
||||
import { computed, ComputedRef } from 'vue';
|
||||
import { defineStore } from 'pinia';
|
||||
import { createStorage, storage } from '@/utils/Storage';
|
||||
import { store } from '@/store';
|
||||
import { CURRENT_DICT } from '@/store/mutation-types';
|
||||
import { Dicts } from '@/api/dict/dict';
|
||||
import { SelectMixedOption } from 'naive-ui/es/select/src/interface';
|
||||
import { localDict } from '@/enums/localDictEnum';
|
||||
|
||||
const Storage = createStorage({ storage: localStorage });
|
||||
|
||||
// 默认字典类型,通过后台字典管理添加的
|
||||
export type DefaultDictType = 'sys_normal_disable' | 'sys_switch' | 'sys_user_sex';
|
||||
|
||||
// 枚举字典类型
|
||||
export type EnumsDictType = 'creditType' | 'creditGroup' | 'deptType';
|
||||
|
||||
// 方法字典类型
|
||||
export type FuncDictType = 'testCategoryOption';
|
||||
|
||||
// 可以把常用的字典类型加入进来,这样会有IDE提示
|
||||
export type DictType = DefaultDictType | EnumsDictType | FuncDictType | string;
|
||||
|
||||
export type DictListClass = 'default' | 'error' | 'primary' | 'info' | 'success' | 'warning';
|
||||
|
||||
export interface Option {
|
||||
label: string;
|
||||
value: string | number;
|
||||
key: string | number;
|
||||
listClass: DictListClass;
|
||||
extra: any;
|
||||
}
|
||||
|
||||
export class DictOptions {
|
||||
[name: DictType]: Option[];
|
||||
}
|
||||
|
||||
export type IDictState = {
|
||||
options: DictOptions;
|
||||
};
|
||||
|
||||
export const useDictStore = defineStore({
|
||||
id: 'app-dict',
|
||||
state: (): IDictState => ({
|
||||
options: Storage.get(CURRENT_DICT, new DictOptions()),
|
||||
}),
|
||||
getters: {},
|
||||
actions: {
|
||||
// 加载字典数据选项
|
||||
loadOptions(types: DictType[]) {
|
||||
Dicts({
|
||||
types: types,
|
||||
}).then((res) => {
|
||||
if (!res) {
|
||||
return;
|
||||
}
|
||||
Object.keys(res).forEach((key) => {
|
||||
if (res[key]) {
|
||||
this.options[key] = res[key] as unknown as Option[];
|
||||
}
|
||||
});
|
||||
storage.set(CURRENT_DICT, this.options);
|
||||
});
|
||||
},
|
||||
|
||||
// 设置指定类型的字典选项
|
||||
setOption(type: DictType, opts: Option[]) {
|
||||
this.options[type] = opts;
|
||||
storage.set(CURRENT_DICT, this.options);
|
||||
},
|
||||
|
||||
// 获取指定类型的字典选项
|
||||
getOption(type: DictType): ComputedRef<SelectMixedOption[]> {
|
||||
return computed(() => {
|
||||
const opts = this.checkOptionValue(type) ?? [];
|
||||
return opts as unknown as SelectMixedOption[];
|
||||
});
|
||||
},
|
||||
|
||||
// 获取指定类型的字典选项
|
||||
getOptionUnRef(type: DictType): any[] {
|
||||
return this.checkOptionValue(type) ?? [];
|
||||
},
|
||||
|
||||
// 是否存在选项,不存在返回null,存在就返回选项
|
||||
checkOptionValue(type: DictType): null | Option[] {
|
||||
// 本地字典
|
||||
const local = localDict[type] ?? [];
|
||||
if (local && local.length > 0) {
|
||||
return local;
|
||||
}
|
||||
const opts = this.options[type] ?? [];
|
||||
if (!opts || opts?.length === 0) {
|
||||
return null;
|
||||
}
|
||||
return opts;
|
||||
},
|
||||
|
||||
// 获取选项名称
|
||||
getLabel(type: DictType, value: any) {
|
||||
if (value === null || value === undefined) {
|
||||
return ``;
|
||||
}
|
||||
const opts = this.checkOptionValue(type);
|
||||
if (!opts) {
|
||||
return ``;
|
||||
}
|
||||
for (const item of opts) {
|
||||
if (item.value.toString() === value.toString()) {
|
||||
return item.label;
|
||||
}
|
||||
}
|
||||
return ``;
|
||||
},
|
||||
|
||||
// 获取选项标签类型
|
||||
getType(type: DictType, value: any): DictListClass {
|
||||
if (value === null || value === undefined) {
|
||||
return `default`;
|
||||
}
|
||||
const opts = this.checkOptionValue(type);
|
||||
if (!opts) {
|
||||
return `default`;
|
||||
}
|
||||
for (const item of opts) {
|
||||
if (item.value.toString() === value.toString()) {
|
||||
return item.listClass;
|
||||
}
|
||||
}
|
||||
return `default`;
|
||||
},
|
||||
|
||||
// 获取选项额外的数据配置
|
||||
getExtra(type: DictType, value: any): any {
|
||||
if (value === null || value === undefined) {
|
||||
return null;
|
||||
}
|
||||
const opts = this.checkOptionValue(type);
|
||||
if (!opts) {
|
||||
return null;
|
||||
}
|
||||
for (const item of opts) {
|
||||
if (item.value.toString() === value.toString()) {
|
||||
return item?.extra;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
// 是否存在指定选项值
|
||||
hasValue(type: DictType, value: any): boolean {
|
||||
if (value === null || value === undefined) {
|
||||
return false;
|
||||
}
|
||||
const opts = this.checkOptionValue(type);
|
||||
if (!opts) {
|
||||
return false;
|
||||
}
|
||||
for (const item of opts) {
|
||||
if (item.value.toString() === value.toString()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
// 获取指定类型的可选项数量
|
||||
getOptionLength(type: DictType): number {
|
||||
const opts = this.checkOptionValue(type);
|
||||
if (!opts) {
|
||||
return 0;
|
||||
}
|
||||
return opts?.length;
|
||||
},
|
||||
|
||||
// 生成单个选项
|
||||
genOption(key: any, label: string, type = 'default', extra: any = null): Option {
|
||||
return <Option>{
|
||||
label: label,
|
||||
value: key,
|
||||
key: key,
|
||||
listClass: type,
|
||||
extra: extra,
|
||||
};
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// Need to be used outside the setup
|
||||
export function useDictStoreWidthOut() {
|
||||
return useDictStore(store);
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
import { defineStore } from 'pinia';
|
||||
import { createStorage } from '@/utils/Storage';
|
||||
import { CurrentLocale } from '@/store/mutation-types';
|
||||
import i18n from '@/locale/index';
|
||||
import { useUserStore } from '@/store/modules/user';
|
||||
|
||||
export const availableLocales = [
|
||||
{
|
||||
label: '简体中文',
|
||||
key: 'zh-CN',
|
||||
},
|
||||
{
|
||||
label: '繁體中文',
|
||||
key: 'zh-TW',
|
||||
},
|
||||
{
|
||||
label: 'English',
|
||||
key: 'en',
|
||||
},
|
||||
];
|
||||
|
||||
export interface II18nStore {
|
||||
currentLocale: string;
|
||||
}
|
||||
|
||||
const Storage = createStorage({ storage: localStorage });
|
||||
|
||||
export const useI18nStore = defineStore({
|
||||
id: 'I18nStore',
|
||||
state: (): II18nStore => ({
|
||||
currentLocale: Storage.get(CurrentLocale, 'zh-CN'),
|
||||
}),
|
||||
getters: {},
|
||||
actions: {
|
||||
getLocale(): string {
|
||||
return this.currentLocale;
|
||||
},
|
||||
|
||||
setLocale(locale: string) {
|
||||
if (availableLocales.some((l) => l.key === locale)) {
|
||||
(i18n.global.locale as any).value = locale;
|
||||
this.currentLocale = locale;
|
||||
Storage.set(CurrentLocale, locale);
|
||||
}
|
||||
},
|
||||
|
||||
initLocale(): void {
|
||||
const userStore = useUserStore();
|
||||
const defaultLanguage = userStore.loginConfig?.defaultLanguage || 'zh-CN';
|
||||
|
||||
// 未开启国际化功能,仅设置语言不持久化
|
||||
if (!userStore.loginConfig?.i18nSwitch) {
|
||||
this.setLocale(defaultLanguage);
|
||||
return;
|
||||
}
|
||||
|
||||
// 优先使用本地存储的语言设置
|
||||
const savedLocale = Storage.get(CurrentLocale, defaultLanguage);
|
||||
if (savedLocale && availableLocales.some((l) => l.key === savedLocale)) {
|
||||
this.setLocale(savedLocale);
|
||||
return;
|
||||
}
|
||||
|
||||
// 使用系统配置的默认语言
|
||||
this.setLocale(defaultLanguage);
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,30 @@
|
||||
// @ts-ignore
|
||||
const allModules = import.meta.globEager('./*/index.ts');
|
||||
const modules = {} as any;
|
||||
Object.keys(allModules).forEach((path) => {
|
||||
const fileName = path.split('/')[1];
|
||||
modules[fileName] = allModules[path][fileName] || allModules[path].default || allModules[path];
|
||||
});
|
||||
|
||||
// export default modules
|
||||
// @ts-ignore
|
||||
import asyncRoute from './async-route';
|
||||
// @ts-ignore
|
||||
import user from './user';
|
||||
// @ts-ignore
|
||||
import tabsView from './tabs-view';
|
||||
// @ts-ignore
|
||||
import lockscreen from './lockscreen';
|
||||
// @ts-ignore
|
||||
import dict from './dict';
|
||||
// @ts-ignore
|
||||
import i18n from './i18n';
|
||||
|
||||
export default {
|
||||
asyncRoute,
|
||||
user,
|
||||
tabsView,
|
||||
lockscreen,
|
||||
dict,
|
||||
i18n,
|
||||
};
|
||||
@@ -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;
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,106 @@
|
||||
import { defineStore } from 'pinia';
|
||||
import { store } from '@/store';
|
||||
import { PullMessages } from '@/api/apply/notice';
|
||||
import { MessageRow, MessageTab, parseMessage } from '@/enums/systemMessageEnum';
|
||||
|
||||
export interface INotificationStore {
|
||||
messages: MessageTab[];
|
||||
notifyUnread: number;
|
||||
noticeUnread: number;
|
||||
letterUnread: number;
|
||||
newMessage: MessageRow | null;
|
||||
}
|
||||
|
||||
export const notificationStore = defineStore({
|
||||
id: 'notificationStore',
|
||||
state: (): INotificationStore => ({
|
||||
messages: [
|
||||
{
|
||||
key: 1,
|
||||
name: '通知',
|
||||
badgeProps: { type: 'warning' },
|
||||
list: [],
|
||||
},
|
||||
{
|
||||
key: 2,
|
||||
name: '公告',
|
||||
badgeProps: { type: 'error' },
|
||||
list: [],
|
||||
},
|
||||
{
|
||||
key: 3,
|
||||
name: '私信',
|
||||
badgeProps: { type: 'info' },
|
||||
list: [],
|
||||
},
|
||||
],
|
||||
notifyUnread: 0,
|
||||
noticeUnread: 0,
|
||||
letterUnread: 0,
|
||||
newMessage: null,
|
||||
}),
|
||||
getters: {
|
||||
getMessages(): MessageTab[] {
|
||||
return this.messages;
|
||||
},
|
||||
},
|
||||
actions: {
|
||||
setMessages(messages) {
|
||||
this.messages = messages;
|
||||
},
|
||||
triggerNewMessages(message) {
|
||||
message = parseMessage(message);
|
||||
this.addMessages(message);
|
||||
this.newMessage = message;
|
||||
},
|
||||
addMessages(message: MessageRow) {
|
||||
switch (message.type) {
|
||||
case 1:
|
||||
this.messages[0].list.push(message);
|
||||
this.notifyUnread++;
|
||||
break;
|
||||
case 2:
|
||||
this.messages[1].list.push(message);
|
||||
this.noticeUnread++;
|
||||
break;
|
||||
case 3:
|
||||
this.messages[2].list.push(message);
|
||||
this.letterUnread++;
|
||||
break;
|
||||
}
|
||||
},
|
||||
pullMessages() {
|
||||
PullMessages().then((res) => {
|
||||
if (res.list === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.messages[0].list = [];
|
||||
this.messages[1].list = [];
|
||||
this.messages[2].list = [];
|
||||
|
||||
if (res.list?.length > 0) {
|
||||
for (let i = 0; i < res.list.length; i++) {
|
||||
this.addMessages(parseMessage(res.list[i]));
|
||||
}
|
||||
}
|
||||
|
||||
this.notifyUnread = res.notifyCount;
|
||||
this.noticeUnread = res.noticeCount;
|
||||
this.letterUnread = res.letterCount;
|
||||
});
|
||||
},
|
||||
getUnreadCount() {
|
||||
const count = this.notifyUnread + this.noticeUnread + this.letterUnread;
|
||||
if (count > 0) {
|
||||
return count;
|
||||
}
|
||||
return 0;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// Need to be used outside the setup
|
||||
export function notificationStoreWidthOut() {
|
||||
return notificationStore(store);
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
import { defineStore } from 'pinia';
|
||||
import { store } from '@/store';
|
||||
import projectSetting from '@/settings/projectSetting';
|
||||
import type { IheaderSetting, ImenuSetting, ImultiTabsSetting, IcrumbsSetting } from '/#/config';
|
||||
|
||||
const {
|
||||
navMode,
|
||||
navTheme,
|
||||
isMobile,
|
||||
headerSetting,
|
||||
showFooter,
|
||||
menuSetting,
|
||||
multiTabsSetting,
|
||||
crumbsSetting,
|
||||
permissionMode,
|
||||
isPageAnimate,
|
||||
pageAnimateType,
|
||||
} = projectSetting;
|
||||
|
||||
interface ProjectSettingState {
|
||||
navMode: string; //导航模式
|
||||
navTheme: string; //导航风格
|
||||
headerSetting: IheaderSetting; //顶部设置
|
||||
showFooter: boolean; //页脚
|
||||
menuSetting: ImenuSetting; //多标签
|
||||
multiTabsSetting: ImultiTabsSetting; //多标签
|
||||
crumbsSetting: IcrumbsSetting; //面包屑
|
||||
permissionMode: string; //权限模式
|
||||
isPageAnimate: boolean; //是否开启路由动画
|
||||
pageAnimateType: string; //路由动画类型
|
||||
isMobile: boolean; // 是否处于移动端模式
|
||||
}
|
||||
|
||||
export const useProjectSettingStore = defineStore({
|
||||
id: 'app-project-setting',
|
||||
state: (): ProjectSettingState => ({
|
||||
navMode: navMode,
|
||||
navTheme,
|
||||
isMobile,
|
||||
headerSetting,
|
||||
showFooter,
|
||||
menuSetting,
|
||||
multiTabsSetting,
|
||||
crumbsSetting,
|
||||
permissionMode,
|
||||
isPageAnimate,
|
||||
pageAnimateType,
|
||||
}),
|
||||
getters: {
|
||||
getNavMode(): string {
|
||||
return this.navMode;
|
||||
},
|
||||
getNavTheme(): string {
|
||||
return this.navTheme;
|
||||
},
|
||||
getIsMobile(): boolean {
|
||||
return this.isMobile;
|
||||
},
|
||||
getHeaderSetting(): object {
|
||||
return this.headerSetting;
|
||||
},
|
||||
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;
|
||||
},
|
||||
},
|
||||
actions: {
|
||||
setNavTheme(value: string): void {
|
||||
this.navTheme = value;
|
||||
},
|
||||
setIsMobile(value: boolean): void {
|
||||
this.isMobile = value;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// Need to be used outside the setup
|
||||
export function useProjectSettingStoreWithOut() {
|
||||
return useProjectSettingStore(store);
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
import { defineStore } from 'pinia';
|
||||
import { RouteLocationNormalized } from 'vue-router';
|
||||
|
||||
// 不需要出现在标签页中的路由
|
||||
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[]; // 标签页
|
||||
signal: string; // 关闭信号
|
||||
};
|
||||
|
||||
//保留固定路由
|
||||
function retainAffixRoute(list: any[]) {
|
||||
return list.filter((item) => item?.meta?.affix ?? false);
|
||||
}
|
||||
|
||||
export const useTabsViewStore = defineStore({
|
||||
id: 'app-tabs-view',
|
||||
state: (): ITabsViewState => ({
|
||||
tabsList: [],
|
||||
signal: '',
|
||||
}),
|
||||
getters: {},
|
||||
actions: {
|
||||
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);
|
||||
}
|
||||
return true;
|
||||
},
|
||||
closeLeftTabs(route) {
|
||||
// 关闭左侧
|
||||
const index = this.tabsList.findIndex((item) => item.fullPath == route.fullPath);
|
||||
this.tabsList = this.tabsList.filter((item, i) => i >= index || (item?.meta?.affix ?? false));
|
||||
},
|
||||
closeRightTabs(route) {
|
||||
// 关闭右侧
|
||||
const index = this.tabsList.findIndex((item) => item.fullPath == route.fullPath);
|
||||
this.tabsList = this.tabsList.filter((item, i) => i <= index || (item?.meta?.affix ?? false));
|
||||
},
|
||||
closeOtherTabs(route) {
|
||||
// 关闭其他
|
||||
this.tabsList = this.tabsList.filter(
|
||||
(item) => item.fullPath == route.fullPath || (item?.meta?.affix ?? false)
|
||||
);
|
||||
},
|
||||
closeCurrentTab(route) {
|
||||
// 关闭当前页
|
||||
const index = this.tabsList.findIndex((item) => item.fullPath == route.fullPath);
|
||||
this.tabsList.splice(index, 1);
|
||||
},
|
||||
closeAllTabs() {
|
||||
// 关闭全部
|
||||
console.log(retainAffixRoute(this.tabsList));
|
||||
this.tabsList = retainAffixRoute(this.tabsList);
|
||||
},
|
||||
closeSignal(signal: string) {
|
||||
// 设置tab关闭信号, 1刷新 2关闭当前 3关闭其他 4关闭全部
|
||||
this.signal = signal;
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,276 @@
|
||||
import { defineStore } from 'pinia';
|
||||
import { createStorage, storage } from '@/utils/Storage';
|
||||
import { store } from '@/store';
|
||||
import {
|
||||
ACCESS_TOKEN,
|
||||
CURRENT_CONFIG,
|
||||
CURRENT_DICT,
|
||||
CURRENT_LOGIN_CONFIG,
|
||||
CURRENT_USER,
|
||||
IS_LOCKSCREEN,
|
||||
} from '@/store/mutation-types';
|
||||
import { ResultEnum } from '@/enums/httpEnum';
|
||||
import {
|
||||
getConfig,
|
||||
getLoginConfig,
|
||||
getUserInfo,
|
||||
login,
|
||||
logout,
|
||||
mobileLogin,
|
||||
} from '@/api/system/user';
|
||||
import { isWechatBrowser } from '@/utils/is';
|
||||
import { DeptTypeEnum } from '@/enums/deptEnum';
|
||||
const Storage = createStorage({ storage: localStorage });
|
||||
|
||||
export interface UserInfoState {
|
||||
id: number;
|
||||
deptName: string;
|
||||
deptType: string;
|
||||
roleName: string;
|
||||
cityLabel: string;
|
||||
permissions: string[];
|
||||
username: string;
|
||||
realName: string;
|
||||
avatar: string;
|
||||
balance: number;
|
||||
integral: number;
|
||||
sex: number;
|
||||
qq: string;
|
||||
email: string;
|
||||
mobile: string;
|
||||
birthday: string;
|
||||
cityId: number;
|
||||
address: string;
|
||||
cash: {
|
||||
name: string;
|
||||
account: string;
|
||||
payeeCode: string;
|
||||
};
|
||||
createdAt: string;
|
||||
loginCount: number;
|
||||
lastLoginAt: string;
|
||||
lastLoginIp: string;
|
||||
openId: string;
|
||||
inviteCode: string;
|
||||
}
|
||||
|
||||
export interface ConfigState {
|
||||
domain: string;
|
||||
version: string;
|
||||
wsAddr: string;
|
||||
mode: string;
|
||||
}
|
||||
|
||||
export interface LoginConfigState {
|
||||
loginRegisterSwitch: number;
|
||||
loginCaptchaSwitch: number;
|
||||
loginAutoOpenId: number;
|
||||
loginProtocol: string;
|
||||
loginPolicy: string;
|
||||
i18nSwitch: boolean;
|
||||
defaultLanguage: string;
|
||||
projectName: string;
|
||||
}
|
||||
|
||||
export interface IUserState {
|
||||
token: string;
|
||||
username: string;
|
||||
realName: string;
|
||||
avatar: string;
|
||||
permissions: any[];
|
||||
info: UserInfoState | null;
|
||||
config: ConfigState | null;
|
||||
loginConfig: LoginConfigState | null;
|
||||
}
|
||||
|
||||
export const useUserStore = defineStore({
|
||||
id: 'app-member',
|
||||
state: (): IUserState => ({
|
||||
token: Storage.get(ACCESS_TOKEN, ''),
|
||||
username: '',
|
||||
realName: '',
|
||||
avatar: '',
|
||||
permissions: [],
|
||||
info: Storage.get(CURRENT_USER, null),
|
||||
config: Storage.get(CURRENT_CONFIG, null),
|
||||
loginConfig: Storage.get(CURRENT_LOGIN_CONFIG, null),
|
||||
}),
|
||||
getters: {
|
||||
getToken(): string {
|
||||
return this.token;
|
||||
},
|
||||
getAvatar(): string {
|
||||
return this.avatar;
|
||||
},
|
||||
getUsername(): string {
|
||||
return this.username;
|
||||
},
|
||||
getRealName(): string {
|
||||
return this.realName;
|
||||
},
|
||||
getPermissions(): [any][] {
|
||||
return this.permissions;
|
||||
},
|
||||
getUserInfo(): UserInfoState | null {
|
||||
return this.info;
|
||||
},
|
||||
getConfig(): ConfigState | null {
|
||||
return this.config;
|
||||
},
|
||||
getLoginConfig(): LoginConfigState | null {
|
||||
return this.loginConfig;
|
||||
},
|
||||
isCompanyDept(): boolean {
|
||||
return this.info?.deptType == DeptTypeEnum.Company;
|
||||
},
|
||||
isTenantDept(): boolean {
|
||||
return this.info?.deptType == DeptTypeEnum.Tenant;
|
||||
},
|
||||
isMerchantDept(): boolean {
|
||||
return this.info?.deptType == DeptTypeEnum.Merchant;
|
||||
},
|
||||
isUserDept(): boolean {
|
||||
return this.info?.deptType == DeptTypeEnum.User;
|
||||
},
|
||||
},
|
||||
actions: {
|
||||
setToken(token: string) {
|
||||
this.token = token;
|
||||
},
|
||||
setAvatar(avatar: string) {
|
||||
this.avatar = avatar;
|
||||
},
|
||||
setUsername(username: string) {
|
||||
this.username = username;
|
||||
},
|
||||
setRealName(realName: string) {
|
||||
this.realName = realName;
|
||||
},
|
||||
setPermissions(permissions: string[]) {
|
||||
this.permissions = permissions;
|
||||
},
|
||||
setUserInfo(info: UserInfoState | null) {
|
||||
this.info = info;
|
||||
},
|
||||
setConfig(config: ConfigState | null) {
|
||||
this.config = config;
|
||||
},
|
||||
setLoginConfig(config: LoginConfigState | null) {
|
||||
this.loginConfig = config;
|
||||
},
|
||||
// 账号登录
|
||||
async login(userInfo) {
|
||||
return await this.handleLogin(login(userInfo));
|
||||
},
|
||||
// 手机号登录
|
||||
async mobileLogin(userInfo) {
|
||||
return await this.handleLogin(mobileLogin(userInfo));
|
||||
},
|
||||
async handleLogin(request: Promise<any>) {
|
||||
try {
|
||||
const response = await request;
|
||||
const { data, code } = response;
|
||||
if (code === ResultEnum.SUCCESS) {
|
||||
const ex = 30 * 24 * 60 * 60 * 1000;
|
||||
storage.set(ACCESS_TOKEN, data.token, ex);
|
||||
storage.set(CURRENT_USER, data, ex);
|
||||
storage.set(IS_LOCKSCREEN, false);
|
||||
this.setToken(data.token);
|
||||
this.setUserInfo(data);
|
||||
}
|
||||
return Promise.resolve(response);
|
||||
} catch (e) {
|
||||
return Promise.reject(e);
|
||||
}
|
||||
},
|
||||
// 获取用户信息
|
||||
GetInfo() {
|
||||
const that: any = this;
|
||||
return new Promise((resolve, reject) => {
|
||||
getUserInfo()
|
||||
.then((res) => {
|
||||
const result = res as UserInfoState;
|
||||
if (result.permissions && result.permissions.length) {
|
||||
const permissionsList = result.permissions;
|
||||
that.setPermissions(permissionsList);
|
||||
that.setUserInfo(result);
|
||||
that.setAvatar(result.avatar);
|
||||
that.setUsername(result.username);
|
||||
that.setRealName(result.realName);
|
||||
} else {
|
||||
reject(new Error('getInfo: permissionsList must be a non-null array !'));
|
||||
}
|
||||
resolve(result);
|
||||
})
|
||||
.catch((error) => {
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
},
|
||||
// 获取基础配置
|
||||
GetConfig() {
|
||||
const that = this;
|
||||
return new Promise((resolve, reject) => {
|
||||
getConfig()
|
||||
.then((res) => {
|
||||
const result = res;
|
||||
that.setConfig(result);
|
||||
storage.set(CURRENT_CONFIG, result);
|
||||
resolve(res);
|
||||
})
|
||||
.catch((error) => {
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
},
|
||||
// 获取登录配置
|
||||
LoadLoginConfig: function () {
|
||||
const that = this;
|
||||
return new Promise((resolve, reject) => {
|
||||
getLoginConfig()
|
||||
.then((res) => {
|
||||
const result = res as unknown as LoginConfigState;
|
||||
that.setLoginConfig(result);
|
||||
storage.set(CURRENT_LOGIN_CONFIG, result);
|
||||
resolve(res);
|
||||
})
|
||||
.catch((error) => {
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
},
|
||||
// 是否允许获取微信openid
|
||||
allowWxOpenId(): boolean {
|
||||
if (!isWechatBrowser()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.loginConfig !== null && this.loginConfig.loginAutoOpenId !== 1) {
|
||||
return false;
|
||||
}
|
||||
return this.info === null || this.info.openId === '';
|
||||
},
|
||||
// 登出
|
||||
async logout() {
|
||||
try {
|
||||
const response = await logout();
|
||||
const { code } = response;
|
||||
if (code === ResultEnum.SUCCESS) {
|
||||
this.setPermissions([]);
|
||||
this.setUserInfo(null);
|
||||
storage.remove(ACCESS_TOKEN);
|
||||
storage.remove(CURRENT_USER);
|
||||
storage.remove(CURRENT_DICT);
|
||||
}
|
||||
return Promise.resolve(response);
|
||||
} catch (e) {
|
||||
return Promise.reject(e);
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// Need to be used outside the setup
|
||||
export function useUserStoreWidthOut() {
|
||||
return useUserStore(store);
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
export const ACCESS_TOKEN = 'ACCESS-TOKEN'; // 用户token
|
||||
export const CURRENT_USER = 'CURRENT-USER'; // 当前用户信息
|
||||
export const CURRENT_CONFIG = 'CURRENT-CONFIG'; // 当前基础配置
|
||||
export const CURRENT_LOGIN_CONFIG = 'CURRENT-LOGIN-CONFIG'; // 当前登录配置
|
||||
export const IS_LOCKSCREEN = 'IS-LOCKSCREEN'; // 是否锁屏
|
||||
export const TABS_ROUTES = 'TABS-ROUTES'; // 标签页
|
||||
export const CURRENT_DICT = 'CURRENT-DICT'; // 当前用户字典配置
|
||||
export const CurrentLocale = 'Current-Locale'; // 当前语言
|
||||
@@ -0,0 +1,16 @@
|
||||
import { IAsyncRouteState } from '@/store/modules/asyncRoute';
|
||||
import { IUserState } from '@/store/modules/user';
|
||||
import { ILockscreenState } from '@/store/modules/lockscreen';
|
||||
import { ITabsViewState } from '@/store/modules/tabsView';
|
||||
import { INotificationStore } from '@/store/modules/notification';
|
||||
import { IDictState } from '@/store/modules/dict';
|
||||
|
||||
export interface IStore {
|
||||
asyncRoute: IAsyncRouteState;
|
||||
user: IUserState;
|
||||
lockscreen: ILockscreenState;
|
||||
tabsView: ITabsViewState;
|
||||
dict: IDictState;
|
||||
notification: INotificationStore;
|
||||
count: number;
|
||||
}
|
||||
Reference in New Issue
Block a user