vue3小兔鲜商城项目学习笔记+资料分享05

建议大家先去看我第一篇小兔鲜的文章,强烈建议,非常建议,十分建议,从头开始看更完整。
最近正在学习vue3小兔鲜,
下面是学习笔记

登录模块

路由与组件

目标:登录组件在书写一级路由的时候已经准备,添加路由链接跳转到登录页即可。

  • 添加跳转链接:src/components/app-topnav.vue
<li><RouterLink to="/login">请先登录RouterLink>li>

结构布局-CV

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ismlX4gn-1668072991723)(media/01-16459813154986.png)]

静态结构参考代码 - CV







表单布局-CV

目标: 实现登录页面的整体大结构布局

  • 新建表单组件

src/views/Login/components/login-form.vue

<script setup lang="ts">
//
script>

<template>
  <div class="account-box">
    <div class="form">
      <div class="form-item">
        <div class="input">
          <i class="iconfont icon-user">i>
          <input type="text" placeholder="请输入用户名或手机号" />
        div>
        
        
      div>
      <div class="form-item">
        <div class="input">
          <i class="iconfont icon-lock">i>
          <input type="password" placeholder="请输入密码" />
        div>
      div>
      <div class="form-item">
        <div class="agree">
          <XtxCheckBox />
          <span>我已同意span>
          <a href="javascript:;">《隐私条款》a>
          <span>span>
          <a href="javascript:;">《服务条款》a>
        div>
      div>
      <a href="javascript:;" class="btn">登录a>
    div>
    <div class="action">
      <img
        src="https://qzonestyle.gtimg.cn/qzone/vas/opensns/res/img/Connect_logo_7.png"
        alt=""
      />
      <div class="url">
        <a href="javascript:;">忘记密码a>
        <a href="javascript:;">免费注册a>
      div>
    div>
  div>
template>

<style lang="less" scoped>
// 账号容器
.account-box {
  .toggle {
    padding: 15px 40px;
    text-align: right;
    a {
      color: @xtxColor;
      i {
        font-size: 14px;
      }
    }
  }
  .form {
    padding: 0 20px;
    &-item {
      margin-bottom: 28px;
      .input {
        position: relative;
        height: 36px;
        > i {
          width: 34px;
          height: 34px;
          background: #cfcdcd;
          color: #fff;
          position: absolute;
          left: 1px;
          top: 1px;
          text-align: center;
          line-height: 34px;
          font-size: 18px;
        }
        input {
          padding-left: 44px;
          border: 1px solid #cfcdcd;
          height: 36px;
          line-height: 36px;
          width: 100%;
          &.error {
            border-color: @priceColor;
          }
          &.active,
          &:focus {
            border-color: @xtxColor;
          }
        }
        .code {
          position: absolute;
          right: 1px;
          top: 1px;
          text-align: center;
          line-height: 34px;
          font-size: 14px;
          background: #f5f5f5;
          color: #666;
          width: 90px;
          height: 34px;
          cursor: pointer;
        }
      }
      > .error {
        position: absolute;
        font-size: 12px;
        line-height: 28px;
        color: @priceColor;
        i {
          font-size: 14px;
          margin-right: 2px;
        }
      }
    }
    .agree {
      a {
        color: #069;
      }
    }
    .btn {
      display: block;
      width: 100%;
      height: 40px;
      color: #fff;
      text-align: center;
      line-height: 40px;
      background: @xtxColor;
      &.disabled {
        background: #cfcdcd;
      }
    }
  }
  .action {
    padding: 20px 40px;
    display: flex;
    justify-content: space-between;
    align-items: center;
    .url {
      a {
        color: #999;
        margin-left: 10px;
      }
    }
  }
}
style>

登录模块入口组件 src/views/Login/index.vue

在主体登录区域引入并使用表单组件。




消息提示组件 和 复选框组件

组件外观

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Mqc5JFik-1668072991725)(media/05-1627943191361.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YjHBxdKU-1668072991727)(assets/image-20210803065321123.png)]

XtxCheckBox 组件使用




我已同意

Message 组件使用

Message 组件支持 函数 和 组件 两种调用方式:

