1
This commit is contained in:
@@ -497,6 +497,66 @@ export async function getInvoiceDetail(id: string): Promise<any> {
|
||||
return api.get(`/invoices/${id}`);
|
||||
}
|
||||
|
||||
// ── Invoice Headers ──────────────────────────────────────
|
||||
|
||||
export async function getInvoiceHeaders(): Promise<{ items: any[] }> {
|
||||
return api.get('/invoice-headers');
|
||||
}
|
||||
|
||||
export async function createInvoiceHeader(data: {
|
||||
type: 'personal' | 'company';
|
||||
name: string;
|
||||
taxNo?: string;
|
||||
registerAddress?: string;
|
||||
registerPhone?: string;
|
||||
bankName?: string;
|
||||
bankAccount?: string;
|
||||
email?: string;
|
||||
isDefault?: boolean;
|
||||
}): Promise<any> {
|
||||
return api.post('/invoice-headers', {
|
||||
type: data.type,
|
||||
name: data.name,
|
||||
tax_no: data.taxNo,
|
||||
register_address: data.registerAddress,
|
||||
register_phone: data.registerPhone,
|
||||
bank_name: data.bankName,
|
||||
bank_account: data.bankAccount,
|
||||
email: data.email,
|
||||
is_default: data.isDefault ?? false,
|
||||
});
|
||||
}
|
||||
|
||||
export async function updateInvoiceHeader(id: string, data: {
|
||||
name?: string;
|
||||
taxNo?: string;
|
||||
registerAddress?: string;
|
||||
registerPhone?: string;
|
||||
bankName?: string;
|
||||
bankAccount?: string;
|
||||
email?: string;
|
||||
isDefault?: boolean;
|
||||
}): Promise<any> {
|
||||
return api.put(`/invoice-headers/${id}`, {
|
||||
name: data.name,
|
||||
tax_no: data.taxNo,
|
||||
register_address: data.registerAddress,
|
||||
register_phone: data.registerPhone,
|
||||
bank_name: data.bankName,
|
||||
bank_account: data.bankAccount,
|
||||
email: data.email,
|
||||
is_default: data.isDefault,
|
||||
});
|
||||
}
|
||||
|
||||
export async function deleteInvoiceHeader(id: string): Promise<void> {
|
||||
await api.delete(`/invoice-headers/${id}`);
|
||||
}
|
||||
|
||||
export async function setDefaultInvoiceHeader(id: string): Promise<any> {
|
||||
return api.put(`/invoice-headers/${id}/set-default`);
|
||||
}
|
||||
|
||||
export async function getCreditRatios(): Promise<any[]> {
|
||||
return api.get('/credits/ratios');
|
||||
}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
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 { getPaymentOrders, createInvoice, getInvoices, getInvoiceDetail } from '../api';
|
||||
import dayjs from 'dayjs';
|
||||
import {
|
||||
getPaymentOrders, createInvoice, getInvoices, getInvoiceDetail,
|
||||
getInvoiceHeaders, createInvoiceHeader, updateInvoiceHeader, deleteInvoiceHeader, setDefaultInvoiceHeader,
|
||||
} from '../api';
|
||||
|
||||
interface InvoiceRecord {
|
||||
id: string;
|
||||
@@ -33,13 +35,6 @@ interface InvoiceHeader {
|
||||
isDefault: boolean;
|
||||
}
|
||||
|
||||
const mockHeaders: InvoiceHeader[] = [
|
||||
{ id: 'h1', name: '北京云图科技有限公司', type: 'company', taxNo: '91110108MA01XXXXX1', registerAddress: '北京市海淀区中关村大街1号', bankName: '中国工商银行北京海淀支行', registerPhone: '010-88888888', bankAccount: '0200 0000 0000 0001', isDefault: true },
|
||||
{ id: 'h2', name: '上海智能图像有限公司', type: 'company', taxNo: '91310115MA1KXXXXX2', registerAddress: '上海市浦东新区张江高科技园区', bankName: '招商银行上海张江支行', registerPhone: '021-66666666', bankAccount: '1212 0000 0000 0002', isDefault: false },
|
||||
{ id: 'h3', name: '深圳创新科技有限公司', type: 'company', taxNo: '91440300MA5XXXXX3', registerAddress: '深圳市南山区科技园', bankName: '建设银行深圳南山支行', registerPhone: '0755-99999999', bankAccount: '4400 0000 0000 0003', isDefault: false },
|
||||
{ id: 'h4', name: '张三', type: 'personal', isDefault: false },
|
||||
];
|
||||
|
||||
const InvoicePage: React.FC = () => {
|
||||
const [records, setRecords] = useState<InvoiceRecord[]>([]);
|
||||
const [recordsLoading, setRecordsLoading] = useState(false);
|
||||
@@ -63,14 +58,9 @@ const InvoicePage: React.FC = () => {
|
||||
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]>(() => {
|
||||
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 [orderDateFilter, setOrderDateFilter] = useState<[string | null, string | null]>([null, null]);
|
||||
const [orderDateRange, setOrderDateRange] = useState<any>([null, null]);
|
||||
|
||||
// 说明弹窗
|
||||
const [noticeModalOpen, setNoticeModalOpen] = useState(false);
|
||||
@@ -173,6 +163,7 @@ const InvoicePage: React.FC = () => {
|
||||
useEffect(() => {
|
||||
loadInvoices();
|
||||
loadOccupiedOrderIds();
|
||||
loadHeaders();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -418,46 +409,57 @@ const InvoicePage: React.FC = () => {
|
||||
},
|
||||
];
|
||||
|
||||
// 选择弹窗中的抬头列表(合并 mockHeaders 和用户添加的)
|
||||
const allHeadersForSelect = React.useMemo(() => {
|
||||
const merged = [...mockHeaders];
|
||||
headers.forEach(h => {
|
||||
const idx = merged.findIndex(m => m.id === h.id);
|
||||
if (idx >= 0) {
|
||||
merged[idx] = h;
|
||||
} else {
|
||||
merged.push(h);
|
||||
}
|
||||
});
|
||||
return merged;
|
||||
}, [headers]);
|
||||
// 选择弹窗中的抬头列表
|
||||
const allHeadersForSelect = headers;
|
||||
|
||||
// 加载发票抬头列表
|
||||
const loadHeaders = async () => {
|
||||
try {
|
||||
const data = await getInvoiceHeaders();
|
||||
const items: InvoiceHeader[] = (data.items || []).map((h: any) => ({
|
||||
id: h.id,
|
||||
name: h.name,
|
||||
type: h.type,
|
||||
taxNo: h.taxNo,
|
||||
registerAddress: h.registerAddress,
|
||||
bankName: h.bankName,
|
||||
registerPhone: h.registerPhone,
|
||||
bankAccount: h.bankAccount,
|
||||
email: h.email,
|
||||
isDefault: h.isDefault,
|
||||
}));
|
||||
setHeaders(items);
|
||||
} catch {
|
||||
// 静默失败
|
||||
}
|
||||
};
|
||||
|
||||
const handleAddHeader = async () => {
|
||||
try {
|
||||
const values = await headerForm.validateFields();
|
||||
const isCompany = values.type === 'company';
|
||||
const newHeader: InvoiceHeader = {
|
||||
id: Date.now().toString(),
|
||||
name: values.name,
|
||||
const isDefault = values.isDefault === 'yes' || (headers.length === 0 && !headers.some(h => h.isDefault));
|
||||
await createInvoiceHeader({
|
||||
type: values.type,
|
||||
name: values.name,
|
||||
taxNo: isCompany ? values.taxNo : undefined,
|
||||
registerAddress: isCompany ? values.registerAddress : undefined,
|
||||
bankName: isCompany ? values.bankName : undefined,
|
||||
registerPhone: isCompany ? values.registerPhone : undefined,
|
||||
bankAccount: isCompany ? values.bankAccount : undefined,
|
||||
email: values.email,
|
||||
isDefault: values.isDefault === 'yes' || (headers.length === 0 && !allHeadersForSelect.some(h => h.isDefault)),
|
||||
};
|
||||
if (newHeader.isDefault) {
|
||||
setHeaders(prev => prev.map(h => ({ ...h, isDefault: false })));
|
||||
}
|
||||
setHeaders(prev => [...prev, newHeader]);
|
||||
isDefault,
|
||||
});
|
||||
headerForm.resetFields();
|
||||
setAddModalOpen(false);
|
||||
message.success('发票抬头添加成功');
|
||||
loadHeaders();
|
||||
} catch (e: any) {
|
||||
if (e?.errorFields) {
|
||||
message.warning('请完善表单信息');
|
||||
} else {
|
||||
const msg = e?.message || e?.response?.data?.detail || '添加失败';
|
||||
message.error(msg);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -468,16 +470,26 @@ const InvoicePage: React.FC = () => {
|
||||
content: '确定要删除该发票抬头吗?',
|
||||
okText: '确定',
|
||||
cancelText: '取消',
|
||||
onOk: () => {
|
||||
setHeaders(prev => prev.filter(h => h.id !== id));
|
||||
message.success('删除成功');
|
||||
onOk: async () => {
|
||||
try {
|
||||
await deleteInvoiceHeader(id);
|
||||
message.success('删除成功');
|
||||
loadHeaders();
|
||||
} catch (e: any) {
|
||||
message.error(e?.message || '删除失败');
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const handleSetDefault = (id: string) => {
|
||||
setHeaders(prev => prev.map(h => ({ ...h, isDefault: h.id === id })));
|
||||
message.success('已设为默认');
|
||||
const handleSetDefault = async (id: string) => {
|
||||
try {
|
||||
await setDefaultInvoiceHeader(id);
|
||||
message.success('已设为默认');
|
||||
loadHeaders();
|
||||
} catch (e: any) {
|
||||
message.error(e?.message || '设置失败');
|
||||
}
|
||||
};
|
||||
|
||||
// 开票流程:选择订单后确认 → 打开抬头选择弹窗
|
||||
@@ -838,23 +850,22 @@ const InvoicePage: React.FC = () => {
|
||||
<span style={{ color: '#94a3b8', fontSize: 13 }}>支付时间:</span>
|
||||
<DatePicker.RangePicker
|
||||
size="small"
|
||||
value={orderDateFilter[0] ? [dayjs(orderDateFilter[0]), orderDateFilter[1] ? dayjs(orderDateFilter[1]) : null] : [null, null]}
|
||||
value={orderDateRange}
|
||||
onChange={(dates) => {
|
||||
setOrderDateRange(dates);
|
||||
if (dates && dates[0] && dates[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]);
|
||||
// 清除时展示全部
|
||||
setOrderDateFilter([null, null]);
|
||||
}
|
||||
setOrderPage(1);
|
||||
setTimeout(() => loadOrderData(1), 0);
|
||||
}}
|
||||
allowClear
|
||||
placeholder={['开始日期', '结束日期']}
|
||||
style={{ width: 280 }}
|
||||
/>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user