cart.js 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236
  1. import { defineStore } from 'pinia'
  2. import { computed, ref } from 'vue'
  3. import { TOKEN_KEY } from '@/config/api'
  4. import { request } from '@/utils/request'
  5. function upsertCartItemLocal(list, incoming) {
  6. const next = [...list]
  7. const byId = next.findIndex((i) => i.id === incoming.id)
  8. if (byId >= 0) {
  9. next[byId] = incoming
  10. return next
  11. }
  12. const byProduct = next.findIndex((i) => i.productId === incoming.productId)
  13. if (byProduct >= 0) {
  14. next[byProduct] = incoming
  15. return next
  16. }
  17. next.unshift(incoming)
  18. return next
  19. }
  20. function summaryFromItems(list) {
  21. const lineCount = list.length
  22. const totalQuantity = list.reduce((sum, item) => sum + (Number(item.quantity) || 0), 0)
  23. const totalAmount = list.reduce((sum, item) => sum + (Number(item.amount) || 0), 0)
  24. return { lineCount, totalQuantity, totalAmount }
  25. }
  26. export const useCartStore = defineStore('cart', () => {
  27. const items = ref([])
  28. const summary = ref({ lineCount: 0, totalQuantity: 0, totalAmount: 0 })
  29. const isLoading = ref(false)
  30. const addingProductIds = ref(new Set())
  31. let summarySyncTimer = null
  32. const totalAmount = computed(() => summary.value.totalAmount)
  33. const totalCount = computed(() => summary.value.totalQuantity)
  34. const isEmpty = computed(() => summary.value.lineCount === 0)
  35. const itemsByCategory = computed(() => {
  36. const grouped = {}
  37. items.value.forEach((item) => {
  38. if (!grouped[item.categoryName]) grouped[item.categoryName] = []
  39. grouped[item.categoryName].push(item)
  40. })
  41. return grouped
  42. })
  43. async function fetchCartSummary() {
  44. const token = uni.getStorageSync(TOKEN_KEY)
  45. if (!token) return
  46. try {
  47. const res = await request({ url: '/mini/client/cart/summary', method: 'GET' })
  48. const result = res.data
  49. if (result.code === 200 && result.data) {
  50. const d = result.data
  51. summary.value = {
  52. lineCount: Number(d.lineCount) || 0,
  53. totalQuantity: Number(d.totalQuantity) || 0,
  54. totalAmount: Number(d.totalAmount) || 0
  55. }
  56. }
  57. } catch (error) {
  58. console.error('获取购物车汇总失败:', error)
  59. }
  60. }
  61. function scheduleSummarySync(delay = 500) {
  62. if (summarySyncTimer) clearTimeout(summarySyncTimer)
  63. summarySyncTimer = setTimeout(() => {
  64. void fetchCartSummary()
  65. summarySyncTimer = null
  66. }, delay)
  67. }
  68. async function fetchCart() {
  69. const token = uni.getStorageSync(TOKEN_KEY)
  70. if (!token) return
  71. isLoading.value = true
  72. try {
  73. const res = await request({ url: '/mini/client/cart', method: 'GET' })
  74. const result = res.data
  75. if (result.code === 200) {
  76. const list = (result.data && result.data.list) || []
  77. items.value = list
  78. summary.value = summaryFromItems(list)
  79. }
  80. } catch (error) {
  81. console.error('获取购物车失败:', error)
  82. } finally {
  83. isLoading.value = false
  84. }
  85. }
  86. async function addToCart(product, quantity = 1) {
  87. const token = uni.getStorageSync(TOKEN_KEY)
  88. if (!token) {
  89. uni.showToast({ title: '请先登录', icon: 'none' })
  90. return false
  91. }
  92. const productId = String((product && product.id) || '')
  93. if (!productId) {
  94. uni.showToast({ title: '商品信息无效', icon: 'none' })
  95. return false
  96. }
  97. if (addingProductIds.value.has(productId)) return false
  98. addingProductIds.value.add(productId)
  99. try {
  100. const res = await request({
  101. url: '/mini/client/cart',
  102. method: 'POST',
  103. data: { productId, quantity }
  104. })
  105. const result = res.data
  106. if (result.code === 200) {
  107. if (result.data) {
  108. items.value = upsertCartItemLocal(items.value, result.data)
  109. summary.value = summaryFromItems(items.value)
  110. }
  111. scheduleSummarySync()
  112. uni.showToast({ title: '已加入购物车', icon: 'success' })
  113. return true
  114. }
  115. uni.showToast({ title: result.message || '添加失败', icon: 'none' })
  116. return false
  117. } catch (error) {
  118. uni.showToast({ title: (error && error.message) || '添加失败', icon: 'none' })
  119. return false
  120. } finally {
  121. addingProductIds.value.delete(productId)
  122. }
  123. }
  124. async function updateQuantity(cartId, quantity) {
  125. const token = uni.getStorageSync(TOKEN_KEY)
  126. if (!token) return false
  127. try {
  128. const res = await request({
  129. url: `/mini/client/cart/${cartId}`,
  130. method: 'PUT',
  131. data: { quantity }
  132. })
  133. const result = res.data
  134. if (result.code === 200 && result.data) {
  135. const d = result.data
  136. if (Array.isArray(d.list)) {
  137. items.value = d.list
  138. summary.value = summaryFromItems(d.list)
  139. } else {
  140. items.value = upsertCartItemLocal(items.value, result.data)
  141. summary.value = summaryFromItems(items.value)
  142. }
  143. scheduleSummarySync()
  144. return true
  145. }
  146. return result.code === 200
  147. } catch {
  148. return false
  149. }
  150. }
  151. async function removeItem(cartId) {
  152. const token = uni.getStorageSync(TOKEN_KEY)
  153. if (!token) return false
  154. try {
  155. const res = await request({ url: `/mini/client/cart/${cartId}`, method: 'DELETE' })
  156. const result = res.data
  157. if (result.code === 200) {
  158. items.value = items.value.filter((i) => i.id !== cartId)
  159. summary.value = summaryFromItems(items.value)
  160. scheduleSummarySync()
  161. return true
  162. }
  163. return false
  164. } catch {
  165. return false
  166. }
  167. }
  168. async function clearCart() {
  169. const token = uni.getStorageSync(TOKEN_KEY)
  170. if (!token) return false
  171. try {
  172. const res = await request({ url: '/mini/client/cart', method: 'DELETE' })
  173. const result = res.data
  174. if (result.code === 200) {
  175. items.value = []
  176. summary.value = { lineCount: 0, totalQuantity: 0, totalAmount: 0 }
  177. scheduleSummarySync()
  178. return true
  179. }
  180. return false
  181. } catch {
  182. return false
  183. }
  184. }
  185. async function batchRemove(cartIds) {
  186. const token = uni.getStorageSync(TOKEN_KEY)
  187. if (!token) return false
  188. try {
  189. const res = await request({
  190. url: '/mini/client/cart/batch',
  191. method: 'DELETE',
  192. data: { cartIds }
  193. })
  194. const result = res.data
  195. if (result.code === 200) {
  196. items.value = items.value.filter((i) => !cartIds.includes(i.id))
  197. summary.value = summaryFromItems(items.value)
  198. scheduleSummarySync()
  199. return true
  200. }
  201. return false
  202. } catch {
  203. return false
  204. }
  205. }
  206. return {
  207. items,
  208. summary,
  209. isLoading,
  210. totalAmount,
  211. totalCount,
  212. isEmpty,
  213. itemsByCategory,
  214. fetchCart,
  215. fetchCartSummary,
  216. addToCart,
  217. updateQuantity,
  218. removeItem,
  219. clearCart,
  220. batchRemove
  221. }
  222. })