函数调用 - 推荐
  • 调用函数时由函数内部动态创建组件,使用完成自动卸载

  • 组件库代码升级: src\components\XtxUI\index.ts 组件库做统一出口
// 做统一出口
export { message } from "./Message/index";
  • 调用 message 函数变得更方便

登录前表单校验

目标:校验之前我们已经实现了很多次,这里弱化验证写法,简单做一个非空检验即可。

参考代码

<script setup lang="ts">
import { reactive } from "vue";
import { message } from "@/components/XtxUI";

const form = reactive({
  account: "",
  password: "",
});

const loginBtn = () => {
  if (!form.account) {
    message({ type: "error", text: "用户名或手机号不能为空" });
    return;
  }
  if (!form.password) {
    message({ type: "error", text: "密码不能为空" });
    return;
  }
  if (!isAgree.value) {
    message({ type: "error", text: "请同意许可" });
    return;
  }
  console.log("通过校验,可以发送请求");
};
script>


//  绑定 form 响应式数据到表单元素中
<input v-model.trim="form.account" type="text" placeholder="请输入用户名或手机号" />
<input v-model.trim="form.password" type="password" placeholder="请输入密码" />
<XtxCheckBox v-model="isAgree">我已同意XtxCheckBox>

//  登录按钮
<a href="javascript:;" class="btn" @click="loginBtn">登录a>

会员 Pinia 状态管理

新建 Strore

新建文件: src\store\modules\member.ts

import { defineStore } from "pinia";

const useMemberStore = defineStore("member",{
  // 状态
  state: () => ({}),
  // 计算
  getters: {},
  // 方法
  actions: {},
});

export default useMemberStore;


合并 Store

修改文件:src\store\index.ts

import { defineStore } from 'pinia';
import useHomeStore from './modules/home';
+ import useMemberStore from './modules/member';

// 定义总Store,管理所有模块Store
const useStore = defineStore('main', {
  // state 存放模块 Store
  state: () => ({
    home: useHomeStore(),
+    member: useMemberStore(),
  }),
});

// 默认导出
export default useStore;

  • 调试工具检查 Pinia。

账户登录实现

本节目标: 实现账户名密码登录功能

用户名和密码登录接口

基本信息

Path: /login

Method: POST

接口描述:

登录成功后,后台返回的 token,请在本地保存,并在每次请求接口时在 Header 中携带上。

请求参数

Body

名称 类型 是否必须 默认值 备注 其他信息
account string 必须 用户名或手机号 undefined: ceshi
password string 必须 密码 undefined: 123456

发送登录请求

修改文件:src/store/modules/member.ts

import { http } from "@/utils/request";
import { defineStore } from "pinia";

const useMemberStore = defineStore("member",{
  // 状态
  state: () => ({
    profile: {},
  }),
  // 计算
  getters: {},
  // 方法
  actions: {
    // 用户名和密码登录
    async login(data: { account: string; password: string }) {
      const res = await http("POST", "/login", data);
      console.log("/login", res.data.result);
    },
  },
});

export default useMemberStore;


3)登录表单调用

login-form.vue




定义TS类型

新增类型文件:src\types\api\member.d.ts

export interface Profile {
  id: string;
  account: string;
  mobile: string;
  token: string;
  avatar: string;
  nickname: string;
  gender: string;
  birthday: string;
  cityCode: string;
  provinceCode: string;
  profession: string;
}


应用类型:

import type { Profile } from "@/types";
import { http } from "@/utils/request";
import { defineStore } from "pinia";
import { message } from "@/components/XtxUI";
// 非 vue 组件,导入路由实例
import router from "@/router";

const useMemberStore = defineStore("member", {
  // 状态
  state: () => ({
    // 用户信息
    profile: {} as Profile,
  }),
  // 计算
  getters: {
    // 是否登录
    isLogin(): boolean {
      return Boolean(this.profile.token);
    },
  },
  // 方法
  actions: {
    // 用户名和密码登录
    async login(data: { account: string; password: string }) {
      const res = await http<Profile>("POST", "/login", data);
      // 1. 保存用户信息到 state 中
      this.profile = res.data.result;
      // 2. 请求成功给用户提示
      message({ type: "success", text: "登录成功" });
      // 3. 跳转页面
      router.push('/')
    },
  },
});

