邀请
This commit is contained in:
parent
53b74f9dc2
commit
7a6f0db859
@ -270,6 +270,18 @@
|
||||
"x-permissions": ["first-recharge-reward:view"]
|
||||
}
|
||||
},
|
||||
"/admin/activity/invite-reward/claims": {
|
||||
"get": {
|
||||
"operationId": "listInviteActivityRewardClaims",
|
||||
"responses": {
|
||||
"200": {
|
||||
"$ref": "#/components/responses/EmptyResponse"
|
||||
}
|
||||
},
|
||||
"x-permission": "invite-activity-reward:view",
|
||||
"x-permissions": ["invite-activity-reward:view"]
|
||||
}
|
||||
},
|
||||
"/admin/activity/cumulative-recharge-reward/grants": {
|
||||
"get": {
|
||||
"operationId": "listCumulativeRechargeRewardGrants",
|
||||
@ -488,6 +500,28 @@
|
||||
"x-permissions": ["cumulative-recharge-reward:update"]
|
||||
}
|
||||
},
|
||||
"/admin/activity/invite-reward/config": {
|
||||
"get": {
|
||||
"operationId": "getInviteActivityRewardConfig",
|
||||
"responses": {
|
||||
"200": {
|
||||
"$ref": "#/components/responses/EmptyResponse"
|
||||
}
|
||||
},
|
||||
"x-permission": "invite-activity-reward:view",
|
||||
"x-permissions": ["invite-activity-reward:view"]
|
||||
},
|
||||
"put": {
|
||||
"operationId": "updateInviteActivityRewardConfig",
|
||||
"responses": {
|
||||
"200": {
|
||||
"$ref": "#/components/responses/EmptyResponse"
|
||||
}
|
||||
},
|
||||
"x-permission": "invite-activity-reward:update",
|
||||
"x-permissions": ["invite-activity-reward:update"]
|
||||
}
|
||||
},
|
||||
"/admin/activity/room-turnover-reward/config": {
|
||||
"get": {
|
||||
"operationId": "getRoomTurnoverRewardConfig",
|
||||
|
||||
@ -19,6 +19,8 @@ export interface InviteActivityRewardTierDto {
|
||||
export interface InviteActivityRewardConfigDto {
|
||||
appCode?: string;
|
||||
enabled: boolean;
|
||||
perInviteInviterRewardCoinAmount: number;
|
||||
perInviteInviteeRewardCoinAmount: number;
|
||||
tiers: InviteActivityRewardTierDto[];
|
||||
updatedByAdminId?: number;
|
||||
createdAtMs?: number;
|
||||
@ -27,6 +29,8 @@ export interface InviteActivityRewardConfigDto {
|
||||
|
||||
export interface InviteActivityRewardConfigPayload {
|
||||
enabled: boolean;
|
||||
per_invite_inviter_reward_coin_amount: number;
|
||||
per_invite_invitee_reward_coin_amount: number;
|
||||
tiers: Array<{
|
||||
tier_id?: number;
|
||||
reward_type: string;
|
||||
@ -95,9 +99,7 @@ export function updateInviteActivityRewardConfig(
|
||||
).then(normalizeConfig);
|
||||
}
|
||||
|
||||
export function listInviteActivityRewardClaims(
|
||||
query: PageQuery = {},
|
||||
): Promise<ApiPage<InviteActivityRewardClaimDto>> {
|
||||
export function listInviteActivityRewardClaims(query: PageQuery = {}): Promise<ApiPage<InviteActivityRewardClaimDto>> {
|
||||
const endpoint = API_ENDPOINTS.listInviteActivityRewardClaims;
|
||||
return apiRequest<ApiPage<RawClaim>>(apiEndpointPath(API_OPERATIONS.listInviteActivityRewardClaims), {
|
||||
method: endpoint.method,
|
||||
@ -113,6 +115,12 @@ function normalizeConfig(item: RawConfig): InviteActivityRewardConfigDto {
|
||||
return {
|
||||
appCode: stringValue(item.appCode ?? item.app_code),
|
||||
enabled: Boolean(item.enabled),
|
||||
perInviteInviterRewardCoinAmount: numberValue(
|
||||
item.perInviteInviterRewardCoinAmount ?? item.per_invite_inviter_reward_coin_amount,
|
||||
),
|
||||
perInviteInviteeRewardCoinAmount: numberValue(
|
||||
item.perInviteInviteeRewardCoinAmount ?? item.per_invite_invitee_reward_coin_amount,
|
||||
),
|
||||
tiers: rawTiers.map(normalizeTier),
|
||||
updatedByAdminId: numberValue(item.updatedByAdminId ?? item.updated_by_admin_id),
|
||||
createdAtMs: numberValue(item.createdAtMs ?? item.created_at_ms),
|
||||
|
||||
@ -75,6 +75,39 @@ export function InviteActivityRewardConfigDrawer({
|
||||
onChange={(event) => setForm((current) => ({ ...current, enabled: event.target.checked }))}
|
||||
/>
|
||||
</section>
|
||||
<section className="form-drawer__section">
|
||||
<div className="form-drawer__section-title">每邀请一人奖励</div>
|
||||
<div className={styles.inlineFields}>
|
||||
<TextField
|
||||
disabled={disabled}
|
||||
inputProps={{ min: 0, step: 1 }}
|
||||
label="邀请人获得金币"
|
||||
placeholder="0"
|
||||
type="number"
|
||||
value={form.perInviteInviterRewardCoinAmount || ""}
|
||||
onChange={(event) =>
|
||||
setForm((current) => ({
|
||||
...current,
|
||||
perInviteInviterRewardCoinAmount: event.target.value,
|
||||
}))
|
||||
}
|
||||
/>
|
||||
<TextField
|
||||
disabled={disabled}
|
||||
inputProps={{ min: 0, step: 1 }}
|
||||
label="被邀请人获得金币"
|
||||
placeholder="0"
|
||||
type="number"
|
||||
value={form.perInviteInviteeRewardCoinAmount || ""}
|
||||
onChange={(event) =>
|
||||
setForm((current) => ({
|
||||
...current,
|
||||
perInviteInviteeRewardCoinAmount: event.target.value,
|
||||
}))
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
<section className="form-drawer__section">
|
||||
<div className={styles.drawerSectionTitle}>
|
||||
<span>奖励档位</span>
|
||||
@ -152,7 +185,9 @@ export function InviteActivityRewardConfigDrawer({
|
||||
placeholder="1000"
|
||||
type="number"
|
||||
value={tier.rewardCoinAmount}
|
||||
onChange={(event) => updateTier(index, { rewardCoinAmount: event.target.value })}
|
||||
onChange={(event) =>
|
||||
updateTier(index, { rewardCoinAmount: event.target.value })
|
||||
}
|
||||
/>
|
||||
),
|
||||
},
|
||||
@ -167,7 +202,9 @@ export function InviteActivityRewardConfigDrawer({
|
||||
label={`档位 ${index + 1} 状态`}
|
||||
uncheckedLabel="停用"
|
||||
onChange={(event) =>
|
||||
updateTier(index, { status: event.target.checked ? "active" : "inactive" })
|
||||
updateTier(index, {
|
||||
status: event.target.checked ? "active" : "inactive",
|
||||
})
|
||||
}
|
||||
/>
|
||||
),
|
||||
|
||||
@ -22,10 +22,18 @@ export function InviteActivityRewardConfigSummary({ canUpdate, config, configLoa
|
||||
{config?.enabled ? "启用" : "停用"}
|
||||
</span>
|
||||
</SummaryItem>
|
||||
<SummaryItem label="累计充值档位">{thresholdSummary(rechargeTiers, "thresholdCoinAmount", "金币")}</SummaryItem>
|
||||
<SummaryItem label="累计充值档位">
|
||||
{thresholdSummary(rechargeTiers, "thresholdCoinAmount", "金币")}
|
||||
</SummaryItem>
|
||||
<SummaryItem label="有效人数档位">
|
||||
{thresholdSummary(validInviteTiers, "thresholdValidInviteCount", "人")}
|
||||
</SummaryItem>
|
||||
<SummaryItem label="邀请人即时奖励">
|
||||
{coinSummary(config?.perInviteInviterRewardCoinAmount)}
|
||||
</SummaryItem>
|
||||
<SummaryItem label="被邀请人即时奖励">
|
||||
{coinSummary(config?.perInviteInviteeRewardCoinAmount)}
|
||||
</SummaryItem>
|
||||
<SummaryItem label="奖励">{thresholdSummary(activeTiers, "rewardCoinAmount", "金币")}</SummaryItem>
|
||||
</div>
|
||||
<div className={styles.summaryActions}>
|
||||
@ -69,6 +77,11 @@ function thresholdSummary(tiers, field, unit) {
|
||||
return min === max ? `${formatNumber(min)} ${unit}` : `${formatNumber(min)} - ${formatNumber(max)} ${unit}`;
|
||||
}
|
||||
|
||||
function coinSummary(value) {
|
||||
const amount = Number(value || 0);
|
||||
return amount > 0 ? `${formatNumber(amount)} 金币` : "-";
|
||||
}
|
||||
|
||||
function formatNumber(value) {
|
||||
return new Intl.NumberFormat("zh-CN").format(Number(value || 0));
|
||||
}
|
||||
|
||||
@ -10,7 +10,12 @@ import { useToast } from "@/shared/ui/ToastProvider.jsx";
|
||||
|
||||
const pageSize = 50;
|
||||
const emptyClaims = { items: [], page: 1, pageSize, total: 0 };
|
||||
const emptyForm = { enabled: true, tiers: [] };
|
||||
const emptyForm = {
|
||||
enabled: true,
|
||||
perInviteInviterRewardCoinAmount: "",
|
||||
perInviteInviteeRewardCoinAmount: "",
|
||||
tiers: [],
|
||||
};
|
||||
|
||||
export function useInviteActivityRewardPage() {
|
||||
const abilities = useInviteActivityRewardAbilities();
|
||||
@ -139,6 +144,12 @@ export function useInviteActivityRewardPage() {
|
||||
function formFromConfig(config) {
|
||||
return {
|
||||
enabled: Boolean(config.enabled),
|
||||
perInviteInviterRewardCoinAmount: config.perInviteInviterRewardCoinAmount
|
||||
? String(config.perInviteInviterRewardCoinAmount)
|
||||
: "",
|
||||
perInviteInviteeRewardCoinAmount: config.perInviteInviteeRewardCoinAmount
|
||||
? String(config.perInviteInviteeRewardCoinAmount)
|
||||
: "",
|
||||
tiers: (config.tiers || []).map((tier) => ({
|
||||
tierId: tier.tierId || 0,
|
||||
rewardType: tier.rewardType || "recharge",
|
||||
@ -154,10 +165,16 @@ function formFromConfig(config) {
|
||||
}
|
||||
|
||||
function payloadFromForm(form) {
|
||||
const perInviteInviterRewardCoinAmount = Number(form.perInviteInviterRewardCoinAmount || 0);
|
||||
const perInviteInviteeRewardCoinAmount = Number(form.perInviteInviteeRewardCoinAmount || 0);
|
||||
if (perInviteInviterRewardCoinAmount < 0 || perInviteInviteeRewardCoinAmount < 0) {
|
||||
throw new Error("每邀请一人奖励金币不能小于 0");
|
||||
}
|
||||
const tiers = (form.tiers || []).map((tier, index) => {
|
||||
const rewardType = tier.rewardType === "valid_invite" ? "valid_invite" : "recharge";
|
||||
const thresholdCoinAmount = rewardType === "recharge" ? Number(tier.thresholdCoinAmount || 0) : 0;
|
||||
const thresholdValidInviteCount = rewardType === "valid_invite" ? Number(tier.thresholdValidInviteCount || 0) : 0;
|
||||
const thresholdValidInviteCount =
|
||||
rewardType === "valid_invite" ? Number(tier.thresholdValidInviteCount || 0) : 0;
|
||||
const rewardCoinAmount = Number(tier.rewardCoinAmount || 0);
|
||||
if (rewardType === "recharge" && thresholdCoinAmount <= 0) {
|
||||
throw new Error("累计充值金币门槛必须大于 0");
|
||||
@ -186,10 +203,20 @@ function payloadFromForm(form) {
|
||||
sort_order: index,
|
||||
};
|
||||
});
|
||||
if (form.enabled && tiers.filter((tier) => tier.status === "active").length === 0) {
|
||||
throw new Error("开启后至少需要一个启用档位");
|
||||
if (
|
||||
form.enabled &&
|
||||
tiers.filter((tier) => tier.status === "active").length === 0 &&
|
||||
perInviteInviterRewardCoinAmount <= 0 &&
|
||||
perInviteInviteeRewardCoinAmount <= 0
|
||||
) {
|
||||
throw new Error("开启后至少需要一个启用档位或每邀请一人奖励");
|
||||
}
|
||||
return { enabled: Boolean(form.enabled), tiers };
|
||||
return {
|
||||
enabled: Boolean(form.enabled),
|
||||
per_invite_inviter_reward_coin_amount: perInviteInviterRewardCoinAmount,
|
||||
per_invite_invitee_reward_coin_amount: perInviteInviteeRewardCoinAmount,
|
||||
tiers,
|
||||
};
|
||||
}
|
||||
|
||||
function formatNumber(value) {
|
||||
|
||||
@ -92,6 +92,12 @@
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.inlineFields {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(180px, 1fr));
|
||||
gap: var(--space-3);
|
||||
}
|
||||
|
||||
@media (max-width: 900px) {
|
||||
.summaryPanel {
|
||||
align-items: flex-start;
|
||||
@ -105,3 +111,9 @@
|
||||
gap: var(--space-3);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.inlineFields {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
@ -18,6 +18,8 @@ const claimStatusOptions = [
|
||||
const rewardTypeOptions = [
|
||||
["recharge", "累计充值"],
|
||||
["valid_invite", "有效人数"],
|
||||
["invite_inviter", "邀请人即时"],
|
||||
["invite_invitee", "被邀请人即时"],
|
||||
];
|
||||
|
||||
const columnsBase = [
|
||||
@ -202,10 +204,16 @@ function thresholdLabel(claim) {
|
||||
if (claim.rewardType === "valid_invite") {
|
||||
return `${formatNumber(claim.thresholdValidInviteCount)} 人`;
|
||||
}
|
||||
if (claim.rewardType === "invite_inviter" || claim.rewardType === "invite_invitee") {
|
||||
return claim.tierId ? `关联用户 ${claim.tierId}` : "-";
|
||||
}
|
||||
return `${formatNumber(claim.thresholdCoinAmount)} 金币`;
|
||||
}
|
||||
|
||||
function reachedLabel(claim) {
|
||||
if (claim.rewardType === "invite_inviter" || claim.rewardType === "invite_invitee") {
|
||||
return "1 人";
|
||||
}
|
||||
const unit = claim.rewardType === "valid_invite" ? "人" : "金币";
|
||||
return `${formatNumber(claim.reachedValue)} ${unit}`;
|
||||
}
|
||||
|
||||
@ -111,8 +111,8 @@ export function RoomRocketConfigEditor({
|
||||
<TextField
|
||||
required
|
||||
disabled={disabled}
|
||||
inputProps={{ min: 0, step: 1000 }}
|
||||
label="发射倒计时 ms"
|
||||
slotProps={{ htmlInput: { min: 0, step: 1000 } }}
|
||||
type="number"
|
||||
value={form.launchDelayMs}
|
||||
onChange={(event) => updateRoot(setForm, "launchDelayMs", event.target.value)}
|
||||
@ -127,8 +127,8 @@ export function RoomRocketConfigEditor({
|
||||
<TextField
|
||||
required
|
||||
disabled={disabled}
|
||||
inputProps={{ min: 0, step: 1000 }}
|
||||
label="广播延迟 ms"
|
||||
slotProps={{ htmlInput: { min: 0, step: 1000 } }}
|
||||
type="number"
|
||||
value={form.broadcastDelayMs}
|
||||
onChange={(event) => updateRoot(setForm, "broadcastDelayMs", event.target.value)}
|
||||
@ -175,8 +175,8 @@ export function RoomRocketConfigEditor({
|
||||
<TextField
|
||||
required
|
||||
disabled={disabled}
|
||||
inputProps={{ min: 1, step: 1 }}
|
||||
label="燃料阈值"
|
||||
slotProps={{ htmlInput: { min: 1, step: 1 } }}
|
||||
type="number"
|
||||
value={level.fuelThreshold}
|
||||
onChange={(event) =>
|
||||
@ -326,8 +326,8 @@ function RewardTable({ disabled, levelIndex, poolKey, resourceGroups, rewards, s
|
||||
<TextField
|
||||
required
|
||||
disabled={disabled}
|
||||
inputProps={{ min: 1, step: 1 }}
|
||||
label="权重"
|
||||
slotProps={{ htmlInput: { min: 1, step: 1 } }}
|
||||
type="number"
|
||||
value={reward.weight}
|
||||
onChange={(event) =>
|
||||
@ -338,8 +338,8 @@ function RewardTable({ disabled, levelIndex, poolKey, resourceGroups, rewards, s
|
||||
<TextField
|
||||
required
|
||||
disabled={disabled}
|
||||
inputProps={{ min: 1, step: 1 }}
|
||||
label="数量"
|
||||
slotProps={{ htmlInput: { min: 1, step: 1 } }}
|
||||
type="number"
|
||||
value={reward.quantity}
|
||||
onChange={(event) =>
|
||||
@ -428,8 +428,8 @@ function FuelRuleEditor({ disabled, form, setForm }) {
|
||||
/>
|
||||
<TextField
|
||||
disabled={disabled}
|
||||
inputProps={{ min: 0, step: 1 }}
|
||||
label="倍率 ppm"
|
||||
slotProps={{ htmlInput: { min: 0, step: 1 } }}
|
||||
type="number"
|
||||
value={rule.multiplierPpm}
|
||||
onChange={(event) =>
|
||||
@ -438,8 +438,8 @@ function FuelRuleEditor({ disabled, form, setForm }) {
|
||||
/>
|
||||
<TextField
|
||||
disabled={disabled}
|
||||
inputProps={{ min: 0, step: 1 }}
|
||||
label="固定燃料"
|
||||
slotProps={{ htmlInput: { min: 0, step: 1 } }}
|
||||
type="number"
|
||||
value={rule.fixedFuel}
|
||||
onChange={(event) => updateFuelRule(setForm, index, { fixedFuel: event.target.value })}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
68
src/shared/api/generated/schema.d.ts
vendored
68
src/shared/api/generated/schema.d.ts
vendored
@ -244,6 +244,22 @@ export interface paths {
|
||||
patch?: never;
|
||||
trace?: never;
|
||||
};
|
||||
"/admin/activity/invite-reward/claims": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
get: operations["listInviteActivityRewardClaims"];
|
||||
put?: never;
|
||||
post?: never;
|
||||
delete?: never;
|
||||
options?: never;
|
||||
head?: never;
|
||||
patch?: never;
|
||||
trace?: never;
|
||||
};
|
||||
"/admin/activity/cumulative-recharge-reward/grants": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
@ -420,6 +436,22 @@ export interface paths {
|
||||
patch?: never;
|
||||
trace?: never;
|
||||
};
|
||||
"/admin/activity/invite-reward/config": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
get: operations["getInviteActivityRewardConfig"];
|
||||
put: operations["updateInviteActivityRewardConfig"];
|
||||
post?: never;
|
||||
delete?: never;
|
||||
options?: never;
|
||||
head?: never;
|
||||
patch?: never;
|
||||
trace?: never;
|
||||
};
|
||||
"/admin/activity/room-turnover-reward/config": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
@ -3554,6 +3586,18 @@ export interface operations {
|
||||
200: components["responses"]["EmptyResponse"];
|
||||
};
|
||||
};
|
||||
listInviteActivityRewardClaims: {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
requestBody?: never;
|
||||
responses: {
|
||||
200: components["responses"]["EmptyResponse"];
|
||||
};
|
||||
};
|
||||
listCumulativeRechargeRewardGrants: {
|
||||
parameters: {
|
||||
query?: never;
|
||||
@ -3741,6 +3785,30 @@ export interface operations {
|
||||
200: components["responses"]["EmptyResponse"];
|
||||
};
|
||||
};
|
||||
getInviteActivityRewardConfig: {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
requestBody?: never;
|
||||
responses: {
|
||||
200: components["responses"]["EmptyResponse"];
|
||||
};
|
||||
};
|
||||
updateInviteActivityRewardConfig: {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
requestBody?: never;
|
||||
responses: {
|
||||
200: components["responses"]["EmptyResponse"];
|
||||
};
|
||||
};
|
||||
getRoomTurnoverRewardConfig: {
|
||||
parameters: {
|
||||
query?: never;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user