美文网首页Vue
【Vue】Vue项目实战1

【Vue】Vue项目实战1

作者: LM林慕 | 来源:发表于2019-10-19 14:57 被阅读0次

    此文项目代码:https://github.com/bei-yang/I-want-to-be-an-architect
    码字不易,辛苦点个star,感谢!

    引言


    此篇文章主要涉及以下内容:

    1. UI库选型思路
    2. 全家桶融会贯通vue-router+vuex
    3. 前端登录和权限控制
    4. 前后端交互
    5. 解决跨域问题

    学习资源


    1. UI库:cube-ui
    2. 后端接口编写:koa
    3. 请求后端接口:axios
      着重关注请求、响应拦截
    4. 令牌机制:Bearer Token
    5. 代理配置、mock数据:vue-cli配置指南webpack配置指南

    开发环境


    1. vscode下载
    2. node.js下载

    选择一个合适的UI库


    vue add cube-ui
    

    扩展性


    任何ui库都不能满足全部的业务开发需求,都需要自己进行定制和扩展,组件化设计思路至关重要

    登录页面


    • 安装router:vue add router
    • 安装vuex:vue add vuex
    • 配置路由router.js
    import Vue from "vue";
    import Router from "vue-router";
    import Home from "./views/Home.vue";
    import Login from "./views/Login.vue";
    
    Vue.use(Router);
    
    const router = new Router({
      mode: "history",
      base: process.env.BASE_URL,
      routes: [
        {
          path: "/",
          name: "home",
          component: Home
        },
        {
          path: "/login",
          name: "login",
          component: Login
        },
        {
          path: "/about",
          name: "about",
          meta: {
            auth: true
          },
          // route level code-splitting
          // this generates a separate chunk (about.[hash].js) for this route
          // which is lazy-loaded when the route is visited.
          component: () =>
            import(/* webpackChunkName: "about" */ "./views/About.vue")
        }
      ]
    });
    
    // 路由守卫
    router.beforeEach((to, from, next) => {
      if (to.meta.auth) {
        // 需要登录
        const token = localStorage.getItem("token");
        if (token) {
          next();
        } else {
          next({
            path: "/login",
            query: { redirect: to.path }
          });
        }
      } else { // 不需要登录验证
        next()
      }
    });
    
    export default router;
    
    
    • 登录状态,store.js
    export defalut new Vuex.Store({
      state: {
        isLogin: false
      },
      mutations: {
        setLoginState(state, b) {
          state.isLogin = b;
        }
      },
      actions: {}
    })
    
    <div>
        <div class="logo">
          <img src="https://img.kaikeba.com/logo-new.png"
               alt>
        </div>
        <!-- <cube-button>登录</cube-button> -->
        <cube-form :model="model"
                   :schema="schema"
                   @submit="handleLogin"
                   @validate="haneldValidate"></cube-form>
      </div>
    

    分别设置modelschema

     model: {
            username: "",
            passwd: ""
          },
          schema: {
            // 表单结构定义
            fields: [
              // 字段数组
              {
                type: "input",
                modelKey: "username",
                label: "用户名",
                props: {
                  placeholder: "请输入用户名"
                },
                rules: {
                  // 校验规则
                  required: true
                },
                trigger: "blur"
              },
              {
                type: "input",
                modelKey: "passwd",
                label: "密码",
                props: {
                  type: "password",
                  placeholder: "请输入密码",
                  eye: {
                    open: true
                  }
                },
                rules: {
                  required: true
                },
                trigger: "blur"
              },
              {
                type: "submit",
                label: "登录"
              }
            ]
          }
    

    model就是输入框绑定的数据,schema是具体的表单的描述,会动态渲染一个表单
    每次校验都会触发 handleValidate方法,打印校验的结果

    handleValidate(ret){
      console.log(ret)
    }
    

    点击登录触发handleLogin发起登录请求
    Login.vue

    handleLogin (e) {
          // 组织表单默认提交行为
          e.preventDefault();
          // 登录请求
          //   this.login(this.model) // 使用mapActions
          this.$store
            .dispatch("login", this.model)
            .then(code => {
              if (code) {
                // 登录成功重定向
                const path = this.$route.query.redirect || "/";
                this.$router.push(path);
              }
            })
            .catch(error => {
              // 有错误发生或者登录失败
              const toast = this.$createToast({
                time: 2000,
                txt: error.message || error.response.data.message || "登录失败",
                type: "error"
              });
              toast.show();
            });
        },
    

    登录动作编写:提交登录请求,成功后缓存token并且提交至store
    store.js

    import Vue from "vue";
    import Vuex from "vuex";
    import us from "./service/user";
    
    Vue.use(Vuex);
    
    export default new Vuex.Store({
      state: {
        isLogin: localStorage.getItem('token') ? true : false
      },
      mutations: {
        setLoginState(state, b) {
          state.isLogin = b;
        }
      },
      actions: {
        login({ commit }, user) {
          // 登录请求
          return us.login(user).then(res => {
            const { code, token } = res.data;
            if (code) {
              // 登录成功
              commit("setLoginState", true);
              localStorage.setItem("token", token);
            }
            return code;
          });
        }
      }
    });
    
    

    编写接口服务
    service/user.js

    import axios from 'axios'
    
    export default {
        login(user){
            return axios.get('/api/login', {params:user})
        }
    }
    

    webpack devServerpost支持不好,这里暂时使用get请求

    configureWebpack: {
      devServer: {
        before(app) {
          app.get('/api/login', function (req, res) {
            const {
              username,
              passwd
            } = req.query;
            console.log(username, passwd);
    
            if (username === 'xxx' && password === 'xxx') {
              res.json({
                code: 1,
                token: 'abcdtoken'
              });
            } else {
              res.status(401).json({
                code: 0,
                message: '用户名或密码错误'
              })
            }
          })
        }
      }
    }
    

    检查点


    • 如何路由守卫
    • 如何进行异步操作
    • 如何保存登录状态
    • 如何模拟接口

    http拦截器


    有了token之后,每次http请求发出,都要加在header

    // interceptor.js
    const axios=require('axios')
    
    export default function(){
      axios.interceptors.request.use(config=>{
        const token=localStorage.getItem('token')
        if(token){
          config.headers.token=token;
        }
        return config;
      })
    }
    
    // 启用 main.js
    import interceptor from './interceptor'
    interceptor()
    

    可以通过登录接口测试拦截器效果,但是不合理,我们编一个用户信息接口,需要携带token才能访问

    // mock接口 vue.config.js
    function auth(req, res, next) {
      if (req.headers.token) {
        // 已认证
        next()
      } else {
        res.sendStatus(401)
      }
    }
    
    app.get('/api/userinfo', auth, function (req, res) {
      res.json({
        code: 1,
        data: {
          name: 'xxx',
          age: '18'
        }
      })
    })
    // 清localStorage测试
    

    注销


    • 需要清除token缓存的两种情况:
      1. 用户主动注销
      2. token过期
    • 需要做的事情:
      1. 清空缓存
      2. 重置登录状态
    • 用户主动注销
    // app.vue
    <button v-if="$store.state.isLogin" @click="logout">注销</button>
    export default {
      methods: {
        logout() {
          this.$store.dispatch('logout')
        }
      },
    }
    // store.js
        logout({ commit }){
          // 清缓存
          localStorage.removeItem('token')
          // 重置状态
          commit("setLoginState", false);
        }
    
    • token过期导致请求失败的情况可能出现在项目的任何地方,可以通过响应拦截统一处理

    http拦截响应


    统一处理401状态码,清理token跳转login

    // interceptor.js
    export default function(vm){ // 传入vue实例
      // ...
      // 响应拦截
      // 这里只关心失败响应
      axios.interceptors.response.use(null,err=>{
        if(err.respinse.status===401){
          // 清空vuex和localstorage
          vm.$store.dispatch('logout')
          // 跳转login
          vm.$router.push('/login')
        }
        return Promise.reject(err)
      })
    }
    
    // app.vue
    const app=new Vue({...}).$mount('#app')
    interceptor(app) // 传入vue实例
    

    深入理解令牌机制


    • Bearer Token规范
      • 概念:描述在HTTP访问OAuth2保护资源时如何使用令牌的规范
      • 特点:令牌就是身份证明,无需证明令牌的所有权
      • 具体规定:在请求头中定义Authorization
      Authorization:Bearer <token>
      
    • Json Web Token规范
      • 概念:令牌的具体定义方式
      • 规定:令牌由三部分构成‘头,载荷,签名’
      • 头:包含加密算法、令牌类型等信息
      • 载荷:包含用户信息、签发时间和过期时间等信息
      • 签名:根据头、载荷及秘钥加密得到的哈希串 HMac Sha1 256
    • 实践:
      • 服务端:-server/server.js
      const Koa = require("koa");
      const Router = require("koa-router");
      // 生成令牌、验证令牌
      const jwt = require("jsonwebtoken");
      const jwtAuth = require("koa-jwt");
      
      // 生成数字签名的秘钥
      const secret = "it's a secret";
      
      const app = new Koa();
      const router = new Router();
      
      router.get("/api/login", async ctx => {
        const { username, passwd } = ctx.query;
        console.log(username, passwd);
      
        if (username == "kaikeba" && passwd == "123") {
          // 生成令牌
          const token = jwt.sign(
            {
              data: { name: "kaikeba" }, // 用户信息数据
              exp: Math.floor(Date.now() / 1000) + 60 * 60 // 过期时间
            },
            secret
          );
          ctx.body = { code: 1, token };
        } else {
          ctx.status = 401;
          ctx.body = { code: 0, message: "用户名或者密码错误" };
        }
      });
      
      router.get(
        "/api/userinfo",
        jwtAuth({ secret }),
        async ctx => {
          ctx.body = { code: 1, data: { name: "jerry", age: 20 } };
        }
      );
      app.use(router.routes());
      app.listen(3000);
      
    • 修改配置文件:启用开发服务器代理,vue.config.js
      devServer: {
        proxy: {
          "/api": {
              target: "http://127.0.0.1:3000/", 
              changOrigin: true
          }
        }, 
      
    • 拦截器的修改,interceptor.js
      config.headers.Authorization='Bearer'+token
      

    你的赞是我前进的动力

    求赞,求评论,求分享...

    相关文章

      网友评论

        本文标题:【Vue】Vue项目实战1

        本文链接:https://www.haomeiwen.com/subject/miugmctx.html