Vue实现锁屏功能

这两天刚做了个新需求,要做个系统锁屏(当然锁的是当前的系统),就类似于电脑锁屏似的。

共两种情况下锁屏,一种是无操作一定时间后自动锁屏;第二种是可以按下组合键(快捷键)主动进行锁屏。下面具体来说一下该需求的想法、思路以及实现

然后再说下想法思路吧。首先实现锁屏一般也就两种方式:

一种是去写个页面,当达到条件时要跳转到该锁屏页面,但是有一些要注意的地方, 比如:如果跳转的时候使用的$router.push,那么我是可以通过点击浏览器的回退按钮回到上一个页面的,这个要做一下限制。还有一种可能是我如果直接在浏览器上输入某个页面的路径,那么是不是也会跳转?所以我们要有个全局的变量来记录当前是不是锁屏状态,如果是锁屏状态,我们要对其进行限制。当然这只是举例的两种情况,真实做起来或许还有其他情况,反正要考虑周全,不然后面会出现你意想不到的bug

第二种就是直接去写遮罩层了,也是我当前实现锁屏的方式。这种方式相对上面来讲,会比较简单一些,也还算方便,我们只需要在达到条件后将该遮罩层显示出来就可以了,当然遮罩层的优先级肯定要设置最高的,要不然怎么覆盖掉任意当前的页面呢,也正是因为这个优先级最高,导致在这个遮罩层组件中有些$message这样的消息提示或弹窗显示不出来。这也算是一个缺陷。不过一般锁屏页面也没什么太多操作,主要是页面有个背景图,然后输入解锁密码进行解锁就可以了,所以影响并不大。还有一种需要注意,就是我现在已经锁屏页面了,我这时候打开控制台,然后是不是可以去修改这个遮罩层的样式呢?比如我给遮罩层加个display: none; 遮罩层是不是没了?答案是的,所以我们要做限制! 打开控制台的方式无非也就两种方式,一种是F12(Fn + F12),另一种是鼠标右键,然后点击检查。这样,我们锁屏出来的时候就监听键盘按下事件,阻止F12的按键,同时也阻止鼠标右键行为,这样就可以了,看上去简单粗暴哈,但也是为了实现这么个需求嘛。

下面再说一下实现的方式。

首先我把遮罩层单独写了个组件,里面也都是遮罩层的样式、逻辑。注意这是个组件,而不是个页面,哪里用到哪里引入就可以了。

然后就是遮罩层什么时候显示的问题了,既然是锁屏,那么肯定是跟登录没有关系的,也就是我们要刚一进系统就开始计时了,比如我们的需求是一分钟无操作后就要锁屏,那么我们只需要这个计时无操作的时间就可以了,什么算是操作了?什么算是无操作,鼠标点击就是操作了,鼠标双击是操作了,鼠标右键是操作了,鼠标移动是操作了,鼠标滚轮是操作了,键盘按下是操作了,基本就这些事件,也就是我们在刚进入系统时就要监听这些事件,同时也开始计时(计时就是定义变量初始值为0,然后setInterval每隔1s加1),如果有其中某个事件触发了,我们就要清除掉定时器,并将变量初始化为初始值也就是0,并重新计时,基本跟防抖差不多。然后我们无操作满足时间后会显示遮罩层,也就是进入锁屏状态,这个是,我们就要把那些事件监听移除掉,并清除掉定时器,但是注意不要将变量初始化为0,因为我们要通过这个变量和我们设置时间做对比来决定是否显示遮罩层,进入遮罩层之后就是遮罩层的操作了,解锁后呢,我们将变量的值初始化为0,隐藏遮罩层,并重新监听那些事件,重新计时就可以了。

哎哟,说太多了说太多了~    下面上代码了

我们首先在.env(development / production)的文件中定义个变量,这个变量就是无操作多长时间进入锁屏,单位 s。也就是可配置的

# 锁屏时间(s)
VUE_APP_LOCK_TIME = 300

这个也就是说无操作5分钟后进入锁屏状态 

然后我们需要在Vuex中保存一个计时时间lockTime,也就是当这个时间大于等于我们设置的那个时间就进入锁屏,即 lockTime >= parseInt(process.env.VUE_APP_LOCK_TIME)

