Java:74-SSM项目实战前端开发

SSM项目实战前端开发

通过前面后端的开发,接下来我们来操作前端开发(大致说明一下,不会说明全部)
且要注意,这里会与前面博客中后端项目结合起来
Vue回顾:
项目结构说明:
我们使用脚手架快速构建Vue项目,项目结构如下图:
对应的前端部分项目地址(只要部分,即下面我们来补充与后端的连接):
链接:https://pan.baidu.com/s/1o0yyu9gOtOtwwcaDzsnYDQ
提取码:alsk
注意:我们先不要运行,因为是需要登录的,等后面说明了登录才进行运行,当然也可以进行登录
基本可以随便登录的(因为对应的操作被注释了),且设置了固定值,所以基本可以随便登录,后面会进行改变
部分目录和解释:

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

Views 目录说明:
我们来一起看一下,前端项目的页面部分:

在这里插入图片描述

CourseManage:课程管理
AdvertiseManage:广告管理
PermissionManage:权限管理
CommentManage:公共
Users.vue:用户管理
Login.vue:登录
vue组件化开发:
每一个*.vue 文件都可以看做是一个组件
组件的组成部分:
template:组件的HTML部分
script:组件的JS脚本 (使用ES6语法编写)
style:组件的CSS样式
<!-- 1.template 代表html结构, template中的内容必须有且只有一个根元素
 编写页面静态部分 就是 view部分 -->
    
    <template>
    <div>
       测试页面...
    </div>
</template>
<!-- 2.编写vue.js代码 -->
<script>
    //可以导入其组件
    // import Header from '../components/header.vue' 
    
    //默认写法, 输出该组件信息,那么自然会用到,在vue里面相当于new Vue({}),即可以使用对应vue语法
    export default {
     name:"Home", // 组件名称,用于以后路由跳转
        data() {// 当前组件中需要使用的数据
            return {}
       },
      methods: {}
   }
</script>
<!-- 编写当前组件的样式代码 -->
<style scoped>
 /* 页面样式 加上scoped 表示样式就只在当前组件有效*/
</style>
课程模块回顾
注意:下面代码是项目里的主要代码
功能分析:
Course.vue 组件(改变名称后),完成课程数据的展示和条件查询
使用ElementUI 表格进行数据展示
https://element.eleme.cn/#/zh-CN/component/table
部分最开始请求:
//获取课程数据
    loadCourses() {
      this.loading = true;
      const data = {};

      //js的is中如果是0,null,空串,undifined则相当于false,除此之外则相当于true
      if (this.filter.courseName) data.courseName = this.filter.courseName;
      if (this.filter.status) data.status = this.filter.status;

      //发送请求
      return axios
        .post("/course/findAllCourse", data)
        .then(resp => {
          this.courses = resp.data.content;
          this.loading = false;
        })
        .catch(error => {
          this.$message.error("数据获取失败! ! !");
        });
    }

在这里插入图片描述

prop可以说是绑定数据的对应属性名数据
对应js代码:
//数据部分
  data() {
    //查询条件
    const filter = {
      courseName: "",
      status: ""
    };

    return {
      filter,
      courses: [], //课程数据
      loading: false
    };
  },

  //钩子函数
  created() {
    this.loadCourses();
  }
条件查询(访问的是同一个接口 ):
<el-button @click="handleFilter">查询</el-button>
//条件查询
    handleFilter() {
      this.loadCourses();
   }
新建课程 :
功能分析:
点击新建,由路由导航到 CourseItem.vue
<el-button type="primary" icon="el-icon-plus" @click="handleAdd">新建课程</el-button>
//新建课程 路由跳转
handleAdd() {
    this.$router.push({ name: "CourseItem", params: { courseId: "new" } });
    //对应路由的参数,必须由params属性操作
}
router.js:
 {
        path: "/courses/:courseId",
        name: "CourseItem",
        meta: { requireAuth: true, title: "课程详情" },
        component: () =>
          import(
            /* webpackChunkName: 'courses' */ "../views/CourseManage/CourseItem.vue"
          )
     //使用import可以不用我们来使用import导入了,而直接使用
      }
CourseItem组件使用ElementUI中的表单来提交课程数据:
https://element.eleme.cn/#/zh-CN/component/form

在这里插入图片描述

JS代码编写 :
<el-button type="primary" @click="handleSave">保存</el-button>
//保存修改课程信息
    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("保存课程信息失败! ! !");
          });
      });
    },
课程图片上传:
案例演示:
我们创建一个Vue项目来演示图片上传组件的使用方式
首先要有一个项目:
代码地址如下:
链接:https://pan.baidu.com/s/1PsQRmqdbr3Jj2cbr2X6pcQ
提取码:alsk
我们用这个项目来操作测试
注意上传图片是有要求的,因为目标的服务器(这里是)只会接收符合他条件的图片,如果上传不了,可以上传其他图片
在测试项目的components目录下创建一个 UploadImage.vue组件:
查看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" 
    >
        <!--: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>

然后在components目录下的index.vue修改或者添加(也是一个操作路由的):
 <el-submenu index="1">
              <template slot="title">
                <i class="el-icon-location"></i>
                <span>导航菜单</span>
              </template> 
		<el-menu-item-group>
                <!-- 修改 index的路由地址 -->
                <el-menu-item index="/upload">
                  <i class="el-icon-menu"></i>图片上传
                </el-menu-item>
		</el-menu-item-group>
  </el-submenu>
配置路由(操作router目录下的index.js):
 children: [
      {
        path: "/upload",
        name:"upload",
        component: () => import("@/components/UploadImage"),
      }
          ]
最后可以执行操作了,即访问页面进行测试
属性说明 (了解即可):

在这里插入图片描述

上面的action是将图片上传到那里去
组件的引入:
怎么将一个组件引入另一个组件
接下来我们来演示一下 引入图片组件(实际上是根据import导入后来注册使用组件来操作的,这里是局部组件)
在测试项目components目录下创建一个TestUplopad.vue组件:
<template>
    
    <!--使用组件-->

    <upload-image></upload-image>
</template>
<script>
import UploadImage from '@/components/UploadImage'
export default {  //export default可以理解为可以在给自己使用的同时,也可以给被导入使用
    //注册局部组件,可以给当前组件里使用组件UploadImage,相当于全局操作了
  components: { UploadImage },
    
}
</script>
<style scoped>

</style>
然后在对应的index.vue中加上:
   <el-menu-item-group>
                <!-- 修改 index的路由地址 -->
                <el-menu-item index="/text">
                  <i class="el-icon-menu"></i>测试组件的引入
                </el-menu-item>
              </el-menu-item-group>
<!--当作路由-->
加上对应路由(index.js):
  {
        path: "/text",
        name:"text",
        component: () => import("@/components/TextUpload"),
      }
然后就可以进行测试了
对应的项目执行流程可以参照58章博客
组件的传参 :
UploadImage.vue组件的部分修改
data() {
    return {
      dialogImageUrl: "",
      dialogVisible: false,
      actionUrl:this.uploadUrl //传递的参数要接收
    };
  },
      
/*
  props组件传参,给组件添加参数,为uploadUrl
  uploadUrl当作图片上传路径
  https://jsonplaceholder.typicode.com/posts/
  getUrl:函数
  */
  props:["uploadUrl","getUrl"],
<div>
    <!--
:action="actionUrl" 使用data的值,但是参数时,加上:
-->
    <el-upload
      :action="actionUrl" 
      list-type="picture-card"
      :on-preview="handlePictureCardPreview"
      :on-remove="handleRemove"
      :on-success="successUpload"
    >
         <!--上传成功后,调用successUpload方法-->
对应方法:
 successUpload(response, file){
      this.getUrl(file);
    }
对应操作导入上面组件的TestUplopad.vue组件的部分修改:
 <upload-image uploadUrl="https://jsonplaceholder.typicode.com/posts/" :get-url="show"></upload-image>
<!--设置添加了uploadUrl参数-->
<!--传递了show方法过去-->
对应方法:
  methods:{
        show(file){
            console.log("show方法被调用了")
            console.log(file);
        }
    }
//即我们执行geturl也就是:get-url的值是show方法,所以执行geturl(file)也就是执行show(file)方法
接下来进行测试了
我们通过上面的测试操作,接下来再看看如下操作
课程模块图片上传
我们可以看看对应的CourseItem.vue组件的图片上传代码
  <!-- 使用图片上传组件,完成图片上传 -->
          <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>

<!--发现这也是对应的组件,且设置添加了参数-->
<!--那么对应的js也应该引入了对应组件的-->

对应部分js如下
import UploadImage from "@/components/UploadImage.vue";
对应的部分UploadImage.vue组件:
 <el-upload
      class="upload-demo"
      :action="uploadAction"  
      :multiple="false"
      :limit="1"
      :before-upload="beforeUpload"
      :on-success="uploadSuccess"
      :on-remove="removeSuccess"
      :on-exceed="exceedFile"
      :file-list="fileList"
      list-type="picture-card"
    >
     <!-- :action="uploadAction" 也是对应值路径-->
