Ver Fonte

refactor(lock-page): refactor lock page

vben há 4 anos atrás
pai
commit
4ce1d526c8

+ 1 - 0
CHANGELOG.zh_CN.md

@@ -16,6 +16,7 @@
 ### ✨ Refactor
 
 - tree 组件 ref 函数调用删除 `$`
+- 锁屏界面重构美化,删除不必要的背景图片
 
 ### ⚡ Performance Improvements
 

+ 3 - 2
src/api/sys/user.ts

@@ -5,6 +5,7 @@ import {
   GetUserInfoByUserIdParams,
   GetUserInfoByUserIdModel,
 } from './model/userModel';
+import { ErrorMessageMode } from '/@/utils/http/axios/types';
 
 enum Api {
   Login = '/login',
@@ -15,7 +16,7 @@ enum Api {
 /**
  * @description: user login api
  */
-export function loginApi(params: LoginParams) {
+export function loginApi(params: LoginParams, mode: ErrorMessageMode = 'modal') {
   return defHttp.request<LoginResultModel>(
     {
       url: Api.Login,
@@ -23,7 +24,7 @@ export function loginApi(params: LoginParams) {
       params,
     },
     {
-      errorMessageMode: 'modal',
+      errorMessageMode: mode,
     }
   );
 }

BIN
src/assets/images/lock-page.jpg


+ 1 - 0
src/design/mixins.less

@@ -90,6 +90,7 @@
     @content();
   }
 }
+
 .respond-to (xsmall-and-small, @content) {
   @media only screen and (max-width: @screen-sm-max) {
     @content();

+ 4 - 0
src/design/var/breakpoint.less

@@ -26,9 +26,13 @@
 @screen-xxl: 1600px;
 @screen-xxl-min: @screen-xxl;
 
+@screen-xxxl: 1900px;
+@screen-xxxl-min: @screen-xxxl;
+
 // provide a maximum
 @screen-xs-max: (@screen-sm-min - 1px);
 @screen-sm-max: (@screen-md-min - 1px);
 @screen-md-max: (@screen-lg-min - 1px);
 @screen-lg-max: (@screen-xl-min - 1px);
 @screen-xl-max: (@screen-xxl-min - 1px);
+@screen-xxl-max: (@screen-xxxl-min - 1px);

+ 3 - 0
src/hooks/setting/useRootSetting.ts

@@ -42,6 +42,8 @@ const getColorWeak = computed(() => unref(getRootSetting).colorWeak);
 
 const getGrayMode = computed(() => unref(getRootSetting).grayMode);
 
+const getLockTime = computed(() => unref(getRootSetting).lockTime);
+
 const getLayoutContentMode = computed(() =>
   unref(getRootSetting).contentMode === ContentEnum.FULL ? ContentEnum.FULL : ContentEnum.FIXED
 );
@@ -71,5 +73,6 @@ export function useRootSetting() {
     getShowSettingButton,
     getShowFooter,
     getContentMode,
+    getLockTime,
   };
 }

+ 12 - 4
src/hooks/web/useLockPage.ts

@@ -1,10 +1,13 @@
-import { computed, onUnmounted, watchEffect } from 'vue';
+import { computed, onUnmounted, unref, watchEffect } from 'vue';
 import { useThrottle } from '/@/hooks/core/useThrottle';
 
 import { appStore } from '/@/store/modules/app';
+import { lockStore } from '/@/store/modules/lock';
 import { userStore } from '/@/store/modules/user';
+import { useRootSetting } from '../setting/useRootSetting';
 
 export function useLockPage() {
+  const { getLockTime } = useRootSetting();
   let timeId: TimeoutHandle;
 
   function clear(): void {
@@ -30,7 +33,7 @@ export function useLockPage() {
   }
 
   function lockPage(): void {
-    appStore.commitLockInfoState({
+    lockStore.commitLockInfoState({
       isLock: true,
       pwd: undefined,
     });
@@ -54,8 +57,7 @@ export function useLockPage() {
   const [keyupFn] = useThrottle(resetCalcLockTimeout, 2000);
 
   return computed(() => {
-    const openLockPage = appStore.getProjectConfig.lockTime;
-    if (openLockPage) {
+    if (unref(getLockTime)) {
       return { onKeyup: keyupFn, onMousemove: keyupFn };
     } else {
       clear();
@@ -63,3 +65,9 @@ export function useLockPage() {
     }
   });
 }
+
+export const getIsLock = computed(() => {
+  const { getLockInfo } = lockStore;
+  const { isLock } = getLockInfo;
+  return isLock;
+});

+ 1 - 1
src/layouts/default/index.tsx

@@ -6,7 +6,7 @@ import LayoutHeader from './header/LayoutHeader';
 
 import LayoutContent from './content';
 import LayoutFooter from './footer';
-import LayoutLockPage from './lock';
+import LayoutLockPage from './lock/index.vue';
 import LayoutSideBar from './sider';
 import SettingBtn from './setting/index.vue';
 import LayoutMultipleHeader from './header/LayoutMultipleHeader';

+ 10 - 21
src/layouts/default/lock/LockAction.tsx

@@ -7,9 +7,9 @@ import { BasicForm, useForm } from '/@/components/Form/index';
 
 import headerImg from '/@/assets/images/header.jpg';
 
-import { appStore } from '/@/store/modules/app';
 import { userStore } from '/@/store/modules/user';
 import { useI18n } from '/@/hooks/web/useI18n';
+import { lockStore } from '/@/store/modules/lock';
 
 const prefixCls = 'lock-modal';
 export default defineComponent({
@@ -30,24 +30,16 @@ export default defineComponent({
       ],
     });
 
-    async function lock(valid = true) {
-      let password: string | undefined = '';
+    async function lock() {
+      const values = (await validateFields()) as any;
+      const password: string | undefined = values.password;
+      closeModal();
 
-      try {
-        if (!valid) {
-          password = undefined;
-        } else {
-          const values = (await validateFields()) as any;
-          password = values.password;
-        }
-        closeModal();
-
-        appStore.commitLockInfoState({
-          isLock: true,
-          pwd: password,
-        });
-        await resetFields();
-      } catch (error) {}
+      lockStore.commitLockInfoState({
+        isLock: true,
+        pwd: password,
+      });
+      await resetFields();
     }
 
     return () => (
@@ -71,9 +63,6 @@ export default defineComponent({
               <Button type="primary" block class="mt-2" onClick={lock}>
                 {() => t('layout.header.lockScreenBtn')}
               </Button>
-              <Button block class="mt-2" onClick={lock.bind(null, false)}>
-                {() => t('layout.header.notLockScreenPassword')}
-              </Button>
             </div>
           </div>
         )}

+ 0 - 17
src/layouts/default/lock/index.tsx

@@ -1,17 +0,0 @@
-import { defineComponent, unref, computed } from 'vue';
-import { appStore } from '/@/store/modules/app';
-import LockPage from '/@/views/sys/lock/index.vue';
-
-export default defineComponent({
-  name: 'LayoutLockPage',
-  setup() {
-    const getIsLockRef = computed(() => {
-      const { getLockInfo } = appStore;
-      const { isLock } = getLockInfo;
-      return isLock;
-    });
-    return () => {
-      return unref(getIsLockRef) ? <LockPage /> : null;
-    };
-  },
-});

+ 17 - 0
src/layouts/default/lock/index.vue

@@ -0,0 +1,17 @@
+<template>
+  <transition name="fade-bottom">
+    <LockPage v-if="getIsLock" />
+  </transition>
+</template>
+<script lang="ts">
+  import { defineComponent } from 'vue';
+  import LockPage from '/@/views/sys/lock/index.vue';
+  import { getIsLock } from '/@/hooks/web/useLockPage';
+  export default defineComponent({
+    name: 'LayoutLockPage',
+    components: { LockPage },
+    setup() {
+      return { getIsLock };
+    },
+  });
+</script>

+ 0 - 1
src/locales/lang/en/layout/header.ts

@@ -14,7 +14,6 @@ export default {
   lockScreenPassword: 'Lock screen password',
   lockScreen: 'Lock screen',
   lockScreenBtn: 'Locking',
-  notLockScreenPassword: 'No password lock screen',
 
   home: 'Home',
 };

+ 2 - 0
src/locales/lang/en/sys/lock.ts

@@ -1,6 +1,8 @@
 export default {
+  unlock: 'Click to unlock',
   alert: 'Lock screen password error',
   backToLogin: 'Back to login',
+  back: 'Back',
   entry: 'Enter the system',
   placeholder: 'Please enter the lock screen password or user password',
 };

+ 0 - 1
src/locales/lang/zh_CN/layout/header.ts

@@ -15,7 +15,6 @@ export default {
   lockScreenPassword: '锁屏密码',
   lockScreen: '锁定屏幕',
   lockScreenBtn: '锁定',
-  notLockScreenPassword: '不设置密码锁屏',
 
   home: '首页',
 };

+ 2 - 0
src/locales/lang/zh_CN/sys/lock.ts

@@ -1,5 +1,7 @@
 export default {
+  unlock: '点击解锁',
   alert: '锁屏密码错误',
+  back: '返回',
   backToLogin: '返回登录',
   entry: '进入系统',
   placeholder: '请输入锁屏密码或者用户密码',

+ 2 - 61
src/store/modules/app.ts

@@ -3,16 +3,10 @@ import type { ProjectConfig } from '/@/types/config';
 import { VuexModule, getModule, Module, Mutation, Action } from 'vuex-module-decorators';
 import store from '/@/store';
 
-import { PROJ_CFG_KEY, LOCK_INFO_KEY } from '/@/enums/cacheEnum';
+import { PROJ_CFG_KEY } from '/@/enums/cacheEnum';
 
 import { hotModuleUnregisterModule } from '/@/utils/helper/vuexHelper';
-import {
-  setLocal,
-  getLocal,
-  removeLocal,
-  clearSession,
-  clearLocal,
-} from '/@/utils/helper/persistent';
+import { setLocal, getLocal, clearSession, clearLocal } from '/@/utils/helper/persistent';
 import { deepMerge } from '/@/utils';
 
 import { resetRouter } from '/@/router';
@@ -37,9 +31,6 @@ class App extends VuexModule {
   // project config
   private projectConfigState: ProjectConfig | null = getLocal(PROJ_CFG_KEY);
 
-  // lock info
-  private lockInfoState: LockInfo | null = getLocal(LOCK_INFO_KEY);
-
   // set main overflow hidden
   private lockMainScrollState = false;
 
@@ -51,10 +42,6 @@ class App extends VuexModule {
     return this.lockMainScrollState;
   }
 
-  get getLockInfo(): LockInfo {
-    return this.lockInfoState || ({} as LockInfo);
-  }
-
   get getProjectConfig(): ProjectConfig {
     return this.projectConfigState || ({} as ProjectConfig);
   }
@@ -75,18 +62,6 @@ class App extends VuexModule {
     setLocal(PROJ_CFG_KEY, this.projectConfigState);
   }
 
-  @Mutation
-  commitLockInfoState(info: LockInfo): void {
-    this.lockInfoState = Object.assign({}, this.lockInfoState, info);
-    setLocal(LOCK_INFO_KEY, this.lockInfoState);
-  }
-
-  @Mutation
-  resetLockInfo(): void {
-    removeLocal(LOCK_INFO_KEY);
-    this.lockInfoState = null;
-  }
-
   @Action
   async resumeAllState() {
     resetRouter();
@@ -111,39 +86,5 @@ class App extends VuexModule {
       clearTimeout(timeId);
     }
   }
-
-  /**
-   * @description: unlock page
-   */
-  @Action
-  public async unLockAction({ password, valid = true }: { password: string; valid?: boolean }) {
-    if (!valid) {
-      this.resetLockInfo();
-      return true;
-    }
-    const tryLogin = async () => {
-      try {
-        const username = userStore.getUserInfoState.username;
-        const res = await userStore.login({ username, password }, false);
-        if (res) {
-          this.resetLockInfo();
-        }
-        return res;
-      } catch (error) {
-        return false;
-      }
-    };
-
-    if (this.getLockInfo) {
-      if (this.getLockInfo.pwd === password) {
-        this.resetLockInfo();
-        return true;
-      }
-      const res = await tryLogin();
-      return res;
-    }
-    const res = await tryLogin();
-    return res;
-  }
 }
 export const appStore = getModule<App>(App);

+ 64 - 0
src/store/modules/lock.ts

@@ -0,0 +1,64 @@
+import { VuexModule, getModule, Module, Mutation, Action } from 'vuex-module-decorators';
+import store from '/@/store';
+
+import { LOCK_INFO_KEY } from '/@/enums/cacheEnum';
+
+import { hotModuleUnregisterModule } from '/@/utils/helper/vuexHelper';
+import { setLocal, getLocal, removeLocal } from '/@/utils/helper/persistent';
+
+import { userStore } from './user';
+
+export interface LockInfo {
+  pwd: string | undefined;
+  isLock: boolean;
+}
+
+const NAME = 'lock';
+hotModuleUnregisterModule(NAME);
+@Module({ dynamic: true, namespaced: true, store, name: NAME })
+class Lock extends VuexModule {
+  // lock info
+  private lockInfoState: LockInfo | null = getLocal(LOCK_INFO_KEY);
+
+  get getLockInfo(): LockInfo {
+    return this.lockInfoState || ({} as LockInfo);
+  }
+
+  @Mutation
+  commitLockInfoState(info: LockInfo): void {
+    this.lockInfoState = Object.assign({}, this.lockInfoState, info);
+    setLocal(LOCK_INFO_KEY, this.lockInfoState);
+  }
+
+  @Mutation
+  resetLockInfo(): void {
+    removeLocal(LOCK_INFO_KEY);
+    this.lockInfoState = null;
+  }
+
+  /**
+   * @description: unlock page
+   */
+  @Action
+  public async unLockAction({ password }: { password: string }) {
+    const tryLogin = async () => {
+      try {
+        const username = userStore.getUserInfoState.username;
+        const res = await userStore.login({ username, password, goHome: false, mode: 'none' });
+        if (res) {
+          this.resetLockInfo();
+        }
+        return res;
+      } catch (error) {
+        return false;
+      }
+    };
+
+    if (this.getLockInfo?.pwd === password) {
+      this.resetLockInfo();
+      return true;
+    }
+    return await tryLogin();
+  }
+}
+export const lockStore = getModule<Lock>(Lock);

+ 11 - 3
src/store/modules/user.ts

@@ -21,6 +21,7 @@ import { loginApi, getUserInfoById } from '/@/api/sys/user';
 import { setLocal, getLocal, getSession, setSession } from '/@/utils/helper/persistent';
 import { useProjectSetting } from '/@/hooks/setting';
 import { useI18n } from '/@/hooks/web/useI18n';
+import { ErrorMessageMode } from '/@/utils/http/axios/types';
 
 export type UserInfo = Omit<GetUserInfoByUserIdModel, 'roles'>;
 
@@ -94,9 +95,16 @@ class User extends VuexModule {
    * @description: login
    */
   @Action
-  async login(params: LoginParams, goHome = true): Promise<GetUserInfoByUserIdModel | null> {
+  async login(
+    params: LoginParams & {
+      goHome?: boolean;
+      mode?: ErrorMessageMode;
+    }
+  ): Promise<GetUserInfoByUserIdModel | null> {
     try {
-      const data = await loginApi(params);
+      const { goHome = true, mode, ...loginParams } = params;
+      const data = await loginApi(loginParams, mode);
+
       const { token, userId } = data;
       // get user info
       const userInfo = await this.getUserInfoAction({ userId });
@@ -106,7 +114,7 @@ class User extends VuexModule {
 
       // const name = FULL_PAGE_NOT_FOUND_ROUTE.name;
       // name && router.removeRoute(name);
-      goHome && router.push(PageEnum.BASE_HOME);
+      goHome && router.replace(PageEnum.BASE_HOME);
       return userInfo;
     } catch (error) {
       return null;

+ 1 - 0
src/utils/http/axios/Axios.ts

@@ -80,6 +80,7 @@ export class VAxios {
 
     // 请求拦截器配置处理
     this.axiosInstance.interceptors.request.use((config: AxiosRequestConfig) => {
+      // If cancel repeat request is turned on, then cancel repeat request is prohibited
       const { headers: { ignoreCancelToken } = { ignoreCancelToken: false } } = config;
       !ignoreCancelToken && axiosCanceler.addPending(config);
       if (requestInterceptors && isFunction(requestInterceptors)) {

+ 2 - 2
src/utils/http/axios/index.ts

@@ -58,7 +58,7 @@ const transform: AxiosTransform = {
         // errorMessageMode=‘modal’的时候会显示modal错误弹窗,而不是消息提示,用于一些比较重要的错误
         if (options.errorMessageMode === 'modal') {
           createErrorModal({ title: t('sys.api.errorTip'), content: message });
-        } else {
+        } else if (options.errorMessageMode === 'message') {
           createMessage.error(message);
         }
       }
@@ -201,7 +201,7 @@ function createAxios(opt?: Partial<CreateAxiosOptions>) {
           // 格式化提交参数时间
           formatDate: true,
           // 消息提示类型
-          errorMessageMode: 'none',
+          errorMessageMode: 'message',
           // 接口地址
           apiUrl: globSetting.apiUrl,
         },

+ 3 - 1
src/utils/http/axios/types.ts

@@ -1,6 +1,8 @@
 import type { AxiosRequestConfig } from 'axios';
 import { AxiosTransform } from './axiosTransform';
 
+export type ErrorMessageMode = 'none' | 'modal' | 'message' | undefined;
+
 export interface RequestOptions {
   // 请求参数拼接到url
   joinParamsToUrl?: boolean;
@@ -13,7 +15,7 @@ export interface RequestOptions {
   // 接口地址, 不填则使用默认apiUrl
   apiUrl?: string;
   // 错误消息提示类型
-  errorMessageMode?: 'none' | 'modal';
+  errorMessageMode?: ErrorMessageMode;
 }
 
 export interface CreateAxiosOptions extends AxiosRequestConfig {

+ 224 - 78
src/views/sys/lock/index.vue

@@ -1,85 +1,108 @@
 <template>
-  <div class="lock-page">
-    <div class="lock-page__entry">
-      <div class="lock-page__header">
-        <img src="../../../assets/images/header.jpg" class="lock-page__header-img" />
-        <p class="lock-page__header-name">{{ realName }}</p>
+  <div :class="prefixCls">
+    <div :class="`${prefixCls}__unlock`" @click="handleShowForm(false)" v-show="showDate">
+      <LockOutlined />
+      <span>{{ t('sys.lock.unlock') }}</span>
+    </div>
+
+    <div :class="`${prefixCls}__date`">
+      <div :class="`${prefixCls}__hour`">
+        {{ hour }}
+        <span class="meridiem" v-show="showDate">{{ meridiem }}</span>
       </div>
-      <BasicForm @register="register" v-if="!getIsNotPwd" />
-      <Alert v-if="errMsgRef" type="error" :message="t('alert')" banner />
-      <div class="lock-page__footer">
-        <a-button type="default" class="mt-2 mr-2" @click="goLogin" v-if="!getIsNotPwd">
-          {{ t('sys.lock.backToLogin') }}
-        </a-button>
-        <a-button type="primary" class="mt-2" @click="unLock(!getIsNotPwd)" :loading="loadingRef">
-          {{ t('sys.lock.entry') }}
-        </a-button>
+      <div :class="`${prefixCls}__minute`">{{ minute }} </div>
+    </div>
+    <transition name="fade-slide">
+      <div :class="`${prefixCls}-entry`" v-show="!showDate">
+        <div :class="`${prefixCls}-entry-content`">
+          <div :class="`${prefixCls}-entry__header`">
+            <img src="/@/assets/images/header.jpg" :class="`${prefixCls}-entry__header-img`" />
+            <p :class="`${prefixCls}-entry__header-name`">{{ realName }}</p>
+          </div>
+          <InputPassword :placeholder="t('sys.lock.placeholder')" v-model:value="password" />
+          <span :class="`${prefixCls}-entry__err-msg`" v-if="errMsgRef">
+            {{ t('sys.lock.alert') }}
+          </span>
+          <div :class="`${prefixCls}-entry__footer`">
+            <a-button
+              type="link"
+              size="small"
+              class="mt-2 mr-2"
+              :disabled="loadingRef"
+              @click="handleShowForm(true)"
+            >
+              {{ t('sys.lock.back') }}
+            </a-button>
+            <a-button
+              type="link"
+              size="small"
+              class="mt-2 mr-2"
+              :disabled="loadingRef"
+              @click="goLogin"
+            >
+              {{ t('sys.lock.backToLogin') }}
+            </a-button>
+            <a-button class="mt-2" type="link" size="small" @click="unLock()" :loading="loadingRef">
+              {{ t('sys.lock.entry') }}
+            </a-button>
+          </div>
+        </div>
       </div>
+    </transition>
+
+    <div :class="`${prefixCls}__footer-date`">
+      <div class="time" v-show="!showDate">
+        {{ hour }}:{{ minute }} <span class="meridiem">{{ meridiem }}</span>
+      </div>
+      <div class="date"> {{ year }}/{{ month }}/{{ day }} {{ week }} </div>
     </div>
   </div>
 </template>
 <script lang="ts">
   import { defineComponent, ref, computed } from 'vue';
-  import { Alert } from 'ant-design-vue';
-
-  import { BasicForm, useForm } from '/@/components/Form';
+  import { Alert, Input } from 'ant-design-vue';
 
   import { userStore } from '/@/store/modules/user';
-  import { appStore } from '/@/store/modules/app';
+  import { lockStore } from '/@/store/modules/lock';
   import { useI18n } from '/@/hooks/web/useI18n';
 
+  import { useNow } from './useNow';
+  import { useDesign } from '/@/hooks/web/useDesign';
+
+  import { LockOutlined } from '@ant-design/icons-vue';
+
   export default defineComponent({
     name: 'LockPage',
-    components: { Alert, BasicForm },
+    components: { Alert, LockOutlined, InputPassword: Input.Password },
 
     setup() {
+      const passwordRef = ref('');
       const loadingRef = ref(false);
       const errMsgRef = ref(false);
+      const showDate = ref(true);
+
+      const { prefixCls } = useDesign('lock-page');
+
+      const { start, stop, ...state } = useNow(true);
 
       const { t } = useI18n();
-      const [register, { validateFields }] = useForm({
-        showActionButtonGroup: false,
-        schemas: [
-          {
-            field: 'password',
-            label: '',
-            component: 'InputPassword',
-            componentProps: {
-              style: { width: '100%' },
-              placeholder: t('sys.lock.placeholder'),
-            },
-            rules: [{ required: true }],
-          },
-        ],
-      });
+
       const realName = computed(() => {
         const { realName } = userStore.getUserInfoState || {};
         return realName;
       });
 
-      const getIsNotPwd = computed(() => {
-        if (!appStore.getLockInfo) {
-          return true;
-        }
-        return appStore.getLockInfo.pwd === undefined;
-      });
-
       /**
        * @description: unLock
        */
-      async function unLock(valid = true) {
-        let password = '';
-        if (valid) {
-          try {
-            const values = (await validateFields()) as any;
-            password = values.password;
-          } catch (error) {
-            return;
-          }
+      async function unLock() {
+        if (!passwordRef.value) {
+          return;
         }
+        let password = passwordRef.value;
         try {
           loadingRef.value = true;
-          const res = await appStore.unLockAction({ password, valid });
+          const res = await lockStore.unLockAction({ password });
           errMsgRef.value = !res;
         } finally {
           loadingRef.value = false;
@@ -88,67 +111,190 @@
 
       function goLogin() {
         userStore.loginOut(true);
-        appStore.resetLockInfo();
+        lockStore.resetLockInfo();
+      }
+
+      function handleShowForm(show = false) {
+        showDate.value = show;
       }
 
       return {
-        register,
-        getIsNotPwd,
         goLogin,
         realName,
         unLock,
         errMsgRef,
         loadingRef,
         t,
+        prefixCls,
+        showDate,
+        password: passwordRef,
+        handleShowForm,
+        ...state,
       };
     },
   });
 </script>
 <style lang="less" scoped>
   @import (reference) '../../../design/index.less';
+  @prefix-cls: ~'@{namespace}-lock-page';
 
-  .lock-page {
+  .@{prefix-cls} {
     position: fixed;
     top: 0;
+    right: 0;
+    bottom: 0;
     left: 0;
-    z-index: 999999;
+    z-index: 3000;
     display: flex;
     width: 100vw;
     height: 100vh;
-    background: url(../../../assets/images/lock-page.jpg) no-repeat;
-    background-size: 100% 100%;
+    // background: rgba(23, 27, 41);
+    background: #000;
     align-items: center;
-    justify-content: flex-end;
+    justify-content: center;
+
+    &__unlock {
+      position: absolute;
+      top: 0;
+      left: 50%;
+      display: flex;
+      height: 50px;
+      padding-top: 20px;
+      font-size: 18px;
+      color: #fff;
+      cursor: pointer;
+      transform: translate(-50%, 0);
+      flex-direction: column;
+      align-items: center;
+      justify-content: space-between;
+      transition: all 0.3s;
+    }
+
+    &__date {
+      display: flex;
+      width: 100vw;
+      height: 100vh;
+      align-items: center;
+      justify-content: center;
+    }
 
-    &__entry {
+    &__hour {
       position: relative;
-      width: 400px;
-      // height: 260px;
-      padding: 80px 50px 50px 50px;
-      margin-right: 50px;
-      background: #fff;
-      border-radius: 6px;
+      margin-right: 80px;
+
+      .meridiem {
+        position: absolute;
+        top: 20px;
+        left: 20px;
+        font-size: 26px;
+      }
+      @media (max-width: @screen-xs) {
+        margin-right: 20px;
+      }
     }
 
-    &__header {
+    &__hour,
+    &__minute {
+      display: flex;
+      width: 40%;
+      height: 74%;
+      // font-size: 50em;
+      font-weight: 700;
+      color: #bababa;
+      background: #141313;
+      border-radius: 30px;
+      justify-content: center;
+      align-items: center;
+      // .respond-to(large-only, { font-size: 25em;});
+      // .respond-to(large-only, { font-size: 30em;});
+      @media (min-width: @screen-xxxl-min) {
+        font-size: 46em;
+      }
+      @media (min-width: @screen-xl-max) and (max-width: @screen-xxl-max) {
+        font-size: 38em;
+      }
+
+      @media (min-width: @screen-lg-max) and (max-width: @screen-xl-max) {
+        font-size: 30em;
+      }
+      @media (min-width: @screen-md-max) and (max-width: @screen-lg-max) {
+        font-size: 23em;
+      }
+      @media (min-width: @screen-sm-max) and (max-width: @screen-md-max) {
+        font-size: 19em;
+      }
+      @media (min-width: @screen-xs-max) and (max-width: @screen-sm-max) {
+        font-size: 13em;
+      }
+      @media (max-width: @screen-xs) {
+        height: 50%;
+        font-size: 6em;
+        border-radius: 20px;
+      }
+    }
+
+    &__footer-date {
       position: absolute;
-      top: -35px;
-      left: calc(50% - 45px);
-      width: auto;
-      text-align: center;
+      bottom: 20px;
+      left: 50%;
+      font-family: helvetica;
+      color: #bababa;
+      transform: translate(-50%, 0);
 
-      &-img {
-        width: 70px;
-        border-radius: 50%;
+      .time {
+        font-size: 50px;
+
+        .meridiem {
+          font-size: 32px;
+        }
       }
 
-      &-name {
-        margin-top: 5px;
+      .date {
+        font-size: 26px;
       }
     }
 
-    &__footer {
-      text-align: center;
+    &-entry {
+      position: absolute;
+      top: 0;
+      left: 0;
+      display: flex;
+      width: 100%;
+      height: 100%;
+      background: rgba(0, 0, 0, 0.5);
+      backdrop-filter: blur(10px);
+      justify-content: center;
+      align-items: center;
+
+      &-content {
+        width: 260px;
+      }
+
+      &__header {
+        text-align: center;
+
+        &-img {
+          width: 70px;
+          border-radius: 50%;
+        }
+
+        &-name {
+          margin-top: 5px;
+          font-weight: 500;
+          color: #bababa;
+        }
+      }
+
+      &__err-msg {
+        display: inline-block;
+        margin-top: 10px;
+        color: @error-color;
+      }
+
+      &__footer {
+        display: flex;
+        justify-content: space-between;
+      }
     }
   }
 </style>

+ 63 - 0
src/views/sys/lock/useNow.ts

@@ -0,0 +1,63 @@
+import moment from 'moment';
+import { reactive, toRefs } from 'vue';
+import { tryOnMounted, tryOnUnmounted } from '/@/utils/helper/vueHelper';
+import { useLocaleSetting } from '/@/hooks/setting/useLocaleSetting';
+
+export function useNow(immediate = true) {
+  const { getLang } = useLocaleSetting();
+  const localData = moment.localeData(getLang.value);
+  let timer: IntervalHandle;
+
+  const state = reactive({
+    year: 0,
+    month: 0,
+    week: '',
+    day: 0,
+    hour: '',
+    minute: '',
+    second: 0,
+    meridiem: '',
+  });
+
+  const update = () => {
+    const now = moment();
+
+    const h = now.format('HH');
+    const m = now.format('mm');
+    const s = now.get('s');
+
+    state.year = now.get('y');
+    state.month = now.get('M');
+    state.week = localData.weekdays()[now.day()];
+    state.day = now.get('D');
+    state.hour = h;
+    state.minute = m;
+    state.second = s;
+
+    state.meridiem = localData.meridiem(Number(h), Number(h), true);
+  };
+
+  function start() {
+    update();
+    clearInterval(timer);
+    timer = setInterval(() => update(), 1000);
+  }
+
+  function stop() {
+    clearInterval(timer);
+  }
+
+  tryOnMounted(() => {
+    immediate && start();
+  });
+
+  tryOnUnmounted(() => {
+    stop();
+  });
+
+  return {
+    ...toRefs(state),
+    start,
+    stop,
+  };
+}