1. Course Publishing Form - Step Navigation
1.1 Demand Analysis
1.2 Course related table relationship
2. Add basic course information background
2.1 Generate course-related codes
Use the code generator to generate course-related code.
2.2 Define the form form object
When we add basic course information, we need to involve two tables edu_course and edu_course_description. At the same time, we also need to specify the teacher of the course and the classification of the course when adding a class. There are a lot of content added, so we encapsulate the data submitted by the front desk into a CourseInfoFormVo object.
Create the CourseInfoForm class under entity/vo
@ApiModel(value = "Basic course information", description = "Form object for editing course basic information") @Data public class CourseInfoFormVo implements Serializable { private static final long serialVersionUID = 1L; @ApiModelProperty(value = "Course ID") private String id; @ApiModelProperty(value = "Course Instructor ID") private String teacherId; @ApiModelProperty(value = "Course Major ID") private String subjectId; @ApiModelProperty(value = "course title") private String title; @ApiModelProperty(value = "Course sales price, if you set it to 0, you can watch it for free") private BigDecimal price; @ApiModelProperty(value = "Total Class Hours") private Integer lessonNum; @ApiModelProperty(value = "course cover image path") private String cover; @ApiModelProperty(value = "Course Introduction") private String description; }
2.3 Define the control layer EduCourseController
We have already used the code generation tool to generate the code, we only need to write the code in EduCourseController.java.
@Api(description="Course Management") @CrossOrigin //cross domain @RestController @RequestMapping("/eduservice/course") public class EduCourseController { @Autowired private CourseService courseService; @ApiOperation(value = "Add new course") @PostMapping("/addCourseInfo") public R addCourseInfo(@RequestBody CourseInfoFormVo courseInfoFormVo) { String courseId = eduCourseService.saveCourseInfo(courseInfoFormVo); return R.ok().data("courseId",courseId); } }
2.4 Define the business layer method CourseService
Interface: CourseService.java
/** * Save course and course details information * @param courseInfoForm * @return newly generated course id */ void saveCourseInfo(CourseInfoFormVo courseInfoFormVo);
Implementation: CourseServiceImpl.java
/** * <p> * Course service implementation class * </p> * * @author zjl */ @Service public class EduCourseServiceImpl extends ServiceImpl<EduCourseMapper, EduCourse> implements EduCourseService { @Autowired EduCourseDescriptionService eduCourseDescriptionService; // Add course information @Override public void saveCourseInfo(CourseInfoFormVo courseInfoFormVo) { // Add basic course information to the curriculum EduCourse eduCourse = new EduCourse(); BeanUtils.copyProperties(courseInfoFormVo, eduCourse); int insert = this.baseMapper.insert(eduCourse); // Determine whether adding basic course information is successful if (insert==0) { throw new EduException(20001, "Failed to add course basic information!"); } // Add a course profile to the course profile table EduCourseDescription eduCourseDescription = new EduCourseDescription(); eduCourseDescription.setDescription(courseInfoFormVo.getDescription()); eduCourseDescriptionService.save(eduCourseDescription); } }
2.5 Swagger test
View database information
In database analysis, there is a one-to-one relationship between the course schedule and the course profile table, but when adding data, it is found that it is not a one-to-one relationship.
The id in the edu_course table is 1548334513809661953
, the id in the edu_course_description table is1548334513809661954
It is found that the two tables are not related by the two id values. For a one-to-one relationship, the id value in the edu_course_description table should be the id value in the edu_course table.
2.6 Modify CourseServiceImpl
After adding the course information, get the Id of the course, and then set the id of the course to the course description information.
At the same time, modify the id primary key strategy in the EduCourseDescription class. The id in this table does not need to be generated vividly. We need to insert it manually.
3. Add basic course information front-end
3.1 Add course management route
// course management { path: '/course', component: Layout, redirect: '/course/list', name: 'Course Management', meta: { title: 'Course Management', icon: 'form' }, children: [ { path: 'list', name: 'Course List', component: () => import('@/views/edu/course/list'), meta: { title: 'Course List' } }, { path: 'info', name: 'Add Course', component: () => import('@/views/edu/course/info'), meta: { title: 'Add Course' } }, { path: 'info/:id', name: 'Edit course basic information', component: () => import('@/views/edu/course/info'), meta: { title: 'Edit course basic information', noCache: true }, hidden: true }, { path: 'chapter/:id', name: 'Edit Course Syllabus', component: () => import('@/views/edu/course/chapter'), meta: { title: 'Edit Course Syllabus', noCache: true }, hidden: true }, { path: 'publish/:id', name: 'Publish Course', component: () => import('@/views/edu/course/publish'), meta: { title: 'Publish Course', noCache: true }, hidden: true } ] }
3.2 Add vue components
3.3 Integrate the step bar component
Reference http://element-cn.eleme.io/#/zh-CN/component/steps
3.3.1 Course Information Page
Add the following to info.vue:
<template> <div class="app-container"> <h2 style="text-align: center;">Publish a new course</h2> <el-steps :active="1" process-status="wait" align-center style="margin-bottom: 40px;"> <el-step title="Fill in basic course information"/> <el-step title="Create Course Outline"/> <el-step title="Final Release"/> </el-steps> <el-form label-width="120px"> <el-form-item> <el-button :disabled="saveBtnDisabled" type="primary" @click="next">Save and next</el-button> </el-form-item> </el-form> </div> </template> <script> export default { data() { return { saveBtnDisabled: false // Whether the save button is disabled } }, created() { console.log('info created') }, methods: { next() { console.log('next') this.$router.push({ path: '/course/chapter/1' }) } } } </script>
3.3.2 Course Outline Page
Add the following to chapter.vue
<template> <div class="app-container"> <h2 style="text-align: center;">Publish a new course</h2> <el-steps :active="2" process-status="wait" align-center style="margin-bottom: 40px;"> <el-step title="Fill in basic course information"/> <el-step title="Create Course Outline"/> <el-step title="Final Release"/> </el-steps> <el-form label-width="120px"> <el-form-item> <el-button @click="previous">Back</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 // Whether the save button is disabled } }, created() { console.log('chapter created') }, methods: { previous() { console.log('previous') this.$router.push({ path: '/course/info/1' }) }, next() { console.log('next') this.$router.push({ path: '/course/publish/1' }) } } } </script>
3.3.3 Course release page
Add the following to the publish.vue file
<template> <div class="app-container"> <h2 style="text-align: center;">Publish a new course</h2> <el-steps :active="3" process-status="wait" align-center style="margin-bottom: 40px;"> <el-step title="Fill in basic course information"/> <el-step title="Create Course Outline"/> <el-step title="Final Release"/> </el-steps> <el-form label-width="120px"> <el-form-item> <el-button @click="previous">Back to modify</el-button> <el-button :disabled="saveBtnDisabled" type="primary" @click="publish">Publish course</el-button> </el-form-item> </el-form> </div> </template> <script> export default { data() { return { saveBtnDisabled: false // Whether the save button is disabled } }, created() { console.log('publish created') }, methods: { previous() { console.log('previous') this.$router.push({ path: '/course/chapter/1' }) }, publish() { console.log('publish') this.$router.push({ path: '/course/list' }) } } } </script>
3.4 Add course page implementation
3.4.1 Define APIs
Create a course.js file under src/api/edu and add the following content
import request from '@/utils/request' export default { addCourseInfo(courseInfo) { return request({ url: `/eduservice/course/addCourseInfo`, method: 'post', data: courseInfo }) }
3.4.2 Component Templates
Add the following page information in edu/course/info.vue:
<el-form label-width="120px"> <el-form-item label="Course Title"> <el-input v-model="courseInfo.title" placeholder="Example: Machine Learning Project Course: From Basics to Project Construction Video Course. Pay attention to capitalization of professional names"/> </el-form-item> <!-- Category TODO --> <!-- Course Instructor TODO --> <el-form-item label="Total Class Hours"> <el-input-number :min="0" v-model="courseInfo.lessonNum" controls-position="right" placeholder="Please fill in the total class hours of the course"/> </el-form-item> <!-- Course Introduction TODO --> <el-form-item label="Course Introduction"> <el-input v-model="courseInfo.description" placeholder="fill in course description information"/> </el-form-item> <!-- course cover TODO --> <el-form-item label="Course Price"> <el-input-number :min="0" v-model="courseInfo.price" controls-position="right" placeholder="Please set it to 0 yuan for free courses"/> </el-form-item> <el-form-item> <el-button :disabled="saveBtnDisabled" type="primary" @click="next">Save and next</el-button> </el-form-item> </el-form>
3.4.3 Add js
<script> import course from '@/api/edu/course.js' export default { data() { return { saveBtnDisabled: false , // Whether the save button is disabled courseInfo:{ title: '', subjectId: '', teacherId: '', lessonNum: 0, description: '', cover: '', price: 0 } } }, created() { console.log('info created') }, methods: { saveOrUpdate() { console.log('next') course.addCourseInfo(this.courseInfo).then(response =>{ this.$message({ type: 'success', message: 'Save successfully!' }) this.$router.push({ path: '/course/chapter/1' }) }) } } } </script>
3.4.4 After adding, return the course id
After adding course information, the id of the course is still needed when adding sections later, so we need to modify it as follows:
Modify EduCourseController
In the addCourseInfo method in EduCourseController, add the return value
@PostMapping("/addCourseInfo") public R addCourseInfo(@RequestBody CourseInfoFormVo courseInfoFormVo) { String courseId = eduCourseService.saveCourseInfo(courseInfoFormVo); return R.ok().data("courseId",courseId); }
Modify EduCourseService
Add return value String
public interface EduCourseService extends IService<EduCourse> { String saveCourseInfo(CourseInfoFormVo courseInfoFormVo); }
// Add course information @Override public String saveCourseInfo(CourseInfoFormVo courseInfoFormVo) { // Add basic course information to the curriculum EduCourse eduCourse = new EduCourse(); BeanUtils.copyProperties(courseInfoFormVo, eduCourse); int insert = this.baseMapper.insert(eduCourse); // Determine whether adding basic course information is successful if (insert==0) { throw new EduException(20001, "Failed to add course basic information!"); } // Get the id after adding the course String cid = eduCourse.getId(); // Add a course profile to the course profile table EduCourseDescription eduCourseDescription = new EduCourseDescription(); eduCourseDescription.setId(cid); eduCourseDescription.setDescription(courseInfoFormVo.getDescription()); eduCourseDescriptionService.save(eduCourseDescription); return cid; }
modified info.vue
Modify the saveOrUpdate method in edu/course/info.vue
saveOrUpdate() { console.log('next') course.addCourseInfo(this.courseInfo).then(response =>{ this.$message({ type: 'success', message: 'Save successfully!' }) this.$router.push({ path: '/course/chapter/'+response.data.courseId }) }) }
3.5 Lecturer drop-down list
3.5.1 Component Templates
Add the following code to views/edu/course/info.vue
<!-- Course Instructor --> <el-form-item label="Course Instructor"> <el-select v-model="courseInfo.teacherId" placeholder="Please select"> <el-option v-for="teacher in teacherList" :key="teacher.id" :label="teacher.name" :value="teacher.id"/> </el-select> </el-form-item>
3.5.2 Define the interface for obtaining the lecturer list
Just call the findAll interface in EduTeacherController directly. We've all written about it before.
3.5.3 Define APIs
Define query for all lecturers in api/edu/course.js
getListTeacher() { return request({ url: `/eduservice/edu-teacher/findAll`, method: 'get' }) },
3.5.4 Component scripts
Define data, do not define it in courseInfo
teacherList: [] // list of teachers
Get the list of lecturers when the form is initialized
created() { console.log('info created') // Initialize the lecturer list this.getListTeacher() }, methods: { // query all lecturers getListTeacher(){ course.getListTeacher().then(response =>{ this.teacherList = response.data.items }) }, }
3.5.5 Effect display
3.6 Realization of multi-level linkage of course classification
need
3.6.1 Obtain the primary classification
Component Data Definition
Define the first-level classification and second-level classification collection in the data of views/edu/course/info.vue. Finally, add subjectParentId in courseInfo
subjectOneList: [],//List of first class categories subjectTwoList: []//Secondary classification list
component template
Define the first-level classification drop-down box in course/info.vue
<!-- Level 1 classification--> <el-form-item label="Course Category"> <el-select v-model="courseInfo.subjectParentId" placeholder="Please select"> <el-option v-for="subject in subjectOneList" :key="subject.id" :label="subject.title" :value="subject.id"/> </el-select> </el-form-item>
component script
Introduce subject.js in course/info.vue
import subject from '@/api/edu/subject.js'
Define the method to query all first-level classifications
created() { console.log('info created') // Initialize the lecturer list this.getListTeacher() // Initialize the first-level classification list this.getOneSubjectList() }, methods: { // Query all first-level categories getOneSubjectList(){ subject.getSubjectList().then(response =>{ this.subjectOneList = response.data.list }) }, }
3.6.2 Cascade display of secondary categories
component template
<!-- Secondary classification --> <el-select v-model="courseInfo.subjectId" placeholder="secondary classification"> <el-option v-for="subject in subjectTwoList" :key="subject.id" :label="subject.title" :value="subject.id"/> </el-select>
Register change event
Register the change event in the <el-select> component of the first-level classification
<el-select @change="subjectLevelOneChanged" ......
Define the change event method
subjectLevelOneChanged(value) { console.log(value); // Traverse all categories, including primary and secondary categories for (let i = 0; i < this.subjectOneList.length; i++) { if (this.subjectOneList[i].id === value) { this.subjectTwoList = this.subjectOneList[i].children; // Clear the secondary category this.courseInfo.subjectId = ""; } } },
3.7 Course Cover
3.7.1 Integrated upload component
Refer to http://element-cn.eleme.io/#/zh-CN/component/upload User Avatar Upload
3.7.2 Upload default cover
Create a folder cover and upload the default course cover
3.7.3 Define data data
BASE_API: process.env.BASE_API // interface API address
3.7.4 Component Templates
Add upload component template in info.vue
<!-- Course cover --> <el-form-item label="Course Cover"> <el-upload :show-file-list="false" :on-success="handleAvatarSuccess" :before-upload="beforeAvatarUpload" :action="BASE_API+'/eduoss/file/upload'" class="avatar-uploader"> <img :src="courseInfo.cover" width="250px" height="150px" /> </el-upload> </el-form-item>
3.7.5 Define default cover page
cover: "https://zjledu.oss-cn-beijing.aliyuncs.com/cover/%E9%BB%98%E8%AE%A4%E8%AF%BE%E7%A8%8B%E5%B0%81%E9%9D%A2.jpg",
3.7.6 Result callback
handleAvatarSuccess(res, file) { console.log(res)// upload response 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('The uploaded avatar picture can only be in JPG format!') } if (!isLt2M) { this.$message.error('The size of the uploaded avatar picture cannot exceed 2MB!') } return isJPG && isLt2M }
3.7.8 Comprehensive test
4. Rich text editor Tinymce
On the course basic information adding page, integrate a rich text editor
4.1 Component initialization
Tinymce is a traditional javascript plugin, it cannot be used for Vue.js by default, so some special integration steps are required
4.1.1 Copy script library
Copy the components/Tinymce in the data to the components directory in the front-end project
Get the \static\tinymce4.7.5 in the data to the static directory
4.1.2 Configure html variables
Add configuration in /build/webpack.dev.conf.js to use the BASE_URL variable defined here in the html page
new HtmlWebpackPlugin({ ...... templateParameters: { BASE_URL: config.dev.assetsPublicPath + config.dev.assetsSubDirectory } })
Note: After the configuration is complete, it is best to restart the front-end server
4.1.3 Import js script
Introduce js script in /index.html
<script src=<%= BASE_URL %>/tinymce4.7.5/tinymce.min.js></script> <script src=<%= BASE_URL %>/tinymce4.7.5/langs/zh_CN.js></script>
4.2 Using a text editor
In order to allow Tinymce to be used in Vue.js projects, vue-element-admin-master encapsulates Tinymce, and we will introduce it to our course information page below
4.2.1 Importing components
Introduce Tinymce in course information info.vue
import Tinymce from '@/components/Tinymce' export default { components: { Tinymce }, ...... }
4.2.2 Reference component template
<!-- Course Introduction--> <el-form-item label="Course Introduction"> <tinymce :height="300" v-model="courseInfo.description"/> </el-form-item>
4.2.3 Component styles
Add the following code at the end of the info.vue file to adjust the height of the upload image button
<style scoped> .tinymce-container { line-height: 29px; } </style>
4.2.5 base64 encoding of pictures
The image upload function in Tinymce directly stores the base64 encoding of the image, so there is no need for an image server
4.2.6 Testing
There will be no data when the test will add the secondary classification of the course. It is because, in CourseInfoFormVo, there is no subjectParentId field. Just add this field in the CourseInfoFormVo class.
@ApiModelProperty(value = "Level 1 classification ID") private String subjectParentId;