ソースを参照

设置答题奖励

yfh 1 週間 前
コミット
c6caec271b

+ 53 - 0
src/api/course/relation.js

@@ -0,0 +1,53 @@
+import request from '@/utils/request'
+
+// 查询奖励与视频小节关联关系列表
+export function listRelation(query) {
+  return request({
+    url: '/course/relation/list',
+    method: 'get',
+    params: query
+  })
+}
+
+// 查询奖励与视频小节关联关系详细
+export function getRelation(id) {
+  return request({
+    url: '/course/relation/' + id,
+    method: 'get'
+  })
+}
+
+// 新增奖励与视频小节关联关系
+export function addRelation(data) {
+  return request({
+    url: '/course/relation',
+    method: 'post',
+    data: data
+  })
+}
+
+// 修改奖励与视频小节关联关系
+export function updateRelation(data) {
+  return request({
+    url: '/course/relation',
+    method: 'put',
+    data: data
+  })
+}
+
+// 删除奖励与视频小节关联关系
+export function delRelation(id) {
+  return request({
+    url: '/course/relation/' + id,
+    method: 'delete'
+  })
+}
+
+// 导出奖励与视频小节关联关系
+export function exportRelation(query) {
+  return request({
+    url: '/course/relation/export',
+    method: 'get',
+    params: query
+  })
+}

+ 60 - 0
src/api/course/reward.js

@@ -0,0 +1,60 @@
+import request from '@/utils/request'
+
+// 查询奖励配置列表
+export function listReward(query) {
+  return request({
+    url: '/course/reward/list',
+    method: 'get',
+    params: query
+  })
+}
+
+export function listByIds(ids) {
+  return request({
+    url: '/course/reward/listByIds/' + ids,
+    method: 'get',
+  })
+}
+
+// 查询奖励配置详细
+export function getReward(id) {
+  return request({
+    url: '/course/reward/' + id,
+    method: 'get'
+  })
+}
+
+// 新增奖励配置
+export function addReward(data) {
+  return request({
+    url: '/course/reward',
+    method: 'post',
+    data: data
+  })
+}
+
+// 修改奖励配置
+export function updateReward(data) {
+  return request({
+    url: '/course/reward',
+    method: 'put',
+    data: data
+  })
+}
+
+// 删除奖励配置
+export function delReward(id) {
+  return request({
+    url: '/course/reward/' + id,
+    method: 'delete'
+  })
+}
+
+// 导出奖励配置
+export function exportReward(query) {
+  return request({
+    url: '/course/reward/export',
+    method: 'get',
+    params: query
+  })
+}

+ 13 - 0
src/api/course/userCourseVideo.js

@@ -83,4 +83,17 @@ export function getVideoListByCourseId(query) {
   })
 }
 
+export function getAnswerRewardConfig(videoId) {
+  return request({
+    url: '/course/userCourseVideo/getAnswerRewardConfig/' + videoId,
+    method: 'get',
+  })
+}
 
+export function changeAnswerRewardConfig(data) {
+  return request({
+    url: '/course/userCourseVideo/changeAnswerRewardConfig',
+    method: 'post',
+    data: data
+  })
+}

+ 735 - 13
src/views/components/course/userCourseCatalogDetails.vue

@@ -47,12 +47,25 @@
             @click="handleDetails(scope.row)"
           >查看</el-button>
 
-          <el-button
-            size="mini"
-            type="text"
-            v-hasPermi="['course:userCourseVideo:edit']"
-            @click="updateMoney(scope.row)"
-          >设置红包金额</el-button>
+<!--          <el-button-->
+<!--            size="mini"-->
+<!--            type="text"-->
+<!--            v-hasPermi="['course:userCourseVideo:changeRewardConfig']"-->
+<!--            @click="updateAnswerReward(scope.row)"-->
+<!--          >设置答题奖励</el-button>-->
+
+                    <el-button
+                      size="mini"
+                      type="text"
+                      v-hasPermi="['course:userCourseVideo:edit']"
+                      @click="updateMoney(scope.row)"
+                    >设置红包金额</el-button>
+<!--          <el-button-->
+<!--            size="mini"-->
+<!--            type="text"-->
+<!--            v-hasPermi="['course:relation:edit']"-->
+<!--            @click="updateReward(scope.row)"-->
+<!--          >设置课程奖励</el-button>-->
 
           <el-button size="mini"
                      type="text" @click="openTagDialog(scope.row)">
@@ -121,18 +134,376 @@
     </el-dialog>
 
     <AutoTagDialog
+      v-if="currentVideoId !== null"
       :visible.sync="tagDialogVisible"
       :title="tagDialogTitle"
       :videoId="currentVideoId"
       append-to-body
     />
 