对应的js
data() {
    return {
      //uploadUrl= /course/courseUpload
      //process.env.VUE_APP_API_BASE全局的值,这里也就是路径
      uploadAction: process.env.VUE_APP_API_BASE + this.uploadUrl, //封装了路径来使用
      fileList: [],
      data: {}
    };
  },
//js中可以再属性里加注释,而html在属性里不可加,因为html标签的原因
  /**
   * 组件传参
   *    content 图片地址
   *    get-urls 获取图片地址
   */
  props: ["content", "getUrls", "max", "tipInfo", "uploadUrl"],
发现也是一样的操作
注意:只要不是写死的资源,那么基本都会在检查里中的网络发现访问(注意是全部请求)
但是该访问是自己到这个页面的访问,而在该页面其中操作时,不会显示出来
ctrl+d可以查看当前页面代码选中的其他代码,一般从上到下查找,可以多次按
修改课程 :
CourseItem.vue组件部分代码
 axios
          .post("/course/saveOrUpdateCourse", this.course)
          .then(res => {
            //退回到上个页面
            this.$router.back();
          })
          .catch(error => {
            this.$message.error("保存课程信息失败! ! !");
          });
修改课程与添加课程走的都是同一个后台接口,区别是修改操作必须要携带ID
在这个Typora中按ctrl+.(点)可以切换是否可以输入中文符号,一般默认可以输入
课程状态管理 (Course.vue组件):
点击上架或者下架完成课程状态的切换
部分代码:
 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("状态修改失败! ! !");
        });
课程内容管理 :
获取课程内容数据:
课程内容数据包括章节与课时信息,根据课程ID 查询课程包含的章节与课时信息
CourseSections.vue组件部分代码:
 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("数据获取失败! ! !");
        });
//注意:这里的后台好像需要改变一下,使得不返回空数据,使得有默认,而显示在前端

//且要记住,有些字段是不能为空的,也就是说,若不写对应字段的话一般就会报错,除非你设置了初始值
章节管理:
新建和修改章节:
CourseSections.vue组件部分代码
 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("操作执行失败! ! !");
        });
添加与修改章节访问的都是同一个接口
章节状态:
CourseSections.vue组件部分代码
 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("修改状态失败! ! !");
      });
课时管理基本与章节类似,也就不操作了
大致回顾完毕,接下来我们来操作不一样的
上面是回顾,所以对应登录时可以操作,下面才是真正要操作的地方,所以当你没有编写下面代码,基本是访问不了的
广告模块:
广告位管理:
广告位展示 :
AdvertiseSpaces.vue 组件,为广告位页面
我们需要在其前端项目上进行添加访问后端的代码:
对应的js部分:
 //方法1: 加载广告位信息
    loadPromotionSpace() {
      //开启加载画面
      this.listLoading = true;
      //请求后台接口 获取广告位列表数据
      axios
        .get("/PromotionSpace/PromotionSpace")
        .then(res => {
          this.list = res.data.content;
          //关闭加载画面
          this.listLoading = false;
        })
        .catch(err => {
          this.$message.error("加载广告位列表数据失败");
        });
    },
添加广告位:
在AdvertiseSpaces.vue 组件里有:
 <el-button size="mini" class="btn-add" @click="handleAdd()"
        >添加广告位</el-button>
进行跳转:
对应的js部分:
//添加广告位跳转
    handleAdd() {
      this.$router.push({ path: "/addAdvertiseSpace" });
    },
查看部分路由routes.js(不要认为非常复杂,我们可以将路由看成将vue组件指定显示地方的操作):
 {
        path: "addAdvertiseSpace",
        name: "AddAdvertiseSpace",
        component: () => import("../views/AdvertiseManage/AddAdvertiseSpace"),
        meta: { requireAuth: true, title: "添加广告位" }
      },
发现跳转的是AddAdvertiseSpace.vue组件:
js部分(对该组件进行了引用)
<script>
import HomeAdvertiseDetail from "./AdvertiseSpaceDetail";
export default {
  name: "addHomeAdvertise",
  title: "添加广告位",
  components: { HomeAdvertiseDetail }
};
</script>
最后发现是使用了AdvertiseSpaceDetail.vue组件,一般组件里是不能写script标签和style标签的,不会识别
对应js操作
//钩子函数
  created() {

    //判断是添加还是修改
    if(this.isEdit) {
      //修改
    }else{
      //添加
      this.homeAdvertise = {}//清空数据,防止回显
    }
  }, 
    methods: {
    //方法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.error("保存广告位信息失败");
      });
      })
    },
    }
修改广告位:
对应的跳转步骤与新增类似,可以自行查看(在UpdateAdvertiseSpace.vue组件)
对应js部分:
  //判断是添加还是修改
    if(this.isEdit) {
      //修改
    
      const id = this.$route.query.id //使用$route来接收值
    
      this.loadPromotionSpace(id)
    }
 //方法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.error("回显广告位信息失败");
      });
    }
后台访问方法是一样的,就不做说明了
广告管理:
广告列表的展示,使用到了分页组件,接下来通过一个案例演示一下分页插件的使用
ElementUI 分页组件:

在这里插入图片描述

https://element.eleme.cn/#/zh-CN/component/pagination
在测试项目中,创建一个PageList.vue组件,复制代码如下(进行了修改)
<template>

  <div>

 
  <div class="block">
    <span class="demonstration">完整功能</span>
    <el-pagination
      @size-change="handleSizeChange"
      @current-change="handleCurrentChange"
      :current-page="currentPage4"
      :page-sizes="[10, 20, 30, 40]"
      :page-size="40"
      layout="total, sizes, prev, pager, next, jumper"
      :total="400">
    </el-pagination>
          <!--
:current-page="currentPage4" 设置一开始的前往第几页,以及当前页,实际上是改变中间显示的位置,即当前页位置
若不在页数范围里,即若超过则默认最大的页数,若低于则默认最小的页数
  :page-sizes="[100, 200, 300, 400]"设置几页一条的下拉框,也影响中间的总显示,即当前页
:page-size="100"每页显示条数,有时会影响下拉框的操作,即确定下拉框一开始的值
若超过了则会显示超过的,但没有其他修饰
 :total="400"总条数,会影响中间的总显示,即当前页
 可以发现:总条数=每页条数+当前页的总显示,所以总条数和每页条数会影响当前页总显示
-->
  </div>
  </div>
</template>
<script>
  export default {
    methods: {

        //显示条数变化时触发
      handleSizeChange(val) {
        console.log(`每页 ${val}`);
      },
      //当前页变化时触发,即中间的总显示变化
      handleCurrentChange(val) {
        console.log(`当前页: ${val}`);
      }
    },
    data() {
      return {
       
        currentPage4: 4
      };
    }
  }
</script>
对应路由添加(通过地址将该组件渲染到路由地方):
 {
        path: "/page",
        name:"page",
        component: () => import("@/components/PageList"),
      }
对应index.vue的添加(这个一般是侧边栏的操作,使得访问地址,进行页面的跳转,将组件放到对应地方显示):
   <el-menu-item-group>
                <!-- 修改 index的路由地址 -->
                <el-menu-item index="/page">
                  <i class="el-icon-menu"></i>分页组件
                </el-menu-item>
              </el-menu-item-group>
接下来我们可以进行测试了
现在我们搞一个数据,赋值项目的代码到该测试项目里面
修改后的PageList.vue组件:
<template>
  <div class="app-container">
    
    <div class="table-container">
      <el-table
        ref="homeAdvertiseTable"
        :data="list"
        style="width: 100%;"
        v-loading="listLoading"
        border
      >
        <el-table-column label="id" width="120" align="center">
          <template slot-scope="scope">{{scope.row.id}}</template>
        </el-table-column>
        <el-table-column label="广告名称" align="center">
          <template slot-scope="scope">{{scope.row.name}}</template>
        </el-table-column>

       

        <el-table-column label="广告图片" width="120" 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>
<script>
  export default {
       data() {
      return {
       list:[], //列表数据  后台传递过来
        page: 1, //当前页   前端带过去
        size: 5, //每页显示条数,设置:page-sizes="[5,10, 20]"的初始位置,即这里是5页/条  前端带过去
        //上面两个是给后台的参数的
        total: 0, //总条数   后端传递,因为后端不止传递列表数据,还有其分页数据,因为传递的就是分页对象
        listLoading:true
     };
    },
    //钩子函数
    created(){
        this.loadList();
    },
    methods: {
        //获取广告数据
        loadList(){
            return 
            //注意:这里之所以要加this,是因为当前组件并没有导入axios,但其他组件已经导入,那么就需要全局
            //而其他的导入是直接得到对应的axios的,文件夹里封装对应相应名称,就跟变量一样直接使用
            this.axios.get("http://localhost:8080/ssm_web/PromotionAd/findAllPromotionAdByPage",
                           //这个是后端的访问,前面博客里已经说明了
                           {params:{
                CurrentPage:this.page,
                PageSize:this.size
            }}).then(res => {
                console.log(res);
               this.list = res.data.content.list;
               this.total = res.data.content.total;
               console.log(this.tolal)
               this.listLoading=false
            }).catch(err => {
                console.log("获取广告数据失败");
            })
        },

//下面val是修改后的页数
        //显示条数变化时触发
      handlePageSizeChange(val) {

        this.size = val;
        this.loadList();
      },
      //当前页变化时触发,即中间的总显示变化
      handleCurrentPageChange(val) {
        this.page = val;
        this.loadList();
      }
    },  //后面加,是没问题的,当然也可以不加,加不加无所谓
   
  }
