likes
comments
collection
share

手把手教你实现一个vue3+ts+nodeJS后台管理系统(二十)

作者站长头像
站长
· 阅读数 51

前言

已经实现了router、vuex模块,并能够显示对应角色的权限菜单,接下来我们就完成登录模块看看实现的效果。但是之前的layout模块我们遍历的路由还是前端静态的路由,应该让他使用vuex中拼接后的路由,还有一些地方需要添加和修改(导航栏显示用户数据、重置密码模块、退出登录等)。

修改layout模块

我们导航栏用户信息操作的区域有一个重置密码的功能,主要步骤为需要弹窗弹出表单、校验信息、提交表单,我们按步来实现。为了防止代码过多,我们将重置密码模块与主模块分开,由于重置密码的弹窗是显示在layout主页面index.vue)上,所以弹窗的开关也得在主页面上,主页面只有弹窗。另外添加一个form表单文件(有旧密码、新密码、确认密码字段),主页面引入即可。我们先看看layout总体的目录结构

手把手教你实现一个vue3+ts+nodeJS后台管理系统(二十)

添加重置密码模块

components/layout/resetPassword.vue

<template>
  <div>
    <el-form ref="ruleFormRef" :model="user" :rules="rules" label-width="80px">
      <el-form-item label="旧密码" prop="oldPassword">
        <el-input v-model="user.old_password" placeholder="请输入旧密码" type="password" />
      </el-form-item>
      <el-form-item label="新密码" prop="newPassword">
        <el-input v-model="user.password" placeholder="请输入新密码" type="password" />
      </el-form-item>
      <el-form-item label="确认密码" prop="confirmPassword">
        <el-input v-model="user.repassword" placeholder="请确认密码" type="password" />
      </el-form-item>
    </el-form>
    <div slot="footer" class="dialog-footer">
      <el-button @click="close(ruleFormRef)">取消</el-button>
      <el-button type="primary" @click="submitForm(ruleFormRef)">保存</el-button>
    </div>
  </div>
</template>
​
<script setup lang="ts">
import { ref, reactive } from 'vue';
import { ElMessage, FormInstance } from 'element-plus'
import { updatePwd } from '@/utils/API/user/user';
import { store } from '@/store';
import router from '@/router';
// 引入弹窗关闭的方法
const emit = defineEmits(['closeDialog'])
// 表单实例
const ruleFormRef = ref<FormInstance>()
// 表单数据对象
const user = reactive<resetpass>({
  old_password: '',
  password: '',
  repassword: ''
})
// 校验信息对象
const validatePass = (rule: any, value: any, callback: any) => {
  if (value === '') {
    callback(new Error('请输入新密码'))
  } else {
    if (user.repassword !== '') {
      if (!ruleFormRef.value) return
      ruleFormRef.value.validateField('confirmPassword', () => null)
    }
    callback()
  }
}
const validatePass2 = (rule: any, value: any, callback: any) => {
  if (value === '') {
    callback(new Error('请再次输入确认密码'))
  } else if (value !== user.password) {
    callback(new Error("两次输入的密码不匹配!"))
  } else {
    callback()
  }
}
// 校验规则
const rules = reactive({
  old_password: [
    { required: true, message: "旧密码不能为空", trigger: "blur" }
  ],
  password: [{ validator: validatePass, trigger: 'blur' }],
  repassword: [{ validator: validatePass2, trigger: 'blur' }],
})
// 提交表单的方法
const submitForm = (formEl: FormInstance | undefined) => {
  if (!formEl) return
  formEl.validate((valid) => {
    const form = {
      user_id: store.state.user.user_id,
      ...user
    }
    if (valid) {
      updatePwd(form).then(res => {
        ElMessage.success('重置密码成功')
        // 重置密码后重新登录
        store.dispatch('user/FedLogOut').then(() => {
          router.push('/login')
        });
        emit('closeDialog')
      })
    }
  })
}
// 关闭弹窗的方法
const close = (formEl: FormInstance | undefined) => {
  // 清空弹窗
  formEl?.resetFields()
  emit('closeDialog')
}
​
</script>

然后在主文件(index.vue)中添加一个弹窗内置此表单文件,再添加一个关闭弹窗的方法传递给表单组件即可。

<template>
  <div class="app-container">
    ...
    <el-dialog v-model="dialogFormVisible" width="30%" title="重置密码">
      <reset-password @closeDialog="closeDialog"></reset-password>
    </el-dialog>
  </div>
