Template
1
0
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:
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 zhCn from "element-plus/lib/locale/lang/zh-cn"
const appStore = useAppStore()
/** 初始化主题 */
useAppStore().initTheme()
appStore.initTheme()
/** 将 Element-Plus 的语言设置为中文 */
const locale = zhCn
</script>

View File

@ -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"

View File

@ -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>

View File

@ -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"

View File

@ -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

View File

@ -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 }
})

View File

@ -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 外使用 */

View File

@ -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 }
})

View File

@ -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 }
})

View File

@ -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 外使用 */

View File

@ -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)
}

View File

@ -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)
}