cart.vue 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678
  1. <template>
  2. <view>
  3. <view v-if="storage">
  4. <view class="container">
  5. <!-- 空白页 -->
  6. <view v-if="!hasLogin || empty===true" >
  7. <view v-if="hasLogin" style="padding-top: 180rpx;padding-bottom: 180rpx;width: 750rpx;">
  8. <missing :buttonName="'去添加商品'" :handlerName="'buttonClick'"
  9. @buttonClick="toCategory"
  10. :imgUrl="'http://qiniuoss.nauzone.cn/%E7%BB%84%201%20%E6%8B%B7%E8%B4%9D%403x.png'"
  11. :desc="'购物车空空如也,您不添加吗?'"></missing>
  12. </view>
  13. <view v-else style="padding-top: 180rpx;padding-bottom: 180rpx;width: 750rpx;">
  14. <missing :buttonName="'去登录'" :handlerName="'buttonClick'"
  15. @buttonClick="navToLogin"
  16. :imgUrl="'http://qiniuoss.nauzone.cn/%E7%BB%84%201%20%E6%8B%B7%E8%B4%9D%403x.png'"
  17. :desc="'购物车空空如也,您不添加吗?'"></missing>
  18. </view>
  19. </view>
  20. <view v-else>
  21. <!-- 列表 -->
  22. <view class="cart-list bg-white padding-lr">
  23. <block v-for="(item, index) in cartList" :key="item.id">
  24. <view :class="[index+1 < cartList.length?'solid-bottom':'']" class="padding-tb flex align-center">
  25. <view v-if="item.stockless" style="color: #CFCFCF;font-size: 28rpx;line-height: 40rpx;flex-shrink: 0;">失效</view>
  26. <image v-else @click="check('item', index)"
  27. :src="item.checked ?'/static/cart/selected.png':'/static/cart/select.png'"
  28. mode="aspectFill"
  29. style="width: 40rpx;height: 40rpx;flex-shrink: 0;"></image>
  30. <view class="" style="padding-left: 52rpx;flex-shrink: 0;">
  31. <image :src="(item.productAttrImg?JSON.parse(item.productAttrImg)[0].url:JSON.parse(item.productImg)[0].url)"
  32. mode="aspectFill" lazy-load
  33. @load="onImageLoad(item)"
  34. class="round"
  35. @error="onImageError('cartList', index)"
  36. style="width: 130rpx;height: 130rpx;"></image>
  37. </view>
  38. <view class="flex justify-between flex-direction"
  39. style="padding-left: 48rpx;height: 180rpx;flex-grow: 1;">
  40. <view class="flex justify-between align-start">
  41. <view style="width: 400rpx;" class="lem-text-black lem-text-xxl text-2-cut">{{item.productName}}</view>
  42. <image src="../../static/cart/delete.png" mode="aspectFill"
  43. @click="deleteCartItem(index)"
  44. style="width: 32rpx;height: 32rpx;margin-top: 15rpx;"></image>
  45. </view>
  46. <view class="flex justify-between align-end" >
  47. <view class="flex align-center">
  48. <view style="font-weight: 500;color: #FC6620;"
  49. class=" lem-text-xxl padding-right-sm"> ¥{{(isVip?(item.vipPrice):item.price)}}</view>
  50. <view style="color: #AEAEAE;font-size: 24rpx;line-height: 44rpx;padding: 0 10rpx;text-decoration: line-through;">
  51. ¥{{item.otPrice}}
  52. </view>
  53. </view>
  54. <uni-number-box
  55. class="number-box"
  56. :min="1"
  57. :max="item.stock"
  58. :value="item.cartNum"
  59. :isMin="item.cartNum===0"
  60. :index="index"
  61. :forbid="item.activityId"
  62. @eventChange="numberChange"
  63. ></uni-number-box>
  64. </view>
  65. </view>
  66. </view>
  67. </block>
  68. </view>
  69. <view v-if="stockLessList.length > 0" class="cart-list padding-right butouming" style="border-left: #CFCFCF solid 30rpx;">
  70. <view class="flex align-center justify-center" style="padding: 40rpx 0 6rpx 0;">
  71. <view style="font-size: 28rpx;line-height: 40rpx;color: #666666;"
  72. >以下商品因库存不足已失效</view>
  73. </view>
  74. <block class="bg-white" v-for="(item, index) in stockLessList" :key="item.id">
  75. <view class="padding-tb solid-bottom flex align-center">
  76. <view class="" style="color: #CFCFCF;font-size: 28rpx;line-height: 40rpx;flex-shrink: 0;">失效</view>
  77. <view class="" style="padding-left: 52rpx;flex-shrink: 0;">
  78. <image :src="(item.skuImg?item.skuImg:item.spuImg)"
  79. mode="aspectFit" lazy-load
  80. @load="onImageLoad(item)"
  81. class="round"
  82. @error="onImageError('cartList', index)"
  83. style="width: 130rpx;height: 130rpx;"></image>
  84. </view>
  85. <view class="flex justify-between flex-direction"
  86. style="padding-left: 48rpx;height: 180rpx;flex-grow: 1;">
  87. <view class="lem-text-black lem-text-xxl text-2-cut">{{item.title}}</view>
  88. <view class="flex justify-between align-end" >
  89. <view style="font-weight: 500;color: #FC6620"
  90. class="lem-text-xxl"> ¥ {{item.price}}</view>
  91. <uni-number-box
  92. class="number-box"
  93. :min="1"
  94. :value="item.cartNum"
  95. :isMin="item.cartNum===1"
  96. :index="index"
  97. @eventChange="numberChange"
  98. ></uni-number-box>
  99. </view>
  100. </view>
  101. </view>
  102. </block>
  103. <view class="flex align-center justify-center"
  104. style="padding: 30rpx 0;">
  105. <button @click="clearStockLess" class="lem-btn line-gray round">清空已失效商品</button>
  106. </view>
  107. </view>
  108. <view class="bg-white flex justify-between align-center padding-tb-xs padding-lr submit-class">
  109. <view @click="cancelAllAdd" class="flex align-center">
  110. <image
  111. :src="allChecked ?'/static/cart/selected.png':'/static/cart/select.png'"
  112. mode="aspectFit"
  113. style="width: 40rpx;height: 40rpx;flex-shrink: 0;"></image>
  114. <view class="lem-text-grey"
  115. style="padding-left: 16rpx;font-size: 26rpx;line-height: 36rpx;">全选</view>
  116. </view>
  117. <view style="padding-left: 54rpx;">
  118. <view class="flex align-center">
  119. <view class="lem-text-grey lem-text-lg">总计</view>
  120. <view class="padding-left-xs lem-text-title"
  121. style="color: #FC6620;font-weight: 500;">¥ {{total}}</view>
  122. </view>
  123. </view>
  124. <button @click="createOrder" style="width: 240rpx;height: 84rpx;font-weight: 500;"
  125. class="lem-btn round bg-green lem-text-xl ">提交订单</button>
  126. </view>
  127. </view>
  128. </view>
  129. </view>
  130. <view v-else style="padding-top: 180rpx;padding-bottom: 180rpx;width: 750rpx;">
  131. <missing :buttonName="'换个地址试试吧~'" :handlerName="'buttonClick'"
  132. @buttonClick="chooseLocation"
  133. :imgUrl="'http://qiniuoss.nauzone.cn/%E7%BB%84%204%20%E6%8B%B7%E8%B4%9D@3x.png'"
  134. :desc="'当前地区不在配送范围哦'"></missing>
  135. </view>
  136. </view>
  137. </template>
  138. <script>
  139. import {
  140. mapState
  141. } from 'vuex';
  142. import uniNumberBox from '@/components/uni-number-box.vue'
  143. import missing from "@/components/missing.vue"
  144. export default {
  145. components: {
  146. uniNumberBox,missing
  147. },
  148. data() {
  149. return {
  150. totalItems: 0, //总数量
  151. total: 0, //总价格
  152. allChecked: false, //全选状态 true|false
  153. empty: false, //空白页现实 true|false
  154. cartList: [],
  155. storage:true,
  156. loadedItemIds: new Set(),
  157. yuanjia:0,//商品原价
  158. stockLessList:[],
  159. stockLessIds:'',
  160. isVip: false
  161. };
  162. },
  163. onLoad(){
  164. },
  165. onShow() {
  166. this.isVip = this.$api.isVip()
  167. this.$store.state.storageId ? this.storage = true : this.storage = false
  168. this.loadData();
  169. //如果用户已登录,获取购物车数量
  170. if(this.$store.state.userInfo.accessToken){
  171. this.countTabNum()
  172. }
  173. },
  174. watch:{
  175. //显示空白页
  176. cartList(e){
  177. if(e.length == 0){
  178. this.total = 0
  179. this.yuanjia = 0
  180. this.empty = true
  181. }
  182. }
  183. },
  184. computed:{
  185. ...mapState(['hasLogin'])
  186. },
  187. methods: {
  188. //请求数据
  189. async loadData(){
  190. const that = this
  191. that.$api.request('get', 'cart/app/getCartList',{
  192. storageId:this.$store.state.storageId
  193. }).then(res => {
  194. //修改为for i循环,以找出库存不足的商品索引并去掉,存入库存不足商品组
  195. // var stockless = [];//库存不足商品索引
  196. var lessIds = []
  197. for (var i = 0; i < res.data.length; i++) {
  198. if(res.data[i].stock <= 0){
  199. // stockless.push(i)
  200. res.data[i].stockless = true
  201. lessIds.push(res.data[i].id)
  202. }else{
  203. res.data[i].checked = true
  204. }
  205. }
  206. // console.log('__________________________________')
  207. // console.log(stockless)
  208. // for (var i = 0; i < stockless.length; i++) {
  209. // that.stockLessList.push(res.data[stockless[i]])
  210. // res.data.splice(stockless[i],1)
  211. // }
  212. // stockless.forEach(item => {
  213. // that.stockLessList.push(res.data[item])
  214. // })
  215. that.cartList = res.data
  216. if(that.cartList){
  217. this.empty = false
  218. }
  219. that.calcTotal(); //计算总价
  220. this.stockLessIds = lessIds.join(',')
  221. })
  222. },
  223. jian(arg2, arg1) {
  224. var r1, r2, m, n;
  225. try { r1 = arg1.toString().split(".")[1].length } catch (e) { r1 = 0 }
  226. try { r2 = arg2.toString().split(".")[1].length } catch (e) { r2 = 0 }
  227. m = Math.pow(10, Math.max(r1, r2));
  228. //lastmodifybydeeka
  229. //动态控制精度长度
  230. n = (r1 >= r2) ? r1 : r2;
  231. return ((arg2 * m - arg1 * m) / m).toFixed(n);
  232. },
  233. countTabNum(){
  234. this.$api.request('get','cart/app/countCart',{
  235. storageId:this.$store.state.storageId
  236. }).then(res=>{
  237. if(res.data > 0){
  238. uni.setTabBarBadge({
  239. index:2,
  240. text:res.data+''
  241. })
  242. }else if(res.data <= 0){
  243. uni.removeTabBarBadge({
  244. index:2
  245. })
  246. }
  247. this.$store.commit('addCart',res.data)
  248. }).catch(err=>{
  249. // this.$api.msg('请求失败,请稍后再试')
  250. })
  251. },
  252. //监听image加载完成
  253. onImageLoad(item) {
  254. this.loadedItemIds.add(item.id)
  255. this.$forceUpdate()
  256. },
  257. //监听image加载失败
  258. onImageError(key, index) {
  259. this[key][index].skuImg = '/static/errorImage.jpg';
  260. },
  261. navToLogin(){
  262. uni.navigateTo({
  263. url: '/pages/public/login'
  264. })
  265. },
  266. //选中状态处理
  267. check(type, index){
  268. if(type === 'item'){
  269. this.cartList[index].checked = !this.cartList[index].checked;
  270. }else{
  271. const checked = !this.allChecked
  272. const list = this.cartList;
  273. list.forEach(item=>{
  274. item.checked = checked;
  275. })
  276. this.allChecked = checked;
  277. }
  278. this.calcTotal(type);
  279. },
  280. //数量
  281. numberChange(data){
  282. const that = this
  283. if(data.number > that.cartList[data.index].stock){
  284. this.$api.msg('库存不足')
  285. return
  286. }
  287. if(that.cartList[data.index].activityId && that.cartList[data.index].activityId !== 0 ){
  288. this.$api.msg('抢购商品只允许购买一件')
  289. that.cartList[data.index].cartNum = 1
  290. return
  291. }
  292. if(that.cartList[data.index].couponId && that.cartList[data.index].couponId !== 0){
  293. this.$api.msg('抢购商品只允许购买一件')
  294. that.cartList[data.index].cartNum = 1
  295. return
  296. }
  297. if(data.number > that.cartList[data.index].cartNum){
  298. var cartNum = this.$store.state.cartNum+1
  299. that.$store.commit('addCart',cartNum)
  300. uni.setTabBarBadge({
  301. index:2,
  302. text:cartNum+''
  303. })
  304. }else if(data.number < that.cartList[data.index].cartNum){
  305. var cartNum = this.$store.state.cartNum-1
  306. that.$store.commit('addCart',cartNum)
  307. uni.setTabBarBadge({
  308. index:2,
  309. text:cartNum+''
  310. })
  311. }
  312. if(data.number == 0){
  313. this.deleteCartItem(data.index)
  314. return
  315. }
  316. that.$api.request('get','cart/app/updateCartItemNum', {
  317. cartId: that.cartList[data.index].id,
  318. num: data.number
  319. }, failres => {
  320. uni.showToast({
  321. title: failres.msg,
  322. icon: 'none'
  323. });
  324. that.cartList[data.index].cartNum = that.cartList[data.index].cartNum
  325. }).then(res => {
  326. that.cartList[data.index].cartNum = data.number;
  327. that.calcTotal();
  328. })
  329. },
  330. //取消全选
  331. cancelAllAdd(){
  332. const checked = !this.allChecked
  333. const list = this.cartList;
  334. list.forEach(item=>{
  335. item.checked = checked;
  336. })
  337. this.allChecked = checked;
  338. this.calcTotal();
  339. },
  340. //删除
  341. deleteCartItem(index){
  342. const that = this
  343. that.$api.request('get', 'cart/app/removeCartItem', {
  344. cartId: that.cartList[index].id
  345. }).then(res => {
  346. that.cartList.splice(index, 1);
  347. that.calcTotal();
  348. this.countTabNum()
  349. //uni.hideLoading();
  350. })
  351. },
  352. //清空
  353. clearCart(){
  354. const that = this
  355. uni.showModal({
  356. content: '清空购物车?',
  357. success: (e)=>{
  358. if(e.confirm){
  359. that.$api.request('get','removeCartAll').then(res => {
  360. that.cartList = []
  361. })
  362. }
  363. }
  364. })
  365. },
  366. //清空失效商品
  367. clearStockLess(){
  368. that.$api.request('get', 'removeCartItemBatch', {
  369. cartIdList: this.stockLessList
  370. }).then(res => {
  371. this.stockLessIds = ''
  372. this.stockLessList = []
  373. // that.calcTotal();
  374. this.countTabNum()
  375. //uni.hideLoading();
  376. })
  377. },
  378. //计算总价
  379. calcTotal(){
  380. const that = this
  381. let list = that.cartList;
  382. if(list.length === 0){
  383. // that.empty = true;
  384. return;
  385. }
  386. let total = 0;
  387. let totalItems = 0;
  388. that.yuanjia = 0
  389. let checked = true;
  390. list.forEach(item=>{
  391. console.log(item)
  392. if(item.checked === true){
  393. totalItems += item.cartNum
  394. total += (that.isVip ? item.vipPrice : item.price) * item.cartNum;
  395. that.yuanjia += Number((item.otPrice*item.cartNum).toFixed(2))
  396. }else if(checked === true){
  397. checked = false;
  398. }
  399. })
  400. console.log('原价'+that.yuanjia)
  401. this.allChecked = checked;
  402. this.total = Number(total.toFixed(2));
  403. this.totalItems = totalItems
  404. },
  405. //创建订单
  406. createOrder(){
  407. //滤除未被选择的item
  408. let selectedItems = []
  409. this.cartList.forEach(item => {
  410. if (item.checked) {
  411. selectedItems.push(item)
  412. }
  413. })
  414. if (selectedItems.length === 0) {
  415. this.$api.msg('您没有选中任何商品')
  416. return
  417. }
  418. this.$api.globalData.productList = selectedItems
  419. uni.navigateTo({
  420. url: `/pages/order/create?takeway=cart`
  421. })
  422. },
  423. toCategory(){
  424. uni.switchTab({
  425. url:"../category/category"
  426. })
  427. },
  428. //配送外区域选择区域
  429. chooseLocation(){
  430. uni.chooseLocation({
  431. success: (res1)=> {
  432. console.log(res1)
  433. this.district = res1.name
  434. uni.showLoading({
  435. title:"加载中..."
  436. })
  437. let addressesInfo = {};
  438. addressesInfo.lng = res1.longitude;
  439. addressesInfo.lat = res1.latitude;
  440. addressesInfo.addressesName = res1.name;
  441. this.$api.request('get','storage/position/getRecentlyStorage',{
  442. lng:res1.longitude,
  443. lat:res1.latitude
  444. },failres => {
  445. uni.hideLoading()
  446. uni.setStorageSync('addresses', addressesInfo);
  447. this.logining = false
  448. this.$api.msg(failres.msg)
  449. this.$store.commit('setStorage',11)
  450. this.loadData(11)
  451. if(!11){
  452. this.storage ? this.storage = false : this.storage = true
  453. }else{
  454. this.loadRecommand('refresh')
  455. }
  456. }).then(res=>{
  457. uni.hideLoading()
  458. addressesInfo.distance = res.data.distance;
  459. uni.setStorageSync('addresses', addressesInfo);
  460. console.log(res)
  461. // res.data.id = 11
  462. this.$store.commit('setStorage',res.data.id)
  463. this.newTop = []
  464. this.cheapRecommend = []
  465. this.salesTop = []
  466. this.loadData(res.data.id)
  467. if(!res.data.id){
  468. this.storage = false
  469. }else{
  470. this.storage ? this.storage = false : this.storage = true
  471. // this.loadRecommand('refresh')
  472. }
  473. })
  474. }
  475. });
  476. }
  477. }
  478. }
  479. </script>
  480. <style lang='scss'>
  481. .number-box{
  482. width: 146rpx;
  483. height: 46rpx;
  484. }
  485. .submit-class {
  486. position: fixed;
  487. /* #ifdef H5 */
  488. bottom: 50px;
  489. /* #endif */
  490. /* #ifdef MP-WEIXIN */
  491. bottom: 0px;
  492. /* #endif */
  493. /* #ifdef APP-PLUS */
  494. bottom: 0px;
  495. /* #endif */
  496. width: 750rpx;
  497. z-index: 99999;
  498. }
  499. .butouming{
  500. opacity:0.5;
  501. }
  502. .container{
  503. padding-bottom: 134upx;
  504. /* 空白页 */
  505. .empty{
  506. position:fixed;
  507. left: 0;
  508. top:0;
  509. width: 100%;
  510. height: 100vh;
  511. padding-bottom:100upx;
  512. display:flex;
  513. justify-content: center;
  514. flex-direction: column;
  515. align-items:center;
  516. background: #fff;
  517. image{
  518. width: 240upx;
  519. height: 160upx;
  520. margin-bottom:30upx;
  521. }
  522. .empty-tips{
  523. display:flex;
  524. font-size: $font-sm+2upx;
  525. color: $font-color-disabled;
  526. .navigator{
  527. color: $uni-color-primary;
  528. margin-left: 16upx;
  529. }
  530. }
  531. }
  532. }
  533. /* 购物车列表项 */
  534. .cart-item{
  535. display:flex;
  536. position:relative;
  537. padding:30upx 40upx;
  538. .image-wrapper{
  539. width: 230upx;
  540. height: 230upx;
  541. flex-shrink: 0;
  542. position:relative;
  543. image{
  544. border-radius:8upx;
  545. }
  546. }
  547. .checkbox{
  548. position:absolute;
  549. left:-16upx;
  550. top: -16upx;
  551. z-index: 8;
  552. font-size: 44upx;
  553. line-height: 1;
  554. padding: 4upx;
  555. color: $font-color-disabled;
  556. background:#fff;
  557. border-radius: 50px;
  558. }
  559. .item-right{
  560. display:flex;
  561. flex-direction: column;
  562. flex: 1;
  563. overflow: hidden;
  564. position:relative;
  565. padding-left: 30upx;
  566. .title,.price{
  567. font-size:$font-base + 2upx;
  568. color: $font-color-dark;
  569. height: 40upx;
  570. line-height: 40upx;
  571. }
  572. .attr{
  573. font-size: $font-sm + 2upx;
  574. color: $font-color-light;
  575. height: 50upx;
  576. line-height: 50upx;
  577. }
  578. .price{
  579. height: 50upx;
  580. line-height:50upx;
  581. }
  582. }
  583. .del-btn{
  584. padding:4upx 10upx;
  585. font-size:34upx;
  586. height: 50upx;
  587. color: $font-color-light;
  588. }
  589. }
  590. /* 底部栏 */
  591. .action-section{
  592. /* #ifdef H5 */
  593. margin-bottom:100upx;
  594. /* #endif */
  595. position:fixed;
  596. left: 30upx;
  597. bottom:30upx;
  598. z-index: 95;
  599. display: flex;
  600. align-items: center;
  601. width: 690upx;
  602. height: 100upx;
  603. padding: 0 30upx;
  604. background: rgba(255,255,255,.9);
  605. box-shadow: 0 0 20upx 0 rgba(0,0,0,.5);
  606. border-radius: 16upx;
  607. .checkbox{
  608. height:52upx;
  609. position:relative;
  610. image{
  611. width: 52upx;
  612. height: 100%;
  613. position:relative;
  614. z-index: 5;
  615. }
  616. }
  617. .clear-btn{
  618. position:absolute;
  619. left: 26upx;
  620. top: 0;
  621. z-index: 4;
  622. width: 0;
  623. height: 52upx;
  624. line-height: 52upx;
  625. padding-left: 38upx;
  626. font-size: $font-base;
  627. color: #fff;
  628. background: $font-color-disabled;
  629. border-radius:0 50px 50px 0;
  630. opacity: 0;
  631. transition: .2s;
  632. &.show{
  633. opacity: 1;
  634. width: 120upx;
  635. }
  636. }
  637. .total-box{
  638. flex: 1;
  639. display:flex;
  640. flex-direction: column;
  641. text-align:right;
  642. padding-right: 40upx;
  643. .price{
  644. font-size: $font-lg;
  645. color: $font-color-dark;
  646. }
  647. .coupon{
  648. font-size: $font-sm;
  649. color: $font-color-light;
  650. text{
  651. color: $font-color-dark;
  652. }
  653. }
  654. }
  655. .confirm-btn{
  656. padding: 0 38upx;
  657. margin: 0;
  658. border-radius: 100px;
  659. height: 76upx;
  660. line-height: 76upx;
  661. font-size: $font-base + 2upx;
  662. background: $uni-color-primary;
  663. box-shadow: 1px 2px 5px rgba(217, 60, 93, 0.72)
  664. }
  665. }
  666. /* 复选框选中状态 */
  667. .action-section .checkbox.checked,
  668. .cart-item .checkbox.checked{
  669. color: $uni-color-primary;
  670. }
  671. </style>