优化pinia持久化

This commit is contained in:
2026-01-15 09:51:59 +08:00
parent bb1ed16d8b
commit ab737f8a75
9 changed files with 239 additions and 155 deletions

View File

@@ -16,9 +16,11 @@
"dependencies": { "dependencies": {
"@element-plus/icons-vue": "^2.3.2", "@element-plus/icons-vue": "^2.3.2",
"axios": "^1.13.2", "axios": "^1.13.2",
"crypto-js": "^4.2.0",
"element-plus": "^2.13.1", "element-plus": "^2.13.1",
"nprogress": "^0.2.0", "nprogress": "^0.2.0",
"pinia": "^3.0.4", "pinia": "^3.0.4",
"pinia-plugin-persistedstate": "^4.7.1",
"vue": "^3.5.26", "vue": "^3.5.26",
"vue-i18n": "^11.2.8", "vue-i18n": "^11.2.8",
"vue-router": "^4.6.4" "vue-router": "^4.6.4"

View File

@@ -1,4 +1,14 @@
<script setup></script> <script setup>
import { onMounted } from 'vue'
import { useI18nStore } from './stores/modules/i18n'
import i18n from './i18n'
onMounted(() => {
// 从持久化的 store 中读取语言设置并同步到 i18n
const i18nStore = useI18nStore()
i18n.global.locale.value = i18nStore.currentLocale
})
</script>
<template> <template>
<router-view /> <router-view />

View File

@@ -1,7 +1,51 @@
export default { export default {
app_title: 'vueadmin', APP_NAME: 'vueadmin',
DASHBOARD_URL: '/home', DASHBOARD_URL: '/home',
baseURL: '',
// 白名单路由(不需要登录即可访问) // 白名单路由(不需要登录即可访问)
whiteList: ['/login', '/register', '/reset-password'] whiteList: ['/login', '/register', '/reset-password'],
//版本号
APP_VER: "1.6.6",
//内核版本号
CORE_VER: "1.6.6",
//接口地址
API_URL: "http://localhost:8000/admin/",
//请求超时
TIMEOUT: 50000,
//TokenName
TOKEN_NAME: "authorization",
//Token前缀注意最后有个空格如不需要需设置空字符串
TOKEN_PREFIX: "Bearer ",
//追加其他头
HEADERS: {},
//请求是否开启缓存
REQUEST_CACHE: false,
//语言
LANG: "zh-cn",
//是否加密localStorage, 为空不加密
//支持多种加密方式: 'AES', 'BASE64', 'DES'
LS_ENCRYPTION: "",
//localStorage加密秘钥位数建议填写8的倍数
LS_ENCRYPTION_key: "2XNN4K8LC0ELVWN4",
//localStorage加密模式AES支持: 'ECB', 'CBC', 'CTR', 'OFB', 'CFB'
LS_ENCRYPTION_mode: "ECB",
//localStorage加密填充方式AES支持: 'Pkcs7', 'ZeroPadding', 'Iso10126', 'Iso97971'
LS_ENCRYPTION_padding: "Pkcs7",
//localStorage默认过期时间单位小时0表示永不过期
LS_DEFAULT_EXPIRE: 720, // 30天
//DES加密秘钥必须是8字节
LS_DES_key: "12345678",
} }

View File

@@ -6,7 +6,6 @@ import App from './App.vue'
import router from './router' import router from './router'
import pinia from './stores' import pinia from './stores'
import i18n from './i18n' import i18n from './i18n'
import { useI18nStore } from './stores/modules/i18n'
const app = createApp(App) const app = createApp(App)
@@ -15,8 +14,4 @@ app.use(router)
app.use(pinia) app.use(pinia)
app.use(i18n) app.use(i18n)
// 初始化 i18n store从 localStorage 读取保存的语言设置
const i18nStore = useI18nStore()
i18nStore.initLocale()
app.mount('#app') app.mount('#app')

View File

@@ -1,5 +1,9 @@
import { createPinia } from 'pinia' import { createPinia } from 'pinia'
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
const pinia = createPinia() const pinia = createPinia()
// 注册持久化插件
pinia.use(piniaPluginPersistedstate)
export default pinia export default pinia

View File

@@ -1,34 +1,35 @@
import { defineStore } from 'pinia' import { defineStore } from 'pinia'
import i18n from '@/i18n' import i18n from '@/i18n'
export const useI18nStore = defineStore('i18n', { export const useI18nStore = defineStore(
state: () => ({ 'i18n',
currentLocale: 'zh-CN', {
availableLocales: [ state: () => ({
{ label: '简体中文', value: 'zh-CN' }, currentLocale: 'zh-CN',
{ label: 'English', value: 'en-US' } availableLocales: [
] { label: '简体中文', value: 'zh-CN' },
}), { label: 'English', value: 'en-US' }
]
}),
getters: { getters: {
localeLabel: (state) => { localeLabel: (state) => {
const locale = state.availableLocales.find((item) => item.value === state.currentLocale) const locale = state.availableLocales.find((item) => item.value === state.currentLocale)
return locale ? locale.label : '' return locale ? locale.label : ''
} }
},
actions: {
setLocale(locale) {
this.currentLocale = locale
i18n.global.locale.value = locale
localStorage.setItem('locale', locale)
}, },
initLocale() { actions: {
const savedLocale = localStorage.getItem('locale') setLocale(locale) {
if (savedLocale && this.availableLocales.some((item) => item.value === savedLocale)) { this.currentLocale = locale
this.setLocale(savedLocale) i18n.global.locale.value = locale
} }
},
persist: {
key: 'i18n-store',
storage: localStorage,
pick: ['currentLocale']
} }
} }
}) )

View File

@@ -1,57 +1,67 @@
import { defineStore } from 'pinia' import { defineStore } from 'pinia'
import { ref } from 'vue' import { ref } from 'vue'
export const useLayoutStore = defineStore('layout', () => { export const useLayoutStore = defineStore(
// 布局模式:'sidebar', 'top-nav', 'sidebar-top', 'classic' 'layout',
const layoutMode = ref('sidebar') () => {
// 布局模式:'sidebar', 'top-nav', 'sidebar-top', 'classic'
const layoutMode = ref('sidebar')
// 侧边栏折叠状态 // 侧边栏折叠状态
const sidebarCollapsed = ref(false) const sidebarCollapsed = ref(false)
// 视图标签页(用于记录页面滚动位置) // 视图标签页(用于记录页面滚动位置)
const viewTags = ref([]) const viewTags = ref([])
// 切换侧边栏折叠 // 切换侧边栏折叠
const toggleSidebar = () => { const toggleSidebar = () => {
sidebarCollapsed.value = !sidebarCollapsed.value sidebarCollapsed.value = !sidebarCollapsed.value
} }
// 设置布局模式 // 设置布局模式
const setLayoutMode = (mode) => { const setLayoutMode = (mode) => {
layoutMode.value = mode layoutMode.value = mode
} }
// 更新视图标签 // 更新视图标签
const updateViewTags = (tag) => { const updateViewTags = (tag) => {
const index = viewTags.value.findIndex((item) => item.fullPath === tag.fullPath) const index = viewTags.value.findIndex((item) => item.fullPath === tag.fullPath)
if (index !== -1) { if (index !== -1) {
viewTags.value[index] = tag viewTags.value[index] = tag
} else { } else {
viewTags.value.push(tag) viewTags.value.push(tag)
}
}
// 移除视图标签
const removeViewTags = (fullPath) => {
const index = viewTags.value.findIndex((item) => item.fullPath === fullPath)
if (index !== -1) {
viewTags.value.splice(index, 1)
}
}
// 清空视图标签
const clearViewTags = () => {
viewTags.value = []
}
return {
layoutMode,
sidebarCollapsed,
viewTags,
toggleSidebar,
setLayoutMode,
updateViewTags,
removeViewTags,
clearViewTags,
}
},
{
persist: {
key: 'layout-store',
storage: localStorage,
pick: ['layoutMode', 'sidebarCollapsed']
} }
} }
)
// 移除视图标签
const removeViewTags = (fullPath) => {
const index = viewTags.value.findIndex((item) => item.fullPath === fullPath)
if (index !== -1) {
viewTags.value.splice(index, 1)
}
}
// 清空视图标签
const clearViewTags = () => {
viewTags.value = []
}
return {
layoutMode,
sidebarCollapsed,
viewTags,
toggleSidebar,
setLayoutMode,
updateViewTags,
removeViewTags,
clearViewTags,
}
})

View File

@@ -2,79 +2,80 @@ import { ref } from 'vue'
import { defineStore } from 'pinia' import { defineStore } from 'pinia'
import { resetRouter } from '../../router' import { resetRouter } from '../../router'
export const useUserStore = defineStore('user', () => { export const useUserStore = defineStore(
const token = ref(localStorage.getItem('token') || '') 'user',
const refreshToken = ref(localStorage.getItem('refreshToken') || '') () => {
const userInfo = ref(JSON.parse(localStorage.getItem('userInfo') || 'null')) const token = ref('')
const menu = ref(JSON.parse(localStorage.getItem('MENU') || '[]')) const refreshToken = ref('')
const userInfo = ref(null)
const menu = ref([])
// 设置 token // 设置 token
function setToken(newToken) { function setToken(newToken) {
token.value = newToken token.value = newToken
localStorage.setItem('token', newToken) }
// 设置 refresh token
function setRefreshToken(newRefreshToken) {
refreshToken.value = newRefreshToken
}
// 设置用户信息
function setUserInfo(info) {
userInfo.value = info
}
// 设置菜单
function setMenu(newMenu) {
menu.value = newMenu
}
// 获取菜单
function getMenu() {
return menu.value
}
// 清除菜单
function clearMenu() {
menu.value = []
}
// 登出
function logout() {
token.value = ''
refreshToken.value = ''
userInfo.value = null
menu.value = []
// 重置路由
resetRouter()
}
// 检查是否已登录
function isLoggedIn() {
return !!token.value
}
return {
token,
refreshToken,
userInfo,
menu,
setToken,
setRefreshToken,
setUserInfo,
setMenu,
getMenu,
clearMenu,
logout,
isLoggedIn,
}
},
{
persist: {
key: 'user-store',
storage: localStorage,
pick: ['token', 'refreshToken', 'userInfo', 'menu']
}
} }
)
// 设置 refresh token
function setRefreshToken(newRefreshToken) {
refreshToken.value = newRefreshToken
localStorage.setItem('refreshToken', newRefreshToken)
}
// 设置用户信息
function setUserInfo(info) {
userInfo.value = info
localStorage.setItem('userInfo', JSON.stringify(info))
}
// 设置菜单
function setMenu(newMenu) {
menu.value = newMenu
localStorage.setItem('MENU', JSON.stringify(newMenu))
}
// 获取菜单
function getMenu() {
return menu.value
}
// 清除菜单
function clearMenu() {
menu.value = []
localStorage.removeItem('MENU')
}
// 登出
function logout() {
token.value = ''
refreshToken.value = ''
userInfo.value = null
menu.value = []
localStorage.removeItem('token')
localStorage.removeItem('refreshToken')
localStorage.removeItem('userInfo')
localStorage.removeItem('MENU')
// 重置路由
resetRouter()
}
// 检查是否已登录
function isLoggedIn() {
return !!token.value
}
return {
token,
refreshToken,
userInfo,
menu,
setToken,
setRefreshToken,
setUserInfo,
setMenu,
getMenu,
clearMenu,
logout,
isLoggedIn,
}
})

View File

@@ -1116,6 +1116,11 @@ cross-spawn@^7.0.6:
shebang-command "^2.0.0" shebang-command "^2.0.0"
which "^2.0.1" which "^2.0.1"
crypto-js@^4.2.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/crypto-js/-/crypto-js-4.2.0.tgz#4d931639ecdfd12ff80e8186dba6af2c2e856631"
integrity sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==
cssesc@^3.0.0: cssesc@^3.0.0:
version "3.0.0" version "3.0.0"
resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee" resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee"
@@ -1161,6 +1166,11 @@ define-lazy-prop@^3.0.0:
resolved "https://registry.yarnpkg.com/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz#dbb19adfb746d7fc6d734a06b72f4a00d021255f" resolved "https://registry.yarnpkg.com/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz#dbb19adfb746d7fc6d734a06b72f4a00d021255f"
integrity sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg== integrity sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==
defu@^6.1.4:
version "6.1.4"
resolved "https://registry.yarnpkg.com/defu/-/defu-6.1.4.tgz#4e0c9cf9ff68fe5f3d7f2765cc1a012dfdcb0479"
integrity sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==
delayed-stream@~1.0.0: delayed-stream@~1.0.0:
version "1.0.0" version "1.0.0"
resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
@@ -1880,6 +1890,13 @@ picomatch@^4.0.3:
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-4.0.3.tgz#796c76136d1eead715db1e7bad785dedd695a042" resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-4.0.3.tgz#796c76136d1eead715db1e7bad785dedd695a042"
integrity sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q== integrity sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==
pinia-plugin-persistedstate@^4.7.1:
version "4.7.1"
resolved "https://registry.yarnpkg.com/pinia-plugin-persistedstate/-/pinia-plugin-persistedstate-4.7.1.tgz#d13880e0b7efdafd1b73fca3d73cd64ae34bde84"
integrity sha512-WHOqh2esDlR3eAaknPbqXrkkj0D24h8shrDPqysgCFR6ghqP/fpFfJmMPJp0gETHsvrh9YNNg6dQfo2OEtDnIQ==
dependencies:
defu "^6.1.4"
pinia@^3.0.4: pinia@^3.0.4:
version "3.0.4" version "3.0.4"
resolved "https://registry.yarnpkg.com/pinia/-/pinia-3.0.4.tgz#75dde12784a61e34c1fa6abcd13c1a1061c360c0" resolved "https://registry.yarnpkg.com/pinia/-/pinia-3.0.4.tgz#75dde12784a61e34c1fa6abcd13c1a1061c360c0"