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

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第20天,点击查看

前言

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

修改layout模块

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

添加重置密码模块

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的全局状态显示用户信息
image.png
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(对密码加密解密)。下面是依赖的版本。
image.png
记住密码分两种情况
  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. 登录成功展示对应权限菜单
image.png
  1. 登录失败
image.png
------本页内容已结束,喜欢请分享------

感谢您的来访,获取更多精彩文章请收藏本站。

© 版权声明
THE END
喜欢就支持一下吧
点赞9 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容