feat: 前台匿名浏览、登录引导、客服入口与返水增强
前台: - 未登录可浏览首页/赛事/赔率,下注等操作弹出登录引导(去登录/继续浏览) - 顶部新增客服入口与 iframe 弹窗 - 登录页支持暂不登录返回浏览 API: - 首页/赛事/冠军盘接口改为公开访问,支持 X-Locale 头 - JWT 守卫支持可选认证 返水: - 注单新增 is_cashbacked 字段,发放时自动标记 - 预览展示玩家余额,明确平台直发不从代理扣款 - 后台注单列表与玩家历史展示回水状态 其他: - 串关禁止同场重复选号(SAME_MATCH) - 补充结算资金流分析文档 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -288,6 +288,7 @@ export const adminPagesMs: Record<string, string> = {
|
||||
'bet.col.odds': 'Odds',
|
||||
'bet.col.payout': 'Bayaran',
|
||||
'bet.col.placed_at': 'Masa pertaruhan',
|
||||
'bet.col.cashbacked': 'Rebat dibayar',
|
||||
'bet.dialog.detail': 'Butiran pertaruhan',
|
||||
'bet.field.total_odds': 'Jumlah odds',
|
||||
'bet.field.currency': 'Mata wang',
|
||||
@@ -358,20 +359,22 @@ export const adminPagesMs: Record<string, string> = {
|
||||
'cashback.col.index': '#',
|
||||
'cashback.col.player': 'Pemain',
|
||||
'cashback.col.agent': 'Ejen',
|
||||
'cashback.col.balance': 'Baki semasa',
|
||||
'cashback.col.effective_stake': 'Stake berkesan',
|
||||
'cashback.col.rate': 'Kadar',
|
||||
'cashback.col.amount': 'Rebat',
|
||||
'cashback.confirm_issue': 'Sahkan bayaran',
|
||||
'cashback.cancel_issue': 'Batalkan',
|
||||
'cashback.confirm_prompt': 'Bayar rebat kelompok ini ke dompet pemain? Tindakan ini tidak boleh dibatalkan.',
|
||||
'cashback.confirm_prompt': 'Bayar rebat kelompok ini ke dompet pemain? Rebat dikreditkan terus oleh platform dan tidak ditolak daripada ejen. Tindakan ini tidak boleh dibatalkan.',
|
||||
'cashback.cancel_prompt': 'Batalkan kelompok menunggu ini? Tiada kredit dompet; boleh pratonton semula.',
|
||||
'cashback.status.CANCELLED': 'Dibatalkan',
|
||||
'cashback.rules_title': 'Peraturan rebat',
|
||||
'cashback.rule_period': 'Pilih julat tarikh. Taruhan dikira mengikut masa penyelesaian dalam tempoh tersebut.',
|
||||
'cashback.rule_eligible': 'Termasuk: taruhan selesai WON/LOST (tunggal ikut stake; parlay sekali ikut stake parlay). Tidak termasuk: belum selesai, dibatalkan, batal, push, dan kadar 0.',
|
||||
'cashback.rule_eligible': 'Termasuk: taruhan selesai WON/LOST (tunggal ikut stake; parlay sekali ikut stake parlay). Tidak termasuk: belum selesai, dibatalkan, batal, push, kadar 0, dan taruhan yang sudah dibayar rebat.',
|
||||
'cashback.rule_formula': 'Setiap taruhan: stake × kadar rebat. Jumlah diagregat mengikut pemain.',
|
||||
'cashback.rule_rate': 'Keutamaan kadar: pemain > ejen > global > kadar lalai ejen (cth. 0.01 = 1%).',
|
||||
'cashback.rule_flow': 'Aliran: pratonton (satu menunggu setiap tempoh) → semak → sahkan bayaran; batalkan jika tidak perlu. Tempoh dibayar tidak boleh pratonton semula.',
|
||||
'cashback.rule_platform': 'Bayaran: rebat dikreditkan ke baki tunai pemain oleh platform; tidak ditolak daripada kredit atau baki ejen.',
|
||||
'cashback.rule_note_zero': 'Jika 0, semak taruhan WON/LOST dalam tempoh dan kadar rebat > 0.',
|
||||
|
||||
'user.field.player_id': 'ID pemain',
|
||||
|
||||
@@ -303,6 +303,7 @@ export const adminPagesZh: Record<string, string> = {
|
||||
'bet.col.odds': '赔率',
|
||||
'bet.col.payout': '派彩',
|
||||
'bet.col.placed_at': '投注时间',
|
||||
'bet.col.cashbacked': '已回水',
|
||||
'bet.dialog.detail': '注单详情',
|
||||
'bet.field.total_odds': '总赔率',
|
||||
'bet.field.currency': '币种',
|
||||
@@ -373,20 +374,22 @@ export const adminPagesZh: Record<string, string> = {
|
||||
'cashback.col.index': '#',
|
||||
'cashback.col.player': '玩家',
|
||||
'cashback.col.agent': '所属代理',
|
||||
'cashback.col.balance': '当前余额',
|
||||
'cashback.col.effective_stake': '有效投注',
|
||||
'cashback.col.rate': '返水比例',
|
||||
'cashback.col.amount': '返水金额',
|
||||
'cashback.confirm_issue': '确认发放',
|
||||
'cashback.cancel_issue': '作废',
|
||||
'cashback.confirm_prompt': '确认向玩家钱包发放本批次返水?此操作不可撤销。',
|
||||
'cashback.confirm_prompt': '确认向玩家钱包发放本批次返水?返水由平台直接入账,不从代理扣款。此操作不可撤销。',
|
||||
'cashback.cancel_prompt': '确认作废该待发放批次?作废后不会入账,可重新生成预览。',
|
||||
'cashback.status.CANCELLED': '已作废',
|
||||
'cashback.rules_title': '返水规则说明',
|
||||
'cashback.rule_period': '选择开始/结束日期,统计该周期内、按注单结算时间落在区间内的有效投注。',
|
||||
'cashback.rule_eligible': '计入:已结算且结果为「赢」或「输」的注单(单关按本金,串关按整单本金计一次)。不计入:未结算、已取消、作废、走水,以及返水比例为 0 的注单。',
|
||||
'cashback.rule_eligible': '计入:已结算且结果为「赢」或「输」的注单(单关按本金,串关按整单本金计一次)。不计入:未结算、已取消、作废、走水,以及返水比例为 0 的注单;已返水过的注单不会重复计入。',
|
||||
'cashback.rule_formula': '单笔返水 = 投注本金 × 适用返水比例;同一玩家多笔注单汇总后生成一条返水明细。',
|
||||
'cashback.rule_rate': '返水比例优先级:玩家专属规则 > 代理线规则 > 全局规则 > 所属代理默认返水率(在代理/玩家管理中配置,如 0.01 表示 1%)。',
|
||||
'cashback.rule_flow': '操作流程:生成预览(同周期仅保留一条待发放)→ 核对明细 → 确认发放;不需要的可作废。已发放周期不可重复预览。',
|
||||
'cashback.rule_platform': '发放方式:返水由平台直接打入玩家现金余额,不从代理信用或余额中扣除。',
|
||||
'cashback.rule_note_zero': '预览为 0 时,请检查:周期内是否有已结算输赢注单、代理/玩家是否配置了大于 0 的返水率。',
|
||||
|
||||
'user.field.player_id': '玩家 ID',
|
||||
@@ -1174,6 +1177,7 @@ export const adminPagesEn: Record<string, string> = {
|
||||
'bet.col.odds': 'Odds',
|
||||
'bet.col.payout': 'Payout',
|
||||
'bet.col.placed_at': 'Placed at',
|
||||
'bet.col.cashbacked': 'Cashbacked',
|
||||
'bet.dialog.detail': 'Bet details',
|
||||
'bet.field.total_odds': 'Total odds',
|
||||
'bet.field.currency': 'Currency',
|
||||
@@ -1244,20 +1248,22 @@ export const adminPagesEn: Record<string, string> = {
|
||||
'cashback.col.index': '#',
|
||||
'cashback.col.player': 'Player',
|
||||
'cashback.col.agent': 'Agent',
|
||||
'cashback.col.balance': 'Balance',
|
||||
'cashback.col.effective_stake': 'Effective stake',
|
||||
'cashback.col.rate': 'Rate',
|
||||
'cashback.col.amount': 'Cashback',
|
||||
'cashback.confirm_issue': 'Confirm payout',
|
||||
'cashback.cancel_issue': 'Void',
|
||||
'cashback.confirm_prompt': 'Pay out this cashback batch to player wallets? This cannot be undone.',
|
||||
'cashback.confirm_prompt': 'Pay out this cashback batch to player wallets? Cashback is credited by the platform directly and is not deducted from agents. This cannot be undone.',
|
||||
'cashback.cancel_prompt': 'Void this pending batch? No wallet credit will be made; you can preview again.',
|
||||
'cashback.status.CANCELLED': 'Voided',
|
||||
'cashback.rules_title': 'Cashback rules',
|
||||
'cashback.rule_period': 'Pick a date range. Bets are included by settlement time within that period.',
|
||||
'cashback.rule_eligible': 'Included: settled bets with result WON or LOST (singles by stake; parlays counted once by parlay stake). Excluded: pending, cancelled, void, push, and zero-rate bets.',
|
||||
'cashback.rule_eligible': 'Included: settled bets with result WON or LOST (singles by stake; parlays counted once by parlay stake). Excluded: pending, cancelled, void, push, zero-rate bets, and bets already paid cashback.',
|
||||
'cashback.rule_formula': 'Per bet: stake × applicable cashback rate. Amounts are summed per player into one line item.',
|
||||
'cashback.rule_rate': 'Rate priority: player rule > agent rule > global rule > agent default rate (set under Agents/Players, e.g. 0.01 = 1%).',
|
||||
'cashback.rule_flow': 'Flow: preview (one pending batch per period) → review → confirm payout; void if not needed. Paid periods cannot be previewed again.',
|
||||
'cashback.rule_platform': 'Payout: cashback is credited to player cash balance by the platform; it is not deducted from agent credit or balance.',
|
||||
'cashback.rule_note_zero': 'If preview is 0, check for settled WON/LOST bets in the period and a cashback rate above 0.',
|
||||
|
||||
'user.field.player_id': 'Player ID',
|
||||
|
||||
@@ -229,6 +229,12 @@ async function openDetail(row: BetListRow) {
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="t('bet.col.cashbacked')" width="88" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag v-if="row.isCashbacked" type="success" size="small" effect="plain">✓</el-tag>
|
||||
<span v-else class="bet-content-empty">—</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="t('bet.col.placed_at')" min-width="168">
|
||||
<template #default="{ row }">{{ formatTime(row.placedAt) }}</template>
|
||||
</el-table-column>
|
||||
@@ -284,6 +290,9 @@ async function openDetail(row: BetListRow) {
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item :label="t('bet.col.placed_at')">{{ formatTime(detail.placedAt) }}</el-descriptions-item>
|
||||
<el-descriptions-item :label="t('bet.field.settled_at')">{{ formatTime(detail.settledAt) }}</el-descriptions-item>
|
||||
<el-descriptions-item :label="t('bet.col.cashbacked')">
|
||||
{{ detail.isCashbacked ? t('common.yes') : t('common.no') }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item :label="t('bet.field.request_id')" :span="2">{{ detail.requestId }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ interface CashbackPreviewItem {
|
||||
userId: string;
|
||||
username: string;
|
||||
agentUsername: string | null;
|
||||
availableBalance: string;
|
||||
effectiveStake: string;
|
||||
betCount: number;
|
||||
rate: string;
|
||||
@@ -288,6 +289,7 @@ onMounted(loadHistory);
|
||||
<li>{{ t('cashback.rule_formula') }}</li>
|
||||
<li>{{ t('cashback.rule_rate') }}</li>
|
||||
<li>{{ t('cashback.rule_flow') }}</li>
|
||||
<li>{{ t('cashback.rule_platform') }}</li>
|
||||
<li class="rules-note">{{ t('cashback.rule_note_zero') }}</li>
|
||||
</ul>
|
||||
</el-dialog>
|
||||
@@ -374,6 +376,18 @@ onMounted(loadHistory);
|
||||
>
|
||||
<template #default="{ row }">{{ row.agentUsername || '—' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="availableBalance"
|
||||
:label="t('cashback.col.balance')"
|
||||
min-width="110"
|
||||
align="right"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-tooltip :content="formatAmountFull(row.availableBalance)" placement="top">
|
||||
<span>{{ formatAmount(row.availableBalance) }}</span>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="betCount"
|
||||
:label="t('cashback.col.bet_count')"
|
||||
@@ -547,6 +561,9 @@ onMounted(loadHistory);
|
||||
<el-table-column prop="agentUsername" :label="t('cashback.col.agent')" min-width="100">
|
||||
<template #default="{ row }">{{ row.agentUsername || '—' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="availableBalance" :label="t('cashback.col.balance')" min-width="100" align="right">
|
||||
<template #default="{ row }">{{ formatAmount(row.availableBalance) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="betCount" :label="t('cashback.col.bet_count')" width="88" align="right" />
|
||||
<el-table-column prop="effectiveStake" :label="t('cashback.col.effective_stake')" min-width="110" align="right">
|
||||
<template #default="{ row }">{{ formatAmount(row.effectiveStake) }}</template>
|
||||
|
||||
@@ -25,6 +25,7 @@ export interface BetListRow {
|
||||
currency: string;
|
||||
placedAt: string;
|
||||
settledAt: string | null;
|
||||
isCashbacked: boolean;
|
||||
selectionCount: number;
|
||||
selectionSummary: string;
|
||||
selectionPreviews: BetSelectionPreview[];
|
||||
|
||||
Reference in New Issue
Block a user