index.vue 57 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606
  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="奖励名称" prop="name">
  5. <el-input
  6. v-model="queryParams.name"
  7. placeholder="请输入奖励名称"
  8. clearable
  9. size="small"
  10. @keyup.enter.native="handleQuery"
  11. />
  12. </el-form-item>
  13. <el-form-item label="奖励描述" prop="description">
  14. <el-input
  15. v-model="queryParams.description"
  16. placeholder="请输入奖励描述"
  17. clearable
  18. size="small"
  19. @keyup.enter.native="handleQuery"
  20. />
  21. </el-form-item>
  22. <el-form-item label="奖励类型" prop="rewardType">
  23. <el-select v-model="queryParams.rewardType" placeholder="请选择奖励类型" clearable size="small">
  24. <el-option
  25. v-for="item in typeOptions"
  26. :key="item.dictValue"
  27. :label="item.dictLabel"
  28. :value="item.dictValue"
  29. />
  30. </el-select>
  31. </el-form-item>
  32. <el-form-item>
  33. <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
  34. <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
  35. </el-form-item>
  36. </el-form>
  37. <el-row :gutter="10" class="mb8">
  38. <el-col :span="1.5">
  39. <el-button
  40. type="primary"
  41. plain
  42. icon="el-icon-plus"
  43. size="mini"
  44. @click="handleAdd"
  45. v-hasPermi="['course:reward:add']"
  46. >新增</el-button>
  47. </el-col>
  48. <el-col :span="1.5">
  49. <el-button
  50. type="success"
  51. plain
  52. icon="el-icon-edit"
  53. size="mini"
  54. :disabled="single"
  55. @click="handleUpdate"
  56. v-hasPermi="['course:reward:edit']"
  57. >修改</el-button>
  58. </el-col>
  59. <el-col :span="1.5">
  60. <el-button
  61. type="danger"
  62. plain
  63. icon="el-icon-delete"
  64. size="mini"
  65. :disabled="multiple"
  66. @click="handleDelete"
  67. v-hasPermi="['course:reward:remove']"
  68. >删除</el-button>
  69. </el-col>
  70. <el-col :span="1.5">
  71. <el-button
  72. type="warning"
  73. plain
  74. icon="el-icon-download"
  75. size="mini"
  76. :loading="exportLoading"
  77. @click="handleExport"
  78. v-hasPermi="['course:reward:export']"
  79. >导出</el-button>
  80. </el-col>
  81. <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
  82. </el-row>
  83. <el-table border v-loading="loading" :data="rewardList" @selection-change="handleSelectionChange">
  84. <el-table-column type="selection" width="55" align="center" />
  85. <el-table-column label="主键ID" align="center" prop="id" />
  86. <el-table-column label="奖励名称" align="center" prop="name" />
  87. <el-table-column label="奖励描述" align="center" prop="description" />
  88. <el-table-column label="奖励类型" align="center" prop="rewardType">
  89. <template slot-scope="scope">
  90. <dict-tag :options="typeOptions" :value="scope.row.rewardType"/>
  91. </template>
  92. </el-table-column>
  93. <el-table-column label="状态" align="center" prop="status">
  94. <template slot-scope="scope">
  95. <dict-tag :options="statusOptions" :value="scope.row.status"/>
  96. </template>
  97. </el-table-column>
  98. <el-table-column label="期望值" align="center" prop="expectedValue" />
  99. <el-table-column label="实际奖励内容" align="center" prop="actualRewards">
  100. <template slot-scope="scope">
  101. <el-button
  102. size="mini"
  103. type="text"
  104. icon="el-icon-view"
  105. @click="handleViewReward(scope.row)"
  106. >查看详情</el-button>
  107. </template>
  108. </el-table-column>
  109. <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
  110. <template slot-scope="scope">
  111. <el-button
  112. size="mini"
  113. type="text"
  114. icon="el-icon-edit"
  115. @click="handleUpdate(scope.row)"
  116. v-hasPermi="['course:reward:edit']"
  117. >修改</el-button>
  118. <el-button
  119. size="mini"
  120. type="text"
  121. icon="el-icon-delete"
  122. @click="handleDelete(scope.row)"
  123. v-hasPermi="['course:reward:remove']"
  124. >删除</el-button>
  125. </template>
  126. </el-table-column>
  127. </el-table>
  128. <pagination
  129. v-show="total>0"
  130. :total="total"
  131. :page.sync="queryParams.pageNum"
  132. :limit.sync="queryParams.pageSize"
  133. @pagination="getList"
  134. />
  135. <!-- 添加或修改奖励配置对话框 -->
  136. <el-dialog :title="title" :visible.sync="open" :width="width" append-to-body>
  137. <el-form ref="form" :model="form" :rules="rules" label-width="80px">
  138. <el-form-item label="奖励名称" prop="name">
  139. <el-input v-model="form.name" placeholder="请输入奖励名称" @input="updateRewardItemsName" />
  140. </el-form-item>
  141. <el-form-item label="奖励类型" prop="rewardType">
  142. <el-select v-model="form.rewardType" placeholder="请选择奖励类型" @change="handleRewardTypeChange">
  143. <el-option
  144. v-for="item in typeOptions"
  145. :key="item.dictValue"
  146. :label="item.dictLabel"
  147. :value="item.dictValue"
  148. />
  149. </el-select>
  150. </el-form-item>
  151. <el-form-item label="期望值" prop="expectedValue">
  152. <el-input-number :min="0" :precision="2" :step="1" v-model="form.expectedValue" placeholder="期望值" />
  153. </el-form-item>
  154. <el-form-item v-if="form.rewardType === '1'" label="宝箱图片">
  155. <div class="image-upload-group">
  156. <div class="image-upload-item">
  157. <span>关闭图片</span>
  158. <el-upload
  159. v-model="form.closeChestUrl"
  160. class="avatar-uploader"
  161. :action="uploadUrl"
  162. :show-file-list="false"
  163. :on-success="handleSuccessClose"
  164. :before-upload="beforeUpload">
  165. <img v-if="form.closeChestUrl" :src="form.closeChestUrl" class="avatar">
  166. <i v-else class="el-icon-plus avatar-uploader-icon"></i>
  167. </el-upload>
  168. </div>
  169. <div class="image-upload-item">
  170. <span>开启图片</span>
  171. <el-upload
  172. v-model="form.openChestUrl"
  173. class="avatar-uploader"
  174. :action="uploadUrl"
  175. :show-file-list="false"
  176. :on-success="handleSuccessOpen"
  177. :before-upload="beforeUpload">
  178. <img v-if="form.openChestUrl" :src="form.openChestUrl" class="avatar">
  179. <i v-else class="el-icon-plus avatar-uploader-icon"></i>
  180. </el-upload>
  181. </div>
  182. </div>
  183. </el-form-item>
  184. <el-form-item v-if="form.rewardType === '2'"
  185. label="红包金额" prop="account">
  186. <el-input-number v-model="form.account" :min="0" :step="0.1" style="width: 200px;"></el-input-number>
  187. </el-form-item>
  188. <el-form-item v-if="form.rewardType === '3'"
  189. label="积分币数" prop="account">
  190. <el-input-number v-model="form.account" :min="0" :step="1" style="width: 200px;"></el-input-number>
  191. </el-form-item>
  192. <el-form-item v-if="form.rewardType === '1'" label="宝箱奖励" prop="rewardItems">
  193. <div class="reward-table-container">
  194. <div class="table-header">
  195. <span>宝箱奖励配置</span>
  196. <el-button type="primary" icon="el-icon-plus" size="mini" @click="addRewardItem">添加奖励项</el-button>
  197. </div>
  198. <el-table :data="form.rewardItems" border size="small">
  199. <el-table-column v-if="false" label="奖励名称" >
  200. <template slot-scope="scope">
  201. <el-input v-model="scope.row.name" :placeholder="form.name || '请输入奖励名称'"></el-input>
  202. </template>
  203. </el-table-column>
  204. <el-table-column label="奖励模式">
  205. <template slot-scope="scope">
  206. <el-select v-model="scope.row.jltype" disabled placeholder="请选择奖励模式" style="width: 100%">
  207. <el-option
  208. v-for="item in jltypeOptions"
  209. :key="item.dictValue"
  210. :label="item.dictLabel"
  211. :value="item.dictValue"
  212. />
  213. </el-select>
  214. </template>
  215. </el-table-column>
  216. <el-table-column label="奖励数量">
  217. <template slot-scope="scope">
  218. <el-input-number v-model="scope.row.amount" :min="1" :controls="false" style="width: 100%"></el-input-number>
  219. </template>
  220. </el-table-column>
  221. <el-table-column label="概率" >
  222. <template slot-scope="scope">
  223. <el-input v-model="scope.row.probability" placeholder="例如: 20%"></el-input>
  224. </template>
  225. </el-table-column>
  226. <el-table-column v-if="false" label="唯一序列号" >
  227. <template slot-scope="scope">
  228. <el-input v-model="scope.row.code" placeholder="奖励唯一序列号">
  229. <el-button slot="append" icon="el-icon-refresh" @click="generateUUID(scope.row)"></el-button>
  230. </el-input>
  231. </template>
  232. </el-table-column>
  233. <el-table-column label="操作" width="80">
  234. <template slot-scope="scope">
  235. <el-button type="danger" icon="el-icon-delete" size="mini" @click="removeRewardItem(scope.$index)"></el-button>
  236. </template>
  237. </el-table-column>
  238. </el-table>
  239. </div>
  240. </el-form-item>
  241. <el-form-item v-if="form.rewardType === '4'" label="转盘奖励" prop="rewardItems">
  242. <div class="reward-table-container spin-reward">
  243. <div class="table-header">
  244. <span>转盘奖励配置</span>
  245. <el-button type="primary" icon="el-icon-plus" @click="addSpinRewardItem">添加奖励项</el-button>
  246. </div>
  247. <div class="spin-tips">建议:概率填写为百分比字符串,如 12.5% ,各项之和=100%</div>
  248. <el-table :data="form.rewardItems" border stripe>
  249. <el-table-column label="图标" align="center" width="72">
  250. <template slot-scope="scope">
  251. <el-upload
  252. class="spin-icon-uploader"
  253. :action="uploadUrl"
  254. :show-file-list="false"
  255. :before-upload="beforeUpload"
  256. accept="image/*"
  257. :on-success="(res,file)=>handleSpinIconSuccess(res, scope.row)"
  258. >
  259. <img v-if="scope.row.iconUrl" :src="scope.row.iconUrl" class="spin-icon" />
  260. <i v-else class="el-icon-plus avatar-uploader-icon spin-icon"></i>
  261. </el-upload>
  262. </template>
  263. </el-table-column>
  264. <el-table-column label="奖励名称" align="center">
  265. <template slot-scope="scope">
  266. <el-input v-model="scope.row.name" placeholder="请输入奖励名称"></el-input>
  267. </template>
  268. </el-table-column>
  269. <el-table-column label="奖励类型" align="center" width="120">
  270. <template slot-scope="scope">
  271. <el-select v-model="scope.row.type" @change="changeType(scope.row)" placeholder="选择类型" style="width: 100%">
  272. <el-option
  273. v-for="opt in spinItemTypeOptions"
  274. :key="opt.dictValue"
  275. :label="opt.dictLabel"
  276. :value="opt.dictValue"
  277. />
  278. </el-select>
  279. </template>
  280. </el-table-column>
  281. <el-table-column label="优惠券" align="center">
  282. <template slot-scope="scope">
  283. <el-select
  284. :disabled="scope.row.type !== '4'"
  285. :ref="`customSelect_${scope.row.code}`"
  286. v-model="scope.row.couponId"
  287. placeholder="请选择优惠券"
  288. @click.native.stop="scope.row.type === '4' ? openCouponDrawer(scope.row) : null"
  289. clearable
  290. style="width: 100%;">
  291. <el-option
  292. v-for="item in couponList"
  293. :key="item.id"
  294. :label="item.name"
  295. :value="item.id">
  296. </el-option>
  297. </el-select>
  298. </template>
  299. </el-table-column>
  300. <el-table-column label="商品" align="center">
  301. <template slot-scope="scope">
  302. <el-select
  303. :disabled="scope.row.type !== '5'"
  304. :ref="`customSelect_${scope.row.code}`"
  305. v-model="scope.row.goodsId"
  306. placeholder="请选择商品"
  307. @click.native.stop="scope.row.type === '5' ? openGoodsDrawer(scope.row) : null"
  308. clearable
  309. style="width: 100%;">
  310. <el-option
  311. v-for="item in goodsList"
  312. :key="item.id"
  313. :label="item.name"
  314. :value="item.id">
  315. </el-option>
  316. </el-select>
  317. </template>
  318. </el-table-column>
  319. <el-table-column label="奖励数量" align="center" show-overflow-tooltip width="180">
  320. <template slot-scope="scope">
  321. <el-input :disabled="scope.row.type === '5'" v-model="scope.row.amount" :controls="false" style="width: 100%"/>
  322. </template>
  323. </el-table-column>
  324. <el-table-column label="概率" align="center" class-name="probability-col" width="120">
  325. <template slot-scope="scope">
  326. <el-input v-model="scope.row.probability" placeholder="例如: 10%"></el-input>
  327. </template>
  328. </el-table-column>
  329. <el-table-column v-if="false" label="唯一序列号">
  330. <template slot-scope="scope">
  331. <el-input v-model="scope.row.code" placeholder="奖励唯一序列号">
  332. <el-button slot="append" icon="el-icon-refresh" @click="generateUUID(scope.row)"></el-button>
  333. </el-input>
  334. </template>
  335. </el-table-column>
  336. <el-table-column label="操作" align="center" width="80">
  337. <template slot-scope="scope">
  338. <el-button type="danger" icon="el-icon-delete" @click="removeRewardItem(scope.$index)"></el-button>
  339. </template>
  340. </el-table-column>
  341. </el-table>
  342. </div>
  343. </el-form-item>
  344. <el-form-item v-if="form.rewardType === '5'" label="保底转盘" prop="rewardItems">
  345. <div class="reward-table-container spin-reward">
  346. <div class="table-header">
  347. <span>保底转盘奖励配置</span>
  348. <el-button type="primary" icon="el-icon-plus" @click="addSpinRewardItem2">添加奖励项</el-button>
  349. </div>
  350. <div class="spin-tips">
  351. 建议:概率填写为百分比字符串,如 12.5% ,各项之和=100%
  352. <br/>
  353. 保底:保底奖励只能有1个且最后5次才能抽中
  354. </div>
  355. <el-table :data="form.rewardItems" border stripe>
  356. <el-table-column label="图标" align="center" width="72">
  357. <template slot-scope="scope">
  358. <el-upload
  359. class="spin-icon-uploader"
  360. :action="uploadUrl"
  361. :show-file-list="false"
  362. :before-upload="beforeUpload"
  363. accept="image/*"
  364. :on-success="(res,file)=>handleSpinIconSuccess(res, scope.row)"
  365. >
  366. <img v-if="scope.row.iconUrl" :src="scope.row.iconUrl" class="spin-icon" />
  367. <i v-else class="el-icon-plus avatar-uploader-icon spin-icon"></i>
  368. </el-upload>
  369. </template>
  370. </el-table-column>
  371. <el-table-column label="奖励名称" align="center">
  372. <template slot-scope="scope">
  373. <el-input v-model="scope.row.name" placeholder="请输入奖励名称"></el-input>
  374. </template>
  375. </el-table-column>
  376. <el-table-column label="奖励类型" align="center" width="120">
  377. <template slot-scope="scope">
  378. <el-select v-model="scope.row.type" @change="changeType(scope.row)" placeholder="选择类型" style="width: 100%">
  379. <el-option
  380. v-for="opt in spinItemTypeOptions"
  381. :key="opt.dictValue"
  382. :label="opt.dictLabel"
  383. :value="opt.dictValue"
  384. />
  385. </el-select>
  386. </template>
  387. </el-table-column>
  388. <el-table-column label="优惠券" align="center">
  389. <template slot-scope="scope">
  390. <el-select
  391. :disabled="scope.row.type !== '4'"
  392. :ref="`customSelect_${scope.row.code}`"
  393. v-model="scope.row.couponId"
  394. placeholder="请选择优惠券"
  395. @click.native.stop="scope.row.type === '4' ? openCouponDrawer(scope.row) : null"
  396. clearable
  397. style="width: 100%;">
  398. <el-option
  399. v-for="item in couponList"
  400. :key="item.id"
  401. :label="item.name"
  402. :value="item.id">
  403. </el-option>
  404. </el-select>
  405. </template>
  406. </el-table-column>
  407. <el-table-column label="商品" align="center">
  408. <template slot-scope="scope">
  409. <el-select
  410. :disabled="scope.row.type !== '5'"
  411. :ref="`customSelect_${scope.row.code}`"
  412. v-model="scope.row.goodsId"
  413. placeholder="请选择商品"
  414. @click.native.stop="scope.row.type === '5' ? openGoodsDrawer(scope.row) : null"
  415. clearable
  416. style="width: 100%;">
  417. <el-option
  418. v-for="item in goodsList"
  419. :key="item.id"
  420. :label="item.name"
  421. :value="item.id">
  422. </el-option>
  423. </el-select>
  424. </template>
  425. </el-table-column>
  426. <el-table-column label="奖励数量" align="center" width="180">
  427. <template slot-scope="scope">
  428. <el-input :disabled="scope.row.type === '5'" v-model="scope.row.amount" :controls="false" style="width: 100%"/>
  429. </template>
  430. </el-table-column>
  431. <el-table-column label="概率" align="center" class-name="probability-col" width="120">
  432. <template slot-scope="scope">
  433. <el-input v-model="scope.row.probability" placeholder="例如: 10%"></el-input>
  434. </template>
  435. </el-table-column>
  436. <el-table-column label="保底" align="center" width="100">
  437. <template slot-scope="scope">
  438. <el-switch
  439. v-model="scope.row.isGuarantee"
  440. active-color="#13ce66">
  441. </el-switch>
  442. </template>
  443. </el-table-column>
  444. <el-table-column v-if="false" label="唯一序列号">
  445. <template slot-scope="scope">
  446. <el-input v-model="scope.row.code" placeholder="奖励唯一序列号">
  447. <el-button slot="append" icon="el-icon-refresh" @click="generateUUID(scope.row)"></el-button>
  448. </el-input>
  449. </template>
  450. </el-table-column>
  451. <el-table-column label="操作" align="center" width="80">
  452. <template slot-scope="scope">
  453. <el-button type="danger" icon="el-icon-delete" @click="removeRewardItem(scope.$index)"></el-button>
  454. </template>
  455. </el-table-column>
  456. </el-table>
  457. </div>
  458. </el-form-item>
  459. <el-form-item v-if="form.rewardType === '6'" label="大礼品" prop="rewardItems">
  460. <div class="reward-table-container spin-reward">
  461. <div class="table-header">
  462. <span>转盘奖励配置</span>
  463. <el-button type="primary" icon="el-icon-plus" @click="addSpinRewardItem">添加奖励项</el-button>
  464. </div>
  465. <div class="spin-tips">建议:概率填写为百分比字符串,如 12.5% ,各项之和=100%</div>
  466. <el-table :data="form.rewardItems" border stripe>
  467. <el-table-column label="图标" align="center" width="72">
  468. <template slot-scope="scope">
  469. <el-upload
  470. class="spin-icon-uploader"
  471. :action="uploadUrl"
  472. :show-file-list="false"
  473. :before-upload="beforeUpload"
  474. accept="image/*"
  475. :on-success="(res,file)=>handleSpinIconSuccess(res, scope.row)"
  476. >
  477. <img v-if="scope.row.iconUrl" :src="scope.row.iconUrl" class="spin-icon" />
  478. <i v-else class="el-icon-plus avatar-uploader-icon spin-icon"></i>
  479. </el-upload>
  480. </template>
  481. </el-table-column>
  482. <el-table-column label="奖励名称" align="center">
  483. <template slot-scope="scope">
  484. <el-input v-model="scope.row.name" placeholder="请输入奖励名称"></el-input>
  485. </template>
  486. </el-table-column>
  487. <el-table-column label="奖励类型" align="center" width="120">
  488. <template slot-scope="scope">
  489. <el-select v-model="scope.row.type" @change="changeType(scope.row)" placeholder="选择类型" style="width: 100%">
  490. <el-option
  491. v-for="opt in spinItemTypeOptions"
  492. :key="opt.dictValue"
  493. :label="opt.dictLabel"
  494. :value="opt.dictValue"
  495. />
  496. </el-select>
  497. </template>
  498. </el-table-column>
  499. <el-table-column label="优惠券" align="center">
  500. <template slot-scope="scope">
  501. <el-select
  502. :disabled="scope.row.type !== '4'"
  503. :ref="`customSelect_${scope.row.code}`"
  504. v-model="scope.row.couponId"
  505. placeholder="请选择优惠券"
  506. @click.native.stop="scope.row.type === '4' ? openCouponDrawer(scope.row) : null"
  507. clearable
  508. style="width: 100%;">
  509. <el-option
  510. v-for="item in couponList"
  511. :key="item.id"
  512. :label="item.name"
  513. :value="item.id">
  514. </el-option>
  515. </el-select>
  516. </template>
  517. </el-table-column>
  518. <el-table-column label="商品" align="center">
  519. <template slot-scope="scope">
  520. <el-select
  521. :disabled="scope.row.type !== '5'"
  522. :ref="`customSelect_${scope.row.code}`"
  523. v-model="scope.row.goodsId"
  524. placeholder="请选择商品"
  525. @click.native.stop="scope.row.type === '5' ? openGoodsDrawer(scope.row) : null"
  526. clearable
  527. style="width: 100%;">
  528. <el-option
  529. v-for="item in goodsList"
  530. :key="item.id"
  531. :label="item.name"
  532. :value="item.id">
  533. </el-option>
  534. </el-select>
  535. </template>
  536. </el-table-column>
  537. <el-table-column label="奖励数量" align="center" show-overflow-tooltip width="180">
  538. <template slot-scope="scope">
  539. <el-input :disabled="scope.row.type === '5'" v-model="scope.row.amount" :controls="false" style="width: 100%"/>
  540. </template>
  541. </el-table-column>
  542. <el-table-column label="概率" align="center" class-name="probability-col" width="120">
  543. <template slot-scope="scope">
  544. <el-input v-model="scope.row.probability" placeholder="例如: 10%"></el-input>
  545. </template>
  546. </el-table-column>
  547. <el-table-column v-if="false" label="唯一序列号">
  548. <template slot-scope="scope">
  549. <el-input v-model="scope.row.code" placeholder="奖励唯一序列号">
  550. <el-button slot="append" icon="el-icon-refresh" @click="generateUUID(scope.row)"></el-button>
  551. </el-input>
  552. </template>
  553. </el-table-column>
  554. <el-table-column label="操作" align="center" width="80">
  555. <template slot-scope="scope">
  556. <el-button type="danger" icon="el-icon-delete" @click="removeRewardItem(scope.$index)"></el-button>
  557. </template>
  558. </el-table-column>
  559. </el-table>
  560. </div>
  561. </el-form-item>
  562. <el-form-item label="状态" prop="status">
  563. <el-radio-group v-model="form.status">
  564. <el-radio v-for="dict in statusOptions" :key="dict.dictValue" :label="dict.dictValue">{{dict.dictLabel}}</el-radio>
  565. </el-radio-group>
  566. </el-form-item>
  567. <el-form-item label="奖励描述" prop="description">
  568. <el-input v-model="form.description" type="textarea" placeholder="请输入奖励描述" />
  569. </el-form-item>
  570. </el-form>
  571. <div slot="footer" class="dialog-footer">
  572. <el-button type="primary" @click="submitForm">确 定</el-button>
  573. <el-button @click="cancel">取 消</el-button>
  574. </div>
  575. </el-dialog>
  576. <el-dialog
  577. :title="`奖励详情 - ${currentReward.name || '未知奖励'}`"
  578. :visible.sync="detailVisible"
  579. width="700px"
  580. append-to-body
  581. >
  582. <div class="reward-detail-container">
  583. <!-- 基础信息 -->
  584. <el-descriptions :column="2" border class="base-info" :label-style="{ width: '120px' }" :content-style="{ width: '200px' }">
  585. <el-descriptions-item label="奖励名称">{{ currentReward.name || '-' }}</el-descriptions-item>
  586. <el-descriptions-item label="奖励类型">
  587. <dict-tag :options="typeOptions" :value="currentReward.rewardType"/>
  588. </el-descriptions-item>
  589. <el-descriptions-item label="状态">
  590. <dict-tag :options="statusOptions" :value="currentReward.status"/>
  591. </el-descriptions-item>
  592. <el-descriptions-item label="期望值">{{ currentReward.expectedValue || 0 }}</el-descriptions-item>
  593. <el-descriptions-item label="描述" :span="2">{{ currentReward.description || '-' }}</el-descriptions-item>
  594. </el-descriptions>
  595. <!-- 奖励内容详情 -->
  596. <div class="reward-content">
  597. <h4>奖励内容详情</h4>
  598. <!-- 宝箱类型奖励 -->
  599. <div v-if="currentReward.rewardType === 1" class="chest-reward">
  600. <el-table :data="parsedRewardItems" size="small" border stripe>
  601. <el-table-column label="奖励模式">
  602. <template slot-scope="{row}">
  603. <el-select v-model="row.jltype" placeholder="请选择奖励模式" style="width: 100%" disabled>
  604. <el-option
  605. v-for="item in jltypeOptions"
  606. :key="item.dictValue"
  607. :label="item.dictLabel"
  608. :value="item.dictValue"
  609. />
  610. </el-select>
  611. </template>
  612. </el-table-column>
  613. <el-table-column label="数量" prop="amount" align="center">
  614. <template slot-scope="{row}">
  615. {{ row.amount || 1 }}
  616. </template>
  617. </el-table-column>
  618. <el-table-column label="概率" prop="probability" align="center">
  619. <template slot-scope="{row}">
  620. <el-tag v-if="row.probability" size="small">{{ row.probability }}</el-tag>
  621. <span v-else>-</span>
  622. </template>
  623. </el-table-column>
  624. </el-table>
  625. <div v-if="!parsedRewardItems || parsedRewardItems.length === 0" class="empty-tip">
  626. 暂无奖励配置
  627. </div>
  628. </div>
  629. <!-- 红包类型奖励 -->
  630. <div v-else-if="currentReward.rewardType === 2" class="redpacket-reward">
  631. <div class="reward-amount">
  632. <i class="el-icon-money" style="color: #e6a23c; font-size: 24px;"></i>
  633. <span class="amount-text">{{ rewardAmount }} 元</span>
  634. <el-tag type="warning" size="small">红包奖励</el-tag>
  635. </div>
  636. </div>
  637. <!-- 积分币类型奖励 -->
  638. <div v-else-if="currentReward.rewardType === 3" class="points-reward">
  639. <div class="reward-amount">
  640. <i class="el-icon-coin" style="color: #67c23a; font-size: 24px;"></i>
  641. <span class="amount-text">{{ rewardAmount }} 积分币</span>
  642. <el-tag type="success" size="small">积分币奖励</el-tag>
  643. </div>
  644. </div>
  645. <!-- 转盘类型奖励 -->
  646. <div v-else-if="currentReward.rewardType === 4 || currentReward.rewardType === 6" class="chest-reward">
  647. <el-table :data="parsedRewardItems" size="small" border stripe>
  648. <el-table-column label="图标" prop="iconUrl" align="center" width="80">
  649. <template slot-scope="{row}">
  650. <el-image
  651. v-if="row.iconUrl"
  652. :src="row.iconUrl"
  653. :preview-src-list="[row.iconUrl]"
  654. fit="cover"
  655. style="width:32px;height:32px;border:1px solid #ebeef5;border-radius:4px;"
  656. />
  657. <span v-else>-</span>
  658. </template>
  659. </el-table-column>
  660. <el-table-column label="奖品名称" prop="name" align="center" />
  661. <el-table-column label="奖品类型" prop="type" align="center">
  662. <template slot-scope="{row}">
  663. <el-tag v-if="row.type" size="small">
  664. {{ getSpinItemTypeLabel(row.type) }}
  665. </el-tag>
  666. <span v-else>-</span>
  667. </template>
  668. </el-table-column>
  669. <el-table-column label="数量" prop="amount" align="center">
  670. <template slot-scope="{row}">
  671. <span v-if="row.amount">
  672. {{ row.amount }}
  673. </span>
  674. <span v-else>-</span>
  675. </template>
  676. </el-table-column>
  677. <el-table-column label="概率" prop="probability" align="center">
  678. <template slot-scope="{row}">
  679. <el-tag v-if="row.probability" size="small">{{ row.probability }}</el-tag>
  680. <span v-else>-</span>
  681. </template>
  682. </el-table-column>
  683. </el-table>
  684. <div v-if="!parsedRewardItems || parsedRewardItems.length === 0" class="empty-tip">
  685. 暂无奖励配置
  686. </div>
  687. </div>
  688. <!-- 保底转盘类型奖励 -->
  689. <div v-else-if="currentReward.rewardType === 5" class="chest-reward">
  690. <el-table :data="parsedRewardItems" size="small" border stripe>
  691. <el-table-column label="图标" prop="iconUrl" align="center" width="80">
  692. <template slot-scope="{row}">
  693. <el-image
  694. v-if="row.iconUrl"
  695. :src="row.iconUrl"
  696. :preview-src-list="[row.iconUrl]"
  697. fit="cover"
  698. style="width:32px;height:32px;border:1px solid #ebeef5;border-radius:4px;"
  699. />
  700. <span v-else>-</span>
  701. </template>
  702. </el-table-column>
  703. <el-table-column label="奖品名称" prop="name" align="center" />
  704. <el-table-column label="奖品类型" prop="type" align="center">
  705. <template slot-scope="{row}">
  706. <el-tag v-if="row.type" size="small">
  707. {{ getSpinItemTypeLabel(row.type) }}
  708. </el-tag>
  709. <span v-else>-</span>
  710. </template>
  711. </el-table-column>
  712. <el-table-column label="数量" prop="amount" align="center">
  713. <template slot-scope="{row}">
  714. <span v-if="row.amount">
  715. {{ row.amount }}
  716. </span>
  717. <span v-else>-</span>
  718. </template>
  719. </el-table-column>
  720. <el-table-column label="概率" prop="probability" align="center">
  721. <template slot-scope="{row}">
  722. <el-tag v-if="row.probability" size="small">{{ row.probability }}</el-tag>
  723. <span v-else>-</span>
  724. </template>
  725. </el-table-column>
  726. <el-table-column label="保底" prop="isGuarantee" align="center">
  727. <template slot-scope="{row}">
  728. <el-tag size="small" :type="row.isGuarantee ? 'success' : 'info'">{{ row.isGuarantee ? '是' : '否' }}</el-tag>
  729. </template>
  730. </el-table-column>
  731. </el-table>
  732. <div v-if="!parsedRewardItems || parsedRewardItems.length === 0" class="empty-tip">
  733. 暂无奖励配置
  734. </div>
  735. </div>
  736. <!-- 未知类型 -->
  737. <div v-else class="unknown-reward">
  738. <el-alert type="info" title="未知奖励类型" :closable="false"></el-alert>
  739. </div>
  740. </div>
  741. </div>
  742. <div slot="footer" class="dialog-footer">
  743. <el-button @click="detailVisible = false">关 闭</el-button>
  744. </div>
  745. </el-dialog>
  746. <!-- 选择优惠券 -->
  747. <el-drawer :append-to-body="true" :with-header="false" size="75%" :visible.sync="couponDrawerOpen">
  748. <coupon-component @select-coupon="selectCoupon"></coupon-component>
  749. </el-drawer>
  750. <!-- 选择商品 -->
  751. <el-drawer :append-to-body="true" :with-header="false" size="75%" :visible.sync="goodsDrawerOpen">
  752. <RewardGoodsComponent @select-goods="selectGoods"></RewardGoodsComponent>
  753. </el-drawer>
  754. </div>
  755. </template>
  756. <script>
  757. import { listReward, getReward, delReward, addReward, updateReward, exportReward } from "@/api/reward/reward";
  758. import CouponComponent from '@/views/components/his/couponComponent.vue'
  759. import RewardGoodsComponent from '@/views/components/course/rewardGoodsComponent.vue'
  760. import {Loading} from "element-ui";
  761. import { getByIds } from '@/api/his/coupon'
  762. import { getGoodsByIds } from '@/api/reward/rewardGoods'
  763. export default {
  764. name: "Reward",
  765. components: {
  766. CouponComponent, RewardGoodsComponent
  767. },
  768. data() {
  769. return {
  770. finalQuality:1,
  771. uploadUrl: process.env.VUE_APP_BASE_API+"/common/uploadOSS",
  772. detailVisible: false,
  773. currentReward: {},
  774. activeCollapse: [],
  775. jltypeOptions: [],
  776. // 遮罩层
  777. loading: true,
  778. // 导出遮罩层
  779. exportLoading: false,
  780. // 选中数组
  781. ids: [],
  782. // 非单个禁用
  783. single: true,
  784. // 非多个禁用
  785. multiple: true,
  786. // 显示搜索条件
  787. showSearch: true,
  788. // 总条数
  789. total: 0,
  790. rewardList: [],
  791. typeOptions: [],
  792. // 转盘奖励-奖励类型选项
  793. spinItemTypeOptions: [],
  794. statusOptions: [],
  795. // 弹出层标题
  796. title: "",
  797. // 是否显示弹出层
  798. open: false,
  799. width: '1200px',
  800. // 查询参数
  801. queryParams: {
  802. pageNum: 1,
  803. pageSize: 10,
  804. name: null,
  805. description: null,
  806. rewardType: null,
  807. status: null,
  808. expectedValue: null,
  809. createId: null,
  810. actualRewards: null
  811. },
  812. // 已选中优惠券列表
  813. couponList: [],
  814. couponDrawerOpen: false,
  815. currentRow: null,
  816. // 已选中商品列表
  817. goodsList: [],
  818. goodsDrawerOpen: false,
  819. // 表单参数
  820. form: {
  821. id: null,
  822. name: null,
  823. description: null,
  824. rewardType: null,
  825. status: "1",
  826. expectedValue: 0,
  827. account: 0,
  828. rewardItems: [],
  829. openChestUrl:"",
  830. closeChestUrl:"",
  831. },
  832. // 表单校验
  833. rules: {
  834. name: [
  835. { required: true, message: "奖励名称不能为空", trigger: "blur" }
  836. ],
  837. rewardType: [
  838. { required: true, message: "奖励类型不能为空", trigger: "change" }
  839. ],
  840. status: [
  841. { required: true, message: "状态 不能为空", trigger: "blur" }
  842. ]
  843. }
  844. };
  845. },
  846. computed: {
  847. // 解析奖励项数据
  848. parsedRewardItems() {
  849. if (!this.currentReward.actualRewards) return null;
  850. try {
  851. const parsed = JSON.parse(this.currentReward.actualRewards);
  852. return Array.isArray(parsed) ? parsed : null;
  853. } catch (e) {
  854. console.error("解析奖励内容失败", e);
  855. return null;
  856. }
  857. },
  858. // 获取红包或积分币金额
  859. rewardAmount() {
  860. if (!this.currentReward.actualRewards) return 0;
  861. try {
  862. const parsed = JSON.parse(this.currentReward.actualRewards);
  863. if (this.currentReward.rewardType === 2) {
  864. return parsed.amount || parsed.money || 0;
  865. } else if (this.currentReward.rewardType === 3) {
  866. return parsed.points || parsed.score || 0;
  867. }
  868. return 0;
  869. } catch (e) {
  870. return 0;
  871. }
  872. }
  873. },
  874. created() {
  875. this.getList();
  876. this.getDicts("sys_reward_type").then((response) => {
  877. this.typeOptions = response.data;
  878. });
  879. this.getDicts("sys_user_status").then((response) => {
  880. this.statusOptions = response.data;
  881. });
  882. this.getDicts("sys_reward_spin_type").then((response) => {
  883. this.jltypeOptions = response.data;
  884. });
  885. this.getDicts("spin_reward_type").then((response) => {
  886. this.spinItemTypeOptions = response.data;
  887. });
  888. },
  889. methods: {
  890. changeType(row) {
  891. row.amount = null
  892. row.couponId = null
  893. row.goodsId = null
  894. if (row.type === '5') {
  895. row.amount = 1
  896. }
  897. },
  898. openCouponDrawer(row) {
  899. this.$nextTick(() => {
  900. this.$refs[`customSelect_${row.code}`]?.blur?.()
  901. });
  902. this.currentRow = row
  903. this.couponDrawerOpen = true
  904. },
  905. selectCoupon(coupon) {
  906. this.currentRow.couponId = coupon.id
  907. if (!this.couponList.some(item => item.id === coupon.id)) {
  908. this.couponList.push(coupon)
  909. }
  910. this.couponDrawerOpen = false
  911. },
  912. openGoodsDrawer(row) {
  913. this.$nextTick(() => {
  914. this.$refs[`customSelect_${row.code}`]?.blur?.()
  915. });
  916. this.currentRow = row
  917. this.goodsDrawerOpen = true
  918. },
  919. selectGoods(goods) {
  920. this.currentRow.goodsId = goods.id
  921. if (!this.goodsList.some(item => item.id === goods.id)) {
  922. this.goodsList.push(goods)
  923. }
  924. this.goodsDrawerOpen = false
  925. },
  926. handleSuccessOpen(res, file) {
  927. if(res.code==200){
  928. this.form.openChestUrl = res.url;
  929. }
  930. else{
  931. this.msgError(res.msg);
  932. }
  933. },
  934. handleSuccessClose(res, file) {
  935. if(res.code==200){
  936. this.form.closeChestUrl = res.url;
  937. }
  938. else{
  939. this.msgError(res.msg);
  940. }
  941. console.log(this.form.closeChestUrl)
  942. },
  943. beforeUpload(file) {
  944. const isPic =
  945. file.type === 'image/jpeg' ||
  946. file.type === 'image/png' ||
  947. file.type === 'image/gif' ||
  948. file.type === 'image/jpg'
  949. // const isLt2M = file.size / 1024 / 1024 < 2
  950. if (!isPic) {
  951. this.$message.error('上传图片只能是 JPG、JPEG、PNG、GIF 格式!')
  952. return false
  953. }
  954. return new Promise((resolve, reject) => {
  955. if (file.size / 1024 / 1024 > 3) {
  956. this.$message.error('上传的图片不能超过3MB');
  957. reject();
  958. return;
  959. }
  960. if (file.size / 1024 / 1024 > 1) {
  961. const loadingInstance = Loading.service({ text: '图片内存过大正在压缩图片...' });
  962. // 文件大于1MB时进行压缩
  963. this.compressImage(file).then((compressedFile) => {
  964. loadingInstance.close();
  965. if (compressedFile.size / 1024 > 500) {
  966. this.$message.error('图片压缩后仍大于500KB');
  967. reject();
  968. } else {
  969. // this.$message.success(`图片压缩成功,最终质量为: ${this.finalQuality.toFixed(2)}`);
  970. console.log(`图片压缩成功,最终质量为: ${this.finalQuality.toFixed(2)}`);
  971. console.log(`最终内存大小为: ${(compressedFile.size/1024).toFixed(2)}KB`);
  972. resolve(compressedFile);
  973. }
  974. }).catch((err) => {
  975. loadingInstance.close();
  976. console.error(err);
  977. reject();
  978. });
  979. } else {
  980. resolve(file);
  981. }
  982. });
  983. },
  984. compressImage(file) {
  985. return new Promise((resolve, reject) => {
  986. const reader = new FileReader();
  987. reader.readAsDataURL(file);
  988. reader.onload = (event) => {
  989. const img = new Image();
  990. img.src = event.target.result;
  991. img.onload = () => {
  992. const canvas = document.createElement('canvas');
  993. const ctx = canvas.getContext('2d');
  994. const width = img.width;
  995. const height = img.height;
  996. canvas.width = width;
  997. canvas.height = height;
  998. ctx.drawImage(img, 0, 0, width, height);
  999. let quality = 1; // 初始压缩质量
  1000. let dataURL = canvas.toDataURL('image/jpeg', quality);
  1001. // 逐步压缩,直到图片大小小于500KB并且压缩质量不再降低
  1002. while (dataURL.length / 1024 > 500 && quality > 0.1) {
  1003. quality -= 0.01;
  1004. dataURL = canvas.toDataURL('image/jpeg', quality);
  1005. }
  1006. this.finalQuality = quality; // 存储最终的压缩质量
  1007. if (dataURL.length / 1024 > 500) {
  1008. reject(new Error('压缩后图片仍然大于500KB'));
  1009. return;
  1010. }
  1011. const arr = dataURL.split(',');
  1012. const mime = arr[0].match(/:(.*?);/)[1];
  1013. const bstr = atob(arr[1]);
  1014. let n = bstr.length;
  1015. const u8arr = new Uint8Array(n);
  1016. while (n--) {
  1017. u8arr[n] = bstr.charCodeAt(n);
  1018. }
  1019. const compressedFile = new Blob([u8arr], { type: mime });
  1020. compressedFile.name = file.name;
  1021. resolve(compressedFile);
  1022. };
  1023. img.onerror = (error) => {
  1024. reject(error);
  1025. };
  1026. };
  1027. reader.onerror = (error) => {
  1028. reject(error);
  1029. };
  1030. });
  1031. },
  1032. // 查看奖励详情
  1033. handleViewReward(row) {
  1034. this.currentReward = { ...row };
  1035. this.detailVisible = true;
  1036. },
  1037. // 格式化JSON显示
  1038. formatJson(json) {
  1039. if (!json) return '无数据';
  1040. try {
  1041. const parsed = JSON.parse(json);
  1042. return JSON.stringify(parsed, null, 2);
  1043. } catch (e) {
  1044. return json;
  1045. }
  1046. },
  1047. /** 查询奖励配置列表 */
  1048. getList() {
  1049. this.loading = true;
  1050. listReward(this.queryParams).then(response => {
  1051. this.rewardList = response.rows;
  1052. this.total = response.total;
  1053. this.loading = false;
  1054. });
  1055. },
  1056. // 取消按钮
  1057. cancel() {
  1058. this.open = false;
  1059. this.reset();
  1060. },
  1061. // 表单重置
  1062. reset() {
  1063. this.form = {
  1064. id: null,
  1065. name: null,
  1066. description: null,
  1067. rewardType: null,
  1068. status: "1",
  1069. expectedValue: 0,
  1070. account: 0,
  1071. openChestUrl: "",
  1072. closeChestUrl:"",
  1073. rewardItems: []
  1074. };
  1075. this.resetForm("form");
  1076. },
  1077. /** 搜索按钮操作 */
  1078. handleQuery() {
  1079. this.queryParams.pageNum = 1;
  1080. this.getList();
  1081. },
  1082. /** 重置按钮操作 */
  1083. resetQuery() {
  1084. this.resetForm("queryForm");
  1085. this.handleQuery();
  1086. },
  1087. // 多选框选中数据
  1088. handleSelectionChange(selection) {
  1089. this.ids = selection.map(item => item.id)
  1090. this.single = selection.length!==1
  1091. this.multiple = !selection.length
  1092. },
  1093. /** 新增按钮操作 */
  1094. handleAdd() {
  1095. this.reset();
  1096. this.open = true;
  1097. this.title = "添加奖励配置";
  1098. // 若选择转盘类型时,默认添加一条配置
  1099. if (this.form.rewardType === '4') {
  1100. this.ensureSpinDefaultItem();
  1101. }
  1102. if (this.form.rewardType === '5') {
  1103. this.ensureSpinDefaultItem2();
  1104. }
  1105. },
  1106. /** 修改按钮操作 */
  1107. handleUpdate(row) {
  1108. this.reset();
  1109. const id = row.id || this.ids
  1110. getReward(id).then(response => {
  1111. this.form = response.data;
  1112. this.form.status = response.data.status.toString();
  1113. this.form.rewardType = response.data.rewardType.toString();
  1114. // 解析actualRewards
  1115. try {
  1116. const actualRewards = JSON.parse(response.data.actualRewards || "[]");
  1117. if (this.form.rewardType === '1' || this.form.rewardType === '4' || this.form.rewardType === '5'
  1118. || this.form.rewardType === '6') {
  1119. // 宝箱类型
  1120. this.form.rewardItems = actualRewards.map(item => ({
  1121. ...item,
  1122. couponId: item.couponId || null,
  1123. goodsId: item.goodsId || null,
  1124. }));
  1125. this.initCouponList(actualRewards.filter(item => item.couponId).map(item => item.couponId).join(","))
  1126. this.initGoodsList(actualRewards.filter(item => item.goodsId).map(item => item.goodsId).join(","))
  1127. } else if (this.form.rewardType === '2') {
  1128. // 红包类型
  1129. this.form.account = actualRewards.amount || 0;
  1130. } else if (this.form.rewardType === '3') {
  1131. // 积分币类型
  1132. this.form.account = actualRewards.points || 0;
  1133. }
  1134. } catch (e) {
  1135. this.form.rewardItems = [];
  1136. this.form.account = 0;
  1137. }
  1138. this.open = true;
  1139. this.title = "修改奖励配置";
  1140. });
  1141. },
  1142. initCouponList(ids) {
  1143. if (!ids) {
  1144. return
  1145. }
  1146. getByIds({ids}).then(response => {
  1147. const {data} = response
  1148. data.forEach(d => {
  1149. if (!this.couponList.some(item => item.id === d.couponId)) {
  1150. this.couponList.push({"id": d.couponId, "name": d.title})
  1151. }
  1152. })
  1153. })
  1154. },
  1155. initGoodsList(ids) {
  1156. if (!ids) {
  1157. return
  1158. }
  1159. getGoodsByIds({ids}).then(response => {
  1160. const {data} = response
  1161. data.forEach(g => {
  1162. if (!this.goodsList.some(item => item.id === g.goodsId)) {
  1163. this.goodsList.push({"id": g.goodsId, "name": g.goodsName})
  1164. }
  1165. })
  1166. })
  1167. },
  1168. // 奖励类型变化处理
  1169. handleRewardTypeChange(value) {
  1170. // 清空相关数据
  1171. this.form.account = 0;
  1172. this.form.rewardItems = [];
  1173. this.form.expectedValue = 0
  1174. // 切换为转盘类型时,默认加入一条配置
  1175. if (value === '4') {
  1176. this.ensureSpinDefaultItem();
  1177. }
  1178. if (value === '5') {
  1179. this.ensureSpinDefaultItem2();
  1180. }
  1181. if (value === '6') {
  1182. this.ensureSpinDefaultItem();
  1183. }
  1184. },
  1185. // 更新奖励项名称(当表单名称变化时)
  1186. updateRewardItemsName() {
  1187. if (this.form.rewardType === '1' && this.form.rewardItems.length > 0) {
  1188. // 可选:当表单名称变化时,自动更新所有奖励项的名称
  1189. // this.form.rewardItems.forEach(item => {
  1190. // item.name = this.form.name;
  1191. // });
  1192. }
  1193. },
  1194. // 添加奖励项
  1195. addRewardItem() {
  1196. this.form.rewardItems.push({
  1197. type: '1',
  1198. jltype: '1',
  1199. name: this.form.name || '', // 默认使用表单中的奖励名称
  1200. couponId: null,
  1201. amount: 1,
  1202. probability: '',
  1203. code: this.generateUUID()
  1204. });
  1205. },
  1206. // 转盘奖励:添加奖励项(包含奖励类型选择)
  1207. addSpinRewardItem() {
  1208. this.form.rewardItems.push({
  1209. type: '2',
  1210. iconUrl: '',
  1211. name: '',
  1212. couponId: null,
  1213. goodsId: null,
  1214. amount: '',
  1215. probability: '',
  1216. code: this.generateUUID()
  1217. });
  1218. },
  1219. addSpinRewardItem2() {
  1220. this.form.rewardItems.push({
  1221. type: '2',
  1222. isGuarantee: 0,
  1223. iconUrl: '',
  1224. name: '',
  1225. couponId: null,
  1226. amount: '',
  1227. probability: '',
  1228. code: this.generateUUID()
  1229. });
  1230. },
  1231. // 转盘奖励:图标上传成功
  1232. handleSpinIconSuccess(res, row) {
  1233. if (res && res.code === 200 && res.url) {
  1234. this.$set(row, 'iconUrl', res.url);
  1235. } else {
  1236. this.msgError(res && res.msg ? res.msg : '图片上传失败');
  1237. }
  1238. },
  1239. // 转盘奖励:确保有一条默认项
  1240. ensureSpinDefaultItem() {
  1241. if (!Array.isArray(this.form.rewardItems)) this.form.rewardItems = [];
  1242. if (this.form.rewardItems.length === 0) {
  1243. this.addSpinRewardItem();
  1244. }
  1245. },
  1246. ensureSpinDefaultItem2() {
  1247. if (!Array.isArray(this.form.rewardItems)) this.form.rewardItems = [];
  1248. if (this.form.rewardItems.length === 0) {
  1249. this.addSpinRewardItem2();
  1250. }
  1251. },
  1252. // 移除奖励项
  1253. removeRewardItem(index) {
  1254. this.form.rewardItems.splice(index, 1);
  1255. },
  1256. // 生成UUID
  1257. generateUUID(item) {
  1258. if (item) {
  1259. item.code = this.generateUUIDString();
  1260. } else {
  1261. return this.generateUUIDString();
  1262. }
  1263. },
  1264. // 生成UUID字符串
  1265. generateUUIDString() {
  1266. return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
  1267. var r = Math.random() * 16 | 0,
  1268. v = c == 'x' ? r : (r & 0x3 | 0x8);
  1269. return v.toString(16);
  1270. });
  1271. },
  1272. // 获取转盘奖励类型标签
  1273. getSpinItemTypeLabel(type) {
  1274. const option = this.spinItemTypeOptions.find(item => item.dictValue === type.toString());
  1275. return option ? option.dictLabel : type;
  1276. },
  1277. /** 提交按钮 */
  1278. submitForm() {
  1279. this.$refs["form"].validate(valid => {
  1280. if (valid) {
  1281. let actualRewards = "";
  1282. if (this.form.rewardType === '1' || this.form.rewardType === '4' || this.form.rewardType === '5'
  1283. || this.form.rewardType === '6') {
  1284. // 宝箱类型,将rewardItems转为JSON
  1285. actualRewards = JSON.stringify(this.form.rewardItems);
  1286. } else if (this.form.rewardType === '2') {
  1287. // 红包类型
  1288. actualRewards = JSON.stringify({ amount: this.form.account });
  1289. } else if (this.form.rewardType === '3') {
  1290. // 积分币类型
  1291. actualRewards = JSON.stringify({ points: this.form.account });
  1292. }
  1293. // 设置actualRewards
  1294. this.form.actualRewards = actualRewards;
  1295. if (this.form.id != null) {
  1296. updateReward(this.form).then(response => {
  1297. this.msgSuccess("修改成功");
  1298. this.open = false;
  1299. this.getList();
  1300. });
  1301. } else {
  1302. addReward(this.form).then(response => {
  1303. this.msgSuccess("新增成功");
  1304. this.open = false;
  1305. this.getList();
  1306. });
  1307. }
  1308. }
  1309. });
  1310. },
  1311. /** 删除按钮操作 */
  1312. handleDelete(row) {
  1313. const ids = row.id || this.ids;
  1314. this.$confirm('是否确认删除奖励配置编号为"' + ids + '"的数据项?', "警告", {
  1315. confirmButtonText: "确定",
  1316. cancelButtonText: "取消",
  1317. type: "warning"
  1318. }).then(function() {
  1319. return delReward(ids);
  1320. }).then(() => {
  1321. this.getList();
  1322. this.msgSuccess("删除成功");
  1323. }).catch(() => {});
  1324. },
  1325. /** 导出按钮操作 */
  1326. handleExport() {
  1327. const queryParams = this.queryParams;
  1328. this.$confirm('是否确认导出所有奖励配置数据项?', "警告", {
  1329. confirmButtonText: "确定",
  1330. cancelButtonText: "取消",
  1331. type: "warning"
  1332. }).then(() => {
  1333. this.exportLoading = true;
  1334. return exportReward(queryParams);
  1335. }).then(response => {
  1336. this.download(response.msg);
  1337. this.exportLoading = false;
  1338. }).catch(() => {});
  1339. }
  1340. }
  1341. };
  1342. </script>
  1343. <style scoped>
  1344. .spin-reward .el-table th,
  1345. .spin-reward .el-table td {
  1346. padding: 6px 8px;
  1347. }
  1348. .spin-reward .probability-col .el-input__inner {
  1349. text-align: center;
  1350. }
  1351. .spin-tips {
  1352. padding: 8px 12px;
  1353. color: #909399;
  1354. font-size: 12px;
  1355. background-color: #fafafa;
  1356. border-top: 1px solid #ebeef5;
  1357. border-bottom: 1px solid #ebeef5;
  1358. }
  1359. .avatar-uploader .el-upload {
  1360. border: 1px dashed #d9d9d9;
  1361. border-radius: 6px;
  1362. cursor: pointer;
  1363. position: relative;
  1364. overflow: hidden;
  1365. }
  1366. .avatar-uploader .el-upload:hover {
  1367. border-color: #409EFF;
  1368. }
  1369. .avatar-uploader-icon {
  1370. font-size: 28px;
  1371. color: #8c939d;
  1372. width: 178px;
  1373. height: 178px;
  1374. line-height: 178px;
  1375. text-align: center;
  1376. }
  1377. .avatar {
  1378. width: 120px;
  1379. height: 120px;
  1380. display: block;
  1381. }
  1382. .spin-icon-uploader {
  1383. display: inline-block;
  1384. }
  1385. .spin-icon {
  1386. width: 32px;
  1387. height: 32px;
  1388. object-fit: cover;
  1389. border: 1px solid #ebeef5;
  1390. border-radius: 4px;
  1391. }
  1392. .spin-icon-uploader .el-upload {
  1393. border: 1px dashed #d9d9d9;
  1394. border-radius: 4px;
  1395. width: 32px;
  1396. height: 32px;
  1397. line-height: 32px;
  1398. display: inline-flex;
  1399. align-items: center;
  1400. justify-content: center;
  1401. }
  1402. .spin-icon-uploader .el-upload:hover {
  1403. border-color: #409EFF;
  1404. }
  1405. /* 覆盖全局 .avatar-uploader-icon 的大尺寸,限定在转盘上传器内为 32x32 */
  1406. .spin-icon-uploader .avatar-uploader-icon {
  1407. width: 32px !important;
  1408. height: 32px !important;
  1409. line-height: 32px !important;
  1410. font-size: 16px !important;
  1411. color: #8c939d;
  1412. display: inline-flex;
  1413. align-items: center;
  1414. justify-content: center;
  1415. }
  1416. .reward-table-container {
  1417. border: 1px solid #ebeef5;
  1418. border-radius: 4px;
  1419. }
  1420. .table-header {
  1421. display: flex;
  1422. justify-content: space-between;
  1423. align-items: center;
  1424. padding: 15px;
  1425. background-color: #f5f7fa;
  1426. border-bottom: 1px solid #ebeef5;
  1427. }
  1428. .table-tips {
  1429. padding: 10px 15px;
  1430. color: #909399;
  1431. font-size: 12px;
  1432. background-color: #f5f7fa;
  1433. }
  1434. .reward-detail-container {
  1435. max-height: 60vh;
  1436. overflow-y: auto;
  1437. }
  1438. .base-info {
  1439. margin-bottom: 20px;
  1440. width: 100%;
  1441. table-layout: fixed;
  1442. }
  1443. .base-info .el-descriptions__label {
  1444. width: 120px !important;
  1445. min-width: 120px;
  1446. }
  1447. .base-info .el-descriptions__content {
  1448. width: 200px !important;
  1449. min-width: 200px;
  1450. }
  1451. .reward-content {
  1452. margin: 20px 0;
  1453. }
  1454. .reward-content h4 {
  1455. margin-bottom: 15px;
  1456. color: #303133;
  1457. font-weight: 600;
  1458. }
  1459. .chest-reward,
  1460. .redpacket-reward,
  1461. .points-reward {
  1462. margin: 15px 0;
  1463. }
  1464. .reward-amount {
  1465. display: flex;
  1466. align-items: center;
  1467. gap: 10px;
  1468. padding: 15px;
  1469. background: #f8f9fa;
  1470. border-radius: 4px;
  1471. }
  1472. .amount-text {
  1473. font-size: 18px;
  1474. font-weight: bold;
  1475. color: #409EFF;
  1476. }
  1477. .code-text {
  1478. font-family: monospace;
  1479. font-size: 12px;
  1480. }
  1481. .empty-tip {
  1482. text-align: center;
  1483. color: #909399;
  1484. padding: 20px;
  1485. }
  1486. .json-pre {
  1487. background: #f5f7fa;
  1488. padding: 12px;
  1489. border-radius: 4px;
  1490. font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
  1491. font-size: 12px;
  1492. line-height: 1.5;
  1493. overflow: auto;
  1494. max-height: 200px;
  1495. margin: 0;
  1496. }
  1497. .raw-data {
  1498. margin-top: 20px;
  1499. }
  1500. /* 新增样式 */
  1501. .image-upload-group {
  1502. display: flex;
  1503. flex-wrap: wrap;
  1504. gap: 20px;
  1505. }
  1506. .image-upload-item {
  1507. display: flex;
  1508. flex-direction: column;
  1509. gap: 10px;
  1510. }
  1511. .image-preview {
  1512. margin-top: 10px;
  1513. border: 1px dashed #d9d9d9;
  1514. padding: 10px;
  1515. border-radius: 4px;
  1516. }
  1517. .image-actions {
  1518. margin-top: 10px;
  1519. display: flex;
  1520. gap: 10px;
  1521. }
  1522. .chest-images-preview {
  1523. display: flex;
  1524. justify-content: center;
  1525. align-items: center;
  1526. }
  1527. .chest-images-section {
  1528. margin: 20px 0;
  1529. }
  1530. .chest-images-display {
  1531. display: flex;
  1532. gap: 30px;
  1533. justify-content: center;
  1534. margin-top: 15px;
  1535. }
  1536. .chest-image-item {
  1537. text-align: center;
  1538. }
  1539. .chest-image-item p {
  1540. margin-bottom: 8px;
  1541. font-weight: bold;
  1542. }
  1543. </style>