Files
SaaS2/src/pages/team/AfterSaleRecord/MultiAfterSalesModal.tsx
RUI eb79e416db
Some checks failed
Next.js CI/CD 流水线 / deploy (push) Failing after 40s
0607.4
2025-06-07 01:41:58 +08:00

340 lines
16 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 文件: 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;