SpringBoot在线教育项目(七)

一、课程发布表单-步骤导航

一、需求

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

二、配置路由

1、添加路由

// 课程管理
{
  path: '/edu/course',
  component: Layout,
  redirect: '/edu/course/list',
  name: 'Course',
  meta: { title: '课程管理', icon: 'form' },
  children: [
    {
      path: 'list',
      name: 'EduCourseList',
      component: () => import('@/views/edu/course/list'),
      meta: { title: '课程列表' }
    },
    {
      path: 'info',
      name: 'EduCourseInfo',
      component: () => import('@/views/edu/course/info'),
      meta: { title: '发布课程' }
    },
    {
      path: 'info/:id',
      name: 'EduCourseInfoEdit',
      component: () => import('@/views/edu/course/info'),
      meta: { title: '编辑课程基本信息', noCache: true },
      hidden: true
    },
    {
      path: 'chapter/:id',
      name: 'EduCourseChapterEdit',
      component: () => import('@/views/edu/course/chapter'),
      meta: { title: '编辑课程大纲', noCache: true },
      hidden: true
    },
    {
      path: 'publish/:id',
      name: 'EduCoursePublishEdit',
      component: () => import('@/views/edu/course/publish'),
      meta: { title: '发布课程', noCache: true },
      hidden: true
    }
  ]
},

2、添加vue组件
在这里插入图片描述

三、整合步骤条组件

参考 http://element-cn.eleme.io/#/zh-CN/component/steps
1、课程信息页面
info.vue

<template>

  <div class="app-container">

    <h2 style="text-align: center;">发布新课程</h2>

    <el-steps :active="1" process-status="wait" align-center style="margin-bottom: 40px;">
      <el-step title="填写课程基本信息"/>
      <el-step title="创建课程大纲"/>
      <el-step title="提交审核"/>
    </el-steps>

    <el-form label-width="120px">

      <el-form-item>
        <el-button :disabled="saveBtnDisabled" type="primary" @click="next">保存并下一步</el-button>
      </el-form-item>
    </el-form>
  </div>
</template>
<script>
export default {
  data() {
    return {
      saveBtnDisabled: false // 保存按钮是否禁用
    }
  },

  created() {
    console.log('info created')
  },

  methods: {

    next() {
      console.log('next')
      this.$router.push({ path: '/edu/course/chapter/1' })
    }
  }
}
</script>

2、课程大纲页面
chapter.vue

<template>

  <div class="app-container">

    <h2 style="text-align: center;">发布新课程</h2>

    <el-steps :active="2" process-status="wait" align-center style="margin-bottom: 40px;">
      <el-step title="填写课程基本信息"/>
      <el-step title="创建课程大纲"/>
      <el-step title="提交审核"/>
    </el-steps>

    <el-form label-width="120px">

      <el-form-item>
        <el-button @click="previous">上一步</el-button>
        <el-button :disabled="saveBtnDisabled" type="primary" @click="next">下一步</el-button>
      </el-form-item>
    </el-form>
  </div>
</template>

<script>

export default {
  data() {
    return {
      saveBtnDisabled: false // 保存按钮是否禁用
    }
  },

  created() {
    console.log('chapter created')
  },

  methods: {
    previous() {
      console.log('previous')
      this.$router.push({ path: '/edu/course/info/1' })
    },

    next() {
      console.log('next')
      this.$router.push({ path: '/edu/course/publish/1' })
    }
  }
}
</script>

3、课程发布页面
publish.vue

<template>

  <div class="app-container">

    <h2 style="text-align: center;">发布新课程</h2>

    <el-steps :active="3" process-status="wait" align-center style="margin-bottom: 40px;">
      <el-step title="填写课程基本信息"/>
      <el-step title="创建课程大纲"/>
      <el-step title="提交审核"/>
    </el-steps>

    <el-form label-width="120px">

      <el-form-item>
        <el-button @click="previous">返回修改</el-button>
        <el-button :disabled="saveBtnDisabled" type="primary" @click="publish">发布课程</el-button>
      </el-form-item>
    </el-form>
  </div>
</template>

<script>

