谷粒学苑 —— 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

@ApiModel(value = "章节信息VO")
@Data
public class ChapterVO {

    private String id;

    private String title;

    private List<VideoVO> children = new ArrayList<>();

}
@Data
@ApiModel(value = "课时(小节)信息VO")
public class VideoVO {

    private String id;

    private String title;

    private Boolean free;

}

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

EduChapterService

    /**
     * 根据课程ID查询课程大纲
     * @param courseId 课程ID
     * @return
     */
    List<ChapterVO> getChapterVideoByCourseId(String courseId);

EduChapterServiceImpl

@Service
public class EduChapterServiceImpl extends ServiceImpl<EduChapterMapper, EduChapter> implements EduChapterService {

    @Autowired
    private EduVideoService eduVideoService;

    /**
     * 根据课程ID查询课程大纲
     * @param courseId 课程ID
     * @return
     */
    @Override
    public List<ChapterVO> getChapterVideoByCourseId(String courseId) {
        // 根据课程ID查询课程的所有章节
        LambdaQueryWrapper<EduChapter> chapterQueryWrapper = new LambdaQueryWrapper<>();
        chapterQueryWrapper.eq(EduChapter::getCourseId, courseId);
        List<EduChapter> chapterList = this.list(chapterQueryWrapper);

        // 根据课程ID查询课程的所有小节
        LambdaQueryWrapper<EduVideo> videoQueryWrapper = new LambdaQueryWrapper<>();
        videoQueryWrapper.eq(EduVideo::getCourseId, courseId);
        List<EduVideo> videoList = eduVideoService.list(videoQueryWrapper);

        // 封装
        List<ChapterVO> chapterVOList = chapterList.stream().map((item) -> {
            ChapterVO chapterVO = new ChapterVO();
            BeanUtils.copyProperties(item, chapterVO);
            List<VideoVO> children = new ArrayList<>();
            for (EduVideo v : videoList) {
                if(v.getChapterId().equals(item.getId())) {
                    VideoVO videoVO = new VideoVO();
                    BeanUtils.copyProperties(v, videoVO);
                    children.add(videoVO);
                }
            }
            chapterVO.setChildren(children);
            return chapterVO;
        }).collect(Collectors.toList());

        return chapterVOList;
    }

}

EduChapterController

@RestController
@CrossOrigin
@Api(description="课程大纲管理")
@RequestMapping("/eduservice/chapter")
public class EduChapterController {

    @Autowired
    private EduChapterService chapterService;

    /**
     * 根据课程ID查询课程大纲
     * @return
     */
    @ApiOperation(value = "根据课程ID查询课程大纲")
    @GetMapping("/getChapterVideo/{courseId}")
    public R getChapterVideo(
            @ApiParam(name = "courseId", value = "课程ID", required = true) @PathVariable String courseId
    ) {
        List<ChapterVO> list = chapterService.getChapterVideoByCourseId(courseId);
        return R.ok().data("allChapterVideo", list);
    }

}

1.2、前端实现

1.2.1、定义 API

src\api\edu\chapter.js

import request from '@/utils/request'

export default {

    /**
     * 根据课程ID获取所有章节和小节信息
     * @param {*} courseId 课程ID
     * @returns 
     */
    getAllChapterVideo(courseId) {
        return request({
            url: `/eduservice/chapter/getChapterVideo/${courseId}`,
            method: 'GET'
        })
    }
}

1.2.2、前端页面

