zouyu
2025-03-19 45792b3776cda2e1ada31755ffc226a663f90b48
锁屏功能
已添加5个文件
已修改7个文件
501 ■■■■■ 文件已修改
src/assets/images/lock_bg.png 补丁 | 查看 | 原始文档 | blame | 历史
src/const/website.js 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/layout/components/Navbar.vue 78 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/layout/components/lock/index.vue 121 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/layout/components/top-lock.vue 73 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/permission.js 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/router/index.js 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/store/getters.js 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/store/modules/app.js 56 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/store.js 103 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/validate.js 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/login.vue 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/images/lock_bg.png
src/const/website.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,25 @@
export default {
  title: 'ZTTC-LIMS',
  copyright: 'Copyright Â© 2021 chinaztt.com. All rights reserved.',
  isFirstPage: true, // é…ç½®é¦–页不可关闭
  key: 'ztt', // é…ç½®ä¸»é”®,目前用于存储
  whiteList: ['/login', '/404', '/401', '/lock'], // é…ç½®æ— æƒé™å¯ä»¥è®¿é—®çš„页面
  whiteTagList: ['/login', '/404', '/401', '/lock'], // é…ç½®ä¸æ·»åŠ tags页面 ï¼ˆ'/advanced-router/mutative-detail/*'——*为通配符)
  fistPage: {
    label: '首页',
    value: '/wel/index',
    params: {},
    query: {},
    group: [],
    close: false
  },
  // é…ç½®èœå•的属性
  menu: {
    props: {
      label: 'label',
      path: 'path',
      icon: 'icon',
      children: 'children'
    }
  }
}
src/layout/components/Navbar.vue
@@ -9,8 +9,7 @@
      <span class="label">LIMS实验室管理系统</span>
    </div>
    <div class="right-menu">
      <div class="avatar-wrapper">
        <!-- <img :src="avatar" class="user-avatar" /> -->
      <!-- <div class="avatar-wrapper">
        <el-avatar :size="28">{{ nickName.substring(0, 1) }}</el-avatar>
        <span class="userName">{{ nickName }}</span>
        <img
@@ -19,54 +18,51 @@
          @click="logout"
          title="退出账号"
        />
      </div> -->
      <template v-if="device !== 'mobile'">
        <el-tooltip
          class="right-menu-item"
          effect="dark"
          content="锁屏"
          placement="bottom"
        >
          <top-lock />
        </el-tooltip>
      </template>
      <el-dropdown
        class="avatar-container right-menu-item hover-effect"
        trigger="click"
      >
        <div class="avatar-wrapper">
          <img :src="avatar" class="user-avatar" />
          <i class="el-icon-caret-bottom" />
      </div>
      <!--      <template v-if="device!=='mobile'">-->
      <!--        <search id="header-search" class="right-menu-item" />-->
      <!--        <el-tooltip content="源码地址" effect="dark" placement="bottom">-->
      <!--          <ruo-yi-git id="ruoyi-git" class="right-menu-item hover-effect" />-->
      <!--        </el-tooltip>-->
      <!--        <el-tooltip content="文档地址" effect="dark" placement="bottom">-->
      <!--          <ruo-yi-doc id="ruoyi-doc" class="right-menu-item hover-effect" />-->
      <!--        </el-tooltip>-->
      <!--        <screenfull id="screenfull" class="right-menu-item hover-effect" />-->
      <!--        <el-tooltip content="布局大小" effect="dark" placement="bottom">-->
      <!--          <size-select id="size-select" class="right-menu-item hover-effect" />-->
      <!--        </el-tooltip>-->
      <!--      </template>-->
      <!--      <div class="avatar-container">-->
      <!--        <el-dropdown-menu slot="dropdown">-->
      <!--          <router-link to="/user/profile">-->
      <!--            <el-dropdown-item>个人中心</el-dropdown-item>-->
      <!--          </router-link>-->
      <!--          <el-dropdown-item @click.native="setting = true">-->
      <!--            <span>布局设置</span>-->
      <!--          </el-dropdown-item>-->
      <!--          <el-dropdown-item divided @click.native="logout">-->
      <!--            <span>退出登录</span>-->
      <!--          </el-dropdown-item>-->
      <!--        </el-dropdown-menu>-->
      <!--      </div>-->
        <el-dropdown-menu slot="dropdown">
          <router-link to="/user/profile">
            <el-dropdown-item>个人中心</el-dropdown-item>
          </router-link>
          <el-dropdown-item @click.native="setting = true">
            <span>布局设置</span>
          </el-dropdown-item>
          <el-dropdown-item divided @click.native="logout">
            <span>退出登录</span>
          </el-dropdown-item>
        </el-dropdown-menu>
      </el-dropdown>
    </div>
  </div>
