【项目记录/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
问题:但执行表达式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 };
  },
};

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
问题:但执行表达式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 };
  },
};

文件: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 };
  },
};

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("登陆失败");
    });
};

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);
        }
      );
  });
};

文件: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('登陆失败')
  })
}

优化后,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 };
  },
};

3-6 Toast 弹窗组件的开发

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

.toast {
  // 在浏览器窗口垂直水平居中
  position: fixed;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
}

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

文件:Login/Login.vue

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

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 };
  },
};

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 }
}

组件引入顺序

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

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,
    };
  },
};

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);

可选链( ?. )

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值