vue3微信公众号商城项目实战系列(6)用户登录

1. 一个商城要实现购物的功能,需要能识别用户的身份,这样才能完成加购物车,下单,付款等操作。

但微信公众号商城和PC端商城有些不一样,区别在于微信公众号商城使用微信支付的时候需要一个openid的参数(以后再具体讲)

这个参数必须访问微信公众号提供的接口才能获取到,基于这个原因,用户登录的时候必须额外调用微信公众号的接口获取openid并保存下来,

以便付款的时候使用。

注:openid也可以在付款这个环节去获取,即时获取即时用,推荐在登录的时候处理。

 

2. 为了简化业务,将重点放在vue3前端技术上,我们的微信公众号商城设计尽可能简洁,用户表结构如下:

表名 字段 功能
userinfo

user_id (int) 用户编号

nickname (varchar) 昵称

acc (varchar) 账号

pwd (varchar) 密码

open_id (varchar) 微信openid

mobile (varchar) 联系电话

address (varchar) 收货地址

用户信息表

 

3. 对于一个商城来说,当用户不登录时是可以以游客的身份浏览商品的,当加购物车的时候再跳转到登录页,

所以登录页有2种类型的入口,一种是在 "我的" tab页,需要放置一个【登录】的连接;

另一种就是在进行【加购物车】等操作需要用户身份的时候自动跳转到登录页;

另外,登录页还需要提供注册功能,页面如下图:

当用户输入账号密码的时候,去调用后端的接口完成登录验证,如果验证通过,就将token及用户信息返回,

前端页面将返回的信息保存到本地,这样后续调用需要用户身份的接口时就不需要重复登录了。

 

4. vue3项目和一般的web服务器项目比如 jsp 、 php 、asp.net  有少许不同,后者不但可以编写页面,还可以直接访问数据库,

而vue3是一个前端框架,它只负责页面的呈现、响应用户的操作,这个过程中需要或产生的数据要以 ajax 的形式通过 webapi 和数据库打交道。

vue3中官方推荐的 ajax 包是 axios , 可以用它来处理各种服务端的 webapi 请求,

官网地址:https://www.axios-http.cn/docs/intro,官方文档截图如下:

 

5. 使用 axios  ,分2步:

第1步:安装 axios 包 , 命令: npm install axios,如下图:

安装完成:

 

第2步:导入 axios 并使用 Axios API 调用 后端的 webapi ,具体可以参考文档。

本系列中会对 Axios API 做封装,方便后续维护。

另外,用户登陆后需要在本地存储 token等信息以便其他接口使用,还需要对 token等信息做持久化处理。

 

6. 完整步骤如下:

第1步:新增如下文件夹和文件

storage.js持久化登录后的用户信息,request.js封装axios请求,api.js 记录具体的api接口,Login.vue是登录页面,代码分别如下。

storage.js 代码

const storage = {
    set:function(val) {
        localStorage.removeItem("userinfo");
        localStorage.setItem("userinfo", JSON.stringify(val));
    },
    get:function(){ 
        let info =  localStorage.getItem("userinfo"); 
        if(!info || typeof info == "undefined"){  
            return {};
        }
         
        try{  
            return JSON.parse(info);
        }
        catch(e){
            return {};
        } 
    },
    clear:function(){
        localStorage.removeItem("userinfo")
    },
    check:function(){
        let info =  localStorage.getItem("userinfo"); 
        if(info && info.length>1){
            return true;
        }
        return false;
    }
}


export default storage;

 定义了一个storage对象,该对象有4个方法,分别用于管理本地的用户信息,localStorage 是浏览器上的全局对象

,我们用 storage 做了一下封装,如果以后要更换持久化的方式,在这里改就可以了,外部的调用不变。

注:

1. 微信浏览器内核是QQ浏览器X5内核。

2. localStorage的几个方法都需要一个key值,我们这里固定为 userinfo,实际项目也可以给 storage 对应的方法加参数,这样可以从外部传入key值。

 

request.js 代码

import axios from 'axios';
import storage from '@/http/storage.js';


//配置默认的Content-Type格式
axios.defaults.headers['Content-Type'] = 'application/x-www-form-urlencoded';

//创建axios实例
const service = axios.create({   
    timeout: 60*1000, //请求超时时间60秒
    baseURL: '/dev-api',
})


//请求拦截器
service.interceptors.request.use(config => {  
    //***在发送请求之前做些什么*** 
    if(storage.check()){ 
        //如果已登录就把token信息加到请求头中,不同的webapi这里的添加方式不一样,这是ASP.NET的写法
        let info = storage.get(); //获取存储在本地的token信息
        config.headers['Authorization'] = 'Bearer ' + info.token 
    } 
    return config;
}, error => {  
    //***对请求错误做些什么***
    return Promise.reject(error);
})


//响应拦截
service.interceptors.response.use(response => {
    //***2xx 范围内的状态码都会触发该函数***
    //***对响应数据做点什么***
    return response.data.data; 
}, error => {   
    //***超出 2xx 范围的状态码都会触发该函数***
    //***对响应错误做点什么***
    return Promise.reject(error); 
})