</script>
然后就可以操作对应的分页了
测试完成后,接下来我们来看真正的项目代码
广告列表展示 :
通过观察项目的Advertises.vue组件来操作
我们已经解决了分页问题,接下来再看一下对应组件代码编写:
<template>
  <div class="app-container">
    <el-card class="operate-container" shadow="never">
      <el-button size="mini" class="btn-add" @click="handleAdd()">添加广告</el-button>
    </el-card>
    <div class="table-container">
      <el-table
        ref="homeAdvertiseTable"
        :data="list"
        style="width: 100%;"
        v-loading="listLoading"
        border
      >
        <el-table-column label="id" width="120" align="center">
          <template slot-scope="scope">{{scope.row.id}}</template>
        </el-table-column>
        <el-table-column label="广告名称" align="center">
          <template slot-scope="scope">{{scope.row.name}}</template>
        </el-table-column>

        <!-- 获取广告位置信息  spaceId 外键id-->
        <el-table-column label="广告位置" width="200" align="center">
          <template slot-scope="scope">{{getSpaceName(scope.row.spaceId)}}</template>
        </el-table-column>

        <el-table-column label="广告图片" width="120" align="center">
          <template slot-scope="scope">
            <img style="height: 80px" :src="scope.row.img" />
          </template>
        </el-table-column>
        <el-table-column label="时间" width="250" align="center">
          <template slot-scope="scope">
            <p>开始时间:{{scope.row.startTime | formatTime}}</p>
            <p>到期时间:{{scope.row.endTime | formatTime}}</p>
          </template>
        </el-table-column>

        <!-- 上线与下线 -->
        <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>

        <!-- 编辑按钮 -->
        <el-table-column label="操作" width="120" align="center">
          <template slot-scope="scope">
            <el-button size="mini" type="text" @click="handleUpdate(scope.row)">编辑</el-button>
          </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>

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

import moment from "moment";

export default {
  name: "homeAdvertiseList",
  title: "广告管理",
  inject: ["reload"],
  //数据部分
  data() {
    return {
      typeMap: {}, //保存广告位对象信息
      total: 0, //总条数
      size: 5, //每页显示条数
      page: 1, //当前页
      list: [], //广告数据
      listLoading: false
    };
  },

  //钩子函数
  created() {
    //获取广告列表数据
    this.loadPromotionAd();

    //获取广告位数据
    this.loadPromotionSpace();

    //打印typeMap
    console.log(this.typeMap); //会先打印,但我们点击时,会出现对应数据,是因为ajax是异步的
  },
  methods: {
    //方法1; 获取广告列表数据
    loadPromotionAd() {
      //加载动画是否开启
      this.listLoading = true;
      axios.get("/PromotionAd/findAllPromotionAdByPage",
      {params:{
        CurrentPage:this.page,
        PageSize:this.size
      }})
      .then(res => {
        //   res.data.content.list.map(item =>{
        //     this.typeMap[item.promotionSpace.id] = item.promotionSpace;
        // }) 这里可以省略掉方法2
        this.list = res.data.content.list;
        this.total = res.data.content.total;
       
        this.listLoading = false
        
      })
      .catch(err => {
        this.$message.error("获取广告列表数据失败");
      })
    },

    //方法2: 获取广告位置数据
    loadPromotionSpace() {
//加载动画是否开启,这里加不加都是一样的,因为数据被添加时已经使得不会操作了
      this.listLoading = true;
       axios.get("PromotionSpace/findAllPromotionSpace")
      .then(res => {
console.log(  res.data.content)
        //对该content对象使用map方法,将集合的数据依次取出变成item
        res.data.content.map(item =>{
         console.log(item)
          //将数据保存到typeMap中,其中得到的item的id就是key,所以可以使用item.id,用括号括起来
          //这里会执行多次,直到没有item的数据
          //实际上在方法1里就可以操作,因为有这个数据,但这里分开写了,这里就在方法2里写
            this.typeMap[item.id] = item; //在空的键值对里,我们可以直接指定key,使得创建获得value
            //而之所以使用id,是因为这个id的广告位的数据的id,那么对应的可以使用外键id来获得
        })
            this.listLoading = false;
      })
      .catch(err => {
        this.$message.error("获取广告位置数据失败");

    }
      )},

    //方法3: 获取广告位置名称
    getSpaceName(spaceId) {
        if(!spaceId){
          return "";
        }
        //返回对应的广告位置名称
        return this.typeMap[spaceId] && this.typeMap[spaceId].name; //使用外键id来获得名称了
        //使用&&防止对应数据没有,做个保险
    },

    //方法4: 修改状态
    handleUpdateStatus(row) {},

    //跳转到新增
    handleAdd() {
      this.$router.push({ path: "/addAdvertise" });
    },

    //跳转到修改
    handleUpdate(row) {
      this.$router.push({ path: "/updateAdvertise", query: { id: row.id } });
    },

    //页码变化触发的函数
    handleCurrentPageChange(page) {
      this.page = page;
      this.loadPromotionAd();
    },

    //每页显示条数变化触发的函数
    handlePageSizeChange(size) {
      this.size = size;
      this.loadPromotionAd();
    }
  },
  filters: {
    formatTime(time) {
      return moment(time).format("YYYY-MM-DD HH:mm:ss");
    }
  }
};
</script>
<style scoped>
.input-width {
  width: 203px;
}
</style>

我们编写了对应js代码,但还有些是我们需要编写的,所以接下来我们来进行操作
广告状态修改:
对应html:
 <!-- 上线与下线 -->
        <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>
 	    <!--
          active-value: switch:打开时的值 
          inactive-value : switch 关闭时的值 

只要我们点击,他们就会进行切换,然后执行对应方法
        -->
添加部分对应js部分:
我们首先去https://element.eleme.cn/#/zh-CN/component/message-box(Element-UI)里面找到对应的js代码复制过来并进行操作
//方法4: 修改状态
    handleUpdateStatus(row) { //row当前这一行的对象
this.$confirm('是否要修改上线/下线状态?', '提示', {
          confirmButtonText: '确定',
          cancelButtonText: '取消',
          type: 'warning'
        }).then(res => {
          axios.get("/PromotionAd/updatePromotionAdStatus",{params:{
            id:row.id,
            status:row.status
        
          }}).then(res => { //当然可以继续使用then,因为方法是返回本身的
          this.loadPromotionAd()
          }).catch(err => {
            this.$message.error("修改状态失败") 
            //不同的操作有不同的显示,可以去Element-UI里找对应的操作
            //对应地址https://element.eleme.cn/#/zh-CN/component/message
          })
        }).catch(err => { //设置取消时出现的操作
          row.status==1?row.status=0:row.status=1;
          //之所以这样做,是因为他是先改变才进行方法执行,若不这样操作
          //那么当你取消时,他却改变了,这是不符合操作的,所以在取消时,需要取反
        })
    },
广告新增&修改 :
新增操作(对应的html):
 <el-button size="mini" class="btn-add" @click="handleAdd()">添加广告</el-button>
对应的js:
 //跳转到新增
    handleAdd() {
      this.$router.push({ path: "/addAdvertise" });
    },
对应路由:
  {
        path: "addAdvertise",
        name: "AddAdvertise",
        component: () => import("../views/AdvertiseManage/AddAdvertise"),
        meta: { requireAuth: true, title: "添加广告" }
      },
对应的AddAdvertise.vue组件:
<template>
  <home-advertise-detail :isEdit="false"></home-advertise-detail>
</template>
<script>
import HomeAdvertiseDetail from './AdvertiseDetail'
export default {
  name: 'addHomeAdvertise',
  title: '添加广告',
  components: { HomeAdvertiseDetail }
}
</script>
<style></style>

