首先说一下现在项目搭建的程度;

vue-cli 创建的项目

引入了 iview 组件库,安装好了 less,安装了 vue-router 路由;

配置好了跳转到后台和登录页面的路由;

使用 iview做好了登录页面和后台页面的框架,如图:

在这里插入图片描述
在这里插入图片描述

后台是需要登录之后才能访问的,所以要对访问进行拦截;

1. 标识一下需要拦截的页面

一个网站有很多页面,一些是需要登录过后才能访问的,一些是不用登录也能访问的;
所以我们要在路由中对需要登录才能访问的页面进行一下标识,以区分这两者;

标识 meta

// meta 字段中 auth_login 为 true 的需要进行登录拦截检测;
{
    path: '/admin',
    component: Admin,
    meta: {
        auth_login: true
    },
    children: [
        {
            path: 'user',
            component: adminUser,
            meta: {
                auth_login: true
            },
        },
        {
            path: 'role',
            component: adminRole,
            meta: {
                auth_login: true
            },
        }
    ]
},
2. 路由拦截器

路由守卫也就是路由拦截器,通过路由拦截器在每一次路由跳转的时候都做一下判断,是否需要验证登录;

后续再判断是否登录成功,这里先用 true代替;

const router = new vueRouter({...});

// 前置守卫--在进入新的路由前被拦截  路由拦截器
router.beforeEach((to, from, next)=>{
    /**
     * 1. to   到哪里去(路由对象)
     * 2. from 从哪里来(路由对象)
     * 3. next 是否允许通行(函数)
     * 
     * 查看 to中有没有需要拦截的标识,判断是否拦截
     * 不需要拦截的话直接 next() 通行;
     * 需要拦截的话,判断是否登录,如果登录了则通行,没登录则拦截;
     */

    // 是否需要拦截
    if(to.meta.auth_login){
        // 需要拦截 -- 判断是否登录
        if(true){
            next();
        }else{
            next({path: '/login'})
        }
    }else{
        // 不需要拦截 -- 通行
        next();
    }
})
3. 请求

有些时候 前端页面和服务接口不在同一个服务器,如果通过 ajax 请求的话是会碰到跨域问题的;
而在 webpack 服务器中有一个 代理转发请求 的功能可以解决这个问题;就是我们请求自己的服务器,由服务器去请求数据,因为跨域是浏览器限制的,在 nodejs是没有限制的;

如:
前端地址:
http://localhost:8080/login
服务器地址:
http://console.ranyunlong.com:8080/renren-fast/swagger/

大体流程就是:
我们从浏览器请求开发服务器 (webpack-dev-server),开发服务器请求真实的服务器,真实的服务器把结果返回给开发服务器,开发服务器再返回给浏览器;
在这里插入图片描述

在开发服务器中配置规则:
vue-cli 3.0 中配置 webpack的方式:

https://cli.vuejs.org/zh/guide/webpack.html#简单的配置方式

1)新建文件 vue.config.js

2)进行配置

https://www.webpackjs.com/configuration/dev-server/#devserver-proxy

【vue.config.js】

module.exports = {
    devServer: {
        proxy: {
            "/api": {
                // 转发的目标地址
                target: "http://console.ranyunlong.com:8080",
                // 路径重写
                pathRewrite: {
                    "^/api": "/renren-fast"
                }
            }
        }
    }
}

讲解:

真实路径:
http://console.ranyunlong.com:8080/renren-fast/sys/login

但是如果我们访问这个路径就跨域了,所以我们要访问我们的开发服务器:
http://localhost:8080/api/sys/login

经过如上规则配置就相当于访问了真实路径;

当我们访问的路径有 /api时,就匹配了 devServer.proxy 的规则,进入了我们写的这条规则;

就会把我们的 http://localhost:8080 替换成 target的目标地址http://console.ranyunlong.com:8080
变成:
http://console.ranyunlong.com:8080/api/sys/login

再经过路径重写,将 /api替换成 /renren-fast
所以访问到的路径就是:
http://console.ranyunlong.com:8080/renren-fast/sys/login

3)测试配置是否生效

重启服务器

访问路径:http://localhost:8080/api/captcha.jpg

返回:
{"msg":"uuid不能为空","code":500}

和访问真实的服务器返回的数据是相同的,配置成功;

4. 验证码

因为已经做了代理了,所以可以直接访问到 服务器的数据,验证码是通过 get 方式请求的,这里直接写就好了