export default useMemberStore;


持久化存储

登录成功后,把用户信息保存到本地。(本地和pinia都保存)

用户信息渲染

用户信息渲染: Layout/components/app-topnav.vue

<script setup lang="ts">
import useStore from "@/store";
import { storeToRefs } from "pinia";

const { member } = useStore();
// storeToRefs 解构出来的数据还能保持响应式
const { profile } = storeToRefs(member);
script>

<template>
  <nav class="app-topnav">
    <div class="container">
      <ul>
        <template v-if="member.isLogin">
          <li>
            <a href="javascript:;">
              <i class="iconfont icon-user">i>
              {{ profile.nickname || profile.account || profile.mobile  }}
            a>
          li>
          <li><a href="javascript:;">退出登录a>li>
        template>
        <template v-else>
          <li><RouterLink to="/login">请先登录RouterLink>li>
          <li><a href="javascript:;">免费注册a>li>
        template>
        ...
      ul>
    div>
  nav>
template>

退出登录实现

本节目标: 实现账户名密码登录功能

1 提供 actions, 清空用户数据 (本地和pinia都清空)

actions: {
    // 退出登录
    async logout() {
      // 1. 清理 Pinia 用户信息
      this.profile = {} as Profile;
      // 2. 提示用户
      message({ type: 'success', text: '退出成功' });
      // 3. 跳转页面
      router.push("/login");
    },
},

  1. 页面中调用
<template v-if="profile.isLogin">
  <li>
    <a href="javascript:;"><i class="iconfont icon-user">i>
      {{ profile.nickname || profile.account || profile.mobile  }}
    a>
  li>
  <li><a @click="member.logout()" href="javascript:;">退出登录a>li>
template>

优化:登录成功页面回跳

src\views\Layout\components\app-topnav.vue



登录成功后跳转处理

const useMemberStore = defineStore("member",{
  ...
  // 方法
  actions: {
    // 用户名密码登录
    async login(data: { account: string; password: string }) {
      ...
      //  在非 .vue 组件中 useRoute() 返回 undefined,没法获取当前路由信息
      //  解决方案,通过 router 路由实例 currentRoute 获取
      const { target = "/" } = router.currentRoute.value.query;
      // 跳转到指定地址
      router.push(target as string);
    },
  },
});


请求拦截器和响应拦截器

请求拦截器 - 请求成功携带 token

完善请求拦截器 utils/request.ts

// 官方说明:https://pinia.vuejs.org/core-concepts/outside-component-usage.html
// ❌ 非组件中,Pinia 常见错误写法
// const { member } = useStore();

// 添加请求拦截器
instance.interceptors.request.use(
  function (config) {
    // 在发送请求之前做些什么
    // ✅ 在组件外,哪里使用,写哪里(消费前获取)
    const { member } = useStore();
    // 1. 获取token
    const { token } = member.profile;
    // 2. token 和 headers 的非空判断
    if (token && config.headers) {
      // 3. 请求头中携带 token 信息
      config.headers.Authorization = `Bearer ${token}`;
    }
    return config;
  },
  function (error) {
    // 对请求错误做些什么
    return Promise.reject(error);
  }
);

响应拦截器 - 请求失败提示用户

响应拦截器,添加错误提示

import { message } from '@/components/Message'

// 添加响应拦截器
instance.interceptors.response.use(
 function (response) {
    // 如果请求成功成功 2xx 就直接返回 data 中的数据
   return response
 },
 function (error) {
    // 对响应错误做点什么
    if (error.code === 'ERR_NETWORK') {
      // 无网络,错误提示
      message({ type: 'error', text: '亲,换个网络试试~' });
    } else {
      // 有网络,但后端认为有错误,提示后端响应的错误
      message({ type: 'error', text: error.response.data.message });
    }
    // 控制台显示错误
    return Promise.reject(error);
})


QQ三方登录 - 前置环境和交互

登录简要流程梳理

