feat(api): 添加区域管理相关接口和类型定义

feat(Dict): 优化字典项表单使用Form实例并添加下载模板功能

feat(Product): 新增产品编辑功能并优化创建表单
This commit is contained in:
tikkhun 2025-11-28 16:32:58 +08:00
parent 15c8588357
commit 456bbac8c6
5 changed files with 449 additions and 87 deletions

View File

@ -36,11 +36,7 @@ const DictPage: React.FC = () => {
// 控制字典项模态框的显示 // 控制字典项模态框的显示
const [isDictItemModalVisible, setIsDictItemModalVisible] = useState(false); const [isDictItemModalVisible, setIsDictItemModalVisible] = useState(false);
const [editingDictItem, setEditingDictItem] = useState<any>(null); const [editingDictItem, setEditingDictItem] = useState<any>(null);
const [dictItemForm, setDictItemForm] = useState({ const [dictItemForm] = Form.useForm();
name: '',
title: '',
value: '',
});
// 获取字典列表 // 获取字典列表
const fetchDicts = async (title?: string) => { const fetchDicts = async (title?: string) => {
@ -126,36 +122,32 @@ const DictPage: React.FC = () => {
// 打开添加字典项模态框 // 打开添加字典项模态框
const handleAddDictItem = () => { const handleAddDictItem = () => {
setEditingDictItem(null); setEditingDictItem(null);
setDictItemForm({ name: '', title: '', value: '' }); dictItemForm.resetFields();
setIsDictItemModalVisible(true); setIsDictItemModalVisible(true);
}; };
// 打开编辑字典项模态框 // 打开编辑字典项模态框
const handleEditDictItem = (item: any) => { const handleEditDictItem = (item: any) => {
setEditingDictItem(item); setEditingDictItem(item);
setDictItemForm(item); dictItemForm.setFieldsValue(item);
setIsDictItemModalVisible(true); setIsDictItemModalVisible(true);
}; };
// 处理字典项表单提交(添加/编辑) // 处理字典项表单提交(添加/编辑)
const handleDictItemFormSubmit = async () => { const handleDictItemFormSubmit = async (values: any) => {
if (!dictItemForm.name || !dictItemForm.title) {
message.warning('请输入名称和标题');
return;
}
try { try {
if (editingDictItem) { if (editingDictItem) {
// 编辑 // 编辑
await request(`/dict/item/${editingDictItem.id}`, { await request(`/dict/item/${editingDictItem.id}`, {
method: 'PUT', method: 'PUT',
data: dictItemForm, data: values,
}); });
message.success('更新成功'); message.success('更新成功');
} else { } else {
// 添加 // 添加
await request('/dict/item', { await request('/dict/item', {
method: 'POST', method: 'POST',
data: { ...dictItemForm, dictId: selectedDict.id }, data: { ...values, dictId: selectedDict.id },
}); });
message.success('添加成功'); message.success('添加成功');
} }
@ -193,6 +185,50 @@ const DictPage: React.FC = () => {
} }
}; };
/**
* @description
*/
const handleDownloadDictTemplate = async () => {
try {
// 使用带有认证拦截的 request 发起下载请求(后端鉴权通过)
const blob = await request('/dict/template', { responseType: 'blob' });
// 创建临时链接并触发下载
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'dict-template.xlsx';
document.body.appendChild(a);
a.click();
a.remove();
window.URL.revokeObjectURL(url);
} catch (error) {
// 错误处理:认证失败或网络错误
message.error('下载模板失败');
}
};
/**
* @description
*/
const handleDownloadDictItemTemplate = async () => {
try {
// 使用带有认证拦截的 request 发起下载请求(后端鉴权通过)
const blob = await request('/dict/item/template', { responseType: 'blob' });
// 创建临时链接并触发下载
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'dict-item-template.xlsx';
document.body.appendChild(a);
a.click();
a.remove();
window.URL.revokeObjectURL(url);
} catch (error) {
// 错误处理:认证失败或网络错误
message.error('下载模板失败');
}
};
// 左侧字典列表的列定义 // 左侧字典列表的列定义
const dictColumns = [ const dictColumns = [
{ {
@ -211,6 +247,7 @@ const DictPage: React.FC = () => {
render: (_: any, record: any) => ( render: (_: any, record: any) => (
<Space> <Space>
<Button <Button
size='small'
type="link" type="link"
onClick={(e) => { onClick={(e) => {
e.stopPropagation(); e.stopPropagation();
@ -220,6 +257,7 @@ const DictPage: React.FC = () => {
</Button> </Button>
<Button <Button
size='small'
type="link" type="link"
danger danger
onClick={(e) => { onClick={(e) => {
@ -246,6 +284,11 @@ const DictPage: React.FC = () => {
dataIndex: 'title', dataIndex: 'title',
key: 'title', key: 'title',
}, },
{
title: '中文标题',
dataIndex: 'titleCN',
key: 'titleCN',
},
{ {
title: '操作', title: '操作',
key: 'action', key: 'action',
@ -308,7 +351,7 @@ const DictPage: React.FC = () => {
> >
<Button icon={<UploadOutlined />}></Button> <Button icon={<UploadOutlined />}></Button>
</Upload> </Upload>
<Button onClick={() => window.open('/dict/template')}> <Button onClick={handleDownloadDictTemplate}>
</Button> </Button>
</Space> </Space>
@ -336,14 +379,14 @@ const DictPage: React.FC = () => {
</Sider> </Sider>
<Content style={{ padding: '16px' }}> <Content style={{ padding: '16px' }}>
<Space direction="vertical" style={{ width: '100%' }}> <Space direction="vertical" style={{ width: '100%' }}>
<Button <div style={{ width: '100%', display: 'flex', flexDirection: 'row', gap: '2px' }}>
type="primary" <Button
onClick={handleAddDictItem} type="primary"
disabled={!selectedDict} onClick={handleAddDictItem}
> disabled={!selectedDict}
>
</Button>
<Space> </Button>
<Upload <Upload
name="file" name="file"
action={`/dict/item/import`} action={`/dict/item/import`}
@ -364,12 +407,12 @@ const DictPage: React.FC = () => {
</Button> </Button>
</Upload> </Upload>
<Button <Button
onClick={() => window.open('/dict/item/template')} onClick={handleDownloadDictItemTemplate}
disabled={!selectedDict} disabled={!selectedDict}
> >
</Button> </Button>
</Space> </div>
<Table <Table
dataSource={dictItems} dataSource={dictItems}
columns={dictItemColumns} columns={dictItemColumns}
@ -382,37 +425,35 @@ const DictPage: React.FC = () => {
<Modal <Modal
title={editingDictItem ? '编辑字典项' : '添加字典项'} title={editingDictItem ? '编辑字典项' : '添加字典项'}
visible={isDictItemModalVisible} open={isDictItemModalVisible}
onOk={handleDictItemFormSubmit} onOk={() => dictItemForm.submit()}
onCancel={() => setIsDictItemModalVisible(false)} onCancel={() => setIsDictItemModalVisible(false)}
destroyOnClose
> >
<Form layout="vertical"> <Form
<Form.Item label="名称"> form={dictItemForm}
<Input layout="vertical"
placeholder="名称 (e.g., zyn)" onFinish={handleDictItemFormSubmit}
value={dictItemForm.name} >
onChange={(e) => <Form.Item
setDictItemForm({ ...dictItemForm, name: e.target.value }) label="名称"
} name="name"
/> rules={[{ required: true, message: '请输入名称' }]}
>
<Input placeholder="名称 (e.g., zyn)" />
</Form.Item> </Form.Item>
<Form.Item label="标题"> <Form.Item
<Input label="标题"
placeholder="标题 (e.g., ZYN)" name="title"
value={dictItemForm.title} rules={[{ required: true, message: '请输入标题' }]}
onChange={(e) => >
setDictItemForm({ ...dictItemForm, title: e.target.value }) <Input placeholder="标题 (e.g., ZYN)" />
}
/>
</Form.Item> </Form.Item>
<Form.Item label="值 (可选)"> <Form.Item label="中文标题" name="titleCN">
<Input <Input placeholder="中文标题 (e.g., 品牌)" />
placeholder="值 (可选)" </Form.Item>
value={dictItemForm.value} <Form.Item label="值 (可选)" name="value">
onChange={(e) => <Input placeholder="值 (可选)" />
setDictItemForm({ ...dictItemForm, value: e.target.value })
}
/>
</Form.Item> </Form.Item>
</Form> </Form>
</Modal> </Modal>

View File

@ -6,6 +6,7 @@ import {
productcontrollerGetproductlist, productcontrollerGetproductlist,
productcontrollerGetstrengthall, productcontrollerGetstrengthall,
productcontrollerUpdateproductnamecn, productcontrollerUpdateproductnamecn,
productcontrollerUpdateproduct,
} from '@/servers/api/product'; } from '@/servers/api/product';
import { templatecontrollerRendertemplate } from '@/servers/api/template'; import { templatecontrollerRendertemplate } from '@/servers/api/template';
import { PlusOutlined } from '@ant-design/icons'; import { PlusOutlined } from '@ant-design/icons';
@ -23,6 +24,7 @@ import {
} from '@ant-design/pro-components'; } from '@ant-design/pro-components';
import { App, Button, Popconfirm } from 'antd'; import { App, Button, Popconfirm } from 'antd';
import React, { useRef, useState } from 'react'; import React, { useRef, useState } from 'react';
const capitalize = (s: string) => s.charAt(0).toLocaleUpperCase() + s.slice(1);
// TODO // TODO
interface DictItem { interface DictItem {
id: number; id: number;
@ -54,7 +56,7 @@ const NameCn: React.FC<{
if (!success) { if (!success) {
return message.error(errMsg); return message.error(errMsg);
} }
tableRef?.current?.reload(); tableRef?.current?.reloadAndRest?.();
}, },
}} }}
/> />
@ -67,6 +69,10 @@ const List: React.FC = () => {
const { message } = App.useApp(); const { message } = App.useApp();
const columns: ProColumns<API.Product>[] = [ const columns: ProColumns<API.Product>[] = [
{
title: 'sku',
dataIndex: 'sku',
},
{ {
title: '名称', title: '名称',
dataIndex: 'name', dataIndex: 'name',
@ -80,6 +86,21 @@ const List: React.FC = () => {
); );
}, },
}, },
{
title: '价格',
dataIndex: 'price',
hideInSearch: true,
},
{
title: '促销价',
dataIndex: 'promotionPrice',
hideInSearch: true,
},
{
title: '库存',
dataIndex: 'stock',
hideInSearch: true,
},
{ {
title: '产品描述', title: '产品描述',
dataIndex: 'description', dataIndex: 'description',
@ -101,10 +122,7 @@ const List: React.FC = () => {
title: '湿度', title: '湿度',
dataIndex: 'humidity', dataIndex: 'humidity',
}, },
{
title: 'sku',
dataIndex: 'sku',
},
{ {
title: '更新时间', title: '更新时间',
dataIndex: 'updatedAt', dataIndex: 'updatedAt',
@ -123,6 +141,7 @@ const List: React.FC = () => {
valueType: 'option', valueType: 'option',
render: (_, record) => ( render: (_, record) => (
<> <>
<EditForm record={record} tableRef={actionRef} />
<Popconfirm <Popconfirm
title="删除" title="删除"
description="确认删除?" description="确认删除?"
@ -218,7 +237,7 @@ const CreateForm: React.FC<{
brand: brand ? brand.name : "", brand: brand ? brand.name : "",
strength: strength ? strength.name : '', strength: strength ? strength.name : '',
flavor: flavor ? flavor.name : '', flavor: flavor ? flavor.name : '',
humidity: humidity === 'dry' ? 'Dry' : 'Moisture', humidity: humidity ? capitalize(humidity) : '',
}, },
); );
if (!success) { if (!success) {
@ -287,18 +306,15 @@ const CreateForm: React.FC<{
destroyOnHidden: true, destroyOnHidden: true,
}} }}
onFinish={async (values) => { onFinish={async (values) => {
try { const { success, message: errMsg } =
const { success, message: errMsg } = await productcontrollerCreateproduct(values);
await productcontrollerCreateproduct(values); if (success) {
if (!success) {
throw new Error(errMsg);
}
tableRef.current?.reload();
message.success('提交成功'); message.success('提交成功');
tableRef.current?.reloadAndRest?.();
return true; return true;
} catch (error: any) {
message.error(error.message);
} }
message.error(errMsg);
return false;
}} }}
> >
<ProFormSelect <ProFormSelect
@ -353,7 +369,7 @@ const CreateForm: React.FC<{
placeholder="请选择干湿" placeholder="请选择干湿"
valueEnum={{ valueEnum={{
dry: 'dry', dry: 'dry',
wet: 'wet', moisture: 'moisture',
}} }}
rules={[{ required: true, message: '请选择干湿' }]} rules={[{ required: true, message: '请选择干湿' }]}
/> />
@ -381,6 +397,20 @@ const CreateForm: React.FC<{
</Button> </Button>
</ProForm.Group> </ProForm.Group>
<ProFormText
name="price"
label="价格"
width="md"
placeholder="请输入价格"
rules={[{ required: false }]}
/>
<ProFormText
name="promotionPrice"
label="促销价"
width="md"
placeholder="请输入促销价"
rules={[{ required: false }]}
/>
<ProFormTextArea <ProFormTextArea
name="description" name="description"
width="lg" width="lg"
@ -392,3 +422,153 @@ const CreateForm: React.FC<{
}; };
export default List; export default List;
const EditForm: React.FC<{
tableRef: React.MutableRefObject<ActionType | undefined>;
record: API.Product;
}> = ({ tableRef, record }) => {
const { message } = App.useApp();
const formRef = useRef<ProFormInstance>();
const [brandOptions, setBrandOptions] = useState<DictItem[]>([]);
const [strengthOptions, setStrengthOptions] = useState<DictItem[]>([]);
const [flavorOptions, setFlavorOptions] = useState<DictItem[]>([]);
const setInitialIds = () => {
const brand = brandOptions.find((item) => item.title === record.brandName);
const strength = strengthOptions.find((item) => item.title === record.strengthName);
const flavor = flavorOptions.find((item) => item.title === record.flavorsName);
formRef.current?.setFieldsValue({
brandId: brand?.id,
strengthId: strength?.id,
flavorsId: flavor?.id,
});
};
React.useEffect(() => {
if (brandOptions.length && strengthOptions.length && flavorOptions.length) {
setInitialIds();
}
}, [brandOptions, strengthOptions, flavorOptions]);
return (
<DrawerForm<API.UpdateProductDTO>
formRef={formRef}
title="编辑"
trigger={<Button type="link"></Button>}
initialValues={{
name: record.name,
sku: record.sku,
description: record.description,
humidity: record.humidity,
price: (record as any).price,
promotionPrice: (record as any).promotionPrice,
}}
onFinish={async (values) => {
const { success, message: errMsg } = await productcontrollerUpdateproduct(
{ id: record.id },
values as any,
);
if (success) {
message.success('更新成功');
tableRef.current?.reloadAndRest?.();
return true;
}
message.error(errMsg);
return false;
}}
>
<ProFormSelect
name="brandId"
width="lg"
label="产品品牌"
placeholder="请选择产品品牌"
request={async () => {
const { data = [] } = await productcontrollerGetbrandall();
setBrandOptions(data);
return data.map((item: DictItem) => ({
label: item.name,
value: item.id,
}));
}}
rules={[{ required: true, message: '请选择产品品牌' }]}
/>
<ProFormSelect
name="strengthId"
width="lg"
label="强度"
placeholder="请选择强度"
request={async () => {
const { data = [] } = await productcontrollerGetstrengthall();
setStrengthOptions(data);
return data.map((item: DictItem) => ({
label: item.name,
value: item.id,
}));
}}
rules={[{ required: true, message: '请选择强度' }]}
/>
<ProFormSelect
name="flavorsId"
width="lg"
label="口味"
placeholder="请选择口味"
request={async () => {
const { data = [] } = await productcontrollerGetflavorsall();
setFlavorOptions(data);
return data.map((item: DictItem) => ({
label: item.name,
value: item.id,
}));
}}
rules={[{ required: true, message: '请选择口味' }]}
/>
<ProFormSelect
name="humidity"
width="lg"
label="干湿"
placeholder="请选择干湿"
valueEnum={{
dry: 'dry',
moisture: 'moisture',
}}
rules={[{ required: true, message: '请选择干湿' }]}
/>
<ProForm.Group>
<ProFormText
name="sku"
label="SKU"
width="md"
placeholder="请输入SKU"
rules={[{ required: true, message: '请输入SKU' }]}
/>
<ProFormText
name="name"
label="名称"
width="md"
placeholder="请输入名称"
rules={[{ required: true, message: '请输入名称' }]}
/>
</ProForm.Group>
<ProFormText
name="price"
label="价格"
width="md"
placeholder="请输入价格"
rules={[{ required: false }]}
/>
<ProFormText
name="promotionPrice"
label="促销价"
width="md"
placeholder="请输入促销价"
rules={[{ required: false }]}
/>
<ProFormTextArea
name="description"
width="lg"
label="产品描述"
placeholder="请输入产品描述"
/>
</DrawerForm>
);
};

90
src/servers/api/area.ts Normal file
View File

@ -0,0 +1,90 @@
// @ts-ignore
/* eslint-disable */
import { request } from 'umi';
/** 获取区域列表(分页) GET /api/area/ */
export async function areacontrollerGetarealist(
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
params: API.areacontrollerGetarealistParams,
options?: { [key: string]: any },
) {
return request<API.Area[]>('/api/area/', {
method: 'GET',
params: {
...params,
},
...(options || {}),
});
}
/** 创建区域 POST /api/area/ */
export async function areacontrollerCreatearea(
body: API.CreateAreaDTO,
options?: { [key: string]: any },
) {
return request<API.Area>('/api/area/', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
data: body,
...(options || {}),
});
}
/** 根据ID获取区域详情 GET /api/area/${param0} */
export async function areacontrollerGetareabyid(
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
params: API.areacontrollerGetareabyidParams,
options?: { [key: string]: any },
) {
const { id: param0, ...queryParams } = params;
return request<API.Area>(`/api/area/${param0}`, {
method: 'GET',
params: { ...queryParams },
...(options || {}),
});
}
/** 更新区域 PUT /api/area/${param0} */
export async function areacontrollerUpdatearea(
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
params: API.areacontrollerUpdateareaParams,
body: API.UpdateAreaDTO,
options?: { [key: string]: any },
) {
const { id: param0, ...queryParams } = params;
return request<API.Area>(`/api/area/${param0}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
},
params: { ...queryParams },
data: body,
...(options || {}),
});
}
/** 删除区域 DELETE /api/area/${param0} */
export async function areacontrollerDeletearea(
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
params: API.areacontrollerDeleteareaParams,
options?: { [key: string]: any },
) {
const { id: param0, ...queryParams } = params;
return request<any>(`/api/area/${param0}`, {
method: 'DELETE',
params: { ...queryParams },
...(options || {}),
});
}
/** 获取所有区域 GET /api/area/all */
export async function areacontrollerGetallareas(options?: {
[key: string]: any;
}) {
return request<API.Area[]>('/api/area/all', {
method: 'GET',
...(options || {}),
});
}

View File

@ -2,6 +2,7 @@
/* eslint-disable */ /* eslint-disable */
// API 更新时间: // API 更新时间:
// API 唯一标识: // API 唯一标识:
import * as area from './area';
import * as customer from './customer'; import * as customer from './customer';
import * as dict from './dict'; import * as dict from './dict';
import * as locales from './locales'; import * as locales from './locales';
@ -17,6 +18,7 @@ import * as user from './user';
import * as webhook from './webhook'; import * as webhook from './webhook';
import * as wpProduct from './wpProduct'; import * as wpProduct from './wpProduct';
export default { export default {
area,
customer, customer,
dict, dict,
locales, locales,

View File

@ -7,6 +7,37 @@ declare namespace API {
postal_code?: string; postal_code?: string;
}; };
type Area = {
id?: number;
/** 区域名称 */
name?: string;
/** 创建时间 */
createdAt: string;
/** 更新时间 */
updatedAt: string;
};
type areacontrollerDeleteareaParams = {
id: number;
};
type areacontrollerGetareabyidParams = {
id: number;
};
type areacontrollerGetarealistParams = {
/** 当前页码 */
currentPage?: number;
/** 每页数量 */
pageSize?: number;
/** 区域名称 */
name?: string;
};
type areacontrollerUpdateareaParams = {
id: number;
};
type BatchSetSkuDTO = { type BatchSetSkuDTO = {
/** sku 数据列表 */ /** sku 数据列表 */
skus?: SkuItemDTO[]; skus?: SkuItemDTO[];
@ -34,6 +65,11 @@ declare namespace API {
items?: Dict[]; items?: Dict[];
}; };
type CreateAreaDTO = {
/** 区域名称 */
name?: string;
};
type CreateBrandDTO = { type CreateBrandDTO = {
/** 品牌名称 */ /** 品牌名称 */
title: string; title: string;
@ -64,12 +100,12 @@ declare namespace API {
description?: string; description?: string;
/** 产品 SKU */ /** 产品 SKU */
sku?: string; sku?: string;
/** 品牌 */ /** 品牌 ID */
brand?: DictItemDTO; brandId?: number;
/** 规格 */ /** 规格 ID */
strength?: DictItemDTO; strengthId?: number;
/** 口味 */ /** 口味 ID */
flavor?: DictItemDTO; flavorsId?: number;
humidity?: string; humidity?: string;
/** 价格 */ /** 价格 */
price?: number; price?: number;
@ -177,13 +213,6 @@ declare namespace API {
id: number; id: number;
}; };
type DictItemDTO = {
/** 显示名称 */
title?: string;
/** 唯一标识 */
name: string;
};
type DisableSiteDTO = {}; type DisableSiteDTO = {};
type localecontrollerGetlocaleParams = { type localecontrollerGetlocaleParams = {
@ -705,6 +734,12 @@ declare namespace API {
sku?: string; sku?: string;
/** 价格 */ /** 价格 */
price?: number; price?: number;
/** 促销价格 */
promotionPrice?: number;
/** 库存 */
stock?: number;
/** 来源 */
source?: number;
/** 创建时间 */ /** 创建时间 */
createdAt: string; createdAt: string;
/** 更新时间 */ /** 更新时间 */
@ -906,6 +941,15 @@ declare namespace API {
items?: PurchaseOrderDTO[]; items?: PurchaseOrderDTO[];
}; };
type QueryAreaDTO = {
/** 当前页码 */
currentPage?: number;
/** 每页数量 */
pageSize?: number;
/** 区域名称 */
name?: string;
};
type QueryBrandDTO = { type QueryBrandDTO = {
/** 页码 */ /** 页码 */
current?: number; current?: number;
@ -1569,6 +1613,11 @@ declare namespace API {
minute?: string; minute?: string;
}; };
type UpdateAreaDTO = {
/** 区域名称 */
name?: string;
};
type UpdateBrandDTO = { type UpdateBrandDTO = {
/** 品牌名称 */ /** 品牌名称 */
title?: string; title?: string;
@ -1594,12 +1643,12 @@ declare namespace API {
description?: string; description?: string;
/** 产品 SKU */ /** 产品 SKU */
sku?: string; sku?: string;
/** 品牌 */ /** 品牌 ID */
brand?: DictItemDTO; brandId?: number;
/** 规格 */ /** 规格 ID */
strength?: DictItemDTO; strengthId?: number;
/** 口味 */ /** 口味 ID */
flavor?: DictItemDTO; flavorsId?: number;
humidity?: string; humidity?: string;
/** 价格 */ /** 价格 */
price?: number; price?: number;