<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>

        <!-- 章节 -->
        <ul class="chanpterList">
            <li v-for="chapter in chapterVideoList" :key="chapter.id">
                <p>
                    {{ chapter.title }}
                    <span class="acts">
                        <el-button type="text">添加课时</el-button>
                        <el-button style="" type="text">编辑</el-button>
                        <el-button type="text">删除</el-button>
                    </span>
                </p>
        
                <!-- 视频 -->
                <ul class="chanpterList videoList">
                    <li v-for="video in chapter.children" :key="video.id">
                        <p>
                            {{ video.title }}
                            <span class="acts">
                                <el-button type="text">编辑</el-button>
                                <el-button type="text">删除</el-button>
                            </span>
                        </p>
                    </li>
                </ul>
            </li>
        </ul>

        <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>
    import chapter from '@/api/edu/chapter';

    export default {
        data() {
            return {
                saveBtnDisabled: false,
                chapterVideoList: [],   // 章节和小节信息
                courseId: ''    // 课程ID
            }
        },
        created() {
            // 获取路由ID值
            if(this.$route.params && this.$route.params.id) {
                this.courseId = this.$route.params.id;
                // console.log('course = ' + this.courseId);
            }

            // 根据课程ID获取所有章节和小节信息
            this.getAllChapterVideo();
        },
        methods: {
            /**
             * 点击上一步跳转到src\views\edu\course\info.vue
             */
            previous() {
                this.$router.push({ path: '/course/info/1' });
            },

            /**
             * 点击下一步跳转到src\views\edu\course\publish.vue
             */
            next() {
                this.$router.push({ path: '/course/publish/1' });
            },

            /**
             * 根据课程ID获取所有章节和小节信息
             */
            getAllChapterVideo() {
                chapter.getAllChapterVideo(this.courseId).then(response => {
                    this.chapterVideoList = response.data.allChapterVideo;
                    // console.log(this.chapterVideoList);
                });
            }
        }
    }
</script>
<style scoped>
.chanpterList {
    position: relative;
    list-style: none;
    margin: 0;
    padding: 0;
}

.chanpterList li {
    position: relative;
}

.chanpterList p {
    float: left;
    font-size: 20px;
    margin: 10px 0;
    padding: 10px;
    height: 70px;
    line-height: 50px;
    width: 100%;
    border: 1px solid #DDD;
    position: relative;
}

.chanpterList .acts {
    float: right;
    font-size: 14px;
    position: relative;
    z-index: 1;
}

.videoList {
    padding-left: 50px;
}

.videoList p {
    float: left;
    font-size: 14px;
    margin: 10px 0;
    padding: 10px;
    height: 50px;
    line-height: 30px;
    width: 100%;
    border: 1px dotted #DDD;
}
</style>

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

页面组件:

        <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>

methods 中的方法

            /**
             * 点击上一步跳转到课程信息页面,并携带课程ID参数
             */
            previous() {
                this.$router.push({ path: '/course/info/' + this.courseId });
            },

            /**
             * 点击下一步跳转到课程发布页面,并携带课程ID参数
             */
            next() {
                this.$router.push({ path: '/course/publish/' + this.courseId });
            },

3、章节管理

3.1、后端

3.1.1、添加章节

EduChapterController

    /**
     * 添加章节
     * @param chapter 章节信息
     * @return
     */
    @PostMapping("/addChapter")
    public R addChapter(
            @ApiParam(name = "EduChapter", value = "章节对象", required = true) @RequestBody EduChapter chapter
    ) {
        chapterService.save(chapter);
        return R.ok();
    }

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

EduChapterController

    /**
     * 根据章节ID查询章节信息
     * @param chapterId 章节ID
     * @return
     */
    @GetMapping("/getChapterInfo/{chapterId}")
    public R getChapterInfo(
            @ApiParam(name = "chapterId", value = "章节ID", required = true) @PathVariable String chapterId
    ) {
        EduChapter chapter = chapterService.getById(chapterId);
        return R.ok().data("chapter", chapter);
    }

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

EduChapterController

    /**
     * 根据章节ID修改章节信息
     * @param chapter 章节信息
     * @return
     */
    @PostMapping("/updateChapter")
    public R updateChapter(
            @ApiParam(name = "EduChapter", value = "章节对象", required = true) @RequestBody EduChapter chapter
    ) {
        chapterService.updateById(chapter);
        return R.ok();
    }

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

EduChapterService

    /**
     * 删除指定章节ID的信息
     * @param chapterId 章节ID
     * @return
     */
    boolean deleteChapter(String chapterId);

EduChapterServiceImpl

    /**
     * 删除指定章节ID的信息
     * @param chapterId 章节ID
     * @return
     */
    @Override
    public boolean deleteChapter(String chapterId) {
        // 查询该章节下是否有小节
        LambdaQueryWrapper<EduVideo> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(EduVideo::getChapterId, chapterId);
        int count = eduVideoService.count(queryWrapper);

        if(count > 0) {
            // 该章节下有小节,不可以删除
            throw new GuliException(20001, "该章节下有小节,不能删除!");
        } else {
            // 该章节下没有小节,可以删除
            boolean result = this.removeById(chapterId);
            return result;
        }
    }

