feat: 手动充值、邀请码注册与后台管理增强
新增玩家手动充值全流程(收款方式配置、充值下单/审核、钱包上分), 支持邀请码注册、邀请历史与专属返水率;完善后台代理/玩家管理与响应式操作栏, 并补充前台注册、充值页及多语言错误码。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -46,6 +46,14 @@ import AdminTableEmpty from '../components/AdminTableEmpty.vue';
|
||||
import PlayerWalletLedgerDialog from '../components/PlayerWalletLedgerDialog.vue';
|
||||
import WalletTransferContext from '../components/WalletTransferContext.vue';
|
||||
import AgentCreditContext from '../components/AgentCreditContext.vue';
|
||||
import RatePercentInput from '../components/RatePercentInput.vue';
|
||||
import { formatRatePercent, percentToDecimalRate, decimalRateToPercent } from '../utils/rate-percent';
|
||||
import InviteCodePanel from '../components/InviteCodePanel.vue';
|
||||
import InviteManageDialog from '../components/InviteManageDialog.vue';
|
||||
import AdminRowActionsDropdown from '../components/AdminRowActionsDropdown.vue';
|
||||
import AdminPlayerRowActions from '../components/AdminPlayerRowActions.vue';
|
||||
import AdminDetailGrid from '../components/AdminDetailGrid.vue';
|
||||
import AdminDetailItem from '../components/AdminDetailItem.vue';
|
||||
import { useAdminPlayerTransfer } from '../composables/useAdminPlayerTransfer';
|
||||
import {
|
||||
fetchAdminAgentCreditContext,
|
||||
@@ -53,6 +61,8 @@ import {
|
||||
type AgentCreditAdjustContext,
|
||||
} from '../utils/agent-credit-context';
|
||||
|
||||
const inviteDialogOpen = ref(false);
|
||||
|
||||
/* ─── Tier-1 agent list ─── */
|
||||
const tier1Agents = ref<AgentRow[]>([]);
|
||||
const tier1Total = ref(0);
|
||||
@@ -75,6 +85,10 @@ const subAgentLevelState = reactive<Record<number, SubAgentLevelState>>({});
|
||||
const agentLevelCounts = ref<Record<number, number>>({});
|
||||
const subAgentTableRefs = ref<Record<number, { toggleRowExpansion: (row: AgentRow) => void } | null>>({});
|
||||
|
||||
function setSubAgentTableRef(level: number, el: unknown) {
|
||||
subAgentTableRefs.value[level] = el as { toggleRowExpansion: (row: AgentRow) => void } | null;
|
||||
}
|
||||
|
||||
function ensureSubAgentState(level: number): SubAgentLevelState {
|
||||
if (!subAgentLevelState[level]) {
|
||||
subAgentLevelState[level] = {
|
||||
@@ -189,7 +203,8 @@ const bettingLimits = ref({
|
||||
});
|
||||
const settingsSaving = ref(false);
|
||||
const limitsSaving = ref(false);
|
||||
const hierarchySettings = ref({ maxAgentLevel: 0, defaultSubAgentCreditRatio: 50 });
|
||||
const hierarchySettings = ref({ maxAgentLevel: 0 });
|
||||
const DEFAULT_SUB_AGENT_CREDIT_RATIO = 50;
|
||||
const freezeAgentVisible = ref(false);
|
||||
const freezeAgentLoading = ref(false);
|
||||
const freezeAgentTarget = ref<AgentRow | null>(null);
|
||||
@@ -199,6 +214,9 @@ const freezeAgentForm = ref({
|
||||
unfreezeDirectPlayers: false,
|
||||
});
|
||||
const hierarchySaving = ref(false);
|
||||
const platformDirectRate = ref(0);
|
||||
const adminInviteRate = ref(0);
|
||||
const platformDirectSaving = ref(false);
|
||||
const resetAllowed = ref(false);
|
||||
const resetLoading = ref(false);
|
||||
const resetConfirmPhrase = ref('');
|
||||
@@ -296,7 +314,7 @@ function computeSubAgentCreditByRatio(available: number, ratioPercent: number):
|
||||
const creditQuickRatios = [10, 15, 20, 30] as const;
|
||||
|
||||
function computeDefaultSubAgentCreditLimit(available: number): number {
|
||||
return computeSubAgentCreditByRatio(available, hierarchySettings.value.defaultSubAgentCreditRatio ?? 50);
|
||||
return computeSubAgentCreditByRatio(available, DEFAULT_SUB_AGENT_CREDIT_RATIO);
|
||||
}
|
||||
|
||||
function applyCreateSubAgentCreditRatio(ratioPercent: number) {
|
||||
@@ -370,6 +388,7 @@ onMounted(() => {
|
||||
loadPlayerSettings();
|
||||
loadBettingLimits();
|
||||
loadHierarchySettings();
|
||||
loadPlatformDirectSettings();
|
||||
loadResetDatabaseStatus();
|
||||
loadAgentOptions();
|
||||
loadAllPlayers();
|
||||
@@ -453,6 +472,14 @@ function onSubAgentSizeChange(level: number, size: number) {
|
||||
loadSubAgentsAtLevel(level);
|
||||
}
|
||||
|
||||
function bindSubAgentPageChange(level: number) {
|
||||
return (p: number) => onSubAgentPageChange(level, p);
|
||||
}
|
||||
|
||||
function bindSubAgentSizeChange(level: number) {
|
||||
return (size: number) => onSubAgentSizeChange(level, size);
|
||||
}
|
||||
|
||||
function searchSubAgentsAtLevel(level: number) {
|
||||
ensureSubAgentState(level).page = 1;
|
||||
loadSubAgentsAtLevel(level);
|
||||
@@ -537,8 +564,14 @@ function onTier1AgentRowClick(row: AgentRow, _column: unknown, event: MouseEvent
|
||||
}
|
||||
|
||||
function onSubAgentRowClick(level: number, row: AgentRow, _column: unknown, event: MouseEvent) {
|
||||
const tableRef = { value: subAgentTableRefs.value[level] };
|
||||
onAgentRowClick(row, tableRef, _column, event);
|
||||
if (!shouldToggleExpandOnRowClick(event)) return;
|
||||
subAgentTableRefs.value[level]?.toggleRowExpansion(row);
|
||||
}
|
||||
|
||||
function bindSubAgentRowClick(level: number) {
|
||||
return (row: AgentRow, column: unknown, event: MouseEvent) => {
|
||||
onSubAgentRowClick(level, row, column, event);
|
||||
};
|
||||
}
|
||||
|
||||
watch(activeViewTab, (tab) => {
|
||||
@@ -682,9 +715,9 @@ async function savePlayerSettings() {
|
||||
async function loadHierarchySettings() {
|
||||
try {
|
||||
const { data } = await api.get('/admin/agents/settings/hierarchy');
|
||||
hierarchySettings.value = data.data;
|
||||
hierarchySettings.value = { maxAgentLevel: data.data?.maxAgentLevel ?? 0 };
|
||||
} catch {
|
||||
hierarchySettings.value = { maxAgentLevel: 0, defaultSubAgentCreditRatio: 50 };
|
||||
hierarchySettings.value = { maxAgentLevel: 0 };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -692,7 +725,7 @@ async function saveHierarchySettings() {
|
||||
hierarchySaving.value = true;
|
||||
try {
|
||||
const { data } = await api.put('/admin/agents/settings/hierarchy', hierarchySettings.value);
|
||||
hierarchySettings.value = data.data;
|
||||
hierarchySettings.value = { maxAgentLevel: data.data?.maxAgentLevel ?? hierarchySettings.value.maxAgentLevel };
|
||||
ElMessage.success(t('msg.saved'));
|
||||
} catch (e: unknown) {
|
||||
const err = e as { response?: { data?: { error?: string } } };
|
||||
@@ -703,6 +736,38 @@ async function saveHierarchySettings() {
|
||||
}
|
||||
}
|
||||
|
||||
async function loadPlatformDirectSettings() {
|
||||
try {
|
||||
const { data } = await api.get('/admin/settings/cashback/platform-direct');
|
||||
const payload = data.data as { platformDirectRate?: number | string; adminInviteRate?: number | string };
|
||||
platformDirectRate.value = decimalRateToPercent(payload?.platformDirectRate ?? 0);
|
||||
adminInviteRate.value = decimalRateToPercent(payload?.adminInviteRate ?? payload?.platformDirectRate ?? 0);
|
||||
} catch {
|
||||
platformDirectRate.value = 0;
|
||||
adminInviteRate.value = 0;
|
||||
}
|
||||
}
|
||||
|
||||
async function savePlatformDirectSettings() {
|
||||
platformDirectSaving.value = true;
|
||||
try {
|
||||
const { data } = await api.put('/admin/settings/cashback/platform-direct', {
|
||||
platformDirectRate: percentToDecimalRate(platformDirectRate.value),
|
||||
adminInviteRate: percentToDecimalRate(adminInviteRate.value),
|
||||
});
|
||||
const payload = data.data as { platformDirectRate?: number | string; adminInviteRate?: number | string };
|
||||
platformDirectRate.value = decimalRateToPercent(payload?.platformDirectRate ?? 0);
|
||||
adminInviteRate.value = decimalRateToPercent(payload?.adminInviteRate ?? payload?.platformDirectRate ?? 0);
|
||||
ElMessage.success(t('msg.saved'));
|
||||
} catch (e: unknown) {
|
||||
const err = e as { response?: { data?: { error?: string } } };
|
||||
ElMessage.error(err.response?.data?.error ?? t('msg.save_failed'));
|
||||
loadPlatformDirectSettings();
|
||||
} finally {
|
||||
platformDirectSaving.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
const walletLedgerVisible = ref(false);
|
||||
const walletLedgerPlayerId = ref('');
|
||||
const walletLedgerPlayerUsername = ref<string | null>(null);
|
||||
@@ -792,7 +857,7 @@ async function submitCreate() {
|
||||
phone: createForm.value.phone.trim() || undefined,
|
||||
email: createForm.value.email.trim() || undefined,
|
||||
creditLimit: createForm.value.creditLimit,
|
||||
cashbackRate: createForm.value.cashbackRate,
|
||||
cashbackRate: percentToDecimalRate(createForm.value.cashbackRate),
|
||||
maxSingleDeposit: createForm.value.maxSingleDeposit > 0 ? createForm.value.maxSingleDeposit : undefined,
|
||||
maxDailyDeposit: createForm.value.maxDailyDeposit > 0 ? createForm.value.maxDailyDeposit : undefined,
|
||||
};
|
||||
@@ -857,12 +922,16 @@ async function submitEditPlayer() {
|
||||
editPlayerLoading.value = true;
|
||||
try {
|
||||
const newPwd = editPlayerForm.value.newPassword.trim();
|
||||
const { data } = await api.put(`/admin/users/${editingId.value}`, {
|
||||
const payload: Record<string, unknown> = {
|
||||
username: editPlayerForm.value.username.trim(),
|
||||
phone: editPlayerForm.value.phone.trim() || undefined,
|
||||
email: editPlayerForm.value.email.trim() || undefined,
|
||||
password: newPwd || undefined,
|
||||
});
|
||||
cashbackRate: editPlayerForm.value.useCustomCashback
|
||||
? percentToDecimalRate(editPlayerForm.value.customCashbackRate ?? 0)
|
||||
: null,
|
||||
};
|
||||
const { data } = await api.put(`/admin/users/${editingId.value}`, payload);
|
||||
const updated = data.data as PlayerDetail;
|
||||
if (newPwd) {
|
||||
editPlayerForm.value.managedPassword = updated.managedPassword ?? newPwd;
|
||||
@@ -904,7 +973,7 @@ async function submitEditAgent() {
|
||||
status: editAgentForm.value.status,
|
||||
phone: editAgentForm.value.phone.trim() || undefined,
|
||||
email: editAgentForm.value.email.trim() || undefined,
|
||||
cashbackRate: editAgentForm.value.cashbackRate,
|
||||
cashbackRate: percentToDecimalRate(editAgentForm.value.cashbackRate),
|
||||
maxSingleDeposit: editAgentForm.value.maxSingleDeposit,
|
||||
maxDailyDeposit: editAgentForm.value.maxDailyDeposit,
|
||||
password: newPwd || undefined,
|
||||
@@ -1178,22 +1247,27 @@ function creditTypeLabel(type: string) {
|
||||
:disabled="hierarchySaving"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('agent.hierarchy.default_sub_credit_ratio')">
|
||||
<el-input-number
|
||||
v-model="hierarchySettings.defaultSubAgentCreditRatio"
|
||||
:min="1"
|
||||
:max="100"
|
||||
:step="5"
|
||||
controls-position="right"
|
||||
:disabled="hierarchySaving"
|
||||
/>
|
||||
<span class="list-settings-unit">%</span>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" :loading="hierarchySaving" @click="saveHierarchySettings">{{ t('common.save') }}</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
<div class="list-settings-block">
|
||||
<p class="list-settings-title">{{ t('cashback.settings_title') }}</p>
|
||||
<el-form inline size="small" class="settings-form">
|
||||
<el-form-item :label="t('cashback.platform_direct_default_rate')">
|
||||
<RatePercentInput v-model="platformDirectRate" />
|
||||
</el-form-item>
|
||||
<p class="list-settings-hint block-hint">{{ t('cashback.platform_direct_default_hint') }}</p>
|
||||
<el-form-item :label="t('cashback.admin_invite_default_rate')">
|
||||
<RatePercentInput v-model="adminInviteRate" />
|
||||
</el-form-item>
|
||||
<p class="list-settings-hint block-hint">{{ t('cashback.admin_invite_default_hint') }}</p>
|
||||
<el-form-item>
|
||||
<el-button type="primary" :loading="platformDirectSaving" @click="savePlatformDirectSettings">{{ t('common.save') }}</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
<div class="list-settings-block">
|
||||
<p class="list-settings-title">{{ t('user.betting_limits') }}</p>
|
||||
<el-form inline size="small" class="settings-form limits-form">
|
||||
@@ -1236,7 +1310,13 @@ function creditTypeLabel(type: string) {
|
||||
</el-collapse-item>
|
||||
</el-collapse>
|
||||
|
||||
<el-tabs v-model="activeViewTab" class="mgr-top-tabs">
|
||||
<InviteManageDialog v-model="inviteDialogOpen" />
|
||||
|
||||
<div class="mgr-tabs-shell">
|
||||
<el-button type="primary" class="invite-prominent-btn" @click="inviteDialogOpen = true">
|
||||
{{ t('invite.menu_btn') }}
|
||||
</el-button>
|
||||
<el-tabs v-model="activeViewTab" class="mgr-top-tabs mgr-top-tabs--with-invite">
|
||||
<!-- ─── Tab: 全部玩家(默认) ─── -->
|
||||
<el-tab-pane :label="`${t('user.type.player')} (${playerTotal})`" name="players">
|
||||
<section class="list-panel player-list-panel">
|
||||
@@ -1297,6 +1377,12 @@ function creditTypeLabel(type: string) {
|
||||
<el-tag size="small" type="info" class="affiliation-tag">{{ affiliationLabel(row) }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="t('user.col.invite_code')" min-width="100" show-overflow-tooltip>
|
||||
<template #default="{ row }">
|
||||
<code v-if="row.inviteCode" class="invite-code-cell">{{ row.inviteCode }}</code>
|
||||
<span v-else>—</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="t('user.col.balance')" min-width="128" align="right">
|
||||
<template #default="{ row }">
|
||||
<el-tooltip :content="`${formatAmountFull(row.availableBalance)} / ${formatAmountFull(row.frozenBalance)}`" placement="top">
|
||||
@@ -1310,32 +1396,17 @@ function creditTypeLabel(type: string) {
|
||||
<span class="amount-compact">{{ formatAmount(row.totalStake) }} / {{ formatAmount(row.totalReturn) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="t('user.col.last_login')" width="108">
|
||||
<el-table-column :label="t('common.actions')" min-width="360" align="center" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-tooltip v-if="row.lastLoginAt" :content="formatTime(row.lastLoginAt)" placement="top">
|
||||
<span>{{ formatLastLogin(row.lastLoginAt) }}</span>
|
||||
</el-tooltip>
|
||||
<span v-else class="text-muted">{{ t('common.never_login') }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="t('user.col.created')" width="108">
|
||||
<template #default="{ row }">
|
||||
<el-tooltip :content="formatTime(row.createdAt)" placement="top">
|
||||
<span>{{ formatLastLogin(row.createdAt) }}</span>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="t('common.actions')" width="420" fixed="right" align="center">
|
||||
<template #default="{ row }">
|
||||
<div class="action-btns">
|
||||
<el-button size="small" link type="primary" @click="openDetailPlayer(row.id)">{{ t('common.detail') }}</el-button>
|
||||
<el-button size="small" link type="primary" @click="openPlayerWalletLedger(row.id, row.username)">{{ t('user.action.view_wallet_ledger') }}</el-button>
|
||||
<el-button size="small" link type="primary" @click="openEditPlayer(row.id)">{{ t('common.edit') }}</el-button>
|
||||
<el-button size="small" link type="success" @click="openTransfer('deposit', row)">{{ t('common.topup') }}</el-button>
|
||||
<el-button size="small" link type="warning" @click="openTransfer('withdraw', row)">{{ t('agent_portal.withdraw_btn_label') }}</el-button>
|
||||
<el-button v-if="row.status === 'ACTIVE'" size="small" link type="warning" @click="toggleFreezePlayer(row)">{{ t('common.freeze') }}</el-button>
|
||||
<el-button v-else size="small" link type="primary" @click="toggleFreezePlayer(row)">{{ t('common.unfreeze') }}</el-button>
|
||||
</div>
|
||||
<AdminPlayerRowActions
|
||||
:row="row"
|
||||
@detail="openDetailPlayer(row.id)"
|
||||
@ledger="openPlayerWalletLedger(row.id, row.username)"
|
||||
@edit="openEditPlayer(row.id)"
|
||||
@deposit="openTransfer('deposit', row)"
|
||||
@withdraw="openTransfer('withdraw', row)"
|
||||
@freeze="toggleFreezePlayer(row)"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
@@ -1408,6 +1479,12 @@ function creditTypeLabel(type: string) {
|
||||
<template #empty><AdminTableEmpty /></template>
|
||||
<el-table-column prop="id" label="ID" width="60" />
|
||||
<el-table-column prop="username" :label="t('user.col.username')" min-width="100" />
|
||||
<el-table-column :label="t('user.col.invite_code')" min-width="96" show-overflow-tooltip>
|
||||
<template #default="{ row: player }">
|
||||
<code v-if="player.inviteCode" class="invite-code-cell">{{ player.inviteCode }}</code>
|
||||
<span v-else>—</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="t('common.status')" width="80">
|
||||
<template #default="{ row: player }">
|
||||
<el-tag :type="statusTagType(player.status)" size="small">{{ statusLabel(player.status) }}</el-tag>
|
||||
@@ -1426,24 +1503,17 @@ function creditTypeLabel(type: string) {
|
||||
<span class="amount-compact">{{ formatAmount(player.totalStake) }} / {{ formatAmount(player.totalReturn) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="t('user.col.last_login')" width="100">
|
||||
<el-table-column :label="t('common.actions')" min-width="320" align="center">
|
||||
<template #default="{ row: player }">
|
||||
<el-tooltip v-if="player.lastLoginAt" :content="formatTime(player.lastLoginAt)" placement="top">
|
||||
<span>{{ formatLastLogin(player.lastLoginAt) }}</span>
|
||||
</el-tooltip>
|
||||
<span v-else class="text-muted">{{ t('common.never_login') }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="t('common.actions')" width="340" fixed="right" align="center">
|
||||
<template #default="{ row: player }">
|
||||
<div class="action-btns">
|
||||
<el-button size="small" link type="primary" @click="openDetailPlayer(player.id)">{{ t('common.detail') }}</el-button>
|
||||
<el-button size="small" link type="primary" @click="openEditPlayer(player.id)">{{ t('common.edit') }}</el-button>
|
||||
<el-button size="small" link type="success" @click="openTransfer('deposit', player)">{{ t('common.topup') }}</el-button>
|
||||
<el-button size="small" link type="warning" @click="openTransfer('withdraw', player)">{{ t('agent_portal.withdraw_btn_label') }}</el-button>
|
||||
<el-button v-if="player.status === 'ACTIVE'" size="small" link type="warning" @click="toggleFreezePlayer(player)">{{ t('common.freeze') }}</el-button>
|
||||
<el-button v-else size="small" link type="primary" @click="toggleFreezePlayer(player)">{{ t('common.unfreeze') }}</el-button>
|
||||
</div>
|
||||
<AdminPlayerRowActions
|
||||
:row="player"
|
||||
@detail="openDetailPlayer(player.id)"
|
||||
@ledger="openPlayerWalletLedger(player.id, player.username)"
|
||||
@edit="openEditPlayer(player.id)"
|
||||
@deposit="openTransfer('deposit', player)"
|
||||
@withdraw="openTransfer('withdraw', player)"
|
||||
@freeze="toggleFreezePlayer(player)"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
@@ -1472,18 +1542,22 @@ function creditTypeLabel(type: string) {
|
||||
<el-table-column prop="directPlayerCount" :label="t('agent.col.direct_players')" min-width="72" align="center" />
|
||||
<el-table-column prop="childAgentCount" :label="t('agent.col.sub_agents')" min-width="72" align="center" />
|
||||
<el-table-column :label="t('agent.col.cashback')" min-width="80" align="right">
|
||||
<template #default="{ row }">{{ row.cashbackRate }}</template>
|
||||
<template #default="{ row }">{{ formatRatePercent(row.cashbackRate) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="t('common.actions')" width="400" fixed="right" align="center">
|
||||
<el-table-column :label="t('common.actions')" width="96" align="center" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<div class="action-btns" @click.stop>
|
||||
<el-button size="small" link type="primary" @click="openDetailAgent(row.userId)">{{ t('common.detail') }}</el-button>
|
||||
<el-button size="small" link type="primary" @click="openEditAgent(row.userId)">{{ t('common.edit') }}</el-button>
|
||||
<el-button size="small" link type="primary" @click="openCredit(row.userId)">{{ t('common.adjust_credit') }}</el-button>
|
||||
<el-button v-if="canAgentCreateSub(row)" size="small" link type="primary" @click="openCreateSubAgent(row.userId)">{{ childAgentActionLabel(row.level) }}</el-button>
|
||||
<el-button v-if="row.status === 'ACTIVE'" size="small" link type="warning" @click="toggleFreezeAgent(row)">{{ t('common.freeze') }}</el-button>
|
||||
<el-button v-else size="small" link type="primary" @click="toggleFreezeAgent(row)">{{ t('common.unfreeze') }}</el-button>
|
||||
</div>
|
||||
<AdminRowActionsDropdown>
|
||||
<el-dropdown-item @click="openDetailAgent(row.userId)">{{ t('common.detail') }}</el-dropdown-item>
|
||||
<el-dropdown-item @click="openEditAgent(row.userId)">{{ t('common.edit') }}</el-dropdown-item>
|
||||
<el-dropdown-item @click="openCredit(row.userId)">{{ t('common.adjust_credit') }}</el-dropdown-item>
|
||||
<el-dropdown-item v-if="canAgentCreateSub(row)" @click="openCreateSubAgent(row.userId)">
|
||||
{{ childAgentActionLabel(row.level) }}
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item v-if="row.status === 'ACTIVE'" divided @click="toggleFreezeAgent(row)">
|
||||
<span class="action-warning">{{ t('common.freeze') }}</span>
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item v-else divided @click="toggleFreezeAgent(row)">{{ t('common.unfreeze') }}</el-dropdown-item>
|
||||
</AdminRowActionsDropdown>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
@@ -1545,14 +1619,14 @@ function creditTypeLabel(type: string) {
|
||||
</div>
|
||||
<div class="table-wrap">
|
||||
<el-table
|
||||
:ref="(el) => { subAgentTableRefs[agentLevel] = el as { toggleRowExpansion: (row: AgentRow) => void } | null }"
|
||||
:ref="(el: unknown) => setSubAgentTableRef(agentLevel, el)"
|
||||
:data="ensureSubAgentState(agentLevel).agents"
|
||||
stripe
|
||||
row-key="userId"
|
||||
:row-class-name="expandableTableRowClassName"
|
||||
class="expandable-table compact-agent-table"
|
||||
@expand-change="onExpandChange"
|
||||
@row-click="(row, column, event) => onSubAgentRowClick(agentLevel, row, column, event)"
|
||||
@row-click="bindSubAgentRowClick(agentLevel)"
|
||||
>
|
||||
<template #empty>
|
||||
<AdminTableEmpty />
|
||||
@@ -1570,6 +1644,12 @@ function creditTypeLabel(type: string) {
|
||||
<template #empty><AdminTableEmpty /></template>
|
||||
<el-table-column prop="id" label="ID" width="60" />
|
||||
<el-table-column prop="username" :label="t('user.col.username')" min-width="100" />
|
||||
<el-table-column :label="t('user.col.invite_code')" min-width="96" show-overflow-tooltip>
|
||||
<template #default="{ row: player }">
|
||||
<code v-if="player.inviteCode" class="invite-code-cell">{{ player.inviteCode }}</code>
|
||||
<span v-else>—</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="t('common.status')" width="80">
|
||||
<template #default="{ row: player }">
|
||||
<el-tag :type="statusTagType(player.status)" size="small">{{ statusLabel(player.status) }}</el-tag>
|
||||
@@ -1580,14 +1660,17 @@ function creditTypeLabel(type: string) {
|
||||
<span class="amount-compact">{{ formatAmount(player.availableBalance) }} / {{ formatAmount(player.frozenBalance) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="t('common.actions')" width="340" fixed="right" align="center">
|
||||
<el-table-column :label="t('common.actions')" min-width="280" align="center">
|
||||
<template #default="{ row: player }">
|
||||
<div class="action-btns">
|
||||
<el-button size="small" link type="primary" @click="openDetailPlayer(player.id)">{{ t('common.detail') }}</el-button>
|
||||
<el-button size="small" link type="primary" @click="openEditPlayer(player.id)">{{ t('common.edit') }}</el-button>
|
||||
<el-button size="small" link type="success" @click="openTransfer('deposit', player)">{{ t('common.topup') }}</el-button>
|
||||
<el-button size="small" link type="warning" @click="openTransfer('withdraw', player)">{{ t('agent_portal.withdraw_btn_label') }}</el-button>
|
||||
</div>
|
||||
<AdminPlayerRowActions
|
||||
:row="player"
|
||||
@detail="openDetailPlayer(player.id)"
|
||||
@ledger="openPlayerWalletLedger(player.id, player.username)"
|
||||
@edit="openEditPlayer(player.id)"
|
||||
@deposit="openTransfer('deposit', player)"
|
||||
@withdraw="openTransfer('withdraw', player)"
|
||||
@freeze="toggleFreezePlayer(player)"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
@@ -1613,16 +1696,20 @@ function creditTypeLabel(type: string) {
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="directPlayerCount" :label="t('agent.col.direct_players')" min-width="72" align="center" />
|
||||
<el-table-column :label="t('common.actions')" width="400" fixed="right" align="center">
|
||||
<el-table-column :label="t('common.actions')" width="96" align="center" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<div class="action-btns" @click.stop>
|
||||
<el-button size="small" link type="primary" @click="openDetailAgent(row.userId)">{{ t('common.detail') }}</el-button>
|
||||
<el-button size="small" link type="primary" @click="openEditAgent(row.userId)">{{ t('common.edit') }}</el-button>
|
||||
<el-button size="small" link type="primary" @click="openCredit(row.userId)">{{ t('common.adjust_credit') }}</el-button>
|
||||
<el-button v-if="canAgentCreateSub(row)" size="small" link type="primary" @click="openCreateSubAgent(row.userId)">{{ childAgentActionLabel(row.level) }}</el-button>
|
||||
<el-button v-if="subAgentAccountStatus(row) === 'ACTIVE'" size="small" link type="warning" @click="toggleFreezeAgent(row)">{{ t('common.freeze') }}</el-button>
|
||||
<el-button v-else size="small" link type="primary" @click="toggleFreezeAgent(row)">{{ t('common.unfreeze') }}</el-button>
|
||||
</div>
|
||||
<AdminRowActionsDropdown>
|
||||
<el-dropdown-item @click="openDetailAgent(row.userId)">{{ t('common.detail') }}</el-dropdown-item>
|
||||
<el-dropdown-item @click="openEditAgent(row.userId)">{{ t('common.edit') }}</el-dropdown-item>
|
||||
<el-dropdown-item @click="openCredit(row.userId)">{{ t('common.adjust_credit') }}</el-dropdown-item>
|
||||
<el-dropdown-item v-if="canAgentCreateSub(row)" @click="openCreateSubAgent(row.userId)">
|
||||
{{ childAgentActionLabel(row.level) }}
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item v-if="subAgentAccountStatus(row) === 'ACTIVE'" divided @click="toggleFreezeAgent(row)">
|
||||
<span class="action-warning">{{ t('common.freeze') }}</span>
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item v-else divided @click="toggleFreezeAgent(row)">{{ t('common.unfreeze') }}</el-dropdown-item>
|
||||
</AdminRowActionsDropdown>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
@@ -1635,13 +1722,14 @@ function creditTypeLabel(type: string) {
|
||||
:page-sizes="[10, 20, 50, 100]"
|
||||
layout="total, sizes, prev, pager, next"
|
||||
background
|
||||
@current-change="(p) => onSubAgentPageChange(agentLevel, p)"
|
||||
@size-change="(size) => onSubAgentSizeChange(agentLevel, size)"
|
||||
@current-change="bindSubAgentPageChange(agentLevel)"
|
||||
@size-change="bindSubAgentSizeChange(agentLevel)"
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</div>
|
||||
|
||||
<!-- ═══════════ DIALOGS ═══════════ -->
|
||||
|
||||
@@ -1798,14 +1886,7 @@ function creditTypeLabel(type: string) {
|
||||
<div v-else class="field-hint">{{ t('agent.hint.credit_limit') }}</div>
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('agent.field.cashback_rate')">
|
||||
<el-input-number
|
||||
v-model="createForm.cashbackRate"
|
||||
:min="0"
|
||||
:max="1"
|
||||
:step="0.001"
|
||||
:precision="4"
|
||||
style="width: 100%"
|
||||
/>
|
||||
<RatePercentInput v-model="createForm.cashbackRate" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('agent.field.max_single_deposit')">
|
||||
<el-input-number v-model="createForm.maxSingleDeposit" :min="0" :step="100" style="width: 100%" />
|
||||
@@ -1875,6 +1956,20 @@ function creditTypeLabel(type: string) {
|
||||
<el-tag size="small" type="info" class="affiliation-tag">{{ affiliationLabel(editPlayerForm) }}</el-tag>
|
||||
<div class="field-hint">{{ t('user.hint.agent_readonly') }}</div>
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('agent.field.cashback_rate')">
|
||||
<div class="cashback-edit-block">
|
||||
<el-checkbox v-model="editPlayerForm.useCustomCashback">
|
||||
{{ t('cashback.use_custom_rate') }}
|
||||
</el-checkbox>
|
||||
<RatePercentInput
|
||||
v-if="editPlayerForm.useCustomCashback"
|
||||
v-model="editPlayerForm.customCashbackRate"
|
||||
/>
|
||||
<p v-else class="field-hint block-hint">
|
||||
{{ t('cashback.use_default_rate', { rate: `${editPlayerForm.defaultCashbackRate.toFixed(2)}%` }) }}
|
||||
</p>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('user.field.phone')">
|
||||
<el-input v-model="editPlayerForm.phone" :placeholder="t('common.optional')" />
|
||||
</el-form-item>
|
||||
@@ -1928,7 +2023,7 @@ function creditTypeLabel(type: string) {
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('agent.field.cashback_rate')">
|
||||
<el-input-number v-model="editAgentForm.cashbackRate" :min="0" :max="1" :step="0.001" :precision="4" style="width: 100%" />
|
||||
<RatePercentInput v-model="editAgentForm.cashbackRate" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('agent.field.max_single_deposit')">
|
||||
<el-input-number v-model="editAgentForm.maxSingleDeposit" :min="0" :step="100" style="width: 100%" />
|
||||
@@ -2026,38 +2121,46 @@ function creditTypeLabel(type: string) {
|
||||
</el-dialog>
|
||||
|
||||
<!-- ── Player Detail ── -->
|
||||
<el-dialog v-model="detailPlayerVisible" :title="t('user.dialog.detail')" width="560px" destroy-on-close>
|
||||
<el-dialog v-model="detailPlayerVisible" :title="t('user.dialog.detail')" width="780px" destroy-on-close class="entity-detail-dialog">
|
||||
<template v-if="playerDetail">
|
||||
<el-descriptions :column="2" border size="small">
|
||||
<el-descriptions-item :label="t('common.col_id')">{{ playerDetail.id }}</el-descriptions-item>
|
||||
<el-descriptions-item :label="t('user.col.username')">{{ playerDetail.username }}</el-descriptions-item>
|
||||
<el-descriptions-item :label="t('user.field.current_password')">{{ playerDetail.managedPassword ?? '—' }}</el-descriptions-item>
|
||||
<el-descriptions-item v-if="!playerDetail.managedPassword" :span="2">
|
||||
<span class="field-hint">{{ t('user.hint.password_reset_to_view') }}</span>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item :label="t('common.status')">
|
||||
<AdminDetailGrid :columns="3">
|
||||
<AdminDetailItem :label="t('common.col_id')">{{ playerDetail.id }}</AdminDetailItem>
|
||||
<AdminDetailItem :label="t('user.col.username')">{{ playerDetail.username }}</AdminDetailItem>
|
||||
<AdminDetailItem :label="t('common.status')">
|
||||
<el-tag :type="statusTagType(playerDetail.status)" size="small">{{ statusLabel(playerDetail.status) }}</el-tag>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item :label="t('user.col.agent')">{{ affiliationLabel(playerDetail) }}</el-descriptions-item>
|
||||
<el-descriptions-item :label="t('user.field.available')">
|
||||
</AdminDetailItem>
|
||||
<AdminDetailItem :label="t('user.col.agent')">{{ affiliationLabel(playerDetail) }}</AdminDetailItem>
|
||||
<AdminDetailItem :label="t('user.col.invite_code')">{{ playerDetail.inviteCode ?? '—' }}</AdminDetailItem>
|
||||
<AdminDetailItem :label="t('agent.field.cashback_rate')">
|
||||
{{
|
||||
playerDetail.customCashbackRate != null
|
||||
? formatRatePercent(playerDetail.customCashbackRate)
|
||||
: t('cashback.use_default_rate', { rate: formatRatePercent(playerDetail.defaultCashbackRate) })
|
||||
}}
|
||||
</AdminDetailItem>
|
||||
<AdminDetailItem :label="t('user.field.available')">
|
||||
{{ formatAmount(playerDetail.availableBalance) }}
|
||||
<span v-if="shouldCompact(playerDetail.availableBalance)" class="amount-full-hint">({{ formatAmountFull(playerDetail.availableBalance) }})</span>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item :label="t('user.field.frozen_balance')">
|
||||
</AdminDetailItem>
|
||||
<AdminDetailItem :label="t('user.field.frozen_balance')">
|
||||
{{ formatAmount(playerDetail.frozenBalance) }}
|
||||
<span v-if="shouldCompact(playerDetail.frozenBalance)" class="amount-full-hint">({{ formatAmountFull(playerDetail.frozenBalance) }})</span>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item :label="t('user.field.phone')">{{ playerDetail.phone ?? '—' }}</el-descriptions-item>
|
||||
<el-descriptions-item :label="t('user.field.email')">{{ playerDetail.email ?? '—' }}</el-descriptions-item>
|
||||
<el-descriptions-item :label="t('user.field.bet_count')">{{ playerDetail.betCount }}</el-descriptions-item>
|
||||
<el-descriptions-item :label="t('user.field.total_stake')">{{ formatAmount(playerDetail.totalStake) }}</el-descriptions-item>
|
||||
<el-descriptions-item :label="t('user.field.total_payout')">{{ formatAmount(playerDetail.totalReturn) }}</el-descriptions-item>
|
||||
<el-descriptions-item :label="t('user.col.last_login')">
|
||||
</AdminDetailItem>
|
||||
<AdminDetailItem :label="t('user.field.bet_count')">{{ playerDetail.betCount }}</AdminDetailItem>
|
||||
<AdminDetailItem :label="t('user.field.total_stake')">{{ formatAmount(playerDetail.totalStake) }}</AdminDetailItem>
|
||||
<AdminDetailItem :label="t('user.field.total_payout')">{{ formatAmount(playerDetail.totalReturn) }}</AdminDetailItem>
|
||||
<AdminDetailItem :label="t('user.field.phone')">{{ playerDetail.phone ?? '—' }}</AdminDetailItem>
|
||||
<AdminDetailItem :label="t('user.col.last_login')">
|
||||
{{ playerDetail.lastLoginAt ? formatTime(playerDetail.lastLoginAt) : t('common.never_login') }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item :label="t('user.field.login_fail')">{{ t('user.login_fail_value', { n: playerDetail.loginFailCount }) }}</el-descriptions-item>
|
||||
<el-descriptions-item :label="t('user.field.registered_at')" :span="2">{{ formatTime(playerDetail.createdAt) }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</AdminDetailItem>
|
||||
<AdminDetailItem :label="t('user.field.login_fail')">{{ t('user.login_fail_value', { n: playerDetail.loginFailCount }) }}</AdminDetailItem>
|
||||
<AdminDetailItem :label="t('user.field.email')">{{ playerDetail.email ?? '—' }}</AdminDetailItem>
|
||||
<AdminDetailItem :label="t('user.field.registered_at')">{{ formatTime(playerDetail.createdAt) }}</AdminDetailItem>
|
||||
<AdminDetailItem :label="t('user.field.current_password')" :span="2">
|
||||
{{ playerDetail.managedPassword ?? '—' }}
|
||||
<span v-if="!playerDetail.managedPassword" class="admin-detail-hint">{{ t('user.hint.password_reset_to_view') }}</span>
|
||||
</AdminDetailItem>
|
||||
</AdminDetailGrid>
|
||||
<div class="detail-actions">
|
||||
<el-button type="primary" link @click="openPlayerWalletLedger(playerDetail.id, playerDetail.username)">
|
||||
{{ t('user.action.view_wallet_ledger') }}
|
||||
@@ -2092,7 +2195,7 @@ function creditTypeLabel(type: string) {
|
||||
<el-descriptions-item :label="t('agent.col.sub_agents')">{{ agentDetail.childAgentCount }} {{ t('common.people') }}</el-descriptions-item>
|
||||
<el-descriptions-item :label="t('agent.field.player_liability')">{{ formatAmount(agentDetail.directPlayerLiability) }}</el-descriptions-item>
|
||||
<el-descriptions-item :label="t('agent.field.sub_agent_exposure')">{{ formatAmount(agentDetail.childAgentExposure) }}</el-descriptions-item>
|
||||
<el-descriptions-item :label="t('agent.col.cashback')">{{ agentDetail.cashbackRate }}</el-descriptions-item>
|
||||
<el-descriptions-item :label="t('agent.col.cashback')">{{ formatRatePercent(agentDetail.cashbackRate) }}</el-descriptions-item>
|
||||
<el-descriptions-item :label="t('user.field.phone')">{{ agentDetail.phone ?? '—' }}</el-descriptions-item>
|
||||
<el-descriptions-item :label="t('user.field.email')">{{ agentDetail.email ?? '—' }}</el-descriptions-item>
|
||||
<el-descriptions-item :label="t('user.col.last_login')" :span="2">
|
||||
@@ -2101,6 +2204,8 @@ function creditTypeLabel(type: string) {
|
||||
<el-descriptions-item :label="t('agent.col.created')" :span="2">{{ formatTime(agentDetail.createdAt) }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
|
||||
<InviteCodePanel :invite-code="agentDetail.inviteCode" compact readonly class="detail-invite" />
|
||||
|
||||
<div class="section-title section-title--row">
|
||||
<span>{{ t('agent.section.credit_log') }}</span>
|
||||
<el-button
|
||||
@@ -2147,18 +2252,57 @@ function creditTypeLabel(type: string) {
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.agent-mgr-page > .agent-list-panel,
|
||||
.agent-mgr-page > .mgr-top-tabs {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
}
|
||||
.agent-mgr-page > .mgr-top-tabs :deep(.el-tabs__content) {
|
||||
.mgr-tabs-shell {
|
||||
position: relative;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.agent-mgr-page > .mgr-top-tabs :deep(.el-tab-pane) {
|
||||
|
||||
.mgr-top-tabs--with-invite :deep(.el-tabs__header) {
|
||||
padding-right: 108px;
|
||||
}
|
||||
|
||||
.invite-prominent-btn {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
z-index: 2;
|
||||
min-width: 96px;
|
||||
height: 38px;
|
||||
padding: 0 22px;
|
||||
font-size: 15px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.06em;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 10px rgba(64, 158, 255, 0.28);
|
||||
}
|
||||
|
||||
.agent-mgr-page > .mgr-tabs-shell {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.mgr-tabs-shell .mgr-top-tabs {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.agent-mgr-page > .agent-list-panel,
|
||||
.agent-mgr-page > .mgr-tabs-shell .mgr-top-tabs {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
}
|
||||
.agent-mgr-page > .mgr-tabs-shell .mgr-top-tabs :deep(.el-tabs__content) {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.agent-mgr-page > .mgr-tabs-shell .mgr-top-tabs :deep(.el-tab-pane) {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
display: flex;
|
||||
@@ -2195,14 +2339,21 @@ function creditTypeLabel(type: string) {
|
||||
.list-panel-toolbar {
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px;
|
||||
padding: 10px 0 8px;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.06);
|
||||
--list-chrome-control-h: 32px;
|
||||
--el-component-size: 32px;
|
||||
}
|
||||
.list-panel-toolbar .list-chrome__grow {
|
||||
flex: 1 1 280px;
|
||||
min-width: 0;
|
||||
flex-wrap: wrap;
|
||||
row-gap: 8px;
|
||||
}
|
||||
.list-panel-toolbar .list-chrome__actions {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
@@ -2293,7 +2444,7 @@ function creditTypeLabel(type: string) {
|
||||
font-weight: 700;
|
||||
}
|
||||
.inner-tabs :deep(.el-tabs__active-bar) {
|
||||
background-color: var(--green-bright);
|
||||
background-color: var(--gold-bright);
|
||||
height: 2px;
|
||||
}
|
||||
.inner-tabs :deep(.el-tabs__content) {
|
||||
@@ -2309,13 +2460,29 @@ function creditTypeLabel(type: string) {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
}
|
||||
.action-btns {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 4px;
|
||||
flex-wrap: nowrap;
|
||||
white-space: nowrap;
|
||||
.player-list-panel .table-wrap :deep(.el-table),
|
||||
.agent-list-panel .table-wrap :deep(.el-table) {
|
||||
min-width: 880px;
|
||||
}
|
||||
|
||||
.expand-panel .inner-table {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.expand-panel :deep(.el-table__body-wrapper) {
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
@media (max-width: 900px) {
|
||||
.mgr-top-tabs--with-invite :deep(.el-tabs__header) {
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
.invite-prominent-btn {
|
||||
position: static;
|
||||
align-self: flex-end;
|
||||
margin: 0 0 8px;
|
||||
}
|
||||
}
|
||||
|
||||
/* ─── Inherited from old pages ─── */
|
||||
@@ -2388,8 +2555,15 @@ function creditTypeLabel(type: string) {
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 0 10px;
|
||||
}
|
||||
.c-green { color: #2fb56a; }
|
||||
.c-green { color: var(--gold-text); }
|
||||
.amount-compact { white-space: nowrap; font-variant-numeric: tabular-nums; cursor: default; }
|
||||
.invite-code-cell {
|
||||
font-family: ui-monospace, monospace;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.06em;
|
||||
color: var(--el-color-primary);
|
||||
}
|
||||
|
||||
.affiliation-tag {
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
@@ -2464,22 +2638,3 @@ function creditTypeLabel(type: string) {
|
||||
gap: 8px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style>
|
||||
/* 玩家列表「冻结」:橙黄底白字 */
|
||||
.agent-mgr-page .el-button.is-link.el-button--warning {
|
||||
color: #ffffff !important;
|
||||
background: linear-gradient(165deg, #e8a84a 0%, #c47a18 42%, #9a5c10 100%) !important;
|
||||
border: 1px solid rgba(232, 168, 74, 0.45) !important;
|
||||
border-radius: 6px !important;
|
||||
padding: 5px 11px !important;
|
||||
box-shadow: 0 1px 0 rgba(255, 255, 255, 0.12) inset, 0 1px 6px rgba(0, 0, 0, 0.35) !important;
|
||||
text-shadow: 0 1px 1px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
.agent-mgr-page .el-button.is-link.el-button--warning:hover,
|
||||
.agent-mgr-page .el-button.is-link.el-button--warning:focus {
|
||||
color: #ffffff !important;
|
||||
background: linear-gradient(165deg, #f0bc62 0%, #d48a28 42%, #a86814 100%) !important;
|
||||
border-color: rgba(240, 188, 98, 0.55) !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user