refactor: 优化pinia setup store组合式函数写法

Former-commit-id: 27347ede51d0952d3422c3a6c3a86652f91e5639
This commit is contained in:
haoxr
2022-12-18 15:27:53 +08:00
parent fe49485563
commit 2a36afae16
27 changed files with 944 additions and 973 deletions

View File

@@ -1,15 +1,11 @@
import useUserStore from './modules/user';
import useAppStore from './modules/app';
import usePermissionStore from './modules/permission';
import useSettingStore from './modules/settings';
import useTagsViewStore from './modules/tagsView';
import type { App } from 'vue';
import { createPinia } from 'pinia';
const useStore = () => ({
user: useUserStore(),
app: useAppStore(),
permission: usePermissionStore(),
setting: useSettingStore(),
tagsView: useTagsViewStore()
});
const store = createPinia();
export default useStore;
// 全局挂载store
export function setupStore(app: App<Element>) {
app.use(store);
}
export { store };

View File

@@ -1,48 +1,96 @@
import { AppState } from './types';
import { localStorage } from '@/utils/storage';
import {
getSidebarStatus,
setSidebarStatus,
getSize,
setSize,
setLanguage
} from '@/utils/localStorage';
import { defineStore } from 'pinia';
import { getLanguage } from '@/lang/index';
import { computed, reactive, ref } from 'vue';
const useAppStore = defineStore({
id: 'app',
state: (): AppState => ({
device: 'desktop',
sidebar: {
opened: localStorage.get('sidebarStatus')
? !!+localStorage.get('sidebarStatus')
: true,
withoutAnimation: false,
},
language: getLanguage(),
size: localStorage.get('size') || 'default',
}),
actions: {
toggleSidebar() {
this.sidebar.opened = !this.sidebar.opened;
this.sidebar.withoutAnimation = false;
if (this.sidebar.opened) {
localStorage.set('sidebarStatus', 1);
} else {
localStorage.set('sidebarStatus', 0);
}
},
closeSideBar(withoutAnimation: any) {
localStorage.set('sidebarStatus', 0);
this.sidebar.opened = false;
this.sidebar.withoutAnimation = withoutAnimation;
},
toggleDevice(device: string) {
this.device = device;
},
setSize(size: string) {
this.size = size;
localStorage.set('size', size);
},
setLanguage(language: string) {
this.language = language;
localStorage.set('language', language);
},
},
// Element Plus 语言包
import zhCn from 'element-plus/es/locale/lang/zh-cn';
import en from 'element-plus/es/locale/lang/en';
export enum DeviceType {
mobile,
desktop
}
export enum SizeType {
default,
large,
small
}
// setup
export const useAppStore = defineStore('app', () => {
// state
const device = ref<DeviceType>(DeviceType.desktop);
const size = ref(getSize() || 'default');
const language = ref(getLanguage());
const sidebar = reactive({
opened: getSidebarStatus() !== 'closed',
withoutAnimation: false
});
const locale = computed(() => {
if (language?.value == 'en') {
return en;
} else {
return zhCn;
}
});
// actions
function toggleSidebar(withoutAnimation: boolean) {
sidebar.opened = !sidebar.opened;
sidebar.withoutAnimation = withoutAnimation;
if (sidebar.opened) {
setSidebarStatus('opened');
} else {
setSidebarStatus('closed');
}
}
function closeSideBar(withoutAnimation: boolean) {
sidebar.opened = false;
sidebar.withoutAnimation = withoutAnimation;
setSidebarStatus('closed');
}
function openSideBar(withoutAnimation: boolean) {
sidebar.opened = true;
sidebar.withoutAnimation = withoutAnimation;
setSidebarStatus('opened');
}
function toggleDevice(val: DeviceType) {
device.value = val;
}
function changeSize(val: string) {
size.value = val;
setSize(val);
}
function changeLanguage(val: string) {
language.value = val;
setLanguage(val);
}
return {
device,
sidebar,
language,
locale,
size,
toggleDevice,
changeSize,
changeLanguage,
toggleSidebar,
closeSideBar,
openSideBar
};
});
export default useAppStore;

View File

@@ -1,8 +1,9 @@
import { PermissionState } from './types';
import { RouteRecordRaw } from 'vue-router';
import { defineStore } from 'pinia';
import { constantRoutes } from '@/router';
import { store } from '@/store';
import { listRoutes } from '@/api/menu';
import { ref } from 'vue';
const modules = import.meta.glob('../../views/**/**.vue');
export const Layout = () => import('@/layout/index.vue');
@@ -21,10 +22,7 @@ const hasPermission = (roles: string[], route: RouteRecordRaw) => {
return false;
};
export const filterAsyncRoutes = (
routes: RouteRecordRaw[],
roles: string[]
) => {
const filterAsyncRoutes = (routes: RouteRecordRaw[], roles: string[]) => {
const res: RouteRecordRaw[] = [];
routes.forEach(route => {
const tmp = { ...route } as any;
@@ -49,32 +47,36 @@ export const filterAsyncRoutes = (
return res;
};
const usePermissionStore = defineStore({
id: 'permission',
state: (): PermissionState => ({
routes: [],
addRoutes: []
}),
actions: {
setRoutes(routes: RouteRecordRaw[]) {
this.addRoutes = routes;
this.routes = constantRoutes.concat(routes);
},
generateRoutes(roles: string[]) {
return new Promise((resolve, reject) => {
listRoutes()
.then(response => {
const asyncRoutes = response.data;
const accessedRoutes = filterAsyncRoutes(asyncRoutes, roles);
this.setRoutes(accessedRoutes);
resolve(accessedRoutes);
})
.catch(error => {
reject(error);
});
});
}
// setup
export const usePermissionStore = defineStore('permission', () => {
// state
const routes = ref<RouteRecordRaw[]>([]);
const addRoutes = ref<RouteRecordRaw[]>([]);
// auctions
function setRoutes(newRoutes: RouteRecordRaw[]) {
addRoutes.value = newRoutes;
routes.value = constantRoutes.concat(newRoutes);
}
function generateRoutes(roles: string[]) {
return new Promise<RouteRecordRaw[]>((resolve, reject) => {
listRoutes()
.then(response => {
const asyncRoutes = response.data;
const accessedRoutes = filterAsyncRoutes(asyncRoutes, roles);
setRoutes(accessedRoutes);
resolve(accessedRoutes);
})
.catch(error => {
reject(error);
});
});
}
return { routes, setRoutes, generateRoutes };
});
export default usePermissionStore;
// 非setup
export function usePermissionStoreHook() {
return usePermissionStore(store);
}

View File

@@ -1,50 +1,55 @@
import { defineStore } from 'pinia';
import { SettingState } from './types';
import defaultSettings from '../../settings';
import { localStorage } from '@/utils/storage';
import { localStorage } from '@/utils/localStorage';
import { ref } from 'vue';
const { showSettings, tagsView, fixedHeader, sidebarLogo } = defaultSettings;
const el = document.documentElement;
export const useSettingStore = defineStore({
id: 'setting',
state: (): SettingState => ({
theme:
localStorage.get('theme') ||
getComputedStyle(el).getPropertyValue(`--el-color-primary`),
showSettings: showSettings,
tagsView:
localStorage.get('tagsView') != null
? localStorage.get('tagsView')
: tagsView,
fixedHeader: fixedHeader,
sidebarLogo: sidebarLogo,
}),
actions: {
async changeSetting(payload: { key: string; value: any }) {
const { key, value } = payload;
switch (key) {
case 'theme':
this.theme = value;
break;
case 'showSettings':
this.showSettings = value;
break;
case 'fixedHeader':
this.fixedHeader = value;
break;
case 'tagsView':
this.tagsView = value;
localStorage.set('tagsView', value);
break;
case 'sidebarLogo':
this.sidebarLogo = value;
break;
default:
break;
}
},
},
});
export const useSettingsStore = defineStore('setting', () => {
// state
const theme = ref(
localStorage.get('theme') ||
getComputedStyle(el).getPropertyValue(`--el-color-primary`)
);
export default useSettingStore;
const showSettings = ref<boolean>(defaultSettings.showSettings);
const tagsView = ref<boolean>(
localStorage.get('tagsView') || defaultSettings.tagsView
);
const fixedHeader = ref<boolean>(defaultSettings.fixedHeader);
const sidebarLogo = ref<boolean>(defaultSettings.sidebarLogo);
// auction
function changeSetting(param: { key: string; value: any }) {
const { key, value } = param;
switch (key) {
case 'theme':
theme.value = value;
break;
case 'showSettings':
showSettings.value = value;
break;
case 'fixedHeader':
fixedHeader.value = value;
localStorage.set('tagsView', value);
break;
case 'tagsView':
tagsView.value = value;
break;
case 'sidevarLogo':
sidebarLogo.value = value;
break;
default:
break;
}
}
return {
theme,
showSettings,
tagsView,
fixedHeader,
sidebarLogo,
changeSetting
};
});

View File

@@ -1,181 +1,214 @@
import { defineStore } from 'pinia';
import { TagsViewState } from './types';
import { ref } from 'vue';
import { RouteLocationNormalized } from 'vue-router';
const useTagsViewStore = defineStore({
id: 'tagsView',
state: (): TagsViewState => ({
visitedViews: [],
cachedViews: [], // keepAlive 缓存页面
}),
actions: {
addVisitedView(view: any) {
if (this.visitedViews.some((v) => v.path === view.path)) return;
if (view.meta && view.meta.affix) {
this.visitedViews.unshift(
Object.assign({}, view, {
title: view.meta?.title || 'no-name',
})
);
} else {
this.visitedViews.push(
Object.assign({}, view, {
title: view.meta?.title || 'no-name',
})
);
}
},
addCachedView(view: any) {
if (this.cachedViews.includes(view.name)) return;
if (view.meta.keepAlive) {
this.cachedViews.push(view.name);
}
},
delVisitedView(view: any) {
return new Promise((resolve) => {
for (const [i, v] of this.visitedViews.entries()) {
if (v.path === view.path) {
this.visitedViews.splice(i, 1);
break;
}
}
resolve([...this.visitedViews]);
});
},
delCachedView(view: any) {
return new Promise((resolve) => {
const index = this.cachedViews.indexOf(view.name);
index > -1 && this.cachedViews.splice(index, 1);
resolve([...this.cachedViews]);
});
},
delOtherVisitedViews(view: any) {
return new Promise((resolve) => {
this.visitedViews = this.visitedViews.filter((v) => {
return v.meta?.affix || v.path === view.path;
});
resolve([...this.visitedViews]);
});
},
delOtherCachedViews(view: any) {
return new Promise((resolve) => {
const index = this.cachedViews.indexOf(view.name);
if (index > -1) {
this.cachedViews = this.cachedViews.slice(index, index + 1);
} else {
// if index = -1, there is no cached tags
this.cachedViews = [];
}
resolve([...this.cachedViews]);
});
},
export interface TagView extends Partial<RouteLocationNormalized> {
title?: string;
}
updateVisitedView(view: any) {
for (let v of this.visitedViews) {
// setup
export const useTagsViewStore = defineStore('tagsView', () => {
// state
const visitedViews = ref<TagView[]>([]);
const cachedViews = ref<string[]>([]);
// auctions
function addVisitedView(view: TagView) {
if (visitedViews.value.some(v => v.path === view.path)) return;
if (view.meta && view.meta.affix) {
visitedViews.value.unshift(
Object.assign({}, view, {
title: view.meta?.title || 'no-name'
})
);
} else {
visitedViews.value.push(
Object.assign({}, view, {
title: view.meta?.title || 'no-name'
})
);
}
}
function addCachedView(view: TagView) {
const viewName = view.name as string;
if (cachedViews.value.includes(viewName)) return;
if (view.meta?.keepAlive) {
cachedViews.value.push(viewName);
}
}
function delVisitedView(view: TagView) {
return new Promise(resolve => {
for (const [i, v] of visitedViews.value.entries()) {
if (v.path === view.path) {
v = Object.assign(v, view);
visitedViews.value.splice(i, 1);
break;
}
}
},
addView(view: any) {
this.addVisitedView(view);
this.addCachedView(view);
},
delView(view: any) {
return new Promise((resolve) => {
this.delVisitedView(view);
this.delCachedView(view);
resolve({
visitedViews: [...this.visitedViews],
cachedViews: [...this.cachedViews],
});
});
},
delOtherViews(view: any) {
return new Promise((resolve) => {
this.delOtherVisitedViews(view);
this.delOtherCachedViews(view);
resolve({
visitedViews: [...this.visitedViews],
cachedViews: [...this.cachedViews],
});
});
},
delLeftViews(view: any) {
return new Promise((resolve) => {
const currIndex = this.visitedViews.findIndex(
(v) => v.path === view.path
);
if (currIndex === -1) {
return;
}
this.visitedViews = this.visitedViews.filter((item, index) => {
// affix:true 固定tag例如“首页”
if (index >= currIndex || (item.meta && item.meta.affix)) {
return true;
}
resolve([...visitedViews.value]);
});
}
const cacheIndex = this.cachedViews.indexOf(item.name as string);
if (cacheIndex > -1) {
this.cachedViews.splice(cacheIndex, 1);
}
return false;
});
resolve({
visitedViews: [...this.visitedViews],
});
});
},
delRightViews(view: any) {
return new Promise((resolve) => {
const currIndex = this.visitedViews.findIndex(
(v) => v.path === view.path
);
if (currIndex === -1) {
return;
}
this.visitedViews = this.visitedViews.filter((item, index) => {
// affix:true 固定tag例如“首页”
if (index <= currIndex || (item.meta && item.meta.affix)) {
return true;
}
function delCachedView(view: TagView) {
const viewName = view.name as string;
return new Promise(resolve => {
const index = cachedViews.value.indexOf(viewName);
index > -1 && cachedViews.value.splice(index, 1);
resolve([...cachedViews.value]);
});
}
const cacheIndex = this.cachedViews.indexOf(item.name as string);
if (cacheIndex > -1) {
this.cachedViews.splice(cacheIndex, 1);
}
return false;
});
resolve({
visitedViews: [...this.visitedViews],
});
function delOtherVisitedViews(view: TagView) {
return new Promise(resolve => {
visitedViews.value = visitedViews.value.filter(v => {
return v.meta?.affix || v.path === view.path;
});
},
delAllViews() {
return new Promise((resolve) => {
const affixTags = this.visitedViews.filter((tag) => tag.meta?.affix);
this.visitedViews = affixTags;
this.cachedViews = [];
resolve({
visitedViews: [...this.visitedViews],
cachedViews: [...this.cachedViews],
});
resolve([...visitedViews.value]);
});
}
function delOtherCachedViews(view: TagView) {
const viewName = view.name as string;
return new Promise(resolve => {
const index = cachedViews.value.indexOf(viewName);
if (index > -1) {
cachedViews.value = cachedViews.value.slice(index, index + 1);
} else {
// if index = -1, there is no cached tags
cachedViews.value = [];
}
resolve([...cachedViews.value]);
});
}
function updateVisitedView(view: TagView) {
for (let v of visitedViews.value) {
if (v.path === view.path) {
v = Object.assign(v, view);
break;
}
}
}
function addView(view: TagView) {
addVisitedView(view);
addCachedView(view);
}
function delView(view: TagView) {
return new Promise(resolve => {
delVisitedView(view);
delCachedView(view);
resolve({
visitedViews: [...visitedViews.value],
cachedViews: [...cachedViews.value]
});
},
delAllVisitedViews() {
return new Promise((resolve) => {
const affixTags = this.visitedViews.filter((tag) => tag.meta?.affix);
this.visitedViews = affixTags;
resolve([...this.visitedViews]);
});
}
function delOtherViews(view: TagView) {
return new Promise(resolve => {
delOtherVisitedViews(view);
delOtherCachedViews(view);
resolve({
visitedViews: [...visitedViews.value],
cachedViews: [...cachedViews.value]
});
},
delAllCachedViews() {
return new Promise((resolve) => {
this.cachedViews = [];
resolve([...this.cachedViews]);
});
}
function delLeftViews(view: TagView) {
return new Promise(resolve => {
const currIndex = visitedViews.value.findIndex(v => v.path === view.path);
if (currIndex === -1) {
return;
}
visitedViews.value = visitedViews.value.filter((item, index) => {
// affix:true 固定tag例如“首页”
if (index >= currIndex || (item.meta && item.meta.affix)) {
return true;
}
const cacheIndex = cachedViews.value.indexOf(item.name as string);
if (cacheIndex > -1) {
cachedViews.value.splice(cacheIndex, 1);
}
return false;
});
},
},
resolve({
visitedViews: [...visitedViews.value]
});
});
}
function delRightViews(view: TagView) {
return new Promise(resolve => {
const currIndex = visitedViews.value.findIndex(v => v.path === view.path);
if (currIndex === -1) {
return;
}
visitedViews.value = visitedViews.value.filter((item, index) => {
// affix:true 固定tag例如“首页”
if (index <= currIndex || (item.meta && item.meta.affix)) {
return true;
}
const cacheIndex = cachedViews.value.indexOf(item.name as string);
if (cacheIndex > -1) {
cachedViews.value.splice(cacheIndex, 1);
}
return false;
});
resolve({
visitedViews: [...visitedViews.value]
});
});
}
function delAllViews() {
return new Promise(resolve => {
const affixTags = visitedViews.value.filter(tag => tag.meta?.affix);
visitedViews.value = affixTags;
cachedViews.value = [];
resolve({
visitedViews: [...visitedViews.value],
cachedViews: [...cachedViews.value]
});
});
}
function delAllVisitedViews() {
return new Promise(resolve => {
const affixTags = visitedViews.value.filter(tag => tag.meta?.affix);
visitedViews.value = affixTags;
resolve([...visitedViews.value]);
});
}
function delAllCachedViews() {
return new Promise(resolve => {
cachedViews.value = [];
resolve([...cachedViews.value]);
});
}
return {
visitedViews,
cachedViews,
addVisitedView,
addCachedView,
delVisitedView,
delCachedView,
delOtherVisitedViews,
delOtherCachedViews,
updateVisitedView,
addView,
delView,
delOtherViews,
delLeftViews,
delRightViews,
delAllViews,
delAllVisitedViews,
delAllCachedViews
};
});
export default useTagsViewStore;

View File

@@ -1,103 +1,101 @@
import { defineStore } from 'pinia';
import { UserState } from './types';
import { localStorage } from '@/utils/storage';
import { getToken, setToken, removeToken } from '@/utils/auth';
import { loginApi, logoutApi } from '@/api/auth';
import { getUserInfo } from '@/api/user';
import { resetRouter } from '@/router';
import { LoginForm } from '@/api/auth/types';
import { store } from '@/store';
import { LoginData } from '@/api/auth/types';
import { ref } from 'vue';
import { UserInfo } from '@/api/user/types';
const useUserStore = defineStore({
id: 'user',
state: (): UserState => ({
token: localStorage.get('token') || '',
nickname: '',
avatar: '',
roles: [],
perms: []
}),
actions: {
async RESET_STATE() {
this.$reset();
},
/**
* 登录
*/
login(data: LoginForm) {
const { username, password } = data;
return new Promise((resolve, reject) => {
loginApi({
grant_type: 'password',
username: username.trim(),
password: password
export const useUserStore = defineStore('user', () => {
// state
const token = ref<string>(getToken() || '');
const nickname = ref<string>('');
const avatar = ref<string>('');
const roles = ref<Array<string>>([]); // 用户角色编码集合 → 判断路由权限
const perms = ref<Array<string>>([]); // 用户权限编码集合 → 判断按钮权限
// auctions
// 登录
function login(loginData: LoginData) {
return new Promise<void>((resolve, reject) => {
loginApi(loginData)
.then(response => {
const { accessToken } = response.data;
token.value = accessToken;
setToken(accessToken);
resolve();
})
.then(response => {
console.log('response.data', response.data);
const accessToken = response.data;
localStorage.set('token', accessToken);
this.token = accessToken;
resolve(accessToken);
})
.catch(error => {
reject(error);
});
});
},
/**
* 获取用户信息(昵称、头像、角色集合、权限集合)
*/
getUserInfo() {
return new Promise((resolve, reject) => {
getUserInfo()
.then(({ data }) => {
if (!data) {
return reject('Verification failed, please Login again.');
}
const { nickname, avatar, roles, perms } = data;
if (!roles || roles.length <= 0) {
reject('getUserInfo: roles must be a non-null array!');
}
this.nickname = nickname;
this.avatar = avatar;
this.roles = roles;
this.perms = perms;
resolve(data);
})
.catch(error => {
reject(error);
});
});
},
/**
* 注销
*/
logout() {
return new Promise((resolve, reject) => {
logoutApi()
.then(() => {
localStorage.remove('token');
this.RESET_STATE();
resetRouter();
resolve(null);
})
.catch(error => {
reject(error);
});
});
},
/**
* 清除 Token
*/
resetToken() {
return new Promise(resolve => {
localStorage.remove('token');
this.RESET_STATE();
resolve(null);
});
}
.catch(error => {
reject(error);
});
});
}
// 获取信息(用户昵称、头像、角色集合、权限集合)
function getInfo() {
return new Promise<UserInfo>((resolve, reject) => {
getUserInfo()
.then(({ data }) => {
if (!data) {
return reject('Verification failed, please Login again.');
}
if (!data.roles || data.roles.length <= 0) {
reject('getUserInfo: roles must be a non-null array!');
}
nickname.value = data.nickname;
avatar.value = data.avatar;
roles.value = data.roles;
perms.value = data.perms;
resolve(data);
})
.catch(error => {
reject(error);
});
});
}
// 注销
function logout() {
return new Promise<void>((resolve, reject) => {
logoutApi()
.then(() => {
resetRouter();
resetToken();
resolve();
})
.catch(error => {
reject(error);
});
});
}
// 重置
function resetToken() {
removeToken();
token.value = '';
nickname.value = '';
avatar.value = '';
roles.value = [];
perms.value = [];
}
return {
token,
nickname,
avatar,
roles,
perms,
login,
getInfo,
logout,
resetToken
};
});
export default useUserStore;
// 非setup
export function useUserStoreHook() {
return useUserStore(store);
}