HirePurchaseUploadPayBillModal.tsx 11.1 KB
import { RESPONSE_CODE } from '@/constants/enum';
import {
  postServiceOrderFileProcess,
  postServiceOrderHirePurchase,
} from '@/services';
import { transImageFile } from '@/utils';
import { PlusOutlined } from '@ant-design/icons';
import { Button, Form, Input, Modal, Radio, Upload, message } from 'antd';
import { RcFile } from 'antd/lib/upload';
import { UploadFile, UploadProps } from 'antd/lib/upload/interface';
import { cloneDeep } from 'lodash';
import { useEffect, useRef, useState } from 'react';
import { COMFIR_RECEIPT_IMAGES_NUMBER } from '../constant';

interface HirePurchaseUploadPayBillModalProps {
  visible: boolean;
  onCancel: () => void;
  onOk: () => void;
  orderAmount?: number;
  paidAmount?: number;
  record?: any;
  subOrders?: any[];
}

const HirePurchaseUploadPayBillModal: React.FC<
  HirePurchaseUploadPayBillModalProps
> = ({
  visible,
  onCancel,
  onOk,
  orderAmount = 100000.0,
  paidAmount = 0,
  record,
  subOrders = [],
}) => {
  // 订单总金额
  const totalPayment = record?.totalPayment || orderAmount;
  // 已回款金额
  const installedMoney = record?.installmentMoneyAudit || paidAmount;
  // 待回款金额
  const remainingMoney = totalPayment - installedMoney;
  const [form] = Form.useForm();
  const [fileList, setFileList] = useState<UploadFile[]>([]);
  const [paymentType, setPaymentType] = useState<string>('INSTALLMENT');
  const [previewOpen, setPreviewOpen] = useState(false);
  const [previewImage, setPreviewImage] = useState('');
  const [previewTitle, setPreviewTitle] = useState('');

  const fileListObj = useRef<UploadFile[]>([]);

  const getBase64 = (file: RcFile): Promise<string> =>
    new Promise((resolve, reject) => {
      const reader = new FileReader();
      reader.readAsDataURL(file);
      reader.onload = () => resolve(reader.result as string);
      reader.onerror = (error) => reject(error);
    });

  const handleCancel = () => setPreviewOpen(false);

  const uploadButton = (
    <div>
      <PlusOutlined />
      <div style={{ marginTop: 8 }}>上传凭证</div>
    </div>
  );

  const handleTypeChange = (e: any) => {
    const newType = e.target.value;
    setPaymentType(newType);

    // 如果选择全部回款,自动填入待回款金额
    if (newType === 'FULL') {
      form.setFieldsValue({
        amount: remainingMoney.toFixed(2),
      });
    }
  };

  // 验证回款金额不能超过待回款金额
  const validateAmount = (_: any, value: string) => {
    if (!value) return Promise.reject('请输入回款金额');

    // Check if the value is a valid number
    if (isNaN(value)) return Promise.reject('请输入有效的数字');

    // Check if the value has more than 2 decimal places
    const decimalCount = (value.match(/\.\d+/) || [''])[0].length - 1;
    if (decimalCount > 2) {
      return Promise.reject('最多只能输入两位小数');
    }

    const amount = parseFloat(value);
    if (amount <= 0) return Promise.reject('回款金额必须大于0');
    if (amount > remainingMoney)
      return Promise.reject(
        `回款金额不能超过待回款金额 ${remainingMoney.toFixed(2)}元`,
      );

    return Promise.resolve();
  };

  const handleBeforeUpload = (file: any) => {
    setFileList([...fileList, file]);
    return false;
  };

  /** 粘贴快捷键的回调 */
  const onPaste = async (e: any) => {
    /** 获取剪切板的数据clipboardData */
    let clipboardData = e.clipboardData,
      i = 0,
      items,
      item,
      types;

    /** 为空判断 */
    if (clipboardData) {
      items = clipboardData.items;
      if (!items) {
        message.info('您的剪贴板中没有照片');
        return;
      }

      item = items[0];
      types = clipboardData.types || [];
      /** 遍历剪切板的数据 */
      for (; i < types.length; i++) {
        if (types[i] === 'Files') {
          item = items[i];
          break;
        }
      }

      /** 判断文件是否为图片 */
      if (item && item.kind === 'file' && item.type.match(/^image\//i)) {
        const imgItem = item.getAsFile();
        const newFileList = cloneDeep(fileListObj.current);
        let filteredArray = newFileList.filter(
          (obj) => obj.status !== 'removed',
        ); //过滤掉状态为已删除的照片
        const listItem = {
          ...imgItem,
          status: 'done',
          url: await getBase64(imgItem),
          originFileObj: imgItem,
        };

        if (filteredArray.length >= COMFIR_RECEIPT_IMAGES_NUMBER) {
          message.info('上传凭证数量不能超过3');
          return;
        }
        fileListObj.current = filteredArray;
        filteredArray.push(listItem);
        setFileList(filteredArray);
        return;
      }
    }

    message.info('您的剪贴板中没有照片');
  };

  const handleChange: UploadProps['onChange'] = ({ fileList: newFileList }) => {
    fileListObj.current = newFileList;
    setFileList(newFileList);
  };

  const handlePreview = async (file: UploadFile) => {
    if (!file.url && !file.preview) {
      file.preview = await getBase64(file.originFileObj as RcFile);
    }
    setPreviewImage(file.url || (file.preview as string));
    setPreviewOpen(true);
    setPreviewTitle(
      file.name ||
        file.originFileObj?.name ||
        file.url!.substring(file.url!.lastIndexOf('/') + 1),
    );
  };

  const props: UploadProps = {
    onRemove: (file) => {
      const index = fileList.indexOf(file);
      const newFileList = fileList.slice();
      newFileList.splice(index, 1);
      setFileList(newFileList);
    },
    beforeUpload: handleBeforeUpload,
    listType: 'picture-card',
    onPreview: handlePreview,
    fileList,
    onChange: handleChange,
    accept: 'image/png, image/jpeg, image/png',
    name: 'files',
    headers: { Authorization: localStorage.getItem('token') },
  };

  useEffect(() => {
    document.addEventListener('paste', onPaste);
    return () => {
      document.removeEventListener('paste', onPaste);
    };
  }, []);

  const handleOk = async () => {
    try {
      const values = await form.validateFields();

      if (fileList.length <= 0) {
        message.error('请上传至少一张凭证');
        return;
      }

      message.open({
        type: 'loading',
        content: '正在上传凭证...',
        duration: 0,
      });

      // 附件处理
      let formData = new FormData();
      for (let file of fileList) {
        if (file.originFileObj) {
          formData.append('files', file.originFileObj as RcFile);
        } else {
          // 有url的话取url(源文件),没url取thumbUrl。有url的时候thumbUrl是略缩图
          if (file?.url === undefined || file?.url === null) {
            formData.append(
              'files',
              transImageFile(file?.thumbUrl),
              file?.originFileObj?.name,
            );
          } else {
            formData.append(
              'files',
              transImageFile(file?.url),
              file?.originFileObj?.name,
            );
          }
        }
      }

      let res = await postServiceOrderFileProcess({
        data: formData,
      });

      message.destroy();

      if (res.result === RESPONSE_CODE.SUCCESS) {
        let fileUrls = res?.data?.map((item) => {
          return { url: item };
        });

        // 分期付款提交
        const installmentMoney = values.amount;
        const installmentComment = values.remarks;

        // 获取子订单IDs
        const subOrderIds =
          subOrders?.map((item: any) => {
            return item.id;
          }) || [];

        const data = await postServiceOrderHirePurchase({
          data: {
            subOrderIds: subOrderIds,
            filePaths: fileUrls,
            installmentMoney: installmentMoney,
            installmentComment: installmentComment,
          },
        });

        if (data.result === RESPONSE_CODE.SUCCESS) {
          message.success(data.message || '提交成功');
          onOk();
        } else {
          message.error(data.message || '提交失败');
        }
      } else {
        message.error(res.message || '上传失败');
      }
    } catch (error) {
      console.error('Validate Failed:', error);
      message.error('提交失败');
    }
  };

  return (
    <>
      <Modal
        title="回款"
        open={visible}
        onCancel={onCancel}
        footer={[
          <Button key="cancel" onClick={onCancel}>
            取消
          </Button>,
          <Button key="submit" type="primary" onClick={handleOk}>
            确认
          </Button>,
        ]}
        width={500}
      >
        <Form form={form} layout="vertical">
          <div style={{ marginBottom: 16 }}>
            <div
              style={{
                display: 'flex',
                justifyContent: 'space-between',
                marginBottom: 8,
              }}
            >
              <span>订单总金额:</span>
              <span>{totalPayment.toFixed(2)}元</span>
            </div>
            <div
              style={{
                display: 'flex',
                justifyContent: 'space-between',
                marginBottom: 8,
              }}
            >
              <span>已回款金额:</span>
              <span>{installedMoney.toFixed(2)}元</span>
            </div>
            <div
              style={{
                display: 'flex',
                justifyContent: 'space-between',
                marginBottom: 8,
              }}
            >
              <span>待回款金额:</span>
              <span>{remainingMoney.toFixed(2)}元</span>
            </div>
          </div>

          <Form.Item
            label="回款类型"
            name="paymentType"
            initialValue={paymentType}
          >
            <Radio.Group onChange={handleTypeChange}>
              <Radio value="INSTALLMENT">分期回款</Radio>
              <Radio value="FULL">全部回款</Radio>
            </Radio.Group>
          </Form.Item>

          <Form.Item
            label="回款金额"
            name="amount"
            rules={[
              { required: true, message: '请输入回款金额' },
              { validator: validateAmount },
            ]}
          >
            <Input
              placeholder="请输入回款金额"
              suffix="元"
              disabled={paymentType === 'FULL'}
            />
          </Form.Item>

          <div className="pb-4 text-xs decoration-gray-50">可复制照片粘贴</div>
          <Form.Item
            label="附件凭证"
            name="attachments"
            rules={[{ required: true, message: '请上传回款凭证' }]}
          >
            <Upload {...props}>
              {fileList.length < COMFIR_RECEIPT_IMAGES_NUMBER
                ? uploadButton
                : ''}
            </Upload>
          </Form.Item>

          <Form.Item label="备注" name="remarks">
            <Input.TextArea rows={4} placeholder="请输入备注信息" />
          </Form.Item>
        </Form>
      </Modal>

      <Modal
        open={previewOpen}
        title={previewTitle}
        footer={null}
        onCancel={handleCancel}
      >
        <img alt="图片预览" style={{ width: '100%' }} src={previewImage} />
      </Modal>
    </>
  );
};

export default HirePurchaseUploadPayBillModal;