Преглед на файлове

1、课程加入内容类型(综合、文本、音频、视频、问题、ai实验室)
2、课程类型大纲增加封面、节点、排序
3、优化课程、类型筛选大纲时标注(大纲还是课程类型)

liyanbo преди 10 месеца
родител
ревизия
7fd026f704

+ 13 - 4
src/api/bjdx/course/index.ts

@@ -4,17 +4,20 @@ import request from '@/config/axios'
 export interface CourseVO {
   id: number // 课程d
   courseName: string // 课程名称
-  coursePath: string // 课程路径
-  courseContent: string // 课程内容
+  courseContentType: "all", // 课程内容类型
+  courseImagePath: undefined, // 课程图片路径
+  courseVideoPath: undefined, // 课程视频路径
+  courseMusicPath: undefined, // 课程音乐路径
+  courseContent: undefined, // 课程内容
   courseAuthor: string // 课程作者
   courseTeacher: string // 课程老师
   courseSize: number // 课程大小
   courseTime: number // 课程时长
-  courseIsInspect: string // 课程是否有检查
+  courseIsInspect: false // 课程是否有检查
   courseType: string // 课程类型
   courseTypeName: string // 课程类型
   courseLabel: string // 课程标签
-  courseStatus: string // 课程状态
+  courseStatus: "0" // 课程状态
 }
 
 // 课程 API
@@ -47,5 +50,11 @@ export const CourseApi = {
   // 导出课程 Excel
   exportCourse: async (params) => {
     return await request.download({ url: `/bjdx/course/export-excel`, params })
+  },
+
+
+  // 根据类型id获取课程列表
+  getCourseByTypeId: async (typeId: String) => {
+    return await request.get({ url: `/bjdx/web/course/getCourseByTypeId?typeId=` + typeId })
   }
 }

+ 12 - 0
src/api/bjdx/coursetype/index.ts

@@ -4,6 +4,8 @@ import request from '@/config/axios'
 export interface CourseTypeVO {
   id: number // 课程类型id
   ctType: string // 课程类型名称
+  ctTypeNode: undefined, // 课程类型节点
+  ctTypeSort: undefined, // 课程类型排序
   ctParentId: number // 课程类型父级id
   ctTypeDescribe: string // 课程类型描述
 }
@@ -38,5 +40,15 @@ export const CourseTypeApi = {
   // 导出课程-类型 Excel
   exportCourseType: async (params) => {
     return await request.download({ url: `/bjdx/course-type/export-excel`, params })
+  },
+
+
+  // 获取年级
+  getTypeGrade: async () => {
+    return request.get({ url: '/bjdx/web/course/getTypeGrade'})
+  },
+  // 根据年级id获取课程大纲
+  getTypeByGradeId: async (id: number) => {
+    return request.get({ url: '/bjdx/web/course/getTypeByGradeId?id=' + id })
   }
 }

+ 232 - 0
src/components/UploadFile/src/UploadMusic.vue

