• 【项目记录/vue移动端】仿京东到家登录页


    在这里插入图片描述

    3-1 登陆页面布局开发

    视口(Viewport)

    目的:让手机的小屏幕尽可能完整显示整个网页,即实现理想视口

    1)布局视口layout viewport

    iOS, Android 都将布局视口分辨率设置为 980px,故 PC端的网页能在手机端呈现,不过元素很小

    2)视觉视口visual viewport

    用户看到的网站区域,可通过放缩操作改变;
    缩放值 = 理想视口宽度 / 视觉视口宽度

    3)理想视口ideal viewport

    指布局视口宽度=屏幕宽度,无需左右滚动条

    定位/浮动后脱离标准流

    表现:元素像飞起来一样;在z轴的另一层显示,原先空间被其他元素占据
    后果:元素宽度由内容宽度决定(自动撑开);

    3-2 路由守卫-实现基础登陆校验功能

    定义:通过跳转或取消的方式守卫导航,比如登录鉴权(没有登录不能进入个人中心页)等;
    有多种机会植入路由导航过程中:全局的, 单个路由独享的, 或者组件级的

    可以简单的理解为一座房子的门口的保安,想要进入这个房子就必须通过保安的检查,要告诉路由守卫你从哪里来?总不能随便陌生人就给放进去?要到哪里去?然后保安再告诉你下一步该怎么做?如果你的确是这个房子主人允许进入的人,那就让你进入,否则就要打电话给房子主人,跟房主商量(登录注册),给你权限

    文件:router/index.js

    router:路由对象
    route:当前激活路由的状态信息
    routes:路由实例的配置项

    const routes = [
      {
        path: "/",
        name: "Home",
        component: Home,
      },
      // 功能:若已是登陆状态,不能再访问Login页面
      // 即输入Login页面网址,设置跳转到Home页面
      {
        path: "/login",
        name: "Login",
        component: Login,
        // 何时执行:访问路由页面前
        beforeEnter(to, from, next) {
          const { isLogin } = localStorage;
          isLogin ? next({ name: "Home" }) : next();
          // next();继续展示当前页面
        },
      },
    ];
    // 创建路由实例
    const router = createRouter({
      //创建hash历史记录
      history: createWebHashHistory(),
      routes,
    });
    // 何时执行:页面跳转之前(路由切换之前)
    router.beforeEach((to, from, next) => {
      // 解构赋值
      // 相当于const isLogin = localStorage.isLogin
      const { isLogin } = localStorage;
      // next();继续展示当前页面
      isLogin || to.name === "Login" ? next() : next({ name: "Login" });
    });
    
    • 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

    逻辑:已登录就执行表达式1;未登录就执行表达式2
    问题:但执行表达式2跳到登录页之前, 又会执行beforeEach();再次进入登录状态判断,表达式1永不会执行,陷入死循环
    解决:加入判断条件|| to.name === "Login
    虽未登录, 但若要跳转的是Login页面, 执行表达式1

    文件:Login/Login.vue

    export default {
      name: "Login",
      // 相当于调用beforeCreate()和create()
      setup() {
        // 实例化路由
        const router = useRouter();
        // 点击登录按钮的事件
        const handleLogin = () => {
          // 模拟登录:暂不验证用户名和密码
          //点击登录按钮,登录状态就变为已登录
          localStorage.isLogin = true;
          // 跳转到首页
          router.push({ name: "Home" });
        };
        return { handleLogin };
      },
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    setup()是围绕beforeCreate()和create()运行,故这俩无需显式定义
    换句话说,在beforeCreate()和create()中编写的任何代码都应该直接在 setup ()编写

    3-3 注册页面开发&路由串联首页/登录/注册三个页面

    文件:router/index.js

    const routes = [
      {
        path: "/",
        name: "Home",
        component: Home,
      },
      // 功能:若已是登陆状态,不能再访问register页面
      // 即输入register页面网址,设置跳转到Home页面
      {
        path: "/register",
        name: "Register",
        component: Register,
        beforeEnter(to, from, next) {
          const { isLogin } = localStorage;
          isLogin ? next({ name: "Home" }) : next();
        },
      },
      // 功能:若已是登陆状态,不能再访问Login页面
      // 即输入Login页面网址,设置跳转到Home页面
      {
        path: "/login",
        name: "Login",
        component: Login,
        beforeEnter(to, from, next) {
          const { isLogin } = localStorage;
          isLogin ? next({ name: "Home" }) : next();
        },
      },
    ];
    
    const router = createRouter({
      history: createWebHashHistory(),
      routes,
    });
    router.beforeEach((to, from, next) => {
      const { isLogin } = localStorage;
      const { name } = to;
    
      const isLoginOrRegister = name === "Login" || name === "Register";
      isLogin || isLoginOrRegister ? next() : next({ name: "Login" });
    });
    
    • 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

    逻辑:已登录就执行表达式1;未登录就执行表达式2
    问题:但执行表达式2跳到登录页之前, 又会执行beforeEach();再次进入登录状态判断,表达式1永不会执行,陷入死循环
    解决:加入判断条件||isLoginOrRegister
    虽未登录, 但若要跳转的是Login/Register页面, 执行表达式1

    文件:register/Register.js

    export default {
      name: "Register",
      setup() {
        const router = useRouter();
        // 涉及新知识:将数据存储在数据库;调用接口
        // const handleRegister = () => { };
        const handleLoginClick = () => {
          router.push({ name: "Login" });
        };
        return { handleLoginClick };
      },
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    文件:Login/Login.vue

    export default {
      name: "Login",
      setup() {
        const router = useRouter();
        const handleLogin = () => {
          localStorage.isLogin = true;
          router.push({ name: "Home" });
        };
        // 点击“立即注册”事件:跳转到注册页
        const handleRegisterClick = () => {
          router.push({ name: "Register" });
        };
        return { handleLogin, handleRegisterClick };
      },
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    3-4 使用axios 发送登陆 Mock 请求

    axios:基于promise的用于浏览器和nodejs的HTTP客户端
    过程:
    1)双向绑定用户名\密码两个输入框;接收其中的数据
    2) // 点击“登录”的事件处理函数

    const handleLogin = () => {
      // 根地址:https://www.fastmock.site/mock/ae8e9031947a302fed5f92425995aa19/jd
      // 登录接口:api/user/login;
      axios
        .post(
          "https://www.fastmock.site/mock/ae8e9031947a302fed5f92425995aa19/jd/api/user/login",
          {
            username: data.username,
            password: data.password,
          }
        )
        .then(() => {
          localStorage.isLogin = true;
          // 切换至首页
          router.push({ name: "Home" });
        })
        .catch(() => {
          alert("登陆失败");
        });
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    3-5 请求函数的封装

    需求:请求某域名下的多个接口,不希望每次发送请求都填写绝对地址
    通过配置baseURL,只需传入的相对地址,其会和baseURL拼接成绝对地址

    文件:utils/request.js

    export const post = (url, data = {}) => {
      return new Promise((resolve, reject) => {
        axios
          .post(url, data, {
            baseURL:
              "https://www.fastmock.site/mock/ae8e9031947a302fed5f92425995aa19/jd",
            headers: {
              "Content-Type": "application/json",
            },
          })
          .then(
            (response) => {
              resolve(response.data);
            },
            (err) => {
              reject(err);
            }
          );
      });
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    文件:Login/Login.vue

    优化异步函数

    优化前,axios(基于promise,对ajax的封装)

    const handleLogin = () => {
      axios.post('https://www.fastmock.site/mock/ae8e9031947a302fed5f92425995aa19/jd/api/user/login', {
        username: data.username,
        password: data.password
      }).then(() => {
        localStorage.isLogin = true
        router.push({ name: 'Home' })
      }).catch(() => {
        alert('登陆失败')
      })
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    优化后,async(基于promise,遵循Generator 函数的语法糖)
    async/await 是 ES7 引入的新语法,更加方便的进行异步操作
    async 关键字用于函数上(async 函数返回值是 Promise 实例对象)
    await 关键字只能用于 async 函数当中,后面可以直接跟一个 Promise 实例对象(await 可以得到异步结果)async/await 让异步代码看起来、表现起来更像同步代码

    export default {
      name: "Login",
      setup() {
        const data = reactive({
          username: "",
          password: "",
        });
        const router = useRouter();
        const handleLogin = async () => {
          try {
            const result = await post("/api/user/login", {
              username: data.username,
              password: data.password,
            });
            if (result?.errno === 0) {
              localStorage.isLogin = true;
              router.push({ name: "Home" });
            } else {
              alert("登陆失败");
            }
          } catch (e) {
            alert("请求失败");
          }
        };
        const handleRegisterClick = () => {
          router.push({ name: "Register" });
        };
        return { handleLogin, handleRegisterClick, data };
      },
    };
    
    • 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

    3-6 Toast 弹窗组件的开发

    默认的弹窗样式不够美观
    属于公共组件,放于src下的components文件

    .toast {
      // 在浏览器窗口垂直水平居中
      position: fixed;
      left: 50%;
      top: 50%;
      transform: translate(-50%, -50%);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    fixed相对浏览器窗口进行定位
    absolute相对最近的、有定位的祖先元素进行定位

    文件:Login/Login.vue

     <Toast v-if="data.showToast" :message="data.toastMessage"/>
    
    • 1

    v-bind 简写为:,作用:1.动态绑定数据或者属性 2.给组件传值
    v-model 作用:用于表单控件双向绑定数据
    v-on简写为@,作用:绑定事件

       
    export default {
      name: "Login",
      // 注册Toast弹窗组件
      components: { Toast },
      setup() {
        const data = reactive({
          username: "",
          password: "",
          showToast: false,
          toastMessage: "",
        });
        const router = useRouter();
        // 定义变量showToas,控制toast组件是否渲染
        const showToast = (message) => {
          data.showToast = true;
          data.toastMessage = message;
          // 弹窗2秒后消失
          setTimeout(() => {
            data.showToast = false;
            data.toastMessage = "";
          }, 2000);
        };
        const handleLogin = async () => {
          try {
            const result = await post("111/api/user/login", {
              username: data.username,
              password: data.password,
            });
            if (result?.errno === 0) {
              localStorage.isLogin = true;
              router.push({ name: "Home" });
            } else {
              showToast("登陆失败");
            }
          } catch (e) {
            showToast("请求失败");
          }
        };
        const handleRegisterClick = () => {
          router.push({ name: "Register" });
        };
        return { handleLogin, handleRegisterClick, data };
      },
    };
    
    • 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

    3-7 通过代码拆分增加逻辑可维护性

    文件:components/Toast.js

    export const useToastEffect = () => {
    
      const toastData = reactive({
        showToast: false,
        toastMessage: ''
      })
    
      const showToast = (message) => {
        toastData.showToast = true
        toastData.toastMessage = message
        setTimeout(() => {
          toastData.showToast = false
          toastData.toastMessage = ''
        }, 2000)
      }
     
      return { toastData, showToast }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    组件引入顺序

    系统组件-公共组件-页面组件

    3-8/9 Setup函数的职责以及注册功能的实现

    代码流程控制函数setup()

    export default {
      name: "Login",
      components: { Toast },
      //Login相关逻辑和Register相关逻辑抽象出去后,就剩代码执行的流程
      //代码流程控制函数
      setup() {
        const { show, toastMessage, showToast } = useToastEffect();
        const { username, password, handleLogin } = useLoginEffect(showToast);
        const { handleRegisterClick } = useRegisterEffect();
        return {
          username,
          password,
          show,
          toastMessage,
          handleLogin,
          handleRegisterClick,
        };
      },
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    ref

    vue操控虚拟DOM,通过ref()操控原生节点,比如改变节点内容
    原理:vue操控虚拟DOM,渲染后,就有了ref属性(不能在模版中用ref进行数据绑定),对组件,获取到了组件的实例对象;对原生节点,与document.getElementById() 作用相同

    toRefs作用

    https://www.cnblogs.com/IwishIcould/p/14891182.html
    data.username,data.password在视图上一个个属性点(.) 会很麻烦,如何不通过点也能正常渲染和自动更新视图呢

      const { username, password } = toRefs(data);
    
    • 1

    可选链( ?. )

    允许读取位于连接对象链深处的属性的值,而不必明确验证链中的每个引用是否有效。
    功能类似于 . 链式操作符
    不同之处在于,在引用为空(nullish ) (null 或者 undefined) 的情况下不会引起错误,该表达式短路返回值是 undefined。
    与函数调用一起使用时,如果给定的函数不存在,则返回 undefined。

  • 相关阅读:
    Java项目利用Redisson实现真正生产可用高并发秒杀功能 支持分布式高并发秒杀
    [HITCON CTF 2022] Superprime,rev Meow_way,BabySSS格基约减法,Secret共模攻击模未知
    flutter课程(The Complete 2021 Flutter Development Bootcamp with Dart)学习总结
    Mac系统在idea中安装tomcat报错 error=13, Permission denied和error =1 Operation not...解决办法
    免费开源!常用策略函数的复用与累积
    Java中的多线程如何理解——精简
    历史名人鲁迅介绍HTML个人网页作业作品下载 历史人物介绍网页设计制作 大学生英雄人物网站作业模板 dreamweaver简单个人网页制作
    DevOps2023现状报告|注重文化、以用户为中心是成功的关键
    C#NET6基于MailKit 进行邮件发送通知
    ES 8.x 新特性:match_phrase 跨值查询中 position_increment_gap 参数用法
  • 原文地址:https://blog.csdn.net/chonger_feifei/article/details/125336160