Переглянути джерело

1、优化直播中控台答题处理

yys 5 днів тому
батько
коміт
cb1a72d928

+ 48 - 0
src/api/live/liveQuestionBank.js

@@ -0,0 +1,48 @@
+import request from '@/utils/request'
+
+export function listLiveQuestionBank(query) {
+  return request({
+    url: '/live/liveQuestionBank/list',
+    method: 'get',
+    params: query
+  })
+}
+
+export function getLiveQuestionBank(id) {
+  return request({
+    url: '/live/liveQuestionBank/' + id,
+    method: 'get'
+  })
+}
+
+export function addLiveQuestionBank(data) {
+  return request({
+    url: '/live/liveQuestionBank',
+    method: 'post',
+    data: data
+  })
+}
+
+export function updateLiveQuestionBank(data) {
+  return request({
+    url: '/live/liveQuestionBank',
+    method: 'put',
+    data: data
+  })
+}
+
+export function delLiveQuestionBank(ids) {
+  const idStr = Array.isArray(ids) ? ids.join(',') : ids
+  return request({
+    url: '/live/liveQuestionBank/' + idStr,
+    method: 'delete'
+  })
+}
+
+export function getLiveQuestionBankByIds(ids) {
+  return request({
+    url: '/live/liveQuestionBank/getByIds',
+    method: 'get',
+    params: { ids }
+  })
+}

+ 74 - 164
src/views/live/liveConfig/answer.vue

@@ -1,63 +1,24 @@
 <template>
   <div class="container-md">
     <div class="tip-box">
-      选择用于本节直播课程的题库试题,试题可用于直播间内发送
-      <el-link
-        type="primary"
-        style="margin-left: 5px;"
-        @click="handleToQuestionBank"
-      >配置题库试题 >></el-link>
+      选择用于本节直播的课题,可用于直播间内发送
+      <el-link type="primary" style="margin-left: 5px;" @click="handleToQuestionBank">配置直播课题 >></el-link>
     </div>
 
-    <el-button type="primary" icon="el-icon-plus" style="margin: 20px 0;" @click="handleAddQuestion">添加试题</el-button>
-    <!-- 试题列表表格 -->
-    <el-table
-      :data="questionLiveList"
-      style="width: 100%"
-      v-loading="loading"
-    >
-      <!-- 题干列:显示试题的主要内容 -->
-      <el-table-column
-        prop="title"
-        label="题干"
-        show-overflow-tooltip
-      ></el-table-column>
-
-      <!-- 题型列:显示是单选还是多选 -->
-      <el-table-column
-        prop="type"
-        label="题型"
-      >
-        <template slot-scope="scope">
-          {{ scope.row.type === 1 ? '单选题' : '多选题' }}
-        </template>
+    <el-button type="primary" icon="el-icon-plus" style="margin: 20px 0;" @click="handleAddQuestion">添加课题</el-button>
+    <el-table :data="questionLiveList" style="width: 100%" v-loading="loading">
+      <el-table-column prop="title" label="题干" show-overflow-tooltip />
+      <el-table-column prop="type" label="题型" width="100">
+        <template slot-scope="scope">{{ scope.row.type === 1 ? '单选题' : '多选题' }}</template>
       </el-table-column>
-
-      <!-- 创建时间列:显示试题创建的时间 -->
-      <el-table-column
-        prop="createTime"
-        label="创建时间"
-      ></el-table-column>
-
-      <!-- 操作列:包含编辑和删除按钮 -->
-      <el-table-column
-        label="操作"
-        width="180"
-        fixed="right"
-      >
+      <el-table-column prop="createTime" label="创建时间" width="180" />
+      <el-table-column label="操作" width="100" fixed="right">
         <template slot-scope="scope">
-          <!-- 删除按钮:用于移除试题 -->
-          <el-button
-            type="text"
-            size="small"
-            style="color: #F56C6C;"
-            @click="handleDelete(scope.row)"
-          >删除</el-button>
+          <el-button type="text" size="small" style="color: #F56C6C;" @click="handleDelete(scope.row)">删除</el-button>
         </template>
       </el-table-column>
     </el-table>
 
-    <!-- 分页组件:用于分页展示试题列表 -->
     <pagination
       v-show="questionTotal > 0"
       :total="questionTotal"
