340 lines
16 KiB
TypeScript
340 lines
16 KiB
TypeScript
/**
|
||
* 文件: 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;
|