UNPKG

@pisell/pisellos

Version:

一个可扩展的前端模块化SDK框架,支持插件系统

423 lines (391 loc) 16 kB
function _toConsumableArray(arr) { return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableSpread(); } function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); } function _iterableToArray(iter) { if (typeof Symbol !== "undefined" && iter[Symbol.iterator] != null || iter["@@iterator"] != null) return Array.from(iter); } function _arrayWithoutHoles(arr) { if (Array.isArray(arr)) return _arrayLikeToArray(arr); } function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; return arr2; } /** * 现金支付推荐算法 * * 核心原理: * 1. 每个推荐金额都应该是独立的最优组合 * 2. 不推荐在已有最优解基础上添加额外面额的组合 * 3. 优先推荐使用不同数量币种的组合 * 4. 根据组合判断去重,避免扩展组合 */ /** * 常见国家货币面额配置 */ export var CURRENCY_DENOMINATIONS = { // 美元 'USD': [100, 50, 20, 10, 5, 1, 0.5, 0.25, 0.1, 0.05, 0.01], // 人民币 'CNY': [100, 50, 20, 10, 5, 1, 0.5, 0.1, 0.05, 0.01], 'RMB': [100, 50, 20, 10, 5, 1, 0.5, 0.1, 0.05, 0.01], // 欧元 'EUR': [500, 200, 100, 50, 20, 10, 5, 2, 1, 0.5, 0.2, 0.1, 0.05, 0.02, 0.01], // 日元 'JPY': [10000, 5000, 2000, 1000, 500, 100, 50, 10, 5, 1], // 英镑 'GBP': [50, 20, 10, 5, 2, 1, 0.5, 0.2, 0.1, 0.05, 0.02, 0.01], // 澳元 'AUD': [100, 50, 20, 10, 5, 2, 1, 0.5, 0.2, 0.1], // 加元 'CAD': [100, 50, 20, 10, 5, 2, 1, 0.25, 0.1, 0.05], // 港元 'HKD': [1000, 500, 100, 50, 20, 10, 5, 2, 1, 0.5, 0.2, 0.1], // 新台币 'TWD': [2000, 1000, 500, 200, 100, 50, 10, 5, 1], // 韩元 'KRW': [50000, 10000, 5000, 1000, 500, 100, 50, 10, 5, 1], // 新加坡元 'SGD': [10000, 1000, 100, 50, 10, 5, 2, 1, 0.5, 0.2, 0.1, 0.05, 0.01], // 瑞士法郎 'CHF': [1000, 200, 100, 50, 20, 10, 5, 2, 1, 0.5, 0.2, 0.1, 0.05], // 默认通用面额(如果币种不在列表中) 'DEFAULT': [100, 50, 20, 10, 5, 1, 0.5, 0.25, 0.1, 0.05, 0.01] }; /** * 支付组合类型定义 */ /** * 最优支付金额推荐算法 * 推荐通过不同数量面额组合刚好足够支付的最小金额 * * @param targetAmount 目标金额 * @param denominations 币种面值数组 * @returns 推荐支付金额数组 */ export function recommendOptimalPayments(targetAmount, denominations) { // 参数验证 if (targetAmount <= 0 || !isFinite(targetAmount) || isNaN(targetAmount)) { return []; } if (!denominations || !Array.isArray(denominations) || denominations.length === 0) { return [Math.ceil(targetAmount)]; } // 过滤有效面额并限制搜索空间以提高性能 var maxReasonableDenom = targetAmount * 5; // 适度限制搜索空间 var validDenoms = denominations.filter(function (denom) { return denom > 0 && isFinite(denom) && !isNaN(denom) && denom <= maxReasonableDenom; }).slice(0, 8); // 限制面额数量以提高性能 if (validDenoms.length === 0) { // 如果没有合适的面额,返回最接近的整数金额 return [Math.ceil(targetAmount)]; } try { // 处理精度问题,将金额转换为整数计算 var precision = 100; // 假设最小单位是0.01 var target = Math.round(targetAmount * precision); var denoms = validDenoms.map(function (d) { return Math.round(d * precision); }).sort(function (a, b) { return b - a; }); // 存储不同的支付组合方案 var paymentOptions = []; // 首先尝试找到精确匹配的组合 var exactCombination = findExactCombination(target, denoms, precision); if (exactCombination) { paymentOptions.push(exactCombination); } // 为了提高效率,我们将按币种数量来生成组合 // 1币种组合:只用一种面额 for (var i = 0; i < Math.min(denoms.length, 6); i++) { var denom = denoms[i]; var count = Math.ceil(target / denom); var sum = count * denom; if (sum >= target && count <= 12) { // 限制硬币数量以提高性能 paymentOptions.push({ combination: Array(count).fill(denom / precision), sum: sum / precision, coinCount: count, denomTypes: 1 }); } } // 2币种组合:使用两种面额(大幅限制搜索范围以提高性能) for (var _i = 0; _i < Math.min(denoms.length - 1, 5); _i++) { for (var j = _i + 1; j < Math.min(denoms.length, 6); j++) { var denom1 = denoms[_i]; var denom2 = denoms[j]; // 大幅限制搜索范围,基于目标金额动态调整 var maxCount1 = Math.min(Math.ceil(target / denom1) + 1, 8); var maxCount2 = Math.min(Math.ceil(target / denom2) + 1, 10); for (var count1 = 1; count1 <= maxCount1; count1++) { for (var count2 = 1; count2 <= maxCount2; count2++) { var _sum = count1 * denom1 + count2 * denom2; if (_sum >= target) { var combination = [].concat(_toConsumableArray(Array(count1).fill(denom1 / precision)), _toConsumableArray(Array(count2).fill(denom2 / precision))); paymentOptions.push({ combination: combination, sum: _sum / precision, coinCount: count1 + count2, denomTypes: 2 }); } } } } } // 3币种组合:使用三种面额(大幅限制搜索以提高效率) for (var _i2 = 0; _i2 < Math.min(denoms.length - 2, 3); _i2++) { for (var _j = _i2 + 1; _j < Math.min(denoms.length - 1, 4); _j++) { for (var k = _j + 1; k < Math.min(denoms.length, 5); k++) { var _denom = denoms[_i2]; var _denom2 = denoms[_j]; var denom3 = denoms[k]; // 大幅限制搜索范围 var _maxCount = Math.min(Math.ceil(target / _denom) + 1, 5); var _maxCount2 = Math.min(Math.ceil(target / _denom2) + 1, 6); var maxCount3 = Math.min(Math.ceil(target / denom3) + 1, 8); for (var _count = 1; _count <= _maxCount; _count++) { for (var _count2 = 1; _count2 <= _maxCount2; _count2++) { for (var count3 = 1; count3 <= maxCount3; count3++) { var _sum2 = _count * _denom + _count2 * _denom2 + count3 * denom3; if (_sum2 >= target) { var _combination = [].concat(_toConsumableArray(Array(_count).fill(_denom / precision)), _toConsumableArray(Array(_count2).fill(_denom2 / precision)), _toConsumableArray(Array(count3).fill(denom3 / precision))); paymentOptions.push({ combination: _combination, sum: _sum2 / precision, coinCount: _count + _count2 + count3, denomTypes: 3 }); } } } } } } } // 4币种组合:使用四种面额(极度限制搜索以提高性能) for (var _i3 = 0; _i3 < Math.min(denoms.length - 3, 2); _i3++) { for (var _j2 = _i3 + 1; _j2 < Math.min(denoms.length - 2, 3); _j2++) { for (var _k = _j2 + 1; _k < Math.min(denoms.length - 1, 4); _k++) { for (var l = _k + 1; l < Math.min(denoms.length, 5); l++) { var _denom3 = denoms[_i3]; var _denom4 = denoms[_j2]; var _denom5 = denoms[_k]; var denom4 = denoms[l]; // 极度限制搜索范围 var _maxCount3 = Math.min(Math.ceil(target / _denom3) + 1, 3); var _maxCount4 = Math.min(Math.ceil(target / _denom4) + 1, 4); var _maxCount5 = Math.min(Math.ceil(target / _denom5) + 1, 5); var maxCount4 = Math.min(Math.ceil(target / denom4) + 1, 6); for (var _count3 = 1; _count3 <= _maxCount3; _count3++) { for (var _count4 = 1; _count4 <= _maxCount4; _count4++) { for (var _count5 = 1; _count5 <= _maxCount5; _count5++) { for (var count4 = 1; count4 <= maxCount4; count4++) { var _sum3 = _count3 * _denom3 + _count4 * _denom4 + _count5 * _denom5 + count4 * denom4; if (_sum3 >= target) { var _combination2 = [].concat(_toConsumableArray(Array(_count3).fill(_denom3 / precision)), _toConsumableArray(Array(_count4).fill(_denom4 / precision)), _toConsumableArray(Array(_count5).fill(_denom5 / precision)), _toConsumableArray(Array(count4).fill(denom4 / precision))); paymentOptions.push({ combination: _combination2, sum: _sum3 / precision, coinCount: _count3 + _count4 + _count5 + count4, denomTypes: 4 }); } } } } } } } } } // 如果没有找到任何组合,提供默认建议 if (paymentOptions.length === 0) { // 找到大于等于目标金额的最小面额 var minValidDenom = validDenoms.find(function (denom) { return denom >= targetAmount; }); if (minValidDenom) { return [minValidDenom]; } else { // 使用最大面额的组合 var maxDenom = Math.max.apply(Math, _toConsumableArray(validDenoms)); var _count6 = Math.ceil(targetAmount / maxDenom); return [_count6 * maxDenom]; } } // 移除重复和扩展组合 var uniqueCombinations = removeDuplicateAndExtendedCombinations(paymentOptions, targetAmount); // 按币种类型数量排序,然后按总硬币数量排序,最后按金额排序 uniqueCombinations.sort(function (a, b) { if (a.denomTypes !== b.denomTypes) { return a.denomTypes - b.denomTypes; } if (a.coinCount !== b.coinCount) { return a.coinCount - b.coinCount; } return a.sum - b.sum; }); // 按金额去重并返回推荐的支付金额 var uniqueAmounts = new Set(); var finalResults = []; for (var _i4 = 0, _uniqueCombinations = uniqueCombinations; _i4 < _uniqueCombinations.length; _i4++) { var item = _uniqueCombinations[_i4]; var roundedAmount = Math.round(item.sum * 100) / 100; // 处理浮点数精度 if (!uniqueAmounts.has(roundedAmount) && finalResults.length < 10) { uniqueAmounts.add(roundedAmount); finalResults.push(roundedAmount); } } return finalResults.sort(function (a, b) { return a - b; }); } catch (error) { // 发生错误时返回安全的默认值 console.warn('推荐支付金额计算出错:', error); // 返回最接近的整数金额作为兜底 var safeAmount = Math.ceil(targetAmount); return [targetAmount, safeAmount]; } } /** * 尝试找到精确匹配目标金额的组合 */ function findExactCombination(target, denoms, precision) { // 简单的深度优先搜索,限制搜索深度 function dfs(remaining, denomIndex, currentCombination) { if (remaining === 0) { return currentCombination; } if (remaining < 0 || denomIndex >= denoms.length || currentCombination.length > 10) { return null; } var denom = denoms[denomIndex]; // 尝试使用当前面额 0 到 maxCount 次 var maxCount = Math.min(Math.floor(remaining / denom), 8); for (var count = maxCount; count >= 0; count--) { var newCombination = [].concat(_toConsumableArray(currentCombination), _toConsumableArray(Array(count).fill(denom))); var result = dfs(remaining - count * denom, denomIndex + 1, newCombination); if (result) { return result; } } return null; } var exactMatch = dfs(target, 0, []); if (exactMatch && exactMatch.length > 0) { var denomTypes = new Set(exactMatch).size; return { combination: exactMatch.map(function (d) { return d / precision; }), sum: target / precision, coinCount: exactMatch.length, denomTypes: denomTypes }; } return null; } /** * 移除重复和扩展组合 * 核心原理:如果组合A包含组合B的所有硬币,并且还有额外硬币,则A是B的扩展,应该被移除 */ function removeDuplicateAndExtendedCombinations(combinations, targetAmount) { var result = []; // 按币种类型数量、硬币数量、金额排序,确保较简单的组合在前面 combinations.sort(function (a, b) { if (a.denomTypes !== b.denomTypes) { return a.denomTypes - b.denomTypes; } if (a.coinCount !== b.coinCount) { return a.coinCount - b.coinCount; } return a.sum - b.sum; }); for (var i = 0; i < combinations.length; i++) { var current = combinations[i]; var shouldSkip = false; // 检查当前组合是否与已添加的组合重复或者是扩展 for (var j = 0; j < result.length; j++) { var existing = result[j]; // 如果完全相同,跳过 if (isSameCombination(current.combination, existing.combination)) { shouldSkip = true; break; } // 如果当前组合是已存在组合的扩展,跳过 if (isExtensionOf(current.combination, existing.combination)) { shouldSkip = true; break; } } // 检查是否有其他组合是当前组合的扩展,如果有,移除那些扩展 if (!shouldSkip) { // 移除所有当前组合的扩展 for (var _j3 = result.length - 1; _j3 >= 0; _j3--) { if (isExtensionOf(result[_j3].combination, current.combination)) { result.splice(_j3, 1); } } result.push(current); } } return result; } /** * 检查组合A是否是组合B的扩展(即A包含B的所有硬币,并且还有额外的硬币) */ function isExtensionOf(combinationA, combinationB) { // 如果A的硬币数量少于等于B,A不可能是B的扩展 if (combinationA.length <= combinationB.length) { return false; } // 统计每个面额的数量 var countA = {}; var countB = {}; combinationA.forEach(function (coin) { countA[coin] = (countA[coin] || 0) + 1; }); combinationB.forEach(function (coin) { countB[coin] = (countB[coin] || 0) + 1; }); // 检查B的所有面额在A中是否都有足够的数量 for (var coin in countB) { if (!countA[coin] || countA[coin] < countB[coin]) { return false; } } // 检查A是否有B没有的额外硬币 var hasExtra = false; for (var _coin in countA) { if (countA[_coin] > (countB[_coin] || 0)) { hasExtra = true; break; } } return hasExtra; } /** * 检查两个组合是否本质相同(相同的面额组合) */ function isSameCombination(combinationA, combinationB) { if (combinationA.length !== combinationB.length) { return false; } var countA = {}; var countB = {}; combinationA.forEach(function (coin) { countA[coin] = (countA[coin] || 0) + 1; }); combinationB.forEach(function (coin) { countB[coin] = (countB[coin] || 0) + 1; }); // 检查两个组合是否有相同的面额和数量 for (var coin in countA) { if (countA[coin] !== (countB[coin] || 0)) { return false; } } for (var _coin2 in countB) { if (countB[_coin2] !== (countA[_coin2] || 0)) { return false; } } return true; }