fix:【antd】【mall】完善评论商品选择

This commit is contained in:
puhui999
2025-11-25 23:36:17 +08:00
parent 9b521cff90
commit c6ef772f93
4 changed files with 168 additions and 55 deletions

View File

@@ -3,7 +3,6 @@ import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { MallCommentApi } from '#/api/mall/product/comment';
import { z } from '#/adapter/form';
import { getSpuSimpleList } from '#/api/mall/product/spu';
import { getRangePickerDefaultProps } from '#/utils';
/** 新增/修改的表单 */
@@ -21,13 +20,13 @@ export function useFormSchema(): VbenFormSchema[] {
{
fieldName: 'spuId',
label: '商品',
component: 'ApiSelect',
component: 'Input',
componentProps: {
api: getSpuSimpleList,
labelField: 'name',
valueField: 'id',
placeholder: '请选择商品',
},
renderComponentContent: () => ({
default: () => null,
}),
rules: 'required',
},
{
@@ -41,6 +40,9 @@ export function useFormSchema(): VbenFormSchema[] {
triggerFields: ['spuId'],
show: (values) => !!values.spuId,
},
renderComponentContent: () => ({
default: () => null,
}),
rules: 'required',
},
{

View File

@@ -1,21 +1,31 @@
<script lang="ts" setup>
import type { MallCommentApi } from '#/api/mall/product/comment';
import type { MallSpuApi } from '#/api/mall/product/spu';
import { computed, ref } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { message } from 'ant-design-vue';
import { Button, message } from 'ant-design-vue';
import { useVbenForm } from '#/adapter/form';
import { createComment, getComment } from '#/api/mall/product/comment';
import { getSpu } from '#/api/mall/product/spu';
import { $t } from '#/locales';
import {
SkuTableSelect,
SpuShowcase,
} from '#/views/mall/product/spu/components';
import { useFormSchema } from '../data';
const emit = defineEmits(['success']);
const formData = ref<MallCommentApi.Comment>();
// 初始化 formData确保始终有值
const formData = ref<Partial<MallCommentApi.Comment>>({
descriptionScores: 5,
benefitScores: 5,
});
const getTitle = computed(() => {
return formData.value?.id
? $t('ui.actionTitle.edit', ['虚拟评论'])
@@ -35,6 +45,37 @@ const [Form, formApi] = useVbenForm({
showDefaultActions: false,
});
const skuTableSelectRef = ref<InstanceType<typeof SkuTableSelect>>();
const selectedSku = ref<MallSpuApi.Sku>();
async function handleSpuChange(spu?: MallSpuApi.Spu | null) {
// 处理商品选择:如果 spu 为 null 或 id 为 0表示清空选择
const spuId = spu?.id && spu.id ? spu.id : undefined;
formData.value.spuId = spuId;
await formApi.setFieldValue('spuId', spuId);
// 清空已选规格
selectedSku.value = undefined;
formData.value.skuId = undefined;
await formApi.setFieldValue('skuId', undefined);
}
async function openSkuSelect() {
const currentValues =
(await formApi.getValues()) as Partial<MallCommentApi.Comment>;
const currentSpuId = currentValues.spuId ?? formData.value?.spuId;
if (!currentSpuId) {
message.warning('请先选择商品');
return;
}
skuTableSelectRef.value?.open({ spuId: currentSpuId });
}
async function handleSkuSelected(sku: MallSpuApi.Sku) {
selectedSku.value = sku;
formData.value.skuId = sku.id;
await formApi.setFieldValue('skuId', sku.id);
}
const [Modal, modalApi] = useVbenModal({
async onConfirm() {
const { valid } = await formApi.validate();
@@ -56,19 +97,42 @@ const [Modal, modalApi] = useVbenModal({
},
async onOpenChange(isOpen: boolean) {
if (!isOpen) {
formData.value = undefined;
// 重置表单数据
formData.value = {
descriptionScores: 5,
benefitScores: 5,
} as Partial<MallCommentApi.Comment>;
selectedSku.value = undefined;
return;
}
// 加载数据
const data = modalApi.getData<MallCommentApi.Comment>();
if (!data || !data.id) {
// 新建模式:重置表单
formData.value = {
descriptionScores: 5,
benefitScores: 5,
} as Partial<MallCommentApi.Comment>;
selectedSku.value = undefined;
await formApi.setValues({ spuId: undefined, skuId: undefined });
return;
}
// 编辑模式:加载数据
modalApi.lock();
try {
formData.value = await getComment(data.id);
// 设置到 values
await formApi.setValues(formData.value);
// 回显已选规格
if (formData.value?.spuId && formData.value?.skuId) {
const spu = await getSpu(formData.value.spuId);
const sku = spu.skus?.find((item) => item.id === formData.value!.skuId);
if (sku) {
selectedSku.value = sku;
}
} else {
selectedSku.value = undefined;
}
} finally {
modalApi.unlock();
}
@@ -78,6 +142,38 @@ const [Modal, modalApi] = useVbenModal({
<template>
<Modal class="w-2/5" :title="getTitle">
<Form class="mx-4" />
<Form class="mx-4">
<template #spuId>
<SpuShowcase
v-model="(formData as any).spuId"
:limit="1"
@change="handleSpuChange"
/>
</template>
<template #skuId>
<div class="flex items-center gap-2">
<Button
type="primary"
:disabled="!formData?.spuId"
@click="openSkuSelect"
>
选择规格
</Button>
<span
v-if="
selectedSku &&
selectedSku.properties &&
selectedSku.properties.length > 0
"
>
已选:{{
selectedSku.properties.map((p: any) => p.valueName).join('/')
}}
</span>
<span v-else-if="selectedSku">已选:{{ selectedSku.id }}</span>
</div>
</template>
</Form>
<SkuTableSelect ref="skuTableSelectRef" @change="handleSkuSelected" />
</Modal>
</template>

View File

@@ -3,7 +3,7 @@
import type { VxeGridProps } from '#/adapter/vxe-table';
import type { MallSpuApi } from '#/api/mall/product/spu';
import { computed, ref } from 'vue';
import { ref } from 'vue';
import { fenToYuan } from '@vben/utils';
@@ -24,7 +24,7 @@ const visible = ref(false);
const spuId = ref<number>();
/** 表格列配置 */
const gridColumns = computed<VxeGridProps['columns']>(() => [
const gridColumns: VxeGridProps['columns'] = [
{
type: 'radio',
width: 55,
@@ -59,27 +59,34 @@ const gridColumns = computed<VxeGridProps['columns']>(() => [
return fenToYuan(cellValue);
},
},
]);
];
// TODO @芋艿:要不要直接非 pager
const [Grid, gridApi] = useVbenVxeGrid({
gridOptions: {
columns: gridColumns.value,
columns: gridColumns,
height: 400,
border: true,
showOverflow: true,
radioConfig: {
reserve: true,
},
rowConfig: {
keyField: 'id',
isHover: true,
},
pagerConfig: {
enabled: false,
},
proxyConfig: {
// autoLoad: false, // 禁用自动加载,手动触发查询
ajax: {
query: async () => {
if (!spuId.value) {
return { items: [], total: 0 };
return { list: [], total: 0 };
}
const spu = await getSpu(spuId.value);
return {
items: spu.skus || [],
list: spu.skus || [],
total: spu.skus?.length || 0,
};
},
@@ -87,17 +94,24 @@ const [Grid, gridApi] = useVbenVxeGrid({
},
},
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 handleRadioChange() {
const selectedRow = gridApi.grid.getRadioRecord() as MallSpuApi.Sku;
if (selectedRow) {
emit('change', selectedRow);
closeModal();
}
/** 关闭弹窗 */
function closeModal() {
visible.value = false;
gridApi.grid.clearRadioRow();
spuId.value = undefined;
}
/** 打开弹窗 */
@@ -105,16 +119,13 @@ async function openModal(data?: SpuData) {
if (!data?.spuId) {
return;
}
visible.value = true;
spuId.value = data.spuId;
await gridApi.query();
}
/** 关闭弹窗 */
function closeModal() {
visible.value = false;
gridApi.grid.clearRadioRow();
spuId.value = undefined;
visible.value = true;
// // 等待弹窗和 Grid 组件完全渲染后再查询数据
// await nextTick();
// if (gridApi.grid) {
// await gridApi.query();
// }
}
/** 对外暴露的方法 */

View File

@@ -5,7 +5,7 @@ import type { VxeGridProps } from '#/adapter/vxe-table';
import type { MallCategoryApi } from '#/api/mall/product/category';
import type { MallSpuApi } from '#/api/mall/product/spu';
import { computed, onMounted, ref } from 'vue';
import { computed, nextTick, onMounted, ref } from 'vue';
import { handleTree } from '@vben/utils';
@@ -168,31 +168,35 @@ const [Grid, gridApi] = useVbenVxeGrid({
async function openModal(data?: MallSpuApi.Spu | MallSpuApi.Spu[]) {
initData.value = data;
visible.value = true;
// 1. 先查询数据
await gridApi.query();
// 2. 设置已选中行
if (props.multiple && Array.isArray(data) && data.length > 0) {
setTimeout(() => {
const tableData = gridApi.grid.getTableData().fullData;
data.forEach((spu) => {
// 等待 Grid 组件完全初始化后再查询数据
await nextTick();
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 && data && !Array.isArray(data)) {
setTimeout(() => {
const tableData = gridApi.grid.getTableData().fullData;
const row = tableData.find(
(item: MallSpuApi.Spu) => item.id === data.id,
);
if (row) {
gridApi.grid.setRadioRow(row);
}
}, 300);
}, 300);
}
}
}