+    <el-dialog
+      title="课程奖励选择"
+      :visible.sync="rewardOpen"
+      width="80%"
+      append-to-body
+      @close="handleRewardDialogClose"
+    >
+      <div class="reward-dialog">
+        <div class="selection-info" v-if="rewardType === 1">
+          <span>已选择 {{ selectedRewards.length }}/2 个奖励项目</span>
+          <el-button
+            type="text"
+            @click="clearSelection"
+            :disabled="selectedRewards.length === 0"
+          >
+            清空选择
+          </el-button>
+        </div>
+
+        <el-table
+          border
+          v-loading="rewardLoading"
+          :data="rewardList"
+          @selection-change="handleRewardSelectionChange"
+          ref="rewardTable"
+        >
+          <el-table-column
+            type="selection"
+            width="55"
+            align="center"
+            :selectable="checkSelectable"
+          />
+          <el-table-column label="主键ID" align="center" prop="id" />
+          <el-table-column label="奖励名称" align="center" prop="name" />
+          <el-table-column label="奖励描述" align="center" prop="description" />
+          <el-table-column label="奖励类型" align="center" prop="rewardType">
+            <template slot-scope="scope">
+              <dict-tag :options="rewardTypeOptions" :value="scope.row.rewardType"/>
+            </template>
+          </el-table-column>
+
+          <el-table-column label="期望值" align="center" prop="expectedValue" />
+          <el-table-column label="实际奖励内容" align="center" prop="actualRewards">
+            <template slot-scope="scope">
+              <el-button
+                size="mini"
+                type="text"
+                icon="el-icon-view"
+                @click="handleViewReward(scope.row)"
+              >查看详情</el-button>
+            </template>
+          </el-table-column>
+          <el-table-column label="创建时间" align="center" prop="createTime" />
+        </el-table>
+
+        <pagination
+          v-show="rewardTotal>0"
+          :total="rewardTotal"
+          :page.sync="rewardQueryParams.pageNum"
+          :limit.sync="rewardQueryParams.pageSize"
+          @pagination="getRewardList"
+        />
+      </div>
+
+      <div slot="footer" class="dialog-footer">
+        <el-button @click="rewardOpen = false">取 消</el-button>
+        <el-button
+          type="primary"
+          @click="submitRewardSelection"
+          :disabled="selectedRewards.length === 0 || (rewardType !== 1 && selectedRewards.length > 1)"
+        >
+          确 定
+        </el-button>
+        <el-dialog
+          :title="`奖励详情 - ${currentReward.name || '未知奖励'}`"
+          :visible.sync="detailVisible"
+          width="700px"
+          append-to-body
+        >
+          <div class="reward-detail-container">
+            <!-- 基础信息 -->
+            <el-descriptions :column="2" border class="base-info">
+              <el-descriptions-item label="奖励名称">{{ currentReward.name || '-' }}</el-descriptions-item>
+              <el-descriptions-item label="奖励类型">
+                <dict-tag :options="rewardTypeOptions" :value="currentReward.rewardType"/>
+              </el-descriptions-item>
+              <el-descriptions-item label="状态">
+                <dict-tag :options="statusOptions" :value="currentReward.status"/>
+              </el-descriptions-item>
+              <el-descriptions-item label="期望值">{{ currentReward.expectedValue || 0 }}</el-descriptions-item>
+              <el-descriptions-item label="描述" :span="2">{{ currentReward.description || '-' }}</el-descriptions-item>
+            </el-descriptions>
+
+            <!-- 奖励内容详情 -->
+            <div class="reward-content">
+              <h4>奖励内容详情</h4>
+
+              <!-- 宝箱类型奖励 -->
+              <div v-if="currentReward.rewardType === 1" class="chest-reward">
+                <el-table :data="parsedRewardItems" size="small" border stripe>
+                  <el-table-column label="数量" prop="amount"  align="center">
+                    <template slot-scope="{row}">
+                      {{ row.amount || 1 }}
+                    </template>
+                  </el-table-column>
+                  <el-table-column label="概率" prop="probability"align="center">
+                    <template slot-scope="{row}">
+                      <el-tag v-if="row.probability" size="small">{{ row.probability }}</el-tag>
+                      <span v-else>-</span>
+                    </template>
+                  </el-table-column>
+                </el-table>
+                <div v-if="!parsedRewardItems || parsedRewardItems.length === 0" class="empty-tip">
+                  暂无奖励配置
+                </div>
+              </div>
+
+              <!-- 红包类型奖励 -->
+              <div v-else-if="currentReward.rewardType === 2" class="redpacket-reward">
+                <div class="reward-amount">
+                  <i class="el-icon-money" style="color: #e6a23c; font-size: 24px;"></i>
+                  <span class="amount-text">{{ rewardAmount }} 元</span>
+                  <el-tag type="warning" size="small">红包奖励</el-tag>
+                </div>
+              </div>
+
+              <!-- 芳华币类型奖励 -->
+              <div v-else-if="currentReward.rewardType === 3" class="points-reward">
+                <div class="reward-amount">
+                  <i class="el-icon-coin" style="color: #67c23a; font-size: 24px;"></i>
+                  <span class="amount-text">{{ rewardAmount }} 芳华币</span>
+                  <el-tag type="success" size="small">芳华币奖励</el-tag>
+                </div>
+              </div>
+
+              <!-- 转盘类型奖励 -->
+              <div v-else-if="currentReward.rewardType === 4" class="chest-reward">
+                <el-table :data="parsedRewardItems" size="small" border stripe>
+                  <el-table-column label="图标" prop="iconUrl" align="center" width="80">
+                    <template slot-scope="{row}">
+                      <el-image
+                        v-if="row.iconUrl"
+                        :src="row.iconUrl"
+                        :preview-src-list="[row.iconUrl]"
+                        fit="cover"
+                        style="width:32px;height:32px;border:1px solid #ebeef5;border-radius:4px;"
+                      />
+                      <span v-else>-</span>
+                    </template>
+                  </el-table-column>
+                  <el-table-column label="奖品名称" prop="name" align="center" />
+                  <el-table-column label="奖品类型" prop="type" align="center">
+                    <template slot-scope="{row}">
+                      <el-tag v-if="row.type" size="small">
+                        {{ getSpinItemTypeLabel(row.type) }}
+                      </el-tag>
+                      <span v-else>-</span>
+                    </template>
+                  </el-table-column>
+                  <el-table-column label="数量" prop="amount" align="center">
+                    <template slot-scope="{row}">
+                  <span v-if="row.amount">
+                    {{ row.amount }} {{ getSpinItemUnit(row.type) }}
+                  </span>
+                      <span v-else>-</span>
+                    </template>
+                  </el-table-column>
+                  <el-table-column label="概率" prop="probability" align="center">
+                    <template slot-scope="{row}">
+                      <el-tag v-if="row.probability" size="small">{{ row.probability }}</el-tag>
+                      <span v-else>-</span>
+                    </template>
+                  </el-table-column>
+                </el-table>
+                <div v-if="!parsedRewardItems || parsedRewardItems.length === 0" class="empty-tip">
+                  暂无奖励配置
+                </div>
+              </div>
+
+              <!-- 保底转盘类型奖励 -->
+              <div v-else-if="currentReward.rewardType === 5" class="chest-reward">
+                <el-table :data="parsedRewardItems" size="small" border stripe>
+                  <el-table-column label="图标" prop="iconUrl" align="center" width="80">
+                    <template slot-scope="{row}">
+                      <el-image
+                        v-if="row.iconUrl"
+                        :src="row.iconUrl"
+                        :preview-src-list="[row.iconUrl]"
+                        fit="cover"
+                        style="width:32px;height:32px;border:1px solid #ebeef5;border-radius:4px;"
+                      />
+                      <span v-else>-</span>
+                    </template>
+                  </el-table-column>
+                  <el-table-column label="奖品名称" prop="name" align="center" />
+                  <el-table-column label="奖品类型" prop="type" align="center">
+                    <template slot-scope="{row}">
+                      <el-tag v-if="row.type" size="small">
+                        {{ getSpinItemTypeLabel(row.type) }}
+                      </el-tag>
+                      <span v-else>-</span>
+                    </template>
+                  </el-table-column>
+                  <el-table-column label="数量" prop="amount" align="center">
+                    <template slot-scope="{row}">
+                  <span v-if="row.amount">
+                    {{ row.amount }} {{ getSpinItemUnit(row.type) }}
+                  </span>
+                      <span v-else>-</span>
+                    </template>
+                  </el-table-column>
+                  <el-table-column label="概率" prop="probability" align="center">
+                    <template slot-scope="{row}">
+                      <el-tag v-if="row.probability" size="small">{{ row.probability }}</el-tag>
+                      <span v-else>-</span>
+                    </template>
+                  </el-table-column>
+                  <el-table-column label="保底" prop="isGuarantee" align="center">
+                    <template slot-scope="{row}">
+                      <el-tag size="small" :type="row.isGuarantee ? 'success' : 'info'">{{ row.isGuarantee ? '是' : '否' }}</el-tag>
+                    </template>
+                  </el-table-column>
+                </el-table>
+                <div v-if="!parsedRewardItems || parsedRewardItems.length === 0" class="empty-tip">
+                  暂无奖励配置
+                </div>
+              </div>
+
+              <!-- 未知类型 -->
+              <div v-else class="unknown-reward">
+                <el-alert type="info" title="未知奖励类型" :closable="false"></el-alert>
+              </div>
+            </div>
+          </div>
+
+          <div slot="footer" class="dialog-footer">
+            <el-button @click="detailVisible = false">关 闭</el-button>
+          </div>
+        </el-dialog>
+      </div>
+    </el-dialog>
+
+    <!-- 答题奖励弹窗 -->
+    <el-dialog
+      :title="answerRewardDialog.title"
+      :visible.sync="answerRewardDialog.open"
+      width="700px"
+      append-to-body
+    >
+      <el-form :model="answerRewardDialog.answerRewardForm" v-loading="answerRewardDialog.loading" label-width="120px">
+        <div class="default-amounts">
+          <!--          <div class="amount-item">-->
+          <!--            <i class="el-icon-money amount-icon red"></i>-->
+          <!--            <span class="label">默认红包奖励</span>-->
+          <!--            <span class="value">{{answerRewardDialog.answerRewardForm.defaultRedPacketAmount || 0}} 元</span>-->
+          <!--            <el-tag :type="answerRewardDialog.answerRewardForm.redPacketRuleId ? 'info' : 'warning'" size="mini">{{ answerRewardDialog.answerRewardForm.redPacketRuleId ? '已选择配置' : '配置生效中' }}</el-tag>-->
+          <!--          </div>-->
+          <div class="amount-item">
+            <i class="el-icon-coin amount-icon green"></i>
+            <span class="label">默认小程序芳华币奖励</span>
+            <span class="value">{{answerRewardDialog.answerRewardForm.defaultPointNum || 0}} 芳华币</span>
+            <el-tag :type="'success'" size="mini">{{ '生效中' }}</el-tag>
+          </div>
+          <div class="amount-item">
+            <i class="el-icon-coin amount-icon green"></i>
+            <span class="label">默认APP小程序芳华币奖励</span>
+            <span class="value">{{answerRewardDialog.answerRewardForm.defaultAppPointNum || 0}} 芳华币</span>
+            <el-tag :type="answerRewardDialog.answerRewardForm.pointRuleId ? 'info' : 'success'" size="mini">{{ answerRewardDialog.answerRewardForm.pointRuleId ? '已选择配置' : '生效中' }}</el-tag>
+          </div>
+          <div class="amount-item">
+            <i class="el-icon-money amount-icon red"></i>
+            <span class="label">默认转盘奖励</span>
+            <span class="value" v-if="shouldShowTurntableRule">{{getTurntableRuleName(answerRewardDialog.answerRewardForm.defaultTurntableRuleId)}}</span>
+            <span class="value" v-else>-</span>
+            <el-tag :type="answerRewardDialog.answerRewardForm.turntableRuleId ? 'info' : 'success'" size="mini">{{ answerRewardDialog.answerRewardForm.turntableRuleId ? '已选择配置' : '生效中' }}</el-tag>
+          </div>
+        </div>
+        <el-form-item label="红包配置">
+          <el-select
+            ref="customSelect2"
+            v-model="answerRewardDialog.answerRewardForm.redPacketRuleId"
+            placeholder="请选择红包"
+            @click.native.stop="openRuleDialog(answerRewardDialog.answerRewardForm.redPacketRuleId, 2)"
+            clearable
+            style="width: 100%;">
+            <el-option
+              v-for="item in ruleList"
+              :key="item.id"
+              :label="item.name"
+              :value="item.id">
+            </el-option>
+          </el-select>
+        </el-form-item>
+        <el-form-item label="芳华币配置">
+          <el-select
+            ref="customSelect3"
+            v-model="answerRewardDialog.answerRewardForm.pointRuleId"
+            placeholder="请选择芳华币"
+            @click.native.stop="openRuleDialog(answerRewardDialog.answerRewardForm.pointRuleId, 3)"
+            clearable
+            style="width: 100%;">
+            <el-option
+              v-for="item in ruleList"
+              :key="item.id"
+              :label="item.name"
+              :value="item.id">
+            </el-option>
+          </el-select>
+        </el-form-item>
+        <el-form-item label="随机转盘配置">
+          <el-select
+            ref="customSelect4"
+            v-model="answerRewardDialog.answerRewardForm.turntableRuleId"
+            placeholder="请选择转盘"
+            @click.native.stop="openRuleDialog(answerRewardDialog.answerRewardForm.turntableRuleId, 4)"
+            clearable
+            style="width: 100%;">
+            <el-option
+              v-for="item in ruleList"
+              :key="item.id"
+              :label="item.name"
+              :value="item.id">
+            </el-option>
+          </el-select>
+        </el-form-item>
+        <el-form-item label="保底转盘配置">
+          <el-select
+            ref="customSelect5"
+            v-model="answerRewardDialog.answerRewardForm.turntableGuaranteeRuleId"
+            placeholder="请选择保底转盘"
+            @click.native.stop="openRuleDialog(answerRewardDialog.answerRewardForm.turntableGuaranteeRuleId, 5)"
+            clearable
+            style="width: 100%;">
+            <el-option
+              v-for="item in ruleList"
+              :key="item.id"
+              :label="item.name"
+              :value="item.id">
+            </el-option>
+          </el-select>
+        </el-form-item>
+      </el-form>
+
+      <div slot="footer" class="dialog-footer">
+        <el-button @click="answerRewardDialog.open=false">取 消</el-button>
+        <el-button type="primary" :loading="saving" :disabled="saving" @click="submitAnswerReward">确 定</el-button>
+      </div>
+    </el-dialog>
+
+
   </div>
 </template>
 
 <script>
