• 谷粒学苑 —— 7、课程管理:课程发布页面2 —— 课程大纲


    目录

    1、课程大纲列表展示

    1.1、后端

    1.1.1、创建 VO

    1.1.2、根据课程 ID 获取大纲信息

    1.2、前端实现

    1.2.1、定义 API

    1.2.2、前端页面

    2、上一步和下一步的跳转

    3、章节管理

    3.1、后端

    3.1.1、添加章节

    3.1.2、根据章节ID查询章节信息

    3.1.3、根据章节ID修改章节信息

    3.1.4、删除指定章节ID的信息

    3.2、前端

    3.2.1、定义 API

    3.2.2、添加章节的按钮和 dialog

    3.2.3、添加章节

    3.2.4、修改章节

    3.2.5、删除章节

    4、小节管理

    4.1、后端

    4.1.1、添加小节

    4.1.2、删除小节

    4.1.3、根据小节ID查询小节信息

    4.1.4、修改小节信息

    4.2、前端

    4.2.1、定义 API

    4.2.2、添加和修改小节表单dialog

    4.2.3、添加小节

    4.2.4、修改小节

    4.2.5、删除小节

    5、完整页面内容


    1、课程大纲列表展示

    1.1、后端

    1.1.1、创建 VO

    1. @ApiModel(value = "章节信息VO")
    2. @Data
    3. public class ChapterVO {
    4. private String id;
    5. private String title;
    6. private List children = new ArrayList<>();
    7. }
    1. @Data
    2. @ApiModel(value = "课时(小节)信息VO")
    3. public class VideoVO {
    4. private String id;
    5. private String title;
    6. private Boolean free;
    7. }

    1.1.2、根据课程 ID 获取大纲信息

    EduChapterService

    1. /**
    2. * 根据课程ID查询课程大纲
    3. * @param courseId 课程ID
    4. * @return
    5. */
    6. List getChapterVideoByCourseId(String courseId);

    EduChapterServiceImpl

    1. @Service
    2. public class EduChapterServiceImpl extends ServiceImpl implements EduChapterService {
    3. @Autowired
    4. private EduVideoService eduVideoService;
    5. /**
    6. * 根据课程ID查询课程大纲
    7. * @param courseId 课程ID
    8. * @return
    9. */
    10. @Override
    11. public List getChapterVideoByCourseId(String courseId) {
    12. // 根据课程ID查询课程的所有章节
    13. LambdaQueryWrapper chapterQueryWrapper = new LambdaQueryWrapper<>();
    14. chapterQueryWrapper.eq(EduChapter::getCourseId, courseId);
    15. List chapterList = this.list(chapterQueryWrapper);
    16. // 根据课程ID查询课程的所有小节
    17. LambdaQueryWrapper videoQueryWrapper = new LambdaQueryWrapper<>();
    18. videoQueryWrapper.eq(EduVideo::getCourseId, courseId);
    19. List videoList = eduVideoService.list(videoQueryWrapper);
    20. // 封装
    21. List chapterVOList = chapterList.stream().map((item) -> {
    22. ChapterVO chapterVO = new ChapterVO();
    23. BeanUtils.copyProperties(item, chapterVO);
    24. List children = new ArrayList<>();
    25. for (EduVideo v : videoList) {
    26. if(v.getChapterId().equals(item.getId())) {
    27. VideoVO videoVO = new VideoVO();
    28. BeanUtils.copyProperties(v, videoVO);
    29. children.add(videoVO);
    30. }
    31. }
    32. chapterVO.setChildren(children);
    33. return chapterVO;
    34. }).collect(Collectors.toList());
    35. return chapterVOList;
    36. }
    37. }

    EduChapterController

    1. @RestController
    2. @CrossOrigin
    3. @Api(description="课程大纲管理")
    4. @RequestMapping("/eduservice/chapter")
    5. public class EduChapterController {
    6. @Autowired
    7. private EduChapterService chapterService;
    8. /**
    9. * 根据课程ID查询课程大纲
    10. * @return
    11. */
    12. @ApiOperation(value = "根据课程ID查询课程大纲")
    13. @GetMapping("/getChapterVideo/{courseId}")
    14. public R getChapterVideo(
    15. @ApiParam(name = "courseId", value = "课程ID", required = true) @PathVariable String courseId
    16. ) {
    17. List list = chapterService.getChapterVideoByCourseId(courseId);
    18. return R.ok().data("allChapterVideo", list);
    19. }
    20. }

    1.2、前端实现

    1.2.1、定义 API

    src\api\edu\chapter.js

    1. import request from '@/utils/request'
    2. export default {
    3. /**
    4. * 根据课程ID获取所有章节和小节信息
    5. * @param {*} courseId 课程ID
    6. * @returns
    7. */
    8. getAllChapterVideo(courseId) {
    9. return request({
    10. url: `/eduservice/chapter/getChapterVideo/${courseId}`,
    11. method: 'GET'
    12. })
    13. }
    14. }

    1.2.2、前端页面

    1. <template>
    2. <div class="app-container">
    3. <h2 style="text-align: center;">发布新课程h2>
    4. <el-steps :active="2" process-status="wait" align-center style="margin-bottom: 40px;">
    5. <el-step title="填写课程基本信息" />
    6. <el-step title="创建课程大纲" />
    7. <el-step title="最终发布" />
    8. el-steps>
    9. <ul class="chanpterList">
    10. <li v-for="chapter in chapterVideoList" :key="chapter.id">
    11. <p>
    12. {{ chapter.title }}
    13. <span class="acts">
    14. <el-button type="text">添加课时el-button>
    15. <el-button style="" type="text">编辑el-button>
    16. <el-button type="text">删除el-button>
    17. span>
    18. p>
    19. <ul class="chanpterList videoList">
    20. <li v-for="video in chapter.children" :key="video.id">
    21. <p>
    22. {{ video.title }}
    23. <span class="acts">
    24. <el-button type="text">编辑el-button>
    25. <el-button type="text">删除el-button>
    26. span>
    27. p>
    28. li>
    29. ul>
    30. li>
    31. ul>
    32. <el-form label-width="120px">
    33. <el-form-item>
    34. <el-button @click="previous">上一步el-button>
    35. <el-button :disabled="saveBtnDisabled" type="primary" @click="next">下一步el-button>
    36. el-form-item>
    37. el-form>
    38. div>
    39. template>
    40. <script>
    41. import chapter from '@/api/edu/chapter';
    42. export default {
    43. data() {
    44. return {
    45. saveBtnDisabled: false,
    46. chapterVideoList: [], // 章节和小节信息
    47. courseId: '' // 课程ID
    48. }
    49. },
    50. created() {
    51. // 获取路由ID值
    52. if(this.$route.params && this.$route.params.id) {
    53. this.courseId = this.$route.params.id;
    54. // console.log('course = ' + this.courseId);
    55. }
    56. // 根据课程ID获取所有章节和小节信息
    57. this.getAllChapterVideo();
    58. },
    59. methods: {
    60. /**
    61. * 点击上一步跳转到src\views\edu\course\info.vue
    62. */
    63. previous() {
    64. this.$router.push({ path: '/course/info/1' });
    65. },
    66. /**
    67. * 点击下一步跳转到src\views\edu\course\publish.vue
    68. */
    69. next() {
    70. this.$router.push({ path: '/course/publish/1' });
    71. },
    72. /**
    73. * 根据课程ID获取所有章节和小节信息
    74. */
    75. getAllChapterVideo() {
    76. chapter.getAllChapterVideo(this.courseId).then(response => {
    77. this.chapterVideoList = response.data.allChapterVideo;
    78. // console.log(this.chapterVideoList);
    79. });
    80. }
    81. }
    82. }
    83. script>
    84. <style scoped>
    85. .chanpterList {
    86. position: relative;
    87. list-style: none;
    88. margin: 0;
    89. padding: 0;
    90. }
    91. .chanpterList li {
    92. position: relative;
    93. }
    94. .chanpterList p {
    95. float: left;
    96. font-size: 20px;
    97. margin: 10px 0;
    98. padding: 10px;
    99. height: 70px;
    100. line-height: 50px;
    101. width: 100%;
    102. border: 1px solid #DDD;
    103. position: relative;
    104. }
    105. .chanpterList .acts {
    106. float: right;
    107. font-size: 14px;
    108. position: relative;
    109. z-index: 1;
    110. }
    111. .videoList {
    112. padding-left: 50px;
    113. }
    114. .videoList p {
    115. float: left;
    116. font-size: 14px;
    117. margin: 10px 0;
    118. padding: 10px;
    119. height: 50px;
    120. line-height: 30px;
    121. width: 100%;
    122. border: 1px dotted #DDD;
    123. }
    124. style>

    2、上一步和下一步的跳转

    页面组件:

    1. <el-form label-width="120px">
    2. <el-form-item>
    3. <el-button @click="previous">上一步el-button>
    4. <el-button :disabled="saveBtnDisabled" type="primary" @click="next">下一步el-button>
    5. el-form-item>
    6. el-form>

    methods 中的方法

    1. /**
    2. * 点击上一步跳转到课程信息页面,并携带课程ID参数
    3. */
    4. previous() {
    5. this.$router.push({ path: '/course/info/' + this.courseId });
    6. },
    7. /**
    8. * 点击下一步跳转到课程发布页面,并携带课程ID参数
    9. */
    10. next() {
    11. this.$router.push({ path: '/course/publish/' + this.courseId });
    12. },

    3、章节管理

    3.1、后端

    3.1.1、添加章节

    EduChapterController

    1. /**
    2. * 添加章节
    3. * @param chapter 章节信息
    4. * @return
    5. */
    6. @PostMapping("/addChapter")
    7. public R addChapter(
    8. @ApiParam(name = "EduChapter", value = "章节对象", required = true) @RequestBody EduChapter chapter
    9. ) {
    10. chapterService.save(chapter);
    11. return R.ok();
    12. }

    3.1.2、根据章节ID查询章节信息

    EduChapterController

    1. /**
    2. * 根据章节ID查询章节信息
    3. * @param chapterId 章节ID
    4. * @return
    5. */
    6. @GetMapping("/getChapterInfo/{chapterId}")
    7. public R getChapterInfo(
    8. @ApiParam(name = "chapterId", value = "章节ID", required = true) @PathVariable String chapterId
    9. ) {
    10. EduChapter chapter = chapterService.getById(chapterId);
    11. return R.ok().data("chapter", chapter);
    12. }

    3.1.3、根据章节ID修改章节信息

    EduChapterController

    1. /**
    2. * 根据章节ID修改章节信息
    3. * @param chapter 章节信息
    4. * @return
    5. */
    6. @PostMapping("/updateChapter")
    7. public R updateChapter(
    8. @ApiParam(name = "EduChapter", value = "章节对象", required = true) @RequestBody EduChapter chapter
    9. ) {
    10. chapterService.updateById(chapter);
    11. return R.ok();
    12. }

    3.1.4、删除指定章节ID的信息

    EduChapterService

    1. /**
    2. * 删除指定章节ID的信息
    3. * @param chapterId 章节ID
    4. * @return
    5. */
    6. boolean deleteChapter(String chapterId);

    EduChapterServiceImpl

    1. /**
    2. * 删除指定章节ID的信息
    3. * @param chapterId 章节ID
    4. * @return
    5. */
    6. @Override
    7. public boolean deleteChapter(String chapterId) {
    8. // 查询该章节下是否有小节
    9. LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>();
    10. queryWrapper.eq(EduVideo::getChapterId, chapterId);
    11. int count = eduVideoService.count(queryWrapper);
    12. if(count > 0) {
    13. // 该章节下有小节,不可以删除
    14. throw new GuliException(20001, "该章节下有小节,不能删除!");
    15. } else {
    16. // 该章节下没有小节,可以删除
    17. boolean result = this.removeById(chapterId);
    18. return result;
    19. }
    20. }

    EduChapterController

    1. /**
    2. * 删除指定章节ID的信息
    3. * @param chapterId 章节ID
    4. * @return
    5. */
    6. @DeleteMapping("/{chapterId}")
    7. public R deleteChapter(
    8. @ApiParam(name = "chapterId", value = "章节ID", required = true) @PathVariable String chapterId
    9. ) {
    10. boolean flag = chapterService.deleteChapter(chapterId);
    11. if(flag) {
    12. return R.ok();
    13. } else {
    14. return R.error();
    15. }
    16. }

    3.2、前端

    3.2.1、定义 API

    src\api\edu\chapter.js

    1. /**
    2. * 添加章节
    3. * @param {*} chapter 章节信息
    4. * @returns
    5. */
    6. addChapter(chapter) {
    7. return request({
    8. url: `/eduservice/chapter/addChapter`,
    9. method: 'POST',
    10. data: chapter
    11. })
    12. },
    13. /**
    14. * 根据章节ID查询章节信息
    15. * @param {*} chapterId 章节ID
    16. * @returns
    17. */
    18. getChapterById(chapterId) {
    19. return request({
    20. url: `/eduservice/chapter/getChapterInfo/${chapterId}`,
    21. method: 'GET'
    22. })
    23. },
    24. /**
    25. * 根据章节ID修改章节信息
    26. * @param {*} chapter 章节信息
    27. * @returns
    28. */
    29. updateChapter(chapter) {
    30. return request({
    31. url: `/eduservice/chapter/updateChapter`,
    32. method: 'POST',
    33. data: chapter
    34. })
    35. },
    36. /**
    37. * 删除指定章节ID的信息
    38. * @param {*} chapterId 章节ID
    39. * @returns
    40. */
    41. deleteChapter(chapterId) {
    42. return request({
    43. url: `/eduservice/chapter/${chapterId}`,
    44. method: 'DELETE'
    45. })
    46. }

    3.2.2、添加章节的按钮和 dialog

    1. <el-button type="text" @click="openChapterDialog()">添加章节el-button>
    2. <el-dialog :visible.sync="dialogChapterFormVisible" title="添加章节">
    3. <el-form :model="chapter" label-width="120px" :rules="rules" ref="chapterForm">
    4. <el-form-item label="章节标题" prop="title">
    5. <el-input v-model="chapter.title" />
    6. el-form-item>
    7. <el-form-item label="章节排序" prop="sort">
    8. <el-input-number v-model="chapter.sort" :min="0" />
    9. el-form-item>
    10. el-form>
    11. <div slot="footer" class="dialog-footer">
    12. <el-button @click="dialogChapterFormVisible = false">取 消el-button>
    13. <el-button type="primary" @click="saveOrUpdate">确 定el-button>
    14. div>
    15. el-dialog>

    在 data 中定义

    1. dialogChapterFormVisible: false, // 添加章节的弹框dialog是否可见
    2. chapter: { // 封装章节数据
    3. title: '',
    4. sort: undefined
    5. },
    6. rules: { // 表单校验规则
    7. title: [
    8. { required: true, message: '请输入章节标题', trigger: 'blur' },
    9. { min: 2, max: 15, message: '长度在 2 到 15 个字符', trigger: 'blur' }
    10. ],
    11. sort: [
    12. { required: true, message: '请输入章节排序', type: 'number', trigger: 'blur' }
    13. ],
    14. },

    3.2.3、添加章节

    在 methods 中添加方法

    1. /**
    2. * 添加或修改章节
    3. */
    4. saveOrUpdate() {
    5. this.addChapter();
    6. },
    7. /**
    8. * 添加章节
    9. */
    10. addChapter() {
    11. this.chapter.courseId = this.courseId; // 设置课程ID
    12. this.$refs['chapterForm'].validate((valid) => {
    13. if (valid) {
    14. chapter.addChapter(this.chapter).then(response => {
    15. // 关闭弹框
    16. this.dialogChapterFormVisible = false;
    17. // 提示信息
    18. this.$message({
    19. type: 'success',
    20. message: '添加成功!'
    21. });
    22. // 刷新页面(重新加载数据)
    23. this.getAllChapterVideo();
    24. });
    25. } else {
    26. return false;
    27. }
    28. });
    29. },
    30. /**
    31. * 点击添加章节按钮后打开弹框
    32. */
    33. openChapterDialog() {
    34. // 初始化
    35. this.chapter.title = '';
    36. this.chapter.sort = undefined;
    37. this.dialogChapterFormVisible = true; // 打开弹框
    38. }

    3.2.4、修改章节

    在章节大纲组件的编辑按钮添加点击事件

    1. <p>
    2. {{ chapter.title }}
    3. <span class="acts">
    4. <el-button type="text">添加课时el-button>
    5. <el-button style="" type="text" @click="openEditChapterDialog(chapter.id)">编辑el-button>
    6. <el-button type="text">删除el-button>
    7. span>
    8. p>

    在 methods 中修改 saveOrUpdate 方法并添加方法

    1. /**
    2. * 添加或修改章节
    3. */
    4. saveOrUpdate() {
    5. // 根据章节ID判断进行添加还是修改操作(注意,不是课程ID)
    6. if(!this.chapter.id) {
    7. this.addChapter();
    8. } else {
    9. this.updateChapter();
    10. }
    11. },
    12. /**
    13. * 点击编辑按钮后打开弹框
    14. * @param {*} chapterId
    15. */
    16. openEditChapterDialog(chapterId) {
    17. // 初始化
    18. chapter.getChapterById(chapterId).then(response => {
    19. this.chapter = response.data.chapter;
    20. });
    21. this.dialogChapterFormVisible = true; // 打开弹框
    22. },
    23. /**
    24. * 修改章节
    25. */
    26. updateChapter() {
    27. this.$refs['chapterForm'].validate((valid) => {
    28. if (valid) {
    29. chapter.updateChapter(this.chapter).then(response => {
    30. // 关闭弹框
    31. this.dialogChapterFormVisible = false;
    32. // 提示信息
    33. this.$message({
    34. type: 'success',
    35. message: '修改成功!'
    36. });
    37. // 刷新页面(重新加载数据)
    38. this.getAllChapterVideo();
    39. });
    40. } else {
    41. return false;
    42. }
    43. });
    44. }

    3.2.5、删除章节

    在章节大纲组件的删除按钮添加点击事件

    1. <p>
    2. {{ chapter.title }}
    3. <span class="acts">
    4. <el-button type="text">添加课时el-button>
    5. <el-button type="text" @click="openEditChapterDialog(chapter.id)">编辑el-button>
    6. <el-button type="text" @click="removeChapter(chapter.id)">删除el-button>
    7. span>
    8. p>

    在 methods 中添加删除方法

    1. /**
    2. * 删除章节
    3. * @param {} chapterId
    4. */
    5. removeChapter(chapterId) {
    6. this.$confirm('此操作将永久删除该章节信息, 是否继续?', '提示', {
    7. confirmButtonText: '确定',
    8. cancelButtonText: '取消',
    9. type: 'warning'
    10. })
    11. // 确认删除
    12. .then(() => {
    13. chapter.deleteChapter(chapterId)
    14. // 删除成功
    15. .then(response => {
    16. // 提示信息
    17. this.$message({
    18. type: 'success',
    19. message: '删除成功!'
    20. });
    21. // 刷新页面(重新加载数据)
    22. this.getAllChapterVideo();
    23. })
    24. })
    25. // 取消删除
    26. .catch(() => {
    27. this.$message({
    28. type: 'info',
    29. message: '已取消删除'
    30. });
    31. });
    32. }

    4、小节管理

    4.1、后端

    4.1.1、添加小节

    EduVideoController

    1. /**
    2. * 添加小节
    3. * @param video 小节信息
    4. * @return
    5. */
    6. @ApiOperation(value = "添加小节")
    7. @PostMapping("/addVideo")
    8. public R addVideo(
    9. @ApiParam(name = "EduVideo", value = "小节信息", required = true) @RequestBody EduVideo video
    10. ) {
    11. videoService.save(video);
    12. return R.ok();
    13. }

    4.1.2、删除小节

    EduVideoController

    1. /**
    2. * 删除小节
    3. * @param videoId 小节ID
    4. * @return
    5. */
    6. @DeleteMapping("/{videoId}")
    7. public R deleteVideo(
    8. @ApiParam(name = "videoId", value = "小节ID", required = true) @PathVariable String videoId
    9. ) {
    10. videoService.removeById(videoId);
    11. return R.ok();
    12. }

    4.1.3、根据小节ID查询小节信息

    EduVideoController

    1. /**
    2. * 根据小节ID查询小节信息
    3. * @param videoId
    4. * @return
    5. */
    6. @GetMapping("/getVideoInfo/{videoId}")
    7. public R getVideoInfo(
    8. @ApiParam(name = "videoId", value = "课时ID", required = true) @PathVariable String videoId
    9. ) {
    10. EduVideo video = videoService.getById(videoId);
    11. return R.ok().data("video", video);
    12. }

    4.1.4、修改小节信息

    EduVideoController

    1. /**
    2. * 修改小节信息
    3. * @param eduVideo 小节信息
    4. * @return
    5. */
    6. @PostMapping("/updateVideo")
    7. public R updateVideo(
    8. @ApiParam(name = "eduVideo", value = "课时基本信息", required = true) @RequestBody EduVideo eduVideo
    9. ) {
    10. videoService.updateById(eduVideo);
    11. return R.ok();
    12. }

    4.2、前端

    4.2.1、定义 API

    src\api\edu\video.js

    1. import request from '@/utils/request'
    2. export default {
    3. /**
    4. * 添加小节
    5. * @param {*} video 小节信息
    6. * @returns
    7. */
    8. addVideo(video) {
    9. return request({
    10. url: `/eduservice/video/addVideo`,
    11. method: 'POST',
    12. data: video
    13. })
    14. },
    15. /**
    16. * 删除指定小节ID的信息
    17. * @param {*} videoId 小节ID
    18. * @returns
    19. */
    20. deleteChapter(videoId) {
    21. return request({
    22. url: `/eduservice/video/${videoId}`,
    23. method: 'DELETE'
    24. })
    25. },
    26. /**
    27. * 根据小节ID查询小节信息
    28. * @param {*} videoId 小节ID
    29. * @returns
    30. */
    31. getVideoById(videoId) {
    32. return request({
    33. url: `/eduservice/video/getVideoInfo/${videoId}`,
    34. method: 'GET'
    35. })
    36. },
    37. /**
    38. * 根据小节ID修改小节信息
    39. * @param {*} video 小节信息
    40. * @returns
    41. */
    42. updateVideo(video) {
    43. return request({
    44. url: `/eduservice/video/updateVideo`,
    45. method: 'POST',
    46. data: video
    47. })
    48. },
    49. }

    并在页面中引入

    import video from '@/api/edu/video';

    4.2.2、添加和修改小节表单dialog

    1. <el-dialog :visible.sync="dialogVideoFormVisible" title="添加课时">
    2. <el-form :model="video" label-width="120px">
    3. <el-form-item label="课时标题">
    4. <el-input v-model="video.title" />
    5. el-form-item>
    6. <el-form-item label="课时排序">
    7. <el-input-number v-model="video.sort" :min="0" controls-position="right" />
    8. el-form-item>
    9. <el-form-item label="是否免费">
    10. <el-radio-group v-model="video.free">
    11. <el-radio :label="true">免费el-radio>
    12. <el-radio :label="false">默认el-radio>
    13. el-radio-group>
    14. el-form-item>
    15. <el-form-item label="上传视频">
    16. el-form-item>
    17. el-form>
    18. <div slot="footer" class="dialog-footer">
    19. <el-button @click="dialogVideoFormVisible = false">取 消el-button>
    20. <el-button :disabled="saveVideoBtnDisabled" type="primary" @click="saveOrUpdateVideo">确 定el-button>
    21. div>
    22. el-dialog>

    在 data 中定义

    1. dialogVideoFormVisible: false, // 添加和修改小节的表单dialog是否可见
    2. video: { // 封装小节数据
    3. title: '',
    4. sort: undefined,
    5. free: undefined,
    6. videoSourceId: ''
    7. },
    8. videoRules: {
    9. title: [
    10. { required: true, message: '请输入小节标题', trigger: 'blur' },
    11. { min: 2, max: 15, message: '长度在 2 到 15 个字符', trigger: 'blur' }
    12. ],
    13. sort: [
    14. { required: true, message: '请输入章节排序', type: 'number', trigger: 'blur' }
    15. ],
    16. free: [
    17. { required: true, message: '请选择是否免费', trigger: 'blur' }
    18. ]
    19. }

    4.2.3、添加小节

    在章节栏的添加小节按钮绑定事件

    1. <li v-for="chapter in chapterVideoList" :key="chapter.id">
    2. <p>
    3. {{ chapter.title }}
    4. <span class="acts">
    5. <el-button type="text" @click="openVideo(chapter.id)">添加小节el-button>
    6. <el-button type="text" @click="openEditChapterDialog(chapter.id)">编辑el-button>
    7. <el-button type="text" @click="removeChapter(chapter.id)">删除el-button>
    8. span>
    9. p>

    在添加和修改小节的 dialog 的确定按钮绑定事件

    1. <div slot="footer" class="dialog-footer">
    2. <el-button @click="dialogVideoFormVisible = false">取 消el-button>
    3. <el-button type="primary" @click="saveOrUpdateVideo">确 定el-button>
    4. div>

    在 methods 中添加方法

    1. /**
    2. * 点击添加小节打开弹框,并初始化video对象
    3. * @param {*} chapterId 本次添加的小节所属的章节的ID
    4. */
    5. openVideo(chapterId) {
    6. // 初始化
    7. this.video = {
    8. title: '',
    9. sort: undefined,
    10. isFree: undefined,
    11. videoSourceId: ''
    12. };
    13. this.video.chapterId = chapterId;
    14. // 显示弹框
    15. this.dialogVideoFormVisible = true;
    16. },
    17. saveOrUpdateVideo() {
    18. if(!this.video.id) {
    19. this.addVideo();
    20. } else {
    21. this.updateVideo();
    22. }
    23. },
    24. /**
    25. * 添加小节
    26. */
    27. addVideo() {
    28. this.$refs['videoForm'].validate((valid) => {
    29. if (valid) {
    30. this.video.courseId = this.courseId; // 设置课程ID
    31. video.addVideo(this.video).then(response => {
    32. // 关闭弹框
    33. this.dialogVideoFormVisible = false;
    34. // 提示信息
    35. this.$message({
    36. type: 'success',
    37. message: '添加成功!'
    38. });
    39. // 刷新页面(重新加载数据)
    40. this.getAllChapterVideo();
    41. });
    42. } else {
    43. return false;
    44. }
    45. });
    46. },

    4.2.4、修改小节

    在小节栏的编辑按钮绑定事件

    1. <ul class="chanpterList videoList">
    2. <li v-for="video in chapter.children" :key="video.id">
    3. <p>
    4. {{ video.title }}
    5. <span class="acts">
    6. <el-button type="text" @click="openEditVideoDialog(video.id)">编辑el-button>
    7. <el-button type="text" @click="removeVideo(video.id)">删除el-button>
    8. span>
    9. p>
    10. li>
    11. ul>

    在 methods 中添加方法

    1. openEditVideoDialog(videoId) {
    2. // 初始化
    3. video.getVideoById(videoId).then(response => {
    4. this.video = response.data.video;
    5. });
    6. this.dialogVideoFormVisible = true; // 打开弹框
    7. },
    8. updateVideo() {
    9. this.$refs['videoForm'].validate((valid) => {
    10. if (valid) {
    11. video.updateVideo(this.video).then(response => {
    12. // 关闭弹框
    13. this.dialogVideoFormVisible = false;
    14. // 提示信息
    15. this.$message({
    16. type: 'success',
    17. message: '添加成功!'
    18. });
    19. // 刷新页面(重新加载数据)
    20. this.getAllChapterVideo();
    21. });
    22. } else {
    23. return false;
    24. }
    25. });
    26. },

    4.2.5、删除小节

    在删除小节按钮绑定事件

    1. <span class="acts">
    2. <el-button type="text" @click="openEditVideoDialog(video.id)">编辑el-button>
    3. <el-button type="text" @click="removeVideo(video.id)">删除el-button>
    4. span>

    在 methods 中添加方法

    1. /**
    2. * 删除小节
    3. * @param {*} videoId 小节Id
    4. */
    5. removeVideo(videoId) {
    6. this.$confirm('此操作将永久删除该小节信息, 是否继续?', '提示', {
    7. confirmButtonText: '确定',
    8. cancelButtonText: '取消',
    9. type: 'warning'
    10. })
    11. // 确认删除
    12. .then(() => {
    13. video.deleteChapter(videoId)
    14. // 删除成功
    15. .then(response => {
    16. // 提示信息
    17. this.$message({
    18. type: 'success',
    19. message: '删除成功!'
    20. });
    21. // 刷新页面(重新加载数据)
    22. this.getAllChapterVideo();
    23. })
    24. })
    25. // 取消删除
    26. .catch(() => {
    27. this.$message({
    28. type: 'info',
    29. message: '已取消删除'
    30. });
    31. });
    32. },

    5、完整页面内容

    1. <template>
    2. <div class="app-container">
    3. <h2 style="text-align: center;">发布新课程h2>
    4. <el-steps :active="2" process-status="wait" align-center style="margin-bottom: 40px;">
    5. <el-step title="填写课程基本信息" />
    6. <el-step title="创建课程大纲" />
    7. <el-step title="最终发布" />
    8. el-steps>
    9. <el-button type="text" @click="openChapterDialog()">添加章节el-button>
    10. <el-dialog :visible.sync="dialogChapterFormVisible" title="添加章节">
    11. <el-form :model="chapter" label-width="120px" :rules="rules" ref="chapterForm">
    12. <el-form-item label="章节标题" prop="title">
    13. <el-input v-model="chapter.title" />
    14. el-form-item>
    15. <el-form-item label="章节排序" prop="sort">
    16. <el-input-number v-model="chapter.sort" :min="0" />
    17. el-form-item>
    18. el-form>
    19. <div slot="footer" class="dialog-footer">
    20. <el-button @click="dialogChapterFormVisible = false">取 消el-button>
    21. <el-button type="primary" @click="saveOrUpdate">确 定el-button>
    22. div>
    23. el-dialog>
    24. <ul class="chanpterList">
    25. <li v-for="chapter in chapterVideoList" :key="chapter.id">
    26. <p>
    27. {{ chapter.title }}
    28. <span class="acts">
    29. <el-button type="text" @click="openVideo(chapter.id)">添加小节el-button>
    30. <el-button type="text" @click="openEditChapterDialog(chapter.id)">编辑el-button>
    31. <el-button type="text" @click="removeChapter(chapter.id)">删除el-button>
    32. span>
    33. p>
    34. <ul class="chanpterList videoList">
    35. <li v-for="video in chapter.children" :key="video.id">
    36. <p>
    37. {{ video.title }}
    38. <span class="acts">
    39. <el-button type="text" @click="openEditVideoDialog(video.id)">编辑el-button>
    40. <el-button type="text" @click="removeVideo(video.id)">删除el-button>
    41. span>
    42. p>
    43. li>
    44. ul>
    45. li>
    46. ul>
    47. <el-dialog :visible.sync="dialogVideoFormVisible" title="添加课时">
    48. <el-form :model="video" label-width="120px" :rules="videoRules" ref="videoForm">
    49. <el-form-item label="课时标题" prop="title">
    50. <el-input v-model="video.title" />
    51. el-form-item>
    52. <el-form-item label="课时排序" prop="sort">
    53. <el-input-number v-model="video.sort" :min="0" controls-position="right" />
    54. el-form-item>
    55. <el-form-item label="是否免费" prop="isFree">
    56. <el-radio-group v-model="video.isFree">
    57. <el-radio :label="true">免费el-radio>
    58. <el-radio :label="false">默认el-radio>
    59. el-radio-group>
    60. el-form-item>
    61. <el-form-item label="上传视频">
    62. el-form-item>
    63. el-form>
    64. <div slot="footer" class="dialog-footer">
    65. <el-button @click="dialogVideoFormVisible = false">取 消el-button>
    66. <el-button type="primary" @click="saveOrUpdateVideo">确 定el-button>
    67. div>
    68. el-dialog>
    69. <el-form label-width="120px">
    70. <el-form-item>
    71. <el-button @click="previous">上一步el-button>
    72. <el-button :disabled="saveBtnDisabled" type="primary" @click="next">下一步el-button>
    73. el-form-item>
    74. el-form>
    75. div>
    76. template>
    77. <script>
    78. import chapter from '@/api/edu/chapter';
    79. import video from '@/api/edu/video';
    80. export default {
    81. data() {
    82. return {
    83. saveBtnDisabled: false,
    84. chapterVideoList: [], // 章节和小节信息
    85. courseId: '', // 课程ID
    86. dialogChapterFormVisible: false, // 添加章节的弹框dialog是否可见
    87. chapter: { // 封装章节数据
    88. title: '',
    89. sort: undefined
    90. },
    91. rules: { // 表单校验规则
    92. title: [
    93. { required: true, message: '请输入章节标题', trigger: 'blur' },
    94. { min: 2, max: 15, message: '长度在 2 到 15 个字符', trigger: 'blur' }
    95. ],
    96. sort: [
    97. { required: true, message: '请输入章节排序', type: 'number', trigger: 'blur' }
    98. ],
    99. },
    100. dialogVideoFormVisible: false, // 添加和修改小节的表单dialog是否可见
    101. video: { // 封装小节数据
    102. title: '',
    103. sort: undefined,
    104. isFree: undefined,
    105. videoSourceId: ''
    106. },
    107. videoRules: {
    108. title: [
    109. { required: true, message: '请输入小节标题', trigger: 'blur' },
    110. { min: 2, max: 15, message: '长度在 2 到 15 个字符', trigger: 'blur' }
    111. ],
    112. sort: [
    113. { required: true, message: '请输入章节排序', type: 'number', trigger: 'blur' }
    114. ],
    115. isFree: [
    116. { required: true, message: '请选择是否免费', trigger: 'blur' }
    117. ]
    118. }
    119. }
    120. },
    121. created() {
    122. // 获取路由ID值
    123. if(this.$route.params && this.$route.params.id) {
    124. this.courseId = this.$route.params.id;
    125. // console.log('course = ' + this.courseId);
    126. }
    127. // 根据课程ID获取所有章节和小节信息
    128. this.getAllChapterVideo();
    129. },
    130. methods: {
    131. /**
    132. * 点击上一步跳转到课程信息页面,并携带课程ID参数
    133. */
    134. previous() {
    135. this.$router.push({ path: '/course/info/' + this.courseId });
    136. },
    137. /**
    138. * 点击下一步跳转到课程发布页面,并携带课程ID参数
    139. */
    140. next() {
    141. this.$router.push({ path: '/course/publish/' + this.courseId });
    142. },
    143. /**
    144. * 根据课程ID获取所有章节和小节信息
    145. */
    146. getAllChapterVideo() {
    147. chapter.getAllChapterVideo(this.courseId).then(response => {
    148. this.chapterVideoList = response.data.allChapterVideo;
    149. // console.log(this.chapterVideoList);
    150. });
    151. },
    152. /**
    153. * 添加或修改章节
    154. */
    155. saveOrUpdate() {
    156. // 根据章节ID判断进行添加还是修改操作(注意,不是课程ID)
    157. if(!this.chapter.id) {
    158. this.addChapter();
    159. } else {
    160. this.updateChapter();
    161. }
    162. },
    163. /**
    164. * 添加章节
    165. */
    166. addChapter() {
    167. this.chapter.courseId = this.courseId; // 设置课程ID
    168. this.$refs['chapterForm'].validate((valid) => {
    169. if (valid) {
    170. chapter.addChapter(this.chapter).then(response => {
    171. // 关闭弹框
    172. this.dialogChapterFormVisible = false;
    173. // 提示信息
    174. this.$message({
    175. type: 'success',
    176. message: '添加成功!'
    177. });
    178. // 刷新页面(重新加载数据)
    179. this.getAllChapterVideo();
    180. });
    181. } else {
    182. return false;
    183. }
    184. });
    185. },
    186. /**
    187. * 点击添加章节按钮后打开弹框
    188. */
    189. openChapterDialog() {
    190. // 初始化
    191. this.chapter.title = '';
    192. this.chapter.sort = undefined;
    193. this.dialogChapterFormVisible = true; // 打开弹框
    194. },
    195. /**
    196. * 点击编辑按钮后打开弹框
    197. * @param {*} chapterId
    198. */
    199. openEditChapterDialog(chapterId) {
    200. // 初始化
    201. chapter.getChapterById(chapterId).then(response => {
    202. this.chapter = response.data.chapter;
    203. });
    204. this.dialogChapterFormVisible = true; // 打开弹框
    205. },
    206. /**
    207. * 修改章节
    208. */
    209. updateChapter() {
    210. this.$refs['chapterForm'].validate((valid) => {
    211. if (valid) {
    212. chapter.updateChapter(this.chapter).then(response => {
    213. // 关闭弹框
    214. this.dialogChapterFormVisible = false;
    215. // 提示信息
    216. this.$message({
    217. type: 'success',
    218. message: '修改成功!'
    219. });
    220. // 刷新页面(重新加载数据)
    221. this.getAllChapterVideo();
    222. });
    223. } else {
    224. return false;
    225. }
    226. });
    227. },
    228. /**
    229. * 删除章节
    230. * @param {} chapterId
    231. */
    232. removeChapter(chapterId) {
    233. this.$confirm('此操作将永久删除该章节信息, 是否继续?', '提示', {
    234. confirmButtonText: '确定',
    235. cancelButtonText: '取消',
    236. type: 'warning'
    237. })
    238. // 确认删除
    239. .then(() => {
    240. chapter.deleteChapter(chapterId)
    241. // 删除成功
    242. .then(response => {
    243. // 提示信息
    244. this.$message({
    245. type: 'success',
    246. message: '删除成功!'
    247. });
    248. // 刷新页面(重新加载数据)
    249. this.getAllChapterVideo();
    250. })
    251. })
    252. // 取消删除
    253. .catch(() => {
    254. this.$message({
    255. type: 'info',
    256. message: '已取消删除'
    257. });
    258. });
    259. },
    260. //============================ 以下为小节操作 ==============================================
    261. /**
    262. * 点击添加小节打开弹框,并初始化video对象
    263. * @param {*} chapterId 本次添加的小节所属的章节的ID
    264. */
    265. openVideo(chapterId) {
    266. // 初始化
    267. this.video = {
    268. title: '',
    269. sort: undefined,
    270. isFree: undefined,
    271. videoSourceId: ''
    272. };
    273. this.video.chapterId = chapterId;
    274. // 显示弹框
    275. this.dialogVideoFormVisible = true;
    276. },
    277. saveOrUpdateVideo() {
    278. if(!this.video.id) {
    279. this.addVideo();
    280. } else {
    281. this.updateVideo();
    282. }
    283. },
    284. /**
    285. * 添加小节
    286. */
    287. addVideo() {
    288. this.$refs['videoForm'].validate((valid) => {
    289. if (valid) {
    290. this.video.courseId = this.courseId; // 设置课程ID
    291. video.addVideo(this.video).then(response => {
    292. // 关闭弹框
    293. this.dialogVideoFormVisible = false;
    294. // 提示信息
    295. this.$message({
    296. type: 'success',
    297. message: '添加成功!'
    298. });
    299. // 刷新页面(重新加载数据)
    300. this.getAllChapterVideo();
    301. });
    302. } else {
    303. return false;
    304. }
    305. });
    306. },
    307. updateVideo() {
    308. this.$refs['videoForm'].validate((valid) => {
    309. if (valid) {
    310. video.updateVideo(this.video).then(response => {
    311. // 关闭弹框
    312. this.dialogVideoFormVisible = false;
    313. // 提示信息
    314. this.$message({
    315. type: 'success',
    316. message: '添加成功!'
    317. });
    318. // 刷新页面(重新加载数据)
    319. this.getAllChapterVideo();
    320. });
    321. } else {
    322. return false;
    323. }
    324. });
    325. },
    326. /**
    327. * 删除小节
    328. * @param {*} videoId 小节Id
    329. */
    330. removeVideo(videoId) {
    331. this.$confirm('此操作将永久删除该小节信息, 是否继续?', '提示', {
    332. confirmButtonText: '确定',
    333. cancelButtonText: '取消',
    334. type: 'warning'
    335. })
    336. // 确认删除
    337. .then(() => {
    338. video.deleteChapter(videoId)
    339. // 删除成功
    340. .then(response => {
    341. // 提示信息
    342. this.$message({
    343. type: 'success',
    344. message: '删除成功!'
    345. });
    346. // 刷新页面(重新加载数据)
    347. this.getAllChapterVideo();
    348. })
    349. })
    350. // 取消删除
    351. .catch(() => {
    352. this.$message({
    353. type: 'info',
    354. message: '已取消删除'
    355. });
    356. });
    357. },
    358. openEditVideoDialog(videoId) {
    359. // 初始化
    360. video.getVideoById(videoId).then(response => {
    361. this.video = response.data.video;
    362. });
    363. this.dialogVideoFormVisible = true; // 打开弹框
    364. },
    365. }
    366. }
    367. script>
    368. <style scoped>
    369. .chanpterList {
    370. position: relative;
    371. list-style: none;
    372. margin: 0;
    373. padding: 0;
    374. }
    375. .chanpterList li {
    376. position: relative;
    377. }
    378. .chanpterList p {
    379. float: left;
    380. font-size: 20px;
    381. margin: 10px 0;
    382. padding: 10px;
    383. height: 70px;
    384. line-height: 50px;
    385. width: 100%;
    386. border: 1px solid #DDD;
    387. position: relative;
    388. }
    389. .chanpterList .acts {
    390. float: right;
    391. font-size: 14px;
    392. position: relative;
    393. z-index: 1;
    394. }
    395. .videoList {
    396. padding-left: 50px;
    397. }
    398. .videoList p {
    399. float: left;
    400. font-size: 14px;
    401. margin: 10px 0;
    402. padding: 10px;
    403. height: 50px;
    404. line-height: 30px;
    405. width: 100%;
    406. border: 1px dotted #DDD;
    407. }
    408. style>

  • 相关阅读:
    什么是智能视频美颜SDK?
    wchar_t* argv[] 数组赋值
    Hadoop集群启动但是没有datanode/namenode的情况
    C++程序设计--第三章内容
    Python入门系列(八)日期时间、数学、json
    Python 文件(文件夹)复制、剪切、删除速查
    【岛上书店】读后感
    WPF旋转变换
    开源生态与软件供应链研讨会
    数论知识点总结(一)
  • 原文地址:https://blog.csdn.net/Mr_zhangyj/article/details/127578230