refactor: Setup Stores

This commit is contained in:
pany 2022-08-24 16:52:01 +08:00
parent 94a50cf54e
commit 8d812dd2d3
12 changed files with 215 additions and 232 deletions

View File

@ -2,8 +2,10 @@
import { useAppStore } from "@/store/modules/app" import { useAppStore } from "@/store/modules/app"
import zhCn from "element-plus/lib/locale/lang/zh-cn" import zhCn from "element-plus/lib/locale/lang/zh-cn"
const appStore = useAppStore()
/** 初始化主题 */ /** 初始化主题 */
useAppStore().initTheme() appStore.initTheme()
/** 将 Element-Plus 的语言设置为中文 */ /** 将 Element-Plus 的语言设置为中文 */
const locale = zhCn const locale = zhCn
</script> </script>

View File

@ -6,7 +6,7 @@ interface ILoginData {
} }
/** 登录并返回 Token */ /** 登录并返回 Token */
export function login(data: ILoginData) { export function loginApi(data: ILoginData) {
return request({ return request({
url: "users/login", url: "users/login",
method: "post", method: "post",
@ -14,7 +14,7 @@ export function login(data: ILoginData) {
}) })
} }
/** 获取用户详情 */ /** 获取用户详情 */
export function getUserInfo() { export function getUserInfoApi() {
return request({ return request({
url: "users/info", url: "users/info",
method: "post" method: "post"

View File

@ -1,17 +1,16 @@
<script lang="ts" setup> <script lang="ts" setup>
import { computed } from "vue" import { computed } from "vue"
import { useAppStore } from "@/store/modules/app" import { useAppStore } from "@/store/modules/app"
import themeList from "@/config/theme"
import type { ThemeName } from "@/config/theme"
import { MagicStick } from "@element-plus/icons-vue" import { MagicStick } from "@element-plus/icons-vue"
const appStore = useAppStore() const appStore = useAppStore()
const themeList = computed(() => {
return appStore.themeList
})
const activeThemeName = computed(() => { const activeThemeName = computed(() => {
return appStore.activeThemeName return appStore.activeThemeName
}) })
const handleSetTheme = (name: string) => { const handleSetTheme = (name: ThemeName) => {
appStore.setTheme(name) appStore.setTheme(name)
} }
</script> </script>

View File

@ -1,5 +1,12 @@
/** 注册的主题 */ /** 注册的主题, 其中 normal 是必须的, dark 是内置的, 如需更多主题,可自行注册 */
const themeList = [ export type ThemeName = "normal" | "dark"
interface IThemeList {
title: string
name: ThemeName
}
const themeList: IThemeList[] = [
{ {
title: "默认", title: "默认",
name: "normal" name: "normal"

View File

@ -13,15 +13,18 @@ const v3SidebarMenuTextColor = getCssVariableValue("--v3-sidebar-menu-text-color
const v3SidebarMenuActiveTextColor = getCssVariableValue("--v3-sidebar-menu-active-text-color") const v3SidebarMenuActiveTextColor = getCssVariableValue("--v3-sidebar-menu-active-text-color")
const route = useRoute() const route = useRoute()
const appStore = useAppStore()
const permissionStore = usePermissionStore()
const settingsStore = useSettingsStore()
const sidebar = computed(() => { const sidebar = computed(() => {
return useAppStore().sidebar return appStore.sidebar
}) })
const routes = computed(() => { const routes = computed(() => {
return usePermissionStore().routes return permissionStore.routes
}) })
const showLogo = computed(() => { const showLogo = computed(() => {
return useSettingsStore().showSidebarLogo return settingsStore.showSidebarLogo
}) })
const activeMenu = computed(() => { const activeMenu = computed(() => {
const { meta, path } = route const { meta, path } = route

View File

@ -1,68 +1,59 @@
import { reactive, ref } from "vue"
import { defineStore } from "pinia" import { defineStore } from "pinia"
import { getSidebarStatus, getActiveThemeName, setSidebarStatus, setActiveThemeName } from "@/utils/cache/localStorage" import { getSidebarStatus, getActiveThemeName, setSidebarStatus, setActiveThemeName } from "@/utils/cache/localStorage"
import themeList from "@/config/theme" import type { ThemeName } from "@/config/theme"
export enum DeviceType { export enum DeviceType {
Mobile, Mobile,
Desktop Desktop
} }
interface IAppState { interface ISidebar {
device: DeviceType opened: boolean
sidebar: { withoutAnimation: boolean
opened: boolean
withoutAnimation: boolean
}
/** 主题列表 */
themeList: { title: string; name: string }[]
/** 正在应用的主题的名字 */
activeThemeName: string
} }
export const useAppStore = defineStore({ const setClassName = (value: ThemeName) => {
id: "app", document.documentElement.className = value
state: (): IAppState => { }
return {
device: DeviceType.Desktop, export const useAppStore = defineStore("app", () => {
sidebar: { const sidebar: ISidebar = reactive({
opened: getSidebarStatus() !== "closed", opened: getSidebarStatus() !== "closed",
withoutAnimation: false withoutAnimation: false
}, })
themeList: themeList, const device = ref<DeviceType>(DeviceType.Desktop)
activeThemeName: getActiveThemeName() || "normal" /** 正在应用的主题的名字 */
} const activeThemeName = ref<ThemeName>(getActiveThemeName() || "normal")
},
actions: { const toggleSidebar = (withoutAnimation: boolean) => {
toggleSidebar(withoutAnimation: boolean) { sidebar.opened = !sidebar.opened
this.sidebar.opened = !this.sidebar.opened sidebar.withoutAnimation = withoutAnimation
this.sidebar.withoutAnimation = withoutAnimation if (sidebar.opened) {
if (this.sidebar.opened) { setSidebarStatus("opened")
setSidebarStatus("opened") } else {
} else {
setSidebarStatus("closed")
}
},
closeSidebar(withoutAnimation: boolean) {
this.sidebar.opened = false
this.sidebar.withoutAnimation = withoutAnimation
setSidebarStatus("closed") setSidebarStatus("closed")
},
toggleDevice(device: DeviceType) {
this.device = device
},
setTheme(activeThemeName: string) {
// 检查这个主题在主题列表里是否存在
this.activeThemeName = this.themeList.find((theme) => theme.name === activeThemeName)
? activeThemeName
: this.themeList[0].name
// 应用到 Dom
document.documentElement.className = this.activeThemeName
// 持久化
setActiveThemeName(this.activeThemeName)
},
initTheme() {
// 初始化
document.documentElement.className = this.activeThemeName
} }
} }
const closeSidebar = (withoutAnimation: boolean) => {
sidebar.opened = false
sidebar.withoutAnimation = withoutAnimation
setSidebarStatus("closed")
}
const toggleDevice = (value: DeviceType) => {
device.value = value
}
const setTheme = (value: ThemeName) => {
activeThemeName.value = value
// 应用到 Dom
setClassName(activeThemeName.value)
// 持久化
setActiveThemeName(activeThemeName.value)
}
const initTheme = () => {
// 初始化
setClassName(activeThemeName.value)
}
return { device, sidebar, activeThemeName, toggleSidebar, closeSidebar, toggleDevice, setTheme, initTheme }
}) })

View File

@ -1,13 +1,9 @@
import { ref } from "vue"
import store from "@/store" import store from "@/store"
import { defineStore } from "pinia" import { defineStore } from "pinia"
import { RouteRecordRaw } from "vue-router" import { RouteRecordRaw } from "vue-router"
import { constantRoutes, asyncRoutes } from "@/router" import { constantRoutes, asyncRoutes } from "@/router"
interface IPermissionState {
routes: RouteRecordRaw[]
dynamicRoutes: RouteRecordRaw[]
}
const hasPermission = (roles: string[], route: RouteRecordRaw) => { const hasPermission = (roles: string[], route: RouteRecordRaw) => {
if (route.meta && route.meta.roles) { if (route.meta && route.meta.roles) {
return roles.some((role) => { return roles.some((role) => {
@ -36,26 +32,22 @@ const filterAsyncRoutes = (routes: RouteRecordRaw[], roles: string[]) => {
return res return res
} }
export const usePermissionStore = defineStore({ export const usePermissionStore = defineStore("permission", () => {
id: "permission", const routes = ref<RouteRecordRaw[]>([])
state: (): IPermissionState => { const dynamicRoutes = ref<RouteRecordRaw[]>([])
return {
routes: [], const setRoutes = (roles: string[]) => {
dynamicRoutes: [] let accessedRoutes
} if (roles.includes("admin")) {
}, accessedRoutes = asyncRoutes
actions: { } else {
setRoutes(roles: string[]) { accessedRoutes = filterAsyncRoutes(asyncRoutes, roles)
let accessedRoutes
if (roles.includes("admin")) {
accessedRoutes = asyncRoutes
} else {
accessedRoutes = filterAsyncRoutes(asyncRoutes, roles)
}
this.routes = constantRoutes.concat(accessedRoutes)
this.dynamicRoutes = accessedRoutes
} }
routes.value = constantRoutes.concat(accessedRoutes)
dynamicRoutes.value = accessedRoutes
} }
return { routes, dynamicRoutes, setRoutes }
}) })
/** 在 setup 外使用 */ /** 在 setup 外使用 */

View File

@ -1,25 +1,14 @@
import { ref } from "vue"
import { defineStore } from "pinia" import { defineStore } from "pinia"
import layoutSettings from "@/config/layout" import layoutSettings from "@/config/layout"
interface ISettingsState { export const useSettingsStore = defineStore("settings", () => {
fixedHeader: boolean const fixedHeader = ref<boolean>(layoutSettings.fixedHeader)
showSettings: boolean const showSettings = ref<boolean>(layoutSettings.showSettings)
showTagsView: boolean const showTagsView = ref<boolean>(layoutSettings.showTagsView)
showSidebarLogo: boolean const showSidebarLogo = ref<boolean>(layoutSettings.showSidebarLogo)
showThemeSwitch: boolean const showThemeSwitch = ref<boolean>(layoutSettings.showThemeSwitch)
showScreenfull: boolean const showScreenfull = ref<boolean>(layoutSettings.showScreenfull)
}
export const useSettingsStore = defineStore({ return { fixedHeader, showSettings, showTagsView, showSidebarLogo, showThemeSwitch, showScreenfull }
id: "settings",
state: (): ISettingsState => {
return {
fixedHeader: layoutSettings.fixedHeader,
showSettings: layoutSettings.showSettings,
showTagsView: layoutSettings.showTagsView,
showSidebarLogo: layoutSettings.showSidebarLogo,
showThemeSwitch: layoutSettings.showThemeSwitch,
showScreenfull: layoutSettings.showScreenfull
}
}
}) })

View File

@ -1,3 +1,4 @@
import { ref } from "vue"
import { defineStore } from "pinia" import { defineStore } from "pinia"
import { _RouteLocationBase, RouteLocationNormalized } from "vue-router" import { _RouteLocationBase, RouteLocationNormalized } from "vue-router"
@ -6,51 +7,43 @@ export interface ITagView extends Partial<RouteLocationNormalized> {
to?: _RouteLocationBase to?: _RouteLocationBase
} }
interface ITagsViewState { export const useTagsViewStore = defineStore("tags-view", () => {
visitedViews: ITagView[] const visitedViews = ref<ITagView[]>([])
}
export const useTagsViewStore = defineStore({ const addVisitedView = (view: ITagView) => {
id: "tags-view", if (visitedViews.value.some((v) => v.path === view.path)) return
state: (): ITagsViewState => { visitedViews.value.push(
return { Object.assign({}, view, {
visitedViews: [] title: view.meta?.title || "no-name"
}
},
actions: {
addVisitedView(view: ITagView) {
if (this.visitedViews.some((v) => v.path === view.path)) return
this.visitedViews.push(
Object.assign({}, view, {
title: view.meta?.title || "no-name"
})
)
},
delVisitedView(view: ITagView) {
for (const [i, v] of this.visitedViews.entries()) {
if (v.path === view.path) {
this.visitedViews.splice(i, 1)
break
}
}
},
delOthersVisitedViews(view: ITagView) {
this.visitedViews = this.visitedViews.filter((v) => {
return v.meta?.affix || v.path === view.path
}) })
}, )
delAllVisitedViews() { }
// keep affix tags const delVisitedView = (view: ITagView) => {
const affixTags = this.visitedViews.filter((tag) => tag.meta?.affix) for (const [i, v] of visitedViews.value.entries()) {
this.visitedViews = affixTags if (v.path === view.path) {
}, visitedViews.value.splice(i, 1)
updateVisitedView(view: ITagView) { break
for (let v of this.visitedViews) {
if (v.path === view.path) {
v = Object.assign(v, view)
break
}
} }
} }
} }
const delOthersVisitedViews = (view: ITagView) => {
visitedViews.value = visitedViews.value.filter((v) => {
return v.meta?.affix || v.path === view.path
})
}
const delAllVisitedViews = () => {
// keep affix tags
const affixTags = visitedViews.value.filter((tag) => tag.meta?.affix)
visitedViews.value = affixTags
}
const updateVisitedView = (view: ITagView) => {
for (let v of visitedViews.value) {
if (v.path === view.path) {
v = Object.assign(v, view)
break
}
}
}
return { visitedViews, addVisitedView, delVisitedView, delOthersVisitedViews, delAllVisitedViews, updateVisitedView }
}) })

View File

@ -1,86 +1,78 @@
import { ref } from "vue"
import store from "@/store" import store from "@/store"
import { defineStore } from "pinia" import { defineStore } from "pinia"
import { usePermissionStore } from "./permission" import { usePermissionStore } from "./permission"
import { getToken, removeToken, setToken } from "@/utils/cache/cookies" import { getToken, removeToken, setToken } from "@/utils/cache/cookies"
import router, { resetRouter } from "@/router" import router, { resetRouter } from "@/router"
import { login, getUserInfo } from "@/api/login" import { loginApi, getUserInfoApi } from "@/api/login"
import { RouteRecordRaw } from "vue-router" import { RouteRecordRaw } from "vue-router"
interface IUserState { export const useUserStore = defineStore("user", () => {
token: string const token = ref<string>(getToken() || "")
roles: string[] const roles = ref<string[]>([])
}
export const useUserStore = defineStore({ /** 设置角色数组 */
id: "user", const setRoles = (value: string[]) => {
state: (): IUserState => { roles.value = value
return {
token: getToken() || "",
roles: []
}
},
actions: {
/** 设置角色数组 */
setRoles(roles: string[]) {
this.roles = roles
},
/** 登录 */
login(userInfo: { username: string; password: string }) {
return new Promise((resolve, reject) => {
login({
username: userInfo.username.trim(),
password: userInfo.password
})
.then((res: any) => {
setToken(res.data.accessToken)
this.token = res.data.accessToken
resolve(true)
})
.catch((error) => {
reject(error)
})
})
},
/** 获取用户详情 */
getInfo() {
return new Promise((resolve, reject) => {
getUserInfo()
.then((res: any) => {
this.roles = res.data.user.roles
resolve(res)
})
.catch((error) => {
reject(error)
})
})
},
/** 切换角色 */
async changeRoles(role: string) {
const token = role + "-token"
this.token = token
setToken(token)
await this.getInfo()
const permissionStore = usePermissionStore()
permissionStore.setRoutes(this.roles)
resetRouter()
permissionStore.dynamicRoutes.forEach((item: RouteRecordRaw) => {
router.addRoute(item)
})
},
/** 登出 */
logout() {
removeToken()
this.token = ""
this.roles = []
resetRouter()
},
/** 重置 Token */
resetToken() {
removeToken()
this.token = ""
this.roles = []
}
} }
/** 登录 */
const login = (userInfo: { username: string; password: string }) => {
return new Promise((resolve, reject) => {
loginApi({
username: userInfo.username,
password: userInfo.password
})
.then((res: any) => {
setToken(res.data.accessToken)
token.value = res.data.accessToken
resolve(true)
})
.catch((error) => {
reject(error)
})
})
}
/** 获取用户详情 */
const getInfo = () => {
return new Promise((resolve, reject) => {
getUserInfoApi()
.then((res: any) => {
roles.value = res.data.user.roles
resolve(res)
})
.catch((error) => {
reject(error)
})
})
}
/** 切换角色 */
const changeRoles = async (role: string) => {
const newToken = role + "-token"
token.value = newToken
setToken(newToken)
await getInfo()
const permissionStore = usePermissionStore()
permissionStore.setRoutes(roles.value)
resetRouter()
permissionStore.dynamicRoutes.forEach((item: RouteRecordRaw) => {
router.addRoute(item)
})
}
/** 登出 */
const logout = () => {
removeToken()
token.value = ""
roles.value = []
resetRouter()
}
/** 重置 Token */
const resetToken = () => {
removeToken()
token.value = ""
roles.value = []
}
return { token, roles, setRoles, login, getInfo, changeRoles, logout, resetToken }
}) })
/** 在 setup 外使用 */ /** 在 setup 外使用 */

View File

@ -3,6 +3,12 @@
import CacheKey from "@/constants/cacheKey" import CacheKey from "@/constants/cacheKey"
import Cookies from "js-cookie" import Cookies from "js-cookie"
export const getToken = () => Cookies.get(CacheKey.TOKEN) export const getToken = () => {
export const setToken = (token: string) => Cookies.set(CacheKey.TOKEN, token) return Cookies.get(CacheKey.TOKEN)
export const removeToken = () => Cookies.remove(CacheKey.TOKEN) }
export const setToken = (token: string) => {
Cookies.set(CacheKey.TOKEN, token)
}
export const removeToken = () => {
Cookies.remove(CacheKey.TOKEN)
}

View File

@ -1,9 +1,18 @@
/** 统一处理 localStorage */ /** 统一处理 localStorage */
import CacheKey from "@/constants/cacheKey" import CacheKey from "@/constants/cacheKey"
import type { ThemeName } from "@/config/theme"
export const getSidebarStatus = () => localStorage.getItem(CacheKey.SIDEBAR_STATUS) export const getSidebarStatus = () => {
export const setSidebarStatus = (sidebarStatus: string) => localStorage.setItem(CacheKey.SIDEBAR_STATUS, sidebarStatus) return localStorage.getItem(CacheKey.SIDEBAR_STATUS)
}
export const setSidebarStatus = (sidebarStatus: "opened" | "closed") => {
localStorage.setItem(CacheKey.SIDEBAR_STATUS, sidebarStatus)
}
export const getActiveThemeName = () => localStorage.getItem(CacheKey.ACTIVE_THEME_NAME) export const getActiveThemeName = () => {
export const setActiveThemeName = (themeName: string) => localStorage.setItem(CacheKey.ACTIVE_THEME_NAME, themeName) return localStorage.getItem(CacheKey.ACTIVE_THEME_NAME) as ThemeName
}
export const setActiveThemeName = (themeName: ThemeName) => {
localStorage.setItem(CacheKey.ACTIVE_THEME_NAME, themeName)
}