EduChapterController

    /**
     * 删除指定章节ID的信息
     * @param chapterId 章节ID
     * @return
     */
    @DeleteMapping("/{chapterId}")
    public R deleteChapter(
            @ApiParam(name = "chapterId", value = "章节ID", required = true) @PathVariable String chapterId
    ) {
        boolean flag = chapterService.deleteChapter(chapterId);
        if(flag) {
            return R.ok();
        } else {
            return R.error();
        }
    }

3.2、前端

3.2.1、定义 API

src\api\edu\chapter.js

    /**
     * 添加章节
     * @param {*} chapter 章节信息
     * @returns 
     */
    addChapter(chapter) {
        return request({
            url: `/eduservice/chapter/addChapter`,
            method: 'POST',
            data: chapter
        })
    },

    /**
     * 根据章节ID查询章节信息
     * @param {*} chapterId 章节ID
     * @returns 
     */
    getChapterById(chapterId) {
        return request({
            url: `/eduservice/chapter/getChapterInfo/${chapterId}`,
            method: 'GET'
        })
    },

    /**
     * 根据章节ID修改章节信息
     * @param {*} chapter 章节信息
     * @returns 
     */
    updateChapter(chapter) {
        return request({
            url: `/eduservice/chapter/updateChapter`,
            method: 'POST',
            data: chapter
        })
    },

    /**
     * 删除指定章节ID的信息
     * @param {*} chapterId 章节ID
     * @returns 
     */
    deleteChapter(chapterId) {
        return request({
            url: `/eduservice/chapter/${chapterId}`,
            method: 'DELETE'
        })
    }

3.2.2、添加章节的按钮和 dialog

        <!-- 添加章节按钮 -->
        <el-button type="text" @click="openChapterDialog()">添加章节</el-button>
        
        <!-- 添加和修改章节表单dialog -->
        <el-dialog :visible.sync="dialogChapterFormVisible" title="添加章节">
            <el-form :model="chapter" label-width="120px" :rules="rules" ref="chapterForm">
                <el-form-item label="章节标题" prop="title">
                    <el-input v-model="chapter.title" />
                </el-form-item>
                <el-form-item label="章节排序" prop="sort">
                    <el-input-number v-model="chapter.sort" :min="0" />
                </el-form-item>
            </el-form>
            <div slot="footer" class="dialog-footer">
                <el-button @click="dialogChapterFormVisible = false">取 消</el-button>
                <el-button type="primary" @click="saveOrUpdate">确 定</el-button>
            </div>
        </el-dialog>

在 data 中定义

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

3.2.3、添加章节

在 methods 中添加方法

            /**
             * 添加或修改章节
             */
            saveOrUpdate() {
                this.addChapter();
            },

            /**
             * 添加章节
             */
            addChapter() {
                this.chapter.courseId = this.courseId;  // 设置课程ID
                this.$refs['chapterForm'].validate((valid) => {
                    if (valid) {
                        chapter.addChapter(this.chapter).then(response => {
                            // 关闭弹框
                            this.dialogChapterFormVisible = false;

                            // 提示信息
                            this.$message({
                                type: 'success',
                                message: '添加成功!'
                            });

                            // 刷新页面(重新加载数据)
                            this.getAllChapterVideo();
                        });
                    } else {
                        return false;
                    }
                });
            },

            /**
             * 点击添加章节按钮后打开弹框
             */
            openChapterDialog() {
                // 初始化
                this.chapter.title = '';
                this.chapter.sort = undefined;

                this.dialogChapterFormVisible = true;   // 打开弹框
            }

3.2.4、修改章节

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

                <p>
                    {{ chapter.title }}
                    <span class="acts">
                        <el-button type="text">添加课时</el-button>
                        <el-button style="" type="text" @click="openEditChapterDialog(chapter.id)">编辑</el-button>
                        <el-button type="text">删除</el-button>
                    </span>
                </p>

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

            /**
             * 添加或修改章节
             */
            saveOrUpdate() {
                // 根据章节ID判断进行添加还是修改操作(注意,不是课程ID)
                if(!this.chapter.id) {
                    this.addChapter();
                } else {
                    this.updateChapter();
                }
            },

            /**
             * 点击编辑按钮后打开弹框
             * @param {*} chapterId 
             */
            openEditChapterDialog(chapterId) {
                // 初始化
                chapter.getChapterById(chapterId).then(response => {
                    this.chapter = response.data.chapter;
                });

                this.dialogChapterFormVisible = true;   // 打开弹框
            },

            /**
             * 修改章节
             */
            updateChapter() {
                this.$refs['chapterForm'].validate((valid) => {
                    if (valid) {
                        chapter.updateChapter(this.chapter).then(response => {
                            // 关闭弹框
                            this.dialogChapterFormVisible = false;

                            // 提示信息
                            this.$message({
                                type: 'success',
                                message: '修改成功!'
                            });

                            // 刷新页面(重新加载数据)
                            this.getAllChapterVideo();
                        });
                    } else {
                        return false;
                    }
                });
            }

