index.vue 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245
  1. <template>
  2. <view class="recommend-item" @click.stop="handleClick">
  3. <!-- Main Info Row -->
  4. <view class="main-info" :style="{ 'margin-bottom': showDesc ? '30rpx' : '0' }">
  5. <image :src="item.cover" class="book-cover" mode="aspectFill"></image>
  6. <view class="info-right">
  7. <view class="title-author">
  8. <view class="title-row">
  9. <rich-text class="title" :nodes="highlightedName"></rich-text>
  10. </view>
  11. <rich-text class="author" :nodes="highlightedAuthor"></rich-text>
  12. </view>
  13. <view class="price-action">
  14. <view class="price-box">
  15. <text class="currency">¥</text>
  16. <text class="price">{{ item.productPrice || item.price }}</text>
  17. <text class="original">¥{{ item.originalPrice || item.price }}</text>
  18. </view>
  19. <view class="cart-btn" :class="{ 'gray-btn': item.availableStock === 0 && item.hasArrivalNotice === 1 }" @click.stop="handleAction">
  20. <text v-if="item.availableStock === 0 && item.hasArrivalNotice === 1">取消到货通知</text>
  21. <text v-else-if="item.availableStock === 0">到货通知</text>
  22. <text v-else>加入购物车</text>
  23. </view>
  24. </view>
  25. </view>
  26. </view>
  27. <!-- Description Row -->
  28. <view class="desc-section" v-if="showDesc">
  29. <view class="desc-header">
  30. <text class="label">简介</text>
  31. <view class="indicator"></view>
  32. </view>
  33. <rich-text class="desc-content" :nodes="highlightedDesc"></rich-text>
  34. </view>
  35. <!-- Product Selection Popup -->
  36. <SelectGoodPopup ref="popup" @confirm="onPopupConfirm"></SelectGoodPopup>
  37. </view>
  38. </template>
  39. <script>
  40. import SelectGoodPopup from '../select-good-popup/index.vue';
  41. export default {
  42. name: 'RecommendItem',
  43. components: {
  44. SelectGoodPopup
  45. },
  46. props: {
  47. item: {
  48. type: Object,
  49. required: true
  50. },
  51. showDesc: {
  52. type: Boolean,
  53. default: true
  54. }
  55. },
  56. computed: {
  57. highlightedName() {
  58. return this.parseEmTag(this.item.name || this.item.title || '');
  59. },
  60. highlightedAuthor() {
  61. return this.parseEmTag(this.item.author || '');
  62. },
  63. highlightedDesc() {
  64. return this.parseEmTag(this.item.description || this.item.desc || '暂无简介');
  65. }
  66. },
  67. methods: {
  68. parseEmTag(text) {
  69. if (!text) return '';
  70. return text.replace(/<em>/g, '<span style="color: #38C148">').replace(/<\/em>/g, '</span>');
  71. },
  72. handleAction() {
  73. if (this.item.availableStock === 0) {
  74. this.handleNotify();
  75. } else {
  76. this.handleAddToCart();
  77. }
  78. },
  79. handleNotify() {
  80. const isCancel = this.item.hasArrivalNotice === 1;
  81. const apiUrl = isCancel ? '/token/shop/user/noticeArrivalCancel' : '/token/shop/user/noticeArrival';
  82. uni.$u.http.post(apiUrl, {
  83. isbn: this.item.isbn,
  84. }).then(res => {
  85. if (res.code === 200) {
  86. const newValue = isCancel ? 0 : 1;
  87. // Notify parent component to update the item
  88. this.$emit('update-item', { ...this.item, hasArrivalNotice: newValue });
  89. // Try updating local object properties directly (Vue reactivity)
  90. this.$set(this.item, 'hasArrivalNotice', newValue);
  91. this.item.hasArrivalNotice = newValue;
  92. this.$u.toast(isCancel ? '已取消到货通知' : '到货通知设置成功');
  93. }
  94. });
  95. },
  96. handleAddToCart() {
  97. // Open the popup using ref
  98. // 加入购物车时,传递 sourceFrom 参数为 2
  99. this.$refs.popup.open(this.item, this.showDesc ? 2 : 1);
  100. },
  101. onPopupConfirm(data) {
  102. // 不再触发事件,避免重复调用接口
  103. // this.$emit('add-cart', data);
  104. },
  105. //图书详情页
  106. handleClick() {
  107. uni.navigateTo({
  108. url: '/pages-sell/pages/detail?isbn=' + this.item.isbn
  109. });
  110. }
  111. }
  112. }
  113. </script>
  114. <style lang="scss" scoped>
  115. .recommend-item {
  116. padding: 30rpx 0;
  117. border-bottom: 1rpx dashed #EEEEEE;
  118. .main-info {
  119. display: flex;
  120. margin-bottom: 30rpx;
  121. .book-cover {
  122. width: 172rpx;
  123. height: 220rpx;
  124. border-radius: 8rpx;
  125. margin-right: 24rpx;
  126. flex-shrink: 0;
  127. }
  128. .info-right {
  129. flex: 1;
  130. display: flex;
  131. flex-direction: column;
  132. justify-content: space-between;
  133. padding: 4rpx 0;
  134. .title-author {
  135. .title {
  136. font-size: 32rpx;
  137. font-weight: bold;
  138. color: #333;
  139. margin-bottom: 12rpx;
  140. display: block;
  141. line-height: 1.4;
  142. }
  143. .author {
  144. font-size: 24rpx;
  145. color: #999;
  146. }
  147. }
  148. .price-action {
  149. display: flex;
  150. justify-content: space-between;
  151. align-items: flex-end;
  152. .price-box {
  153. .currency {
  154. font-size: 24rpx;
  155. color: #FF4B4B;
  156. font-weight: bold;
  157. }
  158. .price {
  159. font-size: 36rpx;
  160. color: #FF4B4B;
  161. font-weight: bold;
  162. margin-right: 12rpx;
  163. }
  164. .original {
  165. font-size: 24rpx;
  166. color: #999;
  167. text-decoration: line-through;
  168. }
  169. }
  170. .cart-btn {
  171. background: #38C248;
  172. border-radius: 30rpx;
  173. height: 60rpx;
  174. line-height: 60rpx;
  175. padding: 0 24rpx;
  176. &.gray-btn {
  177. background: #cccccc;
  178. }
  179. text {
  180. font-size: 26rpx;
  181. color: #fff;
  182. }
  183. }
  184. }
  185. }
  186. }
  187. .desc-section {
  188. .desc-header {
  189. position: relative;
  190. display: inline-block;
  191. margin-bottom: 16rpx;
  192. .label {
  193. font-size: 30rpx;
  194. font-weight: bold;
  195. color: #333;
  196. position: relative;
  197. z-index: 1;
  198. }
  199. .indicator {
  200. position: absolute;
  201. bottom: 4rpx;
  202. right: 4rpx;
  203. width: 30rpx;
  204. height: 8rpx;
  205. background: #4ED964;
  206. border-radius: 4rpx;
  207. z-index: 0;
  208. }
  209. }
  210. .desc-content {
  211. font-family: Source Han Sans SC;
  212. font-size: 26rpx;
  213. color: #8D8D8D;
  214. line-height: 1.6;
  215. display: block;
  216. text-align: justify;
  217. }
  218. }
  219. }
  220. </style>