tireCouponMatcher.js 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262
  1. /**
  2. * 轮胎优惠券匹配器
  3. * 用于判断商品是否匹配优惠券规则
  4. * 修改:每个商品只能匹配一张优惠券
  5. * @version 1.1.0
  6. */
  7. let TireCouponMatcher = (function() {
  8. // 私有辅助函数
  9. const helpers = {
  10. // 标准化品牌名称(忽略大小写和空格)
  11. normalizeBrand: function(brand) {
  12. return String(brand || '').trim().toLowerCase().replace(/\s+/g, '');
  13. },
  14. // 解析商品尺寸(提取数字)
  15. parseSize: function(size) {
  16. if (typeof size === 'number') return size;
  17. if (typeof size === 'string') {
  18. const match = size.match(/(\d+(\.\d+)?)/);
  19. return match ? parseFloat(match[1]) : NaN;
  20. }
  21. return NaN;
  22. },
  23. // 尺寸比较操作符
  24. sizeOperators: {
  25. '>=': (a, b) => a >= b,
  26. '<=': (a, b) => a <= b,
  27. '>': (a, b) => a > b,
  28. '<': (a, b) => a < b,
  29. '=': (a, b) => a === b,
  30. '==': (a, b) => a === b,
  31. '!=': (a, b) => a !== b
  32. }
  33. };
  34. /**
  35. * 匹配单个规则
  36. * @param {Object} item - 商品对象 {brandName, goodsSize}
  37. * @param {Object} rule - 规则对象 {couponType, ruleValue, ruleCondition}
  38. * @returns {boolean} 是否匹配
  39. */
  40. function matchSingleRule(item, rule) {
  41. const { couponType, ruleValue, ruleCondition } = rule;
  42. try {
  43. switch (couponType) {
  44. case 0: // 品牌匹配
  45. return helpers.normalizeBrand(item.brandName) ===
  46. helpers.normalizeBrand(ruleValue);
  47. case 1: // 尺寸匹配
  48. const itemSize = helpers.parseSize(item.goodsSize);
  49. const ruleSize = helpers.parseSize(ruleValue);
  50. if (isNaN(itemSize) || isNaN(ruleSize)) {
  51. return false;
  52. }
  53. // 获取比较操作符,默认为等值比较
  54. const operator = helpers.sizeOperators[ruleCondition] ||
  55. helpers.sizeOperators['='];
  56. return operator(itemSize, ruleSize);
  57. default:
  58. console.warn(`未知的规则类型: ${couponType}`);
  59. return false;
  60. }
  61. } catch (error) {
  62. console.error('规则匹配出错:', error);
  63. return false;
  64. }
  65. }
  66. /**
  67. * 匹配商品和优惠券规则列表
  68. * @param {Object} item - 商品对象 {brandName, goodsSize}
  69. * @param {Array} ruleList - 规则列表 [{couponType, ruleValue, ruleCondition}, ...]
  70. * @returns {boolean} 是否匹配所有规则
  71. */
  72. function matchRuleList(item, ruleList) {
  73. if (!ruleList || !Array.isArray(ruleList) || ruleList.length === 0) {
  74. return true; // 没有规则,默认匹配
  75. }
  76. // 检查所有规则是否都满足(AND 逻辑)
  77. return ruleList.every(rule => matchSingleRule(item, rule));
  78. }
  79. /**
  80. * 查找商品匹配的第一张优惠券ID
  81. * 修改:只返回第一张匹配的优惠券ID,而不是所有匹配的
  82. * @param {Object} item - 商品对象 {brandName, goodsSize}
  83. * @param {Array} couponList - 优惠券列表 [{couponId, tireCouponRuleList}, ...]
  84. * @returns {Array} 匹配的优惠券ID数组(最多一个元素)
  85. */
  86. function findMatchedCouponIds(item, couponList) {
  87. if (!couponList || !Array.isArray(couponList)) {
  88. return [];
  89. }
  90. // 查找第一张匹配的优惠券
  91. const matchedCoupon = couponList.find(coupon =>
  92. matchRuleList(item, coupon.tireCouponRuleList)
  93. );
  94. // 如果找到匹配的优惠券,返回其ID,否则返回空数组
  95. return matchedCoupon ? [matchedCoupon.couponId] : [];
  96. }
  97. /**
  98. * 批量匹配多个商品(每个商品只匹配一张优惠券)
  99. * 修改:每个商品只匹配第一张符合条件的优惠券
  100. * @param {Array} items - 商品对象数组
  101. * @param {Array} couponList - 优惠券列表
  102. * @returns {Object} 匹配结果 {matchedCouponIds, matchesByItem, totalMatches}
  103. */
  104. function batchMatch(items, couponList) {
  105. const matchedCouponIds = new Set();
  106. const matchesByItem = [];
  107. items.forEach((item, index) => {
  108. // 每个商品只匹配第一张优惠券
  109. const itemMatches = findMatchedCouponIds(item, couponList);
  110. // 将匹配到的优惠券ID添加到集合中
  111. if (itemMatches.length > 0) {
  112. matchedCouponIds.add(itemMatches[0]);
  113. }
  114. matchesByItem.push({
  115. itemIndex: index,
  116. brandName: item.brandName,
  117. goodsSize: item.goodsSize,
  118. matchedCouponIds: itemMatches
  119. });
  120. });
  121. return {
  122. matchedCouponIds: Array.from(matchedCouponIds),
  123. matchesByItem: matchesByItem,
  124. totalMatches: matchedCouponIds.size
  125. };
  126. }
  127. /**
  128. * 获取商品匹配的第一张优惠券ID(新增方法)
  129. * 用于明确表示只返回第一张匹配的优惠券
  130. * @param {Object} options - 配置对象
  131. * @param {Object} options.item - 商品对象 {brandName, goodsSize}
  132. * @param {Array} options.couponList - 优惠券列表
  133. * @returns {number|null} 匹配的优惠券ID,如果没有匹配则返回null
  134. */
  135. function findFirstMatchedCouponId(options) {
  136. if (!options || !options.item || !options.couponList) {
  137. console.error('缺少必要参数: item 和 couponList');
  138. return null;
  139. }
  140. const matchedIds = findMatchedCouponIds(options.item, options.couponList);
  141. return matchedIds.length > 0 ? matchedIds[0] : null;
  142. }
  143. // 公共 API
  144. return {
  145. /**
  146. * 判断商品是否匹配优惠券规则
  147. * @param {Object} options - 配置对象
  148. * @param {Object} options.item - 商品对象 {brandName, goodsSize}
  149. * @param {Array} options.ruleList - 优惠券规则列表
  150. * @returns {boolean} 是否匹配
  151. */
  152. isMatch: function(options) {
  153. if (!options || !options.item || !options.ruleList) {
  154. console.error('缺少必要参数: item 和 ruleList');
  155. return false;
  156. }
  157. return matchRuleList(options.item, options.ruleList);
  158. },
  159. /**
  160. * 获取商品匹配的所有优惠券ID
  161. * 注意:现在每个商品只会返回第一张匹配的优惠券ID
  162. * @param {Object} options - 配置对象
  163. * @param {Object} options.item - 商品对象 {brandName, goodsSize}
  164. * @param {Array} options.couponList - 优惠券列表
  165. * @returns {Array} 匹配的优惠券ID数组(最多一个元素)
  166. */
  167. getMatchedCouponIds: function(options) {
  168. if (!options || !options.item || !options.couponList) {
  169. console.error('缺少必要参数: item 和 couponList');
  170. return [];
  171. }
  172. return findMatchedCouponIds(options.item, options.couponList);
  173. },
  174. /**
  175. * 获取商品匹配的第一张优惠券ID(新增方法)
  176. * @param {Object} options - 配置对象
  177. * @param {Object} options.item - 商品对象 {brandName, goodsSize}
  178. * @param {Array} options.couponList - 优惠券列表
  179. * @returns {number|null} 匹配的优惠券ID,如果没有匹配则返回null
  180. */
  181. getFirstMatchedCouponId: function(options) {
  182. return findFirstMatchedCouponId(options);
  183. },
  184. /**
  185. * 批量匹配多个商品
  186. * 修改:每个商品只匹配第一张符合条件的优惠券
  187. * @param {Object} options - 配置对象
  188. * @param {Array} options.items - 商品对象数组
  189. * @param {Array} options.couponList - 优惠券列表
  190. * @returns {Object} 匹配结果
  191. */
  192. batchMatch: function(options) {
  193. console.info('options----', options);
  194. if (!options || !options.items || !options.couponList) {
  195. console.error('缺少必要参数: items 和 couponList');
  196. return { matchedCouponIds: [], matchesByItem: [], totalMatches: 0 };
  197. }
  198. return batchMatch(options.items, options.couponList);
  199. },
  200. /**
  201. * 验证数据格式
  202. * @param {Object} item - 商品对象
  203. * @returns {boolean} 数据格式是否正确
  204. */
  205. validateItem: function(item) {
  206. if (!item || typeof item !== 'object') {
  207. console.error('商品对象不能为空');
  208. return false;
  209. }
  210. const hasBrand = typeof item.brandName !== 'undefined';
  211. const hasSize = typeof item.goodsSize !== 'undefined';
  212. if (!hasBrand || !hasSize) {
  213. console.warn('商品对象缺少必要属性: brandName 或 goodsSize');
  214. }
  215. return hasBrand && hasSize;
  216. },
  217. /**
  218. * 版本信息
  219. */
  220. version: '1.1.0'
  221. };
  222. })();
  223. // 导出为模块(兼容 CommonJS 和 ES6 模块)
  224. if (typeof module !== 'undefined' && module.exports) {
  225. module.exports = TireCouponMatcher;
  226. }
  227. if (typeof window !== 'undefined') {
  228. window.TireCouponMatcher = TireCouponMatcher;
  229. }