收款定制开发Vue项目实战——【基于 Vue3.x + Vant UI】实现一个多功能记账本(登录注册页面,验证码)

基于 .x + Vant UI 收款定制开发收款定制开发收款定制开发收款定制开发的多功能记账本(四)


文章目录

系列内容参考链接
基于 Vue3.x + Vant UI 的多功能记账本(一)
基于 Vue3.x + Vant UI 的多功能记账本(二)
基于 Vue3.x + Vant UI 的多功能记账本(三)

项目演示

1、

页面设计,页面跳转

Login.vue

<template>  <!-- 收款定制开发根据页面显示相应头部 -->  <Header :title="type == 'login' ? '登录' : '注册'" />  <div class="auth">    <img class="logo" src="//s.yezgea02.com/1606836859539/onpeice.png" alt="" />    <!-- 收款定制开发登录界面的表单 -->    <van-form class="form-wrap" @submit="onSubmit" v-if="type == 'login'">      <div class="form">        <!-- 收款定制开发账号输入框,clearable:清除图标,rules:收款定制开发表单校验规则 -->        <van-field          clearable          v-model="username"          name="username"          label="账号"          placeholder="收款定制开发请输入账号"          :rules="[{ required: true, message: '收款定制开发请填写账户' }]"        />        <!-- 密码输入框 -->        <van-field          clearable          v-model="password"          type="password"          name="password"          label="密码"          placeholder="请输入密码"          :rules="[{ required: true, message: '请填写密码' }]"        />      </div>      <div style="margin: 16px 0">        <van-button round block type="primary" native-type="submit">          登录        </van-button>        <p @click="chanegType('register')" class="change-btn">          没有账号,前往注册        </p>      </div>    </van-form>    <!-- 注册页面的表单 -->    <van-form class="form-wrap" @submit="onSubmit" v-if="type == 'register'">      <div class="form">        <van-field          clearable          v-model="username"          name="username"          label="账号"          placeholder="请输入账号"          :rules="[{ required: true, message: '请填写账号' }]"        />        <van-field          clearable          v-model="password"          type="password"          name="password"          label="密码"          placeholder="请输入密码"          :rules="[{ required: true, message: '请填写密码' }]"        />        <!-- 验证码输入框 -->        <van-field          center          clearable          label="验证码"          placeholder="输入验证码"          v-model="verify"        >          <!-- 点击刷新验证码 -->          <template #button>            <!-- 生成验证码图片组件,ref 方便拿到组件内的实例属性 -->            <VueImgVerify ref="verifyRef" />          </template>        </van-field>      </div>      <div style="margin: 16px 0">        <van-button round block type="primary" native-type="submit">          注册        </van-button>        <p @click="chanegType('login')" class="change-btn">登录已有账号</p>      </div>    </van-form>  </div></template><script>import { reactive, toRefs, ref, onMounted } from "vue";// 生成验证码的组件import VueImgVerify from "../components/VueImageVerify.vue";import Header from "../components/Header.vue";import axios from "../utils/axios";// 轻提示(成功/失败...)import { Toast } from "vant";import router from "../router";export default {  name: "Login",  components: {    VueImgVerify, // 验证码组件    Header, //公共头组件  },  setup() {    // 便于拿到 verifyRef 组件内的实例属性    const verifyRef = ref(null);    // 注册登录的相关内容    const state = reactive({      username: "",      password: "",      type: "login", // 登录注册模式切换参数      verify: "", // 验证码输入框输入的内容      imgCode: "", // 生成的验证图片内的文字    });    console.log("verifyRef", verifyRef);    // 提交登录 or 注册表单    const onSubmit = async (values) => {      // 登录功能      if (state.type == "login") {        const { data } = await axios.post("/user/login", {          username: state.username,          password: state.password,        });        // 添加 token 到本地存储        localStorage.setItem("token", data.token);        window.location.href = "/";      } else {        // 生成的图片验证码的文字等于验证码组件生成的验证码        state.imgCode = verifyRef.value.imgCode || "";        // 如果验证码组件生成的验证码的小写 != 用户输入的验证码的小写,则提示错误        if (          verifyRef.value.imgCode.toLowerCase() != state.verify.toLowerCase()        ) {          console.log("verifyRef.value.imgCode", verifyRef.value.imgCode);          Toast.fail("验证码错误");          return;        }        // 验证码匹配成功,注册=>注册成功        await axios.post("/user/register", {          username: state.username,          password: state.password,        });        Toast.success("注册成功");      }    };    // 切换登录和注册两种模式    const chanegType = (type) => {      state.type = type;    };    return {      ...toRefs(state),      onSubmit,      chanegType,      verifyRef,    };  },};</script><style lang='less' scoped>@import url("../config/custom.less");.auth {  height: calc(~"(100% - 46px)");  padding: 30px 20px 0 20px;  background: @primary-bg;  .logo {    width: 150px;    display: block;    margin: 0 auto;    margin-bottom: 30px;  }  .form-wrap {    .form {      border-radius: 10px;      overflow: hidden;      .van-cell:first-child {        padding-top: 20px;      }      .van-cell:last-child {        padding-bottom: 20px;      }    }  }  .change-btn {    text-align: center;    margin: 10px 0;    color: @link-color;    font-size: 14px;  }}</style>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188

