| <template> | |
|   <view :class="['product', size]"  | |
|     @touchstart="onTouchstart"  | |
|     @touchmove="onTouchmove"  | |
|     @touchend="onTouchend" | |
|   > | |
|     <image class="product-img" :src="data.image" mode="aspectFill"></image> | |
|     <view class="flex flex-column product-info"> | |
|       <view class="product-info-top"> | |
|         <view class="product-name text-ellipsis-2">{{ data.title }}</view> | |
|         <view class="product-desc text-ellipsis" v-if="tagDesc">{{ tagDesc }}</view> | |
|       </view> | |
|       <view class="flex product-info-bottom"> | |
|         <view class="product-detail"> | |
|           <view class="flex product-price"> | |
|             <view class="product-price-val"> | |
|               <text>¥</text> | |
|               <text class="highlight">{{ priceInt }}</text> | |
|               <text>{{ `${priceFrac}起` }}</text> | |
|             </view> | |
|             <view class="product-price-bef" v-if="data.priceOrigin"> | |
|               {{ `¥${data.priceOrigin}` }} | |
|             </view> | |
|           </view> | |
|           <view class="product-registered"> | |
|             {{ `${data.applyNum}人已报名` }} | |
|           </view> | |
|         </view> | |
|         <button class="btn" @click="onRegistrate">报名</button> | |
|       </view> | |
|     </view> | |
| 
 | |
|     <button class="flex btn btn-collect"  | |
|       :style="collectBtnStyle" | |
|       @click.stop="onCollect" | |
|       @touchstart.stop="onCollect" | |
|     > | |
|       <view>{{ isCollected ? '移除收藏' : '收藏' }}</view> | |
|     </button> | |
|   </view> | |
| </template> | |
| 
 | |
| <script> | |
|   export default { | |
|     props: { | |
|       data: { | |
|         type: Object, | |
|         default() { | |
|           return {} | |
|         } | |
|       }, | |
|       size: { | |
|         type: String, | |
|         default: 'normal' // normal | small | |
|       } | |
|     }, | |
|     data() { | |
|       return { | |
|         isMove: false, | |
|         startClientX: null, | |
|         displayX: 0, | |
|         collectBtnVisible: false, | |
|       } | |
|     }, | |
|     computed: { | |
|       tagDesc() { | |
|         const { tagList } = this.data | |
| 
 | |
|         return tagList?.length ? tagList.split('、').join('·') : '' | |
|       }, | |
|       priceInt() { | |
|         return Math.floor(this.data.priceDiscount) | |
|       }, | |
|       priceFrac() { | |
|         let frac = this.data.priceDiscount % this.priceInt | |
|         return frac > 0 ? frac.toFixed(2).slice(1) : '' | |
|       }, | |
|       isCollected() { | |
|         return this.data.isCollection == '1' | |
|       }, | |
|       collectBtnWidth() { | |
|         return this.isCollected ? 80 : 56 | |
|       }, | |
|       collectBtnStyle() { | |
|         const width = this.collectBtnWidth | |
|         const background = this.isCollected ? '#26334E' : '#FF9035' | |
| 
 | |
|         let display = Math.ceil(this.displayX / width * 100) | |
| 
 | |
|         display > 100 && (display = 100) | |
| 
 | |
|         const translateX = 100 - display | |
| 
 | |
|         return `width: ${width}px; transform: translateX(${translateX}%); background: ${background};` | |
|       }, | |
|     }, | |
|     methods: { | |
|       onTouchstart(e) { | |
|         const clientX = e.changedTouches[0].clientX | |
| 
 | |
|         this.isMove = false | |
|         this.startClientX = clientX | |
|         this.displayX = 0 | |
|       }, | |
|       onTouchmove(e) { | |
|         const clientX = e.changedTouches[0].clientX | |
| 
 | |
|         if (clientX < this.startClientX) { | |
|           this.displayX = this.startClientX - clientX | |
|         } else { | |
|           this.displayX = 0 | |
|         } | |
| 
 | |
|         this.isMove = true | |
|       }, | |
|       onTouchend() { | |
| 
 | |
|         if (!this.isMove && !this.collectBtnVisible) { | |
|           this.onRegistrate() | |
|         } | |
| 
 | |
|         if (this.displayX < this.collectBtnWidth) { | |
|           this.displayX = 0 | |
|         } | |
| 
 | |
|         if (this.displayX) { | |
|           this.collectBtnVisible = true | |
|         } else { | |
|           this.collectBtnVisible = false | |
|         } | |
| 
 | |
|         this.isMove = false | |
|       }, | |
|       showCollectBtn() { | |
|         this.displayX = 100 | |
|       }, | |
|       hiddenCollectBtn() { | |
|         this.displayX = 0 | |
|       }, | |
|       async onCollect() { | |
| 
 | |
|         try { | |
| 
 | |
|           let succ = await this.$store.dispatch('collect', this.data.id) | |
|           succ && this.hiddenCollectBtn() | |
| 
 | |
|           this.$emit('collect', !this.isCollected) | |
|         } catch (err) { | |
|           console.log('collect err', err) | |
|         } | |
| 
 | |
|       }, | |
|       onRegistrate() { | |
|         this.$utils.navigateTo(`/pages_order/product/productDetail?id=${this.data.id}`) | |
|       }, | |
|     }, | |
|   } | |
| </script> | |
| 
 | |
