Vue3+Vite+Pinia+Naive后台管理系统搭建之八:构建 login.vue 登录页

news/2024/7/7 19:28:03

前言

如果对 vue3 的语法不熟悉的,可以移步Vue3.0 基础入门,快速入门。

项目所需要的图片,icon图标(推荐:阿里巴巴矢量图标库)自行获取,命名一致就行。

1. 构建 src/components/CopyRight.vue 版本号组件

<!-- src/components/CopyRight.vue  -->
<script setup>
const props = defineProps({
  color: {
    type: String,
  },
});
</script>

<template>
  <p :style="{ color }">
    版权所有 © YLD科技有限公司,保留所有权利,川F3-20233308号
  </p>
</template>

<style lang="scss" scoped>
p {
  position: absolute;
  bottom: 15px;
  left: 0;
  right: 0;
  text-align: center;
  color: #fff;
  font-family: Arial;
  font-size: 12px;
  letter-spacing: 1px;
}
</style>

2. 构建 src/components/SvgIcon.vue 图标组件

参考在 vue3 中构建 SvgIcon 组件

3. 编辑 src/pages/login.vue

<!-- src/pages/login.vue -->
<script setup>
import cookie from "js-cookie";
import { ref, reactive } from "vue";
import { NButton, NInput, NForm, NFormItem, NCheckbox } from "naive-ui";
import router from "@/router/index.js";
import { useUserStore } from "@/store/user.js";
import { encrypt, decrypt } from "@/utils/jsencrypt";
import { getCodeImg } from "@/api/login.js";

import CopeRight from "@/components/CopyRight.vue";

// 用户状态管理
let userStore = useUserStore();

// 是否需要验证码
let needCode = ref(false);
let codeUrl = ref("");
// 获取图形验证码
function getCode() {
  getCodeImg().then((res) => {
    needCode.value =
      res.captchaEnabled === undefined ? true : res.captchaEnabled;
    if (needCode.value) {
      codeUrl.value = "data:image/gif;base64," + res.img;
      form.uuid = res.uuid;
    }
  });
}
getCode();

// 登录
let formRef = ref(null);
let form = reactive({
  username: "",
  password: "",
  code: "",
  uuid: "",
});
let rules = {
  username: {
    required: true,
    trigger: ["input", "blur"],
    message: "请输入用户名",
  },
  password: {
    required: true,
    trigger: ["input", "blur"],
    message: "请输入密码",
  },
  code: {
    required: true,
    trigger: ["input", "blur"],
    message: "请输入验证码",
  },
};
let rememberMe = ref(false);
let loginBtnState = ref(false);
let handleLogin = () => {
  loginBtnState.value = true;
  formRef.value?.validate((errors) => {
    if (!errors) {
      if (rememberMe.value) {
        cookie.set("username", form.username, { expires: 30 });
        cookie.set("password", encrypt(form.password), { expires: 30 });
        cookie.set("rememberMe", rememberMe.value, { expires: 30 });
      } else {
        cookie.remove("username");
        cookie.remove("password");
        cookie.remove("rememberMe");
      }

      userStore
        .login(form)
        .then(() => {
          window.$msg.success("登录成功");
          router.push({ name: "home" });
        })
        .catch(() => {
          loginBtnState.value = false;
          getCode();
        });
    } else {
      loginBtnState.value = false;
    }
  });
};

// 获取默认登录账号
function getCookie() {
  form.username = cookie.get("username") || "";
  form.password = decrypt(cookie.get("password")) || "";
  rememberMe.value = Boolean(cookie.get("rememberMe")) || false;
}
getCookie();
</script>

<template>
  <div class="login-bg c-center">
    <div class="login__box">
      <div class="login-logo__box">
        <div class="login__title login-logo">
          登 录
        </div>
      </div>
      <n-form
        ref="formRef"
        class="login-form__box"
        :model="form"
        :rules="rules"
        label-placement="left"
      >
        <n-form-item path="username">
          <n-input
            class="login-input"
            v-model:value="form.username"
            placeholder="请输入用户名/手机号"
            @blur="validate"
          >
            <template #prefix>
              <svg-icon name="username" color="grey"></svg-icon>
            </template>
          </n-input>
        </n-form-item>
        <n-form-item path="password">
          <n-input
            class="login-input"
            v-model:value="form.password"
            placeholder="请输入密码"
            type="password"
            show-password-on="mousedown"
            @keyup.enter="handleLogin"
          >
            <template #prefix>
              <svg-icon name="password" color="grey"></svg-icon>
            </template>
          </n-input>
        </n-form-item>
        <n-form-item v-if="needCode" class="login-code" path="code">
          <n-input
            v-model:value="form.code"
            class="login-input login-input_code"
            placeholder="验证码"
            @keyup.enter="handleLogin"
          >
            <template #prefix>
              <svg-icon name="code" color="grey"></svg-icon>
            </template>
          </n-input>
          <img class="login-code-img" :src="codeUrl" @click="getCode" />
        </n-form-item>
        <div class="login-checkbox_box">
          <n-checkbox
            class="login-checkbox"
            v-model:checked="rememberMe"
          ></n-checkbox>
          <span>记住密码</span>
        </div>
        <n-button
          class="login-btn_login"
          type="info"
          @click="handleLogin"
          :loading="loginBtnState"
          :disabled="loginBtnState"
          >登录</n-button
        >
      </n-form>
      <!-- <div class="login-btn_forget" @click="handleForget">忘记密码 ?</div> -->
    </div>
    <cope-right></cope-right>
  </div>
</template>

<style lang="scss">
.n-form-item {
  margin-top: -20px;
}
</style>

