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

谁借莪1个温暖的怀抱¢ 2023-09-27 09:16 49阅读 0赞

基于 Vue3.x + Vant UI 的多功能记账本(四)


文章目录

  • 基于 Vue3.x + Vant UI 的多功能记账本(四)
      • 项目演示
      • 1、登录注册页面
      • 2、图片验证码
      • 3、修改 axios
      • 4、写到最后(附源码)





















系列内容 参考链接
基于 Vue3.x + Vant UI 的多功能记账本(一) 项目演示,涉及知识点
基于 Vue3.x + Vant UI 的多功能记账本(二) 搭建开发环境
基于 Vue3.x + Vant UI 的多功能记账本(三) 开发导航栏及公共部分

项目演示

Vue3 + Vant UI_多功能记账本

在这里插入图片描述

1、登录注册页面

页面设计,页面跳转

Login.vue

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

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

custom.less

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

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

index.css

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

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

在这里插入图片描述

2、图片验证码

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

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

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

在这里插入图片描述

3、修改 axios

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

axios.js

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

4、写到最后(附源码)

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

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

心动不如行动

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

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

发表评论

表情:
评论列表 (有 0 条评论,49人围观)

还没有评论,来说两句吧...

相关阅读