diff --git a/src/components/Invoice/InvoiceApplyForm.tsx b/src/components/Invoice/InvoiceApplyForm.tsx new file mode 100644 index 00000000..0b3383ef --- /dev/null +++ b/src/components/Invoice/InvoiceApplyForm.tsx @@ -0,0 +1,472 @@ +/** + * 发票申请表单组件 + */ +import React, { useState, useEffect } from 'react'; +import { + Box, + VStack, + HStack, + FormControl, + FormLabel, + FormErrorMessage, + Input, + Textarea, + Button, + Radio, + RadioGroup, + Stack, + Text, + Divider, + useColorModeValue, + Checkbox, + Alert, + AlertIcon, + Collapse, +} from '@chakra-ui/react'; +import InvoiceTypeSelector from './InvoiceTypeSelector'; +import InvoiceTitleSelector from './InvoiceTitleSelector'; +import type { + InvoiceType, + InvoiceTitleType, + InvoiceTitleTemplate, + InvoiceableOrder, + CreateInvoiceRequest, +} from '@/types/invoice'; + +interface InvoiceApplyFormProps { + order: InvoiceableOrder; + onSubmit: (data: CreateInvoiceRequest) => Promise; + onCancel: () => void; + loading?: boolean; +} + +interface FormData { + invoiceType: InvoiceType; + titleType: InvoiceTitleType; + title: string; + taxNumber: string; + companyAddress: string; + companyPhone: string; + bankName: string; + bankAccount: string; + email: string; + phone: string; + mailingAddress: string; + recipientName: string; + recipientPhone: string; + remark: string; + saveTemplate: boolean; +} + +interface FormErrors { + title?: string; + taxNumber?: string; + email?: string; + mailingAddress?: string; + recipientName?: string; + recipientPhone?: string; +} + +const planNameMap: Record = { + pro: 'Pro 专业版', + max: 'Max 旗舰版', +}; + +const billingCycleMap: Record = { + monthly: '月付', + quarterly: '季付', + semiannual: '半年付', + yearly: '年付', +}; + +export default function InvoiceApplyForm({ + order, + onSubmit, + onCancel, + loading = false, +}: InvoiceApplyFormProps) { + const [formData, setFormData] = useState({ + invoiceType: 'electronic', + titleType: 'personal', + title: '', + taxNumber: '', + companyAddress: '', + companyPhone: '', + bankName: '', + bankAccount: '', + email: '', + phone: '', + mailingAddress: '', + recipientName: '', + recipientPhone: '', + remark: '', + saveTemplate: false, + }); + + const [errors, setErrors] = useState({}); + const [showNewTitleForm, setShowNewTitleForm] = useState(false); + + const borderColor = useColorModeValue('gray.200', 'gray.600'); + const bgCard = useColorModeValue('gray.50', 'gray.700'); + + // 更新表单字段 + const updateField = (field: K, value: FormData[K]) => { + setFormData((prev) => ({ ...prev, [field]: value })); + // 清除对应错误 + if (errors[field as keyof FormErrors]) { + setErrors((prev) => ({ ...prev, [field]: undefined })); + } + }; + + // 选择抬头模板 + const handleSelectTemplate = (template: InvoiceTitleTemplate | null) => { + if (template) { + setFormData((prev) => ({ + ...prev, + title: template.title, + titleType: template.titleType, + taxNumber: template.taxNumber || '', + companyAddress: template.companyAddress || '', + companyPhone: template.companyPhone || '', + bankName: template.bankName || '', + bankAccount: template.bankAccount || '', + })); + setShowNewTitleForm(false); + } + }; + + // 切换抬头类型时清空相关字段 + useEffect(() => { + if (formData.titleType === 'personal') { + setFormData((prev) => ({ + ...prev, + taxNumber: '', + companyAddress: '', + companyPhone: '', + bankName: '', + bankAccount: '', + })); + } + }, [formData.titleType]); + + // 表单验证 + const validate = (): boolean => { + const newErrors: FormErrors = {}; + + if (!formData.title.trim()) { + newErrors.title = '请输入发票抬头'; + } + + if (formData.titleType === 'company' && !formData.taxNumber.trim()) { + newErrors.taxNumber = '企业开票必须填写税号'; + } + + if (!formData.email.trim()) { + newErrors.email = '请输入接收邮箱'; + } else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(formData.email)) { + newErrors.email = '请输入有效的邮箱地址'; + } + + // 纸质发票需要邮寄信息 + if (formData.invoiceType === 'paper') { + if (!formData.mailingAddress.trim()) { + newErrors.mailingAddress = '请输入邮寄地址'; + } + if (!formData.recipientName.trim()) { + newErrors.recipientName = '请输入收件人姓名'; + } + if (!formData.recipientPhone.trim()) { + newErrors.recipientPhone = '请输入收件人电话'; + } + } + + setErrors(newErrors); + return Object.keys(newErrors).length === 0; + }; + + // 提交表单 + const handleSubmit = async () => { + if (!validate()) return; + + const request: CreateInvoiceRequest = { + orderId: order.id, + invoiceType: formData.invoiceType, + titleType: formData.titleType, + title: formData.title, + taxNumber: formData.taxNumber || undefined, + companyAddress: formData.companyAddress || undefined, + companyPhone: formData.companyPhone || undefined, + bankName: formData.bankName || undefined, + bankAccount: formData.bankAccount || undefined, + email: formData.email, + phone: formData.phone || undefined, + mailingAddress: formData.mailingAddress || undefined, + recipientName: formData.recipientName || undefined, + recipientPhone: formData.recipientPhone || undefined, + remark: formData.remark || undefined, + }; + + await onSubmit(request); + }; + + return ( + + {/* 订单信息 */} + + + 开票订单 + + + + + 订单号 + + {order.orderNo} + + + + 套餐 + + + {planNameMap[order.planName] || order.planName} ·{' '} + {billingCycleMap[order.billingCycle] || order.billingCycle} + + + + + 开票金额 + + + ¥{order.amount.toFixed(2)} + + + + + + + + {/* 发票类型 */} + + + 发票类型 + + updateField('invoiceType', type)} + /> + + + + + {/* 抬头类型 */} + + + 抬头类型 + + updateField('titleType', value)} + > + + 个人 + 企业 + + + + + {/* 发票抬头选择 */} + + + 发票抬头 + + setShowNewTitleForm(true)} + /> + + + {/* 新抬头表单 */} + + + + 发票抬头 + updateField('title', e.target.value)} + /> + {errors.title} + + + {formData.titleType === 'company' && ( + <> + + 税号 + updateField('taxNumber', e.target.value)} + /> + {errors.taxNumber} + + + + + 以下为选填信息,可用于开具增值税专用发票 + + + + 公司地址 + updateField('companyAddress', e.target.value)} + /> + + + + 公司电话 + updateField('companyPhone', e.target.value)} + /> + + + + + 开户银行 + updateField('bankName', e.target.value)} + /> + + + 银行账号 + updateField('bankAccount', e.target.value)} + /> + + + + )} + + updateField('saveTemplate', e.target.checked)} + > + 保存为常用抬头 + + + + + + + {/* 接收信息 */} + + + 接收邮箱 + + updateField('email', e.target.value)} + /> + {errors.email} + + + + 联系电话(选填) + updateField('phone', e.target.value)} + /> + + + {/* 纸质发票邮寄信息 */} + + + + + 邮寄信息 + + + + 邮寄地址 +