task.vue 37 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081
  1. <template>
  2. <div class="app-container">
  3. <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
  4. <el-form-item label="直播间ID" prop="liveId">
  5. <el-input
  6. v-model="queryParams.liveId"
  7. placeholder="请输入直播间ID"
  8. clearable
  9. :disabled="liveAbled"
  10. size="small"
  11. @keyup.enter.native="handleQuery"
  12. />
  13. </el-form-item>
  14. <el-form-item label="任务名称" prop="taskName">
  15. <el-input
  16. v-model="queryParams.taskName"
  17. placeholder="请输入任务名称"
  18. clearable
  19. size="small"
  20. @keyup.enter.native="handleQuery"
  21. />
  22. </el-form-item>
  23. <el-form-item label="任务类型" prop="taskType">
  24. <el-select v-model="queryParams.taskType" placeholder="请选择任务类型" clearable size="small">
  25. <el-option v-for="i in taskTypeOptions" :key="i.value" :label="i.label" :value="i.value"></el-option>
  26. </el-select>
  27. </el-form-item>
  28. <!-- <el-form-item label="触发类型" prop="triggerType">-->
  29. <!-- <el-select v-model="queryParams.triggerType" placeholder="请选择触发类型" clearable size="small">-->
  30. <!-- <el-option v-for="i in triggerTypeOptions" :key="i.value" :label="i.label" :value="i.value"></el-option>-->
  31. <!-- </el-select>-->
  32. <!-- </el-form-item>-->
  33. <el-form-item label="状态" prop="status">
  34. <el-select v-model="queryParams.status" placeholder="请选择状态" clearable size="small">
  35. <el-option v-for="i in statusOptions" :key="i.value" :label="i.label" :value="i.value"></el-option>
  36. </el-select>
  37. </el-form-item>
  38. <el-form-item>
  39. <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
  40. <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
  41. </el-form-item>
  42. </el-form>
  43. <el-row :gutter="10" class="mb8">
  44. <el-col :span="1.5">
  45. <el-button
  46. :disabled="isViewOnly"
  47. type="primary"
  48. plain
  49. icon="el-icon-plus"
  50. size="mini"
  51. @click="handleAdd"
  52. v-hasPermi="['live:task:add']"
  53. >新增</el-button>
  54. </el-col>
  55. <el-col :span="1.5">
  56. <el-button
  57. type="success"
  58. plain
  59. icon="el-icon-edit"
  60. size="mini"
  61. :disabled="single || isViewOnly"
  62. @click="handleUpdate"
  63. v-hasPermi="['live:task:edit']"
  64. >修改</el-button>
  65. </el-col>
  66. <el-col :span="1.5">
  67. <el-button
  68. type="danger"
  69. plain
  70. icon="el-icon-delete"
  71. size="mini"
  72. :disabled="multiple || isViewOnly"
  73. @click="handleDelete"
  74. v-hasPermi="['live:task:remove']"
  75. >删除</el-button>
  76. </el-col>
  77. <el-col :span="1.5">
  78. <el-button
  79. :disabled="isViewOnly"
  80. type="warning"
  81. plain
  82. icon="el-icon-download"
  83. size="mini"
  84. :loading="exportLoading"
  85. @click="handleExport"
  86. v-hasPermi="['live:task:export']"
  87. >导出</el-button>
  88. </el-col>
  89. <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
  90. </el-row>
  91. <el-table border v-loading="loading" :data="taskList" @selection-change="handleSelectionChange">
  92. <el-table-column type="selection" width="55" align="center" />
  93. <el-table-column label="编号" align="center" prop="id" />
  94. <el-table-column label="任务名称" align="center" prop="taskName" />
  95. <el-table-column label="任务类型" align="center" prop="taskType" :formatter="taskTypeFormatter" />
  96. <el-table-column label="触发类型" align="center" prop="triggerType" :formatter="triggerTypeFormatter" />
  97. <el-table-column label="触发时间" align="center" prop="triggerValue" :formatter="triggerValueFormatter" />
  98. <el-table-column label="关联内容" align="center" prop="productName" :formatter="productNameFormatter" />
  99. <el-table-column label="状态" align="center" prop="status" :formatter="statusFormatter" />
  100. <!-- <el-table-column label="完成状态" align="center" prop="finishStatus" :formatter="finishStatusFormatter" />-->
  101. <el-table-column label="更新时间" align="center" prop="updatedTime" width="180">
  102. <template slot-scope="scope">
  103. <span>{{ parseTime(scope.row.updateTime, '{y}-{m}-{d}') }}</span>
  104. </template>
  105. </el-table-column>
  106. <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
  107. <template slot-scope="scope">
  108. <el-button
  109. :disabled="isViewOnly"
  110. size="mini"
  111. type="text"
  112. icon="el-icon-edit"
  113. @click="handleUpdate(scope.row)"
  114. v-hasPermi="['live:task:edit']"
  115. >修改</el-button>
  116. <el-button
  117. :disabled="isViewOnly"
  118. size="mini"
  119. type="text"
  120. icon="el-icon-delete"
  121. @click="handleDelete(scope.row)"
  122. v-hasPermi="['live:task:remove']"
  123. >删除</el-button>
  124. </template>
  125. </el-table-column>
  126. </el-table>
  127. <pagination
  128. v-show="total>0"
  129. :total="total"
  130. :page.sync="queryParams.pageNum"
  131. :limit.sync="queryParams.pageSize"
  132. @pagination="getList"
  133. />
  134. <!-- 添加或修改直播间自动化任务配置对话框 -->
  135. <el-dialog :title="title" :visible.sync="open" width="620px" append-to-body>
  136. <el-form ref="form" :model="form" :rules="rules" label-width="90px">
  137. <el-form-item label="直播间ID" prop="liveId">
  138. <el-input :disabled="liveAbled" v-model="form.liveId" placeholder="请输入直播间ID" />
  139. </el-form-item>
  140. <el-form-item label="任务名称" prop="taskName">
  141. <el-input v-model="form.taskName" placeholder="请输入任务名称" />
  142. </el-form-item>
  143. <el-form-item label="任务类型" prop="taskType">
  144. <el-select v-model="form.taskType" placeholder="请选择任务类型" @change="updateTaskType()" >
  145. <el-option v-for="i in taskTypeOptions" :key="i.value" :label="i.label" :value="i.value"></el-option>
  146. </el-select>
  147. </el-form-item>
  148. <el-form-item label="商品选择" prop="content" v-if="form.taskType == 1">
  149. <el-select v-model="form.content" placeholder="请选择商品" ref="selectRef" >
  150. <el-option v-for="i in productOptions" :key="i.value" :label="i.label" :value="i.value"></el-option>
  151. <!-- 加载载中状态 -->
  152. <div v-if="isLoading" class="loading-indicator">
  153. <i class="el-icon-loading"></i>
  154. <span>加载中...</span>
  155. </div>
  156. <!-- 没有更多数据 -->
  157. <div v-if="!hasMore && !isLoading" class="no-more">
  158. 没有更多数据了
  159. </div>
  160. </el-select>
  161. </el-form-item>
  162. <el-form-item label="红包选择" prop="content" v-if="form.taskType == 2">
  163. <el-select v-model="form.content" placeholder="请选择红包" ref="selectRef" >
  164. <el-option v-for="i in redOptions" :key="i.value" :label="i.label" :value="i.value"></el-option>
  165. <!-- 加载载中状态 -->
  166. <div v-if="isLoading" class="loading-indicator">
  167. <i class="el-icon-loading"></i>
  168. <span>加载中...</span>
  169. </div>
  170. <!-- 没有更多数据 -->
  171. <div v-if="!hasMore && !isLoading" class="no-more">
  172. 没有更多数据了
  173. </div>
  174. </el-select>
  175. </el-form-item>
  176. <el-form-item label="抽奖选择" prop="content" v-if="form.taskType == 4">
  177. <el-select v-model="form.content" placeholder="请选择抽奖" ref="selectRef" >
  178. <el-option v-for="i in lotteryOptions" :key="i.value" :label="i.label" :value="i.value"></el-option>
  179. <!-- 加载载中状态 -->
  180. <div v-if="isLoading" class="loading-indicator">
  181. <i class="el-icon-loading"></i>
  182. <span>加载中...</span>
  183. </div>
  184. <!-- 没有更多数据 -->
  185. <div v-if="!hasMore && !isLoading" class="no-more">
  186. 没有更多数据了
  187. </div>
  188. </el-select>
  189. </el-form-item>
  190. <el-form-item label="优惠券" prop="content" v-if="form.taskType == 5">
  191. <el-select v-model="form.content" placeholder="请选择优惠券" ref="selectCoupon" >
  192. <el-option v-for="i in couponOptions" :key="i.value" :label="i.label" :value="i.value"></el-option>
  193. <!-- 加载载中状态 -->
  194. <div v-if="isLoading" class="loading-indicator">
  195. <i class="el-icon-loading"></i>
  196. <span>加载中...</span>
  197. </div>
  198. <!-- 没有更多数据 -->
  199. <div v-if="!hasMore && !isLoading" class="no-more">
  200. 没有更多数据了
  201. </div>
  202. </el-select>
  203. </el-form-item>
  204. <el-form-item label="商品选择" prop="goodsId" v-if="form.taskType == 6">
  205. <el-select v-model="form.goodsId" placeholder="请选择商品" ref="selectGoods" >
  206. <el-option v-for="i in productOptions" :key="i.value" :label="i.label" :value="i.value"></el-option>
  207. <!-- 加载载中状态 -->
  208. <div v-if="isLoading" class="loading-indicator">
  209. <i class="el-icon-loading"></i>
  210. <span>加载中...</span>
  211. </div>
  212. <!-- 没有更多数据 -->
  213. <div v-if="!hasMore && !isLoading" class="no-more">
  214. 没有更多数据了
  215. </div>
  216. </el-select>
  217. </el-form-item>
  218. <el-form-item label="上下架状态" prop="goodsStatus" v-if="form.taskType == 6">
  219. <el-select v-model="form.goodsStatus" placeholder="请选择上下架状态">
  220. <el-option v-for="i in goodsStatusOptions" :key="i.value" :label="i.label" :value="i.value"></el-option>
  221. </el-select>
  222. </el-form-item>
  223. <el-form-item label="签到奖励" v-if="form.taskType == 7">
  224. <div class="sign-reward-list">
  225. <div class="sign-reward-item" v-for="(item, index) in form.rewards" :key="index">
  226. <el-select v-model="item.rewardType" placeholder="奖励类型" style="width: 120px; margin-right: 8px;" @change="onSignRewardTypeChange(item, index)">
  227. <el-option v-for="i in rewardTypeOptions" :key="i.value" :label="i.label" :value="i.value"></el-option>
  228. </el-select>
  229. <el-select v-if="item.rewardType == 2" v-model="item.rewardId" placeholder="请选择积分红包" style="width: 180px; margin-right: 8px;">
  230. <el-option v-for="i in redOptions" :key="i.value" :label="i.label" :value="i.value"></el-option>
  231. </el-select>
  232. <template v-else-if="item.rewardType == 5">
  233. <el-select v-model="item.rewardId" placeholder="请选择优惠券" style="width: 160px; margin-right: 8px;">
  234. <el-option v-for="i in couponOptions" :key="i.value" :label="i.label" :value="i.value"></el-option>
  235. </el-select>
  236. <el-input-number v-model="item.couponCount" :min="1" :max="99" controls-position="right" placeholder="数量" style="width: 110px; margin-right: 8px;"></el-input-number>
  237. <span style="margin-right: 8px; color: #909399;">张</span>
  238. </template>
  239. <el-input v-else-if="item.rewardType == 6" v-model="item.amount" placeholder="金额精确到0.1元" style="width: 180px; margin-right: 8px;" @input="onCashAmountInput(item)"></el-input>
  240. <el-button type="text" icon="el-icon-delete" @click="removeSignReward(index)" v-if="form.rewards.length > 1">删除</el-button>
  241. </div>
  242. <el-button type="text" icon="el-icon-plus" @click="addSignReward">添加奖励</el-button>
  243. </div>
  244. </el-form-item>
  245. <el-form-item label="触发时间" prop="content">
  246. <el-time-picker
  247. default-value="2025-01-01 00:00:00"
  248. v-model="form.triggerValue"
  249. :picker-options="{
  250. selectableRange: '00:00:00 - 23:59:59'
  251. }"
  252. placeholder="任意时间点">
  253. </el-time-picker>
  254. </el-form-item>
  255. <!-- <el-form-item label="触发类型" prop="triggerType">-->
  256. <!-- <el-select v-model="form.triggerType" placeholder="请选择触发类型">-->
  257. <!-- <el-option label="请选择字典生成" value="" />-->
  258. <!-- </el-select>-->
  259. <!-- </el-form-item>-->
  260. <!-- <el-form-item label="触发值" prop="triggerValue">-->
  261. <!-- <el-input v-model="form.triggerValue" placeholder="请输入触发值" />-->
  262. <!-- </el-form-item>-->
  263. <!-- <el-form-item label="任务内容">-->
  264. <!-- <editor v-model="form.content" :min-height="192"/>-->
  265. <!-- </el-form-item>-->
  266. <el-form-item label="状态">
  267. <el-radio-group v-model="form.status">
  268. <el-radio :label="1">启用</el-radio>
  269. <el-radio :label="0">禁用</el-radio>
  270. </el-radio-group>
  271. </el-form-item>
  272. </el-form>
  273. <div slot="footer" class="dialog-footer">
  274. <el-button type="primary" @click="submitForm">确 定</el-button>
  275. <el-button @click="cancel">取 消</el-button>
  276. </div>
  277. </el-dialog>
  278. </div>
  279. </template>
  280. <script>
  281. import { listTask, getTask, delTask, addTask, updateTask, exportTask } from "@/api/live/task";
  282. import {listLiveGoods} from "@/api/live/liveGoods";
  283. import {listLiveRedConfOn} from "@/api/live/liveRedConf";
  284. import {listLiveLotteryConfOn} from "@/api/live/liveLotteryConf";
  285. import {listLiveCouponOn} from "@/api/live/liveCoupon";
  286. export default {
  287. props:{
  288. isViewOnly: {
  289. type: Boolean,
  290. default: false,
  291. }
  292. },
  293. name: "Task",
  294. data() {
  295. return {
  296. taskTypeOptions:[
  297. {
  298. value: 1,
  299. label: "定时卡片推荐商品"
  300. },
  301. {
  302. value: 2,
  303. label: "定时红包发送"
  304. },
  305. {
  306. value: 4,
  307. label: "定时抽奖"
  308. },
  309. {
  310. value: 5,
  311. label: "定时优惠券"
  312. },
  313. {
  314. value: 6,
  315. label: "定时商品上下架"
  316. },
  317. {
  318. value: 7,
  319. label: "签到任务"
  320. }
  321. ],
  322. rewardTypeOptions: [
  323. { value: 2, label: "积分红包" },
  324. { value: 5, label: "优惠券" },
  325. { value: 6, label: "金额红包" }
  326. ],
  327. statusOptions:[{
  328. value: "0",
  329. label: "禁用"
  330. },
  331. {
  332. value: "1",
  333. label: "启用"
  334. }
  335. ],
  336. goodsStatusOptions:[{
  337. value: 0,
  338. label: "下架"
  339. },
  340. {
  341. value: 1,
  342. label: "上架"
  343. }
  344. ],
  345. productOptions:[],
  346. haveData:{
  347. goods:false,
  348. red:false,
  349. lottery:false,
  350. coupon:false
  351. },
  352. redOptions:[],
  353. lotteryOptions:[],
  354. couponOptions:[],
  355. // 遮罩层
  356. loading: true,
  357. // 导出遮罩层
  358. exportLoading: false,
  359. // 选中数组
  360. ids: [],
  361. // 非单个禁用
  362. single: true,
  363. // 非多个禁用
  364. multiple: true,
  365. // 显示搜索条件
  366. showSearch: true,
  367. // 总条数
  368. total: 0,
  369. liveAbled: false,
  370. // 直播间自动化任务配置表格数据
  371. taskList: [],
  372. // 弹出层标题
  373. title: "",
  374. // 是否显示弹出层
  375. open: false,
  376. // 查询参数
  377. queryParams: {
  378. pageNum: 1,
  379. pageSize: 10,
  380. liveId: null,
  381. taskName: null,
  382. taskType: null,
  383. triggerType: null,
  384. triggerValue: null,
  385. content: null,
  386. status: null,
  387. createdTime: null,
  388. updatedTime: null
  389. },
  390. // 表单参数
  391. form: {},
  392. // 表单校验
  393. rules: {
  394. liveId: [
  395. { required: true, message: "直播间ID不能为空", trigger: "blur" }
  396. ],
  397. taskName: [
  398. { required: true, message: "任务名称不能为空", trigger: "blur" }
  399. ],
  400. taskType: [
  401. { required: true, message: "任务类型:1-定时推送卡片商品 2-定时发送红包 ", trigger: "change" }
  402. ],
  403. triggerType: [
  404. { required: true, message: "触发类型:相对直播开始时间不能为空", trigger: "change" }
  405. ],
  406. triggerValue: [
  407. { required: true, message: "触发值:绝对时间用yyyy-MM-dd HH:mm:ss,相对时间用分钟数不能为空", trigger: "blur" }
  408. ],
  409. status: [
  410. { required: true, message: "状态:0-禁用 1-启用不能为空", trigger: "blur" }
  411. ],
  412. createdTime: [
  413. { required: true, message: "创建时间不能为空", trigger: "blur" }
  414. ],
  415. updatedTime: [
  416. { required: true, message: "更新时间不能为空", trigger: "blur" }
  417. ]
  418. },
  419. liveId: null,
  420. listParams: {
  421. pageNum: 1,
  422. pageSize: 10,
  423. liveId: null
  424. },
  425. socket: null,
  426. isLoading: false, // 是否正在加载
  427. hasMore: true, // 是否还有更多数据
  428. };
  429. },
  430. watch: {
  431. // 监听路由的 query 参数变化
  432. '$route.query': {
  433. handler(newQuery) {
  434. if (this.$route.params.liveId) {
  435. this.liveId = this.$route.params.liveId;
  436. }else {
  437. this.liveId = this.$route.query.liveId;
  438. }
  439. if(this.liveId == null) {
  440. return;
  441. }
  442. this.liveAbled = true
  443. this.queryParams.liveId = this.liveId;
  444. this.getList();
  445. },
  446. // 初始化时立即执行一次
  447. immediate: true
  448. }
  449. },
  450. created() {
  451. this.socket = this.$store.state.liveWs[this.liveId]
  452. },
  453. methods: {
  454. statusFormatter(row, column, value){
  455. if (!value) return '--'; // 空值处理
  456. switch ( value){
  457. case 0:
  458. return "禁用";
  459. case 1:
  460. return "启用";
  461. default:
  462. return "--";
  463. }
  464. },
  465. finishStatusFormatter(row, column, value){
  466. if (!value) return '--'; // 空值处理
  467. switch ( value){
  468. case 0:
  469. return "未执行";
  470. case 1:
  471. return "已执行";
  472. default:
  473. return "--";
  474. }
  475. },
  476. taskTypeFormatter(row, column, value){
  477. if (!value) return '--'; // 空值处理
  478. switch (value) {
  479. case 1:
  480. return "定时推送卡片商品";
  481. case 2:
  482. return "定时发送红包";
  483. case 4:
  484. return "定时抽奖";
  485. case 5:
  486. return "定时优惠券";
  487. case 6:
  488. return "定时商品上下架";
  489. case 7:
  490. return "签到任务";
  491. default:
  492. return "--";
  493. }
  494. },
  495. triggerTypeFormatter(row, column, value){
  496. if (!value) return '--'; // 空值处理
  497. switch (value) {
  498. case 1:
  499. return "相对直播开始时间";
  500. case 2:
  501. return "相对时间";
  502. default:
  503. return "--";
  504. }
  505. },
  506. productNameFormatter(row, column, value){
  507. let content = JSON.parse(row.content)
  508. if(content.productName) {
  509. return content.productName
  510. }
  511. if (row.taskType == 7) {
  512. const rewards = Array.isArray(content.rewards) ? content.rewards : null;
  513. if (rewards && rewards.length) {
  514. return rewards.map(r => {
  515. if (!r) return '';
  516. if (r.rewardType == 2) return (r.reward && r.reward.desc) || '积分红包';
  517. if (r.rewardType == 5) {
  518. const title = (r.reward && r.reward.title) || '优惠券';
  519. const count = r.couponCount > 0 ? r.couponCount : 1;
  520. return title + '×' + count;
  521. }
  522. if (r.rewardType == 6) return '金额红包¥' + (r.amount != null ? r.amount : '--');
  523. return '';
  524. }).filter(Boolean).join('、') || '--';
  525. }
  526. if (content.reward) {
  527. if (content.rewardType == 2) return content.reward.desc || '--';
  528. if (content.rewardType == 5) return content.reward.title || '--';
  529. }
  530. }
  531. if (row.taskType == 2 && content.desc) return content.desc;
  532. if (row.taskType == 4 && content.desc) return content.desc;
  533. if (row.taskType == 5 && content.title) return content.title;
  534. return "--";
  535. },
  536. triggerValueFormatter(row, column, value) {
  537. if (!value) return '--'; // 空值处理
  538. // 创建日期对象(兼容时间戳和字符串)
  539. let date;
  540. if (typeof value === 'number') {
  541. // 处理时间戳(注意:如果是10位时间戳需要乘以1000)
  542. date = new Date(value.toString().length === 10 ? value * 1000 : value);
  543. } else if (typeof value === 'string') {
  544. // 处理字符串格式(尝试直接转换)
  545. date = new Date(value);
  546. } else {
  547. return '格式错误';
  548. }
  549. // 检查日期是否有效
  550. if (isNaN(date.getTime())) {
  551. return '无效时间';
  552. }
  553. // 格式化日期为 "yyyy-MM-dd HH:mm:ss"
  554. const hours = String(date.getHours()).padStart(2, '0');
  555. const minutes = String(date.getMinutes()).padStart(2, '0');
  556. const seconds = String(date.getSeconds()).padStart(2, '0');
  557. return `${hours}:${minutes}:${seconds}`;
  558. },
  559. async updateTaskType(){
  560. this.hasMore = true;
  561. this.listParams = {
  562. pageNum: 1,
  563. pageSize: 10,
  564. liveId: null
  565. }
  566. this.form.content = null;
  567. this.form.goodsId = null;
  568. this.form.goodsStatus = null;
  569. this.form.rewardType = null;
  570. this.form.rewardId = null;
  571. this.form.rewards = this.form.taskType == 7 ? [{ rewardType: null, rewardId: null, couponCount: 1, amount: null }] : [];
  572. if (this.isLoading || !this.hasMore) return;
  573. this.isLoading = true;
  574. this.listParams.liveId = this.liveId;
  575. if(this.listParams.liveId == null) {
  576. this.$message.error("页面错误,请联系管理员");
  577. return;
  578. }
  579. try{
  580. if (this.form.taskType == 1) {
  581. await this.addGoodsList();
  582. }else if (this.form.taskType == 2) {
  583. await this.addRedList();
  584. }else if (this.form.taskType == 4) {
  585. await this.addLotteryList();
  586. }else if(this.form.taskType == 5){
  587. await this.addCouponList();
  588. }else if(this.form.taskType == 6){
  589. await this.addGoodsList();
  590. }else if(this.form.taskType == 7){
  591. await this.preloadAllSignRewardOptions();
  592. }
  593. }catch ( err){
  594. console.error('加载数据失败:', err);
  595. }finally {
  596. this.isLoading = false;
  597. }
  598. },
  599. async preloadAllSignRewardOptions() {
  600. await this.addRedList();
  601. this.listParams.pageNum = 1;
  602. this.hasMore = true;
  603. await this.addCouponList();
  604. },
  605. async onSignRewardTypeChange(item) {
  606. item.rewardId = null;
  607. item.couponCount = 1;
  608. item.amount = null;
  609. if (!item.rewardType) return;
  610. const others = (this.form.rewards || []).filter(r => r !== item);
  611. if (item.rewardType == 2 && others.some(r => r.rewardType == 2)) {
  612. this.msgError("积分红包只能配置一个");
  613. item.rewardType = null;
  614. return;
  615. }
  616. if (item.rewardType == 6 && others.some(r => r.rewardType == 6)) {
  617. this.msgError("金额红包只能配置一个");
  618. item.rewardType = null;
  619. return;
  620. }
  621. this.listParams = { pageNum: 1, pageSize: 10, liveId: this.liveId };
  622. this.hasMore = true;
  623. if (item.rewardType == 2) await this.addRedList();
  624. else if (item.rewardType == 5) await this.addCouponList();
  625. },
  626. onCashAmountInput(item) {
  627. if (item.amount == null || item.amount === '') return;
  628. let val = String(item.amount).replace(/[^\d.]/g, '');
  629. const parts = val.split('.');
  630. if (parts.length > 2) {
  631. val = parts[0] + '.' + parts.slice(1).join('');
  632. }
  633. // 金额红包精确到 0.1 元(一位小数)
  634. if (parts.length === 2) {
  635. val = parts[0] + '.' + parts[1].slice(0, 1);
  636. }
  637. item.amount = val;
  638. },
  639. addSignReward() {
  640. if (!this.form.rewards) this.form.rewards = [];
  641. this.form.rewards.push({ rewardType: null, rewardId: null, couponCount: 1, amount: null });
  642. },
  643. removeSignReward(index) {
  644. this.form.rewards.splice(index, 1);
  645. },
  646. async loadSignRewardOptions(rewardType) {
  647. this.hasMore = true;
  648. this.listParams = {
  649. pageNum: 1,
  650. pageSize: 10,
  651. liveId: this.liveId
  652. }
  653. if (rewardType == 2) {
  654. await this.addRedList();
  655. } else if (rewardType == 5) {
  656. await this.addCouponList();
  657. }
  658. },
  659. addGoodsList() {
  660. if(this.haveData.goods) return
  661. listLiveGoods(this.listParams).then(res => {
  662. if(res.rows.length > 0) {
  663. res.rows.forEach(item => {
  664. // 根据productName和goodsId组装成为label和value
  665. this.productOptions.push({
  666. value: item.goodsId,
  667. label: item.productName
  668. })
  669. })
  670. this.listParams.pageNum++;
  671. // 判断是否还有更多数据
  672. this.hasMore = this.productOptions.length < res.total;
  673. if (this.hasMore) {
  674. this.addGoodsList();
  675. } else {
  676. this.haveData.goods = true;
  677. }
  678. } else {
  679. this.hasMore = false;
  680. this.haveData.goods = true;
  681. }
  682. });
  683. },
  684. addRedList() {
  685. if(this.haveData.red) return
  686. listLiveRedConfOn(this.listParams).then(res => {
  687. if(res.rows.length > 0) {
  688. res.rows.forEach(item => {
  689. // 根据productName和goodsId组装成为label和value
  690. this.redOptions.push({
  691. value: item.redId,
  692. label: item.desc
  693. })
  694. })
  695. this.listParams.pageNum++;
  696. // 判断是否还有更多数据
  697. this.hasMore = this.redOptions.length < res.total;
  698. if (this.hasMore) {
  699. this.addRedList();
  700. } else {
  701. this.haveData.red = true
  702. }
  703. } else {
  704. this.haveData.red = true
  705. this.hasMore = false;
  706. }
  707. });
  708. },
  709. addLotteryList() {
  710. if(this.haveData.lottery) return
  711. listLiveLotteryConfOn(this.listParams).then(res => {
  712. if(res.rows.length > 0) {
  713. res.rows.forEach(item => {
  714. // 根据productName和goodsId组装成为label和value
  715. this.lotteryOptions.push({
  716. value: item.lotteryId,
  717. label: item.desc
  718. })
  719. })
  720. this.listParams.pageNum++;
  721. // 判断是否还有更多数据
  722. this.hasMore = this.lotteryOptions.length < res.total;
  723. if (this.hasMore) {
  724. this.addLotteryList();
  725. } else {
  726. this.haveData.lottery = true
  727. }
  728. } else {
  729. this.haveData.lottery = true
  730. this.hasMore = false;
  731. }
  732. });
  733. },
  734. addCouponList() {
  735. if(this.haveData.coupon) return
  736. listLiveCouponOn(this.listParams).then(res => {
  737. if(res.rows.length > 0) {
  738. res.rows.forEach(item => {
  739. // 根据productName和goodsId组装成为label和value
  740. this.couponOptions.push({
  741. value: item.couponId,
  742. label: item.title
  743. })
  744. })
  745. this.listParams.pageNum++;
  746. // 判断是否还有更多数据
  747. this.hasMore = this.couponOptions.length < res.total;
  748. if (this.hasMore) {
  749. this.addCouponList();
  750. } else {
  751. this.haveData.coupon = true
  752. }
  753. } else {
  754. this.haveData.coupon = true
  755. this.hasMore = false;
  756. }
  757. });
  758. },
  759. /** 查询直播间自动化任务配置列表 */
  760. getList() {
  761. if(this.liveId == null) {
  762. this.$message.error("页面错误,请联系管理员");
  763. return;
  764. }
  765. this.loading = true;
  766. listTask(this.queryParams).then(res => {
  767. this.taskList = res.rows;
  768. this.total = res.total;
  769. this.loading = false;
  770. });
  771. },
  772. // 取消按钮
  773. cancel() {
  774. this.open = false;
  775. this.reset();
  776. },
  777. // 表单重置
  778. reset() {
  779. this.form = {
  780. id: null,
  781. liveId: this.liveId,
  782. taskName: null,
  783. taskType: null,
  784. triggerType: null,
  785. triggerValue: null,
  786. content: null,
  787. goodsId: null,
  788. goodsStatus: null,
  789. rewardType: null,
  790. rewardId: null,
  791. rewards: [],
  792. status: 1,
  793. createdTime: null,
  794. updatedTime: null
  795. };
  796. this.listParams={
  797. pageNum: 1,
  798. pageSize: 10,
  799. liveId: this.liveId
  800. }
  801. this.hasMore = true;
  802. this.resetForm("form");
  803. },
  804. /** 搜索按钮操作 */
  805. handleQuery() {
  806. this.queryParams.pageNum = 1;
  807. this.getList();
  808. },
  809. /** 重置按钮操作 */
  810. resetQuery() {
  811. this.resetForm("queryForm");
  812. this.handleQuery();
  813. },
  814. // 多选框选中数据
  815. handleSelectionChange(selection) {
  816. this.ids = selection.map(item => item.id)
  817. this.single = selection.length!==1
  818. this.multiple = !selection.length
  819. },
  820. /** 新增按钮操作 */
  821. handleAdd() {
  822. this.reset();
  823. this.open = true;
  824. this.title = "添加直播间自动化任务配置";
  825. },
  826. /** 修改按钮操作 */
  827. async handleUpdate(row) {
  828. this.reset();
  829. if(row.taskType == 1){
  830. await this.addGoodsList();
  831. }else if(row.taskType == 2){
  832. await this.addRedList();
  833. }else if(row.taskType == 4){
  834. await this.addLotteryList();
  835. }else if(row.taskType == 5) {
  836. await this.addCouponList();
  837. }else if(row.taskType == 6) {
  838. await this.addGoodsList();
  839. }
  840. const id = row.id || this.ids
  841. getTask(id).then(async response => {
  842. this.form = response.data;
  843. let content = JSON.parse( response.data.content)
  844. if (this.form.taskType == 1) {
  845. this.form.content = content.goodsId;
  846. }else if (this.form.taskType == 2) {
  847. this.form.content = content.redId;
  848. }else if (this.form.taskType == 4) {
  849. this.form.content = content.lotteryId;
  850. }else if(this.form.taskType == 5){
  851. this.form.content = content.couponId;
  852. }else if(this.form.taskType == 6){
  853. this.form.goodsId = content.goodsId;
  854. this.form.goodsStatus = content.status;
  855. }else if(this.form.taskType == 7){
  856. await this.preloadAllSignRewardOptions();
  857. const rewards = [];
  858. if (Array.isArray(content.rewards) && content.rewards.length) {
  859. content.rewards.forEach(r => {
  860. let rewardId = r.rewardId;
  861. if (rewardId == null && r.reward) {
  862. if (r.rewardType == 2) rewardId = r.reward.redId;
  863. else if (r.rewardType == 5) rewardId = r.reward.couponId;
  864. }
  865. rewards.push({
  866. rewardType: r.rewardType != null ? Number(r.rewardType) : null,
  867. rewardId: rewardId,
  868. couponCount: r.couponCount > 0 ? Number(r.couponCount) : 1,
  869. amount: r.amount != null ? r.amount : null
  870. });
  871. });
  872. } else if (content.rewardType != null) {
  873. // 兼容旧单奖励结构
  874. let rewardId = content.rewardId;
  875. if (rewardId == null && content.reward) {
  876. if (content.rewardType == 2) rewardId = content.reward.redId;
  877. else if (content.rewardType == 5) rewardId = content.reward.couponId;
  878. }
  879. rewards.push({
  880. rewardType: Number(content.rewardType),
  881. rewardId: rewardId,
  882. couponCount: content.couponCount > 0 ? Number(content.couponCount) : 1,
  883. amount: content.amount != null ? content.amount : null
  884. });
  885. }
  886. this.form.rewards = rewards.length ? rewards : [{ rewardType: null, rewardId: null, couponCount: 1, amount: null }];
  887. }
  888. this.open = true;
  889. this.title = "修改直播间自动化任务配置";
  890. });
  891. },
  892. /** 提交按钮 */
  893. submitForm() {
  894. this.form.liveId = this.liveId;
  895. if(this.liveId == null) {
  896. this.msgError("请选择直播间");
  897. return;
  898. }
  899. if(this.form.taskType == 6){
  900. if(this.form.goodsId == null) {
  901. this.msgError("请选择商品");
  902. return;
  903. }
  904. this.form.content = JSON.stringify({
  905. goodsId: this.form.goodsId,
  906. status: this.form.goodsStatus
  907. })
  908. }
  909. if(this.form.taskType == 7){
  910. const rewards = [];
  911. let hasPointRed = false;
  912. let hasCashRed = false;
  913. for (const r of (this.form.rewards || [])) {
  914. if (!r || r.rewardType == null) continue;
  915. if (r.rewardType == 2) {
  916. if (r.rewardId == null) {
  917. this.msgError("请选择积分红包");
  918. return;
  919. }
  920. if (hasPointRed) {
  921. this.msgError("积分红包只能配置一个");
  922. return;
  923. }
  924. hasPointRed = true;
  925. rewards.push({ rewardType: 2, rewardId: r.rewardId });
  926. } else if (r.rewardType == 5) {
  927. if (r.rewardId == null) {
  928. this.msgError("请选择优惠券");
  929. return;
  930. }
  931. const couponCount = r.couponCount > 0 ? Number(r.couponCount) : 1;
  932. rewards.push({ rewardType: 5, rewardId: r.rewardId, couponCount });
  933. } else if (r.rewardType == 6) {
  934. if (hasCashRed) {
  935. this.msgError("金额红包只能配置一个");
  936. return;
  937. }
  938. const amount = Number(r.amount);
  939. if (!amount || amount <= 0) {
  940. this.msgError("请输入正确的金额红包金额");
  941. return;
  942. }
  943. // 精确到 0.1 元
  944. const normalized = Math.round(amount * 10) / 10;
  945. if (Math.abs(amount - normalized) > 1e-8) {
  946. this.msgError("金额红包需精确到0.1元");
  947. return;
  948. }
  949. hasCashRed = true;
  950. rewards.push({ rewardType: 6, amount: normalized.toFixed(1) });
  951. }
  952. }
  953. if (!rewards.length) {
  954. this.msgError("请至少配置一个签到奖励");
  955. return;
  956. }
  957. this.form.content = JSON.stringify({ rewards })
  958. }
  959. this.$refs["form"].validate(valid => {
  960. if (valid) {
  961. if (this.form.id != null) {
  962. updateTask(this.form).then(response => {
  963. this.msgSuccess("修改成功");
  964. this.open = false;
  965. this.getList();
  966. });
  967. } else {
  968. addTask(this.form).then(response => {
  969. this.msgSuccess("新增成功");
  970. this.open = false;
  971. this.getList();
  972. });
  973. }
  974. }
  975. });
  976. },
  977. /** 删除按钮操作 */
  978. handleDelete(row) {
  979. if(this.socket == null) {
  980. this.$message.error("请进入直播间后,在进行操作!")
  981. return;
  982. }
  983. const ids = row.id || this.ids;
  984. this.$confirm('是否确认删除直播间自动化任务配置编号为"' + ids + '"的数据项?', "警告", {
  985. confirmButtonText: "确定",
  986. cancelButtonText: "取消",
  987. type: "warning"
  988. }).then(function() {
  989. return delTask(ids);
  990. }).then(() => {
  991. this.getList();
  992. this.msgSuccess("删除成功");
  993. const msg={
  994. cmd:'delAutoTask',
  995. data:row.absValue,
  996. liveId:this.liveId,
  997. userType:1
  998. }
  999. this.socket.send(JSON.stringify( msg))
  1000. }).catch(() => {});
  1001. },
  1002. /** 导出按钮操作 */
  1003. handleExport() {
  1004. const queryParams = this.queryParams;
  1005. this.$confirm('是否确认导出所有直播间自动化任务配置数据项?', "警告", {
  1006. confirmButtonText: "确定",
  1007. cancelButtonText: "取消",
  1008. type: "warning"
  1009. }).then(() => {
  1010. this.exportLoading = true;
  1011. return exportTask(queryParams);
  1012. }).then(response => {
  1013. this.download(response.msg);
  1014. this.exportLoading = false;
  1015. }).catch(() => {});
  1016. },
  1017. }
  1018. };
  1019. </script>
  1020. <style scoped>
  1021. .loading-indicator {
  1022. padding: 10px;
  1023. text-align: center;
  1024. color: #606266;
  1025. font-size: 12px;
  1026. display: flex;
  1027. align-items: center;
  1028. justify-content: center;
  1029. }
  1030. .loading-indicator .el-icon-loading {
  1031. margin-right: 5px;
  1032. animation: rotate 1s linear infinite;
  1033. }
  1034. .no-more {
  1035. padding: 10px;
  1036. text-align: center;
  1037. color: #909399;
  1038. font-size: 12px;
  1039. }
  1040. @keyframes rotate {
  1041. from { transform: rotate(0deg); }
  1042. to { transform: rotate(360deg); }
  1043. }
  1044. .sign-reward-list {
  1045. width: 100%;
  1046. }
  1047. .sign-reward-item {
  1048. display: flex;
  1049. align-items: center;
  1050. margin-bottom: 8px;
  1051. flex-wrap: wrap;
  1052. }
  1053. </style>