s-point-card.vue 11 KB

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