This commit is contained in:
2026-08-10 16:11:04 +08:00
parent a699c9bab8
commit e364c46ce4
3 changed files with 153 additions and 134 deletions
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -27,7 +27,7 @@
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin /> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;500;600;700&display=swap" rel="stylesheet" /> <link href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;500;600;700&display=swap" rel="stylesheet" />
<title>民众智创</title> <title>民众智创</title>
<script type="module" crossorigin src="/assets/index-B6hD5A9D.js"></script> <script type="module" crossorigin src="/assets/index-Chp4mfc5.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-DtYH0-uL.css"> <link rel="stylesheet" crossorigin href="/assets/index-DtYH0-uL.css">
</head> </head>
<body> <body>
+68 -49
View File
@@ -1,7 +1,8 @@
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { Table, Tag, Empty, Spin, Pagination, Button, Typography, Modal, Form, Input, Radio, message, Space } from 'antd'; import { Table, Tag, Empty, Spin, Pagination, Button, Typography, Modal, Form, Input, Radio, message, Space, DatePicker } from 'antd';
import { FileTextOutlined, PlusOutlined, CloseOutlined, AlipayCircleOutlined, WechatOutlined } from '@ant-design/icons'; import { FileTextOutlined, PlusOutlined, CloseOutlined, AlipayCircleOutlined, WechatOutlined } from '@ant-design/icons';
import { getPaymentOrders, createInvoice, getInvoices, getInvoiceDetail } from '../api'; import { getPaymentOrders, createInvoice, getInvoices, getInvoiceDetail } from '../api';
import dayjs from 'dayjs';
interface InvoiceRecord { interface InvoiceRecord {
id: string; id: string;
@@ -62,7 +63,14 @@ const InvoicePage: React.FC = () => {
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 [occupiedOrderIds, setOccupiedOrderIds] = useState<Set<string>>(new Set());
const [orderDateFilter, setOrderDateFilter] = useState<[string | null, string | null]>([null, null]); // 默认日期范围:当前月份
const [orderDateFilter, setOrderDateFilter] = useState<[string | null, string | null]>(() => {
const now = new Date();
const start = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-01`;
const lastDay = new Date(now.getFullYear(), now.getMonth() + 1, 0).getDate();
const end = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(lastDay).padStart(2, '0')}`;
return [start, end];
});
// 说明弹窗 // 说明弹窗
const [noticeModalOpen, setNoticeModalOpen] = useState(false); const [noticeModalOpen, setNoticeModalOpen] = useState(false);
@@ -82,8 +90,8 @@ const InvoicePage: React.FC = () => {
return `FP${year}${month}${day}${rand}`; return `FP${year}${month}${day}${rand}`;
}; };
// 加载已开票订单ID列表 // 加载已开票订单ID列表,返回 occupied Set
const loadOccupiedOrderIds = async () => { const loadOccupiedOrderIds = async (): Promise<Set<string>> => {
try { try {
const data = await getInvoices({ page: 1, pageSize: 100 }); const data = await getInvoices({ page: 1, pageSize: 100 });
const ids = new Set<string>(); const ids = new Set<string>();
@@ -95,13 +103,14 @@ const InvoicePage: React.FC = () => {
} }
}); });
setOccupiedOrderIds(ids); setOccupiedOrderIds(ids);
return ids;
} catch { } catch {
// 忽略 return occupiedOrderIds;
} }
}; };
// 加载订单数据(显示所有已支付订单,已开票的标记 _occupied) // 加载订单数据(显示所有已支付订单,已开票的标记 _occupied)
const loadOrderData = async (page: number = orderPage) => { const loadOrderData = async (page: number = orderPage, occupiedIds?: Set<string>) => {
setOrderLoading(true); setOrderLoading(true);
try { try {
const data = await getPaymentOrders(page, orderPageSize); const data = await getPaymentOrders(page, orderPageSize);
@@ -121,9 +130,10 @@ const InvoicePage: React.FC = () => {
}); });
} }
// 标记已开票订单 // 标记已开票订单
const occIds = occupiedIds || occupiedOrderIds;
items = items.map((o: any) => ({ items = items.map((o: any) => ({
...o, ...o,
_occupied: occupiedOrderIds.has(o.id || o.order_no || o.orderNo), _occupied: occIds.has(o.id || o.order_no || o.orderNo),
})); }));
setOrderData(items); setOrderData(items);
setOrderTotal(items.length); setOrderTotal(items.length);
@@ -162,12 +172,16 @@ const InvoicePage: React.FC = () => {
useEffect(() => { useEffect(() => {
loadInvoices(); loadInvoices();
loadOccupiedOrderIds();
}, []); }, []);
useEffect(() => { useEffect(() => {
if (issueModalOpen) { if (issueModalOpen) {
loadOrderData(1); // 先刷新已开票订单列表,再加载订单数据
loadOccupiedOrderIds().then((ids) => {
setOrderPage(1); setOrderPage(1);
loadOrderData(1, ids);
});
} }
}, [issueModalOpen]); }, [issueModalOpen]);
@@ -421,15 +435,16 @@ const InvoicePage: React.FC = () => {
const handleAddHeader = async () => { const handleAddHeader = async () => {
try { try {
const values = await headerForm.validateFields(); const values = await headerForm.validateFields();
const isCompany = values.type === 'company';
const newHeader: InvoiceHeader = { const newHeader: InvoiceHeader = {
id: Date.now().toString(), id: Date.now().toString(),
name: values.name, name: values.name,
type: values.type, type: values.type,
taxNo: values.taxNo, taxNo: isCompany ? values.taxNo : undefined,
registerAddress: values.registerAddress, registerAddress: isCompany ? values.registerAddress : undefined,
bankName: values.bankName, bankName: isCompany ? values.bankName : undefined,
registerPhone: values.registerPhone, registerPhone: isCompany ? values.registerPhone : undefined,
bankAccount: values.bankAccount, bankAccount: isCompany ? values.bankAccount : undefined,
email: values.email, 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)),
}; };
@@ -821,44 +836,27 @@ const InvoicePage: React.FC = () => {
{/* 日期筛选 */} {/* 日期筛选 */}
<div style={{ display: 'flex', alignItems: 'center', gap: 12, padding: '12px 0', borderBottom: '1px solid #f1f5f9', marginBottom: 12 }}> <div style={{ display: 'flex', alignItems: 'center', gap: 12, padding: '12px 0', borderBottom: '1px solid #f1f5f9', marginBottom: 12 }}>
<span style={{ color: '#94a3b8', fontSize: 13 }}></span> <span style={{ color: '#94a3b8', fontSize: 13 }}></span>
<Input <DatePicker.RangePicker
type="date"
size="small" size="small"
value={orderDateFilter[0] || ''} value={orderDateFilter[0] ? [dayjs(orderDateFilter[0]), orderDateFilter[1] ? dayjs(orderDateFilter[1]) : null] : [null, null]}
onChange={(e) => { onChange={(dates) => {
const val = e.target.value || null; if (dates && dates[0] && dates[1]) {
setOrderDateFilter([val, orderDateFilter[1]]); const start = dates[0].format('YYYY-MM-DD');
const end = dates[1].format('YYYY-MM-DD');
setOrderDateFilter([start, end]);
} else {
// 清除时恢复默认当前月份
const now = new Date();
const start = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-01`;
const lastDay = new Date(now.getFullYear(), now.getMonth() + 1, 0).getDate();
const end = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(lastDay).padStart(2, '0')}`;
setOrderDateFilter([start, end]);
}
setOrderPage(1); setOrderPage(1);
setTimeout(() => loadOrderData(1), 0); setTimeout(() => loadOrderData(1), 0);
}} }}
style={{ width: 150 }} style={{ width: 280 }}
/> />
<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> </div>
<Spin spinning={orderLoading}> <Spin spinning={orderLoading}>
{orderData.length === 0 ? ( {orderData.length === 0 ? (
@@ -1087,15 +1085,23 @@ const InvoicePage: React.FC = () => {
</Radio.Group> </Radio.Group>
</Form.Item> </Form.Item>
{/* 监听抬头类型,用于条件渲染 */}
<Form.Item noStyle shouldUpdate={(prev, cur) => prev.type !== cur.type}>
{() => {
const type = headerForm.getFieldValue('type');
const isCompany = type === 'company';
return (
<>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '0 16px' }}> <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '0 16px' }}>
<Form.Item <Form.Item
name="name" name="name"
label="公司名称:" label={isCompany ? '公司名称:' : '个人姓名:'}
rules={[{ required: true, message: '请输入公司名称' }]} rules={[{ required: true, message: isCompany ? '请输入公司名称' : '请输入个人姓名' }]}
> >
<Input placeholder="请输入公司名称" /> <Input placeholder={isCompany ? '请输入公司名称' : '请输入个人姓名'} />
</Form.Item> </Form.Item>
{isCompany && (
<Form.Item <Form.Item
name="taxNo" name="taxNo"
label="公司税号:" label="公司税号:"
@@ -1103,34 +1109,43 @@ const InvoicePage: React.FC = () => {
> >
<Input placeholder="请输入公司税号" /> <Input placeholder="请输入公司税号" />
</Form.Item> </Form.Item>
)}
{isCompany && (
<Form.Item <Form.Item
name="registerAddress" name="registerAddress"
label="注册地址:" label="注册地址:"
> >
<Input placeholder="请输入注册地址" /> <Input placeholder="请输入注册地址" />
</Form.Item> </Form.Item>
)}
{isCompany && (
<Form.Item <Form.Item
name="bankName" name="bankName"
label="开户银行:" label="开户银行:"
> >
<Input placeholder="请输入开户银行" /> <Input placeholder="请输入开户银行" />
</Form.Item> </Form.Item>
)}
{isCompany && (
<Form.Item <Form.Item
name="registerPhone" name="registerPhone"
label="注册电话:" label="注册电话:"
> >
<Input placeholder="请输入注册电话" /> <Input placeholder="请输入注册电话" />
</Form.Item> </Form.Item>
)}
{isCompany && (
<Form.Item <Form.Item
name="bankAccount" name="bankAccount"
label="银行账号:" label="银行账号:"
> >
<Input placeholder="请输入银行账号" /> <Input placeholder="请输入银行账号" />
</Form.Item> </Form.Item>
)}
<Form.Item <Form.Item
name="email" name="email"
@@ -1143,6 +1158,10 @@ const InvoicePage: React.FC = () => {
<Input placeholder="发票将发送至该邮箱" /> <Input placeholder="发票将发送至该邮箱" />
</Form.Item> </Form.Item>
</div> </div>
</>
);
}}
</Form.Item>
<Form.Item <Form.Item
name="isDefault" name="isDefault"