本节目标: 掌握第三方登录的实现流程

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NxRV3K20-1668072991731)(media/06-1628025535476.png)]

  1. 在登录页面,QQ登录按钮处,添加个超链接,赋予其打开QQ登录页面功能
  2. 回跳的页面得到QQ给的唯一标识 openId,根据openId去后台查询是否已经绑定过账户
    • 如果绑定过,完成登录
    • 没有绑定过
      • 有账号的,绑定手机号,即为登录
      • 没账号的,完善账户信息,注册后登录
  3. 登录成功后,跳转首页或者来源页面

申请流程

1)参考文档

  1. 准备工作(opens new window)
  2. QQ互联JS_SDK(opens new window)

2)大概步骤

  1. 准备一个已经备案的网站需要有 QQ 登录的逻辑(登录页面,回跳页面)
  2. 然后在 QQ 互联上进行身份认证,并且审核通过
  3. QQ 互联上创建应用,应用需要域名,备案号,回调地址
  4. 等待人工审核,审核通过会得到 应用ID 回调地址 , 应用key 后端使用。
  5. 帮大家申请的结果如下:
# 测试用 appid 
# 100556005

# 测试用 redirect_uri
# http://www.corho.com:8080/#/login/callback

常见疑问❓

  1. 这个申请工作一般由谁去做?
    • 公司的运维 (负责管理公司账号的人)
  2. 申请下来的 id,应用 key,回调地址 uri 能改吗?
    1. 都不能修改,否则无效。
    2. 回调地址 uri 的包含四部分: 1. 域名,2. 端口号 3. 哈希路由模式 4. 路由地址 都必须完全一致,否则不能展示。
  3. 访问 http://www.corho.com:8080/ 看不到内容?
    1. 修改脚手架 vite.config.ts 配置。
    2. 修改电脑的 host 文件,访问本地服务器。

QQ三方登录-电脑环境设置

目标:浏览器访问 http://www.corho.com:8080/#/login/callback 地址,能打开正在开发的本地 Vue 项目。

核心步骤

  1. 修改脚手架 vite.config.ts 配置。
  2. 修改电脑的 host 文件。

第一步:修改 vite 配置

修改 vite.config.ts 文件:

export default defineConfig({
  // 配置开发服务器
  server: {
    // QQ三方登录的回调uri为:http://www.corho.com:8080/#/login/callback
    // vite 中配置: www.corho.com:8080
    host: "www.corho.com",
    port: 8080,
    // 其他有价值的配置项
    open: true, // 帮我们打开浏览器
    cors: true, // 允许开发时 ajax 跨域
  },
  ...
});

第二步:修改 host 文件

windows 系统

提醒:修改电脑配置,需要先退出 360 或 各种管家 各种 杀毒软件

提醒:如果修改 hosts 文件有弹窗警告,点击信任(因为这是我们自己进行的安全操作)

1. 找到 C:\Windows\System32\drivers\etc 下 hosts 文件
2. 在文件中加入  127.0.0.1    www.corho.com
3. 保存即可

# 如果提示没有权限
1. 将hosts文件移到桌面,然后进行修改,确认保存。
2. 将桌面hosts文件替换c盘文件

mac OS 系统
1. 打开命令行窗口
2. 输入:sudo vim /etc/hosts
3. 按下:i 键
4. 输入:127.0.0.1       www.corho.com
5. 按下:esc
6. 按下:shift + :
7. 输入:wq 回车即可

关键步骤验证

步骤验证:浏览器访问 http://www.corho.com:8080/#/ 能看到自己开发的 Vue3 项目表示成功。

DNS 解析概念 - 了解

由于本地我们的网站是访问http://localhost:3000

而回调地址的域名是http://www.corho.com:8080,俩个地址不一致是无法进行跳转的,

需要我们修改本地的 hosts 文件,让域名访问时解析到我们本地的ip上

DNS 解析:(网络中,服务器不认域名的,认的是 ip) www.jd.com

  1. 作用是将域名地址解析成ip地址
  2. 优先级 先以本地的 hosts 文件为主 然后才走线上的dns服务器

DSN解析说明

DNS解析: 将域名解析成ip地址的过程。