我这里分了个模块 common (src/store/modules/common.js)

export default {
  namespaced: true,
  state: {
    // 无操作计时时间
    lockTime: 0
  },
  mutations: {
    updatelockTime(state, lockTime) {
      state.lockTime = lockTime
    }
  }
}

然后是遮罩层(锁屏)什么时候进行显示的逻辑: 

我这里是封装在了mixins中,这是为了可以把锁屏的逻辑单独抽离出来,逻辑更清晰一些,后面如果说我不想在整个系统锁屏了,我只要在某些页面中,也就是只在系统的A页面、B页面中才会有锁屏,其他都不需要锁屏,那我们直接将逻辑混入到组件就可以了。

// 引入锁屏遮罩层组件
import Lock from '@/components/lock'

export default {
  data() {
    return {
      timer: null
    }
  },
  components: { Lock },
  computed: {
    isLock() {
      return this.lockTime >= parseInt(process.env.VUE_APP_LOCK_TIME)
    },
    lockTime: {
      get() {
        return this.$store.state.common.lockTime
      },
      set(lockTime) {
        this.$store.commit('common/updatelockTime', lockTime)
      }
    }
  },
  watch: {
    isLock() {
      if (this.isLock) {
        this.removeHandle()
      } else {
        this.init()
      }
    }
  },
  mounted() {
    this.init()
  },
  destroyed() {
    this.removeHandle()
  },
  methods: {
    init() {
      this.reckonByTime()
      document.addEventListener('click', this.clickHandle)
      document.addEventListener('dblclick', this.dblclickHandle)
      document.addEventListener('contextmenu', this.contextmenuHandle)
      document.addEventListener('mousemove', this.mousemoveHandle)
      document.addEventListener('mousewheel', this.mousewheelHandle)
      document.addEventListener('keydown', this.keydownHandle)
    },
    // 移除事件监听、定时器等
    removeHandle() {
      if (this.timer) {
        clearInterval(this.timer)
        this.timer = null
      }
      document.removeEventListener('click', this.clickHandle)
      document.removeEventListener('dblclick', this.dblclickHandle)
      document.removeEventListener('contextmenu', this.contextmenuHandle)
      document.removeEventListener('mousemove', this.mousemoveHandle)
      document.removeEventListener('mousewheel', this.mousewheelHandle)
      document.removeEventListener('keydown', this.keydownHandle)
    },
    // 无操作计时
    reckonByTime() {
      if (this.timer) {
        this.lockTime = 0
        clearInterval(this.timer)
        this.timer = null
      }
      this.timer = setInterval(() => {
        this.lockTime += 1
      }, 1000)
    },
    // 鼠标点击
    clickHandle() {
      this.reckonByTime()
    },
    // 鼠标双击
    dblclickHandle() {
      this.reckonByTime()
    },
    // 鼠标右键
    contextmenuHandle() {
      this.reckonByTime()
    },
    // 鼠标移动
    mousemoveHandle() {
      this.reckonByTime()
    },
    // 鼠标滚轮
    mousewheelHandle() {
      this.reckonByTime()
    },
    // 键盘按下
    keydownHandle(event) {
      const { altKey, ctrlKey, keyCode } = event
      if (altKey && ctrlKey && keyCode == 76) {
        // Ctrl + Alt + L 快捷键直接锁屏
        this.lockTime = parseInt(process.env.VUE_APP_LOCK_TIME)
      } else {
        this.reckonByTime()
      }
    }
  }
}

然后是锁屏组件(遮罩层组件) 

@/components/lock/index.vue 

<template>
  <div class="lock">
    <div>
      <img src="../../assets/img/lock/lock-icon.png" alt="">
      <el-input class="lockPasswdInp" v-show="!passwdError" ref="lockPasswdInp" size="small" show-password placeholder="密码" v-model="passwd">
        <template slot="append">
          <el-button @click="unLock()" style="background: transparent;">
            <i class="el-icon-right"></i>
          </el-button>
        </template>
      </el-input>
      <div class="passwdError-container" v-show="passwdError">
        <div class="passwdError">密码不正确。请再试一次。</div>
        <div class="confirm-btn" @click="reset()">确认</div>
      </div>
    </div>
    <!-- <img @click="logout" class="logout" src="../../assets/img/system/logout.png"> -->
  </div>