3.2.5、删除章节

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

                <p>
                    {{ chapter.title }}
                    <span class="acts">
                        <el-button type="text">添加课时</el-button>
                        <el-button type="text" @click="openEditChapterDialog(chapter.id)">编辑</el-button>
                        <el-button type="text" @click="removeChapter(chapter.id)">删除</el-button>
                    </span>
                </p>

在 methods 中添加删除方法

            /**
             * 删除章节
             * @param {} chapterId 
             */
            removeChapter(chapterId) {
                this.$confirm('此操作将永久删除该章节信息, 是否继续?', '提示', {
                    confirmButtonText: '确定',
                    cancelButtonText: '取消',
                    type: 'warning'
                })
                // 确认删除
                .then(() => {
                    chapter.deleteChapter(chapterId)
                        // 删除成功
                        .then(response => {
                            // 提示信息
                            this.$message({
                                type: 'success',
                                message: '删除成功!'
                            });
                            // 刷新页面(重新加载数据)
                            this.getAllChapterVideo();
                        })
                })
                // 取消删除
                .catch(() => {
                    this.$message({
                        type: 'info',
                        message: '已取消删除'
                    });
                });
            }

4、小节管理

4.1、后端

4.1.1、添加小节

EduVideoController

    /**
     * 添加小节
     * @param video 小节信息
     * @return
     */
    @ApiOperation(value = "添加小节")
    @PostMapping("/addVideo")
    public R addVideo(
            @ApiParam(name = "EduVideo", value = "小节信息", required = true) @RequestBody EduVideo video
    ) {
        videoService.save(video);
        return R.ok();
    }

4.1.2、删除小节

EduVideoController

    /**
     * 删除小节
     * @param videoId 小节ID
     * @return
     */
    @DeleteMapping("/{videoId}")
    public R deleteVideo(
            @ApiParam(name = "videoId", value = "小节ID", required = true) @PathVariable String videoId
    ) {
        videoService.removeById(videoId);
        return R.ok();
    }

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

EduVideoController

    /**
     * 根据小节ID查询小节信息
     * @param videoId
     * @return
     */
    @GetMapping("/getVideoInfo/{videoId}")
    public R getVideoInfo(
            @ApiParam(name = "videoId", value = "课时ID", required = true) @PathVariable String videoId
    ) {
        EduVideo video = videoService.getById(videoId);
        return R.ok().data("video", video);
    }

4.1.4、修改小节信息

EduVideoController

    /**
     * 修改小节信息
     * @param eduVideo 小节信息
     * @return
     */
    @PostMapping("/updateVideo")
    public R updateVideo(
            @ApiParam(name = "eduVideo", value = "课时基本信息", required = true) @RequestBody EduVideo eduVideo
    ) {
        videoService.updateById(eduVideo);
        return R.ok();
    }

4.2、前端

4.2.1、定义 API

src\api\edu\video.js

import request from '@/utils/request'

export default {

    /**
     * 添加小节
     * @param {*} video 小节信息
     * @returns 
     */
    addVideo(video) {
        return request({
            url: `/eduservice/video/addVideo`,
            method: 'POST',
            data: video
        })
    },

    /**
     * 删除指定小节ID的信息
     * @param {*} videoId 小节ID
     * @returns 
     */
    deleteChapter(videoId) {
        return request({
            url: `/eduservice/video/${videoId}`,
            method: 'DELETE'
        })
    },

    /**
     * 根据小节ID查询小节信息
     * @param {*} videoId 小节ID
     * @returns 
     */
    getVideoById(videoId) {
        return request({
            url: `/eduservice/video/getVideoInfo/${videoId}`,
            method: 'GET'
        })
    },

    /**
     * 根据小节ID修改小节信息
     * @param {*} video 小节信息
     * @returns 
     */
    updateVideo(video) {
        return request({
            url: `/eduservice/video/updateVideo`,
            method: 'POST',
            data: video
        })
    },

}

