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

多选课程绑定销售公司/直播答题

三七 преди 4 дни
родител
ревизия
fed782ad0c

+ 4 - 4
.env.prod-sxsm

@@ -1,15 +1,15 @@
 # 页面标题
-VUE_APP_TITLE =三猫总管理系统
+VUE_APP_TITLE =三猫总管理系统
 # 首页菜单标题
 VUE_APP_TITLE_INDEX =三猫网
 # 公司名称
-VUE_APP_COMPANY_NAME =西安挑宝益康医药连锁有限公司丈八西路第二分公司
+VUE_APP_COMPANY_NAME =西安秦塞升大药房医药有限公司
 # ICP备案号
-VUE_APP_ICP_RECORD =陕ICP备2023000555号-8
+VUE_APP_ICP_RECORD =陕ICP备2026001603号-7
 # ICP网站访问地址
 VUE_APP_ICP_URL =https://beian.miit.gov.cn
 # 网站LOG
-VUE_APP_LOG_URL =@/assets/logo/sxsm.jpg
+VUE_APP_LOG_URL =@/assets/logo/sxsm2.jpg
 # 存储桶配置
 VUE_APP_OBS_ACCESS_KEY_ID = K2UTJGIN7UTZJR2XMXYG
 # 存储桶配置

+ 17 - 0
src/api/course/userCourse.js

@@ -44,6 +44,23 @@ export function updateUserCourse(data) {
   })
 }
 