export default {
  data() {
    return {
      saveBtnDisabled: false // 保存按钮是否禁用
    }
  },

  created() {
    console.log('publish created')
  },

  methods: {
    previous() {
      console.log('previous')
      this.$router.push({ path: '/edu/course/chapter/1' })
    },

    publish() {
      console.log('publish')
      this.$router.push({ path: '/edu/course/list' })
    }
  }
}
</script>

二、编辑课程基本信息

一、后台api

1、定义form表单对象
CourseInfoForm.java

package com.guli.edu.form;

@ApiModel(value = "课程基本信息", description = "编辑课程基本信息的表单对象")
@Data
public class CourseInfoForm implements Serializable {

	private static final long serialVersionUID = 1L;

	@ApiModelProperty(value = "课程ID")
	private String id;

	@ApiModelProperty(value = "课程讲师ID")
	private String teacherId;

	@ApiModelProperty(value = "课程专业ID")
	private String subjectId;

	@ApiModelProperty(value = "课程标题")
	private String title;

	@ApiModelProperty(value = "课程销售价格,设置为0则可免费观看")
	private BigDecimal price;

	@ApiModelProperty(value = "总课时")
	private Integer lessonNum;

	@ApiModelProperty(value = "课程封面图片路径")
	private String cover;

	@ApiModelProperty(value = "课程简介")
	private String description;
}

2、修改CourseDescription主键生成策略

@ApiModelProperty(value = "课程ID")
@TableId(value = "id", type = IdType.INPUT)
private String id;

3、定义常量
实体类Course.Java中定义

public static final String COURSE_DRAFT = "Draft";
public static final String COURSE_NORMAL = "Normal";

4、定义控制层接口
CourseAdminController.java

package com.guli.edu.controller.admin;
​
@Api(description="课程管理")
@CrossOrigin //跨域
@RestController
@RequestMapping("/admin/edu/course")
public class CourseAdminController {
​
    @Autowired
    private CourseService courseService;
​
    @ApiOperation(value = "新增课程")
    @PostMapping("save-course-info")
    public R saveCourseInfo(
            @ApiParam(name = "CourseInfoForm", value = "课程基本信息", required = true)
            @RequestBody CourseInfoForm courseInfoForm){
​
        String courseId = courseService.saveCourseInfo(courseInfoForm);
        if(!StringUtils.isEmpty(courseId)){
            return R.ok().data("courseId", courseId);
        }else{
            return R.error().message("保存失败");
        }
    }
}

5、定义业务层方法
接口:CourseService.java

/**
     * 保存课程和课程详情信息
     * @param courseInfoForm
     * @return 新生成的课程id
     */
String saveCourseInfo(CourseInfoForm courseInfoForm);

实现:CourseServiceImpl.java

@Autowired
private CourseDescriptionService courseDescriptionService;
​
@Override
public String saveCourseInfo(CourseInfoForm courseInfoForm) {
​
    //保存课程基本信息
    Course course = new Course();
    course.setStatus(Course.COURSE_DRAFT);
    BeanUtils.copyProperties(courseInfoForm, course);
    boolean resultCourseInfo = this.save(course);
    if(!resultCourseInfo){
        throw new GuliException(20001, "课程信息保存失败");
    }
​
    //保存课程详情信息
    CourseDescription courseDescription = new CourseDescription();
    courseDescription.setDescription(courseInfoForm.getDescription());
    courseDescription.setId(course.getId());
    boolean resultDescription = courseDescriptionService.save(courseDescription);
    if(!resultDescription){
        throw new GuliException(20001, "课程详情信息保存失败");
    }
​
    return course.getId();
}

6、Swagger测试

二、前端实现

1、定义api

import request from '@/utils/request'
​
const api_name = '/admin/edu/course'
​
export default {
  saveCourseInfo(courseInfo) {
    return request({
      url: `${api_name}/save-course-info`,
      method: 'post',
      data: courseInfo
    })
  }
}

2、组件模板

<el-form label-width="120px">
​
  <el-form-item label="课程标题">
    <el-input v-model="courseInfo.title" placeholder=" 示例:机器学习项目课:从基础到搭建项目视频课程。专业名称注意大小写"/>
  </el-form-item>