@@ -67,95 +28,47 @@
       style="margin-top: 20px;"
     />
 
-    <!-- 添加试题弹窗 -->
-    <el-dialog
-      title="添加试题"
-      :visible.sync="questionDialogVisible"
-      width="800px"
-      :close-on-click-modal="false"
-      :close-on-press-escape="false"
-    >
-      <div class="dialog-content">
-        <div style="text-align: right; margin-bottom: 20px;">
-          <el-input
-            v-model="searchTitle"
-            placeholder="请输入搜索内容"
-            style="width: 300px;"
-            @input="handleQuestionSearch"
-          ></el-input>
-        </div>
-
-        <el-table
-          ref="questionTable"
-          :data="questionList"
-          style="width: 100%"
-          v-loading="questionLoading"
-          @selection-change="handleSelectionChange"
-          @row-click="handleRowClick"
-          row-key="id"
-        >
-          <!-- 复选框列:用于多选试题 -->
-          <el-table-column
-            type="selection"
-            width="55"
-          >
-          </el-table-column>
-          <!-- 题干列:显示试题的主要内容 -->
-          <el-table-column
-            prop="title"
-            label="题干"
-            class-name="clickable-column"
-          ></el-table-column>
-          <!-- 题型列:显示单选或多选 -->
-          <el-table-column
-            prop="type"
-            label="题型"
-            class-name="clickable-column"
-          >
-            <template slot-scope="scope">
-              {{ scope.row.type === 1 ? '单选题' : '多选题' }}
-            </template>
-          </el-table-column>
-          <!-- 创建人列 -->
-          <el-table-column
-            prop="createBy"
-            label="创建人"
-            class-name="clickable-column"
-          ></el-table-column>
-          <!-- 创建时间列 -->
-          <el-table-column
-            prop="createTime"
-            label="创建时间"
-            width="150"
-            class-name="clickable-column"
-          ></el-table-column>
-        </el-table>
-
-        <pagination
-          v-show="total > 0"
-          :total="total"
-          :page.sync="queryParams.pageNum"
-          :limit.sync="queryParams.pageSize"
-          @pagination="getQuestionList"
-          style="margin-top: 20px;"
-        />
+    <el-dialog title="添加课题" :visible.sync="questionDialogVisible" width="800px" append-to-body :close-on-click-modal="false" custom-class="question-select-dialog">
+      <div style="text-align: right; margin-bottom: 20px;">
+        <el-input v-model="searchTitle" placeholder="请输入题干搜索" style="width: 300px;" @keyup.enter.native="handleQuestionSearch">
+          <el-button slot="append" icon="el-icon-search" @click="handleQuestionSearch" />
+        </el-input>
       </div>
-      <div slot="footer" class="dialog-footer">
-        <div style="display: flex; justify-content: space-between; align-items: center;">
-          <span class="selected-count">当前已选择 <span style="color: #00BFFF; font-style: italic;">{{ selectedQuestions.length }}</span> 题</span>
-          <div>
-            <el-button @click="questionDialogVisible = false">取 消</el-button>
-            <el-button type="primary" @click="confirmAddQuestion">确 定</el-button>
-          </div>
-        </div>
+      <el-table
+        ref="questionTable"
+        :data="questionList"
+        v-loading="questionLoading"
+        @selection-change="handleSelectionChange"
+        @row-click="handleRowClick"
+        row-key="id"
+      >
+        <el-table-column type="selection" width="55" />
+        <el-table-column prop="title" label="题干" show-overflow-tooltip />
+        <el-table-column prop="type" label="题型" width="100">
+          <template slot-scope="scope">{{ scope.row.type === 1 ? '单选题' : '多选题' }}</template>
+        </el-table-column>
+        <el-table-column prop="createBy" label="创建人" width="120" />
+        <el-table-column prop="createTime" label="创建时间" width="160" />
+      </el-table>
+      <pagination
+        v-show="total > 0"
+        :total="total"
+        :page.sync="queryParams.pageNum"
+        :limit.sync="queryParams.pageSize"
+        @pagination="getQuestionList"
+        style="margin-top: 20px;"
+      />
+      <div slot="footer">
+        <span style="float:left;line-height:32px;">已选择 <span style="color:#409EFF;">{{ selectedQuestions.length }}</span> 题</span>
+        <el-button @click="questionDialogVisible = false">取 消</el-button>
+        <el-button type="primary" @click="confirmAddQuestion">确 定</el-button>
       </div>
     </el-dialog>
   </div>
 </template>
 
 <script>
