06_05_SSM项目前端开发(课程模块、广告模块、用户模块、权限模块)

SSM项目前端开发

1.Vue回顾

1.1 项目结构说明

我们使用脚手架快速构建Vue项目,项目结构如下图

06_05_SSM项目前端开发(课程模块、广告模块、用户模块、权限模块)_第1张图片

|--- edu-boss 项目名称
    |--- node_modules 存放依赖包的目录
    |--- public 静态资源管理目录
    |--- src 组件源码目录(我们写的代码)
        |--- assets 存放静态图片资源(CSS也可以放在这里)
        |--- components 存放基础组件,可复用
        |--- router 存放了项目路由文件
        |--- services 存放请求后台的 JS文件,
        |--- store 保存组件之间的共享数据
        |--- utils 管理公用的JS文件
        |--- views 放置的为公共组件(各个主要页面)
        |--- App.vue app.vue可以当做是网站首页,是一个vue项目的主组件,页面入口文件
        |--- main.js 打包运行的入口文件,引入了vue模块和app.vue组件以及路由route
    |--- babel.config.js babel配置文件, 对源代码进行转码(把es6=>es5)
    |--- package.json 项目及工具的依赖配置文件
    |--- paxkage-lock.json 依赖配置文件
    |--- README.md 项目说明
    |--- vue.config.js 自定义配置文件

1.2 Views 目录说明

前端项目的页面部分:
06_05_SSM项目前端开发(课程模块、广告模块、用户模块、权限模块)_第2张图片

CourseManage: 课程管理
AdvertiseManage: 广告管理
PermissionManage: 权限管理
CommentManage:公共
Users.vue: 用户管理
Login.vue: 登录

vue组件化开发

每一个*.vue 文件都可以看做是一个组件.

组件的组成部分:

  • template : 组件的HTML部分
  • script: 组件的JS脚本 (使用ES6语法编写)
  • style: 组件的CSS样式

<template>
	<div>
    	测试页面...
    div>
template>


<script>
    //可以导入其组件
    // import Header from '../components/header.vue'
    //默认写法, 输出该组件
    export default {
        name:"Home", // 组件名称,用于以后路由跳转
        data() {// 当前组件中需要使用的数据
            return {}
        },
        methods: {}
    }
script>

<style scoped>
    /* 页面样式 加上scoped 表示样式就只在当前组件有效*/
style>

2.课程模块回顾

2.1 课程数据展示

导入最新的数据库脚本文件ssm_lagou.sql
在这里插入图片描述

2.1.1 功能分析

Course.vue 组件,完成课程数据的展示和条件查询

使用ElementUI 表格进行数据展示 https://element.eleme.cn/#/zh-CN/component/table

06_05_SSM项目前端开发(课程模块、广告模块、用户模块、权限模块)_第3张图片

2.1.2 JS代码编写

  1. 定义数据部分
    //数据部分
    data() {
        //查询条件
        const filter = {
            courseName: "",
            status: ""
        };
        return {
            filter,
            courses: [],
            loading: false
        };
    },
    //钩子函数
    created() {
        this.loadCourses();
    },
  1. 根据接口文档,编写查询课程数据方法
//方法一: 加载课程数据
loadCourses() {
    this.loading = true;
    const data = {};
    //查询条件
    if (this.filter.courseName) data.courseName = this.filter.courseName;
    if (this.filter.status) data.status = this.filter.status;
    console.log(data);
    //发送请求
    return axios
        .post("/course/findAllCourse", data)
        .then(resp => {
            console.log(resp.data.content);
            this.courses = resp.data.content;
            this.loading = false;
    	})
        .catch(error => {
        	this.$message.error("数据获取失败! ! !");
    	});
},
  1. 条件查询( 访问的是同一个接口 )
查询
//条件查询
handleFilter() {
    this.loadCourses();
},

2.2 新建课程

2.2.1 功能分析

  • 点击新建,由路由导航到 CourseItem.vue
<el-button type="primary" icon="el-icon-plus" @click="handleAdd">新建课程el-button>
//新建课程 路由跳转
handleAdd() {
    this.$router.push({ name: "CourseItem", params: { courseId: "new" } });
},
  • router.js
{
    path: "/courses/:courseId",
    name: "CourseItem",
    meta: { requireAuth: true, title: "课程详情" },
    component: () =>
    import(
        /* webpackChunkName: 'courses' */
        "../views/CourseManage/CourseItem.vue"
    )
},
  • CourseItem组件使用ElementUI中的表单来提交课程数据

https://element.eleme.cn/#/zh-CN/component/form

06_05_SSM项目前端开发(课程模块、广告模块、用户模块、权限模块)_第4张图片

保存
//保存课程信息
handleSave() {
    this.$refs.form.validate(valid => {
        if (!valid) return false;
        axios.post("/course/saveOrUpdateCourse", this.course)
            .then(res => {
            this.$router.back();
        })
            .catch(error => {
            this.$message.error("保存课程信息失败! ! !");
        });
    });
},

2.3 课程图片上传

2.3.1 功能分析

在SSM前端项目中,图片上传功能使用的是公共的通用组件 UploadImage.vue
在这里插入图片描述

在CourseItem.vue,引入了该组件

import UploadImage from "@/components/UploadImage.vue";
<!-- 使用图片上传组件,完成图片上传 -->
<el-form-item label="课程封面" prop="courseImgUrl">
    <upload-image
                  :content="course.courseImgUrl && [course.courseImgUrl]"
                  :get-urls="getCourseImgUrl"
                  uploadUrl="/course/courseUpload"
                  ref="courseCoverRef"
                  max="10M"
                  tipInfo="建议尺寸:230*300px,JPG、PNG格式,图片小于10M"
                  ></upload-image>
</el-form-item>

2.3.2 案例演示

  1. 导入准备好的Vue 基础项目my-ssmweb
    在这里插入图片描述

  2. 在components目录下创建一个 UploadImage.vue组件
    06_05_SSM项目前端开发(课程模块、广告模块、用户模块、权限模块)_第5张图片

  3. 查看ElementUI文档,复制代码到 UploadImage.vue

https://element.eleme.cn/#/zh-CN/component/upload

<template>
<div>
    <el-upload
               action="https://jsonplaceholder.typicode.com/posts/"
               list-type="picture-card"
               :on-preview="handlePictureCardPreview"
               :on-remove="handleRemove"
               >
        <i class="el-icon-plus">i>
    el-upload>
    <el-dialog :visible.sync="dialogVisible">
        <img width="100%" :src="dialogImageUrl" alt />
    el-dialog>
    div>
template>
<script>
    export default {
        data() {
            return {
                dialogImageUrl: "",
                dialogVisible: false
            };
        },
        methods: {
            handleRemove(file, fileList) {
                console.log(file, fileList);
            },
            handlePictureCardPreview(file) {
                this.dialogImageUrl = file.url;
                this.dialogVisible = true;
            }
        }
    };
script>
<style scoped>
style>
  1. 配置路由
//布局路由
{
    path: "/index",
    name: "index",
    component: Index,
    //添加子路由,使用 children属性 来表示子路由
    children: [
        //图片上传子路由
        {
            path: "/upload",
            name: "upload",
            component: UploadImage,
        },
    ],
},
  1. 在Index.vue 导航菜单位置添加一个图片上传选项
