Some checks failed
lotteryfront CI / build (push) Has been cancelled
- 合并钱包、订单、开奖、通知等独立 screen 到页面层,减少重复壳组件 - 优化订单列表/详情、钱包划转与待对账展示、开奖核对与结果详情交互 - 改进入口 gate、移动端视口与下拉刷新在窄屏下的表现 - dev 绑定 0.0.0.0 并默认放行 192.168/10 网段,修复局域网 HMR WebSocket
138 lines
4.2 KiB
TypeScript
138 lines
4.2 KiB
TypeScript
import type { TicketItemListRow } from "@/types/api/ticket-items";
|
|
|
|
export type TicketItemGroup = {
|
|
key: string;
|
|
order_no: string | null;
|
|
order_status: string | null;
|
|
draw_no: string | null;
|
|
placed_at: string | null;
|
|
currency_code: string | null;
|
|
status: string;
|
|
items: TicketItemListRow[];
|
|
total_bet_amount: number;
|
|
actual_deduct_amount: number;
|
|
win_amount: number;
|
|
jackpot_win_amount: number;
|
|
};
|
|
|
|
/** 分组键:有 order_no 则按订单;否则 draw_no + placed_at + currency + status 兜底 */
|
|
export function getTicketItemGroupKey(row: TicketItemListRow): string {
|
|
const orderNo = (row.order_no ?? "").trim();
|
|
if (orderNo) {
|
|
return `order:${orderNo}`;
|
|
}
|
|
const drawNo = row.draw_no ?? "";
|
|
const placedAt = row.placed_at ?? "";
|
|
const currency = row.currency_code ?? "";
|
|
const status = row.status ?? "";
|
|
return `fallback:${drawNo}|${placedAt}|${currency}|${status}`;
|
|
}
|
|
|
|
function resolveGroupStatus(items: TicketItemListRow[]): string {
|
|
const orderStatus = items.map((row) => row.order_status).find((s) => s != null && s !== "");
|
|
if (orderStatus === "partial_failed" || orderStatus === "partial_pending_confirm") {
|
|
return orderStatus;
|
|
}
|
|
|
|
const hasStatus = (status: string): boolean => items.some((row) => row.status === status);
|
|
const hasAny = (statuses: string[]): boolean => statuses.some((status) => hasStatus(status));
|
|
const allMatch = (statuses: string[]): boolean =>
|
|
items.length > 0 && items.every((row) => statuses.includes(row.status));
|
|
const hasWinningItem = items.some(
|
|
(row) => row.status === "settled_win" && row.win_amount + row.jackpot_win_amount > 0,
|
|
);
|
|
|
|
if (hasStatus("failed") && hasAny(["pending_draw", "placed", "pending_confirm"])) {
|
|
return "partial_failed";
|
|
}
|
|
|
|
if (hasAny(["pending_confirm", "partial_pending_confirm"])) {
|
|
return hasAny(["failed", "pending_draw", "placed", "pending_payout", "settled_win", "settled_lose"])
|
|
? "partial_pending_confirm"
|
|
: "pending_confirm";
|
|
}
|
|
|
|
if (hasAny(["pending_draw", "placed"])) {
|
|
return "pending_draw";
|
|
}
|
|
|
|
if (hasStatus("pending_payout")) {
|
|
return "pending_payout";
|
|
}
|
|
|
|
if (hasWinningItem) {
|
|
return "settled_win";
|
|
}
|
|
|
|
if (allMatch(["failed"])) {
|
|
return "failed";
|
|
}
|
|
|
|
if (allMatch(["refunded"])) {
|
|
return "refunded";
|
|
}
|
|
|
|
if (allMatch(["settled_lose", "settled_win"])) {
|
|
return "settled_lose";
|
|
}
|
|
|
|
return items[0]?.status ?? "unknown";
|
|
}
|
|
|
|
export function sortTicketGroupItems(items: TicketItemListRow[]): TicketItemListRow[] {
|
|
return [...items].sort((a, b) => {
|
|
const byPlay = a.play_code.localeCompare(b.play_code);
|
|
if (byPlay !== 0) return byPlay;
|
|
return (a.original_number ?? "").localeCompare(b.original_number ?? "");
|
|
});
|
|
}
|
|
|
|
/** 保持 API 返回顺序下,按分组键首次出现顺序输出合并组 */
|
|
export function groupTicketItems(items: TicketItemListRow[]): TicketItemGroup[] {
|
|
const map = new Map<string, TicketItemGroup>();
|
|
const order: string[] = [];
|
|
|
|
for (const row of items) {
|
|
const key = getTicketItemGroupKey(row);
|
|
let group = map.get(key);
|
|
if (!group) {
|
|
const orderNo = (row.order_no ?? "").trim();
|
|
group = {
|
|
key,
|
|
order_no: orderNo || null,
|
|
order_status: row.order_status ?? null,
|
|
draw_no: row.draw_no ?? null,
|
|
placed_at: row.placed_at ?? null,
|
|
currency_code: row.currency_code ?? null,
|
|
status: row.status,
|
|
items: [],
|
|
total_bet_amount: 0,
|
|
actual_deduct_amount: 0,
|
|
win_amount: 0,
|
|
jackpot_win_amount: 0,
|
|
};
|
|
map.set(key, group);
|
|
order.push(key);
|
|
}
|
|
group.items.push(row);
|
|
group.total_bet_amount += row.total_bet_amount;
|
|
group.actual_deduct_amount += row.actual_deduct_amount;
|
|
group.win_amount += row.win_amount;
|
|
group.jackpot_win_amount += row.jackpot_win_amount;
|
|
}
|
|
|
|
return order.map((key) => {
|
|
const group = map.get(key)!;
|
|
const sortedItems = sortTicketGroupItems(group.items);
|
|
return {
|
|
...group,
|
|
items: sortedItems,
|
|
status: resolveGroupStatus(sortedItems),
|
|
order_status: sortedItems[0]?.order_status ?? group.order_status,
|
|
};
|
|
});
|
|
}
|
|
|
|
export function ticketDetailHref(ticketNo: string): string {
|
|
return `/orders/${encodeURIComponent(ticketNo)}`;
|
|
} |