index.vue 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645
  1. <template>
  2. <view>
  3. <view class="car_list">
  4. <view v-for="(pItem, pIndex) in cartListByGroup" :key="pIndex" style="margin-bottom: 20rpx">
  5. <view class="business_name" v-if="pItem.products.length !== 0">
  6. <image
  7. @click="selectBussiness(pIndex)"
  8. class="checkbox"
  9. :src="pItem.checked ? 'https://kailin-mp.oss-cn-shenzhen.aliyuncs.com/static/icon/checked.png' : 'https://kailin-mp.oss-cn-shenzhen.aliyuncs.com/static/icon/checkbox.png'"
  10. ></image
  11. >&nbsp;
  12. {{ pItem.business_name }}
  13. </view>
  14. <view class="product_group">
  15. <view v-for="(item, index) in pItem.products" @longpress="deleteCar(pIndex, index)" :key="index">
  16. <SwipeAction @clickItem="clickItem" :index="index" ref="swipeAction" :pIndex="pIndex">
  17. <view class="car_item">
  18. <view class="check_label" @click="checkedItem(pIndex, index, item.id)">
  19. <image
  20. class="checkbox"
  21. src="https://kailin-mp.oss-cn-shenzhen.aliyuncs.com/static/icon/checkbox_disabled.png"
  22. style="width: 30rpx; height: 30rpx"
  23. v-if="item.product_status !== 0 || item.stock == 0"
  24. />
  25. <image
  26. class="checkbox"
  27. :src="item.checked ? 'https://kailin-mp.oss-cn-shenzhen.aliyuncs.com/static/icon/checked.png' : 'https://kailin-mp.oss-cn-shenzhen.aliyuncs.com/static/icon/checkbox.png'"
  28. v-else
  29. ></image>
  30. </view>
  31. <view class="box_left">
  32. <navigator :url="'/pages/product/index?product_id=' + item.product_id">
  33. <image class="car_image" :src="item.thumb" mode=""></image>
  34. <view class="product_status" v-if="item.product_status !== 0">
  35. {{ item.stock == 0 ? "已售罄" : "已下架" }}
  36. </view>
  37. </navigator>
  38. </view>
  39. <view class="box_center">
  40. <navigator :url="'/pages/product/index?product_id=' + item.product_id" class="car_name">{{ item.name }}</navigator>
  41. <navigator :url="'/pages/product/index?product_id=' + item.product_id" class="car_spec">{{ item.spec }}</navigator>
  42. <view v-if="item.promo_title" class="promo_title">{{ item.promo_title }}</view>
  43. <navigator :url="'/pages/product/index?product_id=' + item.product_id" class="car_price">
  44. <text class="price">¥{{ item.price }}</text>
  45. <text class="market_price">¥{{ item.market_price }}</text>
  46. </navigator>
  47. </view>
  48. <view class="box_right">
  49. <view class="buy_num_box">
  50. <button class="buy_num_sub" @click="changeQuantity(pIndex, index, -1)" data-eventsync="true">
  51. <image class="sub_icon" src="https://kailin-mp.oss-cn-shenzhen.aliyuncs.com/static/icon/sub_icon.png" mode=""></image>
  52. </button>
  53. <input type="number" class="buy_num" placeholder="数量" v-model="item.buy_num" @blur="changeQuantity(pIndex, index, 0)" />
  54. <button class="buy_num_add" @click="changeQuantity(pIndex, index, +1)" data-eventsync="true">
  55. <image class="add_icon" src="https://kailin-mp.oss-cn-shenzhen.aliyuncs.com/static/icon/add_icon.png" mode=""></image>
  56. </button>
  57. </view>
  58. </view>
  59. </view>
  60. </SwipeAction>
  61. </view>
  62. </view>
  63. </view>
  64. </view>
  65. <Empty v-if="cartList.length == 0" text="----- 还没有产品啦 -----" />
  66. <view class="to_bottom" v-if="cartList.length"> -----到底啦-----</view>
  67. <view class="bottom_box">
  68. <view class="check_all_label" @click="checkAll()">
  69. <image
  70. class="checkbox"
  71. :src="checkedAll ? 'https://kailin-mp.oss-cn-shenzhen.aliyuncs.com/static/icon/checked.png' : 'https://kailin-mp.oss-cn-shenzhen.aliyuncs.com/static/icon/checkbox.png'"
  72. ></image>
  73. <text class="checkall">全选</text>
  74. </view>
  75. <view class="price_box">
  76. 合计:<text class="price_total">¥{{ priceTotal }}</text>
  77. </view>
  78. <view class="to_order" @click="toOrder()">预约</view>
  79. </view>
  80. </view>
  81. </template>
  82. <script>
  83. import Empty from "@/components/Empty/Empty.vue";
  84. import SwipeAction from "@/components/SwipeAction/SwipeAction.vue";
  85. export default {
  86. components: { Empty, SwipeAction },
  87. data() {
  88. return {
  89. // 请求参数
  90. requestParam: {},
  91. // 是否全选
  92. checkedAll: false,
  93. // 总价
  94. priceTotal: "0.00",
  95. // 是否请求中
  96. isReqing: false,
  97. //购物车列表
  98. cartListByGroup: [],
  99. cartList: [],
  100. };
  101. },
  102. onLoad() {
  103. // #ifdef MP-WEIXIN
  104. //分享按钮
  105. uni.showShareMenu({
  106. withShareTicket: true,
  107. menus: ["shareAppMessage", "shareTimeline"],
  108. });
  109. // #endif
  110. },
  111. onShareAppMessage(obj) {
  112. // 获取分享信息
  113. let shareList = getApp().globalData.shareList;
  114. // 获取分享信息
  115. let shareObj = {
  116. title: "药优惠 得积分 兑豪礼",
  117. path: "/pages/index/index",
  118. imageUrl: "",
  119. };
  120. // 循环列表
  121. for (let i in shareList) {
  122. if (shareList[i].pages == "pages/car/index") {
  123. shareObj.path = shareList[i].path ? shareList[i].path : shareObj.path;
  124. shareObj.title = shareList[i].title ? shareList[i].title : shareObj.title;
  125. shareObj.imageUrl = shareList[i].image_url ? shareList[i].image_url : shareObj.imageUrl;
  126. }
  127. }
  128. // 返回分享信息
  129. return shareObj;
  130. },
  131. onShow() {
  132. // 登录提示
  133. if (!this.$checkAccess.alterLogin()) return;
  134. // 请求中,不允许刷新
  135. if (this.isReqing) return;
  136. // 设置请求中
  137. this.isReqing = true;
  138. // 非全选
  139. this.checkedAll = 0;
  140. // 请求列表
  141. this.$http.request("api/shop_cart/get_list", this.requestParam).then((re) => {
  142. // 设置非请求中
  143. this.isReqing = false;
  144. // 成功结果
  145. if (re.code == "success") {
  146. // 赋值
  147. this.cartListByGroup = this.formatGroupedData(re.data);
  148. this.cartList = re.data;
  149. // 计算价格
  150. this.priceHandler();
  151. }
  152. });
  153. },
  154. onPullDownRefresh() {
  155. // 登录提示
  156. if (!this.$checkAccess.alterLogin()) return;
  157. // 请求列表
  158. this.$http.request("api/shop_cart/get_list", this.requestParam).then((re) => {
  159. if (re.code == "success") {
  160. // 赋值
  161. this.cartListByGroup = this.formatGroupedData(re.data);
  162. this.cartList = re.data;
  163. // 计算价格
  164. this.priceHandler();
  165. }
  166. });
  167. uni.stopPullDownRefresh();
  168. },
  169. onReachBottom() {},
  170. methods: {
  171. // 转换为数组格式
  172. formatGroupedData(list) {
  173. const groupedData = list.reduce((result, item) => {
  174. const { business_id, business_name } = item;
  175. // 如果该 business_id 的分组不存在,则初始化该分组
  176. if (!result[business_id]) {
  177. result[business_id] = {
  178. business_id,
  179. business_name,
  180. products: [],
  181. checked: false,
  182. };
  183. }
  184. // 将商品添加到对应的 business_id 分组
  185. result[business_id].products.push(item);
  186. return result;
  187. }, {});
  188. return groupedData;
  189. },
  190. //选择商业公司下面的所有商品
  191. selectBussiness(index) {
  192. this.cartListByGroup[index].checked = !this.cartListByGroup[index].checked;
  193. this.cartListByGroup[index].products.forEach((item) => {
  194. item.checked = this.cartListByGroup[index].checked;
  195. });
  196. // 计算价格
  197. this.priceHandler();
  198. },
  199. // 数量调整
  200. changeQuantity(pIndex, index, number) {
  201. // 如果不是0.表示两侧按钮点击,0表示输入的修改
  202. if (number != 0) {
  203. // 计算个数
  204. this.cartListByGroup[pIndex].products[index].buy_num = this.cartListByGroup[pIndex].products[index].buy_num + number;
  205. }
  206. // 如果大于库存
  207. if (this.cartListByGroup[pIndex].products[index].buy_num > this.cartListByGroup[pIndex].products[index].stock) {
  208. // 设置为库存
  209. this.cartListByGroup[pIndex].products[index].buy_num = this.cartListByGroup[pIndex].products[index].stock;
  210. // 提示
  211. uni.showToast({
  212. title: "购买数量不能大于库存",
  213. icon: "none",
  214. });
  215. return;
  216. }
  217. // 如果小于1.设置为1
  218. if (this.cartListByGroup[pIndex].products[index].buy_num < 1) {
  219. // 恢复1
  220. this.cartListByGroup[pIndex].products[index].buy_num = 1;
  221. // 提示
  222. uni.showToast({
  223. title: "数量不可小于1",
  224. icon: "none",
  225. });
  226. return;
  227. }
  228. // 请求列表
  229. this.$http
  230. .request("api/shop_cart/edit", {
  231. id: this.cartListByGroup[pIndex].products[index].id,
  232. buy_num: this.cartListByGroup[pIndex].products[index].buy_num,
  233. })
  234. .then((re) => {
  235. if (re.code == "success") {
  236. // 计算价格
  237. this.priceHandler();
  238. } else {
  239. uni.showToast({
  240. title: re.msg,
  241. icon: "none",
  242. });
  243. }
  244. });
  245. },
  246. // 删除购物车
  247. deleteCar(pIndex, index) {
  248. uni.showModal({
  249. title: "是否删除?",
  250. success: (re) => {
  251. if (re.confirm) {
  252. // 请求列表
  253. this.$http
  254. .request("api/shop_cart/del", {
  255. id: this.cartListByGroup[pIndex].products[index].id,
  256. })
  257. .then((re) => {
  258. // 如果删除成功的话
  259. if (re.code == "success") {
  260. this.cartListByGroup[pIndex].products.splice(index, 1);
  261. // 计算价格
  262. this.priceHandler();
  263. if (this.$refs.swipeAction.length) {
  264. this.$refs.swipeAction[this.$refs.swipeAction.length - 1]?.reset();
  265. }
  266. }
  267. });
  268. } else {
  269. this.$refs.swipeAction[this.$refs.swipeAction.length - 1]?.reset();
  270. }
  271. },
  272. });
  273. },
  274. checkedItem(pIndex, index, product_id) {
  275. const product = this.cartListByGroup[pIndex].products[index];
  276. // 如果商品不可选中,直接返回
  277. if (product.product_status !== 0 || product.stock === 0) {
  278. return;
  279. }
  280. // 切换选中状态
  281. const isChecked = (product.checked = !product.checked);
  282. // 找到对应商家组中的商品并更新选中状态
  283. const productInGroup = this.cartListByGroup[pIndex].products.find((item) => item.id === product_id);
  284. if (productInGroup) {
  285. productInGroup.checked = isChecked;
  286. }
  287. // 检查该商家组内所有商品是否都已选中
  288. const checkedBussinessAll = this.cartListByGroup[pIndex].products.every((item) => item.checked);
  289. // 更新商家组的全选状态
  290. this.cartListByGroup[pIndex].checked = checkedBussinessAll;
  291. // 检查是否所有商品都已选中
  292. let checkedAll = true;
  293. for (const key in this.cartListByGroup) {
  294. if (!this.cartListByGroup[key].checked) {
  295. checkedAll = false;
  296. }
  297. }
  298. // 更新全选状态
  299. this.checkedAll = checkedAll ? 1 : 0;
  300. // 计算价格
  301. this.priceHandler();
  302. },
  303. checkAll() {
  304. // 设置全选/单选
  305. this.checkedAll = this.checkedAll ? 0 : 1;
  306. // 循环处理
  307. for (const index in this.cartListByGroup) {
  308. this.cartListByGroup[index].checked = this.checkedAll;
  309. this.cartListByGroup[index].products.forEach((item) => (item.checked = this.checkedAll));
  310. }
  311. // 计算价格
  312. this.priceHandler();
  313. },
  314. priceHandler() {
  315. // 总价格
  316. let priceTotal = 0;
  317. // 循环处理
  318. for (const index in this.cartListByGroup) {
  319. this.cartListByGroup[index].products.forEach((item) => {
  320. if (item.checked) {
  321. priceTotal = this.$decimal.add(priceTotal, this.$decimal.mul(item.price, item.buy_num));
  322. }
  323. });
  324. }
  325. // 小数点处理
  326. this.priceTotal = priceTotal.toFixed(2);
  327. },
  328. toOrder() {
  329. // 等待支付的信息
  330. let waitList = [];
  331. // 循环处理
  332. for (let index in this.cartListByGroup) {
  333. // 如果选中的
  334. for (const key in this.cartListByGroup[index].products) {
  335. if (this.cartListByGroup[index].products[key].checked) {
  336. // 如果库存不足
  337. if (this.cartListByGroup[index].products[key].buy_num < 1) {
  338. uni.showToast({ icon: "none", title: "选择的产品至少需要1个" });
  339. return;
  340. }
  341. // 如果库存不足
  342. if (this.cartListByGroup[index].products[key].buy_num > this.cartListByGroup[index].products[key].stock) {
  343. uni.showToast({ icon: "none", title: "产品库存不足" });
  344. return;
  345. }
  346. waitList.push(this.cartListByGroup[index].products[key].id);
  347. }
  348. }
  349. }
  350. // 如果没有选择
  351. if (!waitList.length) {
  352. uni.showToast({ icon: "none", title: "请选择需要结算的产品" });
  353. return;
  354. }
  355. // 如果没有选择
  356. if (waitList.length > 99) {
  357. uni.showToast({ icon: "none", title: "这么多产品一个预约单写不下哦" });
  358. return;
  359. }
  360. uni.navigateTo({
  361. url: "/pages/car/order?cart_ids=" + waitList.join(","),
  362. });
  363. },
  364. clickItem(e) {
  365. this.deleteCar(e.pIndex, e.index);
  366. },
  367. },
  368. };
  369. </script>
  370. <style lang="less">
  371. .car_list {
  372. display: block;
  373. overflow: hidden;
  374. margin: 0rpx auto;
  375. margin-top: 20rpx;
  376. padding-bottom: 110rpx;
  377. .business_name {
  378. padding: 8rpx 10rpx;
  379. border-bottom: 1px solid #f3f3f3;
  380. font-size: 32rpx;
  381. z-index: 1;
  382. display: flex;
  383. align-items: center;
  384. background-color: #fff;
  385. .checkbox {
  386. width: 40rpx;
  387. height: 40rpx;
  388. }
  389. .business_icon {
  390. width: 48rpx;
  391. height: 48rpx;
  392. margin-right: 10rpx;
  393. }
  394. }
  395. .car_item {
  396. height: 180rpx;
  397. display: block;
  398. background: #ffffff;
  399. margin: 0rpx auto;
  400. padding: 20rpx 0rpx 0;
  401. position: relative;
  402. .delete_btn {
  403. position: absolute;
  404. top: 0;
  405. right: 0;
  406. bottom: 0;
  407. width: 80px;
  408. background-color: #f44336;
  409. color: #fff;
  410. display: flex;
  411. justify-content: center;
  412. align-items: center;
  413. }
  414. .swipe-content {
  415. transition: transform 0.3s ease;
  416. }
  417. .check_label {
  418. float: left;
  419. width: 40rpx;
  420. height: 40rpx;
  421. display: flex;
  422. align-items: center;
  423. justify-content: center;
  424. margin-top: 10rpx;
  425. padding: 50rpx 20rpx;
  426. .checkbox {
  427. float: left;
  428. width: 40rpx;
  429. height: 40rpx;
  430. }
  431. }
  432. .box_left {
  433. float: left;
  434. width: 140rpx;
  435. height: 200rpx;
  436. margin-top: 10rpx;
  437. position: relative;
  438. .car_image {
  439. width: 140rpx;
  440. height: 140rpx;
  441. border-radius: 5rpx;
  442. }
  443. .product_status {
  444. position: absolute;
  445. width: 100%;
  446. height: 40rpx;
  447. bottom: 60rpx;
  448. display: flex;
  449. justify-content: center;
  450. align-items: center;
  451. font-size: 24rpx;
  452. background-color: #999999;
  453. }
  454. }
  455. .box_center {
  456. float: left;
  457. width: 300rpx;
  458. margin-left: 25rpx;
  459. .car_name {
  460. max-height: 60rpx;
  461. font-size: 30rpx;
  462. line-height: 30rpx;
  463. overflow: hidden;
  464. white-space: nowrap; /* 不换行 */
  465. overflow: hidden; /* 隐藏超出的内容 */
  466. text-overflow: ellipsis; /* 用省略号表示被隐藏的部分 */
  467. }
  468. .promo_title {
  469. max-height: 80rpx;
  470. font-size: 20rpx;
  471. line-height: 40rpx;
  472. overflow: hidden;
  473. padding: 0rpx 0rpx;
  474. color: #dd524d;
  475. }
  476. .car_spec {
  477. color: #999999;
  478. font-size: 24rpx;
  479. max-height: 60rpx;
  480. line-height: 60rpx;
  481. overflow: hidden;
  482. }
  483. .car_price {
  484. font-size: 30rpx;
  485. line-height: 60rpx;
  486. .price {
  487. color: red;
  488. }
  489. .market_price {
  490. font-size: 24rpx;
  491. color: #999999;
  492. margin-left: 10rpx;
  493. padding-left: 10rpx;
  494. text-decoration: line-through;
  495. border-left: 2rpx solid #dddddd;
  496. }
  497. }
  498. }
  499. .box_right {
  500. float: right;
  501. width: 185rpx;
  502. padding-right: 15rpx;
  503. .buy_num_box {
  504. float: right;
  505. color: #333333;
  506. overflow: hidden;
  507. font-size: 24rpx;
  508. margin-top: 70rpx;
  509. text-align: center;
  510. .buy_num_sub {
  511. float: left;
  512. border: none;
  513. height: 36rpx;
  514. background: none;
  515. text-align: center;
  516. line-height: 36rpx;
  517. padding: 10rpx 10rpx;
  518. .sub_icon {
  519. width: 22rpx;
  520. height: 22rpx;
  521. display: block;
  522. }
  523. }
  524. .buy_num_sub::after {
  525. border: none;
  526. background: none;
  527. }
  528. .buy_num {
  529. float: left;
  530. width: 90rpx;
  531. height: 36rpx;
  532. font-size: 24rpx;
  533. min-height: 36rpx;
  534. line-height: 36rpx;
  535. padding: 0rpx 0rpx;
  536. border-radius: 8rpx;
  537. border: 2rpx solid #dddddd;
  538. }
  539. .buy_num_add {
  540. float: left;
  541. border: none;
  542. height: 36rpx;
  543. background: none;
  544. text-align: center;
  545. padding: 10rpx 10rpx;
  546. line-height: 36rpx;
  547. .add_icon {
  548. width: 22rpx;
  549. height: 22rpx;
  550. display: block;
  551. }
  552. }
  553. .buy_num_add::after {
  554. border: none;
  555. background: none;
  556. }
  557. }
  558. }
  559. }
  560. }
  561. .bottom_box {
  562. z-index: 999;
  563. left: 0rpx;
  564. width: 100%;
  565. height: 100rpx;
  566. display: block;
  567. position: fixed;
  568. overflow: hidden;
  569. background: #ffffff;
  570. padding: 0rpx 35rpx;
  571. bottom: var(--window-bottom);
  572. .check_all_label {
  573. float: left;
  574. width: 120rpx;
  575. height: 40rpx;
  576. font-size: 24rpx;
  577. line-height: 40rpx;
  578. padding: 30rpx 0rpx;
  579. .checkbox {
  580. float: left;
  581. width: 40rpx;
  582. height: 40rpx;
  583. }
  584. .checkbox.active {
  585. border: 2rpx solid red;
  586. .checkbox_active {
  587. background-color: #e03519;
  588. }
  589. }
  590. .checkall {
  591. float: left;
  592. height: 40rpx;
  593. display: block;
  594. margin-left: 10rpx;
  595. line-height: 40rpx;
  596. }
  597. }
  598. .price_box {
  599. float: left;
  600. width: 400rpx;
  601. display: block;
  602. color: #666666;
  603. font-size: 26rpx;
  604. text-align: right;
  605. line-height: 100rpx;
  606. margin-right: 20rpx;
  607. .price_total {
  608. color: red;
  609. font-size: 30rpx;
  610. }
  611. }
  612. .to_order {
  613. float: left;
  614. width: 140rpx;
  615. height: 60rpx;
  616. display: block;
  617. color: #ffffff;
  618. font-size: 28rpx;
  619. margin-top: 20rpx;
  620. line-height: 60rpx;
  621. padding: 0rpx 0rpx;
  622. text-align: center;
  623. border-radius: 30rpx;
  624. background-color: #e03519;
  625. }
  626. }
  627. </style>