</template>
<script>
import { mapGetters } from "vuex";
import { mapGetters, mapState } from "vuex";
import Breadcrumb from "@/components/Breadcrumb";
import TopNav from "@/components/TopNav";
import Hamburger from "@/components/Hamburger";
import Screenfull from "@/components/Screenfull";
import SizeSelect from "@/components/SizeSelect";
import Search from "@/components/HeaderSearch";
import RuoYiGit from "@/components/RuoYi/Git";
import RuoYiDoc from "@/components/RuoYi/Doc";
import topLock from "./top-lock";
export default {
  components: {
    Breadcrumb,
@@ -75,10 +71,12 @@
    Screenfull,
    SizeSelect,
    Search,
    RuoYiGit,
    RuoYiDoc,
    topLock,
  },
  computed: {
    ...mapState({
      showLock: (state) => state.app.showLock,
    }),
    ...mapGetters(["avatar", "device", "nickName"]),
    setting: {
      get() {
src/layout/components/lock/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,121 @@
<template>
  <div class="lock-container">
    <div class="lock-form animated bounceInDown">
      <div class="animated" :class="{ shake: passwdError, bounceOut: pass }">
        <h3 class="title">{{ loginUserInfo.nickName }}</h3>
        <el-input
          placeholder="请输入登录密码"
          type="password"
          class="input-with-select animated"
          v-model="passwd"
          @keyup.enter.native="handleLogin"
        >
          <el-button
            slot="append"
            icon="el-icon-unlock"
            @click="handleLogin"
          ></el-button>
          <el-button
            slot="append"
            icon="el-icon-switch-button"
            @click="handleLogout"
          ></el-button>
        </el-input>
      </div>
    </div>
  </div>
</template>
<script>
import { mapGetters } from "vuex";
export default {
  name: "lock",
  data() {
    return {
      passwd: "",
      passwdError: false,
      pass: false,
    };
  },
  created() {},
  mounted() {},
  computed: {
    ...mapGetters([
      "visitedViews",
      "iframeViews",
      "cachedViews",
      "lockPasswd",
      "loginUserInfo",
    ]),
  },
  props: [],
  methods: {
    handleLogout() {
      this.$confirm("是否退出系统, æ˜¯å¦ç»§ç»­?", "提示", {
        confirmButtonText: "确定",
        cancelButtonText: "取消",
        closeOnClickModal: false,
        type: "warning",
      }).then(() => {
        this.$store.dispatch("LogOut").then(() => {
          this.$router.push({ path: "/login" });
        });
      });
    },
    handleLogin() {
      if (this.passwd != this.lockPasswd) {
        this.passwd = "";
        this.$message({
          message: "解锁密码错误,请重新输入",
          type: "error",
        });
        this.passwdError = true;
        setTimeout(() => {
          this.passwdError = false;
        }, 1000);
        return;
      }
      this.pass = true;
      setTimeout(() => {
        this.$store.dispatch("app/clearLock");
        //重定向路由还需调整
        let path =
          this.visitedViews && this.visitedViews.length > 0
            ? this.visitedViews.pop().fullPath
            : "/index";
        this.$router.push({
          path: path,
        });
      }, 1000);
    },
  },
  components: {},
};
</script>
<style lang="scss">
.lock-container {
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  position: relative;
  .title {
    text-align: center;
    margin-bottom: 8px;
  }
}
.lock-container::before {
  z-index: -999;
  content: "";
  position: absolute;
  left: 0;
  top: 0;
  width: 100%;
  height: 100%;
  background-image: url("~@/assets/images/lock_bg.png");
  background-size: cover;
}
.lock-form {
  width: 300px;
}
</style>
src/layout/components/top-lock.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,73 @@
<template>
  <span>
    <i class="fa fa-lock" @click="handleLock"></i>
    <el-dialog
      title="设置锁屏密码"
      :visible.sync="box"
      width="30%"
      append-to-body
    >
      <el-form :model="form" ref="form" label-width="80px">
        <el-form-item
          label="锁屏密码"
          prop="passwd"
          :rules="[{ required: true, message: '锁屏密码不能为空' }]"
        >
          <el-input
            v-model="form.passwd"
            placeholder="请输入锁屏密码"
          ></el-input>
        </el-form-item>
      </el-form>
      <span slot="footer" class="dialog-footer">
        <el-button type="primary" @click="handleSetLock">ç¡® å®š</el-button>
      </span>
    </el-dialog>
  </span>
</template>
<script>
import { validatenull } from "@/utils/validate";
import { mapGetters } from "vuex";
export default {
  name: "top-lock",
  data() {
    return {
      box: false,
      form: {
        passwd: "",
      },
    };
  },
  created() {},
  mounted() {},
  computed: {
    ...mapGetters(["lockPasswd"]),
  },
  props: [],
  methods: {
    handleSetLock() {
      this.$refs["form"].validate((valid) => {
        if (valid) {
          this.$store.dispatch("app/setLockPasswd", this.form.passwd);
          // this.$store.commit("SET_LOCK_PASSWD", this.form.passwd);
          this.handleLock();
        }
      });
    },
    handleLock() {
      if (validatenull(this.lockPasswd)) {
        this.box = true;
        return;
      }
      this.$store.dispatch("app/setLock");
      setTimeout(() => {
        this.$router.push({ path: "/lock" });
      }, 100);
    },
  },
  components: {},
};
</script>
<style lang="scss" scoped></style>
src/permission.js
@@ -43,9 +43,13 @@
            })
          })
      } else {
          if (store.getters.isLock && to.path !=='/lock') {
            next('/lock')
          } else {
        next()
      }
    }
    }
  } else {
    // æ²¡æœ‰token
    if (isWhiteList(to.path)) {
src/router/index.js
@@ -74,6 +74,11 @@
    hidden: true,
  },
  {
    path: "/lock",
    component: () => import("@/layout/components/lock/index"),
    hidden: true,
  },
  {
    path: "",
    component: Layout,
    redirect: "index",
src/store/getters.js
@@ -19,5 +19,7 @@
  topbarRouters: (state) => state.permission.topbarRouters,
  defaultRoutes: (state) => state.permission.defaultRoutes,
  sidebarRouters: (state) => state.permission.sidebarRouters,
  isLock: (state) => state.app.isLock,
  lockPasswd: (state) => state.app.lockPasswd,
};
export default getters;
src/store/modules/app.js
@@ -1,5 +1,6 @@
import Cookies from 'js-cookie'
import website from '@/const/website'
import { getStore, removeStore, setStore } from '@/utils/store'
const state = {
  sidebar: {
    opened: Cookies.get('sidebarStatus') ? !!+Cookies.get('sidebarStatus') : true,
@@ -7,7 +8,11 @@
    hide: false
  },
  device: 'desktop',
  size: Cookies.get('size') || 'medium'
  size: Cookies.get('size') || 'medium',
  lockPasswd: getStore({ name: 'lockPasswd' }) || '',
  isLock: getStore({ name: 'isLock' }) || false,
  website: website,
  showLock: getStore({ name: 'showLock' }),
}
const mutations = {
@@ -37,7 +42,41 @@
  },
  SET_SIDEBAR_HIDE: (state, status) => {
    state.sidebar.hide = status
  }
  },
  SET_LOCK_PASSWD: (state, lockPasswd) => {
    state.lockPasswd = lockPasswd
    setStore({
      name: 'lockPasswd',
      content: state.lockPasswd,
      type: 'session'
    })
  },
  CLEAR_LOCK: (state) => {
    state.isLock = false
    state.lockPasswd = ''
    removeStore({
      name: 'lockPasswd'
    })
    removeStore({
      name: 'isLock',
      type: 'session'
    })
  },
  SET_LOCK: (state) => {
    state.isLock = true
    setStore({
      name: 'isLock',
      content: state.isLock,
      type: 'session'
    })
  },
  SET_SHOW_LOCK: (state, active) => {
    state.showLock = active
    setStore({
      name: 'showLock',
      content: state.showLock
    })
  },
}
const actions = {
@@ -55,7 +94,16 @@
  },
  toggleSideBarHide({ commit }, status) {
    commit('SET_SIDEBAR_HIDE', status)
  }
  },
  setLockPasswd({ commit }, lockPasswd) {
    commit('SET_LOCK_PASSWD', lockPasswd)
  },
  setLock({ commit }) {
    commit('SET_LOCK')
  },
  clearLock({ commit }) {
    commit('CLEAR_LOCK')
  },
}
export default {
src/utils/store.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,103 @@
import { validatenull } from '@/utils/validate'
import website from '@/const/website'
const keyName = website.key + '-'
/**
 * å­˜å‚¨localStorage
 */
export const setStore = (params = {}) => {
  let { name, content, type } = params
  name = keyName + name
  const obj = {
    dataType: typeof content,
    content: content,
    type: type,
    datetime: new Date().getTime()
  }
  if (type) window.sessionStorage.setItem(name, JSON.stringify(obj))
  else window.localStorage.setItem(name, JSON.stringify(obj))
}
/**
 * èŽ·å–localStorage
 */
export const getStore = (params = {}) => {
  let { name, debug } = params
  name = keyName + name
  let obj = {}
  let content
  obj = window.sessionStorage.getItem(name)
  if (validatenull(obj)) obj = window.localStorage.getItem(name)
  if (validatenull(obj)) return
  try {
    obj = JSON.parse(obj)
  } catch (e) {
    return obj
  }
  if (debug) {
    return obj
  }
  if (obj.dataType === 'string') {
    content = obj.content
  } else if (obj.dataType === 'number') {
    content = Number(obj.content)
  } else if (obj.dataType === 'boolean') {
    content = eval(obj.content)
  } else if (obj.dataType === 'object') {
    content = obj.content
  }
  return content
}
/**
 * åˆ é™¤localStorage
 */
export const removeStore = (params = {}) => {
  let { name, type } = params
  name = keyName + name
  if (type) {
    window.sessionStorage.removeItem(name)
  } else {
    window.localStorage.removeItem(name)
  }
}
/**
 * èŽ·å–å…¨éƒ¨localStorage
 */
export const getAllStore = (params = {}) => {
  const list = []
  const { type } = params
  if (type) {
    for (let i = 0; i <= window.sessionStorage.length; i++) {
      list.push({
        name: window.sessionStorage.key(i),
        content: getStore({
          name: window.sessionStorage.key(i),
          type: 'session'
        })
      })
    }
  } else {
    for (let i = 0; i <= window.localStorage.length; i++) {
      list.push({
        name: window.localStorage.key(i),
        content: getStore({
          name: window.localStorage.key(i)
        })
      })
    }
  }
  return list
}
/**
 * æ¸…空全部localStorage
 */
export const clearStore = (params = {}) => {
  const { type } = params
  if (type) {
    window.sessionStorage.clear()
  } else {
    window.localStorage.clear()
  }
}
src/utils/validate.js
@@ -111,4 +111,32 @@
    return Object.prototype.toString.call(arg) === '[object Array]'
  }
  return Array.isArray(arg)
}
/**
 * åˆ¤æ–­æ˜¯å¦ä¸ºç©º
 */