​
  <!-- 所属分类 TODO -->
​
  <!-- 课程讲师 TODO -->
​
  <el-form-item label="总课时">
    <el-input-number :min="0" v-model="courseInfo.lessonNum" controls-position="right" placeholder="请填写课程的总课时数"/>
  </el-form-item>
​
  <!-- 课程简介 TODO -->
​
  <!-- 课程封面 TODO -->
​
  <el-form-item label="课程价格">
    <el-input-number :min="0" v-model="courseInfo.price" controls-position="right" placeholder="免费课程请设置为0元"/> 元
  </el-form-item>
​
  <el-form-item>
    <el-button :disabled="saveBtnDisabled" type="primary" @click="next">保存并下一步</el-button>
  </el-form-item>
</el-form>

3、组件js

<script>
import course from '@/api/edu/course'
​
const defaultForm = {
  title: '',
  subjectId: '',
  teacherId: '',
  lessonNum: 0,
  description: '',
  cover: '',
  price: 0
}
​
export default {
  data() {
    return {
      courseInfo: defaultForm,
      saveBtnDisabled: false // 保存按钮是否禁用
    }
  },
​
  watch: {
    $route(to, from) {
      console.log('watch $route')
      this.init()
    }
  },
​
  created() {
    console.log('info created')
    this.init()
  },
​
  methods: {
​
    init() {
      if (this.$route.params && this.$route.params.id) {
        const id = this.$route.params.id
        console.log(id)
      } else {
        this.courseInfo = { ...defaultForm }
      }
    },
​
    next() {
      console.log('next')
      this.saveBtnDisabled = true
      if (!this.courseInfo.id) {
        this.saveData()
      } else {
        this.updateData()
      }
    },
​
    // 保存
    saveData() {
      course.saveCourseInfo(this.courseInfo).then(response => {
        this.$message({
          type: 'success',
          message: '保存成功!'
        })
        return response// 将响应结果传递给then
      }).then(response => {
        this.$router.push({ path: '/edu/course/chapter/' + response.data.courseId })
      }).catch((response) => {
        this.$message({
          type: 'error',
          message: response.message
        })
      })
    },
​
    updateData() {
      this.$router.push({ path: '/edu/course/chapter/1' })
    }
  }
}
</script>

三、课程分类多级联动的实现

一、需求

在这里插入图片描述

二、获取一级分类

1、组件数据定义
定义在data中

subjectNestedList: [],//一级分类列表
subSubjectList: []//二级分类列表

2、组件模板

<!-- 所属分类:级联下拉列表 -->
<!-- 一级分类 -->
<el-form-item label="课程类别">
  <el-select
    v-model="courseInfo.subjectParentId"
    placeholder="请选择">
    <el-option
      v-for="subject in subjectNestedList"
      :key="subject.id"
      :label="subject.title"
      :value="subject.id"/>
  </el-select>
</el-form-item>

3、组件脚本
表单初始化时获取一级分类嵌套列表,引入subject api

import subject from '@/api/edu/subject'

定义方法

init() {
  ......
  // 初始化分类列表
  this.initSubjectList()
},
​
initSubjectList() {
  subject.getNestedTreeList().then(response => {
    this.subjectNestedList = response.data.items
  })
},

三、级联显示二级分类

1、组件模板

<!-- 二级分类 -->
<el-select v-model="courseInfo.subjectId" placeholder="请选择">
  <el-option
    v-for="subject in subSubjectList"
    :key="subject.value"
    :label="subject.title"
    :value="subject.id"/>
</el-select>

2、注册change事件
在一级分类的组件中注册change事件

 <el-select @change="subjectLevelOneChanged" ......

3、定义change事件方法

subjectLevelOneChanged(value) {
    console.log(value)
    for (let i = 0; i < this.subjectNestedList.length; i++) {
        if (this.subjectNestedList[i].id === value) {
            this.subSubjectList = this.subjectNestedList[i].children
            this.courseInfo.subjectId = ''
        }
    }
},

四、讲师下拉列表

一、前端实现