-import {getVideoListByCourseId, updatePacketMoney, updateUserCourseVideoUpdate} from "@/api/course/userCourseVideo";
+import {
+  updatePacketMoney,
+  getVideoListByCourseId,
+  getAnswerRewardConfig,
+  changeAnswerRewardConfig,
+} from "@/api/course/userCourseVideo";
 import userCourseVideoDetails from '../../components/course/userCourseVideoDetails.vue';
+import {listReward, getReward, listByIds} from "@/api/course/reward";
+import { listRelation,  updateRelation, } from "@/api/course/relation";
 import {createLinkUrl, createRoomLinkUrl, queryQwIds} from "@/api/course/sopCourseLink";
 import AutoTagDialog from "@/views/components/tag/AutoTagDialog.vue";
 import {addTag, updateTag} from "@/api/tag/api";
@@ -143,14 +514,52 @@ export default {
       userCourseVideoDetails,
       AutoTagDialog
     },
-    props: {
-      video: {
-        type: Object,
-        required: true,
-      },
+  props: {
+    video: {
+      type: Object,
+      required: false,  // 改为非必需
+      default: () => ({})  // 提供默认值
     },
+  },
     data() {
       return {
+        answerRewardDialog: {
+          open: false,
+          title: '设置答题奖励',
+          loading: false,
+          answerRewardForm: {
+            videoId: null,
+            redPacketRuleId: null,
+            pointRuleId: null,
+            turntableRuleId: null,
+            turntableGuaranteeRuleId: null,
+            defaultRedPacketAmount: null,
+            defaultPointNum: null,
+            defaultAppPointNum: null,
+            defaultTurntableRuleId: null
+          }
+        },
+        ruleList: [],
+        // 转盘奖励-奖励类型选项
+        spinItemTypeOptions: [],
+        saving: false,
+        currentReward: {},
+        rewardOpen: false,
+        rewardType: null,
+        rewardLoading: false,
+        rewardList: [],
+        selectedRewards: [], // 选中的奖励项
+        rewardsRelation: [], // 选中的奖励项
+        rewardTotal: 0,
+        rewardQueryParams: {
+          pageNum: 1,
+          pageSize: 10,
+          videoId: null,
+          rewardType:1
+        },
+        statusOptions:[],
+        rewardTypeOptions:[],
+        detailVisible: false,
         currentRow: null,
         // 假设这里有当前课程小节的数据传入,里面含id等
         tagGroups: [],
@@ -256,12 +665,298 @@ export default {
         }
       }
     },
+  computed: {
+    // 解析奖励项数据
+    parsedRewardItems() {
+      if (!this.currentReward.actualRewards) return null;
+      try {
+        const parsed = JSON.parse(this.currentReward.actualRewards);
+        return Array.isArray(parsed) ? parsed : null;
+      } catch (e) {
+        console.error("解析奖励内容失败", e);
+        return null;
+      }
+    },
+
+    // 获取红包或芳华币金额
+    rewardAmount() {
+      if (!this.currentReward.actualRewards) return 0;
+
+      try {
+        const parsed = JSON.parse(this.currentReward.actualRewards);
+        if (this.currentReward.rewardType === 2) {
+          return parsed.amount || parsed.money || 0;
+        } else if (this.currentReward.rewardType === 3) {
+          return parsed.points || parsed.score || 0;
+        }
+        return 0;
+      } catch (e) {
+        return 0;
+      }
+    },
+
+    // 只有在dialog打开时才显示转盘规则名称
+    shouldShowTurntableRule() {
+      return this.answerRewardDialog.open && this.answerRewardDialog.answerRewardForm.defaultTurntableRuleId
+    }
+  },
     created() {
       this.getDicts("sys_course_temp_type").then(response => {
         this.typeOptions = response.data;
       });
+      this.getDicts("sys_reward_type").then((response) => {
+        this.rewardTypeOptions = response.data;
+      });
+      this.getDicts("sys_user_status").then((response) => {
+        this.statusOptions = response.data;
+      });
+      this.getDicts("spin_reward_type").then((response) => {
+        this.spinItemTypeOptions = response.data;
+      });
     },
     methods: {
+      getTurntableRuleName(ruleId) {
+        if (!ruleId) return '-'
+
+        // 从ruleList中查找对应的规则名称
+        const rule = this.ruleList.find(item => item.id === ruleId)
+        if (rule) {
+          return rule.name
+        }
+
+        // 如果ruleList中没有,则异步获取
+        getReward(ruleId).then(response => {
+          const {code,data} = response
+          if (code === 200 && data) {
+            if (!this.ruleList.find(item => item.id === ruleId)) {
+              this.ruleList.push(data)
+            }
+            return data.name
+          }
+        }).catch(() => {
+          console.error('获取转盘规则失败:', ruleId)
+        })
+
+        return '加载中...'
+      },
+      // 查看奖励详情
+      handleViewReward(row) {
+        this.currentReward = { ...row };
+        this.detailVisible = true;
+      },
+      updateReward(row) {
+        this.currentVideoId = row.videoId;
+        this.rewardQueryParams.videoId = row.videoId;
+        this.rewardOpen = true;
+        this.rewardType = 1
+        this.getRewardList();
+
+        // 获取已设置的奖励(如果有)
+        this.getCurrentRewards(row.videoId);
+      },
+
+      // 获取奖励列表
+      getRewardList() {
+        this.rewardLoading = true;
+        listRelation({
+          videoSectionId: this.currentVideoId,
+          type: 1
+        }).then(response => {
+          this.rewardsRelation = response.rows;
+        }).catch(() => {
+          this.rewardLoading = false;
+        });
+        listReward(this.rewardQueryParams).then(response => {
+          this.rewardList = response.rows;
+          this.rewardTotal = response.total;
+          this.rewardLoading = false;
+          // 设置已选中的奖励
+          this.$nextTick(() => {
+            this.rewardList.forEach(row => {
+              if (this.rewardsRelation.some(item => item.rewardId === row.id)) {
+                this.$refs.rewardTable.toggleRowSelection(row, true);
+              }
+            });
+          });
+        }).catch(() => {
+          this.rewardLoading = false;
+        });
+      },
+
+      // 获取当前视频已设置的奖励
+      getCurrentRewards(videoId) {
+        // 这里假设有一个API可以获取视频已设置的奖励
+        // 实际实现中需要根据后端API调整
+        this.selectedRewards = []; // 先清空
+        // 模拟数据,实际应从API获取
+        /*
+        getVideoRewards(videoId).then(response => {
+          this.selectedRewards = response.data || [];
+        });
+        */
+      },
+
+      // 处理奖励选择变化
+      handleRewardSelectionChange(selection) {
+        // 限制最多选择两项
+        if (selection.length > 2) {
+          this.$refs.rewardTable.clearSelection();
+          // 保留前两项
+          const limitedSelection = selection.slice(0, 2);
+          limitedSelection.forEach(item => {
+            this.$refs.rewardTable.toggleRowSelection(item, true);
+          });
+          this.selectedRewards = limitedSelection;
+          this.$message.warning('最多只能选择两个奖励项目');
+        } else {
+          this.selectedRewards = selection;
+        }
+      },
+      updateAnswerReward(row) {
+        this.answerRewardDialog.answerRewardForm = {}
+        this.answerRewardDialog.open = true
+        this.answerRewardDialog.loading = true
+        getAnswerRewardConfig(row.videoId).then(response => {
+          this.answerRewardDialog.answerRewardForm = response.data
+          this.answerRewardDialog.answerRewardForm.videoId = row.videoId
+          // 查询已配置列表
+          let ids = []
+          for (const [key, value] of Object.entries(response.data)) {
+            if (key === "redPacketRuleId" || key === "pointRuleId" ||
+              key === "turntableRuleId" || key === "turntableGuaranteeRuleId")
+              if (value) {
+                ids.push(value)
+              }
+          }
+          if (ids.length > 0) {
+            listByIds(ids).then(response => {
+              this.ruleList = response.data
+              this.answerRewardDialog.loading = false
+            })
+          } else {
+            this.answerRewardDialog.loading = false
+          }
+        })
+      },
+      submitAnswerReward(){
+        this.saving = true
+        changeAnswerRewardConfig(this.answerRewardDialog.answerRewardForm).then(response => {
+          const {code, msg} = response
+          if (code === 200){
+            this.msgSuccess("修改成功");
+            this.getList()
+            this.answerRewardDialog.open = false
+          } else {
+            this.msgError(msg);
+          }
+          this.saving = false
+        })
+      },
+      openRuleDialog(id, type) {
+        this.$nextTick(() => {
+          this.$refs['customSelect' + type]?.blur();
+        });
+
+        this.rewardList =[]
+        this.rewardOpen = true
+        this.rewardLoading = true
+        listReward({"rewardType": type, "status": 1}).then(response => {
+          const {rows, total} = response
+          this.rewardList = rows
+          this.rewardTotal = total
+          // 根据传入的规则ID,默认选中对应项
+          const preselected = this.rewardList.filter(item => item.id === id)
+          this.selectedRewards = preselected
+          this.$nextTick(() => {
+            if (this.$refs.rewardTable && preselected.length > 0) {
+              // 先清空再选中,避免残留状态
+              this.$refs.rewardTable.clearSelection()
+              preselected.forEach(row => this.$refs.rewardTable.toggleRowSelection(row, true))
+            }
+          })
+          this.rewardLoading = false
+        })
+
+        this.rewardType = type
+      },
+      // 获取转盘奖励类型标签
+      getSpinItemTypeLabel(type) {
+        const option = this.spinItemTypeOptions.find(item => item.dictValue === type.toString());
+        return option ? option.dictLabel : type;
+      },
+      // 获取转盘奖励类型单位
+      getSpinItemUnit(type) {
+        const option = this.spinItemTypeOptions.find(item => item.dictValue === type.toString());
+        const label = option ? option.dictLabel : '';
+        if (label.includes("红包")) {
+          return '元';
+        } else if (label.includes("芳华币")) {
+          return '芳华币';
+        } else {
+          return '';
+        }
+      },
+      // 检查是否可选
+      checkSelectable(row, index) {
+        // 如果已选择两项且当前行未被选中,则禁用选择
+        return this.selectedRewards.length < 2 ||
+          this.selectedRewards.some(item => item.rewardId === row.rewardId);
+      },
+
+      // 清空选择
+      clearSelection() {
+        this.$refs.rewardTable.clearSelection();
+        this.selectedRewards = [];
+      },
+
+      // 提交奖励选择
+      submitRewardSelection() {
+        if (this.selectedRewards.length === 0) {
+          this.$message.warning('请至少选择一个奖励项目');
+          return;
+        }
+
+        const rewardIds = this.selectedRewards.map(item => item.id);
+        const rewardIdList = this.rewardsRelation.map(item => item.rewardId);
+        if (this.rewardType === 1) {
+          updateRelation({
+            videoSectionId: this.currentVideoId,
+            rewardIds: rewardIds,
+            rewardIdList: rewardIdList
+          }).then(response => {
+            if (response.code === 200) {
+              this.$message.success('奖励设置成功');
+            }
+          });
+        } else {
+          const ruleId = rewardIds[0]
+          if (this.rewardType === 2) {
+            this.answerRewardDialog.answerRewardForm.redPacketRuleId = ruleId
+          }
+          if (this.rewardType === 3) {
+            this.answerRewardDialog.answerRewardForm.pointRuleId = ruleId
+          }
+          if (this.rewardType === 4) {
+            this.answerRewardDialog.answerRewardForm.turntableRuleId = ruleId
+          }
+          if (this.rewardType === 5) {
+            this.answerRewardDialog.answerRewardForm.turntableGuaranteeRuleId = ruleId
+          }
+
+          let rule = this.rewardList.find(item => item.id === ruleId)
+          if (!this.ruleList.some(item => item.id === rule.id)) {
+            this.ruleList.push(rule)
+          }
+        }
+        this.rewardOpen = false
+      },
+
+      // 处理奖励弹窗关闭
+      handleRewardDialogClose() {
+        this.selectedRewards = [];
+        this.currentVideoId = null;
+      },
+
       formatOptionLabel(item) {
         return item.corpName ? `${item.qwUserName} (${item.corpName})`  : item.qwUserName;
       },
@@ -544,5 +1239,32 @@ export default {
        line-height: 150px;
        text-align: center;
      }
-
+  .default-amounts {
+    display: flex;
+    flex-direction: column;
+    gap: 8px;
+    margin-bottom: 8px;
+  }
+  .amount-item {
+    display: inline-flex;
+    align-items: center;
+    gap: 8px;
+    padding: 8px 10px;
+    background: #fafafa;
+    border: 1px solid #ebeef5;
+    border-radius: 4px;
+  }
+  .amount-icon {
+    font-size: 18px;
+  }
+  .amount-icon.red { color: #e6a23c; }
+  .amount-icon.green { color: #67c23a; }
+  .amount-item .label {
+    color: #606266;
+  }
+  .amount-item .value {
+    font-weight: 600;
+    color: #303133;
+    margin-right: 6px;
+  }
 </style>

+ 328 - 0
src/views/course/relation/index.vue

@@ -0,0 +1,328 @@
+<template>
+  <div class="app-container">
+    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
+      <el-form-item label="显示顺序" prop="displayOrder">
+        <el-input
+          v-model="queryParams.displayOrder"
+          placeholder="请输入显示顺序"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="状态 (0:禁用, 1:启用)" prop="status">
+        <el-select v-model="queryParams.status" placeholder="请选择状态 (0:禁用, 1:启用)" clearable size="small">
+          <el-option label="请选择字典生成" value="" />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="创建人ID" prop="createId">
+        <el-input
+          v-model="queryParams.createId"
+          placeholder="请输入创建人ID"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="创建人ID" prop="updateId">
+        <el-input
+          v-model="queryParams.updateId"
+          placeholder="请输入创建人ID"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </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:relation: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:relation: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:relation: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:relation:export']"
+        >导出</el-button>
+      </el-col>
+      <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
+    </el-row>
+
+    <el-table border v-loading="loading" :data="relationList" @selection-change="handleSelectionChange">
+      <el-table-column type="selection" width="55" align="center" />
+      <el-table-column label="关联关系ID" align="center" prop="id" />
+      <el-table-column label="关联reward_.id" align="center" prop="rewardId" />
+      <el-table-column label="关联视频小节ID" align="center" prop="videoSectionId" />
+      <el-table-column label="显示顺序" align="center" prop="displayOrder" />
+      <el-table-column label="状态 (0:禁用, 1:启用)" align="center" prop="status" />
+      <el-table-column label="创建人ID" align="center" prop="createId" />
+      <el-table-column label="创建人ID" align="center" prop="updateId" />
+      <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:relation:edit']"
+          >修改</el-button>
+          <el-button
+            size="mini"
+            type="text"
+            icon="el-icon-delete"
+            @click="handleDelete(scope.row)"
+            v-hasPermi="['course:relation: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="displayOrder">
+          <el-input v-model="form.displayOrder" placeholder="请输入显示顺序" />
+        </el-form-item>
+        <el-form-item label="状态 (0:禁用, 1:启用)">
+          <el-radio-group v-model="form.status">
+            <el-radio label="1">请选择字典生成</el-radio>
+          </el-radio-group>
+        </el-form-item>
+        <el-form-item label="创建人ID" prop="createId">
+          <el-input v-model="form.createId" placeholder="请输入创建人ID" />
+        </el-form-item>
+        <el-form-item label="创建人ID" prop="updateId">
+          <el-input v-model="form.updateId" placeholder="请输入创建人ID" />
+        </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 { listRelation, getRelation, delRelation, addRelation, updateRelation, exportRelation } from "@/api/course/relation";
+
+export default {
+  name: "Relation",
+  data() {
+    return {
+      // 遮罩层
+      loading: true,
+      // 导出遮罩层
+      exportLoading: false,
+      // 选中数组
+      ids: [],
+      // 非单个禁用
+      single: true,
+      // 非多个禁用
+      multiple: true,
+      // 显示搜索条件
+      showSearch: true,
+      // 总条数
+      total: 0,
+      // 奖励与视频小节关联关系表格数据
+      relationList: [],
+      // 弹出层标题
+      title: "",
+      // 是否显示弹出层
+      open: false,
+      // 查询参数
+      queryParams: {
+        pageNum: 1,
+        pageSize: 10,
+        rewardId: null,
+        videoSectionId: null,
+        displayOrder: null,
+        status: null,
+        createId: null,
+        updateId: null,
+      },
+      // 表单参数
+      form: {},
+      // 表单校验
+      rules: {
+        rewardId: [
+          { required: true, message: "关联reward_.id不能为空", trigger: "blur" }
+        ],
+        videoSectionId: [
+          { required: true, message: "关联视频小节ID不能为空", trigger: "blur" }
+        ],
+        displayOrder: [
+          { required: true, message: "显示顺序不能为空", trigger: "blur" }
+        ],
+        status: [
+          { required: true, message: "状态 (0:禁用, 1:启用)不能为空", trigger: "blur" }
+        ],
+        createTime: [
+          { required: true, message: "创建时间不能为空", trigger: "blur" }
+        ],
+      }
+    };
+  },
+  created() {
+    this.getList();
+  },
+  methods: {
+    /** 查询奖励与视频小节关联关系列表 */
+    getList() {
+      this.loading = true;
+      listRelation(this.queryParams).then(response => {
+        this.relationList = response.rows;
+        this.total = response.total;
+        this.loading = false;
+      });
+    },
+    // 取消按钮
+    cancel() {
+      this.open = false;
+      this.reset();
+    },
+    // 表单重置
+    reset() {
+      this.form = {
+        id: null,
+        rewardId: null,
+        videoSectionId: null,
+        displayOrder: null,
+        status: 0,
+        createId: null,
+        createTime: null,
+        updateId: null,
+        updateTime: 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.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
+      getRelation(id).then(response => {
+        this.form = response.data;
+        this.open = true;
+        this.title = "修改奖励与视频小节关联关系";
+      });
+    },
+    /** 提交按钮 */
+    submitForm() {
+      this.$refs["form"].validate(valid => {
+        if (valid) {
+          if (this.form.id != null) {
+            updateRelation(this.form).then(response => {
+              this.msgSuccess("修改成功");
+              this.open = false;
+              this.getList();
+            });
+          } else {
+            addRelation(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 delRelation(ids);
+        }).then(() => {
+          this.getList();
+          this.msgSuccess("删除成功");
+        }).catch(() => {});
+    },
+    /** 导出按钮操作 */
+    handleExport() {
+      const queryParams = this.queryParams;
+      this.$confirm('是否确认导出所有奖励与视频小节关联关系数据项?', "警告", {
+          confirmButtonText: "确定",
+          cancelButtonText: "取消",
+          type: "warning"
+        }).then(() => {
+          this.exportLoading = true;
+          return exportRelation(queryParams);
+        }).then(response => {
+          this.download(response.msg);
+          this.exportLoading = false;
+        }).catch(() => {});
+    }
+  }
+};
+</script>