This commit is contained in:
339
src/pages/team/AfterSaleRecord/MultiAfterSalesModal.tsx
Normal file
339
src/pages/team/AfterSaleRecord/MultiAfterSalesModal.tsx
Normal file
@@ -0,0 +1,339 @@
|
||||
/**
|
||||
* 文件: MultiAfterSalesModal.tsx
|
||||
* 作者: 阿瑞
|
||||
* 功能: 多重售后操作模态框组件
|
||||
* 版本: v2.0.0 - 使用 fetch 替换 axios
|
||||
*/
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Modal, Form, Select, DatePicker, Button, InputNumber, Input, App } from 'antd';
|
||||
import { IAfterSalesRecord, IPaymentPlatform, IProduct } from '@/models/types';
|
||||
import dayjs from 'dayjs';
|
||||
import { useUserInfo } from '@/store/userStore';
|
||||
import ProductImage from '@/components/product/ProductImage';
|
||||
import AddProductComponent from '../sale/components/AddProductComponent';
|
||||
import { CloseOutlined } from '@ant-design/icons';
|
||||
|
||||
interface MultiAfterSalesModalProps {
|
||||
visible: boolean;
|
||||
onOk: () => void;
|
||||
onCancel: () => void;
|
||||
record: IAfterSalesRecord | null; // 传递当前的售后记录
|
||||
type: '退货' | '换货' | '补发' | '补差';
|
||||
}
|
||||
|
||||
const { Option } = Select;
|
||||
|
||||
const MultiAfterSalesModal: React.FC<MultiAfterSalesModalProps> = ({ visible, onOk, onCancel, record, type }) => {
|
||||
const { message } = App.useApp(); // 使用 App.useApp 获取 message 实例
|
||||
const [form] = Form.useForm();
|
||||
const [paymentPlatforms, setPaymentPlatforms] = useState<IPaymentPlatform[]>([]);
|
||||
const [selectedProducts, setSelectedProducts] = useState<IProduct[]>([]);
|
||||
const [products, setProducts] = useState<IProduct[]>([]);
|
||||
const userInfo = useUserInfo();
|
||||
const [paymentCode, setPaymentCode] = useState<string | null>(null);
|
||||
|
||||
//获取收支平台
|
||||
const fetchPayPlatforms = async (teamId: string) => {
|
||||
try {
|
||||
const response = await fetch(`/api/backstage/payment-platforms?teamId=${teamId}`);
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
const data = await response.json();
|
||||
setPaymentPlatforms(data.platforms || []);
|
||||
} catch (error: unknown) {
|
||||
console.error('加载平台数据失败:', error);
|
||||
message.error('加载平台数据失败');
|
||||
setPaymentPlatforms([]);
|
||||
}
|
||||
};
|
||||
|
||||
// 获取产品数据
|
||||
const fetchProducts = async (teamId: string) => {
|
||||
try {
|
||||
const response = await fetch(`/api/backstage/products?teamId=${teamId}`);
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
const data = await response.json();
|
||||
setProducts(data.products || []);
|
||||
} catch (error: unknown) {
|
||||
console.error('加载产品数据失败:', error);
|
||||
message.error('加载产品数据失败');
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
form.resetFields();
|
||||
setSelectedProducts([]);
|
||||
if (record && userInfo.团队?._id) {
|
||||
const teamId = userInfo.团队._id;
|
||||
fetchPayPlatforms(teamId);
|
||||
fetchProducts(teamId); // 加载产品列表
|
||||
|
||||
form.setFieldsValue({
|
||||
前一次售后: record._id, // 使用当前售后记录的ID作为前一次售后
|
||||
类型: type,
|
||||
日期: dayjs(),
|
||||
});
|
||||
}
|
||||
}, [record, type, userInfo.团队?._id]);
|
||||
|
||||
const handleOk = async () => {
|
||||
try {
|
||||
const values = await form.validateFields();
|
||||
if (!record) {
|
||||
message.error('未选择售后记录,无法创建新的售后记录');
|
||||
return;
|
||||
}
|
||||
const replacementProductIds = selectedProducts.map(product => product._id);
|
||||
let paymentCodeId = null;
|
||||
if (paymentCode) {
|
||||
const response = await fetch('/api/backstage/sales/aftersale/uploadPaymentCode', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ 收款码: paymentCode }),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('收款码上传失败');
|
||||
}
|
||||
|
||||
const paymentCodeResponse = await response.json();
|
||||
paymentCodeId = paymentCodeResponse.paymentCodeId;
|
||||
}
|
||||
const afterSalesData = {
|
||||
销售记录: record.销售记录._id,
|
||||
前一次售后: record._id, // 传递前一次售后的 ID
|
||||
类型: type,
|
||||
原产品: values.原产品, // 传递原产品的 ID 数组
|
||||
替换产品: replacementProductIds, // 传递替换产品的 ID 数组
|
||||
团队: userInfo.团队?._id,
|
||||
日期: values.日期.toISOString(),
|
||||
收款码: paymentCodeId, // 添加收款码ID字段
|
||||
原因: values.原因, // 售后原因
|
||||
收支类型: values.收支类型,
|
||||
收支平台: values.收支平台,
|
||||
收支金额: values.收支金额,
|
||||
待收: values.待收,
|
||||
备注: values.备注,
|
||||
};
|
||||
|
||||
const response = await fetch('/api/backstage/sales/aftersale', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(afterSalesData),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
message.success(`${type}操作成功`);
|
||||
onOk();
|
||||
} catch (error: unknown) {
|
||||
console.error(`${type}操作失败:`, error);
|
||||
message.error(`${type}操作失败`);
|
||||
}
|
||||
};
|
||||
|
||||
const [isProductModalVisible, setIsProductModalVisible] = useState(false);
|
||||
// 修改:新增产品成功后的回调函数,更新选中的产品状态
|
||||
const handleAddProductSuccess = (newProduct: IProduct) => {
|
||||
setSelectedProducts(prevProducts => [...prevProducts, newProduct]); // 更新选中的产品
|
||||
setIsProductModalVisible(false); // 关闭产品模态框
|
||||
};
|
||||
const handleProductSelectChange = (selectedProductIds: string[]) => {
|
||||
const selected = products.filter(product => selectedProductIds.includes(product._id));
|
||||
setSelectedProducts(selected);
|
||||
};
|
||||
const renderAdditionalFields = () => {
|
||||
if (type === '换货' || type === '补发') {
|
||||
return (
|
||||
<div>
|
||||
<div style={{ marginTop: 16 }}>
|
||||
{selectedProducts.map(product => (
|
||||
<div key={product._id} style={{ marginBottom: 8 }}>
|
||||
<ProductImage productId={product._id} alt={product.名称} width={72} height={72} />
|
||||
<span style={{ marginLeft: 8 }}>{product.名称}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={() => setIsProductModalVisible(true)}
|
||||
>
|
||||
添加 {type} 产品
|
||||
</Button>
|
||||
<AddProductComponent
|
||||
visible={isProductModalVisible}
|
||||
onClose={() => setIsProductModalVisible(false)}
|
||||
onSuccess={handleAddProductSuccess}
|
||||
/>
|
||||
<Form.Item name="替换产品" label="选择产品">
|
||||
<Select mode="multiple" placeholder="请选择产品" onChange={handleProductSelectChange}>
|
||||
{products.map(product => (
|
||||
<Select.Option key={product._id} value={product._id}>
|
||||
【名称】{product.名称}【价格】{product.售价}【库存】{product.库存}【描述】{product.描述}【货号】{product.货号}【别名】{product.别名}【级别】{product.级别}
|
||||
</Select.Option>
|
||||
))}
|
||||
</Select>
|
||||
</Form.Item>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
const handlePaste = (event: React.ClipboardEvent<HTMLDivElement>) => {
|
||||
const items = event.clipboardData.items;
|
||||
for (const item of items) {
|
||||
if (item.type.startsWith('image/')) {
|
||||
const file = item.getAsFile();
|
||||
if (file) {
|
||||
const reader = new FileReader();
|
||||
reader.onload = async (e) => {
|
||||
const img = new Image();
|
||||
img.src = e.target?.result as string;
|
||||
img.onload = async () => {
|
||||
const canvas = document.createElement('canvas');
|
||||
const ctx = canvas.getContext('2d');
|
||||
const width = img.width / 2;
|
||||
const height = img.height / 2;
|
||||
canvas.width = width;
|
||||
canvas.height = height;
|
||||
ctx?.drawImage(img, 0, 0, width, height);
|
||||
const compressedDataUrl = canvas.toDataURL('image/jpeg', 0.8);
|
||||
setPaymentCode(compressedDataUrl);
|
||||
};
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal
|
||||
open={visible}
|
||||
title={`创建${type}记录`}
|
||||
onCancel={onCancel}
|
||||
footer={[
|
||||
<Button key="cancel" onClick={onCancel}>
|
||||
取消
|
||||
</Button>,
|
||||
<Button key="submit" type="primary" onClick={handleOk}>
|
||||
保存
|
||||
</Button>,
|
||||
]}
|
||||
width="76vw"
|
||||
style={{ top: 20 }}
|
||||
>
|
||||
<Form form={form} layout="vertical">
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
<div className="col-span-1">
|
||||
<h3 className="font-bold">售后信息</h3>
|
||||
<Form.Item name="原产品" label="上一次售后产品" rules={[{ required: true, message: '请选择需要售后的产品' }]}>
|
||||
<Select mode="multiple" placeholder="请选择原订单中的产品">
|
||||
{record?.替换产品.map(product => (
|
||||
<Select.Option key={product._id} value={product._id}>
|
||||
<div style={{ display: 'flex', alignItems: 'center' }}>
|
||||
<ProductImage productId={product._id} alt={product.名称} width={40} height={40} />
|
||||
<span style={{ marginLeft: 8 }}>{product.名称}</span>
|
||||
</div>
|
||||
</Select.Option>
|
||||
))}
|
||||
</Select>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item label="售后原因" name="原因" rules={[{ required: true, message: '请选择原因' }]}>
|
||||
<Select>
|
||||
<Option value="发货原因">发货原因</Option>
|
||||
<Option value="产品质量">产品质量</Option>
|
||||
<Option value="择优选购">择优选购</Option>
|
||||
<Option value="七日退换">七日退换</Option>
|
||||
<Option value="货不对板">货不对板</Option>
|
||||
<Option value="运输损坏">运输损坏</Option>
|
||||
<Option value="配件缺失">配件缺失</Option>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
</div>
|
||||
<div className="col-span-1">
|
||||
<h3 className="font-bold">售后信息</h3>
|
||||
{renderAdditionalFields()}
|
||||
<br />
|
||||
<Form.Item name="日期" label="售后日期" rules={[{ required: true, message: '请选择售后日期' }]}>
|
||||
<DatePicker style={{ width: '100%' }} />
|
||||
</Form.Item>
|
||||
<Form.Item name="备注" label="备注">
|
||||
<Input.TextArea
|
||||
//默认显示4行,最多6行
|
||||
autoSize={{ minRows: 4, maxRows: 6 }}
|
||||
placeholder="备注信息" />
|
||||
</Form.Item>
|
||||
</div>
|
||||
<div className="col-span-1">
|
||||
<h3 className="font-bold">收支信息</h3>
|
||||
<Form.Item name="收支平台" label="收支平台">
|
||||
<Select placeholder="请选择收支平台">
|
||||
{paymentPlatforms.map(platform => (
|
||||
<Select.Option key={platform._id} value={platform._id}>
|
||||
{platform.名称}
|
||||
</Select.Option>
|
||||
))}
|
||||
</Select>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label="收支类型"
|
||||
name="收支类型"
|
||||
>
|
||||
<Select placeholder="请选择收支类型" allowClear>
|
||||
<Option value="收入">收入</Option>
|
||||
<Option value="支出">支出</Option>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
<Form.Item name="收支金额" label="收支金额" >
|
||||
<InputNumber min={0} style={{ width: '100%' }} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label="待收"
|
||||
name="待收"
|
||||
>
|
||||
<InputNumber min={0} style={{ width: '100%' }} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label="收款码"
|
||||
name="收款码"
|
||||
>
|
||||
<div
|
||||
onPaste={handlePaste}
|
||||
className="cursor-pointer flex flex-col items-center justify-center p-4 min-h-[260px] min-w-[260px] border-dashed border rounded-lg"
|
||||
>
|
||||
{paymentCode ? (
|
||||
<>
|
||||
<img src={paymentCode} alt="Payment Code" className="max-w-full max-h-[260px]" />
|
||||
<button
|
||||
onClick={() => setPaymentCode(null)}
|
||||
className="absolute top-2 right-2 p-1 bg-gray-100 hover:bg-gray-200 rounded-full"
|
||||
style={{ width: '22px', height: '22px', display: 'flex', alignItems: 'center', justifyContent: 'center', borderRadius: '50%', outline: 'none', color: 'red' }}
|
||||
>
|
||||
<CloseOutlined />
|
||||
</button>
|
||||
</>
|
||||
) : (
|
||||
<p>粘贴图片到此区域</p>
|
||||
)}
|
||||
</div>
|
||||
</Form.Item>
|
||||
</div>
|
||||
</div>
|
||||
</Form>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export default MultiAfterSalesModal;
|
||||
Reference in New Issue
Block a user