1、组件模板

<!-- 课程讲师 -->
<el-form-item label="课程讲师">
  <el-select
    v-model="courseInfo.teacherId"
    placeholder="请选择">
    <el-option
      v-for="teacher in teacherList"
      :key="teacher.id"
      :label="teacher.name"
      :value="teacher.id"/>
  </el-select>
</el-form-item>

2、定义api
api/edu/teacher.js

getList() {
    return request({
        url: api_name,
        method: 'get'
    })
},

组件中引入teacher api

import teacher from '@/api/edu/teacher'

3、组件脚本
定义data

teacherList: [] // 讲师列表

表单初始化时获取讲师列表

init() {
  ......
  // 获取讲师列表
  this.initTeacherList()
},
​
initTeacherList() {
  teacher.getList().then(response => {
    this.teacherList = response.data.items
  })
},

五、富文本编辑器Tinymce

一、Tinymce可视化编辑器

参考
https://panjiachen.gitee.io/vue-element-admin/#/components/tinymce
https://panjiachen.gitee.io/vue-element-admin/#/example/create

二、组件初始化

Tinymce是一个传统javascript插件,默认不能用于Vue.js因此需要做一些特殊的整合步骤
1、复制脚本库
将脚本库复制到项目的static目录下(在vue-element-admin-master的static路径下)
2、配置html变量
在 guli-admin/build/webpack.dev.conf.js 中添加配置
使在html页面中可是使用这里定义的BASE_URL变量

new HtmlWebpackPlugin({
    ......,
    templateParameters: {
        BASE_URL: config.dev.assetsPublicPath + config.dev.assetsSubDirectory
    }
})

3、引入js脚本
在guli-admin/index.html 中引入js脚本

<script src=<%= BASE_URL %>/tinymce4.7.5/tinymce.min.js></script>
<script src=<%= BASE_URL %>/tinymce4.7.5/langs/zh_CN.js></script>

三、组件引入

为了让Tinymce能用于Vue.js项目,vue-element-admin-master对Tinymce进行了封装,下面我们将它引入到我们的课程信息页面
1、复制组件
src/components/Tinymce
2、引入组件
课程信息组件中引入 Tinymce

import Tinymce from '@/components/Tinymce'
​
export default {
  components: { Tinymce },
  ......
}

3、组件模板

<!-- 课程简介-->
<el-form-item label="课程简介">
    <tinymce :height="300" v-model="courseInfo.description"/>
</el-form-item>

4、组件样式
在info.vue文件的最后添加如下代码,调整上传图片按钮的高度

<style scoped>
.tinymce-container {
  line-height: 29px;
}
</style>

5、图片的base64编码
Tinymce中的图片上传功能直接存储的是图片的base64编码,因此无需图片服务器

六、课程封面

一、整合上传组件

参考 http://element-cn.eleme.io/#/zh-CN/component/upload 用户头像上传
1、上传默认封面
创建文件夹cover,上传默认的课程封面
在这里插入图片描述

2、定义默认封面

const defaultForm = {
  ......,
  cover: process.env.OSS_PATH + '/cover/default.gif',
  ......
}

3、定义data数据

BASE_API: process.env.BASE_API // 接口API地址

4、组件模板
在info.vue中添加上传组件模板

<!-- 课程封面-->
<el-form-item label="课程封面">
​
  <el-upload
    :show-file-list="false"
    :on-success="handleAvatarSuccess"
    :before-upload="beforeAvatarUpload"
    :action="BASE_API+'/admin/oss/file/upload?host=cover'"
    class="avatar-uploader">
    <img :src="courseInfo.cover">
  </el-upload>
​
</el-form-item>

5、结果回调

handleAvatarSuccess(res, file) {
  console.log(res)// 上传响应
  console.log(URL.createObjectURL(file.raw))// base64编码
  this.courseInfo.cover = res.data.url
},
​
beforeAvatarUpload(file) {
  const isJPG = file.type === 'image/jpeg'
  const isLt2M = file.size / 1024 / 1024 < 2
​
  if (!isJPG) {
    this.$message.error('上传头像图片只能是 JPG 格式!')
  }
  if (!isLt2M) {
    this.$message.error('上传头像图片大小不能超过 2MB!')
  }
  return isJPG && isLt2M
}

