The main functions achieved on day 7 are as follows.
1 Front-end implementation of course classification module
(1) Add Routes
router/index.js.
{ path: '/subject', component: Layout, redirect: '/subject/list', //Accessing'/teacher'on the page is redirected to'/teacher/table' name: 'Classified management of courses', meta: { title: 'Classified management of courses', icon: 'example' }, children: [ { path: 'list', name: 'Classified list of courses', component: () => import('@/views/edu/subject/list'), meta: { title: 'Classified list of courses', icon: 'table' } }, { path: 'save', name: 'Add Course Classification', component: () => import('@/views/edu/subject/save'), meta: { title: 'Add Course Classification', icon: 'tree' } } ] }
The results are as follows.
(2) Implementation
/views/edu/subject/save.vue Writes an upload button component. Note the code comments below to store the template file locally.
<template> <div class="app-container"> <el-form label-width="120px"> <el-form-item label="Information Description"> <el-tag type="info">excel Template description</el-tag> <el-tag> <i class="el-icon-download" /> <!-- Note that the template is placed in the local path for the project --> <a :href="'/static/1.xlsx'">Click to download the template</a> </el-tag> </el-form-item> <el-form-item label="Choice Excel"> <el-upload ref="upload" :auto-upload="false" :on-success="fileUploadSuccess" :on-error="fileUploadError" :disabled="importBtnDisabled" :limit="1" :action="BASE_API + '/eduservice/edu-subject/addSubject'" name="file" accept="application/vnd.ms-excel" > <el-button slot="trigger" size="small" type="primary" >Select File</el-button > <el-button :loading="loading" style="margin-left: 10px" size="small" type="success" @click="submitUpload" >Upload to Server</el-button > </el-upload> </el-form-item> </el-form> </div> </template>
Implement script.
<script> export default { data() { return { BASE_API: process.env.BASE_API, // Interface API Address importBtnDisabled: false, // Whether the button is disabled, loading: false, }; }, created() { }, methods:{ // Upload Files submitUpload() { this.importBtnDisabled = true, this.loading = true, this.$refs.upload.submit() }, fileUploadSuccess() { this.loading = false; this.$message({ type: "success", message: "Upload Successful", }); }, fileUploadError() { this.loading = false; this.$message({ type: "error", message: "Upload failed", }); } } } </script>
Under test, empty the data in the database.
DELETE FROM `edu_subject`
Upload.
The results are as follows.
2 Course List Module
2.1 Front End Static Pages
Copy tree/index.vue directly to list.vue.
<template> <div class="app-container"> <el-input v-model="filterText" placeholder="Filter keyword" style="margin-bottom:30px;" /> <el-tree ref="tree2" :data="data2" :props="defaultProps" :filter-node-method="filterNode" class="filter-tree" default-expand-all /> </div> </template> <script> export default { data() { return { filterText: '', data2: [{ id: 1, label: 'Level one 1', children: [{ id: 4, label: 'Level two 1-1', children: [{ id: 9, label: 'Level three 1-1-1' }, { id: 10, label: 'Level three 1-1-2' }] }] }, { id: 2, label: 'Level one 2', children: [{ id: 5, label: 'Level two 2-1' }, { id: 6, label: 'Level two 2-2' }] }, { id: 3, label: 'Level one 3', children: [{ id: 7, label: 'Level two 3-1' }, { id: 8, label: 'Level two 3-2' }] }], defaultProps: { children: 'children', label: 'label' } } }, watch: { filterText(val) { this.$refs.tree2.filter(val) } }, methods: { filterNode(value, data) { if (!value) return true return data.label.indexOf(value) !== -1 } } } </script>
The results are as follows.
What we need to do is implement the back-end interface to bring the data back into the format required by the front-end.
2.2 Backend Implementation
Create entity classes.
@Data public class OneSubject { private String id; private String title; private List<TwoSubject> children = new ArrayList<>(); }
@Data public class TwoSubject { private String id; private String title; }
controller.
public R getAllSubject() { // The first-level classification already contains the second-level classification List<OneSubject> list = eduSubjectService.getAllSubject(); return R.ok(); }
serviceImpl.
@Override public List<OneSubject> getAllSubject() { // 1. Query Level 1 Classification QueryWrapper<EduSubject> wrapperOne = new QueryWrapper<>(); wrapperOne.eq("parentid", "0"); List<EduSubject> firstList = baseMapper.selectList(wrapperOne); // 2. Query secondary classification QueryWrapper<EduSubject> wrapperTwo = new QueryWrapper<>(); wrapperTwo.ne("parentid", "0"); List<EduSubject> secondList = baseMapper.selectList(wrapperTwo); List<OneSubject> finalSubject = new ArrayList(); // 3. Encapsulation Classification Level 1 // 4. Packaging Secondary Classification return null; }
The code above does not yet encapsulate the data (encapsulating the type Edusubject corresponding to the database in the type Onesubject required by the front end). How to encapsulate? The first method is to traverse to get the desired value.
// The final data type returned, the data type stored in the generic is OneSubject, which is consistent with the structure required by the list presented at the front end List<OneSubject> finalSubject = new ArrayList(); // 3. Encapsulation Classification Level 1 // We need to encapsulate the firstList in the finalSubject. // Method 1: Traverse values, transfer values for (int i = 0; i < firstList.size(); i++) { EduSubject eduSubject = firstList.get(i); OneSubject oneSubject = new OneSubject(); oneSubject.setId(eduSubject.getId()); oneSubject.setTitle(eduSubject.getTitle()); finalSubject.add(oneSubject); }
But if you need more than id, title, but a lot of attributes, isn't that a hassle?
//Method 2: BeanUtils for (int i = 0; i < firstList.size(); i++) { EduSubject eduSubject = firstList.get(i); OneSubject oneSubject = new OneSubject(); BeanUtils.copyProperties(eduSubject, oneSubject); finalSubject.add(oneSubject); } return finalSubject;
Add a comment to the controller to return the data.
@GetMapping("/getAllSubject") public R getAllSubject() { // The first-level classification already contains the second-level classification List<OneSubject> list = eduSubjectService.getAllSubject(); return R.ok().data("list", list); }
The swagger-ui test is as follows.
Encapsulate the secondary classification.
for (int i = 0; i < firstList.size(); i++) { EduSubject eduSubject = firstList.get(i); OneSubject oneSubject = new OneSubject(); BeanUtils.copyProperties(eduSubject, oneSubject); // 4. Packaging Secondary Classification List<TwoSubject> twoFinalSubject = new ArrayList<>(); // Traverse through the second class in the first class to find parent_ Second-level classification with ID corresponding to the current first-level classification for (int j = 0; j < secondList.size(); j++) { EduSubject tSubject = secondList.get(j); if(tSubject.getId().equals(eduSubject.getId())) { TwoSubject twoSubject = new TwoSubject(); BeanUtils.copyProperties(tSubject, twoSubject); twoFinalSubject.add(twoSubject); } } oneSubject.setChildren(twoFinalSubject); finalSubject.add(oneSubject); }
The test results are as follows.
2.3 Front End Getting Back End Data
src/api/edu/subject.js implements the front-end interface.
import request from '@/utils/request' export default { // Course Classification: Query all course classifications getAllSubject() { return request({ url: `/eduservice/edu-subject/getAllSubject`, method: 'get' }) } }
edu/subject/list.vue.
<script> import subject from "@/api/edu/subject.js" export default { data() { return { filterText: '', data2: [], defaultProps: { children: "children", label: "title", // The title here matches the property name of the backend }, } }, watch: { filterText(val) { this.$refs.tree2.filter(val) } }, created() { this.getAllSubject() }, methods: { getAllSubject() { subject.getAllSubject() .then(Response => { this.data2 = Response.data.list }) }, // Search for Course Classification filterNode(value, data) { if (!value) return true; return data.title.indexOf(value) !== -1; }, } } </script>
The effect is shown below.
The above retrieval function is available, but it is case sensitive. To improve the user experience, we continue to refine the following retrieval method: no matter what value the user enters, we convert to lower case for comparison.
// Search for Course Classification filterNode(value, data) { if (!value) return true; return data.title.toLowerCase().indexOf(value) !== -1; }
Finally, go back and perfect save.vue to automatically jump to the course list after the course classification is added successfully.
3 Course additions
3.1 Demand Analysis


3.2 Database Design
The data table building process follows.
CREATE TABLE `edu_course_description` ( `id` char(19) NOT NULL COMMENT 'curriculum ID', `description` text COMMENT 'Course introduction', `gmt_create` datetime NOT NULL COMMENT 'Creation Time', `gmt_modified` datetime NOT NULL COMMENT 'Update Time', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='Course introduction'; # # Data for table "edu_course_description" # INSERT INTO `edu_course_description` VALUES ('1104870479077879809','<p>11</p>','2019-03-11 06:23:44','2019-03-11 06:23:44'),('1192252213659774977','<p>test</p>','2019-11-07 09:27:33','2019-11-13 16:21:28'),('14','','2019-03-13 06:04:43','2019-03-13 06:05:33'),('15','','2019-03-13 06:03:33','2019-03-13 06:04:22'),('18','<p>This set Java Video is completely targeted at zero basic students, class recordings, since the release, good reviews have surged! Java The video focuses on interaction with students, teaches humor, details and coverage Java Foundation All Core Knowledge Points, Similar Java Video is also code-heavy, case-rich and practical. Meanwhile, Ben Java Video tutorials focus on the analysis of technical principles, in-depth JDK Source code, supplemented by code practice throughout, with practice-driven theory, supplemented by necessary code exercises.</p>\n<p>------------------------------------</p>\n<p>Video features:</p>\n<p>Through learning books Java Video tutorials, you can really Java Basic knowledge for practical use, learning for active use, structure Java Programming ideas, firmly grasp\"Source Level\"Of Javase Core technology, and for follow-up JavaWeb And other technologies to lay a solid foundation for learning.<br /><br />1.Easy to understand, detailed: each knowledge point is tall, superficial, concise and clear to explain the problem<br />2.Practical: Real code battle throughout, covering hundreds of enterprise application cases and exercises<br />3.In-depth: Source analysis, more Java Reflection, actual application of dynamic agents, etc.<br />4.Sign in to Silicon Valley's official website for free online answers from technical instructors</p>','2019-03-06 18:06:36','2019-10-30 19:58:36');
CREATE TABLE `edu_video` ( `id` char(19) NOT NULL COMMENT 'video ID', `course_id` char(19) NOT NULL COMMENT 'curriculum ID', `chapter_id` char(19) NOT NULL COMMENT 'chapter ID', `title` varchar(50) NOT NULL COMMENT 'Node name', `video_source_id` varchar(100) DEFAULT NULL COMMENT 'Cloud Video Resources', `video_original_name` varchar(100) DEFAULT NULL COMMENT 'Original File Name', `sort` int(10) unsigned NOT NULL DEFAULT '0' COMMENT 'sort field', `play_count` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT 'Play Count', `is_free` tinyint(1) unsigned NOT NULL DEFAULT '0' COMMENT 'Is it OK to listen: 0 fee 1 free', `duration` float NOT NULL DEFAULT '0' COMMENT 'Video duration (seconds)', `status` varchar(20) NOT NULL DEFAULT 'Empty' COMMENT 'Empty Not Uploaded Transcoding In Transcoding Normal normal', `size` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT 'Video Source File Size (Bytes)', `version` bigint(20) unsigned NOT NULL DEFAULT '1' COMMENT 'Optimistic Lock', `gmt_create` datetime NOT NULL COMMENT 'Creation Time', `gmt_modified` datetime NOT NULL COMMENT 'Update Time', PRIMARY KEY (`id`), KEY `idx_course_id` (`course_id`), KEY `idx_chapter_id` (`chapter_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=COMPACT COMMENT='Course Video'; # # Data for table "edu_video" # INSERT INTO `edu_video` VALUES ('1182499307429339137','18','32','Section 1','','',0,0,0,0,'',0,1,'2019-10-11 11:32:59','2019-10-11 11:57:38'),('1185312444399071234','14','1','12','','',0,0,0,0,'Empty',0,1,'2019-10-19 05:51:23','2019-10-19 05:51:33'),('1189434737808990210','18','44','test','','',1,0,0,0,'Empty',0,1,'2019-10-30 14:51:55','2019-10-30 14:51:55'),('1189471423678939138','18','1181729226915577857','test','2b887dc9584d4dc68908780ec57cd3b9','video',1,0,0,0,'Empty',0,1,'2019-10-30 17:17:41','2019-10-30 17:17:41'),('1189476403626409986','18','1181729226915577857','22','5155c73dc112475cbbddccf4723f7cef','video.mp4',0,0,0,0,'Empty',0,1,'2019-10-30 17:37:29','2019-10-30 17:37:29'),('1192252824606289921','1192252213659774977','1192252428399751169','Section A','756cf06db9cb4f30be85a9758b19c645','eae2b847ef8503b81f5d5593d769dde2.mp4',0,0,0,0,'Empty',0,1,'2019-11-07 09:29:59','2019-11-07 09:29:59'),('1192628092797730818','1192252213659774977','1192252428399751169','Section B','2a02d726622f4c7089d44cb993c531e1','eae2b847ef8503b81f5d5593d769dde2.mp4',0,0,1,0,'Empty',0,1,'2019-11-08 10:21:10','2019-11-08 10:21:22'),('1192632495013380097','1192252213659774977','1192252428399751169','The Third Period','4e560c892fdf4fa2b42e0671aa42fa9d','eae2b847ef8503b81f5d5593d769dde2.mp4',0,0,1,0,'Empty',0,1,'2019-11-08 10:38:40','2019-11-08 10:38:40'),('1194117638832111617','1192252213659774977','1192252428399751169','The fourth period','4e560c892fdf4fa2b42e0671aa42fa9d','eae2b847ef8503b81f5d5593d769dde2.mp4',0,0,0,0,'Empty',0,1,'2019-11-12 13:00:05','2019-11-12 13:00:05'),('1196263770832023554','1192252213659774977','1192252428399751169','The fifth period','27d21158b0834cb5a8d50710937de330','eae2b847ef8503b81f5d5593d769dde2.mp4',5,0,0,0,'Empty',0,1,'2019-11-18 11:08:03','2019-11-18 11:08:03'),('17','18','15','Section 1: Java brief introduction','196116a6fee742e1ba9f6c18f65bd8c1','1',1,1000,1,100,'Draft',0,1,'2019-01-01 13:08:57','2019-10-11 11:26:39'),('18','18','15','Section 2: Expressions and assignment statements','2d99b08ca0214909899910c9ba042d47','7 - How Do I Find Time for My ',2,999,1,100,'Draft',0,1,'2019-01-01 13:09:02','2019-03-08 03:30:27'),('19','18','15','Section 3: String class','51120d59ddfd424cb5ab08b44fc8b23a','eae2b847ef8503b81f5d5593d769dde2.mp4',3,888,0,100,'Draft',0,1,'2019-01-01 13:09:05','2019-11-12 12:50:45'),('20','18','15','Section 4: Procedural Style','2a38988892d84df598752226c50f3fa3','00-day10 summary.avi',4,666,0,100,'Draft',0,1,'2019-01-01 13:09:05','2019-10-11 09:20:09');
CREATE TABLE `edu_course` ( `id` char(19) NOT NULL COMMENT 'curriculum ID', `teacher_id` char(19) NOT NULL COMMENT 'Course instructor ID', `subject_id` char(19) NOT NULL COMMENT 'Course Specialty ID', `subject_parent_id` char(19) NOT NULL COMMENT 'Course Specialty Parent ID', `title` varchar(50) NOT NULL COMMENT 'Course Title', `price` decimal(10,2) unsigned NOT NULL DEFAULT '0.00' COMMENT 'Course sales price, set to 0 for free viewing', `lesson_num` int(10) unsigned NOT NULL DEFAULT '0' COMMENT 'Total Hours', `cover` varchar(255) CHARACTER SET utf8 NOT NULL COMMENT 'Course Cover Picture Path', `buy_count` bigint(10) unsigned NOT NULL DEFAULT '0' COMMENT 'Sales volumes', `view_count` bigint(10) unsigned NOT NULL DEFAULT '0' COMMENT 'Number of Browses', `version` bigint(20) unsigned NOT NULL DEFAULT '1' COMMENT 'Optimistic Lock', `status` varchar(10) NOT NULL DEFAULT 'Draft' COMMENT 'Course Status Draft Not published Normal Published', `is_deleted` tinyint(3) DEFAULT NULL COMMENT 'Logically delete 1 ( true)Deleted, 0 ( false)Not deleted', `gmt_create` datetime NOT NULL COMMENT 'Creation Time', `gmt_modified` datetime NOT NULL COMMENT 'Update Time', PRIMARY KEY (`id`), KEY `idx_title` (`title`), KEY `idx_subject_id` (`subject_id`), KEY `idx_teacher_id` (`teacher_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=COMPACT COMMENT='curriculum'; # # Data for table "edu_course" # INSERT INTO `edu_course` VALUES ('1192252213659774977','1189389726308478977','1178214681139539969','1178214681118568449','java Basic courses: test',0.01,2,'https://Guli-file-190513.oss-cn-beijing.aliyuncs.com/cover/default.gif', 4,387,1,'Normal', 0,'2019-11-07 09:27:33','2019-11-18 13:35:03', ('14','1189389726308478977','1101348944971091969','1101348944920760321','XHTML CSS2 JS Site-wide Production Tutorial Course Learning', 0.00,3,' http://guli-file.oss-cn-beijing.aliyuncs.com/cover/2019/03/13/d0086eb0-f2dc-45f7-bba1-744d95e5be0f.jpg ', 3,44,15,'Normal', 0,'2018-04-02 18:33:34','2019-11-16 21:21:45'), ('15','1189389726308478977','1101348944971091969','1101348944920760321','Introduction to HTML5', 0.00,23,'http://guli-file.oss-cn-beijing.aliyuncs.com/cover/2019/03/13/22997b8e-3606-4d2e-9b4f-09f48418b6e4.jpg',0,51,17,'Normal',0,'2018-04-02 18:34:32','2019-11-12 10:19:20'),('18','1189389726308478977','1178214681139539969','1178214681118568449','Java boutique course', 0.01,20,'http://guli-file.oss-cn-beijing.aliyuncs.com/cover/2019/03/06/866e9aca-b530-4f71-a690-72d4a4bfd1e7.jpg',151,737,6,'Normal',0,'2018-04-02 21:28:46','2019-11-18 11:14:52';
CREATE TABLE `edu_chapter` ( `id` char(19) NOT NULL COMMENT 'chapter ID', `course_id` char(19) NOT NULL COMMENT 'curriculum ID', `title` varchar(50) NOT NULL COMMENT 'Chapter Name', `sort` int(10) unsigned NOT NULL DEFAULT '0' COMMENT 'Display Sort', `gmt_create` datetime NOT NULL COMMENT 'Creation Time', `gmt_modified` datetime NOT NULL COMMENT 'Update Time', PRIMARY KEY (`id`), KEY `idx_course_id` (`course_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=COMPACT COMMENT='curriculum'; # # Data for table "edu_chapter" # INSERT INTO `edu_chapter` VALUES ('1','14','Chapter 1: HTML',0,'2019-01-01 12:27:40','2019-01-01 12:55:30'),('1181729226915577857','18','Chapter VII: I/O flow',70,'2019-10-09 08:32:58','2019-10-09 08:33:20'),('1192252428399751169','1192252213659774977','Section 1',0,'2019-11-07 09:28:25','2019-11-07 09:28:25'),('15','18','Chapter 1: Java Introduction',0,'2019-01-01 12:27:40','2019-10-09 09:13:19'),('3','14','Chapter II: CSS',0,'2019-01-01 12:55:35','2019-01-01 12:27:40'),('32','18','Chapter 2: Console input and output',0,'2019-01-01 12:27:40','2019-01-01 12:27:40'),('44','18','Chapter III: Control Flow',0,'2019-01-01 12:27:40','2019-01-01 12:27:40'),('48','18','Chapter IV: Definition of Classes',0,'2019-01-01 12:27:40','2019-01-01 12:27:40'),('63','18','Chapter Five: Arrays',0,'2019-01-01 12:27:40','2019-01-01 12:27:40'),('64','18','Chapter Six: Inheritance',61,'2019-01-01 12:27:40','2019-10-09 08:32:47');
3.3. Backend implementation
(1) Generate mvc template code
Modify tables in codeGenerator.
Generate code.
Since the course introduction does not need to provide interface access separately, but is used in conjunction with the course, we can delete the EduCourseDescriptionController. EduCourseController adds a comment @CrossOrigin.
(2) Functional analysis
The main functions of the course management module and database building have been completed, but the following issues need to be considered before the functions can be implemented.
1) How is the data encapsulated?
Based on previous experience, data added by the front end can be passed to the back end through json data, but what type of data is passed? Delivering EduCourse satisfies most of the transfer of course information, but it is not possible to transfer the course introduction, so we need to create a vo class specifically for front-end and back-end data transfer.
2) How data is added to the database
Maybe one data corresponds to multiple tables, such as course information and course introduction.
3) Secondary Action Problems
When choosing a course classification, use the drop-down list to implement the first-level course classification, and the drop-down box for the second-level course classification should belong to the previously selected course classification.
(3) Implement vo class
@ApiModel(value = "Course Basic Information", description = "Form object for editing course basic information") @Data public class CourseInfoForm implements Serializable { private static final long serialVersionUID = 1L; @ApiModelProperty(value = "curriculum ID") private String id; @ApiModelProperty(value = "Course instructor ID") private String teacherId; @ApiModelProperty(value = "Course Specialty ID") private String subjectId; @ApiModelProperty(value = "Course Title") private String title; @ApiModelProperty(value = "Course sales price, set to 0 for free viewing") private BigDecimal price; @ApiModelProperty(value = "Total Hours") private Integer lessonNum; @ApiModelProperty(value = "Course Cover Picture Path") private String cover; @ApiModelProperty(value = "Course introduction") private String description; }
(4)controller
@RestController @RequestMapping("/eduservice/edu-course") @CrossOrigin public class EduCourseController { @Autowired EduCourseService eduCourseService; @PostMapping("/addCourse") public R addCourse(@RequestBody CourseInfoForm courseInfoForm) { eduCourseService.saveCourse(courseInfoForm); return R.ok(); } }
(5)service
The interface section is omitted and will not be repeated later.
@Service public class EduCourseServiceImpl extends ServiceImpl<EduCourseMapper, EduCourse> implements EduCourseService { @Autowired EduCourseDescriptionService eduCourseDescriptionService; @Override public void saveCourse(CourseInfoForm courseInfoForm) { // 1. Store EduCourse EduCourse eduCourse = new EduCourse(); BeanUtils.copyProperties(courseInfoForm, eduCourse); baseMapper.insert(eduCourse); // 2. Store EduCourseDescription // This is EduCourseServiceImpl, and baseMapper cannot save the data EduCourseDescription directly to the database EduCourseDescription eduCourseDescription = new EduCourseDescription(); eduCourseDescription.setDescription(courseInfoForm.getDescription()); eduCourseDescriptionService.save(eduCourseDescription); } }
Also:
- The time properties of entity classes are annotated with the @TableField annotation, as shown in the EduCourseDescription example, and the same is true with EduCourse.
public class EduCourseDescription implements Serializable { //... @TableField(fill = FieldFill.INSERT) @ApiModelProperty(value = "Creation Time") private Date gmtCreate; @TableField(fill = FieldFill.INSERT_UPDATE) @ApiModelProperty(value = "Update Time") private Date gmtModified; }
- The default value of subjectParentId in database ```EduCourse'is set to null.
The back-end implementation of adding course functionality is complete, and the reader is asked to self-test with swagger.
(6) Establish a one-to-one relationship between course information and details
However, as we mentioned earlier, the two tables of course information EduCourse and course details EduCourseDescription should be one-to-one, and they do not have any relationship at present. How can I do that? Just make their IDs consistent, since they were all automatically generated before, now set the id of the eduCourseDescription manually in the service to be consistent with the eduCourse.
// Manually set the id of the eduCourseDescription to match the eduCourse eduCourseDescription.setId(eduCourse.getId());
Then set the primary key policy to input.
3.4 Front End Implementation
(1) Add routes to router/index.js.
{ path: '/subject', component: Layout, redirect: '/subject/list', //Accessing'/teacher'on the page is redirected to'/teacher/table' name: 'course management', meta: { title: 'course management', icon: 'example' }, children: [ { path: 'list', name: 'Course List', component: () => import('@/views/edu/subject/list'), meta: { title: 'Course List', icon: 'table' } }, { path: 'save', name: 'Add Course', component: () => import('@/views/edu/subject/save'), meta: { title: 'Add Course', icon: 'tree' } } ] }
(2) Create a page
Previous needs analysis mentioned that we need to show three pages during the course.
Let's create a vue file for each of the three pages.
Refer to the official element-ui documentation implementation.
info.vue.
.<template> <div class="app-container"> <h2 style="text-align: center">Publish new courses</h2> <el-steps :active="1" process-status="wait" align-center style="margin- bottom: 40px;" > <el-step title="Fill in course basic information" /> <el-step title="Create a 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 Step</el-button > </el-form-item> </el-form> </div> </template> <script> export default { data() { return { saveBtnDisabled:false, }; }, methods: { next() { //Jump to Step 2 this.$router.push({path:'/course/chapter/1'}) }, }, created(){ } }; </script> <style></style>
chapter.vue.
.<template> <div class="app-container"> <h2 style="text-align: center">Publish new courses</h2> <el-steps :active="2" process-status="wait" align-center style="margin- bottom: 40px;" > <el-step title="Fill in course basic information" /> <el-step title="Create a course outline" /> <el-step title="Final release" /> </el-steps> <el-form label-width="120px"> <el-form-item> <el-button @click="previous">Previous step</el-button> <el-button :disabled="saveBtnDisabled" type="primary" @click="next" >Next step</el-button > </el-form-item> </el-form> </div> </template> <script> export default { data() { return { saveBtnDisabled: false, }; }, methods: { //Jump to the previous step previous() { this.$router.push({ path: "/course/info/1" }); }, next() { //Jump to Step 3 this.$router.push({ path: "/course/publish/1" }); }, }, created() {}, }; </script> <style> </style>
publish.vue.
.<template> <div class="app-container"> <h2 style="text-align: center">Publish new courses</h2> <el-steps :active="3" process-status="wait" align-center style="margin- bottom: 40px;" > <el-step title="Fill in course basic information" /> <el-step title="Create a course outline" /> <el-step title="Final release" /> </el-steps> <el-form label-width="120px"> <el-form-item> <el-button @click="previous">Return to Modification</el-button> <el-button :disabled="saveBtnDisabled" type="primary" @click="publish" >Publish courses</el-button > </el-form-item> </el-form> </div> </template> <script> export default { data() { return { saveBtnDisabled: false, }; }, methods: { //Jump to the previous step previous() { this.$router.push({ path: "/course/chapter/1" }); }, publish(){ this.$router.push({ path: "/course/list" }); } }, }; </script> <style> </style>
The last pubish.vue page saved above jumps to list.vue, so remember to create list.vue in the same directory.
(3) Hide Routes
Modify previous routes and add hidden routes.
{ path: '/course', component: Layout, redirect: '/course/list', name: 'course management', meta: { title: 'course management', icon: 'example' }, children: [ { path: 'list', name: 'Course List', component: () => import('@/views/edu/course/list'), meta: { title: 'Course List', icon: 'table' } }, { path: 'save', name: 'Add Course', component: () => import('@/views/edu/course/info'), meta: { title: 'Add Course', icon: 'tree' } }, // Access the three pages of the added course by hiding routes { path: 'info/:id', name: 'EduCourseInfoEdit', component: () => import('@/views/edu/course/info.vue'), meta: { title: 'Edit course basic information', noCache: true }, hidden: true }, { path: 'chapter/:id', name: 'EduCourseChapterEdit', component: () => import('@/views/edu/course/chapter.vue'), meta: { title: 'Edit the course outline', noCache: true }, hidden: true }, { path: 'publish/:id', name: 'EduCoursePublishEdit', component: () => import('@/views/edu/course/publish.vue'), meta: { title: 'Publish courses', noCache: true }, hidden: true } ] }
Show me the current effect.
(4) Implement adding basic information forms
Perfect ui components as follows (info.vue).
.<template> <div class="app-container"> <h2 style="text-align: center">Publish new courses</h2> <el-steps :active="1" process-status="wait" align-center style="margin- bottom: 40px;" > <el-step title="Fill in course basic information" /> <el-step title="Create a course outline" /> <el-step title="Final release" /> </el-steps> <el-form label-width="120px"> <el-form-item label="Course Title"> <el-input v-model="courseInfo.title" placeholder=" Example: Machine Learning Project Course: From Foundation to Building Project Video Course. Professional Names Case-sensitive" /> </el-form-item> <!-- Subordinate Classification TODO --> <!-- Course instructor TODO --> <el-form-item label="Total Hours"> <el-input-number :min="0" v-model="courseInfo.lessonNum" controls-position="right" placeholder="Please fill in the total number of hours in the course" /> </el-form-item> <!-- Course introduction TODO --> <el-form-item label="Course introduction"> <el-input v-model="courseInfo.description" placeholder="" /> </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="Free course set to 0 yuan" /> element </el-form-item> <el-form-item> <el-button :disabled="saveBtnDisabled" type="primary" @click="saveOrUpdate" >Save and Next Step</el-button > </el-form-item> </el-form> </div> </template> <script> import course from "@/api/edu/course.js"; export default { data() { return { saveBtnDisabled: false, courseInfo: { title: "", subjectId: "", teacherId: "", lessonNum: 0, description: "", cover: "", price: 0, }, }; }, methods: { saveOrUpdate() { course.addCourseInfo(this.courseInfo).then(resp => { this.$message({ message: "Success in adding course information", type: "success", }) //Jump to Step 2 with the id generated by this course this.$router.push({ path: "/course/chapter/"+resp.data.courseId }); }); }, }, created() {}, }; </script> <style> </style>
Create a new course.js.
import request from '@/utils/request' export default { // Add Course Information addCourseInfo(courseInfo) { return request({ url: `/eduservice/edu-course/addCourse`, method: 'post', data: courseInfo }) } }
Under test.
Notice the red part code, why pass the course id?
Remember our need, the second part creates the course outline, and the page of this course outline needs the course id of the first part, so we route this id value to the second part's page.
Where does this id come from? Of course the backend is coming in. Modify the back end.
public class EduCourseController { @Autowired EduCourseService eduCourseService; @PostMapping("/addCourse") public R addCourse(@RequestBody CourseInfoForm courseInfoForm) { String id = eduCourseService.saveCourse(courseInfoForm); return R.ok().data("courseId",id); } }
public interface EduCourseService extends IService<EduCourse> { String saveCourse(CourseInfoForm courseInfoForm); }
@Service public class EduCourseServiceImpl extends ServiceImpl<EduCourseMapper, EduCourse> implements EduCourseService { @Autowired EduCourseDescriptionService eduCourseDescriptionService; @Override public String saveCourse(CourseInfoForm courseInfoForm) { ... return eduCourse.getId(); } }
Under test. Note the route in the path below.
3.5 Perfect Functions
The next step is to further refine the functionality to meet the actual business needs.
(1) Implement the function of selecting drop-down boxes for lecturers and classifications of courses
info.vue implements components.
<!--Course instructor--> <el-form-item label="Course instructor"> <el-select v-model="courseInfo.teacherId" placeholder="Please select"> <el-option v-for="teacher in teacherLists" :key="teacher.id" :label="teacher.name" :value="teacher.id" ></el-option> </el-select> </el-form-item>
.course.js Writes an interface to find instructors.
//Query all instructors getAllTeacher(){ return request({ url:"/eduservice/edu-teacher/findAll", method: 'get' }) }
Call the interface in info.vue.
data() { return { ... teacherLists: [], //Encapsulate all instructor data }; }, methods: { ... //Query all instructors getListTeacher() { course.getAllTeacher().then((resp) => { this.teacherLists = resp.data.items; }); } }, created() { this.getListTeacher(); }
In the ui written earlier, data from the back end has been traversed, which is excerpted here.
Test the function.
(2) Realizing the Linkage between Classification I and Classification II of Courses
info.vue
.<template> ... <!-- First Classification --> <el-form-item label="Classification of courses"> <el-select v-model="courseInfo.subjectParentId" placeholder="First Classification" @change="subjectOneChanged"> <!-- Traversal of course classification information --> <el-option v-for="subject in subjectOneList" :key="subject.id" :label="subject.title" :value="subject.id" ></el-option> </el-select> </el-form-item> <!-- Secondary Classification --> <el-form-item label="Classification of courses"> <el-select v-model="courseInfo.subjectId" placeholder="Secondary Classification"> <!-- Traversal of course classification information --> <el-option v-for="subject in subjectTwoList" :key="subject.id" :label="subject.title" :value="subject.id" ></el-option> </el-select> </el-form-item> </template> <script> ...... import subject from "@/api/edu/subject.js"; export default { data() { return { ...... subjectOneList: [], //Encapsulate Level 1 Course Classification subjecTwoList: [] //Encapsulate Level 2 Course Classification }; }, methods: { ...... getOneSubject() { subject.getAllSubject().then((resp) => { this.subjectOneList = resp.data.list; }) }, // Trigger display of secondary classification when clicking on a first-level classification subjectOneChanged(value) { //value is the id of a first-level classification, and it's what the framework does for us //Value is the id value of a first-level classification for (let i = 0; i < this.subjectOneList.length; i++) { if (this.subjectOneList[i].id === value) { this.subjectTwoList = this.subjectOneList[i].children; this.courseInfo.subjectId = ""; //Empty to avoid choosing new and post-categorized secondary categories that still have value, and the reader can eliminate self-testing } } } }, created() { ... this.getOneSubject() } } </script> <style> </style>
The results are as follows.
[External chain picture transfer failed, source station may have anti-theft chain mechanism, it is recommended to save the picture and upload it directly (img-LqySEfpE-1637946746593)(C:/Users/24724/AppData/Roaming/Typora/typora-user-images/image-20211127005318905.png)]