cart.vue 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604
  1. <template>
  2. <view class="content">
  3. <!-- 商品列表 -->
  4. <mescroll-body ref="mescrollRef" @init="mescrollInit" @down="downCallback" @up="upCallback" :down="downOption"
  5. :up="upOption">
  6. <view class="goods-list">
  7. <view class="item" v-for="(item,index) in carts" :key="index">
  8. <view class="choose" @click.stop="checkChange(item,index)">
  9. <image src="@/static/images/integral/sign_in_on_icon.png" v-show="item.checked"></image>
  10. <image src="https://hdtobs.obs.cn-north-4.myhuaweicloud.com/fs/20250729/1753758821601.png"
  11. v-show="!item.checked"></image>
  12. </view>
  13. <image class="goods-img" :src="item.imgUrl" mode="aspectFit" @click="showProduct(item)"></image>
  14. <view class="info-box">
  15. <view>
  16. <view class="title-box">
  17. <view class="title ellipsis">{{ item.goodsName }}</view>
  18. </view>
  19. </view>
  20. <view class="price-num">
  21. <view class="price">
  22. <text class="text">{{item.goodsIntegral}}</text>
  23. <text class="unit">健康币</text>
  24. <!-- <text class="unit">+</text> -->
  25. <text class="oldprice">¥{{item.otPrice.toFixed(2)}}</text>
  26. </view>
  27. <view class="num-box" @click.stop>
  28. <u-number-box v-model="item.quantity" buttonSize="48rpx" integer :step="1" :min="1"
  29. :max="100000" @input="val => changeCartNum(val, item)"></u-number-box>
  30. </view>
  31. </view>
  32. </view>
  33. </view>
  34. <!-- 自定义空数据 -->
  35. <view v-if="carts.length === 0 && isPostBack" class="no-data-box">
  36. <image src="https://cos.his.cdwjyyh.com/fs/20240423/cf4a86b913a04341bb44e34bb4d37aa2.png"
  37. mode="aspectFit"></image>
  38. <view class="empty-title">购物车为空</view>
  39. <view class="empty-cart-btn x-f" @click="goPage">去逛逛</view>
  40. </view>
  41. </view>
  42. <view style="height: 140rpx;"></view>
  43. </mescroll-body>
  44. <!-- 底部按钮 -->
  45. <view class="btn-foot">
  46. <view class="left">
  47. <view class="choose" @click="handleCheckAll()">
  48. <image src="@/static/images/integral/sign_in_on_icon.png" v-show="checkAll"></image>
  49. <image src="https://hdtobs.obs.cn-north-4.myhuaweicloud.com/fs/20250729/1753758821601.png"
  50. v-show="!checkAll"></image>
  51. </view>
  52. <text class="text">全选</text>
  53. <text class="text" @click="delCart()">删除</text>
  54. </view>
  55. <view class="right">
  56. <!-- <view> -->
  57. <!-- <view class="total">
  58. <text class="label">合计:</text>
  59. <view class="price">
  60. <text class="unit">¥</text>
  61. <text class="num">{{totalMoney.toFixed(2)}}</text>
  62. </view>
  63. </view> -->
  64. <view class="total">
  65. <text class="label">所需健康币</text>
  66. <view class="price">
  67. <text class="num">{{totalIntegral}}</text>
  68. </view>
  69. </view>
  70. <!-- </view> -->
  71. <view class="btn" @click="submit">结算</view>
  72. </view>
  73. </view>
  74. </view>
  75. </template>
  76. <script>
  77. import {
  78. getFsIntegralCartList,
  79. deleteCart,
  80. putGoodsQuantityFromCart
  81. } from '@/api/integral.js'
  82. // import likeProduct from '@/components/likeProduct.vue'
  83. export default {
  84. // components: {
  85. // likeProduct
  86. // },
  87. data() {
  88. return {
  89. totalMoney: 0.00,
  90. totalIntegral: 0,
  91. carts: [],
  92. checkAll: false,
  93. changeNumTimer: null, // 防抖定时器
  94. isPostBack: false, // 数据是否已加载完成
  95. downOption: {
  96. auto: false //不要自动加载
  97. },
  98. // 上拉加载的配置
  99. upOption: {
  100. onScroll: false,
  101. use: true, // 是否启用上拉加载; 默认true
  102. page: {
  103. pae: 0, // 当前页码,默认0,回调之前会加1,即callback(page)会从1开始
  104. size: 10 // 每页数据的数量,默认10
  105. },
  106. noMoreSize: 10, // 配置列表的总数量要大于等于5条才显示'-- END --'的提示
  107. textNoMore: "已经到底了",
  108. empty: {
  109. icon: '', // 隐藏默认图标
  110. tip: '' // 隐藏默认提示,使用自定义空数据
  111. }
  112. },
  113. }
  114. },
  115. onLoad() {
  116. // this.getCarts();
  117. },
  118. onShow() {
  119. //this.getCarts();
  120. },
  121. onReachBottom() {
  122. // this.$refs.product.getGoodsProducts();
  123. },
  124. onUnload() {
  125. // 组件销毁时清理定时器
  126. if (this.changeNumTimer) {
  127. clearTimeout(this.changeNumTimer);
  128. this.changeNumTimer = null;
  129. }
  130. },
  131. methods: {
  132. mescrollInit(mescroll) {
  133. this.mescroll = mescroll;
  134. },
  135. /*下拉刷新的回调 */
  136. downCallback(mescroll) {
  137. mescroll.resetUpScroll()
  138. },
  139. upCallback(page) {
  140. //联网加载数据
  141. var that = this;
  142. var data = {
  143. goodsName: this.searchKey,
  144. pageNum: page.num,
  145. pageSize: page.size
  146. };
  147. getFsIntegralCartList(data).then(res => {
  148. if (res.code == 200) {
  149. // 标记数据已加载
  150. this.isPostBack = true;
  151. this.carts = res.data.list;
  152. this.carts.forEach((item, index, arr) => {
  153. item.checked = false;
  154. })
  155. this.checkAll = this.carts.length > 0 && this.carts.every(item => item.checked)
  156. this.computedMoney();
  157. //设置列表数据
  158. if (page.num == 1) {
  159. that.cartst = res.data.list;
  160. } else {
  161. that.carts = that.carts.concat(res.data.list);
  162. }
  163. that.mescroll.endBySize(res.data.list.length, res.data.total);
  164. } else {
  165. uni.showToast({
  166. icon: 'none',
  167. title: "请求失败",
  168. });
  169. that.dataList = null;
  170. that.mescroll.endErr();
  171. }
  172. });
  173. },
  174. delCart() {
  175. var selectCarts = this.carts.filter(ele => ele.checked == true).map(ele => {
  176. return ele.cartId
  177. });
  178. if (selectCarts.length == 0) {
  179. uni.showToast({
  180. icon: 'none',
  181. title: "请选择商品删除",
  182. });
  183. return;
  184. }
  185. // 将数组转换为逗号拼接的字符串
  186. const cartIds = selectCarts.join(',');
  187. deleteCart(cartIds).then(
  188. res => {
  189. if (res.code == 200) {
  190. uni.showToast({
  191. icon: 'success',
  192. title: "操作成功",
  193. });
  194. this.mescroll.resetUpScroll()
  195. } else {
  196. uni.showToast({
  197. icon: 'none',
  198. title: res.msg || '删除失败',
  199. });
  200. }
  201. },
  202. rej => {
  203. uni.showToast({
  204. icon: 'none',
  205. title: rej.msg || '网络错误,请稍后重试',
  206. });
  207. }
  208. );
  209. },
  210. computedMoney() {
  211. let totalIntegral = 0;
  212. let totalMoney = 0;
  213. let arry = this.carts.filter(item => item.checked)
  214. arry.forEach(item => {
  215. totalIntegral += item.goodsIntegral * item.quantity;
  216. totalMoney += item.otPrice * item.quantity;
  217. });
  218. this.totalIntegral = totalIntegral;
  219. this.totalMoney = totalMoney;
  220. },
  221. handleCheckAll() {
  222. this.checkAll = !this.checkAll;
  223. var that = this;
  224. this.carts.forEach((item, index, arr) => {
  225. item.checked = that.checkAll;
  226. })
  227. this.computedMoney();
  228. },
  229. checkChange(item, index) {
  230. item.checked = !item.checked;
  231. this.checkAll = this.carts.length > 0 && this.carts.every(item => item.checked)
  232. this.computedMoney();
  233. },
  234. changeCartNum(val, item) {
  235. // 清除之前的定时器(防抖处理)
  236. if (this.changeNumTimer) {
  237. clearTimeout(this.changeNumTimer);
  238. }
  239. // 保存原始数量
  240. const originalNum = item.quantity;
  241. const maxQuantity = item.stock || 100000;
  242. // 验证并限制数量范围
  243. const newQuantity = Math.max(1, Math.min(parseInt(val) || 1, maxQuantity));
  244. // 显示提示
  245. if (newQuantity !== parseInt(val)) {
  246. uni.showToast({
  247. icon: 'none',
  248. title: newQuantity === 1 ? '数量不能小于1' : `库存不足,最多只能购买${maxQuantity}件`,
  249. duration: 2000
  250. });
  251. }
  252. // 更新本地UI并计算总积分
  253. item.quantity = newQuantity;
  254. this.computedMoney();
  255. // 防抖处理:延迟300ms执行API请求
  256. this.changeNumTimer = setTimeout(() => {
  257. if (!item.cartId) {
  258. item.quantity = originalNum;
  259. this.computedMoney();
  260. this.$forceUpdate();
  261. uni.showToast({
  262. icon: 'none',
  263. title: '购物车商品信息异常',
  264. duration: 2000
  265. });
  266. return;
  267. }
  268. putGoodsQuantityFromCart({
  269. cartId: item.cartId,
  270. quantity: item.quantity
  271. }).then(
  272. res => {
  273. if (res.code == 200) {
  274. // 同步服务器返回的数据
  275. if (res.data) {
  276. res.data.quantity !== undefined && (item.quantity = res.data.quantity);
  277. res.data.stock !== undefined && (item.stock = res.data.stock);
  278. }
  279. this.computedMoney();
  280. } else {
  281. // 失败时恢复原始数量
  282. item.quantity = originalNum;
  283. if (res.data?.stock !== undefined) {
  284. item.stock = res.data.stock;
  285. }
  286. this.computedMoney();
  287. this.$forceUpdate();
  288. uni.showToast({
  289. icon: 'none',
  290. title: res.msg || '操作失败',
  291. duration: 2000
  292. });
  293. }
  294. },
  295. err => {
  296. // 网络错误,恢复原始数量
  297. item.quantity = originalNum;
  298. this.computedMoney();
  299. this.$forceUpdate();
  300. uni.showToast({
  301. icon: 'none',
  302. title: err.msg || '网络错误,请稍后重试',
  303. duration: 2000
  304. });
  305. }
  306. );
  307. }, 300);
  308. },
  309. // 结算
  310. submit() {
  311. var selectCarts = this.carts.filter(ele => ele.checked == true).map(ele => {
  312. return ele.cartId
  313. });
  314. var selectGoods = this.carts.filter(ele => ele.checked == true).map(ele => {
  315. return ele.goodsId
  316. });
  317. if (selectCarts.length == 0) {
  318. uni.showToast({
  319. icon: 'none',
  320. title: "请选择商品",
  321. });
  322. return;
  323. }
  324. uni.navigateTo({
  325. url: '/pages/user/integral/integralOrderCartPay?cartIds=' + selectCarts
  326. })
  327. },
  328. showProduct(item) {
  329. console.log("这个item")
  330. uni.navigateTo({
  331. url: '/pages/user/integral/integralGoodsDetails?goodsId=' + item.goodsId
  332. })
  333. },
  334. goPage() {
  335. uni.navigateTo({
  336. url: '/pages/user/integral/integralGoodsList'
  337. })
  338. }
  339. }
  340. }
  341. </script>
  342. <style lang="scss">
  343. page {
  344. height: 100%;
  345. }
  346. .oldprice {
  347. font-size: 28rpx;
  348. font-family: PingFang SC;
  349. font-weight: 500;
  350. color: #999999;
  351. // margin-top: 27rpx;
  352. line-height: 1;
  353. text-decoration: line-through;
  354. margin-left: 24rpx;
  355. }
  356. .content {
  357. height: 100%;
  358. .goods-list {
  359. padding: 20upx;
  360. .item {
  361. box-sizing: border-box;
  362. background: #FFFFFF;
  363. border-radius: 16upx;
  364. margin-bottom: 20upx;
  365. padding: 30upx;
  366. display: flex;
  367. align-items: center;
  368. &:last-child {
  369. margin-bottom: 0;
  370. }
  371. .goods-img {
  372. width: 160upx;
  373. height: 160upx;
  374. background: #FFFFFF;
  375. margin-right: 30upx;
  376. flex-shrink: 0;
  377. }
  378. .info-box {
  379. display: flex;
  380. flex-direction: column;
  381. justify-content: space-between;
  382. width: calc(100% - 255upx);
  383. .title-box {
  384. width: 100%;
  385. display: flex;
  386. align-items: center;
  387. .title {
  388. flex: 1;
  389. font-size: 28upx;
  390. font-family: PingFang SC;
  391. font-weight: 500;
  392. color: #111111;
  393. line-height: 1;
  394. }
  395. }
  396. .price-num {
  397. margin-top: 24rpx;
  398. .price {
  399. margin-bottom: 24rpx;
  400. display: flex;
  401. align-items: flex-end;
  402. .unit {
  403. font-size: 24upx;
  404. font-family: PingFang SC;
  405. font-weight: 500;
  406. color: #FF5030;
  407. line-height: 1.2;
  408. margin-right: 4upx;
  409. }
  410. .text {
  411. font-size: 32upx;
  412. font-family: PingFang SC;
  413. font-weight: bold;
  414. color: #FF5030;
  415. line-height: 1;
  416. }
  417. }
  418. .num-box {
  419. display: flex;
  420. align-items: center;
  421. justify-content: flex-end;
  422. .img-box {
  423. width: 60upx;
  424. height: 60upx;
  425. // border-radius: 4upx;
  426. border: 1px solid #dddddd;
  427. display: flex;
  428. align-items: center;
  429. justify-content: center;
  430. image {
  431. width: 25rpx;
  432. height: 25rpx;
  433. }
  434. }
  435. input {
  436. width: 60upx;
  437. height: 60upx;
  438. line-height: 60upx;
  439. font-size: 28upx;
  440. font-family: PingFang SC;
  441. font-weight: 500;
  442. color: #111111;
  443. // border-radius: 4upx;
  444. border-top: 1px solid #dddddd;
  445. border-bottom: 1px solid #dddddd;
  446. text-align: center;
  447. // margin: 0 16upx;
  448. }
  449. }
  450. }
  451. }
  452. }
  453. }
  454. .like-product {
  455. padding-bottom: 120upx;
  456. }
  457. .btn-foot {
  458. box-sizing: border-box;
  459. width: 100%;
  460. height: 121upx;
  461. background: #FFFFFF;
  462. padding: 16upx 30upx 16upx 30upx;
  463. display: flex;
  464. align-items: center;
  465. justify-content: space-between;
  466. position: fixed;
  467. left: 0;
  468. bottom: 0;
  469. z-index: 99;
  470. .left {
  471. display: flex;
  472. align-items: center;
  473. .text {
  474. margin-left: 20upx;
  475. font-size: 28upx;
  476. font-family: PingFang SC;
  477. font-weight: 500;
  478. color: #666666;
  479. line-height: 1;
  480. }
  481. }
  482. .right {
  483. display: flex;
  484. align-items: center;
  485. .total {
  486. margin-right: 36upx;
  487. display: flex;
  488. flex-direction: column;
  489. align-items: flex-start;
  490. .label {
  491. font-size: 26upx;
  492. font-family: PingFang SC;
  493. font-weight: 500;
  494. color: #999999;
  495. line-height: 1.5;
  496. }
  497. .price {
  498. display: flex;
  499. align-items: flex-end;
  500. .unit {
  501. font-size: 24upx;
  502. font-family: PingFang SC;
  503. font-weight: bold;
  504. color: #FF5030;
  505. line-height: 1.2;
  506. margin-right: 10upx;
  507. }
  508. .num {
  509. font-size: 32rpx;
  510. font-family: PingFang SC;
  511. font-weight: bold;
  512. color: #FF5030;
  513. line-height: 1;
  514. }
  515. }
  516. }
  517. .btn {
  518. width: 200upx;
  519. height: 88upx;
  520. line-height: 88upx;
  521. text-align: center;
  522. font-size: 30upx;
  523. font-family: PingFang SC;
  524. font-weight: bold;
  525. color: #FFFFFF;
  526. background: #FF5030;
  527. border-radius: 44upx;
  528. }
  529. }
  530. }
  531. }
  532. .choose {
  533. margin-right: 20rpx;
  534. display: flex;
  535. align-items: center;
  536. justify-content: center;
  537. image {
  538. width: 36rpx;
  539. height: 36rpx;
  540. flex-shrink: 0;
  541. }
  542. }
  543. .no-data-box {
  544. width: 100%;
  545. padding: 100rpx 50rpx;
  546. text-align: center;
  547. display: flex;
  548. flex-direction: column;
  549. align-items: center;
  550. justify-content: center;
  551. min-height: 400rpx;
  552. }
  553. .empty-cart-btn {
  554. border: 1rpx solid #FF5030;
  555. color: #FF5030;
  556. padding: 10rpx 30rpx;
  557. border-radius: 40rpx;
  558. margin-top: 50rpx;
  559. display: inline-block;
  560. }
  561. </style>