activity.js 87 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850
  1. define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'template'], function ($, undefined, Backend, Table, Form, Template) {
  2. var Controller = {
  3. index: function () {
  4. // 初始化表格参数
  5. Table.api.init({
  6. extend: {
  7. index_url: 'lottery/activity/index' + location.search,
  8. add_url: 'lottery/activity/add',
  9. edit_url: 'lottery/activity/edit',
  10. del_url: 'lottery/activity/del',
  11. multi_url: 'lottery/activity/multi',
  12. import_url: 'lottery/activity/import',
  13. table: 'shop_lottery_activity',
  14. }
  15. });
  16. var table = $("#table");
  17. // 初始化表格
  18. table.bootstrapTable({
  19. url: $.fn.bootstrapTable.defaults.extend.index_url,
  20. pk: 'id',
  21. sortName: 'id',
  22. fixedColumns: true,
  23. fixedRightNumber: 1,
  24. columns: [
  25. [
  26. {checkbox: true},
  27. {field: 'id', title: __('Id'), operate: false},
  28. {field: 'name', title: __('活动名称'), operate: 'LIKE'},
  29. {field: 'type', title: __('活动类型'), searchList: {"1":"消费抽奖"}, formatter: Table.api.formatter.normal},
  30. {field: 'status', title: __('状态'), searchList: {"0":"草稿","1":"进行中","2":"已结束","3":"已暂停"}, formatter: Table.api.formatter.status},
  31. {field: 'lottery_type', title: __('开奖方式'), searchList: {"1":"即抽即中","2":"按时间开奖","3":"按人数开奖"}, formatter: Table.api.formatter.normal},
  32. {field: 'start_time', title: __('开始时间'), operate:'RANGE', addclass:'datetimerange', autocomplete:false, formatter: Table.api.formatter.datetime},
  33. {field: 'end_time', title: __('结束时间'), operate:'RANGE', addclass:'datetimerange', autocomplete:false, formatter: Table.api.formatter.datetime},
  34. {field: 'total_people_count', title: __('参与人数'), operate: false},
  35. {field: 'total_draw_count', title: __('抽奖次数'), operate: false},
  36. {field: 'total_win_count', title: __('中奖次数'), operate: false},
  37. {field: 'createtime', title: __('Create time'), operate:'RANGE', addclass:'datetimerange', autocomplete:false, formatter: Table.api.formatter.datetime},
  38. {field: 'operate', title: __('Operate'), table: table, events: Table.api.events.operate,
  39. buttons: [
  40. {
  41. name: 'prize',
  42. title: __('奖品管理'),
  43. classname: 'btn btn-xs btn-success btn-magic btn-ajax',
  44. icon: 'fa fa-gift',
  45. url: 'lottery/activity/prize',
  46. callback: function (data) {
  47. Layer.alert("接收到回传数据:" + JSON.stringify(data), {title: "回传数据"});
  48. },
  49. visible: function (row) {
  50. //返回true时按钮显示,返回false隐藏
  51. return true;
  52. }
  53. },
  54. {
  55. name: 'condition',
  56. title: __('参与条件'),
  57. classname: 'btn btn-xs btn-info btn-magic btn-ajax',
  58. icon: 'fa fa-tasks',
  59. url: 'lottery/activity/condition'
  60. },
  61. {
  62. name: 'records',
  63. title: __('抽奖记录'),
  64. classname: 'btn btn-xs btn-warning btn-magic btn-ajax',
  65. icon: 'fa fa-list',
  66. url: 'lottery/activity/records'
  67. },
  68. {
  69. name: 'statistics',
  70. title: __('统计数据'),
  71. classname: 'btn btn-xs btn-primary btn-magic btn-ajax',
  72. icon: 'fa fa-bar-chart',
  73. url: 'lottery/activity/statistics'
  74. }
  75. ],
  76. formatter: Table.api.formatter.operate
  77. }
  78. ]
  79. ]
  80. });
  81. // 为表格绑定事件
  82. Table.api.bindevent(table);
  83. },
  84. add: function () {
  85. Controller.api.bindevent();
  86. Controller.api.initAddPage();
  87. },
  88. edit: function () {
  89. Controller.api.bindevent();
  90. Controller.api.initEditPage();
  91. },
  92. api: {
  93. // 解析Config中的JSON字符串的辅助函数
  94. parseConfigJson: function(configKey, defaultValue) {
  95. var configValue = Config[configKey] || defaultValue || {};
  96. // 如果是字符串,尝试解析JSON
  97. if (typeof configValue === 'string') {
  98. try {
  99. return JSON.parse(configValue);
  100. } catch (e) {
  101. return defaultValue || {};
  102. }
  103. }
  104. return configValue;
  105. },
  106. bindevent: function () {
  107. // 自定义验证规则
  108. $.validator.config({
  109. rules: {
  110. // 奖品设置验证
  111. prize_setting_check: function(element) {
  112. // 检查奖品数量(总共需要8个奖品,包含系统默认的未中奖项)
  113. var totalPrizeCount = $('.prize-item').length; // 总奖品数
  114. if (totalPrizeCount < 8) {
  115. var userPrizeCount = totalPrizeCount - 1; // 用户添加的奖品数
  116. var needCount = 8 - totalPrizeCount; // 还需要添加的奖品数
  117. return '奖品数量不足,还需要添加' + needCount + '个奖品(当前总共' + totalPrizeCount + '个奖品)';
  118. }
  119. // 检查总概率
  120. var totalRate = Controller.api.calculateTotalRate();
  121. if (Math.abs(totalRate - 100) > 0.01) {
  122. return '奖品总概率必须等于100.00%,当前为' + totalRate.toFixed(2) + '%';
  123. }
  124. // 检查每个奖品的概率是否大于0
  125. var hasZeroRate = false;
  126. $('.prize-rate').each(function() {
  127. var rate = parseFloat($(this).val()) || 0;
  128. if (rate <= 0) {
  129. hasZeroRate = true;
  130. return false;
  131. }
  132. });
  133. if (hasZeroRate) {
  134. return '所有奖品的中奖概率必须大于0%';
  135. }
  136. // 检查奖品数量设置
  137. var hasInvalidQuantity = false;
  138. $('.prize-item').each(function() {
  139. var prizeItem = $(this);
  140. if (prizeItem.attr('data-type') === 'no-prize') {
  141. return true;
  142. }
  143. var quantity = parseInt(prizeItem.find('.prize-quantity').val()) || 0;
  144. if (quantity <= 0) {
  145. hasInvalidQuantity = true;
  146. return false;
  147. }
  148. });
  149. if (hasInvalidQuantity) {
  150. return '所有奖品的数量必须大于0';
  151. }
  152. return true;
  153. }
  154. }
  155. });
  156. Form.api.bindevent($("form[role=form]"));
  157. },
  158. // 初始化添加页面
  159. initAddPage: function() {
  160. // 初始化基础UI组件
  161. Controller.api.initBasicUI();
  162. // 初始化奖品管理
  163. Controller.api.initPrizeManagement();
  164. // 初始化任务规则管理
  165. Controller.api.initTaskRuleManagement();
  166. // 确保DOM渲染完成后再计算奖品数量
  167. setTimeout(function() {
  168. Controller.api.updatePrizeCount();
  169. }, 50);
  170. // 初始化表单验证
  171. Controller.api.initFormValidation();
  172. },
  173. // 初始化基础UI组件
  174. initBasicUI: function() {
  175. // 活动名称字数统计
  176. $('#c-name').on('input', function(){
  177. var len = $(this).val().length;
  178. $('#activity-name-count').text(len + '/24');
  179. });
  180. // 初始化商品选择
  181. Controller.api.initGoodsSelector();
  182. },
  183. // 初始化任务规则管理
  184. initTaskRuleManagement: function() {
  185. // 任务类型切换(复选框)
  186. $(document).on('change', 'input[name="row[task_type][]"]', function() {
  187. Controller.api.handleTaskTypeChange();
  188. });
  189. // 商品规则切换
  190. $(document).on('change', 'input[name^="condition"][name$="[goods_rule]"]', function() {
  191. Controller.api.updateGoodsRuleDisplay($(this));
  192. });
  193. // 初始化任务类型状态
  194. Controller.api.initTaskTypeState();
  195. },
  196. // 处理任务类型变化
  197. handleTaskTypeChange: function() {
  198. var checkedTypes = $('input[name="row[task_type][]"]:checked');
  199. // 更新复选框样式
  200. $('input[name="row[task_type][]"]').each(function() {
  201. var label = $(this).closest('label');
  202. if ($(this).is(':checked')) {
  203. label.addClass('checked');
  204. } else {
  205. label.removeClass('checked');
  206. }
  207. });
  208. // 隐藏所有任务设置项
  209. $('.task-setting-item').hide();
  210. // 显示选中的任务设置项
  211. checkedTypes.each(function() {
  212. var type = parseInt($(this).val());
  213. $('#task-setting-' + type).show();
  214. });
  215. // 更新验证规则
  216. Controller.api.updateTaskValidation();
  217. },
  218. // 初始化任务类型状态
  219. initTaskTypeState: function() {
  220. var checkedTasks = $('input[name="row[task_type][]"]:checked');
  221. // 初始化复选框样式
  222. $('input[name="row[task_type][]"]').each(function() {
  223. var label = $(this).closest('label');
  224. if ($(this).is(':checked')) {
  225. label.addClass('checked');
  226. } else {
  227. label.removeClass('checked');
  228. }
  229. });
  230. // 初始化任务设置区域显示
  231. $('.task-setting-item').hide();
  232. checkedTasks.each(function() {
  233. var type = parseInt($(this).val());
  234. $('#task-setting-' + type).show();
  235. });
  236. },
  237. // 更新商品规则显示
  238. updateGoodsRuleDisplay: function($radio) {
  239. var container = $radio.closest('.task-setting-item');
  240. var rule = $radio.val();
  241. var helpText = container.find('.goods-description-box');
  242. if (rule == '1') {
  243. helpText.html('<span class="text-muted">指定商品下单后将自动触发抽奖活动且发放一次抽奖机会,若用户产生维权退款,已发放的奖励自动回收。</span>');
  244. } else {
  245. helpText.html('<span class="text-muted">除指定商品外的其他商品下单后将自动触发抽奖活动且发放一次抽奖机会。</span>');
  246. }
  247. },
  248. // 更新任务验证规则
  249. updateTaskValidation: function() {
  250. var checkedTypes = $('input[name="row[task_type][]"]:checked');
  251. // 移除旧的验证规则
  252. $('[name^="condition"]').removeAttr('data-rule');
  253. // 为选中的任务类型添加验证规则
  254. checkedTypes.each(function() {
  255. var type = parseInt($(this).val());
  256. switch(type) {
  257. case 1: // 购买指定商品
  258. // 商品选择验证会在选择商品时处理
  259. break;
  260. case 2: // 单笔订单消费满额
  261. case 3: // 单次充值满额
  262. case 4: // 活动期间累计消费满额
  263. $('[name="condition[' + type + '][condition_value]"]').attr('data-rule', 'required;number(0.01~999999.99)');
  264. break;
  265. }
  266. });
  267. },
  268. // 初始化表单验证
  269. initFormValidation: function() {
  270. // 自定义验证规则已在bindevent中定义
  271. // 监听表单提交事件
  272. $('form[role="form"]').on('submit', function(e) {
  273. console.log('表单提交开始');
  274. // 验证奖品设置
  275. if (!Controller.api.validatePrizes()) {
  276. e.preventDefault();
  277. return false;
  278. }
  279. // 验证任务规则设置
  280. if (!Controller.api.validateTaskRules()) {
  281. e.preventDefault();
  282. return false;
  283. }
  284. // 收集奖品数据并添加到表单
  285. var prizeData = Controller.api.collectPrizeData();
  286. if (prizeData.length > 0) {
  287. // 移除之前的隐藏字段
  288. $('form[role="form"] input[name^="prizes["]').remove();
  289. // 添加新的奖品数据
  290. for (var i = 0; i < prizeData.length; i++) {
  291. var prize = prizeData[i];
  292. for (var key in prize) {
  293. if (prize[key] !== null && prize[key] !== undefined) {
  294. $('<input>').attr({
  295. type: 'hidden',
  296. name: 'prizes[' + i + '][' + key + ']',
  297. value: prize[key]
  298. }).appendTo('form[role="form"]');
  299. }
  300. }
  301. }
  302. }
  303. // 收集条件数据并添加到表单
  304. var conditionResult = Controller.api.collectConditionData();
  305. var conditionData = conditionResult.conditionData;
  306. var taskTypes = conditionResult.taskTypes;
  307. if (conditionData.length > 0) {
  308. // 移除之前的隐藏字段
  309. $('form[role="form"] input[name^="condition["]').remove();
  310. // 添加新的条件数据
  311. for (var i = 0; i < conditionData.length; i++) {
  312. var condition = conditionData[i];
  313. for (var key in condition) {
  314. if (condition[key] !== null && condition[key] !== undefined) {
  315. var value = condition[key];
  316. if (Array.isArray(value)) {
  317. value = JSON.stringify(value);
  318. }
  319. $('<input>').attr({
  320. type: 'hidden',
  321. name: 'condition[' + i + '][' + key + ']',
  322. value: value
  323. }).appendTo('form[role="form"]');
  324. }
  325. }
  326. }
  327. }
  328. // 添加任务类型数据
  329. if (taskTypes.length > 0) {
  330. // 移除之前的task_type隐藏字段
  331. $('form[role="form"] input[type="hidden"][name^="task_type"]').remove();
  332. // 添加任务类型数据
  333. for (var i = 0; i < taskTypes.length; i++) {
  334. $('<input>').attr({
  335. type: 'hidden',
  336. name: 'task_type[' + i + ']',
  337. value: taskTypes[i]
  338. }).appendTo('form[role="form"]');
  339. }
  340. }
  341. console.log('表单验证通过,数据收集完成,准备提交');
  342. console.log('奖品数据:', prizeData);
  343. console.log('条件数据:', conditionData);
  344. console.log('任务类型:', taskTypes);
  345. return true;
  346. });
  347. },
  348. // 初始化编辑页面
  349. initEditPage: function() {
  350. Controller.api.initAddPage(); // 复用添加页面的初始化逻辑
  351. },
  352. // 验证奖品设置
  353. validatePrizes: function() {
  354. var prizeItems = $('.prize-item');
  355. var prizeCount = prizeItems.length;
  356. // 检查奖品数量
  357. if (prizeCount < 1) {
  358. Toastr.error('请至少设置一个奖品');
  359. return false;
  360. }
  361. // 检查奖品总概率
  362. var totalRate = 0;
  363. var hasValidPrize = false;
  364. prizeItems.each(function() {
  365. var type = $(this).attr('data-type');
  366. var rateInput = $(this).find('.prize-rate');
  367. var rate = parseFloat(rateInput.val()) || 0;
  368. totalRate += rate;
  369. // 检查非未中奖项是否有有效概率
  370. if (type !== 'no-prize' && rate > 0) {
  371. hasValidPrize = true;
  372. }
  373. // 检查奖品设置是否完整
  374. var nameInput = $(this).find('.prize-name');
  375. if (nameInput.length > 0 && !nameInput.val().trim()) {
  376. Toastr.error('请填写奖品名称');
  377. nameInput.focus();
  378. return false;
  379. }
  380. // 检查奖品数量(未中奖项除外)
  381. if (type !== 'no-prize') {
  382. var quantityInput = $(this).find('.prize-quantity');
  383. var quantity = parseInt(quantityInput.val()) || 0;
  384. if (quantity <= 0) {
  385. Toastr.error('奖品数量必须大于0');
  386. quantityInput.focus();
  387. return false;
  388. }
  389. }
  390. });
  391. // 检查是否有非未中奖的有效奖品
  392. if (!hasValidPrize) {
  393. Toastr.error('请至少设置一个有效的奖品(概率大于0)');
  394. return false;
  395. }
  396. // 检查总概率是否合理
  397. if (totalRate > 100) {
  398. Toastr.error('奖品总概率不能超过100%');
  399. return false;
  400. }
  401. return true;
  402. },
  403. // 验证任务规则设置
  404. validateTaskRules: function() {
  405. var checkedTypes = $('input[name="row[task_type][]"]:checked');
  406. // 检查是否选择了任务类型
  407. if (checkedTypes.length === 0) {
  408. Toastr.error('请选择至少一种参与条件');
  409. return false;
  410. }
  411. // 验证每个选中的任务类型
  412. var isValid = true;
  413. checkedTypes.each(function() {
  414. var type = parseInt($(this).val());
  415. var container = $('#task-setting-' + type);
  416. switch(type) {
  417. case 1: // 购买指定商品
  418. var goodsContainer = container.find('#selected-goods');
  419. if (goodsContainer.find('.goods-list-item').length === 0) {
  420. Toastr.error('请选择参与的商品');
  421. isValid = false;
  422. return false;
  423. }
  424. break;
  425. case 2: // 单笔订单消费满额
  426. case 3: // 单次充值满额
  427. case 4: // 活动期间累计消费满额
  428. var amountInput = container.find('input[name*="[condition_value]"]');
  429. var amount = parseFloat(amountInput.val()) || 0;
  430. if (amount <= 0) {
  431. var typeName = type === 2 ? '订单消费' : (type === 3 ? '充值' : '累计消费');
  432. Toastr.error('请填写' + typeName + '金额');
  433. amountInput.focus();
  434. isValid = false;
  435. return false;
  436. }
  437. break;
  438. }
  439. });
  440. return isValid;
  441. },
  442. // 初始化奖品管理
  443. initPrizeManagement: function() {
  444. // 初始化拖拽排序
  445. Controller.api.initPrizeSort();
  446. // 初始化概率计算
  447. Controller.api.updateTotalRate();
  448. // 绑定系统默认项的事件
  449. Controller.api.bindDefaultPrizeEvents();
  450. },
  451. // 绑定系统默认项的事件
  452. bindDefaultPrizeEvents: function() {
  453. // 绑定概率输入框事件
  454. $('.prize-rate').off('input blur').on('input', function() {
  455. Controller.api.updateTotalRate();
  456. }).on('blur', function() {
  457. // 失去焦点时格式化为两位小数
  458. var value = parseFloat($(this).val()) || 0;
  459. $(this).val(value.toFixed(2));
  460. Controller.api.updateTotalRate();
  461. });
  462. // 绑定编辑按钮事件
  463. $('.edit-prize').off('click').on('click', function() {
  464. var prizeItem = $(this).closest('.prize-item');
  465. // 统一使用editPrize处理所有类型
  466. Controller.api.editPrize(prizeItem);
  467. });
  468. // 绑定删除按钮事件
  469. $('.delete-prize').off('click').on('click', function() {
  470. var prizeItem = $(this).closest('.prize-item');
  471. // 检查是否是系统默认项
  472. if (prizeItem.attr('data-type') === 'no-prize') {
  473. Toastr.error('系统默认项不能删除');
  474. return;
  475. }
  476. prizeItem.remove();
  477. Controller.api.updatePrizeCount();
  478. Controller.api.updateTotalRate();
  479. Controller.api.updatePrizeOrder(); // 更新奖品索引
  480. });
  481. },
  482. // 添加奖品
  483. addPrize: function() {
  484. var prizeCount = $('.prize-item').length - 1; // 减去未中奖项
  485. var maxCount = 8;
  486. if (prizeCount >= maxCount) {
  487. Toastr.error('最多只能添加' + maxCount + '个奖品');
  488. return;
  489. }
  490. // 使用通用表单,添加模式
  491. Controller.api.openPrizeForm({
  492. mode: 'add',
  493. title: '添加奖品'
  494. });
  495. },
  496. // 通用奖品表单
  497. openPrizeForm: function(options) {
  498. var defaults = {
  499. mode: 'add', // 'add' 或 'edit'
  500. title: '奖品表单',
  501. prizeItem: null,
  502. isSystemNoPrize: false,
  503. data: {
  504. name: '',
  505. image: '',
  506. type: '2', // 默认实物奖品
  507. quantity: '1',
  508. rate: '0.00'
  509. }
  510. };
  511. var config = $.extend({}, defaults, options);
  512. var isEditMode = config.mode === 'edit';
  513. // 获取奖品类型数据
  514. var prizeTypeMap = Controller.api.parseConfigJson('prizeTypeList', {});
  515. var prizeTypes = [];
  516. for (var key in prizeTypeMap) {
  517. if (prizeTypeMap.hasOwnProperty(key)) {
  518. // 添加模式和编辑模式都包含所有类型
  519. prizeTypes.push({
  520. value: parseInt(key),
  521. text: prizeTypeMap[key]
  522. });
  523. }
  524. }
  525. // 确定是否显示奖励设置区域
  526. var selectedType = parseInt(config.data.type) || (isEditMode ? 1 : 2);
  527. var rewardVisible = selectedType > 2;
  528. var rewardType = Controller.api.getRewardTypeByPrizeType(selectedType);
  529. // 生成表单HTML
  530. var formHtml = Template('prize-form-template', {
  531. mode: config.mode,
  532. name: config.data.name,
  533. image: config.data.image,
  534. quantity: config.data.quantity,
  535. rate: config.data.rate,
  536. prizeTypes: prizeTypes,
  537. selectedType: selectedType,
  538. rewardVisible: rewardVisible,
  539. rewardType: rewardType
  540. });
  541. // 弹出表单
  542. Layer.open({
  543. type: 1,
  544. title: config.title,
  545. area: isEditMode ? ['80%', '80%'] : ['80%', '80%'],
  546. content: formHtml,
  547. btn: [isEditMode ? '保存' : '确定', '取消'],
  548. success: function(layero, index) {
  549. // 编辑模式需要设置表单数据
  550. if (isEditMode) {
  551. Controller.api.setFormData(layero, config);
  552. // 系统默认未中奖项的特殊处理
  553. if (config.isSystemNoPrize) {
  554. // 隐藏奖品数量字段(未中奖不需要数量)
  555. layero.find('.prize-quantity-group').hide();
  556. // 禁用奖品类型选择(固定为未中奖)
  557. layero.find('input[name="row[prize_type]"]').prop('disabled', true);
  558. }
  559. } else {
  560. // 添加模式:设置默认奖品类型并触发相应逻辑
  561. var defaultType = selectedType || 2; // 默认实物奖品
  562. layero.find('input[name="row[prize_type]"][value="' + defaultType + '"]').prop('checked', true);
  563. // 设置默认图片
  564. Controller.api.setPrizeDefaultImage(layero.find('.prize-form'), defaultType);
  565. }
  566. // 设置正确的字数统计初始值
  567. var currentName = layero.find('input[name="prize_name"]').val();
  568. var currentLength = currentName ? currentName.length : 0;
  569. layero.find('#prize-name-count').text(currentLength + '/20');
  570. // 绑定表单事件
  571. Controller.api.bindPrizeFormEvents(layero, config);
  572. },
  573. yes: function(index, layero) {
  574. var success = false;
  575. if (isEditMode) {
  576. success = Controller.api.updatePrize(layero, config.prizeItem);
  577. } else {
  578. success = Controller.api.savePrize(layero);
  579. }
  580. if (success) {
  581. Layer.close(index);
  582. }
  583. }
  584. });
  585. },
  586. // 设置表单数据(编辑模式)
  587. setFormData: function(layero, config) {
  588. var form = layero.find('.prize-form');
  589. var data = config.data;
  590. // 设置基本字段
  591. form.find('input[name="prize_name"]').val(data.name);
  592. form.find('input[name="prize_image"]').val(data.image);
  593. form.find('input[name="prize_quantity"]').val(data.quantity);
  594. form.find('input[name="prize_rate"]').val(data.rate);
  595. // 设置奖品类型(单选框)
  596. form.find('input[name="row[prize_type]"][value="' + data.type + '"]').prop('checked', true);
  597. // 根据奖品类型处理特殊字段
  598. if (data.type == '8' && data.goodsId) {
  599. // 商城奖品:设置商品ID并获取商品信息
  600. form.find('input[name="goods_id"]').val(data.goodsId);
  601. Controller.api.loadGoodsInfo(form, data.goodsId, function() {
  602. // 商品信息加载完成,触发FastAdmin的表单更新
  603. setTimeout(function() {
  604. Form.api.bindevent(form);
  605. }, 100);
  606. });
  607. } else if (data.type == '5' && data.couponId) {
  608. // 优惠券:设置优惠券ID并获取优惠券信息
  609. form.find('input[name="coupon_id"]').val(data.couponId);
  610. Controller.api.loadCouponInfo(form, data.couponId);
  611. }
  612. // FastAdmin的动态显示会根据奖品类型自动处理字段显示
  613. // 无需手动控制显示逻辑
  614. // 触发上传组件的图片预览更新
  615. if (data.image) {
  616. form.find('input[name="prize_image"]').trigger('change');
  617. }
  618. },
  619. // 绑定奖品表单事件
  620. bindPrizeFormEvents: function(layero, config) {
  621. var form = layero.find('.prize-form');
  622. var isEditMode = config && config.mode === 'edit';
  623. // 奖品名称字数统计
  624. form.find('input[name="prize_name"]').on('input', function(){
  625. var len = $(this).val().length;
  626. form.find('#prize-name-count').text(len + '/20');
  627. });
  628. // 奖品类型切换
  629. form.find('input[name="row[prize_type]"]').change(function() {
  630. var type = $(this).val();
  631. // 根据模式设置默认图片
  632. if (!isEditMode) {
  633. // 添加模式:设置默认图片
  634. Controller.api.setPrizeDefaultImage(form, type);
  635. }
  636. // 编辑模式:不改变图片,保持原有图片
  637. // FastAdmin的动态显示会自动处理字段的显示/隐藏和验证
  638. // 无需手动控制显示逻辑
  639. });
  640. // 商品选择按钮
  641. form.find('#select-goods-btn').click(function() {
  642. Controller.api.openGoodsSelector(form);
  643. });
  644. // 优惠券选择按钮
  645. form.find('#select-coupon-btn').click(function() {
  646. Controller.api.openCouponSelector(form);
  647. });
  648. // 兑换码类型切换(FastAdmin动态显示会自动处理)
  649. // 无需手动控制显示逻辑
  650. // 概率输入框格式化
  651. form.find('input[name="prize_rate"]').on('blur', function() {
  652. var value = parseFloat($(this).val()) || 0;
  653. $(this).val(value.toFixed(2));
  654. });
  655. // 初始化上传组件
  656. Form.api.bindevent(form);
  657. },
  658. // 设置奖品默认图片
  659. setPrizeDefaultImage: function(form, type) {
  660. // 使用辅助函数解析默认图片配置
  661. var defaultImages = Controller.api.parseConfigJson('prizeDefaultImages', {
  662. 1: '/assets/img/lottery/no_prize.png', // 未中奖
  663. 2: '/assets/img/lottery/goods.png', // 实物奖品
  664. 3: '/assets/img/lottery/credit.png', // 积分
  665. 4: '/assets/img/lottery/balance.png', // 余额
  666. 5: '/assets/img/lottery/coupon.png', // 优惠券
  667. 6: '/assets/img/lottery/redbag.png', // 红包
  668. 7: '/assets/img/lottery/code.png', // 兑换码
  669. 8: '/assets/img/lottery/shop_goods.png' // 商城奖品
  670. });
  671. var defaultImage = defaultImages[type] || '/assets/img/lottery/default.png';
  672. form.find('input[name="prize_image"]').val(defaultImage).trigger('change');
  673. },
  674. // 加载商品信息用于编辑模式回显
  675. loadGoodsInfo: function(form, goodsId, callback) {
  676. if (!goodsId) {
  677. console.warn('loadGoodsInfo: 商品ID为空');
  678. if (callback) callback();
  679. return;
  680. }
  681. console.log('正在加载商品信息,ID:', goodsId);
  682. // 通过Ajax获取商品信息
  683. $.ajax({
  684. url: Fast.api.fixurl('shop/goods/info'),
  685. type: 'POST',
  686. data: {id: goodsId},
  687. dataType: 'json',
  688. success: function(data) {
  689. console.log('商品信息API响应:', data);
  690. if (data.code === 1 && data.data) {
  691. var goods = data.data; // 标准FastAdmin响应格式
  692. console.log('成功获取商品信息:', goods);
  693. // 处理商品数据
  694. var processedGoods = {
  695. id: goods.id,
  696. name: goods.name || goods.title,
  697. title: goods.title || goods.name,
  698. image: goods.image ? Fast.api.cdnurl(goods.image) : '',
  699. price: goods.price || goods.sellprice || '',
  700. goods_sn: goods.goods_sn || '',
  701. spec_type: goods.spec_type !== undefined ? goods.spec_type : null,
  702. category: goods.category || null,
  703. type_name: Controller.api.getGoodsTypeName(goods),
  704. type_text: Controller.api.getGoodsTypeText(goods)
  705. };
  706. // 使用商品模板显示
  707. var html = Template('prize-goods-template', processedGoods);
  708. form.find('#selected-goods-display').html(html).show();
  709. // 绑定删除事件
  710. form.find('.remove-prize-goods').off('click').on('click', function() {
  711. $(this).closest('.prize-goods-item').remove();
  712. form.find('input[name="goods_id"]').val('');
  713. });
  714. console.log('商品信息已成功显示');
  715. // 执行回调
  716. if (callback) callback();
  717. } else {
  718. console.warn('商品信息API返回异常:', data);
  719. // 显示友好的提示信息
  720. var errorMsg = data.msg || '获取商品信息失败';
  721. form.find('#selected-goods-display').html(
  722. '<div class="alert alert-warning" style="margin-top: 10px;">' +
  723. '<i class="fa fa-warning"></i> ' + errorMsg +
  724. ' (商品ID: ' + goodsId + ')' +
  725. '</div>'
  726. ).show();
  727. // 即使失败也执行回调
  728. if (callback) callback();
  729. }
  730. },
  731. error: function(xhr, status, error) {
  732. console.error('加载商品信息失败 - 详细信息:');
  733. console.error('- 商品ID:', goodsId);
  734. console.error('- HTTP状态:', xhr.status);
  735. console.error('- 错误信息:', error);
  736. console.error('- 响应内容:', xhr.responseText);
  737. // 显示友好的错误提示
  738. var errorHtml = '<div class="alert alert-danger" style="margin-top: 10px;">' +
  739. '<i class="fa fa-exclamation-circle"></i> 加载商品信息失败<br>' +
  740. '<small>商品ID: ' + goodsId + '</small><br>' +
  741. '<small>错误: ' + (xhr.status === 404 ? '商品不存在或已删除' :
  742. xhr.status === 403 ? '无权限访问' :
  743. '网络错误,请稍后重试') + '</small>' +
  744. '</div>';
  745. form.find('#selected-goods-display').html(errorHtml).show();
  746. // 错误时也执行回调
  747. if (callback) callback();
  748. }
  749. });
  750. },
  751. // 加载优惠券信息用于编辑模式回显
  752. loadCouponInfo: function(form, couponId) {
  753. if (!couponId) {
  754. console.warn('loadCouponInfo: 优惠券ID为空');
  755. return;
  756. }
  757. console.log('正在加载优惠券信息,ID:', couponId);
  758. // 暂时显示一个简单的占位信息,提醒需要重新选择
  759. var placeholderHtml = '<div class="alert alert-info" style="margin-top: 10px;">' +
  760. '<i class="fa fa-info-circle"></i> 编辑模式下需要重新选择优惠券<br>' +
  761. '<small>优惠券ID: ' + couponId + '</small>' +
  762. '</div>';
  763. form.find('#selected-coupon-display').html(placeholderHtml).show();
  764. console.log('优惠券占位信息已显示,用户需要重新选择');
  765. },
  766. // 打开商品选择器
  767. openGoodsSelector: function(form) {
  768. Fast.api.open('shop/goods/select?selectMode=single', '选择商品', {
  769. callback: function(data) {
  770. if (data.length > 0) {
  771. var goods = data[0]; // 取第一个选中的商品
  772. // 处理商品数据,确保格式完整
  773. var processedGoods = {
  774. id: goods.id,
  775. name: goods.name || goods.title,
  776. title: goods.title || goods.name,
  777. image: goods.image ? (typeof Fast !== 'undefined' && Fast.api ? Fast.api.cdnurl(goods.image) : goods.image) : '',
  778. price: goods.price || goods.sellprice || '',
  779. goods_sn: goods.goods_sn || '',
  780. spec_type: goods.spec_type !== undefined ? goods.spec_type : null,
  781. category: goods.category || null,
  782. type_name: Controller.api.getGoodsTypeName(goods),
  783. type_text: Controller.api.getGoodsTypeText(goods)
  784. };
  785. // 使用奖品商品模板
  786. var html = Template('prize-goods-template', processedGoods);
  787. form.find('#selected-goods-display').html(html).show();
  788. // 设置隐藏域的值以触发验证
  789. form.find('input[name="goods_id"]').val(goods.id);
  790. // 设置商品图片为奖品图片
  791. form.find('input[name="prize_image"]').val(goods.image).trigger('change');
  792. // 绑定删除事件
  793. form.find('.remove-prize-goods').off('click').on('click', function() {
  794. $(this).closest('.prize-goods-item').remove();
  795. form.find('input[name="goods_id"]').val(''); // 清空隐藏域
  796. });
  797. }
  798. }
  799. });
  800. },
  801. // 打开优惠券选择器
  802. openCouponSelector: function(form) {
  803. Fast.api.open('shop/coupon/select', '选择优惠券', {
  804. callback: function(data) {
  805. if (data.length > 0) {
  806. var coupon = data[0]; // 取第一个选中的优惠券
  807. // 直接使用模板ID
  808. var html = Template('selected-coupon-template', {
  809. name: coupon.name,
  810. type_text: coupon.type_text || '优惠券',
  811. id: coupon.id
  812. });
  813. form.find('#selected-coupon-display').html(html).show();
  814. // 设置隐藏域的值以触发验证
  815. form.find('input[name="coupon_id"]').val(coupon.id);
  816. }
  817. }
  818. });
  819. },
  820. // 保存奖品
  821. savePrize: function(layero) {
  822. var form = layero.find('.prize-form');
  823. var name = form.find('input[name="prize_name"]').val().trim();
  824. var type = form.find('input[name="row[prize_type]"]:checked').val();
  825. var image = form.find('input[name="prize_image"]').val();
  826. var quantity = form.find('input[name="prize_quantity"]').val();
  827. // 根据奖品类型获取对应的奖励值
  828. var rewardValue = '';
  829. if (type == 3) { // 积分
  830. rewardValue = form.find('input[name="reward_value_integral"]').val();
  831. } else if (type == 4) { // 余额
  832. rewardValue = form.find('input[name="reward_value_balance"]').val();
  833. } else if (type == 6) { // 红包
  834. rewardValue = form.find('input[name="reward_value_redpack"]').val();
  835. }
  836. var goodsId = form.find('input[name="goods_id"]').val();
  837. var couponId = form.find('input[name="coupon_id"]').val();
  838. if (!name) {
  839. Toastr.error('请输入奖品名称');
  840. return false;
  841. }
  842. if (!type) {
  843. Toastr.error('请选择奖品类型');
  844. return false;
  845. }
  846. if (!image) {
  847. Toastr.error('请选择奖品图片');
  848. return false;
  849. }
  850. // 未中奖类型不需要验证数量
  851. if (type != 1 && (!quantity || quantity < 1)) {
  852. Toastr.error('请输入正确的奖品数量');
  853. return false;
  854. }
  855. // 验证积分数量
  856. if (type == 3 && !rewardValue) {
  857. Toastr.error('请输入积分数量');
  858. return false;
  859. }
  860. // 验证余额金额
  861. if (type == 4 && !rewardValue) {
  862. Toastr.error('请输入余额金额');
  863. return false;
  864. }
  865. // 验证优惠券选择
  866. if (type == 5 && !couponId) {
  867. Toastr.error('请选择优惠券');
  868. return false;
  869. }
  870. // 验证红包金额
  871. if (type == 6 && !rewardValue) {
  872. Toastr.error('请输入红包金额');
  873. return false;
  874. }
  875. // 验证商品选择(只有商城奖品需要选择商品)
  876. if (type == 8 && !goodsId) {
  877. Toastr.error('请选择商品');
  878. return false;
  879. }
  880. // 验证兑换码设置
  881. if (type == 7) {
  882. var codeType = form.find('input[name="code_type"]:checked').val();
  883. if (codeType === 'manual') {
  884. var manualCodes = form.find('textarea[name="manual_codes"]').val().trim();
  885. if (!manualCodes) {
  886. Toastr.error('请输入兑换码列表');
  887. return false;
  888. }
  889. }
  890. }
  891. // 构建奖品数据
  892. var prizeData = {
  893. name: name,
  894. type: parseInt(type),
  895. image: image,
  896. quantity: type == 1 ? 0 : parseInt(quantity), // 未中奖类型数量为0
  897. rate: 0
  898. };
  899. // 根据奖品类型添加特定数据
  900. switch(parseInt(type)) {
  901. case 3: // 积分
  902. prizeData.points = parseInt(rewardValue);
  903. break;
  904. case 4: // 余额
  905. prizeData.balance = parseFloat(rewardValue);
  906. break;
  907. case 5: // 优惠券
  908. prizeData.coupon_id = couponId;
  909. prizeData.coupon_name = form.find('#selected-coupon-display .selected-coupon-item span:first').text();
  910. break;
  911. case 6: // 红包
  912. prizeData.redpack_amount = parseFloat(rewardValue);
  913. break;
  914. case 7: // 兑换码
  915. prizeData.code_type = form.find('input[name="code_type"]:checked').val();
  916. if (prizeData.code_type === 'manual') {
  917. prizeData.manual_codes = form.find('textarea[name="manual_codes"]').val();
  918. }
  919. break;
  920. case 8: // 商城奖品
  921. prizeData.goods_id = goodsId;
  922. // 使用简化的选择器获取商品名称
  923. prizeData.goods_name = form.find('#selected-goods-display .goods-name-display').text() || '';
  924. break;
  925. }
  926. // 添加奖品到列表
  927. Controller.api.appendPrizeToList(prizeData);
  928. Controller.api.updatePrizeCount();
  929. Controller.api.updateTotalRate();
  930. Controller.api.updatePrizeOrder(); // 更新奖品索引
  931. return true;
  932. },
  933. // 添加奖品到列表
  934. appendPrizeToList: function(prize) {
  935. // 获取奖品类型信息
  936. var typeName = Controller.api.getPrizeTypeName(prize.type);
  937. var labelClass = Controller.api.getPrizeLabelClass(prize.type);
  938. var rewardText = Controller.api.getPrizeRewardText(prize);
  939. // 获取当前奖品索引 (跳过未中奖项,从1开始)
  940. var prizeIndex = $('.prize-list .prize-item').length;
  941. // 处理图片URL
  942. var imageRelative = prize.image; // 相对路径,用于表单提交
  943. var imageDisplay = prize.image ? (typeof Fast !== 'undefined' && Fast.api ? Fast.api.cdnurl(prize.image) : prize.image) : ''; // CDN URL,用于显示
  944. // 使用外部模板
  945. var prizeHtml = Template('prize-item-template', {
  946. index: prizeIndex,
  947. name: prize.name,
  948. type: prize.type,
  949. typeName: typeName,
  950. labelClass: labelClass,
  951. rewardText: rewardText,
  952. imageRelative: imageRelative, // 相对路径
  953. imageDisplay: imageDisplay, // CDN URL
  954. quantity: prize.quantity || 0,
  955. rate: prize.rate || 0,
  956. goodsId: prize.goods_id || '',
  957. couponId: prize.coupon_id || ''
  958. });
  959. $('.prize-list').append(prizeHtml);
  960. // 绑定事件
  961. $('.prize-rate').off('input blur').on('input', function() {
  962. Controller.api.updateTotalRate();
  963. }).on('blur', function() {
  964. // 失去焦点时格式化为两位小数
  965. var value = parseFloat($(this).val()) || 0;
  966. $(this).val(value.toFixed(2));
  967. Controller.api.updateTotalRate();
  968. });
  969. $('.delete-prize').off('click').on('click', function() {
  970. var prizeItem = $(this).closest('.prize-item');
  971. // 检查是否是系统默认项
  972. if (prizeItem.attr('data-type') === 'no-prize') {
  973. Toastr.error('系统默认项不能删除');
  974. return;
  975. }
  976. prizeItem.remove();
  977. Controller.api.updatePrizeCount();
  978. Controller.api.updateTotalRate();
  979. Controller.api.updatePrizeOrder(); // 更新奖品索引
  980. });
  981. $('.edit-prize').off('click').on('click', function() {
  982. var prizeItem = $(this).closest('.prize-item');
  983. // 统一使用editPrize处理所有类型
  984. Controller.api.editPrize(prizeItem);
  985. });
  986. // 重新初始化拖拽排序
  987. Controller.api.initPrizeSort();
  988. },
  989. // 初始化奖品拖拽排序
  990. initPrizeSort: function() {
  991. require(['../libs/Sortable/Sortable'], function(Sortable) {
  992. var prizeListEl = document.querySelector('.prize-list');
  993. if (prizeListEl) {
  994. // 如果已经初始化过,先销毁
  995. if (prizeListEl.sortableInstance) {
  996. prizeListEl.sortableInstance.destroy();
  997. }
  998. // 创建新的 Sortable 实例
  999. prizeListEl.sortableInstance = new Sortable(prizeListEl, {
  1000. handle: '.prize-sort-handle',
  1001. animation: 150,
  1002. onUpdate: function(evt) {
  1003. Controller.api.updatePrizeOrder();
  1004. }
  1005. });
  1006. }
  1007. });
  1008. },
  1009. // 获取奖品类型名称
  1010. getPrizeTypeName: function(type) {
  1011. var prizeTypeMap = Controller.api.parseConfigJson('prizeTypeList', {});
  1012. return prizeTypeMap[type] || '未知';
  1013. },
  1014. // 获取奖品标签样式类
  1015. getPrizeLabelClass: function(type) {
  1016. var classMap = {
  1017. 1: 'default', // 未中奖 - 灰色
  1018. 2: 'primary', // 实物奖品 - 蓝色
  1019. 3: 'warning', // 积分 - 橙色
  1020. 4: 'info', // 余额 - 浅蓝色
  1021. 5: 'success', // 优惠券 - 绿色
  1022. 6: 'danger', // 红包 - 红色
  1023. 7: 'default', // 兑换码 - 灰色
  1024. 8: 'primary' // 商城奖品 - 蓝色
  1025. };
  1026. return classMap[type] || 'default';
  1027. },
  1028. // 获取奖品奖励文本
  1029. getPrizeRewardText: function(prize) {
  1030. if (!prize.type || prize.type == 1) {
  1031. return ''; // 未中奖不显示奖励文本
  1032. }
  1033. switch(parseInt(prize.type)) {
  1034. case 2: // 实物奖品
  1035. return '';
  1036. case 3: // 积分
  1037. return prize.points ? (prize.points + ' 积分') : '';
  1038. case 4: // 余额
  1039. return prize.balance ? (prize.balance + ' 余额') : '';
  1040. case 5: // 优惠券
  1041. return prize.coupon_name ? prize.coupon_name : '';
  1042. case 6: // 红包
  1043. return prize.redpack_amount ? (prize.redpack_amount + ' 元红包') : '';
  1044. case 7: // 兑换码
  1045. return prize.code_type == 'manual' ? '手动兑换码' : '自动兑换码';
  1046. case 8: // 商城奖品
  1047. return prize.goods_name ? prize.goods_name : '';
  1048. default:
  1049. return '';
  1050. }
  1051. },
  1052. // 从表单获取奖品奖励文本
  1053. getPrizeRewardTextFromForm: function(form, type) {
  1054. if (!type || type == 1) {
  1055. return ''; // 未中奖不显示奖励文本
  1056. }
  1057. switch(parseInt(type)) {
  1058. case 2: // 实物奖品
  1059. return '';
  1060. case 3: // 积分
  1061. var points = form.find('input[name="reward_value_integral"]').val();
  1062. return points ? (points + ' 积分') : '';
  1063. case 4: // 余额
  1064. var balance = form.find('input[name="reward_value_balance"]').val();
  1065. return balance ? (balance + ' 余额') : '';
  1066. case 5: // 优惠券
  1067. var couponName = form.find('#selected-coupon-display .selected-coupon-item span:first').text();
  1068. return couponName || '';
  1069. case 6: // 红包
  1070. var amount = form.find('input[name="reward_value_redpack"]').val();
  1071. return amount ? (amount + ' 元红包') : '';
  1072. case 7: // 兑换码
  1073. var codeType = form.find('input[name="code_type"]:checked').val();
  1074. return codeType == 'manual' ? '手动兑换码' : '自动兑换码';
  1075. case 8: // 商城奖品
  1076. // 使用简化的选择器获取商品名称
  1077. return form.find('#selected-goods-display .goods-name-display').text() || '';
  1078. default:
  1079. return '';
  1080. }
  1081. },
  1082. // 根据奖品类型获取奖励类型
  1083. getRewardTypeByPrizeType: function(prizeType) {
  1084. switch(parseInt(prizeType)) {
  1085. case 3: return 'integral'; // 积分
  1086. case 4: return 'balance'; // 余额
  1087. case 5: return 'coupon'; // 优惠券
  1088. case 6: return 'redbag'; // 红包
  1089. case 7: return 'code'; // 兑换码
  1090. case 8: return 'goods'; // 商城奖品
  1091. default: return 'none';
  1092. }
  1093. },
  1094. // 更新奖品数量统计
  1095. updatePrizeCount: function() {
  1096. var totalCount = $('.prize-item').length; // 总奖品数(包含未中奖项)
  1097. var userCount = totalCount - 1; // 用户添加的奖品数(减去未中奖项)
  1098. // 显示总奖品数(包含系统默认的未中奖项)
  1099. $('#prize-count').text(totalCount);
  1100. // 更新添加奖品按钮状态(基于用户可添加的奖品数)
  1101. Controller.api.updateAddPrizeButtonState(userCount);
  1102. // 更新验证状态
  1103. $('input[name="prize_setting_check"]').val('1').trigger('change');
  1104. },
  1105. // 更新添加奖品按钮状态
  1106. updateAddPrizeButtonState: function(userCount) {
  1107. var maxUserCount = 8; // 用户最大可添加奖品数量
  1108. var addButton = $('#add-prize-btn');
  1109. var remainingCount = maxUserCount - userCount;
  1110. if (userCount >= maxUserCount) {
  1111. // 达到最大数量,禁用按钮
  1112. addButton.prop('disabled', true).addClass('disabled');
  1113. addButton.find('.btn-text').text('已达最大数量');
  1114. $('#prize-count-tip').text('已添加最大数量的奖品').removeClass('text-muted').addClass('text-danger');
  1115. } else {
  1116. // 未达到最大数量,启用按钮
  1117. addButton.prop('disabled', false).removeClass('disabled');
  1118. addButton.find('.btn-text').text('添加奖品');
  1119. if (remainingCount <= 2) {
  1120. $('#prize-count-tip').text('还可添加 ' + remainingCount + ' 个奖品').removeClass('text-muted text-danger').addClass('text-warning');
  1121. } else {
  1122. // 当用户还没添加奖品时,显示还需添加多少个才能满足最低要求
  1123. var minRequired = Math.max(0, 7 - userCount); // 至少需要7个用户奖品(总共8个,已有1个系统默认)
  1124. if (minRequired > 0) {
  1125. $('#prize-count-tip').text('还需添加 ' + minRequired + ' 个奖品').removeClass('text-warning text-danger').addClass('text-muted');
  1126. } else {
  1127. $('#prize-count-tip').text('还可添加 ' + remainingCount + ' 个奖品').removeClass('text-warning text-danger').addClass('text-muted');
  1128. }
  1129. }
  1130. }
  1131. },
  1132. // 计算总概率
  1133. calculateTotalRate: function() {
  1134. var total = 0;
  1135. $('.prize-rate').each(function() {
  1136. total += parseFloat($(this).val()) || 0;
  1137. });
  1138. // 保留两位小数
  1139. return Math.round(total * 100) / 100;
  1140. },
  1141. // 更新总概率显示
  1142. updateTotalRate: function() {
  1143. var total = Controller.api.calculateTotalRate();
  1144. // 格式化显示为两位小数
  1145. $('#total-rate').text(total.toFixed(2) + '%');
  1146. // 根据概率设置颜色
  1147. if (total > 100) {
  1148. $('#total-rate').removeClass('text-success text-warning').addClass('text-danger');
  1149. } else if (total === 100) {
  1150. $('#total-rate').removeClass('text-danger text-warning').addClass('text-success');
  1151. } else {
  1152. $('#total-rate').removeClass('text-danger text-success').addClass('text-warning');
  1153. }
  1154. // 更新验证状态
  1155. $('input[name="prize_setting_check"]').val('1').trigger('change');
  1156. },
  1157. // 编辑奖品(统一处理所有类型)
  1158. editPrize: function(prizeItem) {
  1159. // 判断是否为系统默认未中奖项
  1160. var isSystemNoPrize = prizeItem.attr('data-type') === 'no-prize';
  1161. // 获取奖品信息
  1162. var name, imageRelative, type, quantity, rate, goodsId, couponId;
  1163. if (isSystemNoPrize) {
  1164. // 系统默认项从不同位置获取数据
  1165. name = prizeItem.find('td:eq(1)').text().trim(); // 去除空格
  1166. imageRelative = prizeItem.find('.prize-image-input').val();
  1167. type = prizeItem.find('.prize-type-input').val() || '1';
  1168. quantity = 0; // 未中奖项数量固定为0
  1169. rate = prizeItem.find('.prize-rate').val();
  1170. } else {
  1171. // 普通奖品从标准位置获取数据
  1172. name = prizeItem.find('.prize-name').text().trim(); // 去除空格
  1173. imageRelative = prizeItem.find('.prize-image-input').val();
  1174. type = prizeItem.find('.prize-type-input').val();
  1175. quantity = prizeItem.find('.prize-quantity').val();
  1176. rate = prizeItem.find('.prize-rate').val();
  1177. // 获取商品ID和优惠券ID(如果存在)
  1178. goodsId = prizeItem.find('.prize-goods-id-input').val();
  1179. couponId = prizeItem.find('.prize-coupon-id-input').val();
  1180. }
  1181. var title = isSystemNoPrize ? '编辑未中奖项' : '编辑奖品';
  1182. // 使用通用表单,编辑模式
  1183. Controller.api.openPrizeForm({
  1184. mode: 'edit',
  1185. title: title,
  1186. prizeItem: prizeItem,
  1187. isSystemNoPrize: isSystemNoPrize,
  1188. data: {
  1189. name: name,
  1190. image: imageRelative,
  1191. type: type,
  1192. quantity: quantity,
  1193. rate: rate,
  1194. goodsId: goodsId,
  1195. couponId: couponId
  1196. }
  1197. });
  1198. },
  1199. // 更新奖品信息
  1200. updatePrize: function(layero, prizeItem) {
  1201. var form = layero.find('.prize-form');
  1202. var name = form.find('input[name="prize_name"]').val().trim();
  1203. var type = form.find('input[name="row[prize_type]"]:checked').val();
  1204. var image = form.find('input[name="prize_image"]').val();
  1205. var quantity = form.find('input[name="prize_quantity"]').val();
  1206. var rate = form.find('input[name="prize_rate"]').val();
  1207. if (!name) {
  1208. Toastr.error('请输入奖品名称');
  1209. return false;
  1210. }
  1211. if (!image) {
  1212. Toastr.error('请选择奖品图片');
  1213. return false;
  1214. }
  1215. // 未中奖类型不需要验证数量
  1216. if (type != 1 && (!quantity || quantity < 1)) {
  1217. Toastr.error('请输入正确的奖品数量');
  1218. return false;
  1219. }
  1220. // 更新界面显示
  1221. var imageDisplay = image ? (typeof Fast !== 'undefined' && Fast.api ? Fast.api.cdnurl(image) : image) : '';
  1222. // 检查是否是系统默认项
  1223. if (prizeItem.attr('data-type') === 'no-prize') {
  1224. // 系统默认项直接更新文本
  1225. prizeItem.find('td:eq(1)').text(name);
  1226. } else {
  1227. // 普通奖品更新显示元素和隐藏域
  1228. prizeItem.find('.prize-name').text(name);
  1229. prizeItem.find('.prize-name-input').val(name);
  1230. prizeItem.find('.prize-quantity').val(quantity);
  1231. // 更新奖品类型隐藏域
  1232. prizeItem.find('.prize-type-input').val(type);
  1233. // 更新奖品类型显示标签
  1234. var typeName = Controller.api.getPrizeTypeName(type);
  1235. var labelClass = Controller.api.getPrizeLabelClass(type);
  1236. var typeLabel = prizeItem.find('td:eq(2) .label');
  1237. typeLabel.removeClass().addClass('label label-' + labelClass).text(typeName);
  1238. // 更新奖励文本显示
  1239. var rewardText = Controller.api.getPrizeRewardTextFromForm(form, type);
  1240. var rewardDiv = prizeItem.find('td:eq(2) div');
  1241. if (rewardText) {
  1242. if (rewardDiv.length === 0) {
  1243. // 如果没有奖励文本div,创建一个
  1244. prizeItem.find('td:eq(2)').append('<div style="font-size: 11px; color: #666; margin-top: 2px;">' + rewardText + '</div>');
  1245. } else {
  1246. // 更新现有的奖励文本
  1247. rewardDiv.text(rewardText);
  1248. }
  1249. } else {
  1250. // 删除奖励文本
  1251. rewardDiv.remove();
  1252. }
  1253. }
  1254. // 更新图片显示和隐藏域
  1255. prizeItem.find('img').attr('src', imageDisplay);
  1256. prizeItem.find('.prize-image-input').val(image); // 隐藏域存储相对路径
  1257. prizeItem.find('.prize-rate').val(rate);
  1258. Controller.api.updateTotalRate();
  1259. return true;
  1260. },
  1261. // 更新奖品排序
  1262. updatePrizeOrder: function() {
  1263. // 重新编号所有奖品的隐藏域name属性
  1264. $('.prize-list .prize-item').each(function(index) {
  1265. var $item = $(this);
  1266. var prizeIndex = index; // 按实际位置索引
  1267. // 更新隐藏域的name属性
  1268. $item.find('.prize-name-input').attr('name', 'prizes[' + prizeIndex + '][name]');
  1269. $item.find('.prize-type-input').attr('name', 'prizes[' + prizeIndex + '][type]');
  1270. $item.find('.prize-image-input').attr('name', 'prizes[' + prizeIndex + '][image]');
  1271. // 更新商品ID和优惠券ID的隐藏域name属性
  1272. $item.find('.prize-goods-id-input').attr('name', 'prizes[' + prizeIndex + '][goods_id]');
  1273. $item.find('.prize-coupon-id-input').attr('name', 'prizes[' + prizeIndex + '][coupon_id]');
  1274. // 更新其他表单字段的name属性
  1275. $item.find('.prize-quantity').attr('name', 'prizes[' + prizeIndex + '][quantity]');
  1276. $item.find('.prize-rate').attr('name', 'prizes[' + prizeIndex + '][rate]');
  1277. // 更新奖品特定字段的name属性
  1278. var prizeType = parseInt($item.find('.prize-type-input').val());
  1279. switch(prizeType) {
  1280. case 3: // 积分
  1281. $item.find('.prize-points-input').attr('name', 'prizes[' + prizeIndex + '][points]');
  1282. break;
  1283. case 4: // 余额
  1284. $item.find('.prize-balance-input').attr('name', 'prizes[' + prizeIndex + '][balance]');
  1285. break;
  1286. case 6: // 红包
  1287. $item.find('.prize-redpack-input').attr('name', 'prizes[' + prizeIndex + '][redpack_amount]');
  1288. break;
  1289. case 7: // 兑换码
  1290. $item.find('.prize-codes-input').attr('name', 'prizes[' + prizeIndex + '][manual_codes]');
  1291. break;
  1292. }
  1293. });
  1294. console.log('奖品排序已更新');
  1295. },
  1296. // 在表单提交前收集奖品数据
  1297. collectPrizeData: function() {
  1298. var prizesData = [];
  1299. $('.prize-list .prize-item').each(function(index) {
  1300. var $item = $(this);
  1301. var prizeType = parseInt($item.find('.prize-type-input').val());
  1302. var prizeData = {
  1303. name: $item.find('.prize-name-input').val() || $item.find('.prize-name').text() || $item.find('td:eq(1)').text(),
  1304. type: prizeType,
  1305. image: $item.find('.prize-image-input').val(),
  1306. quantity: $item.find('.prize-quantity').val() || 0,
  1307. rate: $item.find('.prize-rate').val() || 0
  1308. };
  1309. // 根据奖品类型添加特定字段
  1310. switch(prizeType) {
  1311. case 3: // 积分
  1312. var pointsInput = $item.find('.prize-points-input');
  1313. if (pointsInput.length === 0) {
  1314. // 如果隐藏域不存在,从表单中获取
  1315. pointsInput = $('input[name="reward_value_integral"]');
  1316. }
  1317. prizeData.points = pointsInput.val() || 0;
  1318. break;
  1319. case 4: // 余额
  1320. var balanceInput = $item.find('.prize-balance-input');
  1321. if (balanceInput.length === 0) {
  1322. balanceInput = $('input[name="reward_value_balance"]');
  1323. }
  1324. prizeData.balance = balanceInput.val() || 0;
  1325. break;
  1326. case 5: // 优惠券
  1327. prizeData.coupon_id = $item.find('.prize-coupon-id-input').val() || 0;
  1328. break;
  1329. case 6: // 红包
  1330. var redpackInput = $item.find('.prize-redpack-input');
  1331. if (redpackInput.length === 0) {
  1332. redpackInput = $('input[name="reward_value_redpack"]');
  1333. }
  1334. prizeData.redpack_amount = redpackInput.val() || 0;
  1335. break;
  1336. case 7: // 兑换码
  1337. var codesInput = $item.find('.prize-codes-input');
  1338. if (codesInput.length === 0) {
  1339. codesInput = $('textarea[name="manual_codes"]');
  1340. }
  1341. prizeData.manual_codes = codesInput.val() || '';
  1342. break;
  1343. case 8: // 商城奖品
  1344. prizeData.goods_id = $item.find('.prize-goods-id-input').val() || 0;
  1345. break;
  1346. }
  1347. prizesData.push(prizeData);
  1348. });
  1349. return prizesData;
  1350. },
  1351. // 在表单提交前收集条件数据
  1352. collectConditionData: function() {
  1353. var conditionData = {};
  1354. var taskTypes = [];
  1355. // 获取选中的任务类型
  1356. $('input[name="row[task_type][]"]:checked').each(function() {
  1357. taskTypes.push($(this).val());
  1358. });
  1359. // 为每个任务类型收集条件数据
  1360. taskTypes.forEach(function(taskType) {
  1361. var type = parseInt(taskType);
  1362. var condition = {};
  1363. switch(type) {
  1364. case 1: // 购买指定商品
  1365. condition.type = type;
  1366. condition.goods_rule = $('input[name="condition[' + type + '][goods_rule]"]:checked').val() || 1;
  1367. condition.goods_ids = $('#task-goods-ids-' + type).val() || '[]';
  1368. break;
  1369. case 2: // 单笔订单消费满额
  1370. case 3: // 单次充值满额
  1371. case 4: // 活动期间累计消费满额
  1372. condition.type = type;
  1373. condition.condition_value = $('input[name="condition[' + type + '][condition_value]"]').val() || 0;
  1374. break;
  1375. }
  1376. conditionData[type] = condition;
  1377. });
  1378. return {
  1379. conditionData: conditionData,
  1380. taskTypes: taskTypes
  1381. };
  1382. },
  1383. // 初始化商品选择器
  1384. initGoodsSelector: function() {
  1385. // 商品选择器初始化逻辑
  1386. },
  1387. // 选择商品 - 通用方法,支持单选和多选
  1388. selectGoods: function(options) {
  1389. var defaults = {
  1390. mode: 'single', // 'single' 单选, 'multiple' 多选
  1391. title: '选择商品',
  1392. container: '#selected-goods',
  1393. template: 'goods-list-template', // 统一使用goods-list-template
  1394. maxSelect: 0, // 最大选择数量,0表示无限制
  1395. callback: null
  1396. };
  1397. var config = $.extend({}, defaults, options);
  1398. // 构建URL参数
  1399. var urlParams = [];
  1400. // 添加选择模式参数
  1401. if (config.mode === 'single') {
  1402. urlParams.push('selectMode=single');
  1403. } else {
  1404. urlParams.push('selectMode=multiple');
  1405. // 如果有最大选择限制,添加参数
  1406. if (config.maxSelect > 0) {
  1407. urlParams.push('maxSelect=' + config.maxSelect);
  1408. }
  1409. }
  1410. // 构建完整URL
  1411. var url = 'shop/goods/select';
  1412. if (urlParams.length > 0) {
  1413. url += '?' + urlParams.join('&');
  1414. }
  1415. Fast.api.open(url, config.title, {
  1416. callback: function(data) {
  1417. if (data && data.length > 0) {
  1418. // 单选模式只取第一个
  1419. if (config.mode === 'single' && data.length > 1) {
  1420. data = [data[0]];
  1421. }
  1422. Controller.api.updateSelectedGoods(data, config);
  1423. // 执行回调
  1424. if (typeof config.callback === 'function') {
  1425. config.callback(data);
  1426. }
  1427. } else {
  1428. Toastr.error('请选择商品');
  1429. }
  1430. }
  1431. });
  1432. },
  1433. // 选择任务商品(多选模式)
  1434. selectTaskGoods: function(container) {
  1435. Controller.api.selectGoods({
  1436. mode: 'multiple',
  1437. title: '选择参与商品',
  1438. container: container || '#task-goods-container',
  1439. template: 'task-goods-template',
  1440. callback: function(data) {
  1441. // 如果是购买指定商品任务,更新隐藏字段
  1442. if (container === '#selected-goods' && data && data.length > 0) {
  1443. var goodsIds = data.map(function(item) {
  1444. return item.id;
  1445. });
  1446. // 更新隐藏字段,存储JSON格式的商品IDs
  1447. $('#task-goods-ids-1').val(JSON.stringify(goodsIds));
  1448. console.log('购买指定商品任务 - 商品IDs已更新:', goodsIds);
  1449. // 绑定删除事件
  1450. Controller.api.bindTaskGoodsEvents();
  1451. }
  1452. }
  1453. });
  1454. },
  1455. // 选择奖品商品(单选模式)
  1456. selectPrizeGoods: function(container) {
  1457. Controller.api.selectGoods({
  1458. mode: 'single',
  1459. title: '选择奖品商品',
  1460. container: container || '#prize-goods-container',
  1461. template: 'goods-list-template' // 使用统一的商品选择模板
  1462. });
  1463. },
  1464. // 更新已选商品
  1465. updateSelectedGoods: function(goods, config) {
  1466. config = config || {};
  1467. var container = config.container || '#selected-goods';
  1468. var template = config.template || 'goods-list-template';
  1469. // 预处理商品数据
  1470. var processedGoods = Controller.api.processGoodsData(goods);
  1471. var html = '';
  1472. if (template === 'goods-list-template') {
  1473. // 商品列表(支持单选和多选)
  1474. html = Template(template, {
  1475. goods: processedGoods
  1476. });
  1477. $(container).html(html);
  1478. // 绑定删除事件
  1479. $('.remove-goods-item').off('click').on('click', function() {
  1480. $(this).closest('.goods-list-item').remove();
  1481. });
  1482. } else if (template === 'prize-goods-template') {
  1483. // 奖品商品(单选)
  1484. if (processedGoods.length > 0) {
  1485. html = Template(template, processedGoods[0]);
  1486. $(container).html(html);
  1487. // 绑定删除事件
  1488. $('.remove-prize-goods').off('click').on('click', function() {
  1489. $(this).closest('.prize-goods-item').remove();
  1490. });
  1491. }
  1492. } else {
  1493. // 其他模板(向后兼容)
  1494. html = Template(template, {
  1495. goods: processedGoods
  1496. });
  1497. $(container).html(html);
  1498. }
  1499. },
  1500. // 处理商品数据
  1501. processGoodsData: function(goods) {
  1502. var processedGoods = [];
  1503. if (goods && goods.length) {
  1504. for (var i = 0; i < goods.length; i++) {
  1505. var item = goods[i];
  1506. var processedItem = {
  1507. id: item.id,
  1508. name: item.name || item.title,
  1509. title: item.title || item.name,
  1510. image: item.image ? (typeof Fast !== 'undefined' && Fast.api ? Fast.api.cdnurl(item.image) : item.image) : '',
  1511. price: item.price || item.sellprice || '',
  1512. goods_sn: item.goods_sn || '',
  1513. spec_type: item.spec_type !== undefined ? item.spec_type : null,
  1514. category: item.category || null,
  1515. type_name: Controller.api.getGoodsTypeName(item),
  1516. type_text: Controller.api.getGoodsTypeText(item)
  1517. };
  1518. processedGoods.push(processedItem);
  1519. }
  1520. }
  1521. return processedGoods;
  1522. },
  1523. // 获取商品类型文本(用于显示标签)
  1524. getGoodsTypeText: function(goods) {
  1525. // 根据商品类型返回相应的标签文本
  1526. if (goods.type == 2) {
  1527. return '密'; // 虚拟商品显示"密"
  1528. }
  1529. if (goods.isrecom == 1) {
  1530. return '荐'; // 推荐商品显示"荐"
  1531. }
  1532. if (goods.ishot == 1) {
  1533. return '热'; // 热门商品显示"热"
  1534. }
  1535. if (goods.isnew == 1) {
  1536. return '新'; // 新品显示"新"
  1537. }
  1538. return ''; // 默认无标签
  1539. },
  1540. // 获取商品类型名称
  1541. getGoodsTypeName: function(goods) {
  1542. // 根据商品类型返回类型名称
  1543. if (goods.type == 1) {
  1544. return '普通商品';
  1545. } else if (goods.type == 2) {
  1546. return '虚拟商品';
  1547. } else if (goods.type == 3) {
  1548. return '服务商品';
  1549. }
  1550. return '普通商品'; // 默认类型
  1551. },
  1552. // 绑定任务商品删除事件
  1553. bindTaskGoodsEvents: function() {
  1554. $('.remove-task-goods').off('click').on('click', function() {
  1555. var goodsId = $(this).data('id');
  1556. var $item = $(this).closest('.goods-list-item');
  1557. // 从DOM中移除商品项
  1558. $item.remove();
  1559. // 更新隐藏字段中的商品IDs
  1560. Controller.api.updateTaskGoodsIds();
  1561. });
  1562. },
  1563. // 更新任务商品IDs隐藏字段
  1564. updateTaskGoodsIds: function() {
  1565. var goodsIds = [];
  1566. $('#selected-goods .goods-list-item').each(function() {
  1567. var goodsId = $(this).data('id');
  1568. if (goodsId) {
  1569. goodsIds.push(goodsId);
  1570. }
  1571. });
  1572. // 更新隐藏字段
  1573. $('#task-goods-ids-1').val(JSON.stringify(goodsIds));
  1574. console.log('任务商品IDs已更新:', goodsIds);
  1575. }
  1576. }
  1577. };
  1578. // 全局函数,供HTML调用
  1579. window.addPrize = function() {
  1580. Controller.api.addPrize();
  1581. };
  1582. // 默认商品选择(针对购买指定商品任务,使用多选模式)
  1583. window.selectGoods = function() {
  1584. Controller.api.selectGoods({
  1585. mode: 'multiple',
  1586. title: '选择参与商品',
  1587. container: '#selected-goods',
  1588. template: 'goods-list-template'
  1589. });
  1590. };
  1591. // 任务商品选择(多选)
  1592. window.selectTaskGoods = function(container) {
  1593. Controller.api.selectTaskGoods(container);
  1594. };
  1595. // 奖品商品选择(单选)
  1596. window.selectPrizeGoods = function(container) {
  1597. Controller.api.selectPrizeGoods(container);
  1598. };
  1599. return Controller;
  1600. });