</template>
​
<script setup lang="ts">
...
const dialogFormVisible = ref(false)
...
const closeDialog = () => {
  dialogFormVisible.value = false
}
</script>

导航栏显示用户数据及退出登录

index.vue html结构

主要就是利用vuex的全局状态显示用户信息

手把手教你实现一个vue3+ts+nodeJS后台管理系统(二十)

index.vue js代码

退出登录方法主要是点击后显示确认框,确认后调用vuex中的异步函数,清空用户信息、token等并重置路由然后跳转到登录页重新登录

<script setup lang="ts">
import { ref, computed } from 'vue'
import {
  Tools,
} from '@element-plus/icons-vue'
import { useStore } from '@/store';
import { useRouter } from 'vue-router'
import { ElMessageBox } from 'element-plus';
import ResetPassword from './resetPassword.vue';
import SidebarItem from './SidebarItem.vue';
// 导入vuex模块
const store = useStore()
// 导入router模块
const router = useRouter()
// 获得用户权限菜单
const routes = computed(() => store.state.permission.routes);
// 表单显示标识
const dialogFormVisible = ref(false)
// 高亮的菜单项
const activeIndex = ref('1')
// 退出登录的方法
const logout = () => {
  ElMessageBox.confirm(
    '确定注销并退出系统吗?',
    '提示',
    {
      confirmButtonText: '确定',
      cancelButtonText: '取消',
      type: 'warning',
    }
  ).then(() => {
    store.dispatch('user/FedLogOut').then(() => {
      router.replace('/login')
    })
  })
}
// 关闭弹窗方法
const closeDialog = () => {
  dialogFormVisible.value = false
}
</script>

接下来我们来实现登录模块,就可看到主页的权限效果渲染正确与否

实现登录模块

登录模块也比较简单,就是一个登录表单,涉及到表单校验、提交表单。还有一个记住密码的功能是需要安装额外的模块js-cookie(对cookie信息进行操作)、jsencrypt(对密码加密解密)。下面是依赖的版本。

手把手教你实现一个vue3+ts+nodeJS后台管理系统(二十)

记住密码分两种情况

  1. 选择了记住密码。那么提交表单的方法我们就要存储用户名密码信息(密码需要用jesncrypt加密)到cookie中。下次再需要登录的时候直接先将密码解密再从cookie中取信息放到表单中
  2. 未选择记住密码就清空cookie中存在的用户名密码信息

还有一个登录页面可能是其它页面(例如首页、用户管理等)重定向过来的,这时候会在query路由信息中有之前页面的地址还有其它query信息,我们监听此信息登录成功后跳转到此页面。接下来用代码看看总体的登录页面

<template>
  <div class="loginbody">
    <div class="logindata">
      <div class="logintext">
        <h2>Welcome</h2>
      </div>
      <div class="form">
        <el-form ref="ruleFormRef" :model="form" :rules="rules">
          <el-form-item prop="username">
            <el-input v-model="form.username" clearable placeholder="请输入账号"></el-input>
          </el-form-item>
          <el-form-item prop="password">
            <el-input v-model="form.password" clearable placeholder="请输入密码" show-password></el-input>
          </el-form-item>
          <el-form-item prop="checkCode">
            <el-input v-model="form.checkCode" placeholder="请输入验证码" class="login-code-input" maxlength="4" minlength="4"
              clearable></el-input>
            <div class="login-code" @click="getCode" v-html="verifyImg">
            </div>
          </el-form-item>
          <el-form-item prop="remember">
            <el-checkbox v-model="form.remember" class="check-box">记住密码</el-checkbox>
          </el-form-item>
        </el-form>
      </div>
      <div class="butt">
        <el-button :loading="loading" type="primary" @click="submitForm(ruleFormRef)">登录</el-button>
        <el-button @click="ruleFormRef?.resetFields()">重置</el-button>
      </div>
    </div>
  </div>
