index.vue 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709
  1. <template>
  2. <view class="app-container" :class="isNoticebar ? 'hasNoticebar' : ''">
  3. <view class="noticebar" v-if="isNoticebar">
  4. <uni-notice-bar v-if="noticeContent" scrollable="true" single="true" showClose="true" :text="noticeContent"
  5. background-color="#f6e6e7" color="#de3749" @close="closeNoticebar"></uni-notice-bar>
  6. </view>
  7. <view class="top" :class="isNoticebar ? 'hasNoticebar' : ''">
  8. <view class="total">共{{ totalNum }}件商品</view>
  9. <view v-if="!isEditor" @tap="isEditor = !isEditor">编辑</view>
  10. <view v-else @tap="isEditor = !isEditor">完成</view>
  11. </view>
  12. <view class="list">
  13. <block v-for="(item, index) in goodsList" :key="index">
  14. <view class="item">
  15. <view class="check" v-if="item.selected" @tap="selectList(index)">
  16. <image src="@/static/icon/select_1.png" mode=""></image>
  17. </view>
  18. <view class="check" v-else @tap="selectList(index)">
  19. <image src="@/static/icon/select_0.png" mode="" />
  20. </view>
  21. <image :src="item.goodsImg" mode="aspectFill" class="img"></image>
  22. <view class="right">
  23. <view class="title ellipsis-2">{{ item.goodsName }}</view>
  24. <view class="des ellipsis">{{ item.specValue }}</view>
  25. <view class="last">
  26. <view class="price">
  27. <view class="price-1">¥{{ item.price | numToFixed }}</view>
  28. <view class="price-2">¥{{ item.orgPrice | numToFixed }}</view>
  29. </view>
  30. <u-number-box @tap.stop v-model="item.num" :min="1" :buttonSize="26"
  31. iconStyle="font-size: 12px;" @change="changeCount($event, index)">
  32. </u-number-box>
  33. </view>
  34. </view>
  35. </view>
  36. </block>
  37. </view>
  38. <no-data v-if="!goodsList.length" :showText="'购物车暂无商品'"></no-data>
  39. <view
  40. style="width: 100%;display: flex;justify-content: space-between;align-items: center;margin-bottom: 20rpx;margin-top: 20rpx;">
  41. <view style="width: 30%;height: 2rpx;background: #a8a8a8;"></view>
  42. <view style="font-size: 24rpx;color: #8a8a8a;">猜你想要</view>
  43. <view style="width: 30%;height: 2rpx;background: #a8a8a8;"></view>
  44. </view>
  45. <view class="goods-waterfall-list">
  46. <view class="left">
  47. <block v-for="(item, index) in goodsList2" :key='index'>
  48. <view class="item" v-if="index%2==0" @tap="toGoodsDetail(item.goodsId)">
  49. <view class="image">
  50. <image :src="item.imgUrl" mode="aspectFill" class="img"></image>
  51. <image :src="item.logo" mode="aspectFill" class="water" v-if="item.isShowWater"></image>
  52. </view>
  53. <view class="content">
  54. <view class="title ellipsis-2">{{item.goodsName}}</view>
  55. <view class="tags" v-if="item.tags1 && item.tags1.length > 0">
  56. <view class="it" v-for="(it, idx) in item.tags1" :key="idx">{{it}}</view>
  57. </view>
  58. <view class="price">
  59. <view class="price-1">¥{{item.goodsPrice | numToFixed}}</view>
  60. <view class="price-2">¥{{item.orgGoodsPrice | numToFixed}}</view>
  61. </view>
  62. <view class="tags2" v-if="item.tags2 && item.tags2.length > 0">
  63. <view class="it" v-for="(it, idx) in item.tags2" :key="idx">{{it}}</view>
  64. </view>
  65. <view class="text">销量:{{item.soldNum}}</view>
  66. </view>
  67. </view>
  68. </block>
  69. </view>
  70. <view class="right">
  71. <block v-for="(item, index) in goodsList2" :key='index'>
  72. <view class="item" v-if="index%2==1" @tap="toGoodsDetail(item.goodsId)">
  73. <view class="image">
  74. <image :src="item.imgUrl" mode="aspectFill" class="img"></image>
  75. <image :src="item.logo" mode="aspectFill" class="water" v-if="item.isShowWater"></image>
  76. </view>
  77. <view class="content">
  78. <view class="title ellipsis-2">{{item.goodsName}}</view>
  79. <view class="tags" v-if="item.tags1 && item.tags1.length > 0">
  80. <view class="it" v-for="(it, idx) in item.tags1" :key="idx">{{it}}</view>
  81. </view>
  82. <view class="price">
  83. <view class="price-1">¥{{item.goodsPrice | numToFixed}}</view>
  84. <view class="price-2">¥{{item.orgGoodsPrice | numToFixed}}</view>
  85. </view>
  86. <view class="tags2" v-if="item.tags2 && item.tags2.length > 0">
  87. <view class="it" v-for="(it, idx) in item.tags2" :key="idx">{{it}}</view>
  88. </view>
  89. <view class="text">销量:{{item.soldNum}}</view>
  90. </view>
  91. </view>
  92. </block>
  93. </view>
  94. </view>
  95. <view class="bottom">
  96. <view class="check" @tap="selectAll">
  97. <image src="@/static/icon/select_1.png" mode="" v-if="selectAllStatus" />
  98. <image src="@/static/icon/select_0.png" mode="" v-else />
  99. <text class="text">全选</text>
  100. </view>
  101. <view class="right" v-if="!isEditor">
  102. <view class="price">
  103. 合计:<text>¥{{ totalPrice }}</text>
  104. </view>
  105. <view class="button"><button @tap="toOrder">去结算</button></view>
  106. </view>
  107. <view class="right" v-else>
  108. <button class="delete" @tap="clickDelete">删除</button>
  109. </view>
  110. </view>
  111. <u-popup :round="10" :show="isShowPhoneDialog">
  112. <view class="phone-dialog">
  113. <view class="content">
  114. <view>申请获取以下权限</view>
  115. <text>获得你的公开信息(手机号码)</text>
  116. </view>
  117. <view class="btn">
  118. <button @tap="isShowPhoneDialog = false">取消</button>
  119. <button type="primary" open-type="getPhoneNumber" @getphonenumber="getPhoneNumber">
  120. 确定授权
  121. </button>
  122. </view>
  123. </view>
  124. </u-popup>
  125. <modal-dialog showText="确定要删除选中的商品吗?" :isShowDialog="isDialog" @cancel="isDialog = false"
  126. @confirm="confirmDelete"></modal-dialog>
  127. </view>
  128. </template>
  129. <script>
  130. import {
  131. mapState
  132. } from 'vuex';
  133. import modalDialog from '@/components/modalDialog.vue';
  134. export default {
  135. components: {
  136. modalDialog,
  137. },
  138. data() {
  139. return {
  140. goodsList: [],
  141. totalPrice: 0, // 总价,初始为0
  142. totalNum: 0,
  143. selectAllStatus: true, // 全选状态,默认全选
  144. isEditor: false, // 是否编辑
  145. isDialog: false, // 是否显示删除弹窗
  146. isNoticebar: false, // 是否显示通告栏
  147. noticeContent: '', // 公告内容
  148. isShowPhoneDialog: false,
  149. goodsList2: [],
  150. pageNum: 1,
  151. pageSize: 8,
  152. noMore: false,
  153. };
  154. },
  155. computed: {
  156. ...mapState(['userInfo', 'isLogin', 'userId']),
  157. },
  158. onShow() {
  159. this.getGoodsList();
  160. this.goodsList2 = [];
  161. this.getGoodsList22();
  162. },
  163. onLoad() {
  164. this.getNoticebar();
  165. },
  166. // 下拉刷新
  167. onPullDownRefresh() {
  168. this.pageNum = 1;
  169. this.getGoodsList22();
  170. },
  171. // 上拉加载
  172. onReachBottom() {
  173. this.getGoodsList22(1);
  174. },
  175. methods: {
  176. // 获取商品列表
  177. getGoodsList22(loadMore) {
  178. if (this.noMore && loadMore) return;
  179. this.noMore = false
  180. if (!loadMore) {
  181. this.pageNum = 1;
  182. }
  183. this.$axios({
  184. url: '/goods/list/sort/page',
  185. method: 'get',
  186. params: {
  187. pageNum: this.pageNum,
  188. pageSize: this.pageSize,
  189. categoryId: '',
  190. sort: 5,
  191. },
  192. isLoading: !loadMore
  193. }).then(res => {
  194. res.data.records.forEach(item => {
  195. if (item.logo && item.logoStartTime) {
  196. item.isShowWater = this.$compareTime(item.logoStartTime, item.logoEndTime);
  197. } else {
  198. item.isShowWater = false;
  199. }
  200. })
  201. let _list = res.data.records;
  202. let pageTotal = res.data.pages;
  203. if (this.pageNum >= pageTotal) {
  204. this.noMore = true;
  205. }
  206. if (_list.length) {
  207. this.pageNum += 1
  208. }
  209. if (loadMore) {
  210. this.goodsList2 = this.goodsList.concat(_list);
  211. } else {
  212. this.goodsList2 = _list;
  213. }
  214. })
  215. },
  216. // 进入商品详情
  217. toGoodsDetail(id) {
  218. if (!id) {
  219. return false;
  220. }
  221. this.$axios({
  222. url: '/goods/detail',
  223. method: 'get',
  224. params: {
  225. goodsId: id,
  226. userId: this.userId,
  227. },
  228. })
  229. .then((res) => {
  230. uni.navigateTo({
  231. url: '/packageGoods/pages/detail?id=' + id
  232. })
  233. })
  234. .catch((res) => {
  235. uni.switchTab({
  236. url: '/pages/goods/all'
  237. });
  238. })
  239. .finally((res) => {});
  240. },
  241. // 获取手机号
  242. getPhoneNumber(e) {
  243. if (e?.detail?.iv && e?.detail?.encryptedData) {
  244. this.$axios({
  245. url: '/user/mobile/grant',
  246. params: {
  247. userId: this.userId,
  248. iv: e.detail.iv,
  249. encryptedData: e.detail.encryptedData,
  250. },
  251. }).then((res) => {
  252. uni.setStorageSync('token', res.data.token);
  253. uni.setStorageSync('isActiveLogout', false);
  254. this.isShowPhoneDialog = false;
  255. this.$getUserInfo();
  256. });
  257. }
  258. },
  259. // 获取公告栏
  260. getNoticebar() {
  261. this.$axios({
  262. url: '/shpping/cart/notice',
  263. method: 'get',
  264. params: {
  265. userId: this.userId,
  266. },
  267. }).then((res) => {
  268. this.noticeContent = res.data;
  269. this.isNoticebar = res.data ? true : false;
  270. });
  271. },
  272. // 获取商品列表
  273. getGoodsList() {
  274. this.$axios({
  275. url: '/shpping/cart/list',
  276. method: 'get',
  277. params: {
  278. userId: this.userId,
  279. },
  280. }).then((res) => {
  281. this.goodsList = res.data.shoppingCartLists;
  282. this.totalNum = res.data.totalNum;
  283. this.goodsList.forEach((item, index) => {
  284. item.selected = true;
  285. });
  286. this.selectAllStatus = true;
  287. this.getTotalPrice();
  288. });
  289. },
  290. // 统计总价
  291. getTotalPrice() {
  292. // 获取购物车列表
  293. let goodsList = this.goodsList;
  294. let total = 0;
  295. // 循环列表
  296. for (let i = 0; i < goodsList.length; i++) {
  297. // 判断选中才会计算价格
  298. if (goodsList[i].selected) {
  299. // 所有价格加起来
  300. total += goodsList[i].num * goodsList[i].price;
  301. }
  302. }
  303. // 赋值到data中渲染到页面
  304. this.goodsList = goodsList;
  305. this.totalPrice = total.toFixed(2);
  306. },
  307. // 选择事件
  308. selectList(index) {
  309. // 获取购物车列表
  310. let goodsList = this.goodsList;
  311. // 获取当前商品的选中状态
  312. let selected = goodsList[index].selected;
  313. // 改变状态
  314. goodsList[index].selected = !selected;
  315. this.goodsList = goodsList;
  316. // 改变全选状态
  317. for (var i = 0; i < this.goodsList.length; i++) {
  318. // 当状态为全选时,每个元素其中有一个为false,则取消全选
  319. // 当状态为非全选时,每个元素都为true,则全选
  320. if (this.selectAllStatus) {
  321. if (!this.goodsList[i].selected) {
  322. this.selectAllStatus = false;
  323. break;
  324. }
  325. } else {
  326. if (this.goodsList[i].selected) {
  327. this.selectAllStatus = true;
  328. } else {
  329. this.selectAllStatus = false;
  330. break;
  331. }
  332. }
  333. }
  334. // 重新获取总价
  335. this.getTotalPrice();
  336. },
  337. // 全选事件
  338. selectAll(e) {
  339. // 是否全选状态
  340. let selectAllStatus = this.selectAllStatus;
  341. selectAllStatus = !selectAllStatus;
  342. let goodsList = this.goodsList;
  343. for (let i = 0; i < goodsList.length; i++) {
  344. // 改变所有商品状态
  345. goodsList[i].selected = selectAllStatus;
  346. }
  347. this.selectAllStatus = selectAllStatus;
  348. this.goodsList = goodsList;
  349. // 重新获取总价
  350. this.getTotalPrice();
  351. },
  352. // 更改数量
  353. changeCount(e, index) {
  354. this.goodsList[index].num = e.value;
  355. this.initCart();
  356. },
  357. // 修改数量后更新购物车数据
  358. initCart() {
  359. let buyGoods = [];
  360. this.goodsList.forEach((item, index) => {
  361. let goodsItem = {
  362. goodsId: item.goodsId,
  363. goodsSpecId: item.goodsSpecId,
  364. num: item.num,
  365. shoppingCartId: item.shoppingCartId,
  366. secKillId: item.secKillId || '',
  367. promotionGroupId: item.promotionGroupId || '',
  368. };
  369. buyGoods.push(goodsItem);
  370. });
  371. this.$axios({
  372. url: '/shpping/cart/add',
  373. type: 'application/json',
  374. params: {
  375. userId: this.userId,
  376. buyGoods: buyGoods,
  377. },
  378. }).then((res) => {
  379. this.totalNum = res.data.totalNum;
  380. this.getTotalPrice();
  381. });
  382. },
  383. // 点击删除
  384. clickDelete() {
  385. let delIds = [];
  386. for (let i = 0; i < this.goodsList.length; i++) {
  387. if (this.goodsList[i].selected) {
  388. delIds.push(this.goodsList[i].shoppingCartId);
  389. }
  390. }
  391. if (delIds.length < 1) {
  392. return this.$toast('至少选择一件商品');
  393. }
  394. this.isDialog = true;
  395. },
  396. // 确认删除
  397. confirmDelete() {
  398. let delIds = [];
  399. for (let i = 0; i < this.goodsList.length; i++) {
  400. if (this.goodsList[i].selected) {
  401. delIds.push(this.goodsList[i].shoppingCartId);
  402. }
  403. }
  404. this.$axios({
  405. url: '/shpping/cart/remove',
  406. params: {
  407. shoppingCartIds: delIds,
  408. userId: this.userId,
  409. },
  410. }).then((res) => {
  411. this.isDialog = false;
  412. this.isEditor = false;
  413. this.$successToast('删除成功');
  414. this.getGoodsList();
  415. });
  416. },
  417. // 去结算
  418. toOrder() {
  419. let buyList = [];
  420. this.goodsList.forEach((item, index) => {
  421. if (item.selected) {
  422. let goodsItem = {
  423. goodsId: item.goodsId,
  424. goodsSpecId: item.goodsSpecId,
  425. num: item.num,
  426. shoppingCartId: item.shoppingCartId,
  427. secKillId: item.secKillId || '',
  428. promotionGroupId: item.promotionGroupId || '',
  429. };
  430. buyList.push(goodsItem);
  431. }
  432. });
  433. if (buyList.length < 1) {
  434. return this.$toast('至少选择一件商品');
  435. }
  436. if (!this.userInfo.mobile) {
  437. this.isShowPhoneDialog = true;
  438. return;
  439. }
  440. // 跳转结算页面
  441. uni.navigateTo({
  442. url: '/packageGoods/pages/order?buyList=' + JSON.stringify(buyList),
  443. });
  444. },
  445. // 关闭通告栏
  446. closeNoticebar() {
  447. this.isNoticebar = false;
  448. },
  449. },
  450. };
  451. </script>
  452. <style lang="scss">
  453. .app-container {
  454. background: #f4f2f2;
  455. box-sizing: border-box;
  456. }
  457. .app-container {
  458. height: auto;
  459. padding: 110rpx 0;
  460. &.hasNoticebar {
  461. padding-top: 174rpx;
  462. }
  463. .noticebar {
  464. position: fixed;
  465. top: 0;
  466. left: 0;
  467. width: 100%;
  468. z-index: 100;
  469. }
  470. .top {
  471. position: fixed;
  472. top: 0;
  473. left: 0;
  474. z-index: 99;
  475. width: 100%;
  476. padding: 0 20rpx;
  477. box-sizing: border-box;
  478. background: #ffffff;
  479. height: 88rpx;
  480. display: flex;
  481. justify-content: space-between;
  482. align-items: center;
  483. border-bottom: 1px solid #f4f2f2;
  484. &.hasNoticebar {
  485. top: 64rpx;
  486. }
  487. .total {
  488. font-size: 32rpx;
  489. }
  490. }
  491. .list {
  492. padding: 0 20rpx;
  493. .item {
  494. margin-bottom: 20rpx;
  495. border-radius: 10rpx;
  496. padding: 20rpx 20rpx;
  497. display: flex;
  498. align-items: center;
  499. background: #ffffff;
  500. .check {
  501. image {
  502. width: 32rpx;
  503. height: 32rpx;
  504. display: block;
  505. }
  506. }
  507. .img {
  508. width: 180rpx;
  509. height: 180rpx;
  510. display: block;
  511. margin: 0 20rpx;
  512. flex-shrink: 0;
  513. }
  514. .right {
  515. width: 420rpx;
  516. height: 190rpx;
  517. display: flex;
  518. justify-content: space-between;
  519. flex-direction: column;
  520. .title {
  521. font-size: 28rpx;
  522. color: #333333;
  523. line-height: 36rpx;
  524. }
  525. .des {
  526. font-size: 24rpx;
  527. color: #999999;
  528. line-height: 36rpx;
  529. }
  530. .last {
  531. display: flex;
  532. justify-content: space-between;
  533. align-items: center;
  534. .price {
  535. display: flex;
  536. flex-direction: column;
  537. }
  538. .price-1 {
  539. font-size: 32rpx;
  540. color: #ff3f42;
  541. line-height: 36rpx;
  542. }
  543. .price-2 {
  544. font-size: 26rpx;
  545. color: #666666;
  546. line-height: 30rpx;
  547. text-decoration: line-through;
  548. }
  549. }
  550. }
  551. }
  552. }
  553. .bottom {
  554. position: fixed;
  555. left: 0;
  556. bottom: 0;
  557. z-index: 99;
  558. width: 100%;
  559. box-sizing: border-box;
  560. background: #ffffff;
  561. height: 100rpx;
  562. display: flex;
  563. align-items: center;
  564. justify-content: space-between;
  565. padding: 0 20rpx;
  566. border-top: 1px solid #f4f2f2;
  567. .check {
  568. display: flex;
  569. align-items: center;
  570. image {
  571. width: 32rpx;
  572. height: 32rpx;
  573. display: block;
  574. margin-right: 20rpx;
  575. }
  576. }
  577. .right {
  578. display: flex;
  579. align-items: center;
  580. .price {
  581. font-size: 28rpx;
  582. line-height: 36rpx;
  583. text {
  584. color: #ff3f42;
  585. }
  586. margin-right: 20rpx;
  587. }
  588. .button {
  589. button {
  590. height: 80rpx;
  591. line-height: 80rpx;
  592. padding: 0;
  593. border-radius: 80rpx;
  594. font-size: 30rpx;
  595. color: #fff;
  596. background: linear-gradient(-90deg, #ff3f42 0%, #fe781f 100%);
  597. padding: 0 40rpx;
  598. }
  599. }
  600. .delete {
  601. width: 170rpx;
  602. height: 80rpx;
  603. line-height: 80rpx;
  604. text-align: center;
  605. padding: 0;
  606. border-radius: 80rpx;
  607. font-size: 30rpx;
  608. color: #ff3f42;
  609. border: 1px solid #ff3f42;
  610. }
  611. }
  612. }
  613. }
  614. .phone-dialog {
  615. padding: 40rpx;
  616. box-sizing: border-box;
  617. .content {
  618. margin-top: 30rpx;
  619. margin-bottom: 30rpx;
  620. line-height: 50rpx;
  621. text {
  622. color: #9d9d9d;
  623. }
  624. }
  625. .btn {
  626. display: flex;
  627. justify-content: center;
  628. margin-top: 60rpx;
  629. button::after {
  630. border: none;
  631. }
  632. button {
  633. width: 180rpx;
  634. height: 70rpx;
  635. line-height: 70rpx;
  636. margin: 0;
  637. font-size: 28rpx;
  638. margin: 0 30rpx;
  639. &:first-child {
  640. background: #f5f5f5;
  641. color: #00ba5c;
  642. }
  643. }
  644. }
  645. }
  646. </style>