skuTable.js 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743
  1. /*
  2. * Name: skuTable
  3. * Author: cshaptx4869
  4. * Project: https://github.com/cshaptx4869/skuTable
  5. */
  6. layui.define(['jquery', 'form', 'upload', 'layer', 'sortable'], function (exports) {
  7. "use strict";
  8. var $ = layui.jquery,
  9. form = layui.form,
  10. upload = layui.upload,
  11. layer = layui.layer,
  12. sortable = layui.sortable,
  13. MOD_NAME = 'skuTable';
  14. //工具类
  15. class Util {
  16. static config = {
  17. shade: [0.02, '#000'],
  18. time: 2000
  19. };
  20. static msg = {
  21. // 成功消息
  22. success: function (msg, callback = null) {
  23. return layer.msg(msg, {
  24. icon: 1,
  25. shade: Util.config.shade,
  26. scrollbar: false,
  27. time: Util.config.time,
  28. shadeClose: true
  29. }, callback);
  30. },
  31. // 失败消息
  32. error: function (msg, callback = null) {
  33. return layer.msg(msg, {
  34. icon: 2,
  35. shade: Util.config.shade,
  36. scrollbar: false,
  37. time: Util.config.time,
  38. shadeClose: true
  39. }, callback);
  40. },
  41. // 警告消息框
  42. alert: function (msg, callback = null) {
  43. return layer.alert(msg, {end: callback, scrollbar: false});
  44. },
  45. // 对话框
  46. confirm: function (msg, ok, no) {
  47. var index = layer.confirm(msg, {title: '操作确认', btn: ['确认', '取消']}, function () {
  48. typeof ok === 'function' && ok.call(this);
  49. }, function () {
  50. typeof no === 'function' && no.call(this);
  51. Util.msg.close(index);
  52. });
  53. return index;
  54. },
  55. // 消息提示
  56. tips: function (msg, callback = null) {
  57. return layer.msg(msg, {
  58. time: Util.config.time,
  59. shade: Util.config.shade,
  60. end: callback,
  61. shadeClose: true
  62. });
  63. },
  64. // 加载中提示
  65. loading: function (msg, callback = null) {
  66. return msg ? layer.msg(msg, {
  67. icon: 16,
  68. scrollbar: false,
  69. shade: Util.config.shade,
  70. time: 0,
  71. end: callback
  72. }) : layer.load(2, {time: 0, scrollbar: false, shade: Util.config.shade, end: callback});
  73. },
  74. // 输入框
  75. prompt: function (option, callback = null) {
  76. return layer.prompt(option, callback);
  77. },
  78. // 关闭消息框
  79. close: function (index) {
  80. return layer.close(index);
  81. }
  82. };
  83. static request = {
  84. post: function (option, ok, no, ex) {
  85. return Util.request.ajax('post', option, ok, no, ex);
  86. },
  87. get: function (option, ok, no, ex) {
  88. return Util.request.ajax('get', option, ok, no, ex);
  89. },
  90. ajax: function (type, option, ok, no, ex) {
  91. type = type || 'get';
  92. option.url = option.url || '';
  93. option.data = option.data || {};
  94. option.statusName = option.statusName || 'code';
  95. option.statusCode = option.statusCode || 200;
  96. ok = ok || function (res) {
  97. };
  98. no = no || function (res) {
  99. var msg = res.msg == undefined ? '返回数据格式有误' : res.msg;
  100. Util.msg.error(msg);
  101. return false;
  102. };
  103. ex = ex || function (res) {
  104. };
  105. if (option.url == '') {
  106. Util.msg.error('请求地址不能为空');
  107. return false;
  108. }
  109. var index = Util.msg.loading('加载中');
  110. $.ajax({
  111. url: option.url,
  112. type: type,
  113. contentType: "application/x-www-form-urlencoded; charset=UTF-8",
  114. dataType: "json",
  115. data: option.data,
  116. timeout: 60000,
  117. success: function (res) {
  118. Util.msg.close(index);
  119. if (res[option.statusName] == option.statusCode) {
  120. return ok(res);
  121. } else {
  122. return no(res);
  123. }
  124. },
  125. error: function (xhr, textstatus, thrown) {
  126. Util.msg.error('Status:' + xhr.status + ',' + xhr.statusText + ',请稍后再试!', function () {
  127. ex(xhr);
  128. });
  129. return false;
  130. }
  131. });
  132. }
  133. };
  134. static tool = {
  135. uuid: function uuid(randomLength = 8) {
  136. return Number(Math.random().toString().substr(2, randomLength) + Date.now()).toString(36)
  137. }
  138. }
  139. }
  140. class SkuTable {
  141. options = {
  142. isAttributeValue: 0, //规格类型 0统一规格 1多规格
  143. isAttributeElemId: 'fairy-is-attribute', //规格类型容器id
  144. specTableElemId: 'fairy-spec-table', //规格表容器id
  145. skuTableElemId: 'fairy-sku-table', //SKU表容器id
  146. rowspan: false, //是否开启SKU行合并,
  147. sortable: false, //规格拖拽排序
  148. skuIcon: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyZpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNi1jMDY3IDc5LjE1Nzc0NywgMjAxNS8wMy8zMC0yMzo0MDo0MiAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTUgKFdpbmRvd3MpIiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOjczN0RFNzU1MTk1RTExRTlBMEQ5OEEwMEM5NDNFOEE4IiB4bXBNTTpEb2N1bWVudElEPSJ4bXAuZGlkOjczN0RFNzU2MTk1RTExRTlBMEQ5OEEwMEM5NDNFOEE4Ij4gPHhtcE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6NzM3REU3NTMxOTVFMTFFOUEwRDk4QTAwQzk0M0U4QTgiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6NzM3REU3NTQxOTVFMTFFOUEwRDk4QTAwQzk0M0U4QTgiLz4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz5NHmJUAAAA+0lEQVR42pySPwsBYRzH7zk3KIP34CVIKSOrELLJdpuymyzew90kIwMZvACDsCldWZTFn5WQpPN5rlPXlXJ39en7/J57fn+fR9i2rYT5NNM0B2gC3n/6qHBQDMOwZNYg4LOQ3vcQld40/w6lC13Xbd/eHElC3G1JqL4DFWSNprz7BMpAFJ6YkW+jThaosuxAD/rY6R9lCmeq8IAmtKBA1A1OW9YjtIS9QvPYRZkcXo43EzqjF/mDQ5an7ALShTFk4eQOsgFTWeoNKl4nt68J0oYc1LHLbmtDp1IyLgPe4QCuMkIsyAWSuYbs5HD29DML8OTkHR9F2Ef+EWAAdwmkvBAtw94AAAAASUVORK5CYII=',
  149. uploadUrl: '',
  150. requestSuccessCode: 1, //请求成功返回状态码值
  151. specDataDelete: false, //开启规格删除
  152. productId: '', //商品id 配合specDataUrl和skuDataUrl使用
  153. specData: [], //规格数据
  154. specDataUrl: '', //优先级大于specData
  155. skuData: {}, //SKU数据
  156. skuDataUrl: '', //优先级大于skuDataUrl
  157. skuNameType: 0,
  158. skuNameDelimiter: '-',
  159. //统一规格配置项
  160. singleSkuTableConfig: {
  161. thead: [
  162. {title: '销售价(元)', icon: 'layui-icon-cols'},
  163. {title: '市场价(元)', icon: 'layui-icon-cols'},
  164. {title: '成本价(元)', icon: 'layui-icon-cols'},
  165. {title: '库存', icon: 'layui-icon-cols'},
  166. {title: '状态', icon: ''},
  167. ],
  168. tbody: [
  169. {type: 'input', field: 'price', value: '', verify: 'required|number', reqtext: '销售价不能为空'},
  170. {type: 'input', field: 'market_price', value: '0', verify: 'required|number', reqtext: '市场价不能为空'},
  171. {type: 'input', field: 'cost_price', value: '0', verify: 'required|number', reqtext: '成本价不能为空'},
  172. {type: 'input', field: 'stock', value: '0', verify: 'required|number', reqtext: '库存不能为空'},
  173. {type: 'select', field: 'status', option: [{key: '启用', value: '1'}, {key: '禁用', value: '0'}], verify: 'required', reqtext: '状态不能为空'},
  174. ]
  175. },
  176. //多规格配置项
  177. multipleSkuTableConfig: {
  178. thead: [
  179. {title: '图片', icon: ''},
  180. {title: '销售价(元)', icon: 'layui-icon-cols'},
  181. {title: '市场价(元)', icon: 'layui-icon-cols'},
  182. {title: '成本价(元)', icon: 'layui-icon-cols'},
  183. {title: '库存', icon: 'layui-icon-cols'},
  184. {title: '状态', icon: ''},
  185. ],
  186. tbody: [
  187. {type: 'image', field: 'picture', value: '', verify: '', reqtext: ''},
  188. {type: 'input', field: 'price', value: '', verify: 'required|number', reqtext: '销售价不能为空'},
  189. {type: 'input', field: 'market_price', value: '0', verify: 'required|number', reqtext: '市场价不能为空'},
  190. {type: 'input', field: 'cost_price', value: '0', verify: 'required|number', reqtext: '成本价不能为空'},
  191. {type: 'input', field: 'stock', value: '0', verify: 'required|number', reqtext: '库存不能为空'},
  192. {
  193. type: 'select',
  194. field: 'status',
  195. option: [{key: '启用', value: '1'}, {key: '禁用', value: '0'}],
  196. verify: '',
  197. reqtext: ''
  198. },
  199. ]
  200. }
  201. };
  202. constructor(options) {
  203. this.options = $.extend(this.options, options);
  204. if (this.options.skuDataUrl && this.options.productId) {
  205. Util.request.get({
  206. url: this.options.skuDataUrl,
  207. data: {
  208. product_id: this.options.productId
  209. },
  210. statusCode: this.options.requestSuccessCode
  211. }, (res) => {
  212. this.options.skuData = res.data;
  213. this.css();
  214. this.render();
  215. this.listen();
  216. });
  217. } else {
  218. this.css();
  219. this.render();
  220. this.listen();
  221. }
  222. }
  223. css() {
  224. $('head').append(`<style>
  225. ${this.options.sortable ? `#${this.options.specTableElemId} tbody tr {cursor: move;transition:unset;-webkit-transition:unset;}` : ''}
  226. #${this.options.specTableElemId} tbody tr td:first-child > i.layui-icon-delete {
  227. margin-left:3px;
  228. }
  229. #${this.options.specTableElemId} tbody tr td:last-child > i.layui-icon-delete {
  230. margin-right:15px;
  231. margin-left:-7px;
  232. vertical-align: top;
  233. }
  234. #${this.options.specTableElemId} tbody tr td div.fairy-spec-value-create,
  235. #${this.options.specTableElemId} tfoot tr td div.fairy-spec-create {
  236. display: inline-block;
  237. color: #1E9FFF;
  238. vertical-align: middle;
  239. padding: 4px 6px;
  240. }
  241. #${this.options.specTableElemId} tfoot tr td div.layui-form-checkbox {
  242. margin-top: 0;
  243. }
  244. #${this.options.specTableElemId} tfoot tr td div.layui-form-checkbox > span{
  245. color: #1E9FFF;
  246. }
  247. #${this.options.skuTableElemId} tbody tr td > img.fairy-sku-img{
  248. width: 16px;
  249. height: 16px;
  250. padding: 6px;
  251. border: 1px solid #eceef1;
  252. vertical-align: middle;
  253. }
  254. #${this.options.specTableElemId} tbody tr td > i.layui-icon-delete,
  255. #${this.options.specTableElemId} tbody tr td div.fairy-spec-value-create,
  256. #${this.options.specTableElemId} tfoot tr td div.fairy-spec-create,
  257. #${this.options.skuTableElemId} thead tr th > i.layui-icon,
  258. #${this.options.skuTableElemId} tbody tr td > img.fairy-sku-img {
  259. cursor: pointer;
  260. }
  261. </style>`
  262. );
  263. }
  264. listen() {
  265. var that = this;
  266. /**
  267. * 监听规格类型选择
  268. */
  269. form.on('radio(fairy-is-attribute)', function (data) {
  270. that.options.isAttributeValue = data.value;
  271. that.render();
  272. });
  273. /**
  274. * 监听所选规格值的变化
  275. */
  276. form.on('checkbox(fairy-spec-filter)', function (data) {
  277. var specData = [];
  278. $.each($(`#${that.options.specTableElemId} tbody tr`), function () {
  279. var child = [];
  280. $.each($(this).find('input[type=checkbox]'), function () {
  281. child.push({id: $(this).val(), title: $(this).attr('title'), checked: $(this).is(':checked')});
  282. });
  283. var specItem = {
  284. id: $(this).find('td').eq(0).data('spec-id'),
  285. title: $(this).find('td').eq(0).text(),
  286. child: child
  287. };
  288. specData.push(specItem);
  289. });
  290. that.options.specData = specData;
  291. that.options.skuData = $.extend(that.options.skuData, that.getFormSkuData());
  292. that.resetRender(that.options.skuTableElemId);
  293. that.renderMultipleSkuTable();
  294. });
  295. /**
  296. * 监听批量赋值
  297. */
  298. $(document).on('click', `#${this.options.skuTableElemId} thead tr th i`, function () {
  299. var thisI = this;
  300. Util.msg.prompt({title: $(thisI).parent().text().trim() + '批量赋值'}, function (value, index, elem) {
  301. $.each($(`#${that.options.skuTableElemId} tbody tr`), function () {
  302. var index = that.options.rowspan ?
  303. $(thisI).parent().index() - ($(`#${that.options.skuTableElemId} thead th.fairy-spec-name`).length - $(this).children('td.fairy-spec-value').length) :
  304. $(thisI).parent().index();
  305. $(this).find('td').eq(index).children('input').val(value);
  306. });
  307. Util.msg.close(index);
  308. });
  309. });
  310. /**
  311. * 监听添加规格
  312. */
  313. $(document).on('click', `#${this.options.specTableElemId} .fairy-spec-create`, function () {
  314. layer.prompt({title: '规格'}, function (value, index, elem) {
  315. var specTitleArr = [];
  316. $.each(that.options.specData, function (k, v) {
  317. specTitleArr.push(v.title)
  318. })
  319. if (specTitleArr.includes(value)) {
  320. Util.msg.error('规格名已存在');
  321. } else {
  322. that.options.specData.push({id: Util.tool.uuid(), title: value, child: []});
  323. that.resetRender(that.options.specTableElemId);
  324. that.renderSpecTable();
  325. }
  326. Util.msg.close(index);
  327. });
  328. });
  329. /**
  330. * 监听添加规格值
  331. */
  332. $(document).on('click', `#${this.options.specTableElemId} .fairy-spec-value-create`, function () {
  333. var specId = $(this).parent('td').prev().data('spec-id');
  334. layer.prompt({title: '规格值'}, function (value, index, elem) {
  335. that.options.specData.forEach(function (v, i) {
  336. if (v.id == specId) {
  337. v.child.push({id: Util.tool.uuid(), title: value, checked: false});
  338. }
  339. });
  340. that.resetRender(that.options.specTableElemId);
  341. that.renderSpecTable();
  342. Util.msg.close(index);
  343. });
  344. });
  345. /**
  346. * 监听删除规格/规格值
  347. */
  348. $(document).on('click', `#${this.options.specTableElemId} i.layui-icon-delete`, function () {
  349. if (typeof $(this).attr('data-spec-index') !== "undefined") {
  350. that.options.specData.splice($(this).data('spec-index'), 1);
  351. that.resetRender([that.options.specTableElemId, that.options.skuTableElemId]);
  352. that.renderSpecTable();
  353. that.renderMultipleSkuTable();
  354. } else if (typeof $(this).attr('data-spec-value-index') !== "undefined") {
  355. var [i, ii] = $(this).data('spec-value-index').split('-');
  356. that.options.specData[i].child.splice(ii, 1);
  357. that.resetRender([that.options.specTableElemId, that.options.skuTableElemId]);
  358. that.renderSpecTable();
  359. that.renderMultipleSkuTable();
  360. }
  361. });
  362. /**
  363. * 监听规格表是否开启删除
  364. */
  365. form.on('checkbox(fairy-spec-delete-filter)', function (data) {
  366. that.options.specDataDelete = data.elem.checked;
  367. if (data.elem.checked) {
  368. $(`#${that.options.specTableElemId} tbody tr i.layui-icon-delete`).removeClass('layui-hide');
  369. } else {
  370. $(`#${that.options.specTableElemId} tbody tr i.layui-icon-delete`).addClass('layui-hide')
  371. }
  372. });
  373. /**
  374. * 图片移入放大/移出恢复
  375. */
  376. var imgLayerIndex = null;
  377. $(document).on('mouseenter', '.fairy-sku-img', function () {
  378. imgLayerIndex = layer.tips('<img src="' + $(this).attr('src') + '" style="max-width:200px;" alt=""/>', this, {
  379. tips: [2, 'rgba(41,41,41,.5)'],
  380. time: 0
  381. });
  382. }).on('mouseleave', '.fairy-sku-img', function () {
  383. layer.close(imgLayerIndex);
  384. })
  385. }
  386. /**
  387. * 渲染
  388. */
  389. render() {
  390. this.resetRender();
  391. this.renderIsAttribute(this.options.isAttributeValue);
  392. if (this.options.isAttributeValue == '1') {
  393. if (this.options.specDataUrl && this.options.productId) {
  394. Util.request.get({
  395. url: this.options.specDataUrl,
  396. productId: this.options.productId,
  397. statusCode: this.options.requestSuccessCode
  398. }, (res) => {
  399. this.options.specData = res.data;
  400. this.renderSpecTable();
  401. this.renderMultipleSkuTable();
  402. });
  403. } else {
  404. this.renderSpecTable();
  405. this.renderMultipleSkuTable();
  406. }
  407. } else {
  408. this.renderSingleSkuTable();
  409. }
  410. }
  411. /**
  412. * 重新渲染
  413. * @param targets
  414. */
  415. resetRender(targets) {
  416. if (typeof targets === 'string') {
  417. $(`#${targets}`).parents('.layui-form-item').replaceWith(`<div id="${targets}"></div>`);
  418. } else if ($.isArray(targets) && targets.length) {
  419. targets.forEach((item) => {
  420. $(`#${item}`).parents('.layui-form-item').replaceWith(`<div id="${item}"></div>`);
  421. })
  422. } else {
  423. $(`#${this.options.isAttributeElemId}`).parents('.layui-form-item').replaceWith(`<div id="${this.options.isAttributeElemId}"></div>`);
  424. $(`#${this.options.specTableElemId}`).parents('.layui-form-item').replaceWith(`<div id="${this.options.specTableElemId}"></div>`);
  425. $(`#${this.options.skuTableElemId}`).parents('.layui-form-item').replaceWith(`<div id="${this.options.skuTableElemId}"></div>`);
  426. }
  427. }
  428. /**
  429. * 渲染规格类型
  430. * @param checkedValue
  431. */
  432. renderIsAttribute(checkedValue) {
  433. var html = '';
  434. html += `<input type="radio" name="is_attribute" title="统一规格" value="0" lay-filter="fairy-is-attribute" ${checkedValue == '0' ? 'checked' : ''}>`;
  435. html += `<input type="radio" name="is_attribute" title="多规格" value="1" lay-filter="fairy-is-attribute" ${checkedValue == '1' ? 'checked' : ''}>`;
  436. this.renderFormItem('规格类型', html, this.options.isAttributeElemId);
  437. }
  438. renderSingleSkuTable() {
  439. var that = this,
  440. table = `<table class="layui-table" id="${this.options.skuTableElemId}">`;
  441. table += '<thead>';
  442. table += '<tr>';
  443. this.options.singleSkuTableConfig.thead.forEach((item) => {
  444. table += `<th>${item.title}</th>`;
  445. });
  446. table += '</tr>';
  447. table += '</thead>';
  448. table += '<tbody>';
  449. table += '<tr>';
  450. that.options.singleSkuTableConfig.tbody.forEach(function (item) {
  451. switch (item.type) {
  452. case "select":
  453. table += '<td>';
  454. table += `<select name="${item.field}" lay-verify="${item.verify}" lay-reqtext="${item.reqtext}">`;
  455. item.option.forEach(function (o) {
  456. table += `<option value="${o.value}" ${that.options.skuData[item.field] == o.value ? 'selected' : ''}>${o.key}</option>`;
  457. });
  458. table += '</select>';
  459. table += '</td>';
  460. break;
  461. case "input":
  462. default:
  463. table += '<td>';
  464. table += `<input type="text" name="${item.field}" value="${that.options.skuData[item.field] ? that.options.skuData[item.field] : item.value}" class="layui-input" lay-verify="${item.verify}" lay-reqtext="${item.reqtext}">`;
  465. table += '</td>';
  466. break;
  467. }
  468. });
  469. table += '</tr>';
  470. table += '<tbody>';
  471. table += '</table>';
  472. this.renderFormItem('SKU', table, this.options.skuTableElemId);
  473. }
  474. /**
  475. * 渲染规格表
  476. */
  477. renderSpecTable() {
  478. var that = this,
  479. table = `<table class="layui-table" id="${this.options.specTableElemId}"><thead><tr><th>规格名</th><th>规格值</th></tr></thead><colgroup><col width="140"></colgroup><tbody>`;
  480. $.each(this.options.specData, function (index, item) {
  481. table += that.options.sortable ? `<tr data-id="${item.id}">` : '<tr>';
  482. table += `<td data-spec-id="${item.id}">${item.title}<i class="layui-icon layui-icon-delete layui-anim layui-anim-scale ${that.options.specDataDelete ? '' : 'layui-hide'}" data-spec-index="${index}"></i></td>`;
  483. table += '<td>';
  484. $.each(item.child, function (key, value) {
  485. table += `<input type="checkbox" title="${value.title}" lay-filter="fairy-spec-filter" value="${value.id}" ${value.checked ? 'checked' : ''} /><i class="layui-icon layui-icon-delete layui-anim layui-anim-scale ${that.options.specDataDelete ? '' : 'layui-hide'}" data-spec-value-index="${index}-${key}"></i> `;
  486. });
  487. table += '<div class="fairy-spec-value-create"><i class="layui-icon layui-icon-addition"></i>规格值</div>'
  488. table += '</td>';
  489. table += '</tr>';
  490. });
  491. table += '</tbody>';
  492. table += '<tfoot><tr><td colspan="2">';
  493. table += `<input type="checkbox" title="开启删除" lay-skin="primary" lay-filter="fairy-spec-delete-filter" ${that.options.specDataDelete ? 'checked' : ''}/>`;
  494. table += `<div class="fairy-spec-create"><i class="layui-icon layui-icon-addition"></i>规格</div>`;
  495. table += '</td></tr></tfoot>';
  496. table += '</table>';
  497. this.renderFormItem('商品规格', table, this.options.specTableElemId);
  498. if (this.options.sortable) {
  499. /**
  500. * 拖拽
  501. */
  502. var sortableObj = sortable.create($(`#${this.options.specTableElemId} tbody`)[0], {
  503. animation: 1000,
  504. onEnd: (evt) => {
  505. //获取拖动后的排序
  506. var sortArr = sortableObj.toArray(),
  507. sortSpecData = [];
  508. this.options.specData.forEach((item) => {
  509. sortSpecData[sortArr.indexOf(String(item.id))] = item;
  510. });
  511. this.options.specData = sortSpecData;
  512. this.resetRender(that.options.skuTableElemId);
  513. this.renderMultipleSkuTable();
  514. },
  515. });
  516. }
  517. }
  518. /**
  519. * 渲染sku表
  520. */
  521. renderMultipleSkuTable() {
  522. var that = this, table = `<table class="layui-table" id="${this.options.skuTableElemId}">`;
  523. if ($(`#${this.options.specTableElemId} tbody input[type=checkbox]:checked`).length) {
  524. var prependThead = [], prependTbody = [];
  525. $.each(this.options.specData, function (index, item) {
  526. var isShow = item.child.some(function (value, index, array) {
  527. return value.checked;
  528. });
  529. if (isShow) {
  530. prependThead.push(item.title);
  531. var prependTbodyItem = [];
  532. $.each(item.child, function (key, value) {
  533. if (value.checked) {
  534. prependTbodyItem.push({id: value.id, title: value.title});
  535. }
  536. });
  537. prependTbody.push(prependTbodyItem);
  538. }
  539. });
  540. table += '<colgroup>' + '<col width="70">'.repeat(prependThead.length + 1) + '</colgroup>';
  541. table += '<thead>';
  542. if (prependThead.length > 0) {
  543. var theadTr = '<tr>';
  544. theadTr += prependThead.map(function (t, i, a) {
  545. return '<th class="fairy-spec-name">' + t + '</th>';
  546. }).join('');
  547. this.options.multipleSkuTableConfig.thead.forEach(function (item) {
  548. theadTr += '<th>' + item.title + (item.icon ? ' <i class="layui-icon ' + item.icon + '"></i>' : '') + '</th>';
  549. });
  550. theadTr += '</tr>';
  551. table += theadTr;
  552. }
  553. table += '</thead>';
  554. if (this.options.rowspan) {
  555. var skuRowspanArr = [];
  556. prependTbody.forEach(function (v, i, a) {
  557. var num = 1, index = i;
  558. while (index < a.length - 1) {
  559. num *= a[index + 1].length;
  560. index++;
  561. }
  562. skuRowspanArr.push(num);
  563. });
  564. }
  565. var prependTbodyTrs = [];
  566. prependTbody.reduce(function (prev, cur, index, array) {
  567. var tmp = [];
  568. prev.forEach(function (a) {
  569. cur.forEach(function (b) {
  570. tmp.push({id: a.id + that.options.skuNameDelimiter + b.id, title: a.title + that.options.skuNameDelimiter + b.title});
  571. })
  572. });
  573. return tmp;
  574. }).forEach(function (item, index, array) {
  575. var tr = '<tr>';
  576. tr += item.title.split(that.options.skuNameDelimiter).map(function (t, i, a) {
  577. if (that.options.rowspan) {
  578. if (index % skuRowspanArr[i] === 0 && skuRowspanArr[i] > 1) {
  579. return '<td class="fairy-spec-value" rowspan="' + skuRowspanArr[i] + '">' + t + '</td>';
  580. } else if (skuRowspanArr[i] === 1) {
  581. return '<td class="fairy-spec-value">' + t + '</td>';
  582. } else {
  583. return '';
  584. }
  585. } else {
  586. return '<td>' + t + '</td>';
  587. }
  588. }).join('');
  589. that.options.multipleSkuTableConfig.tbody.forEach(function (c) {
  590. switch (c.type) {
  591. case "image":
  592. tr += '<td><input type="hidden" name="' + that.makeSkuName(item, c) + '" value="' + (that.options.skuData[that.makeSkuName(item, c)] ? that.options.skuData[that.makeSkuName(item, c)] : c.value) + '" lay-verify="' + c.verify + '" lay-reqtext="' + c.reqtext + '"><img class="fairy-sku-img" src="' + (that.options.skuData[that.makeSkuName(item, c)] ? that.options.skuData[that.makeSkuName(item, c)] : that.options.skuIcon) + '" alt="' + c.field + '图片"></td>';
  593. break;
  594. case "select":
  595. tr += '<td><select name="' + that.makeSkuName(item, c) + '" lay-verify="' + c.verify + '" lay-reqtext="' + c.reqtext + '">';
  596. c.option.forEach(function (o) {
  597. tr += '<option value="' + o.value + '" ' + (that.options.skuData[that.makeSkuName(item, c)] == o.value ? 'selected' : '') + '>' + o.key + '</option>';
  598. });
  599. tr += '</select></td>';
  600. break;
  601. case "input":
  602. default:
  603. tr += '<td><input type="text" name="' + that.makeSkuName(item, c) + '" value="' + (that.options.skuData[that.makeSkuName(item, c)] ? that.options.skuData[that.makeSkuName(item, c)] : c.value) + '" class="layui-input" lay-verify="' + c.verify + '" lay-reqtext="' + c.reqtext + '"></td>';
  604. break;
  605. }
  606. });
  607. tr += '</tr>';
  608. tr && prependTbodyTrs.push(tr);
  609. });
  610. table += '<tbody>';
  611. if (prependTbodyTrs.length > 0) {
  612. table += prependTbodyTrs.join('');
  613. }
  614. table += '</tbody>';
  615. } else {
  616. table += '<thead></thead><tbody></tbody><tfoot><tr><td>请先选择规格值</td></tr></tfoot>';
  617. }
  618. table += '</table>';
  619. this.renderFormItem('SKU', table, this.options.skuTableElemId);
  620. //上传
  621. if (this.options.uploadUrl) {
  622. upload.render({
  623. elem: '.fairy-sku-img',
  624. url: this.options.uploadUrl,
  625. exts: 'png|jpg|ico|jpeg|gif',
  626. accept: 'images',
  627. acceptMime: 'image/*',
  628. multiple: false,
  629. done: function (res) {
  630. if (res.code === that.options.requestSuccessCode) {
  631. var url = res.data.url;
  632. $(this.item).attr('src', url).prev().val(url);
  633. Util.msg.success(res.msg);
  634. } else {
  635. var msg = res.msg == undefined ? '返回数据格式有误' : res.msg;
  636. Util.msg.error(msg);
  637. }
  638. return false;
  639. }
  640. });
  641. }
  642. }
  643. /**
  644. * 渲染表单项
  645. * @param label 标题
  646. * @param content 内容
  647. * @param target id
  648. * @param isRequired
  649. */
  650. renderFormItem(label, content, target, isRequired = true) {
  651. var html = '';
  652. html += '<div class="layui-form-item">';
  653. html += `<label class="layui-form-label ${isRequired ? 'required' : ''}">${label.length ? label : ''}</label>`;
  654. html += '<div class="layui-input-block">';
  655. html += content;
  656. html += '</div>';
  657. html += '</div>';
  658. $(`#${target}`).html(html);
  659. form.render();
  660. }
  661. makeSkuName(sku, conf) {
  662. return 'skus[' + (this.options.skuNameType === 0 ? sku.id : sku.title) + '][' + conf.field + ']';
  663. }
  664. getSpecData() {
  665. return this.options.specData;
  666. }
  667. getFormFilter() {
  668. var fariyForm = $('form.fairy-form');
  669. if (!fariyForm.attr('lay-filter')) {
  670. fariyForm.attr('lay-filter', 'fairy-form-filter');
  671. }
  672. return fariyForm.attr('lay-filter');
  673. }
  674. getFormSkuData() {
  675. var skuData = {};
  676. $.each(form.val(this.getFormFilter()), function (key, value) {
  677. if (key.startsWith('skus')) {
  678. skuData[key] = value;
  679. }
  680. });
  681. return skuData;
  682. }
  683. }
  684. exports(MOD_NAME, {
  685. render: function (options) {
  686. return new SkuTable(options);
  687. }
  688. })
  689. });