对应的AdvertiseDetail.vue组件(这里进行修改操作,因为最后操作的就是这个组件了):
<template>
  <el-card class="form-container" shadow="never">
    <!-- 新增&修改广告表单 -->
    <el-form :model="homeAdvertise" :rules="rules" ref="form" label-width="150px" size="small">
      <el-form-item label="广告名称:" prop="name">
        <el-input v-model="homeAdvertise.name" class="input-width"></el-input>
      </el-form-item>

      <!-- 广告位下拉列表
            typeOptions 广告位数据
            label option的内容
            value 广告位的id
      -->
      <el-form-item label="广告位置:">
        <el-select v-model="homeAdvertise.spaceId">
          <el-option
            v-for="type in typeOptions"
            :key="type.value"
            :label="type.label"
            :value="type.value"
          ></el-option>
        </el-select>
      </el-form-item>
      <el-form-item label="开始时间:" prop="startTime">
        <el-date-picker type="datetime" placeholder="选择日期" v-model="homeAdvertise.startTime">
          </el-date-picker>
      </el-form-item>
      <el-form-item label="到期时间:" prop="endTime">
        <el-date-picker type="datetime" placeholder="选择日期" v-model="homeAdvertise.endTime">
            </el-date-picker>
      </el-form-item>

      <!-- 上线下线 -->
      <el-form-item label="上线/下线:">
        <el-radio-group v-model="homeAdvertise.status">
          <el-radio :label="0">下线</el-radio>
          <el-radio :label="1">上线</el-radio>
        </el-radio-group>
      </el-form-item>

      <!-- 广告图片 -->
      <el-form-item label="广告图片:">
        <upload-image
          :content="homeAdvertise.img && [homeAdvertise.img]"
          :get-urls="getADImgUrl"
          uploadUrl="/PromotionAd/PromotionAdUpload"
          ref="courseCoverRef"
          max="10M"
          tipInfo="建议尺寸:230*300px,JPG、PNG格式,图片小于150K"
        />
      </el-form-item>

      <el-form-item label="广告链接:" prop="link">
        <el-input v-model="homeAdvertise.link" class="input-width"></el-input>
      </el-form-item>
      <el-form-item label="广告备注:">
        <el-input
          class="input-width"
          type="textarea"
          :rows="5"
          placeholder="请输入内容"
          v-model="homeAdvertise.text"
        ></el-input>
      </el-form-item>
      <el-form-item>
        <el-button type="primary" @click="handleSave()">提交</el-button>
      </el-form-item>
    </el-form>
  </el-card>
</template>
<script>
import UploadImage from "@/components/UploadImage.vue";
import { axios } from "../../utils";

//广告对象 defaultHomeAdvertise
const homeAdvertise = {
  id: undefined,
  name: null,
  spaceId: "",
  img: null,
  startTime: null,
  endTime: null,
  status: 0,
  link: null,
  text: null,
  sort: 0
};
const rules = {
  name: [
    { required: true, message: "请输入广告名称", trigger: "blur" },
    {
      min: 2,
      max: 140,
      message: "长度在 2 到 140 个字符",
      trigger: "blur"
    }
  ],
  link: [{ required: true, message: "请输入广告链接", trigger: "blur" }],
  startTime: [{ required: true, message: "请选择开始时间", trigger: "blur" }],
  endTime: [{ required: true, message: "请选择到期时间", trigger: "blur" }],
  img: [{ required: true, message: "请选择广告图片", trigger: "blur" }]
};

export default {
  name: "HomeAdvertiseDetail",
  components: { UploadImage },

  //组件传参
  props: {
    isEdit: {
      type: Boolean,
      default: false
    }
  },

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

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

    //获取广告位数据
    this.loadPromotionSpace();
  },
  methods: {
    //方法1: 获取广告位置数据
    loadPromotionSpace() {
      return axios.get("/PromotionSpace/findAllPromotionSpace").then(res => {
        //使用map函数进行遍历,得到所有的广告位信息,实现下拉列表的操作
        this.typeOptions = res.data.content.map(item => {
          return {
            value: item.id,
            label: item.name
          };
        });
        //前面说过,它会执行很多次,实际上是这个item的箭头函数执行很多次,他也是方法,不要弄错
        //前面我们只是用来赋值,实际上他1返回值也是一个对象,且是多个return的{}集合
        //我们可以输出this.typeOptions看一看
        console.log(this.typeOptions);
        //发现他输出一个数组,那么就知道,map函数返回一个数组,且对应值是return的值
      })
    },

    //方法2: 保存广告信息
    handleSave() {
      this.$refs.form.validate(valid => {
		if (!valid) return false;
      if(this.homeAdvertise.id == undefined){
        if(this.homeAdvertise.startTime != undefined&& this.homeAdvertise.endTime!= undefined){
       var year = this.homeAdvertise.startTime.getFullYear(); // 年份
        var month = this.homeAdvertise.startTime.getMonth() + 1; //月份从0开,11结束,所以国内习惯要+1
        var day = this.homeAdvertise.startTime.getDate(); // 几号
        var hour = this.homeAdvertise.startTime.getHours(); // 几点
        var mm = this.homeAdvertise.startTime.getMinutes(); // 分钟
        var s = this.homeAdvertise.startTime.getSeconds(); //秒
        this.homeAdvertise.startTime= year+"-"+month+"-"+day+" "+hour+":"+mm+":"+s;
         year = this.homeAdvertise.endTime.getFullYear(); // 年份
        month = this.homeAdvertise.endTime.getMonth() + 1; //月份从0开,11结束,所以国内习惯要+1
        day = this.homeAdvertise.endTime.getDate(); // 几号
        hour = this.homeAdvertise.endTime.getHours(); // 几点
        mm = this.homeAdvertise.endTime.getMinutes(); // 分钟
        s = this.homeAdvertise.endTime.getSeconds(); //秒
        this.homeAdvertise.endTime= year+"-"+month+"-"+day+" "+hour+":"+mm+":"+s;
        }
      }
      //对应的时间不符合后台格式(因为使用了注解操作json必须为对应的格式),所有这里操作一下对应的格式
      console.log(this.homeAdvertise.startTime);
       console.log(this.homeAdvertise.endTime);
      axios.post("/PromotionAd/saveOrUpdatePromotionAd",this.homeAdvertise).then(res => {
        //返回上个页面
        this.$router.back();
      }).catch(err => {
        this.$message.error("保存失败");
      });
      })
    },

    //方法3: 修改回显广告信息
    loadPromotion(id) {},

    //获取图片路径,进行回显
    getADImgUrl(urls) {
      if (urls.length > 0) {
        this.homeAdvertise.img = urls && urls[0].filePath;
      }
    }
  }
};
</script>
<style scoped>
.input-width {
  width: 70%;
}
</style>

修改操作对应的html
 <el-button size="mini" type="text" @click="handleUpdate(scope.row)">编辑</el-button>
对应的js:
 //跳转到修改
    handleUpdate(row) {
      this.$router.push({ path: "/updateAdvertise", query: { id: row.id } }); //query传递id参数
    },
对应的路由:
 {
        path: "updateAdvertise",
        name: "UpdateAdvertise",
        component: () => import("../views/AdvertiseManage/UpdateAdvertise"),
        meta: { requireAuth: true, title: "编辑广告" }
      },
          //可能你会发现,并没有上面操作query,这是因为他只是存放,而并没有使用而已
对应的UpdateAdvertise.vue组件:
<template>
  <home-advertise-detail :isEdit="true"></home-advertise-detail>
</template>
<script>
import HomeAdvertiseDetail from './AdvertiseDetail'
export default {
  name: 'updateHomeAdvertise',
  title: '编辑广告',
  components: { HomeAdvertiseDetail }
}
</script>
<style></style>

发现他最后使用的是AdvertiseDetail组件,与新增是一样的
即我们任然操作AdvertiseDetail组件(实际上修改和添加之所以可以使用一个方法,是因为前端操作了对应标识):
 <home-advertise-detail :isEdit="true"></home-advertise-detail>
<!--修改这就是true,新增就是false,这里简单说明一下-->
然后通过对应的true和false,给对应对象加上id,使得操作新增和修改
接下来我们看一看修改的主要js代码:
  //方法3: 修改回显广告信息
    loadPromotion(id) {
      return axios.get("/PromotionAd/findPromotionAdById?id=" + id).then(res => {
        Object.assign(this.homeAdvertise, res.data.content);
          //之所以使用上面进行赋值,而不使用下面的,是防止我们已有的信息被覆盖,虽然大多数情况下不会
          //而使用Object.assign方法,只会赋值我们对应的,若多出一些信息,那么也会添加,即不会消除键key
          //this.homeAdvertise = res.data.content
          this.homeAdvertise.id = id;
      }).catch(err => {
        this.$message.error("回显广告信息失败");
    
      })
    },
用户管理:
分页&条件查询用户数据:
日期选择器组件 :
在查询条件中使用了 ElementUI中的日期选择器,我们一起来简单学习一下日期选择器的使用
https://element.eleme.cn/#/zh-CN/component/date-picker#mo-ren-xian-shi-ri-qi
在测试项目中,创建一个 TestDate.vue组件,复制代码到页面(下面我进行了修改)
注意复制粘贴时,记得组件只能有一个根标签,若报错了基本就是这个错误,在最外面加上div即可(其他也行,但最好div)
对应html:
<template>
  
  <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>