<el-menu-item-group>
    
    <el-menu-item index="/upload">
        <i class="el-icon-menu">i>图片上传
    el-menu-item>
el-menu-item-group>
  1. 访问页面进行测试

2.3.3 属性说明

参数 说明 类型
action 必选参数,上传的地址 string
multiple 是否支持多选文件 boolean
limit 最大允许上传个数 number
before-upload 上传文件之前的钩子,参数为上传的文件 function(file)
on-success 文件上传成功时的钩子 function(response, file, fileList)
on-remove 文件列表移除文件时的钩子 function(file, fileList)
on-exceed 文件超出个数限制时的钩子 function(files, fileList)
file-list 上传的文件列表 array

2.3.4 组件的引入

怎么将一个组件引入另一个组件 ? 接下来我们来演示一下 引入图片组件.

  1. 创建一个TestUplopad.vue组件
<template>
<div>
    
    <upload-image>upload-image>
    div>
template>
<script>
    //1.导入组件
    import UploadImage from "@/components/UploadImage";
    export default {
        //2.注册组件
        components: {
            UploadImage
        }
    };
script>
<style scoped>
style>

2.3.5 组件的传参

UploadImage.vue

/*
组件传参
uploadUrl:图片上传路径,
getUrl: 函数
*/
props: ["uploadUrl", "getUrl"],
data() {
    return {
    uploadAction: this.uploadUrl
};
},
//上传成功后的回调函数
uploadSuccess(res, file) {
    this.getUrl(file);
}
<el-upload
           :action="uploadAction"
           list-type="picture-card"
           :on-preview="handlePictureCardPreview"
           :on-remove="handleRemove"
           :on-success="uploadSuccess"
           >

TestUpload.vue

<template>
<div>
    
    <upload-image
                  uploadUrl="https://jsonplaceholder.typicode.com/posts/"
                  :get-url="show">
    upload-image>
    div>
template>
methods: {
    show(file) {
        console.log(file.name);
    }
}

2.3.6 课程模块图片上传

CourseItem.vue

引入图片上传组件,并使用(UploadImage.vue)

import UploadImage from "@/components/UploadImage.vue";
export default {
    name: "CourseItem",
    title: "营销信息",
    components: { Editor, UploadImage },
}
<el-form-item label="课程封面" prop="courseImgUrl">
    
    <upload-image
                  :content="course.courseImgUrl && [course.courseImgUrl]"
                  :get-urls="getCourseImgUrl"
                  uploadUrl="/course/courseUpload"
                  ref="courseCoverRef"
                  max="10M"
                  tipInfo="建议尺寸:230*300px,JPG、PNG格式,图片小于10M"
                  >upload-image>
el-form-item>

2.4 修改课程

  1. 点击编辑携带当前数据的id,导航到CourseItem.vue
<el-button size="mini" @click="handleNavigate('CourseItem', scope.row.id)">
编辑
el-button>
//课程编辑&内容管理路由
handleNavigate(name, id) {
    this.$router.push({ name, params: { courseId: id } });
},
  1. 在CourseItem组件的钩子函数中,会进行判断,如果是修改会先获取对应课程数据,进行回显
//钩子函数
created() {
    //获取课程id
    const id = this.$route.params.courseId;
    if (!id) return this.redirectToError();
    //判断是新建还是修改
    if (id === "new") {
        this.pathTitle = "新增课程";
        this.$breadcrumbs = [
            { name: "Courses", text: "课程管理" },
            { text: "新增课程" }
        ];
    } else {
        this.$breadcrumbs = [
            { name: "Courses", text: "课程管理" },
            { text: "营销信息" }
        ];
        this.loadCourse(id);
    }
},
    //回显课程信息
    loadCourse(id) {
        this.loading = true;
        return axios
            .get("/course/findCourseById?id=" + id)
            .then(resp => {
            console.log(resp);
            this.pathTitle = resp.data.content.courseName;
            this.course = Object.assign(this.course, resp.data.content);
            this.course.id = id;
            this.loading = false;
        })
            .catch(error => {
            this.$message.error("回显数据失败! !");
        });
    },
  1. 修改课程与添加课程走的都是同一个后台接口,区别是修改操作必须要携带ID

2.5 课程状态管理

点击上架或者下架完成课程状态的切换.

<el-button size="mini" type="danger" v-if="scope.row.status === 1"
           @click="handleToggleStatus(scope.row)">下架el-button>
<el-button size="mini" type="success" v-else-if="scope.row.status === 0"
           @click="handleToggleStatus(scope.row)">上架el-button>
//切换课程状态
handleToggleStatus(item) {
    //设置最新状态
    const toggledStatus = 1 - item.status;
    //请求后台接口
    axios
        .get("/course/updateCourseStatus", {
        params: {
            status: toggledStatus,
            id: item.id
        }
    })
        .then(res => {
        debugger;
        //设置最新的值
        item.status = toggledStatus;
        console.log(item);
        //重新加载页面
        window.location.reload;
    })
        .catch(error => {
        this.$message.error("状态修改失败! ! !");
    });
},

2.6 课程内容管理

2.6.1 获取课程内容数据

课程内容数据包括章节与课时信息, 根据课程ID 查询课程包含的章节与课时信息

<el-button size="mini" @click="handleNavigate('CourseSections', scope.row.id)">
内容管理
el-button>
created() {
    //1.显示当前页面在网站中的位置
    this.$breadcrumbs = [
        { name: "Courses", text: "课程管理" },
        { text: "课程结构" }
    ];
    //2.从路由中获取传递的参数 课程id
    const id = this.$route.params.courseId;
    if (!id) return this.redirectToError();
    this.loading = true;
    //3.加载课程信息
    this.loadCourse(id);
    //4.加载课程内容
    this.loadSections(id);
},
    //加载课程信息
    loadCourse(id) {
        axios
            .get("/courseContent/findCourseByCourseId?courseId=" + id)
            .then(res => {
            const course = res.data.content;
            //将数据保存到章节表单对象中
            this.addSectionForm.courseId = course.id;
            this.addSectionForm.courseName = course.courseName;
            //将数据保存到课时表单对象中
            this.addLessonForm.courseId = course.id;
            this.addLessonForm.courseName = course.courseName;
        })
            .catch(error => {
            this.$message.error("数据获取失败! ! !");
        });
    },
        //加载课程内容(树形结构)
        loadSections(courseId) {
            this.loading = true;
            axios
                .get("/courseContent/findSectionAndLesson?courseId=" + courseId)
                .then(res => {
                this.sections = res.data.content;
                console.log(res.data.content);
                this.loading = false;
            })
                .catch(error => {
                this.$message.error("数据获取失败! ! !");
            });
        },

2.6.2 章节管理

2.6.2.1 新建章节
<el-button type="primary" icon="el-icon-plus" @click="handleShowAddSection">添加章节
el-button>

新增章节,需要回显章节对应的课程名称