想看一个网站 www.jd.com => 电脑不知道什么是 www.jd.com,需要询问的

  1. 先问本地 hosts 文件(一般不改) 如果本地配置了 域名 和 地址的映射关系,优先使用 hosts 中的映射

    127.0.0.1           www.jd.com
    
    
  2. 如果本地hosts文件里面没配(默认一般都没配)比如:找www.baidu.com

    会找线上的 dns 服务器, dns 服务器就像一个字典, 字典中记录大量的 网站域名 和 网站ip 的对应关系
    
    dns 服务器
    112.80.248.75      www.baidu.com
    xxx.xx.xxx.xx      www.xxx.com
    
    

回跳页面路由准备

前面的配置完成了:http://www.corho.com:8080/#/

接下来在完善:http://www.corho.com:8080/#/login/callback

配置路由和组件

新建组件:views/Login/callback.vue





配置路由:

2)绑定路由 (一级路由)

{
  path: '/login/callback',
  component: () => import('@/views/Login/callback.vue')
},

步骤验证:http://www.corho.com:8080/#/login/callback 看到回调页面组件。

常见错误:没有使用 hash 哈希路由模式,无法识别 # 哈希部分路径,请检查并修正。

QQ授权登录实现

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RcTw1uat-1668072991732)(media/image-20220222213438246.png)]

按钮跳转实现

1)在index.html 开发需要的添加 sdk.js 文件导入。

<script src="http://connect.qq.com/qc_jssdk.js" data-appid="100556005" data-redirecturi="http://www.corho.com:8080/#/login/callback">script>

2)在 src/views/login/components/login-form.vue 给图片套上跳转链接。

      
        
      

3)点击QQ登录按钮,点击后新窗口打开登录页面

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3WlMOMEq-1668072991734)(media/08-1628025535476.png)]

小知识补充:

// 这是什么东西,怎么看起来像乱码?
http%3A%2F%2Fwww.corho.com%3A8080%2F%23%2Flogin%2Fcallback

// 解码
decodeURIComponent('http%3A%2F%2Fwww.corho.com%3A8080%2F%23%2Flogin%2Fcallback')
// 解码结果:'http://www.corho.com:8080/#/login/callback'

// 转码
encodeURIComponent('http://www.corho.com:8080/#/login/callback')
// 转码结果:'http%3A%2F%2Fwww.corho.com%3A8080%2F%23%2Flogin%2Fcallback'

QQ互联核心 API

官方文档

项目中需要用到的 3 个 API

  • QC.Login.check
    • 检查用户是否登录
  • QC.api(“get_user_info”).success
    • 获取信息
  • QC.Login.getMe
    • 获取 QQ 用户唯一标识 openId

ESlint 添全局变量

  • 注意:由于项目开启了 eslint 检查,需要在 .eslintrc.cjs 添加QC 全局变量。
// eslintrc.cjs

module.exports = {
  ...
  // 全局变量
  globals: {
    QC: true,
  },
}

TS 类型声明文件

  • 注意:由于是 TS 开发,需要在 env.d.ts 添加 QC 类型声明。
// env.d.ts

// QC 类型声明 - QQ 登录模块
declare namespace QC {
  const Login: {
    // QC.Login.check()
    check: () => boolean;
    // QC.Login.getMe((openId) => {
    //   console.log("获取QQ用户openId", openId);
    // });
    getMe: (callback: (openId: string) => void) => void;
  };
  // QC.api("get_user_info").success((res: unknown) => {
  //   console.log("获取QQ用户资料", res);
  // });
  function api(s: string): {
    success: (res: unknown) => void;
  };
}

测试





回跳组件静态结构

目标:准备静态结果,渲染切换效果。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Q7OKGrln-1668072991736)(media/image-20210804062529636.png)]

静态结构

  • 复制组件静态结构:views/Login/callback.vue
  • 温馨提示:头部和底部和登录页相同,自己完成 LoginHeader 头部组件和 LoginFooter 底部组件的抽离。
...

<template>
  <LoginHeader>联合登录LoginHeader>
  <section class="container">
    <nav class="tab">
      <a
        href="javascript:;"
        class="active" 
      >
        <i class="iconfont icon-bind" />
        <span>已有小兔鲜账号,请绑定手机span>
      a>
      <a
        href="javascript:;"
      >
        <i class="iconfont icon-edit" />
        <span>没有小兔鲜账号,请完善资料span>
      a>
    nav>
    <div class="tab-content">
        
    div>
  section>
  <LoginFooter />