<style lang="scss" scoped>
.login-bg {
  width: 100vw;
  height: 100vh;
  background-image: url("@/assets/imgs/login_bg.png");
  background-size: cover;
  background-repeat: no-repeat;
  overflow: hidden;
  .login__box {
    width: 425px;
    padding: 20px 40px;
    border-radius: 10px;
    background: rgba(2, 57, 104, 0.7);
    box-shadow: 0 0 30px rgba(2, 57, 104, 0.7);
    box-sizing: border-box;
    .login__title {
      font-size: 24px;
      text-align: center;
      color: #fff;
    }
  }
}

.login-title {
  margin-bottom: 15px;
  text-align: center;
  letter-spacing: 10px;
  font-size: 18px;
  color: #fff;
  opacity: 1;
}

.login-logo__box {
  display: flex;
  justify-content: center;
  align-items: center;
  margin-bottom: 20px;

  text {
    display: flex;
    align-items: center;
    font-size: 16px;
    font-family: "Gill Sans", "Gill Sans MT", Calibri, "Trebuchet MS",
      sans-serif;
    color: #1d69a3;

    &::before {
      content: "";
      display: inline-block;
      margin: 0 5px;
      width: 5px;
      height: 5px;
      background: gray;
      border-radius: 50%;
    }
  }
}

.login-logo {
  width: 55%;

  img {
    width: 100%;
    object-fit: cover;
  }
}

.login-code {
  .login-input_code {
    width: 240px;
  }

  .login-btn_code {
    color: #fff;
    margin-left: 20px;
  }
}

.login-btn_login {
  height: 40px;
  width: 100%;
}

.login-input {
  margin-top: 20px;
}

.login-checkbox_box {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  margin: -16px 0 10px;
  color: #71a1c5;
  font-size: 12px;
}

.login-checkbox {
  margin-right: 5px;
}

.login-btn_forget {
  margin-top: 10px;
  text-align: center;
  font-size: 12px;
  color: #71a1c5;
  cursor: pointer;
}

.login-code-img {
  margin-top: 20px;
  width: 30%;
  height: 35px;
  margin-left: 20px;
  object-fit: contain;
  cursor: pointer;
}
</style>

 

综上

login.vue 登录页构建完成。下一章:layout 动态路由布局


http://lihuaxi.xjx100.cn/news/1344005.html

相关文章

【每日一题】2673. 使二叉树所有路径值相等的最小代价

【每日一题】2673. 使二叉树所有路径值相等的最小代价 2673. 使二叉树所有路径值相等的最小代价题目描述解题思路 2673. 使二叉树所有路径值相等的最小代价 题目描述 给你一个整数 n 表示一棵 满二叉树 里面节点的数目&#xff0c;节点编号从 1 到 n 。根节点编号为 1 &#…

云计算的学习(六)

六、云计算的发展趋势 1.云计算相关领域介绍 1.1物联网 物联网来源于互联网&#xff0c;是万物互联的结果&#xff0c;是人和物、物和物之间产生通信和交互。 物联网主要技术&#xff1a; RFID技术&#xff08;射频识别技术&#xff09;传感器技术嵌入式系统技术 1.2大数据…

scrapy集成selenium

前言 使用scrapy默认下载器---》类似于requests模块发送请求&#xff0c;不能执行js&#xff0c;有的页面拿回来数据不完整 想在scrapy中集成selenium&#xff0c;获取数据更完整&#xff0c;获取完后&#xff0c;自己组装成 Response对象&#xff0c;就会进爬虫解析&#xff0…

Pinia学习笔记 | 入门 - 映射辅助函数

文章目录 Pinia学习笔记简介Pinia是什么 代码分割机制案例1.挂载PiniaVue3Vue2&#xff1a;安装PiniaVuePlugin插件 2.定义store的两种方式options API 和 composition API使用options API模式定义使用composition API模式 2.业务组件对store的使用创建store实例解构访问Pinia容…

Java并发编程:解锁多线程魔法的奥秘

代码内容在最后 在当今并行和分布式计算的时代&#xff0c;Java作为一门强大的编程语言&#xff0c;在多线程编程方面扮演着重要的角色。本文将介绍Java并发编程的基础知识和最佳实践&#xff0c;并提供实际示例来演示多线程编程的应用和解决方案。 为什么需要并发编程&#xf…

前端实现 DIV 高度只有100px,宽度只有100px ,我要在这个DIV放一个宽度200的DIV,左右拉动滚动条显示

<!DOCTYPE html> <html> <head><title>点击监听两组span标签</title><style>.outer-div {width: 100px;height: 100px;overflow-x: scroll;background-color: #abc1ee;}.inner-div {width: 200px;}/* 自定义滚动条样式 */.outer-div::-web…

gma 2 教程(二)数据操作:3. 支持生成的栅格格式信息

为了方便了解和选择输出栅格格式、配置高级创建选项&#xff0c;下表列出了gma可以生成&#xff08;复制/创建/转换&#xff09;的所有栅格格式的主要信息&#xff1a; 格式名生成模式支持数据类型扩展名多维栅格支持色彩映射表支持的数据类型多波段支持压缩模式AAIGrid复制By…

RPC和HTTP区别是什么?

&#x1f3c6;今日学习目标&#xff1a; &#x1f340;RPC和HTTP区别是什么&#xff1f; ✅创作者&#xff1a;林在闪闪发光 ⏰预计时间&#xff1a;30分钟 &#x1f389;个人主页&#xff1a;林在闪闪发光的个人主页 &#x1f341;林在闪闪发光的个人社区&#xff0c;欢迎你的…