userCourseCatalogDetails.vue 44 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270
  1. <template>
  2. <div class="app-container">
  3. <div style="padding-bottom: 20px">
  4. <span v-if="courseName!=null">{{ courseName }}</span>
  5. </div>
  6. <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
  7. <el-form-item label="小节名称" prop="title">
  8. <el-input
  9. v-model="queryParams.title"
  10. placeholder="请输入小节名称"
  11. clearable
  12. size="small"
  13. @keyup.enter.native="handleQuery"
  14. />
  15. </el-form-item>
  16. <el-form-item>
  17. <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
  18. <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
  19. </el-form-item>
  20. </el-form>
  21. <el-table border v-loading="loading" :data="userCourseVideoList" @selection-change="handleSelectionChange">
  22. <el-table-column type="selection" width="55" align="center" />
  23. <el-table-column label="视频ID" align="center" prop="videoId" />
  24. <el-table-column label="小节名称" align="center" show-overflow-tooltip prop="title" />
  25. <el-table-column label="视频文件名称" align="center" show-overflow-tooltip prop="fileName" >
  26. </el-table-column>
  27. <el-table-column label="视频时长" align="center" prop="duration">
  28. <template slot-scope="{ row }">
  29. {{ formatDuration(row.duration) }}
  30. </template>
  31. </el-table-column>
  32. <el-table-column label="排序" align="center" prop="courseSort" />
  33. <el-table-column label="上传时间" align="center" prop="createTime" />
  34. <el-table-column label="默认红包" align="center" prop="redPacketMoney" />
  35. <el-table-column label="公司红包" align="center" prop="companyRedPacketMoney" />
  36. <el-table-column label="是否上架" align="center" prop="isOnPut">
  37. <template slot-scope="{ row }">
  38. <el-tag v-if="row.isOnPut == 0">是</el-tag>
  39. <el-tag type="danger" v-if="row.isOnPut == 1">否</el-tag>
  40. </template>
  41. </el-table-column>
  42. <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
  43. <template slot-scope="scope">
  44. <el-button
  45. size="mini"
  46. type="text"
  47. @click="handleDetails(scope.row)"
  48. >查看</el-button>
  49. <!-- <el-button-->
  50. <!-- size="mini"-->
  51. <!-- type="text"-->
  52. <!-- v-hasPermi="['course:userCourseVideo:changeRewardConfig']"-->
  53. <!-- @click="updateAnswerReward(scope.row)"-->
  54. <!-- >设置答题奖励</el-button>-->
  55. <el-button
  56. size="mini"
  57. type="text"
  58. v-hasPermi="['course:userCourseVideo:edit']"
  59. @click="updateMoney(scope.row)"
  60. >设置红包金额</el-button>
  61. <!-- <el-button-->
  62. <!-- size="mini"-->
  63. <!-- type="text"-->
  64. <!-- v-hasPermi="['course:relation:edit']"-->
  65. <!-- @click="updateReward(scope.row)"-->
  66. <!-- >设置课程奖励</el-button>-->
  67. <el-button size="mini"
  68. type="text" @click="openTagDialog(scope.row)">
  69. 绑定看课标签
  70. </el-button>
  71. <el-button size="mini"
  72. type="text" v-if="projectFrom === 'hzyy'" @click="openDialog(scope.row)">
  73. 复制看课链接
  74. </el-button>
  75. </template>
  76. </el-table-column>
  77. </el-table>
  78. <pagination
  79. v-show="total>0"
  80. :total="total"
  81. :page.sync="queryParams.pageNum"
  82. :limit.sync="queryParams.pageSize"
  83. @pagination="getList"
  84. />
  85. <el-drawer
  86. :with-header="false"
  87. size="75%"
  88. :visible.sync="open" append-to-body>
  89. <userCourseVideoDetails ref="userCourseVideoDetails" />
  90. </el-drawer>
  91. <el-dialog title="设置红包金额" :visible.sync="moneyOpen" width="600px" append-to-body>
  92. <el-form ref="form" label-width="100px">
  93. <el-form-item label="红包金额" prop="corpId">
  94. <el-input-number v-model="redPacketMoneyForm.redPacketMoney" :min="0.1" :max="200" :step="0.1" ></el-input-number>
  95. </el-form-item>
  96. </el-form>
  97. <div slot="footer" class="dialog-footer">
  98. <el-button type="primary" @click="submitForm">确 定</el-button>
  99. <el-button @click="cancel">取 消</el-button>
  100. </div>
  101. </el-dialog>
  102. <el-dialog
  103. title="生成链接"
  104. :visible.sync="dialogVisible"
  105. width="400px"
  106. @close="resetRoomForm"
  107. append-to-body>
  108. <el-form :model="roomLinkForm" label-width="120px">
  109. <!-- 新增下拉框 -->
  110. <el-form-item label="销售企微选择">
  111. <el-select
  112. v-model="roomLinkForm.qwUserId"
  113. placeholder="请选择销售企微"
  114. style="width: 100%">
  115. <el-option
  116. v-for="item in qwUserList"
  117. :key="item.id"
  118. :label="formatOptionLabel(item)"
  119. :value="item.id">
  120. </el-option>
  121. </el-select>
  122. </el-form-item>
  123. </el-form>
  124. <div slot="footer" class="dialog-footer">
  125. <el-button @click="dialogVisible = false">取消</el-button>
  126. <el-button type="primary" @click="confirm">确认</el-button>
  127. </div>
  128. </el-dialog>
  129. <AutoTagDialog
  130. v-if="currentVideoId !== null"
  131. :visible.sync="tagDialogVisible"
  132. :title="tagDialogTitle"
  133. :videoId="currentVideoId"
  134. append-to-body
  135. />
  136. <el-dialog
  137. title="课程奖励选择"
  138. :visible.sync="rewardOpen"
  139. width="80%"
  140. append-to-body
  141. @close="handleRewardDialogClose"
  142. >
  143. <div class="reward-dialog">
  144. <div class="selection-info" v-if="rewardType === 1">
  145. <span>已选择 {{ selectedRewards.length }}/2 个奖励项目</span>
  146. <el-button
  147. type="text"
  148. @click="clearSelection"
  149. :disabled="selectedRewards.length === 0"
  150. >
  151. 清空选择
  152. </el-button>
  153. </div>
  154. <el-table
  155. border
  156. v-loading="rewardLoading"
  157. :data="rewardList"
  158. @selection-change="handleRewardSelectionChange"
  159. ref="rewardTable"
  160. >
  161. <el-table-column
  162. type="selection"
  163. width="55"
  164. align="center"
  165. :selectable="checkSelectable"
  166. />
  167. <el-table-column label="主键ID" align="center" prop="id" />
  168. <el-table-column label="奖励名称" align="center" prop="name" />
  169. <el-table-column label="奖励描述" align="center" prop="description" />
  170. <el-table-column label="奖励类型" align="center" prop="rewardType">
  171. <template slot-scope="scope">
  172. <dict-tag :options="rewardTypeOptions" :value="scope.row.rewardType"/>
  173. </template>
  174. </el-table-column>
  175. <el-table-column label="期望值" align="center" prop="expectedValue" />
  176. <el-table-column label="实际奖励内容" align="center" prop="actualRewards">
  177. <template slot-scope="scope">
  178. <el-button
  179. size="mini"
  180. type="text"
  181. icon="el-icon-view"
  182. @click="handleViewReward(scope.row)"
  183. >查看详情</el-button>
  184. </template>
  185. </el-table-column>
  186. <el-table-column label="创建时间" align="center" prop="createTime" />
  187. </el-table>
  188. <pagination
  189. v-show="rewardTotal>0"
  190. :total="rewardTotal"
  191. :page.sync="rewardQueryParams.pageNum"
  192. :limit.sync="rewardQueryParams.pageSize"
  193. @pagination="getRewardList"
  194. />
  195. </div>
  196. <div slot="footer" class="dialog-footer">
  197. <el-button @click="rewardOpen = false">取 消</el-button>
  198. <el-button
  199. type="primary"
  200. @click="submitRewardSelection"
  201. :disabled="selectedRewards.length === 0 || (rewardType !== 1 && selectedRewards.length > 1)"
  202. >
  203. 确 定
  204. </el-button>
  205. <el-dialog
  206. :title="`奖励详情 - ${currentReward.name || '未知奖励'}`"
  207. :visible.sync="detailVisible"
  208. width="700px"
  209. append-to-body
  210. >
  211. <div class="reward-detail-container">
  212. <!-- 基础信息 -->
  213. <el-descriptions :column="2" border class="base-info">
  214. <el-descriptions-item label="奖励名称">{{ currentReward.name || '-' }}</el-descriptions-item>
  215. <el-descriptions-item label="奖励类型">
  216. <dict-tag :options="rewardTypeOptions" :value="currentReward.rewardType"/>
  217. </el-descriptions-item>
  218. <el-descriptions-item label="状态">
  219. <dict-tag :options="statusOptions" :value="currentReward.status"/>
  220. </el-descriptions-item>
  221. <el-descriptions-item label="期望值">{{ currentReward.expectedValue || 0 }}</el-descriptions-item>
  222. <el-descriptions-item label="描述" :span="2">{{ currentReward.description || '-' }}</el-descriptions-item>
  223. </el-descriptions>
  224. <!-- 奖励内容详情 -->
  225. <div class="reward-content">
  226. <h4>奖励内容详情</h4>
  227. <!-- 宝箱类型奖励 -->
  228. <div v-if="currentReward.rewardType === 1" class="chest-reward">
  229. <el-table :data="parsedRewardItems" size="small" border stripe>
  230. <el-table-column label="数量" prop="amount" align="center">
  231. <template slot-scope="{row}">
  232. {{ row.amount || 1 }}
  233. </template>
  234. </el-table-column>
  235. <el-table-column label="概率" prop="probability"align="center">
  236. <template slot-scope="{row}">
  237. <el-tag v-if="row.probability" size="small">{{ row.probability }}</el-tag>
  238. <span v-else>-</span>
  239. </template>
  240. </el-table-column>
  241. </el-table>
  242. <div v-if="!parsedRewardItems || parsedRewardItems.length === 0" class="empty-tip">
  243. 暂无奖励配置
  244. </div>
  245. </div>
  246. <!-- 红包类型奖励 -->
  247. <div v-else-if="currentReward.rewardType === 2" class="redpacket-reward">
  248. <div class="reward-amount">
  249. <i class="el-icon-money" style="color: #e6a23c; font-size: 24px;"></i>
  250. <span class="amount-text">{{ rewardAmount }} 元</span>
  251. <el-tag type="warning" size="small">红包奖励</el-tag>
  252. </div>
  253. </div>
  254. <!-- 福币类型奖励 -->
  255. <div v-else-if="currentReward.rewardType === 3" class="points-reward">
  256. <div class="reward-amount">
  257. <i class="el-icon-coin" style="color: #67c23a; font-size: 24px;"></i>
  258. <span class="amount-text">{{ rewardAmount }} 福币</span>
  259. <el-tag type="success" size="small">福币奖励</el-tag>
  260. </div>
  261. </div>
  262. <!-- 转盘类型奖励 -->
  263. <div v-else-if="currentReward.rewardType === 4" class="chest-reward">
  264. <el-table :data="parsedRewardItems" size="small" border stripe>
  265. <el-table-column label="图标" prop="iconUrl" align="center" width="80">
  266. <template slot-scope="{row}">
  267. <el-image
  268. v-if="row.iconUrl"
  269. :src="row.iconUrl"
  270. :preview-src-list="[row.iconUrl]"
  271. fit="cover"
  272. style="width:32px;height:32px;border:1px solid #ebeef5;border-radius:4px;"
  273. />
  274. <span v-else>-</span>
  275. </template>
  276. </el-table-column>
  277. <el-table-column label="奖品名称" prop="name" align="center" />
  278. <el-table-column label="奖品类型" prop="type" align="center">
  279. <template slot-scope="{row}">
  280. <el-tag v-if="row.type" size="small">
  281. {{ getSpinItemTypeLabel(row.type) }}
  282. </el-tag>
  283. <span v-else>-</span>
  284. </template>
  285. </el-table-column>
  286. <el-table-column label="数量" prop="amount" align="center">
  287. <template slot-scope="{row}">
  288. <span v-if="row.amount">
  289. {{ row.amount }} {{ getSpinItemUnit(row.type) }}
  290. </span>
  291. <span v-else>-</span>
  292. </template>
  293. </el-table-column>
  294. <el-table-column label="概率" prop="probability" align="center">
  295. <template slot-scope="{row}">
  296. <el-tag v-if="row.probability" size="small">{{ row.probability }}</el-tag>
  297. <span v-else>-</span>
  298. </template>
  299. </el-table-column>
  300. </el-table>
  301. <div v-if="!parsedRewardItems || parsedRewardItems.length === 0" class="empty-tip">
  302. 暂无奖励配置
  303. </div>
  304. </div>
  305. <!-- 保底转盘类型奖励 -->
  306. <div v-else-if="currentReward.rewardType === 5" class="chest-reward">
  307. <el-table :data="parsedRewardItems" size="small" border stripe>
  308. <el-table-column label="图标" prop="iconUrl" align="center" width="80">
  309. <template slot-scope="{row}">
  310. <el-image
  311. v-if="row.iconUrl"
  312. :src="row.iconUrl"
  313. :preview-src-list="[row.iconUrl]"
  314. fit="cover"
  315. style="width:32px;height:32px;border:1px solid #ebeef5;border-radius:4px;"
  316. />
  317. <span v-else>-</span>
  318. </template>
  319. </el-table-column>
  320. <el-table-column label="奖品名称" prop="name" align="center" />
  321. <el-table-column label="奖品类型" prop="type" align="center">
  322. <template slot-scope="{row}">
  323. <el-tag v-if="row.type" size="small">
  324. {{ getSpinItemTypeLabel(row.type) }}
  325. </el-tag>
  326. <span v-else>-</span>
  327. </template>
  328. </el-table-column>
  329. <el-table-column label="数量" prop="amount" align="center">
  330. <template slot-scope="{row}">
  331. <span v-if="row.amount">
  332. {{ row.amount }} {{ getSpinItemUnit(row.type) }}
  333. </span>
  334. <span v-else>-</span>
  335. </template>
  336. </el-table-column>
  337. <el-table-column label="概率" prop="probability" align="center">
  338. <template slot-scope="{row}">
  339. <el-tag v-if="row.probability" size="small">{{ row.probability }}</el-tag>
  340. <span v-else>-</span>
  341. </template>
  342. </el-table-column>
  343. <el-table-column label="保底" prop="isGuarantee" align="center">
  344. <template slot-scope="{row}">
  345. <el-tag size="small" :type="row.isGuarantee ? 'success' : 'info'">{{ row.isGuarantee ? '是' : '否' }}</el-tag>
  346. </template>
  347. </el-table-column>
  348. </el-table>
  349. <div v-if="!parsedRewardItems || parsedRewardItems.length === 0" class="empty-tip">
  350. 暂无奖励配置
  351. </div>
  352. </div>
  353. <!-- 未知类型 -->
  354. <div v-else class="unknown-reward">
  355. <el-alert type="info" title="未知奖励类型" :closable="false"></el-alert>
  356. </div>
  357. </div>
  358. </div>
  359. <div slot="footer" class="dialog-footer">
  360. <el-button @click="detailVisible = false">关 闭</el-button>
  361. </div>
  362. </el-dialog>
  363. </div>
  364. </el-dialog>
  365. <!-- 答题奖励弹窗 -->
  366. <el-dialog
  367. :title="answerRewardDialog.title"
  368. :visible.sync="answerRewardDialog.open"
  369. width="700px"
  370. append-to-body
  371. >
  372. <el-form :model="answerRewardDialog.answerRewardForm" v-loading="answerRewardDialog.loading" label-width="120px">
  373. <div class="default-amounts">
  374. <!-- <div class="amount-item">-->
  375. <!-- <i class="el-icon-money amount-icon red"></i>-->
  376. <!-- <span class="label">默认红包奖励</span>-->
  377. <!-- <span class="value">{{answerRewardDialog.answerRewardForm.defaultRedPacketAmount || 0}} 元</span>-->
  378. <!-- <el-tag :type="answerRewardDialog.answerRewardForm.redPacketRuleId ? 'info' : 'warning'" size="mini">{{ answerRewardDialog.answerRewardForm.redPacketRuleId ? '已选择配置' : '配置生效中' }}</el-tag>-->
  379. <!-- </div>-->
  380. <div class="amount-item">
  381. <i class="el-icon-coin amount-icon green"></i>
  382. <span class="label">默认小程序福币奖励</span>
  383. <span class="value">{{answerRewardDialog.answerRewardForm.defaultPointNum || 0}} 福币</span>
  384. <el-tag :type="'success'" size="mini">{{ '生效中' }}</el-tag>
  385. </div>
  386. <div class="amount-item">
  387. <i class="el-icon-coin amount-icon green"></i>
  388. <span class="label">默认APP小程序福币奖励</span>
  389. <span class="value">{{answerRewardDialog.answerRewardForm.defaultAppPointNum || 0}} 福币</span>
  390. <el-tag :type="answerRewardDialog.answerRewardForm.pointRuleId ? 'info' : 'success'" size="mini">{{ answerRewardDialog.answerRewardForm.pointRuleId ? '已选择配置' : '生效中' }}</el-tag>
  391. </div>
  392. <div class="amount-item">
  393. <i class="el-icon-money amount-icon red"></i>
  394. <span class="label">默认转盘奖励</span>
  395. <span class="value" v-if="shouldShowTurntableRule">{{getTurntableRuleName(answerRewardDialog.answerRewardForm.defaultTurntableRuleId)}}</span>
  396. <span class="value" v-else>-</span>
  397. <el-tag :type="answerRewardDialog.answerRewardForm.turntableRuleId ? 'info' : 'success'" size="mini">{{ answerRewardDialog.answerRewardForm.turntableRuleId ? '已选择配置' : '生效中' }}</el-tag>
  398. </div>
  399. </div>
  400. <el-form-item label="红包配置">
  401. <el-select
  402. ref="customSelect2"
  403. v-model="answerRewardDialog.answerRewardForm.redPacketRuleId"
  404. placeholder="请选择红包"
  405. @click.native.stop="openRuleDialog(answerRewardDialog.answerRewardForm.redPacketRuleId, 2)"
  406. clearable
  407. style="width: 100%;">
  408. <el-option
  409. v-for="item in ruleList"
  410. :key="item.id"
  411. :label="item.name"
  412. :value="item.id">
  413. </el-option>
  414. </el-select>
  415. </el-form-item>
  416. <el-form-item label="福币配置">
  417. <el-select
  418. ref="customSelect3"
  419. v-model="answerRewardDialog.answerRewardForm.pointRuleId"
  420. placeholder="请选择福币"
  421. @click.native.stop="openRuleDialog(answerRewardDialog.answerRewardForm.pointRuleId, 3)"
  422. clearable
  423. style="width: 100%;">
  424. <el-option
  425. v-for="item in ruleList"
  426. :key="item.id"
  427. :label="item.name"
  428. :value="item.id">
  429. </el-option>
  430. </el-select>
  431. </el-form-item>
  432. <el-form-item label="随机转盘配置">
  433. <el-select
  434. ref="customSelect4"
  435. v-model="answerRewardDialog.answerRewardForm.turntableRuleId"
  436. placeholder="请选择转盘"
  437. @click.native.stop="openRuleDialog(answerRewardDialog.answerRewardForm.turntableRuleId, 4)"
  438. clearable
  439. style="width: 100%;">
  440. <el-option
  441. v-for="item in ruleList"
  442. :key="item.id"
  443. :label="item.name"
  444. :value="item.id">
  445. </el-option>
  446. </el-select>
  447. </el-form-item>
  448. <el-form-item label="保底转盘配置">
  449. <el-select
  450. ref="customSelect5"
  451. v-model="answerRewardDialog.answerRewardForm.turntableGuaranteeRuleId"
  452. placeholder="请选择保底转盘"
  453. @click.native.stop="openRuleDialog(answerRewardDialog.answerRewardForm.turntableGuaranteeRuleId, 5)"
  454. clearable
  455. style="width: 100%;">
  456. <el-option
  457. v-for="item in ruleList"
  458. :key="item.id"
  459. :label="item.name"
  460. :value="item.id">
  461. </el-option>
  462. </el-select>
  463. </el-form-item>
  464. </el-form>
  465. <div slot="footer" class="dialog-footer">
  466. <el-button @click="answerRewardDialog.open=false">取 消</el-button>
  467. <el-button type="primary" :loading="saving" :disabled="saving" @click="submitAnswerReward">确 定</el-button>
  468. </div>
  469. </el-dialog>
  470. </div>
  471. </template>
  472. <script>
  473. import {
  474. updatePacketMoney,
  475. getVideoListByCourseId,
  476. getAnswerRewardConfig,
  477. changeAnswerRewardConfig,
  478. } from "@/api/course/userCourseVideo";
  479. import userCourseVideoDetails from '../../components/course/userCourseVideoDetails.vue';
  480. import {listReward, getReward, listByIds} from "@/api/course/reward";
  481. import { listRelation, updateRelation, } from "@/api/course/relation";
  482. import {createLinkUrl, createRoomLinkUrl, queryQwIds} from "@/api/course/sopCourseLink";
  483. import AutoTagDialog from "@/views/components/tag/AutoTagDialog.vue";
  484. import {addTag, updateTag} from "@/api/tag/api";
  485. export default {
  486. name: "userCourseCatalog",
  487. components: {
  488. userCourseVideoDetails,
  489. AutoTagDialog
  490. },
  491. props: {
  492. video: {
  493. type: Object,
  494. required: false, // 改为非必需
  495. default: () => ({}) // 提供默认值
  496. },
  497. },
  498. data() {
  499. return {
  500. answerRewardDialog: {
  501. open: false,
  502. title: '设置答题奖励',
  503. loading: false,
  504. answerRewardForm: {
  505. videoId: null,
  506. redPacketRuleId: null,
  507. pointRuleId: null,
  508. turntableRuleId: null,
  509. turntableGuaranteeRuleId: null,
  510. defaultRedPacketAmount: null,
  511. defaultPointNum: null,
  512. defaultAppPointNum: null,
  513. defaultTurntableRuleId: null
  514. }
  515. },
  516. ruleList: [],
  517. // 转盘奖励-奖励类型选项
  518. spinItemTypeOptions: [],
  519. saving: false,
  520. currentReward: {},
  521. rewardOpen: false,
  522. rewardType: null,
  523. rewardLoading: false,
  524. rewardList: [],
  525. selectedRewards: [], // 选中的奖励项
  526. rewardsRelation: [], // 选中的奖励项
  527. rewardTotal: 0,
  528. rewardQueryParams: {
  529. pageNum: 1,
  530. pageSize: 10,
  531. videoId: null,
  532. rewardType:1
  533. },
  534. statusOptions:[],
  535. rewardTypeOptions:[],
  536. detailVisible: false,
  537. currentRow: null,
  538. // 假设这里有当前课程小节的数据传入,里面含id等
  539. tagGroups: [],
  540. tagsInGroup: [],
  541. tagDialogVisible: false,
  542. tagDialogTitle: "",
  543. tagDialogFormData: null,
  544. currentVideoId:null,
  545. projectFrom:process.env.VUE_APP_PROJECT_FROM,
  546. tagDialog: {
  547. videoId: null,
  548. groupId: null,
  549. watchTagId: null,
  550. finishTagId: null,
  551. tgId: null,
  552. watchingTgId: null,
  553. watchedTgId: null
  554. },
  555. linkForm:{
  556. days:null,
  557. courseId:null,
  558. videoId:null
  559. },
  560. roomLinkForm:{
  561. courseId:null,
  562. videoId:null,
  563. qwUserId:null,
  564. qwUserName:null,
  565. corpId:null,
  566. title:null,
  567. },
  568. dialogVisible: false, // 控制弹框显示
  569. //短链
  570. sortLink:'',
  571. //课题
  572. questionBank:{
  573. title:'',
  574. open:false,
  575. },
  576. moneyOpen:false,
  577. videoUrl: "",
  578. uploadTypeOptions: [
  579. { dictLabel: "线路一", dictValue: 2 },
  580. { dictLabel: "线路二", dictValue: 1 },
  581. { dictLabel: "线路三", dictValue: 3 },
  582. ],
  583. uploadLoading:false,
  584. courseId:null,
  585. videoName:'',
  586. title: "",
  587. redPacketMoneyForm:{
  588. redPacketMoney:null,
  589. voidId:null
  590. },
  591. // 是否显示弹出层
  592. open: false,
  593. uploadUrl:process.env.VUE_APP_BASE_API+"/common/uploadHuaWeiObs",
  594. baseUrl: process.env.VUE_APP_BASE_API,
  595. typeOptions:[],
  596. files:[],
  597. fileList: [],
  598. // 上传成功后的地址
  599. videoURL: '',
  600. // 进度条百分比
  601. progress: 0,
  602. // 上传视频获取成功后拿到的fileID【备用】
  603. fileId: '',
  604. courseName:null,
  605. userCourseVideoList:[],
  606. fetchingQwIds: false,
  607. qwUserList:[],
  608. total: 0,
  609. queryParams: {
  610. pageNum: 1,
  611. pageSize: 10,
  612. courseId:null,
  613. title:null
  614. },
  615. // 显示搜索条件
  616. showSearch: true,
  617. // 遮罩层
  618. loading: true,
  619. // 导出遮罩层
  620. exportLoading: false,
  621. // 选中数组
  622. ids: [],
  623. // 非单个禁用
  624. single: true,
  625. // 非多个禁用
  626. multiple: true,
  627. // 表单参数
  628. form: {},
  629. // 表单校验
  630. rules: {
  631. title: [
  632. { required: true, message: "小节名称不能为空", trigger: "change" }
  633. ],
  634. courseSort: [
  635. { required: true, message: "排序不能为空", trigger: "change" }
  636. ],
  637. }
  638. }
  639. },
  640. computed: {
  641. // 解析奖励项数据
  642. parsedRewardItems() {
  643. if (!this.currentReward.actualRewards) return null;
  644. try {
  645. const parsed = JSON.parse(this.currentReward.actualRewards);
  646. return Array.isArray(parsed) ? parsed : null;
  647. } catch (e) {
  648. console.error("解析奖励内容失败", e);
  649. return null;
  650. }
  651. },
  652. // 获取红包或福币金额
  653. rewardAmount() {
  654. if (!this.currentReward.actualRewards) return 0;
  655. try {
  656. const parsed = JSON.parse(this.currentReward.actualRewards);
  657. if (this.currentReward.rewardType === 2) {
  658. return parsed.amount || parsed.money || 0;
  659. } else if (this.currentReward.rewardType === 3) {
  660. return parsed.points || parsed.score || 0;
  661. }
  662. return 0;
  663. } catch (e) {
  664. return 0;
  665. }
  666. },
  667. // 只有在dialog打开时才显示转盘规则名称
  668. shouldShowTurntableRule() {
  669. return this.answerRewardDialog.open && this.answerRewardDialog.answerRewardForm.defaultTurntableRuleId
  670. }
  671. },
  672. created() {
  673. this.getDicts("sys_course_temp_type").then(response => {
  674. this.typeOptions = response.data;
  675. });
  676. this.getDicts("sys_reward_type").then((response) => {
  677. this.rewardTypeOptions = response.data;
  678. });
  679. this.getDicts("sys_user_status").then((response) => {
  680. this.statusOptions = response.data;
  681. });
  682. this.getDicts("spin_reward_type").then((response) => {
  683. this.spinItemTypeOptions = response.data;
  684. });
  685. },
  686. methods: {
  687. getTurntableRuleName(ruleId) {
  688. if (!ruleId) return '-'
  689. // 从ruleList中查找对应的规则名称
  690. const rule = this.ruleList.find(item => item.id === ruleId)
  691. if (rule) {
  692. return rule.name
  693. }
  694. // 如果ruleList中没有,则异步获取
  695. getReward(ruleId).then(response => {
  696. const {code,data} = response
  697. if (code === 200 && data) {
  698. if (!this.ruleList.find(item => item.id === ruleId)) {
  699. this.ruleList.push(data)
  700. }
  701. return data.name
  702. }
  703. }).catch(() => {
  704. console.error('获取转盘规则失败:', ruleId)
  705. })
  706. return '加载中...'
  707. },
  708. // 查看奖励详情
  709. handleViewReward(row) {
  710. this.currentReward = { ...row };
  711. this.detailVisible = true;
  712. },
  713. updateReward(row) {
  714. this.currentVideoId = row.videoId;
  715. this.rewardQueryParams.videoId = row.videoId;
  716. this.rewardOpen = true;
  717. this.rewardType = 1
  718. this.getRewardList();
  719. // 获取已设置的奖励(如果有)
  720. this.getCurrentRewards(row.videoId);
  721. },
  722. // 获取奖励列表
  723. getRewardList() {
  724. this.rewardLoading = true;
  725. listRelation({
  726. videoSectionId: this.currentVideoId,
  727. type: 1
  728. }).then(response => {
  729. this.rewardsRelation = response.rows;
  730. }).catch(() => {
  731. this.rewardLoading = false;
  732. });
  733. listReward(this.rewardQueryParams).then(response => {
  734. this.rewardList = response.rows;
  735. this.rewardTotal = response.total;
  736. this.rewardLoading = false;
  737. // 设置已选中的奖励
  738. this.$nextTick(() => {
  739. this.rewardList.forEach(row => {
  740. if (this.rewardsRelation.some(item => item.rewardId === row.id)) {
  741. this.$refs.rewardTable.toggleRowSelection(row, true);
  742. }
  743. });
  744. });
  745. }).catch(() => {
  746. this.rewardLoading = false;
  747. });
  748. },
  749. // 获取当前视频已设置的奖励
  750. getCurrentRewards(videoId) {
  751. // 这里假设有一个API可以获取视频已设置的奖励
  752. // 实际实现中需要根据后端API调整
  753. this.selectedRewards = []; // 先清空
  754. // 模拟数据,实际应从API获取
  755. /*
  756. getVideoRewards(videoId).then(response => {
  757. this.selectedRewards = response.data || [];
  758. });
  759. */
  760. },
  761. // 处理奖励选择变化
  762. handleRewardSelectionChange(selection) {
  763. // 限制最多选择两项
  764. if (selection.length > 2) {
  765. this.$refs.rewardTable.clearSelection();
  766. // 保留前两项
  767. const limitedSelection = selection.slice(0, 2);
  768. limitedSelection.forEach(item => {
  769. this.$refs.rewardTable.toggleRowSelection(item, true);
  770. });
  771. this.selectedRewards = limitedSelection;
  772. this.$message.warning('最多只能选择两个奖励项目');
  773. } else {
  774. this.selectedRewards = selection;
  775. }
  776. },
  777. updateAnswerReward(row) {
  778. this.answerRewardDialog.answerRewardForm = {}
  779. this.answerRewardDialog.open = true
  780. this.answerRewardDialog.loading = true
  781. getAnswerRewardConfig(row.videoId).then(response => {
  782. this.answerRewardDialog.answerRewardForm = response.data
  783. this.answerRewardDialog.answerRewardForm.videoId = row.videoId
  784. // 查询已配置列表
  785. let ids = []
  786. for (const [key, value] of Object.entries(response.data)) {
  787. if (key === "redPacketRuleId" || key === "pointRuleId" ||
  788. key === "turntableRuleId" || key === "turntableGuaranteeRuleId")
  789. if (value) {
  790. ids.push(value)
  791. }
  792. }
  793. if (ids.length > 0) {
  794. listByIds(ids).then(response => {
  795. this.ruleList = response.data
  796. this.answerRewardDialog.loading = false
  797. })
  798. } else {
  799. this.answerRewardDialog.loading = false
  800. }
  801. })
  802. },
  803. submitAnswerReward(){
  804. this.saving = true
  805. changeAnswerRewardConfig(this.answerRewardDialog.answerRewardForm).then(response => {
  806. const {code, msg} = response
  807. if (code === 200){
  808. this.msgSuccess("修改成功");
  809. this.getList()
  810. this.answerRewardDialog.open = false
  811. } else {
  812. this.msgError(msg);
  813. }
  814. this.saving = false
  815. })
  816. },
  817. openRuleDialog(id, type) {
  818. this.$nextTick(() => {
  819. this.$refs['customSelect' + type]?.blur();
  820. });
  821. this.rewardList =[]
  822. this.rewardOpen = true
  823. this.rewardLoading = true
  824. listReward({"rewardType": type, "status": 1}).then(response => {
  825. const {rows, total} = response
  826. this.rewardList = rows
  827. this.rewardTotal = total
  828. // 根据传入的规则ID,默认选中对应项
  829. const preselected = this.rewardList.filter(item => item.id === id)
  830. this.selectedRewards = preselected
  831. this.$nextTick(() => {
  832. if (this.$refs.rewardTable && preselected.length > 0) {
  833. // 先清空再选中,避免残留状态
  834. this.$refs.rewardTable.clearSelection()
  835. preselected.forEach(row => this.$refs.rewardTable.toggleRowSelection(row, true))
  836. }
  837. })
  838. this.rewardLoading = false
  839. })
  840. this.rewardType = type
  841. },
  842. // 获取转盘奖励类型标签
  843. getSpinItemTypeLabel(type) {
  844. const option = this.spinItemTypeOptions.find(item => item.dictValue === type.toString());
  845. return option ? option.dictLabel : type;
  846. },
  847. // 获取转盘奖励类型单位
  848. getSpinItemUnit(type) {
  849. const option = this.spinItemTypeOptions.find(item => item.dictValue === type.toString());
  850. const label = option ? option.dictLabel : '';
  851. if (label.includes("红包")) {
  852. return '元';
  853. } else if (label.includes("福币")) {
  854. return '福币';
  855. } else {
  856. return '';
  857. }
  858. },
  859. // 检查是否可选
  860. checkSelectable(row, index) {
  861. // 如果已选择两项且当前行未被选中,则禁用选择
  862. return this.selectedRewards.length < 2 ||
  863. this.selectedRewards.some(item => item.rewardId === row.rewardId);
  864. },
  865. // 清空选择
  866. clearSelection() {
  867. this.$refs.rewardTable.clearSelection();
  868. this.selectedRewards = [];
  869. },
  870. // 提交奖励选择
  871. submitRewardSelection() {
  872. if (this.selectedRewards.length === 0) {
  873. this.$message.warning('请至少选择一个奖励项目');
  874. return;
  875. }
  876. const rewardIds = this.selectedRewards.map(item => item.id);
  877. const rewardIdList = this.rewardsRelation.map(item => item.rewardId);
  878. if (this.rewardType === 1) {
  879. updateRelation({
  880. videoSectionId: this.currentVideoId,
  881. rewardIds: rewardIds,
  882. rewardIdList: rewardIdList
  883. }).then(response => {
  884. if (response.code === 200) {
  885. this.$message.success('奖励设置成功');
  886. }
  887. });
  888. } else {
  889. const ruleId = rewardIds[0]
  890. if (this.rewardType === 2) {
  891. this.answerRewardDialog.answerRewardForm.redPacketRuleId = ruleId
  892. }
  893. if (this.rewardType === 3) {
  894. this.answerRewardDialog.answerRewardForm.pointRuleId = ruleId
  895. }
  896. if (this.rewardType === 4) {
  897. this.answerRewardDialog.answerRewardForm.turntableRuleId = ruleId
  898. }
  899. if (this.rewardType === 5) {
  900. this.answerRewardDialog.answerRewardForm.turntableGuaranteeRuleId = ruleId
  901. }
  902. let rule = this.rewardList.find(item => item.id === ruleId)
  903. if (!this.ruleList.some(item => item.id === rule.id)) {
  904. this.ruleList.push(rule)
  905. }
  906. }
  907. this.rewardOpen = false
  908. },
  909. // 处理奖励弹窗关闭
  910. handleRewardDialogClose() {
  911. this.selectedRewards = [];
  912. this.currentVideoId = null;
  913. },
  914. formatOptionLabel(item) {
  915. return item.corpName ? `${item.qwUserName} (${item.corpName})` : item.qwUserName;
  916. },
  917. closeTagDialog(){
  918. this.tagDialogVisible = false;
  919. },
  920. openTagDialog(row) {
  921. this.currentRow = row;
  922. this.tagDialogVisible = true;
  923. this.currentVideoId = row.videoId;
  924. },
  925. updateTagsInGroup(groupId) {
  926. let tagGroup = this.tagGroups.find(e=> e.groupId === groupId);
  927. this.tagsInGroup = tagGroup.tag || [];
  928. // 切换组时清空标签选择
  929. this.tagDialog.watchTagId = null;
  930. this.tagDialog.finishTagId = null;
  931. this.tagDialog.tgId = tagGroup.id;
  932. },
  933. resetDialog() {
  934. this.tagDialog = {
  935. videoId: null,
  936. groupId: null,
  937. watchTagId: null,
  938. finishTagId: null,
  939. };
  940. this.tagGroups = [];
  941. this.tagsInGroup = [];
  942. },
  943. // 打开弹框
  944. openDialog(row) {
  945. if (!this.fetchingQwIds) {
  946. this.fetchingQwIds = true;
  947. queryQwIds().then(response => {
  948. if (response.code === 200){
  949. this.qwUserList = response.list;
  950. }
  951. }).finally(() => {
  952. this.fetchingQwIds = false;
  953. // 在请求完成后再显示弹框
  954. this.showDialog(row);
  955. });
  956. } else {
  957. // 如果已经在请求中,直接显示弹框
  958. this.showDialog(row);
  959. }
  960. },
  961. // 新增方法:显示弹框
  962. showDialog(row) {
  963. this.roomLinkForm.courseId = row.courseId;
  964. this.roomLinkForm.videoId = row.videoId;
  965. this.roomLinkForm.title = row.title;
  966. this.dialogVisible = true;
  967. },
  968. // 确认按钮操作
  969. confirm() {
  970. if (!this.roomLinkForm.qwUserId) {
  971. this.$message.error("请选择销售企微!");
  972. return;
  973. }
  974. // 获取选中的用户信息
  975. const selectedUser = this.qwUserList.find(user => user.id === this.roomLinkForm.qwUserId);
  976. if (selectedUser) {
  977. // 将用户信息填充到表单中
  978. this.roomLinkForm.qwUserName = selectedUser.qwUserName;
  979. this.roomLinkForm.corpId = selectedUser.corpId;
  980. console.log(this.roomLinkForm);
  981. // 调用创建链接的API
  982. createRoomLinkUrl(this.roomLinkForm).then(response => {
  983. if (response.code === 200){
  984. this.msgSuccess("创建成功");
  985. this.copyLink(response.link);
  986. console.log(response.link);
  987. }
  988. });
  989. }
  990. // 关闭弹框
  991. this.dialogVisible = false;
  992. },
  993. // 重置表单
  994. resetForm() {
  995. this.linkForm={
  996. days:null,
  997. courseId:null,
  998. videoId:null
  999. }
  1000. },
  1001. resetRoomForm() {
  1002. this.roomLinkForm={
  1003. courseId:null,
  1004. videoId:null,
  1005. qwUserId:null,
  1006. }
  1007. },
  1008. handleCreateLink(){
  1009. createLinkUrl(this.linkForm).then(response => {
  1010. if (response.code === 200){
  1011. this.copyLink(response.url);
  1012. }
  1013. });
  1014. },
  1015. copyLink(url) {
  1016. const link = url;
  1017. // navigator clipboard 需要https等安全上下文
  1018. if (navigator.clipboard && window.isSecureContext) {
  1019. // navigator clipboard 向剪贴板写文本
  1020. navigator.clipboard.writeText(link).then(() => {
  1021. this.$message.success('链接已复制到剪贴板');
  1022. });
  1023. } else {
  1024. // document.execCommand('copy') 向剪贴板写文本
  1025. let input = document.createElement('input')
  1026. input.style.position = 'fixed'
  1027. input.style.top = '-10000px'
  1028. input.style.zIndex = '-999'
  1029. document.body.appendChild(input)
  1030. input.value = link
  1031. input.focus()
  1032. input.select()
  1033. try {
  1034. let result = document.execCommand('copy')
  1035. document.body.removeChild(input)
  1036. if (!result || result === 'unsuccessful') {
  1037. this.$message.error('复制失败');
  1038. console.log('复制失败')
  1039. } else {
  1040. this.$message.success('链接已复制到剪贴板');
  1041. console.log('复制成功')
  1042. }
  1043. } catch (e) {
  1044. document.body.removeChild(input)
  1045. alert('当前浏览器不支持复制功能,请检查更新或更换其他浏览器操作')
  1046. }
  1047. }
  1048. },
  1049. formatDuration(seconds) {
  1050. if (seconds === null || seconds === undefined) {
  1051. return '未上传视频'; // 或者您可以根据具体需求返回其他默认值
  1052. }
  1053. const hours = Math.floor(seconds / 3600);
  1054. const minutes = Math.floor((seconds % 3600) / 60);
  1055. const remainingSeconds = seconds % 60;
  1056. const formattedHours = hours > 0 ? hours.toString() + ':' : '';
  1057. const formattedMinutes = minutes.toString().padStart(2, '0');
  1058. const formattedSeconds = remainingSeconds.toString().padStart(2, '0');
  1059. return `${formattedHours}${formattedMinutes}:${formattedSeconds}`;
  1060. },
  1061. getDetails(courseId,courseName) {
  1062. this.courseName = courseName
  1063. this.courseId = courseId;
  1064. this.queryParams.courseId = courseId;
  1065. this.getList();
  1066. },
  1067. getList() {
  1068. this.loading = true;
  1069. getVideoListByCourseId(this.queryParams).then(response => {
  1070. this.userCourseVideoList = response.rows;
  1071. this.total = response.total;
  1072. this.loading = false;
  1073. });
  1074. },
  1075. // 取消按钮
  1076. cancel() {
  1077. this.open = false;
  1078. this.reset();
  1079. },
  1080. updateMoney(row){
  1081. this.redPacketMoneyForm.redPacketMoney=row.companyRedPacketMoney;
  1082. this.redPacketMoneyForm.videoId=row.videoId;
  1083. this.moneyOpen=true;
  1084. },
  1085. submitForm(){
  1086. updatePacketMoney(this.redPacketMoneyForm).then(response => {
  1087. this.msgSuccess("修改成功");
  1088. this.moneyOpen=false;
  1089. this.getList()
  1090. });
  1091. },
  1092. updateMoneycancel(){
  1093. this.moneyOpen=false;
  1094. },
  1095. // 表单重置
  1096. reset() {
  1097. this.form = {
  1098. videoId: null,
  1099. title: null,
  1100. description: null,
  1101. url: null,
  1102. thumbnail: null,
  1103. duration: null,
  1104. createTime: null,
  1105. uploadType:null,
  1106. lineOne:null,
  1107. lineTwo:null,
  1108. lineThree:null,
  1109. fileName:null,
  1110. userId: null,
  1111. cateId: null,
  1112. courseId: null,
  1113. likes: null,
  1114. views: null,
  1115. comments: null,
  1116. status: 0,
  1117. courseSort: 1,
  1118. isHot: null,
  1119. isShow: null,
  1120. isAudit: null,
  1121. auditBy: null,
  1122. auditTime: null,
  1123. updateTime: null,
  1124. source: null,
  1125. isDel: null,
  1126. shares: null,
  1127. tags: null,
  1128. productId: null,
  1129. productJson: null,
  1130. questionBankId:null,
  1131. questionBankList:[],
  1132. };
  1133. this.videoURL = '';
  1134. this.progress=0;
  1135. this.resetForm("form");
  1136. },
  1137. /** 搜索按钮操作 */
  1138. handleQuery() {
  1139. this.queryParams.pageNum = 1;
  1140. this.getList();
  1141. },
  1142. /** 重置按钮操作 */
  1143. resetQuery() {
  1144. this.resetForm("queryForm");
  1145. this.queryParams.title = null;
  1146. this.handleQuery();
  1147. },
  1148. // 多选框选中数据
  1149. handleSelectionChange(selection) {
  1150. this.ids = selection.map(item => item.courseId)
  1151. this.single = selection.length!==1
  1152. this.multiple = !selection.length
  1153. },
  1154. /** 修改按钮操作 */
  1155. handleDetails(row) {
  1156. this.open=true;
  1157. setTimeout(() => {
  1158. this.$refs.userCourseVideoDetails.getDetails(row.videoId);
  1159. }, 500);
  1160. }
  1161. }
  1162. }
  1163. </script>
  1164. <style>
  1165. .avatar-uploader .el-upload {
  1166. border: 1px dashed #d9d9d9;
  1167. border-radius: 6px;
  1168. cursor: pointer;
  1169. position: relative;
  1170. overflow: hidden;
  1171. }
  1172. .avatar-uploader .el-upload:hover {
  1173. border-color: #409EFF;
  1174. }
  1175. .avatar-uploader-icon {
  1176. font-size: 28px;
  1177. color: #8c939d;
  1178. width: 150px;
  1179. height: 150px;
  1180. line-height: 150px;
  1181. text-align: center;
  1182. }
  1183. .default-amounts {
  1184. display: flex;
  1185. flex-direction: column;
  1186. gap: 8px;
  1187. margin-bottom: 8px;
  1188. }
  1189. .amount-item {
  1190. display: inline-flex;
  1191. align-items: center;
  1192. gap: 8px;
  1193. padding: 8px 10px;
  1194. background: #fafafa;
  1195. border: 1px solid #ebeef5;
  1196. border-radius: 4px;
  1197. }
  1198. .amount-icon {
  1199. font-size: 18px;
  1200. }
  1201. .amount-icon.red { color: #e6a23c; }
  1202. .amount-icon.green { color: #67c23a; }
  1203. .amount-item .label {
  1204. color: #606266;
  1205. }
  1206. .amount-item .value {
  1207. font-weight: 600;
  1208. color: #303133;
  1209. margin-right: 6px;
  1210. }
  1211. </style>