feat: replace watermarks with corner tags, improve parlay layout and admin polish

- MatchDetailView: replace StatusWatermark with top-right solid status tags, remove match-hero background image
- ParlayPanel: replace StatusWatermark with corner tags, improve toggle button style and layout spacing, fix overflow clipping
- Admin: wallet ledger dialog, agent manager, and player page refinements

🤖 Generated with [Qoder][https://qoder.com]
This commit is contained in:
2026-06-10 16:24:01 +08:00
parent ef6b15f119
commit a8ee28fcce
7 changed files with 159 additions and 58 deletions

View File

@@ -133,7 +133,7 @@ watch(
<el-dialog
v-model="visible"
:title="dialogTitle"
width="960px"
width="980px"
destroy-on-close
class="player-wallet-ledger-dialog"
append-to-body
@@ -172,10 +172,10 @@ watch(
<el-table-column :label="t('finance.col.tx_id')" min-width="120" show-overflow-tooltip>
<template #default="{ row }">{{ row.transactionId }}</template>
</el-table-column>
<el-table-column :label="t('agent.col.credit_type')" width="92">
<el-table-column :label="t('agent.col.credit_type')" min-width="88">
<template #default="{ row }">{{ walletTypeLabel(row.transactionType) }}</template>
</el-table-column>
<el-table-column :label="t('finance.col.balance_change')" width="100" align="right">
<el-table-column :label="t('finance.col.balance_change')" min-width="96" align="right">
<template #default="{ row }">
<el-tooltip :content="formatAmountFull(row.amount)" placement="top">
<span :class="parseFloat(row.amount) >= 0 ? 'amt-pos' : 'amt-neg'">
@@ -184,28 +184,28 @@ watch(
</el-tooltip>
</template>
</el-table-column>
<el-table-column :label="t('finance.col.balance_before')" width="96" align="right">
<el-table-column :label="t('finance.col.balance_before')" min-width="92" align="right">
<template #default="{ row }">
<el-tooltip :content="formatAmountFull(row.balanceBefore)" placement="top">
<span>{{ formatAmount(row.balanceBefore) }}</span>
</el-tooltip>
</template>
</el-table-column>
<el-table-column :label="t('finance.col.balance_after')" width="96" align="right">
<el-table-column :label="t('finance.col.balance_after')" min-width="92" align="right">
<template #default="{ row }">
<el-tooltip :content="formatAmountFull(row.balanceAfter)" placement="top">
<span>{{ formatAmount(row.balanceAfter) }}</span>
</el-tooltip>
</template>
</el-table-column>
<el-table-column :label="t('finance.col.frozen_before')" width="96" align="right">
<el-table-column :label="t('finance.col.frozen_before')" min-width="92" align="right">
<template #default="{ row }">
<el-tooltip :content="formatAmountFull(row.frozenBefore)" placement="top">
<span>{{ formatAmount(row.frozenBefore) }}</span>
</el-tooltip>
</template>
</el-table-column>
<el-table-column :label="t('finance.col.frozen_after')" width="96" align="right">
<el-table-column :label="t('finance.col.frozen_after')" min-width="92" align="right">
<template #default="{ row }">
<el-tooltip :content="formatAmountFull(row.frozenAfter)" placement="top">
<span>{{ formatAmount(row.frozenAfter) }}</span>
@@ -250,18 +250,22 @@ watch(
<style scoped>
.ledger-filter {
margin-bottom: 12px;
margin-bottom: 8px;
}
.ledger-filter :deep(.el-form-item) {
margin-bottom: 0;
margin-right: 12px;
margin-right: 8px;
}
.table-wrap {
margin: 0 -2px;
}
.pager {
display: flex;
justify-content: flex-end;
margin-top: 12px;
margin-top: 8px;
}
.amt-pos {
@@ -283,3 +287,18 @@ watch(
text-decoration: underline;
}
</style>
<style>
.player-wallet-ledger-dialog .el-dialog__header {
padding: 12px 16px 6px;
margin-right: 0;
}
.player-wallet-ledger-dialog .el-dialog__body {
padding: 10px 12px;
}
.player-wallet-ledger-dialog .el-dialog__headerbtn {
top: 10px;
}
</style>

View File

@@ -543,6 +543,9 @@ export const adminPagesMs: Record<string, string> = {
'agent_portal.no_players': 'Tiada pemain langsung. Klik butang di atas untuk cipta.',
'agent_portal.search_sub_agent_ph': 'Nama pengguna atau ID',
'agent_portal.no_sub_agents': 'Tiada ejen peringkat 2. Klik butang di atas untuk cipta.',
'agent_portal.no_sub_agents_level': 'Tiada ejen peringkat {level}. Klik butang di atas untuk cipta.',
'agent_portal.sub_agent_players_readonly': 'Pemain langsung di bawah sub-ejen ini hanya boleh dilihat. Pembukaan akaun dan tambah baki diurus oleh sub-ejen.',
'agent_portal.sub_agent_players_readonly_level': 'Pemain langsung di bawah ejen peringkat {level} ini hanya boleh dilihat. Pembukaan akaun dan tambah baki diurus oleh ejen tersebut.',
'agent_portal.create_sub_agent_dialog': 'Ejen peringkat 2 baharu',
'agent_portal.sub_agent_credit_hint': 'Kredit awal diperuntukkan dari had tersedia anda',
'agent_portal.adjust_credit_dialog': 'Laraskan kredit {name}',

View File

@@ -608,6 +608,8 @@ export const adminPagesZh: Record<string, string> = {
'agent_portal.no_players': '暂无直属玩家,点击右上角创建',
'agent_portal.search_sub_agent_ph': '用户名或 ID',
'agent_portal.no_sub_agents': '暂无二级代理,点击右上角创建',
'agent_portal.no_sub_agents_level': '暂无{level}级代理,点击右上角创建',
'agent_portal.sub_agent_players_readonly_level': '以下为该{level}级代理直属玩家,仅可查看;开户、上分等操作由该代理自行处理。',
'agent_portal.create_sub_agent_dialog': '新建二级代理',
'agent_portal.sub_agent_credit_hint': '初始授信从您的可用额度中划拨,不能超过可用授信',
'agent_portal.adjust_credit_dialog': '调整 {name} 授信',
@@ -1478,6 +1480,8 @@ export const adminPagesEn: Record<string, string> = {
'agent_portal.no_players': 'No direct players yet. Use the button above to create one.',
'agent_portal.search_sub_agent_ph': 'Username or ID',
'agent_portal.no_sub_agents': 'No tier-2 agents yet. Use the button above to create one.',
'agent_portal.no_sub_agents_level': 'No L{level} agents yet. Use the button above to create one.',
'agent_portal.sub_agent_players_readonly_level': 'Direct players under this L{level} agent are read-only here. Account opening and top-ups are handled by that agent.',
'agent_portal.create_sub_agent_dialog': 'New tier-2 agent',
'agent_portal.sub_agent_credit_hint': 'Initial credit is allocated from your available limit',
'agent_portal.adjust_credit_dialog': 'Adjust credit for {name}',

View File

@@ -1452,26 +1452,26 @@ function creditTypeLabel(type: string) {
</template>
</el-table-column>
<el-table-column prop="userId" label="ID" width="64" />
<el-table-column prop="username" :label="t('user.col.username')" width="100" show-overflow-tooltip />
<el-table-column :label="t('common.status')" width="72">
<el-table-column prop="userId" label="ID" min-width="64" />
<el-table-column prop="username" :label="t('user.col.username')" min-width="100" show-overflow-tooltip />
<el-table-column :label="t('common.status')" min-width="72">
<template #default="{ row }">
<el-tag :type="statusTagType(row.status)" size="small">{{ statusLabel(row.status) }}</el-tag>
</template>
</el-table-column>
<el-table-column prop="level" :label="t('agent.col.level')" width="52" align="center">
<el-table-column prop="level" :label="t('agent.col.level')" min-width="52" align="center">
<template #default="{ row }">L{{ row.level }}</template>
</el-table-column>
<el-table-column :label="t('agent.col.credit')" width="132" align="right">
<el-table-column :label="t('agent.col.credit')" min-width="148" align="right">
<template #default="{ row }">
<el-tooltip :content="creditLineFull(row)" placement="top">
<span class="amount-compact">{{ creditLine(row) }}</span>
</el-tooltip>
</template>
</el-table-column>
<el-table-column prop="directPlayerCount" :label="t('agent.col.direct_players')" width="72" align="center" />
<el-table-column prop="childAgentCount" :label="t('agent.col.sub_agents')" width="72" align="center" />
<el-table-column :label="t('agent.col.cashback')" width="80" align="right">
<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>
</el-table-column>
<el-table-column :label="t('common.actions')" width="400" fixed="right" align="center">
@@ -1595,24 +1595,24 @@ function creditTypeLabel(type: string) {
</div>
</template>
</el-table-column>
<el-table-column prop="userId" label="ID" width="64" />
<el-table-column prop="username" :label="t('user.col.username')" width="100" show-overflow-tooltip />
<el-table-column :label="t('agent.col.parent_chain')" width="120" show-overflow-tooltip>
<el-table-column prop="userId" label="ID" min-width="64" />
<el-table-column prop="username" :label="t('user.col.username')" min-width="100" show-overflow-tooltip />
<el-table-column :label="t('agent.col.parent_chain')" min-width="120" show-overflow-tooltip>
<template #default="{ row }">{{ parentChainLabel(row) }}</template>
</el-table-column>
<el-table-column :label="t('common.status')" width="72">
<el-table-column :label="t('common.status')" min-width="72">
<template #default="{ row }">
<el-tag :type="statusTagType(subAgentAccountStatus(row))" size="small">{{ statusLabel(subAgentAccountStatus(row)) }}</el-tag>
</template>
</el-table-column>
<el-table-column :label="t('agent.col.credit')" width="132" align="right">
<el-table-column :label="t('agent.col.credit')" min-width="148" align="right">
<template #default="{ row }">
<el-tooltip :content="creditLineFull(row)" placement="top">
<span class="amount-compact">{{ creditLine(row) }}</span>
</el-tooltip>
</template>
</el-table-column>
<el-table-column prop="directPlayerCount" :label="t('agent.col.direct_players')" width="72" align="center" />
<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">
<template #default="{ row }">
<div class="action-btns" @click.stop>
@@ -2248,7 +2248,9 @@ function creditTypeLabel(type: string) {
.expandable-table :deep(.row-expandable) {
cursor: pointer;
}
.compact-agent-table :deep(.el-table__header .el-table__cell),
.compact-agent-table :deep(.el-table__header .el-table__cell) {
padding: 4px 8px;
}
.compact-agent-table :deep(.el-table__body .el-table__cell) {
padding: 6px 8px;
}

View File

@@ -42,11 +42,13 @@ import {
snapshotFromAgentRow,
type AgentCreditAdjustContext,
} from '../../utils/agent-credit-context';
import { formatAgentLevelNumeral } from '../../utils/agent-level-label';
const { t, localeTag } = useAdminLocale();
const auth = useAuthStore();
const profile = ref<{
level?: number;
creditLimit?: string;
usedCredit?: string;
availableCredit?: string;
@@ -57,6 +59,41 @@ const canManageSubAgents = computed(
() => profile.value.canManageSubAgents === true || auth.canManageSubAgents.value,
);
const myAgentLevel = computed(() => profile.value.level ?? auth.user.value?.agentLevel ?? 1);
const childAgentLevel = computed(() => myAgentLevel.value + 1);
function lvlLabel(level: number) {
return formatAgentLevelNumeral(level, localeTag.value);
}
const childAgentTierName = computed(() =>
t('agent.level_name', { level: lvlLabel(childAgentLevel.value) }),
);
const subAgentsTabLabel = computed(() =>
`${childAgentTierName.value} (${subAgents.value.length})`,
);
const createSubBtnLabel = computed(() => {
if (childAgentLevel.value === 2) return t('agent.create_sub_btn');
return t('agent.create_level_agent_btn', { level: lvlLabel(childAgentLevel.value) });
});
const createSubDialogTitle = computed(() => {
if (childAgentLevel.value === 2) return t('agent_portal.create_sub_agent_dialog');
return t('agent.dialog.create_level_agent', { level: lvlLabel(childAgentLevel.value) });
});
const noSubAgentsHint = computed(() => {
if (childAgentLevel.value === 2) return t('agent_portal.no_sub_agents');
return t('agent_portal.no_sub_agents_level', { level: lvlLabel(childAgentLevel.value) });
});
const subAgentPlayersReadonlyHint = computed(() => {
if (childAgentLevel.value === 2) return t('agent_portal.sub_agent_players_readonly');
return t('agent_portal.sub_agent_players_readonly_level', { level: lvlLabel(childAgentLevel.value) });
});
/* ─── Top-level tab: players | subAgents ─── */
const activeTab = ref('players');
@@ -654,7 +691,7 @@ function statusTagType(s: string) {
</el-tab-pane>
<!-- Tab: 下级代理 (仅一级代理可见) -->
<el-tab-pane v-if="canManageSubAgents" :label="`${t('nav.subAgents')} (${subAgents.length})`" name="subAgents">
<el-tab-pane v-if="canManageSubAgents" :label="subAgentsTabLabel" name="subAgents">
<div class="inner-toolbar">
<el-form inline size="small" style="flex: 1">
<el-form-item :label="t('common.search')">
@@ -662,7 +699,7 @@ function statusTagType(s: string) {
</el-form-item>
</el-form>
<el-button type="primary" size="small" @click="openCreateSub">
+ {{ t('agent_portal.create_tier2_btn') }}
{{ createSubBtnLabel }}
</el-button>
</div>
@@ -677,7 +714,7 @@ function statusTagType(s: string) {
@expand-change="onSubAgentExpand"
@row-click="onSubAgentRowClick"
>
<template #empty><div class="empty-hint">{{ t('agent_portal.no_sub_agents') || '暂无下级代理' }}</div></template>
<template #empty><div class="empty-hint">{{ noSubAgentsHint }}</div></template>
<el-table-column type="expand">
<template #default="{ row }">
@@ -687,7 +724,7 @@ function statusTagType(s: string) {
</div>
<template v-else>
<div class="expand-section-title">{{ t('nav.players') }} ({{ getSubAgentPlayers(row.userId).length }})</div>
<p class="expand-readonly-hint">{{ t('agent_portal.sub_agent_players_readonly') }}</p>
<p class="expand-readonly-hint">{{ subAgentPlayersReadonlyHint }}</p>
<el-table :data="getSubAgentPlayers(row.userId)" stripe size="small" class="inner-table nested-table">
<template #empty><div class="empty-hint">暂无数据</div></template>
<el-table-column prop="id" :label="t('common.col_id')" width="60" />
@@ -869,7 +906,7 @@ function statusTagType(s: string) {
</el-dialog>
<!-- Create Sub-Agent -->
<el-dialog v-model="createSubVisible" :title="t('agent_portal.create_tier2_btn')" width="480px" destroy-on-close>
<el-dialog v-model="createSubVisible" :title="createSubDialogTitle" width="480px" destroy-on-close>
<el-form label-width="100px">
<el-form-item :label="t('user.col.username')" required>
<el-input v-model="createSubForm.username" :placeholder="t('agent_portal.agent_username_ph')" />