sync(theme-4): 同步 main 最新 API/Admin 与玩家端逻辑
从 main 同步站内信手动发送、媒体库选择、列表子路由重构等 API/Admin 改动;玩家端仅合并 ADMIN_CUSTOM 富文本、消息预览与充值截图压缩逻辑,保留 theme-4 样式。
This commit is contained in:
233
apps/admin/src/views/agent/AgentDirectPlayersView.vue
Normal file
233
apps/admin/src/views/agent/AgentDirectPlayersView.vue
Normal file
@@ -0,0 +1,233 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, inject, onMounted, onUnmounted, watch } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { useAdminLocale } from '../../composables/useAdminLocale';
|
||||
import {
|
||||
agentDirectPlayersReloadKey,
|
||||
agentPlayerActionsKey,
|
||||
} from '../../composables/agent-direct-players-context';
|
||||
import api from '../../api';
|
||||
import AdminSubNav from '../../components/AdminSubNav.vue';
|
||||
import AdminTableWrap from '../../components/AdminTableWrap.vue';
|
||||
import AdminTableEmpty from '../../components/AdminTableEmpty.vue';
|
||||
import AdminPlayerStatusCell from '../../components/AdminPlayerStatusCell.vue';
|
||||
import AdminPlayerRowActions from '../../components/AdminPlayerRowActions.vue';
|
||||
import { formatAmount, formatAmountFull } from '../../utils/format-amount';
|
||||
import type { PlayerRow } from '../user-form';
|
||||
|
||||
defineOptions({ name: 'AdminAgentDirectPlayersView' });
|
||||
|
||||
const route = useRoute();
|
||||
const { t } = useAdminLocale();
|
||||
const actions = inject(agentPlayerActionsKey);
|
||||
const reloadRef = inject(agentDirectPlayersReloadKey);
|
||||
|
||||
const agentId = computed(() => String(route.params.agentId ?? ''));
|
||||
const agentUsername = computed(() => {
|
||||
const name = String(route.query.username ?? '').trim();
|
||||
return name || `#${agentId.value}`;
|
||||
});
|
||||
|
||||
const players = ref<PlayerRow[]>([]);
|
||||
const total = ref(0);
|
||||
const page = ref(1);
|
||||
const pageSize = ref(20);
|
||||
const keyword = ref('');
|
||||
const filterStatus = ref('');
|
||||
const loading = ref(false);
|
||||
|
||||
const pageTitle = computed(() =>
|
||||
t('agent.direct_players_title', { name: agentUsername.value }),
|
||||
);
|
||||
|
||||
async function loadPlayers() {
|
||||
if (!agentId.value) return;
|
||||
loading.value = true;
|
||||
try {
|
||||
const { data } = await api.get('/admin/users', {
|
||||
params: {
|
||||
parentId: agentId.value,
|
||||
page: page.value,
|
||||
pageSize: pageSize.value,
|
||||
keyword: keyword.value.trim() || undefined,
|
||||
status: filterStatus.value || undefined,
|
||||
},
|
||||
});
|
||||
players.value = (data.data.items ?? []) as PlayerRow[];
|
||||
total.value = data.data.total ?? 0;
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
function onSearch() {
|
||||
page.value = 1;
|
||||
void loadPlayers();
|
||||
}
|
||||
|
||||
function onPageChange(p: number) {
|
||||
page.value = p;
|
||||
void loadPlayers();
|
||||
}
|
||||
|
||||
function onSizeChange(size: number) {
|
||||
pageSize.value = size;
|
||||
page.value = 1;
|
||||
void loadPlayers();
|
||||
}
|
||||
|
||||
watch(agentId, () => {
|
||||
page.value = 1;
|
||||
void loadPlayers();
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
if (reloadRef) reloadRef.value = () => void loadPlayers();
|
||||
void loadPlayers();
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
if (reloadRef) reloadRef.value = null;
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="admin-list-page agent-direct-players-page">
|
||||
<AdminSubNav :title="pageTitle" :subtitle="t('agent.direct_players')" />
|
||||
|
||||
<section class="list-panel player-list-panel">
|
||||
<div class="list-panel-toolbar">
|
||||
<el-form inline class="list-chrome__grow">
|
||||
<el-form-item :label="t('common.keyword')">
|
||||
<el-input
|
||||
v-model="keyword"
|
||||
:placeholder="t('user.filter.username_ph')"
|
||||
clearable
|
||||
style="width: 180px"
|
||||
@keyup.enter="onSearch"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('common.status')">
|
||||
<el-select v-model="filterStatus" :placeholder="t('common.all')" clearable style="width: 120px">
|
||||
<el-option :label="t('user.status.ACTIVE')" value="ACTIVE" />
|
||||
<el-option :label="t('user.status.SUSPENDED')" value="SUSPENDED" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="onSearch">{{ t('common.search') }}</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div v-if="actions?.canCreatePlayer" class="list-chrome__actions">
|
||||
<el-button type="primary" @click="actions?.openCreatePlayer(agentId)">
|
||||
{{ t('user.create_btn') }}
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<AdminTableWrap>
|
||||
<el-table v-loading="loading" :data="players" stripe class="inner-table">
|
||||
<template #empty>
|
||||
<AdminTableEmpty />
|
||||
</template>
|
||||
<el-table-column
|
||||
type="index"
|
||||
:index="(i: number) => (page - 1) * pageSize + i + 1"
|
||||
:label="t('common.seq')"
|
||||
width="70"
|
||||
align="center"
|
||||
/>
|
||||
<el-table-column prop="username" :label="t('user.col.username')" min-width="120" />
|
||||
<el-table-column :label="t('common.status')" min-width="128">
|
||||
<template #default="{ row }">
|
||||
<AdminPlayerStatusCell :status="row.status" :is-online="row.isOnline" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="t('user.col.invite_code')" min-width="96" 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="120" align="right">
|
||||
<template #default="{ row }">
|
||||
<el-tooltip
|
||||
:content="`${formatAmountFull(row.availableBalance)} / ${formatAmountFull(row.frozenBalance)}`"
|
||||
placement="top"
|
||||
>
|
||||
<span class="amount-compact">
|
||||
{{ formatAmount(row.availableBalance) }} / {{ formatAmount(row.frozenBalance) }}
|
||||
</span>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="betCount" :label="t('user.col.bets')" width="56" align="center" />
|
||||
<el-table-column :label="t('user.col.stake_payout')" min-width="100" align="right">
|
||||
<template #default="{ row }">
|
||||
<span class="amount-compact">
|
||||
{{ formatAmount(row.totalStake) }} / {{ formatAmount(row.totalReturn) }}
|
||||
</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column v-if="actions" :label="t('common.actions')" min-width="320" align="center">
|
||||
<template #default="{ row }">
|
||||
<AdminPlayerRowActions
|
||||
v-bind="actions.playerActionFlags"
|
||||
:row="row"
|
||||
@detail="actions.openDetailPlayer(row.id)"
|
||||
@ledger="actions.openPlayerWalletLedger(row.id, row.username)"
|
||||
@edit="actions.openEditPlayer(row.id)"
|
||||
@deposit="actions.openTransfer('deposit', row)"
|
||||
@withdraw="actions.openTransfer('withdraw', row)"
|
||||
@freeze="actions.toggleFreezePlayer(row)"
|
||||
@delete="actions.deletePlayer(row)"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</AdminTableWrap>
|
||||
|
||||
<div class="pager">
|
||||
<el-pagination
|
||||
v-model:current-page="page"
|
||||
v-model:page-size="pageSize"
|
||||
:total="total"
|
||||
:page-sizes="[10, 20, 50, 100]"
|
||||
layout="total, sizes, prev, pager, next"
|
||||
background
|
||||
@current-change="onPageChange"
|
||||
@size-change="onSizeChange"
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.agent-direct-players-page {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.agent-direct-players-page :deep(.admin-subnav) {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.agent-direct-players-page .list-panel {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.agent-direct-players-page .player-list-panel :deep(.admin-table-wrap) {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.agent-direct-players-page .inner-table {
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user