-
-import {addLiveQuestionLive, listLiveQuestionLive, listLiveQuestionOptionList} from "@/api/live/liveQuestionLive";
+import { addLiveQuestionLive, listLiveQuestionLive, listLiveQuestionOptionList, deleteLiveQuestionLive } from '@/api/live/liveQuestionLive'
 
 export default {
   data() {
@@ -169,18 +82,9 @@ export default {
       loading: true,
       searchTitle: '',
       total: 0,
-      queryParams: {
-        pageNum: 1,
-        pageSize: 10,
-        title: null,
-        liveId: null
-      },
-      questionParams: {
-        pageNum: 1,
-        pageSize: 10,
-        liveId: null
-      },
-    };
+      queryParams: { pageNum: 1, pageSize: 10, title: null, liveId: null },
+      questionParams: { pageNum: 1, pageSize: 10, liveId: null }
+    }
   },
   created() {
     this.liveId = this.$route.params.liveId
@@ -207,6 +111,7 @@ export default {
     },
     handleAddQuestion() {
       this.questionDialogVisible = true
+      this.selectedQuestions = []
       this.getQuestionList()
     },
     getQuestionList() {
@@ -222,39 +127,44 @@ export default {
     },
     confirmAddQuestion() {
       if (this.selectedQuestions.length === 0) {
-        this.$message({
-          message: '请选择要添加的试题',
-          type: 'warning'
-        })
+        this.$message.warning('请选择要添加的课题')
         return
       }
-      // 调用添加直播间试题接口
       addLiveQuestionLive({
         liveId: this.liveId,
         questionIds: this.selectedQuestions.map(item => item.id).join(',')
-      }).then(response => {
+      }).then(() => {
         this.questionDialogVisible = false
         this.getLiveQuestionLiveList()
       })
     },
-    handleRowClick(row, column) {
-      // 如果点击的是复选框列,不进行处理
-      if (column.type === 'selection') {
+    handleDelete(row) {
+      const relationId = row.relationId
+      if (!relationId) {
+        this.$message.error('无法删除,缺少关联ID')
         return
       }
-
-      // 获取表格实例
-      const table = this.$refs.questionTable[0]
-      if (!table) {
-        return
-      }
-
-      // 判断当前行是否已经被选中
+      this.$confirm('确认移除该课题?', '提示', { type: 'warning' }).then(() => {
+        return deleteLiveQuestionLive({ liveId: this.liveId, ids: [relationId] })
+      }).then(() => {
+        this.msgSuccess('删除成功')
+        this.getLiveQuestionLiveList()
+      })
+    },
+    handleRowClick(row, column) {
+      if (column.type === 'selection') return
+      const table = this.$refs.questionTable
+      if (!table) return
       const isSelected = this.selectedQuestions.some(item => item.id === row.id)
-
-      // 切换选中状态
       table.toggleRowSelection(row, !isSelected)
-    },
+    }
   }
-};
+}
 </script>
+
+<style>
+.question-select-dialog .el-dialog__body {
+  max-height: 65vh;
+  overflow-y: auto;
+}
+</style>

+ 4 - 1
src/views/live/liveConfig/index.vue

