You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

436 lines
11 KiB

3 years ago
  1. <template>
  2. <view class="app">
  3. <mescroll-body
  4. ref="mescrollRef"
  5. @init="mescrollInit"
  6. :top="0"
  7. @down="downCallback"
  8. :up="upOption"
  9. @up="loadList"
  10. >
  11. <!-- 列表 -->
  12. <uni-swipe-action>
  13. <uni-swipe-action-item v-for="(item, index) in list" :key="item._id" :options="options" @click="remove($event,index)" >
  14. <view class="item row">
  15. <text class="mix-icon icon-xuanzhong" :class="{active: item.checked}" @click.stop.prevent="checkRow(item)"></text>
  16. <view class="image-wrapper lazyload lazypic" :class="{loaded: item.loaded}" @click="navTo('/pages/product/detail?id='+item.product_id)">
  17. <image :src="item.image" mode="aspectFill" lazy-load="true" @load="imageOnLoad(item)" ></image>
  18. </view>
  19. <view class="right column">
  20. <text class="title clamp">{{item.title}}</text>
  21. <text class="sku">{{item.sku.name}}</text>
  22. <text class="price">¥{{item.price || ''}}</text>
  23. <view class="number-box" @click.stop.prevent="stopPrevent">
  24. <mix-number-box :min="1" :max="item.stock" :value="item.number" :isMax="item.number >= item.stock" :isMin="item.number === 1" :index="index" @eventChange="onNumberChange" ></mix-number-box>
  25. </view>
  26. </view>
  27. </view>
  28. </uni-swipe-action-item>
  29. </uni-swipe-action>
  30. <!-- 失效商品 -->
  31. <view v-if="invalidList.length > 0" class="invalid">
  32. <view class="invalid-header row" style=" height: 72rpx; padding: 0 24rpx 0 22rpx; font-size: 24rpx; color: #333; background-color: #f5f5f5; ">
  33. <text class="mix-icon icon-tishi"></text>
  34. <text class="fill">商品调整库存不足等原因会导致商品失效</text>
  35. <view class="btn center" @click="clearInvalid">
  36. <text class="mix-icon icon-lajitong"></text>
  37. <text>清除</text>
  38. </view>
  39. </view>
  40. <view v-for="(item, index) in invalidList" :key="item._id" class="item row">
  41. <text class="mix-icon icon-xuanzhong"></text>
  42. <view class="image-wrapper lazyload lazypic" :class="{loaded: item.loaded}">
  43. <image :src="item.image" mode="aspectFill" lazy-load="true" @load="imageOnLoad(item)" ></image>
  44. </view>
  45. <view class="right column">
  46. <text class="title clamp">{{item.title}}</text>
  47. <text class="sku">{{item.sku.name}}</text>
  48. <view class="row">
  49. <text class="price fill">¥{{item.price || ''}}</text>
  50. <view class="tag">失效</view>
  51. </view>
  52. </view>
  53. </view>
  54. </view>
  55. <!-- 底部栏 -->
  56. <view class="bot-fill-view"></view>
  57. </mescroll-body>
  58. <view v-if="loaded && (list.length > 0 || invalidList.length > 0)" class="bottom row">
  59. <text class="mix-icon icon-xuanzhong" :class="{active: allChecked}" @click="checkAll"></text>
  60. <text v-if="!allChecked" class="check-tip">全选</text>
  61. <view class="del-btn center" :class="{active: allChecked}" @click="showPopup('mixModal')">
  62. <text>清空</text>
  63. </view>
  64. <text class="price fill">{{ totalPrice | price(2) }}</text>
  65. <view class="btn center" @click="createOrder">
  66. <text>去结算{{ confirmNumber > 0 ? '(' + confirmNumber + ')' : '' }}</text>
  67. </view>
  68. </view>
  69. <!-- 缺省 -->
  70. <mix-empty v-else-if="loaded" type="cart"></mix-empty>
  71. <!-- 加载 -->
  72. <mix-loading v-if="isLoading" :type="loaded ? 1 : 2" :mask="true"></mix-loading>
  73. <!-- 确认对话框 -->
  74. <mix-modal ref="mixModal" title="删除提示" text="挑了这么久,真的要狠心清空吗" confirmText="清空" @onConfirm="clear"></mix-modal>
  75. </view>
  76. </template>
  77. <script>
  78. import tabbarMixin from './mixin/tabbar'
  79. import MescrollMixin from "@/components/mescroll-uni/mescroll-mixins.js";
  80. export default {
  81. mixins: [tabbarMixin, MescrollMixin],
  82. data() {
  83. return {
  84. options: [{
  85. text: '删除',
  86. style: {
  87. backgroundColor: '#ff536f'
  88. }
  89. }],
  90. upOption:{
  91. auto: false, // 是否自动加载
  92. page: {
  93. num: 0, // 当前页码,默认0,回调之前会加1,即callback(page)会从1开始
  94. size: 15 // 每页数据的数量
  95. },
  96. empty: {
  97. onShow: false,
  98. },
  99. noMoreSize: 50,
  100. },
  101. list: [],
  102. invalidList: [],
  103. totalPrice: 0,
  104. }
  105. },
  106. computed: {
  107. allChecked(){
  108. return this.list.length > 0 && this.list.every(item=> item.checked);
  109. },
  110. confirmNumber(){
  111. return this.list.filter(item=> item.checked).length;
  112. },
  113. },
  114. watch: {
  115. list(){
  116. this.calcTotalPrice();
  117. }
  118. },
  119. onLoad() {
  120. uni.$on('refreshCart', ()=>{
  121. this.list = [];
  122. this.invalidList = [];
  123. this.mescroll.resetUpScroll(false)
  124. })
  125. },
  126. onShow() {
  127. this.mescroll && this.mescroll.resetUpScroll(false)
  128. },
  129. methods: {
  130. async loadList(type){
  131. const res = await this.$request('cart', 'get');
  132. const list = res.map(item=> {
  133. return {
  134. loaded: this.loaded,
  135. ...item
  136. }
  137. })
  138. this.list = list.filter(item=> item.invalid === false);
  139. this.invalidList = list.filter(item=> item.invalid);
  140. this.mescroll.endSuccess(list.length); //结束加载状态
  141. },
  142. mescrollInit(mescroll){
  143. this.isLoading = true;
  144. this.mescroll = mescroll;
  145. this.mescroll.resetUpScroll(false)
  146. },
  147. //去结算 创建订单
  148. createOrder(){
  149. const ids = this.list.filter(item=> item.checked).map(item=> item._id);
  150. if(ids.length === 0){
  151. this.$util.msg('请选择结算商品');
  152. return;
  153. }
  154. this.navTo('/pages/order/createOrder?type=cart&ids='+ids);
  155. },
  156. //数量修改
  157. onNumberChange(e){
  158. this.$util.debounce(async ()=>{
  159. const res = await this.$request('cart', 'updateNumber', {
  160. id: this.list[e.index]._id,
  161. number: e.number
  162. })
  163. if(res.status == 1){
  164. this.list[e.index].number = e.number
  165. this.calcTotalPrice();
  166. }else{
  167. this.$util.msg('数量更新失败');
  168. }
  169. }, 500)
  170. },
  171. //单选
  172. checkRow(item){
  173. if(this.chenageChecked([item._id], !item.checked)){
  174. item.checked = !item.checked;
  175. this.calcTotalPrice();
  176. }
  177. },
  178. //全选
  179. checkAll(){
  180. const ids = this.list.map(item=> item._id);
  181. const allChecked = !this.allChecked;
  182. if(this.chenageChecked(ids, allChecked)){
  183. this.list.forEach(item=> {
  184. item.checked = allChecked;
  185. })
  186. this.calcTotalPrice();
  187. }
  188. },
  189. //修改选中状态
  190. async chenageChecked(ids, checked){
  191. this.$request('cart', 'updateCheck', {
  192. ids,
  193. checked
  194. }, {showLoading: true}).then(res=>{
  195. if(res.status === 1){
  196. return true;
  197. }else{
  198. this.$util.msg('修改失败');
  199. return false;
  200. }
  201. })
  202. },
  203. //删除
  204. remove(e, index){
  205. this.$util.throttle(async ()=>{
  206. const res = await this.$request('cart', 'remove', {
  207. ids: [this.list[index]._id]
  208. }, {
  209. showLoading: true
  210. })
  211. this.$util.msg(res.msg);
  212. if(res.status === 1){
  213. this.list.splice(index, 1);
  214. this.$store.dispatch('getCartCount');//更新数量
  215. }
  216. })
  217. },
  218. //清空失效
  219. clearInvalid(){
  220. this.$util.throttle(async ()=>{
  221. const res = await this.$request('cart', 'remove', {
  222. ids: this.invalidList.map(item=> item._id)
  223. }, {
  224. showLoading: true
  225. })
  226. this.$util.msg(res.msg);
  227. if(res.status === 1){
  228. this.invalidList = [];
  229. this.$store.dispatch('getCartCount');//更新数量
  230. }
  231. })
  232. },
  233. //清空商品
  234. clear(){
  235. this.$util.throttle(async ()=>{
  236. const res = await this.$request('cart', 'removeAll', {}, {
  237. showLoading: true
  238. })
  239. this.$util.msg(res.msg);
  240. if(res.status === 1){
  241. this.list = [];
  242. this.$store.dispatch('getCartCount');//更新数量
  243. }
  244. })
  245. },
  246. //计算总价
  247. calcTotalPrice(){
  248. const checkedList = this.list.filter(item=> item.checked);
  249. let total = 0;
  250. checkedList.forEach(item=> {
  251. console.log(item);
  252. total += item.price * item.number
  253. })
  254. this.totalPrice = total;
  255. }
  256. }
  257. }
  258. </script>
  259. <style scoped lang="scss">
  260. .app{
  261. /deep/ .uni-swipe{
  262. padding: 24rpx 0;
  263. }
  264. }
  265. .item{
  266. width: 100%;
  267. padding-right: 30rpx;
  268. overflow: hidden;
  269. .icon-xuanzhong{
  270. padding: 60rpx 20rpx;
  271. font-size: 36rpx;
  272. color: #ddd;
  273. &.active{
  274. color: $base-color;
  275. }
  276. }
  277. .image-wrapper{
  278. flex-shrink: 0;
  279. width: 170rpx;
  280. height: 170rpx;
  281. margin-right: 20rpx;
  282. border-radius: 10rpx;
  283. overflow: hidden;
  284. image{
  285. width: 100%;
  286. height: 100%;
  287. }
  288. }
  289. .right{
  290. flex: 1;
  291. overflow: hidden;
  292. }
  293. .title{
  294. font-size: 30rpx;
  295. line-height: 42rpx;
  296. }
  297. .sku{
  298. min-height: 20rpx;
  299. margin: 20rpx 0 28rpx;
  300. font-size: 24rpx;
  301. color: #999;
  302. }
  303. .price{
  304. margin-bottom: 4rpx;
  305. font-size: 30rpx;
  306. color: #333;
  307. }
  308. .number-box{
  309. position: absolute;
  310. right: 0;
  311. bottom: 0;
  312. padding: 20rpx 30rpx 0;
  313. }
  314. /deep/{
  315. .uni-numbox-minus, .uni-numbox-value, .uni-numbox-plus{
  316. height: 48rpx;
  317. }
  318. }
  319. }
  320. .invalid{
  321. .item{
  322. padding: 24rpx 24rpx 24rpx 0;
  323. opacity: 0.5;
  324. }
  325. .invalid-header{
  326. height: 72rpx;
  327. padding: 0 24rpx 0 22rpx;
  328. font-size: 24rpx;
  329. color: #333;
  330. background-color: #f5f5f5;
  331. }
  332. .icon-tishi{
  333. margin-right: 10rpx;
  334. font-size: 32rpx;
  335. color: $base-color;
  336. }
  337. .btn{
  338. padding: 8rpx 16rpx;
  339. font-size: 24rpx;
  340. color: #666;
  341. }
  342. .icon-lajitong{
  343. margin-right: 8rpx;
  344. font-size: 32rpx;
  345. }
  346. .tag{
  347. padding: 6rpx 18rpx;
  348. font-size: 24rpx;
  349. color: #fff;
  350. border-radius: 100rpx;
  351. background-color: #555;
  352. }
  353. }
  354. .bot-fill-view{
  355. width: 100%;
  356. height: 110rpx;
  357. box-sizing: content-box;
  358. padding-bottom: constant(safe-area-inset-bottom);
  359. padding-bottom: env(safe-area-inset-bottom);
  360. }
  361. .bottom{
  362. position: fixed;
  363. left: 0;
  364. bottom: var(--window-bottom);
  365. z-index: 90;
  366. width: 100%;
  367. height: 100rpx;
  368. background-color: #fff;
  369. box-shadow: 0 -2rpx 10rpx 0 rgba(0,0,0,.06);
  370. box-sizing: content-box;
  371. padding-bottom: constant(safe-area-inset-bottom);
  372. padding-bottom: env(safe-area-inset-bottom);
  373. .icon-xuanzhong{
  374. margin-left: 20rpx;
  375. font-size: 48rpx;
  376. color: #ddd;
  377. position: relative;
  378. z-index: 10;
  379. background-color: #fff;
  380. border-radius: 100rpx;
  381. &.active{
  382. color: $base-color;
  383. }
  384. }
  385. .check-tip{
  386. position: absolute;
  387. left: 80rpx;
  388. font-size: 28rpx;
  389. color: #333;
  390. }
  391. .del-btn{
  392. width: 0rpx;
  393. height: 44rpx;
  394. padding-left: 14rpx;
  395. font-size: 28rpx;
  396. color: #fff;
  397. background-color: #C0C4CC;
  398. border-radius: 0 100rpx 100rpx 0;
  399. position: relative;
  400. left: -24rpx;
  401. transition: width .2s;
  402. &.active{
  403. width: 110rpx;
  404. }
  405. }
  406. .price{
  407. margin-right: 30rpx;
  408. font-size: 34rpx;
  409. color: $base-color;
  410. font-weight: 700;
  411. text-align: right;
  412. }
  413. .btn{
  414. min-width: 180rpx;
  415. height: 70rpx;
  416. padding: 0 26rpx;
  417. margin-right: 20rpx;
  418. border-radius: 100rpx;
  419. background-color: $base-color;
  420. font-size: 30rpx;
  421. color: #fff;
  422. }
  423. }
  424. </style>