//显示新增章节表单
handleShowAddSection() {
    this.addSectionForm = {
        courseId: this.addSectionForm.courseId,
        courseName: this.addSectionForm.courseName
    };
    this.showAddSection = true;
},
2.6.2.2 修改章节
<el-button size="small" @click.stop="handleEditSection(data)">编辑el-button>
//编辑章节(回显)
handleEditSection(section) {
    this.addSectionForm = Object.assign(this.addSectionForm, section);
    this.showAddSection = true;
},
2.6.2.3 添加与修改章节访问的都是同一个接口
//添加&修改章节
handleAddSection() {
    axios
        .post("/courseContent/saveOrUpdateSection", this.addSectionForm)
        .then(res => {
        this.showAddSection = false;
        //重新加载列表
        return this.loadSections(this.addSectionForm.courseId);
    })
        .then(() => {
        //重置表单内容
        this.addSectionForm.sectionName = "";
        this.addSectionForm.description = "";
        this.addSectionForm.orderNum = 0;
        this.reload();
    })
        .catch(error => {
        this.showAddSection = false;
        this.$message.error("操作执行失败! ! !");
    });
},
2.6.2.4 章节状态

章节状态有3种

//状态信息
const statusMapping = {
    0: "已隐藏",
    1: "待更新",
    2: "已更新"
};

选择状态,点击确定修改状态

<el-button type="primary" @click="handleToggleStatus">确 定el-button>
//修改章节状态
handleToggleStatus() {
    //判断要修改的状态
    if (this.toggleStatusForm.data.sectionName) {
        //修改章节状态
        axios
            .get("/courseContent/updateSectionStatus", {
            params: {
                id: this.toggleStatusForm.id,
                status: this.toggleStatusForm.status
            }
        })
            .then(resp => {
            this.toggleStatusForm.data.status = this.toggleStatusForm.status;
            this.toggleStatusForm = {};
            this.showStatusForm = false;
            this.reload();
        })
            .catch(error => {
            this.showStatusForm = false;
            this.$message.error("修改状态失败! ! !");
        });
    } else {
        //修改课时状态
    }
},

2.6.3 课时管理

课时管理 包括 课时新增、课时修改、课时状态管理. 与章节管理基本相同.

3.广告模块

3.1 广告位管理

3.1.1 广告位展示

AdvertiseSpaces.vue 组件,为广告位页面
在这里插入图片描述

3.1.1.1 广告位展示

JS部分

<script>
import { axios } from "../../utils";
import moment from "moment";

export default {
data() {
    return {
        list: null,
        listLoading: false
    };
},
    created() {
        //加载广告位数据
        this.loadPromotionSpace();
    },
        //方法1: 加载广告位信息
        loadPromotionSpace() {
            this.listLoading = true;
            axios
                .get("/PromotionSpace/findAllPromotionSpace")
                .then(res => {
                this.list = res.data.content;
                this.listLoading = false;
            })
                .catch(err => {
                this.$message("加载数据失败! ! !");
            });
        },
}

3.1.2 添加广告位

1)点击 按钮,通过路由导航到指定组件

<el-button size="mini" class="btn-add" @click="handleAdd()">添加广告位el-button>
//添加广告位跳转
handleAdd() {
    this.$router.push({ path: "/addAdvertiseSpace" });
},

2) 查看路由 router.js, 跳转到的是 AddAdvertiseSpace.vue

{
    path: "addAdvertiseSpace",
    name: "AddAdvertiseSpace",
    component: () => import("../views/AdvertiseManage/AddAdvertiseSpace"),
    meta: { requireAuth: true, title: "添加广告位" }
},

3) 查看AddAdvertiseSpace.vue

<template>
//显示组件,并传递了参数 isEdit="false" , 表示是新增操作
<home-advertise-detail :isEdit="false">home-advertise-detail>
template>
<script>
    //引入了AdvertiseSpaceDetail组件
    import HomeAdvertiseDetail from "./AdvertiseSpaceDetail";
    export default {
        name: "addHomeAdvertise",
        title: "添加广告位",
        components: { HomeAdvertiseDetail }
    };
script>

4)真正显示的组件是 AdvertiseSpaceDetail.vue

首先判断要进行 新增还是修改操作, 根据isEdit ,true 为修改,false为新增

//钩子函数
created() {
    //判断是添加还是修改操作
    if (this.isEdit) {
        //修改
        const id = this.$route.query.id;
        this.loadPromotionSpace(id);
    } else {
        //新增
        this.homeAdvertise = {};
    }
},

新增

//方法1: 保存广告位信息
handleSave() {
    this.$refs.form.validate(valid => {
        if (!valid) return false;
        //请求后台
        axios
            .post(
            "/PromotionSpace/saveOrUpdatePromotionSpace",
            this.homeAdvertise
        )
            .then(res => {
            //返回上个页面
            this.$router.back();
        })
            .catch(err => {
            this.$message("数据处理失败! !");
        });
    });
},

3.1.3 修改广告位

需要请求后台接口,进行广告位信息回显

//方法2: 回显广告位信息
loadPromotionSpace(id) {
    return axios
        .get("/PromotionSpace/findPromotionSpaceById?id=" + id)
        .then(res => {
        Object.assign(this.homeAdvertise, res.data.content);
        this.homeAdvertise.id = id;
    })
        .catch(err => {
        this.$message("数据处理失败! !");
    });
}

3.2 广告管理

3.2.1 ElementUI 分页组件

Advertises.vue 组件,为广告列表页面
在这里插入图片描述

广告列表的展示,使用到了分页组件, 接下来通过一个案例演示一下分页插件的使用
在这里插入图片描述
https://element.eleme.cn/#/zh-CN/component/pagination

3.2.1.1 快速使用
  1. 在测试项目中,创建一个PageList.vue ,复制代码如下
<template>
<div>
    <div class="block">
        <span class="demonstration">完整功能span>
        <el-pagination
                       @size-change="handleSizeChange"
                       @current-change="handleCurrentChange"
                       :current-page="currentPage4"
                       :page-sizes="[100, 200, 300, 400]"
                       :page-size="100"
                       layout="total, sizes, prev, pager, next, jumper"
                       :total="400"
                       >el-pagination>
    div>
    div>
template>
<script>
    export default {
        methods: {
            handleSizeChange(val) {
                console.log(`每页 ${val} 条`);
            },
            handleCurrentChange(val) {
                console.log(`当前页: ${val}`);
            }
        },
        data() {
            return {
                currentPage4: 4
            };
        }
    };
script>
属性介绍
参数 说明 类型
page-size 每页显示条数 int
current-page 当前页 int
total 总条数 int
page-sizes 每页显示个数选项设置 [10, 20, 30,]
layout 组件布局

分析:

  • page-size 与 current-page 是需要前端传给后端的数据
  • total 和 列表数据 是需要后端返回给前端的
事件介绍
事件 说明 回调函数
size-change 每页显示条数,改变时会触发 每页条数
current-change 当前页,改变时会触发 当前页
案例演示
  1. 复制下面代码到 PageList
<template>
<div class="app-container">
    <div class="table-container">
        <el-table ref="homeAdvertiseTable" :data="list" style="width: 100%;" border>
            <el-table-column label="id" width="220" align="center">
                <template slot-scope="scope">{{scope.row.id}}template>
			el-table-column>
            <el-table-column label="广告名称" align="center" width="320">
            	<template slot-scope="scope">{{scope.row.name}}template>
            el-table-column>
            <el-table-column label="广告图片" width="420" align="center">
            	<template slot-scope="scope">
                	<img style="height: 80px" :src="scope.row.img" />
            	template>
			el-table-column>
		el-table>
	div>
	<div class="pagination-container">
    	<el-pagination
                   background
                   @size-change="handlePageSizeChange"
                   @current-change="handleCurrentPageChange"
                   layout="total, sizes,prev, pager, next,jumper"
                   :current-page="page"
                   :page-sizes="[5,10, 20]"
                   :page-size="size"
                   :total="total"
                   >el-pagination>
	div>
