feat(产品列表): 添加CSV导入导出功能并重构属性选择逻辑

重构产品创建和编辑表单中的属性选择组件,支持搜索和新增标签模式
添加CSV导入导出功能,支持通过接口下载产品数据表格
修改属性数据结构,使用name作为值而非id,提高灵活性
This commit is contained in:
tikkhun 2025-11-29 11:39:38 +08:00
parent 6244438f26
commit 0a6ea0396f
1 changed files with 274 additions and 118 deletions

View File

@ -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"