并在页面中引入

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

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

        <!-- 添加和修改小节表单dialog -->
        <el-dialog :visible.sync="dialogVideoFormVisible" title="添加课时">
            <el-form :model="video" label-width="120px">
                <el-form-item label="课时标题">
                    <el-input v-model="video.title" />
                </el-form-item>
                <el-form-item label="课时排序">
                    <el-input-number v-model="video.sort" :min="0" controls-position="right" />
                </el-form-item>
                <el-form-item label="是否免费">
                    <el-radio-group v-model="video.free">
                        <el-radio :label="true">免费</el-radio>
                        <el-radio :label="false">默认</el-radio>
                    </el-radio-group>
                </el-form-item>
                <el-form-item label="上传视频">
                    <!-- TODO -->
                </el-form-item>
            </el-form>
            <div slot="footer" class="dialog-footer">
                <el-button @click="dialogVideoFormVisible = false">取 消</el-button>
                <el-button :disabled="saveVideoBtnDisabled" type="primary" @click="saveOrUpdateVideo">确 定</el-button>
            </div>
        </el-dialog>

在 data 中定义

                dialogVideoFormVisible: false,   // 添加和修改小节的表单dialog是否可见
                video: {    // 封装小节数据
                    title: '',
                    sort: undefined,
                    free: undefined,
                    videoSourceId: ''
                },
                videoRules: {
                    title: [
                        { required: true, message: '请输入小节标题', trigger: 'blur' },
                        { min: 2, max: 15, message: '长度在 2 到 15 个字符', trigger: 'blur' }
                    ],
                    sort: [
                        { required: true, message: '请输入章节排序', type: 'number', trigger: 'blur' }
                    ],
                    free: [
                        { required: true, message: '请选择是否免费', trigger: 'blur' }
                    ]
                }

4.2.3、添加小节

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

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

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

            <div slot="footer" class="dialog-footer">
                <el-button @click="dialogVideoFormVisible = false">取 消</el-button>
                <el-button type="primary" @click="saveOrUpdateVideo">确 定</el-button>
            </div>

在 methods 中添加方法

            /**
             * 点击添加小节打开弹框,并初始化video对象
             * @param {*} chapterId 本次添加的小节所属的章节的ID
             */
            openVideo(chapterId) {
                // 初始化
                this.video = {
                    title: '',
                    sort: undefined,
                    isFree: undefined,
                    videoSourceId: ''
                };
                this.video.chapterId = chapterId;

                // 显示弹框
                this.dialogVideoFormVisible = true;
            },

            saveOrUpdateVideo() {
                if(!this.video.id) {
                    this.addVideo();
                } else {
                    this.updateVideo();
                }
            },

            /**
             * 添加小节
             */
            addVideo() {
                this.$refs['videoForm'].validate((valid) => {
                    if (valid) {
                        this.video.courseId = this.courseId;    // 设置课程ID
                        video.addVideo(this.video).then(response => {
                            // 关闭弹框
                            this.dialogVideoFormVisible = false;

                            // 提示信息
                            this.$message({
                                type: 'success',
                                message: '添加成功!'
                            });

                            // 刷新页面(重新加载数据)
                            this.getAllChapterVideo();
                        });
                    } else {
                        return false;
                    }
                });
            },

4.2.4、修改小节

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

                <!-- 小节 -->
                <ul class="chanpterList videoList">
                    <li v-for="video in chapter.children" :key="video.id">
                        <p>
                            {{ video.title }}
                            <span class="acts">
                                <el-button type="text" @click="openEditVideoDialog(video.id)">编辑</el-button>
                                <el-button type="text" @click="removeVideo(video.id)">删除</el-button>
                            </span>
                        </p>
                    </li>
                </ul>