二、修改后端api

1、修改上传controller
添加host可选参数

/**
     * 文件上传
     *
     * @param file
     */
@ApiOperation(value = "文件上传")
@PostMapping("upload")
public R upload(
    @ApiParam(name = "file", value = "文件", required = true)
    @RequestParam("file") MultipartFile file,
​
    @ApiParam(name = "host", value = "文件上传路径", required = false)) {
​
    String uploadUrl = fileService.upload(file);
    //返回r对象
    return R.ok().message("文件上传成功").data("url", uploadUrl);
​
}

2、综合测试

七、课程信息回显

一、后端实现

1、业务层
接口:CourseService.java

CourseInfoForm getCourseInfoFormById(String id);

实现:CourseServiceImpl.java

@Override
public CourseInfoForm getCourseInfoFormById(String id) {
​
    Course course = this.getById(id);
    if(course == null){
        throw new GuliException(20001, "数据不存在");
    }
    CourseInfoForm courseInfoForm = new CourseInfoForm();
    BeanUtils.copyProperties(course, courseInfoForm);
​
    CourseDescription courseDescription = courseDescriptionService.getById(id);
    if(course != null){
        courseInfoForm.setDescription(courseDescription.getDescription());
    }
​
    return courseInfoForm;
}

2、web层

@ApiOperation(value = "根据ID查询课程")
@GetMapping("course-info/{id}")
public R getById(
    @ApiParam(name = "id", value = "课程ID", required = true)
    @PathVariable String id){
​
    CourseInfoForm courseInfoForm = courseService.getCourseInfoFormById(id);
    return R.ok().data("item", courseInfoForm);
}

3、Swagger中测试

二、前端实现

1、定义api
api/edu/course.js

getCourseInfoById(id) {
    return request({
        url: `${api_name}/course-info/${id}`,
        method: 'get'
    })
}

2、组件js

init() {
    if (this.$route.params && this.$route.params.id) {
        const id = this.$route.params.id
        //根据id获取课程基本信息
        this.fetchCourseInfoById(id)
    } 
    
    ......
},
    
fetchCourseInfoById(id) {
  course.getCourseInfoById(id).then(response => {
    this.courseInfo = response.data.item
  }).catch((response) => {
    this.$message({
      type: 'error',
      message: response.message
    })
  })
},

三、解决级联下拉菜单回显问题

1、数据库中增加冗余列

subject_parent_id 课程专业父级ID

2、pojo中增加属性
entity.Course.java
form.CourseInfo.java

@ApiModelProperty(value = "课程专业父级ID")
private String subjectParentId;

3、vue组件中绑定数据
edu/course/infoinfo.vue

<el-select v-model="courseInfo.subjectParentId" ......

4、修改init方法
将 this.initSubjectList() 和 this.initTeacherList()移至else

init() {
  if (this.$route.params && this.$route.params.id) {
    const id = this.$route.params.id
    // 根据id获取课程基本信息
    this.fetchCourseInfoById(id)
  } else {
    this.courseInfo = { ...defaultForm }
    // 初始化分类列表
    this.initSubjectList()
    // 获取讲师列表
    this.initTeacherList()
  }
},

5、修改fetchCourseInfoById方法

fetchCourseInfoById(id) {
  course.getCourseInfoById(id).then(responseCourse => {
    this.courseInfo = responseCourse.data.item
    // 初始化分类列表
    subject.getNestedTreeList().then(responseSubject => {
      this.subjectNestedList = responseSubject.data.items
      for (let i = 0; i < this.subjectNestedList.length; i++) {
        if (this.subjectNestedList[i].id === this.courseInfo.subjectParentId) {
          this.subSubjectList = this.subjectNestedList[i].children
        }
      }
    })
​
    // 获取讲师列表
    this.initTeacherList()
  }).catch((response) => {
    this.$message({
      type: 'error',
      message: response.message
    })
  })
},

八、更新课程信息

一、后端实现

