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 [editingDictItem, setEditingDictItem] = useState<any>(null);
const [dictItemForm, setDictItemForm] = useState({
name: '',
title: '',
value: '',
});
const [dictItemForm] = Form.useForm();
// 获取字典列表
const fetchDicts = async (title?: string) => {
@ -126,36 +122,32 @@ const DictPage: React.FC = () => {
// 打开添加字典项模态框
const handleAddDictItem = () => {
setEditingDictItem(null);
setDictItemForm({ name: '', title: '', value: '' });
dictItemForm.resetFields();
setIsDictItemModalVisible(true);
};
// 打开编辑字典项模态框
const handleEditDictItem = (item: any) => {
setEditingDictItem(item);
setDictItemForm(item);
dictItemForm.setFieldsValue(item);
setIsDictItemModalVisible(true);
};
// 处理字典项表单提交(添加/编辑)
const handleDictItemFormSubmit = async () => {
if (!dictItemForm.name || !dictItemForm.title) {
message.warning('请输入名称和标题');
return;
}
const handleDictItemFormSubmit = async (values: any) => {
try {
if (editingDictItem) {
// 编辑
await request(`/dict/item/${editingDictItem.id}`, {
method: 'PUT',
data: dictItemForm,
data: values,
});
message.success('更新成功');
} else {
// 添加
await request('/dict/item', {
method: 'POST',
data: { ...dictItemForm, dictId: selectedDict.id },
data: { ...values, dictId: selectedDict.id },
});
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 = [
{
@ -211,6 +247,7 @@ const DictPage: React.FC = () => {
render: (_: any, record: any) => (
<Space>
<Button
size='small'
type="link"
onClick={(e) => {
e.stopPropagation();
@ -220,6 +257,7 @@ const DictPage: React.FC = () => {
</Button>
<Button
size='small'
type="link"
danger
onClick={(e) => {
@ -246,6 +284,11 @@ const DictPage: React.FC = () => {
dataIndex: 'title',
key: 'title',
},
{
title: '中文标题',
dataIndex: 'titleCN',
key: 'titleCN',
},
{
title: '操作',
key: 'action',
@ -308,7 +351,7 @@ const DictPage: React.FC = () => {
>
<Button icon={<UploadOutlined />}></Button>
</Upload>
<Button onClick={() => window.open('/dict/template')}>
<Button onClick={handleDownloadDictTemplate}>
</Button>
</Space>
@ -336,6 +379,7 @@ const DictPage: React.FC = () => {
</Sider>
<Content style={{ padding: '16px' }}>
<Space direction="vertical" style={{ width: '100%' }}>
<div style={{ width: '100%', display: 'flex', flexDirection: 'row', gap: '2px' }}>
<Button
type="primary"
onClick={handleAddDictItem}
@ -343,7 +387,6 @@ const DictPage: React.FC = () => {
>
</Button>
<Space>
<Upload
name="file"
action={`/dict/item/import`}
@ -364,12 +407,12 @@ const DictPage: React.FC = () => {
</Button>
</Upload>
<Button
onClick={() => window.open('/dict/item/template')}
onClick={handleDownloadDictItemTemplate}
disabled={!selectedDict}
>
</Button>
</Space>
</div>
<Table
dataSource={dictItems}
columns={dictItemColumns}
@ -382,37 +425,35 @@ const DictPage: React.FC = () => {
<Modal
title={editingDictItem ? '编辑字典项' : '添加字典项'}
visible={isDictItemModalVisible}
onOk={handleDictItemFormSubmit}
open={isDictItemModalVisible}
onOk={() => dictItemForm.submit()}
onCancel={() => setIsDictItemModalVisible(false)}
destroyOnClose
>
<Form layout="vertical">
<Form.Item label="名称">
<Input
placeholder="名称 (e.g., zyn)"
value={dictItemForm.name}
onChange={(e) =>
setDictItemForm({ ...dictItemForm, name: e.target.value })
}
/>
<Form
form={dictItemForm}
layout="vertical"
onFinish={handleDictItemFormSubmit}
>
<Form.Item
label="名称"
name="name"
rules={[{ required: true, message: '请输入名称' }]}
>
<Input placeholder="名称 (e.g., zyn)" />
</Form.Item>
<Form.Item label="标题">
<Input
placeholder="标题 (e.g., ZYN)"
value={dictItemForm.title}
onChange={(e) =>
setDictItemForm({ ...dictItemForm, title: e.target.value })
}
/>
<Form.Item
label="标题"
name="title"
rules={[{ required: true, message: '请输入标题' }]}
>
<Input placeholder="标题 (e.g., ZYN)" />
</Form.Item>
<Form.Item label="值 (可选)">
<Input
placeholder="值 (可选)"
value={dictItemForm.value}
onChange={(e) =>
setDictItemForm({ ...dictItemForm, value: e.target.value })
}
/>
<Form.Item label="中文标题" name="titleCN">
<Input placeholder="中文标题 (e.g., 品牌)" />
</Form.Item>
<Form.Item label="值 (可选)" name="value">
<Input placeholder="值 (可选)" />
</Form.Item>
</Form>
</Modal>

View File

@ -6,6 +6,7 @@ import {
productcontrollerGetproductlist,
productcontrollerGetstrengthall,
productcontrollerUpdateproductnamecn,
productcontrollerUpdateproduct,
} from '@/servers/api/product';
import { templatecontrollerRendertemplate } from '@/servers/api/template';
import { PlusOutlined } from '@ant-design/icons';
@ -23,6 +24,7 @@ import {
} from '@ant-design/pro-components';
import { App, Button, Popconfirm } from 'antd';
import React, { useRef, useState } from 'react';
const capitalize = (s: string) => s.charAt(0).toLocaleUpperCase() + s.slice(1);
// TODO
interface DictItem {
id: number;
@ -54,7 +56,7 @@ const NameCn: React.FC<{
if (!success) {
return message.error(errMsg);
}
tableRef?.current?.reload();
tableRef?.current?.reloadAndRest?.();
},
}}
/>
@ -67,6 +69,10 @@ const List: React.FC = () => {
const { message } = App.useApp();
const columns: ProColumns<API.Product>[] = [
{
title: 'sku',
dataIndex: 'sku',
},
{
title: '名称',
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: '产品描述',
dataIndex: 'description',
@ -101,10 +122,7 @@ const List: React.FC = () => {
title: '湿度',
dataIndex: 'humidity',
},
{
title: 'sku',
dataIndex: 'sku',
},
{
title: '更新时间',
dataIndex: 'updatedAt',
@ -123,6 +141,7 @@ const List: React.FC = () => {
valueType: 'option',
render: (_, record) => (
<>
<EditForm record={record} tableRef={actionRef} />
<Popconfirm
title="删除"
description="确认删除?"
@ -218,7 +237,7 @@ const CreateForm: React.FC<{
brand: brand ? brand.name : "",
strength: strength ? strength.name : '',
flavor: flavor ? flavor.name : '',
humidity: humidity === 'dry' ? 'Dry' : 'Moisture',
humidity: humidity ? capitalize(humidity) : '',
},
);
if (!success) {
@ -287,18 +306,15 @@ const CreateForm: React.FC<{
destroyOnHidden: true,
}}
onFinish={async (values) => {
try {
const { success, message: errMsg } =
await productcontrollerCreateproduct(values);
if (!success) {
throw new Error(errMsg);
}
tableRef.current?.reload();
if (success) {
message.success('提交成功');
tableRef.current?.reloadAndRest?.();
return true;
} catch (error: any) {
message.error(error.message);
}
message.error(errMsg);
return false;
}}
>
<ProFormSelect
@ -353,7 +369,7 @@ const CreateForm: React.FC<{
placeholder="请选择干湿"
valueEnum={{
dry: 'dry',
wet: 'wet',
moisture: 'moisture',
}}
rules={[{ required: true, message: '请选择干湿' }]}
/>
@ -381,6 +397,20 @@ const CreateForm: React.FC<{
</Button>
</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"
@ -392,3 +422,153 @@ const CreateForm: React.FC<{
};
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 */
// API 更新时间:
// API 唯一标识:
import * as area from './area';
import * as customer from './customer';
import * as dict from './dict';
import * as locales from './locales';
@ -17,6 +18,7 @@ import * as user from './user';
import * as webhook from './webhook';
import * as wpProduct from './wpProduct';
export default {
area,
customer,
dict,
locales,

View File

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