<img src="/api/captcha.jpg?uuid=0101010" alt="">

但是这个的这个 uuid 只能用一次,这个问题可以通过模块 uuid来解决;

https://www.npmjs.com/package/uuid

# 安装
npm i uuid
// 导入
// uuid()会生成一个字符串
import uuid from 'uuid'

// 计算属性拼接成验证码路径;
// 没有在计算属性直接使用 uuid() 生成路径是因为后面要点击验证码换一张验证码,计算属性的值没法设置,写到 data方便后续更新;
export default {
	data() {
		return{
			uid: uuid()
		}
	},
	computed: {
		codeUrl(){
			return '/api/captcha.jpg?uuid=' + this.uid;
		}
	}
}
<!--绑定 计算属性-->
<img :src="codeUrl" alt="">

点击验证码,生成新的验证码

<img @click="newCodeUrl()" :src="codeUrl" alt="">
methods:{
    newCodeUrl(){
        this.uid = uuid();
    }
},
computed: {
    codeUrl(){
        return '/api/captcha.jpg?uuid=' + this.uid;
    }
},
data() {
	return{
		uid: uuid()
	}
},
5. 验证表单数据

步骤见博客:

https://blog.csdn.net/qq_39125684/article/details/91125528

6. 表单提交

表单提交,是点击登陆按钮,然后检测表单数据,检测通过之后,进行 ajax 提交;

登录按钮

<Button @click="loginFn()" long>login</Button>

登录按钮 点击事件

loginFn(){
    // this.$refs['loginForm']   表单对象

    this.$refs['loginForm'].validate((val)=>{
        /**
         * val  true:  效验成功
         *      false: 效验失败
         */
 		
    });
}

axios
在这里,我们提交没有使用 ajax 而是使用了 axios 模块;
文档:https://www.kancloud.cn/yunye/axios/234845

1)安装 axios

npm install axios

2)新建文件夹 src/api/

3)新建文件 src/api/http.js

在这里使用自定义配置新建一个 axios 实例

// 导入 axios模块
import axios from 'axios'

// 创建一个请求模板    新建一个axios 实例
const http = axios.create({
    // 设置基础路径
    baseURL: '/api',
})

// 拦截请求的 每次请求都会经过这个方法
http.interceptors.request.use((config) => {
    console.log(config);

    // 可以在这里处理所有的 请求错误
    return config; 
})

// 拦截响应的 每次收到响应都会经过这个方法
http.interceptors.response.use((response)=>{
    console.log(response);

    // 可以在这里处理所有的 响应错误
    return response;
})

// 暴露出去
export default http;

4) 登录接口 src/api/login.js

导入新建的 axios 实例,然后执行 post 请求;

/**
 * 登录接口
 */
 
import http from "./http";

function login(data){
    return http.post('/sys/login', data);
}


export default login;

5)在页面中调用这个登录接口,传入数据

<Button @click="loginFn()" long>login</Button>
import login from  '../api/login'
_________

data(){
	return {
		uid: uuid(),
        userInfo: {
            username: '',
            password: '',
            captcha: ''
        },
	}
},
methods:{
    loginFn(){
        // this.$refs['loginForm']   表单对象

        this.$refs['loginForm'].validate((val)=>{
            /**
             * val  true:  效验成功
             *      false: 效验失败
             */
            if(val) {
                login({
                    ...this.userInfo,
                    uuid: this.uid
                })
            }
        });
    }
},

6)通过响应消息判断请求是否成功

请求成功:
存储成功凭据:localStorage
跳转到后台页面: this.$router.push('/admin')

请求失败:
弹窗提示:iview 的全局提示
刷新验证码:this.uid = uuid();

loginFn(){
    // this.$refs['loginForm']   表单对象

    this.$refs['loginForm'].validate((val)=>{
        /**
         * val  true:  效验成功
         *      false: 效验失败
         */
        if(val) {
            login({
                ...this.userInfo,
                uuid: this.uid
            }).then((response)=>{
                // response 响应过来的信息
                // response.data.code === 0  请求成功
                const {code, msg, token} = response.data;
                if(code === 0) {
                    // 请求成功,存储成功信息
                    localStorage.setItem('token', token)
                    
                    // 路由跳转
                    this.$router.push('/admin');
                }else {
                    // iview 的全局提示
                    this.$Message.error('登陆失败:' + msg);
                    // 刷新验证码
                    this.uid = uuid();
                }
            })
        }
    });
}

