新建vue项目

vue 模拟商城 路由导航守卫_javascript

 

vue 模拟商城 路由导航守卫_javascript_02

 main.js

import Vue from 'vue'
import App from './App.vue'
import store from "@/store";
import router from "@/router";
import '@/utils/vant-ui';
import '@/styles/common.css'

Vue.config.productionTip = false;

new Vue({
  render: h => h(App),
  router,
  store,
}).$mount('#app');

app.vue

<template>
  <div id="app">
    <router-view></router-view>
  </div>
</template>

<script>

export default {
  name: 'App'
}
</script>

<style>

</style>

search index.vue

<template>
    <div>我是Search</div>
</template>

<script>
    export default {
        name: "SearchIndex"
    }
</script>

<style scoped>

</style>

search list.vue

<template>
    <div>我是List</div>
</template>

<script>
    export default {
        name: "ListIndex"
    }
</script>

<style scoped>

</style>

productDetails index.vue

<template>
    <div>我是ProductDetails</div>
</template>

<script>
    export default {
        name: "ProductDetailsIndex"
    }
</script>

<style scoped>

</style>

pay index.vue

<template>
    <div>我是Pay</div>
</template>

<script>
    export default {
        name: "PayIndex"
    }
</script>

<style scoped>

</style>

myorder index.vue

<template>
    <div>我是MyOrder</div>
</template>

<script>
    export default {
        name: "MyOrderIndex"
    }
</script>

<style scoped>

</style>

login index.vue

<template>
    <div class="login">
        <!-- 头部       -->
        <van-nav-bar
            title="会员登录"
            left-arrow
            @click-left="$router.go(-1)"></van-nav-bar>
        <!--  底部      -->
        <div class="container">
            <div class="title">
                <h3>手机号登录</h3>
                <p>未注册的手机号登录后自动注册</p>
            </div>

            <div class="form">
                <div class="form-item">
                    <input type="text" v-model="mobile" class="inp" placeholder="请输入手机号码" maxlength="11">
                </div>
                <div class="form-item">
                    <input type="text" v-model="picCode" class="inp" placeholder="请输入图形验证码" max="5">
                    <img v-if="picUrl" :src="picUrl" alt="" @click="getPicCode">
                </div>
                <div class="form-item">
                    <input type="text" v-model="smsCode" class="inp" placeholder="请输入短信验证码" >
                    <button @click="handleCode">
                        {{ currentSecond === totalSecond ? '获取验证码' : currentSecond + '秒之后重新发送'}}
                    </button>
                </div>
            </div>

            <div class="login-btn" @click="handleLogin">登录</div>
        </div>
    </div>
</template>

<script>
    //按需导入
    import { getRandomPicCode, getMsgCode, doLogin } from '@/api/login'
    //import { Toast } from 'vant'

    export default {
        name: "LoginIndex",
        data(){
          return {
              picKey:'', //请求后台时的图形验证码唯一标识
              picUrl:'', //存储请求渲染的图片地址,
              totalSecond: 60, //总秒数
              currentSecond: 60, //当前数秒
              timer: null, //定时器
              mobile:'', //手机号
              picCode:'', //用户输入的图形验证码
              smsCode:'' //短信验证码
          }
        },
        created(){
            //页面一加载,就请求验证码
            this.getPicCode()
        },
        methods:{
            //获取图形验证码
            async getPicCode(){
                //展开分解
                const { data:{ base64, key }} = await getRandomPicCode();
                this.picUrl = base64;  //存储地址
                this.picKey = key; // 存储唯一标识
                //Toast('获取图形验证码成功!');
                this.$toast('获取验证码成功');
            },
            //获取短信验证码
            async handleCode(){
                //校验没通过,不发送短信验证码
                if(!this.validFn()){
                    return;
                }

                //当前没有开启定时器,且totalSecond==CurrentSecond时,
                if (!this.timer && this.currentSecond === this.totalSecond){
                    //发送请求,
                    const res = await getMsgCode(this.picCode,this.picKey,this.mobile);
                    //console.log(res);
                    this.$toast('短信发送成功,请注意查收!' + res.message);

                    this.timer = setInterval( ()=>{
                        console.log('正在倒计时...');
                        //开始数秒
                        this.currentSecond--;
                        //如果当前秒 = 0时,关闭清除定时器,恢复当前秒
                        if(this.currentSecond <= 0){
                            clearInterval(this.timer);
                            this.timer = null;
                            this.currentSecond = this.totalSecond;
                        }
                    },1000);
                }
            },
            //校验 手机号和图形验证码是否合法
            validFn(){
                //手机号正则表达式
                if(!(/^1[3-9]\d{9}$/.test(this.mobile))){
                    this.$toast('请输入正确的手机号码');
                    return false;
                }
                //图形验证码正则表达式
                if(!(/^\w{4}$/.test(this.picCode))){
                    this.$toast('请输入正确的图形验证码');
                    return  false;
                }
                return true;
            },
            //登录
            async handleLogin(){
                if(!this.validFn()){
                    return;
                }

                if(!(/^\d{6}$/.test(this.smsCode))){
                    this.$toast('请输入正确的短信验证码');
                    return;
                }

                const res = await doLogin(this.mobile,this.smsCode);
                //console.log(res);
                if(res.status === 200){
                    //存入vuex
                    this.$store.commit('user/setUserInfo',res.data);
                    this.$toast(res.message);
                    await this.$router.push('/');
                }
            }
        },
        destroyed(){
            //离开页面,清除定时器
            clearInterval(this.timer);
        }

    }
