Skip to content

Commit c10d4dc

Browse files
authored
feat(assist-pool): 拼多多砍价/助力池通用能力 + activity 节点接入 (#44)
1 parent 97b9a50 commit c10d4dc

28 files changed

Lines changed: 22717 additions & 0 deletions

apps/admin/messages/en.json

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,49 @@
11
{
2+
"assistpool_title": "Assist Pools",
3+
"assistpool_new_config": "New Pool",
4+
"assistpool_name": "Name",
5+
"assistpool_name_placeholder": "Cut a Slice",
6+
"assistpool_alias": "Alias",
7+
"assistpool_alias_optional": "Alias (optional)",
8+
"assistpool_alias_placeholder": "cut-a-slice",
9+
"assistpool_alias_help": "Human-readable key, unique within your organization.",
10+
"assistpool_description": "Description",
11+
"assistpool_mode": "Mode",
12+
"assistpool_mode_decrement": "decrement (砍价)",
13+
"assistpool_mode_accumulate": "accumulate (集气)",
14+
"assistpool_target_amount": "Target amount",
15+
"assistpool_policy": "Contribution policy",
16+
"assistpool_policy_fixed": "fixed — each assist = N",
17+
"assistpool_policy_uniform": "uniform — random [min, max]",
18+
"assistpool_policy_decaying": "decaying — 砍一刀 tail throttle",
19+
"assistpool_amount_per_assist": "Amount per assist",
20+
"assistpool_min": "Min",
21+
"assistpool_max": "Max",
22+
"assistpool_base": "Base",
23+
"assistpool_tail_ratio": "Tail ratio",
24+
"assistpool_tail_floor": "Tail floor",
25+
"assistpool_per_assister_limit": "Per-assister limit",
26+
"assistpool_expires_in_seconds": "Expires in (seconds)",
27+
"assistpool_initiator_can_assist": "Initiator can assist",
28+
"assistpool_initiator_can_assist_help": "Allow the initiator to contribute to their own pool.",
29+
"assistpool_active": "Active",
30+
"assistpool_active_help": "Inactive configs reject new instance creation.",
31+
"assistpool_cancel": "Cancel",
32+
"assistpool_create": "Create",
33+
"assistpool_creating": "Creating…",
34+
"assistpool_created": "Assist-pool config created",
35+
"assistpool_failed_create": "Failed to create assist-pool config",
36+
"assistpool_failed_load_configs": "Failed to load configs:",
37+
"assistpool_no_configs": "No assist-pool configs yet. Create one to get started.",
38+
"assistpool_col_name": "Name",
39+
"assistpool_col_alias": "Alias",
40+
"assistpool_col_mode": "Mode",
41+
"assistpool_col_target": "Target",
42+
"assistpool_col_policy": "Policy",
43+
"assistpool_col_ttl": "TTL (s)",
44+
"assistpool_col_active": "Active",
45+
"assistpool_yes": "yes",
46+
"assistpool_no": "no",
247
"apikeys_title": "API Keys",
348
"apikeys_admin_keys": "Admin Keys",
449
"apikeys_client_credentials": "Client Credentials",
@@ -1126,6 +1171,7 @@
11261171
"nav_rank": "Ranking",
11271172
"nav_storage_box": "Storage Box",
11281173
"nav_lottery": "Lottery",
1174+
"nav_assist_pool": "Assist Pool",
11291175
"nav_group_overview": "Overview",
11301176
"nav_group_economy": "Economy",
11311177
"nav_group_operations": "Operations",

apps/admin/messages/zh.json

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,49 @@
11
{
2+
"assistpool_title": "助力池",
3+
"assistpool_new_config": "新建助力池",
4+
"assistpool_name": "名称",
5+
"assistpool_name_placeholder": "砍一刀",
6+
"assistpool_alias": "别名",
7+
"assistpool_alias_optional": "别名(可选)",
8+
"assistpool_alias_placeholder": "cut-a-slice",
9+
"assistpool_alias_help": "组织内唯一的可读标识,便于 API 引用。",
10+
"assistpool_description": "描述",
11+
"assistpool_mode": "模式",
12+
"assistpool_mode_decrement": "砍价(decrement)",
13+
"assistpool_mode_accumulate": "集气(accumulate)",
14+
"assistpool_target_amount": "目标值",
15+
"assistpool_policy": "贡献策略",
16+
"assistpool_policy_fixed": "固定 — 每次助力 = N",
17+
"assistpool_policy_uniform": "均匀随机 — [min, max]",
18+
"assistpool_policy_decaying": "递减 — 砍一刀尾部节流",
19+
"assistpool_amount_per_assist": "每次贡献量",
20+
"assistpool_min": "最小值",
21+
"assistpool_max": "最大值",
22+
"assistpool_base": "基础值",
23+
"assistpool_tail_ratio": "尾部比例",
24+
"assistpool_tail_floor": "尾部下限",
25+
"assistpool_per_assister_limit": "单人助力上限",
26+
"assistpool_expires_in_seconds": "过期时长(秒)",
27+
"assistpool_initiator_can_assist": "允许发起人自助",
28+
"assistpool_initiator_can_assist_help": "允许发起人为自己的助力池贡献。",
29+
"assistpool_active": "启用",
30+
"assistpool_active_help": "关闭后,新的实例创建请求会被拒绝。",
31+
"assistpool_cancel": "取消",
32+
"assistpool_create": "创建",
33+
"assistpool_creating": "创建中…",
34+
"assistpool_created": "助力池配置已创建",
35+
"assistpool_failed_create": "助力池配置创建失败",
36+
"assistpool_failed_load_configs": "配置加载失败:",
37+
"assistpool_no_configs": "还没有助力池配置。创建一个开始使用吧。",
38+
"assistpool_col_name": "名称",
39+
"assistpool_col_alias": "别名",
40+
"assistpool_col_mode": "模式",
41+
"assistpool_col_target": "目标",
42+
"assistpool_col_policy": "策略",
43+
"assistpool_col_ttl": "时长(秒)",
44+
"assistpool_col_active": "启用",
45+
"assistpool_yes": "",
46+
"assistpool_no": "",
247
"apikeys_title": "API 密钥",
348
"apikeys_admin_keys": "管理员密钥",
449
"apikeys_client_credentials": "客户端凭证",
@@ -1126,6 +1171,7 @@
11261171
"nav_rank": "天梯",
11271172
"nav_storage_box": "存储箱",
11281173
"nav_lottery": "抽奖",
1174+
"nav_assist_pool": "助力池",
11291175
"nav_group_overview": "概览",
11301176
"nav_group_economy": "经济系统",
11311177
"nav_group_operations": "运营活动",

apps/admin/src/components/AppSidebar.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
Contact,
1010
Dices,
1111
FolderOpen,
12+
HeartHandshake,
1213
GalleryHorizontal,
1314
Gift,
1415
KeyRound,
@@ -73,6 +74,7 @@ type NavItem = {
7374
| "/announcement"
7475
| "/activity"
7576
| "/lottery"
77+
| "/assist-pool"
7678
| "/friend-gift"
7779
| "/task"
7880
| "/media-library"
@@ -129,6 +131,7 @@ function getNavGroups(): NavGroup[] {
129131
{ title: m.nav_announcement, to: "/announcement", icon: Megaphone },
130132
{ title: m.nav_activity, to: "/activity", icon: PartyPopper },
131133
{ title: m.nav_lottery, to: "/lottery", icon: Dices },
134+
{ title: m.nav_assist_pool, to: "/assist-pool", icon: HeartHandshake },
132135
{ title: m.nav_gift, to: "/friend-gift", icon: Gift },
133136
{ title: m.nav_task, to: "/task", icon: ListTodo },
134137
],
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"
2+
3+
import { api } from "#/lib/api-client"
4+
import type {
5+
AssistPoolConfig,
6+
AssistPoolInstance,
7+
AssistPoolStatus,
8+
CreateAssistPoolConfigInput,
9+
UpdateAssistPoolConfigInput,
10+
} from "#/lib/types/assist-pool"
11+
12+
const CONFIGS_KEY = ["assist-pool-configs"] as const
13+
const INSTANCES_KEY = ["assist-pool-instances"] as const
14+
15+
export function useAssistPoolConfigs(
16+
filter: { activityId?: string; includeActivity?: boolean } = {},
17+
) {
18+
const params = new URLSearchParams()
19+
if (filter.activityId) params.set("activityId", filter.activityId)
20+
if (filter.includeActivity) params.set("includeActivity", "true")
21+
const qs = params.toString()
22+
return useQuery({
23+
queryKey: [
24+
...CONFIGS_KEY,
25+
filter.activityId ?? null,
26+
!!filter.includeActivity,
27+
],
28+
queryFn: () =>
29+
api.get<{ items: AssistPoolConfig[] }>(
30+
`/api/assist-pool/configs${qs ? `?${qs}` : ""}`,
31+
),
32+
select: (data) => data.items,
33+
})
34+
}
35+
36+
export function useAssistPoolConfig(key: string) {
37+
return useQuery({
38+
queryKey: [...CONFIGS_KEY, key],
39+
queryFn: () =>
40+
api.get<AssistPoolConfig>(`/api/assist-pool/configs/${key}`),
41+
enabled: !!key,
42+
})
43+
}
44+
45+
export function useCreateAssistPoolConfig() {
46+
const qc = useQueryClient()
47+
return useMutation({
48+
mutationFn: (input: CreateAssistPoolConfigInput) =>
49+
api.post<AssistPoolConfig>("/api/assist-pool/configs", input),
50+
onSuccess: () => qc.invalidateQueries({ queryKey: CONFIGS_KEY }),
51+
})
52+
}
53+
54+
export function useUpdateAssistPoolConfig() {
55+
const qc = useQueryClient()
56+
return useMutation({
57+
mutationFn: ({
58+
id,
59+
...input
60+
}: UpdateAssistPoolConfigInput & { id: string }) =>
61+
api.patch<AssistPoolConfig>(`/api/assist-pool/configs/${id}`, input),
62+
onSuccess: () => qc.invalidateQueries({ queryKey: CONFIGS_KEY }),
63+
})
64+
}
65+
66+
export function useDeleteAssistPoolConfig() {
67+
const qc = useQueryClient()
68+
return useMutation({
69+
mutationFn: (id: string) => api.delete(`/api/assist-pool/configs/${id}`),
70+
onSuccess: () => qc.invalidateQueries({ queryKey: CONFIGS_KEY }),
71+
})
72+
}
73+
74+
export function useAssistPoolInstances(filter: {
75+
configKey?: string
76+
initiatorEndUserId?: string
77+
status?: AssistPoolStatus
78+
limit?: number
79+
}) {
80+
const params = new URLSearchParams()
81+
if (filter.configKey) params.set("configKey", filter.configKey)
82+
if (filter.initiatorEndUserId)
83+
params.set("initiatorEndUserId", filter.initiatorEndUserId)
84+
if (filter.status) params.set("status", filter.status)
85+
if (filter.limit) params.set("limit", String(filter.limit))
86+
const qs = params.toString()
87+
return useQuery({
88+
queryKey: [
89+
...INSTANCES_KEY,
90+
filter.configKey ?? null,
91+
filter.initiatorEndUserId ?? null,
92+
filter.status ?? null,
93+
filter.limit ?? null,
94+
],
95+
queryFn: () =>
96+
api.get<{ items: AssistPoolInstance[] }>(
97+
`/api/assist-pool/instances${qs ? `?${qs}` : ""}`,
98+
),
99+
select: (data) => data.items,
100+
})
101+
}
102+
103+
export function useForceExpireInstance() {
104+
const qc = useQueryClient()
105+
return useMutation({
106+
mutationFn: (instanceId: string) =>
107+
api.post<AssistPoolInstance>(
108+
`/api/assist-pool/instances/${instanceId}/force-expire`,
109+
),
110+
onSuccess: () => qc.invalidateQueries({ queryKey: INSTANCES_KEY }),
111+
})
112+
}
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
export type AssistPoolMode = "accumulate" | "decrement"
2+
export type AssistPoolStatus = "in_progress" | "completed" | "expired"
3+
4+
export type RewardItem = {
5+
type: "item" | "entity" | "currency"
6+
id: string
7+
count: number
8+
}
9+
10+
export type AssistContributionPolicy =
11+
| { kind: "fixed"; amount: number }
12+
| { kind: "uniform"; min: number; max: number }
13+
| {
14+
kind: "decaying"
15+
base: number
16+
tailRatio: number
17+
tailFloor: number
18+
}
19+
20+
export type AssistPoolConfig = {
21+
id: string
22+
organizationId: string
23+
alias: string | null
24+
name: string
25+
description: string | null
26+
mode: AssistPoolMode
27+
targetAmount: number
28+
contributionPolicy: AssistContributionPolicy
29+
perAssisterLimit: number
30+
initiatorCanAssist: boolean
31+
expiresInSeconds: number
32+
maxInstancesPerInitiator: number | null
33+
rewards: RewardItem[]
34+
isActive: boolean
35+
activityId: string | null
36+
activityNodeId: string | null
37+
metadata: Record<string, unknown> | null
38+
createdAt: string
39+
updatedAt: string
40+
}
41+
42+
export type AssistPoolInstance = {
43+
id: string
44+
organizationId: string
45+
configId: string
46+
initiatorEndUserId: string
47+
status: AssistPoolStatus
48+
remaining: number
49+
targetAmount: number
50+
contributionCount: number
51+
expiresAt: string
52+
completedAt: string | null
53+
rewardGrantedAt: string | null
54+
version: number
55+
metadata: Record<string, unknown> | null
56+
createdAt: string
57+
updatedAt: string
58+
}
59+
60+
export type CreateAssistPoolConfigInput = {
61+
name: string
62+
alias?: string | null
63+
description?: string | null
64+
mode?: AssistPoolMode
65+
targetAmount: number
66+
contributionPolicy: AssistContributionPolicy
67+
perAssisterLimit?: number
68+
initiatorCanAssist?: boolean
69+
expiresInSeconds?: number
70+
maxInstancesPerInitiator?: number | null
71+
rewards?: RewardItem[]
72+
isActive?: boolean
73+
activityId?: string | null
74+
activityNodeId?: string | null
75+
metadata?: Record<string, unknown> | null
76+
}
77+
78+
export type UpdateAssistPoolConfigInput = {
79+
name?: string
80+
alias?: string | null
81+
description?: string | null
82+
perAssisterLimit?: number
83+
initiatorCanAssist?: boolean
84+
maxInstancesPerInitiator?: number | null
85+
rewards?: RewardItem[]
86+
isActive?: boolean
87+
activityId?: string | null
88+
activityNodeId?: string | null
89+
metadata?: Record<string, unknown> | null
90+
}

0 commit comments

Comments
 (0)