</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() {
            console.log(this.dateTime); 
            //输出的是数组,即开始时间和结束时间两个组合的数组,且他们都是Date对象
            //但是发现对应的时分秒都是00:00:00,所以下面的操作是操作对应的时分秒
            //实际上是操作结束时间的时分秒,,位23:59:59表示这一天的末尾
            const params = {};
            params.startCreateTime = this.dateTime[0];
            //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);
            //设置好了对应的时分秒了,否则默认都是0
            console.log(this.dateTime); 
            console.log(params);

            //注意:日期的标签,只会识别对应位置,如2022-06-032,最后就是2022-06-03,2不是对应位置即不识别
		//而超过了对应数,如2022-09-59,会自动进行操作,变成2022-10-29,进行,进日期操作
        }
    }
  };
</script>
对应路由:
  {
        path: "/date",
        name:"date",
        component: () => import("@/components/TestDate"),
      }
对应侧边栏:
  <el-menu-item-group>
                <!-- 修改 index的路由地址 -->
                <el-menu-item index="/date">
                  <i class="el-icon-menu"></i>日期控件
                </el-menu-item>
              </el-menu-item-group>
接下来执行项目就可以看到对应效果了
测试完毕后,我们再来操作项目的用户的分页&条件查询
对应的组件是Users.vue组件(发现也是有这个日期的操作的)
首先我们先操作分页&条件查询:
主要的js部分:
 //方法1: 加载用户数据
    loadUsers() {
      this.loading = true;
      //设置日期参数
      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);
      }
      //设置参数
      const params = { currentPage: this.page, pageSize: this.size };

      //请求后台接口
      return axios
        .post("/user/findAllUserByPage", params)
        //之所以这里不用设置年月日的格式,是因为后台是操作json的,json会默认转换Date字符串
        .then(res => {
          this.users = res.data.content.list;
          this.total = res.data.content.total;
          this.loading = false;
        })
        .catch(err => {
          this.$message.error("获取用户数据失败");
        });
    },
        //注意:这是数据的改变,也就是说数据改变会直接影响页面,而不需要刷新
        //而像什么添加操作等等,并没有改变本来数据,所以一般需要刷新使得去数据库里再次得到
用户状态设置:
对应的html部分:
 <!-- 禁用按钮  v-if="scope.row.status == 'ENABLE'"-->
      <el-table-column label="操作" align="center" min-width="120">
        <template slot-scope="scope">
          <el-button
            size="mini"
            type="text"
            @click="handleToggleStatus(scope.row)"
            >{{ scope.row.status == "ENABLE" ? "禁用" : "启用" }}</el-button
          >
          <el-button
            size="mini"
            type="text"
            @click="handleSelectRole(scope.$index, scope.row)"
            >分配角色</el-button
          >
        </template>
      </el-table-column>
对应js部分:
//方法2: 修改用户状态
    handleToggleStatus(item) {
      item.status == "ENABLE"?item.status = "DISABLE":item.status = "ENABLE" //取反实现切换
      //js的三元表达式可以不用获得值,而java必须要
      return axios.get("/user/updateUserStatus",{
        params:{
          id:item.id,
          status:item.status
        }
      }).then(res => {
        console.log(item.status)
      console.log(res.data.content)
        item.status = res.data.content;
      }).catch(err => {
        this.$message.error("修改用户状态失败");
    
      })
    },
用户模块的分配角色后面再进行操作
权限管理:
角色管理:
展示&查询角色列表 :
角色组件是 Roles.vue ,在该组件中对角色信息进行管理
一开始得到数据的对应js部分(条件查询也是):
  //获取角色数据
    loadRoles() {

this.listLoading =true
      axios.post("/role/findAllRole",this.listQuery).then(res => {
        this.list = res.data.content;
        this.listLoading =false
      }).catch(err => {
        this.$message.error("获取角色列表失败");
      });
   
    },
添加&修改角色 :
对应的js:
 //添加&修改角色
    handleSave() {

      return axios.post("/role/saveOrUpdateRole",this.role).then(res => {
this.loadRoles();

        this.dialogVisible = false;
      }).catch(err => {
        this.$message.error("操作失败");
        
      })
    },
在这里说明一下,在添加数据到sql时,若是字符串的null,即"null"
是可以添加到设置了not null的字段的,因为他与not null的约束的null是不同的,只有单纯的null才不能添加到设置了not null里面
而"null"(字符串的null)可以
而在sql手动加null时,都默认为null,而不是字符串的"null",即出现不了null在字段中,一般只能通过程序来操作
而我们手动添加"null","也是会当作数据的,所以加上null一般需要程序来操作
删除角色的方法:
对应的js:
 //删除角色
    handleDelete(row) {

      //在打出confirm时,出现的提示中可以点击帮我们生成下面代码
      //注意:要是对应的confirm的提示,因为有不同的confirm的提示
      this.$confirm('是否要删除该角色', '提示', { //这个方法也进行了异步的操作,所以方法外的代码也执行了
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning'
      }).then(() => {
        //请求后台
        axios.get("/role/deleteRole?id=" + row.id).then(res => {
      
          this.loadRoles();
        }).catch(err => {
          this.$message.error("删除失败");
        });
      }).catch(() => {
        this.$message("已取消");
      });
       
    },
为角色分配菜单:
为角色分配菜单,一个角色可以拥有多个菜单权限
一个菜单权限也可以被多个角色拥有
点击分配菜单,页面展示效果:
前端要实现的效果
第一步:获取到所有的菜单数据,在树形控件中进行展示
第二步:将当前角色拥有的菜单权限,勾选上
菜单展示功能实现:
对应的js:
 //为角色分配菜单
    handleSelectMenu(row) {
      this.$router.push({ path: "/allocMenu", query: { roleId: row.id } });
    },
发现他进行了跳转,对应路由:
 	  {
        path: "allocMenu",
        name: "AllocMenu",
        component: () =>
          import(
            /* webpackChunkName: 'allocMenu' */ "../views/PermissionManage/AllocMenu"
          ),
        meta: { requireAuth: true, title: "角色菜单管理" }
      },
找到AllocMenu.vue组件:
对应的html:
<template>
  <el-card class="form-container" shadow="never">
    <el-tree
      :data="menuTreeList"
      show-checkbox
      default-expand-all
      node-key="id"
      ref="tree"
      highlight-current
      :props="defaultProps"
    ></el-tree>
    <div style="margin-top: 20px" align="center">
      <el-button type="primary" @click="handleSave()">保存</el-button>
      <el-button @click="handleClear()">清空</el-button>
    </div>
  </el-card>
</template>

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

export default {
  name: "allocMenu",
  title: "角色菜单管理",
  data() {
    return {
      menuTreeList: [], //菜单数据
      checkedMenuId: [], //被选中的菜单

      //树形结构子节点设置
      defaultProps: {
        children: "subMenuList",
        label: "name"
      },
      roleId: null
    };
  },
  //钩子函数
  created() {
    //获取路由携带的id
    this.roleId = this.$route.query.roleId;

    //获取菜单列表
    this.treeList();

    //获取角色所拥有的菜单信息
    this.getRoleMenu(this.roleId);
  },
  methods: {
    //方法1: 获取菜单列表,使用树形控件展示
    treeList() {},

    //方法2: 获取当前角色所拥有菜单列表id
    getRoleMenu(roleId) {},

    //方法3: 修改角色所拥有的菜单列表
    handleSave() {},

    //清空选择
    handleClear() {
      this.$refs.tree.setCheckedKeys([]);
    }
  }
};
</script>

<style scoped>
</style>

操作获取菜单列表数据:
对应的js:
 //方法1: 获取菜单列表,使用树形控件展示
    treeList() {
      axios.get("/menu/findAllMenu").then(res => {
this.menuTreeList =  res.data.content.parentMenuList //树形控件控的就是数组
//即得到了对应的数组
      }).catch(err => {

       this.$message.error("获取菜单列表失败");
      });
    },
然后回显对应已有的菜单
对应的js:
//方法2: 获取当前角色所拥有菜单列表id
    getRoleMenu(roleId) {
      axios.get("/menu/findMenuByRoleId?roleId=" + roleId).then(res => {
        console.log(res.data.content);
        //将角色已有的菜单权限进行勾选
        this.$refs.tree.setCheckedKeys(res.data.content);
        //这是树形控件的操作,指定对应tree,勾选对应的节点
        //具体可以查询Element-UI的树形控件中的树节点选择的那个地方
      })
    },