template>

<style scoped lang='less'>
.container {
  padding: 25px 0;
}
.tab {
  background: #fff;
  height: 80px;
  padding-top: 40px;
  font-size: 18px;
  text-align: center;
  a {
    color: #666;
    display: inline-block;
    width: 350px;
    line-height: 40px;
    border-bottom: 2px solid #e4e4e4;
    i {
      font-size: 22px;
      vertical-align: middle;
    }
    span {
      vertical-align: middle;
      margin-left: 4px;
    }
    &.active {
      color: @xtxColor;
      border-color: @xtxColor;
    }
  }
}
.tab-content {
  min-height: 600px;
  background: #fff;
}
style>

3)准备绑定手机组件 (有老账号) 和 完善信息组件(新账号)

src/views/Login/components/callback-bind.vue 绑定手机








src/views/Login/components/callback-register.vue 注册信息








4)使用组件

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IG1Z2AkN-1668072991737)(media/image-20210818144919351.png)]

完成切换交互

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-v0vb0R0C-1668072991738)(media/image-20210804062606238.png)]

实现思路 : 典型的tab类效果实现,升级成 动态组件实现。

代码落地

<script setup lang="ts">
...
const isBind = ref(true);
script>

<template>
  
  <LoginHeader>联合登录LoginHeader>
  
  <section class="container">
    
    <nav class="tab">
      <a
        href="javascript:;"
        @click="isBind = true"
        :class="{ active: isBind === true }"
      >
        <i class="iconfont icon-bind">i>
        <span>已有小兔鲜账号,请绑定手机span>
      a>
      <a
        href="javascript:;"
        @click="isBind = false"
        :class="{ active: isBind === false }"
      >
        <i class="iconfont icon-edit">i>
        <span>没有小兔鲜账号,请完善资料span>
      a>
    nav>
    
    <div class="tab-content">
      <KeepAlive>
        <component
          :is="isBind ? CallbackBind : CallbackRegister"
        />
      KeepAlive>
    div>
  section>
  
  <LoginFooter />
template>

QQ头像和昵称渲染

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GlcJxvhl-1668072991739)(media/image-20220222213959584.png)]

TS类型声明文件

新建文件:src\types\api\qq.d.ts

export interface Data {
  ret: number;
  msg: string;
  is_lost: number;
  nickname: string;
  gender: string;
  gender_type: number;
  province: string;
  city: string;
  year: string;
  constellation: string;
  figureurl: string;
  figureurl_1: string;
  figureurl_2: string;
  figureurl_qq_1: string;
  figureurl_qq_2: string;
  figureurl_qq: string;
  figureurl_type: string;
  is_yellow_vip: string;
  vip: string;
  yellow_vip_level: string;
  level: string;
  is_yellow_year_vip: string;
}

export interface QQUserInfo {
  status: string;
  fmt: string;
  ret: number;
  code: number;
  data: Data;
  seq: string;
  dataText: string;
}


统一出口导出 src\types\index.d.ts

// 统一导出所有自定义的类型文件
export * from "./api/home";
export * from "./api/category";
export * from "./api/goods";
export * from "./api/member";
+export * from "./api/qq";


渲染用户信息
  • 父组件传递属性




  • 子组件渲染




QQ三方登录 - 多状态介绍

回跳地址这样一个组件内做判断态,分以下 3 种状态。

状态1:已经有账号并且已经绑定 qq 直接获取用户信息,并直接跳转。

状态2:QQ 绑定某个已注册账号,绑定成功后,获取用户信息,再跳转。

状态3:注册全新账号并绑定 QQ,注册成功后,获取用户信息,再跳转。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xleEomrB-1668072991739)(media/image-20220222213459020.png)]

有账号未绑定 (绑定测试账号)

目标:把自己的 QQ 信息 绑定到某个测试账号中。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pwRAtjF6-1668072991749)(media/image-20220222215020282.png)]

温馨提示:如果测试账号已被绑定的,手动调用一下解绑被占用的测试手机号。

http://pcapi-xiaotuxian-front-devtest.itheima.net/login/social/unbind?mobile=测试手机号

