feat: add the ability to lock the screen (#30)

* feat: 锁屏功能

* feat: 锁屏样式调整

* feat: complete the lock-screen screen and support shortcut keys and preference configuration

---------

Co-authored-by: vince <vince292007@gmail.com>
pull/48/MERGE
Laychen 2024-07-12 12:14:09 +08:00 committed by GitHub
parent 61dbb05b5d
commit 06f5d5686d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
27 changed files with 482 additions and 48 deletions

View File

@ -1,11 +1,12 @@
<script lang="ts" setup>
import { computed, ref, toRefs } from 'vue';
import { computed, ref } from 'vue';
import { useRouter } from 'vue-router';
import { LOGIN_PATH } from '@vben/constants';
import { IcRoundCreditScore, MdiDriveDocument, MdiGithub } from '@vben/icons';
import {
BasicLayout,
LockScreen,
Notification,
NotificationItem,
UserDropdown,
@ -16,7 +17,7 @@ import { preferences } from '@vben-core/preferences';
import { $t } from '#/locales';
import { resetRoutes } from '#/router';
import { useAccessStore, useAppStore } from '#/store';
import { storeToRefs, useAccessStore, useAppStore } from '#/store';
const notifications = ref<NotificationItem[]>([
{
@ -85,11 +86,18 @@ const menus = computed(() => [
const appStore = useAppStore();
const accessStore = useAccessStore();
const { isLockScreen, lockScreenPassword } = storeToRefs(appStore);
const {
loading: loginLoading,
openLoginExpiredModal,
userInfo,
} = toRefs(accessStore);
} = storeToRefs(accessStore);
const avatar = computed(() => {
return userInfo.value?.avatar ?? preferences.app.defaultAvatar;
});
const router = useRouter();
async function handleLogout() {
@ -105,17 +113,22 @@ function handleNoticeClear() {
function handleMakeAll() {
notifications.value.forEach((item) => (item.isRead = true));
}
function handleLockScreen(password: string) {
appStore.lockScreen(password);
}
</script>
<template>
<BasicLayout @clear-preferences-and-logout="handleLogout">
<template #user-dropdown>
<UserDropdown
:avatar="userInfo?.avatar ?? preferences.app.defaultAvatar"
:menus="menus"
:avatar
:menus
:text="userInfo?.realName"
description="ann.vben@gmail.com"
tag-text="Pro"
@lock-screen="handleLockScreen"
@logout="handleLogout"
/>
</template>
@ -127,7 +140,7 @@ function handleMakeAll() {
@make-all="handleMakeAll"
/>
</template>
<template #dialog>
<template #extra>
<AuthenticationLoginExpiredModal
v-model:open="openLoginExpiredModal"
:loading="loginLoading"
@ -136,5 +149,14 @@ function handleMakeAll() {
@submit="accessStore.authLogin"
/>
</template>
<template #lock-screen>
<LockScreen
v-if="isLockScreen"
:avatar
:cached-password="lockScreenPassword"
@to-login="handleLogout"
@unlock="appStore.unlockScreen"
/>
</template>
</BasicLayout>
</template>

View File

@ -51,7 +51,7 @@ const essentialsRoutes: RouteRecordRaw[] = [
component: () =>
import('#/views/_essential/authentication/code-login.vue'),
meta: {
title: $t('page.essentials.code-login'),
title: $t('page.essentials.codeLogin'),
},
},
{

View File

@ -2,7 +2,7 @@ import type { InitStoreOptions } from '@vben-core/stores';
import type { App } from 'vue';
import { initStore } from '@vben-core/stores';
import { initStore, storeToRefs } from '@vben-core/stores';
/**
* @zh_CN pinia
@ -13,7 +13,7 @@ async function setupStore(app: App, options: InitStoreOptions) {
app.use(pinia);
}
export { setupStore };
export { setupStore, storeToRefs };
export { useAccessStore } from './modules/access';
export { useAppStore } from './modules/app';

View File

@ -4,19 +4,35 @@ import { defineStore } from 'pinia';
import { useAccessStore } from './access';
export const useAppStore = defineStore('app', () => {
const accessStore = useAccessStore();
const coreTabbarStore = useCoreTabbarStore();
interface AppState {
isLockScreen: boolean;
lockScreenPassword?: string;
}
/**
*
*/
async function resetAppState() {
accessStore.reset();
coreTabbarStore.$reset();
}
export const useAppStore = defineStore('app', {
actions: {
lockScreen(password: string) {
this.isLockScreen = true;
this.lockScreenPassword = password;
},
return {
resetAppState,
};
resetAppState() {
const accessStore = useAccessStore();
const coreTabbarStore = useCoreTabbarStore();
accessStore.reset();
coreTabbarStore.$reset();
},
unlockScreen() {
this.isLockScreen = false;
this.lockScreenPassword = undefined;
},
},
persist: {
paths: ['isLockScreen', 'lockScreenPassword'],
},
state: (): AppState => ({
isLockScreen: false,
lockScreenPassword: undefined,
}),
});

View File

@ -3,7 +3,6 @@ import type { Preferences } from './types';
const defaultPreferences: Preferences = {
app: {
accessMode: 'frontend',
aiAssistant: true,
authPageLayout: 'panel-right',
colorGrayMode: false,
colorWeakMode: false,
@ -55,6 +54,7 @@ const defaultPreferences: Preferences = {
},
shortcutKeys: {
enable: true,
globalLockScreen: true,
globalLogout: true,
globalPreferences: true,
globalSearch: true,
@ -95,6 +95,7 @@ const defaultPreferences: Preferences = {
fullscreen: true,
globalSearch: true,
languageToggle: true,
lockScreen: true,
notification: true,
sidebarToggle: true,
themeToggle: true,

View File

@ -26,8 +26,6 @@ type AuthPageLayoutType = 'panel-center' | 'panel-left' | 'panel-right';
interface AppPreferences {
/** 权限模式 */
accessMode: AccessModeType;
/** 是否开启vben助手 */
aiAssistant: boolean;
/** 登录注册页面布局 */
authPageLayout: AuthPageLayoutType;
/** 是否开启灰色模式 */
@ -136,6 +134,8 @@ interface SidebarPreferences {
interface ShortcutKeyPreferences {
/** 是否启用快捷键-全局 */
enable: boolean;
/** 是否启用全局锁屏快捷键 */
globalLockScreen: boolean;
/** 是否启用全局注销快捷键 */
globalLogout: boolean;
/** 是否启用全局偏好设置快捷键 */
@ -194,6 +194,8 @@ interface WidgetPreferences {
globalSearch: boolean;
/** 是否启用语言切换部件 */
languageToggle: boolean;
/** 是否开启锁屏功能 */
lockScreen: boolean;
/** 是否显示通知部件 */
notification: boolean;
/** 是否显示侧边栏显示/隐藏部件 */

View File

@ -125,6 +125,11 @@ function usePreferences() {
return enable && globalLogout;
});
const globalLockScreenShortcutKey = computed(() => {
const { enable, globalLockScreen } = shortcutKeysPreferences.value;
return enable && globalLockScreen;
});
/**
* @zh_CN
*/
@ -138,6 +143,7 @@ function usePreferences() {
authPanelLeft,
authPanelRight,
diffPreference,
globalLockScreenShortcutKey,
globalLogoutShortcutKey,
globalPreferencesShortcutKey,
globalSearchShortcutKey,

View File

@ -68,6 +68,16 @@
"noResults": "No Search Results Found",
"noRecent": "No Search History",
"recent": "Search History"
},
"lockScreen": {
"title": "Lock Screen",
"screenButton": "Locking",
"password": "Password",
"placeholder": "Please enter password",
"unlock": "Click to unlock",
"errorPasswordTip": "Password error, please re-enter",
"backToLogin": "Back to login",
"entry": "Enter the system"
}
},
"authentication": {
@ -263,7 +273,8 @@
"languageToggle": "Enable Language Toggle",
"notification": "Enable Notification",
"sidebarToggle": "Enable Sidebar Toggle",
"aiAssistant": "Enable AI Assistant"
"aiAssistant": "Enable AI Assistant",
"lockScreen": "Enable Lock Screen"
}
}
}

View File

@ -68,6 +68,16 @@
"noResults": "未找到搜索结果",
"noRecent": "没有搜索历史",
"recent": "搜索历史"
},
"lockScreen": {
"title": "锁定屏幕",
"screenButton": "锁定",
"password": "密码",
"placeholder": "请输入锁屏密码",
"unlock": "点击解锁",
"errorPasswordTip": "密码错误,请重新输入",
"backToLogin": "返回登录",
"entry": "进入系统"
}
},
"authentication": {
@ -263,7 +273,8 @@
"languageToggle": "启用语言切换",
"notification": "启用通知",
"sidebarToggle": "启用侧边栏切换",
"aiAssistant": "启用 AI 助手"
"aiAssistant": "启用 AI 助手",
"lockScreen": "启用锁屏"
}
}
}

View File

@ -1,4 +1,4 @@
@forward './constants.scss';
@forward './constants';
@mixin b($block) {
$B: $namespace + '-' + $block !global;

View File

@ -1,9 +1,14 @@
import { h } from 'vue';
import { defineComponent, h } from 'vue';
import { Icon } from '@iconify/vue';
function createIconifyIcon(icon: string) {
return h(Icon, { icon });
return defineComponent({
name: `SvgIcon-${icon}`,
setup(props, { attrs }) {
return () => h(Icon, { icon, ...props, ...attrs });
},
});
}
export { createIconifyIcon };

View File

@ -82,3 +82,5 @@ export const IcRoundMultipleStop = createIconifyIcon('ic:round-multiple-stop');
export const IcRoundRefresh = createIconifyIcon('ic:round-refresh');
export const IcRoundCreditScore = createIconifyIcon('ic:round-credit-score');
export const IcRoundLock = createIconifyIcon('ic:round-lock');

View File

@ -279,7 +279,10 @@ function clearPreferencesAndLogout() {
</template>
<template #extra>
<slot name="dialog"></slot>
<slot name="extra"></slot>
<Transition v-if="preferences.widget.lockScreen" name="slide-up">
<slot name="lock-screen"></slot>
</Transition>
</template>
</VbenAdminLayout>
</template>

View File

@ -4,6 +4,7 @@ export { default as CozeAssistant } from './coze-assistant.vue';
export * from './global-search';
export { default as LanguageToggle } from './language-toggle.vue';
export { default as AuthenticationLayoutToggle } from './layout-toggle.vue';
export * from './lock-screen';
export * from './notification';
export * from './preferences';
export * from './theme-toggle';

View File

@ -0,0 +1,2 @@
export { default as LockScreen } from './lock-screen.vue';
export { default as LockScreenModal } from './lock-screen-modal.vue';

View File

@ -0,0 +1,106 @@
<script setup lang="ts">
import type { RegisterEmits } from './typings';
import { computed, reactive } from 'vue';
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
VbenAvatar,
VbenButton,
VbenInputPassword,
} from '@vben-core/shadcn-ui';
interface Props {
avatar?: string;
text?: string;
}
defineOptions({
name: 'LockScreenModal',
});
withDefaults(defineProps<Props>(), {
avatar: '',
text: '',
});
const emit = defineEmits<{
submit: RegisterEmits['submit'];
}>();
const formState = reactive({
lockScreenPassword: '',
submitted: false,
});
const openModal = defineModel<boolean>('open');
const passwordStatus = computed(() => {
return formState.submitted && !formState.lockScreenPassword
? 'error'
: 'default';
});
function handleClose() {
openModal.value = false;
}
function handleSubmit() {
formState.submitted = true;
if (passwordStatus.value !== 'default') {
return;
}
emit('submit', {
lockScreenPassword: formState.lockScreenPassword,
});
}
</script>
<template>
<div>
<Dialog :open="openModal">
<DialogContent
class="top-0 h-full w-full -translate-y-0 border-none p-0 shadow-xl sm:top-[20%] sm:h-[unset] sm:w-[600px] sm:rounded-2xl"
@close="handleClose"
>
<DialogDescription />
<DialogHeader>
<DialogTitle
class="border-border flex h-8 items-center px-5 font-normal"
>
{{ $t('widgets.lockScreen.title') }}
</DialogTitle>
</DialogHeader>
<div
class="mb-10 flex w-full flex-col items-center"
@keypress.enter.prevent="handleSubmit"
>
<div class="w-2/3">
<div class="ml-2 flex w-full flex-col items-center">
<VbenAvatar
:src="avatar"
class="size-24"
dot-class="bottom-0 right-1 border-2 size-4 bg-green-500"
/>
<div class="text-foreground my-6 flex items-center font-medium">
{{ text }}
</div>
</div>
<VbenInputPassword
v-model="formState.lockScreenPassword"
:error-tip="$t('widgets.lockScreen.placeholder')"
:label="$t('widgets.lockScreen.password')"
:placeholder="$t('widgets.lockScreen.placeholder')"
:status="passwordStatus"
name="password"
required
type="password"
/>
<VbenButton class="w-full" @click="handleSubmit">
{{ $t('widgets.lockScreen.screenButton') }}
</VbenButton>
</div>
</div>
</DialogContent>
</Dialog>
</div>
</template>

View File

@ -0,0 +1,170 @@
<script setup lang="ts">
import { computed, reactive, ref, watchEffect } from 'vue';
import { IcRoundLock } from '@vben-core/iconify';
import { $t } from '@vben-core/locales';
import {
VbenAvatar,
VbenButton,
VbenInputPassword,
} from '@vben-core/shadcn-ui';
import { useDateFormat, useNow } from '@vueuse/core';
interface Props {
avatar?: string;
cachedPassword?: string;
}
defineOptions({
name: 'LockScreen',
});
const props = withDefaults(defineProps<Props>(), {
avatar: '',
cachedPassword: undefined,
});
const emit = defineEmits<{ toLogin: []; unlock: [string] }>();
const now = useNow();
const year = useDateFormat(now, 'YYYY');
const month = useDateFormat(now, 'MM');
const day = useDateFormat(now, 'DD');
const week = useDateFormat(now, 'dddd');
const hour = useDateFormat(now, 'HH');
const meridiem = useDateFormat(now, 'A');
const minute = useDateFormat(now, 'mm');
const showUnlockForm = ref(false);
const validPass = ref(true);
const formState = reactive({
password: '',
submitted: false,
});
const passwordStatus = computed(() => {
if (formState.submitted && !formState.password) {
return 'error';
}
if (formState.submitted && !validPass.value) {
return 'error';
}
return 'default';
});
const errorTip = computed(() => {
return props.cachedPassword === undefined || !formState.password
? $t('widgets.lockScreen.placeholder')
: $t('widgets.lockScreen.errorPasswordTip');
});
watchEffect(() => {
if (!formState.password) {
validPass.value = true;
}
});
function handleSubmit() {
formState.submitted = true;
if (passwordStatus.value !== 'default') {
return;
}
if (props.cachedPassword !== formState.password) {
validPass.value = false;
return;
}
emit('unlock', formState.password);
}
function toggleUnlockForm() {
showUnlockForm.value = !showUnlockForm.value;
}
</script>
<template>
<div class="bg-background fixed z-[2000] size-full">
<transition name="slide-left">
<div v-show="!showUnlockForm" class="size-full">
<div
class="flex-col-center text-foreground/80 hover:text-foreground group my-4 cursor-pointer text-xl font-semibold"
@click="toggleUnlockForm"
>
<IcRoundLock
class="size-5 transition-all duration-300 group-hover:scale-125"
/>
<span>{{ $t('widgets.lockScreen.unlock') }}</span>
</div>
<div class="flex h-full justify-center px-[10%]">
<div
class="bg-accent flex-center relative mb-14 mr-20 h-4/5 w-2/5 flex-auto rounded-3xl text-center text-[260px]"
>
<span class="absolute left-4 top-4 text-xl font-semibold">{{
meridiem
}}</span>
{{ hour }}
</div>
<div
class="bg-accent flex-center mb-14 h-4/5 w-2/5 flex-auto rounded-3xl text-center text-[260px]"
>
{{ minute }}
</div>
</div>
</div>
</transition>
<transition name="slide-right">
<div
v-if="showUnlockForm"
class="flex-center size-full"
@keypress.enter.prevent="handleSubmit"
>
<div class="flex-col-center mb-10 w-[300px]">
<VbenAvatar :src="avatar" class="enter-x mb-6 size-20" />
<div class="items-cente enter-x mb-2 w-full">
<VbenInputPassword
v-model="formState.password"
:autofocus="true"
:error-tip="errorTip"
:label="$t('widgets.lockScreen.password')"
:placeholder="$t('widgets.lockScreen.placeholder')"
:status="passwordStatus"
name="password"
required
type="password"
/>
</div>
<VbenButton class="enter-x w-full" @click="handleSubmit">
{{ $t('widgets.lockScreen.entry') }}
</VbenButton>
<VbenButton
class="enter-x my-2 w-full"
variant="ghost"
@click="$emit('toLogin')"
>
{{ $t('widgets.lockScreen.backToLogin') }}
</VbenButton>
<VbenButton
class="enter-x mr-2 w-full"
variant="ghost"
@click="toggleUnlockForm"
>
{{ $t('common.back') }}
</VbenButton>
</div>
</div>
</transition>
<div
class="enter-y absolute bottom-5 w-full text-center text-gray-300 xl:text-xl 2xl:text-3xl"
>
<div v-if="showUnlockForm" class="enter-x mb-2 text-3xl">
{{ hour }}:{{ minute }} <span class="text-lg">{{ meridiem }}</span>
</div>
<div class="text-3xl">{{ year }}/{{ month }}/{{ day }} {{ week }}</div>
</div>
</div>
</template>

View File

@ -0,0 +1,9 @@
interface LockAndRegisterParams {
lockScreenPassword: string;
}
interface RegisterEmits {
submit: [LockAndRegisterParams];
}
export type { LockAndRegisterParams, RegisterEmits };

View File

@ -14,6 +14,7 @@ const widgetNotification = defineModel<boolean>('widgetNotification');
const widgetThemeToggle = defineModel<boolean>('widgetThemeToggle');
const widgetAiAssistant = defineModel<boolean>('widgetAiAssistant');
const widgetSidebarToggle = defineModel<boolean>('widgetSidebarToggle');
const widgetLockScreen = defineModel<boolean>('widgetLockScreen');
</script>
<template>
@ -35,6 +36,9 @@ const widgetSidebarToggle = defineModel<boolean>('widgetSidebarToggle');
<SwitchItem v-model="widgetAiAssistant">
{{ $t('preferences.widget.aiAssistant') }}
</SwitchItem>
<SwitchItem v-model="widgetLockScreen">
{{ $t('preferences.widget.lockScreen') }}
</SwitchItem>
<SwitchItem v-model="widgetSidebarToggle">
{{ $t('preferences.widget.sidebarToggle') }}
</SwitchItem>

View File

@ -16,6 +16,7 @@ const shortcutKeysGlobalSearch = defineModel<boolean>(
);
const shortcutKeysLogout = defineModel<boolean>('shortcutKeysLogout');
const shortcutKeysPreferences = defineModel<boolean>('shortcutKeysPreferences');
const shortcutKeysLockScreen = defineModel<boolean>('shortcutKeysLockScreen');
const altView = computed(() => (isWindowsOs() ? 'Alt' : '⌥'));
</script>
@ -24,19 +25,26 @@ const altView = computed(() => (isWindowsOs() ? 'Alt' : '⌥'));
<SwitchItem v-model="shortcutKeysEnable">
{{ $t('preferences.shortcutKeys.title') }}
</SwitchItem>
<SwitchItem v-if="shortcutKeysEnable" v-model="shortcutKeysGlobalSearch">
<SwitchItem
v-model="shortcutKeysGlobalSearch"
:disabled="!shortcutKeysEnable"
>
{{ $t('preferences.shortcutKeys.search') }}
<template #shortcut>
{{ isWindowsOs() ? 'Ctrl' : '⌘' }}
<kbd> K </kbd>
</template>
</SwitchItem>
<SwitchItem v-if="shortcutKeysEnable" v-model="shortcutKeysLogout">
<SwitchItem v-model="shortcutKeysLogout" :disabled="!shortcutKeysEnable">
{{ $t('preferences.shortcutKeys.logout') }}
<template #shortcut> {{ altView }} Q </template>
</SwitchItem>
<SwitchItem v-if="shortcutKeysEnable" v-model="shortcutKeysPreferences">
<SwitchItem v-model="shortcutKeysPreferences" :disabled="!shortcutKeysEnable">
{{ $t('preferences.shortcutKeys.preferences') }}
<template #shortcut> {{ altView }} , </template>
</SwitchItem>
<SwitchItem v-model="shortcutKeysLockScreen" :disabled="!shortcutKeysEnable">
{{ $t('widgets.lockScreen.title') }}
<template #shortcut> {{ altView }} L </template>
</SwitchItem>
</template>

View File

@ -128,6 +128,9 @@ const shortcutKeysGlobalLogout = defineModel<boolean>(
const shortcutKeysGlobalPreferences = defineModel<boolean>(
'shortcutKeysGlobalPreferences',
);
const shortcutKeysGlobalLockScreen = defineModel<boolean>(
'shortcutKeysGlobalLockScreen',
);
const widgetGlobalSearch = defineModel<boolean>('widgetGlobalSearch');
const widgetFullscreen = defineModel<boolean>('widgetFullscreen');
@ -136,6 +139,7 @@ const widgetNotification = defineModel<boolean>('widgetNotification');
const widgetThemeToggle = defineModel<boolean>('widgetThemeToggle');
const widgetAiAssistant = defineModel<boolean>('widgetAiAssistant');
const widgetSidebarToggle = defineModel<boolean>('widgetSidebarToggle');
const widgetLockScreen = defineModel<boolean>('widgetLockScreen');
const {
diffPreference,
@ -355,6 +359,7 @@ async function handleReset() {
v-model:widget-fullscreen="widgetFullscreen"
v-model:widget-global-search="widgetGlobalSearch"
v-model:widget-language-toggle="widgetLanguageToggle"
v-model:widget-lock-screen="widgetLockScreen"
v-model:widget-notification="widgetNotification"
v-model:widget-sidebar-toggle="widgetSidebarToggle"
v-model:widget-theme-toggle="widgetThemeToggle"
@ -384,6 +389,7 @@ async function handleReset() {
<GlobalShortcutKeys
v-model:shortcut-keys-enable="shortcutKeysEnable"
v-model:shortcut-keys-global-search="shortcutKeysGlobalSearch"
v-model:shortcut-keys-lock-screen="shortcutKeysGlobalLockScreen"
v-model:shortcut-keys-logout="shortcutKeysGlobalLogout"
v-model:shortcut-keys-preferences="
shortcutKeysGlobalPreferences

View File

@ -8,7 +8,7 @@ import Preferences from './preferences-sheet.vue';
/**
* preferences 转成 vue props
* preferences.app.aiAssistant=>appAiAssistant
* preferences.widget.aiAssistant=>widgetAiAssistant
*/
const attrs = computed(() => {
const result: Record<string, any> = {};
@ -22,7 +22,7 @@ const attrs = computed(() => {
/**
* preferences 转成 vue listener
* preferences.app.aiAssistant=>@update:appAiAssistant
* preferences.widget.aiAssistant=>@update:widgetAiAssistant
*/
const listen = computed(() => {
const result: Record<string, any> = {};

View File

@ -4,7 +4,11 @@ import type { AnyFunction } from '@vben/types';
import type { Component } from 'vue';
import { computed, ref } from 'vue';
import { IcRoundLogout, IcRoundSettingsSuggest } from '@vben-core/iconify';
import {
IcRoundLock,
IcRoundLogout,
IcRoundSettingsSuggest,
} from '@vben-core/iconify';
import { $t } from '@vben-core/locales';
import { preferences, usePreferences } from '@vben-core/preferences';
import {
@ -24,6 +28,7 @@ import { isWindowsOs } from '@vben-core/toolkit';
import { useMagicKeys, whenever } from '@vueuse/core';
import { LockScreenModal } from '../lock-screen';
import { useOpenPreferences } from '../preferences';
interface Props {
@ -68,12 +73,16 @@ const props = withDefaults(defineProps<Props>(), {
text: '',
});
const emit = defineEmits<{ logout: [] }>();
const emit = defineEmits<{ lockScreen: [string]; logout: [] }>();
const openPopover = ref(false);
const openDialog = ref(false);
const openLock = ref(false);
const { globalLogoutShortcutKey, globalPreferencesShortcutKey } =
usePreferences();
const {
globalLockScreenShortcutKey,
globalLogoutShortcutKey,
globalPreferencesShortcutKey,
} = usePreferences();
const { handleOpenPreference } = useOpenPreferences();
const altView = computed(() => (isWindowsOs() ? 'Alt' : '⌥'));
@ -82,6 +91,10 @@ const enableLogoutShortcutKey = computed(() => {
return props.enableShortcutKey && globalLogoutShortcutKey.value;
});
const enableLockScreenShortcutKey = computed(() => {
return props.enableShortcutKey && globalLockScreenShortcutKey.value;
});
const enableShortcutKey = computed(() => {
return props.enableShortcutKey && preferences.shortcutKeys.enable;
});
@ -90,6 +103,18 @@ const enablePreferencesShortcutKey = computed(() => {
return props.enableShortcutKey && globalPreferencesShortcutKey.value;
});
function handleOpenLock() {
openLock.value = true;
}
function handleSubmitLock({
lockScreenPassword,
}: {
lockScreenPassword: string;
}) {
openLock.value = false;
emit('lockScreen', lockScreenPassword);
}
function handleLogout() {
// emit
openDialog.value = true;
@ -114,10 +139,23 @@ if (enableShortcutKey.value) {
handleOpenPreference();
}
});
whenever(keys['Alt+KeyL'], () => {
if (enableLockScreenShortcutKey.value) {
handleOpenLock();
}
});
}
</script>
<template>
<LockScreenModal
v-if="preferences.widget.lockScreen"
v-model:open="openLock"
:avatar="avatar"
:text="text"
@submit="handleSubmitLock"
/>
<VbenAlertDialog
v-model:open="openDialog"
:cancel-text="$t('common.cancel')"
@ -180,6 +218,17 @@ if (enableShortcutKey.value) {
{{ altView }} ,
</DropdownMenuShortcut>
</DropdownMenuItem>
<DropdownMenuItem
v-if="preferences.widget.lockScreen"
class="mx-1 flex cursor-pointer items-center rounded-sm py-1 leading-8"
@click="handleOpenLock"
>
<IcRoundLock class="mr-2 size-5" />
{{ $t('widgets.lockScreen.title') }}
<DropdownMenuShortcut v-if="enableLockScreenShortcutKey">
{{ altView }} L
</DropdownMenuShortcut>
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem
class="mx-1 flex cursor-pointer items-center rounded-sm py-1 leading-8"

View File

@ -82,7 +82,7 @@ function handleSubmit() {
});
}
function goLogin() {
function goToLogin() {
router.push(props.loginPath);
}
@ -151,7 +151,7 @@ onBeforeUnmount(() => {
<VbenButton :loading="loading" class="mt-2 w-full" @click="handleSubmit">
{{ $t('common.login') }}
</VbenButton>
<VbenButton class="mt-4 w-full" variant="outline" @click="goLogin()">
<VbenButton class="mt-4 w-full" variant="outline" @click="goToLogin()">
{{ $t('common.back') }}
</VbenButton>
</div>

View File

@ -50,7 +50,7 @@ function handleSubmut() {
emit('submit', formState.email);
}
function goLogin() {
function goToLogin() {
router.push(props.loginPath);
}
</script>
@ -79,7 +79,7 @@ function goLogin() {
<VbenButton class="mt-2 w-full" @click="handleSubmut">
{{ $t('authentication.sendResetLink') }}
</VbenButton>
<VbenButton class="mt-4 w-full" variant="outline" @click="goLogin()">
<VbenButton class="mt-4 w-full" variant="outline" @click="goToLogin()">
{{ $t('common.back') }}
</VbenButton>
</div>

View File

@ -39,7 +39,7 @@ const qrcode = useQRCode(text, {
margin: 4,
});
function goLogin() {
function goToLogin() {
router.push(props.loginPath);
}
</script>
@ -62,7 +62,7 @@ function goLogin() {
</p>
</div>
<VbenButton class="mt-4 w-full" variant="outline" @click="goLogin()">
<VbenButton class="mt-4 w-full" variant="outline" @click="goToLogin()">
{{ $t('common.back') }}
</VbenButton>
</div>

View File

@ -78,7 +78,7 @@ function handleSubmit() {
});
}
function goLogin() {
function goToLogin() {
router.push(props.loginPath);
}
</script>
@ -160,7 +160,7 @@ function goLogin() {
{{ $t('authentication.alreadyHaveAccount') }}
<span
class="text-primary hover:text-primary-hover cursor-pointer text-sm font-normal"
@click="goLogin()"
@click="goToLogin()"
>
{{ $t('authentication.goToLogin') }}
</span>