feat:【ele】【mall】spu 选择组件优化

This commit is contained in:
puhui999
2025-12-15 16:07:47 +08:00
parent e0d3fac19e
commit 5417b19a8b
2 changed files with 116 additions and 124 deletions

View File

@@ -1,16 +1,16 @@
<!-- SKU 选择弹窗组件 --> <!-- SKU 选择弹窗组件 -->
<script lang="ts" setup> <script lang="ts" setup>
import type { VxeGridProps } from '#/adapter/vxe-table';
import type { MallSpuApi } from '#/api/mall/product/spu'; import type { MallSpuApi } from '#/api/mall/product/spu';
import { computed, ref } from 'vue'; import { ref } from 'vue';
import { useVbenModal } from '@vben/common-ui'; import { ElDialog } from 'element-plus';
import { fenToYuan } from '@vben/utils';
import { useVbenVxeGrid } from '#/adapter/vxe-table'; import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { getSpu } from '#/api/mall/product/spu'; import { getSpu } from '#/api/mall/product/spu';
import { useSkuGridColumns } from './spu-select-data';
interface SpuData { interface SpuData {
spuId: number; spuId: number;
} }
@@ -19,78 +19,28 @@ const emit = defineEmits<{
change: [sku: MallSpuApi.Sku]; change: [sku: MallSpuApi.Sku];
}>(); }>();
const visible = ref(false);
const spuId = ref<number>(); const spuId = ref<number>();
/** 表格列配置 */
const gridColumns = computed<VxeGridProps['columns']>(() => [
{
type: 'radio',
width: 55,
},
{
field: 'picUrl',
title: '图片',
width: 100,
align: 'center',
cellRender: {
name: 'CellImage',
},
},
{
field: 'properties',
title: '规格',
minWidth: 120,
align: 'center',
formatter: ({ cellValue }) => {
return (
cellValue?.map((p: MallSpuApi.Property) => p.valueName)?.join(' ') ||
'-'
);
},
},
{
field: 'price',
title: '销售价(元)',
width: 120,
align: 'center',
formatter: ({ cellValue }) => {
return fenToYuan(cellValue);
},
},
]);
/** 处理选中 */ /** 处理选中 */
function handleRadioChange() { function handleRadioChange() {
const selectedRow = gridApi.grid.getRadioRecord() as MallSpuApi.Sku; const selectedRow = gridApi.grid.getRadioRecord() as MallSpuApi.Sku;
if (selectedRow) { if (selectedRow) {
emit('change', selectedRow); emit('change', selectedRow);
modalApi.close(); closeModal();
} }
} }
// TODO @芋艿:要不要直接非 pager
const [Grid, gridApi] = useVbenVxeGrid({ const [Grid, gridApi] = useVbenVxeGrid({
gridOptions: { gridOptions: {
columns: gridColumns.value, columns: useSkuGridColumns(),
height: 400, height: 400,
border: true, border: true,
showOverflow: true,
radioConfig: { radioConfig: {
reserve: true, reserve: true,
}, },
proxyConfig: { pagerConfig: {
ajax: { enabled: false,
query: async () => {
if (!spuId.value) {
return { list: [], total: 0 };
}
const spu = await getSpu(spuId.value);
return {
list: spu.skus || [],
total: spu.skus?.length || 0,
};
},
},
}, },
}, },
gridEvents: { gridEvents: {
@@ -98,26 +48,43 @@ const [Grid, gridApi] = useVbenVxeGrid({
}, },
}); });
const [Modal, modalApi] = useVbenModal({ /** 关闭弹窗 */
destroyOnClose: true, function closeModal() {
onOpenChange: async (isOpen: boolean) => { visible.value = false;
if (!isOpen) { gridApi.grid.clearRadioRow();
gridApi.grid.clearRadioRow(); spuId.value = undefined;
spuId.value = undefined; }
return;
} /** 打开弹窗 */
const data = modalApi.getData<SpuData>(); async function openModal(data?: SpuData) {
if (!data?.spuId) { if (!data?.spuId) {
return; return;
} }
spuId.value = data.spuId; spuId.value = data.spuId;
await gridApi.query(); visible.value = true;
}, if (!spuId.value) {
gridApi.grid?.reloadData([]);
return;
}
const spu = await getSpu(spuId.value);
gridApi.grid?.reloadData(spu.skus || []);
}
/** 对外暴露的方法 */
defineExpose({
open: openModal,
}); });
</script> </script>
<template> <template>
<Modal class="w-[700px]" title="选择规格"> <ElDialog
v-model="visible"
title="选择规格"
width="700px"
:destroy-on-close="true"
:append-to-body="true"
@close="closeModal"
>
<Grid /> <Grid />
</Modal> </ElDialog>
</template> </template>

View File

@@ -5,11 +5,12 @@ import type { VxeGridProps } from '#/adapter/vxe-table';
import type { MallCategoryApi } from '#/api/mall/product/category'; import type { MallCategoryApi } from '#/api/mall/product/category';
import type { MallSpuApi } from '#/api/mall/product/spu'; import type { MallSpuApi } from '#/api/mall/product/spu';
import { computed, onMounted, ref } from 'vue'; import { computed, nextTick, onMounted, ref } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { handleTree } from '@vben/utils'; import { handleTree } from '@vben/utils';
import { ElDialog } from 'element-plus';
import { useVbenVxeGrid } from '#/adapter/vxe-table'; import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { getCategoryList } from '#/api/mall/product/category'; import { getCategoryList } from '#/api/mall/product/category';
import { getSpuPage } from '#/api/mall/product/spu'; import { getSpuPage } from '#/api/mall/product/spu';
@@ -30,12 +31,16 @@ const emit = defineEmits<{
const categoryList = ref<MallCategoryApi.Category[]>([]); // 分类列表 const categoryList = ref<MallCategoryApi.Category[]>([]); // 分类列表
const categoryTreeList = ref<any[]>([]); // 分类树 const categoryTreeList = ref<any[]>([]); // 分类树
/** 弹窗显示状态 */
const visible = ref(false);
const initData = ref<MallSpuApi.Spu | MallSpuApi.Spu[]>();
/** 单选:处理选中变化 */ /** 单选:处理选中变化 */
function handleRadioChange() { function handleRadioChange() {
const selectedRow = gridApi.grid.getRadioRecord() as MallSpuApi.Spu; const selectedRow = gridApi.grid.getRadioRecord() as MallSpuApi.Spu;
if (selectedRow) { if (selectedRow) {
emit('change', selectedRow); emit('change', selectedRow);
modalApi.close(); closeModal();
} }
} }
@@ -153,56 +158,65 @@ const [Grid, gridApi] = useVbenVxeGrid({
}, },
}); });
const [Modal, modalApi] = useVbenModal({ /** 打开弹窗 */
destroyOnClose: true, async function openModal(data?: MallSpuApi.Spu | MallSpuApi.Spu[]) {
showConfirmButton: props.multiple, // 特殊radio 单选情况下,走 handleRadioChange 处理。 initData.value = data;
onConfirm: () => { visible.value = true;
const selectedRows = gridApi.grid.getCheckboxRecords() as MallSpuApi.Spu[]; await nextTick();
emit('change', selectedRows); // 1. 查询数据
modalApi.close(); await gridApi.query();
}, // 2. 设置已选中行
async onOpenChange(isOpen: boolean) { const tableData = gridApi.grid.getTableData().fullData;
if (!isOpen) { if (
await gridApi.grid.clearCheckboxRow(); props.multiple &&
await gridApi.grid.clearRadioRow(); Array.isArray(initData.value) &&
return; initData.value.length > 0
} ) {
setTimeout(() => {
// 1. 先查询数据 (initData.value as unknown as MallSpuApi.Spu[])!.forEach((spu) => {
await gridApi.query();
// 2. 设置已选中行
const data = modalApi.getData<MallSpuApi.Spu | MallSpuApi.Spu[]>();
if (props.multiple && Array.isArray(data) && data.length > 0) {
setTimeout(() => {
const tableData = gridApi.grid.getTableData().fullData;
data.forEach((spu) => {
const row = tableData.find(
(item: MallSpuApi.Spu) => item.id === spu.id,
);
if (row) {
gridApi.grid.setCheckboxRow(row, true);
}
});
}, 300);
} else if (!props.multiple && data && !Array.isArray(data)) {
setTimeout(() => {
const tableData = gridApi.grid.getTableData().fullData;
const row = tableData.find( const row = tableData.find(
(item: MallSpuApi.Spu) => item.id === data.id, (item: MallSpuApi.Spu) => item.id === spu.id,
); );
if (row) { if (row) {
gridApi.grid.setRadioRow(row); gridApi.grid.setCheckboxRow(row, true);
} }
}, 300); });
} }, 300);
}, } else if (
}); !props.multiple &&
initData.value &&
!Array.isArray(initData.value)
) {
setTimeout(() => {
const row = tableData.find(
(item: MallSpuApi.Spu) =>
item.id === (initData.value as MallSpuApi.Spu).id,
);
if (row) {
gridApi.grid.setRadioRow(row);
}
}, 300);
}
}
/** 关闭弹窗 */
async function closeModal() {
visible.value = false;
await gridApi.grid.clearCheckboxRow();
await gridApi.grid.clearRadioRow();
initData.value = undefined;
}
/** 确认选择(多选模式) */
function handleConfirm() {
const selectedRows = gridApi.grid.getCheckboxRecords() as MallSpuApi.Spu[];
emit('change', selectedRows);
closeModal();
}
/** 对外暴露的方法 */ /** 对外暴露的方法 */
defineExpose({ defineExpose({
open: (data?: MallSpuApi.Spu | MallSpuApi.Spu[]) => { open: openModal,
modalApi.setData(data).open();
},
}); });
/** 初始化分类数据 */ /** 初始化分类数据 */
@@ -213,7 +227,18 @@ onMounted(async () => {
</script> </script>
<template> <template>
<Modal title="选择商品" class="w-[950px]"> <ElDialog
v-model="visible"
title="选择商品"
width="950px"
:destroy-on-close="true"
:append-to-body="true"
@close="closeModal"
>
<Grid /> <Grid />
</Modal> <template v-if="props.multiple" #footer>
<el-button @click="closeModal">取消</el-button>
<el-button type="primary" @click="handleConfirm">确定</el-button>
</template>
</ElDialog>
</template> </template>