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

View File

@@ -1,21 +1,31 @@
<script lang="ts" setup> <script lang="ts" setup>
import type { MallCommentApi } from '#/api/mall/product/comment'; import type { MallCommentApi } from '#/api/mall/product/comment';
import type { MallSpuApi } from '#/api/mall/product/spu';
import { computed, ref } from 'vue'; import { computed, ref } from 'vue';
import { useVbenModal } from '@vben/common-ui'; 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 { useVbenForm } from '#/adapter/form';
import { createComment, getComment } from '#/api/mall/product/comment'; import { createComment, getComment } from '#/api/mall/product/comment';
import { getSpu } from '#/api/mall/product/spu';
import { $t } from '#/locales'; import { $t } from '#/locales';
import {
SkuTableSelect,
SpuShowcase,
} from '#/views/mall/product/spu/components';
import { useFormSchema } from '../data'; import { useFormSchema } from '../data';
const emit = defineEmits(['success']); const emit = defineEmits(['success']);
const formData = ref<MallCommentApi.Comment>(); // 初始化 formData确保始终有值
const formData = ref<Partial<MallCommentApi.Comment>>({
descriptionScores: 5,
benefitScores: 5,
});
const getTitle = computed(() => { const getTitle = computed(() => {
return formData.value?.id return formData.value?.id
? $t('ui.actionTitle.edit', ['虚拟评论']) ? $t('ui.actionTitle.edit', ['虚拟评论'])
@@ -35,6 +45,37 @@ const [Form, formApi] = useVbenForm({
showDefaultActions: false, 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({ const [Modal, modalApi] = useVbenModal({
async onConfirm() { async onConfirm() {
const { valid } = await formApi.validate(); const { valid } = await formApi.validate();
@@ -56,19 +97,42 @@ const [Modal, modalApi] = useVbenModal({
}, },
async onOpenChange(isOpen: boolean) { async onOpenChange(isOpen: boolean) {
if (!isOpen) { if (!isOpen) {
formData.value = undefined; // 重置表单数据
formData.value = {
descriptionScores: 5,
benefitScores: 5,
} as Partial<MallCommentApi.Comment>;
selectedSku.value = undefined;
return; return;
} }
// 加载数据 // 加载数据
const data = modalApi.getData<MallCommentApi.Comment>(); const data = modalApi.getData<MallCommentApi.Comment>();
if (!data || !data.id) { 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; return;
} }
// 编辑模式:加载数据
modalApi.lock(); modalApi.lock();
try { try {
formData.value = await getComment(data.id); formData.value = await getComment(data.id);
// 设置到 values // 设置到 values
await formApi.setValues(formData.value); 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 { } finally {
modalApi.unlock(); modalApi.unlock();
} }
@@ -78,6 +142,38 @@ const [Modal, modalApi] = useVbenModal({
<template> <template>
<Modal class="w-2/5" :title="getTitle"> <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> </Modal>
</template> </template>

View File

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

View File

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