This commit is contained in:
2026-08-10 16:42:47 +08:00
parent d1e8eb7316
commit 86c65f9ec8
7 changed files with 697 additions and 641 deletions
+21 -2
View File
@@ -294,17 +294,36 @@ async def alipay_callback(request: Request, db: AsyncSession = Depends(get_db)):
async def list_orders(
page: int = Query(1, ge=1),
page_size: int = Query(20, ge=1, le=100),
status_filter: str | None = Query(None, description="按状态筛选: pending/paid/refunded/failed/cancelled"),
start_date: str | None = Query(None, description="创建时间起始,格式 YYYY-MM-DD"),
end_date: str | None = Query(None, description="创建时间结束,格式 YYYY-MM-DD"),
invoice_mode: bool = Query(False, description="开票模式:仅返回已支付订单"),
current_user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db),
):
from app.services.payment import _check_and_expire_order
from datetime import datetime, timezone, timedelta
count_query = select(func.count(PaymentOrder.id)).where(PaymentOrder.user_id == current_user.id)
# 构建筛选条件
conditions = [PaymentOrder.user_id == current_user.id]
if status_filter:
conditions.append(PaymentOrder.status == status_filter)
if invoice_mode:
conditions.append(PaymentOrder.status == "paid")
if start_date:
start_dt = datetime.strptime(start_date, "%Y-%m-%d").replace(tzinfo=timezone.utc)
conditions.append(PaymentOrder.created_at >= start_dt)
if end_date:
end_dt = (datetime.strptime(end_date, "%Y-%m-%d") + timedelta(days=1)).replace(tzinfo=timezone.utc)
conditions.append(PaymentOrder.created_at < end_dt)
# 统计总数
count_query = select(func.count(PaymentOrder.id)).where(*conditions)
total = (await db.execute(count_query)).scalar() or 0
result = await db.execute(
select(PaymentOrder)
.where(PaymentOrder.user_id == current_user.id)
.where(*conditions)
.order_by(PaymentOrder.created_at.desc())
.offset((page - 1) * page_size)
.limit(page_size)
File diff suppressed because one or more lines are too long
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 href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;500;600;700&display=swap" rel="stylesheet" />
<title>民众智创</title>
<script type="module" crossorigin src="/assets/index-D5aCA_p5.js"></script>
<script type="module" crossorigin src="/assets/index-BlvEhxKB.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-DtYH0-uL.css">
</head>
<body>
+9 -1
View File
@@ -445,10 +445,18 @@ export async function createRechargeOrder(planId: string, method: string = 'wech
return api.post('/payments/recharge', { plan: planId, method });
}
export async function getPaymentOrders(page = 1, pageSize = 20): Promise<{ items: any[]; total: number }> {
export async function getPaymentOrders(
page = 1,
pageSize = 20,
options?: { statusFilter?: string; startDate?: string; endDate?: string; invoiceMode?: boolean }
): Promise<{ items: any[]; total: number }> {
const params = new URLSearchParams();
params.set('page', String(page));
params.set('page_size', String(pageSize));
if (options?.statusFilter) params.set('status_filter', options.statusFilter);
if (options?.startDate) params.set('start_date', options.startDate);
if (options?.endDate) params.set('end_date', options.endDate);
if (options?.invoiceMode) params.set('invoice_mode', 'true');
return api.get(`/payments/orders?${params.toString()}`);
}
+11 -20
View File
@@ -99,36 +99,27 @@ const InvoicePage: React.FC = () => {
}
};
// 加载订单数据(显示所有已支付订单,已开票的标记 _occupied
// 加载订单数据(开票模式:后端只返回已支付订单
const loadOrderData = async (page: number = orderPage, occupiedIds?: Set<string>) => {
setOrderLoading(true);
try {
const data = await getPaymentOrders(page, orderPageSize);
// 只显示已支付订单
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';
});
}
const data = await getPaymentOrders(page, orderPageSize, {
invoiceMode: true,
startDate: orderDateFilter[0] || undefined,
endDate: orderDateFilter[1] || undefined,
});
const items = data.items || [];
// 标记已开票订单
const occIds = occupiedIds || occupiedOrderIds;
items = items.map((o: any) => ({
const markedItems = items.map((o: any) => ({
...o,
_occupied: occIds.has(o.id || o.order_no || o.orderNo),
}));
setOrderData(items);
setOrderTotal(items.length);
setOrderData(markedItems);
setOrderTotal(data.total || 0);
} catch {
setOrderData([]);
setOrderTotal(0);
}
setOrderLoading(false);
};
+42 -4
View File
@@ -1,5 +1,5 @@
import React, { useEffect, useState } from 'react';
import { Table, Tag, Empty, Spin, Typography, Pagination } from 'antd';
import { Table, Tag, Empty, Spin, Typography, Pagination, Select, DatePicker, Space } from 'antd';
import { FileTextOutlined, AlipayCircleOutlined, WechatOutlined } from '@ant-design/icons';
import { getPaymentOrders } from '../api';
@@ -9,15 +9,21 @@ const OrderRecordsPage: React.FC = () => {
const [total, setTotal] = useState(0);
const [page, setPage] = useState(1);
const [pageSize] = useState(10);
const [statusFilter, setStatusFilter] = useState<string>('');
const [dateRange, setDateRange] = useState<any>([null, null]);
useEffect(() => {
loadData();
}, [page]);
}, [page, statusFilter]);
const loadData = async () => {
setLoading(true);
try {
const data = await getPaymentOrders(page, pageSize);
const data = await getPaymentOrders(page, pageSize, {
statusFilter: statusFilter || undefined,
startDate: dateRange[0]?.format?.('YYYY-MM-DD'),
endDate: dateRange[1]?.format?.('YYYY-MM-DD'),
});
setOrders(data.items || []);
setTotal(data.total || 0);
} catch {
@@ -30,6 +36,12 @@ const OrderRecordsPage: React.FC = () => {
setPage(newPage);
};
const handleReset = () => {
setStatusFilter('');
setDateRange([null, null]);
setPage(1);
};
const columns = [
{
title: '订单号',
@@ -139,7 +151,33 @@ const OrderRecordsPage: React.FC = () => {
<div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 12 }}>
<FileTextOutlined style={{ color: '#0ea5e9', fontSize: 16 }} />
<Typography.Text strong style={{ fontSize: 16 }}></Typography.Text>
<span style={{ color: '#94a3b8', fontSize: 13 }}> {orders.length} </span>
<span style={{ color: '#94a3b8', fontSize: 13 }}> {total} </span>
</div>
{/* 搜索栏 */}
<div style={{ display: 'flex', alignItems: 'center', gap: 12, marginBottom: 16, flexWrap: 'wrap' }}>
<Select
placeholder="支付状态"
allowClear
style={{ width: 130 }}
value={statusFilter || undefined}
onChange={(v) => { setStatusFilter(v || ''); setPage(1); }}
options={[
{ value: 'pending', label: '待支付' },
{ value: 'paid', label: '已支付' },
{ value: 'refunded', label: '已退款' },
{ value: 'failed', label: '支付失败' },
{ value: 'cancelled', label: '已取消' },
]}
/>
<DatePicker.RangePicker
value={dateRange}
onChange={(dates) => { setDateRange(dates); setPage(1); }}
allowClear
placeholder={['开始日期', '结束日期']}
/>
{(statusFilter || dateRange[0] || dateRange[1]) && (
<Typography.Link onClick={handleReset}></Typography.Link>
)}
</div>
<div style={{ borderRadius: 16, background: '#fff', border: '1px solid #f0f0f5', overflow: 'hidden' }}>
<Spin spinning={loading}>