div>
template>
  1. 编写JS部分代码
<script>
    export default {
        data() {
            return {
                total: 0, //总条数
                size: 5, //每页显示条数
                page: 1, //当前页
                list: [] //广告数据
            };
        },
        created() {
            this.loadList();
        },
        methods: {
            //加载广告数据
            loadList() {
                return this.axios
                    .get("http://localhost:8080/ssm-web/PromotionAd/findAllPromotionAd",{
                    params: {
                        currentPage: this.page,
                        pageSize: this.size
                    }
                })
                    .then(res => {
                    this.list = res.data.content.list;
                    this.total = res.data.content.total;
                    this.listLoading = false;
                })
                    .catch(error => {
                    this.$message.error("数据获取失败! ! !");
                });
            },
            //每页显示条数发生变化
            handlePageSizeChange(size) {
                this.size = size;
                this.loadList();
            },
            //当前页发生变化
            handleCurrentPageChange(page) {
                this.page = page;
                this.loadList();
            }
        }
    };
script>

3.2.2 广告列表展示

需求分析

我们已经解决了分页问题,接下来再看一下广告页面要展示哪些内容:
在这里插入图片描述

广告列表的展示数据来源于两张表:
promotion_ad 广告表
promotion_space 广告位表

功能实现
数据部分

edu-boss\src\views\AdvertiseManage\Advertises.vue

//数据部分
data() {
    return {
        typeMap: {}, //保存广告位对象信息
        total: 0, //总条数
        size: 5, //每页显示条数
        page: 1, //当前页
        list: [], //广告数据
        listLoading: false
    };
},
钩子函数
created() {
    //获取广告列表数据
    this.loadPromotionAd();
    //获取广告位置数据
    this.loadPromotionSpace();
},
函数部分
//方法1; 获取广告列表数据
loadPromotionAd() {
    this.listLoading = true;
    return axios
        .get("/PromotionAd/findAllPromotionAd", {
        params: {
            currentPage: this.page,
            pageSize: this.size
        }
    })
        .then(res => {
        this.list = res.data.content.list;
        this.total = res.data.content.total;
        this.listLoading = false;
    })
        .catch(err => {});
},
    //方法2: 获取广告位置数据
    loadPromotionSpace() {
        this.listLoading = true;
        return axios.get("/PromotionSpace/findAllPromotionSpace").then(res => {
            //使用map进行遍历
            res.data.content.map(item => {
                //将数据保存到 typeMap key就是id,value就是 广告位对象
                this.typeMap[item.id] = item;
            });
            this.listLoading = false;
        });
    },
        //方法3: 获取广告位置名称
        getSpaceName(spaceId) {
            if (!spaceId) {
                return "";
            }
            return this.typeMap[spaceId] && this.typeMap[spaceId].name;
        },

3.2.3 广告状态修改

需求分析: 点击按钮实现 状态修改, 0 下线,1 上线

06_05_SSM项目前端开发(课程模块、广告模块、用户模块、权限模块)_第6张图片

功能实现

页面部分,使用的是 el-switch 组件

  • active-value: switch:打开时的值
  • inactive-value : switch 关闭时的值

<el-table-column label="上线/下线" width="120" align="center">
    <template slot-scope="scope">
        <el-switch
                   @change="handleUpdateStatus(scope.row)"
                   :active-value="1"
                   :inactive-value="0"
                   v-model="scope.row.status">el-switch>
    template>
el-table-column>

JS部分

//方法4: 修改状态
handleUpdateStatus(row) {
    this.$confirm("是否要修改上线/下线状态?", "提示", {
        confirmButtonText: "确定",
        cancelButtonText: "取消",
        type: "warning"
    }).then(() => {
        //请求后台
        axios
            .get("/PromotionAd/updatePromotionAdStatus", {
            params: {
                id: row.id,
                status: row.status
            }
        })
            .then(res => {
            this.loadPromotionAd();
        })
            .catch(err => {
            this.$message("修改状态失败! ! !");
        });
    });
},

3.3.4 广告新增&修改

需求分析
  1. 点击添加广告,触发事件
<el-button size="mini" class="btn-add" @click="handleAdd()">添加广告el-button>
  1. 路由导航到指定组件
//跳转到新增
handleAdd() {
    this.$router.push({ path: "/addAdvertise" });
},
  1. 查看路由信息,跳转到的是 AddAdvertise.vue组件
{
    path: "addAdvertise",
    name: "AddAdvertise",
    component: () => import("../views/AdvertiseManage/AddAdvertise"),
    meta: { requireAuth: true, title: "添加广告" }
},
  1. AddAdvertise.vue组件

在AddAdvertise组件中,引入了 AdvertiseDetail组件,真正的操作是在这个组件中完成的

:isEdit=“false” : false表示是新增操作

<template>
<home-advertise-detail :isEdit="false">home-advertise-detail>
template>

<script>
    import HomeAdvertiseDetail from './AdvertiseDetail'
    export default {
        name: 'addHomeAdvertise',
        title: '添加广告',
        components: { HomeAdvertiseDetail }
    }
script>
  1. AdvertiseDetail.vue 组件

该组件是进行 新增和修改广告的页面.
在这里插入图片描述

功能实现

数据部分

data() {
    return {
        homeAdvertise, //广告表单对象
        typeOptions: [] //广告位下拉列表
    };
},

钩子函数

created() {
    //判断是新增还是修改
    if (this.isEdit) {
        //修改
        const id = this.$route.query.id;
        this.loadPromotion(id);
    } else {
        //新增
        this.homeAdvertise = {};
    }
    this.loadPromotionSpace();
},

方法

//方法1: 获取广告位置数据
loadPromotionSpace() {
    return axios.get("/PromotionSpace/findAllPromotionSpace").then(res => {
        //使用map函数进行遍历,获取广告位id 与 name,保存到typeOptions
        this.typeOptions = res.data.content.map(item => {
            return { label: item.name, value: item.id };
        });
    });
},
    //方法2: 保存广告信息
    handleSave() {
        this.$refs.form.validate(valid => {
            if (!valid) return false;
            axios
                .post("/PromotionAd/saveOrUpdatePromotionAd", this.homeAdvertise)
                .then(res => {
                //返回上个页面 并刷新
                this.$router.back();
            })
                .catch(err => {});
        });
    },
        //方法3: 修改回显广告信息
        loadPromotion(id) {
            return axios
                .get("/PromotionAd/findPromotionAdById?id=" + id)
                .then(res => {
                Object.assign(this.homeAdvertise, res.data.content);
                this.homeAdvertise.id = id;
            })
                .catch(err => {});
        },
遇到的问题:为设置时间格式,传值原为Date类型,传到后端为Sring类型

前端向后端传输数据是,没有设置时间格式,那么可能会发生数据的传输上出现为Sring类型,然后报错
所以修改方式:加上value-format="yyyy-MM-dd HH:mm:ss"设置时间格式

 <el-date-picker
          type="datetime"
          placeholder="选择日期"
          value-format="yyyy-MM-dd HH:mm:ss"
          v-model="homeAdvertise.endTime"
        ></el-date-picker>

任务二 SSM前端项目开发_2

1. 用户管理

1.1 分页&条件查询用户数据

查询条件:

  1. 用户手机号
  2. 注册时间,包含开始日期和结束日期

06_05_SSM项目前端开发(课程模块、广告模块、用户模块、权限模块)_第7张图片

1.1.1 日期选择器组件

在查询条件中使用了 ElementUI中的日期选择器,我们一起来简单学习一下日期选择器的使用.

https://element.eleme.cn/#/zh-CN/component/date-picker#mo-ren-xian-shi-ri-qi
06_05_SSM项目前端开发(课程模块、广告模块、用户模块、权限模块)_第8张图片

  1. 在测试项目中,创建一个 TestDate.vue组件,复制代码到页面
<template>
<div>
    <div class="block">
        <span class="demonstration">带快捷选项span>
        <el-date-picker
                        v-model="dateTime"
                        type="daterange"
                        align="right"
                        unlink-panels
                        range-separator=""
                        start-placeholder="开始日期"
                        end-placeholder="结束日期"
                        :picker-options="pickerOptions"
                        >el-date-picker>
        <el-button type="primary" @click="getDate">查询el-button>
    div>
    div>
template>
<script>
    export default {
        data() {
            return {
                pickerOptions: {
                    shortcuts: [
                        {
                            text: "最近一周",
                            onClick(picker) {
                                const end = new Date();
                                const start = new Date();
                                start.setTime(start.getTime() - 3600 * 1000 * 24 * 7);
                                picker.$emit("pick", [start, end]);
                            }
                        },
                        {
                            text: "最近一个月",
                            onClick(picker) {
                                const end = new Date();
                                const start = new Date();
                                start.setTime(start.getTime() - 3600 * 1000 * 24 * 30);
                                picker.$emit("pick", [start, end]);
                            }
                        },
                        {
                            text: "最近三个月",
                            onClick(picker) {
                                const end = new Date();
                                const start = new Date();
                                start.setTime(start.getTime() - 3600 * 1000 * 24 * 90);
                                picker.$emit("pick", [start, end]);
                            }
                        }
                    ]
                },
                dateTime: ""
            };
        },
        methods: {
            getDate() {
                const params = {};
                params.startCreateTime = this.dateTime[0];
                params.startCreateTime.setHours(0);
                params.startCreateTime.setMinutes(0);
                params.startCreateTime.setSeconds(0);
                params.endCreateTime = this.dateTime[1];
                params.endCreateTime.setHours(23);
                params.endCreateTime.setMinutes(59);
                params.endCreateTime.setSeconds(59);
                console.log(params);
            }
        }
    };
script>

edu-boss\src\views\Users.vue

1.1.2 功能实现

数据部分

//数据部分
return {
    pickerOptions,//日期选择器选项设置
    total: 0, //总条数
    size: 10, //每页显示条数
    page: 1, //当前页
    filter,
    users: [],
    loading: false,
    allocAdminId: "",
    allocDialogVisible: false,
    allocRoleIds: [],
    allRoleList: []
};

JS部分

created() {
    //初始化用户数据
    this.loadUsers();
}
//方法1: 加载用户数据
loadUsers() {
    this.loading = true;
    //设置参数
    const params = { currentPage: this.page, pageSize: this.size };
    //过滤条件
    if (this.filter.username) params.username = this.filter.username;
    //设置日期参数
    if (this.filter.resTime) {
        params.startCreateTime = this.filter.resTime[0];
        params.startCreateTime.setHours(0);
        params.startCreateTime.setMinutes(0);
        params.startCreateTime.setSeconds(0);
        params.endCreateTime = this.filter.resTime[1];
        params.endCreateTime.setHours(23);
        params.endCreateTime.setMinutes(59);
        params.endCreateTime.setSeconds(59);
    }
    //请求后台接口
    return axios
        .post("/user/findAllUserByPage", params)
        .then(res => {
        this.users = res.data.content.list; //用户数据
        this.total = res.data.content.total;
        this.loading = false;
    })
        .catch(err => {
        this.$message("获取数据失败! ! !");
    });
},

出现的问题:当在mybatis中使用了插件驼峰命名的插件来进行对照数据,那么在实体类中不能使用xxx_xxx的方式而是要用xxxXxx的方式。

1.2 用户状态设置

状态按钮

<el-button size="mini" type="text" @click="handleToggleStatus(scope.row)">
{{ scope.row.status == "ENABLE" ? "禁用" : "启用" }}el-button>

JS部分

//修改用户状态
handleToggleStatus(item) {
    return axios
        .get("/user/updateUserStatus", {
        params: {
            id: item.id,
            status: item.status
        }
    })
        .then(res => {
        debugger;
        console.log(res.data.content);
        item.status = res.data.content;
    })
        .catch(err => {
        this.$message.error("状态修改失败! ! !");
    });
},

2.权限管理

2.1 角色管理

2.1.1 展示&查询角色列表

角色组件是 Roles.vue ,在该组件中对角色信息进行管理
在这里插入图片描述

需求分析

在这里插入图片描述

功能实现
  1. 数据部分
data() {
    return {
        listQuery: { name: "" },
        list: null,
        listLoading: false,
        dialogVisible: false,
        role: Object.assign({}, defaultRole),
        isEdit: false
    };
},
  1. 钩子函数,调用loadRoles,获取角色数据
created() {
    //获取角色列表
    this.loadRoles();
},
    //获取角色数据
    loadRoles() {
        return axios
            .post("/role/findAllRole", this.listQuery)
            .then(res => {
            this.list = res.data.content;
            this.listLoading = false;
        })
            .catch(err => {});
    },
  1. 请求携带的参数是: listQuery
<el-input v-model="listQuery.name" class="input-width" placeholder="角色名称" clearable>
el-input>
//条件查询
handleSearchList() {
    this.loadRoles();
},

2.1.2 添加&修改角色

  1. 页面部分
<el-button size="mini" class="btn-add" @click="handleAdd()" style="margin-left: 20px">
添加角色
el-button>
  1. 打开添加角色窗口的方法
//添加角色弹窗
handleAdd() {
    this.dialogVisible = true; //打开对话框
    this.isEdit = false; //false 修改操作
    this.role = Object.assign({}, defaultRole);
},
  1. 添加角色对话框,使用v-model 进行双向数据绑定

<el-dialog :title="isEdit?'编辑角色':'添加角色'" :visible.sync="dialogVisible"
           width="40%">
    <el-form :model="role" label-width="150px" size="small">
        <el-form-item label="角色名称:">
            <el-input v-model="role.name" style="width: 250px">el-input>
        el-form-item>
        <el-form-item label="角色编码:">
            <el-input v-model="role.code" style="width: 250px">el-input>
        el-form-item>
        <el-form-item label="描述:">
            <el-input v-model="role.description" type="textarea" :rows="5"
                      style="width: 250px">el-input>
        el-form-item>
    el-form>
    <span slot="footer" class="dialog-footer">
        <el-button @click="dialogVisible = false" size="small">取 消el-button>
        <el-button type="primary" @click="handleSave()" size="small">确 定elbutton>
    span>