@@ -0,0 +1,232 @@
+<template>
+  <div v-if="!disabled" class="upload-file">
+    <el-upload
+      ref="uploadRef"
+      v-model:file-list="fileList"
+      :action="uploadUrl"
+      :auto-upload="autoUpload"
+      :before-upload="beforeUpload"
+      :disabled="disabled"
+      :drag="drag"
+      :http-request="httpRequest"
+      :limit="props.limit"
+      :multiple="props.limit > 1"
+      :on-error="excelUploadError"
+      :on-exceed="handleExceed"
+      :on-preview="handlePreview"
+      :on-remove="handleRemove"
+      :on-success="handleFileSuccess"
+      :show-file-list="true"
+      class="upload-file-uploader"
+      name="file"
+    >
+      <el-button type="primary">
+        <Icon icon="ep:upload-filled" />
+        选取视频
+      </el-button>
+      <template v-if="isShowTip" #tip>
+        <div style="font-size: 8px">
+          大小不超过 <b style="color: #f56c6c">{{ fileSize }}MB</b>
+        </div>
+        <div style="font-size: 8px">
+          格式为 <b style="color: #f56c6c">{{ fileType.join('/') }}</b> 的视频
+        </div>
+      </template>
+      <template #file="row">
+        <div class="flex items-center">
+          <span>{{ row.file.name }}</span>
+          <div class="ml-10px">
+            <el-link
+              :href="row.file.url"
+              :underline="false"
+              download
+              target="_blank"
+              type="primary"
+            >
+              下载
+            </el-link>
+          </div>
+          <div class="ml-10px">
+            <el-button link type="danger" @click="handleRemove(row.file)"> 删除</el-button>
+          </div>
+        </div>
+      </template>
+    </el-upload>
+  </div>
+
+  <!-- 上传操作禁用时 -->
+  <div v-if="disabled" class="upload-file">
+    <div v-for="(file, index) in fileList" :key="index" class="flex items-center file-list-item">
+      <span>{{ file.name }}</span>
+      <div class="ml-10px">
+        <el-link :href="file.url" :underline="false" download target="_blank" type="primary">
+          下载
+        </el-link>
+      </div>
+    </div>
+  </div>
+</template>
+<script lang="ts" setup>
+import { propTypes } from '@/utils/propTypes'
+import type { UploadInstance, UploadProps, UploadRawFile, UploadUserFile } from 'element-plus'
+import { isString } from '@/utils/is'
+import { useUpload } from '@/components/UploadFile/src/useUpload'
+import { UploadFile } from 'element-plus/es/components/upload/src/upload'
+
+defineOptions({ name: 'UploadFile' })
+
+const message = useMessage() // 消息弹窗
+const emit = defineEmits(['update:modelValue'])
+
+const props = defineProps({
+  modelValue: propTypes.oneOfType<string | string[]>([String, Array<String>]).isRequired,
+  fileType: propTypes.array.def(['MP3', 'WMA', 'AAC', 'FLAC', 'FAPE',
+                                      'mp3', 'wma', 'aac', 'flac', 'fape']), // 音频类型
+  fileSize: propTypes.number.def(50), // 大小限制(MB)
+  limit: propTypes.number.def(1), // 数量限制
+  autoUpload: propTypes.bool.def(true), // 自动上传
+  drag: propTypes.bool.def(false), // 拖拽上传
+  isShowTip: propTypes.bool.def(true), // 是否显示提示
+  disabled: propTypes.bool.def(false), // 是否禁用上传组件 ==> 非必传(默认为 false)
+  directory: propTypes.string.def(undefined) // 上传目录 ==> 非必传(默认为 undefined)
+})
+
+// ========== 上传相关 ==========
+const uploadRef = ref<UploadInstance>()
+const uploadList = ref<UploadUserFile[]>([])
+const fileList = ref<UploadUserFile[]>([])
+const uploadNumber = ref<number>(0)
+
+const { uploadUrl, httpRequest } = useUpload(props.directory)
+
+// 视频上传之前判断
+const beforeUpload: UploadProps['beforeUpload'] = (file: UploadRawFile) => {
+  if (fileList.value.length >= props.limit) {
+    message.error(`上传视频数量不能超过${props.limit}个!`)
+    return false
+  }
+  let fileExtension = ''
+  if (file.name.lastIndexOf('.') > -1) {
+    fileExtension = file.name.slice(file.name.lastIndexOf('.') + 1)
+  }
+  const isImg = props.fileType.some((type: string) => {
+    if (file.type.indexOf(type) > -1) return true
+    return !!(fileExtension && fileExtension.indexOf(type) > -1)
+  })
+  const isLimit = file.size < props.fileSize * 1024 * 1024
+  if (!isImg) {
+    message.error(`视频格式不正确, 请上传${props.fileType.join('/')}格式!`)
+    return false
+  }
+  if (!isLimit) {
+    message.error(`上传视频大小不能超过${props.fileSize}MB!`)
+    return false
+  }
+  message.success('正在上传视频,请稍候...')
+  uploadNumber.value++
+}
+// 处理上传的视频发生变化
+// const handleFileChange = (uploadFile: UploadFile): void => {
+//   uploadRef.value.data.path = uploadFile.name
+// }
+// 视频上传成功
+const handleFileSuccess: UploadProps['onSuccess'] = (res: any): void => {
+  message.success('上传成功')
+  // 删除自身
+  const index = fileList.value.findIndex((item) => item.response?.data === res.data)
+  fileList.value.splice(index, 1)
+  uploadList.value.push({ name: res.data, url: res.data })
+  if (uploadList.value.length == uploadNumber.value) {
+    fileList.value.push(...uploadList.value)
+    uploadList.value = []
+    uploadNumber.value = 0
+    emitUpdateModelValue()
+  }
+}
+// 视频数超出提示
+const handleExceed: UploadProps['onExceed'] = (): void => {
+  message.error(`上传视频数量不能超过${props.limit}个!`)
+}
+// 上传错误提示
+const excelUploadError: UploadProps['onError'] = (): void => {
+  message.error('导入数据失败,请您重新上传!')
+}
+// 删除上传视频
+const handleRemove = (file: UploadFile) => {
+  const index = fileList.value.map((f) => f.name).indexOf(file.name)
+  if (index > -1) {
+    fileList.value.splice(index, 1)
+    emitUpdateModelValue()
+  }
+}
+const handlePreview: UploadProps['onPreview'] = (uploadFile) => {
+  console.log(uploadFile)
+}
+
+// 监听模型绑定值变动
+watch(
+  () => props.modelValue,
+  (val: string | string[]) => {
+    if (!val) {
+      fileList.value = [] // fix:处理掉缓存,表单重置后上传组件的内容并没有重置
+      return
+    }
+
+    fileList.value = [] // 保障数据为空
+    // 情况1:字符串
+    if (isString(val)) {
+      fileList.value.push(
+        ...val.split(',').map((url) => ({ name: url.substring(url.lastIndexOf('/') + 1), url }))
+      )
+      return
+    }
+    // 情况2:数组
+    fileList.value.push(
+      ...(val as string[]).map((url) => ({ name: url.substring(url.lastIndexOf('/') + 1), url }))
+    )
+  },
+  { immediate: true, deep: true }
+)
+// 发送视频链接列表更新
+const emitUpdateModelValue = () => {
+  // 情况1:数组结果
+  let result: string | string[] = fileList.value.map((file) => file.url!)
+  // 情况2:逗号分隔的字符串
+  if (props.limit === 1 || isString(props.modelValue)) {
+    result = result.join(',')
+  }
+  emit('update:modelValue', result)
+}
+</script>
+<style lang="scss" scoped>
+.upload-file-uploader {
+  margin-bottom: 5px;
+}
+
+:deep(.upload-file-list .el-upload-list__item) {
+  position: relative;
+  margin-bottom: 10px;
+  line-height: 2;
+  border: 1px solid #e4e7ed;
+}
+
+:deep(.el-upload-list__item-file-name) {
+  max-width: 250px;
+}
+
+:deep(.upload-file-list .ele-upload-list__item-content) {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  color: inherit;
+}
+
+:deep(.ele-upload-list__item-content-action .el-link) {
+  margin-right: 10px;
+}
+
+.file-list-item {
+  border: 1px dashed var(--el-border-color-darker);
+  border-radius: 8px;
+}
+</style>