</script>

<style scoped>

    .container{
        padding: 49px 29px;
    }

    .container .title{
        margin-bottom: 20px;

    }

    .title h3{
        font-size: 26px;
        font-weight: normal;
    }

    .title p{
        line-height: 40px;
        font-size: 14px;
        color: #b8b8b8;
    }

    .form-item{
        border-bottom: 1px solid #f3f1f2;
        padding: 8px;
        margin-bottom: 14px;
        display: flex;
        align-items: center;

    }

    .form-item .inp{
        display: block;
        border:none;
        outline: none;
        height: 32px;
        font-size: 14px;
        flex: 1;
    }

    .form-item img{
        width: 94px;
        height: 31px;
    }

    .form-item button{
        height: 31px;
        border: none;
        font-size: 13px;
        color: #cea26a;
        background-color: transparent;
        padding-right: 9px;
    }

    .login-btn{
        width: 100%;
        height: 42px;
        margin-top: 39px;
        background: linear-gradient(90deg,#ecb53c,#ff9211,#ff9211);
        color: #ffffff;
        border-radius: 39px;
        box-shadow: 0 10px 20px 0 rgba(0,0,0,.1);
        letter-spacing: 2px;
        display: flex;
        justify-content: center;
        align-items: center;
    }
</style>

layout user.vue

<template>
    <div>我是user</div>
</template>

<script>
    export default {
        name: "UserPage"
    }
</script>

<style scoped>

</style>

layout index.vue

<template>
    <div class="layout-index">
        <!-- 二级路由出口,二级组件展示的位置   -->
        <router-view></router-view>

        <van-tabbar route active-color="#ee0a24" inactive-color="#000">
            <van-tabbar-item to="/home" icon="wap-home-o">首页</van-tabbar-item>
            <van-tabbar-item to="/category" icon="apps-o">分类页</van-tabbar-item>
            <van-tabbar-item to="/cart" icon="shopping-cart-o">购物车</van-tabbar-item>
            <van-tabbar-item to="/user" icon="user-o">我的</van-tabbar-item>
        </van-tabbar>
    </div>
</template>

<script>
    export default {
        name: "LayoutIndex"
    }
</script>

<style scoped>

</style>

layout home.vue

<template>
    <div>我是Home</div>
</template>

<script>
    export default {
        name: "HomePage"
    }
</script>

<style scoped>

</style>

layout category.vue

<template>
    <div>我是category</div>
</template>

<script>
    export default {
        name: "CategoryPage"
    }
</script>

<style scoped>

</style>

layout cart.vue

<template>
    <div>我是cart</div>
</template>

<script>
    export default {
        name: "CartPage"
    }
</script>

<style scoped>

</style>

vant-ui.js

import Vue from 'vue'

//Vant组件库,全部导入方式
// npm i vant@latest-v2 -s
// import Vant from 'vant'
// import 'vant/lib/index.css'
// Vue.use(Vant);

//按需导入
import { Button, Tabbar, TabbarItem, NavBar, Toast } from 'vant'
//注册使用
Vue.use(Button);
//底部菜单栏
Vue.use(Tabbar);
Vue.use(TabbarItem);
//登录页顶部导航栏
Vue.use(NavBar);
//轻提示
Vue.use(Toast);

storage.js

//约定一个通用的键名
const INFO_KEY = 'xx_shopping_info';
//获取个人登录信息
export const getInfo = () => {
    const defaultObj = { token:'',userId:''};
    const result = localStorage.getItem(INFO_KEY);
    return result ? JSON.parse(result) : defaultObj;
};

//设置个人信息
export const setInfo = (obj) => {
    localStorage.setItem(INFO_KEY,JSON.stringify(obj));
};

//删除个人信息
export const removeInfo = () => {
    localStorage.removeItem(INFO_KEY);
};

request.js

import axios from "axios";
import { Toast } from 'vant'
//创建 axios 实例,将来对创建出来的实例,进行自定义配置
//好处,不会污染原始的axios
const instance = axios.create({
   baseURL:'http://cba.itlike.com/public/index.php?s=/api/',
   timeout:5000
});

//自定义配置 请求-响应 拦截器
//添加请求拦截器
instance.interceptors.request.use(function (config) {
   //在发送请求之前做些什么
    // 开启loading,禁止背景点击,节流处理,防止用户多次无效触发
    Toast.loading({
       message:'加载中...',
       forbidClick:true, //是否禁止点击背景功能
       loadingType: "spinner", //配置loading图标
       duration: 0, //图标不会自动消失
    });
   return config;
},function (error) {
   //对请求错误做些什么
   return Promise.reject(error);
});

//添加响应拦截器
instance.interceptors.response.use(function (response) {
    //2xx 范围内的状态码都会触发该函数
    //对响应数据做点什么
    const res = response.data;
    if(res.status !== 200){
        //提示
        Toast(res.message);
        //抛出一个错误的Promise
        return Promise.reject(res.message);
    }
    else{
        //正确情况下,清除loading效果
        Toast.clear();
    }

    return res;
},function (error) {
    //超出2xx 范围的状态码都会触发该函数
    //对响应错误做点什么
    return Promise.reject(error);
});


//导出配置好的实例
export default instance;

common.js

/*重置默认样式*/
* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}

