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
sidebar: {
opened: boolean opened: boolean
withoutAnimation: boolean withoutAnimation: boolean
} }
/** 主题列表 */
themeList: { title: string; name: string }[] const setClassName = (value: ThemeName) => {
/** 正在应用的主题的名字 */ document.documentElement.className = value
activeThemeName: string
} }
export const useAppStore = defineStore({ export const useAppStore = defineStore("app", () => {
id: "app", const sidebar: ISidebar = reactive({
state: (): IAppState => {
return {
device: DeviceType.Desktop,
sidebar: {
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") setSidebarStatus("closed")
} }
}, }
closeSidebar(withoutAnimation: boolean) { const closeSidebar = (withoutAnimation: boolean) => {
this.sidebar.opened = false sidebar.opened = false
this.sidebar.withoutAnimation = withoutAnimation sidebar.withoutAnimation = withoutAnimation
setSidebarStatus("closed") setSidebarStatus("closed")
}, }
toggleDevice(device: DeviceType) { const toggleDevice = (value: DeviceType) => {
this.device = device device.value = value
}, }
setTheme(activeThemeName: string) { const setTheme = (value: ThemeName) => {
// 检查这个主题在主题列表里是否存在 activeThemeName.value = value
this.activeThemeName = this.themeList.find((theme) => theme.name === activeThemeName)
? activeThemeName
: this.themeList[0].name
// 应用到 Dom // 应用到 Dom
document.documentElement.className = this.activeThemeName setClassName(activeThemeName.value)
// 持久化 // 持久化
setActiveThemeName(this.activeThemeName) setActiveThemeName(activeThemeName.value)
}, }
initTheme() { const initTheme = () => {
// 初始化 // 初始化
document.documentElement.className = this.activeThemeName 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: []
}
},
actions: {
setRoutes(roles: string[]) {
let accessedRoutes let accessedRoutes
if (roles.includes("admin")) { if (roles.includes("admin")) {
accessedRoutes = asyncRoutes accessedRoutes = asyncRoutes
} else { } else {
accessedRoutes = filterAsyncRoutes(asyncRoutes, roles) accessedRoutes = filterAsyncRoutes(asyncRoutes, roles)
} }
this.routes = constantRoutes.concat(accessedRoutes) routes.value = constantRoutes.concat(accessedRoutes)
this.dynamicRoutes = 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 {
visitedViews: []
}
},
actions: {
addVisitedView(view: ITagView) {
if (this.visitedViews.some((v) => v.path === view.path)) return
this.visitedViews.push(
Object.assign({}, view, { Object.assign({}, view, {
title: view.meta?.title || "no-name" title: view.meta?.title || "no-name"
}) })
) )
}, }
delVisitedView(view: ITagView) { const delVisitedView = (view: ITagView) => {
for (const [i, v] of this.visitedViews.entries()) { for (const [i, v] of visitedViews.value.entries()) {
if (v.path === view.path) { if (v.path === view.path) {
this.visitedViews.splice(i, 1) visitedViews.value.splice(i, 1)
break break
} }
} }
}, }
delOthersVisitedViews(view: ITagView) { const delOthersVisitedViews = (view: ITagView) => {
this.visitedViews = this.visitedViews.filter((v) => { visitedViews.value = visitedViews.value.filter((v) => {
return v.meta?.affix || v.path === view.path return v.meta?.affix || v.path === view.path
}) })
}, }
delAllVisitedViews() { const delAllVisitedViews = () => {
// keep affix tags // keep affix tags
const affixTags = this.visitedViews.filter((tag) => tag.meta?.affix) const affixTags = visitedViews.value.filter((tag) => tag.meta?.affix)
this.visitedViews = affixTags visitedViews.value = affixTags
}, }
updateVisitedView(view: ITagView) { const updateVisitedView = (view: ITagView) => {
for (let v of this.visitedViews) { for (let v of visitedViews.value) {
if (v.path === view.path) { if (v.path === view.path) {
v = Object.assign(v, view) v = Object.assign(v, view)
break 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",
state: (): IUserState => {
return {
token: getToken() || "",
roles: []
}
},
actions: {
/** 设置角色数组 */ /** 设置角色数组 */
setRoles(roles: string[]) { const setRoles = (value: string[]) => {
this.roles = roles roles.value = value
}, }
/** 登录 */ /** 登录 */
login(userInfo: { username: string; password: string }) { const login = (userInfo: { username: string; password: string }) => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
login({ loginApi({
username: userInfo.username.trim(), username: userInfo.username,
password: userInfo.password password: userInfo.password
}) })
.then((res: any) => { .then((res: any) => {
setToken(res.data.accessToken) setToken(res.data.accessToken)
this.token = res.data.accessToken token.value = res.data.accessToken
resolve(true) resolve(true)
}) })
.catch((error) => { .catch((error) => {
reject(error) reject(error)
}) })
}) })
}, }
/** 获取用户详情 */ /** 获取用户详情 */
getInfo() { const getInfo = () => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
getUserInfo() getUserInfoApi()
.then((res: any) => { .then((res: any) => {
this.roles = res.data.user.roles roles.value = res.data.user.roles
resolve(res) resolve(res)
}) })
.catch((error) => { .catch((error) => {
reject(error) reject(error)
}) })
}) })
}, }
/** 切换角色 */ /** 切换角色 */
async changeRoles(role: string) { const changeRoles = async (role: string) => {
const token = role + "-token" const newToken = role + "-token"
this.token = token token.value = newToken
setToken(token) setToken(newToken)
await this.getInfo() await getInfo()
const permissionStore = usePermissionStore() const permissionStore = usePermissionStore()
permissionStore.setRoutes(this.roles) permissionStore.setRoutes(roles.value)
resetRouter() resetRouter()
permissionStore.dynamicRoutes.forEach((item: RouteRecordRaw) => { permissionStore.dynamicRoutes.forEach((item: RouteRecordRaw) => {
router.addRoute(item) router.addRoute(item)
}) })
}, }
/** 登出 */ /** 登出 */
logout() { const logout = () => {
removeToken() removeToken()
this.token = "" token.value = ""
this.roles = [] roles.value = []
resetRouter() resetRouter()
}, }
/** 重置 Token */ /** 重置 Token */
resetToken() { const resetToken = () => {
removeToken() removeToken()
this.token = "" token.value = ""
this.roles = [] 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)
}