1
This commit is contained in:
@@ -28,6 +28,7 @@ interface InvoiceHeader {
|
||||
bankName?: string;
|
||||
registerPhone?: string;
|
||||
bankAccount?: string;
|
||||
email?: string;
|
||||
isDefault: boolean;
|
||||
}
|
||||
|
||||
@@ -60,6 +61,8 @@ const InvoicePage: React.FC = () => {
|
||||
const [orderTotal, setOrderTotal] = useState(0);
|
||||
const [orderPage, setOrderPage] = useState(1);
|
||||
const [orderPageSize] = useState(10);
|
||||
const [occupiedOrderIds, setOccupiedOrderIds] = useState<Set<string>>(new Set());
|
||||
const [orderDateFilter, setOrderDateFilter] = useState<[string | null, string | null]>([null, null]);
|
||||
|
||||
// 说明弹窗
|
||||
const [noticeModalOpen, setNoticeModalOpen] = useState(false);
|
||||
@@ -79,14 +82,51 @@ const InvoicePage: React.FC = () => {
|
||||
return `FP${year}${month}${day}${rand}`;
|
||||
};
|
||||
|
||||
// 加载订单数据
|
||||
// 加载已开票订单ID列表
|
||||
const loadOccupiedOrderIds = async () => {
|
||||
try {
|
||||
const data = await getInvoices({ page: 1, pageSize: 100 });
|
||||
const ids = new Set<string>();
|
||||
(data.items || []).forEach((inv: any) => {
|
||||
if (inv.status === 'processing' || inv.status === 'success') {
|
||||
(inv.orders || []).forEach((o: any) => {
|
||||
ids.add(o.orderId || o.order_id || o.orderNo || o.order_no);
|
||||
});
|
||||
}
|
||||
});
|
||||
setOccupiedOrderIds(ids);
|
||||
} catch {
|
||||
// 忽略
|
||||
}
|
||||
};
|
||||
|
||||
// 加载订单数据(显示所有已支付订单,已开票的标记 _occupied)
|
||||
const loadOrderData = async (page: number = orderPage) => {
|
||||
setOrderLoading(true);
|
||||
try {
|
||||
const data = await getPaymentOrders(page, orderPageSize);
|
||||
const items = data.items || [];
|
||||
// 只显示已支付订单
|
||||
let items = (data.items || []).filter((o: any) => (o.status || o.payment_status) === 'paid');
|
||||
// 日期过滤
|
||||
if (orderDateFilter[0]) {
|
||||
items = items.filter((o: any) => {
|
||||
const created = o.created_at || o.createdAt;
|
||||
return created && created >= orderDateFilter[0];
|
||||
});
|
||||
}
|
||||
if (orderDateFilter[1]) {
|
||||
items = items.filter((o: any) => {
|
||||
const created = o.created_at || o.createdAt;
|
||||
return created && created <= orderDateFilter[1] + 'T23:59:59';
|
||||
});
|
||||
}
|
||||
// 标记已开票订单
|
||||
items = items.map((o: any) => ({
|
||||
...o,
|
||||
_occupied: occupiedOrderIds.has(o.id || o.order_no || o.orderNo),
|
||||
}));
|
||||
setOrderData(items);
|
||||
setOrderTotal(data.total || 0);
|
||||
setOrderTotal(items.length);
|
||||
} catch {
|
||||
setOrderData([]);
|
||||
}
|
||||
@@ -265,8 +305,12 @@ const InvoicePage: React.FC = () => {
|
||||
{
|
||||
title: '状态',
|
||||
key: 'status',
|
||||
width: 100,
|
||||
width: 120,
|
||||
render: (_: any, record: any) => {
|
||||
// 已开票标签
|
||||
if (record._occupied) {
|
||||
return <Tag color="orange" style={{ fontWeight: 500, padding: '4px 12px' }}>已开票</Tag>;
|
||||
}
|
||||
const status = record.status || 'pending';
|
||||
const statusConfig: Record<string, { color: string; label: string; bg: string }> = {
|
||||
pending: { color: '#f59e0b', label: '待支付', bg: 'rgba(245,158,11,0.1)' },
|
||||
@@ -386,6 +430,7 @@ const InvoicePage: React.FC = () => {
|
||||
bankName: values.bankName,
|
||||
registerPhone: values.registerPhone,
|
||||
bankAccount: values.bankAccount,
|
||||
email: values.email,
|
||||
isDefault: values.isDefault === 'yes' || (headers.length === 0 && !allHeadersForSelect.some(h => h.isDefault)),
|
||||
};
|
||||
if (newHeader.isDefault) {
|
||||
@@ -438,12 +483,14 @@ const InvoicePage: React.FC = () => {
|
||||
message.warning('请选择一个发票抬头');
|
||||
return;
|
||||
}
|
||||
if (!email.trim()) {
|
||||
// 优先使用弹窗中填写的邮箱,否则使用抬头中保存的邮箱
|
||||
const finalEmail = email.trim() || selectedHeader?.email || '';
|
||||
if (!finalEmail) {
|
||||
message.warning('请输入电子邮箱');
|
||||
return;
|
||||
}
|
||||
const emailRegex = /^[\w.\-]+@[\w.\-]+\.\w+$/;
|
||||
if (!emailRegex.test(email.trim())) {
|
||||
if (!emailRegex.test(finalEmail)) {
|
||||
message.warning('请输入正确的邮箱格式');
|
||||
return;
|
||||
}
|
||||
@@ -461,7 +508,7 @@ const InvoicePage: React.FC = () => {
|
||||
headerRegisterPhone: selectedHeader?.registerPhone,
|
||||
headerBankName: selectedHeader?.bankName,
|
||||
headerBankAccount: selectedHeader?.bankAccount,
|
||||
email: email.trim(),
|
||||
email: finalEmail,
|
||||
orderIds,
|
||||
});
|
||||
message.success('开票申请已提交,请等待审核');
|
||||
@@ -485,10 +532,10 @@ const InvoicePage: React.FC = () => {
|
||||
const currentPageKeys = orderData.map((o: any) => o.order_no || o.orderNo || o.id);
|
||||
// First remove all keys from current page
|
||||
currentPageKeys.forEach(key => newMap.delete(key));
|
||||
// Then add back the selected ones
|
||||
// Then add back the selected ones(排除已开票订单)
|
||||
keys.forEach(key => {
|
||||
const row = selectedRows.find((r: any) => (r.order_no || r.orderNo || r.id) === key);
|
||||
if (row) {
|
||||
if (row && !row._occupied) {
|
||||
newMap.set(key as string, row);
|
||||
}
|
||||
});
|
||||
@@ -497,6 +544,8 @@ const InvoicePage: React.FC = () => {
|
||||
preserveSelectedRowKeys: true,
|
||||
getCheckboxProps: (record: any) => ({
|
||||
name: record.order_no || record.orderNo || record.id,
|
||||
// 已开票订单不可选
|
||||
disabled: !!record._occupied,
|
||||
}),
|
||||
};
|
||||
|
||||
@@ -769,9 +818,51 @@ const InvoicePage: React.FC = () => {
|
||||
closeIcon={<CloseOutlined style={{ fontSize: 16, color: '#94a3b8' }} />}
|
||||
>
|
||||
<div style={{ padding: '0 24px 24px' }}>
|
||||
{/* 日期筛选 */}
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 12, padding: '12px 0', borderBottom: '1px solid #f1f5f9', marginBottom: 12 }}>
|
||||
<span style={{ color: '#94a3b8', fontSize: 13 }}>支付时间:</span>
|
||||
<Input
|
||||
type="date"
|
||||
size="small"
|
||||
value={orderDateFilter[0] || ''}
|
||||
onChange={(e) => {
|
||||
const val = e.target.value || null;
|
||||
setOrderDateFilter([val, orderDateFilter[1]]);
|
||||
setOrderPage(1);
|
||||
setTimeout(() => loadOrderData(1), 0);
|
||||
}}
|
||||
style={{ width: 150 }}
|
||||
/>
|
||||
<span style={{ color: '#94a3b8' }}>至</span>
|
||||
<Input
|
||||
type="date"
|
||||
size="small"
|
||||
value={orderDateFilter[1] || ''}
|
||||
onChange={(e) => {
|
||||
const val = e.target.value || null;
|
||||
setOrderDateFilter([orderDateFilter[0], val]);
|
||||
setOrderPage(1);
|
||||
setTimeout(() => loadOrderData(1), 0);
|
||||
}}
|
||||
style={{ width: 150 }}
|
||||
/>
|
||||
{(orderDateFilter[0] || orderDateFilter[1]) && (
|
||||
<Button
|
||||
type="link"
|
||||
size="small"
|
||||
onClick={() => {
|
||||
setOrderDateFilter([null, null]);
|
||||
setOrderPage(1);
|
||||
loadOrderData(1);
|
||||
}}
|
||||
>
|
||||
清除
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
<Spin spinning={orderLoading}>
|
||||
{orderData.length === 0 ? (
|
||||
<Empty description={<span style={{ color: '#94a3b8' }}>暂无订单记录</span>} />
|
||||
<Empty description={<span style={{ color: '#94a3b8' }}>暂无符合条件的订单</span>} />
|
||||
) : (
|
||||
<>
|
||||
<Table
|
||||
@@ -842,6 +933,15 @@ const InvoicePage: React.FC = () => {
|
||||
style={{ maxWidth: 360 }}
|
||||
allowClear
|
||||
/>
|
||||
{selectedHeader?.email && !email && (
|
||||
<Button
|
||||
type="link"
|
||||
size="small"
|
||||
onClick={() => setEmail(selectedHeader.email || '')}
|
||||
>
|
||||
使用抬头邮箱({selectedHeader.email})
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<Table
|
||||
@@ -1026,6 +1126,17 @@ const InvoicePage: React.FC = () => {
|
||||
>
|
||||
<Input placeholder="请输入银行账号" />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name="email"
|
||||
label="接收邮箱:"
|
||||
rules={[
|
||||
{ required: true, message: '请输入接收邮箱' },
|
||||
{ pattern: /^[\w.\-]+@[\w.\-]+\.\w+$/, message: '请输入正确的邮箱格式' },
|
||||
]}
|
||||
>
|
||||
<Input placeholder="发票将发送至该邮箱" />
|
||||
</Form.Item>
|
||||
</div>
|
||||
|
||||
<Form.Item
|
||||
|
||||
Reference in New Issue
Block a user