1
This commit is contained in:
@@ -28,6 +28,7 @@ interface InvoiceHeader {
|
|||||||
bankName?: string;
|
bankName?: string;
|
||||||
registerPhone?: string;
|
registerPhone?: string;
|
||||||
bankAccount?: string;
|
bankAccount?: string;
|
||||||
|
email?: string;
|
||||||
isDefault: boolean;
|
isDefault: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,6 +61,8 @@ const InvoicePage: React.FC = () => {
|
|||||||
const [orderTotal, setOrderTotal] = useState(0);
|
const [orderTotal, setOrderTotal] = useState(0);
|
||||||
const [orderPage, setOrderPage] = useState(1);
|
const [orderPage, setOrderPage] = useState(1);
|
||||||
const [orderPageSize] = useState(10);
|
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);
|
const [noticeModalOpen, setNoticeModalOpen] = useState(false);
|
||||||
@@ -79,14 +82,51 @@ const InvoicePage: React.FC = () => {
|
|||||||
return `FP${year}${month}${day}${rand}`;
|
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) => {
|
const loadOrderData = async (page: number = orderPage) => {
|
||||||
setOrderLoading(true);
|
setOrderLoading(true);
|
||||||
try {
|
try {
|
||||||
const data = await getPaymentOrders(page, orderPageSize);
|
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);
|
setOrderData(items);
|
||||||
setOrderTotal(data.total || 0);
|
setOrderTotal(items.length);
|
||||||
} catch {
|
} catch {
|
||||||
setOrderData([]);
|
setOrderData([]);
|
||||||
}
|
}
|
||||||
@@ -265,8 +305,12 @@ const InvoicePage: React.FC = () => {
|
|||||||
{
|
{
|
||||||
title: '状态',
|
title: '状态',
|
||||||
key: 'status',
|
key: 'status',
|
||||||
width: 100,
|
width: 120,
|
||||||
render: (_: any, record: any) => {
|
render: (_: any, record: any) => {
|
||||||
|
// 已开票标签
|
||||||
|
if (record._occupied) {
|
||||||
|
return <Tag color="orange" style={{ fontWeight: 500, padding: '4px 12px' }}>已开票</Tag>;
|
||||||
|
}
|
||||||
const status = record.status || 'pending';
|
const status = record.status || 'pending';
|
||||||
const statusConfig: Record<string, { color: string; label: string; bg: string }> = {
|
const statusConfig: Record<string, { color: string; label: string; bg: string }> = {
|
||||||
pending: { color: '#f59e0b', label: '待支付', bg: 'rgba(245,158,11,0.1)' },
|
pending: { color: '#f59e0b', label: '待支付', bg: 'rgba(245,158,11,0.1)' },
|
||||||
@@ -386,6 +430,7 @@ const InvoicePage: React.FC = () => {
|
|||||||
bankName: values.bankName,
|
bankName: values.bankName,
|
||||||
registerPhone: values.registerPhone,
|
registerPhone: values.registerPhone,
|
||||||
bankAccount: values.bankAccount,
|
bankAccount: values.bankAccount,
|
||||||
|
email: values.email,
|
||||||
isDefault: values.isDefault === 'yes' || (headers.length === 0 && !allHeadersForSelect.some(h => h.isDefault)),
|
isDefault: values.isDefault === 'yes' || (headers.length === 0 && !allHeadersForSelect.some(h => h.isDefault)),
|
||||||
};
|
};
|
||||||
if (newHeader.isDefault) {
|
if (newHeader.isDefault) {
|
||||||
@@ -438,12 +483,14 @@ const InvoicePage: React.FC = () => {
|
|||||||
message.warning('请选择一个发票抬头');
|
message.warning('请选择一个发票抬头');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!email.trim()) {
|
// 优先使用弹窗中填写的邮箱,否则使用抬头中保存的邮箱
|
||||||
|
const finalEmail = email.trim() || selectedHeader?.email || '';
|
||||||
|
if (!finalEmail) {
|
||||||
message.warning('请输入电子邮箱');
|
message.warning('请输入电子邮箱');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const emailRegex = /^[\w.\-]+@[\w.\-]+\.\w+$/;
|
const emailRegex = /^[\w.\-]+@[\w.\-]+\.\w+$/;
|
||||||
if (!emailRegex.test(email.trim())) {
|
if (!emailRegex.test(finalEmail)) {
|
||||||
message.warning('请输入正确的邮箱格式');
|
message.warning('请输入正确的邮箱格式');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -461,7 +508,7 @@ const InvoicePage: React.FC = () => {
|
|||||||
headerRegisterPhone: selectedHeader?.registerPhone,
|
headerRegisterPhone: selectedHeader?.registerPhone,
|
||||||
headerBankName: selectedHeader?.bankName,
|
headerBankName: selectedHeader?.bankName,
|
||||||
headerBankAccount: selectedHeader?.bankAccount,
|
headerBankAccount: selectedHeader?.bankAccount,
|
||||||
email: email.trim(),
|
email: finalEmail,
|
||||||
orderIds,
|
orderIds,
|
||||||
});
|
});
|
||||||
message.success('开票申请已提交,请等待审核');
|
message.success('开票申请已提交,请等待审核');
|
||||||
@@ -485,10 +532,10 @@ const InvoicePage: React.FC = () => {
|
|||||||
const currentPageKeys = orderData.map((o: any) => o.order_no || o.orderNo || o.id);
|
const currentPageKeys = orderData.map((o: any) => o.order_no || o.orderNo || o.id);
|
||||||
// First remove all keys from current page
|
// First remove all keys from current page
|
||||||
currentPageKeys.forEach(key => newMap.delete(key));
|
currentPageKeys.forEach(key => newMap.delete(key));
|
||||||
// Then add back the selected ones
|
// Then add back the selected ones(排除已开票订单)
|
||||||
keys.forEach(key => {
|
keys.forEach(key => {
|
||||||
const row = selectedRows.find((r: any) => (r.order_no || r.orderNo || r.id) === 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);
|
newMap.set(key as string, row);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -497,6 +544,8 @@ const InvoicePage: React.FC = () => {
|
|||||||
preserveSelectedRowKeys: true,
|
preserveSelectedRowKeys: true,
|
||||||
getCheckboxProps: (record: any) => ({
|
getCheckboxProps: (record: any) => ({
|
||||||
name: record.order_no || record.orderNo || record.id,
|
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' }} />}
|
closeIcon={<CloseOutlined style={{ fontSize: 16, color: '#94a3b8' }} />}
|
||||||
>
|
>
|
||||||
<div style={{ padding: '0 24px 24px' }}>
|
<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}>
|
<Spin spinning={orderLoading}>
|
||||||
{orderData.length === 0 ? (
|
{orderData.length === 0 ? (
|
||||||
<Empty description={<span style={{ color: '#94a3b8' }}>暂无订单记录</span>} />
|
<Empty description={<span style={{ color: '#94a3b8' }}>暂无符合条件的订单</span>} />
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<Table
|
<Table
|
||||||
@@ -842,6 +933,15 @@ const InvoicePage: React.FC = () => {
|
|||||||
style={{ maxWidth: 360 }}
|
style={{ maxWidth: 360 }}
|
||||||
allowClear
|
allowClear
|
||||||
/>
|
/>
|
||||||
|
{selectedHeader?.email && !email && (
|
||||||
|
<Button
|
||||||
|
type="link"
|
||||||
|
size="small"
|
||||||
|
onClick={() => setEmail(selectedHeader.email || '')}
|
||||||
|
>
|
||||||
|
使用抬头邮箱({selectedHeader.email})
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Table
|
<Table
|
||||||
@@ -1026,6 +1126,17 @@ const InvoicePage: React.FC = () => {
|
|||||||
>
|
>
|
||||||
<Input placeholder="请输入银行账号" />
|
<Input placeholder="请输入银行账号" />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item
|
||||||
|
name="email"
|
||||||
|
label="接收邮箱:"
|
||||||
|
rules={[
|
||||||
|
{ required: true, message: '请输入接收邮箱' },
|
||||||
|
{ pattern: /^[\w.\-]+@[\w.\-]+\.\w+$/, message: '请输入正确的邮箱格式' },
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<Input placeholder="发票将发送至该邮箱" />
|
||||||
|
</Form.Item>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Form.Item
|
<Form.Item
|
||||||
|
|||||||
Reference in New Issue
Block a user