在 methods 中添加方法

            openEditVideoDialog(videoId) {
                // 初始化
                video.getVideoById(videoId).then(response => {
                    this.video = response.data.video;
                });

                this.dialogVideoFormVisible = true;     // 打开弹框
            },

            updateVideo() {
                this.$refs['videoForm'].validate((valid) => {
                    if (valid) {
                        video.updateVideo(this.video).then(response => {
                            // 关闭弹框
                            this.dialogVideoFormVisible = false;

                            // 提示信息
                            this.$message({
                                type: 'success',
                                message: '添加成功!'
                            });

                            // 刷新页面(重新加载数据)
                            this.getAllChapterVideo();
                        });
                    } else {
                        return false;
                    }
                });
            },

4.2.5、删除小节

在删除小节按钮绑定事件

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

在 methods 中添加方法

            /**
             * 删除小节
             * @param {*} videoId 小节Id
             */
            removeVideo(videoId) {
                this.$confirm('此操作将永久删除该小节信息, 是否继续?', '提示', {
                    confirmButtonText: '确定',
                    cancelButtonText: '取消',
                    type: 'warning'
                })
                // 确认删除
                .then(() => {
                    video.deleteChapter(videoId)
                        // 删除成功
                        .then(response => {
                            // 提示信息
                            this.$message({
                                type: 'success',
                                message: '删除成功!'
                            });
                            // 刷新页面(重新加载数据)
                            this.getAllChapterVideo();
                        })
                })
                // 取消删除
                .catch(() => {
                    this.$message({
                        type: 'info',
                        message: '已取消删除'
                    });
                });
            },

5、完整页面内容