实现思路

  1. 进行手机号码校验
  2. 进行短信验证码发送 (必须调用接口,验证码接口比较多,要看清楚后再调用)
  3. 进行绑定,完成后把当前用户数据存入Pinia,跳转到首页

手机号码校验



模板绑定



验证码按钮倒计时效果

自己补充

发送验证码业务

注意:这个发送的动作必须要有!也就是接口必须要调用才可以绑定。

接口文档:三方登录_发送已有账号短信

Path: /login/social/code

Method: GET

请求参数

Query

参数名称 是否必须 示例 备注
mobile 13211112222 手机号

参考代码



unionId 参数父传子

目标:把 unionId 参数传递给子组件,作为 QQ 登录接口其中一个参数。

父组件:src\views\Login\callback.vue





子组件:src\views\Login\components\callback-bind.vue



QQ登录并绑定手机号

接口文档:三方登录_账号绑定

Path: /login/social/bind

Method: POST

请求参数

Body

名称 类型 是否必须 默认值 备注 其他信息
unionId string 必须 三方标识 QQ登录后的 openId
mobile string 必须 手机号
code string 必须 验证码

Pinia 定义

import { http } from "@/utils/request";
import { defineStore } from "pinia";
import type { Profile } from "@/types";
import { message } from "@/components/XtxUI";
// 在非 .vue 组件文件中,可通过导入 router 直接获取路由实例
import router from "@/router";
import {
  clearStorageProfile,
  getStorageProfile,
  saveStorageProfile,
} from "@/utils/storage";

const useMemberStore = defineStore("member", {
  // 使用插件遇到小问题,自己手写本地存储
  // persist: true,
  // 状态
  state: () => ({
    // 用户资料
    profile: {} as Profile,
  }),
  // 方法
  actions: {
    // 登录成功后的复用逻辑封装
    loginSuccess(profile: Profile) {
      // 1. 提示
      message({ type: 'success', text: '登录成功' });
      // 2. 存储数据
      this.profile = profile;
      // 获取目标页面 target,如果没有目标页,则设置默认值 '/' 去首页
      const { target = '/' } = router.currentRoute.value.query;
      // 3. 跳转页面
      router.push(target as string);
    },
    // 用户名密码登录
    async login(data: { account: string; password: string }) {
      // 发送请求
      const res = await http<Profile>("POST", "/login", data);
      // 调用登录成功后的逻辑
      this.loginSuccess(res.data.result);
    },
    // 退出登录
    async logout() {
      // 1. 提示
      message({ type: 'success', text: '退出成功' });
      // 2. 清空用户信息
      this.profile = {} as Profile;
      // 3. 跳转页面
      router.push('/login');
    },
    // 三方登录_账号绑定  /login/social/bind
    async loginBind(data: { unionId: string; mobile: string; code: string }) {
      // 发送请求
      const res = await http<Profile>("POST", "/login/social/bind", data);
      // 调用登录成功后的逻辑
      this.loginSuccess(res.data.result);
    },
    // 获取三方登录验证码  /login/social/code
    async loginBindGetCode(mobile: string) {
      // 注意接口:三方登录_发送已有账号短信
      await http('GET', '/login/social/code', { mobile: mobile });
      // 温馨提醒:验证码是发送到用户手机上的
      message({ type: 'success', text: '验证码发送成功' });
    },
  },
});

export default useMemberStore;


QQ 三方登录回跳处理

遇到问题:QQ 三方登录无法实现回跳,如何解决?

目标:把路由的 target 目标页传递给 QQ 登录页,实现 QQ 登录后回跳。

      
        
      

有账号已绑定 (QQ登录直接跳转)

目标:QQ登录成功后,直接跳转。

实现思路

  1. 回跳组件初始化的时候,获取openId (openId => 对应用户身份 - QQ返回的唯一id身份标识)
  2. 根据 openId 去自己后台尝试进行 直接登录
  3. 如果成功,就代表已注册已绑定,记录返回的用户信息,跳转到首页或者来源页面

接口描述:三方直接登录

Path: /login/social

Method: POST

请求参数

Body

名称 类型 是否必须 备注
unionId string 必须 三方标识
source integer 必须 注册来源 注册来源,1为pc,2为webapp,3为微信小程序,4为Android,5为ios,6为qq,7为微信