el-dialog>
  1. 添加角色方法
//添加&修改角色
handleSave() {
    axios
        .post("/role/saveOrUpdateRole", this.role)
        .then(res => {
        this.dialogVisible = false;
        this.loadRoles();
    })
        .catch(error => {
        this.$message.error("保存课程信息失败! ! !");
    });
},
  1. 修改角色的方法

修改按钮,点击传递当前行数据对象

<el-button size="mini" type="text" @click="handleUpdate( scope.row)">编辑el-button>

显示对话框,回显数据

//修改角色弹窗
handleUpdate( row) {
    this.dialogVisible = true;
    this.isEdit = true;
    //回显数据
    this.role = Object.assign({}, row);
},

修改角色,还是调用的handleSave 方法

2.1.3 删除角色

<el-button size="mini" type="text" @click="handleDelete(scope.row)">删除el-button>

这里使用到了ElementIUI中的 MessageBox 弹框

https://element.eleme.cn/#/zh-CN/component/message-box#options

handleDelete(row) {
    this.$confirm("是否要删除该角色?", "提示", {
        confirmButtonText: "确定",
        cancelButtonText: "取消",
        type: "warning"
    }).then(() => {
        axios("/role/deleteRole?id=" + row.id)
            .then(res => {
            this.loadRoles();
        })
            .catch(err => {
            this.$message.error("操作失败! ! !");
        });
    });
},

2.1.4 为角色分配菜单

需求分析:

  • 为角色分配菜单,一个角色可以拥有多个菜单权限;
  • 一个菜单权限也可以被多个角色拥有在这里插入图片描述
  • 角色与菜单之间的关系 是多对多
    06_05_SSM项目前端开发(课程模块、广告模块、用户模块、权限模块)_第9张图片
    点击分配菜单,页面展示效果
    06_05_SSM项目前端开发(课程模块、广告模块、用户模块、权限模块)_第10张图片

前端要实现的效果:

  • 第一步: 获取到所有的菜单数据,在树形控件中进行展示
  • 第二步: 将当前角色拥有的菜单权限,勾选上
菜单展示功能实现
  1. 分配菜单按钮,点击传递当前行数据
<el-button size="mini" type="text" @click="handleSelectMenu(scope.row)">
分配菜单
el-button>
  1. 路由导航到 allocMenu
//为角色分配菜单
handleSelectMenu(row) {
    this.$router.push({ path: "/allocMenu", query: { roleId: row.id } });
},
  1. routes.js
{
    path: "allocMenu",
    name: "AllocMenu",
    component: () =>
    import(
        /* webpackChunkName: 'allocMenu' */ "../views/PermissionManage/AllocMenu"
    ),
    meta: { requireAuth: true, title: "角色菜单管理" }
},
  1. 在AllocMenu.vue组件中完成 为角色分配菜单操作
    在这里插入图片描述

  2. 数据部分

data() {
    return {
        menuTreeList: [], //菜单数据
        checkedMenuId: [], //被选中的菜单
        //树形结构子节点设置
        defaultProps: {
            children: "subMenuList",
            label: "name"
        },
        roleId: null
    };
},
  1. 钩子函数
//钩子函数
created() {
    //获取路由携带的id
    this.roleId = this.$route.query.roleId;
    //获取菜单列表
    this.treeList();
    //获取角色所拥有的菜单信息
    this.getRoleMenu(this.roleId);
},
    //方法1: 获取菜单列表,使用树形控件展示
    treeList() {
        axios.get("/role/findAllMenu").then(res => {
            console.log(res.data.content);
            //获取树形控件所需数据
            this.menuTreeList = res.data.content.parentMenuList;
        });
    },
        //方法2: 获取当前角色所拥有菜单列表id
        getRoleMenu(roleId) {
            axios.get("/role/findMenuByRoleId?roleId=" + roleId).then(res => {
                console.log(res.data.content);
                //将已有菜单权限设置为选中
                this.$refs.tree.setCheckedKeys(res.data.content);
            });
        },

分配菜单功能实现

分配菜单按钮

保存 清空
//方法3: 修改角色所拥有的菜单列表
handleSave() {
    //debugger;
    //获取所有被选中的节点
    const checkedNodes = this.$refs.tree.getCheckedNodes();
    //定义常量 保存被选中的菜单id
    const checkedMenuIds = [];
    if (checkedNodes != null && checkedNodes.length > 0) {
        //遍历获取节点对象
        for (let i = 0; i < checkedNodes.length; i++) {
            const checkedNode = checkedNodes[i];
            //保存菜单列表id
            checkedMenuIds.push(checkedNode.id);
            //判断: 当前节点为子节点 && 其父ID在数组没有出现过,就保存这个父Id
            if (
                checkedNode.parentId !== -1 &&
                checkedMenuIds.filter(item => checkedNode.parentId).length === 0
            ) {
                checkedMenuIds.push(checkedNode.parentId);
            }
        }
    }
    this.$confirm("是否分配菜单?", "提示", {
        confirmButtonText: "确定",
        cancelButtonText: "取消",
        type: "warning"
    }).then(() => {
        //准备参数
        const params = {
            roleId: this.roleId, //角色ID
            menuIdList: checkedMenuIds //当前角色拥有的菜单权限ID
        };
        //请求后台
        axios
            .post("/role/RoleContextMenu", params)
            .then(res => {
            this.$router.back();
        })
            .catch(err => {
            this.$message.error("权限分配失败! ! !");
        });
    });
},

2.2 菜单管理

菜单组件是 Menus.vue ,在该组件中对菜单信息进行管理
在这里插入图片描述

2.2.1 展示菜单列表

需求分析: 菜单列表的展示是带有分页的.
在这里插入图片描述

  1. 数据部分
data() {
    return {
        total: 0, //总条数
        size: 10, //每页显示条数
        page: 1, //当前页
        list: [], //广告数据
        listLoading: true,
        parentId: 0 //菜单父id
    };
},
  1. 钩子函数
created() {
    //获取菜单列表
    this.loadMenuList();
},
    //方法1: 加载菜单列表数据
    loadMenuList() {
        this.listLoading = true;
        return axios
            .get("/menu/findAllMenu", {
            params: {
                currentPage: this.page,
                pageSize: this.size
            }
        })
            .then(res => {
            this.list = res.data.content.list;
            this.total = res.data.content.total;
            this.listLoading = false;
        })
            .catch(error => {
            this.$message.error("数据获取失败! ! !");
        });
    },

问题:在后端的接口中的查询是未带分页,需要进行更新后端接口,将不是分页的修改为分页,或者是直接添加一个带参数的接口方法。

2.2.2 新增&修改菜单

路由跳转流程
  1. 新增按钮, 点击跳转
添加菜单
//新增菜单跳转
handleAddMenu() {
    this.$router.push("/addMenu");
},

2)AddMenu.vue 组件中引入了MenuDetail



  1. MenuDetail.vue 中完成菜单的新增与修改操作
    在这里插入图片描述
需求分析