| <style scoped lang="scss"> | |
|   .product { | |
|     position: relative; | |
|     height: 464rpx; | |
|     background: #FFFFFF; | |
|     border: 2rpx solid #FFFFFF; | |
|     border-radius: 32rpx; | |
|     overflow: hidden; | |
|     font-size: 0; | |
| 
 | |
|     &-img { | |
|       width: 100%; | |
|       height: 220rpx; | |
|     } | |
| 
 | |
|     &-info { | |
|       height: 244rpx; | |
|       padding: 16rpx 16rpx 24rpx 16rpx; | |
|       box-sizing: border-box; | |
|       justify-content: space-between; | |
| 
 | |
|       &-top { | |
|         width: 100%; | |
|       } | |
| 
 | |
|       &-bottom { | |
|         width: 100%; | |
|         justify-content: space-between; | |
|       } | |
| 
 | |
|     } | |
| 
 | |
|     &-name { | |
|       font-size: 28rpx; | |
|       font-weight: 500; | |
|       color: #000000; | |
|     } | |
| 
 | |
|     &-desc { | |
|       margin-top: 8rpx; | |
|       font-size: 24rpx; | |
|       color: #8B8B8B; | |
|     } | |
| 
 | |
|     &-detail { | |
| 
 | |
|     } | |
|      | |
|     &-price { | |
|       justify-content: flex-start; | |
|       align-items: baseline; | |
|       column-gap: 12rpx; | |
|       flex-wrap: wrap; | |
| 
 | |
|       &-val { | |
|         font-size: 24rpx; | |
|         font-weight: 500; | |
|         color: #FF4800; | |
|         white-space: nowrap; | |
| 
 | |
|         .highlight { | |
|           font-size: 32rpx; | |
|         } | |
| 
 | |
|       } | |
| 
 | |
|       &-bef { | |
|         text-decoration: line-through; | |
|         font-size: 24rpx; | |
|         color: #8B8B8B; | |
|       } | |
|     } | |
| 
 | |
|     &-registered { | |
|       font-size: 24rpx; | |
|       color: #8B8B8B; | |
|     } | |
| 
 | |
|     .btn { | |
|       padding: 11rpx 32rpx; | |
|       font-size: 26rpx; | |
|       font-weight: 500; | |
|       color: #FFFFFF; | |
|       background: #00A9FF; | |
|       border-radius: 24rpx; | |
|       white-space: nowrap; | |
|     } | |
| 
 | |
|     &.small { | |
|       .btn { | |
|         padding: 11rpx 16rpx; | |
|       } | |
|     } | |
| 
 | |
|   } | |
| 
 | |
|   .btn.btn-collect { | |
|     position: absolute; | |
|     top: 0; | |
|     right: 0; | |
|     row-gap: 8rpx; | |
|     // width: 112rpx; | |
|     height: 100%; | |
|     font-size: 24rpx; | |
|     line-height: 1; | |
|     color: #FFFFFF; | |
|     // background: #FF9035; | |
|     border-radius: 0; | |
|   } | |
| 
 | |
| </style> |