feeding-plan.ts 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203
  1. import type { FeedingPlanProduct } from '@/model/feeding-plan'
  2. import type { Product } from '@/model/product'
  3. import { createFeedingPlan } from '@/api/feeding-plan'
  4. import { createPet } from '@/api/pet'
  5. import {
  6. type CreateFeedingPlanRequest,
  7. type CreatePetRequest,
  8. FeedingGoal,
  9. Gender,
  10. type Pet,
  11. PetBodyType,
  12. PetType,
  13. } from '@/model/pet'
  14. import { defineStore } from 'pinia'
  15. export const useFeedingPlanStore = defineStore('feeding-plan', () => {
  16. const petTypeOptions = [
  17. { value: PetType.CAT, label: '猫猫' },
  18. { value: PetType.DOG, label: '狗狗' },
  19. ]
  20. const genderOptions = [
  21. { value: Gender.MALE, label: '男孩' },
  22. { value: Gender.FEMALE, label: '女孩' },
  23. ]
  24. const dailyCalories = ref(400)
  25. const pet = ref <CreatePetRequest>({
  26. birthday: new Date().toISOString().split('T')[0],
  27. bodyType: PetBodyType.IDEAL,
  28. gender: Gender.MALE,
  29. isActive: false,
  30. isLactation: false,
  31. isPregnant: false,
  32. isSterilization: false,
  33. name: '子龙',
  34. photo: '',
  35. type: PetType.CAT,
  36. weight: 0,
  37. })
  38. const savedPet = ref<Pet | null>(null)
  39. const petTags = computed(() => {
  40. return [
  41. { key: 'age', value: `${new Date().getFullYear() - new Date(pet.value.birthday).getFullYear()}岁` },
  42. { key: 'type', value: pet.value.type === PetType.CAT ? '猫猫' : '狗狗' },
  43. { key: 'weight', value: `${pet.value.weight}kg` },
  44. ]
  45. })
  46. const feedingPlan = ref<CreateFeedingPlanRequest>({
  47. feedingGoal: FeedingGoal.LOSE,
  48. targetWeight: pet.value.weight - 1,
  49. petId: savedPet.value?.id || '',
  50. products: [],
  51. })
  52. const feedingGoalOptions = [
  53. { value: FeedingGoal.GAIN, label: '增肥' },
  54. { value: FeedingGoal.LOSE, label: '减重' },
  55. { value: FeedingGoal.MAINTAIN, label: '保持' },
  56. ]
  57. const setPetValue = (key: keyof CreatePetRequest, value: any) => {
  58. // @ts-expect-error @ts-ignore
  59. pet.value[key] = (value as CreatePetRequest[keyof CreatePetRequest])
  60. }
  61. const persistentPet = async () => {
  62. savedPet.value = await createPet({
  63. ...pet.value,
  64. })
  65. }
  66. /**
  67. * 用户选中的商品列表,每个含有 product 信息和 percentage 百分比
  68. */
  69. const selectedProducts = ref<FeedingPlanProduct[]>([])
  70. const arrangeDailyConsumeWeight = () => {
  71. selectedProducts.value.forEach((item) => {
  72. item.dailyUsageWeight = Math.floor((dailyCalories.value * item.percentage / 100) / item.product.totalCalories * item.product.totalWeight)
  73. })
  74. }
  75. /**
  76. * 将所有已选商品的 percentage【平分】为 100 / total,
  77. * 并用 Math.floor 取整后,将多余 remainder 逐个分配(防止总和小于 100)。
  78. */
  79. function recalculatePercentage() {
  80. const total = selectedProducts.value.length
  81. if (total === 0)
  82. return
  83. // 1. 先把 100 整除
  84. const base = Math.floor(100 / total) // 每个至少分到多少
  85. const remainder = 100 - base * total // 还剩多少未分配
  86. // 2. 所有人先设置为 base
  87. selectedProducts.value.forEach((item) => {
  88. item.percentage = base
  89. })
  90. // 3. 余数 remainder 逐个+1 分配给前 remainder 个
  91. // 如果你不在意 1% 的误差,可以省略这一步
  92. for (let i = 0; i < remainder; i++) {
  93. selectedProducts.value[i].percentage += 1
  94. }
  95. }
  96. /**
  97. * 添加一个新商品
  98. * - 如果已存在则直接 return
  99. * - 如果不存在则 push 到 selectedProducts,并平均分配
  100. */
  101. function addProductToSelected(product: Product) {
  102. // 如果已存在,不重复添加
  103. if (selectedProducts.value.some(item => item.product.id === product.id)) {
  104. return
  105. }
  106. // 新商品默认 0%(稍后 re calc 会统一分配)
  107. selectedProducts.value.push({
  108. product,
  109. percentage: 0,
  110. dailyUsageWeight: 0,
  111. })
  112. // 重新平分所有商品的比例
  113. recalculatePercentage()
  114. arrangeDailyConsumeWeight()
  115. }
  116. /**
  117. * 给除 exceptId 之外的商品,分摊 -delta;
  118. * 也就是说,如果 delta 是正,其他商品就要减掉 delta;如果 delta 是负,其他商品就要加上 -delta。
  119. *
  120. * 注意:这里用 `-=` 实现把 diff 分摊给其他产品
  121. */
  122. function modifyPercentageExceptId(exceptId: string, delta: number) {
  123. selectedProducts.value.forEach((item) => {
  124. if (item.product.id !== exceptId) {
  125. item.percentage -= delta
  126. }
  127. })
  128. }
  129. /**
  130. * 修改指定 productId 的 percentage
  131. * @param productId 产品的id
  132. * @param newVal 用户想要设置的新值 (0~100)
  133. */
  134. function changePercentage(productId: string, newVal: number) {
  135. const product = selectedProducts.value.find(item => item.product.id === productId)
  136. if (product) {
  137. const oldVal = product.percentage
  138. const diff = newVal - oldVal
  139. // 先更新本产品的新值
  140. product.percentage = newVal
  141. // 计算需要从(或给)其他商品分配的增量
  142. // total-1 防止只有一个商品时出现除以0
  143. const total = selectedProducts.value.length
  144. if (total > 1) {
  145. const deltaPerProduct = diff / (total - 1)
  146. // 其他商品分摊 -deltaPerProduct (实际在函数里 -= deltaPerProduct)
  147. modifyPercentageExceptId(productId, deltaPerProduct)
  148. }
  149. arrangeDailyConsumeWeight()
  150. }
  151. }
  152. const confirm = async () => {
  153. if (savedPet.value === null) {
  154. return
  155. }
  156. feedingPlan.value.petId = savedPet.value.id
  157. feedingPlan.value.products = selectedProducts.value.map((item) => {
  158. return {
  159. id: item.product.id,
  160. dailyUsageWeight: item.dailyUsageWeight,
  161. }
  162. })
  163. await createFeedingPlan(feedingPlan.value)
  164. }
  165. return {
  166. pet,
  167. petTypeOptions,
  168. genderOptions,
  169. setPetValue,
  170. persistentPet,
  171. petTags,
  172. feedingPlan,
  173. feedingGoalOptions,
  174. selectedProducts,
  175. addProductToSelected,
  176. changePercentage,
  177. dailyCalories,
  178. confirm,
  179. }
  180. })