代码落地

1)准备使用QQ uionId进行应用登录的接口

const useMemberStore = defineStore("member",{
  // 方法
  actions: {
    ...
    // QQ直接登录
    async loginQQUnionId(data: { unionId: string; source: number }) {
      const res = await http<Profile>("POST", "/login/social", data);
      // console.log("POST", "/login/social", res.data.result);
      this.profile = res.data.result;
      this.loginSuccess();
    },
  },
});

export default useMemberStore;


调用



可以考虑升级成枚举

// 1为pc,2为webapp,3为微信小程序,4为Android,5为ios,6为qq,7为微信
enum LoginSource {
  PC = 1,
  WebApp,
  MiniProgram,
  Android,
  IOS,
  QQ,
  WeChat,
}

无账号未绑定 (注册登录-课后作业)

说明:业务流程和绑定测试账号流程几乎一致,表单校验,发送验证码,绑定 openId 实现注册登录。

温馨提示:一个手机号和一个 QQ 号只能 注册并绑定一次。

  • 如果QQ号已绑定某个手机号,手动调用一下解绑接口。

http://pcapi-xiaotuxian-front-devtest.itheima.net/login/social/unbind?mobile=手机号

  • 如果手机号已被使用,更换新的手机号,建议记到小本本上,否则无法找回。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QWQhsf0i-1668072991751)(media/11-1628025535476.png)]

接口三方登录-注册登录

Path: /login/social/:unionId/complement

Method: POST

路径参数

参数名称 示例 备注
unionId megasuiscool unionId

Body

名称 类型 是否必须 默认值 备注 其他信息
account string 必须
mobile string 必须
code string 必须
password string 必须

倒计时逻辑函数封装-课后练习

封装一个通用的倒计时逻辑函数 useCountDown

需求描述

  1. 支持自定义参数传入定制倒计时初始值

    eg: useCountDown(60)

  2. 返回当前计数/暂停计时方法/开始计时方法供业务使用

    eg: const {countTime, start, pause} = useCountDown(60)

逻辑实现

参考文档:https://vueuse.org/shared/useintervalfn/

核心逻辑:每隔一秒钟,对 countTime 进行减 1 操作

  1. resume:可执行函数,开启定时器,每隔一个时间执行一次
  2. pause: 可执行函数,只要执行这个函数,定时器就会暂停

组件中写法

<script setup lang="ts">
import { useIntervalFn } from "@vueuse/core";
import { ref } from "vue";
    
const countTime = ref(0);
const { pause, resume } = useIntervalFn(
  () => {
    countTime.value--;
    if (countTime.value <= 0) {
      pause();
    }
  },
  1000,
  { immediate: false }
);

const start = () => {
  countTime.value = 60;
  resume();
};
script>

<template>
  <div>
    {{ countTime }}
    <button @click="start">开始定时器button>
    <button @click="pause">暂停定时器button>
    <button @click="resume">继续定时器button>
  div>
template>


封装成 hooks 钩子函数

src\hooks\index.ts 文件中新增以下代码:

/**
 * 倒计时效果
 * @param startTime  初始化时间
 * @returns count    倒计时秒数
 * @returns start    初始化启动
 * @returns resume   继续启动
 * @returns pause    暂停(清理定时器)
 */
export const useCountDown = (startTime = 60) => {
  // 倒计时秒数
  const count = ref(0);
  // resume  继续启动
  // pause   暂停(清理定时器)
  const { resume, pause } = useIntervalFn(
    () => {
      // 倒计时减少
      count.value--;
      // 倒计时结束
      if (count.value === 0) {
        pause();
      }
    },
    1000,
    // 不需要立即执行
    { immediate: false }
  );

  // 初始化启动函数
  const start = () => {
    // 倒计时
    count.value = startTime;
    resume();
  };

  // count   倒计时秒数
  // start   初始化启动
  // resume  继续启动
  // pause   暂停(清理定时器)
  return { count, resume, pause, start };
};

test 组件测试






课后应用

  • 自己把封装的倒计时 hooks 钩子函数,整合到发送验证码业务中。

你可能感兴趣的:(前端,商城,VUE,学习,vue.js,javascript)