//导出实例对象
export default service;

这里的代码基本上是从axios上复制而来,只在请求拦截器上加了一个校验,如果 持久化存储中的 userinfo 有值

,就把 token 加到请求头中,userinfo 的 json 格式如下(微信建议不要将 open_id 下发到客户端,实际项目中请去掉,这里只为演示):

{
userId:111,
nickname:'张三',
acc:'abcd1',
pwd:'1',
open_id:'xxxxx',
mobile:'15900008888',
address:'阳光大道123号豫王府2栋1单元1601',
token:'xxxxxxxxxxxxxxxxx'
}

每段代码都有注释,重点说一下 "baseURL: '/dev-api'," 这一段,baseURL 是请求的基地址 , 先看 官方文档上的解释:

它可以是一个完整api接口前缀 , 也可以是本文上面配置的 "/dev-api" ,配置完后,我们调用接口的时候

,实际的请求地址会和 baseURL 拼接成完整的地址作为最终的接口地址,这里我们为什么要配置成  "/dev-api"的形式

,而不用完整的地址呢,接下来会讲到这样做的目的。

 

api.js 代码:

import request from './request.js'

//登录接口
//write by: 屏风马
//date : 2023-4-17
export function signin(data) {
    return request({
      url: '/sign/signin', 
      method: 'post',
      data: data
    })
}

这里对后端接口做了封装,统一放置在这个文件中,如果有其他接口,只需要在这里加方法就可以了

,"import request from './request.js'" 中的 request 就是 request.js 中导出的 service 对象,它代表的是一个配置好的 axios 实例对象

,return request( ) 传入的参数是一个 json 对象,url 是接口地址 , 实际请求会和 baseURL 拼接后再发送出去

,如果 baseURL = 'https://some-domain.com/api/' ,那么实际请求地址就是 'https://some-domain.com/api/sign/signin'

,相应的, 如果baseURL = '/dev-api' ,那么实际请求地址就是 '/dev-api/sign/signin' 。

method 和 data 分别表示请求的方法和参数。Login.vue页面调用的时候只需要传入请求的参数就可以,非常简洁,因为所有接口都在这个文件中,后期如果

要改接口对应的路径,比如部分路径不符合 RESTful 风格可能要调整,直接到这个文件修改就可以了,否则,大量的接口 url 散落在项目的各个文件中

,维护起来就比较头大。

 

Login.vue 代码

 1 <template>
 2     <div class="login"> <br /> 
 3         <div><span>用户登录</span></div> <br />
 4         <div>
 5             <dl>
 6                 <dd>账号</dd> 
 7                 <dt><input name="acc" v-model="loginForm.acc" /></dt>
 8                 <dd>密码</dd>
 9                 <dt><input name="pwd" v-model="loginForm.pwd" /></dt> 
10                 <dt><input type="button" value="登录" @click="onLogin" /></dt> 
11                 <dt>没有账号?去注册</dt>
12             </dl> 
13         </div> 
14     </div>    
15 </template>
16  
17 <script setup>
18 import { reactive } from 'vue';
19 import { useRouter } from "vue-router";
20 import { signin } from "@/http/api";
21 import storage from '@/http/storage.js';
22 
23 const router = useRouter();
24 const loginForm = reactive({
25     acc: 'abcde',
26     pwd: '1'
27 })
28 
29 function onLogin() { 
30     signin(loginForm).then(res=>{  
31         storage.set(res);
32         router.push({name:'home'});
33     }); 
34 } 
35 </script>
36  
37 <style>
38 .login{
39     width: 370px;
40     margin: 0 auto; 
41     text-align: center;
42 }
43 </style>

我们逐行分析一下这里的代码:

第7行和9行是一个输入框对象,上面有一个 v-model的属性 , 它对应的是 24 行定义的 loginForm 对象的2个属性,这种写法是 vue3 的绑定语法

,任何一个 json对象都可以用 vue3 框架内置的 reactive() 函数处理一下,这样做的结果就是 <script>代码块中的 loginForm 对象的2个属性和

输入框的 v-model 属性进行了绑定,当用户在页面上输入值时 , loginForm 对象中的属性被同步修改了,反之亦然, 即做到了输入框的值和

代码中的值完全同步 !这就是 vue3 的响应式编程的双向数据绑定,如果用传统的3件套来写,取值赋值及onblur事件都要写一堆的代码,效率很低。

而 vue3 只要用 reactive( ) 把一个 json对象处理一个就解决了全部的问题。

第10行是一个按钮,上面有一个click事件,只要在 click 前面加上 @ 符号,后面的字符串 "onLogin"  就和 <script>代码块中的

函数 function onLogin() { }绑定在一起了,当用户点击登录按钮的时候就会执行 onLogin() 这个方法。

第17行,<script>代码块中有一个 setup 关键字,这表示组件的书写方式是组合式API(Composition API)

,另一种方式是选项式API(Options API),下图是官网上的说明,通过对比组合式API更扁平化、更清晰,本系列全部用组合式API

 

