variantTools.ts 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192
  1. import {
  2. ProductOption,
  3. VatiantImages,
  4. ResolvedVariant,
  5. ProductFlexibleVariant,
  6. ProductFlexibleVariantValue,
  7. ProductFlexibleVariantPriceIndices
  8. } from "@/components/catalog/type";
  9. /**
  10. * 辅助函数:判断一个完整的选项值ID组合是否对应一个可购买的变体(quantity > 0)
  11. * @param selectedIds {number[]} 选中的选项值ID的数组
  12. * @param variants {ResolvedVariant[]} 变体列表
  13. * @returns {boolean} 该选项组和对应的变体是否可用
  14. * */
  15. export function isCombinationAvailable(
  16. selectedIds: number[],
  17. variants: ResolvedVariant[]
  18. ): boolean {
  19. // some 每一项都带入参数函数执行,如果遇到一项带入函数执行结果是true,则返回true;如果所有项带入函数执行,结果都是false,则返回false
  20. return variants.some((variant) => {
  21. if (variant.quantity <= 0) return false;
  22. const values = variant.optionValues;
  23. const variantValueIds = values.map((v) => v.id);
  24. return (
  25. selectedIds.length === variantValueIds.length &&
  26. selectedIds.every((id) => variantValueIds.includes(id))
  27. );
  28. });
  29. }
  30. /**
  31. * 辅助函数:根据部分选中的映射,判断某个选项值是否可用
  32. * @param optionId {number} 选项组ID
  33. * @param valueId {number} 选项值ID
  34. * @param currentSelected {Record<number, number>} 当前已选中的映射
  35. * @param allOptions 产品选项列表
  36. * @param variants 产品变体列表
  37. * @returns {boolean} 该选项值是否可用
  38. * */
  39. export function isOptionValueAvailable(
  40. optionId: number,
  41. valueId: number,
  42. currentSelected: Record<number, number>, // 当前已选中的映射
  43. allOptions: ProductOption[],
  44. variants: ResolvedVariant[]
  45. ): boolean {
  46. const testSelected = { ...currentSelected, [optionId]: valueId };
  47. // 检查是否所有选项组都已选中(有些组可能尚未选择,但禁用判断时我们只关心已选中的组能否与当前测试值组成可用变体)
  48. // 获取所有选项值ID的列表
  49. const selectedValueIds = Object.values(testSelected);
  50. // 如果有任何组未选中,则无法构成完整变体,此时应视为可能可用(不禁用),因为用户还需要继续选择
  51. if (selectedValueIds.length < allOptions.length) {
  52. // 但需要检查:在已选中的这些组中,是否存在一个变体其对应的值包含这些已选中的值?
  53. // 即是否存在变体,其选项值集合包含当前测试选中的值(作为子集)
  54. return variants.some((variant) => {
  55. if (variant.quantity <= 0) return false;
  56. const values = variant.optionValues;
  57. const variantValueIds = values.map((v) => v.id);
  58. // 检查测试选中的值是否都是该变体选项值的子集
  59. return selectedValueIds.every((id) => variantValueIds.includes(id));
  60. });
  61. } else {
  62. // 所有组都已选中,直接检查完整组合是否可用
  63. return isCombinationAvailable(selectedValueIds, variants);
  64. }
  65. }
  66. /**
  67. * 根据传入的value id判断变体中是否存在可购买变体,如果有返回true,如果没有则返回false
  68. * @param valueId {number} 选项值ID
  69. * @param variants {ResolvedVariant[]} 变体列表
  70. * @returns {boolean} 该value id是否可用
  71. * */
  72. export function isValueAvailable( valueId: number, variants: ResolvedVariant[]): boolean {
  73. return variants.some((variant) => {
  74. if (variant.quantity <= 0) return false;
  75. const values = variant.optionValues;
  76. const variantValueIds = values.map((v) => v.id);
  77. return variantValueIds.includes(valueId);
  78. });
  79. }
  80. /**
  81. * 根据传入的value id组合,找到一个最近的可购买的变体
  82. * @param fixedOptionId {number} 调过校验的选项组id
  83. * @param fixedValueId {number} 调过校验的选项值id
  84. * @param valueIdMap {Record<number, number>} 选中的选项值映射
  85. * @param allOptions 产品的选项列表
  86. * @param variants 产品的变体列表
  87. * @returns {Record<number, number>} 返回一个最近可用变体的选项值映射
  88. * */
  89. export function findNearestAvailableVariant(
  90. fixedOptionId: number,
  91. fixedValueId: number,
  92. valueIdMap: Record<number, number>,
  93. allOptions: ProductOption[],
  94. variants: ResolvedVariant[] // 可购买的变体列表
  95. ): Record<number, number>{
  96. let res:Record<number, number> = {[fixedOptionId]: fixedValueId};
  97. for (const option of allOptions) {
  98. const optionId = option.id;
  99. if(fixedOptionId === optionId) continue;
  100. const valueId = valueIdMap[optionId];
  101. if(isOptionValueAvailable(optionId,valueId,res,allOptions,variants)) {
  102. res = {...res,[optionId]: valueId};
  103. } else {
  104. let found = false;
  105. for(let i = 0; i < option.values.length; i++) {
  106. if(isOptionValueAvailable(optionId,option.values[i].id,res,allOptions,variants)) {
  107. found = true;
  108. res = {...res,[optionId]: option.values[i].id};
  109. break;
  110. }
  111. }
  112. if (!found) {
  113. // 极端情况:找不到可用值,保留原值以维持完整状态
  114. res = { ...res, [optionId]: valueId };
  115. }
  116. }
  117. }
  118. return res;
  119. }
  120. // 递归回溯查找第一个可用组合
  121. export function getFirstAvailable(productOptions: ProductOption[],flexibleVariants: ResolvedVariant[]) {
  122. function findFirstAvailable(
  123. optionIndex: number,
  124. currentMap: Record<number, number>
  125. ): Record<number, number> | null {
  126. if (optionIndex >= productOptions.length) {
  127. // 所有组都已选,检查完整组合是否可用
  128. const valueIds = Object.values(currentMap);
  129. return isCombinationAvailable(valueIds, flexibleVariants) ? { ...currentMap } : null;
  130. }
  131. const option = productOptions[optionIndex];
  132. for (const value of option.values) {
  133. const nextMap = { ...currentMap, [option.id]: value.id };
  134. // 剪枝:检查当前部分组合是否可能导向可用变体
  135. const partialValueIds = Object.values(nextMap);
  136. const hasPotential = flexibleVariants.some((v) => {
  137. if (v.quantity <= 0) return false;
  138. const vIds = v.optionValues.map((ov) => ov.id);
  139. return partialValueIds.every((id) => vIds.includes(id));
  140. });
  141. if (!hasPotential) continue;
  142. const result = findFirstAvailable(optionIndex + 1, nextMap);
  143. if (result) return result;
  144. }
  145. return null;
  146. }
  147. return findFirstAvailable(0, {});
  148. }
  149. // 格式化接口返的变体数据
  150. export function formatFlexibleVariants(originData: ProductFlexibleVariant[] | undefined) {
  151. let res: ResolvedVariant[] = [];
  152. if(originData) {
  153. res = originData.map((edge: ProductFlexibleVariant ) => {
  154. let variantImages: VatiantImages[] = [],
  155. optionValues: ProductFlexibleVariantValue[] = [],
  156. priceIndices: ProductFlexibleVariantPriceIndices[] = [];
  157. if(typeof edge.variantImages === 'string') {
  158. variantImages = JSON.parse(edge.variantImages);
  159. } else if(edge.variantImages === null) {
  160. variantImages = [];
  161. }
  162. if(typeof edge.optionValues === 'string') {
  163. optionValues = JSON.parse(edge.optionValues);
  164. }
  165. if(typeof edge.priceIndices === 'string') {
  166. priceIndices = JSON.parse(edge.priceIndices);
  167. } else if(edge.priceIndices === null) {
  168. priceIndices = [];
  169. }
  170. return {...edge,optionValues,variantImages,priceIndices};
  171. });
  172. }
  173. return res;
  174. }
  175. // 获取可以购买的变体
  176. export function getAvailableVariants(flexibleVariants: ResolvedVariant[]) {
  177. return flexibleVariants.filter((variant) => variant.quantity > 0);
  178. }