接下来进行分配:
对应的js部分:
 //方法3: 修改角色所拥有的菜单列表
    handleSave() {

      //首先获得他的树形控件数据
      const ca = this.$refs.tree.getCheckedNodes();  
      //getCheckedKeys获得选择节点的key来操作,即这里就是id,因为node-key="id"
      //this.$refs.tree可以看成得到当前文档的tree对应的节点,即树形所有节点
   
      console.log(ca)
     
      //定义一个获取对应id的变量数组
      const id = [];

  
      //然后循环操作id放入
      if(ca != null){
        for(let i =0 ;i<ca.length;i++){
          const cid = ca[i];
        
          //添加对应id放入数组里面
          id.push(cid.id);

          //在这个树形控件中,只有当子节点全部选择后,才会选择父节点
          //但是我们需要局部的操作也要加上父节点,即这里操作加上对应的父节点
          //而在代码中也是一般先有父节点才会操作子节点的,虽然你可以不用去设置
          //实际上也不需要去设置,因为只有能显示给你看的,基本就是对应的父子节点关系
            //但为了顺便加上对应父节点,这里就这样操作了,实际上并不需要,因为添加联系最好还是对应的添加
            //而不是程序帮忙添加,而为了使得程序顺便添加是要考虑到非常多的情况的,如这里

          //所以这里需要判断一下,如果当前节点的父节点是否已经在id中,如果不在,则添加进去
         

          //使用数组的filter方法来判断,将创建一个数组得到id这个数组,再进行过滤
          //通过返回的规则进行过滤,满足规则的则保留,不满足的则移除数组中的不满足规则的元素
          //而由于是创建的,即原数组不会变

          //解释下面的操作
          //若数组没有cid.parentId元素,即对应的父元素id,则对应判断相等
            //那么就说明前面没有父节点,那么就添加进去,当然若当前就是父节点,那么这里也就不会执行了
          //即下面的操作实现了判断当前是否是父节点,以及数组里面是否有父节点
            //只要他们有,这个条件就不会为true
        
    
         if(cid.parentId !== -1 && id.filter(item => item!=cid.parentId).length === id.length){
            
            id.push(cid.parentId);
          }
        }


        }
         //但是上面有个问题
         //即当加上父节点后,下次回显一般都会默认加上其所有子节点
         //因为父节点加上了,树形控件会加上其子节点的信息
         //所以我们在回显的时候,来判断是否添加了全部子节点来进行回显
        console.log(id)

      //接下来我们来操作访问后台
      
      this.$confirm('是否分配菜单', '提示', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning'
      }).then(() => {

        const params = {
          roleId: this.roleId, //角色id
          menuIdList: id  //菜单id数组
        } 
        console.log(params)
        axios.post("/menu/roleContextMenu",params).then(res => {
        this.$router.back();

        }).catch(err => {
          this.$message.error("分配失败")
        })
        
      }).catch(() => {
        this.$message("已经取消了")
      });

      },
      
          
          
接下来我们来修改一下回显:
 //方法2: 获取当前角色所拥有菜单列表id
    getRoleMenu(roleId) {
      axios.get("/menu/findMenuByRoleId?roleId=" + roleId).then(res => {
        console.log(res.data.content);
        //将角色已有的菜单权限进行勾选
        //先得到返回的id数据
        const d =[];
        console.log(this.menuTreeList)
       console.log(this.menuTreeList.length)
        for(let a =0;a<this.menuTreeList.length;a++){
         
          for(let b =0;b<res.data.content.length;b++){
          console.log("a:"+this.menuTreeList[a].id)
           console.log("b:"+res.data.content[b])
            if(this.menuTreeList[a].id == res.data.content[b]){
             
              d.push(res.data.content[b])
            }
          }
        }
console.log(d)
        //判断对应父节点id是否全部勾选

        let s =0;
        for(let c =0;c<d.length;c++){
          for(let dd =0;dd<this.menuTreeList.length;dd++){
            
          if(this.menuTreeList[dd].id==d[c]){
     
            for(let j =0;j<this.menuTreeList[dd].subMenuList.length;j++){
                for(let k =0;k<res.data.content.length;k++){
             
                  
              if(this.menuTreeList[dd].subMenuList[j].id==res.data.content[k]){
           
                s++;
           
              }
                }
            }
        
          
            if(s!=this.menuTreeList[dd].subMenuList.length){
            s=0
              for(let k =0;k<res.data.content.length;k++){
              
                if(res.data.content[k]==this.menuTreeList[dd].id){
             
                  res.data.content.splice(k,1); //删除数组的k元素,即不是全部勾选的删除
                  
                }
              }
              
            }
          }
        }
        }
        console.log("a"+res.data.content);

        this.$refs.tree.setCheckedKeys(res.data.content);
        //这是树形控件的操作,指定对应tree,勾选对应的节点
        //具体可以查询Element-UI的树形控件中的树节点选择的那个地方
      })
    },
而由于axios是异步的,即我们需要让回显慢一点运行:
 //钩子函数
  created() {
    //获取路由携带的id
    this.roleId = this.$route.query.roleId;

    //获取菜单列表
    this.treeList();

  let that = this
    //获取角色所拥有的菜单信息
     setTimeout(function(){that.getRoleMenu(that.roleId)},1000);
    
  },
但是我们也会发现,若获取菜单列表执行速度超过了1秒钟,那么对应的数据是会出现问题的,所以这里改进一下:
改进后的代码:
 //钩子函数
  created() {
    //获取路由携带的id
    this.roleId = this.$route.query.roleId;

    //获取菜单列表
    this.treeList();

  let that = this
    //获取角色所拥有的菜单信息
    
   let ki = setInterval(function(){
      if(that.menuTreeList.length != 0){
      that.getRoleMenu(that.roleId)
       clearInterval(ki);
      }
      alert(1)
      },500);
  },
只有当对应的数组有数据时,也就是得到后台响应的数据时,才会进行方法调用
即一直监听是否得到数据,且间隔0.5秒钟进行一次监听
我们发现,为了实现一个功能,代码一般很多的,而算法就可以解决代码非常多的问题,来进行简化
以后来说明算法的妙用
菜单管理:
对应的组件是Menus.vue组件
首先我们进行展示菜单列表
对应的js:
注意:这里需要在对应后台加上分页操作(因为后台一直是查询所有)
 //方法1: 加载菜单列表数据,且可以进行分页操作
    loadMenuList() {
      this.listLoading = true;
      axios.post("/menu/findAllMenuList",
        {
          "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 => {
        this.$message.error("获取菜单列表失败");
      });
    },
新增&修改菜单:
新增js:
 //新增菜单跳转
    handleAddMenu() {
      this.$router.push("/addMenu");
    },

对应路由:
 {
        path: "addMenu",
        name: "AddMenu",
        component: () =>
          import(
            /* webpackChunkName: 'menuAdd' */ "../views/PermissionManage/AddMenu"
          ),
        meta: { requireAuth: true, title: "添加菜单" }
      },
发现是AddMenu.vue组件:
对应的html:
<template>
  <menu-detail :is-edit='false'></menu-detail>
</template>
<script>
import MenuDetail from './MenuDetail'
export default {
  name: 'addMenu',
  title: '添加菜单',
  components: { MenuDetail }
}
</script>
<style>
</style>

发现导入了MenuDetail.vue组件来进行显示:
对应组件的html信息:
<template>
  <el-card class="form-container" shadow="never">
    <el-form :model="menu" :rules="rules" ref="form" label-width="150px">
      <el-form-item label="菜单名称:" prop="name">
        <el-input v-model="menu.name"></el-input>
      </el-form-item>
      <el-form-item label="菜单路径:" prop="href">
        <el-input v-model="menu.href"></el-input>
      </el-form-item>
      <el-form-item label="上级菜单:">
        <el-select v-model="menu.parentId" placeholder="请选择菜单">
          <el-option
            v-for="item in selectMenuList"
            :key="item.id"
            :label="item.title"
            :value="item.id"
          ></el-option>
        </el-select>
      </el-form-item>
      <el-form-item label="描述:" prop="description">
        <el-input v-model="menu.description"></el-input>
      </el-form-item>
      <el-form-item label="前端图标:" prop="icon">
        <el-input v-model="menu.icon" style="width: 80%"></el-input>
      </el-form-item>
      <el-form-item label="是否显示:">
        <el-radio-group v-model="menu.shown">
          <el-radio :label="0"></el-radio>
          <el-radio :label="1"></el-radio>
        </el-radio-group>
      </el-form-item>
      <el-form-item label="排序:">
        <el-input v-model="menu.orderNum"></el-input>
      </el-form-item>
      <el-form-item>
        <el-button type="primary" @click="handleSave()">提交</el-button>
      </el-form-item>
    </el-form>
  </el-card>
</template>

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

const menu = {
  description: "",
  parentId: -1,
  name: "",
  icon: "",
  shown: 0,
  orderNum: 0
};

const rules = {
  description: [
    { required: true, message: "请输入菜单描述", trigger: "blur" },
    {
      min: 2,
      max: 140,
      message: "长度在 2 到 140 个字符",
      trigger: "blur"
    }
  ],
  name: [
    { required: true, message: "请输入菜单名称", trigger: "blur" },
    {
      min: 2,
      max: 140,
      message: "长度在 2 到 140 个字符",
      trigger: "blur"
    }
  ],
  icon: [
    { required: true, message: "请输入前端图标", trigger: "blur" },
    {
      min: 2,
      max: 140,
      message: "长度在 2 到 140 个字符",
      trigger: "blur"
    }
  ]
};

