yjwang 1 týždeň pred
rodič
commit
0452e2056f

+ 8 - 2
src/views/course/courseWatchComment/index.vue

@@ -237,6 +237,8 @@ export default {
   name: "CourseWatchComment",
   data() {
     return {
+      /** 与后端 cate_type 一致:0-评论(课程),1-公域看课评论 */
+      pageCateType: 0,
       // 弹幕状态选项
       barrageStatusOptions: [
         { dictValue: 0, dictLabel: "正常" },
@@ -274,6 +276,7 @@ export default {
         isAll: true, //判断是否属于全局查询
         status: '',
         userStatus: '',
+        cateType: 0,
         // userId: null,
         // userType: null,
         // courseId: null,
@@ -303,6 +306,7 @@ export default {
     /** 查询看课评论列表 */
     getList() {
       this.loading = true;
+      this.queryParams.cateType = this.pageCateType;
       listCourseWatchComment(this.queryParams).then(response => {
         this.courseWatchCommentList = response.rows.list;
         this.total = response.rows.total;
@@ -357,6 +361,7 @@ export default {
     /** 重置按钮操作 */
     resetQuery() {
       this.resetForm("queryForm");
+      this.queryParams.cateType = this.pageCateType;
       this.handleQuery();
     },
     // 多选框选中数据
@@ -376,6 +381,7 @@ export default {
       this.reset();
       this.form.status = row.status;
       this.form.commentId = row.commentId;
+      this.form.cateType = this.pageCateType;
       this.updateStatus = true;
       this.title = "修改弹幕状态";
     },
@@ -425,7 +431,7 @@ export default {
       this.$refs["form"].validate(valid => {
         if (valid) {
           console.log(this.form)
-          updateBarrageStatus(this.form).then(response => {
+          updateBarrageStatus({ ...this.form, cateType: this.pageCateType }).then(response => {
             this.msgSuccess("修改成功");
             this.updateStatus = false;
             this.getList();
@@ -484,7 +490,7 @@ export default {
     },
     /** 导出按钮操作 */
     handleExport() {
-      const queryParams = this.queryParams;
+      const queryParams = { ...this.queryParams, cateType: this.pageCateType };
       this.$confirm('是否确认导出当前看课评论数据项?', "警告", {
         confirmButtonText: "确定",
         cancelButtonText: "取消",

+ 280 - 0
src/views/course/publiccourse/commentSensitive/index.vue

@@ -0,0 +1,280 @@
+<template>
+  <div class="app-container">
+    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="80px">
+      <el-form-item label="敏感词" prop="word">
+        <el-input
+          v-model="queryParams.word"
+          placeholder="请输入需要过滤的敏感词"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="敏感词添加时间" prop="createTime" label-width="120px">
+        <el-date-picker clearable size="small"
+                        v-model="queryParams.createTime"
+                        type="date"
+                        value-format="yyyy-MM-dd"
+                        placeholder="选择敏感词添加时间">
+        </el-date-picker>
+      </el-form-item>
+      <el-form-item>
+        <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
+        <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
+      </el-form-item>
+    </el-form>
+
+    <el-row :gutter="10" class="mb8">
+      <el-col :span="1.5">
+        <el-button
+          type="primary"
+          plain
+          icon="el-icon-plus"
+          size="mini"
+          @click="handleAdd"
+          v-hasPermi="['live:words:add']"
+        >新增</el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button
+          type="success"
+          plain
+          icon="el-icon-edit"
+          size="mini"
+          :disabled="single"
+          @click="handleUpdate"
+          v-hasPermi="['live:words:edit']"
+        >修改</el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button
+          type="danger"
+          plain
+          icon="el-icon-delete"
+          size="mini"
+          :disabled="multiple"
+          @click="handleDelete"
+          v-hasPermi="['live:words:remove']"
+        >删除</el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button
+          type="warning"
+          plain
+          icon="el-icon-download"
+          size="mini"
+          :loading="exportLoading"
+          @click="handleExport"
+          v-hasPermi="['live:words:export']"
+        >导出</el-button>
+      </el-col>
+      <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
+    </el-row>
+
+    <el-table border v-loading="loading" :data="wordsList" @selection-change="handleSelectionChange">
+      <el-table-column type="selection" width="55" align="center" />
+      <el-table-column label="敏感词唯一标识" align="center" prop="wordId" />
+      <el-table-column label="需要过滤的敏感词" align="center" prop="word" />
+      <el-table-column label="敏感词添加时间" align="center" prop="createTime" width="180">
+        <template slot-scope="scope">
+          <span>{{ parseTime(scope.row.createTime, '{y}-{m}-{d}') }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
+        <template slot-scope="scope">
+          <el-button
+            size="mini"
+            type="text"
+            icon="el-icon-edit"
+            @click="handleUpdate(scope.row)"
+            v-hasPermi="['live:words:edit']"
+          >修改</el-button>
+          <el-button
+            size="mini"
+            type="text"
+            icon="el-icon-delete"
+            @click="handleDelete(scope.row)"
+            v-hasPermi="['live:words:remove']"
+          >删除</el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+
+    <pagination
+      v-show="total>0"
+      :total="total"
+      :page.sync="queryParams.pageNum"
+      :limit.sync="queryParams.pageSize"
+      @pagination="getList"
+    />
+
+    <!-- 添加或修改直播间敏感词过滤对话框 -->
+    <el-dialog :title="title" :visible.sync="open" width="500px" append-to-body>
+      <el-form ref="form" :model="form" :rules="rules" label-width="120px">
+        <el-form-item label="敏感词" prop="word">
+          <el-input v-model="form.word" placeholder="请输入需要过滤的敏感词" />
+        </el-form-item>
+      </el-form>
+      <div slot="footer" class="dialog-footer">
+        <el-button type="primary" @click="submitForm">确 定</el-button>
+        <el-button @click="cancel">取 消</el-button>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import {listWords, getWords, delWords, addWords, updateWords, exportWords} from "@/api/live/words";
+
+export default {
+  name: "Words",
+  data() {
+    return {
+      // 遮罩层
+      loading: true,
+      // 导出遮罩层
+      exportLoading: false,
+      // 选中数组
+      ids: [],
+      // 非单个禁用
+      single: true,
+      // 非多个禁用
+      multiple: true,
+      // 显示搜索条件
+      showSearch: true,
+      // 总条数
+      total: 0,
+      // 直播间敏感词过滤表格数据
+      wordsList: [],
+      // 弹出层标题
+      title: "",
+      // 是否显示弹出层
+      open: false,
+      // 查询参数
+      queryParams: {
+        pageNum: 1,
+        pageSize: 10,
+        word: null,
+        createTime: null
+      },
+      // 表单参数
+      form: {},
+      // 表单校验
+      rules: {
+        word: [
+          { required: true, message: "需要过滤的敏感词不能为空", trigger: "blur" }
+        ],
+      }
+    };
+  },
+  created() {
+    this.getList();
+  },
+  methods: {
+    /** 查询直播间敏感词过滤列表 */
+    getList() {
+      this.loading = true;
+      listWords(this.queryParams).then(response => {
+        this.wordsList = response.rows;
+        this.total = response.total;
+        this.loading = false;
+      });
+    },
+    // 取消按钮
+    cancel() {
+      this.open = false;
+      this.reset();
+    },
+    // 表单重置
+    reset() {
+      this.form = {
+        wordId: null,
+        word: null,
+        createTime: null
+      };
+      this.resetForm("form");
+    },
+    /** 搜索按钮操作 */
+    handleQuery() {
+      this.queryParams.pageNum = 1;
+      this.getList();
+    },
+    /** 重置按钮操作 */
+    resetQuery() {
+      this.resetForm("queryForm");
+      this.handleQuery();
+    },
+    // 多选框选中数据
+    handleSelectionChange(selection) {
+      this.ids = selection.map(item => item.wordId)
+      this.single = selection.length!==1
+      this.multiple = !selection.length
+    },
+    /** 新增按钮操作 */
+    handleAdd() {
+      this.reset();
+      this.open = true;
+      this.title = "添加直播间敏感词过滤";
+    },
+    /** 修改按钮操作 */
+    handleUpdate(row) {
+      this.reset();
+      const wordId = row.wordId || this.ids
+      getWords(wordId).then(response => {
+        this.form = response.data;
+        this.open = true;
+        this.title = "修改直播间敏感词过滤";
+      });
+    },
+    /** 提交按钮 */
+    submitForm() {
+      this.$refs["form"].validate(valid => {
+        if (valid) {
+          if (this.form.wordId != null) {
+            updateWords(this.form).then(response => {
+              this.msgSuccess("修改成功");
+              this.open = false;
+              this.getList();
+            });
+          } else {
+            addWords(this.form).then(response => {
+              this.msgSuccess("新增成功");
+              this.open = false;
+              this.getList();
+            });
+          }
+        }
+      });
+    },
+    /** 删除按钮操作 */
+    handleDelete(row) {
+      const wordIds = row.wordId || this.ids;
+      this.$confirm('是否确认删除直播间敏感词过滤编号为"' + wordIds + '"的数据项?', "警告", {
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "warning"
+      }).then(function() {
+        return delWords(wordIds);
+      }).then(() => {
+        this.getList();
+        this.msgSuccess("删除成功");
+      }).catch(() => {});
+    },
+    /** 导出按钮操作 */
+    handleExport() {
+      const queryParams = this.queryParams;
+      this.$confirm('是否确认导出所有直播间敏感词过滤数据项?', "警告", {
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "warning"
+      }).then(() => {
+        this.exportLoading = true;
+        return exportWords(queryParams);
+      }).then(response => {
+        this.download(response.msg);
+        this.exportLoading = false;
+      }).catch(() => {});
+    }
+  }
+};
+</script>

+ 753 - 0
src/views/course/publiccourse/courseQuestionBank/index.vue

@@ -0,0 +1,753 @@
+<template>
+  <div class="app-container">
+    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
+      <el-form-item label="问题" prop="title">
+        <el-input
+          v-model="queryParams.title"
+          placeholder="请输入问题标题"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <!--      <el-form-item label="排序" prop="sort">-->
+      <!--        <el-input-->
+      <!--          v-model="queryParams.sort"-->
+      <!--          placeholder="请输入排序"-->
+      <!--          clearable-->
+      <!--          size="small"-->
+      <!--          @keyup.enter.native="handleQuery"-->
+      <!--        />-->
+      <!--      </el-form-item>-->
+      <el-form-item label="类别" prop="type">
+        <el-select v-model="queryParams.type" placeholder="请选择类别" clearable size="small">
+          <el-option
+            v-for="dict in typeOptions"
+            :key="dict.dictValue"
+            :label="dict.dictLabel"
+            :value="dict.dictValue"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="题目类别" prop="questionType">
+        <el-select v-model="queryParams.questionType" ref="typeSelect" placeholder="请选择类别" @change="changeCateType" clearable size="small">
+          <el-option
+            v-for="item in questionRootTypeOptions"
+            :key="item.dictValue"
+            :label="item.dictLabel"
+            :value="item.dictValue"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="题目子类别" prop="questionSubType" label-width="100px">
+        <el-select v-model="queryParams.questionSubType" ref="typeSelect" placeholder="请选择子类别" clearable size="small">
+          <el-option
+            v-for="item in questionSubTypeOptions"
+            :key="item.dictValue"
+            :label="item.dictLabel"
+            :value="item.dictValue"
+          />
+        </el-select>
+      </el-form-item>
+
+
+      <!--      <el-form-item label="状态" prop="status">-->
+      <!--        <el-select v-model="queryParams.status" placeholder="请选择状态" clearable size="small">-->
+      <!--          <el-option-->
+      <!--            v-for="dict in statusOptions"-->
+      <!--            :key="dict.dictValue"-->
+      <!--            :label="dict.dictLabel"-->
+      <!--            :value="dict.dictValue"-->
+      <!--          />-->
+      <!--        </el-select>-->
+      <!--      </el-form-item>-->
+      <el-form-item>
+        <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
+        <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
+      </el-form-item>
+    </el-form>
+
+    <el-row :gutter="10" class="mb8">
+      <el-col :span="1.5">
+        <el-button
+          type="primary"
+          plain
+          icon="el-icon-plus"
+          size="mini"
+          @click="handleAdd"
+          v-hasPermi="['course:courseQuestionBank:add']"
+        >新增</el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button
+          type="success"
+          plain
+          icon="el-icon-edit"
+          size="mini"
+          :disabled="single"
+          @click="handleUpdate"
+          v-hasPermi="['course:courseQuestionBank:edit']"
+        >修改</el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button
+          type="danger"
+          plain
+          icon="el-icon-delete"
+          size="mini"
+          :disabled="multiple"
+          @click="handleDelete"
+          v-hasPermi="['course:courseQuestionBank:remove']"
+        >删除</el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button
+          type="warning"
+          plain
+          icon="el-icon-download"
+          size="mini"
+          :loading="exportLoading"
+          @click="handleExport"
+          v-hasPermi="['course:courseQuestionBank:export']"
+        >导出</el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button
+          plain
+          type="info"
+          icon="el-icon-upload2"
+          size="mini"
+          @click="handleImport"
+          v-hasPermi="['course:courseQuestionBank:importData']"
+        >导入</el-button>
+      </el-col>
+      <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
+    </el-row>
+
+    <el-table border v-loading="loading" :data="courseQuestionBankList" @selection-change="handleSelectionChange">
+      <el-table-column type="selection" width="55" align="center" />
+      <el-table-column label="标题" align="center" prop="title" show-overflow-tooltip width="300" />
+      <el-table-column label="类别" align="center" prop="type">
+        <template slot-scope="scope">
+          <dict-tag :options="typeOptions" :value="scope.row.type"/>
+        </template>
+      </el-table-column>
+      <el-table-column label="题目类别" align="center" prop="questionType">
+        <template slot-scope="scope">
+          <el-tag v-if="scope.row.questionType">{{ getCategoryName(scope.row.questionType) }}</el-tag>
+        </template>
+      </el-table-column>
+      <el-table-column label="题目子类别" align="center" prop="questionType">
+        <template slot-scope="scope">
+          <el-tag v-if="scope.row.questionSubType">{{ getCategoryName(scope.row.questionSubType) }}</el-tag>
+        </template>
+      </el-table-column>
+      <el-table-column label="状态" align="center" prop="status">
+        <template slot-scope="scope">
+          <dict-tag :options="statusOptions" :value="scope.row.status"/>
+        </template>
+      </el-table-column>
+      <el-table-column label="排序" align="center" prop="sort" />
+      <el-table-column v-if="!hideAnswerFields" label="答案" align="center" prop="answer" />
+      <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
+        <template slot-scope="scope">
+          <el-button
+            size="mini"
+            type="text"
+            icon="el-icon-edit"
+            @click="handleUpdate(scope.row)"
+            v-hasPermi="['course:courseQuestionBank:edit']"
+          >修改</el-button>
+          <el-button
+            size="mini"
+            type="text"
+            icon="el-icon-delete"
+            @click="handleDelete(scope.row)"
+            v-hasPermi="['course:courseQuestionBank:remove']"
+          >删除</el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+
+    <pagination
+      v-show="total>0"
+      :total="total"
+      :page.sync="queryParams.pageNum"
+      :limit.sync="queryParams.pageSize"
+      @pagination="getList"
+    />
+
+    <!-- 添加或修改题库对话框 -->
+    <el-dialog :title="title" :visible.sync="open" width="1000px" append-to-body>
+      <el-form ref="form" :model="form" :rules="dynamicRules" label-width="80px">
+        <el-form-item label="标题" prop="title">
+          <el-input v-model="form.title" placeholder="请输入问题" />
+        </el-form-item>
+        <el-form-item label="排序" prop="sort">
+          <el-input-number v-model="form.sort"  :min="0" label="请输入排序"></el-input-number>
+        </el-form-item>
+        <el-form-item label="类别 " prop="type">
+          <el-select v-model="form.type" placeholder="请选择类别" @change="changeType">
+            <el-option
+              v-for="dict in typeOptions"
+              :key="dict.dictValue"
+              :label="dict.dictLabel"
+              :value="parseInt(dict.dictValue)"
+            ></el-option>
+          </el-select>
+        </el-form-item>
+        <el-form-item label="题目类别 " prop="questionType">
+          <el-select v-model="form.questionType" clearable placeholder="请选择题目类别" @change="changeCateType">
+            <el-option
+              v-for="item in questionRootTypeOptions"
+              :key="item.dictValue"
+              :label="item.dictLabel"
+              :value="item.dictValue"
+            />
+          </el-select>
+          <el-select v-model="form.questionSubType" clearable placeholder="请选择题目子类别" >
+            <el-option
+              v-for="item in questionSubTypeOptions"
+              :key="item.dictValue"
+              :label="item.dictLabel"
+              :value="item.dictValue"
+            />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="状态">
+          <el-radio-group v-model="form.status">
+            <el-radio
+              v-for="dict in statusOptions"
+              :key="dict.dictValue"
+              :label="parseInt(dict.dictValue)"
+            >{{dict.dictLabel}}</el-radio>
+          </el-radio-group>
+        </el-form-item>
+        <!-- 提示用户选择类别 -->
+        <el-alert v-if="!form.type" title="选择题目类别后添加选项" type="warning" show-icon></el-alert>
+
+        <el-form-item label="选项"  v-if="form.type">
+          <el-button @click="addRow" size="mini" type="primary">新增选项</el-button>
+          <el-table border :data="question"  :cell-style="{ textAlign: 'center' }"		 :header-cell-style="{textAlign: 'center'}"   >
+            <el-table-column label="序号" width="65px" >
+              <template slot-scope="scope">
+                {{ '选项'+getOptionLabel(scope.$index)+':' }}
+              </template>
+            </el-table-column>
+            <el-table-column label="选项" prop="name" >
+              <template slot-scope="scope">
+                <el-input v-model="scope.row.name" :placeholder="getOptionLabel(scope.$index) + ':' + '请输入标题'" ></el-input>
+              </template>
+            </el-table-column>
+            <el-table-column v-if="!hideAnswerFields" label="是否为答案" prop="isWrite" >
+              <template slot-scope="scope">
+                <el-tooltip
+                  v-if="!scope.row.name"
+                  content="请先填写选项标题内容"
+                  placement="top"
+                  effect="dark"
+                >
+                  <el-switch
+                    v-model="scope.row.isAnswer"
+                    :active-value="1"
+                    :inactive-value="0"
+                    @change="handleAnswerChange(scope.row,scope.$index)"
+                    :disabled="Boolean(!scope.row.name || (form.type === 1 && scope.row.isAnswer && selectedAnswer !== scope.row))"
+                  ></el-switch>
+                </el-tooltip>
+                <el-switch
+                  v-else
+                  v-model="scope.row.isAnswer"
+                  :active-value="1"
+                  :inactive-value="0"
+                  @change="handleAnswerChange(scope.row,scope.$index)"
+                  :disabled="Boolean(form.type === 1 && scope.row.isAnswer && selectedAnswer !== scope.row)"
+                ></el-switch>
+              </template>
+            </el-table-column>
+            <el-table-column label="操作">
+              <template slot-scope="scope">
+                <el-button @click="deleteRow(scope.$index)"   size="mini" type="text" v-if="question.length>1">删除</el-button>
+              </template>
+            </el-table-column>
+          </el-table>
+        </el-form-item>
+        <el-form-item v-if="!hideAnswerFields" label="答案:" prop="answer">
+          <span style="background-color: #faedc9; font-size: 20px;">
+            {{ form.answer }}
+          </span>
+        </el-form-item>
+      </el-form>
+      <div slot="footer" class="dialog-footer">
+        <el-button type="primary" @click="submitForm">确 定</el-button>
+        <el-button @click="cancel">取 消</el-button>
+      </div>
+    </el-dialog>
+
+    <!-- 导入 -->
+    <el-dialog :title="upload.title" :visible.sync="upload.open" width="400px" append-to-body>
+      <el-upload ref="upload" :limit="1" accept=".xlsx, .xls" :headers="upload.headers" :action="upload.url" :disabled="upload.isUploading" :on-progress="handleFileUploadProgress" :on-success="handleFileSuccess" :auto-upload="false" drag>
+        <i class="el-icon-upload"></i>
+        <div class="el-upload__text">
+          将文件拖到此处,或
+          <em>点击上传</em>
+        </div>
+        <div class="el-upload__tip" slot="tip">
+          <el-link type="info" style="font-size:12px" @click="importTemplate">下载模板</el-link>
+        </div>
+        <div class="el-upload__tip" style="color:red" slot="tip">提示:仅允许导入“xls”或“xlsx”格式文件!</div>
+      </el-upload>
+      <div slot="footer" class="dialog-footer">
+        <el-button type="primary" :loading="upload.isUploading" :disabled="upload.isUploading" @click="submitFileForm">确 定</el-button>
+        <el-button @click="upload.open = false">取 消</el-button>
+      </div>
+    </el-dialog>
+    <el-dialog title="导入结果" :close-on-press-escape="false" :close-on-click-modal="false" :visible.sync="importMsgOpen" width="500px" append-to-body>
+      <div class="import-msg" v-html="importMsg">
+      </div>
+      <div slot="footer" class="dialog-footer">
+        <el-button @click="importMsgOpen = false">关 闭</el-button>
+        <el-button
+          v-if="failList && failList.length > 0"
+          type="primary"
+          @click="handleExportFailList(failList)"
+          :loading="exportFailLoading"
+        >导出失败信息</el-button>
+      </div>
+    </el-dialog>
+
+  </div>
+</template>
+
+<script>
+import {
+  listCourseQuestionBank,
+  getCourseQuestionBank,
+  delCourseQuestionBank,
+  addCourseQuestionBank,
+  updateCourseQuestionBank,
+  exportCourseQuestionBank,
+  importTemplate,
+  exportFail
+} from "@/api/course/courseQuestionBank";
+import { getToken } from "@/utils/auth";
+import {
+  listUserCourseCategory,
+  getCatePidList,
+  getCateListByPid
+} from '@/api/course/userCourseCategory'
+import { getConfigByKey } from '@/api/system/config'
+
+export default {
+  name: "CourseQuestionBank",
+  computed: {
+    alphabet() {
+      return 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
+    },
+    /** 点播配置:不校验答案时隐藏列表/表单中的答案相关项(与 validateAnswerWhenWatch 为 false / "0" 一致) */
+    hideAnswerFields() {
+      const cfg = this.courseConfig;
+      if (!cfg || !Object.prototype.hasOwnProperty.call(cfg, 'validateAnswerWhenWatch')) {
+        return false;
+      }
+      const v = cfg.validateAnswerWhenWatch;
+      return v === false || v === '0' || v === 0 || v === 'false';
+    },
+    dynamicRules() {
+      const r = { ...this.rules };
+      if (this.hideAnswerFields) {
+        delete r.answer;
+      }
+      return r;
+    },
+  },
+  watch: {
+  },
+  mounted() {
+  },
+  data() {
+    return {
+      /** course.config 解析后的对象 */
+      courseConfig: {},
+      exportFailLoading: false,
+      //单选
+      selectedAnswer:null,
+      //多选
+      selectedAnswers: [],
+
+      typeOptions: [],
+      statusOptions: [],
+      question:[
+        { name: "", isAnswer: 0, indexId:0},
+        { name: "", isAnswer: 0, indexId:1},
+        { name: "", isAnswer: 0, indexId:2},
+        { name: "", isAnswer: 0, indexId:3},
+      ],
+      // 遮罩层
+      loading: true,
+      // 导出遮罩层
+      exportLoading: false,
+      // 选中数组
+      ids: [],
+      // 非单个禁用
+      single: true,
+      // 非多个禁用
+      multiple: true,
+      // 显示搜索条件
+      showSearch: true,
+      // 总条数
+      total: 0,
+      // 题库表格数据
+      courseQuestionBankList: [],
+      questionTypeOptions: [],
+      questionRootTypeOptions: [],
+      questionSubTypeOptions: [],
+      // 弹出层标题
+      title: "",
+      // 是否显示弹出层
+      open: false,
+      // 查询参数
+      queryParams: {
+        pageNum: 1,
+        pageSize: 10,
+        title: null,
+        sort: null,
+        type: null,
+        status: null,
+        question: null,
+        questionType:null,
+        questionSubType: null,
+        answer: null,
+      },
+      // 表单参数
+      form: {},
+      // 表单校验
+      rules: {
+        title: [{ required: true, message: "不能为空", trigger: "blur" }],
+        sort: [{ required: true, message: "不能为空", trigger: "blur" }],
+        type: [{ required: true, message: "不能为空", trigger: "blur" }],
+        status: [{ required: true, message: "不能为空", trigger: "blur" }],
+        question: [{ required: true, message: "不能为空", trigger: "blur" }],
+        answer: [{ required: true, message: "不能为空", trigger: "blur" }],
+      },
+      // 导入
+      importMsgOpen:false,
+      failList:[],
+      importMsg: '',
+      upload: {
+        // 是否显示弹出层(文件导入)
+        open: false,
+        // 弹出层标题(文件导入)
+        title: "",
+        // 是否禁用上传
+        isUploading: false,
+        // 设置上传的请求头部
+        headers: { Authorization: "Bearer " + getToken() },
+        // 上传的地址
+        url: process.env.VUE_APP_BASE_API + "/course/courseQuestionBank/importData",
+      }
+    };
+  },
+  created() {
+    this.getList();
+    getConfigByKey('course.config').then(response => {
+      if (response.data && response.data.configValue) {
+        try {
+          this.courseConfig = JSON.parse(response.data.configValue) || {};
+        } catch (e) {
+          this.courseConfig = {};
+        }
+      }
+    }).catch(() => { this.courseConfig = {}; });
+    this.getDicts("sys_course_temp_type").then(response => {
+      this.typeOptions = response.data;
+    });
+    this.getDicts("sys_company_status").then(response => {
+      this.statusOptions = response.data;
+    });
+    this.getCategoryTree()
+  },
+  methods: {
+    getOptionLabel(index) {
+      return this.alphabet[index];
+    },
+// 导出失败信息
+    handleExportFailList(failList) {
+      if (!failList || failList.length === 0) {
+        this.msgWarning("没有失败信息可导出");
+        return;
+      }
+
+
+      this.exportFailLoading = true;
+      this.$confirm('是否确认导出失败信息?', "提示", {
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "info"
+      }).then(() => {
+        // 调用导出失败信息的API
+        exportFail(failList).then(response => {
+          this.download(response.msg);
+          this.msgSuccess("失败信息导出成功");
+          this.exportFailLoading = false;
+        }).catch(() => {
+          this.msgError("失败信息导出失败");
+          this.exportFailLoading = false;
+        });
+      }).catch(() => {
+        this.exportFailLoading = false;
+      });
+    },
+
+    // 分类树
+    getCategoryTree() {
+      listUserCourseCategory().then(response => {
+        this.questionTypeOptions = response.data
+      });
+      getCatePidList().then(response => {
+        this.questionRootTypeOptions = response.data
+      });
+    },
+    changeCateType(val) {
+      if (!val) {
+        return
+      }
+      getCateListByPid(val).then(response => {
+        this.questionSubTypeOptions = response.data
+      })
+    },
+    getCategoryName(id) {
+      return this.questionTypeOptions.find(item => item.cateId === id)?.cateName || '';
+    },
+    //题目类型发生变化
+    changeType(){
+      this.selectedAnswers = [];
+      this.selectedAnswer = null;
+      this.question.forEach(item => {
+        item.isAnswer = 0; // 清除所有选项的选中状态
+      });
+      // 重置答案的显示值
+      this.form.answer = null;
+    },
+    //选择答案
+    handleAnswerChange(row, index) {
+      if (this.form.type === 1) {
+        // 单选模式:记录选中的答案并取消其他选项
+        this.selectedAnswer = row;
+        this.question.forEach((item) => {
+          if (item !== row) {
+            item.isAnswer = 0;
+          }
+        });
+        // 保存单选的答案
+        // this.form.answer = this.getOptionLabel(index);
+        this.form.answer = row.name;
+      } else if (this.form.type === 2) {
+
+        // 多选模式:将所有被选中的答案存储到 selectedAnswers 数组 先检查当前选项是否已经在选中答案列表中
+        if (row.isAnswer === 1) {
+          // 如果选中,将该答案加入到选中数组
+          this.selectedAnswers.push(row);
+        } else {
+          // 如果取消选中,从选中数组中移除
+          this.selectedAnswers = this.selectedAnswers.filter(item => item.indexId !== row.indexId);
+        }
+
+        // 对 selectedAnswers 进行排序 (假设按 row.indexId 排序)
+        this.selectedAnswers.sort((a, b) => a.indexId - b.indexId);
+
+        // 更新 form.answer,保存所有选中的答案
+        this.form.answer = this.selectedAnswers.map(item => item.name);
+      }
+
+    },
+    addRow() {
+      this.question.push({ name: '', isAnswer: 0,indexId: this.question.length});},
+    deleteRow(index) {
+      if (this.form.type === 1 && this.question[index] === this.selectedAnswer) {
+        this.selectedAnswer = null;
+      }
+      this.question.splice(index, 1);
+    },
+    /** 查询题库列表 */
+    getList() {
+      this.loading = true;
+      listCourseQuestionBank(this.queryParams).then(response => {
+        this.courseQuestionBankList = response.rows;
+        this.total = response.total;
+        this.loading = false;
+      });
+    },
+    // 取消按钮
+    cancel() {
+      this.open = false;
+      this.reset();
+      this.changeCateType(this.queryParams.questionType)
+    },
+    // 表单重置
+    reset() {
+      this.form = {
+        id: null,
+        title: null,
+        sort: null,
+        type: 1,
+        status: 1,
+        question: null,
+        createTime: null,
+        answer: null,
+        createBy: null
+      };
+      this.resetForm("form");
+      this.question = [
+        { name: "", isAnswer: 0, indexId:0},
+        { name: "", isAnswer: 0, indexId:1},
+        { name: "", isAnswer: 0, indexId:2},
+        { name: "", isAnswer: 0, indexId:3},
+      ]
+    },
+    /** 搜索按钮操作 */
+    handleQuery() {
+      this.queryParams.pageNum = 1;
+      this.getList();
+    },
+    /** 重置按钮操作 */
+    resetQuery() {
+      this.resetForm("queryForm");
+      this.handleQuery();
+    },
+    // 多选框选中数据
+    handleSelectionChange(selection) {
+      this.ids = selection.map(item => item.id)
+      this.single = selection.length!==1
+      this.multiple = !selection.length
+    },
+    /** 新增按钮操作 */
+    handleAdd() {
+      this.reset();
+      this.questionSubTypeOptions = []
+      this.open = true;
+      this.title = "添加题库";
+    },
+    /** 修改按钮操作 */
+    handleUpdate(row) {
+      this.reset();
+      const id = row.id || this.ids
+      getCourseQuestionBank(id).then(response => {
+        this.form = response.data;
+        this.changeCateType(this.form.questionType)
+
+        //初始化多选的选择结果,单选的选择的时候,就取消其他的了 selectedAnswers
+        if (this.form.type===2) {
+          this.form.answer=JSON.parse(this.form.answer)
+          this.selectedAnswers = JSON.parse(this.form.question).filter(item => item.isAnswer === 1);
+        }
+
+        this.question=JSON.parse(this.form.question)
+        this.open = true;
+        this.title = "修改题库";
+      });
+    },
+    /** 提交按钮 */
+    submitForm() {
+      this.$refs["form"].validate(valid => {
+        if (valid) {
+
+          if (this.question.some(q => q.name === "")) {
+            this.$message.error("不能存在空选项")
+            return
+          }
+
+          if (this.hideAnswerFields) {
+            this.question.forEach(q => { q.isAnswer = 0 });
+            this.selectedAnswer = null;
+            this.selectedAnswers = [];
+            if (this.form.type === 2) {
+              this.form.answer = [];
+            } else {
+              this.form.answer = '';
+            }
+          }
+
+          this.form.question=JSON.stringify(this.question)
+
+          if (this.form.type===2){
+            this.form.answer=JSON.stringify(this.form.answer)
+            // this.form.answer = this.form.answer.join(',');
+          }
+          if (this.form.id != null) {
+            updateCourseQuestionBank(this.form).then(response => {
+              this.msgSuccess("修改成功");
+              this.open = false;
+              this.getList();
+            });
+          } else {
+
+            addCourseQuestionBank(this.form).then(response => {
+              this.msgSuccess("新增成功");
+              this.open = false;
+              this.getList();
+            });
+          }
+        }
+      });
+    },
+    /** 删除按钮操作 */
+    handleDelete(row) {
+      const ids = row.id || this.ids;
+      this.$confirm('是否确认删除题库编号为"' + ids + '"的数据项?', "警告", {
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "warning"
+      }).then(function() {
+        return delCourseQuestionBank(ids);
+      }).then(() => {
+        this.getList();
+        this.msgSuccess("删除成功");
+      }).catch(() => {});
+    },
+    /** 导出按钮操作 */
+    handleExport() {
+      const queryParams = this.queryParams;
+      this.$confirm('是否确认导出所有题库数据项?', "警告", {
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "warning"
+      }).then(() => {
+        this.exportLoading = true;
+        return exportCourseQuestionBank(queryParams);
+      }).then(response => {
+        this.download(response.msg);
+        this.exportLoading = false;
+      }).catch(() => {});
+    },
+    // 下载模板
+    importTemplate() {
+      importTemplate().then((response) => {
+        this.download(response.msg);
+      });
+    },
+    // 导入
+    handleImport() {
+      this.upload.title = "导入题目";
+      this.upload.open = true;
+    },
+    submitFileForm() {
+      this.$refs.upload.submit();
+    },
+    // 文件上传中处理
+    handleFileUploadProgress(event, file, fileList) {
+      this.upload.isUploading = true;
+    },
+    // 文件上传成功处理
+    handleFileSuccess(response, file, fileList) {
+      this.upload.open = false;
+      this.upload.isUploading = false;
+      this.$refs.upload.clearFiles();
+      this.importMsgOpen=true;
+      this.importMsg = response.data.message
+      this.failList = response.data.failList
+      this.getList();
+    },
+  }
+};
+</script>

+ 508 - 0
src/views/course/publiccourse/courseWatchComment/index.vue

@@ -0,0 +1,508 @@
+<template>
+  <div class="app-container">
+    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
+      <el-form-item label="搜索" prop="keywords">
+        <el-input
+          v-model="queryParams.keywords"
+          placeholder="请输入用户昵称/课程名称/视频名称"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+          style="width: 280px"
+        />
+      </el-form-item>
+      <el-form-item label="状态" prop="status">
+        <el-select
+          v-model="queryParams.status"
+          placeholder="操作状态"
+          clearable
+          size="small"
+          style="width: 240px"
+        >
+          <el-option
+            v-for="dict in statusOptions"
+            :key="dict.dictValue"
+            :label="dict.dictLabel"
+            :value="dict.dictValue"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item>
+        <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
+        <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
+      </el-form-item>
+    </el-form>
+
+    <el-row :gutter="10" class="mb8">
+      <!--      <el-col :span="1.5">-->
+      <!--        <el-button-->
+      <!--          type="primary"-->
+      <!--          plain-->
+      <!--          icon="el-icon-plus"-->
+      <!--          size="mini"-->
+      <!--          @click="handleAdd"-->
+      <!--          v-hasPermi="['course:courseWatchComment:add']"-->
+      <!--        >新增</el-button>-->
+      <!--      </el-col>-->
+      <!--      <el-col :span="1.5">-->
+      <!--        <el-button-->
+      <!--          type="success"-->
+      <!--          plain-->
+      <!--          icon="el-icon-edit"-->
+      <!--          size="mini"-->
+      <!--          :disabled="single"-->
+      <!--          @click="handleUpdate"-->
+      <!--          v-hasPermi="['course:courseWatchComment:edit']"-->
+      <!--        >修改</el-button>-->
+      <!--      </el-col>-->
+      <el-col :span="1.5">
+        <el-button
+          type="danger"
+          plain
+          icon="el-icon-delete"
+          size="mini"
+          :disabled="multiple"
+          @click="handleDelete"
+          v-hasPermi="['course:courseWatchComment:remove']"
+        >删除</el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button
+          type="warning"
+          plain
+          icon="el-icon-download"
+          size="mini"
+          :loading="exportLoading"
+          @click="handleExport"
+          v-hasPermi="['course:courseWatchComment:export']"
+        >导出</el-button>
+      </el-col>
+      <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
+    </el-row>
+
+    <el-table border v-loading="loading" :data="courseWatchCommentList" @selection-change="handleSelectionChange">
+      <el-table-column type="selection" width="55" align="center" />
+      <!--      <el-table-column label="评论id" align="center" prop="commentId" />-->
+      <!--      <el-table-column label="用户id" align="center" prop="userId" />-->
+      <el-table-column label="用户昵称" align="center" prop="nickName" width="130px" />
+      <!--      <el-table-column label="用户类型,1-管理员,2-用户" align="center" prop="userType" />-->
+      <!--      <el-table-column label="评论类型 1:评论,2:回复" align="center" prop="type" />-->
+      <!--      <el-table-column label="父评论id" align="center" prop="parentId" />-->
+      <el-table-column label="评论内容" align="center" prop="content" />
+      <el-table-column label="所属课程" align="center" prop="courseName" />
+      <el-table-column label="所属小节" align="center" prop="title" />
+      <!--      <el-table-column label="弹幕状态" align="center" prop="status">-->
+      <!--        <template slot-scope="scope">-->
+      <!--          <el-tag :type="getStatusTagType(scope.row.status)">-->
+      <!--            {{ formatBarrageStatus(scope.row.status) }}-->
+      <!--          </el-tag>-->
+      <!--        </template>-->
+      <!--      </el-table-column>-->
+      <el-table-column label="评论时间" align="center" prop="createTime" width="160px"/>
+      <!--      <el-table-column label="是否是撤回的消息,1-是,0-否" align="center" prop="isRevoke" />-->
+      <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
+        <template slot-scope="scope">
+          <!--          <el-button-->
+          <!--            size="mini"-->
+          <!--            type="text"-->
+          <!--            icon="el-icon-edit"-->
+          <!--            @click="handleUpdate(scope.row)"-->
+          <!--            v-hasPermi="['course:courseWatchComment:edit']"-->
+          <!--          >修改</el-button>-->
+          <el-button
+            size="mini"
+            type="text"
+            icon="el-icon-edit"
+            @click="handleUpdateStatus(scope.row)"
+          >修改弹幕状态</el-button>
+          <el-button
+            size="mini"
+            type="text"
+            icon="el-icon-edit"
+            @click="handleAddKeyWords(scope.row)"
+          >添加关键字</el-button>
+          <el-button
+            v-if="scope.row.userStatus === 1 "
+            size="mini"
+            type="text"
+            icon="el-icon-edit"
+            @click="handleAddBlack(scope.row)"
+          >拉黑</el-button>
+          <el-button
+            v-if="scope.row.userStatus === 0 "
+            size="mini"
+            type="text"
+            icon="el-icon-edit"
+            @click="handleClearBlack(scope.row)"
+          >解除拉黑</el-button>
+          <el-button
+            size="mini"
+            type="text"
+            icon="el-icon-delete"
+            @click="handleDelete(scope.row)"
+            v-hasPermi="['course:courseWatchComment:remove']"
+          >删除</el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+
+    <pagination
+      v-show="total>0"
+      :total="total"
+      :page.sync="queryParams.pageNum"
+      :limit.sync="queryParams.pageSize"
+      @pagination="getList"
+    />
+    <el-dialog :title="title" :visible.sync="open" width="500px" append-to-body>
+      <el-form ref="form" :model="form" :rules="rules" label-width="80px">
+        <el-form-item label="关键字" prop="keyword">
+          <el-input v-model="form.keyword" placeholder="请输入关键字" />
+        </el-form-item>
+      </el-form>
+      <div slot="footer" class="dialog-footer">
+        <el-button type="primary" @click="submitForm">确 定</el-button>
+        <el-button @click="cancel">取 消</el-button>
+      </div>
+    </el-dialog>
+
+    <el-dialog :title="title" :visible.sync="updateStatus" width="500px" append-to-body>
+      <el-form ref="form" :model="form" :rules="rules" label-width="80px">
+        <el-form-item label="弹幕状态" prop="barrageStatus">
+          <el-radio-group v-model="form.status">
+            <el-radio
+              v-for="dict in barrageStatusOptions"
+              :key="dict.dictValue"
+              :label="dict.dictValue"
+              v-if="dict.dictValue !== 2"
+            >
+              <el-tag :type="getStatusTagType(dict.dictValue)" size="small">
+                {{dict.dictLabel}}
+              </el-tag>
+            </el-radio>
+          </el-radio-group>
+        </el-form-item>
+      </el-form>
+      <div slot="footer" class="dialog-footer">
+        <el-button type="primary" @click="updateForm">确 定</el-button>
+        <el-button @click="cancel">取 消</el-button>
+      </div>
+    </el-dialog>
+    <!--    &lt;!&ndash; 添加或修改看课评论对话框 &ndash;&gt;-->
+    <!--    <el-dialog :title="title" :visible.sync="open" width="500px" append-to-body>-->
+    <!--      <el-form ref="form" :model="form" :rules="rules" label-width="80px">-->
+    <!--        <el-form-item label="用户id" prop="userId">-->
+    <!--          <el-input v-model="form.userId" placeholder="请输入用户id" />-->
+    <!--        </el-form-item>-->
+    <!--        <el-form-item label="用户类型,1-管理员,2-用户" prop="userType">-->
+    <!--          <el-select v-model="form.userType" placeholder="请选择用户类型,1-管理员,2-用户">-->
+    <!--            <el-option label="请选择字典生成" value="" />-->
+    <!--          </el-select>-->
+    <!--        </el-form-item>-->
+    <!--        <el-form-item label="课程id" prop="courseId">-->
+    <!--          <el-input v-model="form.courseId" placeholder="请输入课程id" />-->
+    <!--        </el-form-item>-->
+    <!--        <el-form-item label="视频id" prop="videoId">-->
+    <!--          <el-input v-model="form.videoId" placeholder="请输入视频id" />-->
+    <!--        </el-form-item>-->
+    <!--        <el-form-item label="评论类型 1:评论,2:回复" prop="type">-->
+    <!--          <el-select v-model="form.type" placeholder="请选择评论类型 1:评论,2:回复">-->
+    <!--            <el-option label="请选择字典生成" value="" />-->
+    <!--          </el-select>-->
+    <!--        </el-form-item>-->
+    <!--        <el-form-item label="父评论id" prop="parentId">-->
+    <!--          <el-input v-model="form.parentId" placeholder="请输入父评论id" />-->
+    <!--        </el-form-item>-->
+    <!--        <el-form-item label="评论内容" prop="content">-->
+    <!--          <el-input v-model="form.content" type="textarea" placeholder="请输入内容" />-->
+    <!--        </el-form-item>-->
+    <!--        <el-form-item label="是否是撤回的消息,1-是,0-否" prop="isRevoke">-->
+    <!--          <el-select v-model="form.isRevoke" placeholder="请选择是否是撤回的消息,1-是,0-否">-->
+    <!--            <el-option label="请选择字典生成" value="" />-->
+    <!--          </el-select>-->
+    <!--        </el-form-item>-->
+    <!--      </el-form>-->
+    <!--      <div slot="footer" class="dialog-footer">-->
+    <!--        <el-button type="primary" @click="submitForm">确 定</el-button>-->
+    <!--        <el-button @click="cancel">取 消</el-button>-->
+    <!--      </div>-->
+    <!--    </el-dialog>-->
+  </div>
+</template>
+
+<script>
+import { listCourseWatchComment, delCourseWatchComment, exportCourseWatchComment, addBlack,clearBlack,updateBarrageStatus } from "@/api/course/courseWatchComment";
+import { addKeyword } from "@/api/system/keyword";
+
+export default {
+  name: "CourseWatchComment",
+  data() {
+    return {
+      /** 与后端 cate_type 一致:0-评论(课程),1-公域看课评论 */
+      pageCateType: 1,
+      // 弹幕状态选项
+      barrageStatusOptions: [
+        { dictValue: 0, dictLabel: "正常" },
+        { dictValue: 1, dictLabel: "屏蔽" },
+        { dictValue: 2, dictLabel: "人工" }
+      ],
+      // 遮罩层
+      loading: true,
+      // 导出遮罩层
+      exportLoading: false,
+      // 选中数组
+      ids: [],
+      // 非单个禁用
+      single: true,
+      // 非多个禁用
+      multiple: true,
+      // 显示搜索条件
+      showSearch: true,
+      // 总条数
+      total: 0,
+      // 看课评论表格数据
+      courseWatchCommentList: [],
+      //下拉状态
+      statusOptions:[],
+      // 弹出层标题
+      title: "",
+      // 是否显示弹出层
+      open: false,
+      updateStatus: false,
+      // 查询参数
+      queryParams: {
+        pageNum: 1,
+        pageSize: 10,
+        keywords: null,
+        isAll: true, //判断是否属于全局查询
+        status: '',
+        userStatus: '',
+        cateType: 1,
+        // userId: null,
+        // userType: null,
+        // courseId: null,
+        // videoId: null,
+        // type: null,
+        // parentId: null,
+        // content: null,
+        // isRevoke: null
+      },
+      // 表单参数
+      form: {},
+      // 表单校验
+      rules: {
+        keyword: [
+          { required: true, message: "关键字不能为空", trigger: "blur" }
+        ]
+      }
+    };
+  },
+  created() {
+    this.getDicts("sys_barrage_clean_type").then((response) => {
+      this.statusOptions = response.data;
+    });
+    this.getList();
+  },
+  methods: {
+    /** 查询看课评论列表 */
+    getList() {
+      this.loading = true;
+      this.queryParams.cateType = this.pageCateType;
+      listCourseWatchComment(this.queryParams).then(response => {
+        this.courseWatchCommentList = response.rows.list;
+        this.total = response.rows.total;
+        this.loading = false;
+      });
+    },
+    // 获取状态标签类型
+    getStatusTagType(status) {
+      switch(status) {
+        case 0: return 'success';
+        case 1: return 'danger';
+        case 2: return 'primary';
+        default: return 'info';
+      }
+    },
+    // 格式化状态显示
+    formatBarrageStatus(status) {
+      const dict = this.barrageStatusOptions.find(item => item.dictValue === status);
+      return dict ? dict.dictLabel : status;
+    },
+    // 取消按钮
+    cancel() {
+      this.open = false;
+      this.updateStatus = false;
+      this.reset();
+    },
+    // 表单重置
+    reset() {
+      this.form = {
+        commentId: null,
+        userId: null,
+        userType: null,
+        courseId: null,
+        videoId: null,
+        type: null,
+        parentId: null,
+        content: null,
+        createTime: null,
+        updateTime: null,
+        isRevoke: null,
+        keyword: null,
+        status: '',
+        userStatus: ''
+      };
+      this.resetForm("form");
+    },
+    /** 搜索按钮操作 */
+    handleQuery() {
+      this.queryParams.pageNum = 1;
+      this.getList();
+    },
+    /** 重置按钮操作 */
+    resetQuery() {
+      this.resetForm("queryForm");
+      this.queryParams.cateType = this.pageCateType;
+      this.handleQuery();
+    },
+    // 多选框选中数据
+    handleSelectionChange(selection) {
+      this.ids = selection.map(item => item.commentId)
+      this.single = selection.length!==1
+      this.multiple = !selection.length
+    },
+    /** 添加关键字按钮操作 */
+    handleAddKeyWords(row) {
+      this.reset();
+      this.form.keyword = row.content; // 将评论内容设置为关键字
+      this.open = true;
+      this.title = "添加到关键字";
+    },
+    handleUpdateStatus(row) {
+      this.reset();
+      this.form.status = row.status;
+      this.form.commentId = row.commentId;
+      this.form.cateType = this.pageCateType;
+      this.updateStatus = true;
+      this.title = "修改弹幕状态";
+    },
+    /** 解除拉黑用户按钮操作 */
+    handleClearBlack(row) {
+      this.$confirm(`谨慎操作,确定要解除"${row.nickName}"用户拉黑吗`, "警告", {
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "warning"
+      }).then(() => {
+        const data = {
+          fsUserId: row.userId,
+          commentStatus: 0
+        };
+        clearBlack(data).then(response => {
+          this.msgSuccess("操作成功");
+          this.getList(); // 重新加载列表
+        }).catch(() => {
+          this.msgError("操作失败");
+        });
+      }).catch(() => {
+        // 用户取消操作
+      });
+    },
+    /** 拉黑用户按钮操作 */
+    handleAddBlack(row) {
+      this.$confirm(`谨慎操作,确定要拉黑用户"${row.nickName}"吗`, "警告", {
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "warning"
+      }).then(() => {
+        const data = {
+          fsUserId: row.userId,
+          commentStatus: 1
+        };
+        addBlack(data).then(response => {
+          this.msgSuccess("操作成功");
+          this.getList(); // 重新加载列表
+        }).catch(() => {
+          this.msgError("操作失败");
+        });
+      }).catch(() => {
+        // 用户取消操作
+      });
+    },
+    updateForm() {
+      this.$refs["form"].validate(valid => {
+        if (valid) {
+          console.log(this.form)
+          updateBarrageStatus({ ...this.form, cateType: this.pageCateType }).then(response => {
+            this.msgSuccess("修改成功");
+            this.updateStatus = false;
+            this.getList();
+          }).catch(() => {
+            this.msgError("修改失败");
+          });
+        }
+      })
+    },
+    /** 提交按钮 */
+    submitForm() {
+      this.$refs["form"].validate(valid => {
+        if (valid) {
+          const data = {
+            keyword: this.form.keyword
+          };
+          addKeyword(data).then(response => {
+            this.msgSuccess("操作成功");
+            this.open = false;
+            this.getList();
+          }).catch(() => {
+            this.msgError("操作失败");
+          });
+        }
+      });
+    },
+    // /** 新增按钮操作 */
+    // handleAdd() {
+    //   this.reset();
+    //   this.open = true;
+    //   this.title = "添加看课评论";
+    // },
+    // /** 修改按钮操作 */
+    // handleUpdate(row) {
+    //   this.reset();
+    //   const commentId = row.commentId || this.ids
+    //   getCourseWatchComment(commentId).then(response => {
+    //     this.form = response.data;
+    //     this.open = true;
+    //     this.title = "修改看课评论";
+    //   });
+    // },
+    /** 删除按钮操作 */
+    handleDelete(row) {
+      const commentIds = row.commentId || this.ids;
+      this.$confirm('是否确认删除此看课评论?删除后不可恢复', "警告", {
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "warning"
+      }).then(function() {
+        return delCourseWatchComment(commentIds);
+      }).then(() => {
+        this.getList();
+        this.msgSuccess("删除成功");
+      }).catch(() => {});
+    },
+    /** 导出按钮操作 */
+    handleExport() {
+      const queryParams = { ...this.queryParams, cateType: this.pageCateType };
+      this.$confirm('是否确认导出当前看课评论数据项?', "警告", {
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "warning"
+      }).then(() => {
+        this.exportLoading = true;
+        return exportCourseWatchComment(queryParams);
+      }).then(response => {
+        this.download(response.msg);
+        this.exportLoading = false;
+      }).catch(() => {});
+    }
+  }
+};
+</script>

+ 515 - 0
src/views/course/publiccourse/userCourseCategory/index.vue

@@ -0,0 +1,515 @@
+<template>
+  <div class="app-container">
+    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
+      <el-form-item label="分类名称" prop="cateName">
+        <el-input
+          v-model="queryParams.cateName"
+          placeholder="请输入分类名称"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="是否推荐" prop="isShow">
+        <el-select v-model="queryParams.isShow" placeholder="是否推荐" clearable size="small">
+          <el-option
+            v-for="dict in orOptions"
+            :key="dict.dictValue"
+            :label="dict.dictLabel"
+            :value="dict.dictValue"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item>
+        <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
+        <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
+      </el-form-item>
+    </el-form>
+
+    <el-row :gutter="10" class="mb8">
+      <el-col :span="1.5">
+        <el-button
+          type="primary"
+          plain
+          icon="el-icon-plus"
+          size="mini"
+          @click="handleAdd"
+          v-hasPermi="['course:userCourseCategory:add']"
+        >新增</el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button
+          type="success"
+          plain
+          icon="el-icon-edit"
+          size="mini"
+          :disabled="single"
+          @click="handleUpdate"
+          v-hasPermi="['course:userCourseCategory:edit']"
+        >修改</el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button
+          type="danger"
+          plain
+          icon="el-icon-delete"
+          size="mini"
+          :disabled="multiple"
+          @click="handleDelete"
+          v-hasPermi="['course:userCourseCategory:remove']"
+        >删除</el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button
+          type="warning"
+          plain
+          icon="el-icon-download"
+          size="mini"
+          :loading="exportLoading"
+          @click="handleExport"
+          v-hasPermi="['course:userCourseCategory:export']"
+        >导出</el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button
+          plain
+          type="info"
+          icon="el-icon-upload2"
+          size="mini"
+          @click="handleImport"
+          v-hasPermi="['course:userCourseCategory:importData']"
+        >导入</el-button>
+      </el-col>
+
+      <el-col :span="1.5">
+        <el-button
+          type="warning"
+          plain
+          icon="el-icon-download"
+          size="mini"
+          :loading="exportLoading"
+          @click="handleFansExport"
+          v-hasPermi="['course:userCourseCategory:fansExport']"
+        >重粉导出</el-button>
+      </el-col>
+      <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
+    </el-row>
+
+    <el-table
+      height="500" border
+      v-loading="loading"
+      :data="userCourseCategoryList"
+      row-key="cateId"
+      :tree-props="{children: 'children', hasChildren: 'hasChildren'}"
+      @selection-change="handleSelectionChange"
+    >
+      <el-table-column type="selection" width="55" align="center"/>
+      <el-table-column label="分类id" align="left" prop="cateId" />
+      <el-table-column label="分类名称" align="left" prop="cateName" />
+      <el-table-column  label="状态" align="center" prop="isShow">
+        <template slot-scope="scope">
+          <div>
+            <el-tag v-if="scope.row.isShow === 1" :type="''">显示</el-tag>
+            <el-tag v-else :type=" 'info' ">隐藏</el-tag>
+          </div>
+        </template>
+      </el-table-column>
+      <el-table-column label="排序" sortable align="center" prop="sort" />
+      <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
+        <template slot-scope="scope">
+          <el-button
+            size="mini"
+            type="text"
+            icon="el-icon-edit"
+            @click="handleUpdate(scope.row)"
+            v-hasPermi="['course:userCourseCategory:edit']"
+          >修改</el-button>
+          <el-button
+            size="mini"
+            type="text"
+            icon="el-icon-delete"
+            @click="handleDelete(scope.row)"
+            v-hasPermi="['course:userCourseCategory:remove']"
+          >删除</el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+
+    <!--    <pagination-->
+    <!--      v-show="total>0"-->
+    <!--      :total="total"-->
+    <!--      :page.sync="queryParams.pageNum"-->
+    <!--      :limit.sync="queryParams.pageSize"-->
+    <!--      @pagination="getList"-->
+    <!--    />-->
+
+    <!-- 添加或修改课堂分类对话框 -->
+    <el-dialog :title="title" :visible.sync="open" width="500px" append-to-body>
+      <el-form ref="form" :model="form" :rules="rules" label-width="80px">
+        <el-form-item label="上级分类" prop="pid"  >
+          <treeselect v-model="form.pid" :options="categoryOptions" :normalizer="normalizer" placeholder="请选择上级分类" />
+        </el-form-item>
+        <el-form-item label="分类名称" prop="cateName">
+          <el-input v-model="form.cateName" placeholder="请输入分类名称" />
+        </el-form-item>
+        <!-- <el-form-item label="图标"  prop="pic" v-if="form.pid!=0">
+           <Material v-model="picArr" type="image" :num="1" :width="150" :height="150" />
+        </el-form-item> -->
+        <el-form-item label="状态" prop="isShow">
+          <el-radio v-model="form.isShow" label="1">显示</el-radio>
+          <el-radio v-model="form.isShow" label="0">隐藏</el-radio>
+        </el-form-item>
+        <el-form-item label="排序" prop="sort">
+          <el-input-number v-model="form.sort" placeholder="请输入排序" />
+        </el-form-item>
+
+      </el-form>
+      <div slot="footer" class="dialog-footer">
+        <el-button type="primary" @click="submitForm">确 定</el-button>
+        <el-button @click="cancel">取 消</el-button>
+      </div>
+    </el-dialog>
+
+    <!-- 导入 -->
+    <el-dialog :title="upload.title" :visible.sync="upload.open" width="400px" append-to-body>
+      <el-upload ref="upload" :limit="1" accept=".xlsx, .xls" :headers="upload.headers" :action="upload.url" :disabled="upload.isUploading" :on-progress="handleFileUploadProgress" :on-success="handleFileSuccess" :auto-upload="false" drag>
+        <i class="el-icon-upload"></i>
+        <div class="el-upload__text">
+          将文件拖到此处,或
+          <em>点击上传</em>
+        </div>
+        <div class="el-upload__tip" slot="tip">
+          <el-link type="info" style="font-size:12px" @click="importTemplate">下载模板</el-link>
+        </div>
+        <div class="el-upload__tip" style="color:red" slot="tip">提示:仅允许导入“xls”或“xlsx”格式文件!</div>
+      </el-upload>
+      <div slot="footer" class="dialog-footer">
+        <el-button type="primary" :loading="upload.isUploading" :disabled="upload.isUploading" @click="submitFileForm">确 定</el-button>
+        <el-button @click="upload.open = false">取 消</el-button>
+      </div>
+    </el-dialog>
+    <el-dialog title="导入结果" :close-on-press-escape="false" :close-on-click-modal="false" :visible.sync="importMsgOpen" width="500px" append-to-body>
+      <div class="import-msg" v-html="importMsg">
+      </div>
+      <div slot="footer" class="dialog-footer">
+        <el-button @click="importMsgOpen = false">关 闭</el-button>
+        <el-button
+          v-if="failList && failList.length > 0"
+          type="primary"
+          v-hasPermi="['course:userCourseCategory:exportFail']"
+          @click="handleExportFailList(failList)"
+          :loading="exportFailLoading"
+        >导出失败信息</el-button>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import { listUserCourseCategory, getUserCourseCategory, delUserCourseCategory, addUserCourseCategory, updateUserCourseCategory, exportUserCourseCategory, exportFans,importTemplate, exportFail } from "@/api/course/userCourseCategory";
+import Treeselect from "@riophae/vue-treeselect";
+import "@riophae/vue-treeselect/dist/vue-treeselect.css";
+import { getToken } from '@/utils/auth'
+export default {
+  name: "UserCourseCategory",
+  components: {
+    Treeselect
+  },
+  data() {
+    return {
+      orOptions:[],
+      categoryOptions:[],
+      // 遮罩层
+      loading: true,
+      // 导出遮罩层
+      exportLoading: false,
+      // 选中数组
+      ids: [],
+      // 非单个禁用
+      single: true,
+      // 非多个禁用
+      multiple: true,
+      // 显示搜索条件
+      showSearch: true,
+      // 总条数
+      total: 0,
+      // 课堂分类表格数据
+      userCourseCategoryList: [],
+      // 弹出层标题
+      title: "",
+      // 是否显示弹出层
+      open: false,
+      // 查询参数
+      queryParams: {
+        pageNum: 1,
+        pageSize: 10,
+        pid: null,
+        cateName: null,
+        sort: null,
+        isShow: null,
+        isDel: null,
+        cateType: 1
+      },
+      // 表单参数
+      form: {},
+      // 表单校验
+      rules: {
+        pid: [
+          { required: true, message: "父id不能为空", trigger: "blur" }
+        ],
+        cateName: [
+          { required: true, message: "分类名称不能为空", trigger: "blur" }
+        ],
+      },
+      // 导入
+      importMsgOpen:false,
+      exportFailLoading: false,
+      failList:[],
+      importMsg: '',
+      upload: {
+        // 是否显示弹出层(文件导入)
+        open: false,
+        // 弹出层标题(文件导入)
+        title: "",
+        // 是否禁用上传
+        isUploading: false,
+        // 设置上传的请求头部
+        headers: { Authorization: "Bearer " + getToken() },
+        // 上传的地址
+        url: process.env.VUE_APP_BASE_API + "/course/userCourseCategory/importData",
+      }
+    };
+  },
+  created() {
+    this.getList();
+    this.getDicts("sys_company_or").then(response => {
+      this.orOptions = response.data;
+    });
+  },
+  methods: {
+    /** 查询课堂分类列表 */
+    getList() {
+      this.loading = true;
+      listUserCourseCategory(this.queryParams).then(response => {
+        this.userCourseCategoryList = this.handleTree(response.data, "cateId", "pid");
+        this.total = response.total;
+        this.loading = false;
+      });
+    },
+    /** 转换课程分类数据结构 */
+    normalizer(node) {
+      if (node.children && !node.children.length) {
+        delete node.children;
+      }
+      return {
+        id: node.cateId,
+        label: node.cateName,
+        children: node.children
+      };
+    },
+    getTreeselect() {
+      listUserCourseCategory({ cateType: 1 }).then(response => {
+        this.categoryOptions = [];
+        const data = { cateId: 0, cateName: '顶级目录', children: [] };
+        data.children = response.data.filter(item => item.pid == 0)
+        this.categoryOptions.push(data);
+      });
+    },
+    // 取消按钮
+    cancel() {
+      this.open = false;
+      this.reset();
+    },
+    // 表单重置
+    reset() {
+      this.form = {
+        cateId: null,
+        pid: null,
+        cateName: null,
+        sort: null,
+        isShow: null,
+        createTime: null,
+        updateTime: null,
+        isDel: null,
+        cateType: 1
+      };
+      this.resetForm("form");
+    },
+    /** 搜索按钮操作 */
+    handleQuery() {
+      this.queryParams.pageNum = 1;
+      this.getList();
+    },
+    /** 重置按钮操作 */
+    resetQuery() {
+      this.resetForm("queryForm");
+      this.handleQuery();
+    },
+    // 多选框选中数据
+    handleSelectionChange(selection) {
+      this.ids = selection.map(item => item.cateId)
+      this.single = selection.length!==1
+      this.multiple = !selection.length
+
+      console.log("selection",  selection);
+    },
+    /** 新增按钮操作 */
+    handleAdd() {
+      this.reset();
+      this.getTreeselect();
+      this.open = true;
+      this.title = "添加课堂分类";
+    },
+    /** 修改按钮操作 */
+    handleUpdate(row) {
+      this.reset();
+      this.getTreeselect();
+      const cateId = row.cateId || this.ids
+      getUserCourseCategory(cateId).then(response => {
+        this.form = response.data;
+        this.form.isShow = response.data.isShow.toString();
+        this.open = true;
+        this.title = "修改课堂分类";
+      });
+    },
+    /** 提交按钮 */
+    submitForm() {
+      this.$refs["form"].validate(valid => {
+        if (valid) {
+          if (this.form.cateId != null) {
+            updateUserCourseCategory(this.form).then(response => {
+              this.msgSuccess("修改成功");
+              this.open = false;
+              this.getList();
+            });
+          } else {
+            addUserCourseCategory(this.form).then(response => {
+              this.msgSuccess("新增成功");
+              this.open = false;
+              this.getList();
+            });
+          }
+        }
+      });
+    },
+    /** 删除按钮操作 */
+    handleDelete(row) {
+      const cateIds = row.cateId || this.ids;
+      this.$confirm('是否确认删除课堂分类编号为"' + cateIds + '"的数据项?', "警告", {
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "warning"
+      }).then(function() {
+        return delUserCourseCategory(cateIds);
+      }).then(() => {
+        this.getList();
+        this.msgSuccess("删除成功");
+      }).catch(() => {});
+    },
+    /** 导出按钮操作 */
+    handleExport() {
+      const queryParams = this.queryParams;
+      this.$confirm('是否确认导出所有课堂分类数据项?', "警告", {
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "warning"
+      }).then(() => {
+        this.exportLoading = true;
+        return exportUserCourseCategory(queryParams);
+      }).then(response => {
+        this.download(response.msg);
+        this.exportLoading = false;
+      }).catch(() => {});
+    },
+    // 下载模板
+    importTemplate() {
+      importTemplate().then((response) => {
+        this.download(response.msg);
+      });
+    },
+    // 导入
+    handleImport() {
+      this.upload.title = "导入题目";
+      this.upload.open = true;
+    },
+    submitFileForm() {
+      this.$refs.upload.submit();
+    },
+    // 文件上传中处理
+    handleFileUploadProgress(event, file, fileList) {
+      this.upload.isUploading = true;
+    },
+    // 文件上传成功处理
+    handleFileSuccess(response, file, fileList) {
+      this.upload.open = false;
+      this.upload.isUploading = false;
+      this.$refs.upload.clearFiles();
+      this.importMsgOpen=true;
+      this.importMsg = response.data.message
+      this.failList = response.data.failList
+      this.getList();
+    },
+    // 导出失败信息
+    handleExportFailList(failList) {
+      if (!failList || failList.length === 0) {
+        this.msgError("没有失败信息可导出");
+        return;
+      }
+      this.exportFailLoading = true;
+      this.$confirm('是否确认导出失败信息?', "提示", {
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "info"
+      }).then(() => {
+        // 调用导出失败信息的API
+        exportFail(failList).then(response => {
+          this.download(response.msg);
+          this.msgSuccess("失败信息导出成功");
+          this.exportFailLoading = false;
+        }).catch(() => {
+          this.msgError("失败信息导出失败");
+          this.exportFailLoading = false;
+        });
+      }).catch(() => {
+        this.exportFailLoading = false;
+      });
+    },
+    handleFansExport() {
+      if (!Array.isArray(this.ids) || this.ids.length === 0) {
+        this.$message.warning('请先选择至少一条数据');
+        return;
+      }
+
+      const params = { ids: this.ids };
+
+      this.$confirm('是否确认导出重粉?', '警告', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning'
+      }).then(() => {
+        const loading = this.$loading({
+          lock: true,
+          text: '正在导出数据,请稍候...',
+          spinner: 'el-icon-loading',
+          background: 'rgba(0, 0, 0, 0.3)'
+        });
+
+        return exportFans(params)
+          .then(res => {
+            if (res.code !== 200){
+              this.$message.warning(res.msg);
+              return;
+            }
+
+            this.download(res.msg);
+          })
+
+          .finally(() => {
+            loading.close();
+          });
+      });
+    }
+  },
+
+};
+</script>

+ 1248 - 0
src/views/course/publiccourse/usercourse/index.vue

@@ -0,0 +1,1248 @@
+<template>
+  <div class="app-container">
+    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="100px">
+      <el-form-item label="课堂分类" prop="cateId">
+        <el-select v-model="queryParams.cateId" placeholder="请选择" clearable size="small"
+                   @change="getQuerySubCateList(queryParams.cateId)">
+          <el-option
+            v-for="dict in categoryOptions"
+            :key="dict.dictValue"
+            :label="dict.dictLabel"
+            :value="dict.dictValue"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="课堂子分类" prop="subCateId">
+        <el-select v-model="queryParams.subCateId" placeholder="请选择" clearable size="small">
+          <el-option
+            v-for="dict in querySubCateOptions"
+            :key="dict.dictValue"
+            :label="dict.dictLabel"
+            :value="dict.dictValue"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="课堂id" prop="courseId">
+        <el-input
+          v-model="queryParams.courseId"
+          placeholder="请输入课堂id"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="课堂名称" prop="courseName">
+        <el-input
+          v-model="queryParams.courseName"
+          placeholder="请输入课堂名称"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="关联的公司" prop="companyIds">
+        <el-select v-model="queryParams.companyIdsList" multiple placeholder="请选择公司" filterable clearable style="width: 90%;">
+          <el-option
+            v-for="dict in companyOptions"
+            :key="dict.dictValue"
+            :label="dict.dictLabel"
+            :value="dict.dictValue"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="课堂类型" prop="isPrivate" style="display: none">
+        <el-select v-model="queryParams.isPrivate" placeholder="请选择" clearable size="small">
+          <el-option
+            v-for="dict in courseTypeOptions"
+            :key="dict.dictValue"
+            :label="dict.dictLabel"
+            :value="dict.dictValue"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item>
+        <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
+        <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
+      </el-form-item>
+    </el-form>
+
+    <el-row :gutter="10" class="mb8">
+      <el-col :span="1.5">
+        <el-button
+          type="primary"
+          plain
+          icon="el-icon-plus"
+          size="mini"
+          @click="handleAdd"
+          v-hasPermi="['course:userCourse:add']"
+        >新增
+        </el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button
+          type="success"
+          plain
+          icon="el-icon-edit"
+          size="mini"
+          :disabled="single"
+          @click="handleUpdate"
+          v-hasPermi="['course:userCourse:edit']"
+        >修改
+        </el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button
+          type="danger"
+          plain
+          icon="el-icon-delete"
+          size="mini"
+          :disabled="multiple"
+          @click="handleDelete"
+          v-hasPermi="['course:userCourse:remove']"
+        >删除
+        </el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button
+          type="warning"
+          plain
+          icon="el-icon-download"
+          size="mini"
+          :loading="exportLoading"
+          @click="handleExport"
+          v-hasPermi="['course:userCourse:export']"
+        >导出
+        </el-button>
+      </el-col>
+      <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
+    </el-row>
+
+    <!--    <el-table height="600" border v-loading="loading" :data="userCourseList" @selection-change="handleSelectionChange" style="width: 100%" :fit="true">-->
+    <el-table max-height="600" border v-loading="loading" :data="userCourseList" @selection-change="handleSelectionChange" style="width: 100%" :fit="true">
+      <el-table-column type="selection" width="55" align="center"/>
+      <el-table-column label="课程ID" align="center" prop="courseId" width="55"/>
+      <el-table-column label="所属项目" align="center" prop="projectName" width="120"/>
+      <el-table-column label="封面图片" align="center" prop="imgUrl" width="170">
+        <template slot-scope="scope">
+          <el-popover
+            placement="right"
+            title=""
+            trigger="hover"
+          >
+            <img slot="reference" :src="scope.row.imgUrl" width="100">
+            <img :src="scope.row.imgUrl" style="max-width: 300px;">
+          </el-popover>
+        </template>
+      </el-table-column>
+      <el-table-column label="课堂名称" align="center" show-overflow-tooltip prop="courseName" min-width="100"/>
+      <el-table-column label="排序" align="center" prop="sort" width="80"/>
+      <el-table-column label="分类名称" align="center" prop="cateName" width="120"/>
+      <el-table-column label="子分类名称" align="center" prop="subCateName" width="120"/>
+      <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
+        <template slot-scope="scope">
+          <el-button
+            size="mini"
+            type="text"
+            @click="handleCatalog(scope.row)"
+            v-hasPermi="['course:userCourse:cateMange']"
+          >目录管理
+          </el-button>
+          <el-button
+            size="mini"
+            type="text"
+            icon="el-icon-edit"
+            @click="handleUpdate(scope.row)"
+            v-hasPermi="['course:userCourse:edit']"
+          >修改
+          </el-button>
+          <el-button
+            size="mini"
+            type="text"
+            icon="el-icon-edit"
+            @click="handleUpdateRedPage(scope.row)"
+            v-hasPermi="['course:userCourse:editRedPage']"
+          >统一修改红包金额
+          </el-button>
+          <el-button
+            size="mini"
+            type="text"
+            icon="el-icon-edit"
+            @click="handleCopy(scope.row)"
+            v-hasPermi="['course:userCourse:copy']"
+          >复制
+          </el-button>
+          <el-button
+            size="mini"
+            type="text"
+            icon="el-icon-delete"
+            @click="handleDelete(scope.row)"
+            v-hasPermi="['course:userCourse:remove']"
+          >删除
+          </el-button>
+          <el-button
+            size="mini"
+            type="text"
+            v-if="scope.row.isPrivate === 1"
+            v-has-permi="['course:userCourse:editConfig']"
+            @click="configCourse(scope.row)"
+          >过程页配置
+          </el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+
+    <pagination
+      v-show="total>0"
+      :total="total"
+      :page.sync="queryParams.pageNum"
+      :limit.sync="queryParams.pageSize"
+      @pagination="getList"
+    />
+
+    <!-- 添加或修改课程对话框 -->
+    <el-dialog :title="title" :visible.sync="open" width="1200px" append-to-body>
+      <el-form ref="form" :model="form" :rules="rules" label-width="110px">
+        <el-row>
+          <el-form-item label="所属项目" prop="project">
+            <el-select v-model="form.project" placeholder="请选择项目" filterable clearable size="small">
+              <el-option
+                v-for="dict in projectOptions"
+                :key="dict.dictValue"
+                :label="dict.dictLabel"
+                :value="dict.dictValue"
+              />
+            </el-select>
+          </el-form-item>
+        </el-row>
+        <el-row>
+          <el-col :span="8">
+            <el-form-item label="课堂名称" prop="courseName">
+              <el-input v-model="form.courseName" placeholder="请输入课堂名称"/>
+            </el-form-item>
+          </el-col>
+          <el-col :span="8">
+            <el-form-item label="课堂分类" prop="cateId">
+              <el-select v-model="form.cateId" placeholder="请选择" clearable size="small"
+                         @change="getSubCateList(form.cateId)">
+                <el-option
+                  v-for="dict in categoryOptions"
+                  :key="dict.dictValue"
+                  :label="dict.dictLabel"
+                  :value="dict.dictValue"
+                />
+              </el-select>
+            </el-form-item>
+          </el-col>
+          <el-col :span="8">
+            <el-form-item label="课堂子分类" prop="subCateId">
+              <el-select v-model="form.subCateId" placeholder="请选择" clearable size="small">
+                <el-option
+                  v-for="dict in subCategoryOptions"
+                  :key="dict.dictValue"
+                  :label="dict.dictLabel"
+                  :value="dict.dictValue"
+                />
+              </el-select>
+            </el-form-item>
+          </el-col>
+        </el-row>
+        <el-row>
+          <el-col :span="24">
+            <el-form-item label="排序" prop="sort">
+              <el-input-number v-model="form.sort"  :min="0"  label="排序"></el-input-number>
+            </el-form-item>
+          </el-col>
+        </el-row>
+        <el-row>
+          <el-col :span="24">
+            <el-form-item label="课堂简介" prop="description">
+              <el-input v-model="form.description" type="textarea" :rows="2" placeholder="请输入课堂简介"/>
+            </el-form-item>
+          </el-col>
+        </el-row>
+        <el-form-item label="课程封面" prop="imgUrl">
+          <ImageUpload v-model="form.imgUrl" type="image" :num="10" :width="150" :height="150"/>
+        </el-form-item>
+        <el-form-item label="关联公司" prop="tags">
+          <el-select v-model="companyIds" multiple placeholder="请选择公司" filterable clearable style="width: 90%;">
+            <el-option
+              v-for="dict in companyOptions"
+              :key="dict.dictValue"
+              :label="dict.dictLabel"
+              :value="dict.dictValue"
+            />
+          </el-select>
+        </el-form-item>
+
+        <el-divider content-position="left">推荐管理</el-divider>
+        <el-row :gutter="16" class="rec-row" v-for="(rec, idx) in recommendSlots" :key="idx">
+          <el-col :span="24">
+            <el-form-item :label="rec.label">
+              <div class="rec-inline">
+                <el-checkbox v-model="form[rec.enabledKey]" :true-label="1" :false-label="0">启用</el-checkbox>
+                <el-select v-model="form[rec.modeKey]" placeholder="方式" clearable size="small" style="width: 120px; margin-left: 12px;">
+                  <el-option label="插入" :value="1"/>
+                  <el-option label="替换" :value="2"/>
+                </el-select>
+                <el-input-number v-model="form[rec.sortKey]" :min="0" :controls="true" placeholder="位置序号" size="small" style="width: 160px; margin-left: 12px;"/>
+              </div>
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <el-form-item label="是否上架" prop="isShow" required>
+          <el-radio-group v-model="form.isShow">
+            <el-radio label="1">上架</el-radio>
+            <el-radio label="0">下架</el-radio>
+          </el-radio-group>
+        </el-form-item>
+      </el-form>
+      <div slot="footer" class="dialog-footer">
+        <el-button type="primary" @click="submitForm">确 定</el-button>
+        <el-button @click="cancel">取 消</el-button>
+      </div>
+    </el-dialog>
+
+
+    <el-dialog title="修改课程红包金额" :visible.sync="openRedPage.open" width="1000px" append-to-body>
+      <el-form ref="openRedPage" :model="openRedPage" :rules="rulesRedPage" label-width="110px">
+        <el-form-item label="红包金额" prop="redPacketMoney">
+          <el-input-number v-model="openRedPage.redPacketMoney" :min="0.1" :max="200" :step="0.1"></el-input-number>
+        </el-form-item>
+        <div v-if="!!enableRandomRedPacket" style=" display: flex;
+                  flex-direction: column;">
+          <div v-for="(rule, index) in openRedPage.rules" :key="index" class="form-row">
+            <el-form-item
+              label="随机红包金额区间"
+              :prop="`rules.${index}.minAmount`"
+              :rules="[
+              { required: true, message: '请输入最小金额', trigger: 'blur' },
+              { validator: validateMinAmount, trigger: 'blur', index: index }
+            ]"
+              class="form-item-amount"
+            >
+              <el-input
+                v-model.number="rule.minAmount"
+                type="number"
+                :min="0.01"
+                :precision="2"
+                :step="0.01"
+                placeholder="最小金额"
+                size="small"
+                class="amount-input"
+                @input="handleAmountInput(rule, 'minAmount')"
+              ></el-input>
+              <span class="separator">-</span>
+              <el-input
+                v-model.number="rule.maxAmount"
+                type="number"
+                :min="rule.minAmount || 0.01"
+                :precision="2"
+                :step="0.01"
+                placeholder="最大金额"
+                size="small"
+                class="amount-input"
+                @input="handleAmountInput(rule, 'maxAmount')"
+              ></el-input>
+              <span class="suffix">元</span>
+            </el-form-item>
+            <el-form-item
+              label="随机权重"
+              :prop="`rules.${index}.weight`"
+              :rules="[
+                  { required: true, message: '请输入权重', trigger: 'blur' },
+                  { type: 'integer', message: '权重必须为整数', trigger: 'blur' },
+                ]"
+              class="form-item-weight"
+            >
+              <el-input
+                v-model.number="rule.weight"
+                type="number"
+                :min="1"
+                placeholder="权重"
+                size="small"
+              ></el-input>
+            </el-form-item>
+            <el-tooltip class="item" effect="dark" content="权重越高,被随机到的概率越大" placement="top">
+              <i class="el-icon-question"></i>
+            </el-tooltip>
+            <div class="action-buttons">
+              <el-button
+                icon="el-icon-plus"
+                size="mini"
+                type="text"
+                @click="addRule(index)"
+                class="add-btn"
+              >
+                新增
+              </el-button>
+              <el-button
+                icon="el-icon-delete"
+                size="mini"
+                type="text"
+                @click="deleteRule(index)"
+                :disabled="openRedPage.rules.length <= 1"
+                class="delete-btn"
+              >
+                删除
+              </el-button>
+            </div>
+          </div>
+        </div>
+      </el-form>
+      <div slot="footer" class="dialog-footer">
+        <el-button type="primary" @click="submitFormRedPage">确 定</el-button>
+        <el-button @click="cancelRedPage">取 消</el-button>
+      </div>
+    </el-dialog>
+
+    <!-- 过程页配置 -->
+    <el-dialog
+      :visible.sync="configDialog.dialogVisible"
+      title="过程页配置"
+      append-to-body
+      width="1200px"
+    >
+      <el-form :model="configDialog.form" :rules="configDialog.rules" ref="configForm" label-width="110px">
+        <el-form-item label="过程页图片" prop="coverImg">
+          <ImageUpload v-model="configDialog.form.coverImg" :height="150" :limit="1" :width="150" type="image"/>
+          <i class="el-icon-warning"/>
+          <span style="color: rgb(153, 169, 191)"> 不配置将使用课程默认图片</span>
+        </el-form-item>
+        <el-form-item label="首播电视台" prop="tvEnable">
+          <el-switch v-model="configDialog.form.tvEnable" active-color="#13ce66"/>
+        </el-form-item>
+        <el-form-item prop="tv" v-if="configDialog.form.tvEnable">
+          <el-input v-model="configDialog.form.tv" clearable></el-input>
+          <i class="el-icon-warning"/>
+          <span style="color: rgb(153, 169, 191)"> 多个首播电视台,请用英文逗号隔开</span>
+        </el-form-item>
+        <el-form-item label="网络播放平台" prop="networkEnable">
+          <el-switch v-model="configDialog.form.networkEnable" active-color="#13ce66"/>
+        </el-form-item>
+        <el-form-item prop="network" v-if="configDialog.form.networkEnable">
+          <el-input v-model="configDialog.form.network" clearable></el-input>
+          <i class="el-icon-warning"/>
+          <span style="color: rgb(153, 169, 191)"> 多个网络播放平台,请用英文逗号隔开</span>
+        </el-form-item>
+        <el-form-item label="制作单位" prop="unitEnable">
+          <el-switch v-model="configDialog.form.unitEnable" active-color="#13ce66"/>
+        </el-form-item>
+        <el-form-item prop="unit" v-if="configDialog.form.unitEnable">
+          <el-input v-model="configDialog.form.unit" clearable></el-input>
+          <i class="el-icon-warning"/>
+          <span style="color: rgb(153, 169, 191)"> 多个制作单位,请用英文逗号隔开</span>
+        </el-form-item>
+        <el-form-item label="专家顾问团队" prop="teamEnable">
+          <el-switch v-model="configDialog.form.teamEnable" active-color="#13ce66"/>
+        </el-form-item>
+        <el-form-item prop="team" v-if="configDialog.form.teamEnable">
+          <el-input v-model="configDialog.form.team" clearable></el-input>
+          <i class="el-icon-warning"/>
+          <span style="color: rgb(153, 169, 191)"> 多个专家顾问,请用英文逗号隔开</span>
+        </el-form-item>
+        <el-form-item label="支持单位" prop="supportEnable">
+          <el-switch v-model="configDialog.form.supportEnable" active-color="#13ce66"/>
+        </el-form-item>
+        <el-form-item prop="support" v-if="configDialog.form.supportEnable">
+          <el-input v-model="configDialog.form.support" clearable></el-input>
+          <i class="el-icon-warning"/>
+          <span style="color: rgb(153, 169, 191)"> 多个支持单位,请用英文逗号隔开</span>
+        </el-form-item>
+        <el-form-item label="监督投诉电话" prop="complaintPhone">
+          <el-input v-model="configDialog.form.complaintPhone" placeholder="请输入监督投诉电话" clearable/>
+        </el-form-item>
+      </el-form>
+      <div slot="footer" class="dialog-footer">
+        <el-button type="primary"
+                   :loading="configDialog.updating"
+                   :disabled="configDialog.updating"
+                   @click="submitConfigForm">确 定</el-button>
+        <el-button @click="cancelConfig">取 消</el-button>
+      </div>
+    </el-dialog>
+
+    <el-drawer
+      :with-header="false"
+      size="75%"
+      :title="show.title" :visible.sync="show.open" append-to-body>
+      <userCourseCatalogDetails ref="userCourseCatalogDetails"/>
+    </el-drawer>
+  </div>
+</template>
+
+<script>
+import {
+  listUserCourse,
+  getUserCourse,
+  delUserCourse,
+  addUserCourse,
+  updateUserCourse,
+  exportUserCourse,
+  updateIsShow,
+  copyUserCourse,
+  putOn,
+  pullOff, updateUserCourseRedPage,
+  editConfig
+} from '@/api/course/userCourse'
+
+import {getSelectableRange} from "@/api/qw/sopTemp";
+import Treeselect from "@riophae/vue-treeselect";
+import "@riophae/vue-treeselect/dist/vue-treeselect.css";
+import Editor from '@/components/Editor/wang';
+import ImageUpload from '@/components/ImageUpload/index';
+import {listBySearch} from "@/api/course/userTalent";
+import userCourseCatalogDetails from '@/views/components/course/userCourseCatalogDetails.vue';
+import {getAllCourseCategoryList, getCatePidList, getCateListByPid} from "@/api/course/userCourseCategory";
+import {allList} from "@/api/company/company";
+import VideoUpload from '@/components/VideoUpload/index.vue'
+import { getConfigByKey } from '@/api/system/config'
+export default {
+  name: "UserCourse",
+  components: {
+    VideoUpload,
+    Treeselect,
+    Editor, ImageUpload, userCourseCatalogDetails
+  },
+  watch:{
+    // 深度监听 rules 数组的变化,以更新总权重
+    "openRedPage.rules": {
+      handler(val) {
+        // this.calculateTotalWeight();
+        this.validateRules();
+      },
+      deep: true,
+    },
+  },
+  data() {
+    return {
+      /** 与后端 is_private 一致:1-课程管理(私域),0-公域课程管理 */
+      pageIsPrivate: 0,
+      talentParam: {
+        phone: null,
+        talentId: null
+      },
+      talentList: [],
+      startTimeRange: [],
+      show: {
+        title: "目录管理",
+        open: false
+      },
+      activeName: "1",
+      projectOptions: [],
+      tagsOptions: [],
+      tags: [],
+      companyIds: [],
+      courseTypeOptions: [],
+      orOptions: [],
+      specShowOptions: [],
+      specTypeOptions: [],
+      categoryOptions: [],
+      subCategoryOptions: [],
+      querySubCateOptions: [],
+      // 遮罩层
+      loading: true,
+      // 导出遮罩层
+      exportLoading: false,
+      // 选中数组
+      ids: [],
+      // 非单个禁用
+      single: true,
+      // 非多个禁用
+      multiple: true,
+      // 显示搜索条件
+      showSearch: true,
+      // 总条数
+      total: 0,
+      // 课程表格数据
+      userCourseList: [],
+      companyOptions: [],
+      // 弹出层标题
+      title: "",
+      // 是否显示弹出层
+      open: false,
+      openRedPage:{
+        open:false,
+        courseId:null,
+        courseName:null,
+        redPacketMoney:0.1,
+        //随机红包配置
+        rules:[
+          {
+            minAmount: 0.01,
+            maxAmount: 0.01,
+            weight: 100,
+          }
+        ]
+      },
+      // 查询参数
+      queryParams: {
+        pageNum: 1,
+        pageSize: 10,
+        cateId: null,
+        subCateId: null,
+        title: null,
+        imgUrl: null,
+        userId: null,
+        sort: null,
+        status: null,
+        isVip: null,
+        isHot: null,
+        isShow: "1",
+        views: null,
+        duration: null,
+        description: null,
+        hotRanking: null,
+        integral: null,
+        price: null,
+        courseId: null,
+        isPrivate: 0,
+        companyIdsList:[],
+      },
+      // 表单参数
+      form: {},
+      // 表单校验
+      rules: {
+        courseName: [
+          {required: true, message: "课堂名称不能为空", trigger: "blur"}
+        ],
+        imgUrl: [
+          {required: true, message: "封面图片不能为空", trigger: "blur"}
+        ],
+        isTui: [
+          {required: true, message: "是否推荐不能为空", trigger: "blur"}
+        ],
+        isBest: [
+          {required: true, message: "是否精选不能为空", trigger: "blur"}
+        ],
+        isFast: [
+          {required: true, message: "是否允许快进不能为空", trigger: "blur"}
+        ],
+        isAutoPlay: [
+          {required: true, message: "是否自动播放不能为空", trigger: "blur"}
+        ],
+        sort: [
+          {required: true, message: "排序不能为空", trigger: "blur"}
+        ],
+        views: [
+          {required: true, message: "播放量不能为空", trigger: "blur"}
+        ],
+        likes: [
+          {required: true, message: "点赞数不能为空", trigger: "blur"}
+        ],
+        favoriteNum: [
+          {required: true, message: "收藏数不能为空", trigger: "blur"}
+        ],
+        shares: [
+          {required: true, message: "分享数不能为空", trigger: "blur"}
+        ],
+        isIntegral: [
+          {required: true, message: "是否允许积分兑换不能为空", trigger: "blur"}
+        ],
+        isShow: [
+          {required: true, message: "上架状态不能为空", trigger: "blur"}
+        ],
+        isPrivate: [
+          {required: true, message: "公私域不能为空", trigger: "blur"}
+        ],
+        integral: [
+          {required: true, message: "小节兑换积分不能为空", trigger: "blur"}
+        ],
+      },
+
+      rulesRedPage:{
+        redPacketMoney: [
+          {required: true, message: "红包金额不能为空", trigger: "blur"}
+        ],
+      },
+      configDialog: {
+        dialogVisible: false,
+        updating: false,
+        form: {
+          id: null,
+          coverImg: null,
+          tvEnable: 0,
+          tv: null,
+          networkEnable: 0,
+          network: null,
+          unitEnable: 0,
+          unit: null,
+          teamEnable: 0,
+          team: null,
+          supportEnable: 0,
+          support: null,
+          //监督投诉电话
+          complaintPhone: null,
+        },
+        rules: {
+          tv: [
+            { required: true, message: '首播电视台不能为空', trigger: 'blur' }
+          ],
+          network: [
+            { required: true, message: '网络播放平台不能为空', trigger: 'blur' }
+          ],
+          unit: [
+            { required: true, message: '制作单位不能为空', trigger: 'blur' }
+          ],
+          team: [
+            { required: true, message: '专家顾问团队不能为空', trigger: 'blur' }
+          ],
+          support: [
+            { required: true, message: '支持单位不能为空', trigger: 'blur' }
+          ],
+        }
+      },
+      enableRandomRedPacket:false,
+      /** 推荐位配置(与 form 字段名对应) */
+      recommendSlots: [
+        { label: '首页课程顶部推荐位', enabledKey: 'recHomeCourseTopEnabled', modeKey: 'recHomeCourseTopMode', sortKey: 'recHomeCourseTopSort' },
+        { label: '商城首页推荐位', enabledKey: 'recMallHomeEnabled', modeKey: 'recMallHomeMode', sortKey: 'recMallHomeSort' },
+        { label: '首页长视频瀑布流', enabledKey: 'recHomeLongVideoEnabled', modeKey: 'recHomeLongVideoMode', sortKey: 'recHomeLongVideoSort' }
+      ]
+    };
+  },
+  created() {
+    this.getList();
+    getConfigByKey('randomRedpacket:config').then(res=>{
+      console.log("res::")
+      console.log(res);
+      let configData = res.data;
+      if(!!configData && !!configData.configValue){
+        let configValue = JSON.parse(configData.configValue);
+        console.log(configValue);
+        if(!!configValue.enableRandomRedpacket){
+          this.enableRandomRedPacket = configValue.enableRandomRedpacket;
+        }
+      }
+    }).catch(res=>{
+
+    })
+    getCatePidList().then(response => {
+      this.categoryOptions = response.data;
+    });
+
+
+    getSelectableRange().then(e => {
+      this.startTimeRange = e.data;
+    })
+    // this.getTreeselect();
+    this.getDicts("sys_spec_show").then(response => {
+      this.specShowOptions = response.data;
+    });
+    this.getDicts("sys_spec_type").then(response => {
+      this.specTypeOptions = response.data;
+    });
+    this.getDicts("sys_course_type").then(response => {
+      this.courseTypeOptions = response.data;
+    });
+    this.getDicts("sys_course_project").then(response => {
+      this.projectOptions = response.data;
+    });
+    this.getDicts("sys_course_tags").then(response => {
+      this.tagsOptions = response.data;
+    });
+    this.getDicts("sys_company_or").then(response => {
+      this.orOptions = response.data;
+    });
+    allList().then(response => {
+      this.companyOptions = response.rows;
+    });
+  },
+  methods: {
+    selectTalent() {
+
+    },
+    talentMethod(query) {
+      if (query !== '') {
+        this.talentParam.phone = query;
+        listBySearch(this.talentParam).then(response => {
+          this.talentList = response.data;
+        });
+      }
+    },
+    getSubCateList(pid) {
+      this.form.subCateId = null;
+      if (pid == '') {
+        this.subCategoryOptions = [];
+        return
+      }
+      getCateListByPid(pid).then(response => {
+        this.subCategoryOptions = response.data;
+      });
+    },
+    getQuerySubCateList(pid) {
+      this.queryParams.subCateId = null;
+      if (pid == '') {
+        this.querySubCateOptions = [];
+        return
+      }
+      this.queryParams.subCateId = null;
+      getCateListByPid(pid).then(response => {
+        this.querySubCateOptions = response.data;
+      });
+    },
+    handleShow(row) {
+      var isShowValue = row.isShow === 0 ? 1 : 0;
+      var course = {courseId: row.courseId, isShow: isShowValue};
+      updateIsShow(course).then(response => {
+        this.msgSuccess("修改成功");
+        this.getList();
+      });
+    },
+    handleCatalog(row) {
+      const courseId = row.courseId;
+      this.show.open = true;
+      setTimeout(() => {
+        this.$refs.userCourseCatalogDetails.getDetails(courseId, row.courseName, row.isPrivate);
+      }, 200);
+    },
+    /** 转换课堂分类数据结构 */
+    normalizer(node) {
+      if (node.children && !node.children.length) {
+        delete node.children;
+      }
+      return {
+        id: node.cateId,
+        label: node.cateName,
+        children: node.children
+      };
+    },
+    getTreeselect() {
+      getAllCourseCategoryList().then(response => {
+        this.categoryOptions = [];
+        const data = this.handleTree(response.data, "cateId", "pid");
+        this.categoryOptions = data;
+      });
+    },
+    /** 查询课程列表 */
+    getList() {
+      this.loading = true;
+      this.queryParams.isPrivate = this.pageIsPrivate;
+      listUserCourse(this.queryParams).then(response => {
+        this.userCourseList = response.rows;
+        this.total = response.total;
+        this.loading = false;
+      });
+    },
+    // 取消按钮
+    cancel() {
+      this.open = false;
+      this.reset();
+    },
+    // 表单重置
+    reset() {
+      this.form = {
+        courseId: null,
+        cateId: null,
+        subCateId: null,
+        title: null,
+        imgUrl: null,
+        secondImg: null,
+        userId: null,
+        sort: null,
+        createTime: null,
+        updateTime: null,
+        status: 0,
+        isVip: null,
+        isAutoPlay: "1",
+        isIntegral: "0",
+        isShow: "1",
+        isFast: "1",
+        isTui: "1",
+        isBest: "1",
+        isNext: "1",
+        isPrivate: String(this.pageIsPrivate),
+        views: 100000,
+        duration: null,
+        description: null,
+        hotRanking: null,
+        integral: null,
+        price: null,
+        likes: 100000,
+        shares: 100000,
+        favoriteNum: 100000,
+        hotNum: 100000,
+        recHomeCourseTopEnabled: 0,
+        recHomeCourseTopMode: 1,
+        recHomeCourseTopSort: undefined,
+        recMallHomeEnabled: 0,
+        recMallHomeMode: 1,
+        recMallHomeSort: undefined,
+        recHomeLongVideoEnabled: 0,
+        recHomeLongVideoMode: 1,
+        recHomeLongVideoSort: undefined,
+      };
+      this.tags = [];
+      this.subCategoryOptions = []
+      this.companyIds = []
+      this.resetForm("form");
+    },
+    /** 搜索按钮操作 */
+    handleQuery() {
+      this.queryParams.pageNum = 1;
+      this.getList();
+    },
+    /** 重置按钮操作 */
+    resetQuery() {
+      this.resetForm("queryForm");
+      this.queryParams.companyIdsList = [];
+      this.queryParams.isShow = this.activeName
+      this.queryParams.isPrivate = this.pageIsPrivate;
+      this.handleQuery();
+    },
+    // 多选框选中数据
+    handleSelectionChange(selection) {
+      this.ids = selection.map(item => item.courseId)
+      this.single = selection.length !== 1
+      this.multiple = !selection.length
+    },
+    /** 新增按钮操作 */
+    handleAdd() {
+      this.reset();
+      this.talentList = [];
+      this.open = true;
+      this.title = "添加课程";
+    },
+    /** 修改按钮操作 */
+    handleUpdate(row) {
+      this.reset();
+      this.talentList = [];
+      const courseId = row.courseId || this.ids
+      getUserCourse(courseId).then(response => {
+        this.form = response.data;
+        // this.form.cateId = response.data.cateId.toString();
+        if (this.form.cateId) {
+          getCateListByPid(this.form.cateId).then(response => {
+            this.subCategoryOptions = response.data;
+          });
+        }
+        // this.form.courseType = response.data.courseType.toString();
+        if (response.data.project != null) {
+          this.form.project = response.data.project.toString();
+        }
+        if (response.data.tags != null) {
+          this.tags = response.data.tags.split(",")
+        }
+        this.form.isAutoPlay = response.data.isAutoPlay.toString();
+        this.form.isShow = response.data.isShow.toString();
+        this.form.isBest = response.data.isBest.toString();
+        this.form.isFast = response.data.isFast.toString();
+        this.form.isIntegral = response.data.isIntegral.toString();
+        this.form.isTui = response.data.isTui.toString();
+        this.form.isNext = response.data.isNext.toString();
+        this.form.isPrivate = (response.data.isPrivate != null && response.data.isPrivate !== '')
+          ? response.data.isPrivate.toString()
+          : String(this.pageIsPrivate);
+        const n = v => (v === undefined || v === null ? undefined : Number(v));
+        this.form.recHomeCourseTopEnabled = n(response.data.recHomeCourseTopEnabled) ?? 0;
+        this.form.recHomeCourseTopMode = n(response.data.recHomeCourseTopMode) ?? 1;
+        this.form.recHomeCourseTopSort = n(response.data.recHomeCourseTopSort);
+        this.form.recMallHomeEnabled = n(response.data.recMallHomeEnabled) ?? 0;
+        this.form.recMallHomeMode = n(response.data.recMallHomeMode) ?? 1;
+        this.form.recMallHomeSort = n(response.data.recMallHomeSort);
+        this.form.recHomeLongVideoEnabled = n(response.data.recHomeLongVideoEnabled) ?? 0;
+        this.form.recHomeLongVideoMode = n(response.data.recHomeLongVideoMode) ?? 1;
+        this.form.recHomeLongVideoSort = n(response.data.recHomeLongVideoSort);
+        this.talentParam.talentId = response.data.talentId;
+        if (this.form.companyIds != null) {
+          this.companyIds = ((this.form.companyIds).split(",").map(Number))
+        } else {
+          this.companyIds = []
+        }
+
+        listBySearch(this.talentParam).then(response => {
+          this.talentList = response.data;
+        });
+        this.open = true;
+        this.title = "修改课程";
+      });
+    },
+
+    handleUpdateRedPage(row){
+      this.openRedPage.open=true;
+      this.openRedPage.courseId=row.courseId;
+      this.openRedPage.courseName=row.courseName;
+
+    },
+    /** 提交按钮 */
+    submitForm() {
+      this.$refs["form"].validate(valid => {
+        if (valid) {
+
+          this.form.companyIds = this.companyIds.toString()
+          this.form.isPrivate = this.pageIsPrivate
+          if (this.form.courseId != null) {
+            updateUserCourse(this.form).then(response => {
+              this.msgSuccess("修改成功");
+              this.open = false;
+              this.getList();
+            });
+          } else {
+            addUserCourse(this.form).then(response => {
+              this.msgSuccess("新增成功");
+              this.open = false;
+              this.getList();
+            });
+          }
+        }
+      });
+    },
+
+    submitFormRedPage(){
+
+      const courseId = this.openRedPage.courseId;
+      const redPacketMoney = this.openRedPage.redPacketMoney;
+      let randomRedPacketRules = JSON.stringify( this.openRedPage.rules);
+      console.log(randomRedPacketRules)
+      this.$confirm('是否确认将课程id 为"' + courseId + '"的红包批量修改为:【'+redPacketMoney+'】?', "警告", {
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "warning"
+      }).then(function () {
+        return updateUserCourseRedPage({courseId:courseId,redPacketMoney:redPacketMoney,randomRedPacketRules:randomRedPacketRules});
+      }).then(() => {
+        this.getList();
+        this.msgSuccess("修改成功");
+        this.openRedPage.open=false;
+      }).finally(() => {
+        this.getList();
+        this.openRedPage.open=false;
+      });
+
+    },
+    cancelRedPage(){
+      this.openRedPage.open=false;
+    },
+
+    /** 复制按钮操作 */
+    handleCopy(row) {
+      const courseId = row.courseId;
+      this.$confirm('是否确认复制课程编号为"' + courseId + '"的数据项?', "警告", {
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "warning"
+      }).then(function () {
+        return copyUserCourse(courseId);
+      }).then(() => {
+        this.getList();
+        this.msgSuccess("复制成功");
+      }).catch(() => {
+      });
+    },
+    /** 删除按钮操作 */
+    handleDelete(row) {
+      const courseIds = row.courseId || this.ids;
+      this.$confirm('是否确认删除课程编号为"' + courseIds + '"的数据项?', "警告", {
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "warning"
+      }).then(function () {
+        return delUserCourse(courseIds);
+      }).then(() => {
+        this.getList();
+        this.msgSuccess("删除成功");
+      }).catch(() => {
+      });
+    },
+    /** 导出按钮操作 */
+    handleExport() {
+      const queryParams = { ...this.queryParams, isPrivate: this.pageIsPrivate };
+      this.$confirm('是否确认导出所有课程数据项?', "警告", {
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "warning"
+      }).then(() => {
+        this.exportLoading = true;
+        return exportUserCourse(queryParams);
+      }).then(response => {
+        this.download(response.msg);
+        this.exportLoading = false;
+      }).catch(() => {
+      });
+    },
+    putOn() {
+      const courseIds = this.ids;
+      if (courseIds == null || courseIds == "") {
+        return this.$message("未选择课程");
+      }
+      this.$confirm('是否确认批量上架课程?', "警告", {
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "warning"
+      }).then(function () {
+        return putOn(courseIds);
+      }).then(() => {
+        this.getList();
+        this.msgSuccess("上架成功");
+      }).catch(function () {
+      });
+    },
+    pullOff() {
+      const courseIds = this.ids;
+      if (courseIds == null || courseIds == "") {
+        return this.$message("未选择课程");
+      }
+      this.$confirm('是否确认批量下架课程?', "警告", {
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "warning"
+      }).then(function () {
+        return pullOff(courseIds);
+      }).then(() => {
+        this.getList();
+        this.msgSuccess("下架成功");
+      }).catch(function () {
+      });
+    },
+    configCourse(row) {
+      if (row.configJson) {
+        this.configDialog.form = {
+          tvEnable: 0,
+          networkEnable: 0,
+          unitEnable: 0,
+          teamEnable: 0,
+          supportEnable: 0,
+          ...JSON.parse(row.configJson)
+        }
+      }
+      this.configDialog.form.id = row.courseId
+      this.configDialog.dialogVisible = true;
+      this.configDialog.updating = false
+    },
+    submitConfigForm() {
+      this.$refs['configForm'].validate(valid => {
+        if (!valid) {
+          this.msgError('请完善配置内容')
+          return
+        }
+
+        if (this.configDialog.updating) {
+          return
+        }
+        this.configDialog.updating = true
+
+        const content = {
+          coverImg: this.configDialog.form.coverImg,
+          tvEnable: this.configDialog.form.tvEnable,
+          tv: this.configDialog.form.tv?.replace(",",","),
+          networkEnable: this.configDialog.form.networkEnable,
+          network: this.configDialog.form.network?.replace(",",","),
+          unitEnable: this.configDialog.form.unitEnable,
+          unit: this.configDialog.form.unit?.replace(",",","),
+          teamEnable: this.configDialog.form.teamEnable,
+          team: this.configDialog.form.team?.replace(",",","),
+          supportEnable: this.configDialog.form.supportEnable,
+          support: this.configDialog.form.support?.replace(",",","),
+        }
+
+        const params = {
+          id: this.configDialog.form.id,
+          configJson: JSON.stringify(content)
+        }
+
+        editConfig(params).then(() => {
+          this.msgSuccess('修改成功')
+          this.configDialog.dialogVisible = false;
+          this.getList()
+        }).finally(() => {
+          setTimeout(() => {
+            this.configDialog.updating = false
+          }, 500)
+        })
+      })
+    },
+    cancelConfig() {
+      this.configDialog.form = {
+        id: null,
+        coverImg: null,
+        tvEnable: 0,
+        tv: null,
+        networkEnable: 0,
+        network: null,
+        unitEnable: 0,
+        unit: null,
+        teamEnable: 0,
+        team: null,
+        supportEnable: 0,
+        support: null
+      }
+      this.resetForm('configForm')
+      this.configDialog.dialogVisible = false;
+    },
+    // 实时过滤金额输入,只允许两位小数
+    handleAmountInput(rule, field) {
+      let value = rule[field];
+      if (value === null || value === undefined) return;
+
+      // 转换为字符串处理
+      let str = value.toString();
+
+      // 移除除数字和小数点外的所有字符
+      str = str.replace(/[^0-9.]/g, '');
+
+      // 只保留一个小数点
+      const dotIndex = str.indexOf('.');
+      if (dotIndex !== -1) {
+        str = str.substring(0, dotIndex + 1) + str.substring(dotIndex + 1).replace(/\./g, '');
+      }
+
+      // 限制小数点后最多两位
+      if (dotIndex !== -1 && str.length > dotIndex + 3) {
+        str = str.substring(0, dotIndex + 3);
+      }
+
+      // 转换回数字并更新
+      rule[field] = parseFloat(str) || 0;
+    },
+    deleteRule(index) {
+      this.$confirm("确定要删除这个区间吗?", "提示", {
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "warning",
+      }).then(() => {
+        this.openRedPage.rules.splice(index, 1);
+        this.$message({
+          type: "success",
+          message: "删除成功!",
+        });
+      });
+    },
+    addRule(index) {
+      // 在当前行的后面插入一个新行
+      this.openRedPage.rules.splice(index + 1, 0, {
+        minAmount: 0.01,
+        maxAmount: 0.01,
+        weight: 100,
+      });
+    },
+    // 自定义校验规则:确保最大金额大于最小金额
+    validateMinAmount(rule, value, callback) {
+      // debugger;
+      // const maxAmount = this.form29.rules[].maxAmount
+
+      const index = rule.index;
+      const maxAmount = this.openRedPage.rules[index].maxAmount;
+
+      if (value > maxAmount) {
+        callback(new Error("最小金额不能大于最大金额"));
+      } else {
+        callback();
+      }
+    },
+    validateRules() {
+      this.openRedPage.rules.forEach((rule) => {
+        if (rule.minAmount === undefined || rule.minAmount < 0.01) {
+          rule.minAmount = 0.01;
+        }
+        if (rule.maxAmount === undefined || rule.maxAmount < rule.minAmount) {
+          rule.maxAmount = rule.minAmount;
+        }
+        if (rule.weight === undefined || rule.weight < 1) {
+          rule.weight = 1;
+        }
+      });
+    },
+
+  }
+};
+</script>

+ 3160 - 0
src/views/course/publiccourse/videoResource/index.vue

@@ -0,0 +1,3160 @@
+<template>
+  <div class="app-container">
+    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
+      <el-form-item label="素材名称" prop="resourceName">
+        <el-input
+          v-model="queryParams.resourceName"
+          placeholder="请输入素材名称"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="文件名称" prop="fileName">
+        <el-input
+          v-model="queryParams.fileName"
+          placeholder="请输入素材名称"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="分类" prop="typeId">
+        <el-select
+          v-model="queryParams.typeId"
+          clearable
+          placeholder="请选择或输入分类"
+          @change="val => changeCateType(val, 1)"
+          filterable
+          :filter-method="filterTypeOptions"
+          @visible-change="handleSelectVisibleChange">
+          <el-option
+            v-for="item in filteredRootTypeList"
+            :key="item.dictValue"
+            :label="item.dictLabel"
+            :value="item.dictValue">
+          </el-option>
+        </el-select>
+      </el-form-item>
+      <el-form-item label="子分类" prop="typeSubId">
+        <el-select v-model="queryParams.typeSubId" clearable placeholder="请选择子分类">
+          <el-option
+            v-for="item in subTypeList"
+            :key="item.dictValue"
+            :label="item.dictLabel"
+            :value="item.dictValue">
+          </el-option>
+        </el-select>
+      </el-form-item>
+      <el-form-item>
+        <el-button type="cyan" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
+        <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
+      </el-form-item>
+    </el-form>
+
+    <el-row :gutter="10" class="mb8">
+      <el-col :span="1.5">
+        <el-button
+          type="primary"
+          icon="el-icon-plus"
+          size="mini"
+          @click="handleAdd"
+          :disabled="hasMinimizableDialog"
+          v-hasPermi="['course:videoResource:add']"
+        >新增</el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button
+          type="primary"
+          icon="el-icon-plus"
+          size="mini"
+          @click="handleBatchAdd"
+          :disabled="hasMinimizableDialog"
+          v-hasPermi="['course:videoResource:batchAdd']"
+        >批量新增</el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button
+          type="primary"
+          icon="el-icon-plus"
+          size="mini"
+          :disabled="multiple"
+          @click="handleBatchUpdate"
+          v-hasPermi="['course:videoResource:batchUpdateClass']"
+        >批量修改分类</el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button
+          type="danger"
+          icon="el-icon-delete"
+          size="mini"
+          :disabled="multiple"
+          @click="handleDelete"
+          v-hasPermi="['course:videoResource:remove']"
+        >删除</el-button>
+      </el-col>
+      <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
+    </el-row>
+
+    <el-table v-loading="loading" :data="resourceList" @selection-change="handleSelectionChange" border>
+      <el-table-column type="selection" width="55" align="center" />
+      <el-table-column label="序号" width="55" align="center">
+        <template slot-scope="scope">
+          {{ scope.$index + 1 }}
+        </template>
+      </el-table-column>
+      <el-table-column label="素材名称" align="center" :show-overflow-tooltip="true" prop="resourceName"/>
+      <el-table-column label="文件名称" align="center" :show-overflow-tooltip="true" prop="fileName"/>
+      <el-table-column label="排序" align="center" prop="sort" />
+      <el-table-column label="分类" align="center">
+        <template slot-scope="scope">
+          <span v-if="scope.row.typeId">{{ getTypeName(scope.row.typeId) }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="子分类" align="center">
+        <template slot-scope="scope">
+          <span v-if="scope.row.typeSubId">{{ getTypeName(scope.row.typeSubId) }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="视频文件" align="center">
+        <template slot-scope="scope">
+          <a
+            @click="handleVideoPreview(scope.row.videoUrl)"
+            style="background-color: #409EFF; color: white; border: none; border-radius: 4px; padding: 4px 12px; cursor: pointer; font-size: 12px; display: inline-block; text-decoration: none;">
+            查看文件
+          </a>
+        </template>
+      </el-table-column>
+      <el-table-column label="CDN" align="center">
+        <template slot-scope="scope">
+          <a
+            @click="copy(scope.row.videoUrl)"
+            style="color: #409EFF; border: none; border-radius: 4px; padding: 4px 12px; cursor: pointer; font-size: 12px; display: inline-block; text-decoration: none;">
+            复制链接
+          </a>
+        </template>
+      </el-table-column>
+      <el-table-column label="关联题目" align="center">
+        <template slot-scope="scope">
+          <a
+            @click="handleViewProject(scope.row, 3)"
+            :style="scope.row.projectIds ? {
+              backgroundColor: '#409EFF',
+              color: 'white',
+              border: 'none',
+              borderRadius: '4px',
+              padding: '4px 12px',
+              cursor: 'pointer',
+              fontSize: '12px',
+              display: 'inline-block',
+              textDecoration: 'none'
+            } : {
+              backgroundColor: 'rgb(154 156 159)',
+              color: 'white',
+              border: 'none',
+              cursor: 'pointer',
+              borderRadius: '4px',
+              padding: '4px 12px',
+              fontSize: '12px',
+              display: 'inline-block',
+              textDecoration: 'none'
+            }">
+            {{ scope.row.projectIds ? '查看详情' : '未关联题目' }}
+          </a>
+        </template>
+      </el-table-column>
+      <el-table-column label="视频时长" align="center">
+        <template slot-scope="scope">
+          <div style="padding: 4px 12px;background: linear-gradient(to right, rgb(196 219 255), #409EFF)">{{ formatDuration(scope.row.duration) }}</div>
+        </template>
+      </el-table-column>
+      <el-table-column label="视频展示类型" align="center" prop="displayType" width="110">
+        <template slot-scope="scope">
+          {{ scope.row.displayType === 'portrait' ? '竖屏' : '横屏' }}
+        </template>
+      </el-table-column>
+      <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
+        <template slot-scope="scope">
+          <el-button
+            size="mini"
+            type="text"
+            icon="el-icon-edit"
+            @click="handleUpdate(scope.row)"
+            :disabled="hasMinimizableDialog"
+            v-hasPermi="['course:videoResource:edit']"
+          >修改</el-button>
+          <el-button
+            size="mini"
+            type="text"
+            icon="el-icon-delete"
+            @click="handleDelete(scope.row)"
+            v-hasPermi="['course:videoResource:remove']"
+          >删除</el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+
+    <pagination
+      v-show="total>0"
+      :total="total"
+      :page.sync="queryParams.pageNum"
+      :limit.sync="queryParams.pageSize"
+      @pagination="getList"
+    />
+
+    <!-- 添加或修改视频素材库对话框 -->
+    <el-dialog :title="title" :visible.sync="open" width="700px" append-to-body :before-close="cancel"
+               @minimize="hasMinimizableDialog = true" @restore="hasMinimizableDialog = false">
+      <el-form ref="form" :model="form" :rules="rules" label-width="80px">
+        <el-form-item label="素材名称" prop="resourceName" style="margin-top: 20px">
+          <el-input v-model="form.resourceName" placeholder="请输入" />
+        </el-form-item>
+
+        <el-form-item label="文件名称" prop="fileName" style="margin-top: 20px;display: none">
+          <el-input v-model="form.fileName" placeholder="请输入" />
+        </el-form-item>
+
+        <el-form-item label="分类" prop="typeId">
+          <el-select v-model="form.typeId" placeholder="请选择分类" style="width: 100%" @change="val => changeCateType(val, 2)">
+            <el-option
+              v-for="item in rootTypeList"
+              :key="item.dictValue"
+              :label="item.dictLabel"
+              :value="item.dictValue">
+            </el-option>
+          </el-select>
+        </el-form-item>
+
+        <el-form-item label="子分类" prop="typeSubId">
+          <el-select v-model="form.typeSubId" clearable placeholder="请选择子分类" style="width: 100%">
+            <el-option
+              v-for="item in subTypeList"
+              :key="item.dictValue"
+              :label="item.dictLabel"
+              :value="item.dictValue">
+            </el-option>
+          </el-select>
+        </el-form-item>
+
+        <el-form-item label="排序" prop="sort">
+          <el-input-number v-model="form.sort"  :min="0" label="请输入排序"></el-input-number>
+        </el-form-item>
+
+        <el-form-item label="视频展示类型" prop="displayType">
+          <el-radio-group v-model="form.displayType">
+            <el-radio label="landscape">横屏</el-radio>
+            <el-radio label="portrait">竖屏</el-radio>
+          </el-radio-group>
+        </el-form-item>
+
+        <el-form-item label="关联题目" prop="projectIds">
+          <el-select
+            ref="customSelect"
+            class="custom-select-class"
+            v-model="form.projectIds"
+            multiple
+            placeholder="请选择关联题目"
+            @click.native.stop="openProjectDialog(form.projectIds, 0)"
+            style="width: 100%;">
+            <el-option
+              v-for="item in projectShowList"
+              :key="item.id"
+              :label="item.title"
+              :value="item.id">
+            </el-option>
+          </el-select>
+        </el-form-item>
+
+        <el-form-item label="上传视频" prop="videoUrl" required>
+          <el-upload
+            ref="videoUpload"
+            action="#"
+            list-type="picture-card"
+            :http-request="videoUpload"
+            :file-list="fileList"
+            :on-change="handleFileChange"
+            accept=".mp4">
+            <i slot="default" class="el-icon-plus"></i>
+            <div slot="file" slot-scope="{file}">
+              <div
+                class="el-upload-list__item-thumbnail"
+                style="display: flex; justify-content: center; align-items: center; background-color: #f5f7fa; height: 146px;"
+              >
+                <i class="el-icon-video-camera" style="font-size: 48px;" />
+              </div>
+
+              <span v-if="file.status === 'success'" class="el-upload-list__item-status-label">
+                <i class="el-icon-upload-success el-icon--check"></i>
+              </span>
+              <span v-if="file.status === 'fail'" class="el-upload-list__item-status-label">
+                <i class="el-icon-circle-close"></i>
+              </span>
+
+              <span class="el-upload-list__item-actions">
+                <span
+                  class="el-upload-list__item-preview"
+                  @click="handleVideoPreview(form.videoUrl)"
+                >
+                  <i class="el-icon-zoom-in"></i>
+                </span>
+                <span
+                  class="el-upload-list__item-delete"
+                  @click="handleRemove(file)"
+                >
+                  <i class="el-icon-delete"></i>
+                </span>
+              </span>
+
+              <div v-if="file.status === 'uploading'" class="el-upload-list__item-progress">
+                <div class="dual-upload-progress">
+                  <div class="total-progress">
+                    <el-progress
+                      :percentage="currentUploadProgress.total"
+                      :show-text="true"
+                      :width="52"
+                      :format="() => `${Math.round(currentUploadProgress.total)}%`"
+                    ></el-progress>
+                  </div>
+                  <div class="line-progress-container">
+                    <div class="line-progress-item">
+                      <span class="line-label">线路1:</span>
+                      <el-progress
+                        :percentage="currentUploadProgress.line1"
+                        :show-text="false"
+                        :width="30"
+                        :status="currentUploadProgress.line1Status === 'success' ? 'success' :
+                                currentUploadProgress.line1Status === 'failed' ? 'exception' : ''"
+                      ></el-progress>
+                      <span class="line-status-text">{{ Math.round(currentUploadProgress.line1) }}%</span>
+                    </div>
+                    <div class="line-progress-item">
+                      <span class="line-label">线路2:</span>
+                      <el-progress
+                        :percentage="currentUploadProgress.line2"
+                        :show-text="false"
+                        :width="30"
+                        :status="currentUploadProgress.line2Status === 'success' ? 'success' :
+                                currentUploadProgress.line2Status === 'failed' ? 'exception' : ''"
+                      ></el-progress>
+                      <span class="line-status-text">{{ Math.round(currentUploadProgress.line2) }}%</span>
+                    </div>
+                  </div>
+                </div>
+              </div>
+            </div>
+          </el-upload>
+        </el-form-item>
+
+        <el-form-item label="时长">
+          <span>{{ formatDuration(form.duration) }}</span>
+        </el-form-item>
+      </el-form>
+      <div slot="footer" class="dialog-footer">
+        <el-button @click="cancel">取消</el-button>
+        <el-button type="primary" @click="submitForm" :loading="add" :disabled="isUploading || add">
+          {{ isUploading ? '上传中...' : '保存' }}
+        </el-button>
+      </div>
+    </el-dialog>
+
+    <el-dialog
+      title="视频预览"
+      :visible.sync="videoPreviewVisible"
+      append-to-body
+      :close-on-click-modal="true"
+      width="700px"
+      class="video-preview-dialog"
+      :modal-append-to-body="false"
+      :before-close="handleCloseVideoPreview">
+      <video ref="up-video" id="video" width="100%" height="400px" controls :src="videoPreviewUrl" />
+    </el-dialog>
+
+    <!--批量修改弹框-->
+    <el-dialog :title="'批量修改'" :visible.sync="batchUpdateVisible" width="700px" append-to-body :before-close="cancel"
+               @minimize="hasMinimizableDialog = true" @restore="hasMinimizableDialog = false">
+      <el-form ref="form" :model="batchUpdateForm" :rules="rules" label-width="80px">
+        <el-form-item label="分类" prop="typeId">
+          <el-select v-model="batchUpdateForm.typeId" placeholder="请选择分类" style="width: 100%" @change="val => changeCateType(val, 2)">
+            <el-option
+              v-for="item in rootTypeList"
+              :key="item.dictValue"
+              :label="item.dictLabel"
+              :value="item.dictValue">
+            </el-option>
+          </el-select>
+        </el-form-item>
+
+        <el-form-item label="子分类" prop="typeSubId">
+          <el-select v-model="batchUpdateForm.typeSubId" clearable placeholder="请选择子分类" style="width: 100%">
+            <el-option
+              v-for="item in subTypeList"
+              :key="item.dictValue"
+              :label="item.dictLabel"
+              :value="item.dictValue">
+            </el-option>
+          </el-select>
+        </el-form-item>
+      </el-form>
+      <div class="dialog-footer">
+        <el-button @click="cancelBatch">取 消</el-button>
+        <el-button type="primary" @click="submitBatchUpdate">保 存</el-button>
+      </div>
+    </el-dialog>
+    <!-- 批量选择视频弹窗 -->
+    <minimizable-dialog :title="'选择视频'" :visible.sync="batchAddVisible" width="1200px" append-to-body class="batch-dialog"
+                        :close-on-click-modal="false" :before-close="cancelBeforeBatch" @minimize="hasMinimizableDialog = true"
+                        @restore="hasMinimizableDialog = false">
+      <div class="filter-container">
+        <el-button type="primary" icon="el-icon-plus" size="small" @click="showUploadPanel">上传视频</el-button>
+      </div>
+
+      <el-table
+        v-loading="batchLoading"
+        :data="videoList"
+        height="350"
+        border>
+        <el-table-column label="序号" width="60" align="center">
+          <template slot-scope="scope">
+            {{ scope.$index + 1 }}
+          </template>
+        </el-table-column>
+        <el-table-column label="素材名称" align="center" prop="resourceName" min-width="120" />
+        <el-table-column label="文件名称" align="center" prop="fileName" min-width="120" />
+        <el-table-column label="分类" align="center" min-width="100">
+          <template slot-scope="scope">
+            {{ getTypeName(scope.row.typeId) }}
+          </template>
+        </el-table-column>
+        <el-table-column label="子分类" align="center" min-width="100">
+          <template slot-scope="scope">
+            {{ getTypeName(scope.row.typeSubId) }}
+          </template>
+        </el-table-column>
+        <el-table-column label="关联项目" align="center" min-width="100">
+          <template slot-scope="scope">
+            <a
+              @click="handleViewProject(scope.row, 4)"
+              :style="scope.row.projectIds.length > 0 ? {
+              backgroundColor: '#409EFF',
+              color: 'white',
+              border: 'none',
+              borderRadius: '4px',
+              padding: '4px 12px',
+              cursor: 'pointer',
+              fontSize: '12px',
+              display: 'inline-block',
+              textDecoration: 'none'
+            } : {
+              backgroundColor: 'rgb(154 156 159)',
+              color: 'white',
+              border: 'none',
+              cursor: 'pointer',
+              borderRadius: '4px',
+              padding: '4px 12px',
+              fontSize: '12px',
+              display: 'inline-block',
+              textDecoration: 'none'
+            }">
+              {{ scope.row.projectIds.length > 0 ? '查看详情' : '未关联题目' }}
+            </a>
+          </template>
+        </el-table-column>
+        <el-table-column label="视频文件" align="center" prop="fileName" min-width="120">
+          <template slot-scope="scope">
+            <a
+              @click="handleVideoPreview(scope.row.videoUrl)"
+              style="background-color: #409EFF; color: white; border: none; border-radius: 4px; padding: 4px 12px; cursor: pointer; font-size: 12px; display: inline-block; text-decoration: none;">
+              查看文件
+            </a>
+          </template>
+        </el-table-column>
+        <el-table-column label="视频时长" align="center" width="80">
+          <template slot-scope="scope">
+            {{ formatDuration(scope.row.duration) }}
+          </template>
+        </el-table-column>
+        <el-table-column label="上传进度" align="center" width="200">
+          <template slot-scope="scope">
+            <div class="batch-upload-progress">
+              <div v-if="scope.row.uploadStatus === 'queued'" class="queue-status">
+                <el-tag :color="getQueueStatusColor(scope.row.uploadStatus)" size="small">
+                  {{ getUploadStatusText(scope.row.uploadStatus, scope.row.queuePosition) }}
+                </el-tag>
+              </div>
+              <div v-else class="total-progress-row">
+                <span class="progress-label">总进度:</span>
+                <el-progress
+                  :percentage="scope.row.progress || 0"
+                  :status="getProgressStatus(scope.row)"
+                  :show-text="true"
+                  :format="() => `${Math.round(scope.row.progress || 0)}%`"
+                ></el-progress>
+              </div>
+              <div v-if="scope.row.uploadDetails && scope.row.uploadStatus !== 'queued'" class="line-progress-rows">
+                <div class="line-progress-row">
+                  <span class="line-label">线路1:</span>
+                  <el-progress
+                    :percentage="scope.row.uploadDetails.line1 || 0"
+                    :status="scope.row.uploadDetails.line1Status === 'success' ? 'success' :
+                            scope.row.uploadDetails.line1Status === 'failed' ? 'exception' : 'warning'"
+                    :show-text="false"
+                    style="width: 60px;"
+                  ></el-progress>
+                  <span class="line-percentage">{{ Math.round(scope.row.uploadDetails.line1 || 0) }}%</span>
+                </div>
+                <div class="line-progress-row">
+                  <span class="line-label">线路2:</span>
+                  <el-progress
+                    :percentage="scope.row.uploadDetails.line2 || 0"
+                    :status="scope.row.uploadDetails.line2Status === 'success' ? 'success' :
+                            scope.row.uploadDetails.line2Status === 'failed' ? 'exception' : 'warning'"
+                    :show-text="false"
+                    style="width: 60px;"
+                  ></el-progress>
+                  <span class="line-percentage">{{ Math.round(scope.row.uploadDetails.line2 || 0) }}%</span>
+                </div>
+              </div>
+            </div>
+          </template>
+
+        </el-table-column>
+        <el-table-column label="操作" align="center" width="150">
+          <template slot-scope="scope">
+            <el-button
+              size="mini"
+              type="text"
+              icon="el-icon-edit"
+              :disabled="scope.row.progress !== 100"
+              @click="handleEditVideo(scope.row)">
+              编辑
+              <el-tooltip
+                v-if="scope.row.progress !== 100"
+                content="上传完成后可编辑"
+                placement="top"
+              >
+                <i class="el-icon-question"></i>
+              </el-tooltip>
+            </el-button>
+            <el-button
+              size="mini"
+              type="text"
+              icon="el-icon-delete"
+              @click="handleDeleteVideo(scope.row)"
+              :style="scope.row.uploadStatus === 'queued' ? 'color: #E6A23C;' : ''"
+            >
+              {{ scope.row.uploadStatus === 'queued' ? '取消' : '删除' }}
+            </el-button>
+            <el-button
+              v-if="scope.row.progress < 100 && scope.row.uploadStatus === 'failed'"
+              size="mini"
+              type="text"
+              icon="el-icon-refresh"
+              @click="retryBatchUpload(scope.row)"
+              style="color: #E6A23C;"
+            >
+              重试
+            </el-button>
+          </template>
+        </el-table-column>
+      </el-table>
+
+      <div class="dialog-footer">
+        <el-button @click="cancelBatch">取 消</el-button>
+        <el-button type="primary" :loading="add" :disabled="add || isUploading" @click="submitBatchAdd">保 存</el-button>
+      </div>
+      <!-- 批量上传视频弹窗 -->
+      <el-dialog
+        title="批量上传视频"
+        :visible.sync="showUpload"
+        width="500px"
+        append-to-body
+        :close-on-click-modal="false"
+        class="upload-dialog">
+        <el-form :model="batchUploadForm" ref="batchUploadForm" label-width="80px">
+          <el-form-item style="margin-top: 20px" label="分类" prop="typeId" :rules="[{ required: true, message: '请选择分类', trigger: 'blur' }]">
+            <el-select
+              v-model="batchUploadForm.typeId"
+              placeholder="请选择分类"
+              style="width: 100%"
+              @change="val => changeCateType(val, 3)"
+              filterable
+            >
+              <el-option
+                v-for="item in rootTypeList"
+                :key="item.dictValue"
+                :label="item.dictLabel"
+                :value="item.dictValue">
+              </el-option>
+            </el-select>
+          </el-form-item>
+
+          <el-form-item label="子分类" prop="typeSubId" :rules="[{ required: true, message: '请选择子分类', trigger: 'blur' }]">
+            <el-select
+              v-model="batchUploadForm.typeSubId"
+              clearable
+              placeholder="请选择子分类"
+              style="width: 100%"
+              @change="changeSubType"
+              filterable
+            >
+              <el-option
+                v-for="item in subTypeList"
+                :key="item.dictValue"
+                :label="item.dictLabel"
+                :value="item.dictValue">
+              </el-option>
+            </el-select>
+          </el-form-item>
+
+          <el-form-item label="视频展示类型" prop="displayType">
+            <el-radio-group v-model="batchUploadForm.displayType">
+              <el-radio label="landscape">横屏</el-radio>
+              <el-radio label="portrait">竖屏</el-radio>
+            </el-radio-group>
+          </el-form-item>
+
+          <el-form-item label="关联题目" prop="projectIds" v-show="currentProject === 'myhk'">
+            <el-select
+              ref="customSelect"
+              class="custom-select-class"
+              v-model="batchUploadForm.projectIds"
+              multiple
+              placeholder="请选择关联题目"
+              @click.native.stop="openProjectDialog(batchUploadForm.projectIds, 1)"
+              style="width: 100%;">
+              <el-option
+                v-for="item in projectShowList"
+                :key="item.id"
+                :label="item.title"
+                :value="item.id">
+              </el-option>
+            </el-select>
+          </el-form-item>
+
+          <el-form-item label="上传视频" prop="files">
+            <el-upload
+              ref="batchVideoUpload"
+              action="#"
+              :http-request="batchVideoUpload"
+              :file-list="batchFileList"
+              :on-change="handleBatchFileChange"
+              multiple
+              accept=".mp4">
+              <el-button type="primary" :disabled="batchUploadForm.typeSubId === null">选择文件</el-button>
+              <div slot="tip" class="el-upload__tip">选择分类后才可以上传视频</div>
+            </el-upload>
+          </el-form-item>
+        </el-form>
+      </el-dialog>
+
+      <!-- 添加或修改视频素材库对话框 -->
+      <el-dialog :title="batchEditDialog.title" :visible.sync="batchEditDialog.open" width="600px" append-to-body :before-close="batchEditCancel">
+        <el-form ref="batchEditDialogForm" :model="batchEditDialog.form" :rules="batchEditDialog.rules" label-width="80px">
+          <el-form-item label="素材名称" prop="resourceName" style="margin-top: 20px">
+            <el-input v-model="batchEditDialog.form.resourceName" placeholder="请输入" />
+          </el-form-item>
+
+          <el-form-item label="视频展示类型" prop="displayType">
+            <el-radio-group v-model="batchEditDialog.form.displayType">
+              <el-radio label="landscape">横屏</el-radio>
+              <el-radio label="portrait">竖屏</el-radio>
+            </el-radio-group>
+          </el-form-item>
+
+          <el-form-item label="文件名称" prop="fileName" style="margin-top: 20px;display: none">
+            <el-input v-model="batchEditDialog.form.fileName" placeholder="请输入" />
+          </el-form-item>
+
+          <el-form-item label="关联题目" prop="projectIds">
+            <el-select
+              ref="customSelect"
+              class="custom-select-class"
+              v-model="batchEditDialog.form.projectIds"
+              multiple
+              placeholder="请选择关联题目"
+              @click.native.stop="openProjectDialog(batchEditDialog.form.projectIds, 2)"
+              style="width: 100%;">
+              <el-option
+                v-for="item in projectShowList"
+                :key="item.id"
+                :label="item.title"
+                :value="item.id">
+              </el-option>
+            </el-select>
+          </el-form-item>
+        </el-form>
+        <div slot="footer" class="dialog-footer">
+          <el-button @click="batchEditCancel">取消</el-button>
+          <el-button type="primary" @click="batchEditSubmitForm">保存</el-button>
+        </div>
+      </el-dialog>
+
+    </minimizable-dialog>
+
+    <!-- 项目选择弹窗 -->
+    <el-dialog
+      title="选择题目"
+      :visible.sync="projectDialogVisible"
+      width="1000px"
+      append-to-body
+      @close="cancelSelectProject"
+      :close-on-click-modal="false">
+      <div class="project-container">
+        <!-- 左侧分类树 -->
+        <div class="category-tree">
+          <div class="tree-fixed-header">
+            <el-input
+              placeholder="请输入分类名称"
+              v-model="categoryFilterText"
+              size="small"
+              clearable
+              prefix-icon="el-icon-search">
+            </el-input>
+          </div>
+          <div class="tree-content">
+            <el-tree
+              ref="categoryTree"
+              :data="categoryTreeData"
+              node-key="cateId"
+              highlight-current
+              :filter-node-method="filterNode"
+              :props="{ label: 'cateName', children: 'children' }"
+              @node-click="handleCategoryClick"
+              :expand-on-click-node="false"
+              default-expand-all>
+              <span class="custom-tree-node" slot-scope="{ node, data }">
+                <span>{{ node.label }}</span>
+              </span>
+            </el-tree>
+          </div>
+        </div>
+
+        <!-- 右侧题目列表 -->
+        <div class="project-list">
+          <div class="filter-container">
+            <el-form :inline="true" :model="projectQueryParams" ref="projectForm">
+              <el-form-item>
+                <el-input prefix-icon="el-icon-search" @input="searchProjects" v-model="projectQueryParams.title" placeholder="请输入题目标题" clearable size="small" />
+              </el-form-item>
+            </el-form>
+          </div>
+
+          <el-table
+            ref="projectTable"
+            height="350"
+            :data="projectList"
+            row-key="id"
+            v-loading="projectLoading"
+            border
+            @select="handleProjectSelect"
+            @select-all="handleProjectSelect">
+            <el-table-column type="selection" reserve-selection width="55" align="center" />
+            <el-table-column label="序号" width="60" align="center">
+              <template slot-scope="scope">
+                {{ scope.row.sort }}
+              </template>
+            </el-table-column>
+            <el-table-column label="题目标题" align="center" prop="title" min-width="200" show-overflow-tooltip />
+          </el-table>
+
+          <div class="table-footer">
+            <el-pagination style="text-align: right" v-show="projectListTotal>0" :pager-count="5" background
+                           @size-change="handleProjectPageSizeChange" @current-change="handleProjectPageChange" :current-page="projectQueryParams.pageNum"
+                           :page-sizes="[10, 20, 30, 50]" :page-size="projectQueryParams.pageSize" layout="total, sizes, prev, pager, next, jumper"
+                           :total="projectListTotal">
+            </el-pagination>
+          </div>
+        </div>
+      </div>
+      <div slot="footer" class="dialog-footer">
+        <span style="float: left; color: #606266; font-size: 13px;">
+          共选择 <span style="color: #409EFF; font-weight: bold;">{{ selectedProjectIds.length }}</span> 个题目
+        </span>
+        <el-button @click="projectDialogVisible = false">取消</el-button>
+        <el-button type="primary" @click="confirmSelectProject">确定</el-button>
+      </div>
+    </el-dialog>
+
+    <!-- 项目列表弹窗 -->
+    <el-dialog
+      title="题目列表"
+      :visible.sync="projectListDialogVisible"
+      width="600px"
+      append-to-body
+      :close-on-click-modal="false">
+      <div class="project-list-container" style="max-height: 500px; overflow-y: auto; padding: 10px;">
+        <!-- 题目列表 -->
+        <div v-for="(item, index) in projectShowList" :key="index" class="question-card">
+          <!-- 题目标题 -->
+          <div class="question-header">
+            <span class="question-index">{{ index + 1 }}</span>
+            <span class="question-title">{{ item.title }}</span>
+          </div>
+
+          <!-- 题目类型 -->
+          <div class="question-type">
+            <el-tag size="small" type="primary">{{ item.type === 1 ? '单选' : '多选' }}</el-tag>
+          </div>
+
+          <!-- 题目内容 -->
+          <div class="question-content" v-if="item.question && item.question.length > 0">
+            <div class="content-title">题目内容:</div>
+            <div v-for="(q, qIndex) in JSON.parse(item.question)" :key="qIndex" class="question-item"
+                 :style=" q.isAnswer === 1 ? 'background-color: rgb(234 245 251)' : ''">
+              <div class="question-item-header">
+                <span class="item-index">{{ convertToLetter(qIndex) }}</span>
+                <span class="item-name">{{ q.name }}</span>
+              </div>
+            </div>
+          </div>
+
+          <!-- 答案 -->
+          <div class="question-answer" v-if="item.answer">
+            <span class="answer-label">答案:</span>
+            <span class="answer-content">{{ item.answer }}</span>
+          </div>
+        </div>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import {
+  addVideoResource,
+  deleteVideoResource,
+  getVideoResource,
+  listVideoResource,
+  updateVideoResource,
+  batchAddVideoResource,
+  batchUpdateVideoResource
+} from '@/api/course/videoResource'
+import {listUserCourseCategory,getCatePidList,getCateListByPid} from '@/api/course/userCourseCategory'
+import {getByIds, listCourseQuestionBank} from '@/api/course/courseQuestionBank'
+import {getThumbnail} from "@/api/course/userVideo";
+import {uploadObject} from "@/utils/cos.js";
+import {uploadToOBS} from "@/utils/obs.js";
+import {uploadToHSY} from "@/utils/hsy.js";
+import MinimizableDialog from "@/components/MinimizableDialog"
+import log from "@/views/monitor/job/log.vue";
+
+export default {
+  name: 'VideoResource',
+  components: {
+    MinimizableDialog
+  },
+  data() {
+    return {
+      /** 本页视频资源类型:0-视频资源,1-公域课视频资源(与后端 video_type 一致) */
+      pageVideoType: 1,
+      filteredRootTypeList: [], // 过滤后的分类列表
+      originalRootTypeList: [], // 原始分类列表
+      // 遮罩层
+      loading: true,
+      // 选中数组
+      ids: [],
+      // 非单个禁用
+      single: true,
+      // 非多个禁用
+      multiple: true,
+      // 显示搜索条件
+      showSearch: true,
+      // 总条数
+      total: 0,
+      // 视频素材库表格数据
+      resourceList: [],
+      // 弹出层标题
+      title: "",
+      // 是否显示弹出层
+      open: false,
+      // 查询参数
+      queryParams: {
+        pageNum: 1,
+        pageSize: 10,
+        resourceName: null,
+        fileName: null,
+        typeId: null,
+        typeSubId: null,
+        videoType: 1
+      },
+      // 表单参数
+      form: {
+        id: null,
+        resourceName: null,
+        fileName: null,
+        thumbnail: null,
+        line1: null,
+        line2: null,
+        line3: null,
+        duration: null,
+        fileSize: null,
+        fileKey: null,
+        videoUrl: null,
+        typeId: null,
+        typeSubId: null,
+        projectIds: [],
+        sort: null,
+        displayType: 'landscape',
+        videoType: 1,
+        hsyVid:null,//火山云上传视频返回vid
+        hsyVodUrl:null,//火山云url
+        // 新增上传状态字段
+        uploadStatus: 'pending', // pending, uploading, success, failed
+        uploadProgress: {
+          total: 0,
+          line1: 0,
+          line2: 0,
+          line1Status: 'pending', // pending, uploading, success, failed
+          line2Status: 'pending'
+        }
+      },
+      // 表单校验
+      rules: {
+        resourceName: [
+          { required: true, message: "素材名称不能为空", trigger: "blur" }
+        ],
+        fileName: [
+          { required: true, message: "文件名称不能为空", trigger: "blur" }
+        ],
+        typeId: [
+          { required: true, message: "请选择分类", trigger: "change" }
+        ],
+        typeSubId: [
+          { required: true, message: "请选择子分类", trigger: "change" }
+        ],
+        sort: [
+          { required: true, message: '排序不能为空', trigger: 'blur' }
+        ],
+        videoUrl: [
+          { required: true, message: "请上传视频", trigger: "change" }
+        ]
+      },
+      // 课程列表数据
+      projectList: [],
+      projectShowList: [],
+      // 分类
+      typeList: [],
+      rootTypeList: [],
+      subTypeList: [],
+      // 视频上传
+      videoDisabled: false,
+      videoPreviewVisible: false,
+      videoPreviewUrl: '',
+      fileList: [],
+      // 批量添加相关
+      batchAddVisible: false,
+      batchLoading: false,
+      videoList: [],
+
+      // 批量修改相关
+      batchUpdateVisible: false,
+      batchUpdateLoading: false,
+      batchUpdateForm: {
+        typeId: null,
+        typeSubId: null,
+        ids: [],
+      },
+
+      // 批量上传相关
+      showUpload: false,
+      batchUploadForm: {
+        typeId: null,
+        typeSubId: null,
+        projectIds: [],
+        files: [],
+        displayType: 'landscape'
+      },
+      batchFileList: [],
+
+      // 弹窗选择项目相关
+      projectDialogVisible: false,
+      projectQueryParams: {
+        pageNum: 1,
+        pageSize: 10,
+        questionType: null,
+        questionSubType: null,
+        title: null
+      },
+      projectLoading: false,
+      projectListTotal: 0,
+      selectedProjectIds: [],
+      selectedType: 0,
+      currentRow: null,
+      // 分类树相关数据
+      categoryFilterText: '',
+      categoryTreeData: [],
+      add: false,
+      // 题目列表
+      projectListDialogVisible: false,
+      // 修改视频记录
+      batchEditDialog: {
+        title: '修改视频',
+        open: false,
+        form: {},
+        rules: {
+          resourceName: [
+            { required: true, message: "素材名称不能为空", trigger: "blur" }
+          ],
+          fileName: [
+            { required: true, message: "文件名称不能为空", trigger: "blur" }
+          ]
+        },
+      },
+      // 是否存在最小化窗口
+      hasMinimizableDialog: false,
+      // 新增上传相关状态
+      isUploading: false,
+      currentUploadProgress: {
+        total: 0,
+        line1: 0,
+        line2: 0,
+        line1Status: 'pending',
+        line2Status: 'pending'
+      },
+      // 上传任务队列
+      uploadQueue: [], // 上传队列
+      currentBatchSize: 2, // 每批上传数量
+      isProcessingBatch: false, // 是否正在处理批次
+      currentBatchIndex: 0, // 当前批次索引
+      uploadCancellationTokens: new Map(), // Store cancellation functions by video ID
+      currentProject: process.env.VUE_APP_PROJECT
+    }
+  },
+  watch: {
+    categoryFilterText(val) {
+      this.$refs.categoryTree.filter(val);
+    }
+  },
+  created() {
+    this.getTypeList();
+    this.getRootTypeList()
+    this.getList();
+  },
+  methods: {
+    /** 将数字索引转换为字母序号 (0->A, 1->B, 等) */
+    convertToLetter(index) {
+      // 确保索引是数字
+      let numIndex = parseInt(index, 10);
+      // 如果无法转换成数字,返回原始值
+      if (isNaN(numIndex)) {
+        return index;
+      }
+      // 处理负数情况
+      if (numIndex < 0) {
+        return index;
+      }
+      // 直接使用索引计算ASCII码(0对应A,1对应B,以此类推)
+      return String.fromCharCode(65 + numIndex); // 65 是大写字母 A 的ASCII码
+    },
+    /** 查询视频素材库列表 */
+    getList() {
+      this.loading = true;
+      this.queryParams.videoType = this.pageVideoType;
+      listVideoResource(this.queryParams).then(response => {
+        this.resourceList = response.rows;
+        this.total = response.total;
+        this.loading = false;
+      });
+    },
+    // 取消按钮
+    cancel() {
+      this.open = false;
+      this.reset();
+      this.resetForm("form");
+      this.batchUpdateVisible = false;
+      this.changeCateType(this.queryParams.typeId)
+      this.$refs.videoUpload.clearFiles()
+    },
+    // 表单重置
+    reset() {
+      // 初始化表单对象
+      this.form = {
+        id: null,
+        resourceName: null,
+        fileName: null,
+        thumbnail: null,
+        line1: null,
+        line2: null,
+        line3: null,
+        duration: null,
+        fileSize: null,
+        fileKey: null,
+        videoUrl: null,
+        typeId: null,
+        typeSubId: null,
+        projectIds: [],
+        displayType: 'landscape',
+        videoType: this.pageVideoType
+      };
+      // 重置表单验证状态
+      this.resetForm("form");
+      this.add = false
+    },
+    /** 搜索按钮操作 */
+    handleQuery() {
+      this.queryParams.pageNum = 1;
+      this.getList();
+    },
+    /** 重置按钮操作 */
+    resetQuery() {
+      this.resetForm("queryForm");
+      this.queryParams.videoType = this.pageVideoType;
+      this.handleQuery();
+    },
+    // 多选框选中数据
+    handleSelectionChange(selection) {
+      this.ids = selection.map(item => item.id)
+      this.single = selection.length!==1
+      this.multiple = !selection.length
+    },
+    /** 新增按钮操作 */
+    handleAdd() {
+      // 先清空文件列表
+      this.fileList = [];
+      this.projectShowList = [];
+
+      // 先重置表单
+      this.reset();
+      this.subTypeList = []
+
+      // 重置上传组件
+      if (this.$refs.videoUpload) {
+        this.$refs.videoUpload.clearFiles();
+      }
+
+      // 所有重置完成后再打开弹窗
+      this.open = true;
+      this.title = "添加视频素材库";
+    },
+    /** 修改按钮操作 */
+    handleUpdate(row) {
+      // 先清空文件列表
+      this.fileList = [];
+      this.projectShowList = [];
+
+      // 先重置表单
+      this.reset();
+      this.subTypeList = []
+
+      const id = row.id
+
+      // 获取数据并设置表单
+      getVideoResource(id).then(async response => {
+        this.form = response.data;
+        // 视频展示类型:旧数据可能为空,默认横屏
+        if (!this.form.displayType) {
+          this.form.displayType = 'landscape';
+        }
+        if (this.form.videoType === null || this.form.videoType === undefined) {
+          this.form.videoType = this.pageVideoType;
+        }
+        await this.changeCateType(this.form.typeId)
+
+        // 处理projectIds,确保是数组格式
+        if (this.form.projectIds && typeof this.form.projectIds === 'string') {
+          this.form.projectIds = this.form.projectIds.split(',').map(id => parseInt(id));
+        } else if (!this.form.projectIds) {
+          this.form.projectIds = [];
+        }
+
+        // 如果存在关联项目,获取项目详情用于回显
+        if (this.form.projectIds && this.form.projectIds.length > 0) {
+          // 加载项目列表信息用于回显
+          await getByIds({ids: this.form.projectIds.join(',')}).then(reponse => {
+            this.projectShowList = reponse.data
+          });
+        }
+
+        // 如果存在视频URL,设置fileList
+        if (this.form.videoUrl) {
+          this.fileList = [{
+            name: this.form.fileName || '视频文件',
+            url: this.form.videoUrl,
+            thumbnail: this.form.thumbnail,
+            status: 'success' // 设置为成功状态
+          }];
+        }
+
+        // 所有设置完成后再打开弹窗
+        this.open = true;
+        this.title = "修改视频素材库";
+      });
+    },
+    /** 提交按钮 */
+    submitForm() {
+      this.$refs["form"].validate(valid => {
+        if (valid) {
+          if (this.add){
+            this.$message.warning("请勿重复提交")
+            return
+          }
+          this.add = true
+
+          const params = Object.assign({}, this.form);
+          console.log("提交素材表单参数",this.form)
+          params.videoType = this.pageVideoType;
+          params.projectIds = this.form.projectIds.join(',');
+          if (this.form.id != null) {
+            updateVideoResource(params).then(response => {
+              if (response.code === 200) {
+                this.msgSuccess("修改成功");
+                this.open = false;
+                this.getList();
+              }
+            })
+          } else {
+            addVideoResource(params).then(response => {
+              if (response.code === 200) {
+                this.msgSuccess("新增成功");
+                this.open = false;
+                this.getList();
+              }
+            })
+          }
+        }
+      });
+    },
+    /** 删除按钮操作 */
+    handleDelete(row) {
+      const ids = row.id || this.ids;
+      this.$confirm('是否确认删除视频素材库编号为"' + ids + '"的数据项?', "警告", {
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "warning"
+      }).then(function() {
+        return deleteVideoResource(ids);
+      }).then(() => {
+        this.getList();
+        this.msgSuccess("删除成功");
+      }).catch(function() {});
+    },
+    /** 查询视频分类列表 */
+    getTypeList() {
+      listUserCourseCategory().then(response => {
+        this.typeList = response.data;
+      });
+    },
+    getRootTypeList() {
+      getCatePidList().then(response => {
+        this.rootTypeList = response.data;
+        this.originalRootTypeList = [...response.data]; // 保存原始数据
+        this.filteredRootTypeList = [...response.data]; // 初始化过滤列表
+      });
+    },
+    /** 过滤分类选项 */
+    filterTypeOptions(query) {
+      if (!query) {
+        this.filteredRootTypeList = [...this.originalRootTypeList];
+        return;
+      }
+
+      this.filteredRootTypeList = this.originalRootTypeList.filter(item => {
+        return item.dictLabel.toLowerCase().includes(query.toLowerCase()) ||
+          item.dictValue.toString().includes(query);
+      });
+    },
+    /** 选择后重置过滤 */
+    onTypeSelect(value) {
+      // 确保选中后恢复完整列表
+      this.filteredRootTypeList = [...this.originalRootTypeList];
+      // 触发 change 事件
+      this.changeCateType(value, 1);
+    },
+    /** 重置过滤 */
+    resetFilter() {
+      this.filteredRootTypeList = [...this.originalRootTypeList];
+    },
+
+    /** 清除过滤 */
+    clearFilter() {
+      this.$nextTick(() => {
+        if (!this.queryParams.typeId) {
+          this.filteredRootTypeList = [...this.originalRootTypeList];
+        }
+      });
+    },
+    /** 处理下拉框显示状态变化 */
+    handleSelectVisibleChange(visible) {
+      if (!visible) {
+        // 下拉框关闭时恢复完整列表
+        this.filteredRootTypeList = [...this.originalRootTypeList];
+      } else {
+        // 下拉框打开时也确保列表完整
+        this.filteredRootTypeList = [...this.originalRootTypeList];
+      }
+    },
+    async changeCateType(val, type) {
+      if (type === 1) {
+        this.queryParams.typeSubId = null
+      }
+      if (type === 2) {
+        this.form.typeSubId = null
+      }
+      if (type === 3) {
+        this.batchUploadForm.typeSubId = null
+        if (this.currentProject === 'myhk') {
+          this.batchUploadForm.projectIds = []
+        }
+      }
+      this.subTypeList = []
+      if (!val) {
+        return
+      }
+      await getCateListByPid(val).then(response => {
+        this.subTypeList = response.data
+      })
+    },
+    changeSubType(val) {
+      if (this.currentProject !== 'myhk') {
+        return
+      }
+      this.projectShowList = []
+      this.batchUploadForm.projectIds = []
+      listCourseQuestionBank({questionSubType: val}).then(async response => {
+        const projectIds = response.rows.map(item => item.id)
+
+        // 如果存在关联项目,获取项目详情用于回显
+        if (projectIds && projectIds.length > 0) {
+          // 加载项目列表信息用于回显
+          await getByIds({ids: projectIds.join(',')}).then(reponse => {
+            this.projectShowList = reponse.data
+            this.batchUploadForm.projectIds = projectIds
+          });
+        }
+      })
+    },
+    /** 预览视频 */
+    handleVideoPreview(url) {
+      this.videoPreviewVisible = true;
+      this.videoPreviewUrl = url || this.form.videoUrl;
+    },
+
+    /** 格式化视频时长 */
+    formatDuration(seconds) {
+      if (!seconds) return '00:00';
+
+      const minutes = Math.floor(seconds / 60);
+      const remainingSeconds = seconds % 60;
+
+      return `${minutes.toString().padStart(2, '0')}:${remainingSeconds.toString().padStart(2, '0')}`;
+    },
+    /** 移除视频 */
+    handleRemove(file) {
+      this.fileList.splice(this.fileList.indexOf(file), 1);
+      this.form.videoUrl = '';
+      this.form.thumbnail = '';
+      this.form.duration = 0;
+      this.form.fileSize = 0;
+      this.form.fileName = '';
+      this.form.line1 = '';
+      this.form.line_2 = '';
+      this.form.line_3 = '';
+      this.uploadType = null
+      this.fileSize = null
+      this.fileKey = null
+    },
+    //获取第一帧封面
+    async getFirstThumbnail(file, form){
+      try {
+        //截取小文件
+        // 打印原始文件名和大小
+        console.log("原始文件名:", file.name);
+        console.log("原始文件大小:", file.size, "bytes");
+        console.log("原始文件类型:", file.type);
+
+        // 截取小文件
+        const clippedBlob = await this.clipVideoFirstTwoSeconds(file);
+        console.log("clippedBlob:::::::::", clippedBlob);
+        console.log("截取后的Blob大小:", clippedBlob.size, "bytes");
+        console.log("截取后的Blob类型:", clippedBlob.type);
+
+        const clippedFile = new File([clippedBlob], 'clipped_video.mp4', {
+          type: 'video/mp4',
+          lastModified: Date.now()
+        });
+        // 3. 调用接口获取封面
+        const response = await getThumbnail(clippedFile);
+        console.log("获取封面请求---------------》",response)
+        form.thumbnail = response.url;
+      } catch (error) {
+        console.error('获取封面失败:', error);
+      }
+    },
+
+    //截取大文件视频
+    async clipVideoFirstTwoSeconds(file) {
+      return new Promise((resolve, reject) => {
+        const video = document.createElement('video');
+        video.src = URL.createObjectURL(file);
+        video.muted = true;
+        video.playsInline = true;
+
+        video.onloadedmetadata = () => {
+          video.currentTime = 0; // 定位到第一帧
+          video.onseeked = () => {
+            const canvas = document.createElement('canvas');
+            canvas.width = video.videoWidth;
+            canvas.height = video.videoHeight;
+            const ctx = canvas.getContext('2d');
+            ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
+
+            canvas.toBlob(
+              (blob) => {
+                URL.revokeObjectURL(video.src);
+                resolve(blob); // 返回 JPEG Blob
+              },
+              'image/jpeg',
+              0.8 // 质量
+            );
+          };
+        };
+
+        video.onerror = () => {
+          URL.revokeObjectURL(video.src);
+          reject(new Error('视频加载失败'));
+        };
+      });
+    },
+    //上传腾讯云Pcdn
+    async uploadVideoToTxPcdn(file, form, onProgress) {
+      try {
+        // 更新线路1状态为上传中
+        this.updateUploadProgress('line1Status', 'uploading');
+
+        const data = await uploadObject(file, (progress) => {
+          const progressPercent = Math.floor(progress.percent * 100);
+          this.updateUploadProgress('line1', progressPercent);
+          const progressEvent = { percent: progressPercent, loaded: progress.loaded, total: progress.total, lengthComputable: true };
+          onProgress(progressEvent);
+        }, 1, (uploadInfo) => {
+          if (form.tempId) {
+            const tokens = this.uploadCancellationTokens.get(form.tempId) || {};
+            tokens.cos = uploadInfo.cancel;
+            this.uploadCancellationTokens.set(form.tempId, tokens);
+          }
+        });
+
+        let line_1 = `${process.env.VUE_APP_VIDEO_LINE_1}${data.urlPath}`;
+        form.fileKey = data.urlPath.substring(1);
+        form.videoUrl = line_1;
+        form.line1 = line_1;
+
+        // 更新线路1状态为成功
+        this.updateUploadProgress('line1Status', 'success');
+        this.updateUploadProgress('line1', 100);
+
+        this.$message.success("线路一上传成功");
+        return { success: true, url: line_1 };
+      } catch (error) {
+        // 更新线路1状态为失败
+        this.updateUploadProgress('line1Status', 'failed');
+        this.$message.error("线路一上传失败");
+        return { success: false, error: error.message };
+      }
+    },
+    //上传华为云Obs
+    // async uploadVideoToHwObs(file, form, onProgress) {
+    //   try {
+    //     // 更新线路2状态为上传中
+    //     this.updateUploadProgress('line2Status', 'uploading');
+    //
+    //     const data = await uploadToOBS(file, (progress) => {
+    //       const progressPercent = Math.floor(progress);
+    //       this.updateUploadProgress('line2', progressPercent);
+    //       const progressEvent = { percent: progressPercent, loaded: progress, total: progress, lengthComputable: true };
+    //       onProgress(progressEvent);
+    //     }, 1, (uploadInfo) => {
+    //       if (form.tempId) {
+    //         const tokens = this.uploadCancellationTokens.get(form.tempId) || {};
+    //         tokens.obs = uploadInfo.cancel;
+    //         this.uploadCancellationTokens.set(form.tempId, tokens);
+    //       }
+    //     });
+    //
+    //     form.line2 = `${process.env.VUE_APP_VIDEO_LINE_2}/${data.urlPath}`;
+    //
+    //     // 更新线路2状态为成功
+    //     this.updateUploadProgress('line2Status', 'success');
+    //     this.updateUploadProgress('line2', 100);
+    //
+    //     this.$message.success("线路二上传成功");
+    //     return { success: true, url: form.line2 };
+    //   } catch (error) {
+    //     // 更新线路2状态为失败
+    //     this.updateUploadProgress('line2Status', 'failed');
+    //     this.$message.error("线路二上传失败");
+    //     return { success: false, error: error.message };
+    //   }
+    // },
+    //上传火山云
+    async uploadVideoToHsy(file, form, onProgress) {
+      try {
+        this.updateUploadProgress('line2Status', 'uploading');
+
+        const data = await uploadToHSY(
+          file,
+          (progress) => {
+            // 火山云的进度是小数0-1
+            if (typeof progress.percent === 'number') {
+              const percent = Math.floor(progress.percent * 100);
+
+              // 更新线路2进度
+              this.updateUploadProgress('line2', percent);
+
+              // 对外统一 progress 事件(模拟 xhr)
+              onProgress?.({
+                percent,
+                loaded: percent,
+                total: 100,
+                lengthComputable: true
+              });
+            }
+
+            // 状态同步(成功 / 失败)
+            if (progress.status === 'success') {
+              this.updateUploadProgress('line2Status', 'success');
+              this.updateUploadProgress('line2', 100);
+            }
+
+            if (progress.status === 'failed') {
+              this.updateUploadProgress('line2Status', 'failed');
+            }
+          },
+          1,
+          (uploadInfo) => {
+            if (form.tempId) {
+              const tokens = this.uploadCancellationTokens.get(form.tempId) || {};
+              tokens.hsy = uploadInfo.cancel;
+              this.uploadCancellationTokens.set(form.tempId, tokens);
+            }
+          }
+        );
+        console.log("上传火山云返回参数",data)
+
+        form.line2 = `${process.env.VUE_APP_VIDEO_URL}/${data.SourceInfo.FileName}`;
+        this.form.hsyVid = data.Vid
+        this.form.hsyVodUrl = process.env.VUE_APP_VIDEO_URL+"/"+data.SourceInfo.FileName
+        console.log("this.form",this.form)
+        this.$message.success('线路二上传成功');
+        return { success: true, url: form.line2 };
+
+      } catch (error) {
+        this.updateUploadProgress('line2Status', 'failed');
+        this.$message.error('线路二上传失败');
+        return { success: false, error: error?.message || 'upload failed' };
+      }
+    },
+    // 更新上传进度的辅助方法
+    updateUploadProgress(key, value) {
+      this.currentUploadProgress[key] = value;
+
+      // 计算总进度:只有两个线路都成功才算100%
+      if (this.currentUploadProgress.line1Status === 'success' && this.currentUploadProgress.line2Status === 'success') {
+        this.currentUploadProgress.total = 100;
+      } else if (this.currentUploadProgress.line1Status === 'failed' || this.currentUploadProgress.line2Status === 'failed') {
+        // 如果任一线路失败,总进度保持当前状态
+        this.currentUploadProgress.total = Math.min(
+          (this.currentUploadProgress.line1 + this.currentUploadProgress.line2) / 2,
+          99 // 失败时最多99%
+        );
+      } else {
+        // 正常上传中,计算平均进度
+        this.currentUploadProgress.total = Math.min(
+          (this.currentUploadProgress.line1 + this.currentUploadProgress.line2) / 2,
+          99 // 上传中最多99%,只有都成功才100%
+        );
+      }
+    },
+    // 上传视频
+    async videoUpload(options) {
+      this.isUploading = true;
+      this.form.uploadStatus = 'uploading';
+      this.form.tempId = Math.random().toString(36).substring(2, 15);
+      const file = options.file;
+      this.getMediaDuration(file);
+      this.currentUploadProgress = { total: 0, line1: 0, line2: 0, line1Status: 'pending', line2Status: 'pending' };
+
+      try {
+        await this.getFirstThumbnail(file, this.form);
+        const [line1Result, line2Result] = await Promise.allSettled([
+          this.uploadVideoToTxPcdn(file, this.form, options.onProgress),
+          //this.uploadVideoToHwObs(file, this.form, options.onProgress)
+          this.uploadVideoToHsy(file, this.form, options.onProgress)
+        ]);
+
+        const line1Success = line1Result.status === 'fulfilled' && line1Result.value.success;
+        const line2Success = line2Result.status === 'fulfilled' && line2Result.value.success;
+
+        if (line1Success && line2Success) {
+          this.form.uploadStatus = 'success';
+          this.form.uploadProgress = { total: 100, line1: 100, line2: 100, line1Status: 'success', line2Status: 'success' };
+          this.currentUploadProgress.total = 100;
+          this.$message.success("视频上传完成!两个线路都上传成功");
+        } else {
+          this.form.uploadStatus = 'failed';
+          this.form.uploadProgress = {
+            total: this.currentUploadProgress.total,
+            line1: this.currentUploadProgress.line1,
+            line2: this.currentUploadProgress.line2,
+            line1Status: this.currentUploadProgress.line1Status,
+            line2Status: this.currentUploadProgress.line2Status
+          };
+          const failedLines = [];
+          if (!line1Success) failedLines.push('线路1');
+          if (!line2Success) failedLines.push('线路2');
+          this.$message.error(`视频上传失败!${failedLines.join('、')} 上传失败,请重试`);
+        }
+        this.form.fileName = file.name;
+        this.form.fileSize = file.size;
+      } catch (error) {
+        this.form.uploadStatus = 'failed';
+        this.$message.error("视频上传过程中发生错误,请重试");
+      } finally {
+        this.isUploading = false;
+        if (this.form.tempId) {
+          this.uploadCancellationTokens.delete(this.form.tempId);
+        }
+      }
+    },
+    // 获取媒体文件时长
+    getMediaDuration(file) {
+      // 处理视频
+      const video = document.createElement('video');
+      video.preload = 'metadata';
+      video.onloadedmetadata = () => {
+        this.form.duration = Math.round(video.duration);
+      };
+      video.src = URL.createObjectURL(file);
+    },
+    handleFileChange(file, files) {
+      // 保留最后一个文件
+      this.fileList = files.slice(-1);
+    },
+    /** 批量新增 */
+    handleBatchAdd() {
+      this.batchAddVisible = true;
+      this.add =false
+      this.videoList = []; // 清空之前的视频列表
+    },
+    /** 批量修改 */
+    handleBatchUpdate(){
+      if (this.ids.length === 0) {
+        this.$message.warning("请至少选择一条数据");
+        return;
+      }
+      this.resetForm("form");
+      this.batchUpdateForm.ids = this.ids; // 将选中的ID传递给批量修改表单
+      this.batchUpdateVisible = true;
+    },
+    cancelBeforeBatch(done, cancel) {
+      if (!this.videoList || this.videoList.length === 0) {
+        done()
+        return
+      }
+      this.$confirm('关闭窗口视频需要重新上传,确定关闭吗?', '提示', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning'
+      }).then(() => {
+        done()
+      }).catch(() => {
+        cancel()
+      });
+    },
+    /** 取消批量 */
+    cancelBatch() {
+      if (!this.videoList || this.videoList.length === 0) {
+        this.batchAddVisible = false
+        this.batchUpdateVisible = false
+
+        return
+      }
+      this.$confirm('关闭窗口视频需要重新上传,确定关闭吗?', '提示', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning'
+      }).then(() => {
+        this.batchAddVisible = false
+        this.batchUpdateVisible = false
+        this.changeCateType(this.queryParams.typeId)
+      }).catch(() => { });
+    },
+    /** 批量修改 */
+    submitBatchUpdate() {
+      console.log("批量上传表单提交参数",this.form)
+      this.$refs["form"].validate(valid => {
+        if (valid) {
+          if (this.batchUpdateForm.ids.length === 0) {
+            this.$message.warning("未选择任何数据");
+            return;
+          }
+          this.$confirm('是否确认修改视频素材库编号为"' + this.batchUpdateForm.ids.join(',') + '"的数据项?', "警告", {
+            confirmButtonText: "确定",
+            cancelButtonText: "取消",
+            type: "warning"
+          }).then(() => {
+            // 构造正确的参数格式
+            const params = new URLSearchParams();
+            params.append('typeId', this.batchUpdateForm.typeId || '');
+            params.append('typeSubId', this.batchUpdateForm.typeSubId || '');
+            params.append('ids', this.batchUpdateForm.ids.join(','));
+            params.append('videoType', String(this.pageVideoType));
+            return batchUpdateVideoResource(params);
+          }).then(() => {
+            this.getList();
+            this.batchUpdateVisible = false;
+            this.msgSuccess("修改成功");
+          }).catch(() => {});
+        }
+      });
+    },
+    /** 提交批量添加 */
+    submitBatchAdd() {
+      if (this.videoList.length === 0) {
+        this.$message.warning("请选择视频");
+        return;
+      }
+
+      // 检查是否所有选中的视频都已上传完成
+      console.log("videoList",this.videoList)
+      const incompleteVideos = this.videoList.filter(item => (item.progress || 0) < 100);
+      if (incompleteVideos.length > 0) {
+        this.$message.warning('有未完成上传的视频,请先完成上传');
+        return;
+      }
+
+      if (this.add){
+        this.$message.warning("请勿重复提交")
+        return
+      }
+      this.add = true
+
+      // 调用批量添加API
+      const videoList = JSON.parse(JSON.stringify(this.videoList));
+      videoList.forEach(item => {
+        item.projectIds = item.projectIds.join(",");
+        item.videoType = this.pageVideoType;
+      });
+      batchAddVideoResource(videoList).then(response => {
+        if (response.code === 200) {
+          this.$message.success('批量添加成功');
+          this.batchAddVisible = false;
+          this.getList();
+        }
+      })
+    },
+    /** 获取分类名称 */
+    getTypeName(typeId) {
+      const type = this.typeList.find(item => item.cateId === typeId);
+      return type ? type.cateName : '';
+    },
+    /** 编辑视频信息 */
+    handleEditVideo(row) {
+      if (row.progress !== 100) {
+        this.$message.warning('请等待上传完成后再编辑');
+        return;
+      }
+      this.batchEditDialog.form = Object.assign({}, row)
+      this.changeCateType(row.typeId)
+      this.batchEditDialog.open = true
+    },
+    batchEditCancel() {
+      this.resetForm('batchEditDialogForm')
+      this.batchEditDialog.open = false
+    },
+    batchEditSubmitForm() {
+      let temp = this.videoList.find(item => item.tempId === this.batchEditDialog.form.tempId)
+      Object.assign(temp, this.batchEditDialog.form)
+      this.batchEditDialog.open = false
+    },
+    handleDeleteVideo(row) {
+      if (row.uploadStatus === 'uploading') {
+        this.$confirm('该视频正在上传中,确认要取消上传并删除吗?', '取消上传', {
+          confirmButtonText: '确定取消',
+          cancelButtonText: '继续上传',
+          type: 'warning'
+        }).then(() => {
+          // Cancel the upload and remove from list
+          this.cancelVideoUpload(row);
+        }).catch(() => {
+          // User chose to continue uploading
+          this.$message.info('继续上传该视频');
+        });
+      } else {
+        // Original confirmation for non-uploading videos
+        this.$confirm('确认要从列表中删除该视频吗?', '提示', {
+          confirmButtonText: '确定',
+          cancelButtonText: '取消',
+          type: 'warning'
+        }).then(() => {
+          this.removeVideoFromList(row);
+        }).catch(() => {
+          // 取消删除
+        });
+      }
+    },
+
+    cancelVideoUpload(row) {
+      const cancellationTokens = this.uploadCancellationTokens.get(row.tempId);
+      if (cancellationTokens) {
+        // Cancel COS upload if exists
+        if (cancellationTokens.cos) {
+          try {
+            cancellationTokens.cos();
+            console.log('COS upload cancelled for video:', row.tempId);
+          } catch (error) {
+            console.error('Error cancelling COS upload:', error);
+          }
+        }
+
+        // Cancel OBS upload if exists
+        if (cancellationTokens.obs) {
+          try {
+            cancellationTokens.obs();
+            console.log('OBS upload cancelled for video:', row.tempId);
+          } catch (error) {
+            console.error('Error cancelling OBS upload:', error);
+          }
+        }
+
+        // Remove cancellation tokens
+        this.uploadCancellationTokens.delete(row.tempId);
+      }
+
+      const videoIndex = this.videoList.findIndex(item => item.tempId === row.tempId);
+      if (videoIndex !== -1) {
+        this.videoList[videoIndex].uploadStatus = 'cancelled';
+        this.videoList.splice(videoIndex, 1);
+      }
+
+      const queueIndex = this.uploadQueue.findIndex(item => item.tempId === row.tempId);
+      if (queueIndex !== -1) {
+        this.uploadQueue.splice(queueIndex, 1);
+        this.updateQueuePositions();
+      }
+
+      this.$message.success('已取消视频上传并从列表中移除');
+    },
+
+    removeVideoFromList(row) {
+      const videoIndex = this.videoList.findIndex(item => item.tempId === row.tempId);
+      if (videoIndex !== -1) {
+        this.videoList.splice(videoIndex, 1);
+      }
+
+      // Remove from upload queue if it's still queued
+      const queueIndex = this.uploadQueue.findIndex(item => item.tempId === row.tempId);
+      if (queueIndex !== -1) {
+        this.uploadQueue.splice(queueIndex, 1);
+        this.updateQueuePositions();
+        this.$message.success('已从上传队列中移除该视频');
+      } else {
+        this.$message.success('已从列表中移除该视频');
+      }
+    },
+    /** 删除视频 */
+    // handleDeleteVideo(row) {
+    //   this.$confirm('确认要从列表中删除该视频吗?', '提示', {
+    //     confirmButtonText: '确定',
+    //     cancelButtonText: '取消',
+    //     type: 'warning'
+    //   }).then(() => {
+    //     const videoIndex = this.videoList.findIndex(item => item.tempId === row.tempId);
+    //     if (videoIndex !== -1) {
+    //       this.videoList.splice(videoIndex, 1);
+    //     }
+
+    //     // Remove from upload queue if it's still queued
+    //     const queueIndex = this.uploadQueue.findIndex(item => item.tempId === row.tempId);
+    //     if (queueIndex !== -1) {
+    //       this.uploadQueue.splice(queueIndex, 1);
+    //       this.updateQueuePositions();
+    //       this.$message.success('已从上传队列中移除该视频');
+    //     } else {
+    //       this.$message.success('已从列表中移除该视频');
+    //     }
+    //   }).catch(() => {
+    //     // 取消删除
+    //   });
+    // },
+    /** 显示上传面板 */
+    showUploadPanel() {
+      this.showUpload = true;
+
+      if (this.currentProject === 'myhk') {
+        this.batchUploadForm = {
+          ...this.batchUploadForm,
+          files: [],
+          videoType: this.pageVideoType
+        };
+      } else {
+        this.batchUploadForm = {
+          typeId: null,
+          typeSubId: null,
+          projectIds: [],
+          files: [],
+          displayType: 'landscape',
+          videoType: this.pageVideoType
+        };
+        this.subTypeList = []
+      }
+
+      this.batchFileList = [];
+      if (this.$refs.batchVideoUpload) {
+        this.$refs.batchVideoUpload.clearFiles();
+      }
+    },
+    /** 批量文件变更 */
+    handleBatchFileChange(file, fileList) {
+      this.batchFileList = fileList;
+    },
+    /** 批量上传视频 */
+    async batchVideoUpload(options) {
+      const file = options.file;
+
+      const tempVideo = {
+        tempId: Math.random().toString(36).substring(2, 15),
+        resourceName: file.name.substring(0, file.name.lastIndexOf('.')),
+        fileName: file.name,
+        thumbnail: null,
+        line1: null,
+        line2: null,
+        line3: null,
+        duration: 0,
+        fileSize: file.size,
+        fileKey: null,
+        videoUrl: null,
+        typeId: this.batchUploadForm.typeId,
+        typeSubId: this.batchUploadForm.typeSubId,
+        projectIds: this.batchUploadForm.projectIds,
+        displayType: this.batchUploadForm.displayType || 'landscape',
+        videoType: this.pageVideoType,
+        progress: 0,
+        uploadStatus: 'queued', // Set initial status to queued
+        uploadDetails: {
+          line1: 0,
+          line2: 0,
+          line1Status: 'pending',
+          line2Status: 'pending'
+        },
+        file: file,
+        queuePosition: this.uploadQueue.length + 1 // Track queue position
+      };
+
+      this.uploadQueue.push(tempVideo);
+      this.videoList.unshift(tempVideo);
+
+      // 获取视频时长
+      const video = document.createElement('video');
+      video.preload = 'metadata';
+      video.onloadedmetadata = () => {
+        const index = this.videoList.findIndex(item => item.tempId === tempVideo.tempId);
+        if (index !== -1) {
+          tempVideo.duration = Math.round(video.duration);
+        }
+      };
+      video.src = URL.createObjectURL(file);
+
+      // 关闭上传弹窗
+      this.showUpload = false;
+
+      if (!this.isProcessingBatch) {
+        this.processUploadQueue();
+      }
+
+      if (this.$refs.batchVideoUpload) {
+        this.$refs.batchVideoUpload.clearFiles();
+      }
+    },
+
+    async processUploadQueue() {
+      if (this.isProcessingBatch || this.uploadQueue.length === 0) {
+        return;
+      }
+
+      this.isUploading = true;
+      this.isProcessingBatch = true;
+
+      while (this.uploadQueue.length > 0) {
+        // Get next batch (up to 5 videos)
+        const currentBatch = this.uploadQueue.splice(0, this.currentBatchSize);
+
+        // Update status for current batch
+        currentBatch.forEach(video => {
+          const index = this.videoList.findIndex(item => item.tempId === video.tempId);
+          if (index !== -1) {
+            this.videoList[index].uploadStatus = 'uploading';
+          }
+        });
+
+        // Process current batch in parallel
+        const batchPromises = currentBatch.map(video => this.uploadSingleVideo(video));
+
+        try {
+          await Promise.allSettled(batchPromises);
+          this.$message.success(`批次上传完成,已处理 ${currentBatch.length} 个视频`);
+        } catch (error) {
+          this.$message.error(`批次上传过程中发生错误: ${error.message}`);
+        }
+
+        // Update queue positions for remaining videos
+        this.updateQueuePositions();
+
+        // Small delay between batches
+        if (this.uploadQueue.length > 0) {
+          await new Promise(resolve => setTimeout(resolve, 1000));
+        }
+      }
+
+      this.isProcessingBatch = false;
+      this.isUploading = false;
+      this.$message.success('所有视频上传队列处理完成!');
+      console.log("批量上传form",this.form)
+    },
+
+    async uploadSingleVideo(tempVideo) {
+      try {
+        // 获取封面
+        await this.getFirstThumbnail(tempVideo.file, tempVideo);
+
+        // 并行上传到两个服务器
+        const [line1Result, line2Result] = await Promise.allSettled([
+          this.uploadVideoToTxPcdnBatch(tempVideo.file, tempVideo),
+          // this.uploadVideoToHwObsBatch(tempVideo.file, tempVideo)
+          this.uploadVideoToHSYBatch(tempVideo.file, tempVideo),
+
+        ]);
+
+        // 检查上传结果
+        const line1Success = line1Result.status === 'fulfilled' && line1Result.value.success;
+        const line2Success = line2Result.status === 'fulfilled' && line2Result.value.success;
+
+        const index = this.videoList.findIndex(item => item.tempId === tempVideo.tempId);
+        if (index !== -1) {
+          if (line1Success && line2Success) {
+            this.videoList[index].progress = 100;
+            this.videoList[index].uploadStatus = 'success';
+            this.videoList[index].uploadDetails.line1Status = 'success';
+            this.videoList[index].uploadDetails.line2Status = 'success';
+            this.videoList[index].uploadDetails.line1 = 100;
+            this.videoList[index].uploadDetails.line2 = 100;
+          } else {
+            this.videoList[index].uploadStatus = 'failed';
+            this.videoList[index].uploadDetails.line1Status = line1Success ? 'success' : 'failed';
+            this.videoList[index].uploadDetails.line2Status = line2Success ? 'success' : 'failed';
+            this.updateBatchProgress(index);
+          }
+        }
+
+        return { success: line1Success && line2Success, tempVideo };
+      } catch (error) {
+        const index = this.videoList.findIndex(item => item.tempId === tempVideo.tempId);
+        if (index !== -1) {
+          this.videoList[index].uploadStatus = 'failed';
+        }
+        throw error;
+      }
+    },
+
+
+    /** 复制视频链接到剪贴板 */
+    copy(url) {
+      // 创建一个临时的input元素
+      const input = document.createElement('input');
+      // 设置input的值为要复制的视频链接
+      input.value = url;
+      // 将input添加到DOM中
+      document.body.appendChild(input);
+      // 选中input的值
+      input.select();
+      // 执行复制命令
+      document.execCommand('copy');
+      // 从DOM中移除input元素
+      document.body.removeChild(input);
+      // 提示用户复制成功
+      this.$message({
+        message: '已复制到剪贴板',
+        type: 'success',
+        duration: 1500
+      });
+    },
+    getProjectList() {
+      this.projectLoading = true
+      listCourseQuestionBank(this.projectQueryParams).then(response => {
+        this.projectList = response.rows;
+        this.projectListTotal = response.total;
+
+        // 如果存在已选择的项目,预选中表格中对应的行
+        this.$nextTick(() => {
+          // 获取表格组件实例
+          const projectTable = this.$refs.projectTable;
+          if (projectTable) {
+            if (this.selectedProjectIds.length > 0) {
+              // 遍历项目列表,找到匹配的ID并选中对应行
+              const selectedIds = this.selectedProjectIds;
+              this.projectList.forEach(row => {
+                if (selectedIds.includes(row.id)) {
+                  projectTable.toggleRowSelection(row, true);
+                }
+              });
+            }
+          }
+        });
+        this.projectLoading = false
+      });
+    },
+    /** 打开项目选择弹窗 */
+    openProjectDialog(projectIds, type) {
+      this.$nextTick(() => {
+        if (this.$refs.customSelect) {
+          this.$refs.customSelect.blur();
+        }
+      });
+      // 重置查询参数
+      this.projectQueryParams = {
+        pageNum: 1,
+        pageSize: 10,
+        questionType: null,
+        title: null
+      };
+
+      this.selectedType = type
+
+      // 设置选中的项目IDs
+      if (projectIds) {
+        if (typeof projectIds === 'string') {
+          this.selectedProjectIds = projectIds.split(',').map(id => parseInt(id));
+        } else if (Array.isArray(projectIds)) {
+          this.selectedProjectIds = [...projectIds];
+        } else if (typeof projectIds === 'number') {
+          this.selectedProjectIds = [projectIds];
+        }
+      } else {
+        this.selectedProjectIds = [];
+      }
+
+      // 显示弹窗
+      this.projectDialogVisible = true;
+
+      // 加载分类树数据
+      this.initCategoryTree();
+
+      // 加载项目列表
+      this.getProjectList();
+    },
+
+    /** 确认选择项目 */
+    confirmSelectProject() {
+      // 更新表单中的项目ID
+      if (this.selectedType === 0) {
+        this.form.projectIds = this.selectedProjectIds;
+      }
+
+      else if (this.selectedType === 1) {
+        this.batchUploadForm.projectIds = this.selectedProjectIds;
+      }
+
+      else if (this.selectedType === 2) {
+        this.batchEditDialog.form.projectIds = this.selectedProjectIds;
+      }
+
+      else if (this.selectedType === 3) {
+        const params = {
+          id: this.currentRow.id,
+          projectIds: this.selectedProjectIds.join(","),
+          videoType: this.pageVideoType
+        }
+        updateVideoResource(params).then(response => {
+          if (response.code === 200) {
+            this.msgSuccess("修改成功");
+            this.open = false;
+            this.getList();
+          }
+        });
+      }
+
+      else if (this.selectedType === 4) {
+        this.currentRow.projectIds = this.selectedProjectIds
+      }
+
+      this.projectDialogVisible = false;
+    },
+
+    /** 取消选择项目 */
+    cancelSelectProject() {
+      const projectTable = this.$refs.projectTable;
+      if (projectTable) {
+        // 清空表格数据
+        projectTable.clearSelection();
+      }
+      this.projectDialogVisible = false;
+    },
+    handleProjectSelect(selection) {
+      this.selectedProjectIds = selection.map(item => item.id);
+      selection.forEach(item => {
+        // 检查是否已存在该项目,不存在则添加
+        if (!this.projectShowList.some(p => p.id === item.id)) {
+          this.projectShowList.push(item);
+        }
+      });
+    },
+    /** 项目列表页码变更 */
+    handleProjectPageChange(page) {
+      this.projectQueryParams.pageNum = page;
+      this.getProjectList();
+    },
+
+    /** 项目列表每页条数变更 */
+    handleProjectPageSizeChange(size) {
+      this.projectQueryParams.pageNum = 1;
+      this.projectQueryParams.pageSize = size;
+      this.getProjectList();
+    },
+
+    /** 处理查看项目 */
+    handleViewProject(row, type) {
+      // 保存当前选择的行,以便后续操作
+      this.currentRow = row;
+
+      // 设置form对象的projectIds为当前行的项目IDs
+      this.projectShowList = [];
+
+      if (!row.projectIds || row.projectIds.length === 0) {
+        this.openProjectDialog(null, type)
+        return;
+      }
+
+      let projectIds = row.projectIds
+      if (Array.isArray(row.projectIds)) {
+        projectIds = projectIds.join(',');
+      }
+
+      getByIds({ids: projectIds}).then(response => {
+        this.projectShowList = response.data;
+      })
+
+      // 打开弹窗展示列表
+      this.projectListDialogVisible = true;
+    },
+    /** 初始化分类树 */
+    initCategoryTree() {
+      // 获取分类列表
+      listUserCourseCategory().then(response => {
+        const treeDate = this.handleTree(response.data, "cateId", "pid");
+        this.categoryTreeData = [{
+          cateId: 0,
+          cateName: '全部',
+          children: treeDate
+        }];
+      });
+    },
+    filterNode(value, data) {
+      if (!value) return true;
+      return data.cateName.indexOf(value) !== -1;
+    },
+    /** 处理分类点击 */
+    handleCategoryClick(data, node) {
+      // 更新查询参数
+      this.projectQueryParams.pageNum = 1;
+
+      // 如果是全部分类,则清空分类过滤
+      if (node.level === 1) {
+        this.projectQueryParams.questionType = null;
+        this.projectQueryParams.questionSubType = null;
+      }
+      else if (node.level === 2) {
+        this.projectQueryParams.questionType = data.cateId;
+        this.projectQueryParams.questionSubType = null;
+      }
+      else if (node.level === 3) {
+        this.projectQueryParams.questionType = null;
+        this.projectQueryParams.questionSubType = data.cateId;
+      }
+
+      // 重新加载项目列表
+      this.getProjectList();
+    },
+
+    /** 搜索项目 */
+    searchProjects() {
+      this.projectQueryParams.pageNum = 1;
+      this.getProjectList();
+    },
+
+    /** 视频预览弹窗关闭前的处理函数 */
+    handleCloseVideoPreview(done) {
+      // 停止视频播放
+      const video = document.getElementById('video');
+      if (video) {
+        video.pause();
+        video.currentTime = 0;
+      }
+      // 关闭弹窗
+      this.videoPreviewVisible = false;
+      done();
+    },
+
+    updateQueuePositions() {
+      this.uploadQueue.forEach((video, index) => {
+        video.queuePosition = index + 1;
+        const videoIndex = this.videoList.findIndex(item => item.tempId === video.tempId);
+        if (videoIndex !== -1) {
+          this.videoList[videoIndex].queuePosition = index + 1;
+        }
+      });
+    },
+
+    getQueueStatusColor(status) {
+      switch (status) {
+        case 'success':
+          return '#67C23A';
+        case 'failed':
+          return '#F56C6C';
+        case 'uploading':
+          return '#409EFF';
+        case 'queued':
+          return '#E6A23C';
+        default:
+          return '#909399';
+      }
+    },
+
+    // 批量上传 - 腾讯云
+    async uploadVideoToTxPcdnBatch(file, tempVideo) {
+      console.log("--------------",JSON.stringify(tempVideo))
+      try {
+        const data = await uploadObject(file, (progress) => {
+          const progressPercent = Math.floor(progress.percent * 100);
+          const index = this.videoList.findIndex(item => item.tempId === tempVideo.tempId);
+          if (index !== -1) {
+            this.videoList[index].uploadDetails.line1 = progressPercent;
+            this.videoList[index].uploadDetails.line1Status = 'uploading';
+            this.updateBatchProgress(index);
+          }
+        }, 1, (uploadInfo) => {
+          const tokens = this.uploadCancellationTokens.get(tempVideo.tempId) || {};
+          tokens.cos = uploadInfo.cancel;
+          this.uploadCancellationTokens.set(tempVideo.tempId, tokens);
+        });
+
+        let line_1 = `${process.env.VUE_APP_VIDEO_LINE_1}${data.urlPath}`;
+        tempVideo.fileKey = data.urlPath.substring(1);
+        tempVideo.videoUrl = line_1;
+        tempVideo.line1 = line_1;
+        return { success: true, url: line_1 };
+      } catch (error) {
+        return { success: false, error: error.message };
+      }
+    },
+    // 批量上传 - 华为云
+    // async uploadVideoToHwObsBatch(file, tempVideo) {
+    //   try {
+    //     const data = await uploadToOBS(file, (progress) => {
+    //       const progressPercent = Math.floor(progress);
+    //       const index = this.videoList.findIndex(item => item.tempId === tempVideo.tempId);
+    //       if (index !== -1) {
+    //         this.videoList[index].uploadDetails.line2 = progressPercent;
+    //         this.videoList[index].uploadDetails.line2Status = 'uploading';
+    //         this.updateBatchProgress(index);
+    //       }
+    //     }, 1, (uploadInfo) => {
+    //       const tokens = this.uploadCancellationTokens.get(tempVideo.tempId) || {};
+    //       tokens.obs = uploadInfo.cancel;
+    //       this.uploadCancellationTokens.set(tempVideo.tempId, tokens);
+    //     });
+    //
+    //     tempVideo.line2 = `${process.env.VUE_APP_VIDEO_LINE_2}/${data.urlPath}`;
+    //     return { success: true, url: tempVideo.line2 };
+    //   } catch (error) {
+    //     return { success: false, error: error.message };
+    //   }
+    // },
+    async uploadVideoToHSYBatch(file, tempVideo) {
+      try {
+        const data = await uploadToHSY(file, (progress) => {
+          const progressPercent = Math.floor(progress);
+          const index = this.videoList.findIndex(item => item.tempId === tempVideo.tempId);
+          if (index !== -1) {
+            this.videoList[index].uploadDetails.line2 = progressPercent;
+            this.videoList[index].uploadDetails.line2Status = 'uploading';
+            this.updateBatchProgress(index);
+          }
+        }, 1, (uploadInfo) => {
+          const tokens = this.uploadCancellationTokens.get(tempVideo.tempId) || {};
+          tokens.obs = uploadInfo.cancel;
+          this.uploadCancellationTokens.set(tempVideo.tempId, tokens);
+        });
+        console.log("批量上传返回参数",data)
+        tempVideo.line2 = `${process.env.VUE_APP_VIDEO_URL}/${data.SourceInfo.FileName}`;
+        tempVideo.hsyVid = data.Vid;
+
+        return { success: true, url: tempVideo.line2};
+      } catch (error) {
+        return { success: false, error: error.message };
+      }
+    },
+    // 更新批量上传的总进度
+    updateBatchProgress(index) {
+      if (index >= 0 && index < this.videoList.length) {
+        const item = this.videoList[index];
+        const line1Progress = item.uploadDetails.line1 || 0;
+        const line2Progress = item.uploadDetails.line2 || 0;
+        // 只有两个线路都成功才算100%
+        if (item.uploadDetails.line1Status === 'success' && item.uploadDetails.line2Status === 'success') {
+          item.progress = 100;
+        } else if (item.uploadDetails.line1Status === 'failed' || item.uploadDetails.line2Status === 'failed') {
+          // 如果任一线路失败,总进度保持当前状态,不超过99%
+          item.progress = Math.min((line1Progress + line2Progress) / 2, 99);
+        } else {
+          // 正常上传中,计算平均进度,不超过99%
+          item.progress = Math.min((line1Progress + line2Progress) / 2, 99);
+        }
+      }
+    },
+    // 重试批量上传
+    async retryBatchUpload(row) {
+      // const index = this.videoList.findIndex(item => item.tempId === row.tempId);
+      // if (index === -1) return;
+      // const {line1, line2,line1Status,line2Status} = this.videoList[index].uploadDetails
+      // // 重置状态
+      // this.videoList[index].uploadStatus = 'uploading';
+      // // this.videoList[index].progress = 0;
+      // this.videoList[index].uploadDetails = {
+      // line1: line1 == 100?line1:0,
+      // line2: line2 == 100?line2:0,
+      // line1Status: line1Status =='success'?line1Status:'pending',
+      // line2Status: line2Status =='success'?line2Status:'pending'
+      // };
+      // const tempVideo = this.videoList[index];
+      // try {
+      // // 重新上传
+      // // const [line1Result, line2Result] = await Promise.allSettled([
+      // // this.uploadVideoToTxPcdnBatch(tempVideo.file, tempVideo),
+      // // // this.uploadVideoToHwObsBatch(tempVideo.file, tempVideo)
+      // // ]);
+      // if (line1 !== 100) {
+      // const line1Result = await this.uploadVideoToTxPcdnBatch(tempVideo.file, tempVideo)
+      // }
+      // if (line2 !== 100) {
+      // const line2Result = await this.uploadVideoToHwObsBatch(tempVideo.file, tempVideo)
+      // }
+      // const line1Success = line1Result.status === 'fulfilled' && line1Result.value.success;
+      // const line2Success = line2Result.status === 'fulfilled' && line2Result.value.success;
+      // if ( line1 == 100 || line1Success) {
+      // this.videoList[index].uploadDetails.line1Status = 'success';
+      // this.videoList[index].uploadDetails.line1 = 100;
+      // this.$message.success(`文件 ${tempVideo.fileName} 重试上传成功`);
+      // } else {
+      // this.videoList[index].uploadDetails.line1Status = line1Success ? 'success' : 'failed';
+      // this.$message.error(`文件 ${tempVideo.fileName} 重试上传失败`);
+      // }
+      // if (line2 == 100 || line2Success) {
+      // this.videoList[index].uploadDetails.line2Status = 'success';
+      // this.videoList[index].uploadDetails.line2 = 100;
+      // this.$message.success(`文件 ${tempVideo.fileName} 重试上传成功`);
+      // } else {
+      // this.videoList[index].uploadDetails.line2Status = line2Success ? 'success' : 'failed';
+      // this.$message.error(`文件 ${tempVideo.fileName} 重试上传失败`);
+      // }
+      // // if (line1Success && line2Success) {
+      // // this.videoList[index].progress = 100;
+      // // this.videoList[index].uploadStatus = 'success';
+      // // this.videoList[index].uploadDetails.line1Status = 'success';
+      // // this.videoList[index].uploadDetails.line2Status = 'success';
+      // // this.videoList[index].uploadDetails.line1 = 100;
+      // // this.videoList[index].uploadDetails.line2 = 100;
+      // // this.$message.success(`文件 ${tempVideo.fileName} 重试上传成功`);
+      // // } else {
+      // // this.videoList[index].uploadStatus = 'failed';
+      // // this.videoList[index].uploadDetails.line1Status = line1Success ? 'success' : 'failed';
+      // // this.videoList[index].uploadDetails.line2Status = line2Success ? 'success' : 'failed';
+      // // this.$message.error(`文件 ${tempVideo.fileName} 重试上传失败`);
+      // // }
+      // } catch (error) {
+      // this.videoList[index].uploadStatus = 'failed';
+      // this.$message.error(`文件 ${tempVideo.fileName} 重试上传失败`);
+      // }
+      const index = this.videoList.findIndex(item => item.tempId === row.tempId);
+      if (index === -1) return;
+      const tempVideo = this.videoList[index];
+      const uploadDetails = tempVideo.uploadDetails || {};
+
+      // 检查哪些线路需要重试
+      const needRetryLine1 = uploadDetails.line1Status === 'failed' || uploadDetails.line1Status === 'pending';
+      const needRetryLine2 = uploadDetails.line2Status === 'failed' || uploadDetails.line2Status === 'pending';
+
+      if (!needRetryLine1 && !needRetryLine2) {
+        this.$message.info('所有线路都已上传成功,无需重试');
+        return;
+      }
+
+      // 更新整体状态为上传中
+      this.videoList[index].uploadStatus = 'uploading';
+
+      // 只重置需要重试的线路状态
+      if (needRetryLine1) {
+        this.videoList[index].uploadDetails.line1 = 0;
+        this.videoList[index].uploadDetails.line1Status = 'pending';
+      }
+      if (needRetryLine2) {
+        this.videoList[index].uploadDetails.line2 = 0;
+        this.videoList[index].uploadDetails.line2Status = 'pending';
+      }
+
+      try {
+        const uploadPromises = [];
+
+        // 根据需要重试的线路创建上传任务
+        if (needRetryLine1) {
+          uploadPromises.push(
+            this.uploadVideoToTxPcdnBatch(tempVideo.file, tempVideo)
+              .then(result => ({ line: 'line1', result }))
+              .catch(error => ({ line: 'line1', result: { success: false, error: error.message } }))
+          );
+        } else {
+          // 如果线路1不需要重试,创建一个已成功的Promise
+          uploadPromises.push(Promise.resolve({ line: 'line1', result: { success: true } }));
+        }
+
+        if (needRetryLine2) {
+          uploadPromises.push(
+            this.uploadVideoToHwObsBatch(tempVideo.file, tempVideo)
+              .then(result => ({ line: 'line2', result }))
+              .catch(error => ({ line: 'line2', result: { success: false, error: error.message } }))
+          );
+        } else {
+          // 如果线路2不需要重试,创建一个已成功的Promise
+          uploadPromises.push(Promise.resolve({ line: 'line2', result: { success: true } }));
+        }
+
+        // 等待所有上传任务完成
+        const results = await Promise.all(uploadPromises);
+
+        // 处理结果
+        let line1Success = true;
+        let line2Success = true;
+        let retryMessages = [];
+
+        results.forEach(({ line, result }) => {
+          if (line === 'line1') {
+            line1Success = result.success;
+            if (needRetryLine1) {
+              if (result.success) {
+                this.videoList[index].uploadDetails.line1Status = 'success';
+                this.videoList[index].uploadDetails.line1 = 100;
+                retryMessages.push('线路1重试成功');
+              } else {
+                this.videoList[index].uploadDetails.line1Status = 'failed';
+                retryMessages.push('线路1重试失败');
+              }
+            }
+          } else if (line === 'line2') {
+            line2Success = result.success;
+            if (needRetryLine2) {
+              if (result.success) {
+                this.videoList[index].uploadDetails.line2Status = 'success';
+                this.videoList[index].uploadDetails.line2 = 100;
+                retryMessages.push('线路2重试成功');
+              } else {
+                this.videoList[index].uploadDetails.line2Status = 'failed';
+                retryMessages.push('线路2重试失败');
+              }
+            }
+          }
+        });
+
+        // 更新总体状态和进度
+        if (line1Success && line2Success) {
+          this.videoList[index].progress = 100;
+          this.videoList[index].uploadStatus = 'success';
+          this.$message.success(`文件 ${tempVideo.fileName} 重试完成:${retryMessages.join(',')}`);
+        } else {
+          this.videoList[index].uploadStatus = 'failed';
+          // 重新计算进度
+          this.updateBatchProgress(index);
+          this.$message.error(`文件 ${tempVideo.fileName} 重试完成:${retryMessages.join(',')}`);
+        }
+      } catch (error) {
+        this.videoList[index].uploadStatus = 'failed';
+        this.$message.error(`文件 ${tempVideo.fileName} 重试过程中发生错误:${error.message || '未知错误'}`);
+      }
+    },
+    // 重试单个上传
+    async retryUpload(row) {
+      this.$confirm('确认要重新上传该视频吗?', '提示', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning'
+      }).then(async () => {
+        // 这里需要重新触发上传,但由于原始文件可能已经不存在
+        // 建议用户重新选择文件上传
+        this.$message.info('请重新选择文件进行上传');
+        this.handleUpdate(row);
+      }).catch(() => {});
+    },
+    // 获取上传状态图标
+    getUploadStatusIcon(status) {
+      switch (status) {
+        case 'success': return 'el-icon-success';
+        case 'failed': return 'el-icon-error';
+        case 'uploading': return 'el-icon-loading';
+        default: return 'el-icon-time';
+      }
+    },
+    // 获取上传状态颜色
+    getUploadStatusColor(status) {
+      switch (status) {
+        case 'success': return '#67C23A';
+        case 'failed': return '#F56C6C';
+        case 'uploading': return '#409EFF';
+        default: return '#909399';
+      }
+    },
+    // 获取上传状态文本
+    getUploadStatusText(status, queuePosition) {
+      switch (status) {
+        case 'success':
+          return '上传成功';
+        case 'failed':
+          return '上传失败';
+        case 'uploading':
+          return '上传中';
+        case 'queued':
+          return `队列中 (第${queuePosition}位)`;
+        default:
+          return '待上传';
+      }
+    },
+    // 获取线路状态图标
+    getLineStatusIcon(status) {
+      switch (status) {
+        case 'success': return 'el-icon-check';
+        case 'failed': return 'el-icon-close';
+        case 'uploading': return 'el-icon-loading';
+        default: return 'el-icon-minus';
+      }
+    },
+    // 获取线路状态颜色
+    getLineStatusColor(status) {
+      switch (status) {
+        case 'success': return '#67C23A';
+        case 'failed': return '#F56C6C';
+        case 'uploading': return '#409EFF';
+        default: return '#C0C4CC';
+      }
+    },
+    // 获取进度条状态
+    getProgressStatus(row) {
+      if (row.progress === 100 && row.uploadStatus === 'success') {
+        return 'success';
+      } else if (row.uploadStatus === 'failed') {
+        return 'exception';
+      }
+      return '';
+    },
+  }
+}
+</script>
+
+<style scoped>
+/* 自定义表格样式 */
+.el-table .video-cell {
+  padding: 5px;
+}
+
+/* 设置删除和修改按钮的间距 */
+.el-button + .el-button {
+  margin-left: 5px;
+}
+
+/* 上传状态样式 */
+.upload-status-container {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  gap: 4px;
+}
+
+.status-indicator {
+  display: flex;
+  align-items: center;
+  gap: 4px;
+}
+
+.status-text {
+  font-size: 12px;
+}
+
+.upload-details {
+  display: flex;
+  flex-direction: column;
+  gap: 2px;
+  font-size: 11px;
+}
+
+.line-status {
+  display: flex;
+  align-items: center;
+  gap: 4px;
+}
+
+.line-label {
+  min-width: 35px;
+  color: #606266;
+}
+
+.line-progress {
+  color: #909399;
+}
+
+/* 双线上传进度样式 */
+.dual-upload-progress {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  gap: 4px;
+}
+
+.total-progress {
+  margin-bottom: 4px;
+}
+
+.line-progress-container {
+  display: flex;
+  flex-direction: column;
+  gap: 2px;
+  width: 100%;
+}
+
+.line-progress-item {
+  display: flex;
+  align-items: center;
+  gap: 4px;
+  font-size: 10px;
+}
+
+.line-label {
+  min-width: 30px;
+  color: #606266;
+}
+
+.line-status-text {
+  min-width: 25px;
+  color: #909399;
+  font-size: 10px;
+}
+
+/* 批量上传进度样式 */
+.batch-upload-progress {
+  display: flex;
+  flex-direction: column;
+  gap: 4px;
+}
+
+.total-progress-row {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  white-space: nowrap;
+  /* 防止换行 */
+  min-width: 0;
+  /* 允许flex项目收缩 */
+}
+
+.progress-label {
+  font-size: 12px;
+  color: #606266;
+  min-width: 40px;
+}
+
+.line-progress-rows {
+  display: flex;
+  flex-direction: column;
+  gap: 2px;
+}
+
+.line-progress-row {
+  display: flex;
+  align-items: center;
+  gap: 4px;
+  font-size: 11px;
+}
+
+.line-percentage {
+  min-width: 30px;
+  color: #909399;
+  font-size: 11px;
+}
+
+::v-deep .upload-icon {
+  font-size: 28px;
+  color: #8c939d;
+}
+
+::v-deep .upload-text {
+  font-size: 12px;
+  color: #606266;
+  margin-top: 10px;
+}
+
+/* 表单样式 */
+::v-deep .el-form-item {
+  margin-bottom: 22px;
+}
+
+/* 批量选择弹窗样式 */
+::v-deep .batch-dialog .el-dialog__body {
+  padding: 0 20px 20px;
+  max-height: 70vh;
+  overflow-y: auto;
+}
+
+::v-deep .el-dialog .el-dialog__body {
+  padding: 0 20px 20px;
+  max-height: 70vh;
+  overflow-y: auto;
+}
+
+.filter-container {
+  padding: 15px 0;
+  background-color: #fff;
+  border-bottom: 1px solid #ebeef5;
+  position: relative;
+  text-align: right;
+}
+
+.dialog-footer {
+  text-align: right;
+  margin-top: 20px;
+  padding-right: 20px;
+}
+
+::v-deep .batch-dialog .el-table {
+  margin-top: 10px;
+}
+
+::v-deep .el-table__header-wrapper th {
+  background-color: #f5f7fa;
+  color: #606266;
+  font-weight: 500;
+  padding: 8px 0;
+}
+
+::v-deep .el-table__header-wrapper th {
+  background-color: #f5f7fa;
+  color: #606266;
+  font-weight: 500;
+  padding: 8px 0;
+}
+
+/* 确保弹窗中表格内容垂直居中 */
+::v-deep .batch-dialog .el-table .cell {
+  line-height: 23px;
+}
+
+::v-deep .el-table .cell {
+  line-height: 23px;
+}
+
+/* 自定义滚动条样式 */
+::v-deep .batch-dialog .el-dialog__body::-webkit-scrollbar {
+  width: 6px;
+  height: 6px;
+}
+
+::v-deep .el-dialog .el-dialog__body::-webkit-scrollbar {
+  width: 6px;
+  height: 6px;
+}
+
+::v-deep .batch-dialog .el-dialog__body::-webkit-scrollbar-thumb {
+  background: #c0c4cc;
+  border-radius: 3px;
+}
+
+::v-deep .el-dialog .el-dialog__body::-webkit-scrollbar-thumb {
+  background: #c0c4cc;
+  border-radius: 3px;
+}
+
+::v-deep .batch-dialog .el-dialog__body::-webkit-scrollbar-track {
+  background: #f5f7fa;
+}
+
+::v-deep .el-dialog .el-dialog__body::-webkit-scrollbar-track {
+  background: #f5f7fa;
+}
+
+/* 批量上传视频弹窗样式 */
+.upload-dialog .el-dialog__body {
+  padding: 20px;
+}
+
+::v-deep .upload-dialog .el-dialog__header {
+  padding: 15px 20px;
+  background-color: #f9f9f9;
+  border-bottom: 1px solid #ebeef5;
+}
+
+::v-deep .upload-dialog .el-dialog__title {
+  font-size: 16px;
+  font-weight: 500;
+  color: #303133;
+}
+
+/* 项目选择弹窗样式 */
+::v-deep .el-dialog__wrapper {
+  z-index: 2001 !important;
+}
+
+::v-deep .el-dialog__header {
+  padding: 15px 20px;
+  background-color: #f9f9f9;
+  border-bottom: 1px solid #ebeef5;
+}
+
+::v-deep .el-dialog__title {
+  font-size: 16px;
+  font-weight: 500;
+  color: #303133;
+}
+
+::v-deep .el-pagination {
+  text-align: center;
+  margin-top: 10px;
+}
+
+/* 项目选择弹窗的样式 */
+.project-container {
+  display: flex;
+  height: 500px;
+}
+
+.category-tree {
+  width: 250px;
+  height: 100%;
+  border-right: 1px solid #ebeef5;
+  padding: 0;
+  display: flex;
+  flex-direction: column;
+  overflow: hidden;
+  flex-shrink: 0;
+}
+
+.tree-fixed-header {
+  padding: 10px;
+  background-color: #fff;
+  border-bottom: 1px solid #ebeef5;
+  z-index: 10;
+  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.05);
+}
+
+.tree-content {
+  flex: 1;
+  overflow-y: auto;
+  padding: 10px 10px 10px 10px;
+}
+
+.project-list {
+  flex: 1;
+  padding: 10px;
+  height: 100%;
+  display: flex;
+  flex-direction: column;
+  width: calc(100% - 250px);
+  /* 固定宽度为剩余空间 */
+}
+
+.project-list .filter-container {
+  padding: 0 0 15px 0;
+  text-align: left;
+}
+
+.project-list .table-footer {
+  margin-top: 15px;
+}
+
+::v-deep .el-tree-node__content {
+  height: 36px;
+  border-radius: 4px;
+  margin-bottom: 2px;
+}
+
+::v-deep .el-tree-node:focus > .el-tree-node__content {
+  background-color: #e6f7ff;
+}
+
+::v-deep .el-tree-node__content:hover {
+  background-color: #f0f7ff;
+}
+
+::v-deep .el-tree--highlight-current .el-tree-node.is-current > .el-tree-node__content {
+  background-color: #e6f7ff;
+  color: #1890ff;
+}
+
+::v-deep .custom-tree-node {
+  flex: 1;
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  font-size: 14px;
+  padding: 0 8px;
+}
+
+/* 视频预览弹窗样式 */
+::v-deep .video-preview-dialog {
+  z-index: 3000 !important;
+}
+
+::v-deep .video-preview-dialog .el-dialog {
+  margin-top: 10vh !important;
+}
+
+::v-deep .video-preview-dialog .el-dialog__header {
+  padding: 15px 20px;
+  background-color: #f9f9f9;
+  border-bottom: 1px solid #ebeef5;
+}
+
+::v-deep .video-preview-dialog .el-dialog__body {
+  padding: 15px;
+  background-color: #000;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+}
+
+::v-deep .video-preview-dialog video {
+  max-width: 100%;
+  max-height: 70vh;
+}
+
+/* 题目列表样式 */
+.project-list-container {
+  background-color: #f5f7fa;
+  border-radius: 4px;
+}
+
+.question-card {
+  background-color: #ffffff;
+  border-radius: 4px;
+  box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
+  padding: 15px;
+  margin-bottom: 15px;
+  position: relative;
+}
+
+.question-header {
+  display: flex;
+  align-items: flex-start;
+  margin-bottom: 10px;
+  border-bottom: 1px solid #ebeef5;
+  padding-bottom: 10px;
+}
+
+.question-index {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  width: 24px;
+  height: 24px;
+  background-color: #409EFF;
+  color: white;
+  border-radius: 50%;
+  font-size: 14px;
+  margin-right: 10px;
+  flex-shrink: 0;
+}
+
+.question-title {
+  font-size: 16px;
+  font-weight: 500;
+  color: #303133;
+  line-height: 1.5;
+  word-break: break-all;
+}
+
+.question-type {
+  margin-bottom: 15px;
+}
+
+.content-title {
+  font-weight: 500;
+  color: #606266;
+  margin-bottom: 10px;
+}
+
+.question-content {
+  margin-bottom: 15px;
+}
+
+.question-item {
+  background-color: #f8f8f8;
+  border-left: 3px solid #409EFF;
+  padding: 10px;
+  margin-bottom: 10px;
+  border-radius: 0 4px 4px 0;
+}
+
+.question-item-header {
+  display: flex;
+  align-items: center;
+  margin-bottom: 5px;
+}
+
+.item-index {
+  display: inline-block;
+  margin-right: 10px;
+  font-weight: 500;
+  color: #409EFF;
+}
+
+.item-name {
+  color: #303133;
+}
+
+.question-answer {
+  background-color: #f0f9eb;
+  padding: 10px;
+  border-radius: 4px;
+  border-left: 3px solid #67c23a;
+}
+
+.answer-label {
+  font-weight: 500;
+  color: #67c23a;
+  margin-right: 10px;
+}
+
+.answer-content {
+  color: #606266;
+}
+
+::v-deep .custom-select-class .el-select__tags {
+  display: flex;
+  flex-direction: column;
+  align-items: flex-start;
+  flex-wrap: nowrap;
+  max-height: 200px;
+  overflow-y: auto;
+}
+
+::v-deep .custom-select-class .el-tag {
+  margin-bottom: 4px;
+  margin-right: 0;
+}
+
+/* Added queue status styles */
+.queue-status {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  padding: 8px;
+}
+
+.queue-status .el-tag {
+  font-size: 12px;
+  padding: 4px 8px;
+}
+</style>

+ 10 - 5
src/views/course/userCourse/index.vue

@@ -493,6 +493,8 @@ export default {
   },
   data() {
     return {
+      /** 与后端 is_private 一致:1-课程管理(私域),0-公域课程管理 */
+      pageIsPrivate: 1,
       talentParam: {
         phone: null,
         talentId: null
@@ -786,6 +788,7 @@ export default {
     /** 查询课程列表 */
     getList() {
       this.loading = true;
+      this.queryParams.isPrivate = this.pageIsPrivate;
       listUserCourse(this.queryParams).then(response => {
         this.userCourseList = response.rows;
         this.total = response.total;
@@ -819,7 +822,7 @@ export default {
         isTui: "1",
         isBest: "1",
         isNext: "1",
-        isPrivate: "1",
+        isPrivate: String(this.pageIsPrivate),
         views: 100000,
         duration: null,
         description: null,
@@ -846,6 +849,7 @@ export default {
       this.resetForm("queryForm");
       this.queryParams.companyIdsList = [];
       this.queryParams.isShow = this.activeName
+      this.queryParams.isPrivate = this.pageIsPrivate;
       this.handleQuery();
     },
     // 多选框选中数据
@@ -888,7 +892,9 @@ export default {
         this.form.isIntegral = response.data.isIntegral.toString();
         this.form.isTui = response.data.isTui.toString();
         this.form.isNext = response.data.isNext.toString();
-        this.form.isPrivate = response.data.isPrivate.toString();
+        this.form.isPrivate = (response.data.isPrivate != null && response.data.isPrivate !== '')
+          ? response.data.isPrivate.toString()
+          : String(this.pageIsPrivate);
         this.talentParam.talentId = response.data.talentId;
         if (this.form.companyIds != null) {
           this.companyIds = ((this.form.companyIds).split(",").map(Number))
@@ -916,8 +922,7 @@ export default {
         if (valid) {
 
           this.form.companyIds = this.companyIds.toString()
-          // 私域课程
-          this.form.isPrivate = 1
+          this.form.isPrivate = this.pageIsPrivate
           if (this.form.courseId != null) {
             updateUserCourse(this.form).then(response => {
               this.msgSuccess("修改成功");
@@ -993,7 +998,7 @@ export default {
     },
     /** 导出按钮操作 */
     handleExport() {
-      const queryParams = this.queryParams;
+      const queryParams = { ...this.queryParams, isPrivate: this.pageIsPrivate };
       this.$confirm('是否确认导出所有课程数据项?', "警告", {
         confirmButtonText: "确定",
         cancelButtonText: "取消",

+ 5 - 3
src/views/course/userCourseCategory/index.vue

@@ -247,7 +247,8 @@ export default {
         cateName: null,
         sort: null,
         isShow: null,
-        isDel: null
+        isDel: null,
+        cateType: 0
       },
       // 表单参数
       form: {},
@@ -307,7 +308,7 @@ export default {
       };
     },
     getTreeselect() {
-      listUserCourseCategory().then(response => {
+      listUserCourseCategory({ cateType: 0 }).then(response => {
         this.categoryOptions = [];
         const data = { cateId: 0, cateName: '顶级目录', children: [] };
         data.children = response.data.filter(item => item.pid == 0)
@@ -329,7 +330,8 @@ export default {
         isShow: null,
         createTime: null,
         updateTime: null,
-        isDel: null
+        isDel: null,
+        cateType: 0
       };
       this.resetForm("form");
     },

+ 22 - 5
src/views/course/videoResource/index.vue

@@ -845,6 +845,8 @@ export default {
   },
   data() {
     return {
+      /** 本页视频资源类型:0-视频资源,1-公域课视频资源(与后端 video_type 一致) */
+      pageVideoType: 0,
       filteredRootTypeList: [], // 过滤后的分类列表
       originalRootTypeList: [], // 原始分类列表
       // 遮罩层
@@ -872,7 +874,8 @@ export default {
         resourceName: null,
         fileName: null,
         typeId: null,
-        typeSubId: null
+        typeSubId: null,
+        videoType: 0
       },
       // 表单参数
       form: {
@@ -892,6 +895,7 @@ export default {
         projectIds: [],
         sort: null,
         displayType: 'landscape',
+        videoType: 0,
         hsyVid:null,//火山云上传视频返回vid
         hsyVodUrl:null,//火山云url
         // 新增上传状态字段
@@ -1045,6 +1049,7 @@ export default {
     /** 查询视频素材库列表 */
     getList() {
       this.loading = true;
+      this.queryParams.videoType = this.pageVideoType;
       listVideoResource(this.queryParams).then(response => {
         this.resourceList = response.rows;
         this.total = response.total;
@@ -1078,7 +1083,8 @@ export default {
         typeId: null,
         typeSubId: null,
         projectIds: [],
-        displayType: 'landscape'
+        displayType: 'landscape',
+        videoType: this.pageVideoType
       };
       // 重置表单验证状态
       this.resetForm("form");
@@ -1092,6 +1098,7 @@ export default {
     /** 重置按钮操作 */
     resetQuery() {
       this.resetForm("queryForm");
+      this.queryParams.videoType = this.pageVideoType;
       this.handleQuery();
     },
     // 多选框选中数据
@@ -1138,6 +1145,9 @@ export default {
         if (!this.form.displayType) {
           this.form.displayType = 'landscape';
         }
+        if (this.form.videoType === null || this.form.videoType === undefined) {
+          this.form.videoType = this.pageVideoType;
+        }
         await this.changeCateType(this.form.typeId)
 
         // 处理projectIds,确保是数组格式
@@ -1182,6 +1192,7 @@ export default {
 
           const params = Object.assign({}, this.form);
           console.log("提交素材表单参数",this.form)
+          params.videoType = this.pageVideoType;
           params.projectIds = this.form.projectIds.join(',');
           if (this.form.id != null) {
             updateVideoResource(params).then(response => {
@@ -1687,6 +1698,7 @@ export default {
             params.append('typeId', this.batchUpdateForm.typeId || '');
             params.append('typeSubId', this.batchUpdateForm.typeSubId || '');
             params.append('ids', this.batchUpdateForm.ids.join(','));
+            params.append('videoType', String(this.pageVideoType));
             return batchUpdateVideoResource(params);
           }).then(() => {
             this.getList();
@@ -1721,6 +1733,7 @@ export default {
       const videoList = JSON.parse(JSON.stringify(this.videoList));
       videoList.forEach(item => {
         item.projectIds = item.projectIds.join(",");
+        item.videoType = this.pageVideoType;
       });
       batchAddVideoResource(videoList).then(response => {
         if (response.code === 200) {
@@ -1871,7 +1884,8 @@ export default {
       if (this.currentProject === 'myhk') {
         this.batchUploadForm = {
           ...this.batchUploadForm,
-          files: []
+          files: [],
+          videoType: this.pageVideoType
         };
       } else {
         this.batchUploadForm = {
@@ -1879,7 +1893,8 @@ export default {
           typeSubId: null,
           projectIds: [],
           files: [],
-          displayType: 'landscape'
+          displayType: 'landscape',
+          videoType: this.pageVideoType
         };
         this.subTypeList = []
       }
@@ -1913,6 +1928,7 @@ export default {
         typeSubId: this.batchUploadForm.typeSubId,
         projectIds: this.batchUploadForm.projectIds,
         displayType: this.batchUploadForm.displayType || 'landscape',
+        videoType: this.pageVideoType,
         progress: 0,
         uploadStatus: 'queued', // Set initial status to queued
         uploadDetails: {
@@ -2145,7 +2161,8 @@ export default {
       else if (this.selectedType === 3) {
         const params = {
           id: this.currentRow.id,
-          projectIds: this.selectedProjectIds.join(",")
+          projectIds: this.selectedProjectIds.join(","),
+          videoType: this.pageVideoType
         }
         updateVideoResource(params).then(response => {
           if (response.code === 200) {