Vue

【Vue】Vue项目实战1

2019-10-19  本文已影响0人  LM林慕

此文项目代码: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库都不能满足全部的业务开发需求,都需要自己进行定制和扩展,组件化设计思路至关重要

登录页面


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;

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测试

注销


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

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实例

深入理解令牌机制


你的赞是我前进的动力

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

上一篇下一篇

猜你喜欢

热点阅读