activity.js 112 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362
  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: Controller.api.parseConfigJson("typeList"), formatter: Table.api.formatter.normal},
  30. {field: 'status', title: __('状态'), searchList: Controller.api.parseConfigJson("statusList"), formatter: Table.api.formatter.status},
  31. {field: 'lottery_type', title: __('开奖方式'), searchList: Controller.api.parseConfigJson("lotteryTypeList"), 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. Form.api.bindevent($("form[role=form]"));
  108. },
  109. // 初始化添加页面
  110. initAddPage: function() {
  111. // 初始化基础UI组件
  112. Controller.api.initBasicUI();
  113. // 初始化奖品数据管理器
  114. Controller.api.prizesDataManager.init();
  115. // 同步DOM中现有的奖品数据到数据管理器
  116. Controller.api.syncExistingPrizesToDataManager();
  117. // 初始化奖品管理
  118. Controller.api.initPrizeManagement();
  119. // 初始化任务规则管理
  120. Controller.api.initTaskRuleManagement();
  121. // 确保系统默认项有正确的概率值
  122. Controller.api.initDefaultPrizeRate();
  123. // 确保DOM渲染完成后再计算奖品数量
  124. setTimeout(function() {
  125. console.log('页面初始化 - 开始更新奖品计数');
  126. Controller.api.updatePrizeCount();
  127. }, 50);
  128. // 再次延迟调用,确保所有DOM都已渲染
  129. setTimeout(function() {
  130. console.log('页面初始化 - 二次更新奖品计数');
  131. Controller.api.updatePrizeCount();
  132. }, 500);
  133. // 初始化表单验证
  134. Controller.api.initFormValidation();
  135. },
  136. // 初始化基础UI组件
  137. initBasicUI: function() {
  138. // 活动名称字数统计
  139. $('#c-name').on('input', function(){
  140. var len = $(this).val().length;
  141. $('#activity-name-count').text(len + '/24');
  142. });
  143. },
  144. // 初始化任务规则管理
  145. initTaskRuleManagement: function() {
  146. // 任务类型切换(复选框)
  147. $(document).on('change', 'input[name="row[task_type][]"]', function() {
  148. Controller.api.handleTaskTypeChange();
  149. });
  150. // 商品规则切换
  151. $(document).on('change', 'input[name^="condition"][name$="[goods_rule]"]', function() {
  152. Controller.api.updateGoodsRuleDisplay($(this));
  153. });
  154. // 初始化任务类型状态
  155. Controller.api.initTaskTypeState();
  156. },
  157. // 处理任务类型变化
  158. handleTaskTypeChange: function() {
  159. var checkedTypes = $('input[name="row[task_type][]"]:checked');
  160. // 更新复选框样式
  161. $('input[name="row[task_type][]"]').each(function() {
  162. var label = $(this).closest('label');
  163. if ($(this).is(':checked')) {
  164. label.addClass('checked');
  165. } else {
  166. label.removeClass('checked');
  167. }
  168. });
  169. // 隐藏所有任务设置项
  170. $('.task-setting-item').hide();
  171. // 显示选中的任务设置项
  172. checkedTypes.each(function() {
  173. var type = parseInt($(this).val());
  174. $('#task-setting-' + type).show();
  175. });
  176. // 更新验证规则
  177. Controller.api.updateTaskValidation();
  178. },
  179. // 初始化任务类型状态
  180. initTaskTypeState: function() {
  181. var checkedTasks = $('input[name="row[task_type][]"]:checked');
  182. // 初始化复选框样式
  183. $('input[name="row[task_type][]"]').each(function() {
  184. var label = $(this).closest('label');
  185. if ($(this).is(':checked')) {
  186. label.addClass('checked');
  187. } else {
  188. label.removeClass('checked');
  189. }
  190. });
  191. // 初始化任务设置区域显示
  192. $('.task-setting-item').hide();
  193. checkedTasks.each(function() {
  194. var type = parseInt($(this).val());
  195. $('#task-setting-' + type).show();
  196. });
  197. },
  198. // 更新商品规则显示
  199. updateGoodsRuleDisplay: function($radio) {
  200. var container = $radio.closest('.task-setting-item');
  201. var rule = $radio.val();
  202. var helpText = container.find('.goods-description-box');
  203. if (rule == '1') {
  204. helpText.html('<span class="text-muted">指定商品下单后将自动触发抽奖活动且发放一次抽奖机会,若用户产生维权退款,已发放的奖励自动回收。</span>');
  205. } else {
  206. helpText.html('<span class="text-muted">除指定商品外的其他商品下单后将自动触发抽奖活动且发放一次抽奖机会。</span>');
  207. }
  208. },
  209. // 更新任务验证规则
  210. updateTaskValidation: function() {
  211. var checkedTypes = $('input[name="row[task_type][]"]:checked');
  212. // 移除旧的验证规则
  213. $('[name^="condition"]').removeAttr('data-rule');
  214. // 为选中的任务类型添加验证规则
  215. checkedTypes.each(function() {
  216. var type = parseInt($(this).val());
  217. switch(type) {
  218. case 1: // 购买指定商品
  219. // 商品选择验证会在选择商品时处理
  220. break;
  221. case 2: // 单笔订单消费满额
  222. case 3: // 单次充值满额
  223. case 4: // 活动期间累计消费满额
  224. $('[name="condition[' + type + '][condition_value]"]').attr('data-rule', 'required;number(0.01~999999.99)');
  225. break;
  226. }
  227. });
  228. },
  229. // 初始化表单验证
  230. initFormValidation: function() {
  231. // 添加自定义验证规则 - 验证奖品JSON数据
  232. if (typeof Nice !== 'undefined' && Nice.Validator) {
  233. Nice.Validator.addRule('prizesJson', function(val, el) {
  234. try {
  235. var prizes = JSON.parse(val || '[]');
  236. // 验证是否有奖品
  237. if (!prizes || prizes.length === 0) {
  238. return false;
  239. }
  240. // 验证每个奖品的基本字段
  241. for (var i = 0; i < prizes.length; i++) {
  242. var prize = prizes[i];
  243. if (!prize.name || !prize.type || !prize.image) {
  244. return false;
  245. }
  246. // 验证概率值
  247. if (typeof prize.rate !== 'number' || prize.rate < 0 || prize.rate > 100) {
  248. return false;
  249. }
  250. }
  251. return true;
  252. } catch (e) {
  253. return false;
  254. }
  255. }, '奖品数据不完整或格式错误');
  256. }
  257. // 监听表单提交事件
  258. $('form[role="form"]').on('submit', function(e) {
  259. console.log('表单提交开始');
  260. // 验证任务规则设置
  261. if (!Controller.api.validateTaskRules()) {
  262. e.preventDefault();
  263. return false;
  264. }
  265. // 收集条件数据并添加到表单
  266. var conditionResult = Controller.api.collectConditionData();
  267. var conditionData = conditionResult.conditionData;
  268. var taskTypes = conditionResult.taskTypes;
  269. if (Object.keys(conditionData).length > 0) {
  270. // 移除之前的隐藏字段
  271. $('form[role="form"] input[name^="condition["]').remove();
  272. // 添加新的条件数据
  273. for (var type in conditionData) {
  274. var condition = conditionData[type];
  275. for (var key in condition) {
  276. if (condition[key] !== null && condition[key] !== undefined) {
  277. var value = condition[key];
  278. if (Array.isArray(value)) {
  279. value = JSON.stringify(value);
  280. }
  281. $('<input>').attr({
  282. type: 'hidden',
  283. name: 'condition[' + type + '][' + key + ']',
  284. value: value
  285. }).appendTo('form[role="form"]');
  286. }
  287. }
  288. }
  289. }
  290. // 添加任务类型数据
  291. if (taskTypes.length > 0) {
  292. // 移除之前的task_type隐藏字段
  293. $('form[role="form"] input[type="hidden"][name^="task_type"]').remove();
  294. // 添加任务类型数据
  295. for (var i = 0; i < taskTypes.length; i++) {
  296. $('<input>').attr({
  297. type: 'hidden',
  298. name: 'task_type[' + i + ']',
  299. value: taskTypes[i]
  300. }).appendTo('form[role="form"]');
  301. }
  302. }
  303. console.log('条件数据:', conditionData);
  304. console.log('任务类型:', taskTypes);
  305. return true;
  306. });
  307. },
  308. // 初始化编辑页面
  309. initEditPage: function() {
  310. // 初始化基础UI组件
  311. Controller.api.initBasicUI();
  312. // 初始化奖品数据管理器
  313. Controller.api.prizesDataManager.init();
  314. // 初始化奖品管理
  315. Controller.api.initPrizeManagement();
  316. // 初始化任务规则管理
  317. Controller.api.initTaskRuleManagement();
  318. // 初始化已有的奖品数据
  319. Controller.api.initExistingPrizes();
  320. // 初始化已有的条件数据
  321. Controller.api.initExistingConditions();
  322. // 确保所有概率输入框有正确的值
  323. setTimeout(function() {
  324. Controller.api.initDefaultPrizeRate();
  325. }, 100);
  326. // 初始化表单验证
  327. Controller.api.initFormValidation();
  328. console.log('编辑页面初始化完成');
  329. },
  330. // 同步DOM中现有的奖品数据到数据管理器
  331. syncExistingPrizesToDataManager: function() {
  332. console.log('开始同步DOM中现有的奖品数据到数据管理器');
  333. var existingPrizes = [];
  334. // 遍历DOM中现有的奖品项
  335. $('.prize-item').each(function(index) {
  336. var $item = $(this);
  337. var prizeData = Controller.api.extractPrizeDataFromDOM($item, index);
  338. if (prizeData) {
  339. existingPrizes.push(prizeData);
  340. console.log('提取到奖品数据:', prizeData);
  341. }
  342. });
  343. // 如果有奖品数据,同步到数据管理器
  344. if (existingPrizes.length > 0) {
  345. Controller.api.prizesDataManager.prizesData = existingPrizes;
  346. Controller.api.prizesDataManager.saveToHiddenField();
  347. console.log('已同步奖品数据到数据管理器,共', existingPrizes.length, '个奖品');
  348. } else {
  349. console.log('DOM中没有找到现有的奖品数据');
  350. }
  351. },
  352. // 从DOM中提取奖品数据
  353. extractPrizeDataFromDOM: function($item, index) {
  354. try {
  355. var isNoPrize = $item.attr('data-type') === 'no-prize';
  356. var prizeData = {
  357. id: $item.attr('data-prize-id') || ('prize_' + Date.now() + '_' + index),
  358. name: '',
  359. type: 1,
  360. image: '',
  361. quantity: 0,
  362. rate: 0
  363. };
  364. // 提取奖品名称
  365. if (isNoPrize) {
  366. // 系统默认未中奖项
  367. prizeData.name = $item.find('.prize-name-input').val() || $item.find('td:eq(1)').text().trim() || '谢谢参与';
  368. prizeData.type = parseInt($item.find('.prize-type-input').val() || 1);
  369. prizeData.image = $item.find('.prize-image-input').val() || '';
  370. // 如果隐藏域中没有图片,尝试从img标签获取(去掉CDN前缀)
  371. if (!prizeData.image) {
  372. var imgSrc = $item.find('.prize-image').attr('src') || '';
  373. if (imgSrc && typeof Fast !== 'undefined' && Fast.api && Fast.api.cdnurl) {
  374. // 去掉CDN前缀,获取相对路径
  375. prizeData.image = imgSrc.replace(/^https?:\/\/[^\/]+/, '');
  376. } else {
  377. prizeData.image = imgSrc;
  378. }
  379. }
  380. prizeData.quantity = 0; // 未中奖项数量固定为0
  381. prizeData.rate = parseFloat($item.find('.prize-rate').val() || $item.find('#no-prize-rate-input').val() || 50);
  382. } else {
  383. // 用户添加的奖品
  384. prizeData.name = $item.find('.prize-name').text().trim();
  385. prizeData.type = parseInt($item.find('.prize-type-input').val() || 2);
  386. prizeData.image = $item.find('.prize-image-input').val() || $item.find('.prize-image').attr('src') || '';
  387. prizeData.quantity = parseInt($item.find('.prize-quantity').val() || 1);
  388. prizeData.rate = parseFloat($item.find('.prize-rate').val() || 0);
  389. }
  390. // 根据奖品类型提取特定数据
  391. switch(prizeData.type) {
  392. case 3: // 积分
  393. prizeData.points = parseInt($item.find('.prize-points-input').val() || 0);
  394. break;
  395. case 4: // 余额
  396. prizeData.balance = parseFloat($item.find('.prize-balance-input').val() || 0);
  397. break;
  398. case 5: // 优惠券
  399. prizeData.coupon_id = $item.find('.prize-coupon-id-input').val();
  400. prizeData.couponId = prizeData.coupon_id;
  401. prizeData.coupon_name = $item.find('.prize-coupon-name').text() || '';
  402. break;
  403. case 6: // 红包
  404. prizeData.redpack_amount = parseFloat($item.find('.prize-redpack-input').val() || 0);
  405. break;
  406. case 7: // 兑换码
  407. prizeData.code_type = $item.find('.prize-code-type-input').val() || 'auto';
  408. prizeData.manual_codes = $item.find('.prize-manual-codes-input').val() || '';
  409. break;
  410. case 8: // 商城奖品
  411. prizeData.goods_id = $item.find('.prize-goods-id-input').val();
  412. prizeData.goodsId = prizeData.goods_id;
  413. prizeData.goods_name = $item.find('.prize-goods-name').text() || '';
  414. break;
  415. }
  416. console.log('提取奖品数据:', {
  417. index: index,
  418. isNoPrize: isNoPrize,
  419. extractedData: prizeData
  420. });
  421. return prizeData;
  422. } catch (e) {
  423. console.warn('提取奖品数据时出错:', e, $item);
  424. return null;
  425. }
  426. },
  427. // 初始化已有的奖品数据(重构版 - 使用数据管理器)
  428. initExistingPrizes: function() {
  429. // 检查是否有后台传递的奖品数据
  430. if (typeof window.existingPrizes !== 'undefined' && window.existingPrizes.length > 0) {
  431. console.log('正在初始化已有奖品数据:', window.existingPrizes);
  432. // 将已有奖品数据加载到数据管理器
  433. Controller.api.prizesDataManager.prizesData = window.existingPrizes;
  434. Controller.api.prizesDataManager.saveToHiddenField();
  435. // 重新构建奖品列表
  436. Controller.api.rebuildPrizeList();
  437. // 更新奖品计数和概率统计
  438. Controller.api.updatePrizeCount();
  439. Controller.api.updateTotalRate();
  440. console.log('奖品数据初始化完成,数据管理器已同步');
  441. } else {
  442. console.log('没有找到已有的奖品数据,尝试从DOM同步');
  443. // 如果没有后台数据,尝试从DOM同步现有数据
  444. Controller.api.syncExistingPrizesToDataManager();
  445. }
  446. },
  447. // 初始化已有的条件数据
  448. initExistingConditions: function() {
  449. // 检查是否有后台传递的条件数据
  450. if (typeof window.existingConditions !== 'undefined' && window.existingConditions.length > 0) {
  451. console.log('正在初始化已有条件数据:', window.existingConditions);
  452. // 根据条件数据勾选任务类型
  453. window.existingConditions.forEach(function(condition) {
  454. var checkbox = $('input[name="row[task_type][]"][value="' + condition.type + '"]');
  455. if (checkbox.length > 0) {
  456. checkbox.prop('checked', true);
  457. // 显示对应的任务设置
  458. $('#task-setting-' + condition.type).show();
  459. // 根据条件类型设置具体的数据
  460. switch (parseInt(condition.type)) {
  461. case 1: // 购买指定商品
  462. if (condition.goods_ids_array && condition.goods_ids_array.length > 0) {
  463. // 需要调用商品选择API来获取商品详情
  464. Controller.api.loadConditionGoods(condition.type, condition.goods_ids_array);
  465. }
  466. break;
  467. case 2: // 单笔订单消费满额
  468. case 3: // 单次充值满额
  469. case 4: // 活动期间累计消费满额
  470. var amountInput = $('input[name="condition[' + condition.type + '][condition_value]"]');
  471. if (amountInput.length > 0) {
  472. amountInput.val(condition.condition_value);
  473. }
  474. break;
  475. }
  476. }
  477. });
  478. console.log('条件数据初始化完成');
  479. } else {
  480. console.log('没有找到已有的条件数据');
  481. }
  482. },
  483. // 加载条件中的商品数据
  484. loadConditionGoods: function(taskType, goodsIds) {
  485. if (!goodsIds || goodsIds.length === 0) return;
  486. // 调用商品信息API
  487. $.ajax({
  488. url: 'shop/goods/info',
  489. type: 'POST',
  490. data: {
  491. ids: goodsIds.join(',')
  492. },
  493. dataType: 'json',
  494. success: function(ret) {
  495. if (ret.code === 1 && ret.data && ret.data.length > 0) {
  496. // 更新商品显示
  497. Controller.api.updateSelectedGoods(ret.data, {
  498. container: '#task-setting-' + taskType + ' #selected-goods',
  499. template: 'goods-list-template'
  500. });
  501. // 更新隐藏字段
  502. $('#task-goods-ids-' + taskType).val(JSON.stringify(goodsIds));
  503. console.log('任务商品数据加载完成:', ret.data);
  504. }
  505. },
  506. error: function(xhr, status, error) {
  507. console.error('加载任务商品数据失败:', error);
  508. }
  509. });
  510. },
  511. // 验证任务规则设置
  512. validateTaskRules: function() {
  513. var checkedTypes = $('input[name="row[task_type][]"]:checked');
  514. // 检查是否选择了任务类型
  515. if (checkedTypes.length === 0) {
  516. Toastr.error('请选择至少一种参与条件');
  517. return false;
  518. }
  519. // 验证每个选中的任务类型
  520. var isValid = true;
  521. checkedTypes.each(function() {
  522. var type = parseInt($(this).val());
  523. var container = $('#task-setting-' + type);
  524. switch(type) {
  525. case 1: // 购买指定商品
  526. var goodsContainer = container.find('#selected-goods');
  527. if (goodsContainer.find('.goods-list-item').length === 0) {
  528. Toastr.error('请选择参与的商品');
  529. isValid = false;
  530. return false;
  531. }
  532. break;
  533. case 2: // 单笔订单消费满额
  534. case 3: // 单次充值满额
  535. case 4: // 活动期间累计消费满额
  536. var amountInput = container.find('input[name*="[condition_value]"]');
  537. var amount = parseFloat(amountInput.val()) || 0;
  538. if (amount <= 0) {
  539. var typeName = type === 2 ? '订单消费' : (type === 3 ? '充值' : '累计消费');
  540. Toastr.error('请填写' + typeName + '金额');
  541. amountInput.focus();
  542. isValid = false;
  543. return false;
  544. }
  545. break;
  546. }
  547. });
  548. return isValid;
  549. },
  550. // 初始化奖品管理 - 重构版
  551. initPrizeManagement: function() {
  552. console.log('初始化奖品管理模块');
  553. // 初始化拖拽排序
  554. Controller.api.initPrizeSort();
  555. // 初始化概率计算
  556. Controller.api.updateTotalRate();
  557. // 绑定奖品相关事件
  558. Controller.api.bindPrizeEvents();
  559. // 初始化表单验证
  560. Controller.api.initPrizeFormValidation();
  561. console.log('奖品管理模块初始化完成');
  562. },
  563. // 添加奖品
  564. addPrize: function() {
  565. var totalCount = $('.prize-item').length; // 总奖品数(包含未中奖项)
  566. var userCount = totalCount - 1; // 用户添加的奖品数(减去未中奖项)
  567. var maxUserCount = 7; // 用户最多只能添加7个奖品(总共8个,包括系统默认的未中奖项)
  568. if (userCount >= maxUserCount) {
  569. Toastr.error('最多只能添加' + maxUserCount + '个奖品(不包括系统默认的未中奖项)');
  570. return;
  571. }
  572. // 使用通用表单,添加模式
  573. Controller.api.openPrizeForm({
  574. mode: 'add',
  575. title: '添加奖品'
  576. });
  577. },
  578. // 初始化奖品表单验证
  579. initPrizeFormValidation: function() {
  580. console.log('初始化奖品表单验证');
  581. // FastAdmin使用Nice-validator,验证规则通过data-rule属性定义
  582. // 无需额外的自定义验证方法,表单验证会在Form.api.bindevent中自动处理
  583. console.log('FastAdmin表单验证已通过data-rule属性配置');
  584. },
  585. // 通用奖品表单 - 重构版
  586. openPrizeForm: function(options) {
  587. var defaults = {
  588. mode: 'add', // 'add' 或 'edit'
  589. title: '奖品表单',
  590. prizeItem: null,
  591. isSystemNoPrize: false,
  592. data: {
  593. name: '',
  594. image: '',
  595. type: '2', // 默认实物奖品
  596. quantity: '1',
  597. rate: '0.00'
  598. }
  599. };
  600. var config = $.extend({}, defaults, options);
  601. var isEditMode = config.mode === 'edit';
  602. // 获取奖品类型数据
  603. var prizeTypeMap = Controller.api.parseConfigJson('prizeTypeList', {});
  604. var prizeTypes = [];
  605. for (var key in prizeTypeMap) {
  606. if (prizeTypeMap.hasOwnProperty(key)) {
  607. // 添加模式和编辑模式都包含所有类型
  608. prizeTypes.push({
  609. value: parseInt(key),
  610. text: prizeTypeMap[key]
  611. });
  612. }
  613. }
  614. // 确定是否显示奖励设置区域
  615. var selectedType = parseInt(config.data.type) || (isEditMode ? 1 : 2);
  616. var rewardVisible = selectedType > 2;
  617. var rewardType = Controller.api.getRewardTypeByPrizeType(selectedType);
  618. // 生成表单HTML - 使用重构的模板
  619. var formHtml = Template('prize-form-template', {
  620. mode: config.mode,
  621. prizeTypes: prizeTypes,
  622. selectedType: selectedType
  623. });
  624. // 弹出表单 - 学习提供的样式代码
  625. Layer.open({
  626. type: 1,
  627. title: config.title,
  628. area: isEditMode ? ['80%', '80%'] : ['80%', '80%'],
  629. content: formHtml,
  630. btn: [isEditMode ? '保存' : '确定', '取消'],
  631. success: function(layero, index) {
  632. var $form = layero.find('.prize-form');
  633. // 初始化FastAdmin表单组件
  634. Form.api.bindevent($form);
  635. // 编辑模式需要设置表单数据
  636. if (isEditMode) {
  637. Controller.api.setFormData($form, config);
  638. // 系统默认未中奖项的特殊处理
  639. if (config.isSystemNoPrize) {
  640. // 隐藏奖品数量字段(未中奖不需要数量)
  641. $form.find('.prize-quantity-group').hide();
  642. $form.find('[data-favisible="prize_type!=1"]').hide();
  643. // 禁用奖品类型选择(固定为未中奖)
  644. $form.find('input[name="row[prize_type]"]').prop('disabled', true);
  645. }
  646. } else {
  647. // 添加模式:设置默认奖品类型并触发相应逻辑
  648. var defaultType = selectedType || 2; // 默认实物奖品
  649. $form.find('input[name="row[prize_type]"][value="' + defaultType + '"]').prop('checked', true);
  650. // 设置默认图片
  651. Controller.api.setPrizeDefaultImage($form, defaultType);
  652. // 触发奖品类型改变事件
  653. $form.find('input[name="row[prize_type]"]').trigger('change');
  654. }
  655. // 设置正确的字数统计初始值
  656. var currentName = $form.find('input[name="prize_name"]').val();
  657. var currentLength = currentName ? currentName.length : 0;
  658. $form.find('#prize-name-count').text(currentLength + '/20');
  659. // 绑定自定义事件
  660. Controller.api.bindPrizeFormEvents($form, config);
  661. // 初始化字数统计
  662. Controller.api.initNameCounter($form);
  663. // 优化表单样式,使其更紧凑
  664. Controller.api.optimizePrizeFormStyle($form);
  665. console.log('奖品表单初始化完成');
  666. },
  667. yes: function(index, layero) {
  668. var $form = layero.find('.prize-form');
  669. var success = false;
  670. if (isEditMode) {
  671. success = Controller.api.updatePrize($form, config);
  672. } else {
  673. success = Controller.api.savePrize($form);
  674. }
  675. if (success) {
  676. Layer.close(index);
  677. }
  678. // 如果验证失败,success会是false,Layer不会关闭
  679. return success;
  680. }
  681. });
  682. },
  683. // 设置默认表单数据(添加模式)
  684. setDefaultFormData: function($form, config) {
  685. console.log('设置默认表单数据');
  686. // 设置默认奖品类型
  687. var defaultType = config.data.type || 2;
  688. $form.find('input[name="row[prize_type]"][value="' + defaultType + '"]').prop('checked', true);
  689. // 设置默认图片
  690. Controller.api.setPrizeDefaultImage($form, defaultType);
  691. // 初始化图片上传组件
  692. Controller.api.initImageUpload($form);
  693. // 触发奖品类型改变事件
  694. $form.find('input[name="row[prize_type]"]').trigger('change');
  695. console.log('默认表单数据设置完成');
  696. },
  697. // 初始化名称字符计数
  698. initNameCounter: function($form) {
  699. var $nameInput = $form.find('input[name="prize_name"]');
  700. var $counter = $form.find('#prize-name-count');
  701. // 初始化计数
  702. var currentLength = $nameInput.val().length;
  703. $counter.text(currentLength + '/20');
  704. // 绑定输入事件
  705. $nameInput.on('input', function() {
  706. var length = $(this).val().length;
  707. $counter.text(length + '/20');
  708. });
  709. },
  710. // 优化奖品表单样式,使其更紧凑
  711. optimizePrizeFormStyle: function($form) {
  712. // 减少表单组的间距
  713. $form.find('.form-group').css({
  714. 'margin-bottom': '15px'
  715. });
  716. // 减少奖励设置区域的间距
  717. $form.find('.reward-group').css({
  718. 'margin-bottom': '10px'
  719. });
  720. // 减少表单区块的间距
  721. $form.find('.form-section').css({
  722. 'margin-bottom': '20px'
  723. });
  724. // 减少帮助文本的上边距
  725. $form.find('.help-block').css({
  726. 'margin-top': '5px',
  727. 'margin-bottom': '0px'
  728. });
  729. // 优化警告框的间距
  730. $form.find('.alert').css({
  731. 'margin-top': '10px',
  732. 'margin-bottom': '10px',
  733. 'padding': '10px'
  734. });
  735. // 减少图片预览区域的间距
  736. $form.find('.plupload-preview').css({
  737. 'margin-top': '10px'
  738. });
  739. console.log('表单样式优化完成');
  740. },
  741. // 初始化图片上传组件
  742. initImageUpload: function($form) {
  743. console.log('初始化图片上传组件');
  744. // 使用FastAdmin的Form组件来重新初始化表单内的上传组件
  745. Form.api.bindevent($form);
  746. // 绑定图片输入框的变化事件,实现预览功能
  747. $form.find('input[name="prize_image"]').on('change', function() {
  748. var imageUrl = $(this).val();
  749. if (imageUrl) {
  750. // 生成预览HTML
  751. var previewHtml = '<li class="col-xs-3">' +
  752. '<img src="' + (typeof Fast !== 'undefined' && Fast.api ? Fast.api.cdnurl(imageUrl) : imageUrl) + '" ' +
  753. 'class="img-responsive img-thumbnail" style="max-width: 100px; max-height: 100px;">' +
  754. '</li>';
  755. // 更新预览区域
  756. $form.find('#p-prize-image').html(previewHtml);
  757. } else {
  758. // 清空预览
  759. $form.find('#p-prize-image').empty();
  760. }
  761. });
  762. console.log('图片上传组件初始化完成');
  763. },
  764. // 设置表单数据(编辑模式) - 重构版
  765. setFormData: function($form, config) {
  766. console.log('设置编辑表单数据', config.data);
  767. var data = config.data;
  768. // 设置基本字段
  769. $form.find('input[name="prize_name"]').val(data.name);
  770. $form.find('input[name="prize_image"]').val(data.image);
  771. $form.find('input[name="prize_quantity"]').val(data.quantity || '1');
  772. $form.find('input[name="prize_rate"]').val(parseFloat(data.rate || 0).toFixed(2));
  773. // 设置奖品类型
  774. $form.find('input[name="row[prize_type]"][value="' + data.type + '"]').prop('checked', true);
  775. // 根据奖品类型处理特殊字段
  776. switch(parseInt(data.type)) {
  777. case 3: // 积分
  778. if (data.points) {
  779. $form.find('input[name="reward_value_integral"]').val(data.points);
  780. }
  781. break;
  782. case 4: // 余额
  783. if (data.balance) {
  784. $form.find('input[name="reward_value_balance"]').val(data.balance);
  785. }
  786. break;
  787. case 5: // 优惠券
  788. // 支持两种字段名格式
  789. var couponId = data.couponId || data.coupon_id;
  790. if (couponId) {
  791. $form.find('input[name="coupon_id"]').val(couponId);
  792. Controller.api.loadCouponInfo($form, couponId);
  793. }
  794. break;
  795. case 6: // 红包
  796. if (data.redpack_amount) {
  797. $form.find('input[name="reward_value_redpack"]').val(data.redpack_amount);
  798. }
  799. break;
  800. case 7: // 兑换码
  801. if (data.code_type) {
  802. $form.find('input[name="code_type"][value="' + data.code_type + '"]').prop('checked', true);
  803. }
  804. if (data.manual_codes) {
  805. $form.find('textarea[name="manual_codes"]').val(data.manual_codes);
  806. }
  807. break;
  808. case 8: // 商城奖品
  809. // 支持两种字段名格式
  810. var goodsId = data.goodsId || data.goods_id;
  811. if (goodsId) {
  812. $form.find('input[name="goods_id"]').val(goodsId);
  813. Controller.api.loadGoodsInfo($form, goodsId);
  814. }
  815. break;
  816. }
  817. // 触发奖品类型改变事件以显示对应的奖励设置
  818. $form.find('input[name="row[prize_type]"]').trigger('change');
  819. // 初始化图片上传组件
  820. Controller.api.initImageUpload($form);
  821. // 触发图片预览更新
  822. if (data.image) {
  823. $form.find('input[name="prize_image"]').trigger('change');
  824. }
  825. console.log('编辑表单数据设置完成');
  826. },
  827. // 绑定奖品表单事件 - 重构版
  828. bindPrizeFormEvents: function($form, config) {
  829. console.log('绑定奖品表单事件');
  830. var isEditMode = config && config.mode === 'edit';
  831. // 奖品类型切换事件
  832. $form.find('input[name="row[prize_type]"]').off('change.prizeType').on('change.prizeType', function() {
  833. var type = parseInt($(this).val());
  834. console.log('奖品类型切换至:', type);
  835. // 显示/隐藏对应的奖励设置
  836. Controller.api.toggleRewardSettings($form, type);
  837. // 非编辑模式时设置默认图片
  838. if (!isEditMode) {
  839. Controller.api.setPrizeDefaultImage($form, type);
  840. }
  841. });
  842. // 商品选择按钮
  843. $form.find('#select-goods-btn').off('click.selectGoods').on('click.selectGoods', function() {
  844. Controller.api.openGoodsSelector($form);
  845. });
  846. // 优惠券选择按钮
  847. $form.find('#select-coupon-btn').off('click.selectCoupon').on('click.selectCoupon', function() {
  848. Controller.api.openCouponSelector($form);
  849. });
  850. // 兑换码类型切换
  851. $form.find('input[name="code_type"]').off('change.codeType').on('change.codeType', function() {
  852. var codeType = $(this).val();
  853. Controller.api.toggleManualCodesInput($form, codeType);
  854. });
  855. // 概率输入框格式化
  856. $form.find('input[name="prize_rate"]').off('blur.rateFormat').on('blur.rateFormat', function() {
  857. var value = parseFloat($(this).val()) || 0;
  858. $(this).val(value.toFixed(2));
  859. });
  860. // 删除选中项事件
  861. $form.find('.selected-display').off('click.removeItem').on('click.removeItem', '.remove-item', function() {
  862. var $item = $(this).closest('.selected-item');
  863. var $hiddenInput = $item.siblings('input[type="hidden"]');
  864. $item.remove();
  865. $hiddenInput.val('');
  866. });
  867. console.log('奖品表单事件绑定完成');
  868. },
  869. // 切换奖励设置显示
  870. toggleRewardSettings: function($form, type) {
  871. console.log('切换奖励设置显示:', type);
  872. // 隐藏所有奖励设置
  873. $form.find('.reward-group').removeClass('active').hide();
  874. // 根据类型显示对应的设置
  875. var rewardType = '';
  876. switch(parseInt(type)) {
  877. case 3:
  878. rewardType = 'integral';
  879. break;
  880. case 4:
  881. rewardType = 'balance';
  882. break;
  883. case 5:
  884. rewardType = 'coupon';
  885. break;
  886. case 6:
  887. rewardType = 'redpack';
  888. break;
  889. case 7:
  890. rewardType = 'code';
  891. break;
  892. case 8:
  893. rewardType = 'goods';
  894. break;
  895. }
  896. if (rewardType) {
  897. $form.find('.reward-group[data-type="' + rewardType + '"]').addClass('active').show();
  898. }
  899. },
  900. // 切换手动兑换码输入
  901. toggleManualCodesInput: function($form, codeType) {
  902. var $container = $form.find('.manual-codes-container');
  903. if (codeType === 'manual') {
  904. $container.show();
  905. } else {
  906. $container.hide();
  907. }
  908. },
  909. // 设置奖品默认图片
  910. setPrizeDefaultImage: function(form, type) {
  911. // 从后台配置获取默认图片
  912. var defaultImages = Controller.api.parseConfigJson('prizeDefaultImages', {});
  913. var defaultImage = defaultImages[type] || '/assets/img/default.png';
  914. // 设置图片路径并触发change事件以更新预览
  915. form.find('input[name="prize_image"]').val(defaultImage).trigger('change');
  916. },
  917. // 加载商品信息用于编辑模式回显
  918. loadGoodsInfo: function(form, goodsId, callback) {
  919. if (!goodsId) {
  920. console.warn('loadGoodsInfo: 商品ID为空');
  921. if (callback) callback();
  922. return;
  923. }
  924. console.log('正在加载商品信息,ID:', goodsId);
  925. // 通过Ajax获取商品信息
  926. $.ajax({
  927. url: Fast.api.fixurl('shop/goods/info'),
  928. type: 'POST',
  929. data: {id: goodsId},
  930. dataType: 'json',
  931. success: function(data) {
  932. console.log('商品信息API响应:', data);
  933. if (data.code === 1 && data.data) {
  934. var goods = data.data; // 标准FastAdmin响应格式
  935. console.log('成功获取商品信息:', goods);
  936. // 处理商品数据
  937. var processedGoods = {
  938. id: goods.id,
  939. name: goods.name || goods.title,
  940. title: goods.title || goods.name,
  941. image: goods.image ? Fast.api.cdnurl(goods.image) : '',
  942. price: goods.price || goods.sellprice || '',
  943. goods_sn: goods.goods_sn || '',
  944. spec_type: goods.spec_type !== undefined ? goods.spec_type : null,
  945. category: goods.category || null,
  946. type_name: Controller.api.getGoodsTypeName(goods),
  947. type_text: Controller.api.getGoodsTypeText(goods)
  948. };
  949. // 使用商品模板显示
  950. var html = Template('prize-goods-template', processedGoods);
  951. form.find('#selected-goods-display').html(html).show();
  952. // 绑定删除事件
  953. form.find('.remove-prize-goods').off('click').on('click', function() {
  954. $(this).closest('.prize-goods-item').remove();
  955. form.find('input[name="goods_id"]').val('');
  956. });
  957. console.log('商品信息已成功显示');
  958. // 执行回调
  959. if (callback) callback();
  960. } else {
  961. console.warn('商品信息API返回异常:', data);
  962. // 显示友好的提示信息
  963. var errorMsg = data.msg || '获取商品信息失败';
  964. form.find('#selected-goods-display').html(
  965. '<div class="alert alert-warning" style="margin-top: 10px;">' +
  966. '<i class="fa fa-warning"></i> ' + errorMsg +
  967. ' (商品ID: ' + goodsId + ')' +
  968. '</div>'
  969. ).show();
  970. // 即使失败也执行回调
  971. if (callback) callback();
  972. }
  973. },
  974. error: function(xhr, status, error) {
  975. console.error('加载商品信息失败 - 详细信息:');
  976. console.error('- 商品ID:', goodsId);
  977. console.error('- HTTP状态:', xhr.status);
  978. console.error('- 错误信息:', error);
  979. console.error('- 响应内容:', xhr.responseText);
  980. // 显示友好的错误提示
  981. var errorHtml = '<div class="alert alert-danger" style="margin-top: 10px;">' +
  982. '<i class="fa fa-exclamation-circle"></i> 加载商品信息失败<br>' +
  983. '<small>商品ID: ' + goodsId + '</small><br>' +
  984. '<small>错误: ' + (xhr.status === 404 ? '商品不存在或已删除' :
  985. xhr.status === 403 ? '无权限访问' :
  986. '网络错误,请稍后重试') + '</small>' +
  987. '</div>';
  988. form.find('#selected-goods-display').html(errorHtml).show();
  989. // 错误时也执行回调
  990. if (callback) callback();
  991. }
  992. });
  993. },
  994. // 加载优惠券信息用于编辑模式回显
  995. loadCouponInfo: function(form, couponId) {
  996. if (!couponId) {
  997. console.warn('loadCouponInfo: 优惠券ID为空');
  998. return;
  999. }
  1000. console.log('正在加载优惠券信息,ID:', couponId);
  1001. // 暂时显示一个简单的占位信息,提醒需要重新选择
  1002. var placeholderHtml = '<div class="alert alert-info" style="margin-top: 10px;">' +
  1003. '<i class="fa fa-info-circle"></i> 编辑模式下需要重新选择优惠券<br>' +
  1004. '<small>优惠券ID: ' + couponId + '</small>' +
  1005. '</div>';
  1006. form.find('#selected-coupon-display').html(placeholderHtml).show();
  1007. console.log('优惠券占位信息已显示,用户需要重新选择');
  1008. },
  1009. // 打开商品选择器
  1010. openGoodsSelector: function(form) {
  1011. Fast.api.open('shop/goods/select?selectMode=single', '选择商品', {
  1012. callback: function(data) {
  1013. if (data.length > 0) {
  1014. var goods = data[0]; // 取第一个选中的商品
  1015. // 处理商品数据,确保格式完整
  1016. var processedGoods = {
  1017. id: goods.id,
  1018. name: goods.name || goods.title,
  1019. title: goods.title || goods.name,
  1020. image: goods.image ? (typeof Fast !== 'undefined' && Fast.api ? Fast.api.cdnurl(goods.image) : goods.image) : '',
  1021. price: goods.price || goods.sellprice || '',
  1022. goods_sn: goods.goods_sn || '',
  1023. spec_type: goods.spec_type !== undefined ? goods.spec_type : null,
  1024. category: goods.category || null,
  1025. type_name: Controller.api.getGoodsTypeName(goods),
  1026. type_text: Controller.api.getGoodsTypeText(goods)
  1027. };
  1028. // 使用奖品商品模板
  1029. var html = Template('prize-goods-template', processedGoods);
  1030. form.find('#selected-goods-display').html(html).show();
  1031. // 设置隐藏域的值以触发验证
  1032. form.find('input[name="goods_id"]').val(goods.id);
  1033. // 设置商品图片为奖品图片并触发预览更新
  1034. if (goods.image) {
  1035. form.find('input[name="prize_image"]').val(goods.image).trigger('change');
  1036. }
  1037. // 绑定删除事件
  1038. form.find('.remove-prize-goods').off('click').on('click', function() {
  1039. $(this).closest('.prize-goods-item').remove();
  1040. form.find('input[name="goods_id"]').val(''); // 清空隐藏域
  1041. });
  1042. }
  1043. }
  1044. });
  1045. },
  1046. // 打开优惠券选择器
  1047. openCouponSelector: function(form) {
  1048. Fast.api.open('shop/coupon/select', '选择优惠券', {
  1049. callback: function(data) {
  1050. if (data.length > 0) {
  1051. var coupon = data[0]; // 取第一个选中的优惠券
  1052. // 直接使用模板ID
  1053. var html = Template('selected-coupon-template', {
  1054. name: coupon.name,
  1055. type_text: coupon.type_text || '优惠券',
  1056. id: coupon.id
  1057. });
  1058. form.find('#selected-coupon-display').html(html).show();
  1059. // 设置隐藏域的值以触发验证
  1060. form.find('input[name="coupon_id"]').val(coupon.id);
  1061. }
  1062. }
  1063. });
  1064. },
  1065. // 保存奖品 - 重构版(使用数据管理器)
  1066. savePrize: function($form) {
  1067. console.log('开始保存奖品');
  1068. // 收集表单数据
  1069. var prizeData = Controller.api.collectPrizeFormData($form);
  1070. if (!prizeData) {
  1071. return false; // 验证失败
  1072. }
  1073. // 使用数据管理器添加奖品
  1074. var prizeId = Controller.api.prizesDataManager.addPrize(prizeData);
  1075. // 添加奖品到显示列表(追加模式,不会覆盖现有奖品)
  1076. Controller.api.appendPrizeToList(prizeData);
  1077. // 更新相关统计
  1078. Controller.api.updatePrizeCount();
  1079. Controller.api.updateTotalRate();
  1080. console.log('奖品保存成功:', prizeData, '奖品ID:', prizeId);
  1081. return true;
  1082. },
  1083. // 收集奖品表单数据
  1084. collectPrizeFormData: function($form) {
  1085. console.log('收集奖品表单数据');
  1086. var name = $form.find('input[name="prize_name"]').val().trim();
  1087. var type = parseInt($form.find('input[name="row[prize_type]"]:checked').val());
  1088. var image = $form.find('input[name="prize_image"]').val();
  1089. var quantity = parseInt($form.find('input[name="prize_quantity"]').val()) || 1;
  1090. var rate = parseFloat($form.find('input[name="prize_rate"]').val()) || 0;
  1091. // 基础验证 - FastAdmin的data-rule已经处理了大部分验证
  1092. // 这里只做简单的空值检查,具体验证错误会由Nice-validator显示
  1093. if (!name) {
  1094. Toastr.error('请输入奖品名称');
  1095. return null;
  1096. }
  1097. if (!type) {
  1098. Toastr.error('请选择奖品类型');
  1099. return null;
  1100. }
  1101. if (!image) {
  1102. Toastr.error('请选择奖品图片');
  1103. return null;
  1104. }
  1105. // 构建奖品数据
  1106. var prizeData = {
  1107. name: name,
  1108. type: type,
  1109. image: image,
  1110. quantity: Controller.api.isNoPrizeType(type) ? 0 : quantity,
  1111. rate: rate
  1112. };
  1113. // 根据奖品类型收集特定数据
  1114. switch(type) {
  1115. case 3: // 积分
  1116. prizeData.points = parseInt($form.find('input[name="reward_value_integral"]').val()) || 0;
  1117. break;
  1118. case 4: // 余额
  1119. prizeData.balance = parseFloat($form.find('input[name="reward_value_balance"]').val()) || 0;
  1120. break;
  1121. case 5: // 优惠券
  1122. prizeData.coupon_id = $form.find('input[name="coupon_id"]').val();
  1123. prizeData.couponId = prizeData.coupon_id; // 为模板提供一致的字段名
  1124. // 从优惠券显示区域获取名称
  1125. prizeData.coupon_name = $form.find('#selected-coupon-display').text().trim() ||
  1126. $form.find('.selected-coupon-item span:first').text() || '';
  1127. break;
  1128. case 6: // 红包
  1129. prizeData.redpack_amount = parseFloat($form.find('input[name="reward_value_redpack"]').val()) || 0;
  1130. break;
  1131. case 7: // 兑换码
  1132. prizeData.code_type = $form.find('input[name="code_type"]:checked').val() || 'auto';
  1133. if (prizeData.code_type === 'manual') {
  1134. prizeData.manual_codes = $form.find('textarea[name="manual_codes"]').val() || '';
  1135. }
  1136. break;
  1137. case 8: // 商城奖品
  1138. prizeData.goods_id = $form.find('input[name="goods_id"]').val();
  1139. prizeData.goodsId = prizeData.goods_id; // 为模板提供一致的字段名
  1140. // 从商品显示区域获取名称
  1141. prizeData.goods_name = $form.find('#selected-goods-display .goods-name-display').text() ||
  1142. $form.find('#selected-goods-display .prize-goods-item .goods-name-display').text() ||
  1143. $form.find('.prize-goods-item .goods-name-display').text() || '';
  1144. break;
  1145. }
  1146. console.log('收集到的奖品数据:', prizeData);
  1147. return prizeData;
  1148. },
  1149. // 添加奖品到列表(编辑模式用)
  1150. addPrizeToList: function(prize, index) {
  1151. // 获取奖品类型信息
  1152. var typeName = Controller.api.getPrizeTypeName(prize.type);
  1153. var labelClass = Controller.api.getPrizeLabelClass(prize.type);
  1154. var rewardText = Controller.api.getPrizeRewardText(prize);
  1155. // 使用传入的索引
  1156. var prizeIndex = typeof index !== 'undefined' ? index : $('.prize-list .prize-item').length;
  1157. // 处理图片URL
  1158. var imageRelative = prize.image; // 相对路径,用于表单提交
  1159. var imageDisplay = prize.image ? (typeof Fast !== 'undefined' && Fast.api ? Fast.api.cdnurl(prize.image) : prize.image) : ''; // CDN URL,用于显示
  1160. // 判断是否为系统奖品(未中奖类型)
  1161. var isSystemPrize = Controller.api.isNoPrizeType(prize.type);
  1162. // 生成唯一的奖品ID
  1163. var prizeId = prize.id || ('prize_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9));
  1164. // 使用外部模板 - 重构为仅展示用途
  1165. var prizeHtml = Template('prize-item-template', {
  1166. index: prizeIndex,
  1167. prizeId: prizeId,
  1168. prizeType: isSystemPrize ? 'no-prize' : prize.type, // 系统默认项使用特殊标识
  1169. name: prize.name, // 奖品名称
  1170. description: prize.description || '', // 奖品描述
  1171. type: prize.type, // 奖品类型
  1172. typeName: typeName, // 类型显示名称
  1173. labelClass: labelClass, // 标签样式类
  1174. rewardText: rewardText, // 奖励文本
  1175. imageUrl: imageDisplay, // CDN URL,用于显示
  1176. quantity: prize.quantity || 0, // 奖品数量
  1177. showQuantity: !Controller.api.isNoPrizeType(prize.type), // 是否显示数量
  1178. rate: parseFloat(prize.rate || 0).toFixed(2), // 确保概率值格式化为两位小数
  1179. isSystemPrize: isSystemPrize // 是否为系统奖品
  1180. });
  1181. $('.prize-list').append(prizeHtml);
  1182. console.log('已添加奖品到列表:', prize.name, '索引:', prizeIndex, '系统奖品:', isSystemPrize, '数据:', prize);
  1183. },
  1184. // 添加奖品到列表(新增模式用)- 重构版
  1185. appendPrizeToList: function(prize) {
  1186. // 获取奖品类型信息
  1187. var typeName = Controller.api.getPrizeTypeName(prize.type);
  1188. var labelClass = Controller.api.getPrizeLabelClass(prize.type);
  1189. var rewardText = Controller.api.getPrizeRewardText(prize);
  1190. // 获取当前奖品索引,确保与数据管理器中的索引一致
  1191. var prizeIndex = Controller.api.prizesDataManager.prizesData.length - 1;
  1192. // 处理图片URL
  1193. var imageDisplay = prize.image ? (typeof Fast !== 'undefined' && Fast.api ? Fast.api.cdnurl(prize.image) : prize.image) : '';
  1194. // 生成唯一的奖品ID
  1195. var prizeId = prize.id || ('prize_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9));
  1196. // 使用外部模板 - 重构版(仅用于展示)
  1197. var prizeHtml = Template('prize-item-template', {
  1198. index: prizeIndex,
  1199. prizeId: prizeId,
  1200. prizeType: prize.type, // 奖品类型
  1201. name: prize.name, // 奖品名称
  1202. description: '', // 奖品描述(新增模式通常为空)
  1203. type: prize.type, // 奖品类型
  1204. typeName: typeName, // 类型显示名称
  1205. labelClass: labelClass, // 标签样式类
  1206. rewardText: rewardText, // 奖励文本
  1207. imageUrl: imageDisplay, // CDN URL,用于显示
  1208. quantity: prize.quantity || 0, // 奖品数量
  1209. showQuantity: !Controller.api.isNoPrizeType(prize.type), // 是否显示数量
  1210. rate: parseFloat(prize.rate || 0).toFixed(2), // 确保概率值格式化为两位小数
  1211. isSystemPrize: false // 用户添加的奖品非系统奖品
  1212. });
  1213. $('.prize-list').append(prizeHtml);
  1214. // 重新初始化拖拽排序
  1215. Controller.api.initPrizeSort();
  1216. console.log('奖品已追加到列表,索引:', prizeIndex, '数据:', prize);
  1217. },
  1218. // 初始化默认奖品概率值
  1219. initDefaultPrizeRate: function() {
  1220. console.log('=== 开始初始化奖品概率值 ===');
  1221. // 检查整个表格结构
  1222. var prizeTable = $('.prize-list');
  1223. console.log('奖品表格查找结果:', prizeTable.length > 0);
  1224. if (prizeTable.length > 0) {
  1225. console.log('奖品表格HTML:', prizeTable.html().substring(0, 500) + '...');
  1226. }
  1227. // 检查所有概率输入框
  1228. var allRateInputs = $('.prize-rate');
  1229. console.log('找到的所有概率输入框数量:', allRateInputs.length);
  1230. // 使用更精确的选择器
  1231. var noPrizeInput = $('#no-prize-rate-input');
  1232. console.log('通过ID查找默认未中奖项输入框:', {
  1233. found: noPrizeInput.length > 0,
  1234. value: noPrizeInput.val(),
  1235. visible: noPrizeInput.is(':visible'),
  1236. display: noPrizeInput.css('display'),
  1237. visibility: noPrizeInput.css('visibility')
  1238. });
  1239. // 通过class查找
  1240. var noPrizeInputByClass = $('.prize-item[data-type="no-prize"] .prize-rate');
  1241. console.log('通过class查找默认未中奖项输入框:', {
  1242. found: noPrizeInputByClass.length > 0,
  1243. value: noPrizeInputByClass.val(),
  1244. visible: noPrizeInputByClass.is(':visible')
  1245. });
  1246. // 检查所有奖品行
  1247. var prizeRows = $('.prize-item');
  1248. console.log('找到的奖品行数量:', prizeRows.length);
  1249. prizeRows.each(function(index) {
  1250. var $row = $(this);
  1251. var dataType = $row.attr('data-type');
  1252. var rateInput = $row.find('.prize-rate');
  1253. console.log('奖品行 ' + index + ':', {
  1254. dataType: dataType,
  1255. hasRateInput: rateInput.length > 0,
  1256. rateInputValue: rateInput.val(),
  1257. rateInputVisible: rateInput.is(':visible'),
  1258. rowHTML: $row.html().substring(0, 200) + '...'
  1259. });
  1260. });
  1261. // 确保系统默认未中奖项有正确的概率值
  1262. var defaultPrizeRateInput = noPrizeInput.length > 0 ? noPrizeInput : noPrizeInputByClass;
  1263. if (defaultPrizeRateInput.length > 0) {
  1264. var currentValue = defaultPrizeRateInput.val();
  1265. console.log('当前默认概率值:', currentValue);
  1266. if (!currentValue || currentValue === '' || parseFloat(currentValue) === 0) {
  1267. defaultPrizeRateInput.val('50.00');
  1268. console.log('设置系统默认未中奖项概率为50.00%');
  1269. }
  1270. // 强制确保可见性
  1271. defaultPrizeRateInput.show().css({
  1272. 'display': 'inline-block',
  1273. 'visibility': 'visible',
  1274. 'opacity': '1'
  1275. });
  1276. } else {
  1277. console.log('⚠️ 警告: 未找到系统默认未中奖项的概率输入框!');
  1278. // 尝试创建缺失的输入框
  1279. var noPrizeRow = $('tr[data-type="no-prize"]');
  1280. if (noPrizeRow.length > 0) {
  1281. console.log('找到未中奖行,检查其结构...');
  1282. var rateTd = noPrizeRow.find('td').eq(5); // 概率列
  1283. console.log('概率列内容:', rateTd.html());
  1284. // 如果概率列只有%符号,重新构建
  1285. if (rateTd.text().trim() === '%') {
  1286. console.log('发现概率列只有%符号,重新构建input...');
  1287. rateTd.html(
  1288. '<div class="input-group" style="width: 100px; margin: 0 auto;">' +
  1289. '<input type="number" class="form-control input-sm prize-rate text-center" name="prizes[0][rate]" id="no-prize-rate-input" value="50.00" min="0" max="100" step="0.01">' +
  1290. '<span class="input-group-addon" style="padding: 4px 6px;">%</span>' +
  1291. '</div>'
  1292. );
  1293. console.log('重新构建完成');
  1294. }
  1295. }
  1296. }
  1297. // 确保所有奖品概率输入框都有默认值
  1298. $('.prize-rate').each(function() {
  1299. var $this = $(this);
  1300. var currentValue = $this.val();
  1301. if (!currentValue || currentValue === '') {
  1302. if ($this.attr('id') === 'no-prize-rate-input') {
  1303. $this.val('50.00');
  1304. } else {
  1305. $this.val('0.00');
  1306. }
  1307. }
  1308. });
  1309. // 初始化后更新总概率显示
  1310. Controller.api.updateTotalRate();
  1311. console.log('=== 奖品概率值初始化完成 ===');
  1312. },
  1313. // 绑定奖品相关事件(使用事件委托)
  1314. bindPrizeEvents: function() {
  1315. // 使用事件委托,避免重复绑定
  1316. $(document).off('input.prizeManagement change.prizeManagement click.prizeManagement');
  1317. // 概率输入框事件
  1318. $(document).on('input.prizeManagement', '.prize-rate', function() {
  1319. var $item = $(this).closest('.prize-item');
  1320. var index = parseInt($item.attr('data-index'));
  1321. var rate = parseFloat($(this).val()) || 0;
  1322. // 更新数据管理器中的数据
  1323. Controller.api.prizesDataManager.updatePrizeRate(index, rate);
  1324. // 实时更新总概率
  1325. Controller.api.updateTotalRate();
  1326. });
  1327. $(document).on('change.prizeManagement', '.prize-rate', function() {
  1328. // 失去焦点时格式化为两位小数
  1329. var value = parseFloat($(this).val()) || 0;
  1330. $(this).val(value.toFixed(2));
  1331. Controller.api.updateTotalRate();
  1332. });
  1333. // 奖品数量输入框事件
  1334. $(document).on('input.prizeManagement change.prizeManagement', '.prize-quantity', function() {
  1335. var $item = $(this).closest('.prize-item');
  1336. var index = parseInt($item.attr('data-index'));
  1337. var quantity = parseInt($(this).val()) || 0;
  1338. // 更新数据管理器中的数据
  1339. Controller.api.prizesDataManager.updatePrizeQuantity(index, quantity);
  1340. });
  1341. // 删除奖品按钮事件
  1342. $(document).on('click.prizeManagement', '.delete-prize', function() {
  1343. var $item = $(this).closest('.prize-item');
  1344. var index = parseInt($item.attr('data-index'));
  1345. // 检查是否是系统默认项
  1346. if ($item.attr('data-type') === 'no-prize') {
  1347. Toastr.error('系统默认的未中奖项不能删除');
  1348. return;
  1349. }
  1350. Controller.api.deletePrizeByIndex(index);
  1351. });
  1352. // 编辑奖品按钮事件
  1353. $(document).on('click.prizeManagement', '.edit-prize', function() {
  1354. var $item = $(this).closest('.prize-item');
  1355. var index = parseInt($item.attr('data-index'));
  1356. Controller.api.editPrizeByIndex(index);
  1357. });
  1358. },
  1359. // 通过索引删除奖品
  1360. deletePrizeByIndex: function(index) {
  1361. // 从数据管理器中删除
  1362. if (Controller.api.prizesDataManager.deletePrize(index)) {
  1363. // 重新构建列表
  1364. Controller.api.rebuildPrizeList();
  1365. // 更新统计
  1366. Controller.api.updatePrizeCount();
  1367. Controller.api.updateTotalRate();
  1368. console.log('奖品已删除,索引:', index);
  1369. }
  1370. },
  1371. // 通过索引编辑奖品
  1372. editPrizeByIndex: function(index) {
  1373. var prizeData = Controller.api.prizesDataManager.getPrize(index);
  1374. if (!prizeData) {
  1375. Toastr.error('奖品数据不存在');
  1376. return;
  1377. }
  1378. // 判断是否为系统默认未中奖项
  1379. var isSystemNoPrize = Controller.api.isNoPrizeType(prizeData.type);
  1380. var title = isSystemNoPrize ? '编辑未中奖项' : '编辑奖品';
  1381. // 使用通用表单,编辑模式
  1382. Controller.api.openPrizeForm({
  1383. mode: 'edit',
  1384. title: title,
  1385. prizeIndex: index,
  1386. isSystemNoPrize: isSystemNoPrize,
  1387. data: prizeData
  1388. });
  1389. },
  1390. // 重新构建奖品列表
  1391. rebuildPrizeList: function() {
  1392. // 清空现有列表
  1393. $('.prize-list').empty();
  1394. // 获取所有奖品数据
  1395. var allPrizes = Controller.api.prizesDataManager.getAllPrizes();
  1396. // 重新渲染每个奖品
  1397. for (var i = 0; i < allPrizes.length; i++) {
  1398. Controller.api.addPrizeToList(allPrizes[i], i);
  1399. }
  1400. // 重新初始化拖拽排序
  1401. Controller.api.initPrizeSort();
  1402. console.log('奖品列表已重新构建');
  1403. },
  1404. // 奖品数据管理 - 使用单一JSON隐藏域
  1405. prizesDataManager: {
  1406. // 奖品数据数组
  1407. prizesData: [],
  1408. // 初始化奖品数据管理器
  1409. init: function() {
  1410. // 创建奖品数据隐藏域
  1411. if ($('#prizes-data-json').length === 0) {
  1412. $('<input>').attr({
  1413. type: 'hidden',
  1414. id: 'prizes-data-json',
  1415. name: 'prizes_json',
  1416. value: '[]',
  1417. 'data-rule': 'required;prizesJson',
  1418. 'data-msg-required': '请添加至少一个奖品',
  1419. 'data-msg-prizesJson': '奖品数据格式不正确'
  1420. }).appendTo('form[role="form"]');
  1421. }
  1422. // 初始化数据
  1423. this.loadFromHiddenField();
  1424. },
  1425. // 从隐藏域加载数据
  1426. loadFromHiddenField: function() {
  1427. var jsonData = $('#prizes-data-json').val();
  1428. try {
  1429. this.prizesData = JSON.parse(jsonData || '[]');
  1430. } catch (e) {
  1431. this.prizesData = [];
  1432. console.warn('奖品数据格式错误,已重置为空数组');
  1433. }
  1434. },
  1435. // 保存数据到隐藏域
  1436. saveToHiddenField: function() {
  1437. var jsonData = JSON.stringify(this.prizesData);
  1438. $('#prizes-data-json').val(jsonData);
  1439. console.log('奖品数据已保存到隐藏域:', this.prizesData);
  1440. },
  1441. // 添加奖品
  1442. addPrize: function(prizeData) {
  1443. // 生成唯一ID
  1444. prizeData.id = 'prize_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);
  1445. this.prizesData.push(prizeData);
  1446. this.saveToHiddenField();
  1447. return prizeData.id;
  1448. },
  1449. // 更新奖品
  1450. updatePrize: function(index, prizeData) {
  1451. if (index >= 0 && index < this.prizesData.length) {
  1452. // 保持原有ID
  1453. prizeData.id = this.prizesData[index].id;
  1454. this.prizesData[index] = prizeData;
  1455. this.saveToHiddenField();
  1456. return true;
  1457. }
  1458. return false;
  1459. },
  1460. // 删除奖品
  1461. deletePrize: function(index) {
  1462. if (index >= 0 && index < this.prizesData.length) {
  1463. this.prizesData.splice(index, 1);
  1464. this.saveToHiddenField();
  1465. return true;
  1466. }
  1467. return false;
  1468. },
  1469. // 获取奖品数据
  1470. getPrize: function(index) {
  1471. if (index >= 0 && index < this.prizesData.length) {
  1472. return this.prizesData[index];
  1473. }
  1474. return null;
  1475. },
  1476. // 获取所有奖品数据
  1477. getAllPrizes: function() {
  1478. return this.prizesData;
  1479. },
  1480. // 更新奖品数量
  1481. updatePrizeQuantity: function(index, quantity) {
  1482. if (index >= 0 && index < this.prizesData.length) {
  1483. this.prizesData[index].quantity = parseInt(quantity) || 0;
  1484. this.saveToHiddenField();
  1485. return true;
  1486. }
  1487. return false;
  1488. },
  1489. // 更新奖品概率
  1490. updatePrizeRate: function(index, rate) {
  1491. if (index >= 0 && index < this.prizesData.length) {
  1492. this.prizesData[index].rate = parseFloat(rate) || 0;
  1493. this.saveToHiddenField();
  1494. return true;
  1495. }
  1496. return false;
  1497. },
  1498. // 重新排序奖品
  1499. reorderPrizes: function(newOrder) {
  1500. var reorderedData = [];
  1501. for (var i = 0; i < newOrder.length; i++) {
  1502. var oldIndex = newOrder[i];
  1503. if (oldIndex >= 0 && oldIndex < this.prizesData.length) {
  1504. reorderedData.push(this.prizesData[oldIndex]);
  1505. }
  1506. }
  1507. this.prizesData = reorderedData;
  1508. this.saveToHiddenField();
  1509. }
  1510. },
  1511. // 实时更新奖品隐藏域数据(重构版 - 使用奖品数据管理器)
  1512. updatePrizeHiddenFields: function() {
  1513. // 使用奖品数据管理器保存数据
  1514. Controller.api.prizesDataManager.saveToHiddenField();
  1515. },
  1516. // 初始化奖品拖拽排序 - 重构版
  1517. initPrizeSort: function() {
  1518. require(['../libs/Sortable/Sortable'], function(Sortable) {
  1519. var prizeListEl = document.querySelector('.prize-list');
  1520. if (prizeListEl) {
  1521. // 如果已经初始化过,先销毁
  1522. if (prizeListEl.sortableInstance) {
  1523. prizeListEl.sortableInstance.destroy();
  1524. }
  1525. // 创建新的 Sortable 实例
  1526. prizeListEl.sortableInstance = new Sortable(prizeListEl, {
  1527. handle: '.prize-sort-handle',
  1528. animation: 150,
  1529. onUpdate: function(evt) {
  1530. // 拖拽排序后重新排列数据管理器中的数据
  1531. Controller.api.updatePrizeOrderFromDOM();
  1532. }
  1533. });
  1534. }
  1535. });
  1536. },
  1537. // 从DOM更新奖品顺序到数据管理器
  1538. updatePrizeOrderFromDOM: function() {
  1539. var newOrder = [];
  1540. $('.prize-list .prize-item').each(function(index) {
  1541. var oldIndex = parseInt($(this).attr('data-index'));
  1542. if (!isNaN(oldIndex)) {
  1543. newOrder.push(oldIndex);
  1544. }
  1545. });
  1546. // 重新排序数据管理器中的数据
  1547. Controller.api.prizesDataManager.reorderPrizes(newOrder);
  1548. // 重新构建列表以更新索引
  1549. Controller.api.rebuildPrizeList();
  1550. console.log('奖品拖拽排序完成,新顺序:', newOrder);
  1551. },
  1552. // 获取奖品类型名称
  1553. getPrizeTypeName: function(type) {
  1554. var prizeTypeMap = Controller.api.parseConfigJson('prizeTypeList', {});
  1555. return prizeTypeMap[type] || '未知';
  1556. },
  1557. // 获取奖品标签样式类
  1558. getPrizeLabelClass: function(type) {
  1559. var classMap = {
  1560. 1: 'default', // 未中奖 - 灰色
  1561. 2: 'primary', // 实物奖品 - 蓝色
  1562. 3: 'warning', // 积分 - 橙色
  1563. 4: 'info', // 余额 - 浅蓝色
  1564. 5: 'success', // 优惠券 - 绿色
  1565. 6: 'danger', // 红包 - 红色
  1566. 7: 'default', // 兑换码 - 灰色
  1567. 8: 'primary' // 商城奖品 - 蓝色
  1568. };
  1569. return classMap[type] || 'default';
  1570. },
  1571. // 获取奖品奖励文本
  1572. getPrizeRewardText: function(prize) {
  1573. if (!prize.type || prize.type == 1) {
  1574. return ''; // 未中奖不显示奖励文本
  1575. }
  1576. switch(parseInt(prize.type)) {
  1577. case 2: // 实物奖品
  1578. return '';
  1579. case 3: // 积分
  1580. return prize.points ? (prize.points + ' 积分') : '';
  1581. case 4: // 余额
  1582. return prize.balance ? (prize.balance + ' 余额') : '';
  1583. case 5: // 优惠券
  1584. return prize.coupon_name ? prize.coupon_name : '';
  1585. case 6: // 红包
  1586. return prize.redpack_amount ? (prize.redpack_amount + ' 元红包') : '';
  1587. case 7: // 兑换码
  1588. return prize.code_type == 'manual' ? '手动兑换码' : '自动兑换码';
  1589. case 8: // 商城奖品
  1590. return prize.goods_name ? prize.goods_name : '';
  1591. default:
  1592. return '';
  1593. }
  1594. },
  1595. // 从表单获取奖品奖励文本
  1596. getPrizeRewardTextFromForm: function(form, type) {
  1597. if (!type || type == 1) {
  1598. return ''; // 未中奖不显示奖励文本
  1599. }
  1600. switch(parseInt(type)) {
  1601. case 2: // 实物奖品
  1602. return '';
  1603. case 3: // 积分
  1604. var points = form.find('input[name="reward_value_integral"]').val();
  1605. return points ? (points + ' 积分') : '';
  1606. case 4: // 余额
  1607. var balance = form.find('input[name="reward_value_balance"]').val();
  1608. return balance ? (balance + ' 余额') : '';
  1609. case 5: // 优惠券
  1610. // 从优惠券显示区域获取名称
  1611. var couponName = form.find('#selected-coupon-display').text().trim() ||
  1612. form.find('.selected-coupon-item span:first').text();
  1613. return couponName || '';
  1614. case 6: // 红包
  1615. var amount = form.find('input[name="reward_value_redpack"]').val();
  1616. return amount ? (amount + ' 元红包') : '';
  1617. case 7: // 兑换码
  1618. var codeType = form.find('input[name="code_type"]:checked').val();
  1619. return codeType == 'manual' ? '手动兑换码' : '自动兑换码';
  1620. case 8: // 商城奖品
  1621. // 从商品显示区域获取名称
  1622. return form.find('#selected-goods-display .goods-name-display').text() ||
  1623. form.find('#selected-goods-display .prize-goods-item .goods-name-display').text() ||
  1624. form.find('.prize-goods-item .goods-name-display').text() || '';
  1625. default:
  1626. return '';
  1627. }
  1628. },
  1629. // 根据奖品类型获取奖励类型
  1630. getRewardTypeByPrizeType: function(prizeType) {
  1631. switch(parseInt(prizeType)) {
  1632. case 3: return 'integral'; // 积分
  1633. case 4: return 'balance'; // 余额
  1634. case 5: return 'coupon'; // 优惠券
  1635. case 6: return 'redbag'; // 红包
  1636. case 7: return 'code'; // 兑换码
  1637. case 8: return 'goods'; // 商城奖品
  1638. default: return 'none';
  1639. }
  1640. },
  1641. // 判断是否为未中奖类型
  1642. isNoPrizeType: function(type, element) {
  1643. var typeValue = parseInt(type);
  1644. var isNoPrizeByType = typeValue === 1;
  1645. var isNoPrizeByAttr = element && $(element).attr('data-type') === 'no-prize';
  1646. return isNoPrizeByType || isNoPrizeByAttr;
  1647. },
  1648. // 更新奖品数量统计
  1649. updatePrizeCount: function() {
  1650. var totalCount = $('.prize-item').length; // 总奖品数(包含未中奖项)
  1651. var userCount = totalCount - 1; // 用户添加的奖品数(减去未中奖项)
  1652. console.log('updatePrizeCount - 总奖品数:', totalCount, '用户添加数:', userCount);
  1653. // 显示总奖品数(包含系统默认的未中奖项)
  1654. $('#prize-count').text(totalCount);
  1655. // 更新添加奖品按钮状态(基于用户可添加的奖品数)
  1656. Controller.api.updateAddPrizeButtonState(userCount);
  1657. },
  1658. // 更新添加奖品按钮状态
  1659. updateAddPrizeButtonState: function(userCount) {
  1660. var maxUserCount = 7; // 用户最大可添加奖品数量(总共8个,包括系统默认的未中奖项)
  1661. var addButton = $('#add-prize-btn');
  1662. var remainingCount = maxUserCount - userCount;
  1663. console.log('updateAddPrizeButtonState - 用户奖品数:', userCount, '最大允许:', maxUserCount, '剩余:', remainingCount);
  1664. if (userCount >= maxUserCount) {
  1665. // 达到最大数量,禁用按钮
  1666. addButton.prop('disabled', true).addClass('disabled');
  1667. addButton.find('.btn-text').text('已达最大数量');
  1668. $('#prize-count-tip').text('已添加最大数量的奖品').removeClass('text-muted').addClass('text-danger');
  1669. console.log('按钮已禁用 - 达到最大数量');
  1670. } else {
  1671. // 未达到最大数量,启用按钮
  1672. addButton.prop('disabled', false).removeClass('disabled');
  1673. addButton.find('.btn-text').text('添加奖品');
  1674. if (remainingCount <= 2) {
  1675. $('#prize-count-tip').text('还可添加 ' + remainingCount + ' 个奖品').removeClass('text-muted text-danger').addClass('text-warning');
  1676. } else {
  1677. // 当用户还没添加奖品时,显示还需添加多少个才能满足最低要求
  1678. var minRequired = Math.max(0, maxUserCount - userCount); // 需要添加的奖品数量
  1679. if (minRequired > 0) {
  1680. $('#prize-count-tip').text('还需添加 ' + minRequired + ' 个奖品').removeClass('text-warning text-danger').addClass('text-muted');
  1681. } else {
  1682. $('#prize-count-tip').text('还可添加 ' + remainingCount + ' 个奖品').removeClass('text-warning text-danger').addClass('text-muted');
  1683. }
  1684. }
  1685. console.log('按钮已启用 - 剩余数量:', remainingCount);
  1686. }
  1687. },
  1688. // 计算总概率
  1689. calculateTotalRate: function() {
  1690. var total = 0;
  1691. $('.prize-rate').each(function() {
  1692. total += parseFloat($(this).val()) || 0;
  1693. });
  1694. // 保留两位小数
  1695. return Math.round(total * 100) / 100;
  1696. },
  1697. // 更新总概率显示
  1698. updateTotalRate: function() {
  1699. var total = Controller.api.calculateTotalRate();
  1700. // 格式化显示为两位小数
  1701. $('#total-rate').text(total.toFixed(2) + '%');
  1702. // 根据概率设置颜色
  1703. if (total > 100) {
  1704. $('#total-rate').removeClass('text-success text-warning').addClass('text-danger');
  1705. } else if (total === 100) {
  1706. $('#total-rate').removeClass('text-danger text-warning').addClass('text-success');
  1707. } else {
  1708. $('#total-rate').removeClass('text-danger text-success').addClass('text-warning');
  1709. }
  1710. },
  1711. // 编辑奖品(统一处理所有类型) - 已弃用,请使用 editPrizeByIndex
  1712. editPrize: function(prizeItem) {
  1713. // 获取奖品索引
  1714. var prizeIndex = parseInt(prizeItem.attr('data-index'));
  1715. if (isNaN(prizeIndex)) {
  1716. Toastr.error('无法获取奖品索引');
  1717. return;
  1718. }
  1719. // 调用新的编辑方法
  1720. Controller.api.editPrizeByIndex(prizeIndex);
  1721. },
  1722. // 更新奖品信息(重构版 - 使用数据管理器)
  1723. updatePrize: function($form, config) {
  1724. console.log('updatePrize 调用参数:', {form: $form, config: config});
  1725. // 验证config参数
  1726. if (!config || typeof config !== 'object') {
  1727. console.error('updatePrize: config参数无效:', config);
  1728. Toastr.error('配置参数错误');
  1729. return false;
  1730. }
  1731. // 验证prizeIndex
  1732. if (typeof config.prizeIndex === 'undefined' || config.prizeIndex === null) {
  1733. console.error('updatePrize: prizeIndex参数缺失:', config);
  1734. Toastr.error('奖品索引参数缺失');
  1735. return false;
  1736. }
  1737. // 收集表单数据
  1738. var prizeData = Controller.api.collectPrizeFormData($form);
  1739. if (!prizeData) {
  1740. return false; // 验证失败
  1741. }
  1742. var prizeIndex = parseInt(config.prizeIndex);
  1743. if (isNaN(prizeIndex) || prizeIndex < 0) {
  1744. console.error('updatePrize: prizeIndex无效:', config.prizeIndex);
  1745. Toastr.error('奖品索引无效');
  1746. return false;
  1747. }
  1748. // 使用数据管理器更新奖品
  1749. if (Controller.api.prizesDataManager.updatePrize(prizeIndex, prizeData)) {
  1750. // 重新构建列表以反映更新
  1751. Controller.api.rebuildPrizeList();
  1752. // 更新统计
  1753. Controller.api.updatePrizeCount();
  1754. Controller.api.updateTotalRate();
  1755. console.log('奖品更新成功:', prizeData, '索引:', prizeIndex);
  1756. return true;
  1757. } else {
  1758. console.error('updatePrize: 数据管理器更新失败');
  1759. Toastr.error('更新奖品失败');
  1760. return false;
  1761. }
  1762. },
  1763. // 在表单提交前收集条件数据
  1764. collectConditionData: function() {
  1765. var conditionData = {};
  1766. var taskTypes = [];
  1767. // 获取选中的任务类型
  1768. $('input[name="row[task_type][]"]:checked').each(function() {
  1769. taskTypes.push($(this).val());
  1770. });
  1771. // 为每个任务类型收集条件数据
  1772. taskTypes.forEach(function(taskType) {
  1773. var type = parseInt(taskType);
  1774. var condition = {};
  1775. switch(type) {
  1776. case 1: // 购买指定商品
  1777. condition.type = type;
  1778. condition.goods_rule = $('input[name="condition[' + type + '][goods_rule]"]:checked').val() || 1;
  1779. condition.goods_ids = $('#task-goods-ids-' + type).val() || '[]';
  1780. break;
  1781. case 2: // 单笔订单消费满额
  1782. case 3: // 单次充值满额
  1783. case 4: // 活动期间累计消费满额
  1784. condition.type = type;
  1785. condition.condition_value = $('input[name="condition[' + type + '][condition_value]"]').val() || 0;
  1786. break;
  1787. }
  1788. conditionData[type] = condition;
  1789. });
  1790. return {
  1791. conditionData: conditionData,
  1792. taskTypes: taskTypes
  1793. };
  1794. },
  1795. // 选择商品 - 通用方法,支持单选和多选
  1796. selectGoods: function(options) {
  1797. var defaults = {
  1798. mode: 'single', // 'single' 单选, 'multiple' 多选
  1799. title: '选择商品',
  1800. container: '#selected-goods',
  1801. template: 'goods-list-template', // 统一使用goods-list-template
  1802. maxSelect: 0, // 最大选择数量,0表示无限制
  1803. callback: null
  1804. };
  1805. var config = $.extend({}, defaults, options);
  1806. // 构建URL参数
  1807. var urlParams = [];
  1808. // 添加选择模式参数
  1809. if (config.mode === 'single') {
  1810. urlParams.push('selectMode=single');
  1811. } else {
  1812. urlParams.push('selectMode=multiple');
  1813. // 如果有最大选择限制,添加参数
  1814. if (config.maxSelect > 0) {
  1815. urlParams.push('maxSelect=' + config.maxSelect);
  1816. }
  1817. }
  1818. // 构建完整URL
  1819. var url = 'shop/goods/select';
  1820. if (urlParams.length > 0) {
  1821. url += '?' + urlParams.join('&');
  1822. }
  1823. Fast.api.open(url, config.title, {
  1824. callback: function(data) {
  1825. if (data && data.length > 0) {
  1826. // 单选模式只取第一个
  1827. if (config.mode === 'single' && data.length > 1) {
  1828. data = [data[0]];
  1829. }
  1830. Controller.api.updateSelectedGoods(data, config);
  1831. // 执行回调
  1832. if (typeof config.callback === 'function') {
  1833. config.callback(data);
  1834. }
  1835. } else {
  1836. Toastr.error('请选择商品');
  1837. }
  1838. }
  1839. });
  1840. },
  1841. // 选择任务商品(多选模式)
  1842. selectTaskGoods: function(container) {
  1843. Controller.api.selectGoods({
  1844. mode: 'multiple',
  1845. title: '选择参与商品',
  1846. container: container || '#task-goods-container',
  1847. template: 'task-goods-template',
  1848. callback: function(data) {
  1849. // 如果是购买指定商品任务,更新隐藏字段
  1850. if (container === '#selected-goods' && data && data.length > 0) {
  1851. var goodsIds = data.map(function(item) {
  1852. return item.id;
  1853. });
  1854. // 更新隐藏字段,存储JSON格式的商品IDs
  1855. $('#task-goods-ids-1').val(JSON.stringify(goodsIds));
  1856. console.log('购买指定商品任务 - 商品IDs已更新:', goodsIds);
  1857. // 绑定删除事件
  1858. Controller.api.bindTaskGoodsEvents();
  1859. }
  1860. }
  1861. });
  1862. },
  1863. // 选择奖品商品(单选模式)
  1864. selectPrizeGoods: function(container) {
  1865. Controller.api.selectGoods({
  1866. mode: 'single',
  1867. title: '选择奖品商品',
  1868. container: container || '#prize-goods-container',
  1869. template: 'goods-list-template' // 使用统一的商品选择模板
  1870. });
  1871. },
  1872. // 更新已选商品
  1873. updateSelectedGoods: function(goods, config) {
  1874. config = config || {};
  1875. var container = config.container || '#selected-goods';
  1876. var template = config.template || 'goods-list-template';
  1877. // 预处理商品数据
  1878. var processedGoods = Controller.api.processGoodsData(goods);
  1879. var html = '';
  1880. if (template === 'goods-list-template') {
  1881. // 商品列表(支持单选和多选)
  1882. html = Template(template, {
  1883. goods: processedGoods
  1884. });
  1885. $(container).html(html);
  1886. // 绑定删除事件
  1887. $('.remove-goods-item').off('click').on('click', function() {
  1888. $(this).closest('.goods-list-item').remove();
  1889. });
  1890. } else if (template === 'prize-goods-template') {
  1891. // 奖品商品(单选)
  1892. if (processedGoods.length > 0) {
  1893. html = Template(template, processedGoods[0]);
  1894. $(container).html(html);
  1895. // 绑定删除事件
  1896. $('.remove-prize-goods').off('click').on('click', function() {
  1897. $(this).closest('.prize-goods-item').remove();
  1898. });
  1899. }
  1900. } else {
  1901. // 其他模板(向后兼容)
  1902. html = Template(template, {
  1903. goods: processedGoods
  1904. });
  1905. $(container).html(html);
  1906. }
  1907. },
  1908. // 处理商品数据
  1909. processGoodsData: function(goods) {
  1910. var processedGoods = [];
  1911. if (goods && goods.length) {
  1912. for (var i = 0; i < goods.length; i++) {
  1913. var item = goods[i];
  1914. var processedItem = {
  1915. id: item.id,
  1916. name: item.name || item.title,
  1917. title: item.title || item.name,
  1918. image: item.image ? (typeof Fast !== 'undefined' && Fast.api ? Fast.api.cdnurl(item.image) : item.image) : '',
  1919. price: item.price || item.sellprice || '',
  1920. goods_sn: item.goods_sn || '',
  1921. spec_type: item.spec_type !== undefined ? item.spec_type : null,
  1922. category: item.category || null,
  1923. type_name: Controller.api.getGoodsTypeName(item),
  1924. type_text: Controller.api.getGoodsTypeText(item)
  1925. };
  1926. processedGoods.push(processedItem);
  1927. }
  1928. }
  1929. return processedGoods;
  1930. },
  1931. // 获取商品类型文本(用于显示标签)
  1932. getGoodsTypeText: function(goods) {
  1933. // 根据商品类型返回相应的标签文本
  1934. if (goods.type == 2) {
  1935. return '密'; // 虚拟商品显示"密"
  1936. }
  1937. if (goods.isrecom == 1) {
  1938. return '荐'; // 推荐商品显示"荐"
  1939. }
  1940. if (goods.ishot == 1) {
  1941. return '热'; // 热门商品显示"热"
  1942. }
  1943. if (goods.isnew == 1) {
  1944. return '新'; // 新品显示"新"
  1945. }
  1946. return ''; // 默认无标签
  1947. },
  1948. // 获取商品类型名称
  1949. getGoodsTypeName: function(goods) {
  1950. // 根据商品类型返回类型名称
  1951. if (goods.type == 1) {
  1952. return '普通商品';
  1953. } else if (goods.type == 2) {
  1954. return '虚拟商品';
  1955. } else if (goods.type == 3) {
  1956. return '服务商品';
  1957. }
  1958. return '普通商品'; // 默认类型
  1959. },
  1960. // 绑定任务商品删除事件
  1961. bindTaskGoodsEvents: function() {
  1962. $('.remove-task-goods').off('click').on('click', function() {
  1963. var goodsId = $(this).data('id');
  1964. var $item = $(this).closest('.goods-list-item');
  1965. // 从DOM中移除商品项
  1966. $item.remove();
  1967. // 更新隐藏字段中的商品IDs
  1968. Controller.api.updateTaskGoodsIds();
  1969. });
  1970. },
  1971. // 更新任务商品IDs隐藏字段
  1972. updateTaskGoodsIds: function() {
  1973. var goodsIds = [];
  1974. $('#selected-goods .goods-list-item').each(function() {
  1975. var goodsId = $(this).data('id');
  1976. if (goodsId) {
  1977. goodsIds.push(goodsId);
  1978. }
  1979. });
  1980. // 更新隐藏字段
  1981. $('#task-goods-ids-1').val(JSON.stringify(goodsIds));
  1982. console.log('任务商品IDs已更新:', goodsIds);
  1983. }
  1984. }
  1985. };
  1986. // 全局函数,供HTML调用
  1987. window.addPrize = function() {
  1988. Controller.api.addPrize();
  1989. };
  1990. // 默认商品选择(针对购买指定商品任务,使用多选模式)
  1991. window.selectGoods = function() {
  1992. Controller.api.selectGoods({
  1993. mode: 'multiple',
  1994. title: '选择参与商品',
  1995. container: '#selected-goods',
  1996. template: 'goods-list-template'
  1997. });
  1998. };
  1999. // 任务商品选择(多选)
  2000. window.selectTaskGoods = function(container) {
  2001. Controller.api.selectTaskGoods(container);
  2002. };
  2003. // 奖品商品选择(单选)
  2004. window.selectPrizeGoods = function(container) {
  2005. Controller.api.selectPrizeGoods(container);
  2006. };
  2007. // 暴露奖品数据管理器到全局
  2008. window.Controller = Controller;
  2009. return Controller;
  2010. });