index.vue 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647
  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. if (item.product_status == 0 && item.stock !== 0) 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) => {
  310. if (item.product_status == 0 && item.stock !== 0) item.checked = this.checkedAll;
  311. });
  312. }
  313. // 计算价格
  314. this.priceHandler();
  315. },
  316. priceHandler() {
  317. // 总价格
  318. let priceTotal = 0;
  319. // 循环处理
  320. for (const index in this.cartListByGroup) {
  321. this.cartListByGroup[index].products.forEach((item) => {
  322. if (item.checked) {
  323. priceTotal = this.$decimal.add(priceTotal, this.$decimal.mul(item.price, item.buy_num));
  324. }
  325. });
  326. }
  327. // 小数点处理
  328. this.priceTotal = priceTotal.toFixed(2);
  329. },
  330. toOrder() {
  331. // 等待支付的信息
  332. let waitList = [];
  333. // 循环处理
  334. for (let index in this.cartListByGroup) {
  335. // 如果选中的
  336. for (const key in this.cartListByGroup[index].products) {
  337. if (this.cartListByGroup[index].products[key].checked) {
  338. // 如果库存不足
  339. if (this.cartListByGroup[index].products[key].buy_num < 1) {
  340. uni.showToast({ icon: "none", title: "选择的产品至少需要1个" });
  341. return;
  342. }
  343. // 如果库存不足
  344. if (this.cartListByGroup[index].products[key].buy_num > this.cartListByGroup[index].products[key].stock) {
  345. uni.showToast({ icon: "none", title: "产品库存不足" });
  346. return;
  347. }
  348. waitList.push(this.cartListByGroup[index].products[key].id);
  349. }
  350. }
  351. }
  352. // 如果没有选择
  353. if (!waitList.length) {
  354. uni.showToast({ icon: "none", title: "请选择需要结算的产品" });
  355. return;
  356. }
  357. // 如果没有选择
  358. if (waitList.length > 99) {
  359. uni.showToast({ icon: "none", title: "这么多产品一个预约单写不下哦" });
  360. return;
  361. }
  362. uni.navigateTo({
  363. url: "/pages/car/order?cart_ids=" + waitList.join(","),
  364. });
  365. },
  366. clickItem(e) {
  367. this.deleteCar(e.pIndex, e.index);
  368. },
  369. },
  370. };
  371. </script>
  372. <style lang="less">
  373. .car_list {
  374. display: block;
  375. overflow: hidden;
  376. margin: 0rpx auto;
  377. margin-top: 20rpx;
  378. padding-bottom: 110rpx;
  379. .business_name {
  380. padding: 8rpx 10rpx;
  381. border-bottom: 1px solid #f3f3f3;
  382. font-size: 32rpx;
  383. z-index: 1;
  384. display: flex;
  385. align-items: center;
  386. background-color: #fff;
  387. .checkbox {
  388. width: 40rpx;
  389. height: 40rpx;
  390. }
  391. .business_icon {
  392. width: 48rpx;
  393. height: 48rpx;
  394. margin-right: 10rpx;
  395. }
  396. }
  397. .car_item {
  398. height: 180rpx;
  399. display: block;
  400. background: #ffffff;
  401. margin: 0rpx auto;
  402. padding: 20rpx 0rpx 0;
  403. position: relative;
  404. .delete_btn {
  405. position: absolute;
  406. top: 0;
  407. right: 0;
  408. bottom: 0;
  409. width: 80px;
  410. background-color: #f44336;
  411. color: #fff;
  412. display: flex;
  413. justify-content: center;
  414. align-items: center;
  415. }
  416. .swipe-content {
  417. transition: transform 0.3s ease;
  418. }
  419. .check_label {
  420. float: left;
  421. width: 40rpx;
  422. height: 40rpx;
  423. display: flex;
  424. align-items: center;
  425. justify-content: center;
  426. margin-top: 10rpx;
  427. padding: 50rpx 20rpx;
  428. .checkbox {
  429. float: left;
  430. width: 40rpx;
  431. height: 40rpx;
  432. }
  433. }
  434. .box_left {
  435. float: left;
  436. width: 140rpx;
  437. height: 200rpx;
  438. margin-top: 10rpx;
  439. position: relative;
  440. .car_image {
  441. width: 140rpx;
  442. height: 140rpx;
  443. border-radius: 5rpx;
  444. }
  445. .product_status {
  446. position: absolute;
  447. width: 100%;
  448. height: 40rpx;
  449. bottom: 60rpx;
  450. display: flex;
  451. justify-content: center;
  452. align-items: center;
  453. font-size: 24rpx;
  454. background-color: #999999;
  455. }
  456. }
  457. .box_center {
  458. float: left;
  459. width: 300rpx;
  460. margin-left: 25rpx;
  461. .car_name {
  462. max-height: 60rpx;
  463. font-size: 30rpx;
  464. line-height: 30rpx;
  465. overflow: hidden;
  466. white-space: nowrap; /* 不换行 */
  467. overflow: hidden; /* 隐藏超出的内容 */
  468. text-overflow: ellipsis; /* 用省略号表示被隐藏的部分 */
  469. }
  470. .promo_title {
  471. max-height: 80rpx;
  472. font-size: 20rpx;
  473. line-height: 40rpx;
  474. overflow: hidden;
  475. padding: 0rpx 0rpx;
  476. color: #dd524d;
  477. }
  478. .car_spec {
  479. color: #999999;
  480. font-size: 24rpx;
  481. max-height: 60rpx;
  482. line-height: 60rpx;
  483. overflow: hidden;
  484. }
  485. .car_price {
  486. font-size: 30rpx;
  487. line-height: 60rpx;
  488. .price {
  489. color: red;
  490. }
  491. .market_price {
  492. font-size: 24rpx;
  493. color: #999999;
  494. margin-left: 10rpx;
  495. padding-left: 10rpx;
  496. text-decoration: line-through;
  497. border-left: 2rpx solid #dddddd;
  498. }
  499. }
  500. }
  501. .box_right {
  502. float: right;
  503. width: 185rpx;
  504. padding-right: 15rpx;
  505. .buy_num_box {
  506. float: right;
  507. color: #333333;
  508. overflow: hidden;
  509. font-size: 24rpx;
  510. margin-top: 70rpx;
  511. text-align: center;
  512. .buy_num_sub {
  513. float: left;
  514. border: none;
  515. height: 36rpx;
  516. background: none;
  517. text-align: center;
  518. line-height: 36rpx;
  519. padding: 10rpx 10rpx;
  520. .sub_icon {
  521. width: 22rpx;
  522. height: 22rpx;
  523. display: block;
  524. }
  525. }
  526. .buy_num_sub::after {
  527. border: none;
  528. background: none;
  529. }
  530. .buy_num {
  531. float: left;
  532. width: 90rpx;
  533. height: 36rpx;
  534. font-size: 24rpx;
  535. min-height: 36rpx;
  536. line-height: 36rpx;
  537. padding: 0rpx 0rpx;
  538. border-radius: 8rpx;
  539. border: 2rpx solid #dddddd;
  540. }
  541. .buy_num_add {
  542. float: left;
  543. border: none;
  544. height: 36rpx;
  545. background: none;
  546. text-align: center;
  547. padding: 10rpx 10rpx;
  548. line-height: 36rpx;
  549. .add_icon {
  550. width: 22rpx;
  551. height: 22rpx;
  552. display: block;
  553. }
  554. }
  555. .buy_num_add::after {
  556. border: none;
  557. background: none;
  558. }
  559. }
  560. }
  561. }
  562. }
  563. .bottom_box {
  564. z-index: 999;
  565. left: 0rpx;
  566. width: 100%;
  567. height: 100rpx;
  568. display: block;
  569. position: fixed;
  570. overflow: hidden;
  571. background: #ffffff;
  572. padding: 0rpx 35rpx;
  573. bottom: var(--window-bottom);
  574. .check_all_label {
  575. float: left;
  576. width: 120rpx;
  577. height: 40rpx;
  578. font-size: 24rpx;
  579. line-height: 40rpx;
  580. padding: 30rpx 0rpx;
  581. .checkbox {
  582. float: left;
  583. width: 40rpx;
  584. height: 40rpx;
  585. }
  586. .checkbox.active {
  587. border: 2rpx solid red;
  588. .checkbox_active {
  589. background-color: #e03519;
  590. }
  591. }
  592. .checkall {
  593. float: left;
  594. height: 40rpx;
  595. display: block;
  596. margin-left: 10rpx;
  597. line-height: 40rpx;
  598. }
  599. }
  600. .price_box {
  601. float: left;
  602. width: 400rpx;
  603. display: block;
  604. color: #666666;
  605. font-size: 26rpx;
  606. text-align: right;
  607. line-height: 100rpx;
  608. margin-right: 20rpx;
  609. .price_total {
  610. color: red;
  611. font-size: 30rpx;
  612. }
  613. }
  614. .to_order {
  615. float: left;
  616. width: 140rpx;
  617. height: 60rpx;
  618. display: block;
  619. color: #ffffff;
  620. font-size: 28rpx;
  621. margin-top: 20rpx;
  622. line-height: 60rpx;
  623. padding: 0rpx 0rpx;
  624. text-align: center;
  625. border-radius: 30rpx;
  626. background-color: #e03519;
  627. }
  628. }
  629. </style>