feat(抽奖): 开放人工奖(crypto/physical/manual_other) + 领奖工单管理 + REST 对齐

- prize-form: 解禁 crypto/physical/manual_other 并加各自 config;权重改为中奖概率(%),
  合计校验;vpn_duration 加"无订阅时新建订阅套餐"
- prize-grid: 概率合计=100% 校验告警;卡片渲染真实 config(时长/金额)
- claims-panel(新): 领奖工单列表 + 审核通过/驳回/标记发放
- activity-detail: 新增"领奖工单"Tab
- service: 奖品改/删 id 走 /prizes/:id(对齐后端 REST);新增 claims 5 个接口 + 类型
- i18n: 补齐 zh-CN/en-US lottery 文案

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-16 20:29:47 -07:00
parent ea4f6799e9
commit dd837cac7c
8 changed files with 1224 additions and 73 deletions
+95 -7
View File
@@ -202,14 +202,15 @@ export async function updateLotteryPrize(
body: API.UpdateLotteryPrizeRequest,
options?: { [key: string]: any }
) {
const { id, ...rest } = body;
return request<API.Response & { data?: API.LotteryPrize }>(
`${import.meta.env.VITE_API_PREFIX || ""}/v1/admin/lottery/prizes`,
`${import.meta.env.VITE_API_PREFIX || ""}/v1/admin/lottery/prizes/${id}`,
{
method: "PUT",
headers: {
"Content-Type": "application/json",
},
data: body,
data: rest,
...(options || {}),
}
);
@@ -227,13 +228,9 @@ export async function deleteLotteryPrize(
options?: { [key: string]: any }
) {
return request<API.Response & { data?: any }>(
`${import.meta.env.VITE_API_PREFIX || ""}/v1/admin/lottery/prizes`,
`${import.meta.env.VITE_API_PREFIX || ""}/v1/admin/lottery/prizes/${body.id}`,
{
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
data: body,
...(options || {}),
}
);
@@ -285,3 +282,94 @@ export async function grantLotteryChances(
}
);
}
/**
* 领奖工单列表(Stage 2 人工奖)
* GET /v1/admin/lottery/claims
*/
export async function getLotteryClaimList(
params: API.GetLotteryClaimListParams,
options?: { [key: string]: any }
) {
return request<API.Response & { data?: API.ListLotteryClaimsResponse }>(
`${import.meta.env.VITE_API_PREFIX || ""}/v1/admin/lottery/claims`,
{
method: "GET",
params: { ...params },
...(options || {}),
}
);
}
/**
* 领奖工单统计(各类型 reviewing/paying + 逾期)
* GET /v1/admin/lottery/claims/summary
*/
export async function getLotteryClaimsSummary(options?: {
[key: string]: any;
}) {
return request<API.Response & { data?: API.LotteryClaimsSummary }>(
`${import.meta.env.VITE_API_PREFIX || ""}/v1/admin/lottery/claims/summary`,
{
method: "GET",
...(options || {}),
}
);
}
/**
* 审核通过领奖工单(reviewing → paying
* POST /v1/admin/lottery/claims/approve
*/
export async function approveLotteryClaim(
body: API.ApproveLotteryClaimRequest,
options?: { [key: string]: any }
) {
return request<API.Response & { data?: any }>(
`${import.meta.env.VITE_API_PREFIX || ""}/v1/admin/lottery/claims/approve`,
{
method: "POST",
headers: { "Content-Type": "application/json" },
data: body,
...(options || {}),
}
);
}
/**
* 驳回领奖工单(reviewing/paying → rejectedreason 必填)
* POST /v1/admin/lottery/claims/reject
*/
export async function rejectLotteryClaim(
body: API.RejectLotteryClaimRequest,
options?: { [key: string]: any }
) {
return request<API.Response & { data?: any }>(
`${import.meta.env.VITE_API_PREFIX || ""}/v1/admin/lottery/claims/reject`,
{
method: "POST",
headers: { "Content-Type": "application/json" },
data: body,
...(options || {}),
}
);
}
/**
* 标记已发放(paying → paid),填 tx_hash / delivery_ref
* POST /v1/admin/lottery/claims/mark-paid
*/
export async function markPaidLotteryClaim(
body: API.MarkPaidLotteryClaimRequest,
options?: { [key: string]: any }
) {
return request<API.Response & { data?: any }>(
`${import.meta.env.VITE_API_PREFIX || ""}/v1/admin/lottery/claims/mark-paid`,
{
method: "POST",
headers: { "Content-Type": "application/json" },
data: body,
...(options || {}),
}
);
}
+105 -17
View File
@@ -3302,25 +3302,25 @@ declare namespace API {
type AdminWithdrawalLog = WithdrawalLog;
type LotteryPrizeType =
| 'vpn_duration'
| 'commission'
| 'none'
| 'crypto'
| 'physical'
| 'manual_other'
| 'coupon'
| 'points';
| "vpn_duration"
| "commission"
| "none"
| "crypto"
| "physical"
| "manual_other"
| "coupon"
| "points";
type LotteryActivityStatus = 'draft' | 'running' | 'paused' | 'ended';
type LotteryActivityStatus = "draft" | "running" | "paused" | "ended";
type LotteryChanceSourceType =
| 'daily_signin'
| 'new_subscription'
| 'invite_success'
| 'manual_grant';
| "daily_signin"
| "new_subscription"
| "invite_success"
| "manual_grant";
type LotteryEligibilityNode = {
op?: 'AND' | 'OR';
op?: "AND" | "OR";
children?: LotteryEligibilityNode[];
type?: string;
params?: Record<string, any>;
@@ -3371,11 +3371,20 @@ declare namespace API {
unmet_action: string;
};
type UpdateLotteryActivityRequest = CreateLotteryActivityRequest & { id: number };
type UpdateLotteryActivityRequest = CreateLotteryActivityRequest & {
id: number;
};
type GetLotteryActivityListParams = { page: number; size: number; status?: string };
type GetLotteryActivityListParams = {
page: number;
size: number;
status?: string;
};
type GetLotteryActivityListResponse = { total: number; list: LotteryActivity[] };
type GetLotteryActivityListResponse = {
total: number;
list: LotteryActivity[];
};
type GetLotteryActivityDetailParams = { id: number };
@@ -3415,4 +3424,83 @@ declare namespace API {
amount: number;
source_ref: string;
};
// ---- Stage 2 领奖工单(人工奖)--------------------------------------------
type LotteryClaimStatus =
| "pending_claim"
| "reviewing"
| "paying"
| "paid"
| "rejected"
| "expired";
type GetLotteryClaimListParams = {
type?: LotteryPrizeType;
status?: LotteryClaimStatus;
activity_id?: number;
user_id?: number;
from?: number;
to?: number;
page: number;
size: number;
};
type AdminLotteryClaimUser = {
id: number;
email?: string;
};
type AdminLotteryClaimPrize = {
type: LotteryPrizeType;
name: string;
config: Record<string, any>;
};
type AdminLotteryClaim = {
id: number;
draw_id: number;
activity_id: number;
user: AdminLotteryClaimUser;
prize: AdminLotteryClaimPrize;
status: LotteryClaimStatus;
claim_data?: Record<string, any>;
submitted_at?: number;
expires_at: number;
reviewed_by?: number;
reviewed_at?: number;
reject_reason?: string;
tx_hash?: string;
delivery_ref?: string;
paid_at?: number;
created_at: number;
};
type ListLotteryClaimsResponse = {
total: number;
claims: AdminLotteryClaim[];
};
type LotteryClaimsStatusCount = {
reviewing: number;
paying: number;
};
type LotteryClaimsSummary = {
crypto: LotteryClaimsStatusCount;
physical: LotteryClaimsStatusCount;
manual_other: LotteryClaimsStatusCount;
overdue: number;
};
type ApproveLotteryClaimRequest = { id: number };
type RejectLotteryClaimRequest = { id: number; reason: string };
type MarkPaidLotteryClaimRequest = {
id: number;
tx_hash?: string;
delivery_ref?: string;
paid_at?: number;
};
}