mirror of
https://github.com/un-pany/v3-admin-vite.git
synced 2025-04-21 11:29:20 +08:00
refactor: Setup Stores
This commit is contained in:
parent
94a50cf54e
commit
8d812dd2d3
@ -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
|
||||
</script>
|
||||
|
@ -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"
|
||||
|
@ -1,17 +1,16 @@
|
||||
<script lang="ts" setup>
|
||||
import { computed } from "vue"
|
||||
import { useAppStore } from "@/store/modules/app"
|
||||
import themeList from "@/config/theme"
|
||||
import type { ThemeName } from "@/config/theme"
|
||||
import { MagicStick } from "@element-plus/icons-vue"
|
||||
|
||||
const appStore = useAppStore()
|
||||
|
||||
const themeList = computed(() => {
|
||||
return appStore.themeList
|
||||
})
|
||||
const activeThemeName = computed(() => {
|
||||
return appStore.activeThemeName
|
||||
})
|
||||
const handleSetTheme = (name: string) => {
|
||||
const handleSetTheme = (name: ThemeName) => {
|
||||
appStore.setTheme(name)
|
||||
}
|
||||
</script>
|
||||
|
@ -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"
|
||||
|
@ -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
|
||||
|
@ -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>(DeviceType.Desktop)
|
||||
/** 正在应用的主题的名字 */
|
||||
const activeThemeName = ref<ThemeName>(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 }
|
||||
})
|
||||
|
@ -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<RouteRecordRaw[]>([])
|
||||
const dynamicRoutes = ref<RouteRecordRaw[]>([])
|
||||
|
||||
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 外使用 */
|
||||
|
@ -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<boolean>(layoutSettings.fixedHeader)
|
||||
const showSettings = ref<boolean>(layoutSettings.showSettings)
|
||||
const showTagsView = ref<boolean>(layoutSettings.showTagsView)
|
||||
const showSidebarLogo = ref<boolean>(layoutSettings.showSidebarLogo)
|
||||
const showThemeSwitch = ref<boolean>(layoutSettings.showThemeSwitch)
|
||||
const showScreenfull = ref<boolean>(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 }
|
||||
})
|
||||
|
@ -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<RouteLocationNormalized> {
|
||||
to?: _RouteLocationBase
|
||||
}
|
||||
|
||||
interface ITagsViewState {
|
||||
visitedViews: ITagView[]
|
||||
}
|
||||
export const useTagsViewStore = defineStore("tags-view", () => {
|
||||
const visitedViews = ref<ITagView[]>([])
|
||||
|
||||
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 }
|
||||
})
|
||||
|
@ -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<string>(getToken() || "")
|
||||
const roles = ref<string[]>([])
|
||||
|
||||
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 外使用 */
|
||||
|
12
src/utils/cache/cookies.ts
vendored
12
src/utils/cache/cookies.ts
vendored
@ -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)
|
||||
}
|
||||
|
17
src/utils/cache/localStorage.ts
vendored
17
src/utils/cache/localStorage.ts
vendored
@ -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)
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user