list copy 2.vue 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976
  1. <template>
  2. <section>
  3. <!--工具条-->
  4. <el-col :span="24" class="toolbar" style="padding-bottom: 0px;">
  5. <el-form :inline="true" @submit.native.prevent>
  6. <el-form-item label="课程分类">
  7. <el-select v-model="categoryValue" placeholder="请选择" clearable @change="searchList" size="small">
  8. <el-option v-for="item in categoryOptions" :key="item.value" :label="item.label"
  9. :value="item.value">
  10. </el-option>
  11. </el-select>
  12. </el-form-item>
  13. <el-form-item label="课程名称">
  14. <el-input v-model="keyword" placeholder="请输入" clearable @change="searchList"
  15. size="small"></el-input>
  16. </el-form-item>
  17. <el-form-item label="讲师">
  18. <el-input v-model="instructor" placeholder="请输入" clearable @change="searchList"
  19. size="small"></el-input>
  20. </el-form-item>
  21. <el-form-item>
  22. <el-button type="primary" @click="addCourse()" size="small">添加课程</el-button>
  23. </el-form-item>
  24. <el-form-item>
  25. <el-button type="primary" @click="batchManage" size="small">分类管理</el-button>
  26. </el-form-item>
  27. </el-form>
  28. </el-col>
  29. <!--列表-->
  30. <el-table :data="list" highlight-current-row v-loading="listLoading" :height="tableHeight" style="width: 100%;">
  31. <el-table-column type="index" width="50" align="center" label="#"></el-table-column>
  32. <el-table-column label="封面" width="120" align="center">
  33. <template slot-scope="scope">
  34. <img v-if="scope.row.coverImage" :src="scope.row.coverImage" class="course-cover-image" />
  35. <span v-else>无封面</span>
  36. </template>
  37. </el-table-column>
  38. <el-table-column prop="courseTypeName" label="课程分类" min-width="120" align="center"></el-table-column>
  39. <el-table-column prop="courseName" label="课程名称" min-width="180" align="center"></el-table-column>
  40. <el-table-column prop="courseInstructor" label="讲师" min-width="120" align="center"></el-table-column>
  41. <el-table-column prop="coursePrice" label="价格" min-width="100" align="center">
  42. <template slot-scope="scope">
  43. <span>¥{{ scope.row.coursePrice }}</span>
  44. </template>
  45. </el-table-column>
  46. <el-table-column prop="courseDuration" label="课程时间" min-width="120" align="center">
  47. <template slot-scope="scope">
  48. <span>{{ scope.row.courseDuration ? scope.row.courseDuration + '分钟' : '' }}</span>
  49. </template>
  50. </el-table-column>
  51. <el-table-column label="操作" width="340" class-name="btns" header-align="center" fixed="right">
  52. <template slot-scope="scope">
  53. <el-button size="mini" type="primary" @click="addVideo(scope.row)">添加视频</el-button>
  54. <el-button size="mini" type="primary" @click="addCourse(scope.row)">编辑</el-button>
  55. <el-button size="mini" type="danger" @click="deleteItem(scope.row)">删除</el-button>
  56. <el-button size="mini" :type="scope.row.courseStatus === 1 ? 'warning' : 'success'"
  57. @click="uploadItem(scope.row)">{{ scope.row.courseStatus === 1 ? '下架' : '上架' }}</el-button>
  58. </template>
  59. </el-table-column>
  60. </el-table>
  61. <!--工具条-->
  62. <el-col :span="24" class="toolbar">
  63. <el-pagination
  64. @size-change="handleSizeChange"
  65. @current-change="handleCurrentChange"
  66. :page-sizes="[10, 20, 50, 100]"
  67. :page-size="size"
  68. layout="total, sizes, prev, pager, next, jumper"
  69. :total="total"
  70. style="float:right;"
  71. ></el-pagination>
  72. </el-col>
  73. <!-- 分类管理 -->
  74. <el-dialog :visible.sync="categoryManageVisible" title="分类管理" width="600px">
  75. <el-table :data="categoryList" style="width: 100%" max-height="400">
  76. <el-table-column prop="label" label="分类名称" width="180"></el-table-column>
  77. <el-table-column label="封面" width="180">
  78. <template slot-scope="scope">
  79. <img v-if="scope.row.coverImage" :src="scope.row.coverImage" class="category-cover-image" />
  80. <span v-else>无封面</span>
  81. </template>
  82. </el-table-column>
  83. <el-table-column label="操作" width="180">
  84. <template slot-scope="scope">
  85. <el-button size="mini" type="primary" @click="setCategoryCover(scope.$index, scope.row)">设置封面</el-button>
  86. <el-button size="mini" type="danger" @click="deleteCategory(scope.$index, scope.row)">删除</el-button>
  87. </template>
  88. </el-table-column>
  89. </el-table>
  90. <div style="margin-top: 20px;">
  91. <el-form :inline="true" :model="newCategory" class="demo-form-inline">
  92. <el-form-item label="分类名称">
  93. <el-input v-model="newCategory.label" placeholder="请输入分类名称"></el-input>
  94. </el-form-item>
  95. <el-form-item>
  96. <el-button type="primary" @click="addCategory">添加</el-button>
  97. </el-form-item>
  98. </el-form>
  99. </div>
  100. <span slot="footer" class="dialog-dialog">
  101. <el-button @click="categoryManageVisible = false">关 闭</el-button>
  102. </span>
  103. </el-dialog>
  104. <!-- 设置分类封面 -->
  105. <el-dialog :visible.sync="coverDialogVisible" title="设置分类封面" width="500px">
  106. <div class="cover-upload-container">
  107. <el-upload
  108. class="cover-uploader"
  109. action="#"
  110. :show-file-list="false"
  111. :on-change="handleCoverChange"
  112. :auto-upload="false"
  113. :before-upload="beforeCoverUpload">
  114. <img v-if="coverImageUrl" :src="coverImageUrl" class="cover-image" />
  115. <i v-else class="el-icon-plus cover-uploader-icon"></i>
  116. </el-upload>
  117. <div class="cover-tip">请上传分类封面图片,建议尺寸 16:9</div>
  118. </div>
  119. <span slot="footer" class="dialog-footer">
  120. <el-button @click="coverDialogVisible = false">取 消</el-button>
  121. <el-button type="primary" @click="saveCategoryCover">确 定</el-button>
  122. </span>
  123. </el-dialog>
  124. <!-- 添加课程 -->
  125. <el-dialog :visible.sync="addDialogVisible" title="添加课程" width="800px" top="6.5vh">
  126. <el-form :model="courseForm" :rules="courseRules" ref="courseForm" label-width="120px">
  127. <el-form-item label="课程分类" prop="courseTypeId">
  128. <el-select v-model="courseForm.courseTypeId" placeholder="请选择" style="width:100%">
  129. <el-option v-for="item in categoryOptions" :key="item.value"
  130. :label="item.label" :value="item.value">
  131. </el-option>
  132. </el-select>
  133. </el-form-item>
  134. <el-form-item label="课程名称" prop="courseName">
  135. <el-input v-model="courseForm.courseName" placeholder="请输入课程名称"></el-input>
  136. </el-form-item>
  137. <el-form-item label="课程介绍" style="height: 200px;">
  138. <quill-editor style="height: 150px" ref="text" v-model="courseForm.courseDesc" class="myQuillEditor" :options="editorOption"/>
  139. </el-form-item>
  140. <el-form-item label="课程价格">
  141. <el-input-number v-model="courseForm.coursePrice" :min="0" :precision="2"></el-input-number>
  142. </el-form-item>
  143. <el-form-item label="课程时间(分钟)">
  144. <el-input-number v-model="courseForm.courseDuration" :min="0"></el-input-number>
  145. </el-form-item>
  146. <el-form-item label="课程封面">
  147. <el-upload
  148. class="cover-uploader"
  149. action="#"
  150. :show-file-list="false"
  151. :on-change="handleCourseCoverChange"
  152. :auto-upload="false"
  153. :before-upload="beforeCoverUpload">
  154. <img v-if="courseForm.coverImageUrl" :src="courseForm.coverImageUrl" class="cover-image" />
  155. <i v-else class="el-icon-plus cover-uploader-icon"></i>
  156. </el-upload>
  157. <div class="cover-tip">建议尺寸 16:9,大小不超过2MB</div>
  158. </el-form-item>
  159. </el-form>
  160. <span slot="footer" class="dialog-footer">
  161. <el-button @click="addDialogVisible = false">取 消</el-button>
  162. <el-button type="primary" @click="submitCourseForm">确 定</el-button>
  163. </span>
  164. </el-dialog>
  165. </section>
  166. </template>
  167. <script>
  168. // 富文本样式
  169. import 'quill/dist/quill.core.css'
  170. import 'quill/dist/quill.snow.css'
  171. import 'quill/dist/quill.bubble.css'
  172. // 导入富文本
  173. import { quillEditor } from 'vue-quill-editor'
  174. export default {
  175. components: {
  176. quillEditor, // 富文本
  177. },
  178. data() {
  179. return {
  180. // 课程表单
  181. courseForm: {
  182. courseTypeId: '',
  183. courseName: '',
  184. courseDesc: '',
  185. coursePrice: 0,
  186. courseDuration: 0,
  187. coverImageUrl: '',
  188. coverImageFile: null
  189. },
  190. courseRules: {
  191. courseTypeId: [
  192. { required: true, message: '请选择课程分类', trigger: 'change' }
  193. ],
  194. courseName: [
  195. { required: true, message: '请输入课程名称', trigger: 'blur' }
  196. ]
  197. },
  198. // 课程分类选择
  199. categoryValue: '',
  200. categoryOptions: [],
  201. // 搜索条件
  202. keyword: null, // 课程名称
  203. instructor: null, // 讲师
  204. // 表格相关
  205. tableHeight: 0,
  206. listLoading: false,
  207. total: 0,
  208. page: 1,
  209. size: 20,
  210. list: [],
  211. // 对话框控制
  212. categoryManageVisible: false,
  213. editDialogVisible: false,
  214. addDialogVisible: false,
  215. // 分类管理
  216. categoryList: [],
  217. newCategory: {
  218. label: '',
  219. value: '',
  220. coverImage: ''
  221. },
  222. // 分类封面设置
  223. coverDialogVisible: false,
  224. currentCategoryIndex: -1,
  225. coverImageUrl: '',
  226. coverImageFile: null,
  227. // 用户信息
  228. user: sessionStorage.user ? JSON.parse(sessionStorage.user) : {},
  229. editorOption: { // 富文本框里面的默认值
  230. placeholder: '请输入内容...',
  231. modules: {
  232. toolbar:[
  233. ['bold', 'italic', 'underline', 'strike'], //加粗,斜体,下划线,删除线
  234. // ['blockquote', 'code-block'], //引用,代码块
  235. [{ 'header': 1 }, { 'header': 2 }], // 标题,键值对的形式;1、2表示字体大小
  236. // [{ 'list': 'ordered'}, { 'list': 'bullet' }], //列表
  237. // [{ 'script': 'sub'}, { 'script': 'super' }], // 上下标
  238. // [{ 'indent': '-1'}, { 'indent': '+1' }], // 缩进
  239. // [{ 'direction': 'rtl' }], // 文本方向
  240. [{ 'size': ['small', false, 'large', 'huge'] }], // 字体大小
  241. [{ 'header': [1, 2, 3, 4, 5, 6, false] }], //几级标题
  242. [{ 'color': [] }, { 'background': [] }], // 字体颜色,字体背景颜色
  243. // [{ 'font': [] }], //字体
  244. [{ 'align': [] }], //对齐方式
  245. ['clean'], //清除字体样式
  246. // ['image','video'] //上传图片、上传视频
  247. [] //上传图片、上传视频
  248. ], //工具栏设置
  249. },
  250. theme: 'snow',
  251. },
  252. }
  253. },
  254. methods: {
  255. // 分类管理
  256. batchManage() {
  257. this.categoryManageVisible = true;
  258. // 加载分类数据
  259. this.http.post('/course-type/list', {}, res => {
  260. if (res.code == "ok") {
  261. // 将后端返回的数据转换为前端需要的格式
  262. this.categoryList = res.data.map(item => ({
  263. label: item.typeName,
  264. value: item.id,
  265. coverImage: item.coverImage || ''
  266. }));
  267. // 同步更新下拉选项
  268. this.categoryOptions = [...this.categoryList];
  269. } else {
  270. this.$message({
  271. message: res.msg || '获取分类列表失败',
  272. type: 'error'
  273. });
  274. }
  275. }, error => {
  276. this.$message({
  277. message: error || '获取分类列表失败',
  278. type: 'error'
  279. });
  280. });
  281. },
  282. // 搜索课程列表
  283. searchList() {
  284. this.page = 1;
  285. this.getList();
  286. },
  287. // 获取课程列表
  288. getList() {
  289. this.listLoading = true;
  290. // 调用后端API获取课程列表
  291. this.http.post('/course-info/list', {
  292. page: this.page,
  293. size: this.size,
  294. courseType: this.categoryValue,
  295. courseName: this.keyword,
  296. courseInstructor: this.instructor
  297. }, res => {
  298. this.listLoading = false;
  299. if (res.code == "ok") {
  300. this.list = res.data.records;
  301. this.total = res.data.total;
  302. } else {
  303. this.$message({
  304. message: res.msg || '获取课程列表失败',
  305. type: 'error'
  306. });
  307. }
  308. }, error => {
  309. this.listLoading = false;
  310. this.$message({
  311. message: error || '获取课程列表失败',
  312. type: 'error'
  313. });
  314. });
  315. },
  316. // 分页相关
  317. handleCurrentChange(val) {
  318. this.page = val;
  319. this.getList();
  320. },
  321. handleSizeChange(val) {
  322. this.page = 1;
  323. this.size = val;
  324. this.getList();
  325. },
  326. // 删除课程
  327. deleteItem(row) {
  328. this.$confirm('确认删除该课程?', '提示', {
  329. confirmButtonText: '确定',
  330. cancelButtonText: '取消',
  331. type: 'warning'
  332. }).then(() => {
  333. // 调用后端API删除课程
  334. this.http.post('/course-info/deleteCourse', {
  335. id: row.id
  336. }, res => {
  337. if (res.code == "ok") {
  338. this.$message({
  339. type: 'success',
  340. message: '删除成功!'
  341. });
  342. this.getList();
  343. } else {
  344. this.$message({
  345. message: res.msg || '删除课程失败',
  346. type: 'error'
  347. });
  348. }
  349. }, error => {
  350. this.$message({
  351. message: error || '删除课程失败',
  352. type: 'error'
  353. });
  354. });
  355. })
  356. },
  357. // 上架/下架课程
  358. uploadItem(row) {
  359. const action = row.courseStatus === 1 ? '下架' : '上架';
  360. this.$confirm(`确认${action}该课程?`, '提示', {
  361. confirmButtonText: '确定',
  362. cancelButtonText: '取消',
  363. type: 'warning'
  364. }).then(() => {
  365. // 调用后端API上架/下架课程
  366. this.http.post('/course-info/saveOrUpdate', {
  367. id: row.id,
  368. courseStatus: row.courseStatus === 1 ? 0 : 1 // 0: 下架, 1: 上架
  369. }, res => {
  370. if (res.code == "ok") {
  371. row.courseStatus = row.courseStatus === 1 ? 0 : 1;
  372. this.$message({
  373. type: 'success',
  374. message: `${action}成功!`
  375. });
  376. } else {
  377. this.$message({
  378. message: res.msg || `${action}失败`,
  379. type: 'error'
  380. });
  381. }
  382. }, error => {
  383. this.$message({
  384. message: error || `${action}失败`,
  385. type: 'error'
  386. });
  387. });
  388. })
  389. },
  390. // 保存分类封面
  391. saveCategoryCover() {
  392. if (!this.coverImageUrl) {
  393. this.$message.warning('请先上传封面图片!');
  394. return;
  395. }
  396. // 关闭对话框
  397. this.coverDialogVisible = false;
  398. this.$message({
  399. type: 'success',
  400. message: '设置封面成功!'
  401. });
  402. },
  403. // 处理封面图片变更
  404. handleCoverChange(file) {
  405. this.coverImageFile = file.raw;
  406. if (this.coverImageFile) {
  407. this.coverImageUrl = URL.createObjectURL(this.coverImageFile);
  408. // 获取当前分类的ID
  409. if (this.currentCategoryIndex >= 0) {
  410. const categoryId = this.categoryList[this.currentCategoryIndex].value;
  411. // 立即上传封面图片
  412. this.listLoading = true;
  413. // 创建FormData对象用于上传文件
  414. const formData = new FormData();
  415. formData.append('id', categoryId);
  416. formData.append('coverImage', this.coverImageFile);
  417. // 调用上传图片的API
  418. this.http.uploadFile('/course-type/uploadCover', formData, res => {
  419. this.listLoading = false;
  420. if (res.code == "ok") {
  421. // 上传成功后,获取返回的图片URL
  422. const imageUrl = res.data && res.data.url ? res.data.url : this.coverImageUrl;
  423. // 更新本地数据
  424. this.categoryList[this.currentCategoryIndex].coverImage = imageUrl;
  425. // 同步更新到分类选项中
  426. const optionIndex = this.categoryOptions.findIndex(item => item.value === categoryId);
  427. if (optionIndex !== -1) {
  428. this.categoryOptions[optionIndex].coverImage = imageUrl;
  429. }
  430. this.$message({
  431. type: 'success',
  432. message: '封面图片上传成功!'
  433. });
  434. } else {
  435. this.$message({
  436. message: res.msg || '上传封面图片失败',
  437. type: 'error'
  438. });
  439. }
  440. }, error => {
  441. this.listLoading = false;
  442. this.$message({
  443. message: error || '上传封面图片失败',
  444. type: 'error'
  445. });
  446. });
  447. }
  448. }
  449. },
  450. // 设置分类封面
  451. setCategoryCover(index, row) {
  452. this.currentCategoryIndex = index;
  453. this.coverImageUrl = row.coverImage || '';
  454. this.coverDialogVisible = true;
  455. },
  456. // 处理封面图片上传前的验证
  457. beforeCoverUpload(file) {
  458. const isImage = file.type.indexOf('image/') === 0;
  459. const isLt2M = file.size / 1024 / 1024 < 2;
  460. if (!isImage) {
  461. this.$message.error('上传封面图片只能是图片格式!');
  462. }
  463. if (!isLt2M) {
  464. this.$message.error('上传封面图片大小不能超过 2MB!');
  465. }
  466. return isImage && isLt2M;
  467. },
  468. // 添加课程
  469. addCourse(row) {
  470. this.addDialogVisible = true;
  471. if(!row) {
  472. this.courseForm = {
  473. courseTypeId: '',
  474. courseName: '',
  475. courseDesc: '',
  476. coursePrice: 0,
  477. courseDuration: 0,
  478. coverImageUrl: '',
  479. };
  480. if (this.$refs.courseForm) {
  481. this.$refs.courseForm.resetFields();
  482. }
  483. } else {
  484. console.log(row, '<==== 看看数据')
  485. const { courseTypeId, courseName, courseDesc, coursePrice, courseDuration, coverImage, id } = row
  486. this.courseForm = {
  487. id,
  488. courseTypeId,
  489. courseName,
  490. courseDesc,
  491. coursePrice,
  492. courseDuration,
  493. coverImageUrl: this.checkAndAddUpload(coverImage)
  494. }
  495. }
  496. },
  497. // 提交课程表单
  498. submitCourseForm() {
  499. this.$refs.courseForm.validate(valid => {
  500. if (!valid) {
  501. return false;
  502. }
  503. const formData = new FormData();
  504. if(this.courseForm.id) {
  505. formData.append('id', this.courseForm.id);
  506. }
  507. formData.append('courseTypeId', this.courseForm.courseTypeId);
  508. formData.append('courseName', this.courseForm.courseName);
  509. formData.append('courseDesc', this.courseForm.courseDesc);
  510. formData.append('coursePrice', this.courseForm.coursePrice);
  511. formData.append('courseDuration', this.courseForm.courseDuration);
  512. formData.append('coverImage', this.courseForm.coverImageUrl);
  513. this.listLoading = true;
  514. this.http.uploadFile('/course-info/saveOrUpdate', formData, res => {
  515. this.listLoading = false;
  516. if (res.code === "ok") {
  517. this.$message.success('添加课程成功');
  518. this.addDialogVisible = false;
  519. this.getList();
  520. } else {
  521. this.$message.error(res.msg || '添加课程失败');
  522. }
  523. }, error => {
  524. this.listLoading = false;
  525. this.$message.error(error || '添加课程失败');
  526. });
  527. });
  528. },
  529. // 处理课程封面图片变更
  530. handleCourseCoverChange(file) {
  531. const row = file.raw
  532. const formData = new FormData();
  533. formData.append('multipartFile', row);
  534. this.http.uploadFile('/common/uploadFile', formData, res => {
  535. if (res.code == "ok") {
  536. this.courseForm.coverImageUrl = this.checkAndAddUpload(res.data)
  537. } else {
  538. this.$message({
  539. message: res.msg || '图片上传失败',
  540. type: 'error'
  541. });
  542. }
  543. })
  544. },
  545. checkAndAddUpload(str) {
  546. if(!str) {
  547. return '';
  548. }
  549. if (str.includes('/upload/')) {
  550. return str;
  551. } else {
  552. return '/upload/' + str;
  553. }
  554. },
  555. // 添加分类
  556. addCategory() {
  557. if (!this.newCategory.label || !this.newCategory.label.trim()) {
  558. this.$message({
  559. type: 'warning',
  560. message: '请输入分类名称!'
  561. });
  562. return;
  563. }
  564. // 调用后端API保存课程分类
  565. this.http.post('/course-type/saveOrUpdate', {
  566. typeName: this.newCategory.label
  567. }, res => {
  568. if (res.code == "ok") {
  569. // 生成唯一ID作为value,实际项目中应该使用后端返回的ID
  570. const categoryId = res.data && res.data.id ? res.data.id : 'category_' + Date.now();
  571. this.newCategory.value = categoryId;
  572. // 添加到分类列表
  573. this.categoryList.push({...this.newCategory});
  574. this.categoryOptions.push({...this.newCategory});
  575. // 清空输入
  576. this.newCategory.label = '';
  577. this.newCategory.value = '';
  578. this.$message({
  579. type: 'success',
  580. message: '添加分类成功!'
  581. });
  582. } else {
  583. this.$message({
  584. message: res.msg || '添加分类失败',
  585. type: 'error'
  586. });
  587. }
  588. }, error => {
  589. this.$message({
  590. message: error || '添加分类失败',
  591. type: 'error'
  592. });
  593. });
  594. },
  595. // 删除分类
  596. deleteCategory(index, row) {
  597. this.$confirm('确认删除该分类?', '提示', {
  598. confirmButtonText: '确定',
  599. cancelButtonText: '取消',
  600. type: 'warning'
  601. }).then(() => {
  602. // 调用后端API删除课程分类
  603. this.http.post('/course-type/delete', {
  604. id: row.value
  605. }, res => {
  606. if (res.code == "ok") {
  607. // 从分类列表中删除
  608. this.categoryList.splice(index, 1);
  609. // 从下拉选项中删除
  610. const optionIndex = this.categoryOptions.findIndex(item => item.value === row.value);
  611. if (optionIndex !== -1) {
  612. this.categoryOptions.splice(optionIndex, 1);
  613. }
  614. this.$message({
  615. type: 'success',
  616. message: '删除分类成功!'
  617. });
  618. } else {
  619. this.$message({
  620. message: res.msg || '删除分类失败',
  621. type: 'error'
  622. });
  623. }
  624. }, error => {
  625. this.$message({
  626. message: error || '删除分类失败',
  627. type: 'error'
  628. });
  629. });
  630. })
  631. },
  632. },
  633. created() {
  634. let height = window.innerHeight;
  635. this.tableHeight = height - 195;
  636. const that = this;
  637. window.onresize = function temp() {
  638. that.tableHeight = window.innerHeight - 195;
  639. };
  640. // 初始化分类列表
  641. this.categoryList = [...this.categoryOptions];
  642. },
  643. mounted() {
  644. // 获取课程分类数据
  645. this.http.post('/course-type/list', {}, res => {
  646. if (res.code == "ok") {
  647. // 将后端返回的数据转换为前端需要的格式
  648. this.categoryList = res.data.map(item => ({
  649. label: item.typeName,
  650. value: item.id,
  651. coverImage: item.coverImage || ''
  652. }));
  653. // 同步更新下拉选项
  654. this.categoryOptions = [...this.categoryList];
  655. } else {
  656. this.$message({
  657. message: res.msg || '获取分类列表失败',
  658. type: 'error'
  659. });
  660. }
  661. }, error => {
  662. this.$message({
  663. message: error || '获取分类列表失败',
  664. type: 'error'
  665. });
  666. });
  667. // 获取课程列表
  668. this.getList();
  669. }
  670. }
  671. </script>
  672. <style lang="scss" scoped>
  673. .rg_span {
  674. display: inline-block;
  675. }
  676. .rg_span span {
  677. text-align: right;
  678. box-sizing: border-box;
  679. padding-right: 10px;
  680. }
  681. .el-dialog__title {
  682. display: inline-table;
  683. margin-top: 20px;
  684. }
  685. .btns .el-button {
  686. margin-left: 10px;
  687. margin-bottom: 5px;
  688. }
  689. </style>
  690. <style>
  691. .course-cover-image {
  692. width: 100px;
  693. height: 100px;
  694. object-fit: cover;
  695. border-radius: 4px;
  696. }
  697. .otherForm .el-form-item {
  698. float: left;
  699. width: 50%;
  700. margin: 0;
  701. }
  702. /* 视频播放器样式 */
  703. .video-player-container {
  704. display: flex;
  705. justify-content: center;
  706. align-items: center;
  707. width: 100%;
  708. }
  709. .video-player {
  710. max-width: 100%;
  711. max-height: 500px;
  712. }
  713. .video-preview {
  714. margin-top: 10px;
  715. border: 1px solid #ebeef5;
  716. border-radius: 4px;
  717. padding: 10px;
  718. background-color: #f9f9f9;
  719. }
  720. .video-header {
  721. display: flex;
  722. justify-content: space-between;
  723. align-items: center;
  724. margin-bottom: 10px;
  725. padding-bottom: 8px;
  726. border-bottom: 1px solid #ebeef5;
  727. }
  728. .video-title {
  729. font-weight: bold;
  730. color: #303133;
  731. }
  732. .delete-video-btn {
  733. color: #f56c6c;
  734. padding: 0;
  735. }
  736. .delete-video-btn:hover {
  737. color: #f78989;
  738. }
  739. .video-name {
  740. margin-top: 5px;
  741. color: #606266;
  742. font-size: 14px;
  743. }
  744. /* 编辑器样式 */
  745. .editor-container {
  746. border: 1px solid #dcdfe6;
  747. border-radius: 4px;
  748. overflow: hidden;
  749. }
  750. .editor-toolbar {
  751. background-color: #f5f7fa;
  752. padding: 5px;
  753. border-bottom: 1px solid #dcdfe6;
  754. display: flex;
  755. align-items: center;
  756. }
  757. .editor-btn {
  758. background: none;
  759. border: none;
  760. padding: 5px 8px;
  761. margin: 0 2px;
  762. cursor: pointer;
  763. border-radius: 3px;
  764. }
  765. .editor-btn:hover {
  766. background-color: #e4e7ed;
  767. }
  768. .editor-separator {
  769. width: 1px;
  770. height: 16px;
  771. background-color: #dcdfe6;
  772. margin: 0 5px;
  773. }
  774. .editor-select {
  775. height: 24px;
  776. border: 1px solid #dcdfe6;
  777. border-radius: 3px;
  778. margin: 0 5px;
  779. }
  780. .editor-content {
  781. width: 100%;
  782. min-height: 120px;
  783. padding: 10px;
  784. border: none;
  785. resize: vertical;
  786. outline: none;
  787. font-family: Arial, sans-serif;
  788. font-size: 14px;
  789. }
  790. /* 上传链接样式 */
  791. .upload-item {
  792. margin-bottom: 20px;
  793. }
  794. .upload-link {
  795. color: #409eff;
  796. cursor: pointer;
  797. font-size: 14px;
  798. }
  799. .upload-link:hover {
  800. text-decoration: underline;
  801. }
  802. /* 分类封面图片样式 */
  803. .category-cover-image {
  804. width: 100px;
  805. height: 56px;
  806. object-fit: cover;
  807. border-radius: 4px;
  808. }
  809. .cover-upload-container {
  810. display: flex;
  811. flex-direction: column;
  812. align-items: center;
  813. padding: 20px 0;
  814. }
  815. .cover-uploader {
  816. border: 1px dashed #d9d9d9;
  817. border-radius: 6px;
  818. cursor: pointer;
  819. position: relative;
  820. overflow: hidden;
  821. width: 178px;
  822. height: 100px;
  823. margin-bottom: 10px;
  824. }
  825. .cover-uploader:hover {
  826. border-color: #409EFF;
  827. }
  828. .cover-uploader-icon {
  829. font-size: 28px;
  830. color: #8c939d;
  831. width: 178px;
  832. height: 100px;
  833. line-height: 100px;
  834. text-align: center;
  835. }
  836. .cover-image {
  837. width: 178px;
  838. height: 100px;
  839. display: block;
  840. object-fit: cover;
  841. }
  842. .cover-tip {
  843. color: #909399;
  844. font-size: 12px;
  845. margin-top: 10px;
  846. }
  847. /* 讲师照片上传样式 */
  848. .instructor-uploader {
  849. border: 1px dashed #d9d9d9;
  850. border-radius: 6px;
  851. cursor: pointer;
  852. position: relative;
  853. overflow: hidden;
  854. width: 100px;
  855. height: 100px;
  856. margin-bottom: 10px;
  857. }
  858. .instructor-uploader:hover {
  859. border-color: #409EFF;
  860. }
  861. .instructor-uploader-icon {
  862. font-size: 28px;
  863. color: #8c939d;
  864. width: 100px;
  865. height: 100px;
  866. line-height: 100px;
  867. text-align: center;
  868. }
  869. .instructor-image {
  870. width: 100px;
  871. height: 100px;
  872. display: block;
  873. object-fit: cover;
  874. }
  875. .upload-tip {
  876. color: #909399;
  877. font-size: 12px;
  878. margin-top: 5px;
  879. }
  880. .ql-snow .ql-picker-label::before {
  881. position: relative;
  882. top: -8px;
  883. }
  884. </style>