feat: 实现注册优惠码功能

- 支持创建/编辑/删除优惠码,设置赠送金额和使用限制
  - 注册页面实时验证优惠码并显示赠送金额
  - 支持 URL 参数自动填充 (?promo=CODE)
  - 添加优惠码验证接口速率限制
  - 使用数据库行锁防止并发超限
  - 新增后台优惠码管理页面,支持复制注册链接
This commit is contained in:
long
2026-01-10 13:14:35 +08:00
parent 7d1fe818be
commit d2fc14fb97
79 changed files with 17045 additions and 54 deletions

View File

@@ -9,6 +9,7 @@ import groupsAPI from './groups'
import accountsAPI from './accounts'
import proxiesAPI from './proxies'
import redeemAPI from './redeem'
import promoAPI from './promo'
import settingsAPI from './settings'
import systemAPI from './system'
import subscriptionsAPI from './subscriptions'
@@ -27,6 +28,7 @@ export const adminAPI = {
accounts: accountsAPI,
proxies: proxiesAPI,
redeem: redeemAPI,
promo: promoAPI,
settings: settingsAPI,
system: systemAPI,
subscriptions: subscriptionsAPI,
@@ -43,6 +45,7 @@ export {
accountsAPI,
proxiesAPI,
redeemAPI,
promoAPI,
settingsAPI,
systemAPI,
subscriptionsAPI,

View File

@@ -0,0 +1,69 @@
/**
* Admin Promo Codes API endpoints
*/
import { apiClient } from '../client'
import type {
PromoCode,
PromoCodeUsage,
CreatePromoCodeRequest,
UpdatePromoCodeRequest,
BasePaginationResponse
} from '@/types'
export async function list(
page: number = 1,
pageSize: number = 20,
filters?: {
status?: string
search?: string
}
): Promise<BasePaginationResponse<PromoCode>> {
const { data } = await apiClient.get<BasePaginationResponse<PromoCode>>('/admin/promo-codes', {
params: { page, page_size: pageSize, ...filters }
})
return data
}
export async function getById(id: number): Promise<PromoCode> {
const { data } = await apiClient.get<PromoCode>(`/admin/promo-codes/${id}`)
return data
}
export async function create(request: CreatePromoCodeRequest): Promise<PromoCode> {
const { data } = await apiClient.post<PromoCode>('/admin/promo-codes', request)
return data
}
export async function update(id: number, request: UpdatePromoCodeRequest): Promise<PromoCode> {
const { data } = await apiClient.put<PromoCode>(`/admin/promo-codes/${id}`, request)
return data
}
export async function deleteCode(id: number): Promise<{ message: string }> {
const { data } = await apiClient.delete<{ message: string }>(`/admin/promo-codes/${id}`)
return data
}
export async function getUsages(
id: number,
page: number = 1,
pageSize: number = 20
): Promise<BasePaginationResponse<PromoCodeUsage>> {
const { data } = await apiClient.get<BasePaginationResponse<PromoCodeUsage>>(
`/admin/promo-codes/${id}/usages`,
{ params: { page, page_size: pageSize } }
)
return data
}
const promoAPI = {
list,
getById,
create,
update,
delete: deleteCode,
getUsages
}
export default promoAPI

View File

@@ -113,6 +113,26 @@ export async function sendVerifyCode(
return data
}
/**
* Validate promo code response
*/
export interface ValidatePromoCodeResponse {
valid: boolean
bonus_amount?: number
error_code?: string
message?: string
}
/**
* Validate promo code (public endpoint, no auth required)
* @param code - Promo code to validate
* @returns Validation result with bonus amount if valid
*/
export async function validatePromoCode(code: string): Promise<ValidatePromoCodeResponse> {
const { data } = await apiClient.post<ValidatePromoCodeResponse>('/auth/validate-promo-code', { code })
return data
}
export const authAPI = {
login,
register,
@@ -123,7 +143,8 @@ export const authAPI = {
getAuthToken,
clearAuthToken,
getPublicSettings,
sendVerifyCode
sendVerifyCode,
validatePromoCode
}
export default authAPI