|
@@ -0,0 +1,976 @@
|
|
|
|
+<template>
|
|
|
|
+ <section>
|
|
|
|
+ <!--工具条-->
|
|
|
|
+ <el-col :span="24" class="toolbar" style="padding-bottom: 0px;">
|
|
|
|
+ <el-form :inline="true" @submit.native.prevent>
|
|
|
|
+ <el-form-item label="课程分类">
|
|
|
|
+ <el-select v-model="categoryValue" placeholder="请选择" clearable @change="searchList" size="small">
|
|
|
|
+ <el-option v-for="item in categoryOptions" :key="item.value" :label="item.label"
|
|
|
|
+ :value="item.value">
|
|
|
|
+ </el-option>
|
|
|
|
+ </el-select>
|
|
|
|
+ </el-form-item>
|
|
|
|
+ <el-form-item label="课程名称">
|
|
|
|
+ <el-input v-model="keyword" placeholder="请输入" clearable @change="searchList"
|
|
|
|
+ size="small"></el-input>
|
|
|
|
+ </el-form-item>
|
|
|
|
+ <el-form-item label="讲师">
|
|
|
|
+ <el-input v-model="instructor" placeholder="请输入" clearable @change="searchList"
|
|
|
|
+ size="small"></el-input>
|
|
|
|
+ </el-form-item>
|
|
|
|
+ <el-form-item>
|
|
|
|
+ <el-button type="primary" @click="addCourse()" size="small">添加课程</el-button>
|
|
|
|
+ </el-form-item>
|
|
|
|
+ <el-form-item>
|
|
|
|
+ <el-button type="primary" @click="batchManage" size="small">分类管理</el-button>
|
|
|
|
+ </el-form-item>
|
|
|
|
+ </el-form>
|
|
|
|
+ </el-col>
|
|
|
|
+
|
|
|
|
+ <!--列表-->
|
|
|
|
+ <el-table :data="list" highlight-current-row v-loading="listLoading" :height="tableHeight" style="width: 100%;">
|
|
|
|
+ <el-table-column type="index" width="50" align="center" label="#"></el-table-column>
|
|
|
|
+ <el-table-column label="封面" width="120" align="center">
|
|
|
|
+ <template slot-scope="scope">
|
|
|
|
+ <img v-if="scope.row.coverImage" :src="scope.row.coverImage" class="course-cover-image" />
|
|
|
|
+ <span v-else>无封面</span>
|
|
|
|
+ </template>
|
|
|
|
+ </el-table-column>
|
|
|
|
+ <el-table-column prop="courseTypeName" label="课程分类" min-width="120" align="center"></el-table-column>
|
|
|
|
+ <el-table-column prop="courseName" label="课程名称" min-width="180" align="center"></el-table-column>
|
|
|
|
+ <el-table-column prop="courseInstructor" label="讲师" min-width="120" align="center"></el-table-column>
|
|
|
|
+ <el-table-column prop="coursePrice" label="价格" min-width="100" align="center">
|
|
|
|
+ <template slot-scope="scope">
|
|
|
|
+ <span>¥{{ scope.row.coursePrice }}</span>
|
|
|
|
+ </template>
|
|
|
|
+ </el-table-column>
|
|
|
|
+ <el-table-column prop="courseDuration" label="课程时间" min-width="120" align="center">
|
|
|
|
+ <template slot-scope="scope">
|
|
|
|
+ <span>{{ scope.row.courseDuration ? scope.row.courseDuration + '分钟' : '' }}</span>
|
|
|
|
+ </template>
|
|
|
|
+ </el-table-column>
|
|
|
|
+
|
|
|
|
+ <el-table-column label="操作" width="340" class-name="btns" header-align="center" fixed="right">
|
|
|
|
+ <template slot-scope="scope">
|
|
|
|
+ <el-button size="mini" type="primary" @click="addVideo(scope.row)">添加视频</el-button>
|
|
|
|
+ <el-button size="mini" type="primary" @click="addCourse(scope.row)">编辑</el-button>
|
|
|
|
+ <el-button size="mini" type="danger" @click="deleteItem(scope.row)">删除</el-button>
|
|
|
|
+ <el-button size="mini" :type="scope.row.courseStatus === 1 ? 'warning' : 'success'"
|
|
|
|
+ @click="uploadItem(scope.row)">{{ scope.row.courseStatus === 1 ? '下架' : '上架' }}</el-button>
|
|
|
|
+ </template>
|
|
|
|
+ </el-table-column>
|
|
|
|
+ </el-table>
|
|
|
|
+
|
|
|
|
+ <!--工具条-->
|
|
|
|
+ <el-col :span="24" class="toolbar">
|
|
|
|
+ <el-pagination
|
|
|
|
+ @size-change="handleSizeChange"
|
|
|
|
+ @current-change="handleCurrentChange"
|
|
|
|
+ :page-sizes="[10, 20, 50, 100]"
|
|
|
|
+ :page-size="size"
|
|
|
|
+ layout="total, sizes, prev, pager, next, jumper"
|
|
|
|
+ :total="total"
|
|
|
|
+ style="float:right;"
|
|
|
|
+ ></el-pagination>
|
|
|
|
+ </el-col>
|
|
|
|
+
|
|
|
|
+ <!-- 分类管理 -->
|
|
|
|
+ <el-dialog :visible.sync="categoryManageVisible" title="分类管理" width="600px">
|
|
|
|
+ <el-table :data="categoryList" style="width: 100%" max-height="400">
|
|
|
|
+ <el-table-column prop="label" label="分类名称" width="180"></el-table-column>
|
|
|
|
+ <el-table-column label="封面" width="180">
|
|
|
|
+ <template slot-scope="scope">
|
|
|
|
+ <img v-if="scope.row.coverImage" :src="scope.row.coverImage" class="category-cover-image" />
|
|
|
|
+ <span v-else>无封面</span>
|
|
|
|
+ </template>
|
|
|
|
+ </el-table-column>
|
|
|
|
+ <el-table-column label="操作" width="180">
|
|
|
|
+ <template slot-scope="scope">
|
|
|
|
+ <el-button size="mini" type="primary" @click="setCategoryCover(scope.$index, scope.row)">设置封面</el-button>
|
|
|
|
+ <el-button size="mini" type="danger" @click="deleteCategory(scope.$index, scope.row)">删除</el-button>
|
|
|
|
+ </template>
|
|
|
|
+ </el-table-column>
|
|
|
|
+ </el-table>
|
|
|
|
+ <div style="margin-top: 20px;">
|
|
|
|
+ <el-form :inline="true" :model="newCategory" class="demo-form-inline">
|
|
|
|
+ <el-form-item label="分类名称">
|
|
|
|
+ <el-input v-model="newCategory.label" placeholder="请输入分类名称"></el-input>
|
|
|
|
+ </el-form-item>
|
|
|
|
+ <el-form-item>
|
|
|
|
+ <el-button type="primary" @click="addCategory">添加</el-button>
|
|
|
|
+ </el-form-item>
|
|
|
|
+ </el-form>
|
|
|
|
+ </div>
|
|
|
|
+ <span slot="footer" class="dialog-dialog">
|
|
|
|
+ <el-button @click="categoryManageVisible = false">关 闭</el-button>
|
|
|
|
+ </span>
|
|
|
|
+ </el-dialog>
|
|
|
|
+
|
|
|
|
+ <!-- 设置分类封面 -->
|
|
|
|
+ <el-dialog :visible.sync="coverDialogVisible" title="设置分类封面" width="500px">
|
|
|
|
+ <div class="cover-upload-container">
|
|
|
|
+ <el-upload
|
|
|
|
+ class="cover-uploader"
|
|
|
|
+ action="#"
|
|
|
|
+ :show-file-list="false"
|
|
|
|
+ :on-change="handleCoverChange"
|
|
|
|
+ :auto-upload="false"
|
|
|
|
+ :before-upload="beforeCoverUpload">
|
|
|
|
+ <img v-if="coverImageUrl" :src="coverImageUrl" class="cover-image" />
|
|
|
|
+ <i v-else class="el-icon-plus cover-uploader-icon"></i>
|
|
|
|
+ </el-upload>
|
|
|
|
+ <div class="cover-tip">请上传分类封面图片,建议尺寸 16:9</div>
|
|
|
|
+ </div>
|
|
|
|
+ <span slot="footer" class="dialog-footer">
|
|
|
|
+ <el-button @click="coverDialogVisible = false">取 消</el-button>
|
|
|
|
+ <el-button type="primary" @click="saveCategoryCover">确 定</el-button>
|
|
|
|
+ </span>
|
|
|
|
+ </el-dialog>
|
|
|
|
+
|
|
|
|
+ <!-- 添加课程 -->
|
|
|
|
+ <el-dialog :visible.sync="addDialogVisible" title="添加课程" width="800px" top="6.5vh">
|
|
|
|
+ <el-form :model="courseForm" :rules="courseRules" ref="courseForm" label-width="120px">
|
|
|
|
+ <el-form-item label="课程分类" prop="courseTypeId">
|
|
|
|
+ <el-select v-model="courseForm.courseTypeId" placeholder="请选择" style="width:100%">
|
|
|
|
+ <el-option v-for="item in categoryOptions" :key="item.value"
|
|
|
|
+ :label="item.label" :value="item.value">
|
|
|
|
+ </el-option>
|
|
|
|
+ </el-select>
|
|
|
|
+ </el-form-item>
|
|
|
|
+ <el-form-item label="课程名称" prop="courseName">
|
|
|
|
+ <el-input v-model="courseForm.courseName" placeholder="请输入课程名称"></el-input>
|
|
|
|
+ </el-form-item>
|
|
|
|
+ <el-form-item label="课程介绍" style="height: 200px;">
|
|
|
|
+ <quill-editor style="height: 150px" ref="text" v-model="courseForm.courseDesc" class="myQuillEditor" :options="editorOption"/>
|
|
|
|
+ </el-form-item>
|
|
|
|
+ <el-form-item label="课程价格">
|
|
|
|
+ <el-input-number v-model="courseForm.coursePrice" :min="0" :precision="2"></el-input-number>
|
|
|
|
+ </el-form-item>
|
|
|
|
+ <el-form-item label="课程时间(分钟)">
|
|
|
|
+ <el-input-number v-model="courseForm.courseDuration" :min="0"></el-input-number>
|
|
|
|
+ </el-form-item>
|
|
|
|
+ <el-form-item label="课程封面">
|
|
|
|
+ <el-upload
|
|
|
|
+ class="cover-uploader"
|
|
|
|
+ action="#"
|
|
|
|
+ :show-file-list="false"
|
|
|
|
+ :on-change="handleCourseCoverChange"
|
|
|
|
+ :auto-upload="false"
|
|
|
|
+ :before-upload="beforeCoverUpload">
|
|
|
|
+ <img v-if="courseForm.coverImageUrl" :src="courseForm.coverImageUrl" class="cover-image" />
|
|
|
|
+ <i v-else class="el-icon-plus cover-uploader-icon"></i>
|
|
|
|
+ </el-upload>
|
|
|
|
+ <div class="cover-tip">建议尺寸 16:9,大小不超过2MB</div>
|
|
|
|
+ </el-form-item>
|
|
|
|
+ </el-form>
|
|
|
|
+ <span slot="footer" class="dialog-footer">
|
|
|
|
+ <el-button @click="addDialogVisible = false">取 消</el-button>
|
|
|
|
+ <el-button type="primary" @click="submitCourseForm">确 定</el-button>
|
|
|
|
+ </span>
|
|
|
|
+ </el-dialog>
|
|
|
|
+ </section>
|
|
|
|
+</template>
|
|
|
|
+
|
|
|
|
+<script>
|
|
|
|
+// 富文本样式
|
|
|
|
+import 'quill/dist/quill.core.css'
|
|
|
|
+import 'quill/dist/quill.snow.css'
|
|
|
|
+import 'quill/dist/quill.bubble.css'
|
|
|
|
+// 导入富文本
|
|
|
|
+import { quillEditor } from 'vue-quill-editor'
|
|
|
|
+export default {
|
|
|
|
+ components: {
|
|
|
|
+ quillEditor, // 富文本
|
|
|
|
+ },
|
|
|
|
+ data() {
|
|
|
|
+ return {
|
|
|
|
+ // 课程表单
|
|
|
|
+ courseForm: {
|
|
|
|
+ courseTypeId: '',
|
|
|
|
+ courseName: '',
|
|
|
|
+ courseDesc: '',
|
|
|
|
+ coursePrice: 0,
|
|
|
|
+ courseDuration: 0,
|
|
|
|
+ coverImageUrl: '',
|
|
|
|
+ coverImageFile: null
|
|
|
|
+ },
|
|
|
|
+ courseRules: {
|
|
|
|
+ courseTypeId: [
|
|
|
|
+ { required: true, message: '请选择课程分类', trigger: 'change' }
|
|
|
|
+ ],
|
|
|
|
+ courseName: [
|
|
|
|
+ { required: true, message: '请输入课程名称', trigger: 'blur' }
|
|
|
|
+ ]
|
|
|
|
+ },
|
|
|
|
+ // 课程分类选择
|
|
|
|
+ categoryValue: '',
|
|
|
|
+ categoryOptions: [],
|
|
|
|
+ // 搜索条件
|
|
|
|
+ keyword: null, // 课程名称
|
|
|
|
+ instructor: null, // 讲师
|
|
|
|
+
|
|
|
|
+ // 表格相关
|
|
|
|
+ tableHeight: 0,
|
|
|
|
+ listLoading: false,
|
|
|
|
+ total: 0,
|
|
|
|
+ page: 1,
|
|
|
|
+ size: 20,
|
|
|
|
+ list: [],
|
|
|
|
+
|
|
|
|
+ // 对话框控制
|
|
|
|
+ categoryManageVisible: false,
|
|
|
|
+ editDialogVisible: false,
|
|
|
|
+ addDialogVisible: false,
|
|
|
|
+
|
|
|
|
+ // 分类管理
|
|
|
|
+ categoryList: [],
|
|
|
|
+ newCategory: {
|
|
|
|
+ label: '',
|
|
|
|
+ value: '',
|
|
|
|
+ coverImage: ''
|
|
|
|
+ },
|
|
|
|
+
|
|
|
|
+ // 分类封面设置
|
|
|
|
+ coverDialogVisible: false,
|
|
|
|
+ currentCategoryIndex: -1,
|
|
|
|
+ coverImageUrl: '',
|
|
|
|
+ coverImageFile: null,
|
|
|
|
+
|
|
|
|
+ // 用户信息
|
|
|
|
+ user: sessionStorage.user ? JSON.parse(sessionStorage.user) : {},
|
|
|
|
+ editorOption: { // 富文本框里面的默认值
|
|
|
|
+ placeholder: '请输入内容...',
|
|
|
|
+ modules: {
|
|
|
|
+ toolbar:[
|
|
|
|
+ ['bold', 'italic', 'underline', 'strike'], //加粗,斜体,下划线,删除线
|
|
|
|
+ // ['blockquote', 'code-block'], //引用,代码块
|
|
|
|
+
|
|
|
|
+ [{ 'header': 1 }, { 'header': 2 }], // 标题,键值对的形式;1、2表示字体大小
|
|
|
|
+ // [{ 'list': 'ordered'}, { 'list': 'bullet' }], //列表
|
|
|
|
+ // [{ 'script': 'sub'}, { 'script': 'super' }], // 上下标
|
|
|
|
+ // [{ 'indent': '-1'}, { 'indent': '+1' }], // 缩进
|
|
|
|
+ // [{ 'direction': 'rtl' }], // 文本方向
|
|
|
|
+
|
|
|
|
+ [{ 'size': ['small', false, 'large', 'huge'] }], // 字体大小
|
|
|
|
+ [{ 'header': [1, 2, 3, 4, 5, 6, false] }], //几级标题
|
|
|
|
+
|
|
|
|
+ [{ 'color': [] }, { 'background': [] }], // 字体颜色,字体背景颜色
|
|
|
|
+ // [{ 'font': [] }], //字体
|
|
|
|
+ [{ 'align': [] }], //对齐方式
|
|
|
|
+
|
|
|
|
+ ['clean'], //清除字体样式
|
|
|
|
+ // ['image','video'] //上传图片、上传视频
|
|
|
|
+ [] //上传图片、上传视频
|
|
|
|
+ ], //工具栏设置
|
|
|
|
+ },
|
|
|
|
+ theme: 'snow',
|
|
|
|
+ },
|
|
|
|
+ }
|
|
|
|
+ },
|
|
|
|
+ methods: {
|
|
|
|
+ // 分类管理
|
|
|
|
+ batchManage() {
|
|
|
|
+ this.categoryManageVisible = true;
|
|
|
|
+ // 加载分类数据
|
|
|
|
+ this.http.post('/course-type/list', {}, res => {
|
|
|
|
+ if (res.code == "ok") {
|
|
|
|
+ // 将后端返回的数据转换为前端需要的格式
|
|
|
|
+ this.categoryList = res.data.map(item => ({
|
|
|
|
+ label: item.typeName,
|
|
|
|
+ value: item.id,
|
|
|
|
+ coverImage: item.coverImage || ''
|
|
|
|
+ }));
|
|
|
|
+
|
|
|
|
+ // 同步更新下拉选项
|
|
|
|
+ this.categoryOptions = [...this.categoryList];
|
|
|
|
+ } else {
|
|
|
|
+ this.$message({
|
|
|
|
+ message: res.msg || '获取分类列表失败',
|
|
|
|
+ type: 'error'
|
|
|
|
+ });
|
|
|
|
+ }
|
|
|
|
+ }, error => {
|
|
|
|
+ this.$message({
|
|
|
|
+ message: error || '获取分类列表失败',
|
|
|
|
+ type: 'error'
|
|
|
|
+ });
|
|
|
|
+ });
|
|
|
|
+ },
|
|
|
|
+
|
|
|
|
+ // 搜索课程列表
|
|
|
|
+ searchList() {
|
|
|
|
+ this.page = 1;
|
|
|
|
+ this.getList();
|
|
|
|
+ },
|
|
|
|
+
|
|
|
|
+ // 获取课程列表
|
|
|
|
+ getList() {
|
|
|
|
+ this.listLoading = true;
|
|
|
|
+
|
|
|
|
+ // 调用后端API获取课程列表
|
|
|
|
+ this.http.post('/course-info/list', {
|
|
|
|
+ page: this.page,
|
|
|
|
+ size: this.size,
|
|
|
|
+ courseType: this.categoryValue,
|
|
|
|
+ courseName: this.keyword,
|
|
|
|
+ courseInstructor: this.instructor
|
|
|
|
+ }, res => {
|
|
|
|
+ this.listLoading = false;
|
|
|
|
+ if (res.code == "ok") {
|
|
|
|
+ this.list = res.data.records;
|
|
|
|
+ this.total = res.data.total;
|
|
|
|
+ } else {
|
|
|
|
+ this.$message({
|
|
|
|
+ message: res.msg || '获取课程列表失败',
|
|
|
|
+ type: 'error'
|
|
|
|
+ });
|
|
|
|
+ }
|
|
|
|
+ }, error => {
|
|
|
|
+ this.listLoading = false;
|
|
|
|
+ this.$message({
|
|
|
|
+ message: error || '获取课程列表失败',
|
|
|
|
+ type: 'error'
|
|
|
|
+ });
|
|
|
|
+ });
|
|
|
|
+ },
|
|
|
|
+
|
|
|
|
+ // 分页相关
|
|
|
|
+ handleCurrentChange(val) {
|
|
|
|
+ this.page = val;
|
|
|
|
+ this.getList();
|
|
|
|
+ },
|
|
|
|
+ handleSizeChange(val) {
|
|
|
|
+ this.page = 1;
|
|
|
|
+ this.size = val;
|
|
|
|
+ this.getList();
|
|
|
|
+ },
|
|
|
|
+
|
|
|
|
+ // 删除课程
|
|
|
|
+ deleteItem(row) {
|
|
|
|
+ this.$confirm('确认删除该课程?', '提示', {
|
|
|
|
+ confirmButtonText: '确定',
|
|
|
|
+ cancelButtonText: '取消',
|
|
|
|
+ type: 'warning'
|
|
|
|
+ }).then(() => {
|
|
|
|
+ // 调用后端API删除课程
|
|
|
|
+ this.http.post('/course-info/deleteCourse', {
|
|
|
|
+ id: row.id
|
|
|
|
+ }, res => {
|
|
|
|
+ if (res.code == "ok") {
|
|
|
|
+ this.$message({
|
|
|
|
+ type: 'success',
|
|
|
|
+ message: '删除成功!'
|
|
|
|
+ });
|
|
|
|
+ this.getList();
|
|
|
|
+ } else {
|
|
|
|
+ this.$message({
|
|
|
|
+ message: res.msg || '删除课程失败',
|
|
|
|
+ type: 'error'
|
|
|
|
+ });
|
|
|
|
+ }
|
|
|
|
+ }, error => {
|
|
|
|
+ this.$message({
|
|
|
|
+ message: error || '删除课程失败',
|
|
|
|
+ type: 'error'
|
|
|
|
+ });
|
|
|
|
+ });
|
|
|
|
+ })
|
|
|
|
+ },
|
|
|
|
+
|
|
|
|
+ // 上架/下架课程
|
|
|
|
+ uploadItem(row) {
|
|
|
|
+ const action = row.courseStatus === 1 ? '下架' : '上架';
|
|
|
|
+ this.$confirm(`确认${action}该课程?`, '提示', {
|
|
|
|
+ confirmButtonText: '确定',
|
|
|
|
+ cancelButtonText: '取消',
|
|
|
|
+ type: 'warning'
|
|
|
|
+ }).then(() => {
|
|
|
|
+ // 调用后端API上架/下架课程
|
|
|
|
+ this.http.post('/course-info/saveOrUpdate', {
|
|
|
|
+ id: row.id,
|
|
|
|
+ courseStatus: row.courseStatus === 1 ? 0 : 1 // 0: 下架, 1: 上架
|
|
|
|
+ }, res => {
|
|
|
|
+ if (res.code == "ok") {
|
|
|
|
+ row.courseStatus = row.courseStatus === 1 ? 0 : 1;
|
|
|
|
+ this.$message({
|
|
|
|
+ type: 'success',
|
|
|
|
+ message: `${action}成功!`
|
|
|
|
+ });
|
|
|
|
+ } else {
|
|
|
|
+ this.$message({
|
|
|
|
+ message: res.msg || `${action}失败`,
|
|
|
|
+ type: 'error'
|
|
|
|
+ });
|
|
|
|
+ }
|
|
|
|
+ }, error => {
|
|
|
|
+ this.$message({
|
|
|
|
+ message: error || `${action}失败`,
|
|
|
|
+ type: 'error'
|
|
|
|
+ });
|
|
|
|
+ });
|
|
|
|
+ })
|
|
|
|
+ },
|
|
|
|
+
|
|
|
|
+ // 保存分类封面
|
|
|
|
+ saveCategoryCover() {
|
|
|
|
+ if (!this.coverImageUrl) {
|
|
|
|
+ this.$message.warning('请先上传封面图片!');
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 关闭对话框
|
|
|
|
+ this.coverDialogVisible = false;
|
|
|
|
+ this.$message({
|
|
|
|
+ type: 'success',
|
|
|
|
+ message: '设置封面成功!'
|
|
|
|
+ });
|
|
|
|
+ },
|
|
|
|
+
|
|
|
|
+ // 处理封面图片变更
|
|
|
|
+ handleCoverChange(file) {
|
|
|
|
+ this.coverImageFile = file.raw;
|
|
|
|
+ if (this.coverImageFile) {
|
|
|
|
+ this.coverImageUrl = URL.createObjectURL(this.coverImageFile);
|
|
|
|
+
|
|
|
|
+ // 获取当前分类的ID
|
|
|
|
+ if (this.currentCategoryIndex >= 0) {
|
|
|
|
+ const categoryId = this.categoryList[this.currentCategoryIndex].value;
|
|
|
|
+
|
|
|
|
+ // 立即上传封面图片
|
|
|
|
+ this.listLoading = true;
|
|
|
|
+
|
|
|
|
+ // 创建FormData对象用于上传文件
|
|
|
|
+ const formData = new FormData();
|
|
|
|
+ formData.append('id', categoryId);
|
|
|
|
+ formData.append('coverImage', this.coverImageFile);
|
|
|
|
+
|
|
|
|
+ // 调用上传图片的API
|
|
|
|
+ this.http.uploadFile('/course-type/uploadCover', formData, res => {
|
|
|
|
+ this.listLoading = false;
|
|
|
|
+ if (res.code == "ok") {
|
|
|
|
+ // 上传成功后,获取返回的图片URL
|
|
|
|
+ const imageUrl = res.data && res.data.url ? res.data.url : this.coverImageUrl;
|
|
|
|
+
|
|
|
|
+ // 更新本地数据
|
|
|
|
+ this.categoryList[this.currentCategoryIndex].coverImage = imageUrl;
|
|
|
|
+
|
|
|
|
+ // 同步更新到分类选项中
|
|
|
|
+ const optionIndex = this.categoryOptions.findIndex(item => item.value === categoryId);
|
|
|
|
+ if (optionIndex !== -1) {
|
|
|
|
+ this.categoryOptions[optionIndex].coverImage = imageUrl;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ this.$message({
|
|
|
|
+ type: 'success',
|
|
|
|
+ message: '封面图片上传成功!'
|
|
|
|
+ });
|
|
|
|
+ } else {
|
|
|
|
+ this.$message({
|
|
|
|
+ message: res.msg || '上传封面图片失败',
|
|
|
|
+ type: 'error'
|
|
|
|
+ });
|
|
|
|
+ }
|
|
|
|
+ }, error => {
|
|
|
|
+ this.listLoading = false;
|
|
|
|
+ this.$message({
|
|
|
|
+ message: error || '上传封面图片失败',
|
|
|
|
+ type: 'error'
|
|
|
|
+ });
|
|
|
|
+ });
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ },
|
|
|
|
+
|
|
|
|
+ // 设置分类封面
|
|
|
|
+ setCategoryCover(index, row) {
|
|
|
|
+ this.currentCategoryIndex = index;
|
|
|
|
+ this.coverImageUrl = row.coverImage || '';
|
|
|
|
+ this.coverDialogVisible = true;
|
|
|
|
+ },
|
|
|
|
+
|
|
|
|
+ // 处理封面图片上传前的验证
|
|
|
|
+ beforeCoverUpload(file) {
|
|
|
|
+ const isImage = file.type.indexOf('image/') === 0;
|
|
|
|
+ const isLt2M = file.size / 1024 / 1024 < 2;
|
|
|
|
+
|
|
|
|
+ if (!isImage) {
|
|
|
|
+ this.$message.error('上传封面图片只能是图片格式!');
|
|
|
|
+ }
|
|
|
|
+ if (!isLt2M) {
|
|
|
|
+ this.$message.error('上传封面图片大小不能超过 2MB!');
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return isImage && isLt2M;
|
|
|
|
+ },
|
|
|
|
+
|
|
|
|
+ // 添加课程
|
|
|
|
+ addCourse(row) {
|
|
|
|
+ this.addDialogVisible = true;
|
|
|
|
+ if(!row) {
|
|
|
|
+ this.courseForm = {
|
|
|
|
+ courseTypeId: '',
|
|
|
|
+ courseName: '',
|
|
|
|
+ courseDesc: '',
|
|
|
|
+ coursePrice: 0,
|
|
|
|
+ courseDuration: 0,
|
|
|
|
+ coverImageUrl: '',
|
|
|
|
+ };
|
|
|
|
+ if (this.$refs.courseForm) {
|
|
|
|
+ this.$refs.courseForm.resetFields();
|
|
|
|
+ }
|
|
|
|
+ } else {
|
|
|
|
+ console.log(row, '<==== 看看数据')
|
|
|
|
+ const { courseTypeId, courseName, courseDesc, coursePrice, courseDuration, coverImage, id } = row
|
|
|
|
+ this.courseForm = {
|
|
|
|
+ id,
|
|
|
|
+ courseTypeId,
|
|
|
|
+ courseName,
|
|
|
|
+ courseDesc,
|
|
|
|
+ coursePrice,
|
|
|
|
+ courseDuration,
|
|
|
|
+ coverImageUrl: this.checkAndAddUpload(coverImage)
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ },
|
|
|
|
+
|
|
|
|
+ // 提交课程表单
|
|
|
|
+ submitCourseForm() {
|
|
|
|
+ this.$refs.courseForm.validate(valid => {
|
|
|
|
+ if (!valid) {
|
|
|
|
+ return false;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ const formData = new FormData();
|
|
|
|
+ if(this.courseForm.id) {
|
|
|
|
+ formData.append('id', this.courseForm.id);
|
|
|
|
+ }
|
|
|
|
+ formData.append('courseTypeId', this.courseForm.courseTypeId);
|
|
|
|
+ formData.append('courseName', this.courseForm.courseName);
|
|
|
|
+ formData.append('courseDesc', this.courseForm.courseDesc);
|
|
|
|
+ formData.append('coursePrice', this.courseForm.coursePrice);
|
|
|
|
+ formData.append('courseDuration', this.courseForm.courseDuration);
|
|
|
|
+ formData.append('coverImage', this.courseForm.coverImageUrl);
|
|
|
|
+
|
|
|
|
+ this.listLoading = true;
|
|
|
|
+ this.http.uploadFile('/course-info/saveOrUpdate', formData, res => {
|
|
|
|
+ this.listLoading = false;
|
|
|
|
+ if (res.code === "ok") {
|
|
|
|
+ this.$message.success('添加课程成功');
|
|
|
|
+ this.addDialogVisible = false;
|
|
|
|
+ this.getList();
|
|
|
|
+ } else {
|
|
|
|
+ this.$message.error(res.msg || '添加课程失败');
|
|
|
|
+ }
|
|
|
|
+ }, error => {
|
|
|
|
+ this.listLoading = false;
|
|
|
|
+ this.$message.error(error || '添加课程失败');
|
|
|
|
+ });
|
|
|
|
+ });
|
|
|
|
+ },
|
|
|
|
+
|
|
|
|
+ // 处理课程封面图片变更
|
|
|
|
+ handleCourseCoverChange(file) {
|
|
|
|
+ const row = file.raw
|
|
|
|
+ const formData = new FormData();
|
|
|
|
+ formData.append('multipartFile', row);
|
|
|
|
+ this.http.uploadFile('/common/uploadFile', formData, res => {
|
|
|
|
+ if (res.code == "ok") {
|
|
|
|
+ this.courseForm.coverImageUrl = this.checkAndAddUpload(res.data)
|
|
|
|
+ } else {
|
|
|
|
+ this.$message({
|
|
|
|
+ message: res.msg || '图片上传失败',
|
|
|
|
+ type: 'error'
|
|
|
|
+ });
|
|
|
|
+ }
|
|
|
|
+ })
|
|
|
|
+ },
|
|
|
|
+
|
|
|
|
+ checkAndAddUpload(str) {
|
|
|
|
+ if(!str) {
|
|
|
|
+ return '';
|
|
|
|
+ }
|
|
|
|
+ if (str.includes('/upload/')) {
|
|
|
|
+ return str;
|
|
|
|
+ } else {
|
|
|
|
+ return '/upload/' + str;
|
|
|
|
+ }
|
|
|
|
+ },
|
|
|
|
+
|
|
|
|
+ // 添加分类
|
|
|
|
+ addCategory() {
|
|
|
|
+ if (!this.newCategory.label || !this.newCategory.label.trim()) {
|
|
|
|
+ this.$message({
|
|
|
|
+ type: 'warning',
|
|
|
|
+ message: '请输入分类名称!'
|
|
|
|
+ });
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 调用后端API保存课程分类
|
|
|
|
+ this.http.post('/course-type/saveOrUpdate', {
|
|
|
|
+ typeName: this.newCategory.label
|
|
|
|
+ }, res => {
|
|
|
|
+ if (res.code == "ok") {
|
|
|
|
+ // 生成唯一ID作为value,实际项目中应该使用后端返回的ID
|
|
|
|
+ const categoryId = res.data && res.data.id ? res.data.id : 'category_' + Date.now();
|
|
|
|
+ this.newCategory.value = categoryId;
|
|
|
|
+
|
|
|
|
+ // 添加到分类列表
|
|
|
|
+ this.categoryList.push({...this.newCategory});
|
|
|
|
+ this.categoryOptions.push({...this.newCategory});
|
|
|
|
+
|
|
|
|
+ // 清空输入
|
|
|
|
+ this.newCategory.label = '';
|
|
|
|
+ this.newCategory.value = '';
|
|
|
|
+
|
|
|
|
+ this.$message({
|
|
|
|
+ type: 'success',
|
|
|
|
+ message: '添加分类成功!'
|
|
|
|
+ });
|
|
|
|
+ } else {
|
|
|
|
+ this.$message({
|
|
|
|
+ message: res.msg || '添加分类失败',
|
|
|
|
+ type: 'error'
|
|
|
|
+ });
|
|
|
|
+ }
|
|
|
|
+ }, error => {
|
|
|
|
+ this.$message({
|
|
|
|
+ message: error || '添加分类失败',
|
|
|
|
+ type: 'error'
|
|
|
|
+ });
|
|
|
|
+ });
|
|
|
|
+ },
|
|
|
|
+
|
|
|
|
+ // 删除分类
|
|
|
|
+ deleteCategory(index, row) {
|
|
|
|
+ this.$confirm('确认删除该分类?', '提示', {
|
|
|
|
+ confirmButtonText: '确定',
|
|
|
|
+ cancelButtonText: '取消',
|
|
|
|
+ type: 'warning'
|
|
|
|
+ }).then(() => {
|
|
|
|
+ // 调用后端API删除课程分类
|
|
|
|
+ this.http.post('/course-type/delete', {
|
|
|
|
+ id: row.value
|
|
|
|
+ }, res => {
|
|
|
|
+ if (res.code == "ok") {
|
|
|
|
+ // 从分类列表中删除
|
|
|
|
+ this.categoryList.splice(index, 1);
|
|
|
|
+
|
|
|
|
+ // 从下拉选项中删除
|
|
|
|
+ const optionIndex = this.categoryOptions.findIndex(item => item.value === row.value);
|
|
|
|
+ if (optionIndex !== -1) {
|
|
|
|
+ this.categoryOptions.splice(optionIndex, 1);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ this.$message({
|
|
|
|
+ type: 'success',
|
|
|
|
+ message: '删除分类成功!'
|
|
|
|
+ });
|
|
|
|
+ } else {
|
|
|
|
+ this.$message({
|
|
|
|
+ message: res.msg || '删除分类失败',
|
|
|
|
+ type: 'error'
|
|
|
|
+ });
|
|
|
|
+ }
|
|
|
|
+ }, error => {
|
|
|
|
+ this.$message({
|
|
|
|
+ message: error || '删除分类失败',
|
|
|
|
+ type: 'error'
|
|
|
|
+ });
|
|
|
|
+ });
|
|
|
|
+ })
|
|
|
|
+ },
|
|
|
|
+ },
|
|
|
|
+
|
|
|
|
+ created() {
|
|
|
|
+ let height = window.innerHeight;
|
|
|
|
+ this.tableHeight = height - 195;
|
|
|
|
+ const that = this;
|
|
|
|
+ window.onresize = function temp() {
|
|
|
|
+ that.tableHeight = window.innerHeight - 195;
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+ // 初始化分类列表
|
|
|
|
+ this.categoryList = [...this.categoryOptions];
|
|
|
|
+ },
|
|
|
|
+
|
|
|
|
+ mounted() {
|
|
|
|
+ // 获取课程分类数据
|
|
|
|
+ this.http.post('/course-type/list', {}, res => {
|
|
|
|
+ if (res.code == "ok") {
|
|
|
|
+ // 将后端返回的数据转换为前端需要的格式
|
|
|
|
+ this.categoryList = res.data.map(item => ({
|
|
|
|
+ label: item.typeName,
|
|
|
|
+ value: item.id,
|
|
|
|
+ coverImage: item.coverImage || ''
|
|
|
|
+ }));
|
|
|
|
+
|
|
|
|
+ // 同步更新下拉选项
|
|
|
|
+ this.categoryOptions = [...this.categoryList];
|
|
|
|
+ } else {
|
|
|
|
+ this.$message({
|
|
|
|
+ message: res.msg || '获取分类列表失败',
|
|
|
|
+ type: 'error'
|
|
|
|
+ });
|
|
|
|
+ }
|
|
|
|
+ }, error => {
|
|
|
|
+ this.$message({
|
|
|
|
+ message: error || '获取分类列表失败',
|
|
|
|
+ type: 'error'
|
|
|
|
+ });
|
|
|
|
+ });
|
|
|
|
+
|
|
|
|
+ // 获取课程列表
|
|
|
|
+ this.getList();
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+</script>
|
|
|
|
+
|
|
|
|
+<style lang="scss" scoped>
|
|
|
|
+.rg_span {
|
|
|
|
+ display: inline-block;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.rg_span span {
|
|
|
|
+ text-align: right;
|
|
|
|
+ box-sizing: border-box;
|
|
|
|
+ padding-right: 10px;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.el-dialog__title {
|
|
|
|
+ display: inline-table;
|
|
|
|
+ margin-top: 20px;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.btns .el-button {
|
|
|
|
+ margin-left: 10px;
|
|
|
|
+ margin-bottom: 5px;
|
|
|
|
+}
|
|
|
|
+</style>
|
|
|
|
+<style>
|
|
|
|
+.course-cover-image {
|
|
|
|
+ width: 100px;
|
|
|
|
+ height: 100px;
|
|
|
|
+ object-fit: cover;
|
|
|
|
+ border-radius: 4px;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.otherForm .el-form-item {
|
|
|
|
+ float: left;
|
|
|
|
+ width: 50%;
|
|
|
|
+ margin: 0;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/* 视频播放器样式 */
|
|
|
|
+.video-player-container {
|
|
|
|
+ display: flex;
|
|
|
|
+ justify-content: center;
|
|
|
|
+ align-items: center;
|
|
|
|
+ width: 100%;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.video-player {
|
|
|
|
+ max-width: 100%;
|
|
|
|
+ max-height: 500px;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.video-preview {
|
|
|
|
+ margin-top: 10px;
|
|
|
|
+ border: 1px solid #ebeef5;
|
|
|
|
+ border-radius: 4px;
|
|
|
|
+ padding: 10px;
|
|
|
|
+ background-color: #f9f9f9;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.video-header {
|
|
|
|
+ display: flex;
|
|
|
|
+ justify-content: space-between;
|
|
|
|
+ align-items: center;
|
|
|
|
+ margin-bottom: 10px;
|
|
|
|
+ padding-bottom: 8px;
|
|
|
|
+ border-bottom: 1px solid #ebeef5;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.video-title {
|
|
|
|
+ font-weight: bold;
|
|
|
|
+ color: #303133;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.delete-video-btn {
|
|
|
|
+ color: #f56c6c;
|
|
|
|
+ padding: 0;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.delete-video-btn:hover {
|
|
|
|
+ color: #f78989;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.video-name {
|
|
|
|
+ margin-top: 5px;
|
|
|
|
+ color: #606266;
|
|
|
|
+ font-size: 14px;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/* 编辑器样式 */
|
|
|
|
+.editor-container {
|
|
|
|
+ border: 1px solid #dcdfe6;
|
|
|
|
+ border-radius: 4px;
|
|
|
|
+ overflow: hidden;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.editor-toolbar {
|
|
|
|
+ background-color: #f5f7fa;
|
|
|
|
+ padding: 5px;
|
|
|
|
+ border-bottom: 1px solid #dcdfe6;
|
|
|
|
+ display: flex;
|
|
|
|
+ align-items: center;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.editor-btn {
|
|
|
|
+ background: none;
|
|
|
|
+ border: none;
|
|
|
|
+ padding: 5px 8px;
|
|
|
|
+ margin: 0 2px;
|
|
|
|
+ cursor: pointer;
|
|
|
|
+ border-radius: 3px;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.editor-btn:hover {
|
|
|
|
+ background-color: #e4e7ed;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.editor-separator {
|
|
|
|
+ width: 1px;
|
|
|
|
+ height: 16px;
|
|
|
|
+ background-color: #dcdfe6;
|
|
|
|
+ margin: 0 5px;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.editor-select {
|
|
|
|
+ height: 24px;
|
|
|
|
+ border: 1px solid #dcdfe6;
|
|
|
|
+ border-radius: 3px;
|
|
|
|
+ margin: 0 5px;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.editor-content {
|
|
|
|
+ width: 100%;
|
|
|
|
+ min-height: 120px;
|
|
|
|
+ padding: 10px;
|
|
|
|
+ border: none;
|
|
|
|
+ resize: vertical;
|
|
|
|
+ outline: none;
|
|
|
|
+ font-family: Arial, sans-serif;
|
|
|
|
+ font-size: 14px;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/* 上传链接样式 */
|
|
|
|
+.upload-item {
|
|
|
|
+ margin-bottom: 20px;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.upload-link {
|
|
|
|
+ color: #409eff;
|
|
|
|
+ cursor: pointer;
|
|
|
|
+ font-size: 14px;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.upload-link:hover {
|
|
|
|
+ text-decoration: underline;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/* 分类封面图片样式 */
|
|
|
|
+.category-cover-image {
|
|
|
|
+ width: 100px;
|
|
|
|
+ height: 56px;
|
|
|
|
+ object-fit: cover;
|
|
|
|
+ border-radius: 4px;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.cover-upload-container {
|
|
|
|
+ display: flex;
|
|
|
|
+ flex-direction: column;
|
|
|
|
+ align-items: center;
|
|
|
|
+ padding: 20px 0;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.cover-uploader {
|
|
|
|
+ border: 1px dashed #d9d9d9;
|
|
|
|
+ border-radius: 6px;
|
|
|
|
+ cursor: pointer;
|
|
|
|
+ position: relative;
|
|
|
|
+ overflow: hidden;
|
|
|
|
+ width: 178px;
|
|
|
|
+ height: 100px;
|
|
|
|
+ margin-bottom: 10px;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.cover-uploader:hover {
|
|
|
|
+ border-color: #409EFF;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.cover-uploader-icon {
|
|
|
|
+ font-size: 28px;
|
|
|
|
+ color: #8c939d;
|
|
|
|
+ width: 178px;
|
|
|
|
+ height: 100px;
|
|
|
|
+ line-height: 100px;
|
|
|
|
+ text-align: center;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.cover-image {
|
|
|
|
+ width: 178px;
|
|
|
|
+ height: 100px;
|
|
|
|
+ display: block;
|
|
|
|
+ object-fit: cover;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.cover-tip {
|
|
|
|
+ color: #909399;
|
|
|
|
+ font-size: 12px;
|
|
|
|
+ margin-top: 10px;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/* 讲师照片上传样式 */
|
|
|
|
+.instructor-uploader {
|
|
|
|
+ border: 1px dashed #d9d9d9;
|
|
|
|
+ border-radius: 6px;
|
|
|
|
+ cursor: pointer;
|
|
|
|
+ position: relative;
|
|
|
|
+ overflow: hidden;
|
|
|
|
+ width: 100px;
|
|
|
|
+ height: 100px;
|
|
|
|
+ margin-bottom: 10px;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.instructor-uploader:hover {
|
|
|
|
+ border-color: #409EFF;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.instructor-uploader-icon {
|
|
|
|
+ font-size: 28px;
|
|
|
|
+ color: #8c939d;
|
|
|
|
+ width: 100px;
|
|
|
|
+ height: 100px;
|
|
|
|
+ line-height: 100px;
|
|
|
|
+ text-align: center;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.instructor-image {
|
|
|
|
+ width: 100px;
|
|
|
|
+ height: 100px;
|
|
|
|
+ display: block;
|
|
|
|
+ object-fit: cover;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.upload-tip {
|
|
|
|
+ color: #909399;
|
|
|
|
+ font-size: 12px;
|
|
|
|
+ margin-top: 5px;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.ql-snow .ql-picker-label::before {
|
|
|
|
+ position: relative;
|
|
|
|
+ top: -8px;
|
|
|
|
+}
|
|
|
|
+</style>
|