</template>
<script>
/**
 * 注意:由于遮罩层优先级太高 类似于$message之类的消息提示等会显示不出来
 */
export default {
  data() {
    return {
      passwd: '',
      passwdError: false
    }
  },
  computed: {
    lockPassword: () => process.env.VUE_APP_LOCK_PASSWORD || '123456'
  },
  mounted() {
    this.reset()
    document.addEventListener('keydown', this.keyDownHandle)
    document.addEventListener('contextmenu', this.contextmenuHandle)
  },
  destroyed() {
    document.removeEventListener('keydown', this.keyDownHandle)
    document.removeEventListener('contextmenu', this.contextmenuHandle)
  },
  methods:{
    // 重置初始化
    reset() {
      this.passwdError = false
      this.passwd = ''
      this.$nextTick(() => {
        this.$refs.lockPasswdInp.focus()
      })
    },
    // 解锁
    unLock() {
      if(this.passwd != this.lockPassword) {
        return this.passwdError = true
      }
      this.$store.commit('common/updatelockTime', 0)
    },
    // 监听鼠标按下事件,阻止 F12 事件
    keyDownHandle(event) {
      if(event.keyCode == 13) {
        if(this.passwdError) {
          this.reset()
        } else {
          this.unLock()
        }
      }
      return (event.keyCode !== 123 ) || (event.returnValue = false)
    },
    // 阻止鼠标右键事件
    contextmenuHandle(event) {
      return event.returnValue = false;
    },
    // // 退出登录
    // logout() {
    //   this.$store.commit('common/updatelockTime', 0)
    //   this.$router.replace('/login')
    //   /**
    //    * 走接口 清楚本地缓存等
    //    * ...
    //    */
    // }
  }
}
</script>
<style scoped>
.lock {
  width: 100%;
  height: 100vh;
  background: #ccc;
  position: fixed;
  top: 0;
  left: 0;
  z-index: 999999;
  display: flex;
  justify-content: center;
  align-items: center;
  background-image: url('../../assets/img/lock/lock-bg.jpg');
  background-repeat: no-repeat;
  background-size: 100% 100%;
}
.lock > div {
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
}

.lockPasswdInp {
  margin-top: 8px;
}

/deep/ .el-input__inner {
  background-color: transparent !important;
  border: 1px solid #0076c8 !important;
  color: #fff;
}
/deep/ .el-input-group__append {
  background-color: rgba(6, 14, 22, .5);
  border: 1px solid #0076c8;
  border-left-color: transparent;
}

/deep/ .el-input-group__append:hover {
  background-color: rgba(6, 14, 22, .6);
  cursor: pointer;
}

.passwdError-container {
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
}

.passwdError {
  width: 260px;
  text-align: center;
  color: #fff;
  font-size: 13px;
  margin: 10px 0;
}

.confirm-btn {
  width: 70px;
  height: 28px;
  text-align: center;
  line-height: 28px;
  color: #fff;
  font-size: 13px;
  background-color: rgba(6, 14, 22, .5);
  border: 1px solid #0076c8;
  border-radius: 3px;
  cursor: pointer;
}

.confirm-btn:hover {
  background-color: rgba(6, 14, 22, .8);
}

/* .logout {
  position: fixed;
  bottom: 20px;
  right: 20px;
  height: 40px;
  cursor: pointer;
} */
</style>

最后App.vue中混入上面的逻辑lock.js,然后引入lock.vue组件并使用 

因为我的需求是整个系统都要有锁屏的功能,所以我将其写在App.vue中 

<template>
  <div id="app">
    <router-view />
    <Lock v-if="isLock" />
  </div>
</template>
<script>
import lockMixin from "@/mixins/lock";
export default {
  mixins: [lockMixin]
}
</script>
<style lang="scss">
* {
  margin: 0;
  padding: 0;
}

#app {
  width: 100vw;
  height: 100vh;
  overflow-y: hidden;
}
</style>

最好再给大家上个锁屏遮罩层的样式效果吧,我这个当然是在无操作5分钟才显示出来的哈 

  

好了,这篇就这么多^v^ 

猜你喜欢

转载自blog.csdn.net/m0_51431448/article/details/131095704