feat(points): 新增今日消耗接口 + 个人中心字体调整
后端: - CRUD 新增 sum_consumed_today() 方法,统计用户今日消费积分总和 - API 新增 GET /points/today-consumed 路由 前端: - 个人中心积分数字从 40px 改为 32px - 今日消耗从本地计算改为调用后端接口
This commit is contained in:
@@ -497,6 +497,18 @@ async def get_points_rules(
|
||||
|
||||
|
||||
|
||||
# ── 今日消费统计 ──────────────────────────────────────
|
||||
|
||||
@router.get("/today-consumed", response_model=ApiResponse[dict])
|
||||
async def get_today_consumed(
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user),
|
||||
):
|
||||
"""获取当前用户今日消费积分总额"""
|
||||
total = await point_transaction.sum_consumed_today(db, user_id=current_user.id)
|
||||
return success_response(data={"total": total})
|
||||
|
||||
|
||||
# ── 直接消费扣费(前端/Rust 层调用)───────────────────
|
||||
|
||||
@router.post("/consume", response_model=ApiResponse[dict])
|
||||
|
||||
@@ -5,9 +5,9 @@
|
||||
只增不改,用于审计和对账。
|
||||
"""
|
||||
|
||||
from datetime import datetime
|
||||
from datetime import datetime, time
|
||||
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy import func, select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.crud.base import CRUDBase
|
||||
@@ -101,6 +101,26 @@ class PointTransactionCRUD(CRUDBase[PointTransaction]):
|
||||
)
|
||||
return list(result.scalars().all())
|
||||
|
||||
async def sum_consumed_today(
|
||||
self,
|
||||
db: AsyncSession,
|
||||
*,
|
||||
user_id: str,
|
||||
) -> int:
|
||||
"""统计用户今日消费积分总和"""
|
||||
now = datetime.now()
|
||||
start_of_day = datetime.combine(now.date(), time.min)
|
||||
stmt = (
|
||||
select(func.coalesce(func.sum(PointTransaction.amount), 0))
|
||||
.where(
|
||||
PointTransaction.user_id == user_id,
|
||||
PointTransaction.type == "consume",
|
||||
PointTransaction.created_at >= start_of_day,
|
||||
)
|
||||
)
|
||||
result = await db.execute(stmt)
|
||||
return result.scalar() or 0
|
||||
|
||||
|
||||
# 导出实例
|
||||
point_transaction = PointTransactionCRUD()
|
||||
|
||||
@@ -165,6 +165,15 @@ export const pointsApi = {
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* GET /points/today-consumed
|
||||
*
|
||||
* 获取今日消费积分总额。
|
||||
*/
|
||||
getTodayConsumed: async (): Promise<{ total: number }> => {
|
||||
return client.get<{ total: number }>('/points/today-consumed');
|
||||
},
|
||||
|
||||
/**
|
||||
* 直接消费积分(前端/Rust 层业务扣费)
|
||||
* POST /points/consume
|
||||
|
||||
@@ -44,6 +44,7 @@ export default function Profile() {
|
||||
const [user, setUser] = useState<UserProfile | null>(null);
|
||||
const [balance, setBalance] = useState<PointBalance | null>(null);
|
||||
const [recentTx, setRecentTx] = useState<PointTransaction[]>([]);
|
||||
const [todayConsumed, setTodayConsumed] = useState(0);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [showRechargeModal, setShowRechargeModal] = useState(false);
|
||||
|
||||
@@ -66,10 +67,12 @@ export default function Profile() {
|
||||
}
|
||||
if (balanceData) {setBalance(balanceData);}
|
||||
|
||||
const txData = await pointsApi
|
||||
.getTransactions({ page: 1, pageSize: 10 })
|
||||
.catch(() => null);
|
||||
const [txData, todayData] = await Promise.all([
|
||||
pointsApi.getTransactions({ page: 1, pageSize: 10 }).catch(() => null),
|
||||
pointsApi.getTodayConsumed().catch(() => null),
|
||||
]);
|
||||
if (txData) {setRecentTx(txData.items);}
|
||||
if (todayData) {setTodayConsumed(todayData.total);}
|
||||
} catch (e) {
|
||||
console.error('[Profile] 加载数据失败:', e);
|
||||
} finally {
|
||||
@@ -212,7 +215,7 @@ export default function Profile() {
|
||||
<div style={{ fontSize: '13px', color: 'var(--text-secondary)', marginBottom: '8px' }}>
|
||||
剩余积分
|
||||
</div>
|
||||
<div style={{ fontSize: '40px', fontWeight: 700, color: '#36b26a', lineHeight: 1 }}>
|
||||
<div style={{ fontSize: '32px', fontWeight: 700, color: '#36b26a', lineHeight: 1 }}>
|
||||
{balance?.balance ?? 0}
|
||||
</div>
|
||||
</div>
|
||||
@@ -220,17 +223,8 @@ export default function Profile() {
|
||||
<div style={{ fontSize: '13px', color: 'var(--text-secondary)', marginBottom: '8px' }}>
|
||||
今日消耗
|
||||
</div>
|
||||
<div style={{ fontSize: '40px', fontWeight: 700, color: '#ff6b6b', lineHeight: 1 }}>
|
||||
{(() => {
|
||||
const todayStr = new Date().toLocaleDateString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit' }).replace(/\//g, '-');
|
||||
return recentTx
|
||||
.filter(tx => {
|
||||
if (tx.type !== 'consume') return false;
|
||||
const txDate = new Date(tx.createdAt).toLocaleDateString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit' }).replace(/\//g, '-');
|
||||
return txDate === todayStr;
|
||||
})
|
||||
.reduce((sum, tx) => sum + tx.amount, 0);
|
||||
})()}
|
||||
<div style={{ fontSize: '32px', fontWeight: 700, color: '#ff6b6b', lineHeight: 1 }}>
|
||||
{todayConsumed}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user