1、业务层
接口:CourseService.java

void updateCourseInfoById(CourseInfoForm courseInfoForm);

实现:CourseServiceImpl.java

@Override
public void updateCourseInfoById(CourseInfoForm courseInfoForm) {
    //保存课程基本信息
    Course course = new Course();
    BeanUtils.copyProperties(courseInfoForm, course);
    boolean resultCourseInfo = this.updateById(course);
    if(!resultCourseInfo){
        throw new GuliException(20001, "课程信息保存失败");
    }
​
    //保存课程详情信息
    CourseDescription courseDescription = new CourseDescription();
    courseDescription.setDescription(courseInfoForm.getDescription());
    courseDescription.setId(course.getId());
    boolean resultDescription = courseDescriptionService.updateById(courseDescription);
    if(!resultDescription){
        throw new GuliException(20001, "课程详情信息保存失败");
    }
}

2、web层

@ApiOperation(value = "更新课程")
@PutMapping("update-course-info/{id}")
public R updateCourseInfoById(
    @ApiParam(name = "CourseInfoForm", value = "课程基本信息", required = true)
    @RequestBody CourseInfoForm courseInfoForm,
​
    @ApiParam(name = "id", value = "课程ID", required = true)
    @PathVariable String id){
​
    courseService.updateCourseInfoById(courseInfoForm);
    return R.ok();
}

二、前端实现

1、定义api
course.js

updateCourseInfoById(courseInfo) {
  return request({
    url: `${api_name}/update-course-info/${courseInfo.id}`,
    method: 'put',
    data: courseInfo
  })
}

2、组件js
info.vue

updateData() {
    this.saveBtnDisabled = true
    course.updateCourseInfoById(this.courseInfo).then(response => {
        this.$message({
            type: 'success',
            message: '修改成功!'
        })
        return response// 将响应结果传递给then
    }).then(response => {
        this.$router.push({ path: '/edu/course/chapter/' + response.data.courseId })
    }).catch((response) => {
        // console.log(response)
        this.$message({
            type: 'error',
            message: '保存失败'
        })
    })
},
1.项目概况 ​ 提到宿舍,现在无论是学校,还是工厂,都是在熟悉不过的了,学生宿舍楼,职工教员工,职工宿舍楼等等,每一栋楼房都有很多的房间,每个宿舍分配了多少个床位,住着什么人,那些床位是空的,那些房间是空的,这些都需要人员去维护,曾经的我们,通过纸张登记,查看房间信息,房间床位信息,房间少的情况下还好,我们可以一个个进行比对,看登记信息去查看房间信息,床位信息,但是房间多,登记信息多的情况下,或者有人员进行变动,退房,换房的情况下,可就麻烦了我们的宿舍管理员了,我们的宿舍管理员,需要慢慢的进行查找,浪费了时间成本不说,还容易出错,新来的学生或员工需要等待很长时间才能进去入住,在加上现在楼层在逐渐的向高层发展,人员手动管理已经不能满足社会的需要,因而,社会需要更好的工具去管理这些枯燥的的宿舍管理工作,从而宿舍管理系统应用而生。 ​ 宿舍管理系统,解决了当前人力成本,时间成本问题,可以快速的定位到房间号,宿舍床位号,通过宿舍管理系统,我们只要根据系统的要求去操作,就可以分分钟的时间解决某个宿舍是否满员,可以方便的把人员和宿舍,床位进行绑定,就像我们的身份号似的,都是唯一的,查询起来很方便,相比较起传统的宿舍纸张登记来说,现代化的宿舍管理系统更快捷,更高效,更适应人们快捷化的生活。 ​ 现在高校的发展和体制不断的进行了完善,所以学校的服务也逐渐有了变化,对于学校宿舍的管理有了新形势的管理模式。学校根据自身的实际情况,对于学生宿舍生活管理的服务进行改善。有些学校因为管理工作的错误,影响了学校里学生的现况,甚至给整个学校造成了负面的影响,而且现在学校管理问题普遍存在。所以现在学校也在建设信息化管理系统,这种管理系统,可以为学校的管理工作和宿舍管理工作带来很好的效果。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值