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
<!-- 商品橱窗组件:用于展示和选择商品 SPU -->
<script lang="ts" setup>
import type { MallSpuApi } from '#/api/mall/product/spu';
 
import { computed, ref, watch } from 'vue';
 
import { IconifyIcon } from '..\..\..\..\..\packages\icons\src';
 
import { Image, Tooltip } from 'ant-design-vue';
 
import { getSpuDetailList } from '#/api/mall/product/spu';
 
import SpuTableSelect from './spu-table-select.vue';
 
interface SpuShowcaseProps {
  modelValue?: number | number[];
  limit?: number;
  disabled?: boolean;
}
 
const props = withDefaults(defineProps<SpuShowcaseProps>(), {
  modelValue: undefined,
  limit: Number.MAX_VALUE,
  disabled: false,
});
 
const emit = defineEmits(['update:modelValue', 'change']);
 
const productSpus = ref<MallSpuApi.Spu[]>([]); // 已选择的商品列表
const spuTableSelectRef = ref<InstanceType<typeof SpuTableSelect>>(); // 商品选择表格组件引用
const isMultiple = computed(() => props.limit !== 1); // 是否为多选模式
 
/** 计算是否可以添加 */
const canAdd = computed(() => {
  if (props.disabled) {
    return false;
  }
  if (!props.limit) {
    return true;
  }
  return productSpus.value.length < props.limit;
});
 
/** 监听 modelValue 变化,加载商品详情 */
watch(
  () => props.modelValue,
  async (newValue) => {
    // eslint-disable-next-line unicorn/no-nested-ternary
    const ids = Array.isArray(newValue) ? newValue : newValue ? [newValue] : [];
    if (ids.length === 0) {
      productSpus.value = [];
      return;
    }
    // 只有商品发生变化时才重新查询
    if (
      productSpus.value.length === 0 ||
      productSpus.value.some((spu) => !ids.includes(spu.id!))
    ) {
      productSpus.value = await getSpuDetailList(ids);
    }
  },
  { immediate: true },
);
 
/** 打开商品选择对话框 */
function handleOpenSpuSelect() {
  spuTableSelectRef.value?.open(productSpus.value);
}
 
/** 选择商品后触发 */
function handleSpuSelected(spus: MallSpuApi.Spu | MallSpuApi.Spu[]) {
  productSpus.value = Array.isArray(spus) ? spus : [spus];
  emitSpuChange();
}
 
/** 删除商品 */
function handleRemoveSpu(index: number) {
  productSpus.value.splice(index, 1);
  emitSpuChange();
}
 
/** 触发变更事件 */
function emitSpuChange() {
  if (props.limit === 1) {
    const spu = productSpus.value.length > 0 ? productSpus.value[0] : null;
    emit('update:modelValue', spu?.id || 0);
    emit('change', spu);
  } else {
    emit(
      'update:modelValue',
      productSpus.value.map((spu) => spu.id!),
    );
    emit('change', productSpus.value);
  }
}
</script>
 
<template>
  <div class="flex flex-wrap items-center gap-2">
    <!-- 已选商品列表 -->
    <div
      v-for="(spu, index) in productSpus"
      :key="spu.id"
      class="group relative h-[60px] w-[60px] overflow-hidden rounded-lg"
    >
      <Tooltip :title="spu.name">
        <div class="relative h-full w-full">
          <Image
            :src="spu.picUrl"
            class="h-full w-full rounded-lg object-cover"
          />
          <!-- 删除按钮 -->
          <IconifyIcon
            v-if="!disabled"
            icon="lucide:circle-x"
            class="absolute -right-2 -top-2 cursor-pointer text-xl text-red-500 opacity-0 transition-opacity hover:text-red-600 group-hover:opacity-100"
            @click="handleRemoveSpu(index)"
          />
        </div>
      </Tooltip>
    </div>
 
    <!-- 添加商品按钮 -->
    <Tooltip v-if="canAdd" title="选择商品">
      <div
        class="flex h-[60px] w-[60px] cursor-pointer items-center justify-center rounded-lg border-2 border-dashed transition-colors hover:border-primary hover:bg-primary/5"
        @click="handleOpenSpuSelect"
      >
        <IconifyIcon icon="lucide:plus" class="text-xl text-gray-400" />
      </div>
    </Tooltip>
  </div>
 
  <!-- 商品选择对话框 -->
  <SpuTableSelect
    ref="spuTableSelectRef"
    :multiple="isMultiple"
    @change="handleSpuSelected"
  />
</template>