</template><script lang="ts">
export default { name: 'Login' };
</script>
<script lang="ts" setup>
import { ref, reactive, toRefs, watch, onMounted } from 'vue'
import router from '@/router'
import type { FormInstance, FormRules } from 'element-plus'
import { useStore } from '@/store'
import { useRoute } from 'vue-router'
import Cookies from 'js-cookie'
import { encrypt, decrypt } from '@/utils/jsencrypt'
import { getCheckCode } from '@/utils/API/user/user'// 获取state、route
const store = useStore()
const route = useRoute()
// 要重定向的地址
const redirect = ref(undefined as string | undefined)
// 重定向路由其它query信息
const otherQuery = ref({})
// 登录变量、方法
const loading = ref(false)
const ruleFormRef = ref<FormInstance>()
const form = reactive<loginForm>({
  username: '',
  password: '',
  checkCode: '',
  remember: false,
  uuid: 0
})
// 验证码图片
let verifyImg = ref('')
const rules = reactive<FormRules>({
  username: [{ required: true, message: '请输入账号', trigger: 'blur' }],
  password: [{ required: true, message: '请输入密码', trigger: 'blur' }],
  checkCode: [{ required: true, message: '请输入验证码', trigger: 'blur' }, { min: 4, max: 4, message: '长度为4个字符!', trigger: 'blur' }],
})
const submitForm = (formEl: FormInstance | undefined) => {
  if (!formEl) return
  formEl.validate((valid, fields) => {
    if (valid) {
      loading.value = true
      // 记住密码存储用户信息
      if (form.remember) {
        Cookies.set('username', form.username, { expires: 7 })
        Cookies.set('password', encrypt(form.password), { expires: 7 })
        Cookies.set('remember', form.remember, { expires: 7 })
      } else {
        // 移除用户信息
        Cookies.remove('username')
        Cookies.remove('password')
        Cookies.remove('remember')
      }
      store.dispatch('user/login', form).then(() => {
        router.push({ path: redirect.value || '/', query: otherQuery.value })
        loading.value = false
      })
        .catch(() => {
          getCode()
          loading.value = false
        })
    }
  })
}
// 监听重定向信息
watch(
  route,
  () => {
    const query = route.query;
    if (query) {
      redirect.value = query.redirect as string;
      otherQuery.value = getOtherQuery(query);
    }
  },
  {
    immediate: true
  }
);
// 获取重定向query其它信息
function getOtherQuery(query: any) {
  return Object.keys(query).reduce((acc: any, cur: any) => {
    if (cur !== 'redirect') {
      acc[cur] = query[cur];
    }
    return acc;
  }, {});
}
// 获取cookie中的用户名密码信息
function getCookie() {
  const username = Cookies.get('username')
  const password = Cookies.get('password')
  const remember = Cookies.get('remember')
​
  form.username = username === undefined ? form.username : username
  form.password = password === undefined ? form.password : decrypt(password)
  form.remember = remember === undefined ? form.remember : Boolean(remember)
}
// 获取验证码
function getCode() {
  const uuid = new Date().getTime()
  form.uuid = uuid
  getCheckCode(uuid).then(res => {
    verifyImg.value = res.data
  })
}
onMounted(() => {
  getCode()
  getCookie()
})
</script><style lang="scss" scoped>
.loginbody {
  width: 100%;
  height: 100%;
  background-image: url('../assets/images/cool-background.png');
  background-size: cover;
  background-repeat: no-repeat;
  position: fixed;
  opacity: 0.8;
  display: flex;
  align-items: center;
  justify-content: center;
}
​
.logintext {
  margin-bottom: 20px;
  line-height: 50px;
  text-align: center;
  font-size: 30px;
  font-weight: bolder;
  color: #fbffc8;
  text-shadow: 2px 2px 4px #000000;
}
​
.logindata {
  width: 400px;
  height: 300px;
  margin-bottom: 150px;
}
​
.form {
  width: 100%;
​
  .el-input {
    font-size: 16px;
    width: 100%;
    height: 40px;
    line-height: 40px;
  }
​
  .login-code-input {
    position: relative;
  }
​
  .login-code {
    position: absolute;
    right: 6px;
    top: 4px;
    vertical-align: center;
    width: 100px;
    height: 32px;
    cursor: pointer;
    vertical-align: middle;
​
  }
​
  .check-box {
    font-size: 16px;
    font-weight: 600
  }
}
​
.tool {
  display: flex;
  justify-content: space-between;
  color: #606266;
}
​
.butt {
  font-size: 16px;
  margin-top: 10px;
  text-align: center;
}
​
.shou {
  cursor: pointer;
  color: #606266;
}
</style>

测试

  1. 登录成功展示对应权限菜单

手把手教你实现一个vue3+ts+nodeJS后台管理系统(二十)

  1. 登录失败

手把手教你实现一个vue3+ts+nodeJS后台管理系统(二十)

转载自:https://juejin.cn/post/7177673698831269949
评论
请登录