diff --git a/contracts/admin-openapi.json b/contracts/admin-openapi.json index 09dbbe4..40fc84e 100644 --- a/contracts/admin-openapi.json +++ b/contracts/admin-openapi.json @@ -132,7 +132,7 @@ "x-permissions": ["user-leaderboard:view"] } }, - "/admin/activity/red-packets": { + "/resident-activity/voice-room-red-packet/records": { "get": { "operationId": "listRedPackets", "responses": { @@ -144,7 +144,7 @@ "x-permissions": ["red-packet:view"] } }, - "/admin/activity/red-packets/{packet_id}": { + "/resident-activity/voice-room-red-packet/records/{packet_id}": { "get": { "operationId": "getRedPacket", "responses": { @@ -166,7 +166,7 @@ "x-permissions": ["red-packet:view"] } }, - "/admin/activity/red-packets/config": { + "/resident-activity/voice-room-red-packet/config": { "get": { "operationId": "getRedPacketConfig", "responses": { @@ -177,7 +177,7 @@ "x-permission": "red-packet:view", "x-permissions": ["red-packet:view"] }, - "put": { + "post": { "operationId": "updateRedPacketConfig", "responses": { "200": { @@ -188,6 +188,18 @@ "x-permissions": ["red-packet:update"] } }, + "/resident-activity/voice-room-red-packet/refund/retry": { + "post": { + "operationId": "retryRedPacketRefund", + "responses": { + "200": { + "$ref": "#/components/responses/EmptyResponse" + } + }, + "x-permission": "red-packet:update", + "x-permissions": ["red-packet:update"] + } + }, "/admin/activity/lucky-gifts/v2/config": { "get": { "operationId": "getLuckyGiftConfig", diff --git a/src/features/red-packets/api.ts b/src/features/red-packets/api.ts index 1a5a3d6..6062942 100644 --- a/src/features/red-packets/api.ts +++ b/src/features/red-packets/api.ts @@ -10,6 +10,7 @@ export interface RedPacketConfigDto { delayedOpenSeconds: number; expireSeconds: number; dailySendLimit: number; + ruleUrl?: string; updatedByAdminId?: number; createdAtMs?: number; updatedAtMs?: number; @@ -20,8 +21,9 @@ export interface RedPacketConfigPayload { count_tiers: number[]; amount_tiers: number[]; delayed_open_seconds: number; - expire_seconds: number; + expire_seconds?: number; daily_send_limit: number; + rule_url?: string; } export interface RedPacketClaimDto { @@ -54,15 +56,20 @@ export interface RedPacketDto { expiresAtMs: number; createdAtMs: number; updatedAtMs?: number; + refundAmount?: number; + refundStatus?: string; + refundedAtMs?: number; claims?: RedPacketClaimDto[]; } export interface RedPacketQuery extends PageQuery { - room_id?: string; - sender_user_id?: string; + roomId?: string; + senderUserId?: string; region_id?: string; - packet_type?: string; + packetMode?: string; status?: string; + startTime?: string; + endTime?: string; } type RawConfig = RedPacketConfigDto & Record; @@ -102,6 +109,14 @@ export function getRedPacket(packetId: string): Promise { }).then(normalizePacket); } +export function retryRedPacketRefund(packetId: string): Promise { + const endpoint = API_ENDPOINTS.retryRedPacketRefund; + return apiRequest(apiEndpointPath(API_OPERATIONS.retryRedPacketRefund), { + body: { packetNo: packetId }, + method: endpoint.method, + }); +} + function normalizeConfig(item: RawConfig): RedPacketConfigDto { return { appCode: stringValue(item.appCode ?? item.app_code), @@ -111,6 +126,7 @@ function normalizeConfig(item: RawConfig): RedPacketConfigDto { delayedOpenSeconds: numberValue(item.delayedOpenSeconds ?? item.delayed_open_seconds), expireSeconds: numberValue(item.expireSeconds ?? item.expire_seconds), dailySendLimit: numberValue(item.dailySendLimit ?? item.daily_send_limit), + ruleUrl: stringValue(item.ruleUrl ?? item.rule_url), updatedByAdminId: numberValue(item.updatedByAdminId ?? item.updated_by_admin_id), createdAtMs: numberValue(item.createdAtMs ?? item.created_at_ms), updatedAtMs: numberValue(item.updatedAtMs ?? item.updated_at_ms), @@ -136,6 +152,9 @@ function normalizePacket(item: RawPacket): RedPacketDto { expiresAtMs: numberValue(item.expiresAtMs ?? item.expires_at_ms), createdAtMs: numberValue(item.createdAtMs ?? item.created_at_ms), updatedAtMs: numberValue(item.updatedAtMs ?? item.updated_at_ms), + refundAmount: numberValue(item.refundAmount ?? item.refund_amount), + refundStatus: stringValue(item.refundStatus ?? item.refund_status), + refundedAtMs: numberValue(item.refundedAtMs ?? item.refunded_at_ms), claims: rawClaims.map(normalizeClaim), }; } diff --git a/src/features/red-packets/components/RedPacketConfigDrawer.jsx b/src/features/red-packets/components/RedPacketConfigDrawer.jsx index caa01d4..f18dec5 100644 --- a/src/features/red-packets/components/RedPacketConfigDrawer.jsx +++ b/src/features/red-packets/components/RedPacketConfigDrawer.jsx @@ -61,30 +61,38 @@ export function RedPacketConfigDrawer({
updateValue("delayedOpenSeconds", event.target.value)} /> updateValue("expireSeconds", event.target.value)} /> updateValue("dailySendLimit", event.target.value)} />
+
+ updateValue("ruleUrl", event.target.value)} + /> +
onUpdate(tierKey, index, event.target.value)} diff --git a/src/features/red-packets/components/RedPacketConfigSummary.jsx b/src/features/red-packets/components/RedPacketConfigSummary.jsx index e23d1df..ae95177 100644 --- a/src/features/red-packets/components/RedPacketConfigSummary.jsx +++ b/src/features/red-packets/components/RedPacketConfigSummary.jsx @@ -20,9 +20,10 @@ export function RedPacketConfigSummary({ canUpdate, config, configLoading, onEdi {formatTiers(config?.countTiers)} {formatTiers(config?.amountTiers)} - {Number(config?.delayedOpenSeconds || 0)}s / {Number(config?.expireSeconds || 0)}s + {Number(config?.delayedOpenSeconds || 0)}s / 24h {Number(config?.dailySendLimit || 0)} 次 + {config?.ruleUrl || "-"}
), }, + { + key: "claimed", + label: "已领", + width: "minmax(145px, 0.6fr)", + render: (packet) => ( +
+ {formatNumber(packet.totalAmount - packet.remainingAmount)} 金币 + {packet.packetCount - packet.remainingCount} 个 +
+ ), + }, { key: "status", label: "状态", @@ -101,12 +113,26 @@ export function RedPacketPage() { width: "minmax(180px, 0.75fr)", render: (packet) => formatMillis(packet.expiresAtMs), }, + { + key: "refund", + label: "退款", + width: "minmax(120px, 0.55fr)", + render: (packet) => + packet.refundStatus + ? `${packetStatusLabel(packet.refundStatus)} ${formatNumber(packet.refundAmount)}` + : "-", + }, { key: "actions", label: "操作", - width: "88px", + width: "128px", render: (packet) => ( + {page.abilities.canUpdate && canRetryRefund(packet) ? ( + page.retryRefund(packet.packetId)}> + + + ) : null} page.openPacketDetail(packet.packetId)}> @@ -210,13 +236,20 @@ export function RedPacketPage() { } function packetTypeLabel(type) { - return type === "delayed" ? "延迟红包" : "普通红包"; + const normalized = String(type || "").toLowerCase(); + return normalized === "delayed" ? "延时红包" : "即时红包"; } function packetStatusLabel(status) { - return statusOptions.find(([value]) => value === status)?.[1] || status || "-"; + const normalized = String(status || "").toLowerCase(); + return statusOptions.find(([value]) => value === normalized)?.[1] || status || "-"; } function formatNumber(value) { return new Intl.NumberFormat("zh-CN").format(Number(value || 0)); } + +function canRetryRefund(packet) { + const status = String(packet.status || "").toLowerCase(); + return status === "active" || status === "waiting_open"; +} diff --git a/src/shared/api/generated/endpoints.ts b/src/shared/api/generated/endpoints.ts index 63d4389..7792321 100644 --- a/src/shared/api/generated/endpoints.ts +++ b/src/shared/api/generated/endpoints.ts @@ -155,6 +155,7 @@ export const API_OPERATIONS = { replaceRoleDataScopes: "replaceRoleDataScopes", replaceRolePermissions: "replaceRolePermissions", resetUserPassword: "resetUserPassword", + retryRedPacketRefund: "retryRedPacketRefund", search: "search", setAgencyJoinEnabled: "setAgencyJoinEnabled", setBDLeaderStatus: "setBDLeaderStatus", @@ -712,14 +713,14 @@ export const API_ENDPOINTS: Record = { getRedPacket: { method: "GET", operationId: API_OPERATIONS.getRedPacket, - path: "/v1/admin/activity/red-packets/{packet_id}", + path: "/v1/resident-activity/voice-room-red-packet/records/{packet_id}", permission: "red-packet:view", permissions: ["red-packet:view"] }, getRedPacketConfig: { method: "GET", operationId: API_OPERATIONS.getRedPacketConfig, - path: "/v1/admin/activity/red-packets/config", + path: "/v1/resident-activity/voice-room-red-packet/config", permission: "red-packet:view", permissions: ["red-packet:view"] }, @@ -1004,7 +1005,7 @@ export const API_ENDPOINTS: Record = { listRedPackets: { method: "GET", operationId: API_OPERATIONS.listRedPackets, - path: "/v1/admin/activity/red-packets", + path: "/v1/resident-activity/voice-room-red-packet/records", permission: "red-packet:view", permissions: ["red-packet:view"] }, @@ -1187,6 +1188,13 @@ export const API_ENDPOINTS: Record = { permission: "user:reset-password", permissions: ["user:reset-password"] }, + retryRedPacketRefund: { + method: "POST", + operationId: API_OPERATIONS.retryRedPacketRefund, + path: "/v1/resident-activity/voice-room-red-packet/refund/retry", + permission: "red-packet:update", + permissions: ["red-packet:update"] + }, search: { method: "GET", operationId: API_OPERATIONS.search, @@ -1368,9 +1376,9 @@ export const API_ENDPOINTS: Record = { permissions: ["payment-product:update"] }, updateRedPacketConfig: { - method: "PUT", + method: "POST", operationId: API_OPERATIONS.updateRedPacketConfig, - path: "/v1/admin/activity/red-packets/config", + path: "/v1/resident-activity/voice-room-red-packet/config", permission: "red-packet:update", permissions: ["red-packet:update"] }, diff --git a/src/shared/api/generated/schema.d.ts b/src/shared/api/generated/schema.d.ts index fab8d9d..866f028 100644 --- a/src/shared/api/generated/schema.d.ts +++ b/src/shared/api/generated/schema.d.ts @@ -100,7 +100,7 @@ export interface paths { patch?: never; trace?: never; }; - "/admin/activity/red-packets": { + "/resident-activity/voice-room-red-packet/records": { parameters: { query?: never; header?: never; @@ -116,7 +116,7 @@ export interface paths { patch?: never; trace?: never; }; - "/admin/activity/red-packets/{packet_id}": { + "/resident-activity/voice-room-red-packet/records/{packet_id}": { parameters: { query?: never; header?: never; @@ -132,7 +132,7 @@ export interface paths { patch?: never; trace?: never; }; - "/admin/activity/red-packets/config": { + "/resident-activity/voice-room-red-packet/config": { parameters: { query?: never; header?: never; @@ -140,8 +140,24 @@ export interface paths { cookie?: never; }; get: operations["getRedPacketConfig"]; - put: operations["updateRedPacketConfig"]; - post?: never; + put?: never; + post: operations["updateRedPacketConfig"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/resident-activity/voice-room-red-packet/refund/retry": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["retryRedPacketRefund"]; delete?: never; options?: never; head?: never; @@ -2734,6 +2750,18 @@ export interface operations { 200: components["responses"]["EmptyResponse"]; }; }; + retryRedPacketRefund: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: components["responses"]["EmptyResponse"]; + }; + }; getLuckyGiftConfig: { parameters: { query?: never;