/*文字溢出省略号*/
.text-ellipsis-2{
    overflow: hidden;
    -webkit-line-clamp:2;
    text-overflow: ellipsis;
    display: -webkit-box;
    -webkit-box-orient:vertical;
}

/*
添加导航的通用样式
 */
.van-nav-bar .van-nav-bar__arrow{
    color: #333333;
}

store index.js

import Vue from 'vue'
import Vuex from 'vuex'
import user from "@/store/modules/user";

Vue.use(Vuex);

const store = new Vuex.Store({
    getters:{
        //获取tosken
        token(state){
            return state.user.userInfo.token;
        }
    },
    modules:{
        user
    }
});

export default store;

store user.vue

import { getInfo, setInfo } from '@/utils/storage'

export default {
    //开启命名空间
    namespaced:true,
    state(){
        return {
            //个人权证相关
            userInfo: getInfo()
        }
    },
    mutations:{
        //所有mutation的参数都是state
        setUserInfo(state,obj){
            state.userInfo = obj;
            setInfo(obj);
        }
    },
    actions:{

    },
    getters:{

    }
}

router index.js

import Vue from 'vue'
import VueRouter from 'vue-router'
import Login from '@/views/login'
import Layout from '@/views/layout'
import MyOrder from '@/views/myorder'
import Pay from '@/views/pay'
import ProductDetails from '@/views/productdetails'
import Search from '@/views/search'
import SearchList from '@/views/search/list'
import Home from "@/views/layout/home";
import Cart from "@/views/layout/cart";
import Category from "@/views/layout/category";
import User from "@/views/layout/user";
import store from "@/store";

Vue.use(VueRouter);

const router = new VueRouter({
    routes:[
        { path:'/login',component:Login},
        {
            path:'/',
            component: Layout,
            redirect:'/home',
            children:[
                { path:'/home',component:Home},
                { path:'/category',component:Category},
                { path:'/cart',component: Cart},
                { path:'/user',component:User}
            ]
        },
        { path:'/search',component:Search},
        { path:'/searchList',component: SearchList},
        { path:'/productDetails/:id',component:ProductDetails},
        { path:'/pay',component:Pay},
        { path:'/myOrder',component:MyOrder}
    ]
});

//全局前置导航守卫
// 所有的路径在其被真正访问之前,解析渲染对应的组件页面前,
// 都会完整经过全部前置守卫,只有全局前置守卫放行了,
//才会到达对应的页面
// to 到哪里去
// from 从哪里来得
// next 是否放行 next()直接放行,next(路径),进行拦截
//定义一个数组,专用存放用户需要拦截的页面路径
const authUrl = ['/pay','/myOrder'];

router.beforeEach((to, from, next) => {
    //看to.path 是否在authUrl中出现
    if(!authUrl.includes(to.path)){
        //非权限页面,无须登录,直接放行
        next();
        return;
    }
    //是权限页面,需要判断token
    const token = store.getters.token;
    if(token){
        next();
    }
    else{
        next('/login');
    }
});

export default router;

api login.js

//用于存放所有登录有关的请求接口
import request from '@/utils/request'

//1 获取图形验证码
 export const getRandomPicCode = () =>{
    return request.get('/captcha/image');
 };

//2 获取短信验证码
export const getMsgCode = (captchaCode,captchaKey,mobile) => {
  return request.post('/captcha/sendSmsCaptcha',{
      form:{
          captchaCode, //输入的验证码
          captchaKey, //
          mobile //手机号
      }
  });
};

//3. 登录接口
export const doLogin = (mobile,smsCode) => {
    return request.post('/passport/login',{
        form:{
            isParty:false, //是否第三方登录
            partyData:{}, //第三方的登录信息
            mobile, //手机号
            smsCode // 短信验证码
        }
    })
};