feat: 后台抽奖活动管理(Stage 1)
- 服务层:12 个 /v1/admin/lottery 接口封装与 API 类型定义 - 活动列表:状态筛选、上架/暂停(带确认)、编辑、管理、发次数 - 奖品管理:3×3 九宫格可视化编辑、概率预览、保底奖警告、佣金元↔分转换、无限库存 - 规则设置:chance_sources 动态编辑,eligibility 简单/高级(JSON)双模式,本地 rulecaps 校验(深度≤8/节点≤64/≤8KB),Stage 1 未接线警示 - 手动发次数:自动生成幂等 source_ref - 路由 /dashboard/lottery、Commerce 菜单项、en-US/zh-CN 双语文案 - 对接文档见 vpn/aaaa.txt(Stage 1)
This commit is contained in:
@@ -0,0 +1,181 @@
|
||||
{
|
||||
"activityTime": "Activity Time",
|
||||
"cancel": "Cancel",
|
||||
"confirm": "Confirm",
|
||||
"confirmPause": "Are you sure you want to pause this activity?",
|
||||
"confirmPublish": "Are you sure you want to publish this activity?",
|
||||
"create": "Create",
|
||||
"createActivity": "Create Activity",
|
||||
"createSuccess": "Create Success",
|
||||
"detail": {
|
||||
"prizesTab": "Prizes",
|
||||
"rulesTab": "Rules"
|
||||
},
|
||||
"edit": "Edit",
|
||||
"editActivity": "Edit Activity",
|
||||
"form": {
|
||||
"cancel": "Cancel",
|
||||
"confirm": "Confirm",
|
||||
"description": "Description",
|
||||
"descriptionPlaceholder": "Activity description (Markdown supported)",
|
||||
"endAt": "End Time",
|
||||
"endAtAfterStartAt": "End time must be later than start time",
|
||||
"endAtRequired": "End time is required",
|
||||
"gridSize": "Grid Size",
|
||||
"selectEndAt": "Select time",
|
||||
"selectGridSize": "Select grid size",
|
||||
"selectStartAt": "Select time",
|
||||
"startAt": "Start Time",
|
||||
"startAtRequired": "Start time is required",
|
||||
"title": "Title",
|
||||
"titlePlaceholder": "Activity title",
|
||||
"titleRequired": "Title is required"
|
||||
},
|
||||
"grant": {
|
||||
"amount": "Amount",
|
||||
"description": "Manually grant draw chances to a user for activity: {{title}}",
|
||||
"sourceRef": "Source Reference",
|
||||
"sourceRefHint": "Idempotency key: the same reference only grants once. Repeated submissions will not grant again.",
|
||||
"sourceRefPlaceholder": "grant-20260709-abc123",
|
||||
"success": "Chances granted",
|
||||
"title": "Grant Chances",
|
||||
"userId": "User ID",
|
||||
"userIdHint": "Enter the numeric user ID. You can find it in the user management list.",
|
||||
"userIdPlaceholder": "e.g. 87433"
|
||||
},
|
||||
"grantChances": "Grant Chances",
|
||||
"gridSize": "Grid Size",
|
||||
"id": "ID",
|
||||
"manage": "Manage",
|
||||
"pause": "Pause",
|
||||
"pauseSuccess": "Pause Success",
|
||||
"pauseWarning": "Users will not be able to draw while the activity is paused.",
|
||||
"prize": {
|
||||
"addPrize": "Add Prize",
|
||||
"amountPrecision": "Amount supports at most 2 decimal places",
|
||||
"amountRequired": "Amount must be greater than 0",
|
||||
"amountUnitHint": "Entered in CNY (yuan), stored as cents; up to 2 decimal places",
|
||||
"amountYuan": "Commission Amount (CNY)",
|
||||
"cancel": "Cancel",
|
||||
"centerSlotHint": "Usually the draw button",
|
||||
"confirm": "Confirm",
|
||||
"confirmDelete": "Delete",
|
||||
"createSuccess": "Prize created",
|
||||
"createTitle": "Add Prize",
|
||||
"daysSuffix": "days",
|
||||
"delete": "Delete",
|
||||
"deleteDescription": "This prize will be removed from the grid. This action cannot be undone.",
|
||||
"deleteSuccess": "Prize deleted",
|
||||
"deleteTitle": "Delete this prize?",
|
||||
"durationDays": "Duration (days)",
|
||||
"durationDaysPlaceholder": "e.g. 1",
|
||||
"durationDaysRequired": "Duration days must be a positive integer",
|
||||
"editTitle": "Edit Prize",
|
||||
"fallbackBadge": "Fallback",
|
||||
"fallbackIgnoresWeight": "Fallback prize ignores weight",
|
||||
"iconUrl": "Icon URL",
|
||||
"iconUrlInvalid": "Must be a valid URL",
|
||||
"isFallback": "Fallback Prize",
|
||||
"isFallbackHint": "Awarded when limited prizes are sold out",
|
||||
"name": "Prize Name",
|
||||
"namePlaceholder": "e.g. 1-Day Pass",
|
||||
"nameRequired": "Name is required",
|
||||
"noFallbackHint": "Consider configuring a fallback prize so draws never fail when limited prizes run out.",
|
||||
"noFallbackTitle": "No fallback prize",
|
||||
"noRandomPrizes": "No prizes participate in the random draw yet",
|
||||
"noneHint": "No config needed. Consider enabling fallback so users always land on this prize when limited prizes run out.",
|
||||
"probabilityPreview": "Probability Preview",
|
||||
"selectType": "Select type",
|
||||
"slotLabel": "Slot",
|
||||
"soldOutBadge": "Sold Out",
|
||||
"stage2Hint": "Available in Stage 2",
|
||||
"stockShort": "Stock",
|
||||
"totalStock": "Total Stock",
|
||||
"totalStockPlaceholder": "e.g. 100",
|
||||
"totalStockRequired": "Total stock must be a positive integer",
|
||||
"type": "Prize Type",
|
||||
"typeCommission": "Commission",
|
||||
"typeCoupon": "Coupon",
|
||||
"typeCrypto": "Crypto",
|
||||
"typeManualOther": "Manual (Other)",
|
||||
"typeNone": "No Prize (Thanks)",
|
||||
"typePhysical": "Physical",
|
||||
"typePoints": "Points",
|
||||
"typeVpnDuration": "VPN Duration",
|
||||
"unlimitedStock": "Unlimited Stock",
|
||||
"updateSuccess": "Prize updated",
|
||||
"weight": "Weight",
|
||||
"weightHint": "0 = displayed but excluded from random draw",
|
||||
"weightInteger": "Weight must be an integer",
|
||||
"weightMin": "Weight must be at least 0",
|
||||
"weightShort": "Weight",
|
||||
"yuanSuffix": "CNY"
|
||||
},
|
||||
"publish": "Publish",
|
||||
"publishSuccess": "Publish Success",
|
||||
"publishWarning": "Once published, the activity becomes visible to users.",
|
||||
"rules": {
|
||||
"addCondition": "Add Condition",
|
||||
"addSource": "Add Source",
|
||||
"amount": "Amount",
|
||||
"cannotSimplify": "This rule tree is too complex for simple mode. Please edit it in Advanced mode.",
|
||||
"capsBytesExceeded": "Size {{value}} bytes exceeds limit {{limit}} bytes",
|
||||
"capsDepthExceeded": "Depth {{value}} exceeds limit {{limit}}",
|
||||
"capsExceeded": "Rule tree exceeds limits",
|
||||
"capsNodesExceeded": "Node count {{value}} exceeds limit {{limit}}",
|
||||
"chanceSources": "Chance Sources",
|
||||
"chanceSourcesDescription": "Configure how users earn draw chances. Each source can only appear once.",
|
||||
"conditionType": {
|
||||
"has_subscription": "Has Subscription",
|
||||
"invite_count": "Invite Count",
|
||||
"register_days": "Register Days",
|
||||
"subscription_type": "Subscription Type",
|
||||
"total_recharge": "Total Recharge",
|
||||
"user_tag": "User Tag"
|
||||
},
|
||||
"dailyLimit": "Daily limit (optional)",
|
||||
"duplicateSource": "Duplicate source",
|
||||
"eligibility": "Eligibility",
|
||||
"jsonInvalid": "Invalid JSON: {{error}}",
|
||||
"jsonMustBeObject": "Rule tree must be a JSON object",
|
||||
"jsonPlaceholder": "Rule tree JSON, e.g. {\"op\":\"AND\",\"children\":[...]}. Leave empty for no requirement.",
|
||||
"noConditions": "No conditions. Everyone is eligible to join.",
|
||||
"op": "Combine Conditions With",
|
||||
"opAnd": "AND",
|
||||
"opOr": "OR",
|
||||
"param": {
|
||||
"min": "Min Count",
|
||||
"minDaysRemaining": "Min Days Remaining",
|
||||
"minUsdt": "Min USDT",
|
||||
"planIds": "Plan IDs",
|
||||
"planIdsPlaceholder": "Comma separated, e.g. 1,2,3",
|
||||
"tags": "Tags",
|
||||
"tagsPlaceholder": "Comma separated, e.g. vip,beta",
|
||||
"windowDays": "Window Days (optional)"
|
||||
},
|
||||
"save": "Save Rules",
|
||||
"saveFailed": "Failed to save rules",
|
||||
"saveSuccess": "Rules saved",
|
||||
"selectSource": "Select source",
|
||||
"simpleMode": "Simple",
|
||||
"advancedMode": "Advanced (JSON)",
|
||||
"source": {
|
||||
"dailySignin": "Daily Sign-in",
|
||||
"inviteSuccess": "Invite Success",
|
||||
"manualGrant": "Manual Grant",
|
||||
"newSubscription": "New Subscription"
|
||||
},
|
||||
"stage1Warning": "Stage 1 notice",
|
||||
"stage1WarningDescription": "The rule context is not wired up in the current version (Stage 1). Configuring eligibility will prevent users from joining the lottery. It is recommended to keep it empty."
|
||||
},
|
||||
"status": {
|
||||
"draft": "Draft",
|
||||
"ended": "Ended",
|
||||
"paused": "Paused",
|
||||
"running": "Running"
|
||||
},
|
||||
"statusAll": "All",
|
||||
"statusLabel": "Status",
|
||||
"title": "Title",
|
||||
"updateSuccess": "Update Success"
|
||||
}
|
||||
@@ -14,6 +14,7 @@
|
||||
"Invite Management": "Invite Management",
|
||||
"Login": "Login",
|
||||
"Logs & Analytics": "Logs & Analytics",
|
||||
"Lottery Management": "Lottery Management",
|
||||
"Maintenance": "Maintenance",
|
||||
"Marketing Management": "Marketing Management",
|
||||
"Mobile": "Mobile",
|
||||
|
||||
@@ -0,0 +1,181 @@
|
||||
{
|
||||
"activityTime": "活动时间",
|
||||
"cancel": "取消",
|
||||
"confirm": "确认",
|
||||
"confirmPause": "确定要暂停该活动吗?",
|
||||
"confirmPublish": "确定要上架该活动吗?",
|
||||
"create": "创建",
|
||||
"createActivity": "创建活动",
|
||||
"createSuccess": "创建成功",
|
||||
"detail": {
|
||||
"prizesTab": "奖品",
|
||||
"rulesTab": "规则"
|
||||
},
|
||||
"edit": "编辑",
|
||||
"editActivity": "编辑活动",
|
||||
"form": {
|
||||
"cancel": "取消",
|
||||
"confirm": "确认",
|
||||
"description": "活动描述",
|
||||
"descriptionPlaceholder": "活动描述(支持 Markdown)",
|
||||
"endAt": "结束时间",
|
||||
"endAtAfterStartAt": "结束时间必须晚于开始时间",
|
||||
"endAtRequired": "请选择结束时间",
|
||||
"gridSize": "格子数",
|
||||
"selectEndAt": "选择时间",
|
||||
"selectGridSize": "选择格子数",
|
||||
"selectStartAt": "选择时间",
|
||||
"startAt": "开始时间",
|
||||
"startAtRequired": "请选择开始时间",
|
||||
"title": "标题",
|
||||
"titlePlaceholder": "活动标题",
|
||||
"titleRequired": "请输入活动标题"
|
||||
},
|
||||
"grant": {
|
||||
"amount": "发放数量",
|
||||
"description": "为用户手动发放活动「{{title}}」的抽奖次数",
|
||||
"sourceRef": "来源引用号",
|
||||
"sourceRefHint": "幂等键:相同的引用号只会发放一次,重复提交不会重复发放。",
|
||||
"sourceRefPlaceholder": "grant-20260709-abc123",
|
||||
"success": "次数发放成功",
|
||||
"title": "发放次数",
|
||||
"userId": "用户 ID",
|
||||
"userIdHint": "请输入用户的数字 ID,可在用户管理列表中查看。",
|
||||
"userIdPlaceholder": "例如:87433"
|
||||
},
|
||||
"grantChances": "发放次数",
|
||||
"gridSize": "格子数",
|
||||
"id": "ID",
|
||||
"manage": "管理",
|
||||
"pause": "暂停",
|
||||
"pauseSuccess": "暂停成功",
|
||||
"pauseWarning": "活动暂停期间,用户将无法抽奖。",
|
||||
"prize": {
|
||||
"addPrize": "添加奖品",
|
||||
"amountPrecision": "金额最多支持 2 位小数",
|
||||
"amountRequired": "金额必须大于 0",
|
||||
"amountUnitHint": "以元为单位输入,按分存储;最多支持 2 位小数",
|
||||
"amountYuan": "佣金金额(元)",
|
||||
"cancel": "取消",
|
||||
"centerSlotHint": "通常为抽奖按钮",
|
||||
"confirm": "确认",
|
||||
"confirmDelete": "删除",
|
||||
"createSuccess": "奖品创建成功",
|
||||
"createTitle": "添加奖品",
|
||||
"daysSuffix": "天",
|
||||
"delete": "删除",
|
||||
"deleteDescription": "该奖品将从九宫格中移除,此操作不可撤销。",
|
||||
"deleteSuccess": "奖品删除成功",
|
||||
"deleteTitle": "确定删除该奖品吗?",
|
||||
"durationDays": "时长(天)",
|
||||
"durationDaysPlaceholder": "例如:1",
|
||||
"durationDaysRequired": "时长天数必须为正整数",
|
||||
"editTitle": "编辑奖品",
|
||||
"fallbackBadge": "保底",
|
||||
"fallbackIgnoresWeight": "保底奖品不参与权重计算",
|
||||
"iconUrl": "图标 URL",
|
||||
"iconUrlInvalid": "请输入有效的 URL",
|
||||
"isFallback": "保底奖品",
|
||||
"isFallbackHint": "限量奖品抽完后发放该奖品",
|
||||
"name": "奖品名称",
|
||||
"namePlaceholder": "例如:1 天时长卡",
|
||||
"nameRequired": "请输入奖品名称",
|
||||
"noFallbackHint": "建议配置一个保底奖品,避免限量奖品抽完后抽奖失败。",
|
||||
"noFallbackTitle": "未配置保底奖品",
|
||||
"noRandomPrizes": "暂无参与随机抽取的奖品",
|
||||
"noneHint": "无需额外配置。建议开启保底,限量奖品抽完后用户将始终抽中该奖品。",
|
||||
"probabilityPreview": "概率预览",
|
||||
"selectType": "选择类型",
|
||||
"slotLabel": "格子",
|
||||
"soldOutBadge": "已抽完",
|
||||
"stage2Hint": "Stage 2 开放",
|
||||
"stockShort": "库存",
|
||||
"totalStock": "总库存",
|
||||
"totalStockPlaceholder": "例如:100",
|
||||
"totalStockRequired": "总库存必须为正整数",
|
||||
"type": "奖品类型",
|
||||
"typeCommission": "佣金",
|
||||
"typeCoupon": "优惠券",
|
||||
"typeCrypto": "加密货币",
|
||||
"typeManualOther": "人工发放(其他)",
|
||||
"typeNone": "谢谢参与",
|
||||
"typePhysical": "实物奖品",
|
||||
"typePoints": "积分",
|
||||
"typeVpnDuration": "免费时长",
|
||||
"unlimitedStock": "无限库存",
|
||||
"updateSuccess": "奖品更新成功",
|
||||
"weight": "权重",
|
||||
"weightHint": "权重为 0 时仅展示,不参与随机抽取",
|
||||
"weightInteger": "权重必须为整数",
|
||||
"weightMin": "权重不能小于 0",
|
||||
"weightShort": "权重",
|
||||
"yuanSuffix": "元"
|
||||
},
|
||||
"publish": "上架",
|
||||
"publishSuccess": "上架成功",
|
||||
"publishWarning": "上架后活动将对用户可见。",
|
||||
"rules": {
|
||||
"addCondition": "添加条件",
|
||||
"addSource": "添加来源",
|
||||
"amount": "次数",
|
||||
"cannotSimplify": "规则树结构较复杂,无法用简单模式编辑,请在高级(JSON)模式下修改",
|
||||
"capsBytesExceeded": "大小 {{value}} 字节超过上限 {{limit}} 字节",
|
||||
"capsDepthExceeded": "深度 {{value}} 超过上限 {{limit}}",
|
||||
"capsExceeded": "规则树超出限制",
|
||||
"capsNodesExceeded": "节点数 {{value}} 超过上限 {{limit}}",
|
||||
"chanceSources": "次数来源",
|
||||
"chanceSourcesDescription": "配置用户获取抽奖次数的方式,每种来源只能配置一次。",
|
||||
"conditionType": {
|
||||
"has_subscription": "持有订阅",
|
||||
"invite_count": "邀请人数",
|
||||
"register_days": "注册天数",
|
||||
"subscription_type": "订阅套餐类型",
|
||||
"total_recharge": "累计充值",
|
||||
"user_tag": "用户标签"
|
||||
},
|
||||
"dailyLimit": "每日上限(可选)",
|
||||
"duplicateSource": "来源不能重复",
|
||||
"eligibility": "参与门槛",
|
||||
"jsonInvalid": "JSON 无效:{{error}}",
|
||||
"jsonMustBeObject": "规则树必须是 JSON 对象",
|
||||
"jsonPlaceholder": "规则树 JSON,例如 {\"op\":\"AND\",\"children\":[...]},留空表示无门槛。",
|
||||
"noConditions": "暂无条件,所有用户均可参与。",
|
||||
"op": "条件组合方式",
|
||||
"opAnd": "且(AND)",
|
||||
"opOr": "或(OR)",
|
||||
"param": {
|
||||
"min": "最小数量",
|
||||
"minDaysRemaining": "剩余天数下限",
|
||||
"minUsdt": "最低 USDT 金额",
|
||||
"planIds": "套餐 ID",
|
||||
"planIdsPlaceholder": "逗号分隔,例如 1,2,3",
|
||||
"tags": "标签",
|
||||
"tagsPlaceholder": "逗号分隔,例如 vip,beta",
|
||||
"windowDays": "时间窗口天数(可选)"
|
||||
},
|
||||
"save": "保存规则",
|
||||
"saveFailed": "规则保存失败",
|
||||
"saveSuccess": "规则保存成功",
|
||||
"selectSource": "选择来源",
|
||||
"simpleMode": "简单模式",
|
||||
"advancedMode": "高级模式(JSON)",
|
||||
"source": {
|
||||
"dailySignin": "每日签到",
|
||||
"inviteSuccess": "邀请成功",
|
||||
"manualGrant": "手动发放",
|
||||
"newSubscription": "新订阅"
|
||||
},
|
||||
"stage1Warning": "Stage 1 提示",
|
||||
"stage1WarningDescription": "当前版本(Stage 1)规则上下文尚未接线,配置门槛将导致用户无法参与抽奖,建议保持为空。"
|
||||
},
|
||||
"status": {
|
||||
"draft": "草稿",
|
||||
"ended": "已结束",
|
||||
"paused": "已暂停",
|
||||
"running": "进行中"
|
||||
},
|
||||
"statusAll": "全部",
|
||||
"statusLabel": "状态",
|
||||
"title": "标题",
|
||||
"updateSuccess": "更新成功"
|
||||
}
|
||||
@@ -14,6 +14,7 @@
|
||||
"Invite Management": "邀请管理",
|
||||
"Login": "登录",
|
||||
"Logs & Analytics": "日志与分析",
|
||||
"Lottery Management": "抽奖管理",
|
||||
"Maintenance": "维护",
|
||||
"Marketing Management": "营销管理",
|
||||
"Mobile": "短信",
|
||||
|
||||
Reference in New Issue
Block a user