feat: update voice room red packet admin UI
This commit is contained in:
parent
c22b23cb8c
commit
efaaef7e86
@ -132,7 +132,7 @@
|
|||||||
"x-permissions": ["user-leaderboard:view"]
|
"x-permissions": ["user-leaderboard:view"]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"/admin/activity/red-packets": {
|
"/resident-activity/voice-room-red-packet/records": {
|
||||||
"get": {
|
"get": {
|
||||||
"operationId": "listRedPackets",
|
"operationId": "listRedPackets",
|
||||||
"responses": {
|
"responses": {
|
||||||
@ -144,7 +144,7 @@
|
|||||||
"x-permissions": ["red-packet:view"]
|
"x-permissions": ["red-packet:view"]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"/admin/activity/red-packets/{packet_id}": {
|
"/resident-activity/voice-room-red-packet/records/{packet_id}": {
|
||||||
"get": {
|
"get": {
|
||||||
"operationId": "getRedPacket",
|
"operationId": "getRedPacket",
|
||||||
"responses": {
|
"responses": {
|
||||||
@ -166,7 +166,7 @@
|
|||||||
"x-permissions": ["red-packet:view"]
|
"x-permissions": ["red-packet:view"]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"/admin/activity/red-packets/config": {
|
"/resident-activity/voice-room-red-packet/config": {
|
||||||
"get": {
|
"get": {
|
||||||
"operationId": "getRedPacketConfig",
|
"operationId": "getRedPacketConfig",
|
||||||
"responses": {
|
"responses": {
|
||||||
@ -177,7 +177,7 @@
|
|||||||
"x-permission": "red-packet:view",
|
"x-permission": "red-packet:view",
|
||||||
"x-permissions": ["red-packet:view"]
|
"x-permissions": ["red-packet:view"]
|
||||||
},
|
},
|
||||||
"put": {
|
"post": {
|
||||||
"operationId": "updateRedPacketConfig",
|
"operationId": "updateRedPacketConfig",
|
||||||
"responses": {
|
"responses": {
|
||||||
"200": {
|
"200": {
|
||||||
@ -188,6 +188,18 @@
|
|||||||
"x-permissions": ["red-packet:update"]
|
"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": {
|
"/admin/activity/lucky-gifts/v2/config": {
|
||||||
"get": {
|
"get": {
|
||||||
"operationId": "getLuckyGiftConfig",
|
"operationId": "getLuckyGiftConfig",
|
||||||
|
|||||||
@ -10,6 +10,7 @@ export interface RedPacketConfigDto {
|
|||||||
delayedOpenSeconds: number;
|
delayedOpenSeconds: number;
|
||||||
expireSeconds: number;
|
expireSeconds: number;
|
||||||
dailySendLimit: number;
|
dailySendLimit: number;
|
||||||
|
ruleUrl?: string;
|
||||||
updatedByAdminId?: number;
|
updatedByAdminId?: number;
|
||||||
createdAtMs?: number;
|
createdAtMs?: number;
|
||||||
updatedAtMs?: number;
|
updatedAtMs?: number;
|
||||||
@ -20,8 +21,9 @@ export interface RedPacketConfigPayload {
|
|||||||
count_tiers: number[];
|
count_tiers: number[];
|
||||||
amount_tiers: number[];
|
amount_tiers: number[];
|
||||||
delayed_open_seconds: number;
|
delayed_open_seconds: number;
|
||||||
expire_seconds: number;
|
expire_seconds?: number;
|
||||||
daily_send_limit: number;
|
daily_send_limit: number;
|
||||||
|
rule_url?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RedPacketClaimDto {
|
export interface RedPacketClaimDto {
|
||||||
@ -54,15 +56,20 @@ export interface RedPacketDto {
|
|||||||
expiresAtMs: number;
|
expiresAtMs: number;
|
||||||
createdAtMs: number;
|
createdAtMs: number;
|
||||||
updatedAtMs?: number;
|
updatedAtMs?: number;
|
||||||
|
refundAmount?: number;
|
||||||
|
refundStatus?: string;
|
||||||
|
refundedAtMs?: number;
|
||||||
claims?: RedPacketClaimDto[];
|
claims?: RedPacketClaimDto[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RedPacketQuery extends PageQuery {
|
export interface RedPacketQuery extends PageQuery {
|
||||||
room_id?: string;
|
roomId?: string;
|
||||||
sender_user_id?: string;
|
senderUserId?: string;
|
||||||
region_id?: string;
|
region_id?: string;
|
||||||
packet_type?: string;
|
packetMode?: string;
|
||||||
status?: string;
|
status?: string;
|
||||||
|
startTime?: string;
|
||||||
|
endTime?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
type RawConfig = RedPacketConfigDto & Record<string, unknown>;
|
type RawConfig = RedPacketConfigDto & Record<string, unknown>;
|
||||||
@ -102,6 +109,14 @@ export function getRedPacket(packetId: string): Promise<RedPacketDto> {
|
|||||||
}).then(normalizePacket);
|
}).then(normalizePacket);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function retryRedPacketRefund(packetId: string): Promise<unknown> {
|
||||||
|
const endpoint = API_ENDPOINTS.retryRedPacketRefund;
|
||||||
|
return apiRequest(apiEndpointPath(API_OPERATIONS.retryRedPacketRefund), {
|
||||||
|
body: { packetNo: packetId },
|
||||||
|
method: endpoint.method,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function normalizeConfig(item: RawConfig): RedPacketConfigDto {
|
function normalizeConfig(item: RawConfig): RedPacketConfigDto {
|
||||||
return {
|
return {
|
||||||
appCode: stringValue(item.appCode ?? item.app_code),
|
appCode: stringValue(item.appCode ?? item.app_code),
|
||||||
@ -111,6 +126,7 @@ function normalizeConfig(item: RawConfig): RedPacketConfigDto {
|
|||||||
delayedOpenSeconds: numberValue(item.delayedOpenSeconds ?? item.delayed_open_seconds),
|
delayedOpenSeconds: numberValue(item.delayedOpenSeconds ?? item.delayed_open_seconds),
|
||||||
expireSeconds: numberValue(item.expireSeconds ?? item.expire_seconds),
|
expireSeconds: numberValue(item.expireSeconds ?? item.expire_seconds),
|
||||||
dailySendLimit: numberValue(item.dailySendLimit ?? item.daily_send_limit),
|
dailySendLimit: numberValue(item.dailySendLimit ?? item.daily_send_limit),
|
||||||
|
ruleUrl: stringValue(item.ruleUrl ?? item.rule_url),
|
||||||
updatedByAdminId: numberValue(item.updatedByAdminId ?? item.updated_by_admin_id),
|
updatedByAdminId: numberValue(item.updatedByAdminId ?? item.updated_by_admin_id),
|
||||||
createdAtMs: numberValue(item.createdAtMs ?? item.created_at_ms),
|
createdAtMs: numberValue(item.createdAtMs ?? item.created_at_ms),
|
||||||
updatedAtMs: numberValue(item.updatedAtMs ?? item.updated_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),
|
expiresAtMs: numberValue(item.expiresAtMs ?? item.expires_at_ms),
|
||||||
createdAtMs: numberValue(item.createdAtMs ?? item.created_at_ms),
|
createdAtMs: numberValue(item.createdAtMs ?? item.created_at_ms),
|
||||||
updatedAtMs: numberValue(item.updatedAtMs ?? item.updated_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),
|
claims: rawClaims.map(normalizeClaim),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -61,30 +61,38 @@ export function RedPacketConfigDrawer({
|
|||||||
<div className="form-drawer__grid">
|
<div className="form-drawer__grid">
|
||||||
<TextField
|
<TextField
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
inputProps={{ min: 0 }}
|
|
||||||
label="延迟红包秒数"
|
label="延迟红包秒数"
|
||||||
|
slotProps={{ htmlInput: { min: 0 } }}
|
||||||
type="number"
|
type="number"
|
||||||
value={form.delayedOpenSeconds}
|
value={form.delayedOpenSeconds}
|
||||||
onChange={(event) => updateValue("delayedOpenSeconds", event.target.value)}
|
onChange={(event) => updateValue("delayedOpenSeconds", event.target.value)}
|
||||||
/>
|
/>
|
||||||
<TextField
|
<TextField
|
||||||
disabled={disabled}
|
disabled
|
||||||
inputProps={{ min: 1 }}
|
label="过期退款秒数(固定)"
|
||||||
label="过期退款秒数"
|
slotProps={{ htmlInput: { min: 1 } }}
|
||||||
type="number"
|
type="number"
|
||||||
value={form.expireSeconds}
|
value={form.expireSeconds}
|
||||||
onChange={(event) => updateValue("expireSeconds", event.target.value)}
|
|
||||||
/>
|
/>
|
||||||
<TextField
|
<TextField
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
inputProps={{ min: 1 }}
|
|
||||||
label="每用户每日最多发送"
|
label="每用户每日最多发送"
|
||||||
|
slotProps={{ htmlInput: { min: 1 } }}
|
||||||
type="number"
|
type="number"
|
||||||
value={form.dailySendLimit}
|
value={form.dailySendLimit}
|
||||||
onChange={(event) => updateValue("dailySendLimit", event.target.value)}
|
onChange={(event) => updateValue("dailySendLimit", event.target.value)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
<section className="form-drawer__section">
|
||||||
|
<TextField
|
||||||
|
fullWidth
|
||||||
|
disabled={disabled}
|
||||||
|
label="规则说明 URL"
|
||||||
|
value={form.ruleUrl}
|
||||||
|
onChange={(event) => updateValue("ruleUrl", event.target.value)}
|
||||||
|
/>
|
||||||
|
</section>
|
||||||
<TierSection
|
<TierSection
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
label="红包数量档位"
|
label="红包数量档位"
|
||||||
@ -140,8 +148,8 @@ function TierSection({ disabled, label, onAdd, onRemove, onUpdate, tierKey, valu
|
|||||||
<div className={styles.tierGrid} key={`${tierKey}-${index}`}>
|
<div className={styles.tierGrid} key={`${tierKey}-${index}`}>
|
||||||
<TextField
|
<TextField
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
inputProps={{ min: 1 }}
|
|
||||||
label={`${label} ${index + 1}`}
|
label={`${label} ${index + 1}`}
|
||||||
|
slotProps={{ htmlInput: { min: 1 } }}
|
||||||
type="number"
|
type="number"
|
||||||
value={value}
|
value={value}
|
||||||
onChange={(event) => onUpdate(tierKey, index, event.target.value)}
|
onChange={(event) => onUpdate(tierKey, index, event.target.value)}
|
||||||
|
|||||||
@ -20,9 +20,10 @@ export function RedPacketConfigSummary({ canUpdate, config, configLoading, onEdi
|
|||||||
<SummaryItem label="数量档位">{formatTiers(config?.countTiers)}</SummaryItem>
|
<SummaryItem label="数量档位">{formatTiers(config?.countTiers)}</SummaryItem>
|
||||||
<SummaryItem label="金额档位">{formatTiers(config?.amountTiers)}</SummaryItem>
|
<SummaryItem label="金额档位">{formatTiers(config?.amountTiers)}</SummaryItem>
|
||||||
<SummaryItem label="延迟/过期">
|
<SummaryItem label="延迟/过期">
|
||||||
{Number(config?.delayedOpenSeconds || 0)}s / {Number(config?.expireSeconds || 0)}s
|
{Number(config?.delayedOpenSeconds || 0)}s / 24h
|
||||||
</SummaryItem>
|
</SummaryItem>
|
||||||
<SummaryItem label="每日上限">{Number(config?.dailySendLimit || 0)} 次</SummaryItem>
|
<SummaryItem label="每日上限">{Number(config?.dailySendLimit || 0)} 次</SummaryItem>
|
||||||
|
<SummaryItem label="规则 URL">{config?.ruleUrl || "-"}</SummaryItem>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.summaryActions}>
|
<div className={styles.summaryActions}>
|
||||||
<Button disabled={configLoading} startIcon={<RefreshOutlined fontSize="small" />} onClick={onRefresh}>
|
<Button disabled={configLoading} startIcon={<RefreshOutlined fontSize="small" />} onClick={onRefresh}>
|
||||||
|
|||||||
@ -53,6 +53,20 @@ export function RedPacketDetailDrawer({ loading, onClose, open, packet }) {
|
|||||||
<InfoRow label="状态" value={packetStatusLabel(packet.status)} />
|
<InfoRow label="状态" value={packetStatusLabel(packet.status)} />
|
||||||
<InfoRow label="金额" value={`${formatNumber(packet.totalAmount)} 金币`} />
|
<InfoRow label="金额" value={`${formatNumber(packet.totalAmount)} 金币`} />
|
||||||
<InfoRow label="剩余" value={`${formatNumber(packet.remainingAmount)} / ${packet.remainingCount} 个`} />
|
<InfoRow label="剩余" value={`${formatNumber(packet.remainingAmount)} / ${packet.remainingCount} 个`} />
|
||||||
|
<InfoRow
|
||||||
|
label="已领"
|
||||||
|
value={`${formatNumber(packet.totalAmount - packet.remainingAmount)} / ${
|
||||||
|
packet.packetCount - packet.remainingCount
|
||||||
|
} 个`}
|
||||||
|
/>
|
||||||
|
<InfoRow
|
||||||
|
label="退款"
|
||||||
|
value={
|
||||||
|
packet.refundStatus
|
||||||
|
? `${packetStatusLabel(packet.refundStatus)} ${formatNumber(packet.refundAmount)} 金币`
|
||||||
|
: "-"
|
||||||
|
}
|
||||||
|
/>
|
||||||
<InfoRow label="开启时间" value={formatMillis(packet.openAtMs)} />
|
<InfoRow label="开启时间" value={formatMillis(packet.openAtMs)} />
|
||||||
<InfoRow label="过期时间" value={formatMillis(packet.expiresAtMs)} />
|
<InfoRow label="过期时间" value={formatMillis(packet.expiresAtMs)} />
|
||||||
<DataTable
|
<DataTable
|
||||||
@ -81,7 +95,7 @@ function InfoRow({ label, value }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function packetTypeLabel(type) {
|
function packetTypeLabel(type) {
|
||||||
return type === "delayed" ? "延迟红包" : "普通红包";
|
return String(type || "").toLowerCase() === "delayed" ? "延时红包" : "即时红包";
|
||||||
}
|
}
|
||||||
|
|
||||||
function packetStatusLabel(status) {
|
function packetStatusLabel(status) {
|
||||||
@ -91,7 +105,7 @@ function packetStatusLabel(status) {
|
|||||||
refunded: "已退款",
|
refunded: "已退款",
|
||||||
waiting_open: "待开启",
|
waiting_open: "待开启",
|
||||||
};
|
};
|
||||||
return labels[status] || status || "-";
|
return labels[String(status || "").toLowerCase()] || status || "-";
|
||||||
}
|
}
|
||||||
|
|
||||||
function claimStatusLabel(status) {
|
function claimStatusLabel(status) {
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import {
|
|||||||
getRedPacket,
|
getRedPacket,
|
||||||
getRedPacketConfig,
|
getRedPacketConfig,
|
||||||
listRedPackets,
|
listRedPackets,
|
||||||
|
retryRedPacketRefund,
|
||||||
updateRedPacketConfig,
|
updateRedPacketConfig,
|
||||||
} from "@/features/red-packets/api";
|
} from "@/features/red-packets/api";
|
||||||
import { useRedPacketAbilities } from "@/features/red-packets/permissions.js";
|
import { useRedPacketAbilities } from "@/features/red-packets/permissions.js";
|
||||||
@ -17,8 +18,9 @@ const emptyForm = {
|
|||||||
countTiers: [""],
|
countTiers: [""],
|
||||||
amountTiers: [""],
|
amountTiers: [""],
|
||||||
delayedOpenSeconds: "0",
|
delayedOpenSeconds: "0",
|
||||||
expireSeconds: "300",
|
expireSeconds: "86400",
|
||||||
dailySendLimit: "5",
|
dailySendLimit: "5",
|
||||||
|
ruleUrl: "",
|
||||||
};
|
};
|
||||||
|
|
||||||
export function useRedPacketPage() {
|
export function useRedPacketPage() {
|
||||||
@ -40,9 +42,9 @@ export function useRedPacketPage() {
|
|||||||
|
|
||||||
const filters = useMemo(
|
const filters = useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
room_id: roomId.trim() || undefined,
|
roomId: roomId.trim() || undefined,
|
||||||
sender_user_id: senderUserId.trim() || undefined,
|
senderUserId: senderUserId.trim() || undefined,
|
||||||
packet_type: packetType || undefined,
|
packetMode: packetType || undefined,
|
||||||
status: status || undefined,
|
status: status || undefined,
|
||||||
}),
|
}),
|
||||||
[packetType, roomId, senderUserId, status],
|
[packetType, roomId, senderUserId, status],
|
||||||
@ -124,6 +126,22 @@ export function useRedPacketPage() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const retryRefund = async (packetId) => {
|
||||||
|
if (!packetId || !abilities.canUpdate) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
await retryRedPacketRefund(packetId);
|
||||||
|
showToast("退款重试已提交", "success");
|
||||||
|
await reloadPackets();
|
||||||
|
if (selectedPacket?.packetId === packetId) {
|
||||||
|
await openPacketDetail(packetId);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
showToast(err.message || "退款重试失败", "error");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const closePacketDetail = () => {
|
const closePacketDetail = () => {
|
||||||
setDetailOpen(false);
|
setDetailOpen(false);
|
||||||
setSelectedPacket(null);
|
setSelectedPacket(null);
|
||||||
@ -163,6 +181,7 @@ export function useRedPacketPage() {
|
|||||||
reloadConfig,
|
reloadConfig,
|
||||||
reloadPackets,
|
reloadPackets,
|
||||||
resetFilters,
|
resetFilters,
|
||||||
|
retryRefund,
|
||||||
roomId,
|
roomId,
|
||||||
selectedPacket,
|
selectedPacket,
|
||||||
senderUserId,
|
senderUserId,
|
||||||
@ -183,8 +202,9 @@ function formFromConfig(config) {
|
|||||||
countTiers: valuesToForm(config.countTiers),
|
countTiers: valuesToForm(config.countTiers),
|
||||||
amountTiers: valuesToForm(config.amountTiers),
|
amountTiers: valuesToForm(config.amountTiers),
|
||||||
delayedOpenSeconds: String(config.delayedOpenSeconds || 0),
|
delayedOpenSeconds: String(config.delayedOpenSeconds || 0),
|
||||||
expireSeconds: String(config.expireSeconds || 300),
|
expireSeconds: "86400",
|
||||||
dailySendLimit: String(config.dailySendLimit || 0),
|
dailySendLimit: String(config.dailySendLimit || 0),
|
||||||
|
ruleUrl: config.ruleUrl || "",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -196,7 +216,7 @@ function payloadFromForm(form) {
|
|||||||
const countTiers = normalizePositiveIntegers(form.countTiers);
|
const countTiers = normalizePositiveIntegers(form.countTiers);
|
||||||
const amountTiers = normalizePositiveIntegers(form.amountTiers);
|
const amountTiers = normalizePositiveIntegers(form.amountTiers);
|
||||||
const delayedOpenSeconds = Number(form.delayedOpenSeconds || 0);
|
const delayedOpenSeconds = Number(form.delayedOpenSeconds || 0);
|
||||||
const expireSeconds = Number(form.expireSeconds || 0);
|
const expireSeconds = 86400;
|
||||||
const dailySendLimit = Number(form.dailySendLimit || 0);
|
const dailySendLimit = Number(form.dailySendLimit || 0);
|
||||||
if (!countTiers.length) {
|
if (!countTiers.length) {
|
||||||
throw new Error("红包数量档位不能为空");
|
throw new Error("红包数量档位不能为空");
|
||||||
@ -220,6 +240,7 @@ function payloadFromForm(form) {
|
|||||||
delayed_open_seconds: delayedOpenSeconds,
|
delayed_open_seconds: delayedOpenSeconds,
|
||||||
expire_seconds: expireSeconds,
|
expire_seconds: expireSeconds,
|
||||||
daily_send_limit: dailySendLimit,
|
daily_send_limit: dailySendLimit,
|
||||||
|
rule_url: String(form.ruleUrl || "").trim(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import VisibilityOutlined from "@mui/icons-material/VisibilityOutlined";
|
import VisibilityOutlined from "@mui/icons-material/VisibilityOutlined";
|
||||||
|
import ReplayOutlined from "@mui/icons-material/ReplayOutlined";
|
||||||
import MenuItem from "@mui/material/MenuItem";
|
import MenuItem from "@mui/material/MenuItem";
|
||||||
import TextField from "@mui/material/TextField";
|
import TextField from "@mui/material/TextField";
|
||||||
import { RedPacketConfigDrawer } from "@/features/red-packets/components/RedPacketConfigDrawer.jsx";
|
import { RedPacketConfigDrawer } from "@/features/red-packets/components/RedPacketConfigDrawer.jsx";
|
||||||
@ -20,8 +21,8 @@ import { formatMillis } from "@/shared/utils/time.js";
|
|||||||
|
|
||||||
const packetTypeOptions = [
|
const packetTypeOptions = [
|
||||||
["", "全部类型"],
|
["", "全部类型"],
|
||||||
["normal", "普通红包"],
|
["IMMEDIATE", "即时红包"],
|
||||||
["delayed", "延迟红包"],
|
["DELAYED", "延时红包"],
|
||||||
];
|
];
|
||||||
|
|
||||||
const statusOptions = [
|
const statusOptions = [
|
||||||
@ -83,6 +84,17 @@ export function RedPacketPage() {
|
|||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
key: "claimed",
|
||||||
|
label: "已领",
|
||||||
|
width: "minmax(145px, 0.6fr)",
|
||||||
|
render: (packet) => (
|
||||||
|
<div className={styles.stack}>
|
||||||
|
<span>{formatNumber(packet.totalAmount - packet.remainingAmount)} 金币</span>
|
||||||
|
<span className={styles.meta}>{packet.packetCount - packet.remainingCount} 个</span>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
key: "status",
|
key: "status",
|
||||||
label: "状态",
|
label: "状态",
|
||||||
@ -101,12 +113,26 @@ export function RedPacketPage() {
|
|||||||
width: "minmax(180px, 0.75fr)",
|
width: "minmax(180px, 0.75fr)",
|
||||||
render: (packet) => formatMillis(packet.expiresAtMs),
|
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",
|
key: "actions",
|
||||||
label: "操作",
|
label: "操作",
|
||||||
width: "88px",
|
width: "128px",
|
||||||
render: (packet) => (
|
render: (packet) => (
|
||||||
<AdminRowActions>
|
<AdminRowActions>
|
||||||
|
{page.abilities.canUpdate && canRetryRefund(packet) ? (
|
||||||
|
<AdminActionIconButton label="重试退款" onClick={() => page.retryRefund(packet.packetId)}>
|
||||||
|
<ReplayOutlined fontSize="small" />
|
||||||
|
</AdminActionIconButton>
|
||||||
|
) : null}
|
||||||
<AdminActionIconButton label="查看详情" onClick={() => page.openPacketDetail(packet.packetId)}>
|
<AdminActionIconButton label="查看详情" onClick={() => page.openPacketDetail(packet.packetId)}>
|
||||||
<VisibilityOutlined fontSize="small" />
|
<VisibilityOutlined fontSize="small" />
|
||||||
</AdminActionIconButton>
|
</AdminActionIconButton>
|
||||||
@ -210,13 +236,20 @@ export function RedPacketPage() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function packetTypeLabel(type) {
|
function packetTypeLabel(type) {
|
||||||
return type === "delayed" ? "延迟红包" : "普通红包";
|
const normalized = String(type || "").toLowerCase();
|
||||||
|
return normalized === "delayed" ? "延时红包" : "即时红包";
|
||||||
}
|
}
|
||||||
|
|
||||||
function packetStatusLabel(status) {
|
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) {
|
function formatNumber(value) {
|
||||||
return new Intl.NumberFormat("zh-CN").format(Number(value || 0));
|
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";
|
||||||
|
}
|
||||||
|
|||||||
@ -155,6 +155,7 @@ export const API_OPERATIONS = {
|
|||||||
replaceRoleDataScopes: "replaceRoleDataScopes",
|
replaceRoleDataScopes: "replaceRoleDataScopes",
|
||||||
replaceRolePermissions: "replaceRolePermissions",
|
replaceRolePermissions: "replaceRolePermissions",
|
||||||
resetUserPassword: "resetUserPassword",
|
resetUserPassword: "resetUserPassword",
|
||||||
|
retryRedPacketRefund: "retryRedPacketRefund",
|
||||||
search: "search",
|
search: "search",
|
||||||
setAgencyJoinEnabled: "setAgencyJoinEnabled",
|
setAgencyJoinEnabled: "setAgencyJoinEnabled",
|
||||||
setBDLeaderStatus: "setBDLeaderStatus",
|
setBDLeaderStatus: "setBDLeaderStatus",
|
||||||
@ -712,14 +713,14 @@ export const API_ENDPOINTS: Record<ApiOperationId, ApiEndpoint> = {
|
|||||||
getRedPacket: {
|
getRedPacket: {
|
||||||
method: "GET",
|
method: "GET",
|
||||||
operationId: API_OPERATIONS.getRedPacket,
|
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",
|
permission: "red-packet:view",
|
||||||
permissions: ["red-packet:view"]
|
permissions: ["red-packet:view"]
|
||||||
},
|
},
|
||||||
getRedPacketConfig: {
|
getRedPacketConfig: {
|
||||||
method: "GET",
|
method: "GET",
|
||||||
operationId: API_OPERATIONS.getRedPacketConfig,
|
operationId: API_OPERATIONS.getRedPacketConfig,
|
||||||
path: "/v1/admin/activity/red-packets/config",
|
path: "/v1/resident-activity/voice-room-red-packet/config",
|
||||||
permission: "red-packet:view",
|
permission: "red-packet:view",
|
||||||
permissions: ["red-packet:view"]
|
permissions: ["red-packet:view"]
|
||||||
},
|
},
|
||||||
@ -1004,7 +1005,7 @@ export const API_ENDPOINTS: Record<ApiOperationId, ApiEndpoint> = {
|
|||||||
listRedPackets: {
|
listRedPackets: {
|
||||||
method: "GET",
|
method: "GET",
|
||||||
operationId: API_OPERATIONS.listRedPackets,
|
operationId: API_OPERATIONS.listRedPackets,
|
||||||
path: "/v1/admin/activity/red-packets",
|
path: "/v1/resident-activity/voice-room-red-packet/records",
|
||||||
permission: "red-packet:view",
|
permission: "red-packet:view",
|
||||||
permissions: ["red-packet:view"]
|
permissions: ["red-packet:view"]
|
||||||
},
|
},
|
||||||
@ -1187,6 +1188,13 @@ export const API_ENDPOINTS: Record<ApiOperationId, ApiEndpoint> = {
|
|||||||
permission: "user:reset-password",
|
permission: "user:reset-password",
|
||||||
permissions: ["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: {
|
search: {
|
||||||
method: "GET",
|
method: "GET",
|
||||||
operationId: API_OPERATIONS.search,
|
operationId: API_OPERATIONS.search,
|
||||||
@ -1368,9 +1376,9 @@ export const API_ENDPOINTS: Record<ApiOperationId, ApiEndpoint> = {
|
|||||||
permissions: ["payment-product:update"]
|
permissions: ["payment-product:update"]
|
||||||
},
|
},
|
||||||
updateRedPacketConfig: {
|
updateRedPacketConfig: {
|
||||||
method: "PUT",
|
method: "POST",
|
||||||
operationId: API_OPERATIONS.updateRedPacketConfig,
|
operationId: API_OPERATIONS.updateRedPacketConfig,
|
||||||
path: "/v1/admin/activity/red-packets/config",
|
path: "/v1/resident-activity/voice-room-red-packet/config",
|
||||||
permission: "red-packet:update",
|
permission: "red-packet:update",
|
||||||
permissions: ["red-packet:update"]
|
permissions: ["red-packet:update"]
|
||||||
},
|
},
|
||||||
|
|||||||
38
src/shared/api/generated/schema.d.ts
vendored
38
src/shared/api/generated/schema.d.ts
vendored
@ -100,7 +100,7 @@ export interface paths {
|
|||||||
patch?: never;
|
patch?: never;
|
||||||
trace?: never;
|
trace?: never;
|
||||||
};
|
};
|
||||||
"/admin/activity/red-packets": {
|
"/resident-activity/voice-room-red-packet/records": {
|
||||||
parameters: {
|
parameters: {
|
||||||
query?: never;
|
query?: never;
|
||||||
header?: never;
|
header?: never;
|
||||||
@ -116,7 +116,7 @@ export interface paths {
|
|||||||
patch?: never;
|
patch?: never;
|
||||||
trace?: never;
|
trace?: never;
|
||||||
};
|
};
|
||||||
"/admin/activity/red-packets/{packet_id}": {
|
"/resident-activity/voice-room-red-packet/records/{packet_id}": {
|
||||||
parameters: {
|
parameters: {
|
||||||
query?: never;
|
query?: never;
|
||||||
header?: never;
|
header?: never;
|
||||||
@ -132,7 +132,7 @@ export interface paths {
|
|||||||
patch?: never;
|
patch?: never;
|
||||||
trace?: never;
|
trace?: never;
|
||||||
};
|
};
|
||||||
"/admin/activity/red-packets/config": {
|
"/resident-activity/voice-room-red-packet/config": {
|
||||||
parameters: {
|
parameters: {
|
||||||
query?: never;
|
query?: never;
|
||||||
header?: never;
|
header?: never;
|
||||||
@ -140,8 +140,24 @@ export interface paths {
|
|||||||
cookie?: never;
|
cookie?: never;
|
||||||
};
|
};
|
||||||
get: operations["getRedPacketConfig"];
|
get: operations["getRedPacketConfig"];
|
||||||
put: operations["updateRedPacketConfig"];
|
put?: never;
|
||||||
post?: 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;
|
delete?: never;
|
||||||
options?: never;
|
options?: never;
|
||||||
head?: never;
|
head?: never;
|
||||||
@ -2734,6 +2750,18 @@ export interface operations {
|
|||||||
200: components["responses"]["EmptyResponse"];
|
200: components["responses"]["EmptyResponse"];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
retryRedPacketRefund: {
|
||||||
|
parameters: {
|
||||||
|
query?: never;
|
||||||
|
header?: never;
|
||||||
|
path?: never;
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
requestBody?: never;
|
||||||
|
responses: {
|
||||||
|
200: components["responses"]["EmptyResponse"];
|
||||||
|
};
|
||||||
|
};
|
||||||
getLuckyGiftConfig: {
|
getLuckyGiftConfig: {
|
||||||
parameters: {
|
parameters: {
|
||||||
query?: never;
|
query?: never;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user