export default {
  name: "MenuDetail",
  props: {
    isEdit: {
      type: Boolean,
      default: false
    }
  },
  data() {
    return {
      menu, //菜单对象
      selectMenuList: [], //下拉列表数据
      rules
    };
  },
  created() {},
  methods: {
    //方法1: 添加或修改 下拉父菜单回显
    findMenuInfoById(id) {},

    //保存菜单
    handleSave() {
      this.$refs.form.validate(valid => {
        if (!valid) return false;
      });
    }
  }
};
</script>

<style scoped>
</style>

首先先操作回显的上级菜单信息:
 created() {
if(this.isEdit){
//修改
}else{

  //添加
  //首先解决回显的初始化
 this.menu = Object.assign({},this.menu);

 //获取父菜单信息
 this.findMenuInfoById(-1)  //-1表示新增,在后台是这样设置的

}

  },
  //方法1: 添加或修改 下拉父菜单回显
    findMenuInfoById(id) {
axios.get("/menu/findMenuInfoById?id=" + id).then(res => {
  console.log(res.data)

//而由于新增和修改都是这个方法,且没有对应的回显方法,即这里需要进行回显,用对应的判断
if(res.data.content.menuInfo != null){
    //不为null,则是修改操作
    this.menu = res.data.content.menuInfo;
}

//获取下来列表父菜单信息
this.selectMenuList = res.data.content.parentMenuList.map(item => {
  return {
    id: item.id,
    title: item.name
  };
  //当然他循环的是对应数组长度
  //即我们也可以使用这样的次数来操作不是返回值的,也可以操作返回值来实现创建一个新的数组
});
//我们只需要对应的id和name数据,即使用map函数来操作

//接下来我们给的显示默认值,而不是-1的默认值
this.selectMenuList.unshift({id:-1,title:"无上级菜单"})//将该对象存放在数组的第一个位置
//而对于Select选择器,由于对于的默认的值设置了-1,那么当有循环这个key得到的id值一样时
//就会使用label的操作,使得变成无上级菜单




})

    },
修改js:
 //修改菜单跳转
    handleUpdate(index, row) {
      this.$router.push({ path: "/updateMenu", query: { id: row.id } });
    },

对应路由:
 {
        path: "updateMenu",
        name: "UpdateMenu",
        component: () =>
          import(
            /* webpackChunkName: 'menuUpdate' */ "../views/PermissionManage/UpdateMenu"
          ),
        meta: { requireAuth: true, title: "编辑菜单" }
      },
对应的UpdateMenu.vue组件:
<template>
  <menu-detail :is-edit='true'></menu-detail>
</template>
<script>
import MenuDetail from './MenuDetail'
export default {
  name: 'updateMenu',
  title: '编辑菜单',
  components: { MenuDetail }
}
</script>
<style>
</style>

发现最后也是MenuDetail.vue组件
所以现在我们来操作MenuDetail.vue组件的保存操作
最后注意一下:菜单列表是按照主键来排序的,而不是排序字段,因为我们需要有顺序的显示(可能以后需要用到排序字段)
所以可能对应的父菜单是分开的,而像明显显示给我们看的(如菜单列表),一般都会按照主键来排列
新增&修改菜单的保存操作 :
对应的js:
//保存菜单
    handleSave() {
      this.$refs.form.validate(valid => {
        if (!valid) return false;
        console.log(this.menu)
       this.menu.createdBy="system"
       this.menu.updatedBy="system"
       //上面是为了使得保存时有默认的值,因为mysql的对应字段是设置了not null约束的
      this.menu.parentId==-1?this.menu.level=0:this.menu.level=1; //判断菜单级别
        
        axios.post("/menu/saveOrUpdateMenu",this.menu).then(res => {
         this.$router.back();
          
        }).catch(err => {
          this.$message.error("操作失败");
        });
      });
    }
当我们编辑时,也有进行回显,改变钩子函数:
  created() {
if(this.isEdit){
//修改 回显菜单信息
const id = this.$route.query.id

//获取父菜单信息
 this.findMenuInfoById(id) //回显对应的参数指定的id对应的菜单信息
}else{

  //添加
  //首先解决回显的初始化
 this.menu = Object.assign({},this.menu);

 //获取父菜单信息
 this.findMenuInfoById(-1)  //-1表示新增,在后台是这样设置的

}

  },
那么我们就可以测试了
注意:我们在后台的菜单级数并没有操作,也就是说默认为0,即一级,所以这里需要你去判断设置一下
最后:由于js可以直接指定元素赋值,如一个空的var a ={},你直接指定的a.id=1,那么对应的就会有a这个参数创建
所以vue的双向绑定时,实际上对应的没有也会创建的,但若对应的直接获取,自然是找不到的,即报错
删除功能:
回到原来的Menus.vue组件:
对应删除的js:
  //删除功能
    handleDelete(index, row) {
      this.$confirm('是否删除该菜单信息', '提示', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning'
      }).then(() => {
        axios.get("/menu/deleteMenu?id=" + row.id).then(res => {
           this.loadMenuList();
      }).catch(err => {
        this.$message.error("删除失败");
      
      })
        
      }).catch(() => {
        this.$message('已取消删除');
      
      });
      
    }
资源管理:
对应的组件是Resources.vue组件:
获取资源信息的js:
 //方法1: 获取资源数据
    getResourceList() {
        this.listLoading = true
      axios.post("/resource/findAllResourceByPage",this.listQuery) 
      //this.listQuery的对应信息我们输入时就会提交过去,这就是双向绑定的好处
      .then(res => {
        this.list = res.data.content.list;
        this.total = res.data.content.total;
          this.listLoading = false
      }).catch(err => {
        this.$message.error("获取资源数据失败");
      });
   
    },
注意:有些地方不起作用,最好自己去解决(提示:重置)
获取分类信息的js:
 //方法2: 获取资源分类数据
    getResourceCateList() {
      axios.get("/resource/findAllResourceCategory").then(res => {
          this.cateList = res.data.content //这个可以不写,但我们可以用他来保存对应的数据,在以后使用

          //将该数据保存到下拉列表中,保存对应的名称,但这里需要指定对应名称
          for(let k =0;k<this.cateList.length;k++){
            const ca = this.cateList[k]
            this.categoryOptions.push({label:ca.name,value:ca.id}) //设置下拉列表,和对应的id传递
          }
          
      }).catch(err => {
        this.$message.error("获取资源分类数据失败");

      })
    },

新增&修改资源:
对应的新增js:
首先是对弹出来的框框进行操作显示
 //添加资源回显,开启对应的框框
    handleAdd() {

      this.isEdit = false
      this.dialogVisible = true  //顺序可以很重要的,虽然速度太快,基本无影响
      this.resource = Object.assign({},defaultResource) //初始化对应的回显数据,后面的就是用来初始化的
      
    },
主要添加资源操作js:
注意:对应后台并没有对应的资源的添加,修改,删除,所以需要自己去编写
 //添加&修改资源
    handleSave() {
    console.log(this.resource)
 //由于this.resource里面的值是默认存在的,即并没有通过绑定来创建参数
 //即后台的设置"null"是会被覆盖的,所以这里进行操作
 if(this.resource.name == null) this.resource.name = "null"
 if(this.resource.url == null)this.resource.url = "null"
      axios.post("/resource/saveOrUpdateResource",this.resource).then(res => { 
        //注意:一般设置了json,传递时就要是json的格式,否则是会报错的
        this.getResourceList();
        this.dialogVisible = false;
       
      
      }).catch(err => {
        this.$message.error("添加或修改资源失败");
      })
     
    },
		//注意:一般前端没有设置表单约束,而这个没有设置的,一般可能会与后台冲突,即冲突那么就会报错
        //为了防止这种情况,我们一般操作报错信息,当然我们也会设置特殊情况,如上面的"null"字符串
        //但是这里要注意,最好不要设置"null"字符串,这是为了方便数据库的数据合理性
        //但这里设置了,这是为了降低错误而已,实际情况下基本都会有表单约束的
修改操作也是需要回显框框:
对应的js如下:
 //编辑资源 回显
    handleUpdate(row) {
       this.isEdit = true
      this.dialogVisible = true  
      
      this.resource = Object.assign({},row) //将row拷贝到{}里面,相当于就是得到row
      //但他们虽然值相同,但地址不同,所以==连接时是false
      console.log(row)
      console.log(this.resource)
    },
