feat: 开户备注、账单展示优化与后台代理管理增强
- 新增初始上分备注(日常上分/开户赠金/自定义)及前后台校验与展示 - 优化钱包流水类型与备注显示,区分管理员/代理/玩家上下分 - 修复登录后语言被后端覆盖的问题,登录时同步当前语言到服务端 - 后台代理/玩家表格操作栏重构,充值订单增加备注列 - 前台个人中心、充值、账单与验证码组件体验优化 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -45,12 +45,14 @@ import {
|
||||
import AdminTableEmpty from '../components/AdminTableEmpty.vue';
|
||||
import PlayerWalletLedgerDialog from '../components/PlayerWalletLedgerDialog.vue';
|
||||
import WalletTransferContext from '../components/WalletTransferContext.vue';
|
||||
import InitialDepositRemarkField from '../components/InitialDepositRemarkField.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 AdminTableWrap from '../components/AdminTableWrap.vue';
|
||||
import AdminAgentRowActions from '../components/AdminAgentRowActions.vue';
|
||||
import AdminPlayerRowActions from '../components/AdminPlayerRowActions.vue';
|
||||
import AdminDetailGrid from '../components/AdminDetailGrid.vue';
|
||||
import AdminDetailItem from '../components/AdminDetailItem.vue';
|
||||
@@ -83,11 +85,6 @@ type SubAgentLevelState = {
|
||||
|
||||
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]) {
|
||||
@@ -155,7 +152,8 @@ const agentOptions = ref<{ id: string; username: string; level: number; parentUs
|
||||
const expandedSet = ref(new Set<string>());
|
||||
const agentPlayersMap = ref<Record<string, PlayerRow[]>>({});
|
||||
const expandLoading = ref<Record<string, boolean>>({});
|
||||
const tier1AgentTableRef = ref();
|
||||
|
||||
const expandedRowKeys = computed(() => Array.from(expandedSet.value));
|
||||
|
||||
const createToolbarChildLevel = ref<number | null>(null);
|
||||
|
||||
@@ -402,17 +400,23 @@ onMounted(() => {
|
||||
|
||||
/* ─── Load tier-1 agents ─── */
|
||||
async function loadTier1Agents() {
|
||||
const { data } = await api.get('/admin/agents', {
|
||||
params: {
|
||||
page: tier1Page.value,
|
||||
pageSize: tier1PageSize.value,
|
||||
keyword: tier1Keyword.value.trim() || undefined,
|
||||
status: tier1FilterStatus.value || undefined,
|
||||
level: 1,
|
||||
},
|
||||
});
|
||||
tier1Agents.value = data.data.items as AgentRow[];
|
||||
tier1Total.value = data.data.total;
|
||||
try {
|
||||
const { data } = await api.get('/admin/agents', {
|
||||
params: {
|
||||
page: tier1Page.value,
|
||||
pageSize: tier1PageSize.value,
|
||||
keyword: tier1Keyword.value.trim() || undefined,
|
||||
status: tier1FilterStatus.value || undefined,
|
||||
level: 1,
|
||||
},
|
||||
});
|
||||
tier1Agents.value = data.data.items as AgentRow[];
|
||||
tier1Total.value = data.data.total;
|
||||
} catch (e) {
|
||||
tier1Agents.value = [];
|
||||
tier1Total.value = 0;
|
||||
ElMessage.error(resolveApiError(e, t, 'msg.load_failed'));
|
||||
}
|
||||
}
|
||||
|
||||
function onTier1PageChange(p: number) {
|
||||
@@ -447,17 +451,23 @@ async function loadAgentLevelCounts() {
|
||||
|
||||
async function loadSubAgentsAtLevel(level: number) {
|
||||
const st = ensureSubAgentState(level);
|
||||
const { data } = await api.get('/admin/agents', {
|
||||
params: {
|
||||
page: st.page,
|
||||
pageSize: st.pageSize,
|
||||
keyword: st.keyword.trim() || undefined,
|
||||
status: st.filterStatus || undefined,
|
||||
level,
|
||||
},
|
||||
});
|
||||
st.agents = data.data.items as AgentRow[];
|
||||
st.total = data.data.total;
|
||||
try {
|
||||
const { data } = await api.get('/admin/agents', {
|
||||
params: {
|
||||
page: st.page,
|
||||
pageSize: st.pageSize,
|
||||
keyword: st.keyword.trim() || undefined,
|
||||
status: st.filterStatus || undefined,
|
||||
level,
|
||||
},
|
||||
});
|
||||
st.agents = data.data.items as AgentRow[];
|
||||
st.total = data.data.total;
|
||||
} catch (e) {
|
||||
st.agents = [];
|
||||
st.total = 0;
|
||||
ElMessage.error(resolveApiError(e, t, 'msg.load_failed'));
|
||||
}
|
||||
}
|
||||
|
||||
function onSubAgentPageChange(level: number, p: number) {
|
||||
@@ -560,18 +570,11 @@ function directPlayersTabLabel(ownerName: string, count: number) {
|
||||
}
|
||||
|
||||
function onTier1AgentRowClick(row: AgentRow, _column: unknown, event: MouseEvent) {
|
||||
onAgentRowClick(row, tier1AgentTableRef, _column, event);
|
||||
onAgentRowClick(row, event);
|
||||
}
|
||||
|
||||
function onSubAgentRowClick(level: number, row: AgentRow, _column: unknown, event: MouseEvent) {
|
||||
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);
|
||||
};
|
||||
function onSubAgentRowClick(row: AgentRow, _column: unknown, event: MouseEvent) {
|
||||
onAgentRowClick(row, event);
|
||||
}
|
||||
|
||||
watch(activeViewTab, (tab) => {
|
||||
@@ -595,9 +598,17 @@ async function onExpandChange(row: DisplayAgentRow, expandedRows: DisplayAgentRo
|
||||
}
|
||||
}
|
||||
|
||||
function onAgentRowClick(row: AgentRow, tableRef: typeof tier1AgentTableRef, _column: unknown, event: MouseEvent) {
|
||||
function onAgentRowClick(row: AgentRow, event: MouseEvent) {
|
||||
if (!shouldToggleExpandOnRowClick(event)) return;
|
||||
tableRef.value?.toggleRowExpansion(row);
|
||||
const userId = row.userId;
|
||||
const next = new Set(expandedSet.value);
|
||||
if (next.has(userId)) {
|
||||
next.delete(userId);
|
||||
} else {
|
||||
next.add(userId);
|
||||
if (!agentPlayersMap.value[userId]) void loadExpansionData(userId);
|
||||
}
|
||||
expandedSet.value = next;
|
||||
}
|
||||
|
||||
async function loadExpansionData(agentId: string) {
|
||||
@@ -1360,7 +1371,7 @@ function creditTypeLabel(type: string) {
|
||||
<el-button type="primary" @click="openCreateAccount">{{ t('user.create_btn') }}</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="table-wrap">
|
||||
<AdminTableWrap>
|
||||
<el-table v-loading="playerLoading" :data="allPlayers" stripe>
|
||||
<template #empty>
|
||||
<AdminTableEmpty />
|
||||
@@ -1396,7 +1407,7 @@ function creditTypeLabel(type: string) {
|
||||
<span class="amount-compact">{{ formatAmount(row.totalStake) }} / {{ formatAmount(row.totalReturn) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="t('common.actions')" min-width="360" align="center" fixed="right">
|
||||
<el-table-column :label="t('common.actions')" min-width="340" align="center">
|
||||
<template #default="{ row }">
|
||||
<AdminPlayerRowActions
|
||||
:row="row"
|
||||
@@ -1410,7 +1421,7 @@ function creditTypeLabel(type: string) {
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</AdminTableWrap>
|
||||
<div class="pager">
|
||||
<el-pagination
|
||||
v-model:current-page="playerPage"
|
||||
@@ -1448,12 +1459,12 @@ function creditTypeLabel(type: string) {
|
||||
<el-button type="primary" @click="openCreateTier1Agent">{{ t('agent.create_btn') }}</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="table-wrap">
|
||||
<AdminTableWrap>
|
||||
<el-table
|
||||
ref="tier1AgentTableRef"
|
||||
:data="tier1Agents"
|
||||
stripe
|
||||
row-key="userId"
|
||||
:expand-row-keys="expandedRowKeys"
|
||||
:row-class-name="expandableTableRowClassName"
|
||||
class="expandable-table compact-agent-table"
|
||||
@expand-change="onExpandChange"
|
||||
@@ -1544,24 +1555,22 @@ function creditTypeLabel(type: string) {
|
||||
<el-table-column :label="t('agent.col.cashback')" min-width="80" align="right">
|
||||
<template #default="{ row }">{{ formatRatePercent(row.cashbackRate) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="t('common.actions')" width="96" align="center" fixed="right">
|
||||
<el-table-column :label="t('common.actions')" min-width="300" align="center">
|
||||
<template #default="{ row }">
|
||||
<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>
|
||||
<AdminAgentRowActions
|
||||
:row="{ userId: row.userId, status: row.status }"
|
||||
:can-create-sub="canAgentCreateSub(row)"
|
||||
:create-sub-label="childAgentActionLabel(row.level)"
|
||||
@detail="openDetailAgent(row.userId)"
|
||||
@edit="openEditAgent(row.userId)"
|
||||
@credit="openCredit(row.userId)"
|
||||
@create-sub="openCreateSubAgent(row.userId)"
|
||||
@freeze="toggleFreezeAgent(row)"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</AdminTableWrap>
|
||||
<div class="pager">
|
||||
<el-pagination
|
||||
v-model:current-page="tier1Page"
|
||||
@@ -1617,16 +1626,16 @@ function creditTypeLabel(type: string) {
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="table-wrap">
|
||||
<AdminTableWrap>
|
||||
<el-table
|
||||
:ref="(el: unknown) => setSubAgentTableRef(agentLevel, el)"
|
||||
:data="ensureSubAgentState(agentLevel).agents"
|
||||
stripe
|
||||
row-key="userId"
|
||||
:expand-row-keys="expandedRowKeys"
|
||||
:row-class-name="expandableTableRowClassName"
|
||||
class="expandable-table compact-agent-table"
|
||||
@expand-change="onExpandChange"
|
||||
@row-click="bindSubAgentRowClick(agentLevel)"
|
||||
@row-click="onSubAgentRowClick"
|
||||
>
|
||||
<template #empty>
|
||||
<AdminTableEmpty />
|
||||
@@ -1696,24 +1705,22 @@ 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="96" align="center" fixed="right">
|
||||
<el-table-column :label="t('common.actions')" min-width="300" align="center">
|
||||
<template #default="{ row }">
|
||||
<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>
|
||||
<AdminAgentRowActions
|
||||
:row="{ userId: row.userId, status: subAgentAccountStatus(row) }"
|
||||
:can-create-sub="canAgentCreateSub(row)"
|
||||
:create-sub-label="childAgentActionLabel(row.level)"
|
||||
@detail="openDetailAgent(row.userId)"
|
||||
@edit="openEditAgent(row.userId)"
|
||||
@credit="openCredit(row.userId)"
|
||||
@create-sub="openCreateSubAgent(row.userId)"
|
||||
@freeze="toggleFreezeAgent(row)"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</AdminTableWrap>
|
||||
<div class="pager">
|
||||
<el-pagination
|
||||
:current-page="ensureSubAgentState(agentLevel).page"
|
||||
@@ -1908,8 +1915,15 @@ function creditTypeLabel(type: string) {
|
||||
<el-form-item :label="t('user.field.initial_balance')">
|
||||
<el-input-number v-model="createForm.initialDeposit" :min="0" :step="100" style="width: 100%" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('user.field.deposit_remark')">
|
||||
<el-input v-model="createForm.remark" :placeholder="t('user.ph.remark_initial')" />
|
||||
<el-form-item
|
||||
v-if="createForm.initialDeposit > 0"
|
||||
:label="t('user.field.initial_deposit_kind')"
|
||||
>
|
||||
<InitialDepositRemarkField
|
||||
v-model:kind="createForm.initialDepositRemarkKind"
|
||||
v-model:custom="createForm.initialDepositRemarkCustom"
|
||||
operator="admin"
|
||||
/>
|
||||
</el-form-item>
|
||||
</template>
|
||||
|
||||
@@ -1931,61 +1945,90 @@ function creditTypeLabel(type: string) {
|
||||
</el-dialog>
|
||||
|
||||
<!-- ── Edit Player ── -->
|
||||
<el-dialog v-model="editPlayerVisible" :title="t('user.dialog.edit')" width="480px" destroy-on-close class="user-edit-dialog">
|
||||
<el-dialog v-model="editPlayerVisible" :title="t('user.dialog.edit')" width="560px" destroy-on-close class="user-edit-dialog">
|
||||
<el-form label-width="84px" size="small" class="compact-edit-form">
|
||||
<div class="edit-meta">
|
||||
<span>ID {{ editPlayerForm.id }}</span>
|
||||
<el-tag :type="statusTagType(editPlayerForm.status)" size="small">{{ statusLabel(editPlayerForm.status) }}</el-tag>
|
||||
</div>
|
||||
<el-form-item :label="t('user.col.username')">
|
||||
<el-input v-model="editPlayerForm.username" :placeholder="t('user.ph.username_player')" />
|
||||
<div class="field-hint">{{ t('user.hint.username_player') }}</div>
|
||||
</el-form-item>
|
||||
<div class="password-mgmt-block">
|
||||
<div class="block-title">{{ t('user.section.password_mgmt') }}</div>
|
||||
<el-form-item :label="t('user.field.current_password')">
|
||||
<span v-if="editPlayerForm.managedPassword" class="password-plain">{{ editPlayerForm.managedPassword }}</span>
|
||||
<span v-else class="password-empty">—</span>
|
||||
</el-form-item>
|
||||
<p v-if="!editPlayerForm.managedPassword" class="field-hint block-hint">{{ t('user.hint.password_reset_to_view') }}</p>
|
||||
<el-form-item :label="t('user.field.reset_password')">
|
||||
<el-input v-model="editPlayerForm.newPassword" type="text" autocomplete="off" :placeholder="t('user.ph.reset_password_short')" />
|
||||
|
||||
<div class="edit-form-section">
|
||||
<div class="section-title">{{ t('user.section.basic_info') }}</div>
|
||||
<el-form-item :label="t('user.col.username')">
|
||||
<el-input v-model="editPlayerForm.username" :placeholder="t('user.ph.username_player')" />
|
||||
<div class="field-hint">{{ t('user.hint.username_player') }}</div>
|
||||
</el-form-item>
|
||||
</div>
|
||||
<el-form-item :label="t('user.col.agent')">
|
||||
<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 class="edit-form-section">
|
||||
<div class="password-mgmt-block">
|
||||
<div class="block-title">{{ t('user.section.password_mgmt') }}</div>
|
||||
<div class="password-field-row">
|
||||
<span class="password-field-label">{{ t('user.field.current_password') }}</span>
|
||||
<span v-if="editPlayerForm.managedPassword" class="password-plain">{{ editPlayerForm.managedPassword }}</span>
|
||||
<span v-else class="password-empty">—</span>
|
||||
</div>
|
||||
<p v-if="!editPlayerForm.managedPassword" class="field-hint block-hint">{{ t('user.hint.password_reset_to_view') }}</p>
|
||||
<div class="password-field-row">
|
||||
<span class="password-field-label">{{ t('user.field.reset_password') }}</span>
|
||||
<el-input v-model="editPlayerForm.newPassword" type="text" autocomplete="off" :placeholder="t('user.ph.reset_password_short')" />
|
||||
</div>
|
||||
</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>
|
||||
<el-form-item :label="t('user.field.email')">
|
||||
<el-input v-model="editPlayerForm.email" :placeholder="t('common.optional')" />
|
||||
</el-form-item>
|
||||
<el-descriptions :column="2" size="small" border class="edit-stats">
|
||||
<el-descriptions-item :label="t('user.field.available')">{{ formatAmount(editPlayerForm.availableBalance) }}</el-descriptions-item>
|
||||
<el-descriptions-item :label="t('user.field.frozen_balance')">{{ formatAmount(editPlayerForm.frozenBalance) }}</el-descriptions-item>
|
||||
<el-descriptions-item :label="t('user.field.bets_summary')">{{ t('user.bets_edit_value', { n: editPlayerForm.betCount, stake: formatAmount(editPlayerForm.totalStake) }) }}</el-descriptions-item>
|
||||
<el-descriptions-item :label="t('user.field.total_payout')">{{ formatAmount(editPlayerForm.totalReturn) }}</el-descriptions-item>
|
||||
<el-descriptions-item :label="t('user.col.last_login')" :span="2">
|
||||
{{ editPlayerForm.lastLoginAt ? formatTime(editPlayerForm.lastLoginAt) : t('common.never_login') }}
|
||||
· {{ t('user.login_fail_value', { n: editPlayerForm.loginFailCount }) }}
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</div>
|
||||
|
||||
<div class="edit-form-section">
|
||||
<div class="section-title">{{ t('user.section.affiliation') }}</div>
|
||||
<el-form-item :label="t('user.col.agent')">
|
||||
<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"
|
||||
/>
|
||||
<span v-else class="field-hint inline-hint">
|
||||
{{ `${editPlayerForm.defaultCashbackRate.toFixed(2)}%` }}
|
||||
</span>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</div>
|
||||
|
||||
<div class="edit-form-section">
|
||||
<div class="section-title">{{ t('user.section.contact') }}</div>
|
||||
<el-row :gutter="12" class="contact-row">
|
||||
<el-col :span="12">
|
||||
<el-form-item :label="t('user.field.phone')">
|
||||
<el-input v-model="editPlayerForm.phone" :placeholder="t('common.optional')" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item :label="t('user.field.email')">
|
||||
<el-input v-model="editPlayerForm.email" :placeholder="t('common.optional')" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
|
||||
<div class="edit-form-section edit-stats-panel">
|
||||
<div class="section-title">{{ t('user.section.account_overview') }}</div>
|
||||
<AdminDetailGrid :columns="3">
|
||||
<AdminDetailItem :label="t('user.field.available')">{{ formatAmount(editPlayerForm.availableBalance) }}</AdminDetailItem>
|
||||
<AdminDetailItem :label="t('user.field.frozen_balance')">{{ formatAmount(editPlayerForm.frozenBalance) }}</AdminDetailItem>
|
||||
<AdminDetailItem :label="t('user.field.bets_summary')">
|
||||
{{ t('user.bets_edit_value', { n: editPlayerForm.betCount, stake: formatAmount(editPlayerForm.totalStake) }) }}
|
||||
</AdminDetailItem>
|
||||
<AdminDetailItem :label="t('user.field.total_payout')">{{ formatAmount(editPlayerForm.totalReturn) }}</AdminDetailItem>
|
||||
<AdminDetailItem :label="t('user.col.last_login')" :span="2">
|
||||
{{ editPlayerForm.lastLoginAt ? formatTime(editPlayerForm.lastLoginAt) : t('common.never_login') }}
|
||||
· {{ t('user.login_fail_value', { n: editPlayerForm.loginFailCount }) }}
|
||||
</AdminDetailItem>
|
||||
</AdminDetailGrid>
|
||||
</div>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button size="small" @click="editPlayerVisible = false">{{ t('common.cancel') }}</el-button>
|
||||
@@ -2132,11 +2175,7 @@ function creditTypeLabel(type: string) {
|
||||
<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) })
|
||||
}}
|
||||
{{ formatRatePercent(playerDetail.customCashbackRate ?? playerDetail.defaultCashbackRate) }}
|
||||
</AdminDetailItem>
|
||||
<AdminDetailItem :label="t('user.field.available')">
|
||||
{{ formatAmount(playerDetail.availableBalance) }}
|
||||
@@ -2161,11 +2200,6 @@ function creditTypeLabel(type: string) {
|
||||
<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') }}
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
@@ -2571,41 +2605,6 @@ function creditTypeLabel(type: string) {
|
||||
}
|
||||
.amount-full-hint { font-size: 11px; color: #666; margin-left: 4px; }
|
||||
.text-muted { color: #666; font-size: 12px; }
|
||||
.password-mgmt-block {
|
||||
margin: 4px 0 10px;
|
||||
padding: 10px 12px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||
border-radius: 8px;
|
||||
background: rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
.block-title {
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
color: #e8a84a;
|
||||
margin-bottom: 8px;
|
||||
letter-spacing: 0.04em;
|
||||
}
|
||||
.password-plain {
|
||||
font-family: ui-monospace, monospace;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #f0d090;
|
||||
letter-spacing: 0.06em;
|
||||
}
|
||||
.password-empty { color: #666; }
|
||||
.block-hint { margin: -4px 0 8px; }
|
||||
.edit-meta {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
margin-bottom: 10px;
|
||||
font-size: 12px;
|
||||
color: #888;
|
||||
}
|
||||
.compact-edit-form :deep(.el-form-item) {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.edit-stats { margin-top: 4px; }
|
||||
.list-settings-block--danger {
|
||||
margin-top: 12px;
|
||||
padding-top: 12px;
|
||||
|
||||
Reference in New Issue
Block a user