gaoluyang
2026-06-24 712aa51536236d43e87273e4ce45ac5691dffad8
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
<script setup lang="ts">
import type { ProductListProperty } from './config';
 
import type { MallSpuApi } from '#/api/mall/product/spu';
 
import { onMounted, ref, watch } from 'vue';
 
import { fenToYuan } from '..\..\..\..\..\..\..\..\packages\utils\src';
 
import { Image } from 'ant-design-vue';
 
import { getSpuDetailList } from '#/api/mall/product/spu';
 
/** 商品栏 */
defineOptions({ name: 'ProductList' });
 
const props = defineProps<{ property: ProductListProperty }>();
 
const spuList = ref<MallSpuApi.Spu[]>([]);
 
watch(
  () => props.property.spuIds,
  async () => {
    spuList.value = props.property.spuIds.length > 0
      ? await getSpuDetailList(props.property.spuIds)
      : [];
  },
  {
    immediate: true,
    deep: true,
  },
);
 
const phoneWidth = ref(375); // 手机宽度
const containerRef = ref(); // 容器
const columns = ref(2); // 商品的列数
const scrollbarWidth = ref('100%'); // 滚动条宽度
const imageSize = ref('0'); // 商品图大小
const gridTemplateColumns = ref(''); // 商品网络列数
 
/** 计算布局参数 */
watch(
  () => [props.property, phoneWidth, spuList.value.length],
  () => {
    // 计算列数
    columns.value = props.property.layoutType === 'twoCol' ? 2 : 3;
    // 每列的宽度为:(总宽度 - 间距 * (列数 - 1))/ 列数
    const productWidth =
      (phoneWidth.value - props.property.space * (columns.value - 1)) /
      columns.value;
    // 商品图布局:2列时,左右布局 3列时,上下布局
    imageSize.value = columns.value === 2 ? '64px' : `${productWidth}px`;
    // 根据布局类型,计算行数、列数
    if (props.property.layoutType === 'horizSwiper') {
      // 单行显示
      gridTemplateColumns.value = `repeat(auto-fill, ${productWidth}px)`;
      // 显示滚动条
      scrollbarWidth.value = `${
        productWidth * spuList.value.length +
        props.property.space * (spuList.value.length - 1)
      }px`;
    } else {
      // 指定列数
      gridTemplateColumns.value = `repeat(${columns.value}, auto)`;
      // 不滚动
      scrollbarWidth.value = '100%';
    }
  },
  { immediate: true, deep: true },
);
 
/** 初始化 */
onMounted(() => {
  phoneWidth.value = containerRef.value?.offsetWidth || 375;
});
</script>
<template>
  <div class="z-10 min-h-[30px] w-full" ref="containerRef">
    <!-- 商品网格 -->
    <div
      class="grid overflow-x-auto"
      :style="{
        gridGap: `${property.space}px`,
        gridTemplateColumns,
        width: scrollbarWidth,
      }"
    >
      <!-- 商品 -->
      <div
        class="relative box-content flex flex-row flex-wrap overflow-hidden bg-white"
        :style="{
          borderTopLeftRadius: `${property.borderRadiusTop}px`,
          borderTopRightRadius: `${property.borderRadiusTop}px`,
          borderBottomLeftRadius: `${property.borderRadiusBottom}px`,
          borderBottomRightRadius: `${property.borderRadiusBottom}px`,
        }"
        v-for="(spu, index) in spuList"
        :key="index"
      >
        <!-- 角标 -->
        <div
          v-if="property.badge.show"
          class="absolute left-0 top-0 z-10 items-center justify-center"
        >
          <Image
            :src="property.badge.imgUrl"
            :preview="false"
            class="h-[26px] w-[38px] object-cover"
          />
        </div>
        <!-- 商品封面图 -->
        <Image
          :src="spu.picUrl"
          :style="{ width: imageSize, height: imageSize, objectFit: 'cover' }"
          :preview="false"
        />
        <div
          class="box-border flex flex-col gap-2 p-2"
          :class="[
            {
              'w-[calc(100%-64px)]': columns === 2,
              'w-full': columns === 3,
            },
          ]"
        >
          <!-- 商品名称 -->
          <div
            v-if="property.fields.name.show"
            class="truncate text-xs"
            :style="{ color: property.fields.name.color }"
          >
            {{ spu.name }}
          </div>
          <div>
            <!-- 商品价格 -->
            <span
              v-if="property.fields.price.show"
              class="text-xs"
              :style="{ color: property.fields.price.color }"
            >
              ¥{{ fenToYuan(spu.price || 0) }}
            </span>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>