feat:【mall 商城】商品橱窗组件(antd)
This commit is contained in:
@@ -0,0 +1,3 @@
|
|||||||
|
export { default as SkuTableSelect } from './sku-table-select.vue';
|
||||||
|
export { default as SpuShowcase } from './spu-showcase.vue';
|
||||||
|
export { default as SpuTableSelect } from './spu-table-select.vue';
|
||||||
@@ -0,0 +1,154 @@
|
|||||||
|
<!-- 商品橱窗组件:用于展示和选择商品 SPU -->
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import type { MallSpuApi } from '#/api/mall/product/spu';
|
||||||
|
|
||||||
|
import { computed, ref, watch } from 'vue';
|
||||||
|
|
||||||
|
import { CloseCircleFilled, PlusOutlined } from '@vben/icons';
|
||||||
|
|
||||||
|
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 canAdd = computed(() => {
|
||||||
|
if (props.disabled) return false;
|
||||||
|
if (!props.limit) return true;
|
||||||
|
return productSpus.value.length < props.limit;
|
||||||
|
});
|
||||||
|
|
||||||
|
/** 是否为多选模式 */
|
||||||
|
const isMultiple = computed(() => props.limit !== 1);
|
||||||
|
|
||||||
|
/** 监听 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="spu-item group relative"
|
||||||
|
>
|
||||||
|
<Tooltip :title="spu.name">
|
||||||
|
<div class="relative h-full w-full">
|
||||||
|
<Image
|
||||||
|
:src="spu.picUrl"
|
||||||
|
class="h-full w-full rounded-lg object-cover"
|
||||||
|
:preview="false"
|
||||||
|
/>
|
||||||
|
<!-- 删除按钮 -->
|
||||||
|
<CloseCircleFilled
|
||||||
|
v-if="!disabled"
|
||||||
|
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="spu-add-box hover:border-primary hover:bg-primary/5 flex cursor-pointer items-center justify-center rounded-lg border-2 border-dashed transition-colors"
|
||||||
|
@click="handleOpenSpuSelect"
|
||||||
|
>
|
||||||
|
<PlusOutlined class="text-xl text-gray-400" />
|
||||||
|
</div>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 商品选择对话框 -->
|
||||||
|
<SpuTableSelect
|
||||||
|
ref="spuTableSelectRef"
|
||||||
|
:multiple="isMultiple"
|
||||||
|
@change="handleSpuSelected"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.spu-item {
|
||||||
|
width: 60px;
|
||||||
|
height: 60px;
|
||||||
|
border-radius: 8px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spu-add-box {
|
||||||
|
width: 60px;
|
||||||
|
height: 60px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -216,6 +216,13 @@ onMounted(async () => {
|
|||||||
categoryList.value = await getCategoryList({});
|
categoryList.value = await getCategoryList({});
|
||||||
categoryTreeList.value = handleTree(categoryList.value, 'id', 'parentId');
|
categoryTreeList.value = handleTree(categoryList.value, 'id', 'parentId');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/** 对外暴露的方法 */
|
||||||
|
defineExpose({
|
||||||
|
open: (data?: MallSpuApi.Spu | MallSpuApi.Spu[]) => {
|
||||||
|
modalApi.setData(data).open();
|
||||||
|
},
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|||||||
@@ -60,4 +60,8 @@ export const MenuOutlined = createIconifyIcon('ant-design:menu-outlined');
|
|||||||
|
|
||||||
export const PlusOutlined = createIconifyIcon('ant-design:plus-outlined');
|
export const PlusOutlined = createIconifyIcon('ant-design:plus-outlined');
|
||||||
|
|
||||||
|
export const CloseCircleFilled = createIconifyIcon(
|
||||||
|
'ant-design:close-circle-filled',
|
||||||
|
);
|
||||||
|
|
||||||
export const SelectOutlined = createIconifyIcon('ant-design:select-outlined');
|
export const SelectOutlined = createIconifyIcon('ant-design:select-outlined');
|
||||||
|
|||||||
Reference in New Issue
Block a user