保存的操作与添加操作的保存是一样的
后面的资源分类以及对应的删除资源通过上面的操作,可以自己进行编写了
还有对应的分配资源
注意:我们进行分页时使用的Mybatis的分页操作的总条数是查询的条数,然后根据对应的分页操作来获取
即当我们没有数据时,对应的总条数一般都为0,而前端是虽然总条数可以设置为0或者负数
但对应的当前页,在经过总条数时,就算再小也不会超过1,即一般最新的当前页就是第一页
正是因为这样,所以我们可以操作删除进行当前页的减小,从而实现当删除当前页的最后一个数据时,进行到前一页
对应的提示:一般设置对应的当前页–操作,来实现到前一页
用户权限控制:
对应的组件是:Login.vue组件
对应的js部分
 //发送登录请求
        this.$store
          .dispatch('createToken', this.model) //执行了createToken方法
          .then(res => {
            
            
            //...后面省略
我们去找到createToken方法,在store的actions.js里面:
对应的js:
 //请求后台登录接口,被注释了,我们可以解开,这样就使得不会随便的输入就能登录了
    // const res = await TokenService.userLogin({
    //   phone: username.trim(),
    //   password: password.trim()
    // });


//前面有import { TokenService, UserService } from "../services";
总体js操作:
/**
   * 创建新的客户端令牌,可以这样说
   */
  createToken: async ({ commit }, { username, password }) => {
    //请求后台登录接口
    const res = await TokenService.userLogin({
      phone: username.trim(),
      password: password.trim()
    });

    //const res = login();
    console.log(res);

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

    //获取到content
    //const result = JSON.parse(res.content);
    const result = res.content;

    //将token保存在 session
    commit(CHANGE_SESSION, {
      accessToken: result.access_token
      //refreshToken: result.refresh_token
    });

    return res;
  },
找到services里面的js(好像是tokens.js),即可以看看对应的userLogin方法:
//登录请求
export const userLogin = async (data) => {
  return await PostRequest(`${process.env.VUE_APP_API_FAKE}/user/login?${Serialize(data)}`)
}
Login.vue组件不省略的js:
//提交登录表单
    submit (ref) {
      // form validate
      this.$refs[ref].validate(valid => {
        if (!valid) return false

        // validated
        this.error = null
        this.loading = true

        // create token from remote
        //发送登录请求
        this.$store
          .dispatch('createToken', this.model) //执行了createToken方法
          .then(res => {
            if (res.state !== 1) {  //但是返回1时,说明登录成功
              this.error = {
                title: 'Error occurred',
                message: 'Abnormal, please try again later!'
              }
              switch (res.state) {
                case 201:
                  this.error.message = '请输入正确的手机号'
                  break
                case 202:
                  this.error.message = '请输入密码'
                  break
                case 203:
                  this.error.message = '密码错误'
                  break
                case 204:
                  this.error.message = '验证码过期'
                  break
                case 205:
                  this.error.message = '帐号错误或密码错误'
                  break
                case 206:
                  this.error.message = '帐号错误或密码错误'
                  break
                case 207:
                  this.error.message = '验证码错误'
                  break
                case 500:
                  this.error.message =
                              'Internal server error, please try again later!'
                  break
              }
              this.loading = false
              return
            }
            
            this.loading = false //这个与按钮有关,也可以不写
            //登录成功就会到这里来,对应的'/'也就是首页了
            this.$router.replace({ path: this.$route.query.redirect || '/' })
            
          })
          .catch(err => {
            console.error(err)
            this.error = {
              title: 'Error occurred',
              message: 'Abnormal, please try again later!'
            }
            switch (err.response && err.response.status) {
              case 401:
                this.error.message = 'Incorrect username or password!'
                break
              case 500:
                this.error.message =
                  'Internal server error, please try again later!'
                break
            }
            this.loading = false
          })
      })
    }
动态获取用户菜单:
而我们使用vue时,之所以基本只会显示对应的组件,是因为对应的js并没有运行造浏览器
他们通过js操作返回给我们一个网页,即会发现基本其他的组件也看不到
在我们登录成功后,会立即发送第二个请求,来获取用户的菜单权限列表
对应的在store的actions.js里面:
/**
   * 获取当前登录用户权限
   */
  getUserPermissions: async ({ commit }) => {
    //console.log("访问任何页面都会经过我");
    //const res = fetchUserPermissions();
    const res = UserService.getUserPermissions();
      
      
      //...后面省略
对应的方法在users.js里面,对应的getUserPermissions方法:
export const getUserPermissions = async () => {
  return await baseRequest(`${bossBase}/permission/getUserPermissions`)
}
发现也是传递请求
注意:当跨域请求时,对应的cookie不会传递过去,即不会携带,也就是说,这样的使用cookie来操作的可能会操作不了
但我们的前端项目已经操作可以的,所以不用去解决,具体位置在如下:
在对应的utils里面的axios.js可以发现(我们导入的也是这个,他进行了再次导入):
axios.defaults.withCredentials = true;  
//默认为false,跨域不会携带cookie的
//所以在以前的博客里说过,跨域其实也是设置的(好像是58章博客里面的跨域问题),所以服务器只是解决跨域访问
//当我们设置后,对应的操作可以传递cookie,当服务器看到有cookie自然就会给出cookie,否则是不会给出的
//所以对应的数据提交以及接收就需要对应的前端来设置
//有一个这样的代码,就表示跨域时,携带cookie(默认情况下,一般就算你解决跨域通常前端也不会携带cookie,所以需要手动,即这里需要为true)
且要注意:若出现了不对劲的地方,可以重启项目,使得刷新
因为当项目够大时,对应的部署可能会有缺陷,但通常不会
一般的,当后台相应给我们对应的token时,我们每次的请求都基本是需要访问一次的
但这样的每次访问是进行操作的,看如下:
导航守卫:
在执行路由之前先执行的一些钩子函数,比如验证用户是否有权限之类的操作,就需要使用
authorize.js 中配置了导航守卫,来对用户的登录进行限制
在plugins里面找到authorize.js:
对应的js
/**
 * Check login state
 * Some middleware to help us ensure the user is authenticated.
 */

import router from "../router";
import store from "../store";

export default Vue => {
  //导航守卫 to要访问的url, from从哪个路径跳转过来, next() 放行
  // Authorize (Make sure that is the first hook.)
  router.beforeHooks.unshift((to, from, next) => {
    // don't need authorize
    if (!to.meta.requireAuth) return next();
    // check login state
    store.dispatch("checkToken").then(valid => {
      // authorized
      if (valid) {
        store.dispatch('getUserPermissions').then(res => {
          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 } });
    });
  });

  // login page visiable
  router.beforeEach((to, from, next) => {
    if (to.name !== "Login") return next();
    // check login state
    store.dispatch("checkToken").then(valid => {
      if (!valid) return next();
      // when logged in
      console.log("Authorized");
      next({ path: to.query.redirect || "/" });
    });
  });
};

这些只是对应的基础格式,了解即可
分配角色:
我们再次回到用户模块的Users.vue组件,现在我们来操作分配角色:
对应的html:
<el-button size="mini" type="text" @click="handleSelectRole(scope.$index, scope.row)">
    分配角色</el-button
对应的handleSelectRole方法(回显窗口):
//分配角色
    handleSelectRole(index, row) {
      this.allocAdminId = row.id;
      this.getRoleList();  //获取角色列表
      this.getUserRoleById(row.id);  //回显对应角色
      this.allocDialogVisible = true;
    },
获取角色列表:
 //获取角色列表
    getRoleList(){
      axios.post("/role/findAllRole",{}).then(res => {
this.allRoleList = res.data.content;
console.log(this.allRoleList)
      }).catch(err => {
this.$message.error("获取角色列表失败");
  
      })
    },
获取用户的角色:
//回显对应角色
    getUserRoleById(id){
      
axios.get("/user/findUserRelationRoleById?id=" + id).then(res => {

  const all = res.data.content;
  console.log(all)

  this.allocRoleIds =[];

  if(all != null && all.length >0){
    for(let i=0;i<all.length;i++){
      this.allocRoleIds.push(all[i].id); //在控件里加了multiple,使得操作数组数据,即默认多条数据
    }
  }
  console.log(this.allocRoleIds)
  console.log(this.allRoleList)


}).catch(err => {
this.$message.error("回显对应角色失败");
  
})
    },
最后分配角色:
注意:虽然js的key可以不写分号,但对与json来说是要加上的
而一些插件也会默认给对应的key加上分号,所以axios对应的参数的key可以不加,因为最后帮忙加上了
当然了若有分号自然不会加的,这是判断操作,分号是包括’'(单引号)和""(双引号)
在没有特殊的操作下,他们的最终结果是一样的
若有特殊情况:如java的’'(单引号)和""(双引号),他们就是不同的,但对于js来说他们可以看成是一个结果
但也要注意:字符串只是对数标识,实际上他只是给我们显示看的,最后都是二进制,但正是由于这个标识使得二进制不同
但可以显示相同,如java里面字符串"1"和整数1的显示一般是相同的,乱码除外
 //确认分配角色
    handleAllocDialogConfirm() {
      const params = {
       	userId:this.allocAdminId, 
	      roleIdList: this.allocRoleIds
      };
      axios.post("/user/userContextRole", params).then(res => {
       
        
        this.loadUsers();
        this.allocDialogVisible = false;
      }).catch(err => {
        this.$message.error("分配角色失败");
      });
    },
  
当然,可能角色这块前端会有问题,但是并不需要理会,只需要知道角色的对应显示通常由前端处理显示的(对我们java程序员来说,有时间可以了解一下)
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值