+export function publicEditCompany(data) {
+  return request({
+    url: '/course/userCourse/publicEditCompany',
+    method: 'post',
+    data: data
+  })
+}
+
+
+export function publicRemoveEditCompany(data) {
+  return request({
+    url: '/course/userCourse/publicRemoveEditCompany',
+    method: 'post',
+    data: data
+  })
+}
+
 //统一修改课程红包
 export function updateUserCourseRedPage(data) {
   return request({

BIN
src/assets/logo/sxsm2.jpg


+ 13 - 5
src/views/company/companyUser/indexAll.vue

@@ -352,7 +352,7 @@
 <!--              <el-input v-model="form.userName" placeholder="请输入员工后台账号" />-->
 <!--            </el-form-item>-->
             <el-form-item label="员工姓名" prop="nickName">
-              <el-input v-model="form.nickName" placeholder="请输入员工后台昵称" />
+              <el-input v-model="form.nickName" placeholder="请输入员工后台昵称" @blur="trimInput('nickName', form)" />
             </el-form-item>
           </el-col>
           <el-col :span="12">
@@ -364,24 +364,24 @@
         <el-row>
           <el-col :span="12">
             <el-form-item label="手机号码" prop="phonenumber">
-              <el-input v-model="form.phonenumber" placeholder="请输入手机号码" maxlength="11" />
+              <el-input v-model="form.phonenumber" placeholder="请输入手机号码" maxlength="11" @blur="trimInput('phonenumber', form)" />
             </el-form-item>
           </el-col>
           <el-col :span="12">
             <el-form-item label="邮箱" prop="email">
-              <el-input v-model="form.email" placeholder="请输入邮箱" maxlength="50" />
+              <el-input v-model="form.email" placeholder="请输入邮箱" maxlength="50" @blur="trimInput('email', form)" />
             </el-form-item>
           </el-col>
         </el-row>
         <el-row>
           <el-col :span="12">
             <el-form-item label="员工账号" prop="userName">
-              <el-input v-model="form.userName" placeholder="请输入员工工号" />
+              <el-input v-model="form.userName" placeholder="请输入员工工号" @blur="trimInput('userName', form)" />
             </el-form-item>
           </el-col>
           <el-col :span="12">
             <el-form-item v-if="form.userId == undefined" label="用户密码" prop="password">
-              <el-input v-model="form.password" placeholder="请输入用户密码" type="password" show-password />
+              <el-input v-model="form.password" placeholder="请输入用户密码" type="password" show-password  @blur="trimInput('password', form)" />
             </el-form-item>
           </el-col>
         </el-row>
@@ -933,6 +933,14 @@ export default {
     });
   },
   methods: {
+
+    trimInput(key, obj) {
+      const target = obj || this;
+      if (target[key] && typeof target[key] === 'string') {
+        target[key] = target[key].trim();
+      }
+    },
+
     onDomainBlur() {
       if (this.form.domain != null) {
         let value = this.form.domain.trim();

+ 15 - 15
src/views/course/coursePlaySourceConfig/index.vue

@@ -40,21 +40,21 @@
           @keyup.enter.native="handleQuery"
         />
       </el-form-item>
-            <el-form-item label="状态" prop="status">
-        <el-select
-          v-model="queryParams.status"
-          placeholder="请选择状态"
-          clearable
-          size="small"
-        >
-          <el-option
-            v-for="item in statusOptions"
-            :key="item.value"
-            :label="item.label"
-            :value="item.value"
-          />
-        </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="item in statusOptions"-->
+<!--            :key="item.value"-->
+<!--            :label="item.label"-->
+<!--            :value="item.value"-->
+<!--          />-->
+<!--        </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>

+ 126 - 2
src/views/course/userCourse/index.vue

@@ -114,6 +114,31 @@
         >导出
         </el-button>
       </el-col>
+
+      <el-col :span="1.5">
+        <el-button
+          type="primary"
+          plain
+          icon="el-icon-edit"
+          size="mini"
+          :disabled="multiple"
+          @click="handleCourseEdit"
+          v-hasPermi="['course:userCourse:publicEdit']"
+        >多选课程绑定销售公司
+        </el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button
+          type="danger"
+          plain
+          icon="el-icon-edit"
+          size="mini"
+          :disabled="multiple"
+          @click="handleRemoveCourseEdit"
+          v-hasPermi="['course:userCourse:publicEdit']"
+        >多选课程移除绑定销售公司
+        </el-button>
+      </el-col>
       <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
     </el-row>
 
@@ -445,6 +470,27 @@
       :title="show.title" :visible.sync="show.open" append-to-body>
       <userCourseCatalogDetails ref="userCourseCatalogDetails"/>
     </el-drawer>
+
+
+    <el-dialog :title="syncTemplateOpen.title" :visible.sync="syncTemplateOpen.open" width="500px" append-to-body>
+      <el-form>
+        <el-form-item label="销售公司">
+          <el-select v-model="syncTemplateOpen.companyId" placeholder="请选择销售公司" style="width: 100%">
+            <el-option
+              v-for="item in companyList"
+              :key="item.companyId"
+              :label="item.companyName"
+              :value="item.companyId"
+            />
+          </el-select>
+        </el-form-item>
+      </el-form>
+      <div slot="footer" class="dialog-footer">
+        <el-button @click="syncTemplateOpen.open = false">取 消</el-button>
+        <el-button type="primary" @click="handleSyncCompanyId">确 定</el-button>
+      </div>
+    </el-dialog>
+
   </div>
 </template>
 
@@ -460,7 +506,7 @@ import {
   copyUserCourse,
   putOn,
   pullOff, updateUserCourseRedPage,
-  editConfig
+  editConfig, publicEditCompany, publicRemoveEditCompany
 } from '@/api/course/userCourse'
 
 import {getSelectableRange} from "@/api/qw/sopTemp";
@@ -471,9 +517,10 @@ import ImageUpload from '@/components/ImageUpload/index';
 import {listBySearch} from "@/api/course/userTalent";
 import userCourseCatalogDetails from '../../components/course/userCourseCatalogDetails.vue';
 import {getAllCourseCategoryList, getCatePidList, getCateListByPid} from "@/api/course/userCourseCategory";
-import {allList} from "@/api/company/company";
+import { allList, getCompanyList } from '@/api/company/company'
 import VideoUpload from '@/components/VideoUpload/index.vue'
 import { getConfigByKey } from '@/api/system/config'
+import { syncTemplateByCompanyId } from '@/api/course/userCourseVideo'
 export default {
   name: "UserCourse",
   components: {
@@ -493,6 +540,15 @@ export default {
   },
   data() {
     return {
+      companyList:[],
+      syncTemplateOpen:{
+        open:false,
+        title:null,
+        companyId:null,
+        type:null,
+      },
+
+
       /** 与后端 is_private 一致:1-课程管理(私域),0-公域课程管理 */
       pageIsPrivate: 1,
       talentParam: {
@@ -673,6 +729,7 @@ export default {
     };
   },
   created() {
+    this.getAllCompanyList();
     this.getList();
     getConfigByKey('randomRedpacket:config').then(res=>{
         console.log("res::")
@@ -720,6 +777,13 @@ export default {
     });
   },
   methods: {
+
+    getAllCompanyList(){
+      getCompanyList().then(res=>{
+        this.companyList=res.data
+      })
+    },
+
     selectTalent() {
 
     },
@@ -996,6 +1060,66 @@ export default {
       }).catch(() => {
       });
     },
+
+    // 1增 2移除
+    handleCourseEdit(){
+      this.syncTemplateOpen.open=true;
+      this.syncTemplateOpen.type=1;
+      this.syncTemplateOpen.title="选择要【添加】的销售公司";
+    },
+
+    handleRemoveCourseEdit(){
+      this.syncTemplateOpen.open=true;
+      this.syncTemplateOpen.type=2;
+      this.syncTemplateOpen.title="选择要【移除】的销售公司";
+    },
+
+    /** 课程快捷绑定销售公司*/
+    handleSyncCompanyId() {
+
+      const companyId = this.syncTemplateOpen.companyId;
+      if (!companyId) {
+        this.$message.warning("请选择销售公司");
+        return;
+      }
+
+      const param={
+        courseIds : this.ids,
+        companyId: companyId
+      }
+
+      if (this.syncTemplateOpen.type==1){
+        this.$confirm('确定将选择的课程绑定到销售公司', "确认", {
+          confirmButtonText: "确定",
+          cancelButtonText: "取消",
+          type: "warning"
+        }).then(function () {
+          return publicEditCompany(param);
+        }).then(() => {
+          this.syncTemplateOpen.open = false;
+          this.getList();
+          this.msgSuccess("绑定成功");
+        }).catch(() => {
+        });
+      }else if (this.syncTemplateOpen.type==2){
+        this.$confirm('确定将选择的课程从销售公司移除', "确认", {
+          confirmButtonText: "确定",
+          cancelButtonText: "取消",
+          type: "warning"
+        }).then(function () {
+          return publicRemoveEditCompany(param);
+        }).then(() => {
+          this.syncTemplateOpen.open = false;
+          this.getList();
+          this.msgSuccess("移除成功");
+        }).catch(() => {
+        });
+      }
+
+
+    },
+
+
     /** 导出按钮操作 */
     handleExport() {
       const queryParams = { ...this.queryParams, isPrivate: this.pageIsPrivate };

+ 335 - 10
src/views/live/liveConfig/watchReward.vue

@@ -1,5 +1,5 @@
 <template >
-  <div v-loading="loading">
+  <div  class="watch-reward-page" v-loading="loading">
     <!-- 提示信息 -->
     <div class="tip-message" >
       设置观看奖励,用户达到直播观看时长后可领取奖励
@@ -88,6 +88,39 @@
 <!--        </el-form-item>-->
 
 
+        <div v-if="watchRewardForm.participateCondition === '3'">
+          <div class="section-title">直播红包答题设置</div>
+          <el-form-item label="可答题次数">
+            <el-input-number
+              v-model="watchRewardForm.answerAttempts"
+              :min="1"
+              :precision="0"
+              placeholder="请输入可答题次数"
+              style="width: 200px;"
+            ></el-input-number>
+          </el-form-item>
+          <el-button type="primary" plain icon="el-icon-plus" style="margin-bottom: 1%" @click="openQuestionDialog">选择试题</el-button>
+          <div v-if="selectedQuestionList.length > 0" class="selected-question-card-list">
+            <div
+              v-for="row in selectedQuestionList"
+              :key="row.id"
+              class="selected-question-card"
+            >
+              <div class="selected-question-card-body">
+                <div class="selected-question-card-title">{{ row.title }}</div>
+                <div v-if="row.answer" class="selected-question-card-answer">答案:{{ row.answer }}</div>
+                <div class="selected-question-card-meta">
+                  <el-tag :type="row.type === 1 ? 'primary' : 'success'" size="small">{{ row.type === 1 ? '单选题' : '多选题' }}</el-tag>
+                  <span class="selected-question-card-creator">{{ row.createBy }}</span>
+                  <span class="selected-question-card-time">{{ row.createTime }}</span>
+                </div>
+              </div>
+              <el-button type="text" class="selected-question-card-remove" @click="removeSelectedQuestion(row)">移除</el-button>
+            </div>
+          </div>
+
+        </div>
+
         <div v-if="watchRewardForm.participateCondition === '3'">
           <div class="section-title">直播红包设置</div>
           <!-- 红包金额 -->
@@ -130,7 +163,7 @@
               </el-checkbox-group>
               <el-input-number v-if="item.rewardTypes.includes('1')" v-model="item.redPacketAmount" :min="0.1" :max="10" :step="0.1" style="width: 140px; margin-left: 15px;"></el-input-number>
               <el-input v-if="item.rewardTypes.includes('2')" v-model="item.scoreAmount" placeholder="积分值" style="width: 140px; margin-left: 15px;"></el-input>
-              <el-select v-if="item.rewardTypes.includes('3')" v-model="item.couponId" placeholder="请选择核销卷" clearable style="width: 200px; margin-left: 15px;">
+              <el-select v-if="item.rewardTypes.includes('3')" v-model="item.couponIds" placeholder="请选择核销卷" clearable multiple style="width: 200px; margin-left: 15px;">
                 <el-option
                   v-for="c in couponList"
                   :key="c.id"
@@ -255,6 +288,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>
+      <div v-loading="questionLoading" class="question-card-list">
+        <div
+          v-for="row in questionOptionList"
+          :key="row.id"
+          class="question-card"
+          :class="{ 'is-selected': isQuestionSelected(row.id) }"
+          @click="toggleQuestionCard(row)"
+        >
+          <div class="question-card-check">
+            <i v-if="isQuestionSelected(row.id)" class="el-icon-success" style="color: #409EFF; font-size: 20px;"></i>
+            <span v-else class="question-card-check-empty"></span>
+          </div>
+          <div class="question-card-body">
+            <div class="question-card-title">{{ row.title }}</div>
+            <div v-if="row.answer" class="question-card-answer">答案:{{ row.answer }}</div>
+            <div class="question-card-meta">
+              <el-tag :type="row.type === 1 ? 'primary' : 'success'" size="small">{{ row.type === 1 ? '单选' : '多选' }}</el-tag>
+              <span class="question-card-creator">{{ row.createBy }}</span>
+              <span class="question-card-time">{{ row.createTime }}</span>
+            </div>
+          </div>
+        </div>
+        <div v-if="!questionLoading && questionOptionList.length === 0" class="question-card-empty">暂无数据</div>
+      </div>
+      <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>
 
@@ -262,6 +357,7 @@
 import {addConfig, getConfig, updateConfig} from "@/api/live/liveQuestionLive";
 import { listStoreCouponIssue } from "@/api/live/liveCouponIssue";
 import { options as courserCouponOptions } from '@/api/his/courserCoupon'
+import { getCourseQuestionBank, listCourseQuestionBank } from '@/api/course/courseQuestionBank'
 
 export default {
   data() {
@@ -301,7 +397,7 @@ export default {
         // 是否录播可领取奖励
         recordRedPacketEnabled: false,
         // 录播奖励配置列表(前端展示用)
-        recordTimeRanges: [{ range: null, rewardTypes: ['1'], redPacketAmount: '', scoreAmount: '', couponId: '' }],
+        recordTimeRanges: [{ range: null, rewardTypes: ['1'], redPacketAmount: '', scoreAmount: '', couponIds: [] }],
         // 录播奖励配置字符串(提交后端用)
         recordTimeRangeStr: '',
         // // 最大领取人数
@@ -314,7 +410,11 @@ export default {
         // 完课率要求(1-100)
         completionRate: 90,
         // 连续完课积分配置(第1-10天)
-        pointsConfig: [100, 110, 120, 130, 140, 150, 160, 170, 180, 200]
+        pointsConfig: [100, 110, 120, 130, 140, 150, 160, 170, 180, 200],
+        // 可答题次数
+        answerAttempts: 3,
+        // 今日问题ID(逗号分隔)
+        finishQuestionIds: '',
       },
       rules:{
         participateCondition:[
@@ -406,6 +506,20 @@ export default {
       timeRangeOverlap: false,
       // 核销卷列表(couponType=3)
       couponList: [],
+      //答题
+      questionDialogVisible: false,
+      questionLoading: false,
+      questionSearchTitle: '',
+      questionOptionList: [],
+      questionOptionTotal: 0,
+      questionQueryParams: {
+        pageNum: 1,
+        pageSize: 10,
+        title: null,
+        status: 1
+      },
+      selectedQuestionList: [],
+      tempSelectedQuestions: []
     };
   },
   watch: {
@@ -436,6 +550,7 @@ export default {
     this.getCouponList();
   },
   methods: {
+
     // 获取核销卷列表(couponType=3)
     getCouponList() {
 
@@ -448,7 +563,7 @@ export default {
       });
     },
     addRecordTimeRange() {
-      this.watchRewardForm.recordTimeRanges.push({ range: null, rewardTypes: ['1'], redPacketAmount: '', scoreAmount: '', couponId: '' })
+      this.watchRewardForm.recordTimeRanges.push({ range: null, rewardTypes: ['1'], redPacketAmount: '', scoreAmount: '', couponIds: [] })
     },
     removeRecordTimeRange(index) {
       this.watchRewardForm.recordTimeRanges.splice(index, 1)
@@ -520,16 +635,20 @@ export default {
           if (this.watchRewardForm.recordTimeRangeStr) {
             this.watchRewardForm.recordTimeRanges = this.watchRewardForm.recordTimeRangeStr.split(',').map(item => {
               const parts = item.split('-')
-              return { range: [parts[0], parts[1]], rewardTypes: parts[2] ? parts[2].split('|') : ['1'], redPacketAmount: parts[3] || '', scoreAmount: parts[4] || '', couponId: parts[5] ? Number(parts[5]) : '' }
+              return { range: [parts[0], parts[1]], rewardTypes: parts[2] ? parts[2].split('|') : ['1'], redPacketAmount: parts[3] || '', scoreAmount: parts[4] || '', couponIds: parts[5] ? parts[5].split('~').filter(Boolean).map(Number) : [] }
             })
           } else {
-            this.watchRewardForm.recordTimeRanges = [{ range: null, rewardTypes: ['1'], redPacketAmount: '', scoreAmount: '', couponId: '' }]
+            this.watchRewardForm.recordTimeRanges = [{ range: null, rewardTypes: ['1'], redPacketAmount: '', scoreAmount: '', couponIds: [] }]
           }
           if(this.watchRewardForm.isRedPackageBalanceDeduction == null){
             this.watchRewardForm.isRedPackageBalanceDeduction = '1'
           } else {
             this.watchRewardForm.isRedPackageBalanceDeduction = String(this.watchRewardForm.isRedPackageBalanceDeduction)
           }
+          if(this.watchRewardForm.answerAttempts == null){
+            this.watchRewardForm.answerAttempts = 3
+          }
+          this.loadSelectedQuestions();
         }
         this.loading = false
       })
@@ -540,7 +659,7 @@ export default {
       if (submitData.recordTimeRanges && submitData.recordTimeRanges.length > 0) {
         submitData.recordTimeRangeStr = submitData.recordTimeRanges
           .filter(item => item.range && item.range.length === 2)
-          .map(item => item.range[0] + '-' + item.range[1] + '-' + (item.rewardTypes || []).join('|') + '-' + (item.redPacketAmount || '') + '-' + (item.scoreAmount || '') + '-' + (item.couponId || ''))
+          .map(item => item.range[0] + '-' + item.range[1] + '-' + (item.rewardTypes || []).join('|') + '-' + (item.redPacketAmount || '') + '-' + (item.scoreAmount || '') + '-' + (item.couponIds || []).join('~'))
           .join(',')
       } else {
         submitData.recordTimeRangeStr = ''
@@ -598,7 +717,7 @@ export default {
                 this.$message.error('第' + (i + 1) + '个时间段选择了积分,请填写积分值')
                 return
               }
-              if (item.rewardTypes.includes('3') && !item.couponId) {
+              if (item.rewardTypes.includes('3') && (!item.couponIds || item.couponIds.length === 0)) {
                 this.$message.error('第' + (i + 1) + '个时间段选择了核销卷,请选择核销卷')
                 return
               }
@@ -608,7 +727,7 @@ export default {
           if (submitData.recordTimeRanges && submitData.recordTimeRanges.length > 0) {
             submitData.recordTimeRangeStr = submitData.recordTimeRanges
               .filter(item => item.range && item.range.length === 2)
-              .map(item => item.range[0] + '-' + item.range[1] + '-' + (item.rewardTypes || []).join('|') + '-' + (item.redPacketAmount || '') + '-' + (item.scoreAmount || '') + '-' + (item.couponId || ''))
+              .map(item => item.range[0] + '-' + item.range[1] + '-' + (item.rewardTypes || []).join('|') + '-' + (item.redPacketAmount || '') + '-' + (item.scoreAmount || '') + '-' + (item.couponIds || []).join('~'))
               .join(',')
           } else {
             submitData.recordTimeRangeStr = ''
@@ -628,11 +747,83 @@ export default {
         }
       })
     },
+    openQuestionDialog() {
+      this.questionDialogVisible = true;
+      this.questionSearchTitle = '';
+      this.questionQueryParams.pageNum = 1;
+      this.questionQueryParams.title = null;
+      this.tempSelectedQuestions = [...this.selectedQuestionList];
+      this.loadQuestionOptions();
+    },
+    loadQuestionOptions() {
+      this.questionLoading = true;
+      listCourseQuestionBank(this.questionQueryParams).then(response => {
+        this.questionOptionList = response.rows || [];
+        this.questionOptionTotal = response.total || 0;
+        this.questionLoading = false;
+      }).catch(() => {
+        this.questionLoading = false;
+      });
+    },
+    handleQuestionSearch() {
+      this.questionQueryParams.pageNum = 1;
+      this.questionQueryParams.title = this.questionSearchTitle;
+      this.loadQuestionOptions();
+    },
+    isQuestionSelected(id) {
+      return this.tempSelectedQuestions.some(item => item.id === id);
+    },
+    toggleQuestionCard(row) {
+      const idx = this.tempSelectedQuestions.findIndex(item => item.id === row.id);
+      if (idx > -1) {
+        this.tempSelectedQuestions.splice(idx, 1);
+      } else {
+        this.tempSelectedQuestions.push(row);
+      }
+    },
+    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 => getCourseQuestionBank(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;
+}
+
+
 /* 录播奖励配置项样式 */
 .record-reward-item {
   display: flex;
@@ -747,4 +938,138 @@ export default {
   padding: 8px 20px;
   font-size: 13px;
 }
+
+/* 题目卡片列表 */
+.question-card-list {
+  max-height: 400px;
+  overflow-y: auto;
+  padding-right: 4px;
+}
+.question-card {
+  display: flex;
+  align-items: flex-start;
+  padding: 14px 16px;
+  margin-bottom: 10px;
+  border: 1px solid #e4e7ed;
+  border-radius: 6px;
+  cursor: pointer;
+  transition: border-color 0.2s, background-color 0.2s;
+}
+.question-card:hover {
+  border-color: #c0c4cc;
+  background-color: #f5f7fa;
+}
+.question-card.is-selected {
+  border-color: #409EFF;
+  background-color: #ecf5ff;
+}
+.question-card-check {
+  flex-shrink: 0;
+  width: 24px;
+  height: 24px;
+  margin-right: 12px;
+  margin-top: 2px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+.question-card-check-empty {
+  width: 18px;
+  height: 18px;
+  border: 2px solid #dcdfe6;
+  border-radius: 50%;
+}
+.question-card-body {
+  flex: 1;
+  min-width: 0;
+}
+.question-card-title {
+  font-size: 14px;
+  color: #303133;
+  line-height: 1.5;
+  margin-bottom: 8px;
+  word-break: break-all;
+}
+.question-card-answer {
+  font-size: 12px;
+  color: #67C23A;
+  margin-bottom: 8px;
+  word-break: break-all;
+}
+.question-card-meta {
+  display: flex;
+  align-items: center;
+  gap: 12px;
+  font-size: 12px;
+  color: #909399;
+}
+.question-card-creator {
+  color: #909399;
+}
+.question-card-time {
+  color: #909399;
+  margin-left: auto;
+}
+.question-card-empty {
+  text-align: center;
+  padding: 40px 0;
+  color: #909399;
+  font-size: 14px;
+}
+
+/* 已选题目卡片列表 */
+.selected-question-card-list {
+  margin-bottom: 12px;
+  max-width: 600px;
+}
+.selected-question-card {
+  display: flex;
+  align-items: flex-start;
+  padding: 10px 14px;
+  margin-bottom: 8px;
+  border: 1px solid #e4e7ed;
+  border-radius: 6px;
+  background-color: #fafbfc;
+  transition: border-color 0.2s;
+}
+.selected-question-card:hover {
+  border-color: #c0c4cc;
+}
+.selected-question-card-body {
+  flex: 1;
+  min-width: 0;
+}
+.selected-question-card-title {
+  font-size: 14px;
+  color: #303133;
+  line-height: 1.5;
+  margin-bottom: 6px;
+  word-break: break-all;
+}
+.selected-question-card-answer {
+  font-size: 12px;
+  color: #67C23A;
+  margin-bottom: 6px;
+  word-break: break-all;
+}
+.selected-question-card-meta {
+  display: flex;
+  align-items: center;
+  gap: 12px;
+  font-size: 12px;
+  color: #909399;
+}
+.selected-question-card-creator {
+  color: #909399;
+}
+.selected-question-card-time {
+  color: #909399;
+  margin-left: auto;
+}
+.selected-question-card-remove {
+  flex-shrink: 0;
+  color: #F56C6C !important;
+  margin-left: 12px;
+  padding: 0;
+}
 </style>

+ 12 - 5
src/views/system/user/index.vue

@@ -245,7 +245,7 @@
         <el-row>
           <el-col :span="12">
             <el-form-item label="用户昵称" prop="nickName">
-              <el-input v-model="form.nickName" placeholder="请输入用户昵称" maxlength="30" />
+              <el-input v-model="form.nickName" placeholder="请输入用户昵称" maxlength="30"   @blur="trimInput('nickName', form)" />
             </el-form-item>
           </el-col>
           <el-col :span="12">
@@ -257,24 +257,24 @@
         <el-row>
           <el-col :span="12">
             <el-form-item label="手机号码" prop="phonenumber">
-              <el-input v-model="form.phonenumber" placeholder="请输入手机号码" maxlength="11" />
+              <el-input v-model="form.phonenumber" placeholder="请输入手机号码" maxlength="11"  @blur="trimInput('phonenumber', form)" />
             </el-form-item>
           </el-col>
           <el-col :span="12">
             <el-form-item label="邮箱" prop="email">
-              <el-input v-model="form.email" placeholder="请输入邮箱" maxlength="50" />
+              <el-input v-model="form.email" placeholder="请输入邮箱" maxlength="50"  @blur="trimInput('email', form)" />
             </el-form-item>
           </el-col>
         </el-row>
         <el-row>
           <el-col :span="12">
             <el-form-item v-if="form.userId == undefined" label="用户名称" prop="userName">
-              <el-input v-model="form.userName" placeholder="请输入用户名称" maxlength="30" />
+              <el-input v-model="form.userName" placeholder="请输入用户名称" maxlength="30" @blur="trimInput('userName', form)" />
             </el-form-item>
           </el-col>
           <el-col :span="12">
             <el-form-item v-if="form.userId == undefined" label="用户密码" prop="password">
-              <el-input v-model="form.password" placeholder="请输入用户密码" type="password" maxlength="20" show-password/>
+              <el-input v-model="form.password" placeholder="请输入用户密码" type="password" maxlength="20" show-password @blur="trimInput('password', form)" />
             </el-form-item>
           </el-col>
         </el-row>
@@ -534,6 +534,13 @@ export default {
   },
   methods: {
 
+    trimInput(key, obj) {
+      const target = obj || this;
+      if (target[key] && typeof target[key] === 'string') {
+        target[key] = target[key].trim();
+      }
+    },
+
     /** 查询用户列表 */
     getList() {
       this.loading = true;