feat(产品列表): 添加CSV导入导出功能并重构属性选择逻辑
重构产品创建和编辑表单中的属性选择组件,支持搜索和新增标签模式 添加CSV导入导出功能,支持通过接口下载产品数据表格 修改属性数据结构,使用name作为值而非id,提高灵活性
This commit is contained in:
parent
6244438f26
commit
0a6ea0396f
|
|
@ -12,7 +12,9 @@ import {
|
||||||
productcontrollerSetproductcomponents,
|
productcontrollerSetproductcomponents,
|
||||||
productcontrollerAutobindcomponents,
|
productcontrollerAutobindcomponents,
|
||||||
productcontrollerGetattributeall,
|
productcontrollerGetattributeall,
|
||||||
|
productcontrollerGetattributelist,
|
||||||
} from '@/servers/api/product';
|
} from '@/servers/api/product';
|
||||||
|
import { request } from '@umijs/max';
|
||||||
import { templatecontrollerRendertemplate } from '@/servers/api/template';
|
import { templatecontrollerRendertemplate } from '@/servers/api/template';
|
||||||
import { PlusOutlined } from '@ant-design/icons';
|
import { PlusOutlined } from '@ant-design/icons';
|
||||||
import {
|
import {
|
||||||
|
|
@ -28,7 +30,7 @@ import {
|
||||||
ProFormTextArea,
|
ProFormTextArea,
|
||||||
ProTable,
|
ProTable,
|
||||||
} from '@ant-design/pro-components';
|
} from '@ant-design/pro-components';
|
||||||
import { App, Button, Popconfirm, Tag } from 'antd';
|
import { App, Button, Popconfirm, Tag, Upload } from 'antd';
|
||||||
import { allowedDictNames } from '@/pages/Product/Attribute/consts';
|
import { allowedDictNames } from '@/pages/Product/Attribute/consts';
|
||||||
import React, { useRef, useState } from 'react';
|
import React, { useRef, useState } from 'react';
|
||||||
const capitalize = (s: string) => s.charAt(0).toLocaleUpperCase() + s.slice(1);
|
const capitalize = (s: string) => s.charAt(0).toLocaleUpperCase() + s.slice(1);
|
||||||
|
|
@ -119,6 +121,28 @@ const List: React.FC = () => {
|
||||||
const [selectedRows, setSelectedRows] = React.useState<API.Product[]>([]);
|
const [selectedRows, setSelectedRows] = React.useState<API.Product[]>([]);
|
||||||
|
|
||||||
const { message } = App.useApp();
|
const { message } = App.useApp();
|
||||||
|
// 中文注释:导出产品 CSV(带认证请求)
|
||||||
|
const handleDownloadProductsCSV = async () => {
|
||||||
|
try {
|
||||||
|
// 中文注释:发起认证请求获取 CSV Blob
|
||||||
|
const blob = await request('/product/export', { responseType: 'blob' });
|
||||||
|
// 中文注释:构建下载文件名
|
||||||
|
const d = new Date();
|
||||||
|
const pad = (n: number) => String(n).padStart(2, '0');
|
||||||
|
const filename = `products-${d.getFullYear()}${pad(d.getMonth() + 1)}${pad(d.getDate())}.csv`;
|
||||||
|
// 中文注释:创建临时链接并触发下载
|
||||||
|
const url = window.URL.createObjectURL(blob);
|
||||||
|
const a = document.createElement('a');
|
||||||
|
a.href = url;
|
||||||
|
a.download = filename;
|
||||||
|
document.body.appendChild(a);
|
||||||
|
a.click();
|
||||||
|
a.remove();
|
||||||
|
window.URL.revokeObjectURL(url);
|
||||||
|
} catch (error) {
|
||||||
|
message.error('导出失败');
|
||||||
|
}
|
||||||
|
};
|
||||||
const columns: ProColumns<API.Product>[] = [
|
const columns: ProColumns<API.Product>[] = [
|
||||||
{
|
{
|
||||||
title: 'sku',
|
title: 'sku',
|
||||||
|
|
@ -215,7 +239,30 @@ const List: React.FC = () => {
|
||||||
headerTitle="查询表格"
|
headerTitle="查询表格"
|
||||||
actionRef={actionRef}
|
actionRef={actionRef}
|
||||||
rowKey="id"
|
rowKey="id"
|
||||||
toolBarRender={() => [<CreateForm tableRef={actionRef} />]}
|
toolBarRender={() => [
|
||||||
|
// 中文注释:新建按钮
|
||||||
|
<CreateForm tableRef={actionRef} />,
|
||||||
|
// 中文注释:导出 CSV(后端返回 text/csv,直接新窗口下载)
|
||||||
|
<Button onClick={handleDownloadProductsCSV}>导出CSV</Button>,
|
||||||
|
// 中文注释:导入 CSV(上传文件成功后刷新表格)
|
||||||
|
<Upload
|
||||||
|
name="file"
|
||||||
|
action="/product/import"
|
||||||
|
accept=".csv"
|
||||||
|
showUploadList={false}
|
||||||
|
maxCount={1}
|
||||||
|
onChange={({ file }) => {
|
||||||
|
if (file.status === 'done') {
|
||||||
|
message.success('导入完成');
|
||||||
|
actionRef.current?.reload();
|
||||||
|
} else if (file.status === 'error') {
|
||||||
|
message.error('导入失败');
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Button>导入CSV</Button>
|
||||||
|
</Upload>,
|
||||||
|
]}
|
||||||
request={async (params) => {
|
request={async (params) => {
|
||||||
const { data, success } = await productcontrollerGetproductlist(
|
const { data, success } = await productcontrollerGetproductlist(
|
||||||
params,
|
params,
|
||||||
|
|
@ -248,10 +295,12 @@ const CreateForm: React.FC<{
|
||||||
const { message } = App.useApp();
|
const { message } = App.useApp();
|
||||||
// 表单引用
|
// 表单引用
|
||||||
const formRef = useRef<ProFormInstance>();
|
const formRef = useRef<ProFormInstance>();
|
||||||
// 状态:存储品牌、强度和口味的选项
|
// 状态:存储品牌、强度、口味、规格的选项(label 使用标题,value 使用名称)
|
||||||
const [brandOptions, setBrandOptions] = useState<DictItem[]>([]);
|
const [brandOptions, setBrandOptions] = useState<{ label: string; value: string }[]>([]);
|
||||||
const [strengthOptions, setStrengthOptions] = useState<DictItem[]>([]);
|
const [strengthOptions, setStrengthOptions] = useState<{ label: string; value: string }[]>([]);
|
||||||
const [flavorOptions, setFlavorOptions] = useState<DictItem[]>([]);
|
const [flavorOptions, setFlavorOptions] = useState<{ label: string; value: string }[]>([]);
|
||||||
|
const [sizeOptions, setSizeOptions] = useState<{ label: string; value: string }[]>([]);
|
||||||
|
const [humidityOptions, setHumidityOptions] = useState<{ label: string; value: string }[]>([]);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description 生成 SKU
|
* @description 生成 SKU
|
||||||
|
|
@ -260,26 +309,27 @@ const CreateForm: React.FC<{
|
||||||
try {
|
try {
|
||||||
// 从表单引用中获取当前表单的值
|
// 从表单引用中获取当前表单的值
|
||||||
const formValues = formRef.current?.getFieldsValue();
|
const formValues = formRef.current?.getFieldsValue();
|
||||||
const { humidity, brandId, strengthId, flavorsId } = formValues;
|
const { humidityValues, brandValues, strengthValues, flavorsValues } = formValues;
|
||||||
// 检查是否所有必需的字段都已选择
|
// 检查是否所有必需的字段都已选择
|
||||||
if (!brandId || !strengthId || !flavorsId || !humidity) {
|
if (!brandValues?.length || !strengthValues?.length || !flavorsValues?.length || !humidityValues?.length) {
|
||||||
message.warning('请先选择品牌、强度、口味和干湿');
|
message.warning('请先选择品牌、强度、口味和干湿');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 从选项中查找所选品牌、强度和口味的完整对象
|
// 所选值(用于 SKU 模板传入 name)
|
||||||
const brand = brandOptions.find((item) => item.id === brandId);
|
const brandName: string = String(brandValues[0]);
|
||||||
const strength = strengthOptions.find((item) => item.id === strengthId);
|
const strengthName: string = String(strengthValues[0]);
|
||||||
const flavor = flavorOptions.find((item) => item.id === flavorsId);
|
const flavorName: string = String(flavorsValues[0]);
|
||||||
|
const humidityName: string = String(humidityValues[0]);
|
||||||
|
|
||||||
// 调用模板渲染API来生成SKU
|
// 调用模板渲染API来生成SKU
|
||||||
const { data: rendered, message: msg, success } = await templatecontrollerRendertemplate(
|
const { data: rendered, message: msg, success } = await templatecontrollerRendertemplate(
|
||||||
{ name: 'product.sku' },
|
{ name: 'product.sku' },
|
||||||
{
|
{
|
||||||
brand: brand ? brand.name : "",
|
brand: brandName || "",
|
||||||
strength: strength ? strength.name : '',
|
strength: strengthName || '',
|
||||||
flavor: flavor ? flavor.name : '',
|
flavor: flavorName || '',
|
||||||
humidity: humidity ? capitalize(humidity) : '',
|
humidity: humidityName ? capitalize(humidityName) : '',
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
if (!success) {
|
if (!success) {
|
||||||
|
|
@ -300,27 +350,31 @@ const CreateForm: React.FC<{
|
||||||
try {
|
try {
|
||||||
// 从表单引用中获取当前表单的值
|
// 从表单引用中获取当前表单的值
|
||||||
const formValues = formRef.current?.getFieldsValue();
|
const formValues = formRef.current?.getFieldsValue();
|
||||||
const { humidity, brandId, strengthId, flavorsId } = formValues;
|
const { humidityValues, brandValues, strengthValues, flavorsValues } = formValues;
|
||||||
// 检查是否所有必需的字段都已选择
|
// 检查是否所有必需的字段都已选择
|
||||||
if (!brandId || !strengthId || !flavorsId || !humidity) {
|
if (!brandValues?.length || !strengthValues?.length || !flavorsValues?.length || !humidityValues?.length) {
|
||||||
message.warning('请先选择品牌、强度、口味和干湿');
|
message.warning('请先选择品牌、强度、口味和干湿');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 从选项中查找所选品牌、强度和口味的完整对象
|
// 获取标题(label),若为新输入值则使用原值作为标题
|
||||||
const brand = brandOptions.find((item) => item.id === brandId);
|
const brandName: string = String(brandValues[0]);
|
||||||
const strength = strengthOptions.find((item) => item.id === strengthId);
|
const strengthName: string = String(strengthValues[0]);
|
||||||
const flavor = flavorOptions.find((item) => item.id === flavorsId);
|
const flavorName: string = String(flavorsValues[0]);
|
||||||
|
const humidityName: string = String(humidityValues[0]);
|
||||||
|
const brandTitle = brandOptions.find(i => i.value === brandName)?.label || brandName;
|
||||||
|
const strengthTitle = strengthOptions.find(i => i.value === strengthName)?.label || strengthName;
|
||||||
|
const flavorTitle = flavorOptions.find(i => i.value === flavorName)?.label || flavorName;
|
||||||
|
|
||||||
// 调用模板渲染API来生成产品名称
|
// 调用模板渲染API来生成产品名称
|
||||||
const { message: msg, data: rendered, success } = await templatecontrollerRendertemplate(
|
const { message: msg, data: rendered, success } = await templatecontrollerRendertemplate(
|
||||||
{ name: 'product.title' },
|
{ name: 'product.title' },
|
||||||
{
|
{
|
||||||
brand: brand ? brand.title : "",
|
brand: brandTitle,
|
||||||
strength: strength ? strength.title : '',
|
strength: strengthTitle,
|
||||||
flavor: flavor ? flavor.title : '',
|
flavor: flavorTitle,
|
||||||
model: '',
|
model: '',
|
||||||
humidity: humidity === 'dry' ? 'Dry' : 'Moisture',
|
humidity: humidityName === 'dry' ? 'Dry' : humidityName === 'moisture' ? 'Moisture' : capitalize(humidityName),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
if (!success) {
|
if (!success) {
|
||||||
|
|
@ -334,7 +388,7 @@ const CreateForm: React.FC<{
|
||||||
};
|
};
|
||||||
// TODO 可以输入brand等
|
// TODO 可以输入brand等
|
||||||
return (
|
return (
|
||||||
<DrawerForm<API.CreateProductDTO>
|
<DrawerForm<any>
|
||||||
title="新建"
|
title="新建"
|
||||||
formRef={formRef} // Pass formRef
|
formRef={formRef} // Pass formRef
|
||||||
trigger={
|
trigger={
|
||||||
|
|
@ -348,20 +402,30 @@ const CreateForm: React.FC<{
|
||||||
destroyOnHidden: true,
|
destroyOnHidden: true,
|
||||||
}}
|
}}
|
||||||
onFinish={async (values) => {
|
onFinish={async (values) => {
|
||||||
// 中文注释:将选择的字典项ID与干湿属性组装为后端需要的 attributes
|
// 中文注释:组装 attributes(支持输入新值,按标题传入创建/绑定)
|
||||||
|
const toArray = (v: any) => (Array.isArray(v) ? v : v ? [v] : []);
|
||||||
|
const brandValues = toArray((values as any).brandValues);
|
||||||
|
const strengthValues = toArray((values as any).strengthValues);
|
||||||
|
const flavorsValues = toArray((values as any).flavorsValues);
|
||||||
|
const sizeValues = toArray((values as any).sizeValues);
|
||||||
|
const humidityValues = toArray((values as any).humidityValues);
|
||||||
|
const mapWithLabel = (vals: string[], opts: { label: string; value: string }[], dictName: string) => (
|
||||||
|
vals.map(v => ({ dictName, title: (opts.find(o => o.value === v)?.label || v) }))
|
||||||
|
);
|
||||||
|
const attributes = [
|
||||||
|
...mapWithLabel(brandValues, brandOptions, 'brand'),
|
||||||
|
...mapWithLabel(strengthValues, strengthOptions, 'strength'),
|
||||||
|
...mapWithLabel(flavorsValues, flavorOptions, 'flavor'),
|
||||||
|
...mapWithLabel(sizeValues, sizeOptions, 'size'),
|
||||||
|
...mapWithLabel(humidityValues, humidityOptions, 'humidity'),
|
||||||
|
].filter(Boolean);
|
||||||
const payload: any = {
|
const payload: any = {
|
||||||
name: values.name,
|
name: (values as any).name,
|
||||||
description: values.description,
|
description: (values as any).description,
|
||||||
sku: values.sku,
|
sku: (values as any).sku,
|
||||||
price: values.price,
|
price: (values as any).price,
|
||||||
promotionPrice: values.promotionPrice,
|
promotionPrice: (values as any).promotionPrice,
|
||||||
attributes: [
|
attributes,
|
||||||
values.brandId ? { id: values.brandId } : null,
|
|
||||||
values.strengthId ? { id: values.strengthId } : null,
|
|
||||||
values.flavorsId ? { id: values.flavorsId } : null,
|
|
||||||
values.sizeId ? { id: values.sizeId } : null,
|
|
||||||
values.humidity ? { id: values.humidityId } : null,
|
|
||||||
].filter(Boolean),
|
|
||||||
};
|
};
|
||||||
const { success, message: errMsg } =
|
const { success, message: errMsg } =
|
||||||
await productcontrollerCreateproduct(payload);
|
await productcontrollerCreateproduct(payload);
|
||||||
|
|
@ -374,71 +438,127 @@ const CreateForm: React.FC<{
|
||||||
return false;
|
return false;
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
{/* 品牌(可搜索、可新增) */}
|
||||||
<ProFormSelect
|
<ProFormSelect
|
||||||
name="brandId"
|
name="brandValues"
|
||||||
width="lg"
|
width="lg"
|
||||||
label="产品品牌"
|
label="产品品牌"
|
||||||
placeholder="请选择产品品牌"
|
placeholder="请输入或选择产品品牌"
|
||||||
request={async () => {
|
fieldProps={{
|
||||||
const { data = [] } = await productcontrollerCompatbrandall();
|
mode: 'tags',
|
||||||
setBrandOptions(data);
|
showSearch: true,
|
||||||
return data.map((item: DictItem) => ({
|
filterOption: false,
|
||||||
label: item.name,
|
onSearch: async (val) => {
|
||||||
value: item.id,
|
const items = await productcontrollerGetattributelist({ dictName: 'brand', name: val, current: 1, pageSize: 20 } as any);
|
||||||
}));
|
const options = (items?.data?.items || []).map((it: any) => ({ label: it.title, value: it.name }));
|
||||||
|
setBrandOptions(options);
|
||||||
|
},
|
||||||
}}
|
}}
|
||||||
|
request={async () => {
|
||||||
|
const items = await productcontrollerGetattributelist({ dictName: 'brand', current: 1, pageSize: 20 } as any);
|
||||||
|
const options = (items?.data?.items || []).map((it: any) => ({ label: it.title, value: it.name }));
|
||||||
|
setBrandOptions(options);
|
||||||
|
return options;
|
||||||
|
}}
|
||||||
|
options={brandOptions}
|
||||||
rules={[{ required: true, message: '请选择产品品牌' }]}
|
rules={[{ required: true, message: '请选择产品品牌' }]}
|
||||||
/>
|
/>
|
||||||
|
{/* 强度(可搜索、可新增) */}
|
||||||
<ProFormSelect
|
<ProFormSelect
|
||||||
name="strengthId"
|
name="strengthValues"
|
||||||
width="lg"
|
width="lg"
|
||||||
label="强度"
|
label="强度"
|
||||||
placeholder="请选择强度"
|
placeholder="请输入或选择强度"
|
||||||
request={async () => {
|
fieldProps={{
|
||||||
const { data = [] } = await productcontrollerCompatstrengthall();
|
mode: 'tags',
|
||||||
setStrengthOptions(data);
|
showSearch: true,
|
||||||
return data.map((item: DictItem) => ({
|
filterOption: false,
|
||||||
label: item.name,
|
onSearch: async (val) => {
|
||||||
value: item.id,
|
const items = await productcontrollerGetattributelist({ dictName: 'strength', name: val, current: 1, pageSize: 20 } as any);
|
||||||
}));
|
const options = (items?.data?.items || []).map((it: any) => ({ label: it.title, value: it.name }));
|
||||||
|
setStrengthOptions(options);
|
||||||
|
},
|
||||||
}}
|
}}
|
||||||
|
request={async () => {
|
||||||
|
const items = await productcontrollerGetattributelist({ dictName: 'strength', current: 1, pageSize: 20 } as any);
|
||||||
|
const options = (items?.data?.items || []).map((it: any) => ({ label: it.title, value: it.name }));
|
||||||
|
setStrengthOptions(options);
|
||||||
|
return options;
|
||||||
|
}}
|
||||||
|
options={strengthOptions}
|
||||||
rules={[{ required: true, message: '请选择强度' }]}
|
rules={[{ required: true, message: '请选择强度' }]}
|
||||||
/>
|
/>
|
||||||
|
{/* 口味(可搜索、可新增) */}
|
||||||
<ProFormSelect
|
<ProFormSelect
|
||||||
name="flavorsId"
|
name="flavorsValues"
|
||||||
width="lg"
|
width="lg"
|
||||||
label="口味"
|
label="口味"
|
||||||
placeholder="请选择口味"
|
placeholder="请输入或选择口味"
|
||||||
request={async () => {
|
fieldProps={{
|
||||||
const { data = [] } = await productcontrollerCompatflavorsall();
|
mode: 'tags',
|
||||||
setFlavorOptions(data);
|
showSearch: true,
|
||||||
return data.map((item: DictItem) => ({
|
filterOption: false,
|
||||||
label: item.name,
|
onSearch: async (val) => {
|
||||||
value: item.id,
|
const items = await productcontrollerGetattributelist({ dictName: 'flavor', name: val, current: 1, pageSize: 20 } as any);
|
||||||
}));
|
const options = (items?.data?.items || []).map((it: any) => ({ label: it.title, value: it.name }));
|
||||||
|
setFlavorOptions(options);
|
||||||
|
},
|
||||||
}}
|
}}
|
||||||
|
request={async () => {
|
||||||
|
const items = await productcontrollerGetattributelist({ dictName: 'flavor', current: 1, pageSize: 20 } as any);
|
||||||
|
const options = (items?.data?.items || []).map((it: any) => ({ label: it.title, value: it.name }));
|
||||||
|
setFlavorOptions(options);
|
||||||
|
return options;
|
||||||
|
}}
|
||||||
|
options={flavorOptions}
|
||||||
rules={[{ required: true, message: '请选择口味' }]}
|
rules={[{ required: true, message: '请选择口味' }]}
|
||||||
/>
|
/>
|
||||||
<ProFormSelect
|
<ProFormSelect
|
||||||
name="sizeId"
|
name="sizeValues"
|
||||||
width="lg"
|
width="lg"
|
||||||
label="规格"
|
label="规格"
|
||||||
placeholder="请选择规格"
|
placeholder="请输入或选择规格"
|
||||||
request={async () => {
|
fieldProps={{
|
||||||
const { data = [] } = await productcontrollerCompatsizeall();
|
mode: 'tags',
|
||||||
return (data || []).map((item: any) => ({ label: item.name, value: item.id }));
|
showSearch: true,
|
||||||
|
filterOption: false,
|
||||||
|
onSearch: async (val) => {
|
||||||
|
const items = await productcontrollerGetattributelist({ dictName: 'size', name: val, current: 1, pageSize: 20 } as any);
|
||||||
|
const options = (items?.data?.items || []).map((it: any) => ({ label: it.title, value: it.name }));
|
||||||
|
setSizeOptions(options);
|
||||||
|
},
|
||||||
}}
|
}}
|
||||||
|
request={async () => {
|
||||||
|
const items = await productcontrollerGetattributelist({ dictName: 'size', current: 1, pageSize: 20 } as any);
|
||||||
|
const options = (items?.data?.items || []).map((it: any) => ({ label: it.title, value: it.name }));
|
||||||
|
setSizeOptions(options);
|
||||||
|
return options;
|
||||||
|
}}
|
||||||
|
options={sizeOptions}
|
||||||
rules={[{ required: false }]}
|
rules={[{ required: false }]}
|
||||||
/>
|
/>
|
||||||
<ProFormSelect
|
<ProFormSelect
|
||||||
name="humidity"
|
name="humidityValues"
|
||||||
width="lg"
|
width="lg"
|
||||||
label="干湿"
|
label="干湿"
|
||||||
placeholder="请选择干湿"
|
placeholder="请输入或选择干湿"
|
||||||
request={async () => {
|
fieldProps={{
|
||||||
const { data = [] } = await productcontrollerGetattributeall({ dictName: 'humidity' } as any);
|
mode: 'tags',
|
||||||
return (data || []).map((item: any) => ({ label: item.name, value: item.name }));
|
showSearch: true,
|
||||||
|
filterOption: false,
|
||||||
|
onSearch: async (val) => {
|
||||||
|
const items = await productcontrollerGetattributelist({ dictName: 'humidity', name: val, current: 1, pageSize: 20 } as any);
|
||||||
|
const options = (items?.data?.items || []).map((it: any) => ({ label: it.title, value: it.name }));
|
||||||
|
setHumidityOptions(options);
|
||||||
|
},
|
||||||
}}
|
}}
|
||||||
|
request={async () => {
|
||||||
|
const items = await productcontrollerGetattributelist({ dictName: 'humidity', current: 1, pageSize: 20 } as any);
|
||||||
|
const options = (items?.data?.items || []).map((it: any) => ({ label: it.title, value: it.name }));
|
||||||
|
setHumidityOptions(options);
|
||||||
|
return options;
|
||||||
|
}}
|
||||||
|
options={humidityOptions}
|
||||||
rules={[{ required: true, message: '请选择干湿' }]}
|
rules={[{ required: true, message: '请选择干湿' }]}
|
||||||
/>
|
/>
|
||||||
<ProForm.Group>
|
<ProForm.Group>
|
||||||
|
|
@ -497,29 +617,24 @@ const EditForm: React.FC<{
|
||||||
}> = ({ tableRef, record }) => {
|
}> = ({ tableRef, record }) => {
|
||||||
const { message } = App.useApp();
|
const { message } = App.useApp();
|
||||||
const formRef = useRef<ProFormInstance>();
|
const formRef = useRef<ProFormInstance>();
|
||||||
const [brandOptions, setBrandOptions] = useState<DictItem[]>([]);
|
// 中文注释:各属性的选择项(使用 {label,value},value 用 name,便于 tags 输入匹配)
|
||||||
const [strengthOptions, setStrengthOptions] = useState<DictItem[]>([]);
|
const [brandOptions, setBrandOptions] = useState<{ label: string; value: string }[]>([]);
|
||||||
const [flavorOptions, setFlavorOptions] = useState<DictItem[]>([]);
|
const [strengthOptions, setStrengthOptions] = useState<{ label: string; value: string }[]>([]);
|
||||||
|
const [flavorOptions, setFlavorOptions] = useState<{ label: string; value: string }[]>([]);
|
||||||
const [humidityOptions, setHumidityOptions] = useState<{ label: string; value: string }[]>([]);
|
const [humidityOptions, setHumidityOptions] = useState<{ label: string; value: string }[]>([]);
|
||||||
const [components, setComponents] = useState<{ stockId: number; quantity: number }[]>([]);
|
const [components, setComponents] = useState<{ stockId: number; quantity: number }[]>([]);
|
||||||
|
|
||||||
const setInitialIds = () => {
|
// 中文注释:通用远程选项加载器(支持关键词搜索)
|
||||||
const brand = brandOptions.find((item) => item.title === (record.brand?.name));
|
const fetchDictOptions = async (dictName: string, keyword?: string) => {
|
||||||
const strength = strengthOptions.find((item) => item.title === (record.strength?.name));
|
// 条件判断:构造查询参数
|
||||||
const flavor = flavorOptions.find((item) => item.title === (record.flavors?.name));
|
const params: any = { dictName, current: 1, pageSize: 20 };
|
||||||
formRef.current?.setFieldsValue({
|
if (keyword) params.name = keyword;
|
||||||
brandId: brand?.id,
|
const { data } = await productcontrollerGetattributelist(params);
|
||||||
strengthId: strength?.id,
|
const items = data?.items || [];
|
||||||
flavorsId: flavor?.id,
|
// 中文注释:统一转为 {label,value},value 使用 name
|
||||||
});
|
return items.map((it: DictItem) => ({ label: it.name, value: it.name }));
|
||||||
};
|
};
|
||||||
|
|
||||||
React.useEffect(() => {
|
|
||||||
if (brandOptions.length && strengthOptions.length && flavorOptions.length) {
|
|
||||||
setInitialIds();
|
|
||||||
}
|
|
||||||
}, [brandOptions, strengthOptions, flavorOptions]);
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
// 中文注释:加载干湿选项
|
// 中文注释:加载干湿选项
|
||||||
(async () => {
|
(async () => {
|
||||||
|
|
@ -536,7 +651,7 @@ const EditForm: React.FC<{
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DrawerForm<API.UpdateProductDTO>
|
<DrawerForm<any>
|
||||||
formRef={formRef}
|
formRef={formRef}
|
||||||
title="编辑"
|
title="编辑"
|
||||||
trigger={<Button type="link">编辑</Button>}
|
trigger={<Button type="link">编辑</Button>}
|
||||||
|
|
@ -548,14 +663,22 @@ const EditForm: React.FC<{
|
||||||
promotionPrice: record.promotionPrice,
|
promotionPrice: record.promotionPrice,
|
||||||
components,
|
components,
|
||||||
attributes: record.attributes || [],
|
attributes: record.attributes || [],
|
||||||
|
// 中文注释:为可搜索可新增的选择框提供初始值(使用 name 作为值)
|
||||||
|
brandValues: record.brand?.name ? [record.brand.name] : [],
|
||||||
|
strengthValues: record.strength?.name ? [record.strength.name] : [],
|
||||||
|
flavorsValues: record.flavors?.name ? [record.flavors.name] : [],
|
||||||
}}
|
}}
|
||||||
onFinish={async (values) => {
|
onFinish={async (values) => {
|
||||||
// 中文注释:组装 attributes(若选择了则发送)
|
// 中文注释:组装 attributes(若选择了则发送)
|
||||||
|
const toArray = (v: any) => (Array.isArray(v) ? v : v ? [v] : []);
|
||||||
|
const brandValues = toArray((values as any).brandValues);
|
||||||
|
const strengthValues = toArray((values as any).strengthValues);
|
||||||
|
const flavorsValues = toArray((values as any).flavorsValues);
|
||||||
const attrs = [
|
const attrs = [
|
||||||
values.brandId ? { id: values.brandId } : null,
|
// 条件判断:将 tags 值转为 { dictName, title }
|
||||||
values.strengthId ? { id: values.strengthId } : null,
|
...brandValues.map((t: string) => ({ dictName: 'brand', title: String(t).trim() })),
|
||||||
values.flavorsId ? { id: values.flavorsId } : null,
|
...strengthValues.map((t: string) => ({ dictName: 'strength', title: String(t).trim() })),
|
||||||
values.sizeId ? { id: values.sizeId } : null,
|
...flavorsValues.map((t: string) => ({ dictName: 'flavor', title: String(t).trim() })),
|
||||||
values.humidity ? { dictName: 'humidity', name: values.humidity } : null,
|
values.humidity ? { dictName: 'humidity', name: values.humidity } : null,
|
||||||
].filter(Boolean);
|
].filter(Boolean);
|
||||||
const updatePayload: any = {
|
const updatePayload: any = {
|
||||||
|
|
@ -604,38 +727,71 @@ const EditForm: React.FC<{
|
||||||
rules={[{ required: true, message: '请输入名称' }]}
|
rules={[{ required: true, message: '请输入名称' }]}
|
||||||
/>
|
/>
|
||||||
</ProForm.Group>
|
</ProForm.Group>
|
||||||
|
{/* 中文注释:品牌(可搜索、可新增) */}
|
||||||
<ProFormSelect
|
<ProFormSelect
|
||||||
name="brandId"
|
name="brandValues"
|
||||||
width="lg"
|
width="lg"
|
||||||
label="产品品牌"
|
label="产品品牌"
|
||||||
placeholder="请选择产品品牌"
|
placeholder="请输入或选择产品品牌"
|
||||||
request={async () => {
|
fieldProps={{
|
||||||
const { data = [] } = await productcontrollerCompatbrandall();
|
mode: 'tags',
|
||||||
setBrandOptions(data);
|
showSearch: true,
|
||||||
return data.map((item: DictItem) => ({ label: item.name, value: item.id }));
|
filterOption: false,
|
||||||
|
onSearch: async (val) => {
|
||||||
|
const options = await fetchDictOptions('brand', val);
|
||||||
|
setBrandOptions(options);
|
||||||
|
},
|
||||||
}}
|
}}
|
||||||
|
request={async () => {
|
||||||
|
const options = await fetchDictOptions('brand');
|
||||||
|
setBrandOptions(options);
|
||||||
|
return options;
|
||||||
|
}}
|
||||||
|
options={brandOptions}
|
||||||
/>
|
/>
|
||||||
|
{/* 中文注释:强度(可搜索、可新增) */}
|
||||||
<ProFormSelect
|
<ProFormSelect
|
||||||
name="strengthId"
|
name="strengthValues"
|
||||||
width="lg"
|
width="lg"
|
||||||
label="强度"
|
label="强度"
|
||||||
placeholder="请选择强度"
|
placeholder="请输入或选择强度"
|
||||||
request={async () => {
|
fieldProps={{
|
||||||
const { data = [] } = await productcontrollerCompatstrengthall();
|
mode: 'tags',
|
||||||
setStrengthOptions(data);
|
showSearch: true,
|
||||||
return data.map((item: DictItem) => ({ label: item.name, value: item.id }));
|
filterOption: false,
|
||||||
|
onSearch: async (val) => {
|
||||||
|
const options = await fetchDictOptions('strength', val);
|
||||||
|
setStrengthOptions(options);
|
||||||
|
},
|
||||||
}}
|
}}
|
||||||
|
request={async () => {
|
||||||
|
const options = await fetchDictOptions('strength');
|
||||||
|
setStrengthOptions(options);
|
||||||
|
return options;
|
||||||
|
}}
|
||||||
|
options={strengthOptions}
|
||||||
/>
|
/>
|
||||||
|
{/* 中文注释:口味(可搜索、可新增) */}
|
||||||
<ProFormSelect
|
<ProFormSelect
|
||||||
name="flavorsId"
|
name="flavorsValues"
|
||||||
width="lg"
|
width="lg"
|
||||||
label="口味"
|
label="口味"
|
||||||
placeholder="请选择口味"
|
placeholder="请输入或选择口味"
|
||||||
request={async () => {
|
fieldProps={{
|
||||||
const { data = [] } = await productcontrollerCompatflavorsall();
|
mode: 'tags',
|
||||||
setFlavorOptions(data);
|
showSearch: true,
|
||||||
return data.map((item: DictItem) => ({ label: item.name, value: item.id }));
|
filterOption: false,
|
||||||
|
onSearch: async (val) => {
|
||||||
|
const options = await fetchDictOptions('flavor', val);
|
||||||
|
setFlavorOptions(options);
|
||||||
|
},
|
||||||
}}
|
}}
|
||||||
|
request={async () => {
|
||||||
|
const options = await fetchDictOptions('flavor');
|
||||||
|
setFlavorOptions(options);
|
||||||
|
return options;
|
||||||
|
}}
|
||||||
|
options={flavorOptions}
|
||||||
/>
|
/>
|
||||||
<ProFormSelect
|
<ProFormSelect
|
||||||
name="humidity"
|
name="humidity"
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue