diff --git a/apps/web-antd/.env b/apps/web-antd/.env
index c14a467f..19735f36 100644
--- a/apps/web-antd/.env
+++ b/apps/web-antd/.env
@@ -3,3 +3,6 @@ VITE_APP_TITLE=Vben Admin Antd
# 应用命名空间,用于缓存、store等功能的前缀,确保隔离
VITE_APP_NAMESPACE=vben-web-antd
+
+# 对store进行加密的密钥,在将store持久化到localStorage时会使用该密钥进行加密
+VITE_APP_STORE_SECURE_KEY=please-replace-me-with-your-own-key
diff --git a/apps/web-ele/.env b/apps/web-ele/.env
index 87cb3df1..bb57c865 100644
--- a/apps/web-ele/.env
+++ b/apps/web-ele/.env
@@ -3,3 +3,6 @@ VITE_APP_TITLE=Vben Admin Ele
# 应用命名空间,用于缓存、store等功能的前缀,确保隔离
VITE_APP_NAMESPACE=vben-web-ele
+
+# 对store进行加密的密钥,在将store持久化到localStorage时会使用该密钥进行加密
+VITE_APP_STORE_SECURE_KEY=please-replace-me-with-your-own-key
diff --git a/apps/web-naive/.env b/apps/web-naive/.env
index 350660c0..213b52ce 100644
--- a/apps/web-naive/.env
+++ b/apps/web-naive/.env
@@ -3,3 +3,6 @@ VITE_APP_TITLE=Vben Admin Naive
# 应用命名空间,用于缓存、store等功能的前缀,确保隔离
VITE_APP_NAMESPACE=vben-web-naive
+
+# 对store进行加密的密钥,在将store持久化到localStorage时会使用该密钥进行加密
+VITE_APP_STORE_SECURE_KEY=please-replace-me-with-your-own-key
diff --git a/packages/effects/layouts/src/basic/layout.vue b/packages/effects/layouts/src/basic/layout.vue
index f44b7404..9ae978f4 100644
--- a/packages/effects/layouts/src/basic/layout.vue
+++ b/packages/effects/layouts/src/basic/layout.vue
@@ -12,7 +12,7 @@ import {
updatePreferences,
usePreferences,
} from '@vben/preferences';
-import { useLockStore } from '@vben/stores';
+import { useAccessStore } from '@vben/stores';
import { cloneDeep, mapTree } from '@vben/utils';
import { VbenAdminLayout } from '@vben-core/layout-ui';
@@ -49,7 +49,7 @@ const {
sidebarCollapsed,
theme,
} = usePreferences();
-const lockStore = useLockStore();
+const accessStore = useAccessStore();
const { refresh } = useRefresh();
const sidebarTheme = computed(() => {
@@ -356,7 +356,7 @@ const headerSlots = computed(() => {
/>
-
+
diff --git a/packages/effects/layouts/src/widgets/lock-screen/lock-screen.vue b/packages/effects/layouts/src/widgets/lock-screen/lock-screen.vue
index 38c1d2b6..736d09d5 100644
--- a/packages/effects/layouts/src/widgets/lock-screen/lock-screen.vue
+++ b/packages/effects/layouts/src/widgets/lock-screen/lock-screen.vue
@@ -3,7 +3,7 @@ import { computed, reactive, ref } from 'vue';
import { LockKeyhole } from '@vben/icons';
import { $t, useI18n } from '@vben/locales';
-import { storeToRefs, useLockStore } from '@vben/stores';
+import { storeToRefs, useAccessStore } from '@vben/stores';
import { useScrollLock } from '@vben-core/composables';
import { useVbenForm, z } from '@vben-core/form-ui';
@@ -26,7 +26,7 @@ withDefaults(defineProps(), {
defineEmits<{ toLogin: [] }>();
const { locale } = useI18n();
-const lockStore = useLockStore();
+const accessStore = useAccessStore();
const now = useNow();
const meridiem = useDateFormat(now, 'A');
@@ -35,7 +35,7 @@ const minute = useDateFormat(now, 'mm');
const date = useDateFormat(now, 'YYYY-MM-DD dddd', { locales: locale.value });
const showUnlockForm = ref(false);
-const { lockScreenPassword } = storeToRefs(lockStore);
+const { lockScreenPassword } = storeToRefs(accessStore);
const [Form, { form, validate }] = useVbenForm(
reactive({
@@ -66,7 +66,7 @@ async function handleSubmit() {
const { valid } = await validate();
if (valid) {
if (validPass.value) {
- lockStore.unlockScreen();
+ accessStore.unlockScreen();
} else {
form.setFieldError('password', $t('authentication.passwordErrorTip'));
}
diff --git a/packages/effects/layouts/src/widgets/user-dropdown/user-dropdown.vue b/packages/effects/layouts/src/widgets/user-dropdown/user-dropdown.vue
index af0e85a4..8b5f499f 100644
--- a/packages/effects/layouts/src/widgets/user-dropdown/user-dropdown.vue
+++ b/packages/effects/layouts/src/widgets/user-dropdown/user-dropdown.vue
@@ -9,7 +9,7 @@ import { useHoverToggle } from '@vben/hooks';
import { LockKeyhole, LogOut } from '@vben/icons';
import { $t } from '@vben/locales';
import { preferences, usePreferences } from '@vben/preferences';
-import { useLockStore } from '@vben/stores';
+import { useAccessStore } from '@vben/stores';
import { isWindowsOs } from '@vben/utils';
import { useVbenModal } from '@vben-core/popup-ui';
@@ -82,7 +82,7 @@ const emit = defineEmits<{ logout: [] }>();
const { globalLockScreenShortcutKey, globalLogoutShortcutKey } =
usePreferences();
-const lockStore = useLockStore();
+const accessStore = useAccessStore();
const [LockModal, lockModalApi] = useVbenModal({
connectedComponent: LockScreenModal,
});
@@ -133,7 +133,7 @@ function handleOpenLock() {
function handleSubmitLock(lockScreenPassword: string) {
lockModalApi.close();
- lockStore.lockScreen(lockScreenPassword);
+ accessStore.lockScreen(lockScreenPassword);
}
function handleLogout() {
diff --git a/packages/stores/package.json b/packages/stores/package.json
index 2ff766cb..867f73df 100644
--- a/packages/stores/package.json
+++ b/packages/stores/package.json
@@ -25,6 +25,7 @@
"@vben-core/typings": "workspace:*",
"pinia": "catalog:",
"pinia-plugin-persistedstate": "catalog:",
+ "secure-ls": "catalog:",
"vue": "catalog:",
"vue-router": "catalog:"
}
diff --git a/packages/stores/src/modules/access.ts b/packages/stores/src/modules/access.ts
index 53ff7f2c..b88242e0 100644
--- a/packages/stores/src/modules/access.ts
+++ b/packages/stores/src/modules/access.ts
@@ -27,6 +27,14 @@ interface AccessState {
* 是否已经检查过权限
*/
isAccessChecked: boolean;
+ /**
+ * 是否锁屏状态
+ */
+ isLockScreen: boolean;
+ /**
+ * 锁屏密码
+ */
+ lockScreenPassword?: string;
/**
* 登录是否过期
*/
@@ -61,6 +69,10 @@ export const useAccessStore = defineStore('core-access', {
}
return findMenu(this.accessMenus, path);
},
+ lockScreen(password: string) {
+ this.isLockScreen = true;
+ this.lockScreenPassword = password;
+ },
setAccessCodes(codes: string[]) {
this.accessCodes = codes;
},
@@ -82,10 +94,20 @@ export const useAccessStore = defineStore('core-access', {
setRefreshToken(token: AccessToken) {
this.refreshToken = token;
},
+ unlockScreen() {
+ this.isLockScreen = false;
+ this.lockScreenPassword = undefined;
+ },
},
persist: {
// 持久化
- pick: ['accessToken', 'refreshToken', 'accessCodes'],
+ pick: [
+ 'accessToken',
+ 'refreshToken',
+ 'accessCodes',
+ 'isLockScreen',
+ 'lockScreenPassword',
+ ],
},
state: (): AccessState => ({
accessCodes: [],
@@ -93,6 +115,8 @@ export const useAccessStore = defineStore('core-access', {
accessRoutes: [],
accessToken: null,
isAccessChecked: false,
+ isLockScreen: false,
+ lockScreenPassword: undefined,
loginExpired: false,
refreshToken: null,
}),
diff --git a/packages/stores/src/modules/index.ts b/packages/stores/src/modules/index.ts
index 7941cbea..ec764ae8 100644
--- a/packages/stores/src/modules/index.ts
+++ b/packages/stores/src/modules/index.ts
@@ -1,4 +1,3 @@
export * from './access';
-export * from './lock';
export * from './tabbar';
export * from './user';
diff --git a/packages/stores/src/modules/lock.test.ts b/packages/stores/src/modules/lock.test.ts
deleted file mode 100644
index d8dbe554..00000000
--- a/packages/stores/src/modules/lock.test.ts
+++ /dev/null
@@ -1,31 +0,0 @@
-import { createPinia, setActivePinia } from 'pinia';
-import { beforeEach, describe, expect, it } from 'vitest';
-
-import { useLockStore } from './lock';
-
-describe('useLockStore', () => {
- beforeEach(() => {
- setActivePinia(createPinia());
- });
-
- it('should initialize with correct default state', () => {
- const store = useLockStore();
- expect(store.isLockScreen).toBe(false);
- expect(store.lockScreenPassword).toBeUndefined();
- });
-
- it('should lock screen with a password', () => {
- const store = useLockStore();
- store.lockScreen('1234');
- expect(store.isLockScreen).toBe(true);
- expect(store.lockScreenPassword).toBe('1234');
- });
-
- it('should unlock screen and clear password', () => {
- const store = useLockStore();
- store.lockScreen('1234');
- store.unlockScreen();
- expect(store.isLockScreen).toBe(false);
- expect(store.lockScreenPassword).toBeUndefined();
- });
-});
diff --git a/packages/stores/src/modules/lock.ts b/packages/stores/src/modules/lock.ts
deleted file mode 100644
index 76a96006..00000000
--- a/packages/stores/src/modules/lock.ts
+++ /dev/null
@@ -1,33 +0,0 @@
-import { defineStore } from 'pinia';
-
-interface AppState {
- /**
- * 是否锁屏状态
- */
- isLockScreen: boolean;
- /**
- * 锁屏密码
- */
- lockScreenPassword?: string;
-}
-
-export const useLockStore = defineStore('core-lock', {
- actions: {
- lockScreen(password: string) {
- this.isLockScreen = true;
- this.lockScreenPassword = password;
- },
-
- unlockScreen() {
- this.isLockScreen = false;
- this.lockScreenPassword = undefined;
- },
- },
- persist: {
- pick: ['isLockScreen', 'lockScreenPassword'],
- },
- state: (): AppState => ({
- isLockScreen: false,
- lockScreenPassword: undefined,
- }),
-});
diff --git a/packages/stores/src/setup.ts b/packages/stores/src/setup.ts
index 890c4dc8..b18c27e3 100644
--- a/packages/stores/src/setup.ts
+++ b/packages/stores/src/setup.ts
@@ -3,6 +3,7 @@ import type { Pinia } from 'pinia';
import type { App } from 'vue';
import { createPinia } from 'pinia';
+import SecureLS from 'secure-ls';
let pinia: Pinia;
@@ -20,11 +21,27 @@ export async function initStores(app: App, options: InitStoreOptions) {
const { createPersistedState } = await import('pinia-plugin-persistedstate');
pinia = createPinia();
const { namespace } = options;
+ const ls = new SecureLS({
+ encodingType: 'aes',
+ encryptionSecret: import.meta.env.VITE_APP_STORE_SECURE_KEY,
+ isCompression: true,
+ // @ts-ignore secure-ls does not have a type definition for this
+ metaKey: `${namespace}-secure-meta`,
+ });
pinia.use(
createPersistedState({
// key $appName-$store.id
key: (storeKey) => `${namespace}-${storeKey}`,
- storage: localStorage,
+ storage: import.meta.env.DEV
+ ? localStorage
+ : {
+ getItem(key) {
+ return ls.get(key);
+ },
+ setItem(key, value) {
+ ls.set(key, value);
+ },
+ },
}),
);
app.use(pinia);
diff --git a/playground/.env b/playground/.env
index 32957303..94ff4faf 100644
--- a/playground/.env
+++ b/playground/.env
@@ -3,3 +3,6 @@ VITE_APP_TITLE=Vben Admin
# 应用命名空间,用于缓存、store等功能的前缀,确保隔离
VITE_APP_NAMESPACE=vben-web-play
+
+# 对store进行加密的密钥,在将store持久化到localStorage时会使用该密钥进行加密
+VITE_APP_STORE_SECURE_KEY=please-replace-me-with-your-own-key
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 057d32a5..bf96d3e5 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -393,6 +393,9 @@ catalogs:
sass:
specifier: ^1.86.3
version: 1.86.3
+ secure-ls:
+ specifier: ^2.0.0
+ version: 2.0.0
sortablejs:
specifier: ^1.15.6
version: 1.15.6
@@ -1778,6 +1781,9 @@ importers:
pinia-plugin-persistedstate:
specifier: 'catalog:'
version: 4.2.0(magicast@0.3.5)(pinia@2.3.1(typescript@5.8.3)(vue@3.5.13(typescript@5.8.3)))
+ secure-ls:
+ specifier: 'catalog:'
+ version: 2.0.0
vue:
specifier: ^3.5.13
version: 3.5.13(typescript@5.8.3)
@@ -5666,6 +5672,9 @@ packages:
crossws@0.3.4:
resolution: {integrity: sha512-uj0O1ETYX1Bh6uSgktfPvwDiPYGQ3aI4qVsaC/LWpkIzGj1nUYm5FK3K+t11oOlpN01lGbprFCH4wBlKdJjVgw==}
+ crypto-js@4.2.0:
+ resolution: {integrity: sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==}
+
crypto-random-string@2.0.0:
resolution: {integrity: sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==}
engines: {node: '>=8'}
@@ -7666,6 +7675,10 @@ packages:
peerDependencies:
vue: ^3.5.13
+ lz-string@1.5.0:
+ resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==}
+ hasBin: true
+
magic-string@0.25.9:
resolution: {integrity: sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==}
@@ -9231,6 +9244,10 @@ packages:
resolution: {integrity: sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==}
engines: {node: '>=4'}
+ secure-ls@2.0.0:
+ resolution: {integrity: sha512-Wgtnw0QSm0v7gVKv11nOoeyGS65EThGXnBB7jfd4IhZd2eq3B4AMPcXAL5qJ1h55+Qolun7TONTwX7H5m6e2pQ==}
+ engines: {node: '>=8.0'}
+
seemly@0.3.10:
resolution: {integrity: sha512-2+SMxtG1PcsL0uyhkumlOU6Qo9TAQ/WyH7tthnPIOQB05/12jz9naq6GZ6iZ6ApVsO3rr2gsnTf3++OV63kE1Q==}
@@ -14938,6 +14955,8 @@ snapshots:
dependencies:
uncrypto: 0.1.3
+ crypto-js@4.2.0: {}
+
crypto-random-string@2.0.0: {}
cspell-config-lib@8.18.1:
@@ -17132,6 +17151,8 @@ snapshots:
dependencies:
vue: 3.5.13(typescript@5.8.3)
+ lz-string@1.5.0: {}
+
magic-string@0.25.9:
dependencies:
sourcemap-codec: 1.4.8
@@ -18814,6 +18835,11 @@ snapshots:
extend-shallow: 2.0.1
kind-of: 6.0.3
+ secure-ls@2.0.0:
+ dependencies:
+ crypto-js: 4.2.0
+ lz-string: 1.5.0
+
seemly@0.3.10: {}
select@1.1.2: {}
diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml
index 1227ec91..ccc5e3d2 100644
--- a/pnpm-workspace.yaml
+++ b/pnpm-workspace.yaml
@@ -147,6 +147,7 @@ catalog:
rollup: ^4.39.0
rollup-plugin-visualizer: ^5.14.0
sass: ^1.86.3
+ secure-ls: ^2.0.0
sortablejs: ^1.15.6
stylelint: ^16.18.0
stylelint-config-recess-order: ^5.1.1