完善返利转入余额历史显示
This commit is contained in:
parent
650ddb2e39
commit
3ab40269b4
@ -390,7 +390,7 @@ func (h *UserHandler) GetUserUsage(c *gin.Context) {
|
|||||||
// GetBalanceHistory handles getting user's balance/concurrency change history
|
// GetBalanceHistory handles getting user's balance/concurrency change history
|
||||||
// GET /api/v1/admin/users/:id/balance-history
|
// GET /api/v1/admin/users/:id/balance-history
|
||||||
// Query params:
|
// Query params:
|
||||||
// - type: filter by record type (balance, admin_balance, concurrency, admin_concurrency, subscription)
|
// - type: filter by record type (balance, affiliate_balance, admin_balance, concurrency, admin_concurrency, subscription)
|
||||||
func (h *UserHandler) GetBalanceHistory(c *gin.Context) {
|
func (h *UserHandler) GetBalanceHistory(c *gin.Context) {
|
||||||
userID, err := strconv.ParseInt(c.Param("id"), 10, 64)
|
userID, err := strconv.ParseInt(c.Param("id"), 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
86
backend/internal/service/admin_balance_history_test.go
Normal file
86
backend/internal/service/admin_balance_history_test.go
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/Wei-Shaw/sub2api/internal/pkg/pagination"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMergeBalanceHistoryCodesIncludesAffiliateTransfersByDefault(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
now := time.Date(2026, 5, 3, 12, 0, 0, 0, time.UTC)
|
||||||
|
older := now.Add(-2 * time.Hour)
|
||||||
|
newer := now.Add(time.Hour)
|
||||||
|
|
||||||
|
usedBy := int64(10)
|
||||||
|
redeemCodes := []RedeemCode{
|
||||||
|
{
|
||||||
|
ID: 1,
|
||||||
|
Type: RedeemTypeBalance,
|
||||||
|
Value: 8,
|
||||||
|
Status: StatusUsed,
|
||||||
|
UsedBy: &usedBy,
|
||||||
|
UsedAt: &now,
|
||||||
|
CreatedAt: now,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: 2,
|
||||||
|
Type: RedeemTypeConcurrency,
|
||||||
|
Value: 1,
|
||||||
|
Status: StatusUsed,
|
||||||
|
UsedBy: &usedBy,
|
||||||
|
UsedAt: &older,
|
||||||
|
CreatedAt: older,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
affiliateCodes := []RedeemCode{
|
||||||
|
{
|
||||||
|
ID: -20,
|
||||||
|
Type: RedeemTypeAffiliateBalance,
|
||||||
|
Value: 3.5,
|
||||||
|
Status: StatusUsed,
|
||||||
|
UsedBy: &usedBy,
|
||||||
|
UsedAt: &newer,
|
||||||
|
CreatedAt: newer,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
got := mergeBalanceHistoryCodes(redeemCodes, affiliateCodes, pagination.PaginationParams{
|
||||||
|
Page: 1,
|
||||||
|
PageSize: 2,
|
||||||
|
})
|
||||||
|
|
||||||
|
require.Len(t, got, 2)
|
||||||
|
require.Equal(t, RedeemTypeAffiliateBalance, got[0].Type)
|
||||||
|
require.Equal(t, RedeemTypeBalance, got[1].Type)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMergeBalanceHistoryCodesPaginatesAfterCombiningSources(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
base := time.Date(2026, 5, 3, 12, 0, 0, 0, time.UTC)
|
||||||
|
usedBy := int64(10)
|
||||||
|
at := func(hours int) *time.Time {
|
||||||
|
v := base.Add(time.Duration(hours) * time.Hour)
|
||||||
|
return &v
|
||||||
|
}
|
||||||
|
|
||||||
|
got := mergeBalanceHistoryCodes(
|
||||||
|
[]RedeemCode{
|
||||||
|
{ID: 1, Type: RedeemTypeBalance, UsedBy: &usedBy, UsedAt: at(4), CreatedAt: *at(4)},
|
||||||
|
{ID: 2, Type: RedeemTypeConcurrency, UsedBy: &usedBy, UsedAt: at(2), CreatedAt: *at(2)},
|
||||||
|
},
|
||||||
|
[]RedeemCode{
|
||||||
|
{ID: -3, Type: RedeemTypeAffiliateBalance, UsedBy: &usedBy, UsedAt: at(3), CreatedAt: *at(3)},
|
||||||
|
{ID: -4, Type: RedeemTypeAffiliateBalance, UsedBy: &usedBy, UsedAt: at(1), CreatedAt: *at(1)},
|
||||||
|
},
|
||||||
|
pagination.PaginationParams{Page: 2, PageSize: 2},
|
||||||
|
)
|
||||||
|
|
||||||
|
require.Len(t, got, 2)
|
||||||
|
require.Equal(t, RedeemTypeConcurrency, got[0].Type)
|
||||||
|
require.Equal(t, int64(-4), got[1].ID)
|
||||||
|
}
|
||||||
@ -2,6 +2,7 @@ package service
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"database/sql"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
@ -973,16 +974,213 @@ func (s *adminServiceImpl) GetUserUsageStats(ctx context.Context, userID int64,
|
|||||||
// GetUserBalanceHistory returns paginated balance/concurrency change records for a user.
|
// GetUserBalanceHistory returns paginated balance/concurrency change records for a user.
|
||||||
func (s *adminServiceImpl) GetUserBalanceHistory(ctx context.Context, userID int64, page, pageSize int, codeType string) ([]RedeemCode, int64, float64, error) {
|
func (s *adminServiceImpl) GetUserBalanceHistory(ctx context.Context, userID int64, page, pageSize int, codeType string) ([]RedeemCode, int64, float64, error) {
|
||||||
params := pagination.PaginationParams{Page: page, PageSize: pageSize}
|
params := pagination.PaginationParams{Page: page, PageSize: pageSize}
|
||||||
|
if codeType == RedeemTypeAffiliateBalance {
|
||||||
|
codes, total, err := s.listAffiliateBalanceHistory(ctx, userID, params)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, 0, err
|
||||||
|
}
|
||||||
|
totalRecharged, err := s.redeemCodeRepo.SumPositiveBalanceByUser(ctx, userID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, 0, err
|
||||||
|
}
|
||||||
|
return codes, total, totalRecharged, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if codeType == "" {
|
||||||
|
return s.getAllUserBalanceHistory(ctx, userID, params)
|
||||||
|
}
|
||||||
|
|
||||||
codes, result, err := s.redeemCodeRepo.ListByUserPaginated(ctx, userID, params, codeType)
|
codes, result, err := s.redeemCodeRepo.ListByUserPaginated(ctx, userID, params, codeType)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, 0, 0, err
|
return nil, 0, 0, err
|
||||||
}
|
}
|
||||||
|
total := result.Total
|
||||||
// Aggregate total recharged amount (only once, regardless of type filter)
|
// Aggregate total recharged amount (only once, regardless of type filter)
|
||||||
totalRecharged, err := s.redeemCodeRepo.SumPositiveBalanceByUser(ctx, userID)
|
totalRecharged, err := s.redeemCodeRepo.SumPositiveBalanceByUser(ctx, userID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, 0, 0, err
|
return nil, 0, 0, err
|
||||||
}
|
}
|
||||||
return codes, result.Total, totalRecharged, nil
|
return codes, total, totalRecharged, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *adminServiceImpl) getAllUserBalanceHistory(ctx context.Context, userID int64, params pagination.PaginationParams) ([]RedeemCode, int64, float64, error) {
|
||||||
|
needed := params.Offset() + params.Limit()
|
||||||
|
if needed < params.Limit() {
|
||||||
|
needed = params.Limit()
|
||||||
|
}
|
||||||
|
|
||||||
|
redeemCodes, redeemTotal, err := s.listRedeemBalanceHistoryForMerge(ctx, userID, needed)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, 0, err
|
||||||
|
}
|
||||||
|
affiliateCodes, affiliateTotal, err := s.listAffiliateBalanceHistoryForMerge(ctx, userID, needed)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, 0, err
|
||||||
|
}
|
||||||
|
codes := mergeBalanceHistoryCodes(redeemCodes, affiliateCodes, params)
|
||||||
|
|
||||||
|
totalRecharged, err := s.redeemCodeRepo.SumPositiveBalanceByUser(ctx, userID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, 0, err
|
||||||
|
}
|
||||||
|
return codes, redeemTotal + affiliateTotal, totalRecharged, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *adminServiceImpl) listRedeemBalanceHistoryForMerge(ctx context.Context, userID int64, needed int) ([]RedeemCode, int64, error) {
|
||||||
|
if needed <= 0 {
|
||||||
|
return nil, 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
out []RedeemCode
|
||||||
|
total int64
|
||||||
|
)
|
||||||
|
for page := 1; len(out) < needed; page++ {
|
||||||
|
params := pagination.PaginationParams{Page: page, PageSize: 1000}
|
||||||
|
codes, result, err := s.redeemCodeRepo.ListByUserPaginated(ctx, userID, params, "")
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
if result != nil {
|
||||||
|
total = result.Total
|
||||||
|
}
|
||||||
|
out = append(out, codes...)
|
||||||
|
if len(codes) < params.Limit() || int64(len(out)) >= total {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(out) > needed {
|
||||||
|
out = out[:needed]
|
||||||
|
}
|
||||||
|
return out, total, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *adminServiceImpl) listAffiliateBalanceHistoryForMerge(ctx context.Context, userID int64, needed int) ([]RedeemCode, int64, error) {
|
||||||
|
if needed <= 0 {
|
||||||
|
return nil, 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
out []RedeemCode
|
||||||
|
total int64
|
||||||
|
)
|
||||||
|
for page := 1; len(out) < needed; page++ {
|
||||||
|
params := pagination.PaginationParams{Page: page, PageSize: 1000}
|
||||||
|
codes, currentTotal, err := s.listAffiliateBalanceHistory(ctx, userID, params)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
total = currentTotal
|
||||||
|
out = append(out, codes...)
|
||||||
|
if len(codes) < params.Limit() || int64(len(out)) >= total {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(out) > needed {
|
||||||
|
out = out[:needed]
|
||||||
|
}
|
||||||
|
return out, total, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *adminServiceImpl) listAffiliateBalanceHistory(ctx context.Context, userID int64, params pagination.PaginationParams) ([]RedeemCode, int64, error) {
|
||||||
|
if s == nil || s.entClient == nil || userID <= 0 {
|
||||||
|
return nil, 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
rows, err := s.entClient.QueryContext(ctx, `
|
||||||
|
SELECT id,
|
||||||
|
amount::double precision,
|
||||||
|
created_at
|
||||||
|
FROM user_affiliate_ledger
|
||||||
|
WHERE user_id = $1
|
||||||
|
AND action = 'transfer'
|
||||||
|
ORDER BY created_at DESC, id DESC
|
||||||
|
OFFSET $2
|
||||||
|
LIMIT $3`, userID, params.Offset(), params.Limit())
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
defer func() { _ = rows.Close() }()
|
||||||
|
|
||||||
|
codes := make([]RedeemCode, 0, params.Limit())
|
||||||
|
for rows.Next() {
|
||||||
|
var id int64
|
||||||
|
var amount float64
|
||||||
|
var createdAt time.Time
|
||||||
|
if err := rows.Scan(&id, &amount, &createdAt); err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
usedBy := userID
|
||||||
|
usedAt := createdAt
|
||||||
|
codes = append(codes, RedeemCode{
|
||||||
|
ID: -id,
|
||||||
|
Code: fmt.Sprintf("AFF-%d", id),
|
||||||
|
Type: RedeemTypeAffiliateBalance,
|
||||||
|
Value: amount,
|
||||||
|
Status: StatusUsed,
|
||||||
|
UsedBy: &usedBy,
|
||||||
|
UsedAt: &usedAt,
|
||||||
|
CreatedAt: createdAt,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
total, err := countAffiliateBalanceHistory(ctx, s.entClient, userID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
return codes, total, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func countAffiliateBalanceHistory(ctx context.Context, client *dbent.Client, userID int64) (int64, error) {
|
||||||
|
rows, err := client.QueryContext(ctx, `
|
||||||
|
SELECT COUNT(*)
|
||||||
|
FROM user_affiliate_ledger
|
||||||
|
WHERE user_id = $1
|
||||||
|
AND action = 'transfer'`, userID)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
defer func() { _ = rows.Close() }()
|
||||||
|
|
||||||
|
var total sql.NullInt64
|
||||||
|
if rows.Next() {
|
||||||
|
if err := rows.Scan(&total); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
if !total.Valid {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
return total.Int64, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func mergeBalanceHistoryCodes(redeemCodes, affiliateCodes []RedeemCode, params pagination.PaginationParams) []RedeemCode {
|
||||||
|
combined := append(append([]RedeemCode{}, redeemCodes...), affiliateCodes...)
|
||||||
|
sort.SliceStable(combined, func(i, j int) bool {
|
||||||
|
return redeemCodeHistoryTime(combined[i]).After(redeemCodeHistoryTime(combined[j]))
|
||||||
|
})
|
||||||
|
offset := params.Offset()
|
||||||
|
if offset >= len(combined) {
|
||||||
|
return []RedeemCode{}
|
||||||
|
}
|
||||||
|
end := offset + params.Limit()
|
||||||
|
if end > len(combined) {
|
||||||
|
end = len(combined)
|
||||||
|
}
|
||||||
|
return combined[offset:end]
|
||||||
|
}
|
||||||
|
|
||||||
|
func redeemCodeHistoryTime(code RedeemCode) time.Time {
|
||||||
|
if code.UsedAt != nil {
|
||||||
|
return *code.UsedAt
|
||||||
|
}
|
||||||
|
return code.CreatedAt
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *adminServiceImpl) BindUserAuthIdentity(ctx context.Context, userID int64, input AdminBindAuthIdentityInput) (*AdminBoundAuthIdentity, error) {
|
func (s *adminServiceImpl) BindUserAuthIdentity(ctx context.Context, userID int64, input AdminBindAuthIdentityInput) (*AdminBoundAuthIdentity, error) {
|
||||||
|
|||||||
@ -51,10 +51,11 @@ const (
|
|||||||
|
|
||||||
// Redeem type constants
|
// Redeem type constants
|
||||||
const (
|
const (
|
||||||
RedeemTypeBalance = domain.RedeemTypeBalance
|
RedeemTypeBalance = domain.RedeemTypeBalance
|
||||||
RedeemTypeConcurrency = domain.RedeemTypeConcurrency
|
RedeemTypeConcurrency = domain.RedeemTypeConcurrency
|
||||||
RedeemTypeSubscription = domain.RedeemTypeSubscription
|
RedeemTypeSubscription = domain.RedeemTypeSubscription
|
||||||
RedeemTypeInvitation = domain.RedeemTypeInvitation
|
RedeemTypeInvitation = domain.RedeemTypeInvitation
|
||||||
|
RedeemTypeAffiliateBalance = "affiliate_balance"
|
||||||
)
|
)
|
||||||
|
|
||||||
// PromoCode status constants
|
// PromoCode status constants
|
||||||
|
|||||||
@ -249,7 +249,7 @@ export interface BalanceHistoryResponse extends PaginatedResponse<BalanceHistory
|
|||||||
* @param id - User ID
|
* @param id - User ID
|
||||||
* @param page - Page number
|
* @param page - Page number
|
||||||
* @param pageSize - Items per page
|
* @param pageSize - Items per page
|
||||||
* @param type - Optional type filter (balance, admin_balance, concurrency, admin_concurrency, subscription)
|
* @param type - Optional type filter (balance, affiliate_balance, admin_balance, concurrency, admin_concurrency, subscription)
|
||||||
* @returns Paginated balance history with total_recharged
|
* @returns Paginated balance history with total_recharged
|
||||||
*/
|
*/
|
||||||
export async function getUserBalanceHistory(
|
export async function getUserBalanceHistory(
|
||||||
|
|||||||
@ -196,6 +196,7 @@ const totalPages = computed(() => Math.ceil(total.value / pageSize) || 1)
|
|||||||
const typeOptions = computed(() => [
|
const typeOptions = computed(() => [
|
||||||
{ value: '', label: t('admin.users.allTypes') },
|
{ value: '', label: t('admin.users.allTypes') },
|
||||||
{ value: 'balance', label: t('admin.users.typeBalance') },
|
{ value: 'balance', label: t('admin.users.typeBalance') },
|
||||||
|
{ value: 'affiliate_balance', label: t('admin.users.typeAffiliateBalance') },
|
||||||
{ value: 'admin_balance', label: t('admin.users.typeAdminBalance') },
|
{ value: 'admin_balance', label: t('admin.users.typeAdminBalance') },
|
||||||
{ value: 'concurrency', label: t('admin.users.typeConcurrency') },
|
{ value: 'concurrency', label: t('admin.users.typeConcurrency') },
|
||||||
{ value: 'admin_concurrency', label: t('admin.users.typeAdminConcurrency') },
|
{ value: 'admin_concurrency', label: t('admin.users.typeAdminConcurrency') },
|
||||||
@ -235,7 +236,7 @@ const loadHistory = async (page: number) => {
|
|||||||
const isAdminType = (type: string) => type === 'admin_balance' || type === 'admin_concurrency'
|
const isAdminType = (type: string) => type === 'admin_balance' || type === 'admin_concurrency'
|
||||||
|
|
||||||
// Helper: check if balance type (includes admin_balance)
|
// Helper: check if balance type (includes admin_balance)
|
||||||
const isBalanceType = (type: string) => type === 'balance' || type === 'admin_balance'
|
const isBalanceType = (type: string) => type === 'balance' || type === 'admin_balance' || type === 'affiliate_balance'
|
||||||
|
|
||||||
// Helper: check if subscription type
|
// Helper: check if subscription type
|
||||||
const isSubscriptionType = (type: string) => type === 'subscription'
|
const isSubscriptionType = (type: string) => type === 'subscription'
|
||||||
@ -291,6 +292,8 @@ const getItemTitle = (item: BalanceHistoryItem) => {
|
|||||||
switch (item.type) {
|
switch (item.type) {
|
||||||
case 'balance':
|
case 'balance':
|
||||||
return t('redeem.balanceAddedRedeem')
|
return t('redeem.balanceAddedRedeem')
|
||||||
|
case 'affiliate_balance':
|
||||||
|
return t('redeem.balanceAddedAffiliate')
|
||||||
case 'admin_balance':
|
case 'admin_balance':
|
||||||
return item.value >= 0 ? t('redeem.balanceAddedAdmin') : t('redeem.balanceDeductedAdmin')
|
return item.value >= 0 ? t('redeem.balanceAddedAdmin') : t('redeem.balanceDeductedAdmin')
|
||||||
case 'concurrency':
|
case 'concurrency':
|
||||||
|
|||||||
@ -1050,6 +1050,7 @@ export default {
|
|||||||
recentActivity: 'Recent Activity',
|
recentActivity: 'Recent Activity',
|
||||||
historyWillAppear: 'Your redemption history will appear here',
|
historyWillAppear: 'Your redemption history will appear here',
|
||||||
balanceAddedRedeem: 'Balance Added (Redeem)',
|
balanceAddedRedeem: 'Balance Added (Redeem)',
|
||||||
|
balanceAddedAffiliate: 'Balance Added (Affiliate Transfer)',
|
||||||
balanceAddedAdmin: 'Balance Added (Admin)',
|
balanceAddedAdmin: 'Balance Added (Admin)',
|
||||||
balanceDeductedAdmin: 'Balance Deducted (Admin)',
|
balanceDeductedAdmin: 'Balance Deducted (Admin)',
|
||||||
concurrencyAddedRedeem: 'Concurrency Added (Redeem)',
|
concurrencyAddedRedeem: 'Concurrency Added (Redeem)',
|
||||||
@ -1834,6 +1835,7 @@ export default {
|
|||||||
noBalanceHistory: 'No records found for this user',
|
noBalanceHistory: 'No records found for this user',
|
||||||
allTypes: 'All Types',
|
allTypes: 'All Types',
|
||||||
typeBalance: 'Balance (Redeem)',
|
typeBalance: 'Balance (Redeem)',
|
||||||
|
typeAffiliateBalance: 'Balance (Affiliate Transfer)',
|
||||||
typeAdminBalance: 'Balance (Admin)',
|
typeAdminBalance: 'Balance (Admin)',
|
||||||
typeConcurrency: 'Concurrency (Redeem)',
|
typeConcurrency: 'Concurrency (Redeem)',
|
||||||
typeAdminConcurrency: 'Concurrency (Admin)',
|
typeAdminConcurrency: 'Concurrency (Admin)',
|
||||||
|
|||||||
@ -1054,6 +1054,7 @@ export default {
|
|||||||
recentActivity: '最近活动',
|
recentActivity: '最近活动',
|
||||||
historyWillAppear: '您的兑换历史将显示在这里',
|
historyWillAppear: '您的兑换历史将显示在这里',
|
||||||
balanceAddedRedeem: '余额充值(兑换)',
|
balanceAddedRedeem: '余额充值(兑换)',
|
||||||
|
balanceAddedAffiliate: '余额充值(返利转入)',
|
||||||
balanceAddedAdmin: '余额充值(管理员)',
|
balanceAddedAdmin: '余额充值(管理员)',
|
||||||
balanceDeductedAdmin: '余额扣除(管理员)',
|
balanceDeductedAdmin: '余额扣除(管理员)',
|
||||||
concurrencyAddedRedeem: '并发增加(兑换)',
|
concurrencyAddedRedeem: '并发增加(兑换)',
|
||||||
@ -1891,6 +1892,7 @@ export default {
|
|||||||
noBalanceHistory: '暂无变动记录',
|
noBalanceHistory: '暂无变动记录',
|
||||||
allTypes: '全部类型',
|
allTypes: '全部类型',
|
||||||
typeBalance: '余额(兑换码)',
|
typeBalance: '余额(兑换码)',
|
||||||
|
typeAffiliateBalance: '余额(返利转入)',
|
||||||
typeAdminBalance: '余额(管理员调整)',
|
typeAdminBalance: '余额(管理员调整)',
|
||||||
typeConcurrency: '并发(兑换码)',
|
typeConcurrency: '并发(兑换码)',
|
||||||
typeAdminConcurrency: '并发(管理员调整)',
|
typeAdminConcurrency: '并发(管理员调整)',
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user