在打开新增菜单页面后, 需要展示一个下拉框,下拉框中的数据是所有的顶级父菜单.
06_05_SSM项目前端开发(课程模块、广告模块、用户模块、权限模块)_第11张图片

功能实现
  1. 数据部分
data() {
    return {
        menu, //菜单对象
        selectMenuList: [], //下拉列表数据
        rules
    };
},
  1. 钩子函数

在钩子函数中会进行判断,如果是修改操作,就根据ID 查询当前菜单信息,以及父菜单信息;如果是新增操作,则只查询父类菜单信息即可

created() {
    if (this.isEdit) {
        //修改,回显菜单信息
        const id = this.$route.query.id;
        //获取当前菜单和父菜单信息
        this.findMenuInfoById(id);
    } else {
        //新增
        this.menu = {};
        //获取父类菜单信息
        this.findMenuInfoById(-1);
    }
},
//方法1: 添加或修改 下拉父菜单回显
findMenuInfoById(id) {
    axios
        .get("/menu/findMenuInfoById?id=" + id)
        .then(res => {
        debugger;
        console.log(res.data);
        //判断不为null,修改操作需要回显
        if (res.data.content.menuInfo != null) {
            this.menu = res.data.content.menuInfo;
        }
        //获取到父菜单信息,保存到selectMenuList
        this.selectMenuList = res.data.content.parentMenuList.map(item => {
            return { id: item.id, title: item.name };
        });
        //-1 显示 无上级菜单 (unshift向数组的开头添加一个元素)
        this.selectMenuList.unshift({ id: -1, title: "无上级菜单" });
    })
        .catch(err => {
        this.$message.error("数据获取失败! ! !");
    });
},
  1. 点击保存
提交
//保存菜单
handleSave() {
    this.$refs.form.validate(valid => {
        if (!valid) return false;
        axios
            .post("/menu/saveOrUpdateMenu", this.menu)
            .then(res => {
            this.$router.back();
        })
            .catch(error => {
            this.$message.error("保存课程信息失败! ! !");
        });
    });
}

2.3 资源管理

资源组件是 Resources.vue ,在该组件中对资源信息进行管理.
在这里插入图片描述

2.3.1 展示&查询资源列表

  1. 展示资源数据 带有分页
    在这里插入图片描述

  2. 查询资源数据,查询条件有三个:
    资源名称
    资源路径
    资源分类信息: 下拉列表

  3. 数据部分

//查询条件
const listQuery = {
    currentPage: 1,
    pageSize: 5,
    name: null,
    url: null,
    categoryId: null
};
//资源对象
const defaultResource = {
    id: null,
    name: null,
    url: null,
    categoryId: null,
    description: ""
};
data() {
    return {
        listQuery,//查询条件
        total: 0,
        list: [], //资源数据
        cateList: [], //资源分类数据
        listLoading: false,
        dialogVisible: false,
        resource: Object.assign({}, defaultResource),
        isEdit: false,
        categoryOptions: [],
        defaultCategoryId: null
    };
},
  1. 钩子函数
  • 在钩子函数中,需要获取资源 ,以及资源分类的数据
//钩子函数
created() {
    //获取资源数据
    this.getResourceList();
    //获取资源分类数据
    this.getResourceCateList();
},
  • getResourceList() 方法获取的是资源信息
//方法1: 获取资源数据
getResourceList() {
    this.listLoading = true;
    axios
        .post("/resource/findAllResource", this.listQuery)
        .then(res => {
        this.list = res.data.content.list;
        this.total = res.data.content.total;
        this.listLoading = false;
    })
        .catch(err => {
        this.$message.error("数据获取失败! ! !");
    });
},

getResourceCateList() 方法获取的是资源分类信息,在下拉框中展示

    //方法2: 获取资源分类数据
    getResourceCateList() {
        axios
            .get("/ResourceCategory/findAllResourceCategory")
            .then(res => {
            this.cateList = res.data.content;
            //遍历获取资源分类
            for (let i = 0; i < this.cateList.length; i++) {
                const cate = this.cateList[i];
                //将资源分类名与id保存到 categoryOptions中,供下拉列表展示
                this.categoryOptions.push({ label: cate.name, value: cate.id });
            }
            this.defaultCategoryId = this.cateList[0].id;
        })
            .catch(err => {
            this.$message.error("数据获取失败! ! !");
        });
    },

查询

<el-button style="float:right" type="primary" @click="handleSearchList()" size="small">
查询搜索el-button>
//查询条件对象
const listQuery = {
    currentPage: 1,
    pageSize: 5,
    name: null,
    url: null,
    categoryId: null
};
//查询按钮
handleSearchList() {
    this.getResourceList();
},

2.3.2 新增&修改资源

  1. 添加按钮
<el-button size="mini" class="btn-add" @click="handleAdd()" style="margin-left:20px">
    添加el-button>
  1. 显示添加资源表单的对话框
//添加资源回显
handleAdd() {
    this.dialogVisible = true; //显示表单
    this.isEdit = false; //新增为false
    this.resource = Object.assign({}, defaultResource); //资源对象
    this.resource.categoryId = this.defaultCategoryId; //保存默认分类id
},

06_05_SSM项目前端开发(课程模块、广告模块、用户模块、权限模块)_第12张图片

  1. 资源分类信息使用下拉菜单进行展示:

v-model 的值为当前被选中的 el-option 的 value 属性值

属性 说明
value 选项的值
label 选项的标签名
key 作为 value 唯一标识的键名
<el-form-item label="资源分类:">
    <el-select v-model="resource.categoryId" placeholder="全部" clearable style="width: 250px">
        <el-option
                   v-for="item in categoryOptions"
                   :key="item.value"
                   :label="item.label"
                   :value="item.value"
                   >el-option>
    el-select>
el-form-item>
  1. 点击保存
<el-button type="primary" @click="handleSave()" size="small">确 定el-button>
//添加&修改资源
handleSave() {
    axios
        .post("/resource/saveOrUpdateResource", this.resource)
        .then(res => {
        this.dialogVisible = false;
        this.getResourceList();
    })
        .catch(error => {
        this.$message.error("操作失败! ! !");
    });
},
  1. 修改操作, 参数是当前行数据
<el-button size="mini" type="text" @click="handleUpdate(scope.row)">编辑el-button>
  1. 回显操作
//编辑资源 回显
handleUpdate(row) {
    debugger;
    this.dialogVisible = true;
    this.isEdit = true;
    this.resource = Object.assign({}, row);
},

任务三 SSM项目前端开发_3

1.用户权限控制

1.1 用户登录

1.1.1 流程分析

  1. 用户登录界面,需要输入手机号密码

06_05_SSM项目前端开发(课程模块、广告模块、用户模块、权限模块)_第13张图片

  1. 登录组件 login.vue

在这里插入图片描述

  • 登录按钮
<el-button type="primary" :loading="loading" @click="submit('login-form')">{{ loading ? 'Loading...' : '登录' }}el-button>
  • 提交表的方法
 //提交登录表单
    submit(ref) {
      //校验
      this.$refs[ref].validate(valid => {
        if (!valid) return false;

        this.error = null;
        this.loading = true;

        //发送登录请求
        this.$store.dispatch("createToken", this.model)
          .then(res => {
            if (res.state !== 1) {
              this.error = {
                title: "Error occurred",
                message: "Abnormal, please try again later!"
              };
            }
            this.$router.replace({ path: this.$route.query.redirect || "/" });
            this.loading = false;
          })
          .catch(err => {
            this.loading = false;
          });
      });
    }
  }
  • this.$store.dispatch(“createToken”, this.model)
    • 这段代码的意思是调用 store仓库的actions.js中的createToken方法

