diff --git a/src/App.vue b/src/App.vue index 3f0bedd..9a7afdb 100644 --- a/src/App.vue +++ b/src/App.vue @@ -2,8 +2,10 @@ import { useAppStore } from "@/store/modules/app" import zhCn from "element-plus/lib/locale/lang/zh-cn" +const appStore = useAppStore() + /** 初始化主题 */ -useAppStore().initTheme() +appStore.initTheme() /** 将 Element-Plus 的语言设置为中文 */ const locale = zhCn diff --git a/src/api/login.ts b/src/api/login.ts index 8bf8002..8d8def2 100644 --- a/src/api/login.ts +++ b/src/api/login.ts @@ -6,7 +6,7 @@ interface ILoginData { } /** 登录并返回 Token */ -export function login(data: ILoginData) { +export function loginApi(data: ILoginData) { return request({ url: "users/login", method: "post", @@ -14,7 +14,7 @@ export function login(data: ILoginData) { }) } /** 获取用户详情 */ -export function getUserInfo() { +export function getUserInfoApi() { return request({ url: "users/info", method: "post" diff --git a/src/components/ThemeSwitch/index.vue b/src/components/ThemeSwitch/index.vue index ca9f210..fd3e1cc 100644 --- a/src/components/ThemeSwitch/index.vue +++ b/src/components/ThemeSwitch/index.vue @@ -1,17 +1,16 @@ diff --git a/src/config/theme.ts b/src/config/theme.ts index f0949c2..8705017 100644 --- a/src/config/theme.ts +++ b/src/config/theme.ts @@ -1,5 +1,12 @@ -/** 注册的主题 */ -const themeList = [ +/** 注册的主题, 其中 normal 是必须的, dark 是内置的, 如需更多主题,可自行注册 */ +export type ThemeName = "normal" | "dark" + +interface IThemeList { + title: string + name: ThemeName +} + +const themeList: IThemeList[] = [ { title: "默认", name: "normal" diff --git a/src/layout/components/Sidebar/index.vue b/src/layout/components/Sidebar/index.vue index 7417e46..bb1df12 100644 --- a/src/layout/components/Sidebar/index.vue +++ b/src/layout/components/Sidebar/index.vue @@ -13,15 +13,18 @@ const v3SidebarMenuTextColor = getCssVariableValue("--v3-sidebar-menu-text-color const v3SidebarMenuActiveTextColor = getCssVariableValue("--v3-sidebar-menu-active-text-color") const route = useRoute() +const appStore = useAppStore() +const permissionStore = usePermissionStore() +const settingsStore = useSettingsStore() const sidebar = computed(() => { - return useAppStore().sidebar + return appStore.sidebar }) const routes = computed(() => { - return usePermissionStore().routes + return permissionStore.routes }) const showLogo = computed(() => { - return useSettingsStore().showSidebarLogo + return settingsStore.showSidebarLogo }) const activeMenu = computed(() => { const { meta, path } = route diff --git a/src/store/modules/app.ts b/src/store/modules/app.ts index c9835c6..417e86e 100644 --- a/src/store/modules/app.ts +++ b/src/store/modules/app.ts @@ -1,68 +1,59 @@ +import { reactive, ref } from "vue" import { defineStore } from "pinia" import { getSidebarStatus, getActiveThemeName, setSidebarStatus, setActiveThemeName } from "@/utils/cache/localStorage" -import themeList from "@/config/theme" +import type { ThemeName } from "@/config/theme" export enum DeviceType { Mobile, Desktop } -interface IAppState { - device: DeviceType - sidebar: { - opened: boolean - withoutAnimation: boolean - } - /** 主题列表 */ - themeList: { title: string; name: string }[] - /** 正在应用的主题的名字 */ - activeThemeName: string +interface ISidebar { + opened: boolean + withoutAnimation: boolean } -export const useAppStore = defineStore({ - id: "app", - state: (): IAppState => { - return { - device: DeviceType.Desktop, - sidebar: { - opened: getSidebarStatus() !== "closed", - withoutAnimation: false - }, - themeList: themeList, - activeThemeName: getActiveThemeName() || "normal" - } - }, - actions: { - toggleSidebar(withoutAnimation: boolean) { - this.sidebar.opened = !this.sidebar.opened - this.sidebar.withoutAnimation = withoutAnimation - if (this.sidebar.opened) { - setSidebarStatus("opened") - } else { - setSidebarStatus("closed") - } - }, - closeSidebar(withoutAnimation: boolean) { - this.sidebar.opened = false - this.sidebar.withoutAnimation = withoutAnimation +const setClassName = (value: ThemeName) => { + document.documentElement.className = value +} + +export const useAppStore = defineStore("app", () => { + const sidebar: ISidebar = reactive({ + opened: getSidebarStatus() !== "closed", + withoutAnimation: false + }) + const device = ref(DeviceType.Desktop) + /** 正在应用的主题的名字 */ + const activeThemeName = ref(getActiveThemeName() || "normal") + + const toggleSidebar = (withoutAnimation: boolean) => { + sidebar.opened = !sidebar.opened + sidebar.withoutAnimation = withoutAnimation + if (sidebar.opened) { + setSidebarStatus("opened") + } else { 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 } }) diff --git a/src/store/modules/permission.ts b/src/store/modules/permission.ts index a095fc8..d0687e2 100644 --- a/src/store/modules/permission.ts +++ b/src/store/modules/permission.ts @@ -1,13 +1,9 @@ +import { ref } from "vue" import store from "@/store" import { defineStore } from "pinia" import { RouteRecordRaw } from "vue-router" import { constantRoutes, asyncRoutes } from "@/router" -interface IPermissionState { - routes: RouteRecordRaw[] - dynamicRoutes: RouteRecordRaw[] -} - const hasPermission = (roles: string[], route: RouteRecordRaw) => { if (route.meta && route.meta.roles) { return roles.some((role) => { @@ -36,26 +32,22 @@ const filterAsyncRoutes = (routes: RouteRecordRaw[], roles: string[]) => { return res } -export const usePermissionStore = defineStore({ - id: "permission", - state: (): IPermissionState => { - return { - routes: [], - dynamicRoutes: [] - } - }, - actions: { - setRoutes(roles: string[]) { - let accessedRoutes - if (roles.includes("admin")) { - accessedRoutes = asyncRoutes - } else { - accessedRoutes = filterAsyncRoutes(asyncRoutes, roles) - } - this.routes = constantRoutes.concat(accessedRoutes) - this.dynamicRoutes = accessedRoutes +export const usePermissionStore = defineStore("permission", () => { + const routes = ref([]) + const dynamicRoutes = ref([]) + + const setRoutes = (roles: string[]) => { + let accessedRoutes + if (roles.includes("admin")) { + accessedRoutes = asyncRoutes + } else { + accessedRoutes = filterAsyncRoutes(asyncRoutes, roles) } + routes.value = constantRoutes.concat(accessedRoutes) + dynamicRoutes.value = accessedRoutes } + + return { routes, dynamicRoutes, setRoutes } }) /** 在 setup 外使用 */ diff --git a/src/store/modules/settings.ts b/src/store/modules/settings.ts index 85abce4..d3769bc 100644 --- a/src/store/modules/settings.ts +++ b/src/store/modules/settings.ts @@ -1,25 +1,14 @@ +import { ref } from "vue" import { defineStore } from "pinia" import layoutSettings from "@/config/layout" -interface ISettingsState { - fixedHeader: boolean - showSettings: boolean - showTagsView: boolean - showSidebarLogo: boolean - showThemeSwitch: boolean - showScreenfull: boolean -} +export const useSettingsStore = defineStore("settings", () => { + const fixedHeader = ref(layoutSettings.fixedHeader) + const showSettings = ref(layoutSettings.showSettings) + const showTagsView = ref(layoutSettings.showTagsView) + const showSidebarLogo = ref(layoutSettings.showSidebarLogo) + const showThemeSwitch = ref(layoutSettings.showThemeSwitch) + const showScreenfull = ref(layoutSettings.showScreenfull) -export const useSettingsStore = defineStore({ - id: "settings", - state: (): ISettingsState => { - return { - fixedHeader: layoutSettings.fixedHeader, - showSettings: layoutSettings.showSettings, - showTagsView: layoutSettings.showTagsView, - showSidebarLogo: layoutSettings.showSidebarLogo, - showThemeSwitch: layoutSettings.showThemeSwitch, - showScreenfull: layoutSettings.showScreenfull - } - } + return { fixedHeader, showSettings, showTagsView, showSidebarLogo, showThemeSwitch, showScreenfull } }) diff --git a/src/store/modules/tags-view.ts b/src/store/modules/tags-view.ts index eb1d419..b4d57c8 100644 --- a/src/store/modules/tags-view.ts +++ b/src/store/modules/tags-view.ts @@ -1,3 +1,4 @@ +import { ref } from "vue" import { defineStore } from "pinia" import { _RouteLocationBase, RouteLocationNormalized } from "vue-router" @@ -6,51 +7,43 @@ export interface ITagView extends Partial { to?: _RouteLocationBase } -interface ITagsViewState { - visitedViews: ITagView[] -} +export const useTagsViewStore = defineStore("tags-view", () => { + const visitedViews = ref([]) -export const useTagsViewStore = defineStore({ - id: "tags-view", - state: (): ITagsViewState => { - return { - visitedViews: [] - } - }, - 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 + const addVisitedView = (view: ITagView) => { + if (visitedViews.value.some((v) => v.path === view.path)) return + visitedViews.value.push( + Object.assign({}, view, { + title: view.meta?.title || "no-name" }) - }, - delAllVisitedViews() { - // keep affix tags - const affixTags = this.visitedViews.filter((tag) => tag.meta?.affix) - this.visitedViews = affixTags - }, - updateVisitedView(view: ITagView) { - for (let v of this.visitedViews) { - if (v.path === view.path) { - v = Object.assign(v, view) - break - } + ) + } + const delVisitedView = (view: ITagView) => { + for (const [i, v] of visitedViews.value.entries()) { + if (v.path === view.path) { + visitedViews.value.splice(i, 1) + 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 } }) diff --git a/src/store/modules/user.ts b/src/store/modules/user.ts index 53f4acc..2d73792 100644 --- a/src/store/modules/user.ts +++ b/src/store/modules/user.ts @@ -1,86 +1,78 @@ +import { ref } from "vue" import store from "@/store" import { defineStore } from "pinia" import { usePermissionStore } from "./permission" import { getToken, removeToken, setToken } from "@/utils/cache/cookies" import router, { resetRouter } from "@/router" -import { login, getUserInfo } from "@/api/login" +import { loginApi, getUserInfoApi } from "@/api/login" import { RouteRecordRaw } from "vue-router" -interface IUserState { - token: string - roles: string[] -} +export const useUserStore = defineStore("user", () => { + const token = ref(getToken() || "") + const roles = ref([]) -export const useUserStore = defineStore({ - id: "user", - state: (): IUserState => { - 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 setRoles = (value: string[]) => { + roles.value = value } + /** 登录 */ + 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 外使用 */ diff --git a/src/utils/cache/cookies.ts b/src/utils/cache/cookies.ts index b13f84b..537d6c6 100644 --- a/src/utils/cache/cookies.ts +++ b/src/utils/cache/cookies.ts @@ -3,6 +3,12 @@ import CacheKey from "@/constants/cacheKey" import Cookies from "js-cookie" -export const getToken = () => Cookies.get(CacheKey.TOKEN) -export const setToken = (token: string) => Cookies.set(CacheKey.TOKEN, token) -export const removeToken = () => Cookies.remove(CacheKey.TOKEN) +export const getToken = () => { + return Cookies.get(CacheKey.TOKEN) +} +export const setToken = (token: string) => { + Cookies.set(CacheKey.TOKEN, token) +} +export const removeToken = () => { + Cookies.remove(CacheKey.TOKEN) +} diff --git a/src/utils/cache/localStorage.ts b/src/utils/cache/localStorage.ts index 5bcedd4..36af5aa 100644 --- a/src/utils/cache/localStorage.ts +++ b/src/utils/cache/localStorage.ts @@ -1,9 +1,18 @@ /** 统一处理 localStorage */ import CacheKey from "@/constants/cacheKey" +import type { ThemeName } from "@/config/theme" -export const getSidebarStatus = () => localStorage.getItem(CacheKey.SIDEBAR_STATUS) -export const setSidebarStatus = (sidebarStatus: string) => localStorage.setItem(CacheKey.SIDEBAR_STATUS, sidebarStatus) +export const getSidebarStatus = () => { + 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 setActiveThemeName = (themeName: string) => localStorage.setItem(CacheKey.ACTIVE_THEME_NAME, themeName) +export const getActiveThemeName = () => { + return localStorage.getItem(CacheKey.ACTIVE_THEME_NAME) as ThemeName +} +export const setActiveThemeName = (themeName: ThemeName) => { + localStorage.setItem(CacheKey.ACTIVE_THEME_NAME, themeName) +}