在 custom.less 下补充 link-color 变量的定义,在写样式的时候,以 color: @link-color; 这样的形式引用它

custom.less

@primary: #39be77; // 主题色@danger: #fc3c0c; @primary-bg: #f5f5f5;@link-color: #597fe7;
  • 1
  • 2
  • 3
  • 4

当前页面的外层是 #app、body,作为父级,它们需要先把高度撑开

index.css

body,html,p {  height: 100%;  margin: 0;  padding: 0;}* {  box-sizing: border-box;}#app {  height: 100%;}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

此时,yarn dev,打开浏览器可以看到…

2、图片验证码

注:验证码基本上都是由服务端接口提供,然后上报之后由服务端验证是否正确,所以此部分内容可以自行选择是否去做。

<template>  <div class="img-verify">    <!-- 画布,绑定一个点击事件,用于刷新验证码 -->    <canvas      ref="verify"      :width="width"      :height="height"      @click="handleDraw"    ></canvas>  </div></template><script type="text/ecmascript-6">import { reactive, onMounted, ref, toRefs } from "vue";export default {  setup() {    const verify = ref(null);    const state = reactive({      pool: "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890", // 字符串      width: 120,      height: 40,      imgCode: "", // 初始化验证码为空    });    onMounted(() => {      // 初始化绘制图片验证码      state.imgCode = draw();    });    // 点击图片重新绘制    const handleDraw = () => {      state.imgCode = draw();    };    // 随机数    const randomNum = (min, max) => {      return parseInt(Math.random() * (max - min) + min);    };    // 随机颜色    const randomColor = (min, max) => {      const r = randomNum(min, max);      const g = randomNum(min, max);      const b = randomNum(min, max);      return `rgb(${r},${g},${b})`;    };    // 绘制图片    const draw = () => {      // 3.填充背景颜色,背景颜色要浅一点      const ctx = verify.value.getContext("2d");      // 填充颜色      ctx.fillStyle = randomColor(180, 230);      // 填充的位置      ctx.fillRect(0, 0, state.width, state.height);      // 定义paramText      let imgCode = "";      // 4.随机产生字符串,并且随机旋转      for (let i = 0; i < 4; i++) {        // 随机的四个字        const text = state.pool[randomNum(0, state.pool.length)];        imgCode += text;        // 随机的字体大小        const fontSize = randomNum(18, 40);        // 字体随机的旋转角度        const deg = randomNum(-30, 30);        /*         * 绘制文字并让四个文字在不同的位置显示的思路 :         * 1、定义字体         * 2、定义对齐方式         * 3、填充不同的颜色         * 4、保存当前的状态(以防止以上的状态受影响)         * 5、平移 translate()         * 6、旋转 rotate()         * 7、填充文字         * 8、restore 出栈         * */        ctx.font = fontSize + "px Simhei";        ctx.textBaseline = "top";        ctx.fillStyle = randomColor(80, 150);        /*         * save() 方法把当前状态的一份拷贝压入到一个保存图像状态的栈中。         * 这就允许您临时地改变图像状态,         * 然后,通过调用 restore() 来恢复以前的值。         * save是入栈,restore 是出栈。         * 用来保存Canvas的状态。save 之后,可以调用 Canvas 的平移、放缩、旋转、错切、裁剪等操作。 restore:用来恢复 Canvas 之前保存的状态。防止 save 后对 Canvas 执行的操作对后续的绘制有影响。         *         * */        ctx.save();        ctx.translate(30 * i + 15, 15);        ctx.rotate((deg * Math.PI) / 180);        // fillText() 方法在画布上绘制填色的文本。文本的默认颜色是黑色。        // 请使用 font 属性来定义字体和字号,并使用 fillStyle 属性以另一种颜色/渐变来渲染文本。        // context.fillText(text,x,y,maxWidth);        ctx.fillText(text, -15 + 5, -15);        ctx.restore();      }      // 5.随机产生5条干扰线,干扰线的颜色要浅一点      for (let i = 0; i < 5; i++) {        ctx.beginPath();        ctx.moveTo(randomNum(0, state.width), randomNum(0, state.height));        ctx.lineTo(randomNum(0, state.width), randomNum(0, state.height));        ctx.strokeStyle = randomColor(180, 230);        ctx.closePath();        ctx.stroke();      }      // 6.随机产生40个干扰的小点      for (let i = 0; i < 40; i++) {        ctx.beginPath();        ctx.arc(          randomNum(0, state.width),          randomNum(0, state.height),          1,          0,          2 * Math.PI        );        ctx.closePath();        ctx.fillStyle = randomColor(150, 200);        ctx.fill();      }      return imgCode;    };    return {      ...toRefs(state),      verify,      handleDraw,    };  },};</script><style type="text/css">.img-verify canvas {  cursor: pointer;}</style>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133

此时,yarn dev,打开浏览器可以看到…

3、修改 axios

为避免在页面内请求接口的时候,每次都通过 code 码去判断接口请求是否成功,我们可以这样修改 axios.js 文件

axios.js

import axios from 'axios'// 轻提示插件(Vant UI)import { Toast } from 'vant'import router from '../router'// 根据环境变量切换本地和线上的请求地址axios.defaults.baseURL = process.env.NODE_ENV == 'development' ? '/api' : '//47.99.134.126:7008/api'// 允许跨域axios.defaults.withCredentials = trueaxios.defaults.headers['X-Requested-With'] = 'XMLHttpRequest'// token的用户鉴权方式,在请求头的 headers 内添加 token,每次请求都会验证用户信息axios.defaults.headers['Authorization'] = `${localStorage.getItem('token') || null}`axios.defaults.headers.post['Content-Type'] = 'application/json'axios.interceptors.response.use(res => {  // 返回数据的类型不是对象,则报异常  if (typeof res.data !== 'object') {    Toast.fail('服务端异常!')    return Promise.reject(res)  }  // code 状态码不是200,则报异常  if (res.data.code != 200) {    if (res.data.msg) Toast.fail(res.data.msg)    // code 状态码为 401 代表接口需要登录,继而跳转到登录页面    if (res.data.code == 401) {      router.push({ path: '/login' })    }    // 返回失败的实例    return Promise.reject(res.data)  }  // code 为 200 时,请求成功,返回数据  return res.data})export default axios
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35

4、写到最后(附源码)

看到这么好的项目,是不是有种想自己做出来的冲动?

如果有,那么说明你非常的想提升自己,想检验自己这段时间的学习成果,这个项目绝对是你的 不二选择

心动不如行动

那么接下来,一起从0搭建,开始我们基于 Vue3.x + Vant UI 的项目之旅吧~

源码在下方 ↓【回复:记账本】即可

网站建设定制开发 软件系统开发定制 定制软件开发 软件开发定制 定制app开发 app开发定制 app开发定制公司 电商商城定制开发 定制小程序开发 定制开发小程序 客户管理系统开发定制 定制网站 定制开发 crm开发定制 开发公司 小程序开发定制 定制软件 收款定制开发 企业网站定制开发 定制化开发 android系统定制开发 定制小程序开发费用 定制设计 专注app软件定制开发 软件开发定制定制 知名网站建设定制 软件定制开发供应商 应用系统定制开发 软件系统定制开发 企业管理系统定制开发 系统定制开发