06_05_SSM项目前端开发(课程模块、广告模块、用户模块、权限模块)_第14张图片

  • 发送登录请求,进行登录的代码
  /**
   * 创建新的客户端令牌
   */
  createToken: async ({ commit }, { username, password }) => {
    //请求后台登录接口
    const res = await TokenService.userLogin({
      phone: username.trim(),
      password: password.trim()
    });

    console.log(res);

    //判断结果不等于1,登录失败
    if (res.state !== 1) {
      return Promise.resolve(res);
    }

    //获取到content
    const result = res.content;

    //将token保存
    commit(CHANGE_SESSION, {
      accessToken: result.access_token
    });
      
    return res;
  },
  • TokenService
import { TokenService, UserService } from "../services";

06_05_SSM项目前端开发(课程模块、广告模块、用户模块、权限模块)_第15张图片

//登录请求 async ES6语法, 作用: 发送异步请求
export const userLogin =  async (data) => {
 //await 表示等待接收返回的数据
 return await PostRequest(`${process.env.VUE_APP_API_FAKE}/user/login${Serialize(data)}`)
}

1.2 动态获取用户菜单

1.2.1 流程分析

  1. 在我们登录成功后, 会立即发送第二个请求, 来获取用户的菜单权限列表

06_05_SSM项目前端开发(课程模块、广告模块、用户模块、权限模块)_第16张图片

  1. 在actions.js 中完成请求后台接口 获取数据的操作
  /**
   * 获取当前登录用户权限
   */
  getUserPermissions: async ({ commit }) => {
    //1.请求后台 获取当前用户的权限
    const res = await UserService.getUserPermissions();

    //2.判断
    if (!res.success) {
      //获取失败直接返回 false
      return res.success;
    }

    //3.获取数据成功,取出菜单 与 资源列表
    const { menuList, resourceList } = res.content;

    //4.下面的代码 就是在生成树形结构的菜单
    let menus = [];
    const formatMenu = treeData => {
      if (treeData.length > 0) {
        return treeData.map(item => formatMenu(item));
      }
      const result = {};

      //shown等于表示可以显示,将内容保存
      if (treeData.shown == 1) {
        result.id = treeData.id;
        result.text = treeData.name;
        result.label = treeData.name;
        result.name = treeData.href;
        result.icon = treeData.icon;
        result.shown = treeData.shown;
      } else {
        return "";
      }

      //获取子节点
      if (treeData.subMenuList) {
        result.children = [];
        treeData.subMenuList.forEach(item => {
          formatMenu(item) && result.children.push(formatMenu(item));
        });

        if (result.children.length === 0) {
          delete result.children;
        }
      }
      return result;
    };

    const memusMap = {};

    const splapMenu = treeData => {
      if (treeData.length > 0) {
        return treeData.map(item => splapMenu(item));
      }
      const result = {};
      result.id = treeData.id;
      result.text = treeData.name;
      result.label = treeData.name;
      result.name = treeData.href;
      result.icon = treeData.icon;
      result.shown = treeData.shown;
      result.name && (memusMap[result.name] = result);

      if (treeData.subMenuList) {
        result.children = [];
        treeData.subMenuList.forEach(item => {
          result.children.push(splapMenu(item));
        });
      }
      return result;
    };

    splapMenu(menuList);

    menus = formatMenu(menuList);
    commit(CHANGE_SIDERBAR_MENU, menus);
    return { menus, resourceList, menuList, memusMap };
  },

1.3 验证Token

1.3.1 导航守卫

  • 在执行路由之前先执行的一些钩子函数,比如验证用户是否有权限之类的操作,就需要使用.
  1. authorize.js 中配置了导航守卫,来对用户的登录进行限制
  // 导航守卫 to要访问的url, from从哪个路径跳转过来, next() 放行
  router.beforeHooks.unshift((to, from, next) => {
    //不需要验证直接放行
    if (!to.meta.requireAuth) return next();

    //需要验证token,调用 store中的checkToken方法
    store.dispatch("checkToken").then(valid => {
      //判断是否存在token
      if (valid) {
        //发送请求到后台,在后台再次判断token是否存在
        store.dispatch("getUserPermissions").then(res => {
        
          if (!res) {
            //失效 清除token
            store.dispatch("deleteToken");

            //跳转到登录页面
            return next({ name: "ToLogin" });
          }

          //token正确, 导航到对应的页面
          const { memusMap } = res;
          if (memusMap.Courses && to.name === "Home") {
            return next();
          } else if (memusMap[to.name]) {
            return next();
          } else if (Object.keys(memusMap).length > 0) {
            return next({ name: memusMap[Object.keys(memusMap)[0]].name });
          } else {
            next({ name: "PermissionDenied" });
          }
        });
        return next();
      }
      // unauthorized
      console.log("Unauthorized");

      //用户没有登录 跳转到登录页面
      next({ name: "Login", query: { redirect: to.fullPath } });
    });
  });
  1. 在actions.js 中检查token是否可用
checkToken: async ({ commit, getters }) => {
    //取出token
    const token = getters.session.accessToken;

    if (!token) {
      //不可用
      return Promise.resolve(false);
    }

    return Promise.resolve(true);
  },

1.4 用户角色分配

1.4.1 流程分析

  • 点击分配角色按钮

在这里插入图片描述

<el-button size="mini" type="text" @click="handleSelectRole(scope.row)">分配角色
el-button>
  • 分配角色对话框

06_05_SSM项目前端开发(课程模块、广告模块、用户模块、权限模块)_第17张图片

1.4.2 代码部分

  • 显示对话框
	//分配角色
    handleSelectRole(row) {
      //保存用户ID
      this.allocAdminId = row.id;

      //获取角色列表
      this.getRoleList();

      //获取当前用户拥有的角色
      this.getUserRoleById(row.id);

      //打开对话框
      this.allocDialogVisible = true;
    },
  • 获取角色列表,在下拉菜单中展示
 getRoleList(id) {
      return axios
        .post("/role/findAllRole", this.listQuery)
        .then(res => {
          this.allRoleList = res.data.content.map(item => {
            return { id: item.id, name: item.name };
          });
        })
        .catch(err => {});
    },
  • 获取当前用户拥有的角色,回显默认选中
 getUserRoleById(id) {
      axios.get("/user/findUserRoleById?id=" + id).then(res => {
        const allocRoleList = res.data.content;
        this.allocRoleIds = [];
        if (allocRoleList != null && allocRoleList.length > 0) {
          for (let i = 0; i < allocRoleList.length; i++) {
            this.allocRoleIds.push(allocRoleList[i].id);
          }
        }
      });
    },
  • 为用户分配角色
 handleAllocRole() {
      const params = {
        userId: this.allocAdminId,
        roleIdList: this.allocRoleIds
      };

      axios.post("/user/userContextRole", params).then(res => {
        this.allocDialogVisible = false;
      });
    },

你可能感兴趣的:(vue.js,node.js)