s-point-card.vue 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374
  1. <!-- 装修商品组件:【积分商城】商品卡片 -->
  2. <template>
  3. <!-- 商品卡片 -->
  4. <view>
  5. <!-- 布局1. 单列大图(上图,下内容)-->
  6. <view
  7. v-if="layoutType === LayoutTypeEnum.ONE_COL_BIG_IMG && state.spuList.length"
  8. class="goods-sl-box"
  9. >
  10. <view
  11. class="goods-box"
  12. v-for="item in state.spuList"
  13. :key="item.id"
  14. :style="[{ marginBottom: state.property.space * 2 + 'rpx' }]"
  15. >
  16. <s-goods-column
  17. class=""
  18. size="sl"
  19. :goodsFields="state.property.fields"
  20. :tagStyle="state.property.badge"
  21. :data="item"
  22. :titleColor="state.property.fields.name?.color"
  23. :subTitleColor="state.property.fields.introduction.color"
  24. :topRadius="state.property.borderRadiusTop"
  25. :bottomRadius="state.property.borderRadiusBottom"
  26. @click="sheep.$router.go('/pages/goods/point', { id: item.activityId })"
  27. >
  28. <!-- 购买按钮 -->
  29. <template v-slot:cart>
  30. <button class="ss-reset-button cart-btn" :style="[buyStyle]">
  31. {{ btnBuy.type === 'text' ? btnBuy.text : '' }}
  32. </button>
  33. </template>
  34. </s-goods-column>
  35. </view>
  36. </view>
  37. <!-- 布局2. 单列小图(左图,右内容) -->
  38. <view
  39. v-if="layoutType === LayoutTypeEnum.ONE_COL_SMALL_IMG && state.spuList.length"
  40. class="goods-lg-box"
  41. >
  42. <view
  43. class="goods-box"
  44. :style="[{ marginBottom: state.property.space + 'px' }]"
  45. v-for="item in state.spuList"
  46. :key="item.id"
  47. >
  48. <s-goods-column
  49. class="goods-card"
  50. size="lg"
  51. :goodsFields="state.property.fields"
  52. :data="item"
  53. :tagStyle="state.property.badge"
  54. :titleColor="state.property.fields.name?.color"
  55. :subTitleColor="state.property.fields.introduction.color"
  56. :topRadius="state.property.borderRadiusTop"
  57. :bottomRadius="state.property.borderRadiusBottom"
  58. @tap="sheep.$router.go('/pages/goods/point', { id: item.activityId })"
  59. >
  60. <!-- 购买按钮 -->
  61. <template v-slot:cart>
  62. <button class="ss-reset-button cart-btn" :style="[buyStyle]">
  63. {{ btnBuy.type === 'text' ? btnBuy.text : '' }}
  64. </button>
  65. </template>
  66. </s-goods-column>
  67. </view>
  68. </view>
  69. <!-- 布局3. 双列(每一列:上图,下内容)-->
  70. <view
  71. v-if="layoutType === LayoutTypeEnum.TWO_COL && state.spuList.length"
  72. class="goods-md-wrap ss-flex ss-flex-wrap ss-col-top"
  73. >
  74. <view class="goods-list-box">
  75. <view
  76. class="left-list"
  77. :style="[{ paddingRight: state.property.space + 'rpx', marginBottom: state.property.space + 'px' }]"
  78. v-for="item in state.leftSpuList"
  79. :key="item.id"
  80. >
  81. <s-goods-column
  82. class="goods-md-box"
  83. size="md"
  84. :goodsFields="state.property.fields"
  85. :tagStyle="state.property.badge"
  86. :data="item"
  87. :titleColor="state.property.fields.name?.color"
  88. :subTitleColor="state.property.fields.introduction.color"
  89. :topRadius="state.property.borderRadiusTop"
  90. :bottomRadius="state.property.borderRadiusBottom"
  91. :titleWidth="330 - marginLeft - marginRight"
  92. @click="sheep.$router.go('/pages/goods/point', { id: item.activityId })"
  93. @getHeight="calculateGoodsColumn($event, 'left')"
  94. >
  95. <!-- 购买按钮 -->
  96. <template v-slot:cart>
  97. <button class="ss-reset-button cart-btn" :style="[buyStyle]">
  98. {{ btnBuy.type === 'text' ? btnBuy.text : '' }}
  99. </button>
  100. </template>
  101. </s-goods-column>
  102. </view>
  103. </view>
  104. <view class="goods-list-box">
  105. <view
  106. class="right-list"
  107. :style="[{ paddingLeft: state.property.space + 'rpx', marginBottom: state.property.space + 'px' }]"
  108. v-for="item in state.rightSpuList"
  109. :key="item.id"
  110. >
  111. <s-goods-column
  112. class="goods-md-box"
  113. size="md"
  114. :goodsFields="state.property.fields"
  115. :tagStyle="state.property.badge"
  116. :data="item"
  117. :titleColor="state.property.fields.name?.color"
  118. :subTitleColor="state.property.fields.introduction.color"
  119. :topRadius="state.property.borderRadiusTop"
  120. :bottomRadius="state.property.borderRadiusBottom"
  121. :titleWidth="330 - marginLeft - marginRight"
  122. @click="sheep.$router.go('/pages/goods/point', { id: item.activityId })"
  123. @getHeight="calculateGoodsColumn($event, 'right')"
  124. >
  125. <!-- 购买按钮 -->
  126. <template v-slot:cart>
  127. <button class="ss-reset-button cart-btn" :style="[buyStyle]">
  128. {{ btnBuy.type === 'text' ? btnBuy.text : '' }}
  129. </button>
  130. </template>
  131. </s-goods-column>
  132. </view>
  133. </view>
  134. </view>
  135. </view>
  136. </template>
  137. <script setup>
  138. /**
  139. * 商品卡片
  140. */
  141. import { computed, onMounted, reactive, watch } from 'vue';
  142. import sheep from '@/sheep';
  143. import SpuApi from '@/sheep/api/product/spu';
  144. import { PromotionActivityTypeEnum } from '@/sheep/util/const';
  145. import { isEmpty } from '@/sheep/helper/utils';
  146. // 布局类型
  147. const LayoutTypeEnum = {
  148. // 单列大图
  149. ONE_COL_BIG_IMG: 'oneColBigImg',
  150. // 双列
  151. TWO_COL: 'twoCol',
  152. // 单列小图
  153. ONE_COL_SMALL_IMG: 'oneColSmallImg',
  154. };
  155. const state = reactive({
  156. spuList: [],
  157. leftSpuList: [],
  158. rightSpuList: [],
  159. property: {
  160. 'layoutType': 'oneColBigImg',
  161. 'fields': {
  162. 'name': {
  163. 'show': true,
  164. 'color': '#000',
  165. },
  166. 'introduction': {
  167. 'show': true,
  168. 'color': '#999',
  169. },
  170. 'price': {
  171. 'show': true,
  172. 'color': '#ff3000',
  173. },
  174. 'marketPrice': {
  175. 'show': true,
  176. 'color': '#c4c4c4',
  177. },
  178. 'salesCount': {
  179. 'show': true,
  180. 'color': '#c4c4c4',
  181. },
  182. 'stock': {
  183. 'show': true,
  184. 'color': '#c4c4c4',
  185. },
  186. },
  187. 'badge': {
  188. 'show': false,
  189. 'imgUrl': '',
  190. },
  191. 'btnBuy': {
  192. 'type': 'text',
  193. 'text': '立即兑换',
  194. 'bgBeginColor': '#FF6000',
  195. 'bgEndColor': '#FE832A',
  196. 'imgUrl': '',
  197. },
  198. 'borderRadiusTop': 8,
  199. 'borderRadiusBottom': 8,
  200. 'space': 8,
  201. 'style': {
  202. 'bgType': 'color',
  203. 'bgColor': '',
  204. 'marginLeft': 8,
  205. 'marginRight': 8,
  206. 'marginBottom': 8,
  207. },
  208. },
  209. });
  210. const props = defineProps({
  211. property: {
  212. type: Object,
  213. default: () => ({}),
  214. },
  215. });
  216. // 动态更新 property
  217. watch(() => props.property, (newVal) => {
  218. state.property = { ...state.property, ...newVal };
  219. }, { immediate: true, deep: true });
  220. const { layoutType, btnBuy } = state.property || {};
  221. const { marginLeft, marginRight } = state.styles || {};
  222. // 购买按钮样式
  223. const buyStyle = computed(() => {
  224. if (btnBuy.type === 'text') {
  225. // 文字按钮:线性渐变背景颜色
  226. return {
  227. background: `linear-gradient(to right, ${btnBuy.bgBeginColor}, ${btnBuy.bgEndColor})`,
  228. };
  229. }
  230. if (btnBuy.type === 'img') {
  231. // 图片按钮
  232. return {
  233. width: '54rpx',
  234. height: '54rpx',
  235. background: `url(${sheep.$url.cdn(btnBuy.imgUrl)}) no-repeat`,
  236. backgroundSize: '100% 100%',
  237. };
  238. }
  239. });
  240. //region 商品瀑布流布局
  241. // 下一个要处理的商品索引
  242. let count = 0;
  243. // 左列的高度
  244. let leftHeight = 0;
  245. // 右列的高度
  246. let rightHeight = 0;
  247. /**
  248. * 计算商品在左列还是右列
  249. * @param height 商品的高度
  250. * @param where 添加到哪一列
  251. */
  252. function calculateGoodsColumn(height = 0, where = 'left') {
  253. // 处理完
  254. if (!state.spuList[count]) return;
  255. // 增加列的高度
  256. if (where === 'left') leftHeight += height;
  257. if (where === 'right') rightHeight += height;
  258. // 添加到矮的一列
  259. if (leftHeight <= rightHeight) {
  260. state.leftSpuList.push(state.spuList[count]);
  261. } else {
  262. state.rightSpuList.push(state.spuList[count]);
  263. }
  264. // 计数
  265. count++;
  266. }
  267. //endregion
  268. /**
  269. * 根据商品编号,获取商品详情
  270. * @param ids 商品编号列表
  271. * @return {Promise<undefined>} 商品列表
  272. */
  273. async function getSpuDetail(ids) {
  274. const { data: spu } = await SpuApi.getSpuDetail(ids);
  275. return spu;
  276. }
  277. async function concatActivity(list) {
  278. if (isEmpty(list)) {
  279. return;
  280. }
  281. // 循环获取活动商品SPU详情并添加到spuList
  282. for (const activity of list) {
  283. state.spuList.push(await getSpuDetail(activity.spuId));
  284. }
  285. // 循环活动列表
  286. list.forEach((activity) => {
  287. // 查找对应的 spu 并更新价格
  288. const spu = state.spuList.find((spu) => activity.spuId === spu.id);
  289. if (spu) {
  290. spu.pointStock = activity.stock;
  291. spu.pointTotalStock = activity.totalStock;
  292. spu.point = activity.point;
  293. spu.pointPrice = activity.price;
  294. // 赋值活动ID,为了点击跳转详情页
  295. spu.activityId = activity.id;
  296. // 赋值活动类型
  297. spu.activityType = PromotionActivityTypeEnum.POINT.type;
  298. }
  299. });
  300. }
  301. function getActivityCount() {
  302. return state.spuList.length;
  303. }
  304. defineExpose({ concatActivity,getActivityCount });
  305. // 初始化
  306. onMounted(async () => {
  307. // 只有双列布局时需要
  308. if (layoutType === LayoutTypeEnum.TWO_COL) {
  309. // 分列
  310. calculateGoodsColumn();
  311. }
  312. });
  313. </script>
  314. <style lang="scss" scoped>
  315. .goods-md-wrap {
  316. width: 100%;
  317. }
  318. .goods-list-box {
  319. width: 50%;
  320. box-sizing: border-box;
  321. .left-list {
  322. &:nth-last-child(1) {
  323. margin-bottom: 0 !important;
  324. }
  325. }
  326. .right-list {
  327. &:nth-last-child(1) {
  328. margin-bottom: 0 !important;
  329. }
  330. }
  331. }
  332. .goods-box {
  333. &:nth-last-of-type(1) {
  334. margin-bottom: 0 !important;
  335. }
  336. }
  337. .goods-md-box,
  338. .goods-sl-box,
  339. .goods-lg-box {
  340. position: relative;
  341. .cart-btn {
  342. position: absolute;
  343. bottom: 18rpx;
  344. right: 20rpx;
  345. z-index: 11;
  346. height: 50rpx;
  347. line-height: 50rpx;
  348. padding: 0 20rpx;
  349. border-radius: 25rpx;
  350. font-size: 24rpx;
  351. color: #fff;
  352. }
  353. }
  354. </style>