cart.vue 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471
  1. <template>
  2. <view class="cart">
  3. <tui-skeleton v-if="isRequest" :loadingType="3" :isLoading="true"></tui-skeleton>
  4. <!-- 购物车列表为空 -->
  5. <template v-if="shopList.length === 0 && expiredProducts.length === 0">
  6. <tui-no-data :imgUrl="staticUrl + 'icon-empty-cart.png'" :imgHeight="230" :imgWidth="290">
  7. <view class="empty-tip">购物车空空的,快去逛逛吧~</view>
  8. </tui-no-data>
  9. </template>
  10. <template v-else>
  11. <!-- 顶部 -->
  12. <template v-if="!isDeleted">
  13. <view class="sticky-top">
  14. <view class="goods-total">
  15. <view class="total">共{{ kindCount }}件商品</view>
  16. <tui-button
  17. :size="24"
  18. width="88rpx"
  19. height="42rpx"
  20. shape="circle"
  21. :plain="true"
  22. type="base"
  23. @click="isDeleted = true"
  24. >
  25. 删除
  26. </tui-button>
  27. </view>
  28. <!-- <view class="receive-coupon">
  29. <view class="tip-text" v-text="couponTipText"></view>
  30. <tui-button
  31. :size="24"
  32. width="88rpx"
  33. height="42rpx"
  34. shape="circle"
  35. type="base"
  36. @click="visiable = true"
  37. >
  38. 领券
  39. </tui-button>
  40. </view> -->
  41. </view>
  42. </template>
  43. <view class="list-content">
  44. <!-- 有效商品 -->
  45. <view class="supplier-area" v-for="item in shopList" :key="item.shopId">
  46. <cm-cart-supplier-area
  47. :shopInfo="item"
  48. :isDeleted="isDeleted"
  49. @change="checkedProductChange"
  50. @countChange="onCountChange"
  51. @unitChange="onUnitChange"
  52. ref="supplierArea"
  53. ></cm-cart-supplier-area>
  54. </view>
  55. <!-- 失效商品 -->
  56. <view class="supplier-area" v-if="expiredProducts.length > 0">
  57. <cm-cart-expired-area
  58. :expiredList="expiredProducts"
  59. @clear="removeExpModal = true"
  60. ></cm-cart-expired-area>
  61. </view>
  62. </view>
  63. <view style="height: 100rpx;"></view>
  64. <!-- 购物车导航 -->
  65. <cm-cart-navbar
  66. :isCheckedAll="isChekedAll"
  67. :isDeleted="isDeleted"
  68. :data="navbarData"
  69. @all="onCheckedAll"
  70. @submit="onSumit"
  71. @cancel="isDeleted = false"
  72. @remove="removeModal = true"
  73. ></cm-cart-navbar>
  74. </template>
  75. <!-- 优惠券列表 -->
  76. <cm-coupon-popup
  77. :visiable="visiable"
  78. :list="couponList"
  79. :couponTabs="couponTabs"
  80. :hasTabs="true"
  81. :hasConfirm="false"
  82. :hasSafeArea="false"
  83. @close="visiable = false"
  84. @change="onCouponChange"
  85. @couponClick="onCouponClick"
  86. ></cm-coupon-popup>
  87. <!-- 确认弹框 -->
  88. <tui-modal :show="removeModal" content="确认删除选中的商品吗?" @click="onConfirmRemove"></tui-modal>
  89. <!-- 清空失效商品确认 -->
  90. <tui-modal :show="removeExpModal" content="确认清空失效商品?" @click="onConfirmRemoveExp"></tui-modal>
  91. <!-- 重新选择商品规格 -->
  92. <cm-goods-buy-popup
  93. v-model="goodsBuyPopup"
  94. :product="productInfo"
  95. :couponList="goodsCouponList"
  96. :useType="useType"
  97. :buttonType="buttonType"
  98. @confirm="onSaveNewUnit"
  99. @activityClick="handleShowActivity"
  100. v-if="productInfo"
  101. ></cm-goods-buy-popup>
  102. <!-- 活动价 -->
  103. <cm-goods-activity-popup
  104. ref="activitypPopup"
  105. :ladderList="ladderPriceList"
  106. @open="$refs.receiveBuyPopup.close()"
  107. @close="$refs.receiveBuyPopup.open()"
  108. ></cm-goods-activity-popup>
  109. </view>
  110. </template>
  111. <script>
  112. import { fetchCartInfo } from '@/services/api/cart.js'
  113. import { fetchCouponListByProductIds } from '@/services/api/coupon.js'
  114. import { arrayUnique } from '@/common/utils.js'
  115. import { mapGetters, mapActions } from 'vuex'
  116. import CouponUtils from '@/common/couponUtils.js'
  117. import { fetchPorductInfo, fetchCouponListByProduct } from '@/common/goods.helper.js'
  118. import {
  119. totalAllCheckedProduct,
  120. computeTotalPrice,
  121. initFormatCouponList,
  122. makeCouponUseTip
  123. } from '@/common/business.helper.js'
  124. const resetData = () => ({
  125. isRequest: true,
  126. tabLoading: false,
  127. isDeleted: false,
  128. expiredProducts: [], // 失效商品列表
  129. shopList: [], // 供应商&&商品列表
  130. checkedShopMap: {},
  131. isChekedAll: false,
  132. removeModal: false,
  133. removeExpModal: false,
  134. // 商品价格
  135. checkedProductList: [],
  136. allPrice: 0, // 商品总价
  137. // 优惠券列表
  138. visiable: false,
  139. currentTab: 0,
  140. receiveCouponList: [], // 已领取优惠券
  141. ableCouponList: [], // 可领取优惠券
  142. couponTabs: [],
  143. currentCoupon: null,
  144. nextCoupon: null,
  145. couponTipText: '', // 可用优惠券提示
  146. // 保存变动
  147. changeRef: null,
  148. // 选择商品规格
  149. cartId: 0,
  150. goodsBuyPopup: false,
  151. useType: 'cart',
  152. buttonType: 'left',
  153. goodsCouponList: [],
  154. productInfo: null,
  155. ladderPriceList: []
  156. })
  157. export default {
  158. data() {
  159. return resetData()
  160. },
  161. computed: {
  162. ...mapGetters(['userId', 'kindCount']),
  163. couponList() {
  164. return this.currentTab === 0 ? this.receiveCouponList : this.ableCouponList
  165. },
  166. navbarData() {
  167. const result = {
  168. finallyPrice: 0, // 实际应付
  169. couponAmount: 0,
  170. allPrice: 0,
  171. discountedPrice: 0,
  172. count: 0
  173. }
  174. if (this.currentCoupon) {
  175. result.couponAmount = this.currentCoupon.couponAmount
  176. result.discountedPrice = this.currentCoupon.couponAmount
  177. }
  178. result.allPrice = this.allPrice
  179. if (this.allPrice - result.discountedPrice > 0) {
  180. result.finallyPrice = this.allPrice - result.couponAmount
  181. }
  182. result.count = this.checkedProductList.length
  183. return result
  184. }
  185. },
  186. onPullDownRefresh() {
  187. this.initPage()
  188. },
  189. onShow() {
  190. this.initPage()
  191. },
  192. methods: {
  193. ...mapActions('cart', ['removeFromCart', 'fetchCartKindCount']),
  194. // 页面初始化
  195. initPage() {
  196. this.resetData()
  197. this.fetchCartInfo()
  198. this.fetchCartKindCount()
  199. },
  200. // 初始化数据
  201. resetData() {
  202. const data = resetData()
  203. for (let key in data) {
  204. this[key] = data[key]
  205. }
  206. },
  207. // 确认清空失效商品
  208. async onConfirmRemoveExp(e) {
  209. if (!e.index) return
  210. try {
  211. await this.removeFromCart(this.expiredProducts.map(product => product.cartId))
  212. this.resetData()
  213. this.fetchCartInfo()
  214. } catch (e) {
  215. console.log('删除失效商品失败')
  216. } finally {
  217. this.removeExpModal = false
  218. }
  219. },
  220. // 提交订单
  221. onSumit() {
  222. // return uni.navigateTo({
  223. // url:'/pages/views/goods/Update-Maintenance'
  224. // })
  225. if (this.checkedProductList.length <= 0) {
  226. return this.$toast.error('请选择商品')
  227. }
  228. const params = {}
  229. params.allPrice = this.allPrice
  230. params.allCount = this.checkedProductList.length
  231. params.cartIds = this.checkedProductList.map(product => product.cartId)
  232. // params.skuIds = this.checkedProductList.map(product => product.skuId)
  233. params.productIds = this.checkedProductList.map(product => product.productId)
  234. params.productCount = ''
  235. uni.setStorageSync('COMMIT_CART_INFO', params)
  236. this.$router.navigateTo('order/order-create')
  237. },
  238. // 优惠券列表类型更换
  239. onCouponChange(index) {
  240. this.currentTab = index
  241. },
  242. // 优惠券点击事件
  243. onCouponClick(coupon) {
  244. if (coupon.controlType === 'receive') {
  245. this.fetchCouponList()
  246. } else {
  247. this.visiable = false
  248. }
  249. },
  250. // 设置couponTab数据
  251. makeCouponTabs() {
  252. return [
  253. {
  254. name: '已领取优惠券',
  255. num: this.receiveCouponList.length,
  256. isDot: false,
  257. disabled: false
  258. },
  259. {
  260. name: '可领取优惠券',
  261. num: this.ableCouponList.length,
  262. isDot: false,
  263. disabled: false
  264. }
  265. ]
  266. },
  267. // 获取购物车商品可用优惠券
  268. async fetchCouponList() {
  269. const productIds = []
  270. this.shopList.forEach(shop => shop.productList.forEach(product => productIds.push(product.productId)))
  271. try {
  272. const res = await fetchCouponListByProductIds({ userId: this.userId, productIds: productIds.join(',') })
  273. this.receiveCouponList = initFormatCouponList(res.data.receiveCouponList, 'search', true)
  274. this.ableCouponList = initFormatCouponList(res.data.ableCouponList, 'receive', true)
  275. this.couponTabs = this.makeCouponTabs()
  276. } catch (e) {
  277. console.log(e)
  278. } finally {
  279. this.isRequest = false
  280. }
  281. },
  282. // 获取购物车信息
  283. async fetchCartInfo() {
  284. try {
  285. const res = await fetchCartInfo({ userId: this.userId })
  286. this.expiredProducts = res.data.products
  287. this.shopList = res.data.shopList
  288. if (this.shopList.length > 0) {
  289. // 获取优惠券列表
  290. this.fetchCouponList()
  291. }
  292. this.isRequest = false
  293. return res
  294. } catch (e) {
  295. return e
  296. } finally {
  297. uni.stopPullDownRefresh()
  298. }
  299. },
  300. // 确认删除
  301. async onConfirmRemove({ index }) {
  302. if (!index) return (this.removeModal = false)
  303. const cartIds = this.checkedProductList.map(product => product.cartId)
  304. try {
  305. await this.removeFromCart(cartIds)
  306. this.fetchCartInfo()
  307. } catch (e) {
  308. console.log(e)
  309. } finally {
  310. this.isDeleted = false
  311. this.removeModal = false
  312. }
  313. },
  314. // 选中/取消全部商品
  315. onCheckedAll() {
  316. if (this.isChekedAll) {
  317. this.$refs.supplierArea.forEach(item => item.unSelectAll())
  318. } else {
  319. this.$refs.supplierArea.forEach(item => item.selectAll())
  320. }
  321. },
  322. // 选中商品变化
  323. checkedProductChange(e) {
  324. this.changeRef = e
  325. this.handleCartManager()
  326. },
  327. // 修改商品checked属性
  328. updatePorudctChecked(row) {
  329. const shopInfo = this.shopList.find(item => row.shopId === item.shopId)
  330. if (!shopInfo) return
  331. shopInfo.productList.forEach(item => {
  332. if (row.checkedList.includes(item.skuId.toString())) {
  333. this.$set(item, 'checked', true)
  334. } else {
  335. this.$set(item, 'checked', false)
  336. }
  337. })
  338. },
  339. // 购物车数据处理器
  340. handleCartManager() {
  341. if (!this.changeRef) return
  342. this.updatePorudctChecked(this.changeRef)
  343. // 保存选中商品id与供应商id的k-v关系
  344. this.$set(this.checkedShopMap, this.changeRef.shopId, this.changeRef.checkedList)
  345. this.isChekedAll = this.validateIsChekedAll()
  346. // 获取已选商品列表
  347. this.checkedProductList = totalAllCheckedProduct(this.shopList)
  348. // 计算商品总价
  349. this.allPrice = computeTotalPrice(this.checkedProductList)
  350. const couponUtils = new CouponUtils(this.receiveCouponList, this.checkedProductList)
  351. this.currentCoupon = couponUtils.bestCoupon
  352. this.nextCoupon = couponUtils.secondCoupon
  353. this.couponTipText = makeCouponUseTip(this.currentCoupon, this.nextCoupon, this.allPrice)
  354. },
  355. // 商品数量变化
  356. async onCountChange(e) {
  357. await this.$store.dispatch('cart/updateProductCount', {
  358. cartId: e.cartId,
  359. productCount: e.count
  360. })
  361. await this.fetchCartInfo()
  362. this.handleCartManager()
  363. },
  364. // 商品规格修改
  365. async onUnitChange(e) {
  366. this.cartId = e.cartId
  367. uni.showLoading({ title: '加载中...' })
  368. this.productInfo = await fetchPorductInfo(e.productId)
  369. this.goodsCouponList = await fetchCouponListByProduct(e.productId)
  370. setTimeout(() => {
  371. this.goodsBuyPopup = true
  372. uni.hideLoading()
  373. }, 1000)
  374. },
  375. // 活动价弹窗
  376. handleShowActivity(current) {
  377. if (!current.ladderPriceList) return
  378. this.ladderPriceList = current.ladderPriceList
  379. this.$refs.receiveBuyPopup.open()
  380. },
  381. // 保存商品规格
  382. async onSaveNewUnit(e) {
  383. try {
  384. console.log(e)
  385. await this.$store.dispatch('cart/updateProductUnit', {
  386. newSkuId: e.sku.skuId,
  387. cartId: this.cartId,
  388. productCount: e.count
  389. })
  390. this.$toast.success('规格修改成功')
  391. this.initPage()
  392. } catch (e) {
  393. console.log(e)
  394. this.$toast.error('修改规格失败')
  395. }
  396. },
  397. // 验证是否全选
  398. validateIsChekedAll() {
  399. return this.shopList.every(item => {
  400. const checkedList = this.checkedShopMap[item.shopId] || []
  401. return item.productList.filter(item => item.stock !== 0 || this.isDeleted).length === checkedList.length
  402. })
  403. }
  404. }
  405. }
  406. </script>
  407. <style scoped lang="scss">
  408. .goods-total {
  409. @extend .cm-flex-between;
  410. background: #f7f7f7;
  411. padding: 0 24rpx;
  412. height: 80rpx;
  413. .total {
  414. font-size: 30rpx;
  415. color: #333;
  416. }
  417. }
  418. .receive-coupon {
  419. @extend .cm-flex-between;
  420. background: #fff;
  421. padding: 0 24rpx;
  422. height: 80rpx;
  423. .tip-text {
  424. font-size: 26rpx;
  425. color: #ff457b;
  426. white-space: nowrap;
  427. overflow: hidden;
  428. text-overflow: ellipsis;
  429. }
  430. }
  431. .supplier-area {
  432. margin-bottom: 24rpx;
  433. }
  434. </style>