export function validatenull(val) {
  if (typeof val === 'boolean') {
    return false
  }
  if (typeof val === 'number') {
    return false
  }
  if (val instanceof Array) {
    if (val.length === 0) return true
  } else if (val instanceof Object) {
    if (JSON.stringify(val) === '{}') return true
  } else {
    if (
      val === 'null' ||
      val == null ||
      val === 'undefined' ||
      val === undefined ||
      val === ''
    )
      return true
    return false
  }
  return false
}
src/views/login.vue
@@ -41,7 +41,7 @@
            />
          </el-input>
        </el-form-item>
        <el-form-item prop="code" v-if="captchaEnabled">
        <!-- <el-form-item prop="code" v-if="captchaEnabled">
          <el-input
            v-model="loginForm.code"
            auto-complete="off"
@@ -58,7 +58,7 @@
          <div class="login-code">
            <img :src="codeUrl" @click="getCode" class="login-code-img" />
          </div>
        </el-form-item>
        </el-form-item> -->
        <el-checkbox
          v-model="loginForm.rememberMe"
          style="margin: 0px 0px 25px 0px"
@@ -121,7 +121,7 @@
      },
      loading: false,
      // éªŒè¯ç å¼€å…³
      captchaEnabled: true,
      captchaEnabled: false,
      // æ³¨å†Œå¼€å…³
      register: false,
      redirect: undefined,