feat:【ele】【mall】spu 代码对齐 antd

This commit is contained in:
puhui999
2025-12-20 09:13:38 +08:00
parent f429e74e79
commit e1e0554aca
7 changed files with 119 additions and 132 deletions

View File

@@ -22,16 +22,6 @@ const emit = defineEmits<{
const visible = ref(false);
const spuId = ref<number>();
/** 处理选中 */
function handleRadioChange() {
const selectedRow = gridApi.grid.getRadioRecord() as MallSpuApi.Sku;
if (selectedRow) {
emit('change', selectedRow);
closeModal();
}
}
// TODO @puhui999这里的代码风格对齐 antd 的;可以使用 idea 对比两个文件哈;
const [Grid, gridApi] = useVbenVxeGrid({
gridOptions: {
columns: useSkuGridColumns(),
@@ -39,20 +29,33 @@ const [Grid, gridApi] = useVbenVxeGrid({
border: true,
radioConfig: {
reserve: true,
highlight: true,
},
rowConfig: {
keyField: 'id',
isHover: true,
},
pagerConfig: {
enabled: false,
},
},
gridEvents: {
radioChange: handleRadioChange,
radioChange: () => {
const selectedRow = gridApi.grid.getRadioRecord() as MallSpuApi.Sku;
if (selectedRow) {
emit('change', selectedRow);
// 关闭弹窗
visible.value = false;
gridApi.grid.clearRadioRow();
spuId.value = undefined;
}
},
},
});
/** 关闭弹窗 */
function closeModal() {
visible.value = false;
gridApi.grid.clearRadioRow();
spuId.value = undefined;
}
@@ -63,6 +66,8 @@ async function openModal(data?: SpuData) {
}
spuId.value = data.spuId;
visible.value = true;
// 注意useVbenVxeGrid 关闭分页(pagerConfig.enabled=false)后proxyConfig.ajax.query 的结果不会传递到 vxe-table
// 需要手动调用 reloadData 设置表格数据
if (!spuId.value) {
gridApi.grid?.reloadData([]);
return;

View File

@@ -312,8 +312,10 @@ onMounted(async () => {
</template>
</Grid>
<template #footer>
<el-button @click="closeModal">取消</el-button>
<el-button type="primary" @click="handleConfirm">确定</el-button>
<span class="dialog-footer">
<el-button @click="closeModal">取消</el-button>
<el-button type="primary" @click="handleConfirm">确定</el-button>
</span>
</template>
</ElDialog>
</template>

View File

@@ -108,14 +108,12 @@ function emitSpuChange() {
<ElImage
:src="spu.picUrl"
class="h-full w-full rounded-lg object-cover"
:preview-src-list="[spu.picUrl!]"
fit="cover"
/>
<!-- 删除按钮 -->
<!-- TODO @puhui999还是使用 IconifyIcon使用自己的中立的图标方便 antd ele 共享 -->
<IconifyIcon
v-if="!disabled"
icon="ep:circle-close-filled"
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)"
/>
@@ -129,8 +127,7 @@ function emitSpuChange() {
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"
>
<!-- TODO @puhui999还是使用 IconifyIcon使用自己的中立的图标方便 antd ele 共享 -->
<IconifyIcon icon="ep:plus" class="text-xl text-gray-400" />
<IconifyIcon icon="lucide:plus" class="text-xl text-gray-400" />
</div>
</ElTooltip>
</div>

View File

@@ -52,19 +52,23 @@ const formSchema = computed<VbenFormSchema[]>(() => [
component: 'Input',
componentProps: {
placeholder: '请输入商品名称',
clearable: true,
allowClear: true,
},
},
{
fieldName: 'categoryId',
label: '商品分类',
component: 'ApiTreeSelect',
component: 'TreeSelect',
componentProps: {
options: categoryTreeList,
props: { label: 'name', children: 'children' },
nodeKey: 'id',
data: categoryTreeList,
props: {
label: 'name',
value: 'id',
},
checkStrictly: true,
placeholder: '请选择商品分类',
clearable: true,
filterable: true,
},
},
{
@@ -73,7 +77,7 @@ const formSchema = computed<VbenFormSchema[]>(() => [
component: 'RangePicker',
componentProps: {
...getRangePickerDefaultProps(),
clearable: true,
allowClear: true,
},
},
]);
@@ -162,40 +166,35 @@ const [Grid, gridApi] = useVbenVxeGrid({
async function openModal(data?: MallSpuApi.Spu | MallSpuApi.Spu[]) {
initData.value = data;
visible.value = true;
// 等待 Grid 组件完全初始化后再查询数据
await nextTick();
// 1. 查询数据
await gridApi.query();
// 2. 设置已选中行
const tableData = gridApi.grid.getTableData().fullData;
if (
props.multiple &&
Array.isArray(initData.value) &&
initData.value.length > 0
) {
setTimeout(() => {
(initData.value as unknown as MallSpuApi.Spu[])!.forEach((spu) => {
if (gridApi.grid) {
// 1. 先查询数据
await gridApi.query();
// 2. 设置已选中行
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(
(item: MallSpuApi.Spu) => item.id === spu.id,
(item: MallSpuApi.Spu) => item.id === data.id,
);
if (row) {
gridApi.grid.setCheckboxRow(row, true);
gridApi.grid.setRadioRow(row);
}
});
}, 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);
}, 300);
}
}
}
@@ -214,10 +213,9 @@ function handleConfirm() {
closeModal();
}
/** 对外暴露的方法 */
defineExpose({
open: openModal,
});
}); // 对外暴露的方法
/** 初始化分类数据 */
onMounted(async () => {
@@ -232,13 +230,14 @@ onMounted(async () => {
title="选择商品"
width="950px"
:destroy-on-close="true"
:append-to-body="true"
@close="closeModal"
>
<Grid />
<template v-if="props.multiple" #footer>
<el-button @click="closeModal">取消</el-button>
<el-button type="primary" @click="handleConfirm">确定</el-button>
<template #footer>
<span v-if="props.multiple" class="dialog-footer">
<el-button @click="closeModal">取消</el-button>
<el-button type="primary" @click="handleConfirm">确定</el-button>
</span>
</template>
</ElDialog>
</template>

View File

@@ -32,7 +32,6 @@ const spuId = ref<number>();
const { params, name } = useRoute();
const { closeCurrentTab } = useTabs();
const activeTabName = ref('info');
const formLoading = ref(false); // 表单的加载中1修改时的数据加载2提交的按钮禁用
const isDetail = ref(name === 'ProductSpuDetail'); // 是否查看详情
const skuListRef = ref(); // 商品属性列表 Ref
@@ -70,7 +69,6 @@ const formData = ref<MallSpuApi.Spu>({
}); // spu 表单数据
const propertyList = ref<PropertyAndValues[]>([]); // 商品属性列表
const ruleConfig: RuleConfig[] = [
// TODO @puhui999ele 这里都有 :numberantd 要不要加?
{
name: 'stock',
rule: (arg: number) => arg >= 0,
@@ -200,7 +198,7 @@ async function handleSubmit() {
item.secondBrokeragePrice = convertToInteger(item.secondBrokeragePrice);
});
}
// 处理轮播图列表 TODO @puhui999这个是必须的哇
// 处理轮播图列表:上传组件可能返回对象或字符串,统一处理成字符串数组
const newSliderPicUrls: any[] = [];
values.sliderPicUrls!.forEach((item: any) => {
// 如果是前端选的图
@@ -319,13 +317,10 @@ onMounted(async () => {
<ProductPropertyAddFormModal :property-list="propertyList" />
<Page auto-content-height>
<ElCard class="spu-form-card h-full w-full" v-loading="formLoading">
<ElCard class="h-full w-full" v-loading="formLoading">
<template #header>
<div class="flex items-center justify-between">
<ElTabs
v-model="activeTabName"
@tab-click="(tab: any) => handleTabChange(tab.paneName)"
>
<ElTabs v-model="activeTabName" @tab-change="handleTabChange">
<ElTabPane label="基础设置" name="info" />
<ElTabPane label="价格库存" name="sku" />
<ElTabPane label="物流设置" name="delivery" />
@@ -333,7 +328,7 @@ onMounted(async () => {
<ElTabPane label="其它设置" name="other" />
</ElTabs>
<div>
<ElButton v-if="!isDetail" type="primary" @click="handleSubmit">
<ElButton type="primary" v-if="!isDetail" @click="handleSubmit">
保存
</ElButton>
<ElButton v-else @click="() => closeCurrentTab()">
@@ -343,71 +338,61 @@ onMounted(async () => {
</div>
</template>
<div class="flex-1 overflow-auto">
<InfoForm class="w-3/5" v-show="activeTabName === 'info'" />
<SkuForm class="w-full" v-show="activeTabName === 'sku'">
<template #singleSkuList>
<SkuList
ref="skuListRef"
class="w-full"
<InfoForm class="w-3/5" v-show="activeTabName === 'info'" />
<SkuForm class="w-full" v-show="activeTabName === 'sku'">
<template #singleSkuList>
<SkuList
ref="skuListRef"
class="w-full"
:is-detail="isDetail"
:prop-form-data="formData"
:property-list="propertyList"
:rule-config="ruleConfig"
/>
</template>
<template #productAttributes>
<div>
<ElButton class="mb-10px mr-15px" @click="openPropertyAddForm">
添加属性
</ElButton>
<ProductAttributes
:is-detail="isDetail"
:prop-form-data="formData"
:property-list="propertyList"
:rule-config="ruleConfig"
@success="generateSkus"
/>
</template>
<template #productAttributes>
<div>
<ElButton class="mb-10px mr-15px" @click="openPropertyAddForm">
添加属性
</ElButton>
<ProductAttributes
:is-detail="isDetail"
:property-list="propertyList"
@success="generateSkus"
/>
</div>
</template>
<template #batchSkuList>
<SkuList
:is-batch="true"
:is-detail="isDetail"
:prop-form-data="formData"
:property-list="propertyList"
/>
</template>
<template #multiSkuList>
<SkuList
ref="skuListRef"
:is-detail="isDetail"
:prop-form-data="formData"
:property-list="propertyList"
:rule-config="ruleConfig"
/>
</template>
</SkuForm>
<DeliveryForm class="w-3/5" v-show="activeTabName === 'delivery'" />
<DescriptionForm
class="w-3/5"
v-show="activeTabName === 'description'"
/>
<OtherForm class="w-3/5" v-show="activeTabName === 'other'" />
</div>
</div>
</template>
<template #batchSkuList>
<SkuList
:is-batch="true"
:is-detail="isDetail"
:prop-form-data="formData"
:property-list="propertyList"
/>
</template>
<template #multiSkuList>
<SkuList
ref="skuListRef"
:is-detail="isDetail"
:prop-form-data="formData"
:property-list="propertyList"
:rule-config="ruleConfig"
/>
</template>
</SkuForm>
<DeliveryForm class="w-3/5" v-show="activeTabName === 'delivery'" />
<DescriptionForm
class="w-3/5"
v-show="activeTabName === 'description'"
/>
<OtherForm class="w-3/5" v-show="activeTabName === 'other'" />
</ElCard>
</Page>
</div>
</template>
<style scoped>
.spu-form-card {
display: flex;
flex-direction: column;
}
.spu-form-card :deep(.el-card__body) {
flex: 1;
overflow: hidden;
display: flex;
flex-direction: column;
<style lang="scss" scoped>
:deep(.el-tabs__nav-wrap::after) {
display: none;
}
</style>

View File

@@ -215,18 +215,17 @@ async function getAttributeOptions(propertyId: number) {
<ElOption
v-for="item2 in attributeOptions"
:key="item2.id"
:value="item2.name"
:label="item2.name"
:value="item2.name"
/>
</ElSelect>
<ElTag
v-show="!inputVisible(index)"
@click="showInput(index)"
class="mx-1 cursor-pointer"
effect="plain"
class="mx-1 cursor-pointer border-dashed bg-gray-100"
>
<div class="flex items-center">
<IconifyIcon class="mr-2" icon="ep:plus" />
<IconifyIcon class="mr-2" icon="lucide:plus" />
添加
</div>
</ElTag>

View File

@@ -61,9 +61,9 @@ const formSchema: VbenFormSchema[] = [
}));
},
filterable: true,
allowCreate: true,
placeholder: '请选择属性名称。如果不存在,可手动输入选择',
multiple: true,
allowCreate: true,
clearable: true,
},
rules: 'required',