@@ -294,7 +294,7 @@ export default {
 
 .container {
   display: flex;
-  height: 100%;
+  min-height: calc(100vh - 280px);
 }
 .left-menu {
   width: 200px;
@@ -305,6 +305,9 @@ export default {
 .right-info {
   flex: 1;
   padding: 20px;
+  min-height: 0;
+  overflow-y: auto;
+  max-height: calc(100vh - 280px);
 }
 
 .right-tabs {

+ 232 - 5
src/views/live/liveConfig/watchReward.vue

@@ -1,5 +1,5 @@
-<template >
-  <div v-loading.fullscreen.lock="loading">
+<template>
+  <div class="watch-reward-page" v-loading="loading">
     <!-- 提示信息 -->
     <div class="tip-message">
       设置观看奖励,用户达到直播观看时长后可领取奖励
@@ -70,6 +70,19 @@
 
         <!-- 完课优惠券配置(仅在启用完课优惠券时显示) -->
         <template v-if="watchRewardForm.participateCondition === '3'">
+          <!-- 完课率要求 -->
+          <el-form-item label="完课率要求" prop="completionRate">
+            <el-input-number
+              v-model="watchRewardForm.completionRate"
+              :min="1"
+              :max="100"
+              :precision="0"
+              placeholder="请输入完课率"
+              style="width: 200px;"
+            ></el-input-number>
+            <span style="margin-left: 10px; color: #909399;">%(观看时长占视频总时长的比例)</span>
+          </el-form-item>
+
           <!-- 优惠券选择 - 与运营自动化一致的下拉框样式 -->
           <el-form-item label="选择优惠券" prop="finishCouponId">
             <el-select v-model="watchRewardForm.finishCouponId" placeholder="请选择优惠券" style="width: 300px;">
@@ -107,6 +120,37 @@
               </div>
             </div>
           </el-form-item>
+
+          <!-- 今日问题配置 -->
+          <el-form-item label="今日问题" prop="finishQuestionIds">
+            <div style="width: 100%;">
+              <el-button type="primary" plain icon="el-icon-plus" @click="openQuestionDialog">选择试题</el-button>
+              <el-link type="primary" style="margin-left: 12px;" @click="handleToQuestionBank">配置直播课题 >></el-link>
+              <el-table
+                v-if="selectedQuestionList.length > 0"
+                :data="selectedQuestionList"
+                style="width: 100%; margin-top: 12px;"
+                size="small"
+                border
+                max-height="240"
+              >
+                <el-table-column prop="title" label="题干" show-overflow-tooltip />
+                <el-table-column prop="type" label="题型" width="100">
+                  <template slot-scope="scope">
+                    {{ scope.row.type === 1 ? '单选题' : '多选题' }}
+                  </template>
+                </el-table-column>
+                <el-table-column label="操作" width="80">
+                  <template slot-scope="scope">
+                    <el-button type="text" style="color: #F56C6C;" @click="removeSelectedQuestion(scope.row)">移除</el-button>
+                  </template>
+                </el-table-column>
+              </el-table>
+              <div v-else style="margin-top: 8px; color: #909399; font-size: 13px;">
+                观看完视频后将自动弹出今日问题,用户答对后发放福利券
+              </div>
+            </div>
+          </el-form-item>
         </template>
 
         <!-- 实施动作(仅在达到指定观看时长时显示) -->
@@ -287,12 +331,68 @@
         </div>
       </el-form>
     </div>
+
+    <!-- 选择今日问题弹窗 -->
+    <el-dialog
+      title="选择今日问题"
+      :visible.sync="questionDialogVisible"
+      width="800px"
+      append-to-body
+      :close-on-click-modal="false"
+      custom-class="question-select-dialog"
+    >
+      <div style="text-align: right; margin-bottom: 16px;">
+        <el-input
+          v-model="questionSearchTitle"
+          placeholder="请输入题干搜索"
+          style="width: 300px;"
+          clearable
+          @keyup.enter.native="handleQuestionSearch"
+        >
+          <el-button slot="append" icon="el-icon-search" @click="handleQuestionSearch"></el-button>
+        </el-input>
+      </div>
+      <el-table
+        ref="questionTable"
+        :data="questionOptionList"
+        v-loading="questionLoading"
+        row-key="id"
+        @selection-change="handleQuestionSelectionChange"
+        @row-click="handleQuestionRowClick"
+      >
+        <el-table-column type="selection" width="55" :reserve-selection="true" />
+        <el-table-column prop="title" label="题干" show-overflow-tooltip />
+        <el-table-column prop="type" label="题型" width="100">
+          <template slot-scope="scope">
+            {{ scope.row.type === 1 ? '单选题' : '多选题' }}
+          </template>
+        </el-table-column>
+        <el-table-column prop="createBy" label="创建人" width="120" />
+        <el-table-column prop="createTime" label="创建时间" width="160" />
+      </el-table>
+      <pagination
+        v-show="questionOptionTotal > 0"
+        :total="questionOptionTotal"
+        :page.sync="questionQueryParams.pageNum"
+        :limit.sync="questionQueryParams.pageSize"
+        @pagination="loadQuestionOptions"
+        style="margin-top: 16px;"
+      />
+      <div slot="footer">
+        <span style="float: left; line-height: 32px; color: #606266;">
+          已选择 <span style="color: #409EFF;">{{ tempSelectedQuestions.length }}</span> 题
+        </span>
+        <el-button @click="questionDialogVisible = false">取 消</el-button>
+        <el-button type="primary" @click="confirmQuestionSelection">确 定</el-button>
+      </div>
+    </el-dialog>
   </div>
 </template>
 
 <script>
 import {addConfig, getConfig, updateConfig} from "@/api/live/liveQuestionLive";
 import {listLiveCouponOn} from "@/api/live/liveCoupon";
+import {listLiveQuestionBank, getLiveQuestionBank} from "@/api/live/liveQuestionBank";
 
 export default {
   props: {
@@ -346,6 +446,8 @@ export default {
         // 完课优惠券相关配置(participateCondition为3时使用)
         // 完课优惠券ID
         finishCouponId: null,
+        // 今日问题ID(逗号分隔)
+        finishQuestionIds: '',
         // 实施动作为3(优惠券)时的配置
         // 优惠券ID
         actionCouponId: null,
@@ -375,7 +477,7 @@ export default {
         completionRate:[
           {
             validator: (rule, value, callback) => {
-              if (this.watchRewardForm.participateCondition === '2') {
+              if (this.watchRewardForm.participateCondition === '2' || this.watchRewardForm.participateCondition === '3') {
                 if (!value && value !== 0) {
                   callback(new Error('请输入完课率要求'));
                 } else if (value < 1 || value > 100) {
@@ -422,6 +524,22 @@ export default {
             trigger: 'change'
           }
         ],
+        finishQuestionIds:[
+          {
+            validator: (rule, value, callback) => {
+              if (this.watchRewardForm.participateCondition === '3') {
+                if (!value) {
+                  callback(new Error('请选择今日问题'));
+                } else {
+                  callback();
+                }
+              } else {
+                callback();
+              }
+            },
+            trigger: 'change'
+          }
+        ],
         actionCouponId:[
           {
             validator: (rule, value, callback) => {
@@ -479,7 +597,21 @@ export default {
       // 选中的完课优惠券信息
       selectedFinishCoupon: {},
       // 选中的实施动作优惠券信息
-      selectedActionCoupon: {}
+      selectedActionCoupon: {},
+      // 今日问题相关
+      questionDialogVisible: false,
+      questionLoading: false,
+      questionSearchTitle: '',
+      questionOptionList: [],
+      questionOptionTotal: 0,
+      questionQueryParams: {
+        pageNum: 1,
+        pageSize: 10,
+        title: null,
+        status: 1
+      },
+      selectedQuestionList: [],
+      tempSelectedQuestions: []
 
     };
   },
@@ -567,6 +699,7 @@ export default {
                 this.selectedActionCoupon = coupon;
               }
             }
+            this.loadSelectedQuestions();
           }
           this.loading = false
         }).catch(() => {
@@ -613,6 +746,7 @@ export default {
       }
     },
     saveWatchReward() {
+      this.syncFinishQuestionIds();
       this.$refs["watchRewardForm"].validate(valid => {
         console.log(valid)
         if (valid) {
@@ -631,12 +765,96 @@ export default {
           }
         }
       })
+    },
+    handleToQuestionBank() {
+      this.$router.push('/live/liveQuestionBank');
+    },
+    openQuestionDialog() {
+      this.questionDialogVisible = true;
+      this.tempSelectedQuestions = [...this.selectedQuestionList];
+      this.loadQuestionOptions();
+    },
+    loadQuestionOptions() {
+      this.questionLoading = true;
+      listLiveQuestionBank(this.questionQueryParams).then(response => {
+        this.questionOptionList = response.rows || [];
+        this.questionOptionTotal = response.total || 0;
+        this.questionLoading = false;
+        this.$nextTick(() => {
+          if (this.$refs.questionTable) {
+            this.questionOptionList.forEach(row => {
+              const selected = this.tempSelectedQuestions.some(item => item.id === row.id);
+              this.$refs.questionTable.toggleRowSelection(row, selected);
+            });
+          }
+        });
+      }).catch(() => {
+        this.questionLoading = false;
+      });
+    },
+    handleQuestionSearch() {
+      this.questionQueryParams.pageNum = 1;
+      this.questionQueryParams.title = this.questionSearchTitle;
+      this.loadQuestionOptions();
+    },
+    handleQuestionSelectionChange(selection) {
+      const currentPageIds = this.questionOptionList.map(item => item.id);
+      const otherPageSelected = this.tempSelectedQuestions.filter(item => !currentPageIds.includes(item.id));
+      this.tempSelectedQuestions = [...otherPageSelected, ...selection];
+    },
+    handleQuestionRowClick(row, column) {
+      if (column.type === 'selection') {
+        return;
+      }
+      const table = this.$refs.questionTable;
+      if (!table) {
+        return;
+      }
+      const isSelected = this.tempSelectedQuestions.some(item => item.id === row.id);
+      table.toggleRowSelection(row, !isSelected);
+    },
+    confirmQuestionSelection() {
+      if (this.tempSelectedQuestions.length === 0) {
+        this.$message.warning('请至少选择一题');
+        return;
+      }
+      this.selectedQuestionList = [...this.tempSelectedQuestions];
+      this.syncFinishQuestionIds();
+      this.questionDialogVisible = false;
+    },
+    removeSelectedQuestion(row) {
+      this.selectedQuestionList = this.selectedQuestionList.filter(item => item.id !== row.id);
+      this.syncFinishQuestionIds();
+    },
+    syncFinishQuestionIds() {
+      this.watchRewardForm.finishQuestionIds = this.selectedQuestionList.map(item => item.id).join(',');
+    },
+    loadSelectedQuestions() {
+      if (!this.watchRewardForm.finishQuestionIds) {
+        this.selectedQuestionList = [];
+        return;
+      }
+      const ids = this.watchRewardForm.finishQuestionIds.split(',').filter(Boolean);
+      if (ids.length === 0) {
+        this.selectedQuestionList = [];
+        return;
+      }
+      Promise.all(ids.map(id => getLiveQuestionBank(id))).then(results => {
+        this.selectedQuestionList = results
+          .filter(res => res.code === 200 && res.data)
+          .map(res => res.data);
+      });
     }
   }
 };
 </script>
 
 <style scoped>
+.watch-reward-page {
+  min-height: 100%;
+  padding-bottom: 40px;
+}
+
 /* 提示信息样式 */
 .tip-message {
   padding: 12px 16px;
@@ -669,12 +887,14 @@ export default {
 
 /* 表单区块样式 */
 .section-block {
-  width: 50%;
+  width: 100%;
+  max-width: 960px;
   background-color: #fff;
   padding: 20px;
   border-radius: 4px;
   margin-left: 50px;
   margin-bottom: 20px;
+  box-sizing: border-box;
 }
 
 /* 标题样式 */
@@ -767,3 +987,10 @@ export default {
   font-weight: 500;
 }
 </style>
+
+<style>
+.question-select-dialog .el-dialog__body {
+  max-height: 65vh;
+  overflow-y: auto;
+}
+</style>

+ 302 - 0
src/views/live/liveQuestionBank/index.vue

@@ -0,0 +1,302 @@
+<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="type">
+        <el-select v-model="queryParams.type" placeholder="请选择题型" clearable size="small">
+          <el-option label="单选题" :value="1" />
+          <el-option label="多选题" :value="2" />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="状态" prop="status">
+        <el-select v-model="queryParams.status" placeholder="请选择状态" clearable size="small">
+          <el-option label="启用" :value="1" />
+          <el-option label="停用" :value="0" />
+        </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="['live:liveQuestionBank: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:liveQuestionBank: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:liveQuestionBank:remove']">删除</el-button>
+      </el-col>
+      <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
+    </el-row>
+
+    <el-table border v-loading="loading" :data="questionList" @selection-change="handleSelectionChange">
+      <el-table-column type="selection" width="55" align="center" />
+      <el-table-column label="题干" align="center" prop="title" show-overflow-tooltip min-width="260" />
+      <el-table-column label="题型" align="center" prop="type" width="100">
+        <template slot-scope="scope">{{ scope.row.type === 1 ? '单选题' : '多选题' }}</template>
+      </el-table-column>
+      <el-table-column label="状态" align="center" prop="status" width="100">
+        <template slot-scope="scope">{{ scope.row.status === 1 ? '启用' : '停用' }}</template>
+      </el-table-column>
+      <el-table-column label="排序" align="center" prop="sort" width="80" />
+      <el-table-column label="答案" align="center" prop="answer" show-overflow-tooltip min-width="120" />
+      <el-table-column label="操作" align="center" width="140">
+        <template slot-scope="scope">
+          <el-button size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(scope.row)" v-hasPermi="['live:liveQuestionBank:edit']">修改</el-button>
+          <el-button size="mini" type="text" icon="el-icon-delete" @click="handleDelete(scope.row)" v-hasPermi="['live:liveQuestionBank: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 :close-on-click-modal="false">
+      <el-form ref="form" :model="form" :rules="rules" 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" />
+        </el-form-item>
+        <el-form-item label="题型" prop="type">
+          <el-select v-model="form.type" placeholder="请选择题型" @change="changeType">
+            <el-option label="单选题" :value="1" />
+            <el-option label="多选题" :value="2" />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="状态" prop="status">
+          <el-radio-group v-model="form.status">
+            <el-radio :label="1">启用</el-radio>
+            <el-radio :label="0">停用</el-radio>
+          </el-radio-group>
+        </el-form-item>
+        <el-alert v-if="!form.type" title="请先选择题型后添加选项" type="warning" show-icon />
+        <el-form-item label="选项" v-if="form.type">
+          <el-button @click="addRow" size="mini" type="primary">新增选项</el-button>
+          <el-table border :data="options" style="margin-top: 10px;">
+            <el-table-column label="序号" width="80">
+              <template slot-scope="scope">{{ '选项' + getOptionLabel(scope.$index) }}</template>
+            </el-table-column>
+            <el-table-column label="内容">
+              <template slot-scope="scope">
+                <el-input v-model="scope.row.name" placeholder="请输入选项内容" />
+              </template>
+            </el-table-column>
+            <el-table-column label="正确答案" width="120">
+              <template slot-scope="scope">
+                <el-switch v-model="scope.row.isAnswer" :active-value="1" :inactive-value="0" @change="handleAnswerChange(scope.row)" />
+              </template>
+            </el-table-column>
+            <el-table-column label="操作" width="80">
+              <template slot-scope="scope">
+                <el-button type="text" @click="deleteRow(scope.$index)" v-if="options.length > 1">删除</el-button>
+              </template>
+            </el-table-column>
+          </el-table>
+        </el-form-item>
+        <el-form-item label="答案">
+          <span style="background:#faedc9;padding:4px 8px;">{{ displayAnswer }}</span>
+        </el-form-item>
+      </el-form>
+      <div slot="footer">
+        <el-button type="primary" @click="submitForm">确 定</el-button>
+        <el-button @click="cancel">取 消</el-button>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import {
+  listLiveQuestionBank,
+  getLiveQuestionBank,
+  addLiveQuestionBank,
+  updateLiveQuestionBank,
+  delLiveQuestionBank
+} from '@/api/live/liveQuestionBank'
+
+export default {
+  name: 'LiveQuestionBank',
+  data() {
+    return {
+      loading: false,
+      showSearch: true,
+      total: 0,
+      questionList: [],
+      ids: [],
+      single: true,
+      multiple: true,
+      title: '',
+      open: false,
+      queryParams: {
+        pageNum: 1,
+        pageSize: 10,
+        title: null,
+        type: null,
+        status: null
+      },
+      form: {},
+      options: [],
+      selectedAnswers: [],
+      selectedAnswer: null,
+      rules: {
+        title: [{ required: true, message: '请输入题干', trigger: 'blur' }],
+        sort: [{ required: true, message: '请输入排序', trigger: 'blur' }],
+        type: [{ required: true, message: '请选择题型', trigger: 'change' }],
+        status: [{ required: true, message: '请选择状态', trigger: 'change' }]
+      }
+    }
+  },
+  computed: {
+    alphabet() {
+      return 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
+    },
+    displayAnswer() {
+      if (!this.form.answer) return ''
+      if (this.form.type === 2) {
+        try {
+          const arr = JSON.parse(this.form.answer)
+          return Array.isArray(arr) ? arr.join('、') : this.form.answer
+        } catch (e) {
+          return this.form.answer
+        }
+      }
+      return this.form.answer
+    }
+  },
+  created() {
+    this.getList()
+  },
+  methods: {
+    getOptionLabel(index) {
+      return this.alphabet[index]
+    },
+    getList() {
+      this.loading = true
+      listLiveQuestionBank(this.queryParams).then(res => {
+        this.questionList = res.rows
+        this.total = res.total
+        this.loading = false
+      })
+    },
+    resetOptions() {
+      this.options = [
+        { name: '', isAnswer: 0, indexId: 0 },
+        { name: '', isAnswer: 0, indexId: 1 },
+        { name: '', isAnswer: 0, indexId: 2 },
+        { name: '', isAnswer: 0, indexId: 3 }
+      ]
+      this.selectedAnswer = null
+      this.selectedAnswers = []
+    },
+    reset() {
+      this.form = { id: null, title: null, sort: 0, type: 1, status: 1, question: null, answer: null }
+      this.resetOptions()
+      this.resetForm('form')
+    },
+    cancel() {
+      this.open = false
+      this.reset()
+    },
+    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.open = true
+      this.title = '新增直播课题'
+    },
+    handleUpdate(row) {
+      this.reset()
+      const id = row.id || this.ids[0]
+      getLiveQuestionBank(id).then(res => {
+        this.form = res.data
+        if (this.form.type === 2) {
+          try {
+            this.form.answer = JSON.parse(this.form.answer)
+          } catch (e) {}
+          this.options = JSON.parse(this.form.question)
+          this.selectedAnswers = this.options.filter(item => item.isAnswer === 1)
+        } else {
+          this.options = JSON.parse(this.form.question)
+        }
+        this.open = true
+        this.title = '修改直播课题'
+      })
+    },
+    changeType() {
+      this.selectedAnswers = []
+      this.selectedAnswer = null
+      this.form.answer = null
+      this.options.forEach(item => { item.isAnswer = 0 })
+    },
+    handleAnswerChange(row) {
+      if (this.form.type === 1) {
+        this.selectedAnswer = row
+        this.options.forEach(item => {
+          if (item !== row) item.isAnswer = 0
+        })
+        this.form.answer = row.name
+      } else {
+        this.selectedAnswers = this.options.filter(item => item.isAnswer === 1)
+        this.form.answer = this.selectedAnswers.map(item => item.name)
+      }
+    },
+    addRow() {
+      this.options.push({ name: '', isAnswer: 0, indexId: this.options.length })
+    },
+    deleteRow(index) {
+      this.options.splice(index, 1)
+    },
+    submitForm() {
+      this.$refs.form.validate(valid => {
+        if (!valid) return
+        if (this.options.some(q => !q.name)) {
+          this.$message.error('选项内容不能为空')
+          return
+        }
+        if (!this.form.answer || (Array.isArray(this.form.answer) && this.form.answer.length === 0)) {
+          this.$message.error('请设置正确答案')
+          return
+        }
+        this.form.question = JSON.stringify(this.options)
+        const payload = { ...this.form }
+        if (payload.type === 2) {
+          payload.answer = JSON.stringify(payload.answer)
+        }
+        const req = payload.id ? updateLiveQuestionBank(payload) : addLiveQuestionBank(payload)
+        req.then(() => {
+          this.msgSuccess(payload.id ? '修改成功' : '新增成功')
+          this.open = false
+          this.getList()
+        })
+      })
+    },
+    handleDelete(row) {
+      const ids = row.id ? [row.id] : this.ids
+      this.$confirm('是否确认删除选中的直播课题?', '警告', { type: 'warning' }).then(() => {
+        return delLiveQuestionBank(ids)
+      }).then(() => {
+        this.getList()
+        this.msgSuccess('删除成功')
+      })
+    }
+  }
+}
+</script>