+ 1 - 1
src/components/UploadFile/src/UploadVideo.vue

@@ -82,7 +82,7 @@ const props = defineProps({
   modelValue: propTypes.oneOfType<string | string[]>([String, Array<String>]).isRequired,
   fileType: propTypes.array.def(['MP4', 'AVI', 'MOV', 'FLV',
                                       'mp4', 'avi', 'mov', 'flv']), // 视频类型
-  fileSize: propTypes.number.def(50), // 大小限制(MB)
+  fileSize: propTypes.number.def(64), // 大小限制(MB)
   limit: propTypes.number.def(1), // 数量限制
   autoUpload: propTypes.bool.def(true), // 自动上传
   drag: propTypes.bool.def(false), // 拖拽上传

+ 24 - 3
src/config/axios/index.ts

@@ -4,6 +4,9 @@ import { config } from './config'
 
 const { default_headers } = config
 
+// 设置更长的超时时间,单位为毫秒
+service.defaults.timeout = 60000; // 60 秒
+
 const request = (option: any) => {
   const { headersType, headers, ...otherOption } = option
   return service({
@@ -40,8 +43,26 @@ export default {
     return res as unknown as Promise<T>
   },
   upload: async <T = any>(option: any) => {
-    option.headersType = 'multipart/form-data'
-    const res = await request({ method: 'POST', ...option })
-    return res as unknown as Promise<T>
+    // 为上传请求单独设置更长的超时时间,单位为毫秒
+    const uploadTimeout = 120000; // 120 秒
+    option.timeout = option.timeout || uploadTimeout;
+
+    // 设置请求头为 multipart/form-data
+    option.headersType = 'multipart/form-data';
+
+    // 添加上传进度监听
+    option.onUploadProgress = (progressEvent: ProgressEvent) => {
+      if (progressEvent.lengthComputable) {
+        const percentCompleted = Math.round((progressEvent.loaded * 100) / progressEvent.total);
+        console.log(`上传进度: ${percentCompleted}%`);
+        // 你可以在这里触发自定义事件,通知组件上传进度
+        if (option.onProgress) {
+          option.onProgress(percentCompleted);
+        }
+      }
+    };
+
+    const res = await request({ method: 'POST', ...option });
+    return res as unknown as Promise<T>;
   }
 }

+ 1 - 0
src/utils/dict.ts

@@ -249,4 +249,5 @@ export enum DICT_TYPE {
   // ========== COURSE - 课程管理模块  ==========
   COURSE_QUESTION_TYPE = 'bjdx_quest_type', // 试题类型
   COURSE_LABEL = 'bjdx_course_label', // 课程标签
+  COURSE_COUTNET_TYPE = 'bjdx_course_content_type', // 课程内容类型
 }

+ 110 - 47
src/views/bjdx/course/CourseForm.vue

@@ -1,5 +1,5 @@
 <template>
-  <Dialog :title="dialogTitle" v-model="dialogVisible" width="800px">
+  <Dialog :title="dialogTitle" v-model="dialogVisible" width="1000px">
     <el-form
       ref="formRef"
       :model="formData"
@@ -11,61 +11,103 @@
         <el-tree-select
           v-model="formData.courseType"
           :data="courseTypeTree"
-          :props="{...defaultProps, label: 'ctType'}"
+          :props="{...defaultProps,
+            label: (node) => `${node.ctType}${node.ctTypeNode === '0' ? '(年级)' : '(大纲课程)'}`,
+            // 根据 ctTypeNode 字段判断是否禁用选项
+            disabled: (node) => node.ctTypeNode === '0'}"
           placeholder="请选择课程类型"
+          :default-expand-all="true"
         />
       </el-form-item>
       <el-form-item label="课程名称" prop="courseName">
         <el-input v-model="formData.courseName" placeholder="请输入课程名称" />
       </el-form-item>
-      <el-form-item label="课程视频" prop="coursePath">
-        <UploadVideo v-model="formData.coursePath" />
+
+
+      <el-form-item label="课程名称" prop="courseName">
+        <el-segmented v-model="formData.courseContentType" :options="getStrDictOptions(DICT_TYPE.COURSE_COUTNET_TYPE)" />
       </el-form-item>
-      <el-form-item label="课程内容" prop="courseContent">
+
+      <el-form-item v-if="formData.courseContentType === 'all'" label="课程内容" prop="courseContent">
         <Editor v-model="formData.courseContent" height="150px" />
       </el-form-item>
-      <el-form-item label="课程作者" prop="courseAuthor">
-        <el-input v-model="formData.courseAuthor" placeholder="请输入课程作者" />
+
+      <el-form-item v-if="formData.courseContentType === 'text'" label="纯文本" prop="coursePath">
+        <Editor v-model="formData.courseContent" height="150px"  />
       </el-form-item>
-      <el-form-item label="课程老师" prop="courseTeacher">
-        <el-input v-model="formData.courseTeacher" placeholder="请输入课程老师" />
+
+      <el-form-item v-if="formData.courseContentType === 'image'" label="课程图片集" prop="coursePath">
+        <UploadImgs v-model="formData.courseImagePath" />
       </el-form-item>
-      <el-form-item label="课程大小" prop="courseSize">
-        <el-input-number v-model="formData.courseSize" :min="1" :step="1" step-strictly>
-          <template #suffix><span>MB</span></template>
-        </el-input-number>
+
+      <el-form-item v-if="formData.courseContentType === 'music'" label="课程音频" prop="coursePath">
+        <UploadMusic v-model="formData.courseMusicPath" />
       </el-form-item>
-      <el-form-item label="课程时长" prop="courseTime">
-        <el-input-number v-model="formData.courseTime" :min="1" :step="1" step-strictly>
-          <template #suffix><span>秒</span></template>
-        </el-input-number>
+
+      <el-form-item v-if="formData.courseContentType === 'video'" label="课程视频" prop="coursePath">
+        <UploadVideo v-model="formData.courseVideoPath" />
       </el-form-item>
-      <el-form-item label="课程是否有检查" prop="courseIsInspect">
-        <el-radio-group v-model="formData.courseIsInspect">
-          <el-radio
-            v-for="dict in getStrDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING)"
-            :key="dict.value"
-            :label="dict.value"
+
+      <!--            <el-form-item label="课程大小" prop="courseSize">-->
+      <!--              <el-input-number v-model="formData.courseSize" :min="1" :step="1" step-strictly>-->
+      <!--                <template #suffix><span>MB</span></template>-->
+      <!--              </el-input-number>-->
+      <!--            </el-form-item>-->
+
+
+      <template v-if="formData.courseContentType === 'ailab'">
+        <el-form-item >
+          <br/><h2>AI实验室</h2><br/>
+        </el-form-item>
+      </template>
+
+      <template v-else-if="formData.courseContentType === 'quest'">
+        <el-form-item >
+          <br/><h2>问题</h2><br/>
+        </el-form-item>
+      </template>
+
+      <template v-else>
+        <el-form-item label="课程是否有检查" prop="courseIsInspect">
+          <el-radio-group v-model="formData.courseIsInspect">
+            <el-radio
+              v-for="dict in getStrDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING)"
+              :key="dict.value"
+              :label="dict.value"
+            >
+              {{ dict.label }}
+            </el-radio>
+          </el-radio-group>
+        </el-form-item>
+        <el-form-item label="课程耗时" prop="courseTime">
+          <el-input-number v-model="formData.courseTime" :min="1" :step="1" step-strictly>
+            <template #suffix><span>分</span></template>
+          </el-input-number>
+        </el-form-item>
+
+        <el-form-item label="课程作者" prop="courseAuthor">
+          <el-input v-model="formData.courseAuthor" placeholder="请输入课程作者" />
+        </el-form-item>
+        <el-form-item label="课程老师" prop="courseTeacher">
+          <el-input v-model="formData.courseTeacher" placeholder="请输入课程老师" />
+        </el-form-item>
+        <el-form-item label="课程标签" prop="courseLabel">
+          <el-select
+            v-model="formData.courseLabel"
+            placeholder="请选择课程标签"
+            multiple
+            clearable
           >
-            {{ dict.label }}
-          </el-radio>
-        </el-radio-group>
-      </el-form-item>
-      <el-form-item label="课程标签" prop="courseLabel">
-        <el-select
-          v-model="formData.courseLabel"
-          placeholder="请选择课程标签"
-          multiple
-          clearable
-        >
-          <el-option
-            v-for="dict in getStrDictOptions(DICT_TYPE.COURSE_LABEL)"
-            :key="dict.value"
-            :label="dict.label"
-            :value="dict.value"
-          />
-        </el-select>
-      </el-form-item>
+            <el-option
+              v-for="dict in getStrDictOptions(DICT_TYPE.COURSE_LABEL)"
+              :key="dict.value"
+              :label="dict.label"
+              :value="dict.value"
+            />
+          </el-select>
+        </el-form-item>
+      </template>
+
       <el-form-item label="课程状态" prop="courseStatus">
         <el-radio-group v-model="formData.courseStatus">
           <el-radio
@@ -103,7 +145,10 @@ const formType = ref('') // 表单的类型:create - 新增;update - 修改
 const formData = ref({
   id: undefined,
   courseName: undefined,
-  coursePath: undefined,
+  courseContentType: undefined,
+  courseImagePath: undefined,
+  courseVideoPath: undefined,
+  courseMusicPath: undefined,
   courseContent: undefined,
   courseAuthor: undefined,
   courseTeacher: undefined,
@@ -117,8 +162,8 @@ const formData = ref({
 const formRules = reactive({
   courseName: [{ required: true, message: '课程名称不能为空', trigger: 'blur' }],
   courseContent: [{ required: true, message: '课程内容不能为空', trigger: 'blur' }],
-  courseAuthor: [{ required: true, message: '课程作者不能为空', trigger: 'blur' }],
-  courseTeacher: [{ required: true, message: '课程老师不能为空', trigger: 'blur' }],
+  // courseAuthor: [{ required: true, message: '课程作者不能为空', trigger: 'blur' }],
+  // courseTeacher: [{ required: true, message: '课程老师不能为空', trigger: 'blur' }],
   // courseSize: [{ required: true, message: '课程大小不能为空', trigger: 'blur' }],
   // courseTime: [{ required: true, message: '课程时长不能为空', trigger: 'blur' }],
   // courseLabel: [{ required: true, message: '课程标签不能为空', trigger: 'blur' }]
@@ -177,7 +222,9 @@ const resetForm = () => {
   formData.value = {
     id: undefined,
     courseName: undefined,
-    coursePath: undefined,
+    courseContentType: undefined,
+    courseImagePath: undefined,
+    courseVideoPath: undefined,
     courseContent: undefined,
     courseAuthor: undefined,
     courseTeacher: undefined,
@@ -194,8 +241,24 @@ const resetForm = () => {
 const getCourseTypeTree = async () => {
   courseTypeTree.value = []
   const data = await CourseTypeApi.getCourseTypeList()
-  const root: Tree = { id: 0, ctType: '课程类型', children: [] }
+  const root: Tree = { id: 0, ctType: '课程类型', ctTypeNode: '0', children: [] }
   root.children = handleTree(data, 'id', 'ctParentId')
   courseTypeTree.value.push(root)
 }
 </script>
+
+<style>
+.demo-tabs > .el-tabs__content {
+  padding: 32px;
+  color: #6b778c;
+  font-size: 32px;
+  font-weight: 600;
+}
+.demo-tabs .custom-tabs-label .el-icon {
+  vertical-align: middle;
+}
+.demo-tabs .custom-tabs-label span {
+  vertical-align: middle;
+  margin-left: 4px;
+}
+</style>

+ 29 - 25
src/views/bjdx/course/index.vue

@@ -8,15 +8,32 @@
       :inline="true"
       label-width="90px"
     >
-      <el-form-item label="课程类型" prop="courseType">
+      <el-form-item label="大纲课程" prop="courseType">
         <el-tree-select
           v-model="queryParams.courseType"
           :data="courseTypeTree"
-          :props="{...defaultProps, label: 'ctType'}"
+          :props="{...defaultProps,
+            label: (node) => `${node.ctType}${node.ctTypeNode === '0' ? '(年级)' : '(大纲课程)'}`}"
           placeholder="请选择课程类型"
+          :default-expand-all="true"
           class="!w-240px"
         />
       </el-form-item>
+      <el-form-item label="内容类型" prop="courseContentType">
+        <el-select
+          v-model="queryParams.courseContentType"
+          placeholder="请选择内容类型"
+          clearable
+          class="!w-240px"
+        >
+          <el-option
+            v-for="dict in getStrDictOptions(DICT_TYPE.COURSE_COUTNET_TYPE)"
+            :key="dict.value"
+            :label="dict.label"
+            :value="dict.value"
+          />
+        </el-select>
+      </el-form-item>
       <el-form-item label="课程名称" prop="courseName">
         <el-input
           v-model="queryParams.courseName"
@@ -44,24 +61,6 @@
           class="!w-240px"
         />
       </el-form-item>
-      <el-form-item label="课程大小" prop="courseSize">
-        <el-input
-          v-model="queryParams.courseSize"
-          placeholder="请输入课程大小"
-          clearable
-          @keyup.enter="handleQuery"
-          class="!w-240px"
-        />
-      </el-form-item>
-      <el-form-item label="课程时长" prop="courseTime">
-        <el-input
-          v-model="queryParams.courseTime"
-          placeholder="请输入课程时长"
-          clearable
-          @keyup.enter="handleQuery"
-          class="!w-240px"
-        />
-      </el-form-item>
       <el-form-item label="是否有检查" prop="courseIsInspect">
         <el-select
           v-model="queryParams.courseIsInspect"
@@ -137,15 +136,18 @@
 <!--      <el-table-column label="课程d" align="center" prop="id" />-->
       <el-table-column label="课程类型" align="center" prop="courseTypeName" />
       <el-table-column label="课程名称" align="center" prop="courseName" />
-<!--      <el-table-column label="课程路径" align="center" prop="coursePath" />-->
-      <el-table-column label="课程内容" align="center" prop="courseContent" >
+      <el-table-column label="内容类型" align="center" prop="courseContentType" >
         <template #default="scope">
-          <div v-html="scope.row.courseContent"></div>
+          <dict-tag :type="DICT_TYPE.COURSE_COUTNET_TYPE" :value="scope.row.courseContentType" />
         </template>
       </el-table-column>
+<!--      <el-table-column label="课程路径" align="center" prop="courseVideoPath" />-->
+      <el-table-column label="课程内容" align="center" prop="courseContent" width="120px">
+          【预览查看】
+      </el-table-column>
       <el-table-column label="课程作者" align="center" prop="courseAuthor" />
       <el-table-column label="课程老师" align="center" prop="courseTeacher" />
-      <el-table-column label="课程大小" align="center" prop="courseSize" />
+<!--      <el-table-column label="课程大小" align="center" prop="courseSize" />-->
       <el-table-column label="课程时长" align="center" prop="courseTime" />
       <el-table-column label="是否有检查" align="center" prop="courseIsInspect">
         <template #default="scope">
@@ -236,7 +238,9 @@ const queryParams = reactive({
   pageNo: 1,
   pageSize: 10,
   courseName: undefined,
-  coursePath: undefined,
+  courseContentType: undefined,
+  courseImagePath: undefined,
+  courseVideoPath: undefined,
   courseContent: undefined,
   courseAuthor: undefined,
   courseTeacher: undefined,

+ 13 - 7
src/views/bjdx/coursequestion/CourseQuestionForm.vue

@@ -1,5 +1,5 @@
 <template>
-  <Dialog :title="dialogTitle" v-model="dialogVisible">
+  <Dialog :title="dialogTitle" v-model="dialogVisible" width="800px">
     <el-form
       ref="formRef"
       :model="formData"
@@ -7,18 +7,23 @@
       label-width="100px"
       v-loading="formLoading"
     >
+      <el-form-item label="试题类型" prop="cqQuestType">
+        <el-select v-model="formData.cqQuestType" placeholder="请选择试题类型">
+          <el-option
+            v-for="dict in getStrDictOptions(DICT_TYPE.COURSE_QUESTION_TYPE)"
+            :key="dict.value"
+            :label="dict.label"
+            :value="dict.value"
+          />
+        </el-select>
+      </el-form-item>
       <el-form-item label="试题内容" prop="cqQuestion">
         <Editor v-model="formData.cqQuestion" height="150px" />
       </el-form-item>
       <el-form-item label="试题解析" prop="cqQuestAnalysis">
         <Editor v-model="formData.cqQuestAnalysis" height="150px" />
       </el-form-item>
-      <el-form-item label="试题类型" prop="cqQuestType">
-        <el-select v-model="formData.cqQuestType" placeholder="请选择试题类型">
-          <el-option label="请选择字典生成" value="" />
-        </el-select>
-      </el-form-item>
-      <el-form-item label="试题答案id" prop="cqQuestAnswerId">
+      <el-form-item label="试题答案id" prop="cqQuestAnswerId" v-if="false">
         <el-input v-model="formData.cqQuestAnswerId" placeholder="请输入试题答案id" />
       </el-form-item>
     </el-form>
@@ -30,6 +35,7 @@
 </template>
 <script setup lang="ts">
 import { CourseQuestionApi, CourseQuestionVO } from '@/api/bjdx/coursequestion'
+import { DICT_TYPE, getStrDictOptions } from '@/utils/dict'
 
 /** 课程-试题 表单 */
 defineOptions({ name: 'CourseQuestionForm' })

+ 30 - 4
src/views/bjdx/coursetype/CourseTypeForm.vue

@@ -4,25 +4,42 @@
       ref="formRef"
       :model="formData"
       :rules="formRules"
-      label-width="100px"
+      label-width="120px"
       v-loading="formLoading"
     >
       <el-form-item label="父级类型" prop="ctParentId">
         <el-tree-select
           v-model="formData.ctParentId"
           :data="courseTypeTree"
-          :props="{...defaultProps, label: 'ctType'}"
+          :props="{ ...defaultProps, label: 'ctType' }"
           :default-expanded-keys="[0]"
           check-strictly
           default-expand-all
           placeholder="请选择课程父级类型"
         />
       </el-form-item>
+      <el-form-item label="课程类型节点" prop="ctTypeNode">
+        <el-segmented v-model="formData.ctTypeNode" :options="[
+          { label: '年级', value: 0 },
+          { label: '课程', value: 1 },
+        ]" />
+      </el-form-item>
       <el-form-item label="课程类型名称" prop="ctType">
         <el-input v-model="formData.ctType" placeholder="请输入课程类型名称" />
       </el-form-item>
+      <el-form-item label="课程封面" prop="ctType">
+        <UploadImg v-model="formData.ctTypeImage" />
+      </el-form-item>
       <el-form-item label="课程类型描述" prop="ctTypeDescribe">
-        <el-input type="textarea" v-model="formData.ctTypeDescribe" placeholder="请输入课程类型描述" />
+        <el-input
+          type="textarea"
+          v-model="formData.ctTypeDescribe"
+          placeholder="请输入课程类型描述"
+        />
+      </el-form-item>
+      <el-form-item label="课程类型排序" prop="ctTypeSort">
+        <el-input-number v-model="formData.ctTypeSort" :step="1" step-strictly placeholder="请输入课程类型排序"
+                 class="!w-100%"/>
       </el-form-item>
     </el-form>
     <template #footer>
@@ -48,10 +65,17 @@ const formType = ref('') // 表单的类型:create - 新增;update - 修改
 const formData = ref({
   id: undefined,
   ctType: undefined,
+  ctTypeImage: undefined,
+  ctTypeNode: 0,
+  ctTypeSort: undefined,
   ctParentId: 0,
   ctTypeDescribe: undefined
 })
 const formRules = reactive({
+  ctParentId: [{ required: true, message: '父级节点不能为空', trigger: 'blur' }],
+  ctTypeNode: [{ required: true, message: '节点类型不能为空', trigger: 'blur' }],
+  ctType: [{ required: true, message: '类型名称不能为空', trigger: 'blur' }],
+  ctTypeSort: [{ required: true, message: '排序不能为空', trigger: 'blur' }],
 })
 const formRef = ref() // 表单 Ref
 const courseTypeTree = ref() // 树形结构
@@ -107,7 +131,9 @@ const resetForm = () => {
   formData.value = {
     id: undefined,
     ctType: undefined,
-    ctParentId: undefined,
+    ctTypeNode: 0,
+    ctTypeSort: undefined,
+    ctParentId: 0,
     ctTypeDescribe: undefined
   }
   formRef.value?.resetFields()

+ 25 - 0
src/views/bjdx/coursetype/index.vue

@@ -17,6 +17,17 @@
           class="!w-240px"
         />
       </el-form-item>
+      <el-form-item label="课程类型节点" prop="ctTypeNode">
+        <el-select
+          v-model="queryParams.ctTypeNode"
+          placeholder="请选择类型节点类型"
+          clearable
+          class="!w-240px"
+        >
+          <el-option label="年级" value="0" />
+          <el-option label="大纲课程" value="1" />
+        </el-select>
+      </el-form-item>
       <el-form-item label="课程类型描述" prop="ctTypeDescribe">
         <el-input
           v-model="queryParams.ctTypeDescribe"
@@ -67,7 +78,19 @@
 <!--      <el-table-column label="课程类型id" align="center" prop="id" />-->
 <!--      <el-table-column label="课程类型父级id" align="center" prop="ctParentId" />-->
       <el-table-column label="课程类型名称" align="center" prop="ctType" />
+      <el-table-column label="节点类型" align="center" prop="ctTypeNode" >
+        <template #default="scope">
+          <el-tag type="info" v-if="scope.row.ctTypeNode === '0'">年级</el-tag>
+          <el-tag type="warning" v-else>大纲课程</el-tag>
+        </template>
+      </el-table-column>
+      <el-table-column label="封面" align="center" prop="ctTypeImage" >
+        <template #default="scope">
+          <img :src="scope.row.ctTypeImage" class="w-150px"/>
+        </template>
+      </el-table-column>
       <el-table-column label="课程类型描述" align="center" prop="ctTypeDescribe" />
+      <el-table-column label="排序" align="center" prop="ctTypeSort" />
       <el-table-column label="操作" align="center" min-width="120px">
         <template #default="scope">
           <el-button
@@ -116,6 +139,7 @@ import download from '@/utils/download'
 import { CourseTypeApi, CourseTypeVO } from '@/api/bjdx/coursetype'
 import CourseTypeForm from './CourseTypeForm.vue'
 import { ElButton } from 'element-plus'
+import { DICT_TYPE, getStrDictOptions } from '@/utils/dict'
 
 /** 课程-类型 列表 */
 defineOptions({ name: 'CourseType' })
@@ -127,6 +151,7 @@ const loading = ref(true) // 列表的加载中
 const list = ref<CourseTypeVO[]>([]) // 列表的数据
 const queryParams = reactive({
   ctType: undefined,
+  ctTypeNode: undefined,
   ctParentId: undefined,
   ctTypeDescribe: undefined
 })