第18行 , reactive 是 vue 框架中的函数,使用前要先导入进来后再使用。

第19行,useRouter 是路由对象 vue-router 中的函数,如果不用 <router-link>标签来导航而用编程的方式(类似 location.href )做跳转需要用到这个函数。

第20行,导入 api.js 中的 signin 方法,因为接下来登录会用到它,如果要用到多个接口方法,用逗号隔开就可以了。

js 文件导入时可以省略 .js 的后缀名。from 后的 @ 代表路径 "/src" ,因为它的使用频率很高,就用 @ 代替,这是在 vite.config.js中配置的,如下:

resolve: {
      alias: {
        '@': fileURLToPath(new URL('./src', import.meta.url))
      }
    },

第21行,导入 storage 这个对象来存储登录成功后 接口端传递过来的用户信息,其 json 格式上面有介绍。

第29行,这个函数响应用户的登录操作,signin( ) 是 api.js中暴露出来的登录接口对应的函数,只需要传入 loginForm 就可以了

,因为它是一个响应对象 ,同步记录了用户的输入,不需要额外的处理就得到了输入框的数据,一些都是那么简单

,signin() 的返回值是一个 Promise 对象,执行成功后用 .then( ) 来处理返回值,传入一个箭头函数

,先把返回值 存储在了 localStorage 中,然后用 23 行生成的 路由对象的 push 方法跳转到 home 这个页面。

 

关于 router 对象:

1. 导航到另一个页面用 router.push() 方法,从方法名可以看到除了导航到目标页面外,它还把页面压入栈中

,这样当用户点返回按钮的时候就能退回到上一个页面,而不需要我们在程序中做任何追踪处理。

2. router.push() 跳转时 可以带参数,这个后面根据情况具体讲。

3. 这里的 router 是根据路由表中定义的名称来导航的,见 /src/router/routerx.js 中的配置:

const routes = [
    {path:"/",name:'welcome',component:()=>import("../views/Welcome.vue")},
    {path:"/home",name:'home',component:()=>import("../views/Home.vue")},
    {path:"/login",name:'login',component:()=>import("../views/Login.vue")},
]

 我们还可以根据 path 属性来导航,为了整个项目风格统一,用 name 就可以了。

4. 每一个页面都需要在上面的 routers 数组中预先配置好,这样不至于导航的时候找不到对应的页面。登录 login.vue 能够访问是因为这里已做了配置。

 

接下来,我们在 chrome 中模拟访问 /login 页面, 如下:

页面报错了,这是因为在 request.js 中把 baseURL 设置成了 "/dev-api",这个路径因为没有完整的URL

,axios 默认给我们把当前开发 web 服务器的 ip和端口(localhost:5173) 加上去了,如下图:

实际项目中,后端接口可能是另一个小组的同事在写,为了开发方便,这个接口写好后通常会部署在公网的一台开发服务器上,方便前端同事调用

,其接口地址类似:https://some-domain.com/api/sign/signin,所以我们需要配置后端接口的url地址,打开 vite.config.js , 加入如下的配置信息:

server: { 
    host: true,
    port:80,
    open:true, 
    proxy:{
      '/dev-api':{
        target: 'http://testapi.xxx.com/api',
        changeOrigin: true, 
        secure: false, 
        rewrite:(path)=> path.replace('\/dev-api', '')
      }, 
    } 
  },

host 可以是开发服务器所在的 ip 地址 , 如果不限 ip , 用 true 就可以。port 是端口号,默认是 5173,这里我们改成 80 ,这样chrome中调试就可以省略端口号了。

proxy 用于配置代理服务器信息。在开发阶段,后端接口服务地址前缀一般是这样的 "http://testapi.xxx.com/api/" ,我们本机开发环境运行vue3项目是 localhost:80

, 去访问远程服务器时会存在跨域问题,这是因为 vue3 项目本质是运行在浏览器内核上的,如果访问的服务器域名+端口+协议(https和http是不同的协议)不同

,浏览器就认为跨域了,会禁止访问,所以在这里配置代理信息,当我们的访问网址中含有 "/dev-api" 这个路径的时候

,就以代理服务器  'http://testapi.xxx.com/api' 的名义去访问

,即 "http://localhost:5173/dev-api/sign/signin" 会被变成 "http://testapi.xxx.com/api/dev-api/sign/signin"

,配置中的属性 rewrite 的值是一个箭头函数,作用是用正则表达将路径中的 "'/dev-api'" 替换成空字符串

,所以实际的代理地址是 "http://testapi.xxx.com/api/sign/signin",这正是我们要访问的地址。

 

在 signin中加入log信息,如下红色字体:

function onLogin() { 
    signin(loginForm).then(res=>{ 
        console.log(res);
        storage.set(res);
        router.push({name:'home'});
    }); 
} 

将浏览器地址中的端口号去掉,刷新页面后点登录按钮如下:

登录成功,打印出了后端接口返回的用户数据及token信息,并跳转到了 Home.vue 页面。

 

posted @ 2023-04-17 18:25  屏风马  阅读(658)  评论(0编辑  收藏  举报