在路由拦截器中,判断是否有登陆凭证,有的话通行;
const loginResult = localStorage.getItem('token');

// 前置守卫--在进入新的路由前被拦截  路由拦截器
router.beforeEach((to, from, next)=>{
    /**
     * 1. to   到哪里去(路由对象)
     * 2. from 从哪里来(路由对象)
     * 3. next 是否允许通行(函数)
     * 
     * 查看 to中有没有需要拦截的标识,判断是否拦截
     * 不需要拦截的话直接 next() 通行;
     * 需要拦截的话,判断是否登录,如果登录了则通行,没登录则拦截;
     */

    // 是否需要拦截
    if(to.meta.auth_login){
        // 需要拦截 -- 判断是否登录
        const loginResult = localStorage.getItem('token');
        if(loginResult){
            next();
        }else{
            next({path: '/login'})
        }
    }else{
        // 不需要拦截 -- 通行
        next();
    }
})

至此:登陆成功


获取菜单

  1. 每次提交请求的时候,都要 带上 token 否则请求不到数据
【src/api/http.js】

// 请求拦截器
http.interceptors.request.use((request)=> {
    request.headers.token = localStorage.getItem('token');
    return request;
})
  1. 将无效的 token 清除掉
    如果用户伪造 token 在后台是请求不到数据的,token无效时,返回的 code401
    如果 token为用户伪造的,则将存储在 localStorage中的 token 字段清除掉;
【src/api/http.js】

// 响应拦截器
http.interceptors.response.use((response)=>{
    if(response.data.code === 401){
        localStorage.removeItem('token');
    }
    return response;
})
  1. 新建请求接口 src/api/getMenuList.js
/**
 * 菜单列表接口
 */
import http from './http'

function getMenuList(){
    return http.get('/sys/menu/list');
}

export default getMenuList;
  1. 在页面中请求数据
    导入请求接口
import getMenuList from '../api/getMenuList'
export default {
    data(){
        return {
            menu: []
        }
    },
    beforeCreate(){
    	// 请求数据
        getMenuList().then((response)=>{
            this.menu = response.data;
        })
    }
}

将请求到的数据打印看一下,没问题的话定义一个工具函数处理成方便我们遍历的形式

  1. 工具函数 src/utils/deepMenu.js
/**
 * 递归菜单
 * @param {Array} menu 菜单数据
 * @param {Number} parentId 父级Id
 */
function deepMenu(menu, parentId){
    if(Array.isArray(menu)){
        return menu.filter((item, index, arr)=>{
            if(item.parentId === parentId){
                item.children = deepMenu(menu, item.menuId)
                return true;
            }
        })
    }
}

export default deepMenu;
  1. 使用工具函数,生成我们需要的数据格式
import getMenuList from '../api/getMenuList'
import deepMenu from '../urils/deepMenu'
export default {
    data(){
        return {
            menu: []
        }
    },
    beforeCreate(){
        getMenuList().then((response)=>{
            this.menu = response.data;
        })
    },
    computed: {
        menuList() {
            return deepMenu(this.menu, 0);
        }
    },
}
  1. 遍历数据
<Sider :collapsed-width="78" >
   <Menu theme="dark" width="auto" accordion>
        <Submenu 
            v-for="item in menuList"
            :name="item.menuId"
            :key="item.menuId"
            >
            <template slot="title">
                <Icon type="ios-paper" />
                {{item.name}}
            </template>
            <MenuItem 
                v-for="child in item.children"
                :name="child.menuId"
                :key="child.menuId"
                >
                {{child.name}}
            </MenuItem>
        </Submenu>
    </Menu>
</Sider>
  1. 点击跳转路径
    其实 <MenuItem> 是有 to属性的,和 <router-link>to属性作用相同,但是这里因为接口的数据的原因,就不使用 to了,写一个点击事件
    注意,直接给 <MenuItem>写点击事件是无效的,需要嵌套一个标签,给这个标签写点击事件
<MenuItem 
    v-for="child in item.children"
    :name="child.menuId"
    :key="child.menuId"
    >
    <div @click="goto(child.url)">
        {{child.name}}
    </div>
</MenuItem>
methods: {
    goto(url){
        this.$router.push({
            path: `/${url}`
        })
    }
}

OK,列表也遍历出来了

Logo

前往低代码交流专区

更多推荐