<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-button type="text" @click="openChapterDialog()">添加章节</el-button>
        
        <!-- 添加和修改章节表单dialog -->
        <el-dialog :visible.sync="dialogChapterFormVisible" title="添加章节">
            <el-form :model="chapter" label-width="120px" :rules="rules" ref="chapterForm">
                <el-form-item label="章节标题" prop="title">
                    <el-input v-model="chapter.title" />
                </el-form-item>
                <el-form-item label="章节排序" prop="sort">
                    <el-input-number v-model="chapter.sort" :min="0" />
                </el-form-item>
            </el-form>
            <div slot="footer" class="dialog-footer">
                <el-button @click="dialogChapterFormVisible = false">取 消</el-button>
                <el-button type="primary" @click="saveOrUpdate">确 定</el-button>
            </div>
        </el-dialog>

        <!-- 章节 -->
        <ul class="chanpterList">
            <li v-for="chapter in chapterVideoList" :key="chapter.id">
                <p>
                    {{ chapter.title }}
                    <span class="acts">
                        <el-button type="text" @click="openVideo(chapter.id)">添加小节</el-button>
                        <el-button type="text" @click="openEditChapterDialog(chapter.id)">编辑</el-button>
                        <el-button type="text" @click="removeChapter(chapter.id)">删除</el-button>
                    </span>
                </p>
        
                <!-- 小节 -->
                <ul class="chanpterList videoList">
                    <li v-for="video in chapter.children" :key="video.id">
                        <p>
                            {{ video.title }}
                            <span class="acts">
                                <el-button type="text" @click="openEditVideoDialog(video.id)">编辑</el-button>
                                <el-button type="text" @click="removeVideo(video.id)">删除</el-button>
                            </span>
                        </p>
                    </li>
                </ul>
            </li>
        </ul>

        <!-- 添加和修改小节表单dialog -->
        <el-dialog :visible.sync="dialogVideoFormVisible" title="添加课时">
            <el-form :model="video" label-width="120px" :rules="videoRules" ref="videoForm">
                <el-form-item label="课时标题" prop="title">
                    <el-input v-model="video.title" />
                </el-form-item>
                <el-form-item label="课时排序" prop="sort">
                    <el-input-number v-model="video.sort" :min="0" controls-position="right" />
                </el-form-item>
                <el-form-item label="是否免费" prop="isFree">
                    <el-radio-group v-model="video.isFree">
                        <el-radio :label="true">免费</el-radio>
                        <el-radio :label="false">默认</el-radio>
                    </el-radio-group>
                </el-form-item>
                <el-form-item label="上传视频">
                    <!-- TODO -->
                </el-form-item>
            </el-form>
            <div slot="footer" class="dialog-footer">
                <el-button @click="dialogVideoFormVisible = false">取 消</el-button>
                <el-button type="primary" @click="saveOrUpdateVideo">确 定</el-button>
            </div>
        </el-dialog>

        <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>
    import chapter from '@/api/edu/chapter';
    import video from '@/api/edu/video';

    export default {
        data() {
            return {
                saveBtnDisabled: false,
                chapterVideoList: [],   // 章节和小节信息
                courseId: '',    // 课程ID
                dialogChapterFormVisible: false,     // 添加章节的弹框dialog是否可见
                chapter: {  // 封装章节数据
                    title: '',
                    sort: undefined
                },
                rules: {    // 表单校验规则
                    title: [
                        { required: true, message: '请输入章节标题', trigger: 'blur' },
                        { min: 2, max: 15, message: '长度在 2 到 15 个字符', trigger: 'blur' }
                    ],
                    sort: [
                        { required: true, message: '请输入章节排序', type: 'number', trigger: 'blur' }
                    ],
                },
                dialogVideoFormVisible: false,   // 添加和修改小节的表单dialog是否可见
                video: {    // 封装小节数据
                    title: '',
                    sort: undefined,
                    isFree: undefined,
                    videoSourceId: ''
                },
                videoRules: {
                    title: [
                        { required: true, message: '请输入小节标题', trigger: 'blur' },
                        { min: 2, max: 15, message: '长度在 2 到 15 个字符', trigger: 'blur' }
                    ],
                    sort: [
                        { required: true, message: '请输入章节排序', type: 'number', trigger: 'blur' }
                    ],
                    isFree: [
                        { required: true, message: '请选择是否免费', trigger: 'blur' }
                    ]
                }
            }
        },
        created() {
            // 获取路由ID值
            if(this.$route.params && this.$route.params.id) {
                this.courseId = this.$route.params.id;
                // console.log('course = ' + this.courseId);
            }

            // 根据课程ID获取所有章节和小节信息
            this.getAllChapterVideo();
        },
        methods: {
            /**
             * 点击上一步跳转到课程信息页面,并携带课程ID参数
             */
            previous() {
                this.$router.push({ path: '/course/info/' + this.courseId });
            },

            /**
             * 点击下一步跳转到课程发布页面,并携带课程ID参数
             */
            next() {
                this.$router.push({ path: '/course/publish/' + this.courseId });
            },

            /**
             * 根据课程ID获取所有章节和小节信息
             */
            getAllChapterVideo() {
                chapter.getAllChapterVideo(this.courseId).then(response => {
                    this.chapterVideoList = response.data.allChapterVideo;
                    // console.log(this.chapterVideoList);
                });
            },

            /**
             * 添加或修改章节
             */
            saveOrUpdate() {
                // 根据章节ID判断进行添加还是修改操作(注意,不是课程ID)
                if(!this.chapter.id) {
                    this.addChapter();
                } else {
                    this.updateChapter();
                }
            },

            /**
             * 添加章节
             */
            addChapter() {
                this.chapter.courseId = this.courseId;  // 设置课程ID
                this.$refs['chapterForm'].validate((valid) => {
                    if (valid) {
                        chapter.addChapter(this.chapter).then(response => {
                            // 关闭弹框
                            this.dialogChapterFormVisible = false;

                            // 提示信息
                            this.$message({
                                type: 'success',
                                message: '添加成功!'
                            });

                            // 刷新页面(重新加载数据)
                            this.getAllChapterVideo();
                        });
                    } else {
                        return false;
                    }
                });
            },

            /**
             * 点击添加章节按钮后打开弹框
             */
            openChapterDialog() {
                // 初始化
                this.chapter.title = '';
                this.chapter.sort = undefined;

                this.dialogChapterFormVisible = true;   // 打开弹框
            },

            /**
             * 点击编辑按钮后打开弹框
             * @param {*} chapterId 
             */
            openEditChapterDialog(chapterId) {
                // 初始化
                chapter.getChapterById(chapterId).then(response => {
                    this.chapter = response.data.chapter;
                });

                this.dialogChapterFormVisible = true;   // 打开弹框
            },

            /**
             * 修改章节
             */
            updateChapter() {
                this.$refs['chapterForm'].validate((valid) => {
                    if (valid) {
                        chapter.updateChapter(this.chapter).then(response => {
                            // 关闭弹框
                            this.dialogChapterFormVisible = false;

                            // 提示信息
                            this.$message({
                                type: 'success',
                                message: '修改成功!'
                            });

                            // 刷新页面(重新加载数据)
                            this.getAllChapterVideo();
                        });
                    } else {
                        return false;
                    }
                });
            },

            /**
             * 删除章节
             * @param {} chapterId 
             */
            removeChapter(chapterId) {
                this.$confirm('此操作将永久删除该章节信息, 是否继续?', '提示', {
                    confirmButtonText: '确定',
                    cancelButtonText: '取消',
                    type: 'warning'
                })
                // 确认删除
                .then(() => {
                    chapter.deleteChapter(chapterId)
                        // 删除成功
                        .then(response => {
                            // 提示信息
                            this.$message({
                                type: 'success',
                                message: '删除成功!'
                            });
                            // 刷新页面(重新加载数据)
                            this.getAllChapterVideo();
                        })
                })
                // 取消删除
                .catch(() => {
                    this.$message({
                        type: 'info',
                        message: '已取消删除'
                    });
                });
            },

//============================ 以下为小节操作 ==============================================

            /**
             * 点击添加小节打开弹框,并初始化video对象
             * @param {*} chapterId 本次添加的小节所属的章节的ID
             */
            openVideo(chapterId) {
                // 初始化
                this.video = {
                    title: '',
                    sort: undefined,
                    isFree: undefined,
                    videoSourceId: ''
                };
                this.video.chapterId = chapterId;

                // 显示弹框
                this.dialogVideoFormVisible = true;
            },

            saveOrUpdateVideo() {
                if(!this.video.id) {
                    this.addVideo();
                } else {
                    this.updateVideo();
                }
            },

            /**
             * 添加小节
             */
            addVideo() {
                this.$refs['videoForm'].validate((valid) => {
                    if (valid) {
                        this.video.courseId = this.courseId;    // 设置课程ID
                        video.addVideo(this.video).then(response => {
                            // 关闭弹框
                            this.dialogVideoFormVisible = false;

                            // 提示信息
                            this.$message({
                                type: 'success',
                                message: '添加成功!'
                            });

                            // 刷新页面(重新加载数据)
                            this.getAllChapterVideo();
                        });
                    } else {
                        return false;
                    }
                });
            },

            updateVideo() {
                this.$refs['videoForm'].validate((valid) => {
                    if (valid) {
                        video.updateVideo(this.video).then(response => {
                            // 关闭弹框
                            this.dialogVideoFormVisible = false;

                            // 提示信息
                            this.$message({
                                type: 'success',
                                message: '添加成功!'
                            });

                            // 刷新页面(重新加载数据)
                            this.getAllChapterVideo();
                        });
                    } else {
                        return false;
                    }
                });
            },

            /**
             * 删除小节
             * @param {*} videoId 小节Id
             */
            removeVideo(videoId) {
                this.$confirm('此操作将永久删除该小节信息, 是否继续?', '提示', {
                    confirmButtonText: '确定',
                    cancelButtonText: '取消',
                    type: 'warning'
                })
                // 确认删除
                .then(() => {
                    video.deleteChapter(videoId)
                        // 删除成功
                        .then(response => {
                            // 提示信息
                            this.$message({
                                type: 'success',
                                message: '删除成功!'
                            });
                            // 刷新页面(重新加载数据)
                            this.getAllChapterVideo();
                        })
                })
                // 取消删除
                .catch(() => {
                    this.$message({
                        type: 'info',
                        message: '已取消删除'
                    });
                });
            },

            openEditVideoDialog(videoId) {
                // 初始化
                video.getVideoById(videoId).then(response => {
                    this.video = response.data.video;
                });

                this.dialogVideoFormVisible = true;     // 打开弹框
            },

            
        }
    }
</script>
<style scoped>
.chanpterList {
    position: relative;
    list-style: none;
    margin: 0;
    padding: 0;
}

.chanpterList li {
    position: relative;
}

.chanpterList p {
    float: left;
    font-size: 20px;
    margin: 10px 0;
    padding: 10px;
    height: 70px;
    line-height: 50px;
    width: 100%;
    border: 1px solid #DDD;
    position: relative;
}

.chanpterList .acts {
    float: right;
    font-size: 14px;
    position: relative;
    z-index: 1;
}

.videoList {
    padding-left: 50px;
}

.videoList p {
    float: left;
    font-size: 14px;
    margin: 10px 0;
    padding: 10px;
    height: 50px;
    line-height: 30px;
    width: 100%;
    border: 1px dotted #DDD;
}
</style>

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值